@@ -6,9 +6,12 @@ mod subscriptions;
6
6
pub ( crate ) mod pending_requests;
7
7
8
8
use std:: {
9
+ borrow:: Cow ,
9
10
env,
10
11
error:: Error ,
11
- fmt, panic,
12
+ fmt,
13
+ ops:: Range ,
14
+ panic,
12
15
path:: PathBuf ,
13
16
sync:: Arc ,
14
17
time:: { Duration , Instant } ,
@@ -18,11 +21,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18
21
use itertools:: Itertools ;
19
22
use lsp_server:: { Connection , ErrorCode , Message , Notification , Request , RequestId , Response } ;
20
23
use lsp_types:: {
21
- NumberOrString , WorkDoneProgress , WorkDoneProgressBegin , WorkDoneProgressCreateParams ,
22
- WorkDoneProgressEnd , WorkDoneProgressReport ,
24
+ DidChangeTextDocumentParams , NumberOrString , TextDocumentContentChangeEvent , WorkDoneProgress ,
25
+ WorkDoneProgressBegin , WorkDoneProgressCreateParams , WorkDoneProgressEnd ,
26
+ WorkDoneProgressReport ,
23
27
} ;
24
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
30
use ra_prof:: profile;
27
31
use ra_project_model:: { PackageRoot , ProjectWorkspace } ;
28
32
use ra_vfs:: { VfsFile , VfsTask , Watch } ;
@@ -33,6 +37,7 @@ use threadpool::ThreadPool;
33
37
34
38
use crate :: {
35
39
config:: { Config , FilesWatcher } ,
40
+ conv:: { ConvWith , TryConvWith } ,
36
41
diagnostics:: DiagnosticTask ,
37
42
main_loop:: {
38
43
pending_requests:: { PendingRequest , PendingRequests } ,
@@ -579,12 +584,16 @@ fn on_notification(
579
584
Err ( not) => not,
580
585
} ;
581
586
let not = match notification_cast :: < req:: DidChangeTextDocument > ( not) {
582
- Ok ( mut params) => {
583
- let uri = params. text_document . uri ;
587
+ Ok ( params) => {
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
593
let path = uri. to_file_path ( ) . map_err ( |( ) | format ! ( "invalid uri: {}" , uri) ) ?;
585
- let text =
586
- params . content_changes . pop ( ) . ok_or_else ( || "empty changes" . to_string ( ) ) ? . text ;
587
- state . vfs . write ( ) . change_file_overlay ( path . as_path ( ) , text ) ;
594
+ state . vfs . write ( ) . change_file_overlay ( & path , |old_text| {
595
+ apply_document_changes ( old_text , Cow :: Borrowed ( & line_index ) , content_changes ) ;
596
+ } ) ;
588
597
return Ok ( ( ) ) ;
589
598
}
590
599
Err ( not) => not,
@@ -653,6 +662,48 @@ fn on_notification(
653
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
707
fn on_check_task (
657
708
task : CheckTask ,
658
709
world_state : & mut WorldState ,
@@ -958,3 +1009,64 @@ where
958
1009
{
959
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 => "\n dream" ] ) ;
1057
+ assert_eq ! ( text, "quick foxes\n dream" ) ;
1058
+ run ( & mut text, c ! [ 1 , 0 ; 1 , 0 => "have " ] ) ;
1059
+ assert_eq ! ( text, "quick foxes\n have 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\n have 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 \n have 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\n DREAM\n they have quiet dreams\n DON'T THEY?\n " ) ;
1069
+ run ( & mut text, c ! [ 0 , 10 ; 1 , 5 => "" , 2 , 0 ; 2 , 12 => "" ] ) ;
1070
+ assert_eq ! ( text, "the quick \n they have quiet dreams\n " ) ;
1071
+ }
1072
+ }
0 commit comments