aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs140
-rw-r--r--editors/code/package.json12
-rw-r--r--editors/code/src/client.ts6
-rw-r--r--editors/code/src/config.ts18
-rw-r--r--editors/code/src/main.ts14
5 files changed, 150 insertions, 40 deletions
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 0ba382165..0a00054b2 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -1,6 +1,9 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::{ast::AstNode, TextRange, TextUnit}; 3use ra_syntax::{
4 ast::{self, AstNode},
5 TextRange, TextUnit,
6};
4use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
5 8
6use crate::{ 9use crate::{
@@ -21,13 +24,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
21 None => return, 24 None => return,
22 }; 25 };
23 26
24 let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { 27 let receiver_text =
25 let text = dot_receiver.syntax().text(); 28 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
26 let without_dot = ..text.len() - TextUnit::of_char('.');
27 text.slice(without_dot).to_string()
28 } else {
29 dot_receiver.syntax().text().to_string()
30 };
31 29
32 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { 30 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
33 Some(it) => it, 31 Some(it) => it,
@@ -35,10 +33,17 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
35 }; 33 };
36 34
37 if receiver_ty.is_bool() || receiver_ty.is_unknown() { 35 if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38 postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39 .add_to(acc);
40 postfix_snippet( 36 postfix_snippet(
41 ctx, 37 ctx,
38 &dot_receiver,
39 "if",
40 "if expr {}",
41 &format!("if {} {{$0}}", receiver_text),
42 )
43 .add_to(acc);
44 postfix_snippet(
45 ctx,
46 &dot_receiver,
42 "while", 47 "while",
43 "while expr {}", 48 "while expr {}",
44 &format!("while {} {{\n$0\n}}", receiver_text), 49 &format!("while {} {{\n$0\n}}", receiver_text),
@@ -46,28 +51,70 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
46 .add_to(acc); 51 .add_to(acc);
47 } 52 }
48 53
49 postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); 54 // !&&&42 is a compiler error, ergo process it before considering the references
55 postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
50 56
51 postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); 57 postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52 postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); 58 postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
59 .add_to(acc);
60
61 // The rest of the postfix completions create an expression that moves an argument,
62 // so it's better to consider references now to avoid breaking the compilation
63 let dot_receiver = include_references(dot_receiver);
64 let receiver_text =
65 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
53 66
54 postfix_snippet( 67 postfix_snippet(
55 ctx, 68 ctx,
69 &dot_receiver,
56 "match", 70 "match",
57 "match expr {}", 71 "match expr {}",
58 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), 72 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
59 ) 73 )
60 .add_to(acc); 74 .add_to(acc);
61 75
62 postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); 76 postfix_snippet(
77 ctx,
78 &dot_receiver,
79 "box",
80 "Box::new(expr)",
81 &format!("Box::new({})", receiver_text),
82 )
83 .add_to(acc);
63 84
64 postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) 85 postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text))
65 .add_to(acc); 86 .add_to(acc);
66} 87}
67 88
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 89fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
90 if receiver_is_ambiguous_float_literal {
91 let text = receiver.syntax().text();
92 let without_dot = ..text.len() - TextUnit::of_char('.');
93 text.slice(without_dot).to_string()
94 } else {
95 receiver.to_string()
96 }
97}
98
99fn include_references(initial_element: &ast::Expr) -> ast::Expr {
100 let mut resulting_element = initial_element.clone();
101 while let Some(parent_ref_element) =
102 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
103 {
104 resulting_element = ast::Expr::from(parent_ref_element);
105 }
106 resulting_element
107}
108
109fn postfix_snippet(
110 ctx: &CompletionContext,
111 receiver: &ast::Expr,
112 label: &str,
113 detail: &str,
114 snippet: &str,
115) -> Builder {
69 let edit = { 116 let edit = {
70 let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax(); 117 let receiver_syntax = receiver.syntax();
71 let receiver_range = ctx.sema.original_range(receiver_syntax).range; 118 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 119 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string()) 120 TextEdit::replace(delete_range, snippet.to_string())
@@ -340,4 +387,63 @@ mod tests {
340 "### 387 "###
341 ); 388 );
342 } 389 }
390
391 #[test]
392 fn postfix_completion_for_references() {
393 assert_debug_snapshot!(
394 do_postfix_completion(
395 r#"
396 fn main() {
397 &&&&42.<|>
398 }
399 "#,
400 ),
401 @r###"
402 [
403 CompletionItem {
404 label: "box",
405 source_range: [56; 56),
406 delete: [49; 56),
407 insert: "Box::new(&&&&42)",
408 detail: "Box::new(expr)",
409 },
410 CompletionItem {
411 label: "dbg",
412 source_range: [56; 56),
413 delete: [49; 56),
414 insert: "dbg!(&&&&42)",
415 detail: "dbg!(expr)",
416 },
417 CompletionItem {
418 label: "match",
419 source_range: [56; 56),
420 delete: [49; 56),
421 insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}",
422 detail: "match expr {}",
423 },
424 CompletionItem {
425 label: "not",
426 source_range: [56; 56),
427 delete: [53; 56),
428 insert: "!42",
429 detail: "!expr",
430 },
431 CompletionItem {
432 label: "ref",
433 source_range: [56; 56),
434 delete: [53; 56),
435 insert: "&42",
436 detail: "&expr",
437 },
438 CompletionItem {
439 label: "refm",
440 source_range: [56; 56),
441 delete: [53; 56),
442 insert: "&mut 42",
443 detail: "&mut expr",
444 },
445 ]
446 "###
447 );
448 }
343} 449}
diff --git a/editors/code/package.json b/editors/code/package.json
index eb5748515..1d113ebb6 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -73,10 +73,18 @@
73 "type": "string" 73 "type": "string"
74 }, 74 },
75 "args": { 75 "args": {
76 "type": "array" 76 "type": "array",
77 "items": {
78 "type": "string"
79 }
77 }, 80 },
78 "env": { 81 "env": {
79 "type": "object" 82 "type": "object",
83 "patternProperties": {
84 ".+": {
85 "type": "string"
86 }
87 }
80 } 88 }
81 } 89 }
82 } 90 }
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 08d821dd0..82ca749f3 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -99,8 +99,10 @@ export async function createClient(config: Config, serverPath: string): Promise<
99 // Note that while the CallHierarchyFeature is stable the LSP protocol is not. 99 // Note that while the CallHierarchyFeature is stable the LSP protocol is not.
100 res.registerFeature(new CallHierarchyFeature(res)); 100 res.registerFeature(new CallHierarchyFeature(res));
101 101
102 if (config.highlightingSemanticTokens) { 102 if (config.package.enableProposedApi) {
103 res.registerFeature(new SemanticTokensFeature(res)); 103 if (config.highlightingSemanticTokens) {
104 res.registerFeature(new SemanticTokensFeature(res));
105 }
104 } 106 }
105 107
106 return res; 108 return res;
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index be5296fcf..602538ea1 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -38,17 +38,11 @@ export class Config {
38 ] 38 ]
39 .map(opt => `${this.rootSection}.${opt}`); 39 .map(opt => `${this.rootSection}.${opt}`);
40 40
41 readonly packageJsonVersion: string = vscode 41 readonly package: {
42 .extensions 42 version: string;
43 .getExtension(this.extensionId)! 43 releaseTag: string | undefined;
44 .packageJSON 44 enableProposedApi: boolean | undefined;
45 .version; 45 } = vscode.extensions.getExtension(this.extensionId)!.packageJSON;
46
47 readonly releaseTag: string | undefined = vscode
48 .extensions
49 .getExtension(this.extensionId)!
50 .packageJSON
51 .releaseTag ?? undefined;
52 46
53 private cfg!: vscode.WorkspaceConfiguration; 47 private cfg!: vscode.WorkspaceConfiguration;
54 48
@@ -62,7 +56,7 @@ export class Config {
62 const enableLogging = this.cfg.get("trace.extension") as boolean; 56 const enableLogging = this.cfg.get("trace.extension") as boolean;
63 log.setEnabled(enableLogging); 57 log.setEnabled(enableLogging);
64 log.debug( 58 log.debug(
65 "Extension version:", this.packageJsonVersion, 59 "Extension version:", this.package.version,
66 "using configuration:", this.cfg 60 "using configuration:", this.cfg
67 ); 61 );
68 } 62 }
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 5d2da9a76..7b7c19dfc 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -110,9 +110,9 @@ async function bootstrap(config: Config, state: PersistentState): Promise<string
110} 110}
111 111
112async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> { 112async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
113 if (config.releaseTag === undefined) return; 113 if (config.package.releaseTag === undefined) return;
114 if (config.channel === "stable") { 114 if (config.channel === "stable") {
115 if (config.releaseTag === NIGHTLY_TAG) { 115 if (config.package.releaseTag === NIGHTLY_TAG) {
116 vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension. 116 vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
117To switch to stable, uninstall the extension and re-install it from the marketplace`); 117To switch to stable, uninstall the extension and re-install it from the marketplace`);
118 } 118 }
@@ -185,7 +185,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
185 } 185 }
186 return explicitPath; 186 return explicitPath;
187 }; 187 };
188 if (config.releaseTag === undefined) return "rust-analyzer"; 188 if (config.package.releaseTag === undefined) return "rust-analyzer";
189 189
190 let binaryName: string | undefined = undefined; 190 let binaryName: string | undefined = undefined;
191 if (process.arch === "x64" || process.arch === "x32") { 191 if (process.arch === "x64" || process.arch === "x32") {
@@ -211,21 +211,21 @@ async function getServer(config: Config, state: PersistentState): Promise<string
211 await state.updateServerVersion(undefined); 211 await state.updateServerVersion(undefined);
212 } 212 }
213 213
214 if (state.serverVersion === config.packageJsonVersion) return dest; 214 if (state.serverVersion === config.package.version) return dest;
215 215
216 if (config.askBeforeDownload) { 216 if (config.askBeforeDownload) {
217 const userResponse = await vscode.window.showInformationMessage( 217 const userResponse = await vscode.window.showInformationMessage(
218 `Language server version ${config.packageJsonVersion} for rust-analyzer is not installed.`, 218 `Language server version ${config.package.version} for rust-analyzer is not installed.`,
219 "Download now" 219 "Download now"
220 ); 220 );
221 if (userResponse !== "Download now") return dest; 221 if (userResponse !== "Download now") return dest;
222 } 222 }
223 223
224 const release = await fetchRelease(config.releaseTag); 224 const release = await fetchRelease(config.package.releaseTag);
225 const artifact = release.assets.find(artifact => artifact.name === binaryName); 225 const artifact = release.assets.find(artifact => artifact.name === binaryName);
226 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); 226 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
227 227
228 await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); 228 await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
229 await state.updateServerVersion(config.packageJsonVersion); 229 await state.updateServerVersion(config.package.version);
230 return dest; 230 return dest;
231} 231}