diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 0ba382165b..0a00054b23 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs @@ -1,6 +1,9 @@ //! FIXME: write short doc here -use ra_syntax::{ast::AstNode, TextRange, TextUnit}; +use ra_syntax::{ + ast::{self, AstNode}, + TextRange, TextUnit, +}; use ra_text_edit::TextEdit; use crate::{ @@ -21,13 +24,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { None => return, }; - let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { - let text = dot_receiver.syntax().text(); - let without_dot = ..text.len() - TextUnit::of_char('.'); - text.slice(without_dot).to_string() - } else { - dot_receiver.syntax().text().to_string() - }; + let receiver_text = + get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { Some(it) => it, @@ -35,10 +33,17 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { }; if receiver_ty.is_bool() || receiver_ty.is_unknown() { - postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text)) - .add_to(acc); postfix_snippet( ctx, + &dot_receiver, + "if", + "if expr {}", + &format!("if {} {{$0}}", receiver_text), + ) + .add_to(acc); + postfix_snippet( + ctx, + &dot_receiver, "while", "while expr {}", &format!("while {} {{\n$0\n}}", receiver_text), @@ -46,28 +51,70 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { .add_to(acc); } - postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); + // !&&&42 is a compiler error, ergo process it before considering the references + postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); - postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); - postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); + postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); + postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text)) + .add_to(acc); + + // The rest of the postfix completions create an expression that moves an argument, + // so it's better to consider references now to avoid breaking the compilation + let dot_receiver = include_references(dot_receiver); + let receiver_text = + get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); postfix_snippet( ctx, + &dot_receiver, "match", "match expr {}", &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), ) .add_to(acc); - postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); + postfix_snippet( + ctx, + &dot_receiver, + "box", + "Box::new(expr)", + &format!("Box::new({})", receiver_text), + ) + .add_to(acc); - postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) + postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)) .add_to(acc); } -fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { +fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { + if receiver_is_ambiguous_float_literal { + let text = receiver.syntax().text(); + let without_dot = ..text.len() - TextUnit::of_char('.'); + text.slice(without_dot).to_string() + } else { + receiver.to_string() + } +} + +fn include_references(initial_element: &ast::Expr) -> ast::Expr { + let mut resulting_element = initial_element.clone(); + while let Some(parent_ref_element) = + resulting_element.syntax().parent().and_then(ast::RefExpr::cast) + { + resulting_element = ast::Expr::from(parent_ref_element); + } + resulting_element +} + +fn postfix_snippet( + ctx: &CompletionContext, + receiver: &ast::Expr, + label: &str, + detail: &str, + snippet: &str, +) -> Builder { let edit = { - let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax(); + let receiver_syntax = receiver.syntax(); let receiver_range = ctx.sema.original_range(receiver_syntax).range; let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); TextEdit::replace(delete_range, snippet.to_string()) @@ -340,4 +387,63 @@ mod tests { "### ); } + + #[test] + fn postfix_completion_for_references() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + fn main() { + &&&&42.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: [56; 56), + delete: [49; 56), + insert: "Box::new(&&&&42)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: [56; 56), + delete: [49; 56), + insert: "dbg!(&&&&42)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "match", + source_range: [56; 56), + delete: [49; 56), + insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: [56; 56), + delete: [53; 56), + insert: "!42", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: [56; 56), + delete: [53; 56), + insert: "&42", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: [56; 56), + delete: [53; 56), + insert: "&mut 42", + detail: "&mut expr", + }, + ] + "### + ); + } } diff --git a/editors/code/package.json b/editors/code/package.json index eb57485151..1d113ebb6c 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -73,10 +73,18 @@ "type": "string" }, "args": { - "type": "array" + "type": "array", + "items": { + "type": "string" + } }, "env": { - "type": "object" + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + } } } } diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 08d821dd0a..82ca749f30 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< // Note that while the CallHierarchyFeature is stable the LSP protocol is not. res.registerFeature(new CallHierarchyFeature(res)); - if (config.highlightingSemanticTokens) { - res.registerFeature(new SemanticTokensFeature(res)); + if (config.package.enableProposedApi) { + if (config.highlightingSemanticTokens) { + res.registerFeature(new SemanticTokensFeature(res)); + } } return res; diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index be5296fcff..602538ea17 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -38,17 +38,11 @@ export class Config { ] .map(opt => `${this.rootSection}.${opt}`); - readonly packageJsonVersion: string = vscode - .extensions - .getExtension(this.extensionId)! - .packageJSON - .version; - - readonly releaseTag: string | undefined = vscode - .extensions - .getExtension(this.extensionId)! - .packageJSON - .releaseTag ?? undefined; + readonly package: { + version: string; + releaseTag: string | undefined; + enableProposedApi: boolean | undefined; + } = vscode.extensions.getExtension(this.extensionId)!.packageJSON; private cfg!: vscode.WorkspaceConfiguration; @@ -62,7 +56,7 @@ export class Config { const enableLogging = this.cfg.get("trace.extension") as boolean; log.setEnabled(enableLogging); log.debug( - "Extension version:", this.packageJsonVersion, + "Extension version:", this.package.version, "using configuration:", this.cfg ); } diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 5d2da9a764..7b7c19dfcf 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 { - if (config.releaseTag === undefined) return; + if (config.package.releaseTag === undefined) return; if (config.channel === "stable") { - if (config.releaseTag === NIGHTLY_TAG) { + if (config.package.releaseTag === NIGHTLY_TAG) { vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension. To switch to stable, uninstall the extension and re-install it from the marketplace`); } @@ -185,7 +185,7 @@ async function getServer(config: Config, state: PersistentState): Promise artifact.name === binaryName); assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); - await state.updateServerVersion(config.packageJsonVersion); + await state.updateServerVersion(config.package.version); return dest; }