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"
"anyhow",
"cfg",
"crossbeam-channel 0.5.0",
+ "dissimilar",
"env_logger",
"expect-test",
"flycheck",
[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"
--- /dev/null
+//! 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);
+ }
+}
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,
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 } => {
// 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),
+ )))
}
}
mod lsp_utils;
mod thread_pool;
mod document;
+mod diff;
pub mod lsp_ext;
pub mod config;
},
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 }
}
}
]),
},
json!([
{
- "newText": r#"mod bar;
-
-async fn test() {}
-
-fn main() {}
-
-pub use std::collections::HashMap;
-"#,
+ "newText": "",
+ "range": {
+ "end": { "character": 0, "line": 3 },
+ "start": { "character": 17, "line": 2 }
+ }
+ },
+ {
+ "newText": "",
"range": {
- "end": { "character": 0, "line": 9 },
- "start": { "character": 0, "line": 0 }
+ "end": { "character": 0, "line": 6 },
+ "start": { "character": 11, "line": 5 }
}
}
]),