@@ -10,8 +10,8 @@ use ide::{
10
10
CompletionItemKind , CompletionRelevance , Documentation , FileId , FileRange , FileSystemEdit ,
11
11
Fold , FoldKind , Highlight , HlMod , HlOperator , HlPunct , HlRange , HlTag , Indel , InlayHint ,
12
12
InlayHintLabel , InlayHintLabelPart , InlayKind , Markup , NavigationTarget , ReferenceCategory ,
13
- RenameError , Runnable , Severity , SignatureHelp , SourceChange , StructureNodeKind , SymbolKind ,
14
- TextEdit , TextRange , TextSize ,
13
+ RenameError , Runnable , Severity , SignatureHelp , SnippetEdit , SourceChange , StructureNodeKind ,
14
+ SymbolKind , TextEdit , TextRange , TextSize ,
15
15
} ;
16
16
use itertools:: Itertools ;
17
17
use serde_json:: to_value;
@@ -22,7 +22,7 @@ use crate::{
22
22
config:: { CallInfoConfig , Config } ,
23
23
global_state:: GlobalStateSnapshot ,
24
24
line_index:: { LineEndings , LineIndex , PositionEncoding } ,
25
- lsp_ext,
25
+ lsp_ext:: { self , SnippetTextEdit } ,
26
26
lsp_utils:: invalid_params_error,
27
27
semantic_tokens:: { self , standard_fallback_type} ,
28
28
} ;
@@ -884,16 +884,135 @@ fn outside_workspace_annotation_id() -> String {
884
884
String :: from ( "OutsideWorkspace" )
885
885
}
886
886
887
+ fn merge_text_and_snippet_edit (
888
+ line_index : & LineIndex ,
889
+ edit : TextEdit ,
890
+ snippet_edit : Option < SnippetEdit > ,
891
+ ) -> Vec < SnippetTextEdit > {
892
+ let Some ( snippet_edit) = snippet_edit else {
893
+ return edit. into_iter ( ) . map ( |it| snippet_text_edit ( & line_index, false , it) ) . collect ( ) ;
894
+ } ;
895
+
896
+ let mut edits: Vec < SnippetTextEdit > = vec ! [ ] ;
897
+ let mut snippets = snippet_edit. into_edit_ranges ( ) . into_iter ( ) . peekable ( ) ;
898
+ let mut text_edits = edit. into_iter ( ) ;
899
+
900
+ while let Some ( current_indel) = text_edits. next ( ) {
901
+ let new_range = {
902
+ let insert_len =
903
+ TextSize :: try_from ( current_indel. insert . len ( ) ) . unwrap_or ( TextSize :: from ( u32:: MAX ) ) ;
904
+ TextRange :: at ( current_indel. delete . start ( ) , insert_len)
905
+ } ;
906
+
907
+ // insert any snippets before the text edit
908
+ let first_snippet_in_or_after_edit = loop {
909
+ let Some ( ( snippet_index, snippet_range) ) = snippets. peek ( ) else { break None } ;
910
+
911
+ // check if we're entirely before the range
912
+ // only possible for tabstops
913
+ if snippet_range. end ( ) < new_range. start ( )
914
+ && stdx:: always!(
915
+ snippet_range. is_empty( ) ,
916
+ "placeholder range is before any text edits"
917
+ )
918
+ {
919
+ let range = range ( & line_index, * snippet_range) ;
920
+ let new_text = format ! ( "${snippet_index}" ) ;
921
+
922
+ edits. push ( SnippetTextEdit {
923
+ range,
924
+ new_text,
925
+ insert_text_format : Some ( lsp_types:: InsertTextFormat :: SNIPPET ) ,
926
+ annotation_id : None ,
927
+ } )
928
+ } else {
929
+ break Some ( ( snippet_index, snippet_range) ) ;
930
+ }
931
+ } ;
932
+
933
+ if first_snippet_in_or_after_edit
934
+ . is_some_and ( |( _, range) | new_range. intersect ( * range) . is_some ( ) )
935
+ {
936
+ // at least one snippet edit intersects this text edit,
937
+ // so gather all of the edits that intersect this text edit
938
+ let mut all_snippets = snippets
939
+ . take_while_ref ( |( _, range) | new_range. intersect ( * range) . is_some ( ) )
940
+ . collect_vec ( ) ;
941
+
942
+ // ensure all of the ranges are wholly contained inside of the new range
943
+ all_snippets. retain ( |( _, range) | {
944
+ stdx:: always!(
945
+ new_range. contains_range( * range) ,
946
+ "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}" , range, new_range
947
+ )
948
+ } ) ;
949
+
950
+ let mut text_edit = text_edit ( line_index, current_indel) ;
951
+
952
+ // escape out snippet text
953
+ stdx:: replace ( & mut text_edit. new_text , '\\' , r"\\" ) ;
954
+ stdx:: replace ( & mut text_edit. new_text , '$' , r"\$" ) ;
955
+
956
+ // ...and apply!
957
+ for ( index, range) in all_snippets. iter ( ) . rev ( ) {
958
+ let start = ( range. start ( ) - new_range. start ( ) ) . into ( ) ;
959
+ let end = ( range. end ( ) - new_range. start ( ) ) . into ( ) ;
960
+
961
+ if range. is_empty ( ) {
962
+ text_edit. new_text . insert_str ( start, & format ! ( "${index}" ) ) ;
963
+ } else {
964
+ text_edit. new_text . insert ( end, '}' ) ;
965
+ text_edit. new_text . insert_str ( start, & format ! ( "${{{index}" ) ) ;
966
+ }
967
+ }
968
+
969
+ edits. push ( SnippetTextEdit {
970
+ range : text_edit. range ,
971
+ new_text : text_edit. new_text ,
972
+ insert_text_format : Some ( lsp_types:: InsertTextFormat :: SNIPPET ) ,
973
+ annotation_id : None ,
974
+ } )
975
+ } else {
976
+ // snippet edit was beyond the current one
977
+ // since it wasn't consumed, it's available for the next pass
978
+ edits. push ( snippet_text_edit ( line_index, false , current_indel) ) ;
979
+ }
980
+ }
981
+
982
+ // insert any remaining edits
983
+ // either one of the two or both should've run out at this point,
984
+ // so it's either a tail of text edits or tabstops
985
+ edits. extend ( text_edits. map ( |indel| snippet_text_edit ( line_index, false , indel) ) ) ;
986
+ edits. extend ( snippets. map ( |( snippet_index, snippet_range) | {
987
+ stdx:: always!(
988
+ snippet_range. is_empty( ) ,
989
+ "found placeholder snippet {:?} without a text edit" ,
990
+ snippet_range
991
+ ) ;
992
+
993
+ let range = range ( & line_index, snippet_range) ;
994
+ let new_text = format ! ( "${snippet_index}" ) ;
995
+
996
+ SnippetTextEdit {
997
+ range,
998
+ new_text,
999
+ insert_text_format : Some ( lsp_types:: InsertTextFormat :: SNIPPET ) ,
1000
+ annotation_id : None ,
1001
+ }
1002
+ } ) ) ;
1003
+
1004
+ edits
1005
+ }
1006
+
887
1007
pub ( crate ) fn snippet_text_document_edit (
888
1008
snap : & GlobalStateSnapshot ,
889
- is_snippet : bool ,
890
1009
file_id : FileId ,
891
1010
edit : TextEdit ,
1011
+ snippet_edit : Option < SnippetEdit > ,
892
1012
) -> Cancellable < lsp_ext:: SnippetTextDocumentEdit > {
893
1013
let text_document = optional_versioned_text_document_identifier ( snap, file_id) ;
894
1014
let line_index = snap. file_line_index ( file_id) ?;
895
- let mut edits: Vec < _ > =
896
- edit. into_iter ( ) . map ( |it| snippet_text_edit ( & line_index, is_snippet, it) ) . collect ( ) ;
1015
+ let mut edits = merge_text_and_snippet_edit ( & line_index, edit, snippet_edit) ;
897
1016
898
1017
if snap. analysis . is_library_file ( file_id) ? && snap. config . change_annotation_support ( ) {
899
1018
for edit in & mut edits {
@@ -973,8 +1092,13 @@ pub(crate) fn snippet_workspace_edit(
973
1092
let ops = snippet_text_document_ops ( snap, op) ?;
974
1093
document_changes. extend_from_slice ( & ops) ;
975
1094
}
976
- for ( file_id, ( edit, _snippet_edit) ) in source_change. source_file_edits {
977
- let edit = snippet_text_document_edit ( snap, source_change. is_snippet , file_id, edit) ?;
1095
+ for ( file_id, ( edit, snippet_edit) ) in source_change. source_file_edits {
1096
+ let edit = snippet_text_document_edit (
1097
+ snap,
1098
+ file_id,
1099
+ edit,
1100
+ snippet_edit. filter ( |_| source_change. is_snippet ) ,
1101
+ ) ?;
978
1102
document_changes. push ( lsp_ext:: SnippetDocumentChangeOperation :: Edit ( edit) ) ;
979
1103
}
980
1104
let mut workspace_edit = lsp_ext:: SnippetWorkspaceEdit {
0 commit comments