|
8 | 8 | use std::collections::HashSet; |
9 | 9 | use std::path::PathBuf; |
10 | 10 |
|
11 | | -use aether_lsp_utils::proto::from_proto; |
12 | | -use aether_lsp_utils::proto::PositionEncoding; |
13 | 11 | use aether_path::FilePath; |
14 | 12 | use anyhow::anyhow; |
15 | 13 | use oak_scan::DbScan; |
@@ -56,6 +54,7 @@ use crate::lsp::capabilities::Capabilities; |
56 | 54 | use crate::lsp::config::indent_style_from_lsp; |
57 | 55 | use crate::lsp::config::DOCUMENT_SETTINGS; |
58 | 56 | use crate::lsp::config::GLOBAL_SETTINGS; |
| 57 | +use crate::lsp::content_changes::apply_content_changes; |
59 | 58 | use crate::lsp::inputs::source_root::SourceRoot; |
60 | 59 | use crate::lsp::main_loop::dispatch_scan_requests; |
61 | 60 | use crate::lsp::main_loop::DidCloseVirtualDocumentParams; |
@@ -285,75 +284,6 @@ pub(crate) fn did_change( |
285 | 284 | Ok(()) |
286 | 285 | } |
287 | 286 |
|
288 | | -// --- source |
289 | | -// authors = ["rust-analyzer team"] |
290 | | -// license = "MIT OR Apache-2.0" |
291 | | -// origin = "https://github.com/rust-lang/rust-analyzer/blob/master/crates/rust-analyzer/src/lsp/utils.rs" |
292 | | -// --- |
293 | | -/// Apply a batch of LSP content changes to `contents`, returning the new text. |
294 | | -fn apply_content_changes( |
295 | | - contents: &str, |
296 | | - content_changes: &[lsp_types::TextDocumentContentChangeEvent], |
297 | | - encoding: PositionEncoding, |
298 | | -) -> String { |
299 | | - let mut contents = contents.to_string(); |
300 | | - let mut changes = content_changes.to_vec(); |
301 | | - |
302 | | - // If at least one of the changes is a full document change, use the last of them |
303 | | - // as the starting point and ignore all previous changes. We then know that all |
304 | | - // changes after this (if any!) are incremental changes. |
305 | | - // |
306 | | - // If we do have a full document change, that implies the `last_start_line` |
307 | | - // corresponding to that change is line 0, which will correctly force a rebuild |
308 | | - // of the line index before applying any incremental changes. |
309 | | - let (changes, mut last_start_line) = |
310 | | - match changes.iter().rposition(|change| change.range.is_none()) { |
311 | | - Some(idx) => { |
312 | | - let incremental = changes.split_off(idx + 1); |
313 | | - // Unwrap: `rposition()` confirmed this index contains a full document change |
314 | | - let change = changes.pop().unwrap(); |
315 | | - contents = change.text; |
316 | | - (incremental, 0) |
317 | | - }, |
318 | | - None => (changes, u32::MAX), |
319 | | - }; |
320 | | - |
321 | | - let mut line_index = biome_line_index::LineIndex::new(&contents); |
322 | | - |
323 | | - // Handle all incremental changes after the last full document change. We don't |
324 | | - // typically get >1 incremental change as the user types, but we do get them in a |
325 | | - // batch after a find-and-replace, or after a format-on-save request. |
326 | | - // |
327 | | - // Some editors like VS Code send the edits in reverse order (from the bottom of |
328 | | - // file -> top of file). We can take advantage of this, because applying an edit |
329 | | - // on, say, line 10, doesn't invalidate the `line_index` if we then need to apply |
330 | | - // an additional edit on line 5. That said, we may still have edits that cross |
331 | | - // lines, so rebuilding the `line_index` is not always unavoidable. |
332 | | - for change in changes { |
333 | | - let range = change |
334 | | - .range |
335 | | - .expect("`None` case already handled by finding the last full document change."); |
336 | | - |
337 | | - // If the end of this change is at or past the start of the last change, then |
338 | | - // the `line_index` needed to apply this change is now invalid, so we have to |
339 | | - // rebuild it. |
340 | | - if range.end.line >= last_start_line { |
341 | | - line_index = biome_line_index::LineIndex::new(&contents); |
342 | | - } |
343 | | - last_start_line = range.start.line; |
344 | | - |
345 | | - // This is a panic if we can't convert. It means we can't keep the document up |
346 | | - // to date and something is very wrong. |
347 | | - let range: std::ops::Range<usize> = from_proto::text_range(range, &line_index, encoding) |
348 | | - .expect("Can convert `range` from `Position` to `TextRange`.") |
349 | | - .into(); |
350 | | - |
351 | | - contents.replace_range(range, &change.text); |
352 | | - } |
353 | | - |
354 | | - contents |
355 | | -} |
356 | | - |
357 | 287 | #[tracing::instrument(level = "info", skip_all)] |
358 | 288 | pub(crate) fn did_close( |
359 | 289 | params: DidCloseTextDocumentParams, |
@@ -618,50 +548,3 @@ pub(crate) fn did_close_virtual_document( |
618 | 548 | state.virtual_documents.remove(¶ms.uri); |
619 | 549 | Ok(()) |
620 | 550 | } |
621 | | - |
622 | | -#[cfg(test)] |
623 | | -mod tests { |
624 | | - use biome_line_index::WideEncoding; |
625 | | - |
626 | | - use super::*; |
627 | | - |
628 | | - const ENCODING: PositionEncoding = PositionEncoding::Wide(WideEncoding::Utf16); |
629 | | - |
630 | | - fn insert(text: &str, line: u32, character: u32) -> lsp_types::TextDocumentContentChangeEvent { |
631 | | - let position = lsp_types::Position::new(line, character); |
632 | | - lsp_types::TextDocumentContentChangeEvent { |
633 | | - range: Some(lsp_types::Range::new(position, position)), |
634 | | - range_length: None, |
635 | | - text: text.to_string(), |
636 | | - } |
637 | | - } |
638 | | - |
639 | | - #[test] |
640 | | - fn test_apply_content_changes_incremental_inserts() { |
641 | | - // Type "lib" one character at a time, the way an editor streams it. |
642 | | - let after_l = apply_content_changes("", &[insert("l", 0, 0)], ENCODING); |
643 | | - assert_eq!(after_l, "l"); |
644 | | - |
645 | | - let after_i = apply_content_changes(&after_l, &[insert("i", 0, 1)], ENCODING); |
646 | | - assert_eq!(after_i, "li"); |
647 | | - |
648 | | - let after_b = apply_content_changes(&after_i, &[insert("b", 0, 2)], ENCODING); |
649 | | - assert_eq!(after_b, "lib"); |
650 | | - } |
651 | | - |
652 | | - #[test] |
653 | | - fn test_apply_content_changes_full_replacement_wins() { |
654 | | - // A range-less change replaces the whole buffer; earlier changes in the |
655 | | - // batch are discarded, later incremental ones apply on top of it. |
656 | | - let changes = vec![ |
657 | | - insert("ignored", 0, 0), |
658 | | - lsp_types::TextDocumentContentChangeEvent { |
659 | | - range: None, |
660 | | - range_length: None, |
661 | | - text: "abc\n".to_string(), |
662 | | - }, |
663 | | - insert("X", 0, 3), |
664 | | - ]; |
665 | | - assert_eq!(apply_content_changes("old", &changes, ENCODING), "abcX\n"); |
666 | | - } |
667 | | -} |
0 commit comments