@@ -7,6 +7,7 @@ use std::{collections::hash_map::Entry, iter, mem};
77
88use crate :: SnippetCap ;
99use base_db:: { AnchoredPathBuf , FileId } ;
10+ use itertools:: Itertools ;
1011use nohash_hasher:: IntMap ;
1112use stdx:: never;
1213use syntax:: {
@@ -17,7 +18,7 @@ use text_edit::{TextEdit, TextEditBuilder};
1718
1819#[ derive( Default , Debug , Clone ) ]
1920pub struct SourceChange {
20- pub source_file_edits : IntMap < FileId , TextEdit > ,
21+ pub source_file_edits : IntMap < FileId , ( TextEdit , Option < SnippetEdit > ) > ,
2122 pub file_system_edits : Vec < FileSystemEdit > ,
2223 pub is_snippet : bool ,
2324}
@@ -26,28 +27,47 @@ impl SourceChange {
2627 /// Creates a new SourceChange with the given label
2728 /// from the edits.
2829 pub fn from_edits (
29- source_file_edits : IntMap < FileId , TextEdit > ,
30+ source_file_edits : IntMap < FileId , ( TextEdit , Option < SnippetEdit > ) > ,
3031 file_system_edits : Vec < FileSystemEdit > ,
3132 ) -> Self {
3233 SourceChange { source_file_edits, file_system_edits, is_snippet : false }
3334 }
3435
3536 pub fn from_text_edit ( file_id : FileId , edit : TextEdit ) -> Self {
3637 SourceChange {
37- source_file_edits : iter:: once ( ( file_id, edit) ) . collect ( ) ,
38+ source_file_edits : iter:: once ( ( file_id, ( edit, None ) ) ) . collect ( ) ,
3839 ..Default :: default ( )
3940 }
4041 }
4142
4243 /// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing
4344 /// edits for a file if some already exist.
4445 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+ ) {
4557 match self . source_file_edits . entry ( file_id) {
4658 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+ }
4868 }
4969 Entry :: Vacant ( entry) => {
50- entry. insert ( edit) ;
70+ entry. insert ( ( edit, snippet_edit ) ) ;
5171 }
5272 }
5373 }
@@ -57,7 +77,7 @@ impl SourceChange {
5777 }
5878
5979 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 )
6181 }
6282
6383 pub fn merge ( mut self , other : SourceChange ) -> SourceChange {
@@ -70,7 +90,18 @@ impl SourceChange {
7090
7191impl Extend < ( FileId , TextEdit ) > for SourceChange {
7292 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+ } ) ;
74105 }
75106}
76107
@@ -82,6 +113,14 @@ impl Extend<FileSystemEdit> for SourceChange {
82113
83114impl From < IntMap < FileId , TextEdit > > for SourceChange {
84115 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 {
85124 SourceChange { source_file_edits, file_system_edits : Vec :: new ( ) , is_snippet : false }
86125 }
87126}
@@ -94,6 +133,73 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange {
94133 }
95134}
96135
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+
97203pub struct SourceChangeBuilder {
98204 pub edit : TextEditBuilder ,
99205 pub file_id : FileId ,
@@ -275,6 +381,16 @@ impl SourceChangeBuilder {
275381
276382 pub fn finish ( mut self ) -> SourceChange {
277383 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+
278394 mem:: take ( & mut self . source_change )
279395 }
280396}
@@ -296,6 +412,13 @@ impl From<FileSystemEdit> for SourceChange {
296412 }
297413}
298414
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+
299422enum PlaceSnippet {
300423 /// Place a tabstop before an element
301424 Before ( SyntaxElement ) ,
0 commit comments