]> git.lizzy.rs Git - rust.git/commitdiff
Merge #4602 #4603
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>
Mon, 25 May 2020 14:08:21 +0000 (14:08 +0000)
committerGitHub <noreply@github.com>
Mon, 25 May 2020 14:08:21 +0000 (14:08 +0000)
4602: Add boolean literal semantic token type to package.json r=matklad a=lnicola

Closes #4583.

CC @GrayJack

4603: Add self keyword semantic token type r=matklad a=lnicola

Not sure if this is warranted a new token type or just a modifier.

---

CC #4583, @GrayJack

Co-authored-by: Laurențiu Nicola <lnicola@dend.ro>
35 files changed:
crates/ra_ide/src/lib.rs
crates/ra_ide/src/snapshots/highlighting.html
crates/ra_ide/src/syntax_highlighting.rs
crates/ra_ide/src/syntax_highlighting/tags.rs
crates/ra_ide/src/typing/on_enter.rs
crates/rust-analyzer/src/caps.rs
crates/rust-analyzer/src/lsp_ext.rs
crates/rust-analyzer/src/main_loop/handlers.rs
crates/rust-analyzer/src/semantic_tokens.rs
crates/rust-analyzer/src/to_proto.rs
crates/rust-analyzer/tests/heavy_tests/main.rs
docs/dev/lsp-extensions.md
editors/code/package.json
editors/code/src/ast_inspector.ts [new file with mode: 0644]
editors/code/src/commands.ts [new file with mode: 0644]
editors/code/src/commands/analyzer_status.ts [deleted file]
editors/code/src/commands/expand_macro.ts [deleted file]
editors/code/src/commands/index.ts [deleted file]
editors/code/src/commands/join_lines.ts [deleted file]
editors/code/src/commands/matching_brace.ts [deleted file]
editors/code/src/commands/on_enter.ts [deleted file]
editors/code/src/commands/parent_module.ts [deleted file]
editors/code/src/commands/runnables.ts [deleted file]
editors/code/src/commands/server_version.ts [deleted file]
editors/code/src/commands/ssr.ts [deleted file]
editors/code/src/commands/syntax_tree.ts [deleted file]
editors/code/src/commands/toggle_inlay_hints.ts [deleted file]
editors/code/src/debug.ts
editors/code/src/inlay_hints.ts
editors/code/src/lsp_ext.ts [new file with mode: 0644]
editors/code/src/main.ts
editors/code/src/run.ts [new file with mode: 0644]
editors/code/src/rust-analyzer-api.ts [deleted file]
editors/code/src/snippets.ts [new file with mode: 0644]
editors/code/src/source_change.ts [deleted file]

index 5ac002d82f0c3db93cd6684dfacf1f1615b987f9..d983cd91002370d624177e9634c2da1cc2f1a968 100644 (file)
@@ -309,7 +309,8 @@ pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> {
 
     /// Returns an edit which should be applied when opening a new line, fixing
     /// up minor stuff like continuing the comment.
-    pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
+    /// The edit will be a snippet (with `$0`).
+    pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> {
         self.with_db(|db| typing::on_enter(&db, position))
     }
 
index 198139220efc2fe6eb8e39a78f2170eb7218cbbd..352e350955fc9b11d089fd26d5b039760293107c 100644 (file)
@@ -35,12 +35,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 }
 
 <span class="keyword">trait</span> <span class="trait declaration">Bar</span> {
-    <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -&gt; <span class="builtin_type">i32</span>;
+    <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -&gt; <span class="builtin_type">i32</span>;
 }
 
 <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> {
-    <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -&gt; <span class="builtin_type">i32</span> {
-        <span class="keyword">self</span>.<span class="field">x</span>
+    <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -&gt; <span class="builtin_type">i32</span> {
+        <span class="self_keyword">self</span>.<span class="field">x</span>
     }
 }
 
@@ -92,7 +92,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="keyword">use</span> <span class="enum">Option</span>::*;
 
 <span class="keyword">impl</span>&lt;<span class="type_param declaration">T</span>&gt; <span class="enum">Option</span>&lt;<span class="type_param">T</span>&gt; {
-    <span class="keyword">fn</span> <span class="function declaration">and</span>&lt;<span class="type_param declaration">U</span>&gt;(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span>&lt;<span class="type_param">U</span>&gt;) -&gt; <span class="enum">Option</span>&lt;(<span class="type_param">T</span>, <span class="type_param">U</span>)&gt; {
+    <span class="keyword">fn</span> <span class="function declaration">and</span>&lt;<span class="type_param declaration">U</span>&gt;(<span class="self_keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span>&lt;<span class="type_param">U</span>&gt;) -&gt; <span class="enum">Option</span>&lt;(<span class="type_param">T</span>, <span class="type_param">U</span>)&gt; {
         <span class="keyword control">match</span> <span class="variable">other</span> {
             <span class="enum_variant">None</span> =&gt; <span class="macro">unimplemented!</span>(),
             <span class="variable declaration">Nope</span> =&gt; <span class="variable">Nope</span>,
index 61aeb28cbc73c05bb791dee87287ac104cfe3e41..8a995d779baebefea9c108ff626cae4372ab34da 100644 (file)
@@ -414,6 +414,7 @@ fn highlight_element(
                 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
                 T![unsafe] => h | HighlightModifier::Unsafe,
                 T![true] | T![false] => HighlightTag::BoolLiteral.into(),
+                T![self] => HighlightTag::SelfKeyword.into(),
                 _ => h,
             }
         }
index 09652a5b1ccabb031fdbbe05882119c681ba43bb..46c718c91e23ebcbf47aeee2b0ea66b07ebeb3cf 100644 (file)
@@ -30,6 +30,7 @@ pub enum HighlightTag {
     Macro,
     Module,
     NumericLiteral,
+    SelfKeyword,
     SelfType,
     Static,
     StringLiteral,
@@ -76,6 +77,7 @@ fn as_str(self) -> &'static str {
             HighlightTag::Macro => "macro",
             HighlightTag::Module => "module",
             HighlightTag::NumericLiteral => "numeric_literal",
+            HighlightTag::SelfKeyword => "self_keyword",
             HighlightTag::SelfType => "self_type",
             HighlightTag::Static => "static",
             HighlightTag::StringLiteral => "string_literal",
index e7d64b4f68c2fb68e6d261e0467186007874fad6..a40d8af9c43de32e0d98fcc3154f6090dbe360a2 100644 (file)
@@ -11,9 +11,7 @@
 };
 use ra_text_edit::TextEdit;
 
-use crate::{SourceChange, SourceFileEdit};
-
-pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> {
+pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
     let parse = db.parse(position.file_id);
     let file = parse.tree();
     let comment = file
@@ -41,9 +39,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
     let inserted = format!("\n{}{} $0", indent, prefix);
     let edit = TextEdit::insert(position.offset, inserted);
 
-    let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id });
-    res.is_snippet = true;
-    Some(res)
+    Some(edit)
 }
 
 fn followed_by_comment(comment: &ast::Comment) -> bool {
@@ -90,9 +86,8 @@ fn apply_on_enter(before: &str) -> Option<String> {
         let (analysis, file_id) = single_file(&before);
         let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
 
-        assert_eq!(result.source_file_edits.len(), 1);
         let mut actual = before.to_string();
-        result.source_file_edits[0].edit.apply(&mut actual);
+        result.apply(&mut actual);
         Some(actual)
     }
 
index 780fc93174faf679106fe099309cf266e7a581f8..345693524ea5f4a00e6e0788b42dd8253f19257b 100644 (file)
@@ -85,6 +85,8 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
         experimental: Some(json!({
             "joinLines": true,
             "ssr": true,
+            "onEnter": true,
+            "parentModule": true,
         })),
     }
 }
index 52e4fcbecae51879e1000111c5f64dcb694032c8..acb1dacb6b6622eda78d09caceb37657e9d9446e 100644 (file)
@@ -3,7 +3,7 @@
 use std::{collections::HashMap, path::PathBuf};
 
 use lsp_types::request::Request;
-use lsp_types::{Location, Position, Range, TextDocumentIdentifier};
+use lsp_types::{Position, Range, TextDocumentIdentifier};
 use rustc_hash::FxHashMap;
 use serde::{Deserialize, Serialize};
 
@@ -50,7 +50,7 @@ impl Request for ExpandMacro {
 #[serde(rename_all = "camelCase")]
 pub struct ExpandMacroParams {
     pub text_document: TextDocumentIdentifier,
-    pub position: Option<Position>,
+    pub position: Position,
 }
 
 #[derive(Deserialize, Serialize, Debug)]
@@ -79,8 +79,8 @@ pub enum ParentModule {}
 
 impl Request for ParentModule {
     type Params = lsp_types::TextDocumentPositionParams;
-    type Result = Vec<Location>;
-    const METHOD: &'static str = "rust-analyzer/parentModule";
+    type Result = Option<lsp_types::GotoDefinitionResponse>;
+    const METHOD: &'static str = "experimental/parentModule";
 }
 
 pub enum JoinLines {}
@@ -102,8 +102,8 @@ pub enum OnEnter {}
 
 impl Request for OnEnter {
     type Params = lsp_types::TextDocumentPositionParams;
-    type Result = Option<SnippetWorkspaceEdit>;
-    const METHOD: &'static str = "rust-analyzer/onEnter";
+    type Result = Option<Vec<SnippetTextEdit>>;
+    const METHOD: &'static str = "experimental/onEnter";
 }
 
 pub enum Runnables {}
index d731079681234e68991079bd7b25ce04dfc86436..1f910ff82b9377717c6a3d10c70793a92a0107e3 100644 (file)
@@ -72,15 +72,10 @@ pub fn handle_expand_macro(
     let _p = profile("handle_expand_macro");
     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
     let line_index = world.analysis().file_line_index(file_id)?;
-    let offset = params.position.map(|p| from_proto::offset(&line_index, p));
+    let offset = from_proto::offset(&line_index, params.position);
 
-    match offset {
-        None => Ok(None),
-        Some(offset) => {
-            let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
-            Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
-        }
-    }
+    let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
+    Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
 }
 
 pub fn handle_selection_range(
@@ -174,13 +169,17 @@ pub fn handle_join_lines(
 pub fn handle_on_enter(
     world: WorldSnapshot,
     params: lsp_types::TextDocumentPositionParams,
-) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
+) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
     let _p = profile("handle_on_enter");
     let position = from_proto::file_position(&world, params)?;
-    match world.analysis().on_enter(position)? {
-        None => Ok(None),
-        Some(source_change) => to_proto::snippet_workspace_edit(&world, source_change).map(Some),
-    }
+    let edit = match world.analysis().on_enter(position)? {
+        None => return Ok(None),
+        Some(it) => it,
+    };
+    let line_index = world.analysis().file_line_index(position.file_id)?;
+    let line_endings = world.file_line_endings(position.file_id);
+    let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
+    Ok(Some(edit))
 }
 
 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
@@ -345,11 +344,8 @@ pub fn handle_goto_definition(
         None => return Ok(None),
         Some(it) => it,
     };
-    let res = to_proto::goto_definition_response(
-        &world,
-        FileRange { file_id: position.file_id, range: nav_info.range },
-        nav_info.info,
-    )?;
+    let src = FileRange { file_id: position.file_id, range: nav_info.range };
+    let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
     Ok(Some(res))
 }
 
@@ -363,11 +359,8 @@ pub fn handle_goto_implementation(
         None => return Ok(None),
         Some(it) => it,
     };
-    let res = to_proto::goto_definition_response(
-        &world,
-        FileRange { file_id: position.file_id, range: nav_info.range },
-        nav_info.info,
-    )?;
+    let src = FileRange { file_id: position.file_id, range: nav_info.range };
+    let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
     Ok(Some(res))
 }
 
