Repository: rust-analyzer/rust-analyzer
Pull request #415 (2019-01-03 13:45:24)

use LSP file system operations
implements #131

I've replaced `source_file_edits` and `file_system_edits` with `workspace_edit` because [`WorkspacEdit`](https://docs.rs/languageserver-types/0.53.1/languageserver_types/struct.WorkspaceEdit.html) can represent both.

I only use `document_changes` because `changes` cannot represent file system operations.

But if the client doesn't have the `workspace.workspaceEdit.resourceOperations` capability `WorkspaceEdit` cannot replace the current `FileSystemEdit`. Can we assume that the client will support it?

I also adapted the extension code to make use of the new response type, but only for vscode, i don't know if changes have to be made for the emacs part.


Patch:
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index 1107ffc8b9..7230fb101b 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -1,11 +1,16 @@
 use languageserver_types::{
-    self, Location, Position, Range, SymbolKind, TextDocumentEdit, TextDocumentIdentifier,
-    TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, InsertTextFormat,
+    self, CreateFile, DocumentChangeOperation, DocumentChanges, InsertTextFormat, Location,
+    Position, Range, RenameFile, ResourceOp, SymbolKind, TextDocumentEdit, TextDocumentIdentifier,
+    TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier,
+    WorkspaceEdit,
 };
-use ra_analysis::{FileId, FileSystemEdit, SourceChange, SourceFileEdit, FilePosition,FileRange,  CompletionItem, CompletionItemKind, InsertText, NavigationTarget};
-use ra_editor::{LineCol, LineIndex, translate_offset_with_edit};
-use ra_text_edit::{AtomTextEdit, TextEdit};
+use ra_analysis::{
+    CompletionItem, CompletionItemKind, FileId, FilePosition, FileRange, FileSystemEdit,
+    InsertText, NavigationTarget, SourceChange, SourceFileEdit,
+};
+use ra_editor::{translate_offset_with_edit, LineCol, LineIndex};
 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
+use ra_text_edit::{AtomTextEdit, TextEdit};
 
 use crate::{req, server_world::ServerWorld, Result};
 
@@ -49,7 +54,7 @@ impl Conv for CompletionItemKind {
     type Output = ::languageserver_types::CompletionItemKind;
 
     fn conv(self) -> <Self as Conv>::Output {
-        use ::languageserver_types::CompletionItemKind::*;
+        use languageserver_types::CompletionItemKind::*;
         match self {
             CompletionItemKind::Keyword => Keyword,
             CompletionItemKind::Snippet => Snippet,
@@ -266,12 +271,20 @@ impl TryConvWith for SourceChange {
                 })
             }
         };
-        let source_file_edits = self.source_file_edits.try_conv_with(world)?;
-        let file_system_edits = self.file_system_edits.try_conv_with(world)?;
+        let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
+        for resource_op in self.file_system_edits.try_conv_with(world)? {
+            document_changes.push(DocumentChangeOperation::Op(resource_op));
+        }
+        for text_document_edit in self.source_file_edits.try_conv_with(world)? {
+            document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
+        }
+        let workspace_edit = WorkspaceEdit {
+            changes: None,
+            document_changes: Some(DocumentChanges::Operations(document_changes)),
+        };
         Ok(req::SourceChange {
             label: self.label,
-            source_file_edits,
-            file_system_edits,
+            workspace_edit,
             cursor_position,
         })
     }
