1313
1414package org .eclipse .jdt .ls .core .internal .handlers ;
1515
16+ import java .util .ArrayList ;
17+ import java .util .Iterator ;
18+ import java .util .List ;
1619import java .util .Map ;
1720
1821import org .eclipse .core .runtime .CoreException ;
1922import org .eclipse .core .runtime .IProgressMonitor ;
23+ import org .eclipse .jdt .core .IBuffer ;
24+ import org .eclipse .jdt .core .ICompilationUnit ;
25+ import org .eclipse .jdt .core .IJavaElement ;
2026import org .eclipse .jdt .core .manipulation .ChangeCorrectionProposalCore ;
27+ import org .eclipse .jdt .internal .corext .fix .LinkedProposalModelCore ;
28+ import org .eclipse .jdt .internal .corext .fix .LinkedProposalPositionGroupCore ;
29+ import org .eclipse .jdt .internal .corext .fix .LinkedProposalPositionGroupCore .PositionInformation ;
30+ import org .eclipse .jdt .internal .corext .fix .LinkedProposalPositionGroupCore .ProposalCore ;
31+ import org .eclipse .jdt .internal .ui .text .correction .proposals .LinkedCorrectionProposalCore ;
2132import org .eclipse .jdt .ls .core .internal .ChangeUtil ;
2233import org .eclipse .jdt .ls .core .internal .JSONUtility ;
2334import org .eclipse .jdt .ls .core .internal .JavaLanguageServerPlugin ;
35+ import org .eclipse .jface .text .link .LinkedModeModel ;
36+ import org .eclipse .jface .text .link .LinkedPosition ;
2437import org .eclipse .lsp4j .CodeAction ;
38+ import org .eclipse .lsp4j .Range ;
39+ import org .eclipse .lsp4j .TextEdit ;
2540import org .eclipse .lsp4j .WorkspaceEdit ;
41+ import org .eclipse .lsp4j .extended .SnippetTextEdit ;
2642import org .eclipse .lsp4j .jsonrpc .messages .Either ;
43+ import org .eclipse .text .edits .MultiTextEdit ;
44+ import org .eclipse .text .edits .ReplaceEdit ;
2745
2846public class CodeActionResolveHandler {
2947 public static final String DATA_FIELD_REQUEST_ID = "rid" ;
3048 public static final String DATA_FIELD_PROPOSAL_ID = "pid" ;
49+ public static final String SNIPPET_PREFIX = "${" ;
50+ public static final char SNIPPET_CHOICE_INDICATOR = '|' ;
51+ public static final String SNIPPET_CHOICE_POSTFIX = "|}" ;
52+ public static final String SNIPPET_CHOICE_DELIMITER = "," ;
3153
3254 public CodeAction resolve (CodeAction params , IProgressMonitor monitor ) {
3355 Map <String , String > data = JSONUtility .toModel (params .getData (), Map .class );
@@ -46,8 +68,10 @@ public CodeAction resolve(CodeAction params, IProgressMonitor monitor) {
4668
4769 try {
4870 Either <ChangeCorrectionProposalCore , CodeActionProposal > proposal = response .getProposals ().get (proposalId );
49- WorkspaceEdit edit = proposal .isLeft () ? ChangeUtil .convertToWorkspaceEdit (proposal .getLeft ().getChange ())
50- : proposal .getRight ().resolveEdit (monitor );
71+ WorkspaceEdit edit = proposal .isLeft () ? ChangeUtil .convertToWorkspaceEdit (proposal .getLeft ().getChange ()) : proposal .getRight ().resolveEdit (monitor );
72+ if (JavaLanguageServerPlugin .getPreferencesManager ().getClientPreferences ().isWorkspaceSnippetEditSupported () && proposal .isLeft () && proposal .getLeft () instanceof LinkedCorrectionProposalCore ) {
73+ addSnippetsIfApplicable ((LinkedCorrectionProposalCore ) proposal .getLeft (), edit );
74+ }
5175 if (ChangeUtil .hasChanges (edit )) {
5276 params .setEdit (edit );
5377 }
@@ -57,4 +81,108 @@ public CodeAction resolve(CodeAction params, IProgressMonitor monitor) {
5781
5882 return params ;
5983 }
84+
85+ private static final void addSnippetsIfApplicable (LinkedCorrectionProposalCore proposal , WorkspaceEdit edit ) throws CoreException {
86+ Object modifiedElement = proposal .getChange ().getModifiedElement ();
87+ ICompilationUnit compilationUnit = (ICompilationUnit ) ((IJavaElement ) modifiedElement ).getAncestor (IJavaElement .COMPILATION_UNIT );
88+ IBuffer buffer = compilationUnit .getBuffer ();
89+ LinkedProposalModelCore linkedProposals = proposal .getLinkedProposalModel ();
90+ List <Triple > snippets = new ArrayList <>();
91+ Iterator <LinkedProposalPositionGroupCore > it = linkedProposals .getPositionGroupCoreIterator ();
92+ int snippetNumber = 1 ;
93+ while (it .hasNext ()) {
94+ LinkedProposalPositionGroupCore group = it .next ();
95+ ProposalCore [] proposalList = group .getProposals ();
96+ PositionInformation [] positionList = group .getPositions ();
97+ StringBuilder snippet = new StringBuilder ();
98+ snippet .append (SNIPPET_PREFIX );
99+ snippet .append (snippetNumber );
100+ snippet .append (SNIPPET_CHOICE_INDICATOR );
101+ if (proposalList .length > 1 ) {
102+ for (int i = 0 ; i < positionList .length ; i ++) {
103+ int offset = positionList [i ].getOffset ();
104+ int length = positionList [i ].getLength ();
105+ // Create snippet on first iteration
106+ if (i == 0 ) {
107+ LinkedPosition linkedPosition = new LinkedPosition (JsonRpcHelpers .toDocument (buffer ), positionList [i ].getOffset (), positionList [i ].getLength (), positionList [i ].getSequenceRank ());
108+ for (int j = 0 ; j < proposalList .length ; j ++) {
109+ org .eclipse .text .edits .TextEdit editWithText = findReplaceOrInsertEdit (proposalList [j ].computeEdits (0 , linkedPosition , '\u0000' , 0 , new LinkedModeModel ()));
110+ if (editWithText != null ) {
111+ if (snippet .charAt (snippet .length ()-1 ) != SNIPPET_CHOICE_INDICATOR ) {
112+ snippet .append (SNIPPET_CHOICE_DELIMITER );
113+ }
114+ snippet .append (((ReplaceEdit ) editWithText ).getText ());
115+ }
116+ }
117+ // If snippet is empty or only has one choice, ignore this group
118+ if (snippet .toString ().equals (SNIPPET_PREFIX ) || snippet .indexOf (SNIPPET_CHOICE_DELIMITER ) == -1 ) {
119+ break ;
120+ }
121+ snippet .append (SNIPPET_CHOICE_POSTFIX );
122+ snippetNumber ++;
123+ }
124+ snippets .add (new Triple (snippet .toString (), offset , length ));
125+ }
126+ }
127+ }
128+ if (!snippets .isEmpty ()) {
129+ // Sort snippets in descending order based on offset, so that the edits are applied in an order that does not alter the offset of later edits
130+ snippets .sort (null );
131+ for (int i = 0 ; i < edit .getDocumentChanges ().size (); i ++) {
132+ if (edit .getDocumentChanges ().get (i ).isLeft ()) {
133+ List <TextEdit > edits = edit .getDocumentChanges ().get (i ).getLeft ().getEdits ();
134+ for (int j = 0 ; j < edits .size (); j ++) {
135+ Range editRange = edits .get (j ).getRange ();
136+ StringBuilder replacementText = new StringBuilder (edits .get (j ).getNewText ());
137+ int rangeStart = JsonRpcHelpers .toOffset (buffer , editRange .getStart ().getLine (), editRange .getStart ().getCharacter ());
138+ int rangeEnd = rangeStart + replacementText .length ();
139+ for (int k = 0 ; k < snippets .size (); k ++) {
140+ if (snippets .get (k ).offset >= rangeStart && snippets .get (k ).offset <= rangeEnd ) {
141+ int replaceStart = snippets .get (k ).offset - rangeStart ;
142+ int replaceEnd = replaceStart + snippets .get (k ).length ;
143+ replacementText .replace (replaceStart , replaceEnd , snippets .get (k ).snippet );
144+ }
145+ }
146+ SnippetTextEdit newEdit = new SnippetTextEdit (editRange , replacementText .toString ());
147+ edits .remove (j );
148+ edits .add (j , newEdit );
149+ }
150+ }
151+ }
152+ }
153+ }
154+
155+ private static final org .eclipse .text .edits .TextEdit findReplaceOrInsertEdit (org .eclipse .text .edits .TextEdit edit ) {
156+ if (edit instanceof ReplaceEdit && !((ReplaceEdit ) edit ).getText ().isBlank ()) {
157+ return edit ;
158+ }
159+
160+ if (edit instanceof MultiTextEdit ) {
161+ org .eclipse .text .edits .TextEdit [] children = edit .getChildren ();
162+ for (int i = 0 ; i < children .length ; i ++) {
163+ org .eclipse .text .edits .TextEdit child = findReplaceOrInsertEdit (children [i ]);
164+ if (child != null ) {
165+ return child ;
166+ }
167+ }
168+ }
169+ return null ;
170+ }
171+
172+ private static final class Triple implements Comparable <Triple > {
173+ public String snippet ;
174+ public int offset ;
175+ public int length ;
176+
177+ Triple (String snippet , int offset , int length ) {
178+ this .snippet = snippet ;
179+ this .offset = offset ;
180+ this .length = length ;
181+ }
182+
183+ @ Override
184+ public int compareTo (Triple other ) {
185+ return other .offset - this .offset ;
186+ }
187+ }
60188}
0 commit comments