@@ -381,26 +374,20 @@ pub fn handle_goto_type_definition(
         None => return Ok(None),
         Some(it) => it,
     };
-    let res = to_proto::goto_definition_response(
-        &world,
-        FileRange { file_id: position.file_id, range: nav_info.range },
-        nav_info.info,
-    )?;
+    let src = FileRange { file_id: position.file_id, range: nav_info.range };
+    let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
     Ok(Some(res))
 }
 
 pub fn handle_parent_module(
     world: WorldSnapshot,
     params: lsp_types::TextDocumentPositionParams,
-) -> Result<Vec<Location>> {
+) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
     let _p = profile("handle_parent_module");
     let position = from_proto::file_position(&world, params)?;
-    world
-        .analysis()
-        .parent_module(position)?
-        .into_iter()
-        .map(|it| to_proto::location(&world, it.file_range()))
-        .collect::<Result<Vec<_>>>()
+    let navs = world.analysis().parent_module(position)?;
+    let res = to_proto::goto_definition_response(&world, None, navs)?;
+    Ok(Some(res))
 }
 
 pub fn handle_runnables(
index 9b775871f0e855203ba875ede69b77390a38291c..6f125c37cb99672081a5ffe3c100d45f7adf2694 100644 (file)
@@ -40,6 +40,7 @@ macro_rules! define_semantic_token_types {
     (BUILTIN_TYPE, "builtinType"),
     (ENUM_MEMBER, "enumMember"),
     (LIFETIME, "lifetime"),
+    (SELF_KEYWORD, "selfKeyword"),
     (TYPE_ALIAS, "typeAlias"),
     (UNION, "union"),
     (UNRESOLVED_REFERENCE, "unresolvedReference"),
index 81a347247cb6dedb17e8a21580c787f61cb6a71f..8e8e7033db768d31b1599c598ac6e28762741d6a 100644 (file)
@@ -135,6 +135,18 @@ pub(crate) fn text_edit_vec(
     text_edit.into_iter().map(|indel| self::text_edit(line_index, line_endings, indel)).collect()
 }
 
+pub(crate) fn snippet_text_edit_vec(
+    line_index: &LineIndex,
+    line_endings: LineEndings,
+    is_snippet: bool,
+    text_edit: TextEdit,
+) -> Vec<lsp_ext::SnippetTextEdit> {
+    text_edit
+        .into_iter()
+        .map(|indel| self::snippet_text_edit(line_index, line_endings, is_snippet, indel))
+        .collect()
+}
+
 pub(crate) fn completion_item(
     line_index: &LineIndex,
     line_endings: LineEndings,
@@ -274,6 +286,7 @@ fn semantic_token_type_and_modifiers(
         HighlightTag::TypeAlias => semantic_tokens::TYPE_ALIAS,
         HighlightTag::Trait => lsp_types::SemanticTokenType::INTERFACE,
         HighlightTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
+        HighlightTag::SelfKeyword => semantic_tokens::SELF_KEYWORD,
         HighlightTag::SelfType => lsp_types::SemanticTokenType::TYPE,
         HighlightTag::Field => lsp_types::SemanticTokenType::PROPERTY,
         HighlightTag::Function => lsp_types::SemanticTokenType::FUNCTION,
@@ -391,13 +404,20 @@ pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result<lsp_t
 
 pub(crate) fn location_link(
     world: &WorldSnapshot,
-    src: FileRange,
+    src: Option<FileRange>,
     target: NavigationTarget,
 ) -> Result<lsp_types::LocationLink> {
-    let src_location = location(world, src)?;
+    let origin_selection_range = match src {
+        Some(src) => {
+            let line_index = world.analysis().file_line_index(src.file_id)?;
+            let range = range(&line_index, src.range);
+            Some(range)
+        }
+        None => None,
+    };
     let (target_uri, target_range, target_selection_range) = location_info(world, target)?;
     let res = lsp_types::LocationLink {
-        origin_selection_range: Some(src_location.range),
+        origin_selection_range,
         target_uri,
         target_range,
         target_selection_range,
@@ -420,7 +440,7 @@ fn location_info(
 
 pub(crate) fn goto_definition_response(
     world: &WorldSnapshot,
-    src: FileRange,
+    src: Option<FileRange>,
     targets: Vec<NavigationTarget>,
 ) -> Result<lsp_types::GotoDefinitionResponse> {
     if world.config.client_caps.location_link {
index 738a9a8e37504cb16f4703bc18984e752fa7ab8a..b1bfc968a8dc3272e7560f8b91b6fd2764a2cd7b 100644 (file)
@@ -473,23 +473,14 @@ fn main() {{}}
             text_document: server.doc_id("src/m0.rs"),
             position: Position { line: 0, character: 5 },
         },
-        json!({
-          "documentChanges": [
-            {
-              "edits": [
-                {
-                  "insertTextFormat": 2,
-                  "newText": "\n/// $0",
-                  "range": {
-                    "end": { "character": 5, "line": 0 },
-                    "start": { "character": 5, "line": 0 }
-                  }
-                }
-              ],
-              "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
+        json!([{
+            "insertTextFormat": 2,
+            "newText": "\n/// $0",
+            "range": {
+            "end": { "character": 5, "line": 0 },
+            "start": { "character": 5, "line": 0 }
             }
-          ]
-        }),
+        }]),
     );
     let elapsed = start.elapsed();
     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
@@ -519,23 +510,14 @@ fn preserves_dos_line_endings() {
             text_document: server.doc_id("src/main.rs"),
             position: Position { line: 0, character: 8 },
         },
-        json!({
-          "documentChanges": [
-            {
-              "edits": [
-                {
-                  "insertTextFormat": 2,
-                  "newText": "\r\n/// $0",
-                  "range": {
-                    "end": { "line": 0, "character": 8 },
-                    "start": { "line": 0, "character": 8 }
-                  }
-                }
-              ],
-              "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
+        json!([{
+            "insertTextFormat": 2,
+            "newText": "\r\n/// $0",
+            "range": {
+            "end": { "line": 0, "character": 8 },
+            "start": { "line": 0, "character": 8 }
             }
-          ]
-        }),
+        }]),
     );
 }
 
index 55035cfae183a38adb70cac262bbd8078be5d9dc..209f470eba50be3bf4c032333616330daf0374e8 100644 (file)
@@ -87,6 +87,40 @@ Invoking code action at this position will yield two code actions for importing
 * Is a fixed two-level structure enough?
 * Should we devise a general way to encode custom interaction protocols for GUI refactorings?
 
+## Parent Module
+
+**Issue:** https://github.com/microsoft/language-server-protocol/issues/1002
+
+**Server Capability:** `{ "parentModule": boolean }`
+
+This request is send from client to server to handle "Goto Parent Module" editor action.
+
+**Method:** `experimental/parentModule`
+
+**Request:** `TextDocumentPositionParams`
+
+**Response:** `Location | Location[] | LocationLink[] | null`
+
+
+### Example
+
+```rust
+// src/main.rs
+mod foo;
+// src/foo.rs
+
+/* cursor here*/
+```
+
+`experimental/parentModule` returns a single `Link` to the `mod foo;` declaration.
+
+### Unresolved Question
+
+* An alternative would be to use a more general "gotoSuper" request, which would work for super methods, super classes and super modules.
+  This is the approach IntelliJ Rust is takeing.
+  However, experience shows that super module (which generally has a feeling of navigation between files) should be separate.
+  If you want super module, but the cursor happens to be inside an overriden function, the behavior with single "gotoSuper" request is surprising.
+
 ## Join Lines
 
 **Issue:** https://github.com/microsoft/language-server-protocol/issues/992
@@ -108,11 +142,7 @@ interface JoinLinesParams {
 }
 ```
 
-**Response:**
-
-```typescript
-TextEdit[]
-```
+**Response:** `TextEdit[]`
 
 ### Example
 
@@ -138,6 +168,59 @@ fn main() {
   Currently this is left to editor's discretion, but it might be useful to specify on the server via snippets.
   However, it then becomes unclear how it works with multi cursor.
 
+## On Enter
+
+**Issue:** https://github.com/microsoft/language-server-protocol/issues/1001
+
+**Server Capability:** `{ "onEnter": boolean }`
+
+This request is send from client to server to handle <kbd>Enter</kbd> keypress.
+
+**Method:** `experimental/onEnter`
+
+**Request:**: `TextDocumentPositionParams`
+
+**Response:**
+
+```typescript
+SnippetTextEdit[]
+```
+
+### Example
+
+```rust
+fn main() {
+    // Some /*cursor here*/ docs
+    let x = 92;
+}
+```
+
+`experimental/onEnter` returns the following snippet
+
+```rust
+fn main() {
+    // Some
+    // $0 docs
+    let x = 92;
+}
+```
+
+The primary goal of `onEnter` is to handle automatic indentation when opening a new line.
+This is not yet implemented.
+The secondary goal is to handle fixing up syntax, like continuing doc strings and comments, and escaping `\n` in string literals.
+
+As proper cursor positioning is raison-d'etat for `onEnter`, it uses `SnippetTextEdit`.
+
+### Unresolved Question
+
+* How to deal with synchronicity of the request?
+  One option is to require the client to block until the server returns the response.
+  Another option is to do a OT-style merging of edits from client and server.
+  A third option is to do a record-replay: client applies heuristic on enter immediatelly, then applies all user's keypresses.
+  When the server is ready with the response, the client rollbacks all the changes and applies the recorded actions on top of the correct response.
+* How to deal with multiple carets?
+* Should we extend this to arbitrary typed events and not just `onEnter`?
+
 ## Structural Search Replace (SSR)
 
 **Server Capability:** `{ "ssr": boolean }`
@@ -265,7 +348,7 @@ Primarily for debugging, but very useful for all people working on rust-analyzer
 ```typescript
 interface ExpandMacroParams {
     textDocument: TextDocumentIdentifier,
-    position?: Position,
+    position: Position,
 }
 ```
 
index 0e6755a3222acfc129da548cd1027d28f770d5a8..acf3ca4b59039d9c6e0d624d4a52caf6d7f464af 100644 (file)
                 "id": "lifetime",
                 "description": "Style for lifetimes"
             },
+            {
+                "id": "selfKeyword",
+                "description": "Style for the self keyword",
+                "superType": "keyword"
+            },
             {
                 "id": "typeAlias",
                 "description": "Style for type aliases",
diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts
new file mode 100644 (file)
index 0000000..4fdd167
--- /dev/null
@@ -0,0 +1,185 @@
+import * as vscode from 'vscode';
+
+import { Ctx, Disposable } from './ctx';
+import { RustEditor, isRustEditor } from './util';
+
+// FIXME: consider implementing this via the Tree View API?
+// https://code.visualstudio.com/api/extension-guides/tree-view
+export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable {
+    private readonly astDecorationType = vscode.window.createTextEditorDecorationType({
+        borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'),
+        borderStyle: "solid",
+        borderWidth: "2px",
+    });
+    private rustEditor: undefined | RustEditor;
+
+    // Lazy rust token range -> syntax tree file range.
+    private readonly rust2Ast = new Lazy(() => {
+        const astEditor = this.findAstTextEditor();
+        if (!this.rustEditor || !astEditor) return undefined;
+
+        const buf: [vscode.Range, vscode.Range][] = [];
+        for (let i = 0; i < astEditor.document.lineCount; ++i) {
+            const astLine = astEditor.document.lineAt(i);
+
+            // Heuristically look for nodes with quoted text (which are token nodes)
+            const isTokenNode = astLine.text.lastIndexOf('"') >= 0;
+            if (!isTokenNode) continue;
+
+            const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text);
+            if (!rustRange) continue;
+
+            buf.push([rustRange, this.findAstNodeRange(astLine)]);
+        }
+        return buf;
+    });
+
+    constructor(ctx: Ctx) {
+        ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: 'rust-analyzer' }, this));
+        ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
+        vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
+        vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
+        vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
+
+        ctx.pushCleanup(this);
+    }
+    dispose() {
+        this.setRustEditor(undefined);
+    }
+
+    private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
+        if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) {
+            this.rust2Ast.reset();
+        }
+    }
+
+    private onDidCloseTextDocument(doc: vscode.TextDocument) {
+        if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
+            this.setRustEditor(undefined);
+        }
+    }
+
+    private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
+        if (!this.findAstTextEditor()) {
+            this.setRustEditor(undefined);
+            return;
+        }
+        this.setRustEditor(editors.find(isRustEditor));
+    }
+
+    private findAstTextEditor(): undefined | vscode.TextEditor {
+        return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === 'rust-analyzer');
+    }
+
+    private setRustEditor(newRustEditor: undefined | RustEditor) {
+        if (this.rustEditor && this.rustEditor !== newRustEditor) {
+            this.rustEditor.setDecorations(this.astDecorationType, []);
+            this.rust2Ast.reset();
+        }
+        this.rustEditor = newRustEditor;
+    }
+
+    // additional positional params are omitted
+    provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> {
+        if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return;
+
+        const astEditor = this.findAstTextEditor();
+        if (!astEditor) return;
+
+        const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos));
+        if (!rust2AstRanges) return;
+
+        const [rustFileRange, astFileRange] = rust2AstRanges;
+
+        astEditor.revealRange(astFileRange);
+        astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end);
+
+        return [{
+            targetRange: astFileRange,
+            targetUri: astEditor.document.uri,
+            originSelectionRange: rustFileRange,
+            targetSelectionRange: astFileRange,
+        }];
+    }
+
+    // additional positional params are omitted
+    provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
+        if (!this.rustEditor) return;
+
+        const astFileLine = doc.lineAt(hoverPosition.line);
+
+        const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text);
+        if (!rustFileRange) return;
+
+        this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]);
+        this.rustEditor.revealRange(rustFileRange);
+
+        const rustSourceCode = this.rustEditor.document.getText(rustFileRange);
+        const astFileRange = this.findAstNodeRange(astFileLine);
+
+        return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange);
+    }
+
+    private findAstNodeRange(astLine: vscode.TextLine): vscode.Range {
+        const lineOffset = astLine.range.start;
+        const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
+        const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
+        return new vscode.Range(begin, end);
+    }
+
+    private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
+        const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine);
+        if (!parsedRange) return;
+
+        const [begin, end] = parsedRange
+            .slice(1)
+            .map(off => this.positionAt(doc, +off));
+
+        return new vscode.Range(begin, end);
+    }
+
+    // Memoize the last value, otherwise the CPU is at 100% single core
+    // with quadratic lookups when we build rust2Ast cache
+    cache?: { doc: vscode.TextDocument; offset: number; line: number };
+
+    positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position {
+        if (doc.eol === vscode.EndOfLine.LF) {
+            return doc.positionAt(targetOffset);
+        }
+
+        // Dirty workaround for crlf line endings
+        // We are still in this prehistoric era of carriage returns here...
+
+        let line = 0;
+        let offset = 0;
+
+        const cache = this.cache;
+        if (cache?.doc === doc && cache.offset <= targetOffset) {
+            ({ line, offset } = cache);
+        }
+
+        while (true) {
+            const lineLenWithLf = doc.lineAt(line).text.length + 1;
+            if (offset + lineLenWithLf > targetOffset) {
+                this.cache = { doc, offset, line };
+                return doc.positionAt(targetOffset + line);
+            }
+            offset += lineLenWithLf;
+            line += 1;
+        }
+    }
+}
+
+class Lazy<T> {
+    val: undefined | T;
+
+    constructor(private readonly compute: () => undefined | T) { }
+
+    get() {
+        return this.val ?? (this.val = this.compute());
+    }
+
+    reset() {
+        this.val = undefined;
+    }
+}
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
new file mode 100644 (file)
index 0000000..86302db
--- /dev/null
@@ -0,0 +1,370 @@
+import * as vscode from 'vscode';
+import * as lc from 'vscode-languageclient';
+import * as ra from './lsp_ext';
+
+import { Ctx, Cmd } from './ctx';
+import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
+import { spawnSync } from 'child_process';
+import { RunnableQuickPick, selectRunnable, createTask } from './run';
+import { AstInspector } from './ast_inspector';
+import { isRustDocument, sleep, isRustEditor } from './util';
+
+export * from './ast_inspector';
+export * from './run';
+
+export function analyzerStatus(ctx: Ctx): Cmd {
+    const tdcp = new class implements vscode.TextDocumentContentProvider {
+        readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
+        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
+
+        provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
+            if (!vscode.window.activeTextEditor) return '';
+
+            return ctx.client.sendRequest(ra.analyzerStatus, null);
+        }
+
+        get onDidChange(): vscode.Event<vscode.Uri> {
+            return this.eventEmitter.event;
+        }
+    }();
+
+    let poller: NodeJS.Timer | undefined = undefined;
+
+    ctx.pushCleanup(
+        vscode.workspace.registerTextDocumentContentProvider(
+            'rust-analyzer-status',
+            tdcp,
+        ),
+    );
+
+    ctx.pushCleanup({
+        dispose() {
+            if (poller !== undefined) {
+                clearInterval(poller);
+            }
+        },
+    });
+
+    return async () => {
+        if (poller === undefined) {
+            poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000);
+        }
+        const document = await vscode.workspace.openTextDocument(tdcp.uri);
+        return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
+    };
+}
+
+export function matchingBrace(ctx: Ctx): Cmd {
+    return async () => {
+        const editor = ctx.activeRustEditor;
+        const client = ctx.client;
+        if (!editor || !client) return;
+
+        const response = await client.sendRequest(ra.matchingBrace, {
+            textDocument: { uri: editor.document.uri.toString() },
+            positions: editor.selections.map(s =>
+                client.code2ProtocolConverter.asPosition(s.active),
+            ),
+        });
+        editor.selections = editor.selections.map((sel, idx) => {
+            const active = client.protocol2CodeConverter.asPosition(
+                response[idx],
+            );
+            const anchor = sel.isEmpty ? active : sel.anchor;
+            return new vscode.Selection(anchor, active);
+        });
+        editor.revealRange(editor.selection);
+    };
+}
+
+export function joinLines(ctx: Ctx): Cmd {
+    return async () => {
+        const editor = ctx.activeRustEditor;
+        const client = ctx.client;
+        if (!editor || !client) return;
+
+        const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
+            ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
+            textDocument: { uri: editor.document.uri.toString() },
+        });
+        editor.edit((builder) => {
+            client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
+                builder.replace(edit.range, edit.newText);
+            });
+        });
+    };
+}
+
+export function onEnter(ctx: Ctx): Cmd {
+    async function handleKeypress() {
+        const editor = ctx.activeRustEditor;
+        const client = ctx.client;
+
+        if (!editor || !client) return false;
+
+        const lcEdits = await client.sendRequest(ra.onEnter, {
+            textDocument: { uri: editor.document.uri.toString() },
+            position: client.code2ProtocolConverter.asPosition(
+                editor.selection.active,
+            ),
+        }).catch(_error => {
+            // client.logFailedRequest(OnEnterRequest.type, error);
+            return null;
+        });
+        if (!lcEdits) return false;
+
+        const edits = client.protocol2CodeConverter.asTextEdits(lcEdits);
+        await applySnippetTextEdits(editor, edits);
+        return true;
+    }
+
+    return async () => {
+        if (await handleKeypress()) return;
+
+        await vscode.commands.executeCommand('default:type', { text: '\n' });
+    };
+}
+
+export function parentModule(ctx: Ctx): Cmd {
+    return async () => {
+        const editor = ctx.activeRustEditor;
+        const client = ctx.client;
+        if (!editor || !client) return;
+
+        const response = await client.sendRequest(ra.parentModule, {
+            textDocument: { uri: editor.document.uri.toString() },
+            position: client.code2ProtocolConverter.asPosition(
+                editor.selection.active,
+            ),
+        });
+        const loc = response[0];
+        if (!loc) return;
+
+        const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
+        const range = client.protocol2CodeConverter.asRange(loc.targetRange);
+
+        const doc = await vscode.workspace.openTextDocument(uri);
+        const e = await vscode.window.showTextDocument(doc);
+        e.selection = new vscode.Selection(range.start, range.start);
+        e.revealRange(range, vscode.TextEditorRevealType.InCenter);
+    };
+}
+
+export function ssr(ctx: Ctx): Cmd {
+    return async () => {
+        const client = ctx.client;
+        if (!client) return;
+
+        const options: vscode.InputBoxOptions = {
+            value: "() ==>> ()",
+            prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
+            validateInput: async (x: string) => {
+                try {
+                    await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
+                } catch (e) {
+                    return e.toString();
+                }
+                return null;
+            }
+        };
+        const request = await vscode.window.showInputBox(options);
+        if (!request) return;
+
+        const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
+
+        await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
+    };
+}
+
+export function serverVersion(ctx: Ctx): Cmd {
+    return async () => {
+        const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
+        const commitHash = stdout.slice(`rust-analyzer `.length).trim();
+        const { releaseTag } = ctx.config.package;
+
+        void vscode.window.showInformationMessage(
+            `rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})`
+        );
+    };
+}
+
+export function toggleInlayHints(ctx: Ctx): Cmd {
+    return async () => {
+        await vscode
+            .workspace
+            .getConfiguration(`${ctx.config.rootSection}.inlayHints`)
+            .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Workspace);
+    };
+}
+
+export function run(ctx: Ctx): Cmd {
+    let prevRunnable: RunnableQuickPick | undefined;
+
+    return async () => {
+        const item = await selectRunnable(ctx, prevRunnable);
+        if (!item) return;
+
+        item.detail = 'rerun';
+        prevRunnable = item;
+        const task = createTask(item.runnable);
+        return await vscode.tasks.executeTask(task);
+    };
+}
+
+// Opens the virtual file that will show the syntax tree
+//
+// The contents of the file come from the `TextDocumentContentProvider`
+export function syntaxTree(ctx: Ctx): Cmd {
+    const tdcp = new class implements vscode.TextDocumentContentProvider {
+        readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
+        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
+        constructor() {
+            vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
+            vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
+        }
+
+        private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
+            if (isRustDocument(event.document)) {
+                // We need to order this after language server updates, but there's no API for that.
+                // Hence, good old sleep().
+                void sleep(10).then(() => this.eventEmitter.fire(this.uri));
+            }
+        }
+        private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
+            if (editor && isRustEditor(editor)) {
+                this.eventEmitter.fire(this.uri);
+            }
+        }
+
+        provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
+            const rustEditor = ctx.activeRustEditor;
+            if (!rustEditor) return '';
+
+            // When the range based query is enabled we take the range of the selection
+            const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
+                ? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
+                : null;
+
+            const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
+            return ctx.client.sendRequest(ra.syntaxTree, params, ct);
+        }
+
+        get onDidChange(): vscode.Event<vscode.Uri> {
+            return this.eventEmitter.event;
+        }
+    };
+
+    void new AstInspector(ctx);
+
+    ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
+    ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
+        brackets: [["[", ")"]],
+    }));
+
+    return async () => {
+        const editor = vscode.window.activeTextEditor;
+        const rangeEnabled = !!editor && !editor.selection.isEmpty;
+
+        const uri = rangeEnabled
+            ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
+            : tdcp.uri;
+
+        const document = await vscode.workspace.openTextDocument(uri);
+
+        tdcp.eventEmitter.fire(uri);
+
+        void await vscode.window.showTextDocument(document, {
+            viewColumn: vscode.ViewColumn.Two,
+            preserveFocus: true
+        });
+    };
+}
+
+
+// Opens the virtual file that will show the syntax tree
+//
+// The contents of the file come from the `TextDocumentContentProvider`
+export function expandMacro(ctx: Ctx): Cmd {
+    function codeFormat(expanded: ra.ExpandedMacro): string {
+        let result = `// Recursive expansion of ${expanded.name}! macro\n`;
+        result += '// ' + '='.repeat(result.length - 3);
+        result += '\n\n';
+        result += expanded.expansion;
+
+        return result;
+    }
+
+    const tdcp = new class implements vscode.TextDocumentContentProvider {
+        uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
+        eventEmitter = new vscode.EventEmitter<vscode.Uri>();
+        async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
+            const editor = vscode.window.activeTextEditor;
+            const client = ctx.client;
+            if (!editor || !client) return '';
+
+            const position = editor.selection.active;
+
+            const expanded = await client.sendRequest(ra.expandMacro, {
+                textDocument: { uri: editor.document.uri.toString() },
+                position,
+            });
+
+            if (expanded == null) return 'Not available';
+
+            return codeFormat(expanded);
+        }
+
+        get onDidChange(): vscode.Event<vscode.Uri> {
+            return this.eventEmitter.event;
+        }
+    }();
+
+    ctx.pushCleanup(
+        vscode.workspace.registerTextDocumentContentProvider(
+            'rust-analyzer',
+            tdcp,
+        ),
+    );
+
+    return async () => {
+        const document = await vscode.workspace.openTextDocument(tdcp.uri);
+        tdcp.eventEmitter.fire(tdcp.uri);
+        return vscode.window.showTextDocument(
+            document,
+            vscode.ViewColumn.Two,
+            true,
+        );
+    };
+}
+
+export function collectGarbage(ctx: Ctx): Cmd {
+    return async () => ctx.client.sendRequest(ra.collectGarbage, null);
+}
+
+export function showReferences(ctx: Ctx): Cmd {
+    return (uri: string, position: lc.Position, locations: lc.Location[]) => {
+        const client = ctx.client;
+        if (client) {
+            vscode.commands.executeCommand(
+                'editor.action.showReferences',
+                vscode.Uri.parse(uri),
+                client.protocol2CodeConverter.asPosition(position),
+                locations.map(client.protocol2CodeConverter.asLocation),
+            );
+        }
+    };
+}
+
+export function applyActionGroup(_ctx: Ctx): Cmd {
+    return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => {
+        const selectedAction = await vscode.window.showQuickPick(actions);
+        if (!selectedAction) return;
+        await applySnippetWorkspaceEdit(selectedAction.edit);
+    };
+}
+
+export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
+    return async (edit: vscode.WorkspaceEdit) => {
+        await applySnippetWorkspaceEdit(edit);
+    };
+}
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts
deleted file mode 100644 (file)
index 09daa34..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-import * as vscode from 'vscode';
-
-import * as ra from '../rust-analyzer-api';
-import { Ctx, Cmd } from '../ctx';
-
-// Shows status of rust-analyzer (for debugging)
-export function analyzerStatus(ctx: Ctx): Cmd {
-    let poller: NodeJS.Timer | undefined = undefined;
-    const tdcp = new TextDocumentContentProvider(ctx);
-
-    ctx.pushCleanup(
-        vscode.workspace.registerTextDocumentContentProvider(
-            'rust-analyzer-status',
-            tdcp,
-        ),
-    );
-
-    ctx.pushCleanup({
-        dispose() {
-            if (poller !== undefined) {
-                clearInterval(poller);
-            }
-        },
-    });
-
-    return async () => {
-        if (poller === undefined) {
-            poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000);
-        }
-        const document = await vscode.workspace.openTextDocument(tdcp.uri);
-        return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
-    };
-}
-
-class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
-    readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
-    readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
-
-    constructor(private readonly ctx: Ctx) {
-    }
-
-    provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
-        if (!vscode.window.activeTextEditor) return '';
-
-        return this.ctx.client.sendRequest(ra.analyzerStatus, null);
-    }
-
-    get onDidChange(): vscode.Event<vscode.Uri> {
-        return this.eventEmitter.event;
-    }
-}
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts
deleted file mode 100644 (file)
index 23f2ef1..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as vscode from 'vscode';
-import * as ra from '../rust-analyzer-api';
-
-import { Ctx, Cmd } from '../ctx';
-
-// Opens the virtual file that will show the syntax tree
-//
-// The contents of the file come from the `TextDocumentContentProvider`
-export function expandMacro(ctx: Ctx): Cmd {
-    const tdcp = new TextDocumentContentProvider(ctx);
-    ctx.pushCleanup(
-        vscode.workspace.registerTextDocumentContentProvider(
-            'rust-analyzer',
-            tdcp,
-        ),
-    );
-
-    return async () => {
-        const document = await vscode.workspace.openTextDocument(tdcp.uri);
-        tdcp.eventEmitter.fire(tdcp.uri);
-        return vscode.window.showTextDocument(
-            document,
-            vscode.ViewColumn.Two,
-            true,
-        );
-    };
-}
-
-function codeFormat(expanded: ra.ExpandedMacro): string {
-    let result = `// Recursive expansion of ${expanded.name}! macro\n`;
-    result += '// ' + '='.repeat(result.length - 3);
-    result += '\n\n';
-    result += expanded.expansion;
-
-    return result;
-}
-
-class TextDocumentContentProvider
-    implements vscode.TextDocumentContentProvider {
-    uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
-    eventEmitter = new vscode.EventEmitter<vscode.Uri>();
-
-    constructor(private readonly ctx: Ctx) {
-    }
-
-    async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
-        const editor = vscode.window.activeTextEditor;
-        const client = this.ctx.client;
-        if (!editor || !client) return '';
-
-        const position = editor.selection.active;
-
-        const expanded = await client.sendRequest(ra.expandMacro, {
-            textDocument: { uri: editor.document.uri.toString() },
-            position,
-        });
-
-        if (expanded == null) return 'Not available';
-
-        return codeFormat(expanded);
-    }
-
-    get onDidChange(): vscode.Event<vscode.Uri> {
-        return this.eventEmitter.event;
-    }
-}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
deleted file mode 100644 (file)
index c2a232d..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-import * as vscode from 'vscode';
-import * as lc from 'vscode-languageclient';
-import * as ra from '../rust-analyzer-api';
-
-import { Ctx, Cmd } from '../ctx';
-import * as sourceChange from '../source_change';
-import { assert } from '../util';
-
-export * from './analyzer_status';
-export * from './matching_brace';
-export * from './join_lines';
-export * from './on_enter';
-export * from './parent_module';
-export * from './syntax_tree';
-export * from './expand_macro';
-export * from './runnables';
-export * from './ssr';
-export * from './server_version';
-export * from './toggle_inlay_hints';
-
-export function collectGarbage(ctx: Ctx): Cmd {
-    return async () => ctx.client.sendRequest(ra.collectGarbage, null);
-}
-
-export function showReferences(ctx: Ctx): Cmd {
-    return (uri: string, position: lc.Position, locations: lc.Location[]) => {
-        const client = ctx.client;
-        if (client) {
-            vscode.commands.executeCommand(
-                'editor.action.showReferences',
-                vscode.Uri.parse(uri),
-                client.protocol2CodeConverter.asPosition(position),
-                locations.map(client.protocol2CodeConverter.asLocation),
-            );
-        }
-    };
-}
-
-export function applySourceChange(ctx: Ctx): Cmd {
-    return async (change: ra.SourceChange) => {
-        await sourceChange.applySourceChange(ctx, change);
-    };
-}
-
-export function applyActionGroup(_ctx: Ctx): Cmd {
-    return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => {
-        const selectedAction = await vscode.window.showQuickPick(actions);
-        if (!selectedAction) return;
-        await applySnippetWorkspaceEdit(selectedAction.edit);
-    };
-}
-
-export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
-    return async (edit: vscode.WorkspaceEdit) => {
-        await applySnippetWorkspaceEdit(edit);
-    };
-}
-
-export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
-    assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
-    const [uri, edits] = edit.entries()[0];
-
-    const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
-    if (!editor) return;
-
-    let selection: vscode.Selection | undefined = undefined;
-    let lineDelta = 0;
-    await editor.edit((builder) => {
-        for (const indel of edits) {
-            const parsed = parseSnippet(indel.newText);
-            if (parsed) {
-                const [newText, [placeholderStart, placeholderLength]] = parsed;
-                const prefix = newText.substr(0, placeholderStart);
-                const lastNewline = prefix.lastIndexOf('\n');
-
-                const startLine = indel.range.start.line + lineDelta + countLines(prefix);
-                const startColumn = lastNewline === -1 ?
-                    indel.range.start.character + placeholderStart
-                    : prefix.length - lastNewline - 1;
-                const endColumn = startColumn + placeholderLength;
-                selection = new vscode.Selection(
-                    new vscode.Position(startLine, startColumn),
-                    new vscode.Position(startLine, endColumn),
-                );
-                builder.replace(indel.range, newText);
-            } else {
-                lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
-                builder.replace(indel.range, indel.newText);
-            }
-        }
-    });
-    if (selection) editor.selection = selection;
-}
-
-function parseSnippet(snip: string): [string, [number, number]] | undefined {
-    const m = snip.match(/\$(0|\{0:([^}]*)\})/);
-    if (!m) return undefined;
-    const placeholder = m[2] ?? "";
-    const range: [number, number] = [m.index!!, placeholder.length];
-    const insert = snip.replace(m[0], placeholder);
-    return [insert, range];
-}
-
-function countLines(text: string): number {
-    return (text.match(/\n/g) || []).length;
-}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
deleted file mode 100644 (file)
index 0bf1ee6..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as ra from '../rust-analyzer-api';
-import * as lc from 'vscode-languageclient';
-
-import { Ctx, Cmd } from '../ctx';
-
-export function joinLines(ctx: Ctx): Cmd {
-    return async () => {
-        const editor = ctx.activeRustEditor;
-        const client = ctx.client;
-        if (!editor || !client) return;
-
-        const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
-            ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
-            textDocument: { uri: editor.document.uri.toString() },
-        });
-        editor.edit((builder) => {
-            client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
-                builder.replace(edit.range, edit.newText);
-            });
-        });
-    };
-}
diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts
deleted file mode 100644 (file)
index 9c418b8..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as vscode from 'vscode';
-import * as ra from '../rust-analyzer-api';
-
-import { Ctx, Cmd } from '../ctx';
-
-export function matchingBrace(ctx: Ctx): Cmd {
-    return async () => {
-        const editor = ctx.activeRustEditor;
-        const client = ctx.client;
-        if (!editor || !client) return;
-
-        const response = await client.sendRequest(ra.matchingBrace, {
-            textDocument: { uri: editor.document.uri.toString() },
-            positions: editor.selections.map(s =>
-                client.code2ProtocolConverter.asPosition(s.active),
-            ),
-        });
-        editor.selections = editor.selections.map((sel, idx) => {
-            const active = client.protocol2CodeConverter.asPosition(
-                response[idx],
-            );
-            const anchor = sel.isEmpty ? active : sel.anchor;
-            return new vscode.Selection(anchor, active);
-        });
-        editor.revealRange(editor.selection);
-    };
-}
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
deleted file mode 100644 (file)
index a7871c3..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as vscode from 'vscode';
-import * as ra from '../rust-analyzer-api';
-
-import { Cmd, Ctx } from '../ctx';
-import { applySnippetWorkspaceEdit } from '.';
-
-async function handleKeypress(ctx: Ctx) {
-    const editor = ctx.activeRustEditor;
-    const client = ctx.client;
-
-    if (!editor || !client) return false;
-
-    const change = await client.sendRequest(ra.onEnter, {
-        textDocument: { uri: editor.document.uri.toString() },
-        position: client.code2ProtocolConverter.asPosition(
-            editor.selection.active,
-        ),
-    }).catch(_error => {
-        // client.logFailedRequest(OnEnterRequest.type, error);
-        return null;
-    });
-    if (!change) return false;
-
-    const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change);
-    await applySnippetWorkspaceEdit(workspaceEdit);
-    return true;
-}
-
-export function onEnter(ctx: Ctx): Cmd {
-    return async () => {
-        if (await handleKeypress(ctx)) return;
-
-        await vscode.commands.executeCommand('default:type', { text: '\n' });
-    };
-}
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts
deleted file mode 100644 (file)
index 8f78ddd..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-import * as vscode from 'vscode';
-import * as ra from '../rust-analyzer-api';
-
-import { Ctx, Cmd } from '../ctx';
-
-export function parentModule(ctx: Ctx): Cmd {
-    return async () => {
-        const editor = ctx.activeRustEditor;
-        const client = ctx.client;
-        if (!editor || !client) return;
-
-        const response = await client.sendRequest(ra.parentModule, {
-            textDocument: { uri: editor.document.uri.toString() },
-            position: client.code2ProtocolConverter.asPosition(
-                editor.selection.active,
-            ),
-        });
-        const loc = response[0];
-        if (loc == null) return;
-
-        const uri = client.protocol2CodeConverter.asUri(loc.uri);
-        const range = client.protocol2CodeConverter.asRange(loc.range);
-
-        const doc = await vscode.workspace.openTextDocument(uri);
-        const e = await vscode.window.showTextDocument(doc);
-        e.selection = new vscode.Selection(range.start, range.start);
-        e.revealRange(range, vscode.TextEditorRevealType.InCenter);
-    };
-}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
deleted file mode 100644 (file)
index 0bd30fb..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-import * as vscode from 'vscode';
-import * as lc from 'vscode-languageclient';
-import * as ra from '../rust-analyzer-api';
-
-import { Ctx, Cmd } from '../ctx';
-import { startDebugSession, getDebugConfiguration } from '../debug';
-
-const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
-
-async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> {
-    const editor = ctx.activeRustEditor;
-    const client = ctx.client;
-    if (!editor || !client) return;
-
-    const textDocument: lc.TextDocumentIdentifier = {
-        uri: editor.document.uri.toString(),
-    };
-
-    const runnables = await client.sendRequest(ra.runnables, {
-        textDocument,
-        position: client.code2ProtocolConverter.asPosition(
-            editor.selection.active,
-        ),
-    });
-    const items: RunnableQuickPick[] = [];
-    if (prevRunnable) {
-        items.push(prevRunnable);
-    }
-    for (const r of runnables) {
-        if (
-            prevRunnable &&
-            JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
-        ) {
-            continue;
-        }
-
-        if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) {
-            continue;
-        }
-        items.push(new RunnableQuickPick(r));
-    }
-
-    if (items.length === 0) {
-        // it is the debug case, run always has at least 'cargo check ...'
-        // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables
-        vscode.window.showErrorMessage("There's no debug target!");
-        return;
-    }
-
-    return await new Promise((resolve) => {
-        const disposables: vscode.Disposable[] = [];
-        const close = (result?: RunnableQuickPick) => {
-            resolve(result);
-            disposables.forEach(d => d.dispose());
-        };
-
-        const quickPick = vscode.window.createQuickPick<RunnableQuickPick>();
-        quickPick.items = items;
-        quickPick.title = "Select Runnable";
-        if (showButtons) {
-            quickPick.buttons = quickPickButtons;
-        }
-        disposables.push(
-            quickPick.onDidHide(() => close()),
-            quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
-            quickPick.onDidTriggerButton((_button) => {
-                (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))();
-                close();
-            }),
-            quickPick.onDidChangeActive((active) => {
-                if (showButtons && active.length > 0) {
-                    if (active[0].label.startsWith('cargo')) {
-                        // save button makes no sense for `cargo test` or `cargo check`
-                        quickPick.buttons = [];
-                    } else if (quickPick.buttons.length === 0) {
-                        quickPick.buttons = quickPickButtons;
-                    }
-                }
-            }),
-            quickPick
-        );
-        quickPick.show();
-    });
-}
-
-export function run(ctx: Ctx): Cmd {
-    let prevRunnable: RunnableQuickPick | undefined;
-
-    return async () => {
-        const item = await selectRunnable(ctx, prevRunnable);
-        if (!item) return;
-
-        item.detail = 'rerun';
-        prevRunnable = item;
-        const task = createTask(item.runnable);
-        return await vscode.tasks.executeTask(task);
-    };
-}
-
-export function runSingle(ctx: Ctx): Cmd {
-    return async (runnable: ra.Runnable) => {
-        const editor = ctx.activeRustEditor;
-        if (!editor) return;
-
-        const task = createTask(runnable);
-        task.group = vscode.TaskGroup.Build;
-        task.presentationOptions = {
-            reveal: vscode.TaskRevealKind.Always,
-            panel: vscode.TaskPanelKind.Dedicated,
-            clear: true,
-        };
-
-        return vscode.tasks.executeTask(task);
-    };
-}
-
-export function debug(ctx: Ctx): Cmd {
-    let prevDebuggee: RunnableQuickPick | undefined;
-
-    return async () => {
-        const item = await selectRunnable(ctx, prevDebuggee, true);
-        if (!item) return;
-
-        item.detail = 'restart';
-        prevDebuggee = item;
-        return await startDebugSession(ctx, item.runnable);
-    };
-}
-
-export function debugSingle(ctx: Ctx): Cmd {
-    return async (config: ra.Runnable) => {
-        await startDebugSession(ctx, config);
-    };
-}
-
-async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> {
-    const scope = ctx.activeRustEditor?.document.uri;
-    if (!scope) return;
-
-    const debugConfig = await getDebugConfiguration(ctx, item.runnable);
-    if (!debugConfig) return;
-
-    const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
-    const configurations = wsLaunchSection.get<any[]>("configurations") || [];
-
-    const index = configurations.findIndex(c => c.name === debugConfig.name);
-    if (index !== -1) {
-        const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
-        if (answer === "Cancel") return;
-
-        configurations[index] = debugConfig;
-    } else {
-        configurations.push(debugConfig);
-    }
-
-    await wsLaunchSection.update("configurations", configurations);
-}
-
-export function newDebugConfig(ctx: Ctx): Cmd {
-    return async () => {
-        const item = await selectRunnable(ctx, undefined, true, false);
-        if (!item) return;
-
-        await makeDebugConfig(ctx, item);
-    };
-}
-
-class RunnableQuickPick implements vscode.QuickPickItem {
-    public label: string;
-    public description?: string | undefined;
-    public detail?: string | undefined;
-    public picked?: boolean | undefined;
-
-    constructor(public runnable: ra.Runnable) {
-        this.label = runnable.label;
-    }
-}
-
-interface CargoTaskDefinition extends vscode.TaskDefinition {
-    type: 'cargo';
-    label: string;
-    command: string;
-    args: string[];
-    env?: { [key: string]: string };
-}
-
-function createTask(spec: ra.Runnable): vscode.Task {
-    const TASK_SOURCE = 'Rust';
-    const definition: CargoTaskDefinition = {
-        type: 'cargo',
-        label: spec.label,
-        command: spec.bin,
-        args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args,
-        env: spec.env,
-    };
-
-    const execOption: vscode.ShellExecutionOptions = {
-        cwd: spec.cwd || '.',
-        env: definition.env,
-    };
-    const exec = new vscode.ShellExecution(
-        definition.command,
-        definition.args,
-        execOption,
-    );
-
-    const f = vscode.workspace.workspaceFolders![0];
-    const t = new vscode.Task(
-        definition,
-        f,
-        definition.label,
-        TASK_SOURCE,
-        exec,
-        ['$rustc'],
-    );
-    t.presentationOptions.clear = true;
-    return t;
-}
diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts
deleted file mode 100644 (file)
index d64ac72..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as vscode from "vscode";
-import { spawnSync } from "child_process";
-import { Ctx, Cmd } from '../ctx';
-
-export function serverVersion(ctx: Ctx): Cmd {
-    return async () => {
-        const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
-        const commitHash = stdout.slice(`rust-analyzer `.length).trim();
-        const { releaseTag } = ctx.config.package;
-
-        void vscode.window.showInformationMessage(
-            `rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})`
-        );
-    };
-}
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
deleted file mode 100644 (file)
index 5d40a64..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as vscode from 'vscode';
-import * as ra from "../rust-analyzer-api";
-
-import { Ctx, Cmd } from '../ctx';
-
-export function ssr(ctx: Ctx): Cmd {
-    return async () => {
-        const client = ctx.client;
-        if (!client) return;
-
-        const options: vscode.InputBoxOptions = {
-            value: "() ==>> ()",
-            prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
-            validateInput: async (x: string) => {
-                try {
-                    await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
-                } catch (e) {
-                    return e.toString();
-                }
-                return null;
-            }
-        };
-        const request = await vscode.window.showInputBox(options);
-        if (!request) return;
-
-        const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
-
-        await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
-    };
-}
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
deleted file mode 100644 (file)
index a5446c3..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-import * as vscode from 'vscode';
-import * as ra from '../rust-analyzer-api';
-
-import { Ctx, Cmd, Disposable } from '../ctx';
-import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
-
-const AST_FILE_SCHEME = "rust-analyzer";
-
-// Opens the virtual file that will show the syntax tree
-//
-// The contents of the file come from the `TextDocumentContentProvider`
-export function syntaxTree(ctx: Ctx): Cmd {
-    const tdcp = new TextDocumentContentProvider(ctx);
-
-    void new AstInspector(ctx);
-
-    ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
-    ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
-        brackets: [["[", ")"]],
-    }));
-
-    return async () => {
-        const editor = vscode.window.activeTextEditor;
-        const rangeEnabled = !!editor && !editor.selection.isEmpty;
-
-        const uri = rangeEnabled
-            ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
-            : tdcp.uri;
-
-        const document = await vscode.workspace.openTextDocument(uri);
-
-        tdcp.eventEmitter.fire(uri);
-
-        void await vscode.window.showTextDocument(document, {
-            viewColumn: vscode.ViewColumn.Two,
-            preserveFocus: true
-        });
-    };
-}
-
-class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
-    readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
-    readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
-
-
-    constructor(private readonly ctx: Ctx) {
-        vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
-        vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
-    }
-
-    private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
-        if (isRustDocument(event.document)) {
-            // We need to order this after language server updates, but there's no API for that.
-            // Hence, good old sleep().
-            void sleep(10).then(() => this.eventEmitter.fire(this.uri));
-        }
-    }
-    private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
-        if (editor && isRustEditor(editor)) {
-            this.eventEmitter.fire(this.uri);
-        }
-    }
-
-    provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
-        const rustEditor = this.ctx.activeRustEditor;
-        if (!rustEditor) return '';
-
-        // When the range based query is enabled we take the range of the selection
-        const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
-            ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
-            : null;
-
-        const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
-        return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
-    }
-
-    get onDidChange(): vscode.Event<vscode.Uri> {
-        return this.eventEmitter.event;
-    }
-}
-
-
-// FIXME: consider implementing this via the Tree View API?
-// https://code.visualstudio.com/api/extension-guides/tree-view
-class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable {
-    private readonly astDecorationType = vscode.window.createTextEditorDecorationType({
-        borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'),
-        borderStyle: "solid",
-        borderWidth: "2px",
-
-    });
-    private rustEditor: undefined | RustEditor;
-
-    // Lazy rust token range -> syntax tree file range.
-    private readonly rust2Ast = new Lazy(() => {
-        const astEditor = this.findAstTextEditor();
-        if (!this.rustEditor || !astEditor) return undefined;
-
-        const buf: [vscode.Range, vscode.Range][] = [];
-        for (let i = 0; i < astEditor.document.lineCount; ++i) {
-            const astLine = astEditor.document.lineAt(i);
-
-            // Heuristically look for nodes with quoted text (which are token nodes)
-            const isTokenNode = astLine.text.lastIndexOf('"') >= 0;
-            if (!isTokenNode) continue;
-
-            const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text);
-            if (!rustRange) continue;
-
-            buf.push([rustRange, this.findAstNodeRange(astLine)]);
-        }
-        return buf;
-    });
-
-    constructor(ctx: Ctx) {
-        ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this));
-        ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
-        vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
-        vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
-        vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
-
-        ctx.pushCleanup(this);
-    }
-    dispose() {
-        this.setRustEditor(undefined);
-    }
-
-    private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
-        if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) {
-            this.rust2Ast.reset();
-        }
-    }
-
-    private onDidCloseTextDocument(doc: vscode.TextDocument) {
-        if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
-            this.setRustEditor(undefined);
-        }
-    }
-
-    private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
-        if (!this.findAstTextEditor()) {
-            this.setRustEditor(undefined);
-            return;
-        }
-        this.setRustEditor(editors.find(isRustEditor));
-    }
-
-    private findAstTextEditor(): undefined | vscode.TextEditor {
-        return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME);
-    }
-
-    private setRustEditor(newRustEditor: undefined | RustEditor) {
-        if (this.rustEditor && this.rustEditor !== newRustEditor) {
-            this.rustEditor.setDecorations(this.astDecorationType, []);
-            this.rust2Ast.reset();
-        }
-        this.rustEditor = newRustEditor;
-    }
-
-    // additional positional params are omitted
-    provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> {
-        if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return;
-
-        const astEditor = this.findAstTextEditor();
-        if (!astEditor) return;
-
-        const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos));
-        if (!rust2AstRanges) return;
-
-        const [rustFileRange, astFileRange] = rust2AstRanges;
-
-        astEditor.revealRange(astFileRange);
-        astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end);
-
-        return [{
-            targetRange: astFileRange,
-            targetUri: astEditor.document.uri,
-            originSelectionRange: rustFileRange,
-            targetSelectionRange: astFileRange,
-        }];
-    }
-
-    // additional positional params are omitted
-    provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
-        if (!this.rustEditor) return;
-
-        const astFileLine = doc.lineAt(hoverPosition.line);
-
-        const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text);
-        if (!rustFileRange) return;
-
-        this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]);
-        this.rustEditor.revealRange(rustFileRange);
-
-        const rustSourceCode = this.rustEditor.document.getText(rustFileRange);
-        const astFileRange = this.findAstNodeRange(astFileLine);
-
-        return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange);
-    }
-
-    private findAstNodeRange(astLine: vscode.TextLine): vscode.Range {
-        const lineOffset = astLine.range.start;
-        const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
-        const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
-        return new vscode.Range(begin, end);
-    }
-
-    private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
-        const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine);
-        if (!parsedRange) return;
-
-        const [begin, end] = parsedRange
-            .slice(1)
-            .map(off => this.positionAt(doc, +off));
-
-        return new vscode.Range(begin, end);
-    }
-
-    // Memoize the last value, otherwise the CPU is at 100% single core
-    // with quadratic lookups when we build rust2Ast cache
-    cache?: { doc: vscode.TextDocument; offset: number; line: number };
-
-    positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position {
-        if (doc.eol === vscode.EndOfLine.LF) {
-            return doc.positionAt(targetOffset);
-        }
-
-        // Dirty workaround for crlf line endings
-        // We are still in this prehistoric era of carriage returns here...
-
-        let line = 0;
-        let offset = 0;
-
-        const cache = this.cache;
-        if (cache?.doc === doc && cache.offset <= targetOffset) {
-            ({ line, offset } = cache);
-        }
-
-        while (true) {
-            const lineLenWithLf = doc.lineAt(line).text.length + 1;
-            if (offset + lineLenWithLf > targetOffset) {
-                this.cache = { doc, offset, line };
-                return doc.positionAt(targetOffset + line);
-            }
-            offset += lineLenWithLf;
-            line += 1;
-        }
-    }
-}
-
-class Lazy<T> {
-    val: undefined | T;
-
-    constructor(private readonly compute: () => undefined | T) { }
-
-    get() {
-        return this.val ?? (this.val = this.compute());
-    }
-
-    reset() {
-        this.val = undefined;
-    }
-}
diff --git a/editors/code/src/commands/toggle_inlay_hints.ts b/editors/code/src/commands/toggle_inlay_hints.ts
deleted file mode 100644 (file)
index 7606af8..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-import * as vscode from 'vscode';
-import { Ctx, Cmd } from '../ctx';
-
-export function toggleInlayHints(ctx: Ctx): Cmd {
-    return async () => {
-        await vscode
-            .workspace
-            .getConfiguration(`${ctx.config.rootSection}.inlayHints`)
-            .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Workspace);
-    };
-}
index d3fe588e83191026a3895c05a090f508eff11ee8..027504ecd2a81e470640f14ac660288d2c329a55 100644 (file)
@@ -1,7 +1,7 @@
 import * as os from "os";
 import * as vscode from 'vscode';
 import * as path from 'path';
