From f355a6d8319408a3cc349be399d9e69985737c42 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Thu, 31 Dec 2020 13:05:19 +0100 Subject: [PATCH] Split textDocument/formatting TextEdit with diff --- Cargo.lock | 7 +++ crates/rust-analyzer/Cargo.toml | 1 + crates/rust-analyzer/src/diff.rs | 53 +++++++++++++++++++ crates/rust-analyzer/src/handlers.rs | 12 +++-- crates/rust-analyzer/src/lib.rs | 1 + .../rust-analyzer/tests/rust-analyzer/main.rs | 31 +++++------ 6 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 crates/rust-analyzer/src/diff.rs diff --git a/Cargo.lock b/Cargo.lock index fbb79e01fc..1213e57622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,6 +347,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "dissimilar" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" + [[package]] name = "drop_bomb" version = "0.1.5" @@ -1333,6 +1339,7 @@ dependencies = [ "anyhow", "cfg", "crossbeam-channel 0.5.0", + "dissimilar", "env_logger", "expect-test", "flycheck", diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 0b4d3f4ebb..470c1d4589 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -17,6 +17,7 @@ path = "src/bin/main.rs" [dependencies] anyhow = "1.0.26" crossbeam-channel = "0.5.0" +dissimilar = "1.0.2" env_logger = { version = "0.8.1", default-features = false } itertools = "0.9.0" jod-thread = "0.1.0" diff --git a/crates/rust-analyzer/src/diff.rs b/crates/rust-analyzer/src/diff.rs new file mode 100644 index 0000000000..231be58077 --- /dev/null +++ b/crates/rust-analyzer/src/diff.rs @@ -0,0 +1,53 @@ +//! Generate minimal `TextEdit`s from different text versions +use dissimilar::Chunk; +use ide::{TextEdit, TextRange, TextSize}; + +pub(crate) fn diff(left: &str, right: &str) -> TextEdit { + let chunks = dissimilar::diff(left, right); + textedit_from_chunks(chunks) +} + +fn textedit_from_chunks(chunks: Vec) -> TextEdit { + let mut builder = TextEdit::builder(); + let mut pos = TextSize::default(); + + let mut chunks = chunks.into_iter().peekable(); + while let Some(chunk) = chunks.next() { + if let (Chunk::Delete(deleted), Some(&Chunk::Insert(inserted))) = (chunk, chunks.peek()) { + chunks.next().unwrap(); + let deleted_len = TextSize::of(deleted); + builder.replace(TextRange::at(pos, deleted_len), inserted.into()); + pos += deleted_len; + continue; + } + + match chunk { + Chunk::Equal(text) => { + pos += TextSize::of(text); + } + Chunk::Delete(deleted) => { + let deleted_len = TextSize::of(deleted); + builder.delete(TextRange::at(pos, deleted_len)); + pos += deleted_len; + } + Chunk::Insert(inserted) => { + builder.insert(pos, inserted.into()); + } + } + } + builder.finish() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn diff_applies() { + let mut original = String::from("fn foo(a:u32){\n}"); + let result = "fn foo(a: u32) {}"; + let edit = diff(&original, result); + edit.apply(&mut original); + assert_eq!(original, result); + } +} diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 66f8bee991..ec37fba04f 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -29,6 +29,7 @@ use serde_json::to_value; use stdx::{format_to, split_once}; use syntax::{algo, ast, AstNode, TextRange, TextSize}; +use crate::diff::diff; use crate::{ cargo_target_spec::CargoTargetSpec, config::RustfmtConfig, @@ -799,7 +800,7 @@ pub(crate) fn handle_formatting( let crate_ids = snap.analysis.crate_for(file_id)?; let file_line_index = snap.analysis.file_line_index(file_id)?; - let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str())); + let file_line_endings = snap.file_line_endings(file_id); let mut rustfmt = match &snap.config.rustfmt { RustfmtConfig::Rustfmt { extra_args } => { @@ -858,10 +859,11 @@ pub(crate) fn handle_formatting( // The document is already formatted correctly -- no edits needed. Ok(None) } else { - Ok(Some(vec![lsp_types::TextEdit { - range: Range::new(Position::new(0, 0), end_position), - new_text: captured_stdout, - }])) + Ok(Some(to_proto::text_edit_vec( + &file_line_index, + file_line_endings, + diff(&file, &captured_stdout), + ))) } } diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 79fe30e530..682fa5f7f2 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -34,6 +34,7 @@ mod request_metrics; mod lsp_utils; mod thread_pool; mod document; +mod diff; pub mod lsp_ext; pub mod config; diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs index e51eb26261..84db0856d4 100644 --- a/crates/rust-analyzer/tests/rust-analyzer/main.rs +++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs @@ -190,15 +190,10 @@ pub use std::collections::HashMap; }, json!([ { - "newText": r#"mod bar; - -fn main() {} - -pub use std::collections::HashMap; -"#, + "newText": "", "range": { - "end": { "character": 0, "line": 6 }, - "start": { "character": 0, "line": 0 } + "end": { "character": 0, "line": 3 }, + "start": { "character": 11, "line": 2 } } } ]), @@ -248,17 +243,17 @@ pub use std::collections::HashMap; }, json!([ { - "newText": r#"mod bar; - -async fn test() {} - -fn main() {} - -pub use std::collections::HashMap; -"#, + "newText": "", "range": { - "end": { "character": 0, "line": 9 }, - "start": { "character": 0, "line": 0 } + "end": { "character": 0, "line": 3 }, + "start": { "character": 17, "line": 2 } + } + }, + { + "newText": "", + "range": { + "end": { "character": 0, "line": 6 }, + "start": { "character": 11, "line": 5 } } } ]),