@@ -7,7 +7,7 @@ use std::{collections::hash_map::Entry, iter, mem};
7
7
8
8
use base_db:: { AnchoredPathBuf , FileId } ;
9
9
use stdx:: { hash:: NoHashHashMap , never} ;
10
- use syntax:: { algo, AstNode , SyntaxNode , SyntaxNodePtr , TextRange , TextSize } ;
10
+ use syntax:: { algo, ast , ted , AstNode , SyntaxNode , SyntaxNodePtr , TextRange , TextSize } ;
11
11
use text_edit:: { TextEdit , TextEditBuilder } ;
12
12
13
13
use crate :: SnippetCap ;
@@ -99,13 +99,21 @@ pub struct SourceChangeBuilder {
99
99
100
100
/// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
101
101
pub mutated_tree : Option < TreeMutator > ,
102
+ /// Keeps track of where to place snippets
103
+ pub snippet_builder : Option < SnippetBuilder > ,
102
104
}
103
105
104
106
pub struct TreeMutator {
105
107
immutable : SyntaxNode ,
106
108
mutable_clone : SyntaxNode ,
107
109
}
108
110
111
+ #[ derive( Default ) ]
112
+ pub struct SnippetBuilder {
113
+ /// Where to place snippets at
114
+ places : Vec < PlaceSnippet > ,
115
+ }
116
+
109
117
impl TreeMutator {
110
118
pub fn new ( immutable : & SyntaxNode ) -> TreeMutator {
111
119
let immutable = immutable. ancestors ( ) . last ( ) . unwrap ( ) ;
@@ -131,6 +139,7 @@ impl SourceChangeBuilder {
131
139
source_change : SourceChange :: default ( ) ,
132
140
trigger_signature_help : false ,
133
141
mutated_tree : None ,
142
+ snippet_builder : None ,
134
143
}
135
144
}
136
145
@@ -140,6 +149,17 @@ impl SourceChangeBuilder {
140
149
}
141
150
142
151
fn commit ( & mut self ) {
152
+ // Render snippets first so that they get bundled into the tree diff
153
+ if let Some ( mut snippets) = self . snippet_builder . take ( ) {
154
+ // Last snippet always has stop index 0
155
+ let last_stop = snippets. places . pop ( ) . unwrap ( ) ;
156
+ last_stop. place ( 0 ) ;
157
+
158
+ for ( index, stop) in snippets. places . into_iter ( ) . enumerate ( ) {
159
+ stop. place ( index + 1 )
160
+ }
161
+ }
162
+
143
163
if let Some ( tm) = self . mutated_tree . take ( ) {
144
164
algo:: diff ( & tm. immutable , & tm. mutable_clone ) . into_text_edit ( & mut self . edit )
145
165
}
@@ -214,6 +234,33 @@ impl SourceChangeBuilder {
214
234
self . trigger_signature_help = true ;
215
235
}
216
236
237
+ /// Adds a tabstop snippet to place the cursor before `node`
238
+ pub fn add_tabstop_before ( & mut self , _cap : SnippetCap , node : impl AstNode ) {
239
+ assert ! ( node. syntax( ) . parent( ) . is_some( ) ) ;
240
+
241
+ let snippet_builder = self . snippet_builder . get_or_insert ( SnippetBuilder { places : vec ! [ ] } ) ;
242
+ snippet_builder. places . push ( PlaceSnippet :: Before ( node. syntax ( ) . clone ( ) ) ) ;
243
+ self . source_change . is_snippet = true ;
244
+ }
245
+
246
+ /// Adds a tabstop snippet to place the cursor after `node`
247
+ pub fn add_tabstop_after ( & mut self , _cap : SnippetCap , node : impl AstNode ) {
248
+ assert ! ( node. syntax( ) . parent( ) . is_some( ) ) ;
249
+
250
+ let snippet_builder = self . snippet_builder . get_or_insert ( SnippetBuilder { places : vec ! [ ] } ) ;
251
+ snippet_builder. places . push ( PlaceSnippet :: After ( node. syntax ( ) . clone ( ) ) ) ;
252
+ self . source_change . is_snippet = true ;
253
+ }
254
+
255
+ /// Adds a snippet to move the cursor selected over `node`
256
+ pub fn add_placeholder_snippet ( & mut self , _cap : SnippetCap , node : impl AstNode ) {
257
+ assert ! ( node. syntax( ) . parent( ) . is_some( ) ) ;
258
+
259
+ let snippet_builder = self . snippet_builder . get_or_insert ( SnippetBuilder { places : vec ! [ ] } ) ;
260
+ snippet_builder. places . push ( PlaceSnippet :: Over ( node. syntax ( ) . clone ( ) ) ) ;
261
+ self . source_change . is_snippet = true ;
262
+ }
263
+
217
264
pub fn finish ( mut self ) -> SourceChange {
218
265
self . commit ( ) ;
219
266
mem:: take ( & mut self . source_change )
@@ -236,3 +283,66 @@ impl From<FileSystemEdit> for SourceChange {
236
283
}
237
284
}
238
285
}
286
+
287
+ enum PlaceSnippet {
288
+ /// Place a tabstop before a node
289
+ Before ( SyntaxNode ) ,
290
+ /// Place a tabstop before a node
291
+ After ( SyntaxNode ) ,
292
+ /// Place a placeholder snippet in place of the node
293
+ Over ( SyntaxNode ) ,
294
+ }
295
+
296
+ impl PlaceSnippet {
297
+ /// Places the snippet before or over a node with the given tab stop index
298
+ fn place ( self , order : usize ) {
299
+ // ensure the target node is still attached
300
+ match & self {
301
+ PlaceSnippet :: Before ( node) | PlaceSnippet :: After ( node) | PlaceSnippet :: Over ( node) => {
302
+ // node should still be in the tree, but if it isn't
303
+ // then it's okay to just ignore this place
304
+ if stdx:: never!( node. parent( ) . is_none( ) ) {
305
+ return ;
306
+ }
307
+ }
308
+ }
309
+
310
+ match self {
311
+ PlaceSnippet :: Before ( node) => {
312
+ ted:: insert_raw ( ted:: Position :: before ( & node) , Self :: make_tab_stop ( order) ) ;
313
+ }
314
+ PlaceSnippet :: After ( node) => {
315
+ ted:: insert_raw ( ted:: Position :: after ( & node) , Self :: make_tab_stop ( order) ) ;
316
+ }
317
+ PlaceSnippet :: Over ( node) => {
318
+ let position = ted:: Position :: before ( & node) ;
319
+ node. detach ( ) ;
320
+
321
+ let snippet = ast:: SourceFile :: parse ( & format ! ( "${{{order}:_}}" ) )
322
+ . syntax_node ( )
323
+ . clone_for_update ( ) ;
324
+
325
+ let placeholder =
326
+ snippet. descendants ( ) . find_map ( ast:: UnderscoreExpr :: cast) . unwrap ( ) ;
327
+ ted:: replace ( placeholder. syntax ( ) , node) ;
328
+
329
+ ted:: insert_raw ( position, snippet) ;
330
+ }
331
+ }
332
+ }
333
+
334
+ fn make_tab_stop ( order : usize ) -> SyntaxNode {
335
+ let stop = ast:: SourceFile :: parse ( & format ! ( "stop!(${order})" ) )
336
+ . syntax_node ( )
337
+ . descendants ( )
338
+ . find_map ( ast:: TokenTree :: cast)
339
+ . unwrap ( )
340
+ . syntax ( )
341
+ . clone_for_update ( ) ;
342
+
343
+ stop. first_token ( ) . unwrap ( ) . detach ( ) ;
344
+ stop. last_token ( ) . unwrap ( ) . detach ( ) ;
345
+
346
+ stop
347
+ }
348
+ }
0 commit comments