Skip to content

Commit c485b11

Browse files
committed
Add LinkedProposalModel -> snippet conversion to support linked proposals
1 parent b363a86 commit c485b11

File tree

3 files changed

+169
-2
lines changed

3 files changed

+169
-2
lines changed

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionResolveHandler.java

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,43 @@
1313

1414
package org.eclipse.jdt.ls.core.internal.handlers;
1515

16+
import java.util.ArrayList;
17+
import java.util.Iterator;
18+
import java.util.List;
1619
import java.util.Map;
1720

1821
import org.eclipse.core.runtime.CoreException;
1922
import 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;
2026
import 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;
2132
import org.eclipse.jdt.ls.core.internal.ChangeUtil;
2233
import org.eclipse.jdt.ls.core.internal.JSONUtility;
2334
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
35+
import org.eclipse.jface.text.link.LinkedModeModel;
36+
import org.eclipse.jface.text.link.LinkedPosition;
2437
import org.eclipse.lsp4j.CodeAction;
38+
import org.eclipse.lsp4j.Range;
39+
import org.eclipse.lsp4j.TextEdit;
2540
import org.eclipse.lsp4j.WorkspaceEdit;
41+
import org.eclipse.lsp4j.extended.SnippetTextEdit;
2642
import org.eclipse.lsp4j.jsonrpc.messages.Either;
43+
import org.eclipse.text.edits.MultiTextEdit;
44+
import org.eclipse.text.edits.ReplaceEdit;
2745

2846
public 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
}

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/ClientPreferences.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ public boolean isWorkspaceApplyEditSupported() {
195195
return capabilities.getWorkspace() != null && isTrue(capabilities.getWorkspace().getApplyEdit());
196196
}
197197

198+
public boolean isWorkspaceSnippetEditSupported() {
199+
return Boolean.parseBoolean(extendedClientCapabilities.getOrDefault("snippetEditSupport", "false").toString());
200+
}
201+
198202
public boolean isProgressReportSupported() {
199203
return Boolean.parseBoolean(extendedClientCapabilities.getOrDefault("progressReportProvider", "false").toString());
200204
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License 2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-2.0
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Red Hat Inc. - initial API and implementation
12+
*******************************************************************************/
13+
14+
package org.eclipse.lsp4j.extended;
15+
16+
import org.eclipse.lsp4j.Range;
17+
import org.eclipse.lsp4j.TextEdit;
18+
19+
public class SnippetTextEdit extends TextEdit {
20+
StringValue snippet;
21+
22+
public SnippetTextEdit(Range range, String snippet) {
23+
super(range, "");
24+
this.snippet = new StringValue(snippet);
25+
}
26+
27+
private static final class StringValue {
28+
public static final String kind = "snippet";
29+
String value;
30+
31+
StringValue(String value) {
32+
this.value = value;
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)