aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs62
-rw-r--r--crates/ra_ide_api/src/completion/presentation.rs36
-rw-r--r--crates/ra_lsp_server/src/conv.rs106
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs24
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs10
-rw-r--r--crates/ra_lsp_server/src/world.rs1
-rw-r--r--docs/user/README.md19
-rw-r--r--xtask/src/main.rs42
8 files changed, 267 insertions, 33 deletions
diff --git a/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs
index d808b2357..09f743c66 100644
--- a/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs
+++ b/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs
@@ -56,6 +56,16 @@ mod tests {
56 do_reference_completion( 56 do_reference_completion(
57 " 57 "
58 //- /main.rs 58 //- /main.rs
59 /// Creates a [`Vec`] containing the arguments.
60 ///
61 /// - Create a [`Vec`] containing a given list of elements:
62 ///
63 /// ```
64 /// let v = vec![1, 2, 3];
65 /// assert_eq!(v[0], 1);
66 /// assert_eq!(v[1], 2);
67 /// assert_eq!(v[2], 3);
68 /// ```
59 macro_rules! vec { 69 macro_rules! vec {
60 () => {} 70 () => {}
61 } 71 }
@@ -68,13 +78,61 @@ mod tests {
68 @r##"[ 78 @r##"[
69 CompletionItem { 79 CompletionItem {
70 label: "vec!", 80 label: "vec!",
71 source_range: [46; 46), 81 source_range: [280; 280),
72 delete: [46; 46), 82 delete: [280; 280),
73 insert: "vec![$0]", 83 insert: "vec![$0]",
74 kind: Macro, 84 kind: Macro,
75 detail: "macro_rules! vec", 85 detail: "macro_rules! vec",
86 documentation: Documentation(
87 "Creates a [`Vec`] containing the arguments.\n\n- Create a [`Vec`] containing a given list of elements:\n\n```\nlet v = vec![1, 2, 3];\nassert_eq!(v[0], 1);\nassert_eq!(v[1], 2);\nassert_eq!(v[2], 3);\n```",
88 ),
76 }, 89 },
77]"## 90]"##
78 ); 91 );
79 } 92 }
93
94 #[test]
95 fn completes_macros_braces_guessing() {
96 assert_debug_snapshot!(
97 do_reference_completion(
98 "
99 //- /main.rs
100 /// Foo
101 ///
102 /// Not call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.
103 /// Call as `let _=foo! { hello world };`
104 macro_rules! foo {
105 () => {}
106 }
107
108 fn main() {
109 <|>
110 }
111 "
112 ),
113 @r###"[
114 CompletionItem {
115 label: "foo!",
116 source_range: [163; 163),
117 delete: [163; 163),
118 insert: "foo! {$0}",
119 kind: Macro,
120 detail: "macro_rules! foo",
121 documentation: Documentation(
122 "Foo\n\nNot call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.\nCall as `let _=foo! { hello world };`",
123 ),
124 },
125 CompletionItem {
126 label: "main()",
127 source_range: [163; 163),
128 delete: [163; 163),
129 insert: "main()$0",
130 kind: Function,
131 lookup: "main",
132 detail: "fn main()",
133 },
134]
135 "###
136 );
137 }
80} 138}
diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs
index 20242d293..aed4ce6d4 100644
--- a/crates/ra_ide_api/src/completion/presentation.rs
+++ b/crates/ra_ide_api/src/completion/presentation.rs
@@ -131,6 +131,33 @@ impl Completions {
131 self.add_function_with_name(ctx, None, func) 131 self.add_function_with_name(ctx, None, func)
132 } 132 }
133 133
134 fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
135 let mut votes = [0, 0, 0];
136 for (idx, s) in docs.match_indices(&macro_name) {
137 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
138 // Ensure to match the full word
139 if after.starts_with("!")
140 && before
141 .chars()
142 .rev()
143 .next()
144 .map_or(true, |c| c != '_' && !c.is_ascii_alphanumeric())
145 {
146 // It may have spaces before the braces like `foo! {}`
147 match after[1..].chars().find(|&c| !c.is_whitespace()) {
148 Some('{') => votes[0] += 1,
149 Some('[') => votes[1] += 1,
150 Some('(') => votes[2] += 1,
151 _ => {}
152 }
153 }
154 }
155
156 // Insert a space before `{}`.
157 // We prefer the last one when some votes equal.
158 *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
159 }
160
134 pub(crate) fn add_macro( 161 pub(crate) fn add_macro(
135 &mut self, 162 &mut self,
136 ctx: &CompletionContext, 163 ctx: &CompletionContext,
@@ -141,10 +168,9 @@ impl Completions {
141 if let Some(name) = name { 168 if let Some(name) = name {
142 let detail = macro_label(&ast_node); 169 let detail = macro_label(&ast_node);
143 170
144 let macro_braces_to_insert = match name.as_str() { 171 let docs = macro_.docs(ctx.db);
145 "vec" => "[$0]", 172 let macro_braces_to_insert =
146 _ => "($0)", 173 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str()));
147 };
148 let macro_declaration = name + "!"; 174 let macro_declaration = name + "!";
149 175
150 let builder = CompletionItem::new( 176 let builder = CompletionItem::new(
@@ -153,7 +179,7 @@ impl Completions {
153 &macro_declaration, 179 &macro_declaration,
154 ) 180 )
155 .kind(CompletionItemKind::Macro) 181 .kind(CompletionItemKind::Macro)
156 .set_documentation(macro_.docs(ctx.db)) 182 .set_documentation(docs)
157 .detail(detail) 183 .detail(detail)
158 .insert_snippet(macro_declaration + macro_braces_to_insert); 184 .insert_snippet(macro_declaration + macro_braces_to_insert);
159 185
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index 1318a1738..ee503633d 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -227,22 +227,57 @@ impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
227 } 227 }
228} 228}
229 229
230impl ConvWith<&LineIndex> for Fold { 230pub(crate) struct FoldConvCtx<'a> {
231 pub(crate) text: &'a str,
232 pub(crate) line_index: &'a LineIndex,
233 pub(crate) line_folding_only: bool,
234}
235
236impl ConvWith<&FoldConvCtx<'_>> for Fold {
231 type Output = lsp_types::FoldingRange; 237 type Output = lsp_types::FoldingRange;
232 238
233 fn conv_with(self, line_index: &LineIndex) -> lsp_types::FoldingRange { 239 fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange {
234 let range = self.range.conv_with(&line_index); 240 let kind = match self.kind {
235 lsp_types::FoldingRange { 241 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
236 start_line: range.start.line, 242 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
237 start_character: Some(range.start.character), 243 FoldKind::Mods => None,
238 end_line: range.end.line, 244 FoldKind::Block => None,
239 end_character: Some(range.end.character), 245 };
240 kind: match self.kind { 246
241 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment), 247 let range = self.range.conv_with(&ctx.line_index);
242 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports), 248
243 FoldKind::Mods => None, 249 if ctx.line_folding_only {
244 FoldKind::Block => None, 250 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
245 }, 251 // even if it contains text not in the folding range. To prevent that we exclude
252 // range.end.line from the folding region if there is more text after range.end
253 // on the same line.
254 let has_more_text_on_end_line = ctx.text
255 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
256 .chars()
257 .take_while(|it| *it != '\n')
258 .any(|it| !it.is_whitespace());
259
260 let end_line = if has_more_text_on_end_line {
261 range.end.line.saturating_sub(1)
262 } else {
263 range.end.line
264 };
265
266 lsp_types::FoldingRange {
267 start_line: range.start.line,
268 start_character: None,
269 end_line,
270 end_character: None,
271 kind,
272 }
273 } else {
274 lsp_types::FoldingRange {
275 start_line: range.start.line,
276 start_character: Some(range.start.character),
277 end_line: range.end.line,
278 end_character: Some(range.end.character),
279 kind,
280 }
246 } 281 }
247 } 282 }
248} 283}
@@ -512,3 +547,46 @@ where
512 self.map(|it| it.try_conv_with(ctx)).collect() 547 self.map(|it| it.try_conv_with(ctx)).collect()
513 } 548 }
514} 549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use test_utils::extract_ranges;
555
556 #[test]
557 fn conv_fold_line_folding_only_fixup() {
558 let text = r#"<fold>mod a;
559mod b;
560mod c;</fold>
561
562fn main() <fold>{
563 if cond <fold>{
564 a::do_a();
565 }</fold> else <fold>{
566 b::do_b();
567 }</fold>
568}</fold>"#;
569
570 let (ranges, text) = extract_ranges(text, "fold");
571 assert_eq!(ranges.len(), 4);
572 let folds = vec![
573 Fold { range: ranges[0], kind: FoldKind::Mods },
574 Fold { range: ranges[1], kind: FoldKind::Block },
575 Fold { range: ranges[2], kind: FoldKind::Block },
576 Fold { range: ranges[3], kind: FoldKind::Block },
577 ];
578
579 let line_index = LineIndex::new(&text);
580 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
581 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
582
583 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
584 assert_eq!(converted.len(), expected_lines.len());
585 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
586 assert_eq!(folding_range.start_line, *start_line);
587 assert_eq!(folding_range.start_character, None);
588 assert_eq!(folding_range.end_line, *end_line);
589 assert_eq!(folding_range.end_character, None);
590 }
591 }
592}
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 35c35d32b..0b5d9c44d 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -111,6 +111,21 @@ pub fn main_loop(
111 connection.sender.send(request.into()).unwrap(); 111 connection.sender.send(request.into()).unwrap();
112 } 112 }
113 113
114 let options = {
115 let text_document_caps = client_caps.text_document.as_ref();
116 Options {
117 publish_decorations: config.publish_decorations,
118 supports_location_link: text_document_caps
119 .and_then(|it| it.definition)
120 .and_then(|it| it.link_support)
121 .unwrap_or(false),
122 line_folding_only: text_document_caps
123 .and_then(|it| it.folding_range.as_ref())
124 .and_then(|it| it.line_folding_only)
125 .unwrap_or(false),
126 }
127 };
128
114 let feature_flags = { 129 let feature_flags = {
115 let mut ff = FeatureFlags::default(); 130 let mut ff = FeatureFlags::default();
116 for (flag, value) in config.feature_flags { 131 for (flag, value) in config.feature_flags {
@@ -133,14 +148,7 @@ pub fn main_loop(
133 config.lru_capacity, 148 config.lru_capacity,
134 &globs, 149 &globs,
135 Watch(!config.use_client_watching), 150 Watch(!config.use_client_watching),
136 Options { 151 options,
137 publish_decorations: config.publish_decorations,
138 supports_location_link: client_caps
139 .text_document
140 .and_then(|it| it.definition)
141 .and_then(|it| it.link_support)
142 .unwrap_or(false),
143 },
144 feature_flags, 152 feature_flags,
145 ) 153 )
146 }; 154 };
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 10e271376..af3cd04ea 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -18,7 +18,7 @@ use serde_json::to_value;
18 18
19use crate::{ 19use crate::{
20 cargo_target_spec::{runnable_args, CargoTargetSpec}, 20 cargo_target_spec::{runnable_args, CargoTargetSpec},
21 conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith, TryConvWithToVec}, 21 conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec},
22 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, 22 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
23 world::WorldSnapshot, 23 world::WorldSnapshot,
24 LspError, Result, 24 LspError, Result,
@@ -383,8 +383,14 @@ pub fn handle_folding_range(
383) -> Result<Option<Vec<FoldingRange>>> { 383) -> Result<Option<Vec<FoldingRange>>> {
384 let file_id = params.text_document.try_conv_with(&world)?; 384 let file_id = params.text_document.try_conv_with(&world)?;
385 let folds = world.analysis().folding_ranges(file_id)?; 385 let folds = world.analysis().folding_ranges(file_id)?;
386 let text = world.analysis().file_text(file_id)?;
386 let line_index = world.analysis().file_line_index(file_id)?; 387 let line_index = world.analysis().file_line_index(file_id)?;
387 let res = Some(folds.into_iter().map_conv_with(&*line_index).collect()); 388 let ctx = FoldConvCtx {
389 text: &text,
390 line_index: &line_index,
391 line_folding_only: world.options.line_folding_only,
392 };
393 let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
388 Ok(res) 394 Ok(res)
389} 395}
390 396
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 0eb684de5..51824e7a3 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -27,6 +27,7 @@ use crate::{
27pub struct Options { 27pub struct Options {
28 pub publish_decorations: bool, 28 pub publish_decorations: bool,
29 pub supports_location_link: bool, 29 pub supports_location_link: bool,
30 pub line_folding_only: bool,
30} 31}
31 32
32/// `WorldState` is the primary mutable state of the language server 33/// `WorldState` is the primary mutable state of the language server
diff --git a/docs/user/README.md b/docs/user/README.md
index f45c0d7d1..f1628d6a4 100644
--- a/docs/user/README.md
+++ b/docs/user/README.md
@@ -61,6 +61,25 @@ for details.
61For updates, pull the latest changes from the master branch, run `cargo xtask install` again, and **restart** VS Code instance. 61For updates, pull the latest changes from the master branch, run `cargo xtask install` again, and **restart** VS Code instance.
62See [microsoft/vscode#72308](https://github.com/microsoft/vscode/issues/72308) for why a full restart is needed. 62See [microsoft/vscode#72308](https://github.com/microsoft/vscode/issues/72308) for why a full restart is needed.
63 63
64### VS Code Remote
65
66You can also use `rust-analyzer` with the Visual Studio Code Remote extensions
67(Remote SSH, Remote WSL, Remote Containers). In this case, however, you have to
68manually install the `.vsix` package:
69
701. Build the extension on the remote host using the instructions above (ignore the
71 error if `code` cannot be found in your PATH: VSCode doesn't need to be installed
72 on the remote host).
732. In Visual Studio Code open a connection to the remote host.
743. Open the Extensions View (`View > Extensions`, keyboard shortcut: `Ctrl+Shift+X`).
754. From the top-right kebab menu (`ยทยทยท`) select `Install from VSIX...`
765. Inside the `rust-analyzer` directory find the `editors/code` subdirectory and choose
77 the `ra-lsp-0.0.1.vsix` file.
786. Restart Visual Studio Code and re-establish the connection to the remote host.
79
80In case of errors please make sure that `~/.cargo/bin` is in your `PATH` on the remote
81host.
82
64### Settings 83### Settings
65 84
66* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting 85* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 623058436..c08915aac 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -8,9 +8,12 @@ use pico_args::Arguments;
8use std::{env, path::PathBuf}; 8use std::{env, path::PathBuf};
9use xtask::{ 9use xtask::{
10 gen_tests, generate_boilerplate, install_format_hook, run, run_clippy, run_fuzzer, run_rustfmt, 10 gen_tests, generate_boilerplate, install_format_hook, run, run_clippy, run_fuzzer, run_rustfmt,
11 Cmd, Overwrite, Result, 11 run_with_output, Cmd, Overwrite, Result,
12}; 12};
13 13
14// Latest stable, feel free to send a PR if this lags behind.
15const REQUIRED_RUST_VERSION: u32 = 38;
16
14struct InstallOpt { 17struct InstallOpt {
15 client: Option<ClientOpt>, 18 client: Option<ClientOpt>,
16 server: Option<ServerOpt>, 19 server: Option<ServerOpt>,
@@ -210,9 +213,44 @@ fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
210} 213}
211 214
212fn install_server(opts: ServerOpt) -> Result<()> { 215fn install_server(opts: ServerOpt) -> Result<()> {
213 if opts.jemalloc { 216 let mut old_rust = false;
217 if let Ok(output) = run_with_output("cargo --version", ".") {
218 if let Ok(stdout) = String::from_utf8(output.stdout) {
219 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
220 old_rust = true;
221 }
222 }
223 }
224
225 if old_rust {
226 eprintln!(
227 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
228 REQUIRED_RUST_VERSION
229 )
230 }
231
232 let res = if opts.jemalloc {
214 run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".") 233 run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".")
215 } else { 234 } else {
216 run("cargo install --path crates/ra_lsp_server --locked --force", ".") 235 run("cargo install --path crates/ra_lsp_server --locked --force", ".")
236 };
237
238 if res.is_err() && old_rust {
239 eprintln!(
240 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
241 REQUIRED_RUST_VERSION
242 )
243 }
244
245 res
246}
247
248fn check_version(version_output: &str, min_minor_version: u32) -> bool {
249 // Parse second the number out of
250 // cargo 1.39.0-beta (1c6ec66d5 2019-09-30)
251 let minor: Option<u32> = version_output.split('.').nth(1).and_then(|it| it.parse().ok());
252 match minor {
253 None => true,
254 Some(minor) => minor >= min_minor_version,
217 } 255 }
218} 256}