Skip to content

Commit e468a1a

Browse files
committed
internal: Optimize apply_document_changes a bit
1 parent cd26032 commit e468a1a

File tree

3 files changed

+68
-56
lines changed

3 files changed

+68
-56
lines changed

crates/ide-db/src/line_index.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@ impl LineIndex {
5858
let mut utf16_lines = NoHashHashMap::default();
5959
let mut utf16_chars = Vec::new();
6060

61-
let mut newlines = vec![0.into()];
62-
let mut curr_row @ mut curr_col = 0.into();
61+
let mut newlines = Vec::with_capacity(16);
62+
newlines.push(TextSize::from(0));
63+
64+
let mut curr_row = 0.into();
65+
let mut curr_col = 0.into();
6366
let mut line = 0;
6467
for c in text.chars() {
6568
let c_len = TextSize::of(c);

crates/rust-analyzer/src/lsp_utils.rs

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Utilities for LSP-related boilerplate code.
2-
use std::{ops::Range, sync::Arc};
2+
use std::{mem, ops::Range, sync::Arc};
33

44
use lsp_server::Notification;
55

@@ -133,11 +133,37 @@ impl GlobalState {
133133
}
134134

135135
pub(crate) fn apply_document_changes(
136-
old_text: &mut String,
137-
content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
138-
) {
136+
file_contents: impl FnOnce() -> String,
137+
mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
138+
) -> String {
139+
// Skip to the last full document change, as it invalidates all previous changes anyways.
140+
let mut start = content_changes
141+
.iter()
142+
.rev()
143+
.position(|change| change.range.is_none())
144+
.map(|idx| content_changes.len() - idx - 1)
145+
.unwrap_or(0);
146+
147+
let mut text: String = match content_changes.get_mut(start) {
148+
// peek at the first content change as an optimization
149+
Some(lsp_types::TextDocumentContentChangeEvent { range: None, text, .. }) => {
150+
let text = mem::take(text);
151+
start += 1;
152+
153+
// The only change is a full document update
154+
if start == content_changes.len() {
155+
return text;
156+
}
157+
text
158+
}
159+
Some(_) => file_contents(),
160+
// we received no content changes
161+
None => return file_contents(),
162+
};
163+
139164
let mut line_index = LineIndex {
140-
index: Arc::new(ide::LineIndex::new(old_text)),
165+
// the index will be overwritten in the bottom loop's first iteration
166+
index: Arc::new(ide::LineIndex::new(&text)),
141167
// We don't care about line endings or offset encoding here.
142168
endings: LineEndings::Unix,
143169
encoding: PositionEncoding::Utf16,
@@ -148,38 +174,20 @@ pub(crate) fn apply_document_changes(
148174
// Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
149175
// remember the last valid line in the index and only rebuild it if needed.
150176
// The VFS will normalize the end of lines to `\n`.
151-
enum IndexValid {
152-
All,
153-
UpToLineExclusive(u32),
154-
}
155-
156-
impl IndexValid {
157-
fn covers(&self, line: u32) -> bool {
158-
match *self {
159-
IndexValid::UpToLineExclusive(to) => to > line,
160-
_ => true,
161-
}
162-
}
163-
}
164-
165-
let mut index_valid = IndexValid::All;
177+
let mut index_valid = !0u32;
166178
for change in content_changes {
167-
match change.range {
168-
Some(range) => {
169-
if !index_valid.covers(range.end.line) {
170-
line_index.index = Arc::new(ide::LineIndex::new(old_text));
171-
}
172-
index_valid = IndexValid::UpToLineExclusive(range.start.line);
173-
if let Ok(range) = from_proto::text_range(&line_index, range) {
174-
old_text.replace_range(Range::<usize>::from(range), &change.text);
175-
}
179+
// The None case can't happen as we have handled it above already
180+
if let Some(range) = change.range {
181+
if index_valid <= range.end.line {
182+
*Arc::make_mut(&mut line_index.index) = ide::LineIndex::new(&text);
176183
}
177-
None => {
178-
*old_text = change.text;
179-
index_valid = IndexValid::UpToLineExclusive(0);
184+
index_valid = range.start.line;
185+
if let Ok(range) = from_proto::text_range(&line_index, range) {
186+
text.replace_range(Range::<usize>::from(range), &change.text);
180187
}
181188
}
182189
}
190+
text
183191
}
184192

185193
/// Checks that the edits inside the completion and the additional edits do not overlap.
@@ -242,51 +250,50 @@ mod tests {
242250
};
243251
}
244252

245-
let mut text = String::new();
246-
apply_document_changes(&mut text, vec![]);
253+
let text = apply_document_changes(|| String::new(), vec![]);
247254
assert_eq!(text, "");
248-
apply_document_changes(
249-
&mut text,
255+
let text = apply_document_changes(
256+
|| text,
250257
vec![TextDocumentContentChangeEvent {
251258
range: None,
252259
range_length: None,
253260
text: String::from("the"),
254261
}],
255262
);
256263
assert_eq!(text, "the");
257-
apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]);
264+
let text = apply_document_changes(|| text, c![0, 3; 0, 3 => " quick"]);
258265
assert_eq!(text, "the quick");
259-
apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
266+
let text = apply_document_changes(|| text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
260267
assert_eq!(text, "quick foxes");
261-
apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]);
268+
let text = apply_document_changes(|| text, c![0, 11; 0, 11 => "\ndream"]);
262269
assert_eq!(text, "quick foxes\ndream");
263-
apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]);
270+
let text = apply_document_changes(|| text, c![1, 0; 1, 0 => "have "]);
264271
assert_eq!(text, "quick foxes\nhave dream");
265-
apply_document_changes(
266-
&mut text,
272+
let text = apply_document_changes(
273+
|| text,
267274
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
268275
);
269276
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
270-
apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
277+
let text = apply_document_changes(|| text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
271278
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
272-
apply_document_changes(
273-
&mut text,
279+
let text = apply_document_changes(
280+
|| text,
274281
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
275282
);
276283
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
277-
apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
284+
let text = apply_document_changes(|| text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
278285
assert_eq!(text, "the quick \nthey have quiet dreams\n");
279286

280-
text = String::from("❤️");
281-
apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
287+
let text = String::from("❤️");
288+
let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]);
282289
assert_eq!(text, "a❤️");
283290

284-
text = String::from("a\nb");
285-
apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
291+
let text = String::from("a\nb");
292+
let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
286293
assert_eq!(text, "adcb");
287294

288-
text = String::from("a\nb");
289-
apply_document_changes(&mut text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
295+
let text = String::from("a\nb");
296+
let text = apply_document_changes(|| text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
290297
assert_eq!(text, "ațc\ncb");
291298
}
292299

crates/rust-analyzer/src/main_loop.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,8 +759,10 @@ impl GlobalState {
759759

760760
let vfs = &mut this.vfs.write().0;
761761
let file_id = vfs.file_id(&path).unwrap();
762-
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
763-
apply_document_changes(&mut text, params.content_changes);
762+
let text = apply_document_changes(
763+
|| std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(),
764+
params.content_changes,
765+
);
764766

765767
vfs.set_file_contents(path, Some(text.into_bytes()));
766768
}

0 commit comments

Comments
 (0)