Split textDocument/formatting TextEdit with diff

This commit is contained in:
Jesse Bakker 2020-12-31 13:05:19 +01:00
parent 9bb9fbab3a
commit f355a6d831
6 changed files with 82 additions and 23 deletions

7
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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<dissimilar::Chunk>) -> 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);
}
}

View file

@ -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),
)))
}
}

View file

@ -34,6 +34,7 @@ mod request_metrics;
mod lsp_utils;
mod thread_pool;
mod document;
mod diff;
pub mod lsp_ext;
pub mod config;

View file

@ -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 }
}
}
]),