Skip to content

Commit 5476dec

Browse files
committed
Use byte diff instead of line diff
1 parent 57e132a commit 5476dec

File tree

3 files changed

+73
-79
lines changed

3 files changed

+73
-79
lines changed

Cargo.lock

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ num_cpus = "1.16.0"
5252
regex = "1.10.2"
5353
serde = "1.0.188"
5454
serde_json = "1.0.108"
55-
similar = { version = "2.3.0", features = ["text", "inline", "serde"] }
55+
similar = { version = "2.3.0", features = ["text", "inline", "serde", "bytes"] }
5656
strum = { version = "0.25.0", features = ["derive"], optional = true }
5757
thiserror = "1.0.49"
5858
threadpool = "1.8.1"

src/cli/lsp.rs

Lines changed: 60 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,27 @@ use lsp_server::{Connection, ErrorCode, Message, Response};
44
use lsp_textdocument::{FullTextDocument, TextDocuments};
55
use lsp_types::{
66
request::{Formatting, RangeFormatting, Request},
7-
DocumentFormattingParams, DocumentRangeFormattingParams, InitializeResult, OneOf, Position,
8-
Range, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind,
9-
TextEdit, Uri,
7+
DocumentFormattingParams, DocumentRangeFormattingParams, InitializeResult, OneOf, Range,
8+
ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit,
9+
Uri,
1010
};
1111
use similar::{DiffOp, TextDiff};
1212
use stylua_lib::{format_code, OutputVerification};
1313

1414
use crate::{config::ConfigResolver, opt};
1515

