aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/annotations.rs2
-rw-r--r--crates/ide/src/diagnostics.rs1
-rw-r--r--crates/ide/src/diagnostics/fixes.rs2
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs2
-rw-r--r--crates/ide/src/doc_links.rs197
-rw-r--r--crates/ide/src/expand_macro.rs5
-rw-r--r--crates/ide/src/extend_selection.rs2
-rw-r--r--crates/ide/src/file_structure.rs5
-rw-r--r--crates/ide/src/folding_ranges.rs56
-rw-r--r--crates/ide/src/goto_definition.rs55
-rw-r--r--crates/ide/src/goto_implementation.rs2
-rw-r--r--crates/ide/src/goto_type_definition.rs2
-rw-r--r--crates/ide/src/hover.rs187
-rw-r--r--crates/ide/src/inlay_hints.rs6
-rw-r--r--crates/ide/src/join_lines.rs20
-rw-r--r--crates/ide/src/matching_brace.rs2
-rw-r--r--crates/ide/src/move_item.rs271
-rw-r--r--crates/ide/src/parent_module.rs2
-rw-r--r--crates/ide/src/references.rs2
-rw-r--r--crates/ide/src/references/rename.rs4
-rw-r--r--crates/ide/src/runnables.rs11
-rw-r--r--crates/ide/src/status.rs1
-rw-r--r--crates/ide/src/syntax_highlighting.rs31
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs35
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs119
-rw-r--r--crates/ide/src/syntax_highlighting/macro_.rs (renamed from crates/ide/src/syntax_highlighting/macro_rules.rs)10
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html8
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html10
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html16
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs20
-rw-r--r--crates/ide/src/syntax_tree.rs1
-rw-r--r--crates/ide/src/typing.rs171
-rw-r--r--crates/ide/src/typing/on_enter.rs2
-rw-r--r--crates/ide/src/view_hir.rs1
38 files changed, 983 insertions, 292 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 64bc926f1..5ebe7fd0e 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -19,6 +19,8 @@ use crate::{
19// 19//
20// Provides user with annotations above items for looking up references or impl blocks 20// Provides user with annotations above items for looking up references or impl blocks
21// and running/debugging binaries. 21// and running/debugging binaries.
22//
23// image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[]
22#[derive(Debug)] 24#[derive(Debug)]
23pub struct Annotation { 25pub struct Annotation {
24 pub range: TextRange, 26 pub range: TextRange,
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 22697a537..dd42116a7 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -165,7 +165,6 @@ pub(crate) fn diagnostics(
165 sema.diagnostics_display_range(d.display_source()).range, 165 sema.diagnostics_display_range(d.display_source()).range,
166 d.message(), 166 d.message(),
167 ) 167 )
168 .with_unused(true)
169 .with_fix(d.fix(&sema)) 168 .with_fix(d.fix(&sema))
170 .with_code(Some(d.code())), 169 .with_code(Some(d.code())),
171 ); 170 );
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 2f840909c..5fb3e2d91 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -210,7 +210,7 @@ fn missing_record_expr_field_fix(
210 } 210 }
211 let new_field = make::record_field( 211 let new_field = make::record_field(
212 None, 212 None,
213 make::name(record_expr_field.field_name()?.text()), 213 make::name(&record_expr_field.field_name()?.text()),
214 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), 214 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
215 ); 215 );
216 216
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index 019b0b440..e174fb767 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -63,7 +63,7 @@ impl DiagnosticWithFix for UnlinkedFile {
63 // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id` 63 // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
64 let parent = our_path.parent()?; 64 let parent = our_path.parent()?;
65 let mut paths = 65 let mut paths =
66 vec![parent.join("mod.rs")?, parent.join("main.rs")?, parent.join("lib.rs")?]; 66 vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];
67 67
68 // `submod/bla.rs` -> `submod.rs` 68 // `submod/bla.rs` -> `submod.rs`
69 if let Some(newmod) = (|| { 69 if let Some(newmod) = (|| {
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 0cee741ac..c5dc14a23 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,6 +1,9 @@
1//! Extracts, resolves and rewrites links and intra-doc links in markdown documentation. 1//! Extracts, resolves and rewrites links and intra-doc links in markdown documentation.
2 2
3use std::{convert::TryFrom, iter::once, ops::Range}; 3use std::{
4 convert::{TryFrom, TryInto},
5 iter::once,
6};
4 7
5use itertools::Itertools; 8use itertools::Itertools;
6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; 9use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
@@ -16,8 +19,7 @@ use ide_db::{
16 RootDatabase, 19 RootDatabase,
17}; 20};
18use syntax::{ 21use syntax::{
19 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, 22 ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TokenAtOffset, T,
20 TokenAtOffset, T,
21}; 23};
22 24
23use crate::{FilePosition, Semantics}; 25use crate::{FilePosition, Semantics};
@@ -26,12 +28,7 @@ pub(crate) type DocumentationLink = String;
26 28
27/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 29/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
28pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 30pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
29 let mut cb = |link: BrokenLink| { 31 let mut cb = broken_link_clone_cb;
30 Some((
31 /*url*/ link.reference.to_owned().into(),
32 /*title*/ link.reference.to_owned().into(),
33 ))
34 };
35 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); 32 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
36 33
37 let doc = map_links(doc, |target, title: &str| { 34 let doc = map_links(doc, |target, title: &str| {
@@ -98,76 +95,52 @@ pub(crate) fn remove_links(markdown: &str) -> String {
98 out 95 out
99} 96}
100 97
98/// Retrieve a link to documentation for the given symbol.
99pub(crate) fn external_docs(
100 db: &RootDatabase,
101 position: &FilePosition,
102) -> Option<DocumentationLink> {
103 let sema = Semantics::new(db);
104 let file = sema.parse(position.file_id).syntax().clone();
105 let token = pick_best(file.token_at_offset(position.offset))?;
106 let token = sema.descend_into_macros(token);
107
108 let node = token.parent()?;
109 let definition = match_ast! {
110 match node {
111 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
112 ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)),
113 _ => None,
114 }
115 };
116
117 get_doc_link(db, definition?)
118}
119
120/// Extracts all links from a given markdown text.
101pub(crate) fn extract_definitions_from_markdown( 121pub(crate) fn extract_definitions_from_markdown(
102 markdown: &str, 122 markdown: &str,
103) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> { 123) -> Vec<(TextRange, String, Option<hir::Namespace>)> {
104 let mut res = vec![]; 124 Parser::new_with_broken_link_callback(
105 let mut cb = |link: BrokenLink| { 125 markdown,
106 // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong 126 Options::empty(),
107 // this is fixed in the repo but not on the crates.io release yet 127 Some(&mut broken_link_clone_cb),
108 Some(( 128 )
109 /*url*/ link.reference.to_owned().into(), 129 .into_offset_iter()
110 /*title*/ link.reference.to_owned().into(), 130 .filter_map(|(event, range)| {
111 ))
112 };
113 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
114 for (event, range) in doc.into_offset_iter() {
115 if let Event::Start(Tag::Link(_, target, title)) = event { 131 if let Event::Start(Tag::Link(_, target, title)) = event {
116 let link = if target.is_empty() { title } else { target }; 132 let link = if target.is_empty() { title } else { target };
117 let (link, ns) = parse_intra_doc_link(&link); 133 let (link, ns) = parse_intra_doc_link(&link);
118 res.push((range, link.to_string(), ns)); 134 Some((
119 } 135 TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?),
120 } 136 link.to_string(),
121 res 137 ns,
122} 138 ))
123 139 } else {
124/// Extracts a link from a comment at the given position returning the spanning range, link and 140 None
125/// optionally it's namespace.
126pub(crate) fn extract_positioned_link_from_comment(
127 position: TextSize,
128 comment: &ast::Comment,
129) -> Option<(TextRange, String, Option<hir::Namespace>)> {
130 let doc_comment = comment.doc_comment()?;
131 let comment_start =
132 comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32);
133 let def_links = extract_definitions_from_markdown(doc_comment);
134 let (range, def_link, ns) =
135 def_links.into_iter().find_map(|(Range { start, end }, def_link, ns)| {
136 let range = TextRange::at(
137 comment_start + TextSize::from(start as u32),
138 TextSize::from((end - start) as u32),
139 );
140 range.contains(position).then(|| (range, def_link, ns))
141 })?;
142 Some((range, def_link, ns))
143}
144
145/// Turns a syntax node into it's [`Definition`] if it can hold docs.
146pub(crate) fn doc_owner_to_def(
147 sema: &Semantics<RootDatabase>,
148 item: &SyntaxNode,
149) -> Option<Definition> {
150 let res: hir::ModuleDef = match_ast! {
151 match item {
152 ast::SourceFile(_it) => sema.scope(item).module()?.into(),
153 ast::Fn(it) => sema.to_def(&it)?.into(),
154 ast::Struct(it) => sema.to_def(&it)?.into(),
155 ast::Enum(it) => sema.to_def(&it)?.into(),
156 ast::Union(it) => sema.to_def(&it)?.into(),
157 ast::Trait(it) => sema.to_def(&it)?.into(),
158 ast::Const(it) => sema.to_def(&it)?.into(),
159 ast::Static(it) => sema.to_def(&it)?.into(),
160 ast::TypeAlias(it) => sema.to_def(&it)?.into(),
161 ast::Variant(it) => sema.to_def(&it)?.into(),
162 ast::Trait(it) => sema.to_def(&it)?.into(),
163 ast::Impl(it) => return sema.to_def(&it).map(Definition::SelfType),
164 ast::MacroRules(it) => return sema.to_def(&it).map(Definition::Macro),
165 ast::TupleField(it) => return sema.to_def(&it).map(Definition::Field),
166 ast::RecordField(it) => return sema.to_def(&it).map(Definition::Field),
167 _ => return None,
168 } 141 }
169 }; 142 })
170 Some(Definition::ModuleDef(res)) 143 .collect()
171} 144}
172 145
173pub(crate) fn resolve_doc_path_for_def( 146pub(crate) fn resolve_doc_path_for_def(
@@ -178,15 +151,15 @@ pub(crate) fn resolve_doc_path_for_def(
178) -> Option<hir::ModuleDef> { 151) -> Option<hir::ModuleDef> {
179 match def { 152 match def {
180 Definition::ModuleDef(def) => match def { 153 Definition::ModuleDef(def) => match def {
181 ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), 154 hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns),
182 ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), 155 hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns),
183 ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), 156 hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns),
184 ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), 157 hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns),
185 ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), 158 hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns),
186 ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), 159 hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns),
187 ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), 160 hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns),
188 ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), 161 hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns),
189 ModuleDef::BuiltinType(_) => None, 162 hir::ModuleDef::BuiltinType(_) => None,
190 }, 163 },
191 Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), 164 Definition::Macro(it) => it.resolve_doc_path(db, &link, ns),
192 Definition::Field(it) => it.resolve_doc_path(db, &link, ns), 165 Definition::Field(it) => it.resolve_doc_path(db, &link, ns),
@@ -197,6 +170,42 @@ pub(crate) fn resolve_doc_path_for_def(
197 } 170 }
198} 171}
199 172
173pub(crate) fn doc_attributes(
174 sema: &Semantics<RootDatabase>,
175 node: &SyntaxNode,
176) -> Option<(hir::AttrsWithOwner, Definition)> {
177 match_ast! {
178 match node {
179 ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
180 ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
181 ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Function(def)))),
182 ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(def))))),
183 ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Union(def))))),
184 ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(def))))),
185 ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Variant(def)))),
186 ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Trait(def)))),
187 ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Static(def)))),
188 ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Const(def)))),
189 ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::TypeAlias(def)))),
190 ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))),
191 ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
192 ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
193 ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))),
194 // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
195 _ => return None
196 }
197 }
198}
199
200fn broken_link_clone_cb<'a, 'b>(link: BrokenLink<'a>) -> Option<(CowStr<'b>, CowStr<'b>)> {
201 // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong
202 // this is fixed in the repo but not on the crates.io release yet
203 Some((
204 /*url*/ link.reference.to_owned().into(),
205 /*title*/ link.reference.to_owned().into(),
206 ))
207}
208
200// FIXME: 209// FIXME:
201// BUG: For Option::Some 210// BUG: For Option::Some
202// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some 211// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
@@ -214,7 +223,7 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
214 .and_then(|assoc| match assoc.container(db) { 223 .and_then(|assoc| match assoc.container(db) {
215 AssocItemContainer::Trait(t) => Some(t.into()), 224 AssocItemContainer::Trait(t) => Some(t.into()),
216 AssocItemContainer::Impl(impld) => { 225 AssocItemContainer::Impl(impld) => {
217 impld.target_ty(db).as_adt().map(|adt| adt.into()) 226 impld.self_ty(db).as_adt().map(|adt| adt.into())
218 } 227 }
219 }) 228 })
220 .unwrap_or_else(|| f.clone().into()), 229 .unwrap_or_else(|| f.clone().into()),
@@ -328,28 +337,6 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
328 .map(|url| url.into_string()) 337 .map(|url| url.into_string())
329} 338}
330 339
331/// Retrieve a link to documentation for the given symbol.
332pub(crate) fn external_docs(
333 db: &RootDatabase,
334 position: &FilePosition,
335) -> Option<DocumentationLink> {
336 let sema = Semantics::new(db);
337 let file = sema.parse(position.file_id).syntax().clone();
338 let token = pick_best(file.token_at_offset(position.offset))?;
339 let token = sema.descend_into_macros(token);
340
341 let node = token.parent()?;
342 let definition = match_ast! {
343 match node {
344 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
345 ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)),
346 _ => None,
347 }
348 };
349
350 get_doc_link(db, definition?)
351}
352
353/// Rewrites a markdown document, applying 'callback' to each link. 340/// Rewrites a markdown document, applying 'callback' to each link.
354fn map_links<'e>( 341fn map_links<'e>(
355 events: impl Iterator<Item = Event<'e>>, 342 events: impl Iterator<Item = Event<'e>>,
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index ffb3a6f7d..d5628e3df 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -23,6 +23,8 @@ pub struct ExpandedMacro {
23// 23//
24// | VS Code | **Rust Analyzer: Expand macro recursively** 24// | VS Code | **Rust Analyzer: Expand macro recursively**
25// |=== 25// |===
26//
27// image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
26pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 28pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
27 let sema = Semantics::new(db); 29 let sema = Semantics::new(db);
28 let file = sema.parse(position.file_id); 30 let file = sema.parse(position.file_id);
@@ -101,6 +103,9 @@ fn insert_whitespaces(syn: SyntaxNode) -> String {
101 format!("\n{}}}", " ".repeat(indent)) 103 format!("\n{}}}", " ".repeat(indent))
102 } 104 }
103 R_CURLY => format!("}}\n{}", " ".repeat(indent)), 105 R_CURLY => format!("}}\n{}", " ".repeat(indent)),
106 LIFETIME_IDENT if is_next(|it| it == IDENT, true) => {
107 format!("{} ", token.text().to_string())
108 }
104 T![;] => format!(";\n{}", " ".repeat(indent)), 109 T![;] => format!(";\n{}", " ".repeat(indent)),
105 T![->] => " -> ".to_string(), 110 T![->] => " -> ".to_string(),
106 T![=] => " = ".to_string(), 111 T![=] => " = ".to_string(),
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 5201ce587..7032889ac 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -24,6 +24,8 @@ use crate::FileRange;
24// 24//
25// | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←] 25// | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←]
26// |=== 26// |===
27//
28// image::https://user-images.githubusercontent.com/48062697/113020651-b42fc800-917a-11eb-8a4f-cf1a07859fac.gif[]
27pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 29pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
28 let sema = Semantics::new(db); 30 let sema = Semantics::new(db);
29 let src = sema.parse(frange.file_id); 31 let src = sema.parse(frange.file_id);
diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs
index 9f879a66e..19071d6be 100644
--- a/crates/ide/src/file_structure.rs
+++ b/crates/ide/src/file_structure.rs
@@ -35,6 +35,9 @@ pub enum StructureNodeKind {
35// 35//
36// | VS Code | kbd:[Ctrl+Shift+O] 36// | VS Code | kbd:[Ctrl+Shift+O]
37// |=== 37// |===
38//
39// image::https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif[]
40
38pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> { 41pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
39 let mut res = Vec::new(); 42 let mut res = Vec::new();
40 let mut stack = Vec::new(); 43 let mut stack = Vec::new();
@@ -172,7 +175,7 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
172 }; 175 };
173 Some(node) 176 Some(node)
174 }, 177 },
175 ast::MacroRules(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)), 178 ast::Macro(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)),
176 _ => None, 179 _ => None,
177 } 180 }
178 } 181 }
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs
index 4b1b24562..2b9ed123c 100644
--- a/crates/ide/src/folding_ranges.rs
+++ b/crates/ide/src/folding_ranges.rs
@@ -17,6 +17,9 @@ pub enum FoldKind {
17 Block, 17 Block,
18 ArgList, 18 ArgList,
19 Region, 19 Region,
20 Consts,
21 Statics,
22 Array,
20} 23}
21 24
22#[derive(Debug)] 25#[derive(Debug)]
@@ -30,6 +33,8 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
30 let mut visited_comments = FxHashSet::default(); 33 let mut visited_comments = FxHashSet::default();
31 let mut visited_imports = FxHashSet::default(); 34 let mut visited_imports = FxHashSet::default();
32 let mut visited_mods = FxHashSet::default(); 35 let mut visited_mods = FxHashSet::default();
36 let mut visited_consts = FxHashSet::default();
37 let mut visited_statics = FxHashSet::default();
33 // regions can be nested, here is a LIFO buffer 38 // regions can be nested, here is a LIFO buffer
34 let mut regions_starts: Vec<TextSize> = vec![]; 39 let mut regions_starts: Vec<TextSize> = vec![];
35 40
@@ -91,6 +96,19 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
91 res.push(Fold { range, kind: FoldKind::Mods }) 96 res.push(Fold { range, kind: FoldKind::Mods })
92 } 97 }
93 } 98 }
99
100 // Fold groups of consts
101 if node.kind() == CONST && !visited_consts.contains(&node) {
102 if let Some(range) = contiguous_range_for_group(&node, &mut visited_consts) {
103 res.push(Fold { range, kind: FoldKind::Consts })
104 }
105 }
106 // Fold groups of consts
107 if node.kind() == STATIC && !visited_statics.contains(&node) {
108 if let Some(range) = contiguous_range_for_group(&node, &mut visited_statics) {
109 res.push(Fold { range, kind: FoldKind::Statics })
110 }
111 }
94 } 112 }
95 } 113 }
96 } 114 }
@@ -102,6 +120,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
102 match kind { 120 match kind {
103 COMMENT => Some(FoldKind::Comment), 121 COMMENT => Some(FoldKind::Comment),
104 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), 122 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList),
123 ARRAY_EXPR => Some(FoldKind::Array),
105 ASSOC_ITEM_LIST 124 ASSOC_ITEM_LIST
106 | RECORD_FIELD_LIST 125 | RECORD_FIELD_LIST
107 | RECORD_PAT_FIELD_LIST 126 | RECORD_PAT_FIELD_LIST
@@ -250,6 +269,9 @@ mod tests {
250 FoldKind::Block => "block", 269 FoldKind::Block => "block",
251 FoldKind::ArgList => "arglist", 270 FoldKind::ArgList => "arglist",
252 FoldKind::Region => "region", 271 FoldKind::Region => "region",
272 FoldKind::Consts => "consts",
273 FoldKind::Statics => "statics",
274 FoldKind::Array => "array",
253 }; 275 };
254 assert_eq!(kind, &attr.unwrap()); 276 assert_eq!(kind, &attr.unwrap());
255 } 277 }
@@ -446,6 +468,20 @@ fn foo<fold arglist>(
446 } 468 }
447 469
448 #[test] 470 #[test]
471 fn fold_multiline_array() {
472 check(
473 r#"
474const FOO: [usize; 4] = <fold array>[
475 1,
476 2,
477 3,
478 4,
479]</fold>;
480"#,
481 )
482 }
483
484 #[test]
449 fn fold_region() { 485 fn fold_region() {
450 check( 486 check(
451 r#" 487 r#"
@@ -457,4 +493,24 @@ calling_function(x,y);
457"#, 493"#,
458 ) 494 )
459 } 495 }
496
497 #[test]
498 fn fold_consecutive_const() {
499 check(
500 r#"
501<fold consts>const FIRST_CONST: &str = "first";
502const SECOND_CONST: &str = "second";</fold>
503 "#,
504 )
505 }
506
507 #[test]
508 fn fold_consecutive_static() {
509 check(
510 r#"
511<fold statics>static FIRST_STATIC: &str = "first";
512static SECOND_STATIC: &str = "second";</fold>
513 "#,
514 )
515 }
460} 516}
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index a2c97061f..ca8ccb2da 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,5 +1,5 @@
1use either::Either; 1use either::Either;
2use hir::Semantics; 2use hir::{InFile, Semantics};
3use ide_db::{ 3use ide_db::{
4 defs::{NameClass, NameRefClass}, 4 defs::{NameClass, NameRefClass},
5 RootDatabase, 5 RootDatabase,
@@ -8,7 +8,7 @@ use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, Toke
8 8
9use crate::{ 9use crate::{
10 display::TryToNav, 10 display::TryToNav,
11 doc_links::{doc_owner_to_def, extract_positioned_link_from_comment, resolve_doc_path_for_def}, 11 doc_links::{doc_attributes, extract_definitions_from_markdown, resolve_doc_path_for_def},
12 FilePosition, NavigationTarget, RangeInfo, 12 FilePosition, NavigationTarget, RangeInfo,
13}; 13};
14 14
@@ -21,6 +21,8 @@ use crate::{
21// 21//
22// | VS Code | kbd:[F12] 22// | VS Code | kbd:[F12]
23// |=== 23// |===
24//
25// image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[]
24pub(crate) fn goto_definition( 26pub(crate) fn goto_definition(
25 db: &RootDatabase, 27 db: &RootDatabase,
26 position: FilePosition, 28 position: FilePosition,
@@ -30,9 +32,16 @@ pub(crate) fn goto_definition(
30 let original_token = pick_best(file.token_at_offset(position.offset))?; 32 let original_token = pick_best(file.token_at_offset(position.offset))?;
31 let token = sema.descend_into_macros(original_token.clone()); 33 let token = sema.descend_into_macros(original_token.clone());
32 let parent = token.parent()?; 34 let parent = token.parent()?;
33 if let Some(comment) = ast::Comment::cast(token) { 35 if let Some(_) = ast::Comment::cast(token) {
34 let (_, link, ns) = extract_positioned_link_from_comment(position.offset, &comment)?; 36 let (attributes, def) = doc_attributes(&sema, &parent)?;
35 let def = doc_owner_to_def(&sema, &parent)?; 37
38 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
39 let (_, link, ns) =
40 extract_definitions_from_markdown(docs.as_str()).into_iter().find(|(range, ..)| {
41 doc_mapping.map(range.clone()).map_or(false, |InFile { file_id, value: range }| {
42 file_id == position.file_id.into() && range.contains(position.offset)
43 })
44 })?;
36 let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?; 45 let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?;
37 return Some(RangeInfo::new(original_token.text_range(), vec![nav])); 46 return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
38 } 47 }
@@ -918,6 +927,21 @@ fn f() -> impl Iterator<Item$0 = u8> {}
918 } 927 }
919 928
920 #[test] 929 #[test]
930 #[should_panic = "unresolved reference"]
931 fn unknown_assoc_ty() {
932 check(
933 r#"
934trait Iterator {
935 type Item;
936 //^^^^
937}
938
939fn f() -> impl Iterator<Invalid$0 = u8> {}
940 "#,
941 )
942 }
943
944 #[test]
921 fn goto_def_for_assoc_ty_in_path_multiple() { 945 fn goto_def_for_assoc_ty_in_path_multiple() {
922 check( 946 check(
923 r#" 947 r#"
@@ -1143,4 +1167,25 @@ fn fn_macro() {}
1143 "#, 1167 "#,
1144 ) 1168 )
1145 } 1169 }
1170
1171 #[test]
1172 fn goto_intra_doc_links() {
1173 check(
1174 r#"
1175
1176pub mod theitem {
1177 /// This is the item. Cool!
1178 pub struct TheItem;
1179 //^^^^^^^
1180}
1181
1182/// Gives you a [`TheItem$0`].
1183///
1184/// [`TheItem`]: theitem::TheItem
1185pub fn gimme() -> theitem::TheItem {
1186 theitem::TheItem
1187}
1188"#,
1189 );
1190 }
1146} 1191}
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index f4d7c14a6..05130a237 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -16,6 +16,8 @@ use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo};
16// 16//
17// | VS Code | kbd:[Ctrl+F12] 17// | VS Code | kbd:[Ctrl+F12]
18// |=== 18// |===
19//
20// image::https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif[]
19pub(crate) fn goto_implementation( 21pub(crate) fn goto_implementation(
20 db: &RootDatabase, 22 db: &RootDatabase,
21 position: FilePosition, 23 position: FilePosition,
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 2d38cb112..9d34b109b 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -12,6 +12,8 @@ use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo};
12// 12//
13// | VS Code | **Go to Type Definition* 13// | VS Code | **Go to Type Definition*
14// |=== 14// |===
15//
16// image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
15pub(crate) fn goto_type_definition( 17pub(crate) fn goto_type_definition(
16 db: &RootDatabase, 18 db: &RootDatabase,
17 position: FilePosition, 19 position: FilePosition,
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index c43089476..9de653739 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,6 +1,6 @@
1use either::Either; 1use either::Either;
2use hir::{ 2use hir::{
3 AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, Module, 3 AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, InFile, Module,
4 ModuleDef, Semantics, 4 ModuleDef, Semantics,
5}; 5};
6use ide_db::{ 6use ide_db::{
@@ -16,8 +16,8 @@ use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, Toke
16use crate::{ 16use crate::{
17 display::{macro_label, TryToNav}, 17 display::{macro_label, TryToNav},
18 doc_links::{ 18 doc_links::{
19 doc_owner_to_def, extract_positioned_link_from_comment, remove_links, 19 doc_attributes, extract_definitions_from_markdown, remove_links, resolve_doc_path_for_def,
20 resolve_doc_path_for_def, rewrite_links, 20 rewrite_links,
21 }, 21 },
22 markdown_remove::remove_markdown, 22 markdown_remove::remove_markdown,
23 markup::Markup, 23 markup::Markup,
@@ -82,6 +82,8 @@ pub struct HoverResult {
82// 82//
83// Shows additional information, like type of an expression or documentation for definition when "focusing" code. 83// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
84// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. 84// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
85//
86// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
85pub(crate) fn hover( 87pub(crate) fn hover(
86 db: &RootDatabase, 88 db: &RootDatabase,
87 position: FilePosition, 89 position: FilePosition,
@@ -114,11 +116,19 @@ pub(crate) fn hover(
114 ), 116 ),
115 117
116 _ => ast::Comment::cast(token.clone()) 118 _ => ast::Comment::cast(token.clone())
117 .and_then(|comment| { 119 .and_then(|_| {
120 let (attributes, def) = doc_attributes(&sema, &node)?;
121 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
118 let (idl_range, link, ns) = 122 let (idl_range, link, ns) =
119 extract_positioned_link_from_comment(position.offset, &comment)?; 123 extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| {
124 let InFile { file_id, value: range } = doc_mapping.map(range.clone())?;
125 if file_id == position.file_id.into() && range.contains(position.offset) {
126 Some((range, link, ns))
127 } else {
128 None
129 }
130 })?;
120 range = Some(idl_range); 131 range = Some(idl_range);
121 let def = doc_owner_to_def(&sema, &node)?;
122 resolve_doc_path_for_def(db, def, &link, ns) 132 resolve_doc_path_for_def(db, def, &link, ns)
123 }) 133 })
124 .map(Definition::ModuleDef), 134 .map(Definition::ModuleDef),
@@ -195,7 +205,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
195 let adt = match def { 205 let adt = match def {
196 Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action), 206 Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action),
197 Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it), 207 Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it),
198 Definition::SelfType(it) => it.target_ty(db).as_adt(), 208 Definition::SelfType(it) => it.self_ty(db).as_adt(),
199 _ => None, 209 _ => None,
200 }?; 210 }?;
201 adt.try_to_nav(db).map(to_action) 211 adt.try_to_nav(db).map(to_action)
@@ -318,7 +328,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
318 Definition::ModuleDef(md) => match md { 328 Definition::ModuleDef(md) => match md {
319 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { 329 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
320 AssocItemContainer::Trait(t) => Some(t.name(db)), 330 AssocItemContainer::Trait(t) => Some(t.name(db)),
321 AssocItemContainer::Impl(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)), 331 AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
322 }, 332 },
323 ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), 333 ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
324 _ => None, 334 _ => None,
@@ -376,7 +386,7 @@ fn hover_for_definition(
376 }, 386 },
377 Definition::Local(it) => hover_for_local(it, db), 387 Definition::Local(it) => hover_for_local(it, db),
378 Definition::SelfType(impl_def) => { 388 Definition::SelfType(impl_def) => {
379 impl_def.target_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path)) 389 impl_def.self_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path))
380 } 390 }
381 Definition::GenericParam(it) => from_hir_fmt(db, it, None), 391 Definition::GenericParam(it) => from_hir_fmt(db, it, None),
382 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), 392 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
@@ -470,6 +480,7 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module>
470 480
471fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 481fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
472 return tokens.max_by_key(priority); 482 return tokens.max_by_key(priority);
483
473 fn priority(n: &SyntaxToken) -> usize { 484 fn priority(n: &SyntaxToken) -> usize {
474 match n.kind() { 485 match n.kind() {
475 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, 486 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
@@ -1235,6 +1246,37 @@ fn f() { fo$0o!(); }
1235 } 1246 }
1236 1247
1237 #[test] 1248 #[test]
1249 fn test_hover_macro2_invocation() {
1250 check(
1251 r#"
1252/// foo bar
1253///
1254/// foo bar baz
1255macro foo() {}
1256
1257fn f() { fo$0o!(); }
1258"#,
1259 expect![[r#"
1260 *foo*
1261
1262 ```rust
1263 test
1264 ```
1265
1266 ```rust
1267 macro foo
1268 ```
1269
1270 ---
1271
1272 foo bar
1273
1274 foo bar baz
1275 "#]],
1276 )
1277 }
1278
1279 #[test]
1238 fn test_hover_tuple_field() { 1280 fn test_hover_tuple_field() {
1239 check( 1281 check(
1240 r#"struct TS(String, i32$0);"#, 1282 r#"struct TS(String, i32$0);"#,
@@ -3780,24 +3822,139 @@ fn main() {
3780 fn hover_intra_doc_links() { 3822 fn hover_intra_doc_links() {
3781 check( 3823 check(
3782 r#" 3824 r#"
3783/// This is the [`foo`](foo$0) function. 3825
3784fn foo() {} 3826pub mod theitem {
3827 /// This is the item. Cool!
3828 pub struct TheItem;
3829}
3830
3831/// Gives you a [`TheItem$0`].
3832///
3833/// [`TheItem`]: theitem::TheItem
3834pub fn gimme() -> theitem::TheItem {
3835 theitem::TheItem
3836}
3785"#, 3837"#,
3786 expect![[r#" 3838 expect![[r#"
3787 *[`foo`](foo)* 3839 *[`TheItem`]*
3788 3840
3789 ```rust 3841 ```rust
3790 test 3842 test::theitem
3791 ``` 3843 ```
3792 3844
3793 ```rust 3845 ```rust
3794 fn foo() 3846 pub struct TheItem
3795 ``` 3847 ```
3796 3848
3797 --- 3849 ---
3798 3850
3799 This is the [`foo`](https://docs.rs/test/*/test/fn.foo.html) function. 3851 This is the item. Cool!
3852 "#]],
3853 );
3854 }
3855
3856 #[test]
3857 fn hover_generic_assoc() {
3858 check(
3859 r#"
3860fn foo<T: A>() where T::Assoc$0: {}
3861
3862trait A {
3863 type Assoc;
3864}"#,
3865 expect![[r#"
3866 *Assoc*
3867
3868 ```rust
3869 test
3870 ```
3871
3872 ```rust
3873 type Assoc
3874 ```
3875 "#]],
3876 );
3877 check(
3878 r#"
3879fn foo<T: A>() {
3880 let _: <T>::Assoc$0;
3881}
3882
3883trait A {
3884 type Assoc;
3885}"#,
3886 expect![[r#"
3887 *Assoc*
3888
3889 ```rust
3890 test
3891 ```
3892
3893 ```rust
3894 type Assoc
3895 ```
3896 "#]],
3897 );
3898 check(
3899 r#"
3900trait A where
3901 Self::Assoc$0: ,
3902{
3903 type Assoc;
3904}"#,
3905 expect![[r#"
3906 *Assoc*
3907
3908 ```rust
3909 test
3910 ```
3911
3912 ```rust
3913 type Assoc
3914 ```
3800 "#]], 3915 "#]],
3801 ); 3916 );
3802 } 3917 }
3918
3919 #[test]
3920 fn string_shadowed_with_inner_items() {
3921 check(
3922 r#"
3923//- /main.rs crate:main deps:alloc
3924
3925/// Custom `String` type.
3926struct String;
3927
3928fn f() {
3929 let _: String$0;
3930
3931 fn inner() {}
3932}
3933
3934//- /alloc.rs crate:alloc
3935#[prelude_import]
3936pub use string::*;
3937
3938mod string {
3939 /// This is `alloc::String`.
3940 pub struct String;
3941}
3942 "#,
3943 expect![[r#"
3944 *String*
3945
3946 ```rust
3947 main
3948 ```
3949
3950 ```rust
3951 struct String
3952 ```
3953
3954 ---
3955
3956 Custom `String` type.
3957 "#]],
3958 )
3959 }
3803} 3960}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 16c04eeee..d5ef054d8 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -52,6 +52,8 @@ pub struct InlayHint {
52// 52//
53// | VS Code | **Rust Analyzer: Toggle inlay hints* 53// | VS Code | **Rust Analyzer: Toggle inlay hints*
54// |=== 54// |===
55//
56// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
55pub(crate) fn inlay_hints( 57pub(crate) fn inlay_hints(
56 db: &RootDatabase, 58 db: &RootDatabase,
57 file_id: FileId, 59 file_id: FileId,
@@ -232,7 +234,7 @@ fn hint_iterator(
232 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias), 234 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
233 _ => None, 235 _ => None,
234 })?; 236 })?;
235 if let Some(ty) = ty.normalize_trait_assoc_type(db, iter_trait, &[], assoc_type_item) { 237 if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
236 const LABEL_START: &str = "impl Iterator<Item = "; 238 const LABEL_START: &str = "impl Iterator<Item = ";
237 const LABEL_END: &str = ">"; 239 const LABEL_END: &str = ">";
238 240
@@ -416,7 +418,7 @@ fn get_string_representation(expr: &ast::Expr) -> Option<String> {
416 match expr { 418 match expr {
417 ast::Expr::MethodCallExpr(method_call_expr) => { 419 ast::Expr::MethodCallExpr(method_call_expr) => {
418 let name_ref = method_call_expr.name_ref()?; 420 let name_ref = method_call_expr.name_ref()?;
419 match name_ref.text() { 421 match name_ref.text().as_str() {
420 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()), 422 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()),
421 name_ref => Some(name_ref.to_owned()), 423 name_ref => Some(name_ref.to_owned()),
422 } 424 }
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 4b25135cd..fe2a349e6 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -19,6 +19,8 @@ use text_edit::{TextEdit, TextEditBuilder};
19// 19//
20// | VS Code | **Rust Analyzer: Join lines** 20// | VS Code | **Rust Analyzer: Join lines**
21// |=== 21// |===
22//
23// image::https://user-images.githubusercontent.com/48062697/113020661-b6922200-917a-11eb-87c4-b75acc028f11.gif[]
22pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { 24pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
23 let range = if range.is_empty() { 25 let range = if range.is_empty() {
24 let syntax = file.syntax(); 26 let syntax = file.syntax();
@@ -86,8 +88,11 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
86 } 88 }
87 89
88 // The node is between two other nodes 90 // The node is between two other nodes
89 let prev = token.prev_sibling_or_token().unwrap(); 91 let (prev, next) = match (token.prev_sibling_or_token(), token.next_sibling_or_token()) {
90 let next = token.next_sibling_or_token().unwrap(); 92 (Some(prev), Some(next)) => (prev, next),
93 _ => return,
94 };
95
91 if is_trailing_comma(prev.kind(), next.kind()) { 96 if is_trailing_comma(prev.kind(), next.kind()) {
92 // Removes: trailing comma, newline (incl. surrounding whitespace) 97 // Removes: trailing comma, newline (incl. surrounding whitespace)
93 edit.delete(TextRange::new(prev.text_range().start(), token.text_range().end())); 98 edit.delete(TextRange::new(prev.text_range().start(), token.text_range().end()));
@@ -827,4 +832,15 @@ $0hello world
827"#, 832"#,
828 ); 833 );
829 } 834 }
835 #[test]
836 fn join_last_line_empty() {
837 check_join_lines(
838 r#"
839fn main() {$0}
840"#,
841 r#"
842fn main() {$0}
843"#,
844 );
845 }
830} 846}
diff --git a/crates/ide/src/matching_brace.rs b/crates/ide/src/matching_brace.rs
index 4241a6dac..261dcc255 100644
--- a/crates/ide/src/matching_brace.rs
+++ b/crates/ide/src/matching_brace.rs
@@ -14,6 +14,8 @@ use syntax::{
14// 14//
15// | VS Code | **Rust Analyzer: Find matching brace** 15// | VS Code | **Rust Analyzer: Find matching brace**
16// |=== 16// |===
17//
18// image::https://user-images.githubusercontent.com/48062697/113065573-04298180-91b1-11eb-8dec-d4e2a202f304.gif[]
17pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { 19pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
18 const BRACES: &[SyntaxKind] = 20 const BRACES: &[SyntaxKind] =
19 &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]]; 21 &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs
index 48690b073..8d37f4f92 100644
--- a/crates/ide/src/move_item.rs
+++ b/crates/ide/src/move_item.rs
@@ -4,10 +4,12 @@ use hir::Semantics;
4use ide_db::{base_db::FileRange, RootDatabase}; 4use ide_db::{base_db::FileRange, RootDatabase};
5use itertools::Itertools; 5use itertools::Itertools;
6use syntax::{ 6use syntax::{
7 algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, 7 algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
8 TokenAtOffset,
8}; 9};
9use text_edit::{TextEdit, TextEditBuilder}; 10use text_edit::{TextEdit, TextEditBuilder};
10 11
12#[derive(Copy, Clone, Debug)]
11pub enum Direction { 13pub enum Direction {
12 Up, 14 Up,
13 Down, 15 Down,
@@ -23,6 +25,8 @@ pub enum Direction {
23// | VS Code | **Rust Analyzer: Move item up** 25// | VS Code | **Rust Analyzer: Move item up**
24// | VS Code | **Rust Analyzer: Move item down** 26// | VS Code | **Rust Analyzer: Move item down**
25// |=== 27// |===
28//
29// image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[]
26pub(crate) fn move_item( 30pub(crate) fn move_item(
27 db: &RootDatabase, 31 db: &RootDatabase,
28 range: FileRange, 32 range: FileRange,
@@ -31,14 +35,19 @@ pub(crate) fn move_item(
31 let sema = Semantics::new(db); 35 let sema = Semantics::new(db);
32 let file = sema.parse(range.file_id); 36 let file = sema.parse(range.file_id);
33 37
34 let item = file.syntax().covering_element(range.range); 38 let item = if range.range.is_empty() {
39 SyntaxElement::Token(pick_best(file.syntax().token_at_offset(range.range.start()))?)
40 } else {
41 file.syntax().covering_element(range.range)
42 };
43
35 find_ancestors(item, direction, range.range) 44 find_ancestors(item, direction, range.range)
36} 45}
37 46
38fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { 47fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
39 let root = match item { 48 let root = match item {
40 NodeOrToken::Node(node) => node, 49 SyntaxElement::Node(node) => node,
41 NodeOrToken::Token(token) => token.parent()?, 50 SyntaxElement::Token(token) => token.parent()?,
42 }; 51 };
43 52
44 let movable = [ 53 let movable = [
@@ -51,6 +60,11 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -
51 SyntaxKind::PARAM, 60 SyntaxKind::PARAM,
52 SyntaxKind::LET_STMT, 61 SyntaxKind::LET_STMT,
53 SyntaxKind::EXPR_STMT, 62 SyntaxKind::EXPR_STMT,
63 SyntaxKind::IF_EXPR,
64 SyntaxKind::FOR_EXPR,
65 SyntaxKind::LOOP_EXPR,
66 SyntaxKind::WHILE_EXPR,
67 SyntaxKind::RETURN_EXPR,
54 SyntaxKind::MATCH_EXPR, 68 SyntaxKind::MATCH_EXPR,
55 SyntaxKind::MACRO_CALL, 69 SyntaxKind::MACRO_CALL,
56 SyntaxKind::TYPE_ALIAS, 70 SyntaxKind::TYPE_ALIAS,
@@ -66,6 +80,7 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -
66 SyntaxKind::STATIC, 80 SyntaxKind::STATIC,
67 SyntaxKind::CONST, 81 SyntaxKind::CONST,
68 SyntaxKind::MACRO_RULES, 82 SyntaxKind::MACRO_RULES,
83 SyntaxKind::MACRO_DEF,
69 ]; 84 ];
70 85
71 let ancestor = once(root.clone()) 86 let ancestor = once(root.clone())
@@ -82,11 +97,11 @@ fn move_in_direction(
82) -> Option<TextEdit> { 97) -> Option<TextEdit> {
83 match_ast! { 98 match_ast! {
84 match node { 99 match node {
85 ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction), 100 ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction),
86 ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction), 101 ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction),
87 ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction), 102 ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction),
88 ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction), 103 ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction),
89 ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction), 104 ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction),
90 _ => Some(replace_nodes(node, &match direction { 105 _ => Some(replace_nodes(node, &match direction {
91 Direction::Up => node.prev_sibling(), 106 Direction::Up => node.prev_sibling(),
92 Direction::Down => node.next_sibling(), 107 Direction::Down => node.next_sibling(),
@@ -96,19 +111,27 @@ fn move_in_direction(
96} 111}
97 112
98fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( 113fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
114 node: &SyntaxNode,
99 list: I, 115 list: I,
100 range: TextRange, 116 range: TextRange,
101 direction: Direction, 117 direction: Direction,
102) -> Option<TextEdit> { 118) -> Option<TextEdit> {
103 let (l, r) = list 119 let list_lookup = list
104 .tuple_windows() 120 .tuple_windows()
105 .filter(|(l, r)| match direction { 121 .filter(|(l, r)| match direction {
106 Direction::Up => r.syntax().text_range().contains_range(range), 122 Direction::Up => r.syntax().text_range().contains_range(range),
107 Direction::Down => l.syntax().text_range().contains_range(range), 123 Direction::Down => l.syntax().text_range().contains_range(range),
108 }) 124 })
109 .next()?; 125 .next();
110 126
111 Some(replace_nodes(l.syntax(), r.syntax())) 127 if let Some((l, r)) = list_lookup {
128 Some(replace_nodes(l.syntax(), r.syntax()))
129 } else {
130 // Cursor is beyond any movable list item (for example, on curly brace in enum).
131 // It's not necessary, that parent of list is movable (arg list's parent is not, for example),
132 // and we have to continue tree traversal to find suitable node.
133 find_ancestors(SyntaxElement::Node(node.parent()?), direction, range)
134 }
112} 135}
113 136
114fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { 137fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
@@ -120,6 +143,18 @@ fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
120 edit.finish() 143 edit.finish()
121} 144}
122 145
146fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
147 return tokens.max_by_key(priority);
148
149 fn priority(n: &SyntaxToken) -> usize {
150 match n.kind() {
151 SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2,
152 kind if kind.is_trivia() => 0,
153 _ => 1,
154 }
155 }
156}
157
123#[cfg(test)] 158#[cfg(test)]
124mod tests { 159mod tests {
125 use crate::fixture; 160 use crate::fixture;
@@ -264,6 +299,107 @@ fn main() {
264 "#]], 299 "#]],
265 Direction::Up, 300 Direction::Up,
266 ); 301 );
302 check(
303 r#"
304fn main() {
305 println!("Hello, world");
306
307 if true {
308 println!("Test");
309 }$0$0
310}
311 "#,
312 expect![[r#"
313fn main() {
314 if true {
315 println!("Test");
316 }
317
318 println!("Hello, world");
319}
320 "#]],
321 Direction::Up,
322 );
323 check(
324 r#"
325fn main() {
326 println!("Hello, world");
327
328 for i in 0..10 {
329 println!("Test");
330 }$0$0
331}
332 "#,
333 expect![[r#"
334fn main() {
335 for i in 0..10 {
336 println!("Test");
337 }
338
339 println!("Hello, world");
340}
341 "#]],
342 Direction::Up,
343 );
344 check(
345 r#"
346fn main() {
347 println!("Hello, world");
348
349 loop {
350 println!("Test");
351 }$0$0
352}
353 "#,
354 expect![[r#"
355fn main() {
356 loop {
357 println!("Test");
358 }
359
360 println!("Hello, world");
361}
362 "#]],
363 Direction::Up,
364 );
365 check(
366 r#"
367fn main() {
368 println!("Hello, world");
369
370 while true {
371 println!("Test");
372 }$0$0
373}
374 "#,
375 expect![[r#"
376fn main() {
377 while true {
378 println!("Test");
379 }
380
381 println!("Hello, world");
382}
383 "#]],
384 Direction::Up,
385 );
386 check(
387 r#"
388fn main() {
389 println!("Hello, world");
390
391 return 123;$0$0
392}
393 "#,
394 expect![[r#"
395fn main() {
396 return 123;
397
398 println!("Hello, world");
399}
400 "#]],
401 Direction::Up,
402 );
267 } 403 }
268 404
269 #[test] 405 #[test]
@@ -614,6 +750,115 @@ fn test() {
614 } 750 }
615 751
616 #[test] 752 #[test]
753 fn test_cursor_at_item_start() {
754 check(
755 r#"
756$0$0#[derive(Debug)]
757enum FooBar {
758 Foo,
759 Bar,
760}
761
762fn main() {}
763 "#,
764 expect![[r#"
765fn main() {}
766
767#[derive(Debug)]
768enum FooBar {
769 Foo,
770 Bar,
771}
772 "#]],
773 Direction::Down,
774 );
775 check(
776 r#"
777$0$0enum FooBar {
778 Foo,
779 Bar,
780}
781
782fn main() {}
783 "#,
784 expect![[r#"
785fn main() {}
786
787enum FooBar {
788 Foo,
789 Bar,
790}
791 "#]],
792 Direction::Down,
793 );
794 check(
795 r#"
796struct Test;
797
798trait SomeTrait {}
799
800$0$0impl SomeTrait for Test {}
801
802fn main() {}
803 "#,
804 expect![[r#"
805struct Test;
806
807impl SomeTrait for Test {}
808
809trait SomeTrait {}
810
811fn main() {}
812 "#]],
813 Direction::Up,
814 );
815 }
816
817 #[test]
818 fn test_cursor_at_item_end() {
819 check(
820 r#"
821enum FooBar {
822 Foo,
823 Bar,
824}$0$0
825
826fn main() {}
827 "#,
828 expect![[r#"
829fn main() {}
830
831enum FooBar {
832 Foo,
833 Bar,
834}
835 "#]],
836 Direction::Down,
837 );
838 check(
839 r#"
840struct Test;
841
842trait SomeTrait {}
843
844impl SomeTrait for Test {}$0$0
845
846fn main() {}
847 "#,
848 expect![[r#"
849struct Test;
850
851impl SomeTrait for Test {}
852
853trait SomeTrait {}
854
855fn main() {}
856 "#]],
857 Direction::Up,
858 );
859 }
860
861 #[test]
617 fn handles_empty_file() { 862 fn handles_empty_file() {
618 check(r#"$0$0"#, expect![[r#""#]], Direction::Up); 863 check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
619 } 864 }
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index 22b0d6ecb..99365c8a7 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -18,6 +18,8 @@ use crate::NavigationTarget;
18// 18//
19// | VS Code | **Rust Analyzer: Locate parent module** 19// | VS Code | **Rust Analyzer: Locate parent module**
20// |=== 20// |===
21//
22// image::https://user-images.githubusercontent.com/48062697/113065580-04c21800-91b1-11eb-9a32-00086161c0bd.gif[]
21 23
22/// This returns `Vec` because a module may be included from several places. 24/// This returns `Vec` because a module may be included from several places.
23pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { 25pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 95ed8a045..11ca7ec6b 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -48,6 +48,8 @@ pub struct Declaration {
48// 48//
49// | VS Code | kbd:[Shift+Alt+F12] 49// | VS Code | kbd:[Shift+Alt+F12]
50// |=== 50// |===
51//
52// image::https://user-images.githubusercontent.com/48062697/113020670-b7c34f00-917a-11eb-8003-370ac5f2b3cb.gif[]
51pub(crate) fn find_all_refs( 53pub(crate) fn find_all_refs(
52 sema: &Semantics<RootDatabase>, 54 sema: &Semantics<RootDatabase>,
53 position: FilePosition, 55 position: FilePosition,
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 26d6dc9c9..2408a0181 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -70,6 +70,8 @@ pub(crate) fn prepare_rename(
70// 70//
71// | VS Code | kbd:[F2] 71// | VS Code | kbd:[F2]
72// |=== 72// |===
73//
74// image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[]
73pub(crate) fn rename( 75pub(crate) fn rename(
74 db: &RootDatabase, 76 db: &RootDatabase,
75 position: FilePosition, 77 position: FilePosition,
@@ -307,7 +309,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
307 hir::AssocItemContainer::Impl(impl_) => impl_, 309 hir::AssocItemContainer::Impl(impl_) => impl_,
308 }; 310 };
309 let first_param_ty = first_param.ty(); 311 let first_param_ty = first_param.ty();
310 let impl_ty = impl_.target_ty(sema.db); 312 let impl_ty = impl_.self_ty(sema.db);
311 let (ty, self_param) = if impl_ty.remove_ref().is_some() { 313 let (ty, self_param) = if impl_ty.remove_ref().is_some() {
312 // if the impl is a ref to the type we can just match the `&T` with self directly 314 // if the impl is a ref to the type we can just match the `&T` with self directly
313 (first_param_ty.clone(), "self") 315 (first_param_ty.clone(), "self")
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 5b488e2c5..3eb9e27ee 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -98,6 +98,7 @@ impl Runnable {
98// 98//
99// | VS Code | **Rust Analyzer: Run** 99// | VS Code | **Rust Analyzer: Run**
100// |=== 100// |===
101// image::https://user-images.githubusercontent.com/48062697/113065583-055aae80-91b1-11eb-958f-d67efcaf6a2f.gif[]
101pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 102pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
102 let sema = Semantics::new(db); 103 let sema = Semantics::new(db);
103 104
@@ -226,7 +227,9 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
226 let func = def.source(sema.db)?; 227 let func = def.source(sema.db)?;
227 let name_string = def.name(sema.db).to_string(); 228 let name_string = def.name(sema.db).to_string();
228 229
229 let kind = if name_string == "main" { 230 let root = def.krate(sema.db)?.root_module(sema.db);
231
232 let kind = if name_string == "main" && def.module(sema.db) == root {
230 RunnableKind::Bin 233 RunnableKind::Bin
231 } else { 234 } else {
232 let canonical_path = { 235 let canonical_path = {
@@ -296,7 +299,7 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op
296 // FIXME: this also looks very wrong 299 // FIXME: this also looks very wrong
297 if let Some(assoc_def) = assoc_def { 300 if let Some(assoc_def) = assoc_def {
298 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) { 301 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
299 let ty = imp.target_ty(sema.db); 302 let ty = imp.self_ty(sema.db);
300 if let Some(adt) = ty.as_adt() { 303 if let Some(adt) = ty.as_adt() {
301 let name = adt.name(sema.db); 304 let name = adt.name(sema.db);
302 let idx = path.rfind(':').map_or(0, |idx| idx + 1); 305 let idx = path.rfind(':').map_or(0, |idx| idx + 1);
@@ -444,6 +447,10 @@ fn test_foo() {}
444 447
445#[bench] 448#[bench]
446fn bench() {} 449fn bench() {}
450
451mod not_a_root {
452 fn main() {}
453}
447"#, 454"#,
448 &[&BIN, &TEST, &TEST, &BENCH], 455 &[&BIN, &TEST, &TEST, &BENCH],
449 expect![[r#" 456 expect![[r#"
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
index 137c38c0d..49fde1945 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -31,6 +31,7 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
31// 31//
32// | VS Code | **Rust Analyzer: Status** 32// | VS Code | **Rust Analyzer: Status**
33// |=== 33// |===
34// image::https://user-images.githubusercontent.com/48062697/113065584-05f34500-91b1-11eb-98cc-5c196f76be7f.gif[]
34pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { 35pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
35 let mut buf = String::new(); 36 let mut buf = String::new();
36 format_to!(buf, "{}\n", FileTextQuery.in_db(db).entries::<FilesStats>()); 37 format_to!(buf, "{}\n", FileTextQuery.in_db(db).entries::<FilesStats>());
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index e25b698e0..9df8d21af 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -5,7 +5,7 @@ mod injector;
5 5
6mod highlight; 6mod highlight;
7mod format; 7mod format;
8mod macro_rules; 8mod macro_;
9mod inject; 9mod inject;
10 10
11mod html; 11mod html;
@@ -24,8 +24,8 @@ use syntax::{
24 24
25use crate::{ 25use crate::{
26 syntax_highlighting::{ 26 syntax_highlighting::{
27 format::highlight_format_string, highlights::Highlights, 27 format::highlight_format_string, highlights::Highlights, macro_::MacroHighlighter,
28 macro_rules::MacroRulesHighlighter, tags::Highlight, 28 tags::Highlight,
29 }, 29 },
30 FileId, HlMod, HlTag, 30 FileId, HlMod, HlTag,
31}; 31};
@@ -48,6 +48,9 @@ pub struct HlRange {
48// 48//
49// The general rule is that a reference to an entity gets colored the same way as the entity itself. 49// The general rule is that a reference to an entity gets colored the same way as the entity itself.
50// We also give special modifier for `mut` and `&mut` local variables. 50// We also give special modifier for `mut` and `&mut` local variables.
51//
52// image::https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png[]
53// image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[]
51pub(crate) fn highlight( 54pub(crate) fn highlight(
52 db: &RootDatabase, 55 db: &RootDatabase,
53 file_id: FileId, 56 file_id: FileId,
@@ -93,8 +96,8 @@ fn traverse(
93 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 96 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
94 97
95 let mut current_macro_call: Option<ast::MacroCall> = None; 98 let mut current_macro_call: Option<ast::MacroCall> = None;
96 let mut current_macro_rules: Option<ast::MacroRules> = None; 99 let mut current_macro: Option<ast::Macro> = None;
97 let mut macro_rules_highlighter = MacroRulesHighlighter::default(); 100 let mut macro_highlighter = MacroHighlighter::default();
98 let mut inside_attribute = false; 101 let mut inside_attribute = false;
99 102
100 // Walk all nodes, keeping track of whether we are inside a macro or not. 103 // Walk all nodes, keeping track of whether we are inside a macro or not.
@@ -129,16 +132,16 @@ fn traverse(
129 _ => (), 132 _ => (),
130 } 133 }
131 134
132 match event.clone().map(|it| it.into_node().and_then(ast::MacroRules::cast)) { 135 match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) {
133 WalkEvent::Enter(Some(mac)) => { 136 WalkEvent::Enter(Some(mac)) => {
134 macro_rules_highlighter.init(); 137 macro_highlighter.init();
135 current_macro_rules = Some(mac); 138 current_macro = Some(mac);
136 continue; 139 continue;
137 } 140 }
138 WalkEvent::Leave(Some(mac)) => { 141 WalkEvent::Leave(Some(mac)) => {
139 assert_eq!(current_macro_rules, Some(mac)); 142 assert_eq!(current_macro, Some(mac));
140 current_macro_rules = None; 143 current_macro = None;
141 macro_rules_highlighter = MacroRulesHighlighter::default(); 144 macro_highlighter = MacroHighlighter::default();
142 } 145 }
143 _ => (), 146 _ => (),
144 } 147 }
@@ -164,9 +167,9 @@ fn traverse(
164 167
165 let range = element.text_range(); 168 let range = element.text_range();
166 169
167 if current_macro_rules.is_some() { 170 if current_macro.is_some() {
168 if let Some(tok) = element.as_token() { 171 if let Some(tok) = element.as_token() {
169 macro_rules_highlighter.advance(tok); 172 macro_highlighter.advance(tok);
170 } 173 }
171 } 174 }
172 175
@@ -200,7 +203,7 @@ fn traverse(
200 } 203 }
201 } 204 }
202 205
203 if let Some(_) = macro_rules_highlighter.highlight(element_to_highlight.clone()) { 206 if let Some(_) = macro_highlighter.highlight(element_to_highlight.clone()) {
204 continue; 207 continue;
205 } 208 }
206 209
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index e503abc93..5bbadb0f4 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -31,7 +31,7 @@ fn is_format_string(string: &ast::String) -> Option<()> {
31 let parent = string.syntax().parent()?; 31 let parent = string.syntax().parent()?;
32 32
33 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?; 33 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?;
34 if !matches!(name.text(), "format_args" | "format_args_nl") { 34 if !matches!(name.text().as_str(), "format_args" | "format_args_nl") {
35 return None; 35 return None;
36 } 36 }
37 37
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index b0cfdd8b7..5ccb84714 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -1,6 +1,6 @@
1//! Computes color for a single element. 1//! Computes color for a single element.
2 2
3use hir::{AsAssocItem, Semantics, VariantDef}; 3use hir::{AsAssocItem, AssocItemContainer, Semantics, VariantDef};
4use ide_db::{ 4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
6 RootDatabase, SymbolKind, 6 RootDatabase, SymbolKind,
@@ -275,12 +275,24 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
275 hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module), 275 hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module),
276 hir::ModuleDef::Function(func) => { 276 hir::ModuleDef::Function(func) => {
277 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); 277 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
278 if func.as_assoc_item(db).is_some() { 278 if let Some(item) = func.as_assoc_item(db) {
279 h |= HlMod::Associated; 279 h |= HlMod::Associated;
280 if func.self_param(db).is_none() { 280 if func.self_param(db).is_none() {
281 h |= HlMod::Static 281 h |= HlMod::Static
282 } 282 }
283
284 match item.container(db) {
285 AssocItemContainer::Impl(i) => {
286 if i.trait_(db).is_some() {
287 h |= HlMod::Trait;
288 }
289 }
290 AssocItemContainer::Trait(_t) => {
291 h |= HlMod::Trait;
292 }
293 }
283 } 294 }
295
284 if func.is_unsafe(db) { 296 if func.is_unsafe(db) {
285 h |= HlMod::Unsafe; 297 h |= HlMod::Unsafe;
286 } 298 }
@@ -292,9 +304,20 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
292 hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant), 304 hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant),
293 hir::ModuleDef::Const(konst) => { 305 hir::ModuleDef::Const(konst) => {
294 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)); 306 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const));
295 if konst.as_assoc_item(db).is_some() { 307 if let Some(item) = konst.as_assoc_item(db) {
296 h |= HlMod::Associated 308 h |= HlMod::Associated;
309 match item.container(db) {
310 AssocItemContainer::Impl(i) => {
311 if i.trait_(db).is_some() {
312 h |= HlMod::Trait;
313 }
314 }
315 AssocItemContainer::Trait(_t) => {
316 h |= HlMod::Trait;
317 }
318 }
297 } 319 }
320
298 return h; 321 return h;
299 } 322 }
300 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), 323 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait),
@@ -362,6 +385,10 @@ fn highlight_method_call(
362 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { 385 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
363 h |= HlMod::Unsafe; 386 h |= HlMod::Unsafe;
364 } 387 }
388 if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() {
389 h |= HlMod::Trait
390 }
391
365 if let Some(self_param) = func.self_param(sema.db) { 392 if let Some(self_param) = func.self_param(sema.db) {
366 match self_param.access(sema.db) { 393 match self_param.access(sema.db) {
367 hir::Access::Shared => (), 394 hir::Access::Shared => (),
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 38bf49348..04fafd244 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -1,17 +1,18 @@
1//! "Recursive" Syntax highlighting for code in doctests and fixtures. 1//! "Recursive" Syntax highlighting for code in doctests and fixtures.
2 2
3use std::{mem, ops::Range}; 3use std::mem;
4 4
5use either::Either; 5use either::Either;
6use hir::{HasAttrs, InFile, Semantics}; 6use hir::{InFile, Semantics};
7use ide_db::{call_info::ActiveParameter, defs::Definition, SymbolKind}; 7use ide_db::{call_info::ActiveParameter, SymbolKind};
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode}, 9 ast::{self, AstNode},
10 match_ast, AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, 10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
11}; 11};
12 12
13use crate::{ 13use crate::{
14 doc_links::extract_definitions_from_markdown, Analysis, HlMod, HlRange, HlTag, RootDatabase, 14 doc_links::{doc_attributes, extract_definitions_from_markdown, resolve_doc_path_for_def},
15 Analysis, HlMod, HlRange, HlTag, RootDatabase,
15}; 16};
16 17
17use super::{highlights::Highlights, injector::Injector}; 18use super::{highlights::Highlights, injector::Injector};
@@ -89,34 +90,6 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
89 "edition2021", 90 "edition2021",
90]; 91];
91 92
92fn doc_attributes<'node>(
93 sema: &Semantics<RootDatabase>,
94 node: &'node SyntaxNode,
95) -> Option<(hir::AttrsWithOwner, Definition)> {
96 match_ast! {
97 match node {
98 ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
99 ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
100 ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Function(def)))),
101 ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(def))))),
102 ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Union(def))))),
103 ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(def))))),
104 ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Variant(def)))),
105 ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Trait(def)))),
106 ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Static(def)))),
107 ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Const(def)))),
108 ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::TypeAlias(def)))),
109 ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))),
110 ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
111 ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
112 ast::MacroRules(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))),
113 // ast::MacroDef(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
114 // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
115 _ => return None
116 }
117 }
118}
119
120/// Injection of syntax highlighting of doctests. 93/// Injection of syntax highlighting of doctests.
121pub(super) fn doc_comment( 94pub(super) fn doc_comment(
122 hl: &mut Highlights, 95 hl: &mut Highlights,
@@ -139,8 +112,28 @@ pub(super) fn doc_comment(
139 // Replace the original, line-spanning comment ranges by new, only comment-prefix 112 // Replace the original, line-spanning comment ranges by new, only comment-prefix
140 // spanning comment ranges. 113 // spanning comment ranges.
141 let mut new_comments = Vec::new(); 114 let mut new_comments = Vec::new();
142 let mut intra_doc_links = Vec::new();
143 let mut string; 115 let mut string;
116
117 if let Some((docs, doc_mapping)) = attributes.docs_with_rangemap(sema.db) {
118 extract_definitions_from_markdown(docs.as_str())
119 .into_iter()
120 .filter_map(|(range, link, ns)| {
121 let def = resolve_doc_path_for_def(sema.db, def, &link, ns)?;
122 let InFile { file_id, value: range } = doc_mapping.map(range)?;
123 (file_id == node.file_id).then(|| (range, def))
124 })
125 .for_each(|(range, def)| {
126 hl.add(HlRange {
127 range,
128 highlight: module_def_to_hl_tag(def)
129 | HlMod::Documentation
130 | HlMod::Injected
131 | HlMod::IntraDocLink,
132 binding_hash: None,
133 })
134 });
135 }
136
144 for attr in attributes.by_key("doc").attrs() { 137 for attr in attributes.by_key("doc").attrs() {
145 let InFile { file_id, value: src } = attrs_source_map.source_of(&attr); 138 let InFile { file_id, value: src } = attrs_source_map.source_of(&attr);
146 if file_id != node.file_id { 139 if file_id != node.file_id {
@@ -186,25 +179,7 @@ pub(super) fn doc_comment(
186 is_doctest = is_codeblock && is_rust; 179 is_doctest = is_codeblock && is_rust;
187 continue; 180 continue;
188 } 181 }
189 None if !is_doctest => { 182 None if !is_doctest => continue,
190 intra_doc_links.extend(
191 extract_definitions_from_markdown(line)
192 .into_iter()
193 .filter_map(|(range, link, ns)| {
194 Some(range).zip(validate_intra_doc_link(sema.db, &def, &link, ns))
195 })
196 .map(|(Range { start, end }, def)| {
197 (
198 def,
199 TextRange::at(
200 prev_range_start + TextSize::from(start as u32),
201 TextSize::from((end - start) as u32),
202 ),
203 )
204 }),
205 );
206 continue;
207 }
208 None => (), 183 None => (),
209 } 184 }
210 185
@@ -223,17 +198,6 @@ pub(super) fn doc_comment(
223 } 198 }
224 } 199 }
225 200
226 for (def, range) in intra_doc_links {
227 hl.add(HlRange {
228 range,
229 highlight: module_def_to_hl_tag(def)
230 | HlMod::Documentation
231 | HlMod::Injected
232 | HlMod::IntraDocLink,
233 binding_hash: None,
234 });
235 }
236
237 if new_comments.is_empty() { 201 if new_comments.is_empty() {
238 return; // no need to run an analysis on an empty file 202 return; // no need to run an analysis on an empty file
239 } 203 }
@@ -284,33 +248,6 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
284 } 248 }
285} 249}
286 250
287fn validate_intra_doc_link(
288 db: &RootDatabase,
289 def: &Definition,
290 link: &str,
291 ns: Option<hir::Namespace>,
292) -> Option<hir::ModuleDef> {
293 match def {
294 Definition::ModuleDef(def) => match def {
295 hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns),
296 hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns),
297 hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns),
298 hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns),
299 hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns),
300 hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns),
301 hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns),
302 hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns),
303 hir::ModuleDef::BuiltinType(_) => None,
304 },
305 Definition::Macro(it) => it.resolve_doc_path(db, &link, ns),
306 Definition::Field(it) => it.resolve_doc_path(db, &link, ns),
307 Definition::SelfType(_)
308 | Definition::Local(_)
309 | Definition::GenericParam(_)
310 | Definition::Label(_) => None,
311 }
312}
313
314fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag { 251fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag {
315 let symbol = match def { 252 let symbol = match def {
316 hir::ModuleDef::Module(_) => SymbolKind::Module, 253 hir::ModuleDef::Module(_) => SymbolKind::Module,
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_.rs
index 44620e912..819704294 100644
--- a/crates/ide/src/syntax_highlighting/macro_rules.rs
+++ b/crates/ide/src/syntax_highlighting/macro_.rs
@@ -4,18 +4,18 @@ use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T};
4use crate::{HlRange, HlTag}; 4use crate::{HlRange, HlTag};
5 5
6#[derive(Default)] 6#[derive(Default)]
7pub(super) struct MacroRulesHighlighter { 7pub(super) struct MacroHighlighter {
8 state: Option<MacroMatcherParseState>, 8 state: Option<MacroMatcherParseState>,
9} 9}
10 10
11impl MacroRulesHighlighter { 11impl MacroHighlighter {
12 pub(super) fn init(&mut self) { 12 pub(super) fn init(&mut self) {
13 self.state = Some(MacroMatcherParseState::default()); 13 self.state = Some(MacroMatcherParseState::default());
14 } 14 }
15 15
16 pub(super) fn advance(&mut self, token: &SyntaxToken) { 16 pub(super) fn advance(&mut self, token: &SyntaxToken) {
17 if let Some(state) = self.state.as_mut() { 17 if let Some(state) = self.state.as_mut() {
18 update_macro_rules_state(state, token); 18 update_macro_state(state, token);
19 } 19 }
20 } 20 }
21 21
@@ -74,9 +74,9 @@ impl RuleState {
74 } 74 }
75} 75}
76 76
77fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { 77fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
78 if !state.in_invoc_body { 78 if !state.in_invoc_body {
79 if tok.kind() == T!['{'] { 79 if tok.kind() == T!['{'] || tok.kind() == T!['('] {
80 state.in_invoc_body = true; 80 state.in_invoc_body = true;
81 } 81 }
82 return; 82 return;
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 93db79b89..1cec991aa 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -58,6 +58,8 @@ pub enum HlMod {
58 Associated, 58 Associated,
59 /// Used for intra doc links in doc injection. 59 /// Used for intra doc links in doc injection.
60 IntraDocLink, 60 IntraDocLink,
61 /// Used for items in traits and trait impls.
62 Trait,
61 63
62 /// Keep this last! 64 /// Keep this last!
63 Unsafe, 65 Unsafe,
@@ -158,6 +160,7 @@ impl HlMod {
158 HlMod::Callable, 160 HlMod::Callable,
159 HlMod::Static, 161 HlMod::Static,
160 HlMod::Associated, 162 HlMod::Associated,
163 HlMod::Trait,
161 HlMod::Unsafe, 164 HlMod::Unsafe,
162 ]; 165 ];
163 166
@@ -174,6 +177,7 @@ impl HlMod {
174 HlMod::IntraDocLink => "intra_doc_link", 177 HlMod::IntraDocLink => "intra_doc_link",
175 HlMod::Mutable => "mutable", 178 HlMod::Mutable => "mutable",
176 HlMod::Static => "static", 179 HlMod::Static => "static",
180 HlMod::Trait => "trait",
177 HlMod::Unsafe => "unsafe", 181 HlMod::Unsafe => "unsafe",
178 } 182 }
179 } 183 }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
index 4635ea927..8cde3906c 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -47,12 +47,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
47<span class="brace">}</span> 47<span class="brace">}</span>
48 48
49<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span> 49<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span>
50 <span class="keyword">fn</span> <span class="function declaration static associated">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 50 <span class="keyword">fn</span> <span class="function declaration static associated trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
51 <span class="keyword">fn</span> <span class="function declaration associated">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 51 <span class="keyword">fn</span> <span class="function declaration associated trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
52<span class="brace">}</span> 52<span class="brace">}</span>
53 53
54<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span> 54<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span>
55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
56 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 56 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
57<span class="brace">}</span> 57<span class="brace">}</span>
58 </code></pre> \ No newline at end of file 58 </code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 045162eb8..b6d1cac4e 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -100,10 +100,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
100<span class="brace">}</span> 100<span class="brace">}</span>
101 101
102<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span> 102<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
103<span class="comment documentation">/// </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> is this function</span> 103<span class="comment documentation">/// This function is &gt; </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span>
104<span class="comment documentation">/// [`noop`](noop) is a macro below</span> 104<span class="comment documentation">/// [`noop`](noop) is a macro below</span>
105<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation intra_doc_link injected">[`module`]</span>
106<span class="comment documentation">///</span>
107<span class="comment documentation">/// [`Item`]: module::Item</span>
108<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span>
105<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 109<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
106 110
111<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">module</span> <span class="brace">{</span>
112 <span class="keyword">pub</span> <span class="keyword">struct</span> <span class="struct declaration">Item</span><span class="semicolon">;</span>
113<span class="brace">}</span>
114
107<span class="comment documentation">/// ```</span> 115<span class="comment documentation">/// ```</span>
108<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> 116<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
109<span class="comment documentation">/// ```</span> 117<span class="comment documentation">/// ```</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 9215ddd9e..7c6694a27 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -42,7 +42,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
42<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 42<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
43 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span> 43 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
44 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span> 44 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span>
45 <span class="keyword">fn</span> <span class="function declaration static associated">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 45 <span class="keyword">fn</span> <span class="function declaration static associated trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
46 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> 46 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span>
47 <span class="brace">}</span> 47 <span class="brace">}</span>
48 <span class="brace">}</span><span class="string_literal">"#</span> 48 <span class="brace">}</span><span class="string_literal">"#</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 6a6555208..72910421d 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -62,11 +62,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62<span class="brace">}</span> 62<span class="brace">}</span>
63 63
64<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> 64<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span>
65 <span class="keyword">fn</span> <span class="function declaration associated">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> 65 <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
66<span class="brace">}</span> 66<span class="brace">}</span>
67 67
68<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span> 68<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span>
69 <span class="keyword">fn</span> <span class="function declaration associated">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 69 <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
70<span class="brace">}</span> 70<span class="brace">}</span>
71 71
72<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 72<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
@@ -96,6 +96,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
96 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span> 96 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span>
97 97
98 <span class="comment">// unsafe auto ref of packed field</span> 98 <span class="comment">// unsafe auto ref of packed field</span>
99 <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function associated unsafe">calls_autoref</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 99 <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function associated trait unsafe">calls_autoref</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
100 <span class="brace">}</span> 100 <span class="brace">}</span>
101<span class="brace">}</span></code></pre> \ No newline at end of file 101<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 8b2dd3b70..973173254 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -41,7 +41,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
41<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span><span class="brace">}</span> 41<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span><span class="brace">}</span>
42 42
43<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span> 43<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
44<span class="keyword">macro</span> <span class="unresolved_reference declaration">Copy</span> <span class="brace">{</span><span class="brace">}</span> 44<span class="keyword">macro</span> <span class="macro declaration">Copy</span> <span class="brace">{</span><span class="brace">}</span>
45 45
46<span class="comment">// Needed for function consuming vs normal</span> 46<span class="comment">// Needed for function consuming vs normal</span>
47<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="brace">{</span> 47<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="brace">{</span>
@@ -67,11 +67,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
67<span class="brace">}</span> 67<span class="brace">}</span>
68 68
69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span> 69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span>
70 <span class="keyword">fn</span> <span class="function declaration associated">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span> 70 <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
71<span class="brace">}</span> 71<span class="brace">}</span>
72 72
73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span> 73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span>
74 <span class="keyword">fn</span> <span class="function declaration associated">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 74 <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
76 <span class="brace">}</span> 76 <span class="brace">}</span>
77<span class="brace">}</span> 77<span class="brace">}</span>
@@ -158,6 +158,16 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
158 <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="colon">:</span>ty<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="parenthesis">)</span> 158 <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="colon">:</span>ty<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="parenthesis">)</span>
159<span class="brace">}</span> 159<span class="brace">}</span>
160 160
161<span class="keyword">macro</span> <span class="macro declaration">with_args</span><span class="parenthesis">(</span><span class="punctuation">$</span>i<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="brace">{</span>
162 <span class="punctuation">$</span>i
163<span class="brace">}</span>
164
165<span class="keyword">macro</span> <span class="macro declaration">without_args</span> <span class="brace">{</span>
166 <span class="parenthesis">(</span><span class="punctuation">$</span>i<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
167 <span class="punctuation">$</span>i
168 <span class="brace">}</span>
169<span class="brace">}</span>
170
161<span class="comment">// comment</span> 171<span class="comment">// comment</span>
162<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 172<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
163 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"Hello, {}!"</span><span class="comma">,</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span> 173 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"Hello, {}!"</span><span class="comma">,</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 7b2922b0d..1b02857ec 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -129,6 +129,16 @@ macro_rules! keyword_frag {
129 ($type:ty) => ($type) 129 ($type:ty) => ($type)
130} 130}
131 131
132macro with_args($i:ident) {
133 $i
134}
135
136macro without_args {
137 ($i:ident) => {
138 $i
139 }
140}
141
132// comment 142// comment
133fn main() { 143fn main() {
134 println!("Hello, {}!", 92); 144 println!("Hello, {}!", 92);
@@ -534,10 +544,18 @@ impl Foo {
534} 544}
535 545
536/// [`Foo`](Foo) is a struct 546/// [`Foo`](Foo) is a struct
537/// [`all_the_links`](all_the_links) is this function 547/// This function is > [`all_the_links`](all_the_links) <
538/// [`noop`](noop) is a macro below 548/// [`noop`](noop) is a macro below
549/// [`Item`] is a struct in the module [`module`]
550///
551/// [`Item`]: module::Item
552/// [mix_and_match]: ThisShouldntResolve
539pub fn all_the_links() {} 553pub fn all_the_links() {}
540 554
555pub mod module {
556 pub struct Item;
557}
558
541/// ``` 559/// ```
542/// noop!(1); 560/// noop!(1);
543/// ``` 561/// ```
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 8979de528..633878d1c 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -14,6 +14,7 @@ use syntax::{
14// 14//
15// | VS Code | **Rust Analyzer: Show Syntax Tree** 15// | VS Code | **Rust Analyzer: Show Syntax Tree**
16// |=== 16// |===
17// image::https://user-images.githubusercontent.com/48062697/113065586-068bdb80-91b1-11eb-9507-fee67f9f45a0.gif[]
17pub(crate) fn syntax_tree( 18pub(crate) fn syntax_tree(
18 db: &RootDatabase, 19 db: &RootDatabase,
19 file_id: FileId, 20 file_id: FileId,
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index e10b7d98e..1378048e5 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -22,18 +22,19 @@ use ide_db::{
22use syntax::{ 22use syntax::{
23 algo::find_node_at_offset, 23 algo::find_node_at_offset,
24 ast::{self, edit::IndentLevel, AstToken}, 24 ast::{self, edit::IndentLevel, AstToken},
25 AstNode, SourceFile, 25 AstNode, Parse, SourceFile,
26 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, 26 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
27 TextRange, TextSize, 27 TextRange, TextSize,
28}; 28};
29 29
30use text_edit::TextEdit; 30use text_edit::{Indel, TextEdit};
31 31
32use crate::SourceChange; 32use crate::SourceChange;
33 33
34pub(crate) use on_enter::on_enter; 34pub(crate) use on_enter::on_enter;
35 35
36pub(crate) const TRIGGER_CHARS: &str = ".=>"; 36// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
37pub(crate) const TRIGGER_CHARS: &str = ".=>{";
37 38
38// Feature: On Typing Assists 39// Feature: On Typing Assists
39// 40//
@@ -41,6 +42,7 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>";
41// 42//
42// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression 43// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
43// - typing `.` in a chain method call auto-indents 44// - typing `.` in a chain method call auto-indents
45// - typing `{` in front of an expression inserts a closing `}` after the expression
44// 46//
45// VS Code:: 47// VS Code::
46// 48//
@@ -49,33 +51,87 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>";
49// ---- 51// ----
50// "editor.formatOnType": true, 52// "editor.formatOnType": true,
51// ---- 53// ----
54//
55// image::https://user-images.githubusercontent.com/48062697/113166163-69758500-923a-11eb-81ee-eb33ec380399.gif[]
56// image::https://user-images.githubusercontent.com/48062697/113171066-105c2000-923f-11eb-87ab-f4a263346567.gif[]
52pub(crate) fn on_char_typed( 57pub(crate) fn on_char_typed(
53 db: &RootDatabase, 58 db: &RootDatabase,
54 position: FilePosition, 59 position: FilePosition,
55 char_typed: char, 60 char_typed: char,
56) -> Option<SourceChange> { 61) -> Option<SourceChange> {
57 assert!(TRIGGER_CHARS.contains(char_typed)); 62 if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
58 let file = &db.parse(position.file_id).tree(); 63 return None;
59 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 64 }
65 let file = &db.parse(position.file_id);
66 if !stdx::always!(file.tree().syntax().text().char_at(position.offset) == Some(char_typed)) {
67 return None;
68 }
60 let edit = on_char_typed_inner(file, position.offset, char_typed)?; 69 let edit = on_char_typed_inner(file, position.offset, char_typed)?;
61 Some(SourceChange::from_text_edit(position.file_id, edit)) 70 Some(SourceChange::from_text_edit(position.file_id, edit))
62} 71}
63 72
64fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { 73fn on_char_typed_inner(
65 assert!(TRIGGER_CHARS.contains(char_typed)); 74 file: &Parse<SourceFile>,
75 offset: TextSize,
76 char_typed: char,
77) -> Option<TextEdit> {
78 if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
79 return None;
80 }
66 match char_typed { 81 match char_typed {
67 '.' => on_dot_typed(file, offset), 82 '.' => on_dot_typed(&file.tree(), offset),
68 '=' => on_eq_typed(file, offset), 83 '=' => on_eq_typed(&file.tree(), offset),
69 '>' => on_arrow_typed(file, offset), 84 '>' => on_arrow_typed(&file.tree(), offset),
85 '{' => on_opening_brace_typed(file, offset),
70 _ => unreachable!(), 86 _ => unreachable!(),
71 } 87 }
72} 88}
73 89
90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
91/// block.
92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
94 return None;
95 }
96
97 let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
98
99 // Remove the `{` to get a better parse tree, and reparse
100 let file = file.reparse(&Indel::delete(brace_token.text_range()));
101
102 let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?;
103 if expr.syntax().text_range().start() != offset {
104 return None;
105 }
106
107 // Enclose the outermost expression starting at `offset`
108 while let Some(parent) = expr.syntax().parent() {
109 if parent.text_range().start() != expr.syntax().text_range().start() {
110 break;
111 }
112
113 match ast::Expr::cast(parent) {
114 Some(parent) => expr = parent,
115 None => break,
116 }
117 }
118
119 // If it's a statement in a block, we don't know how many statements should be included
120 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
121 return None;
122 }
123
124 // Insert `}` right after the expression.
125 Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string()))
126}
127
74/// Returns an edit which should be applied after `=` was typed. Primarily, 128/// Returns an edit which should be applied after `=` was typed. Primarily,
75/// this works when adding `let =`. 129/// this works when adding `let =`.
76// FIXME: use a snippet completion instead of this hack here. 130// FIXME: use a snippet completion instead of this hack here.
77fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 131fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
78 assert_eq!(file.syntax().text().char_at(offset), Some('=')); 132 if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) {
133 return None;
134 }
79 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; 135 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
80 if let_stmt.semicolon_token().is_some() { 136 if let_stmt.semicolon_token().is_some() {
81 return None; 137 return None;
@@ -97,7 +153,9 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
97 153
98/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. 154/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
99fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 155fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
100 assert_eq!(file.syntax().text().char_at(offset), Some('.')); 156 if !stdx::always!(file.syntax().text().char_at(offset) == Some('.')) {
157 return None;
158 }
101 let whitespace = 159 let whitespace =
102 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; 160 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
103 161
@@ -126,7 +184,9 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
126/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` 184/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
127fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 185fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
128 let file_text = file.syntax().text(); 186 let file_text = file.syntax().text();
129 assert_eq!(file_text.char_at(offset), Some('>')); 187 if !stdx::always!(file_text.char_at(offset) == Some('>')) {
188 return None;
189 }
130 let after_arrow = offset + TextSize::of('>'); 190 let after_arrow = offset + TextSize::of('>');
131 if file_text.char_at(after_arrow) != Some('{') { 191 if file_text.char_at(after_arrow) != Some('{') {
132 return None; 192 return None;
@@ -149,7 +209,7 @@ mod tests {
149 let edit = TextEdit::insert(offset, char_typed.to_string()); 209 let edit = TextEdit::insert(offset, char_typed.to_string());
150 edit.apply(&mut before); 210 edit.apply(&mut before);
151 let parse = SourceFile::parse(&before); 211 let parse = SourceFile::parse(&before);
152 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 212 on_char_typed_inner(&parse, offset, char_typed).map(|it| {
153 it.apply(&mut before); 213 it.apply(&mut before);
154 before.to_string() 214 before.to_string()
155 }) 215 })
@@ -370,4 +430,85 @@ fn main() {
370 fn adds_space_after_return_type() { 430 fn adds_space_after_return_type() {
371 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }") 431 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }")
372 } 432 }
433
434 #[test]
435 fn adds_closing_brace() {
436 type_char(
437 '{',
438 r#"
439fn f() { match () { _ => $0() } }
440 "#,
441 r#"
442fn f() { match () { _ => {()} } }
443 "#,
444 );
445 type_char(
446 '{',
447 r#"
448fn f() { $0() }
449 "#,
450 r#"
451fn f() { {()} }
452 "#,
453 );
454 type_char(
455 '{',
456 r#"
457fn f() { let x = $0(); }
458 "#,
459 r#"
460fn f() { let x = {()}; }
461 "#,
462 );
463 type_char(
464 '{',
465 r#"
466fn f() { let x = $0a.b(); }
467 "#,
468 r#"
469fn f() { let x = {a.b()}; }
470 "#,
471 );
472 type_char(
473 '{',
474 r#"
475const S: () = $0();
476fn f() {}
477 "#,
478 r#"
479const S: () = {()};
480fn f() {}
481 "#,
482 );
483 type_char(
484 '{',
485 r#"
486const S: () = $0a.b();
487fn f() {}
488 "#,
489 r#"
490const S: () = {a.b()};
491fn f() {}
492 "#,
493 );
494 type_char(
495 '{',
496 r#"
497fn f() {
498 match x {
499 0 => $0(),
500 1 => (),
501 }
502}
503 "#,
504 r#"
505fn f() {
506 match x {
507 0 => {()},
508 1 => (),
509 }
510}
511 "#,
512 );
513 }
373} 514}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 978c479de..9144681bf 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -32,6 +32,8 @@ use text_edit::TextEdit;
32// "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust" 32// "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust"
33// } 33// }
34// ---- 34// ----
35//
36// image::https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif[]
35pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { 37pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
36 let parse = db.parse(position.file_id); 38 let parse = db.parse(position.file_id);
37 let file = parse.tree(); 39 let file = parse.tree();
diff --git a/crates/ide/src/view_hir.rs b/crates/ide/src/view_hir.rs
index f8f3fae3d..7312afe53 100644
--- a/crates/ide/src/view_hir.rs
+++ b/crates/ide/src/view_hir.rs
@@ -10,6 +10,7 @@ use syntax::{algo::find_node_at_offset, ast, AstNode};
10// 10//
11// | VS Code | **Rust Analyzer: View Hir** 11// | VS Code | **Rust Analyzer: View Hir**
12// |=== 12// |===
13// image::https://user-images.githubusercontent.com/48062697/113065588-068bdb80-91b1-11eb-9a78-0b4ef1e972fb.gif[]
13pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { 14pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String {
14 body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string()) 15 body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string())
15} 16}