@@ -7,6 +7,7 @@ use std::{collections::hash_map::Entry, iter, mem};
7
7
8
8
use crate :: SnippetCap ;
9
9
use base_db:: { AnchoredPathBuf , FileId } ;
10
+ use itertools:: Itertools ;
10
11
use nohash_hasher:: IntMap ;
11
12
use stdx:: never;
12
13
use syntax:: {
@@ -17,7 +18,7 @@ use text_edit::{TextEdit, TextEditBuilder};
17
18
18
19
#[ derive( Default , Debug , Clone ) ]
19
20
pub struct SourceChange {
20
- pub source_file_edits : IntMap < FileId , TextEdit > ,
21
+ pub source_file_edits : IntMap < FileId , ( TextEdit , Option < SnippetEdit > ) > ,
21
22
pub file_system_edits : Vec < FileSystemEdit > ,
22
23
pub is_snippet : bool ,
23
24
}
@@ -26,28 +27,47 @@ impl SourceChange {
26
27
/// Creates a new SourceChange with the given label
27
28
/// from the edits.
28
29
pub fn from_edits (
29
- source_file_edits : IntMap < FileId , TextEdit > ,
30
+ source_file_edits : IntMap < FileId , ( TextEdit , Option < SnippetEdit > ) > ,
30
31
file_system_edits : Vec < FileSystemEdit > ,
31
32
) -> Self {
32
33
SourceChange { source_file_edits, file_system_edits, is_snippet : false }
33
34
}
34
35
35
36
pub fn from_text_edit ( file_id : FileId , edit : TextEdit ) -> Self {
36
37
SourceChange {
37
- source_file_edits : iter:: once ( ( file_id, edit) ) . collect ( ) ,
38
+ source_file_edits : iter:: once ( ( file_id, ( edit, None ) ) ) . collect ( ) ,
38
39
..Default :: default ( )
39
40
}
40
41
}
41
42
42
43
/// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing
43
44
/// edits for a file if some already exist.
44
45
pub fn insert_source_edit ( & mut self , file_id : FileId , edit : TextEdit ) {
46
+ self . insert_source_and_snippet_edit ( file_id, edit, None )
47
+ }
48
+
49
+ /// Inserts a [`TextEdit`] and potentially a [`SnippetEdit`] for the given [`FileId`].
50
+ /// This properly handles merging existing edits for a file if some already exist.
51
+ pub fn insert_source_and_snippet_edit (
52
+ & mut self ,
53
+ file_id : FileId ,
54
+ edit : TextEdit ,
55
+ snippet_edit : Option < SnippetEdit > ,
56
+ ) {
45
57
match self . source_file_edits . entry ( file_id) {
46
58
Entry :: Occupied ( mut entry) => {
47
- never ! ( entry. get_mut( ) . union ( edit) . is_err( ) , "overlapping edits for same file" ) ;
59
+ let value = entry. get_mut ( ) ;
60
+ never ! ( value. 0 . union ( edit) . is_err( ) , "overlapping edits for same file" ) ;
61
+ never ! (
62
+ value. 1 . is_some( ) && snippet_edit. is_some( ) ,
63
+ "overlapping snippet edits for same file"
64
+ ) ;
65
+ if value. 1 . is_none ( ) {
66
+ value. 1 = snippet_edit;
67
+ }
48
68
}
49
69
Entry :: Vacant ( entry) => {
50
- entry. insert ( edit) ;
70
+ entry. insert ( ( edit, snippet_edit ) ) ;
51
71
}
52
72
}
53
73
}
@@ -57,7 +77,7 @@ impl SourceChange {
57
77
}
58
78
59
79
pub fn get_source_edit ( & self , file_id : FileId ) -> Option < & TextEdit > {
60
- self . source_file_edits . get ( & file_id)
80
+ self . source_file_edits . get ( & file_id) . map ( | ( edit , _ ) | edit )
61
81
}
62
82
63
83
pub fn merge ( mut self , other : SourceChange ) -> SourceChange {
@@ -70,7 +90,18 @@ impl SourceChange {
70
90
71
91
impl Extend < ( FileId , TextEdit ) > for SourceChange {
72
92
fn extend < T : IntoIterator < Item = ( FileId , TextEdit ) > > ( & mut self , iter : T ) {
73
- iter. into_iter ( ) . for_each ( |( file_id, edit) | self . insert_source_edit ( file_id, edit) ) ;
93
+ self . extend ( iter. into_iter ( ) . map ( |( file_id, edit) | ( file_id, ( edit, None ) ) ) )
94
+ }
95
+ }
96
+
97
+ impl Extend < ( FileId , ( TextEdit , Option < SnippetEdit > ) ) > for SourceChange {
98
+ fn extend < T : IntoIterator < Item = ( FileId , ( TextEdit , Option < SnippetEdit > ) ) > > (
99
+ & mut self ,
100
+ iter : T ,
101
+ ) {
102
+ iter. into_iter ( ) . for_each ( |( file_id, ( edit, snippet_edit) ) | {
103
+ self . insert_source_and_snippet_edit ( file_id, edit, snippet_edit)
104
+ } ) ;
74
105
}
75
106
}
76
107
@@ -82,6 +113,14 @@ impl Extend<FileSystemEdit> for SourceChange {
82
113
83
114
impl From < IntMap < FileId , TextEdit > > for SourceChange {
84
115
fn from ( source_file_edits : IntMap < FileId , TextEdit > ) -> SourceChange {
116
+ let source_file_edits =
117
+ source_file_edits. into_iter ( ) . map ( |( file_id, edit) | ( file_id, ( edit, None ) ) ) . collect ( ) ;
118
+ SourceChange { source_file_edits, file_system_edits : Vec :: new ( ) , is_snippet : false }
119
+ }
120
+ }
121
+
122
+ impl From < IntMap < FileId , ( TextEdit , Option < SnippetEdit > ) > > for SourceChange {
123
+ fn from ( source_file_edits : IntMap < FileId , ( TextEdit , Option < SnippetEdit > ) > ) -> SourceChange {
85
124
SourceChange { source_file_edits, file_system_edits : Vec :: new ( ) , is_snippet : false }
86
125
}
87
126
}
@@ -94,6 +133,73 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange {
94
133
}
95
134
}
96
135
136
+ impl FromIterator < ( FileId , ( TextEdit , Option < SnippetEdit > ) ) > for SourceChange {
137
+ fn from_iter < T : IntoIterator < Item = ( FileId , ( TextEdit , Option < SnippetEdit > ) ) > > (
138
+ iter : T ,
139
+ ) -> Self {
140
+ let mut this = SourceChange :: default ( ) ;
141
+ this. extend ( iter) ;
142
+ this
143
+ }
144
+ }
145
+
146
+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
147
+ pub struct SnippetEdit ( Vec < ( u32 , TextRange ) > ) ;
148
+
149
+ impl SnippetEdit {
150
+ fn new ( snippets : Vec < Snippet > ) -> Self {
151
+ let mut snippet_ranges = snippets
152
+ . into_iter ( )
153
+ . zip ( 1 ..)
154
+ . with_position ( )
155
+ . map ( |pos| {
156
+ let ( snippet, index) = match pos {
157
+ itertools:: Position :: First ( it) | itertools:: Position :: Middle ( it) => it,
158
+ // last/only snippet gets index 0
159
+ itertools:: Position :: Last ( ( snippet, _) )
160
+ | itertools:: Position :: Only ( ( snippet, _) ) => ( snippet, 0 ) ,
161
+ } ;
162
+
163
+ let range = match snippet {
164
+ Snippet :: Tabstop ( pos) => TextRange :: empty ( pos) ,
165
+ Snippet :: Placeholder ( range) => range,
166
+ } ;
167
+ ( index, range)
168
+ } )
169
+ . collect_vec ( ) ;
170
+
171
+ snippet_ranges. sort_by_key ( |( _, range) | range. start ( ) ) ;
172
+
173
+ // Ensure that none of the ranges overlap
174
+ let disjoint_ranges =
175
+ snippet_ranges. windows ( 2 ) . all ( |ranges| ranges[ 0 ] . 1 . end ( ) <= ranges[ 1 ] . 1 . start ( ) ) ;
176
+ stdx:: always!( disjoint_ranges) ;
177
+
178
+ SnippetEdit ( snippet_ranges)
179
+ }
180
+
181
+ /// Inserts all of the snippets into the given text.
182
+ pub fn apply ( & self , text : & mut String ) {
183
+ // Start from the back so that we don't have to adjust ranges
184
+ for ( index, range) in self . 0 . iter ( ) . rev ( ) {
185
+ if range. is_empty ( ) {
186
+ // is a tabstop
187
+ text. insert_str ( range. start ( ) . into ( ) , & format ! ( "${index}" ) ) ;
188
+ } else {
189
+ // is a placeholder
190
+ text. insert ( range. end ( ) . into ( ) , '}' ) ;
191
+ text. insert_str ( range. start ( ) . into ( ) , & format ! ( "${{{index}:" ) ) ;
192
+ }
193
+ }
194
+ }
195
+
196
+ /// Gets the underlying snippet index + text range
197
+ /// Tabstops are represented by an empty range, and placeholders use the range that they were given
198
+ pub fn into_edit_ranges ( self ) -> Vec < ( u32 , TextRange ) > {
199
+ self . 0
200
+ }
201
+ }
202
+
97
203
pub struct SourceChangeBuilder {
98
204
pub edit : TextEditBuilder ,
99
205
pub file_id : FileId ,
@@ -275,6 +381,16 @@ impl SourceChangeBuilder {
275
381
276
382
pub fn finish ( mut self ) -> SourceChange {
277
383
self . commit ( ) ;
384
+
385
+ // Only one file can have snippet edits
386
+ stdx:: never!( self
387
+ . source_change
388
+ . source_file_edits
389
+ . iter( )
390
+ . filter( |( _, ( _, snippet_edit) ) | snippet_edit. is_some( ) )
391
+ . at_most_one( )
392
+ . is_err( ) ) ;
393
+
278
394
mem:: take ( & mut self . source_change )
279
395
}
280
396
}
@@ -296,6 +412,13 @@ impl From<FileSystemEdit> for SourceChange {
296
412
}
297
413
}
298
414
415
+ enum Snippet {
416
+ /// A tabstop snippet (e.g. `$0`).
417
+ Tabstop ( TextSize ) ,
418
+ /// A placeholder snippet (e.g. `${0:placeholder}`).
419
+ Placeholder ( TextRange ) ,
420
+ }
421
+
299
422
enum PlaceSnippet {
300
423
/// Place a tabstop before an element
301
424
Before ( SyntaxElement ) ,
0 commit comments