@@ -7,7 +7,7 @@ use std::{collections::hash_map::Entry, iter, mem};
77
88use base_db:: { AnchoredPathBuf , FileId } ;
99use stdx:: { hash:: NoHashHashMap , never} ;
10- use syntax:: { algo, AstNode , SyntaxNode , SyntaxNodePtr , TextRange , TextSize } ;
10+ use syntax:: { algo, ast , ted , AstNode , SyntaxNode , SyntaxNodePtr , TextRange , TextSize } ;
1111use text_edit:: { TextEdit , TextEditBuilder } ;
1212
1313use crate :: SnippetCap ;
@@ -99,13 +99,21 @@ pub struct SourceChangeBuilder {
9999
100100 /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
101101 pub mutated_tree : Option < TreeMutator > ,
102+ /// Keeps track of where to place snippets
103+ pub snippet_builder : Option < SnippetBuilder > ,
102104}
103105
104106pub struct TreeMutator {
105107 immutable : SyntaxNode ,
106108 mutable_clone : SyntaxNode ,
107109}
108110
111+ #[ derive( Default ) ]
112+ pub struct SnippetBuilder {
113+ /// Where to place snippets at
114+ places : Vec < PlaceSnippet > ,
115+ }
116+
109117impl TreeMutator {
110118 pub fn new ( immutable : & SyntaxNode ) -> TreeMutator {
111119 let immutable = immutable. ancestors ( ) . last ( ) . unwrap ( ) ;
@@ -131,6 +139,7 @@ impl SourceChangeBuilder {
131139 source_change : SourceChange :: default ( ) ,
132140 trigger_signature_help : false ,
133141 mutated_tree : None ,
142+ snippet_builder : None ,
134143 }
135144 }
136145
@@ -140,6 +149,17 @@ impl SourceChangeBuilder {
140149 }
141150
142151 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+
143163 if let Some ( tm) = self . mutated_tree . take ( ) {
144164 algo:: diff ( & tm. immutable , & tm. mutable_clone ) . into_text_edit ( & mut self . edit )
145165 }
@@ -214,6 +234,33 @@ impl SourceChangeBuilder {
214234 self . trigger_signature_help = true ;
215235 }
216236
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+
217264 pub fn finish ( mut self ) -> SourceChange {
218265 self . commit ( ) ;
219266 mem:: take ( & mut self . source_change )
@@ -236,3 +283,66 @@ impl From<FileSystemEdit> for SourceChange {
236283 }
237284 }
238285}
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