diff options
-rw-r--r-- | crates/rust-analyzer/src/lsp_utils.rs | 117 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 126 |
2 files changed, 123 insertions, 120 deletions
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs index 078f8778e..14adb8ae7 100644 --- a/crates/rust-analyzer/src/lsp_utils.rs +++ b/crates/rust-analyzer/src/lsp_utils.rs | |||
@@ -1,9 +1,11 @@ | |||
1 | //! Utilities for LSP-related boilerplate code. | 1 | //! Utilities for LSP-related boilerplate code. |
2 | use std::error::Error; | 2 | use std::{error::Error, ops::Range}; |
3 | 3 | ||
4 | use crate::from_proto; | ||
4 | use crossbeam_channel::Sender; | 5 | use crossbeam_channel::Sender; |
5 | use lsp_server::{Message, Notification}; | 6 | use lsp_server::{Message, Notification}; |
6 | use ra_db::Canceled; | 7 | use ra_db::Canceled; |
8 | use ra_ide::LineIndex; | ||
7 | use serde::{de::DeserializeOwned, Serialize}; | 9 | use serde::{de::DeserializeOwned, Serialize}; |
8 | 10 | ||
9 | pub fn show_message( | 11 | pub fn show_message( |
@@ -42,3 +44,116 @@ where | |||
42 | { | 44 | { |
43 | Notification::new(N::METHOD.to_string(), params) | 45 | Notification::new(N::METHOD.to_string(), params) |
44 | } | 46 | } |
47 | |||
48 | pub(crate) fn apply_document_changes( | ||
49 | old_text: &mut String, | ||
50 | content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, | ||
51 | ) { | ||
52 | let mut line_index = LineIndex::new(old_text); | ||
53 | // The changes we got must be applied sequentially, but can cross lines so we | ||
54 | // have to keep our line index updated. | ||
55 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | ||
56 | // remember the last valid line in the index and only rebuild it if needed. | ||
57 | // The VFS will normalize the end of lines to `\n`. | ||
58 | enum IndexValid { | ||
59 | All, | ||
60 | UpToLineExclusive(u64), | ||
61 | } | ||
62 | |||
63 | impl IndexValid { | ||
64 | fn covers(&self, line: u64) -> bool { | ||
65 | match *self { | ||
66 | IndexValid::UpToLineExclusive(to) => to > line, | ||
67 | _ => true, | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | let mut index_valid = IndexValid::All; | ||
73 | for change in content_changes { | ||
74 | match change.range { | ||
75 | Some(range) => { | ||
76 | if !index_valid.covers(range.end.line) { | ||
77 | line_index = LineIndex::new(&old_text); | ||
78 | } | ||
79 | index_valid = IndexValid::UpToLineExclusive(range.start.line); | ||
80 | let range = from_proto::text_range(&line_index, range); | ||
81 | old_text.replace_range(Range::<usize>::from(range), &change.text); | ||
82 | } | ||
83 | None => { | ||
84 | *old_text = change.text; | ||
85 | index_valid = IndexValid::UpToLineExclusive(0); | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | #[cfg(test)] | ||
92 | mod tests { | ||
93 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; | ||
94 | |||
95 | use super::*; | ||
96 | |||
97 | #[test] | ||
98 | fn test_apply_document_changes() { | ||
99 | macro_rules! c { | ||
100 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { | ||
101 | vec![$(TextDocumentContentChangeEvent { | ||
102 | range: Some(Range { | ||
103 | start: Position { line: $sl, character: $sc }, | ||
104 | end: Position { line: $el, character: $ec }, | ||
105 | }), | ||
106 | range_length: None, | ||
107 | text: String::from($text), | ||
108 | }),+] | ||
109 | }; | ||
110 | } | ||
111 | |||
112 | let mut text = String::new(); | ||
113 | apply_document_changes(&mut text, vec![]); | ||
114 | assert_eq!(text, ""); | ||
115 | apply_document_changes( | ||
116 | &mut text, | ||
117 | vec![TextDocumentContentChangeEvent { | ||
118 | range: None, | ||
119 | range_length: None, | ||
120 | text: String::from("the"), | ||
121 | }], | ||
122 | ); | ||
123 | assert_eq!(text, "the"); | ||
124 | apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); | ||
125 | assert_eq!(text, "the quick"); | ||
126 | apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); | ||
127 | assert_eq!(text, "quick foxes"); | ||
128 | apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); | ||
129 | assert_eq!(text, "quick foxes\ndream"); | ||
130 | apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); | ||
131 | assert_eq!(text, "quick foxes\nhave dream"); | ||
132 | apply_document_changes( | ||
133 | &mut text, | ||
134 | c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], | ||
135 | ); | ||
136 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); | ||
137 | apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); | ||
138 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); | ||
139 | apply_document_changes( | ||
140 | &mut text, | ||
141 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], | ||
142 | ); | ||
143 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); | ||
144 | apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); | ||
145 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); | ||
146 | |||
147 | text = String::from("❤️"); | ||
148 | apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); | ||
149 | assert_eq!(text, "a❤️"); | ||
150 | |||
151 | text = String::from("a\nb"); | ||
152 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); | ||
153 | assert_eq!(text, "adcb"); | ||
154 | |||
155 | text = String::from("a\nb"); | ||
156 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); | ||
157 | assert_eq!(text, "ațc\ncb"); | ||
158 | } | ||
159 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 200641cd5..3b3b83209 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -1,18 +1,16 @@ | |||
1 | //! The main loop of `rust-analyzer` responsible for dispatching LSP | 1 | //! The main loop of `rust-analyzer` responsible for dispatching LSP |
2 | //! requests/replies and notifications back to the client. | 2 | //! requests/replies and notifications back to the client. |
3 | use std::{ | 3 | use std::{ |
4 | env, fmt, | 4 | env, fmt, panic, |
5 | ops::Range, | ||
6 | panic, | ||
7 | sync::Arc, | 5 | sync::Arc, |
8 | time::{Duration, Instant}, | 6 | time::{Duration, Instant}, |
9 | }; | 7 | }; |
10 | 8 | ||
11 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; | 9 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; |
12 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 10 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
13 | use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent}; | 11 | use lsp_types::{request::Request as _, NumberOrString}; |
14 | use ra_db::VfsPath; | 12 | use ra_db::VfsPath; |
15 | use ra_ide::{Canceled, FileId, LineIndex}; | 13 | use ra_ide::{Canceled, FileId}; |
16 | use ra_prof::profile; | 14 | use ra_prof::profile; |
17 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | 15 | use ra_project_model::{PackageRoot, ProjectWorkspace}; |
18 | use serde::{de::DeserializeOwned, Serialize}; | 16 | use serde::{de::DeserializeOwned, Serialize}; |
@@ -24,7 +22,10 @@ use crate::{ | |||
24 | from_proto, | 22 | from_proto, |
25 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot, Status}, | 23 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot, Status}, |
26 | handlers, lsp_ext, | 24 | handlers, lsp_ext, |
27 | lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, show_message}, | 25 | lsp_utils::{ |
26 | apply_document_changes, is_canceled, notification_cast, notification_is, notification_new, | ||
27 | show_message, | ||
28 | }, | ||
28 | request_metrics::RequestMetrics, | 29 | request_metrics::RequestMetrics, |
29 | LspError, Result, | 30 | LspError, Result, |
30 | }; | 31 | }; |
@@ -548,49 +549,6 @@ fn on_notification( | |||
548 | Ok(()) | 549 | Ok(()) |
549 | } | 550 | } |
550 | 551 | ||
551 | fn apply_document_changes( | ||
552 | old_text: &mut String, | ||
553 | content_changes: Vec<TextDocumentContentChangeEvent>, | ||
554 | ) { | ||
555 | let mut line_index = LineIndex::new(old_text); | ||
556 | // The changes we got must be applied sequentially, but can cross lines so we | ||
557 | // have to keep our line index updated. | ||
558 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | ||
559 | // remember the last valid line in the index and only rebuild it if needed. | ||
560 | // The VFS will normalize the end of lines to `\n`. | ||
561 | enum IndexValid { | ||
562 | All, | ||
563 | UpToLineExclusive(u64), | ||
564 | } | ||
565 | |||
566 | impl IndexValid { | ||
567 | fn covers(&self, line: u64) -> bool { | ||
568 | match *self { | ||
569 | IndexValid::UpToLineExclusive(to) => to > line, | ||
570 | _ => true, | ||
571 | } | ||
572 | } | ||
573 | } | ||
574 | |||
575 | let mut index_valid = IndexValid::All; | ||
576 | for change in content_changes { | ||
577 | match change.range { | ||
578 | Some(range) => { | ||
579 | if !index_valid.covers(range.end.line) { | ||
580 | line_index = LineIndex::new(&old_text); | ||
581 | } | ||
582 | index_valid = IndexValid::UpToLineExclusive(range.start.line); | ||
583 | let range = from_proto::text_range(&line_index, range); | ||
584 | old_text.replace_range(Range::<usize>::from(range), &change.text); | ||
585 | } | ||
586 | None => { | ||
587 | *old_text = change.text; | ||
588 | index_valid = IndexValid::UpToLineExclusive(0); | ||
589 | } | ||
590 | } | ||
591 | } | ||
592 | } | ||
593 | |||
594 | fn on_check_task( | 552 | fn on_check_task( |
595 | task: flycheck::Message, | 553 | task: flycheck::Message, |
596 | global_state: &mut GlobalState, | 554 | global_state: &mut GlobalState, |
@@ -862,73 +820,3 @@ fn update_file_notifications_on_threadpool( | |||
862 | }) | 820 | }) |
863 | } | 821 | } |
864 | } | 822 | } |
865 | |||
866 | #[cfg(test)] | ||
867 | mod tests { | ||
868 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; | ||
869 | |||
870 | use super::*; | ||
871 | |||
872 | #[test] | ||
873 | fn test_apply_document_changes() { | ||
874 | macro_rules! c { | ||
875 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { | ||
876 | vec![$(TextDocumentContentChangeEvent { | ||
877 | range: Some(Range { | ||
878 | start: Position { line: $sl, character: $sc }, | ||
879 | end: Position { line: $el, character: $ec }, | ||
880 | }), | ||
881 | range_length: None, | ||
882 | text: String::from($text), | ||
883 | }),+] | ||
884 | }; | ||
885 | } | ||
886 | |||
887 | let mut text = String::new(); | ||
888 | apply_document_changes(&mut text, vec![]); | ||
889 | assert_eq!(text, ""); | ||
890 | apply_document_changes( | ||
891 | &mut text, | ||
892 | vec![TextDocumentContentChangeEvent { | ||
893 | range: None, | ||
894 | range_length: None, | ||
895 | text: String::from("the"), | ||
896 | }], | ||
897 | ); | ||
898 | assert_eq!(text, "the"); | ||
899 | apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); | ||
900 | assert_eq!(text, "the quick"); | ||
901 | apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); | ||
902 | assert_eq!(text, "quick foxes"); | ||
903 | apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); | ||
904 | assert_eq!(text, "quick foxes\ndream"); | ||
905 | apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); | ||
906 | assert_eq!(text, "quick foxes\nhave dream"); | ||
907 | apply_document_changes( | ||
908 | &mut text, | ||
909 | c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], | ||
910 | ); | ||
911 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); | ||
912 | apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); | ||
913 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); | ||
914 | apply_document_changes( | ||
915 | &mut text, | ||
916 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], | ||
917 | ); | ||
918 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); | ||
919 | apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); | ||
920 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); | ||
921 | |||
922 | text = String::from("❤️"); | ||
923 | apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); | ||
924 | assert_eq!(text, "a❤️"); | ||
925 | |||
926 | text = String::from("a\nb"); | ||
927 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); | ||
928 | assert_eq!(text, "adcb"); | ||
929 | |||
930 | text = String::from("a\nb"); | ||
931 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); | ||
932 | assert_eq!(text, "ațc\ncb"); | ||
933 | } | ||
934 | } | ||