diff options
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/caps.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 130 |
4 files changed, 125 insertions, 13 deletions
diff --git a/Cargo.lock b/Cargo.lock index e933598fb..522ecf2ee 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1193,9 +1193,9 @@ dependencies = [ | |||
1193 | 1193 | ||
1194 | [[package]] | 1194 | [[package]] |
1195 | name = "ra_vfs" | 1195 | name = "ra_vfs" |
1196 | version = "0.5.3" | 1196 | version = "0.6.0" |
1197 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1198 | checksum = "58a265769d5e5655345a9fcbd870a1a7c3658558c0d8efaed79e0669358f46b8" | 1198 | checksum = "fcaa5615f420134aea7667253db101d03a5c5f300eac607872dc2a36407b2ac9" |
1199 | dependencies = [ | 1199 | dependencies = [ |
1200 | "crossbeam-channel", | 1200 | "crossbeam-channel", |
1201 | "jod-thread", | 1201 | "jod-thread", |
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 514d6d1a9..0459807fc 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml | |||
@@ -39,7 +39,7 @@ ra_prof = { path = "../ra_prof" } | |||
39 | ra_project_model = { path = "../ra_project_model" } | 39 | ra_project_model = { path = "../ra_project_model" } |
40 | ra_syntax = { path = "../ra_syntax" } | 40 | ra_syntax = { path = "../ra_syntax" } |
41 | ra_text_edit = { path = "../ra_text_edit" } | 41 | ra_text_edit = { path = "../ra_text_edit" } |
42 | ra_vfs = "0.5.2" | 42 | ra_vfs = "0.6.0" |
43 | 43 | ||
44 | # This should only be used in CLI | 44 | # This should only be used in CLI |
45 | ra_db = { path = "../ra_db" } | 45 | ra_db = { path = "../ra_db" } |
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 45b60768a..e22ab8402 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities { | |||
16 | ServerCapabilities { | 16 | ServerCapabilities { |
17 | text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { | 17 | text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { |
18 | open_close: Some(true), | 18 | open_close: Some(true), |
19 | change: Some(TextDocumentSyncKind::Full), | 19 | change: Some(TextDocumentSyncKind::Incremental), |
20 | will_save: None, | 20 | will_save: None, |
21 | will_save_wait_until: None, | 21 | will_save_wait_until: None, |
22 | save: Some(SaveOptions::default()), | 22 | save: Some(SaveOptions::default()), |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index f3aef3f0f..0a0e616c9 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -6,9 +6,12 @@ mod subscriptions; | |||
6 | pub(crate) mod pending_requests; | 6 | pub(crate) mod pending_requests; |
7 | 7 | ||
8 | use std::{ | 8 | use std::{ |
9 | borrow::Cow, | ||
9 | env, | 10 | env, |
10 | error::Error, | 11 | error::Error, |
11 | fmt, panic, | 12 | fmt, |
13 | ops::Range, | ||
14 | panic, | ||
12 | path::PathBuf, | 15 | path::PathBuf, |
13 | sync::Arc, | 16 | sync::Arc, |
14 | time::{Duration, Instant}, | 17 | time::{Duration, Instant}, |
@@ -18,11 +21,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; | |||
18 | use itertools::Itertools; | 21 | use itertools::Itertools; |
19 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 22 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
20 | use lsp_types::{ | 23 | use lsp_types::{ |
21 | NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, | 24 | DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, |
22 | WorkDoneProgressEnd, WorkDoneProgressReport, | 25 | WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, |
26 | WorkDoneProgressReport, | ||
23 | }; | 27 | }; |
24 | use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; | 28 | use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; |
25 | use ra_ide::{Canceled, FileId, LibraryData, SourceRootId}; | 29 | use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; |
26 | use ra_prof::profile; | 30 | use ra_prof::profile; |
27 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | 31 | use ra_project_model::{PackageRoot, ProjectWorkspace}; |
28 | use ra_vfs::{VfsFile, VfsTask, Watch}; | 32 | use ra_vfs::{VfsFile, VfsTask, Watch}; |
@@ -33,6 +37,7 @@ use threadpool::ThreadPool; | |||
33 | 37 | ||
34 | use crate::{ | 38 | use crate::{ |
35 | config::{Config, FilesWatcher}, | 39 | config::{Config, FilesWatcher}, |
40 | conv::{ConvWith, TryConvWith}, | ||
36 | diagnostics::DiagnosticTask, | 41 | diagnostics::DiagnosticTask, |
37 | main_loop::{ | 42 | main_loop::{ |
38 | pending_requests::{PendingRequest, PendingRequests}, | 43 | pending_requests::{PendingRequest, PendingRequests}, |
@@ -579,12 +584,16 @@ fn on_notification( | |||
579 | Err(not) => not, | 584 | Err(not) => not, |
580 | }; | 585 | }; |
581 | let not = match notification_cast::<req::DidChangeTextDocument>(not) { | 586 | let not = match notification_cast::<req::DidChangeTextDocument>(not) { |
582 | Ok(mut params) => { | 587 | Ok(params) => { |
583 | let uri = params.text_document.uri; | 588 | let DidChangeTextDocumentParams { text_document, content_changes } = params; |
589 | let world = state.snapshot(); | ||
590 | let file_id = text_document.try_conv_with(&world)?; | ||
591 | let line_index = world.analysis().file_line_index(file_id)?; | ||
592 | let uri = text_document.uri; | ||
584 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 593 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; |
585 | let text = | 594 | state.vfs.write().change_file_overlay(&path, |old_text| { |
586 | params.content_changes.pop().ok_or_else(|| "empty changes".to_string())?.text; | 595 | apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes); |
587 | state.vfs.write().change_file_overlay(path.as_path(), text); | 596 | }); |
588 | return Ok(()); | 597 | return Ok(()); |
589 | } | 598 | } |
590 | Err(not) => not, | 599 | Err(not) => not, |
@@ -653,6 +662,48 @@ fn on_notification( | |||
653 | Ok(()) | 662 | Ok(()) |
654 | } | 663 | } |
655 | 664 | ||
665 | fn apply_document_changes( | ||
666 | old_text: &mut String, | ||
667 | mut line_index: Cow<'_, LineIndex>, | ||
668 | content_changes: Vec<TextDocumentContentChangeEvent>, | ||
669 | ) { | ||
670 | // The changes we got must be applied sequentially, but can cross lines so we | ||
671 | // have to keep our line index updated. | ||
672 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | ||
673 | // remember the last valid line in the index and only rebuild it if needed. | ||
674 | enum IndexValid { | ||
675 | All, | ||
676 | UpToLine(u64), | ||
677 | } | ||
678 | |||
679 | impl IndexValid { | ||
680 | fn covers(&self, line: u64) -> bool { | ||
681 | match *self { | ||
682 | IndexValid::UpToLine(to) => to >= line, | ||
683 | _ => true, | ||
684 | } | ||
685 | } | ||
686 | } | ||
687 | |||
688 | let mut index_valid = IndexValid::All; | ||
689 | for change in content_changes { | ||
690 | match change.range { | ||
691 | Some(range) => { | ||
692 | if !index_valid.covers(range.start.line) { | ||
693 | line_index = Cow::Owned(LineIndex::new(&old_text)); | ||
694 | } | ||
695 | index_valid = IndexValid::UpToLine(range.start.line); | ||
696 | let range = range.conv_with(&line_index); | ||
697 | old_text.replace_range(Range::<usize>::from(range), &change.text); | ||
698 | } | ||
699 | None => { | ||
700 | *old_text = change.text; | ||
701 | index_valid = IndexValid::UpToLine(0); | ||
702 | } | ||
703 | } | ||
704 | } | ||
705 | } | ||
706 | |||
656 | fn on_check_task( | 707 | fn on_check_task( |
657 | task: CheckTask, | 708 | task: CheckTask, |
658 | world_state: &mut WorldState, | 709 | world_state: &mut WorldState, |
@@ -958,3 +1009,64 @@ where | |||
958 | { | 1009 | { |
959 | Request::new(id, R::METHOD.to_string(), params) | 1010 | Request::new(id, R::METHOD.to_string(), params) |
960 | } | 1011 | } |
1012 | |||
1013 | #[cfg(test)] | ||
1014 | mod tests { | ||
1015 | use std::borrow::Cow; | ||
1016 | |||
1017 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; | ||
1018 | use ra_ide::LineIndex; | ||
1019 | |||
1020 | #[test] | ||
1021 | fn apply_document_changes() { | ||
1022 | fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) { | ||
1023 | let line_index = Cow::Owned(LineIndex::new(&text)); | ||
1024 | super::apply_document_changes(text, line_index, changes); | ||
1025 | } | ||
1026 | |||
1027 | macro_rules! c { | ||
1028 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { | ||
1029 | vec![$(TextDocumentContentChangeEvent { | ||
1030 | range: Some(Range { | ||
1031 | start: Position { line: $sl, character: $sc }, | ||
1032 | end: Position { line: $el, character: $ec }, | ||
1033 | }), | ||
1034 | range_length: None, | ||
1035 | text: String::from($text), | ||
1036 | }),+] | ||
1037 | }; | ||
1038 | } | ||
1039 | |||
1040 | let mut text = String::new(); | ||
1041 | run(&mut text, vec![]); | ||
1042 | assert_eq!(text, ""); | ||
1043 | run( | ||
1044 | &mut text, | ||
1045 | vec![TextDocumentContentChangeEvent { | ||
1046 | range: None, | ||
1047 | range_length: None, | ||
1048 | text: String::from("the"), | ||
1049 | }], | ||
1050 | ); | ||
1051 | assert_eq!(text, "the"); | ||
1052 | run(&mut text, c![0, 3; 0, 3 => " quick"]); | ||
1053 | assert_eq!(text, "the quick"); | ||
1054 | run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); | ||
1055 | assert_eq!(text, "quick foxes"); | ||
1056 | run(&mut text, c![0, 11; 0, 11 => "\ndream"]); | ||
1057 | assert_eq!(text, "quick foxes\ndream"); | ||
1058 | run(&mut text, c![1, 0; 1, 0 => "have "]); | ||
1059 | assert_eq!(text, "quick foxes\nhave dream"); | ||
1060 | run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]); | ||
1061 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); | ||
1062 | run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); | ||
1063 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); | ||
1064 | run( | ||
1065 | &mut text, | ||
1066 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], | ||
1067 | ); | ||
1068 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); | ||
1069 | run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); | ||
1070 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); | ||
1071 | } | ||
1072 | } | ||