@@ -301,21 +314,25 @@ impl TryConvWith for SourceFileEdit {
 
 impl TryConvWith for FileSystemEdit {
     type Ctx = ServerWorld;
-    type Output = req::FileSystemEdit;
-    fn try_conv_with(self, world: &ServerWorld) -> Result<req::FileSystemEdit> {
+    type Output = ResourceOp;
+    fn try_conv_with(self, world: &ServerWorld) -> Result<ResourceOp> {
         let res = match self {
             FileSystemEdit::CreateFile { source_root, path } => {
-                let uri = world.path_to_uri(source_root, &path)?;
-                req::FileSystemEdit::CreateFile { uri }
+                let uri = world.path_to_uri(source_root, &path)?.to_string();
+                ResourceOp::Create(CreateFile { uri, options: None })
             }
             FileSystemEdit::MoveFile {
                 src,
                 dst_source_root,
                 dst_path,
             } => {
-                let src = world.file_id_to_uri(src)?;
-                let dst = world.path_to_uri(dst_source_root, &dst_path)?;
-                req::FileSystemEdit::MoveFile { src, dst }
+                let old_uri = world.file_id_to_uri(src)?.to_string();
+                let new_uri = world.path_to_uri(dst_source_root, &dst_path)?.to_string();
+                ResourceOp::Rename(RenameFile {
+                    old_uri,
+                    new_uri,
+                    options: None,
+                })
             }
         };
         Ok(res)
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 747ab8a8c9..b41e903280 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -1,6 +1,6 @@
-use serde::{Serialize, Deserialize};
 use languageserver_types::{Location, Position, Range, TextDocumentIdentifier, Url};
 use rustc_hash::FxHashMap;
+use serde::{Deserialize, Serialize};
 use url_serde;
 
 pub use languageserver_types::{
@@ -8,7 +8,7 @@ pub use languageserver_types::{
     CompletionResponse, DocumentOnTypeFormattingParams, DocumentSymbolParams,
     DocumentSymbolResponse, ExecuteCommandParams, Hover, InitializeResult,
     PublishDiagnosticsParams, ReferenceParams, SignatureHelp, TextDocumentEdit,
-    TextDocumentPositionParams, TextEdit, WorkspaceSymbolParams,
+    TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams,
 };
 
 pub enum SyntaxTree {}
@@ -151,26 +151,10 @@ pub struct Runnable {
 #[serde(rename_all = "camelCase")]
 pub struct SourceChange {
     pub label: String,
-    pub source_file_edits: Vec<TextDocumentEdit>,
-    pub file_system_edits: Vec<FileSystemEdit>,
+    pub workspace_edit: WorkspaceEdit,
     pub cursor_position: Option<TextDocumentPositionParams>,
 }
 
-#[derive(Serialize, Debug)]
-#[serde(tag = "type", rename_all = "camelCase")]
-pub enum FileSystemEdit {
-    CreateFile {
-        #[serde(with = "url_serde")]
-        uri: Url,
-    },
-    MoveFile {
-        #[serde(with = "url_serde")]
-        src: Url,
-        #[serde(with = "url_serde")]
-        dst: Url,
-    },
-}
-
 pub enum InternalFeedback {}
 
 impl Notification for InternalFeedback {
diff --git a/editors/code/src/commands/apply_source_change.ts b/editors/code/src/commands/apply_source_change.ts
index cf921e3ac0..10dbf72c07 100644
--- a/editors/code/src/commands/apply_source_change.ts
+++ b/editors/code/src/commands/apply_source_change.ts
@@ -3,46 +3,23 @@ import * as lc from 'vscode-languageclient';
 
 import { Server } from '../server';
 
-interface FileSystemEdit {
-    type: string;
-    uri?: string;
-    src?: string;
-    dst?: string;
-}
-
 export interface SourceChange {
     label: string;
-    sourceFileEdits: lc.TextDocumentEdit[];
-    fileSystemEdits: FileSystemEdit[];
+    workspaceEdit: lc.WorkspaceEdit;
     cursorPosition?: lc.TextDocumentPositionParams;
 }
 
 export async function handle(change: SourceChange) {
-    const wsEdit = new vscode.WorkspaceEdit();
-    for (const sourceEdit of change.sourceFileEdits) {
-        const uri = Server.client.protocol2CodeConverter.asUri(
-            sourceEdit.textDocument.uri
-        );
-        const edits = Server.client.protocol2CodeConverter.asTextEdits(
-            sourceEdit.edits
-        );
-        wsEdit.set(uri, edits);
-    }
+    const wsEdit = Server.client.protocol2CodeConverter.asWorkspaceEdit(change.workspaceEdit);
     let created;
     let moved;
-    for (const fsEdit of change.fileSystemEdits) {
-        switch (fsEdit.type) {
-            case 'createFile':
-                const uri = vscode.Uri.parse(fsEdit.uri!);
-                wsEdit.createFile(uri);
-                created = uri;
-                break;
-            case 'moveFile':
-                const src = vscode.Uri.parse(fsEdit.src!);
-                const dst = vscode.Uri.parse(fsEdit.dst!);
-                wsEdit.renameFile(src, dst);
-                moved = dst;
-                break;
+    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;
@@ -65,6 +42,6 @@ export async function handle(change: SourceChange) {
         if (!editor.selection.isEmpty) {
             return;
         }
-        editor!.selection = new vscode.Selection(position, position);
+        editor.selection = new vscode.Selection(position, position);
     }
 }


Test patch:
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index b0e1e65b62..4cae44eab2 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -1,8 +1,12 @@
 mod support;
 
+use languageserver_types::{
+    CodeActionContext, DocumentFormattingParams, FormattingOptions, Position, Range,
+};
+use ra_lsp_server::req::{
+    CodeActionParams, CodeActionRequest, Formatting, Runnables, RunnablesParams,
+};
 use serde_json::json;
-use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams, Formatting};
-use languageserver_types::{Position, Range, CodeActionContext, DocumentFormattingParams, FormattingOptions};
 
 use crate::support::project;
 
@@ -203,14 +207,15 @@ fn main() {}
               "arguments": [
                 {
                   "cursorPosition": null,
-                  "fileSystemEdits": [
-                    {
-                        "type": "createFile",
+                  "workspaceEdit": {
+                    "documentChanges": [
+                      {
+                        "kind": "create",
                         "uri": "file:///[..]/src/bar.rs"
-                    }
-                  ],
-                  "label": "create module",
-                  "sourceFileEdits": []
+                      }
+                    ]
+                  },
+                  "label": "create module"
                 }
               ],
               "command": "ra-lsp.applySourceChange",