55//
66//
77
8- use settings:: LineEnding ;
98use std:: ops:: Range ;
9+
10+ use settings:: LineEnding ;
1011use tower_lsp:: lsp_types;
1112
1213use crate :: line_index:: LineIndex ;
@@ -73,9 +74,7 @@ impl Document {
7374 // select `LineEndings::Auto`, and then pass that to `LineIndex`.
7475
7576 // Create line index to keep track of newline offsets
76- let line_index = LineIndex {
77- index : triomphe:: Arc :: new ( biome_line_index:: LineIndex :: new ( & contents) ) ,
78- } ;
77+ let line_index = LineIndex :: new ( & contents) ;
7978
8079 // Parse document immediately for now
8180 let parse = air_r_parser:: parse ( & contents, Default :: default ( ) ) ;
@@ -103,9 +102,16 @@ impl Document {
103102 ( doc, range)
104103 }
105104
106- pub fn on_did_change ( & mut self , mut params : lsp_types:: DidChangeTextDocumentParams ) {
107- let new_version = params. text_document . version ;
108-
105+ // --- source
106+ // authors = ["rust-analyzer team"]
107+ // license = "MIT OR Apache-2.0"
108+ // origin = "https://github.com/rust-lang/rust-analyzer/blob/master/crates/rust-analyzer/src/lsp/utils.rs"
109+ // ---
110+ pub fn on_did_change (
111+ & mut self ,
112+ mut changes : Vec < lsp_types:: TextDocumentContentChangeEvent > ,
113+ new_version : i32 ,
114+ ) {
109115 // Check for out-of-order change notifications
110116 if let Some ( old_version) = self . version {
111117 // According to the spec, versions might not be consecutive but they must be monotonically
@@ -119,78 +125,69 @@ impl Document {
119125 }
120126 }
121127
122- // Normalize line endings. Changing the line length of inserted or
123- // replaced text can't invalidate the text change events, even those
124- // applied subsequently, since those changes are specified with [line,
125- // col] coordinates.
126- for event in & mut params. content_changes {
127- let text = std:: mem:: take ( & mut event. text ) ;
128- event. text = line_ending:: normalize ( text) ;
129- }
130-
131- let contents = Self :: apply_document_changes (
132- self . position_encoding ,
133- & self . contents ,
134- params. content_changes ,
135- ) ;
136-
137- // No incrementality for now
138- let parse = air_r_parser:: parse ( & contents, Default :: default ( ) ) ;
128+ // If at least one of the changes is a full document change, use the last of them
129+ // as the starting point and ignore all previous changes. We then know that all
130+ // changes after this (if any!) are incremental changes.
131+ //
132+ // If we do have a full document change, that implies the `last_start_line`
133+ // corresponding to that change is line 0, which will correctly force a rebuild
134+ // of the line index before applying any incremental changes. We don't go ahead
135+ // and rebuild the line index here, because it is guaranteed to be rebuilt for
136+ // us on the way out.
137+ let ( changes, mut last_start_line) =
138+ match changes. iter ( ) . rposition ( |change| change. range . is_none ( ) ) {
139+ Some ( idx) => {
140+ let incremental = changes. split_off ( idx + 1 ) ;
141+ // Unwrap: `rposition()` confirmed this index contains a full document change
142+ let change = changes. pop ( ) . unwrap ( ) ;
143+ self . contents = line_ending:: normalize ( change. text ) ;
144+ ( incremental, 0 )
145+ }
146+ None => ( changes, u32:: MAX ) ,
147+ } ;
148+
149+ // Handle all incremental changes after the last full document change. We don't
150+ // typically get >1 incremental change as the user types, but we do get them in a
151+ // batch after a find-and-replace, or after a format-on-save request.
152+ //
153+ // Some editors like VS Code send the edits in reverse order (from the bottom of
154+ // file -> top of file). We can take advantage of this, because applying an edit
155+ // on, say, line 10, doesn't invalidate the `line_index` if we then need to apply
156+ // an additional edit on line 5. That said, we may still have edits that cross
157+ // lines, so rebuilding the `line_index` is not always unavoidable.
158+ //
159+ // We also normalize line endings. Changing the line length of inserted or
160+ // replaced text can't invalidate the text change events since the location of the
161+ // change itself is specified with [line, col] coordinates, separate from the
162+ // actual contents of the change.
163+ for change in changes {
164+ let range = change
165+ . range
166+ . expect ( "`None` case already handled by finding the last full document change." ) ;
167+
168+ // If the end of this change is at or past the start of the last change, then
169+ // the `line_index` needed to apply this change is now invalid, so we have to
170+ // rebuild it.
171+ if range. end . line >= last_start_line {
172+ self . line_index = LineIndex :: new ( & self . contents ) ;
173+ }
174+ last_start_line = range. start . line ;
139175
140- self . parse = parse ;
141- self . contents = contents ;
142- self . line_index . index =
143- triomphe :: Arc :: new ( biome_line_index :: LineIndex :: new ( & self . contents ) ) ;
144- self . version = Some ( new_version ) ;
145- }
176+ // This is a panic if we can't convert. It means we can't keep the document up
177+ // to date and something is very wrong.
178+ let range : Range < usize > =
179+ from_proto :: text_range ( range , & self . line_index . index , self . position_encoding )
180+ . expect ( "Can convert `range` from `Position` to `TextRange`." )
181+ . into ( ) ;
146182
147- // --- source
148- // authors = ["rust-analyzer team"]
149- // license = "MIT OR Apache-2.0"
150- // origin = "https://github.com/rust-lang/rust-analyzer/blob/master/crates/rust-analyzer/src/lsp/utils.rs"
151- // ---
152- fn apply_document_changes (
153- encoding : PositionEncoding ,
154- file_contents : & str ,
155- mut content_changes : Vec < lsp_types:: TextDocumentContentChangeEvent > ,
156- ) -> String {
157- // If at least one of the changes is a full document change, use the last
158- // of them as the starting point and ignore all previous changes.
159- let ( mut text, content_changes) = match content_changes
160- . iter ( )
161- . rposition ( |change| change. range . is_none ( ) )
162- {
163- Some ( idx) => {
164- let text = std:: mem:: take ( & mut content_changes[ idx] . text ) ;
165- ( text, & content_changes[ idx + 1 ..] )
166- }
167- None => ( file_contents. to_owned ( ) , & content_changes[ ..] ) ,
168- } ;
169- if content_changes. is_empty ( ) {
170- return text;
183+ self . contents
184+ . replace_range ( range, & line_ending:: normalize ( change. text ) ) ;
171185 }
172186
173- let mut line_index = biome_line_index:: LineIndex :: new ( & text) ;
174-
175- // The changes we got must be applied sequentially, but can cross lines so we
176- // have to keep our line index updated.
177- // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
178- // remember the last valid line in the index and only rebuild it if needed.
179- // The VFS will normalize the end of lines to `\n`.
180- let mut index_valid = !0u32 ;
181- for change in content_changes {
182- // The None case can't happen as we have handled it above already
183- if let Some ( range) = change. range {
184- if index_valid <= range. end . line {
185- line_index = biome_line_index:: LineIndex :: new ( & text) ;
186- }
187- index_valid = range. start . line ;
188- if let Ok ( range) = from_proto:: text_range ( range, & line_index, encoding) {
189- text. replace_range ( Range :: < usize > :: from ( range) , & change. text ) ;
190- }
191- }
192- }
193- text
187+ // Rebuild the `line_index` after applying the final edit, and sync other fields
188+ self . line_index = LineIndex :: new ( & self . contents ) ;
189+ self . parse = air_r_parser:: parse ( & self . contents , Default :: default ( ) ) ;
190+ self . version = Some ( new_version) ;
194191 }
195192
196193 /// Convenient accessor that returns an annotated `SyntaxNode` type
@@ -209,13 +206,6 @@ mod tests {
209206
210207 use super :: * ;
211208
212- fn dummy_versioned_doc ( ) -> lsp_types:: VersionedTextDocumentIdentifier {
213- lsp_types:: VersionedTextDocumentIdentifier {
214- uri : url:: Url :: parse ( "file:///foo" ) . unwrap ( ) ,
215- version : 1 ,
216- }
217- }
218-
219209 #[ test]
220210 fn test_document_starts_at_0_with_leading_whitespace ( ) {
221211 let document = Document :: doodle ( "\n \n # hi there" ) ;
@@ -244,12 +234,7 @@ mod tests {
244234 doc. endings ,
245235 )
246236 . unwrap ( ) ;
247-
248- let params = lsp_types:: DidChangeTextDocumentParams {
249- text_document : dummy_versioned_doc ( ) ,
250- content_changes : edits,
251- } ;
252- doc. on_did_change ( params) ;
237+ doc. on_did_change ( edits, 1 ) ;
253238
254239 let updated_syntax: RSyntaxNode = doc. parse . syntax ( ) ;
255240 insta:: assert_debug_snapshot!( updated_syntax) ;
@@ -281,33 +266,27 @@ mod tests {
281266 } ,
282267 } ;
283268
284- let mut utf8_replace_params = lsp_types:: DidChangeTextDocumentParams {
285- text_document : dummy_versioned_doc ( ) ,
286- content_changes : vec ! [ ] ,
287- } ;
288- let mut utf16_replace_params = utf8_replace_params. clone ( ) ;
289-
290- utf8_replace_params. content_changes = vec ! [ lsp_types:: TextDocumentContentChangeEvent {
269+ let utf8_content_changes = vec ! [ lsp_types:: TextDocumentContentChangeEvent {
291270 range: Some ( utf8_range) ,
292271 range_length: None ,
293272 text: String :: from( "bar" ) ,
294273 } ] ;
295- utf16_replace_params . content_changes = vec ! [ lsp_types:: TextDocumentContentChangeEvent {
274+ let utf16_content_changes = vec ! [ lsp_types:: TextDocumentContentChangeEvent {
296275 range: Some ( utf16_range) ,
297276 range_length: None ,
298277 text: String :: from( "bar" ) ,
299278 } ] ;
300279
301280 let mut document = Document :: new ( "a𐐀b" . into ( ) , None , PositionEncoding :: Utf8 ) ;
302- document. on_did_change ( utf8_replace_params ) ;
281+ document. on_did_change ( utf8_content_changes , 1 ) ;
303282 assert_eq ! ( document. contents, "a𐐀bar" ) ;
304283
305284 let mut document = Document :: new (
306285 "a𐐀b" . into ( ) ,
307286 None ,
308287 PositionEncoding :: Wide ( biome_line_index:: WideEncoding :: Utf16 ) ,
309288 ) ;
310- document. on_did_change ( utf16_replace_params ) ;
289+ document. on_did_change ( utf16_content_changes , 1 ) ;
311290 assert_eq ! ( document. contents, "a𐐀bar" ) ;
312291 }
313292}
0 commit comments