-import * as ra from './rust-analyzer-api';
+import * as ra from './lsp_ext';
 
 import { Cargo } from './cargo';
 import { Ctx } from "./ctx";
index a2b07d003788d77fd797049298c9060bef2910e7..9e6d6045f349cf4e99a9252e7233fb0e3dec4b12 100644 (file)
@@ -1,6 +1,6 @@
 import * as lc from "vscode-languageclient";
 import * as vscode from 'vscode';
-import * as ra from './rust-analyzer-api';
+import * as ra from './lsp_ext';
 
 import { Ctx, Disposable } from './ctx';
 import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor, sleep } from './util';
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
new file mode 100644 (file)
index 0000000..4da12eb
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ * This file mirrors `crates/rust-analyzer/src/req.rs` declarations.
+ */
+
+import * as lc from "vscode-languageclient";
+
+export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus");
+
+export const collectGarbage = new lc.RequestType<null, null, void>("rust-analyzer/collectGarbage");
+
+export interface SyntaxTreeParams {
+    textDocument: lc.TextDocumentIdentifier;
+    range: lc.Range | null;
+}
+export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>("rust-analyzer/syntaxTree");
+
+
+export interface ExpandMacroParams {
+    textDocument: lc.TextDocumentIdentifier;
+    position: lc.Position;
+}
+export interface ExpandedMacro {
+    name: string;
+    expansion: string;
+}
+export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | null, void>("rust-analyzer/expandMacro");
+
+export interface MatchingBraceParams {
+    textDocument: lc.TextDocumentIdentifier;
+    positions: lc.Position[];
+}
+export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>("experimental/matchingBrace");
+
+export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule");
+
+export interface JoinLinesParams {
+    textDocument: lc.TextDocumentIdentifier;
+    ranges: lc.Range[];
+}
+export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>("experimental/joinLines");
+
+export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>("experimental/onEnter");
+
+export interface RunnablesParams {
+    textDocument: lc.TextDocumentIdentifier;
+    position: lc.Position | null;
+}
+export interface Runnable {
+    range: lc.Range;
+    label: string;
+    bin: string;
+    args: string[];
+    extraArgs: string[];
+    env: { [key: string]: string };
+    cwd: string | null;
+}
+export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("rust-analyzer/runnables");
+
+export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint;
+
+export namespace InlayHint {
+    export const enum Kind {
+        TypeHint = "TypeHint",
+        ParamHint = "ParameterHint",
+        ChainingHint = "ChainingHint",
+    }
+    interface Common {
+        range: lc.Range;
+        label: string;
+    }
+    export type TypeHint = Common & { kind: Kind.TypeHint };
+    export type ParamHint = Common & { kind: Kind.ParamHint };
+    export type ChainingHint = Common & { kind: Kind.ChainingHint };
+}
+export interface InlayHintsParams {
+    textDocument: lc.TextDocumentIdentifier;
+}
+export const inlayHints = new lc.RequestType<InlayHintsParams, InlayHint[], void>("rust-analyzer/inlayHints");
+
+export interface SsrParams {
+    query: string;
+    parseOnly: boolean;
+}
+export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
index 0e5a206410e0a4e6dcedbfb77b60b41f84b447c2..31ac81ee88d2ae056b6410bba0a89d9c79605e98 100644 (file)
@@ -92,7 +92,6 @@ export async function activate(context: vscode.ExtensionContext) {
     ctx.registerCommand('runSingle', commands.runSingle);
     ctx.registerCommand('debugSingle', commands.debugSingle);
     ctx.registerCommand('showReferences', commands.showReferences);
-    ctx.registerCommand('applySourceChange', commands.applySourceChange);
     ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand);
     ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
 
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
new file mode 100644 (file)
index 0000000..2a7a429
--- /dev/null
@@ -0,0 +1,204 @@
+import * as vscode from 'vscode';
+import * as lc from 'vscode-languageclient';
+import * as ra from './lsp_ext';
+
+import { Ctx, Cmd } from './ctx';
+import { startDebugSession, getDebugConfiguration } from './debug';
+
+const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
+
+export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> {
+    const editor = ctx.activeRustEditor;
+    const client = ctx.client;
+    if (!editor || !client) return;
+
+    const textDocument: lc.TextDocumentIdentifier = {
+        uri: editor.document.uri.toString(),
+    };
+
+    const runnables = await client.sendRequest(ra.runnables, {
+        textDocument,
+        position: client.code2ProtocolConverter.asPosition(
+            editor.selection.active,
+        ),
+    });
+    const items: RunnableQuickPick[] = [];
+    if (prevRunnable) {
+        items.push(prevRunnable);
+    }
+    for (const r of runnables) {
+        if (
+            prevRunnable &&
+            JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
+        ) {
+            continue;
+        }
+
+        if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) {
+            continue;
+        }
+        items.push(new RunnableQuickPick(r));
+    }
+
+    if (items.length === 0) {
+        // it is the debug case, run always has at least 'cargo check ...'
+        // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables
+        vscode.window.showErrorMessage("There's no debug target!");
+        return;
+    }
+
+    return await new Promise((resolve) => {
+        const disposables: vscode.Disposable[] = [];
+        const close = (result?: RunnableQuickPick) => {
+            resolve(result);
+            disposables.forEach(d => d.dispose());
+        };
+
+        const quickPick = vscode.window.createQuickPick<RunnableQuickPick>();
+        quickPick.items = items;
+        quickPick.title = "Select Runnable";
+        if (showButtons) {
+            quickPick.buttons = quickPickButtons;
+        }
+        disposables.push(
+            quickPick.onDidHide(() => close()),
+            quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
+            quickPick.onDidTriggerButton((_button) => {
+                (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))();
+                close();
+            }),
+            quickPick.onDidChangeActive((active) => {
+                if (showButtons && active.length > 0) {
+                    if (active[0].label.startsWith('cargo')) {
+                        // save button makes no sense for `cargo test` or `cargo check`
+                        quickPick.buttons = [];
+                    } else if (quickPick.buttons.length === 0) {
+                        quickPick.buttons = quickPickButtons;
+                    }
+                }
+            }),
+            quickPick
+        );
+        quickPick.show();
+    });
+}
+
+export function runSingle(ctx: Ctx): Cmd {
+    return async (runnable: ra.Runnable) => {
+        const editor = ctx.activeRustEditor;
+        if (!editor) return;
+
+        const task = createTask(runnable);
+        task.group = vscode.TaskGroup.Build;
+        task.presentationOptions = {
+            reveal: vscode.TaskRevealKind.Always,
+            panel: vscode.TaskPanelKind.Dedicated,
+            clear: true,
+        };
+
+        return vscode.tasks.executeTask(task);
+    };
+}
+
+export function debug(ctx: Ctx): Cmd {
+    let prevDebuggee: RunnableQuickPick | undefined;
+
+    return async () => {
+        const item = await selectRunnable(ctx, prevDebuggee, true);
+        if (!item) return;
+
+        item.detail = 'restart';
+        prevDebuggee = item;
+        return await startDebugSession(ctx, item.runnable);
+    };
+}
+
+export function debugSingle(ctx: Ctx): Cmd {
+    return async (config: ra.Runnable) => {
+        await startDebugSession(ctx, config);
+    };
+}
+
+async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> {
+    const scope = ctx.activeRustEditor?.document.uri;
+    if (!scope) return;
+
+    const debugConfig = await getDebugConfiguration(ctx, item.runnable);
+    if (!debugConfig) return;
+
+    const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
+    const configurations = wsLaunchSection.get<any[]>("configurations") || [];
+
+    const index = configurations.findIndex(c => c.name === debugConfig.name);
+    if (index !== -1) {
+        const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
+        if (answer === "Cancel") return;
+
+        configurations[index] = debugConfig;
+    } else {
+        configurations.push(debugConfig);
+    }
+
+    await wsLaunchSection.update("configurations", configurations);
+}
+
+export function newDebugConfig(ctx: Ctx): Cmd {
+    return async () => {
+        const item = await selectRunnable(ctx, undefined, true, false);
+        if (!item) return;
+
+        await makeDebugConfig(ctx, item);
+    };
+}
+
+export class RunnableQuickPick implements vscode.QuickPickItem {
+    public label: string;
+    public description?: string | undefined;
+    public detail?: string | undefined;
+    public picked?: boolean | undefined;
+
+    constructor(public runnable: ra.Runnable) {
+        this.label = runnable.label;
+    }
+}
+
+interface CargoTaskDefinition extends vscode.TaskDefinition {
+    type: 'cargo';
+    label: string;
+    command: string;
+    args: string[];
+    env?: { [key: string]: string };
+}
+
+export function createTask(spec: ra.Runnable): vscode.Task {
+    const TASK_SOURCE = 'Rust';
+    const definition: CargoTaskDefinition = {
+        type: 'cargo',
+        label: spec.label,
+        command: spec.bin,
+        args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args,
+        env: spec.env,
+    };
+
+    const execOption: vscode.ShellExecutionOptions = {
+        cwd: spec.cwd || '.',
+        env: definition.env,
+    };
+    const exec = new vscode.ShellExecution(
+        definition.command,
+        definition.args,
+        execOption,
+    );
+
+    const f = vscode.workspace.workspaceFolders![0];
+    const t = new vscode.Task(
+        definition,
+        f,
+        definition.label,
+        TASK_SOURCE,
+        exec,
+        ['$rustc'],
+    );
+    t.presentationOptions.clear = true;
+    return t;
+}
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
deleted file mode 100644 (file)
index 900c5cd..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/**
- * This file mirrors `crates/rust-analyzer/src/req.rs` declarations.
- */
-
-import * as lc from "vscode-languageclient";
-
-type Option<T> = null | T;
-type Vec<T> = T[];
-type FxHashMap<K extends PropertyKey, V> = Record<K, V>;
-
-function request<TParams, TResult>(method: string) {
-    return new lc.RequestType<TParams, TResult, unknown>(`rust-analyzer/${method}`);
-}
-function notification<TParam>(method: string) {
-    return new lc.NotificationType<TParam>(method);
-}
-
-
-export const analyzerStatus = request<null, string>("analyzerStatus");
-
-
-export const collectGarbage = request<null, null>("collectGarbage");
-
-
-export interface SyntaxTreeParams {
-    textDocument: lc.TextDocumentIdentifier;
-    range: Option<lc.Range>;
-}
-export const syntaxTree = request<SyntaxTreeParams, string>("syntaxTree");
-
-
-export interface ExpandMacroParams {
-    textDocument: lc.TextDocumentIdentifier;
-    position: Option<lc.Position>;
-}
-export interface ExpandedMacro {
-    name: string;
-    expansion: string;
-}
-export const expandMacro = request<ExpandMacroParams, Option<ExpandedMacro>>("expandMacro");
-
-
-export interface MatchingBraceParams {
-    textDocument: lc.TextDocumentIdentifier;
-    positions: lc.Position[];
-}
-export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], unknown>('experimental/matchingBrace');
-
-export interface PublishDecorationsParams {
-    uri: string;
-    decorations: Vec<Decoration>;
-}
-export interface Decoration {
-    range: lc.Range;
-    tag: string;
-    bindingHash: Option<string>;
-}
-export const decorationsRequest = request<lc.TextDocumentIdentifier, Vec<Decoration>>("decorationsRequest");
-
-
-export const parentModule = request<lc.TextDocumentPositionParams, Vec<lc.Location>>("parentModule");
-
-
-export interface JoinLinesParams {
-    textDocument: lc.TextDocumentIdentifier;
-    ranges: lc.Range[];
-}
-export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], unknown>('experimental/joinLines');
-
-
-export const onEnter = request<lc.TextDocumentPositionParams, Option<lc.WorkspaceEdit>>("onEnter");
-
-export interface RunnablesParams {
-    textDocument: lc.TextDocumentIdentifier;
-    position: Option<lc.Position>;
-}
-export interface Runnable {
-    range: lc.Range;
-    label: string;
-    bin: string;
-    args: Vec<string>;
-    extraArgs: Vec<string>;
-    env: FxHashMap<string, string>;
-    cwd: Option<string>;
-}
-export const runnables = request<RunnablesParams, Vec<Runnable>>("runnables");
-
-export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint;
-
-export namespace InlayHint {
-    export const enum Kind {
-        TypeHint = "TypeHint",
-        ParamHint = "ParameterHint",
-        ChainingHint = "ChainingHint",
-    }
-    interface Common {
-        range: lc.Range;
-        label: string;
-    }
-    export type TypeHint = Common & { kind: Kind.TypeHint };
-    export type ParamHint = Common & { kind: Kind.ParamHint };
-    export type ChainingHint = Common & { kind: Kind.ChainingHint };
-}
-export interface InlayHintsParams {
-    textDocument: lc.TextDocumentIdentifier;
-}
-export const inlayHints = request<InlayHintsParams, Vec<InlayHint>>("inlayHints");
-
-
-export interface SsrParams {
-    query: string;
-    parseOnly: boolean;
-}
-export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, unknown>('experimental/ssr');
-
-
-export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations");
-
-
-export interface SourceChange {
-    label: string;
-    workspaceEdit: lc.WorkspaceEdit;
-    cursorPosition: Option<lc.TextDocumentPositionParams>;
-}
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
new file mode 100644 (file)
index 0000000..bcb3f2c
--- /dev/null
@@ -0,0 +1,55 @@
+import * as vscode from 'vscode';
+
+import { assert } from './util';
+
+export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
+    assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
+    const [uri, edits] = edit.entries()[0];
+
+    const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
+    if (!editor) return;
+    await applySnippetTextEdits(editor, edits);
+}
+
+export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
+    let selection: vscode.Selection | undefined = undefined;
+    let lineDelta = 0;
+    await editor.edit((builder) => {
+        for (const indel of edits) {
+            const parsed = parseSnippet(indel.newText);
+            if (parsed) {
+                const [newText, [placeholderStart, placeholderLength]] = parsed;
+                const prefix = newText.substr(0, placeholderStart);
+                const lastNewline = prefix.lastIndexOf('\n');
+
+                const startLine = indel.range.start.line + lineDelta + countLines(prefix);
+                const startColumn = lastNewline === -1 ?
+                    indel.range.start.character + placeholderStart
+                    : prefix.length - lastNewline - 1;
+                const endColumn = startColumn + placeholderLength;
+                selection = new vscode.Selection(
+                    new vscode.Position(startLine, startColumn),
+                    new vscode.Position(startLine, endColumn),
+                );
+                builder.replace(indel.range, newText);
+            } else {
+                lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
+                builder.replace(indel.range, indel.newText);
+            }
+        }
+    });
+    if (selection) editor.selection = selection;
+}
+
+function parseSnippet(snip: string): [string, [number, number]] | undefined {
+    const m = snip.match(/\$(0|\{0:([^}]*)\})/);
+    if (!m) return undefined;
+    const placeholder = m[2] ?? "";
+    const range: [number, number] = [m.index!!, placeholder.length];
+    const insert = snip.replace(m[0], placeholder);
+    return [insert, range];
+}
+
+function countLines(text: string): number {
+    return (text.match(/\n/g) || []).length;
+}
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts
deleted file mode 100644 (file)
index af8f1df..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as vscode from 'vscode';
-import * as lc from 'vscode-languageclient';
-import * as ra from './rust-analyzer-api';
-
-import { Ctx } from './ctx';
-
-export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) {
-    const client = ctx.client;
-    if (!client) return;
-
-    const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit(
-        change.workspaceEdit,
-    );
-    let created;
-    let moved;
-    if (change.workspaceEdit.documentChanges) {
-        for (const docChange of change.workspaceEdit.documentChanges) {
-            if (lc.CreateFile.is(docChange)) {
-                created = docChange.uri;
-            } else if (lc.RenameFile.is(docChange)) {
-                moved = docChange.newUri;
-            }
-        }
-    }
-    const toOpen = created || moved;
-    const toReveal = change.cursorPosition;
-    await vscode.workspace.applyEdit(wsEdit);
-    if (toOpen) {
-        const toOpenUri = vscode.Uri.parse(toOpen);
-        const doc = await vscode.workspace.openTextDocument(toOpenUri);
-        await vscode.window.showTextDocument(doc);
-    } else if (toReveal) {
-        const uri = client.protocol2CodeConverter.asUri(
-            toReveal.textDocument.uri,
-        );
-        const position = client.protocol2CodeConverter.asPosition(
-            toReveal.position,
-        );
-        const editor = vscode.window.activeTextEditor;
-        if (!editor || !editor.selection.isEmpty) {
-            return;
-        }
-
-        if (editor.document.uri !== uri) {
-            const doc = await vscode.workspace.openTextDocument(uri);
-            await vscode.window.showTextDocument(doc);
-        }
-        editor.selection = new vscode.Selection(position, position);
-        editor.revealRange(
-            new vscode.Range(position, position),
-            vscode.TextEditorRevealType.Default,
-        );
-    }
-}