16-
fn diffop_to_textedit(op: DiffOp, formatted_contents: &str) -> Option<TextEdit> {
16+
fn diffop_to_textedit(
17+
op: DiffOp,
18+
document: &FullTextDocument,
19+
formatted_contents: &str,
20+
) -> Option<TextEdit> {
21+
let range = |start: usize, len: usize| Range {
22+
start: document.position_at(start.try_into().expect("usize fits into u32")),
23+
end: document.position_at((start + len).try_into().expect("usize fits into u32")),
24+
};
25+
26+
let lookup = |start: usize, len: usize| formatted_contents[start..start + len].to_string();
27+
1728
match op {
1829
DiffOp::Equal {
1930
old_index: _,
@@ -25,66 +36,25 @@ fn diffop_to_textedit(op: DiffOp, formatted_contents: &str) -> Option<TextEdit>
2536
old_len,
2637
new_index: _,
2738
} => Some(TextEdit {
28-
range: Range {
29-
start: Position {
30-
line: old_index.try_into().expect("usize fits into u32"),
31-
character: 0,
32-
},
33-
end: Position {
34-
line: (old_index + old_len)
35-
.try_into()
36-
.expect("usize fits into u32"),
37-
character: 0,
38-
},
39-
},
39+
range: range(old_index, old_len),
4040
new_text: String::new(),
4141
}),
4242
DiffOp::Insert {
4343
old_index,
4444
new_index,
4545
new_len,
46-
} => {
47-
let insert_position = Position {
48-
line: old_index.try_into().expect("usize fits into u32"),
49-
character: 0,
50-
};
51-
Some(TextEdit {
52-
range: Range {
53-
start: insert_position,
54-
end: insert_position,
55-
},
56-
new_text: formatted_contents
57-
.lines()
58-
.skip(new_index)
59-
.take(new_len)
60-
.collect::<Vec<_>>()
61-
.join("\n"),
62-
})
63-
}
46+
} => Some(TextEdit {
47+
range: range(old_index, 0),
48+
new_text: lookup(new_index, new_len),
49+
}),
6450
DiffOp::Replace {
6551
old_index,
6652
old_len,
6753
new_index,
6854
new_len,
6955
} => Some(TextEdit {
70-
range: Range {
71-
start: Position {
72-
line: old_index.try_into().expect("usize fits into u32"),
73-
character: 0,
74-
},
75-
end: Position {
76-
line: (old_index + old_len)
77-
.try_into()
78-
.expect("usize fits into u32"),
79-
character: 0,
80-
},
81-
},
82-
new_text: formatted_contents
83-
.lines()
84-
.skip(new_index)
85-
.take(new_len)
86-
.collect::<Vec<_>>()
87-
.join("\n"),
56+
range: range(old_index, old_len),
57+
new_text: lookup(new_index, new_len),
8858
}),
8959
}
9060
}
@@ -107,13 +77,14 @@ fn handle_formatting(
10777

10878
let formatted_contents = format_code(contents, config, range, OutputVerification::None).ok()?;
10979

110-
let operations = TextDiff::from_lines(contents, &formatted_contents).grouped_ops(0);
80+
let operations =
81+
TextDiff::from_chars(contents.as_bytes(), formatted_contents.as_bytes()).grouped_ops(0);
11182
let edits = operations
11283
.into_iter()
11384
.flat_map(|operations| {
11485
operations
11586
.into_iter()
116-
.filter_map(|op| diffop_to_textedit(op, &formatted_contents))
87+
.filter_map(|op| diffop_to_textedit(op, document, &formatted_contents))
11788
})
11889
.collect();
11990
Some(edits)
@@ -254,6 +225,8 @@ pub fn run(opt: opt::Opt) -> anyhow::Result<()> {
254225

255226
#[cfg(test)]
256227
mod tests {
228+
use std::cmp::Ordering;
229+
use std::convert::TryInto;
257230
use std::str::FromStr;
258231

259232
use clap::Parser;
@@ -373,6 +346,35 @@ mod tests {
373346
assert!(client.receiver.is_empty());
374347
}
375348

349+
fn with_edits(text: &str, mut edits: Vec<TextEdit>) -> String {
350+
edits.sort_by(|a, b| match a.range.start.line.cmp(&b.range.start.line) {
351+
Ordering::Equal => a
352+
.range
353+
.start
354+
.character
355+
.cmp(&b.range.start.character)
356+
.reverse(),
357+
order => order.reverse(),
358+
});
359+
let mut text = text.to_string();
360+
for edit in edits {
361+
let start = text
362+
.lines()
363+
.take(edit.range.start.line.try_into().unwrap())
364+
.map(|line| line.len() + '\n'.len_utf8())
365+
.sum::<usize>()
366+
+ <u32 as TryInto<usize>>::try_into(edit.range.start.character).unwrap();
367+
let end = text
368+
.lines()
369+
.take(edit.range.end.line.try_into().unwrap())
370+
.map(|line| line.len() + '\n'.len_utf8())
371+
.sum::<usize>()
372+
+ <u32 as TryInto<usize>>::try_into(edit.range.end.character).unwrap();
373+
text.replace_range(start..end, &edit.new_text);
374+
}
375+
text
376+
}
377+
376378
#[test]
377379
fn test_lsp_document_formatting() {
378380
let uri = Uri::from_str("file:///home/documents/file.luau").unwrap();
@@ -420,17 +422,8 @@ mod tests {
420422
expect_server_initialized(&client.receiver, 1);
421423

422424
let edits: Vec<TextEdit> = expect_response(&client.receiver, 2);
423-
assert_eq!(edits.len(), 1);
424-
assert_eq!(
425-
edits[0],
426-
TextEdit {
427-
range: Range {
428-
start: Position::new(0, 0),
429-
end: Position::new(0, 14),
430-
},
431-
new_text: "local x = 1\n".to_string(),
432-
}
433-
);
425+
let formatted = with_edits(contents, edits);
426+
assert_eq!(formatted, "local x = 1\n");
434427

435428
expect_server_shutdown(&client.receiver, 3);
436429
assert!(client.receiver.is_empty());
@@ -484,17 +477,8 @@ mod tests {
484477
expect_server_initialized(&client.receiver, 1);
485478

486479
let edits: Vec<TextEdit> = expect_response(&client.receiver, 2);
487-
assert_eq!(edits.len(), 1);
488-
assert_eq!(
489-
edits[0],
490-
TextEdit {
491-
range: Range {
492-
start: Position::new(0, 0),
493-
end: Position::new(1, 18),
494-
},
495-
new_text: "local x = 1\nlocal y = 2\n".to_string(),
496-
}
497-
);
480+
let formatted = with_edits(contents, edits);
481+
assert_eq!(formatted, "local x = 1\nlocal y = 2\n");
498482

499483
expect_server_shutdown(&client.receiver, 3);
500484
assert!(client.receiver.is_empty());

0 commit comments

Comments
 (0)