Bug 525590 - apply additional text edits of completion items
Change-Id: I289881e711f04fcc2891018032f1083b8f453e76
Signed-off-by: Martin Lippert <mlippert@gmail.com>
diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java
index 6ac01c2..0417235 100644
--- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java
+++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java
@@ -185,6 +185,68 @@
}
@Test
+ public void testCompletionWithAdditionalEdits() throws CoreException, InvocationTargetException {
+ List<CompletionItem> items = new ArrayList<>();
+ CompletionItem item = new CompletionItem("additionaEditsCompletion");
+ item.setKind(CompletionItemKind.Function);
+ item.setInsertText("MainInsertText");
+
+ List<TextEdit> additionalTextEdits = new ArrayList<>();
+
+ TextEdit additionaEdit1 = new TextEdit(new Range(new Position(0, 6), new Position(0, 6)), "addOnText1");
+ TextEdit additionaEdit2 = new TextEdit(new Range(new Position(0, 12), new Position(0, 12)), "addOnText2");
+ additionalTextEdits.add(additionaEdit1);
+ additionalTextEdits.add(additionaEdit2);
+
+ item.setAdditionalTextEdits(additionalTextEdits);
+ items.add(item);
+ MockLanguageSever.INSTANCE.setCompletionList(new CompletionList(true, items));
+
+ String content = "this <> is <> the main <> content of the file";
+ IFile testFile = TestUtils.createUniqueTestFile(project, content);
+ ITextViewer viewer = TestUtils.openTextViewer(testFile);
+
+ ICompletionProposal[] proposals = contentAssistProcessor.computeCompletionProposals(viewer, 24);
+ assertEquals(items.size(), proposals.length);
+ // TODO compare both structures
+ LSIncompleteCompletionProposal LSIncompleteCompletionProposal = (LSIncompleteCompletionProposal) proposals[0];
+ LSIncompleteCompletionProposal.apply(viewer.getDocument());
+
+ String newContent = viewer.getDocument().get();
+ assertEquals("this <addOnText1> is <addOnText2> the main <MainInsertText> content of the file", newContent);
+ }
+
+ @Test
+ public void testSnippetCompletionWithAdditionalEdits()
+ throws PartInitException, InvocationTargetException, CoreException {
+ CompletionItem item = new CompletionItem("snippet item");
+ item.setInsertText("$1 and ${2:foo}");
+ item.setKind(CompletionItemKind.Class);
+ item.setInsertTextFormat(InsertTextFormat.Snippet);
+ List<TextEdit> additionalTextEdits = new ArrayList<>();
+
+ TextEdit additionaEdit1 = new TextEdit(new Range(new Position(0, 6), new Position(0, 6)), "addOnText1");
+ TextEdit additionaEdit2 = new TextEdit(new Range(new Position(0, 12), new Position(0, 12)), "addOnText2");
+ additionalTextEdits.add(additionaEdit1);
+ additionalTextEdits.add(additionaEdit2);
+
+ item.setAdditionalTextEdits(additionalTextEdits);
+
+ MockLanguageSever.INSTANCE.setCompletionList(new CompletionList(true, Collections.singletonList(item)));
+
+ String content = "this <> is <> the main <> content of the file";
+ ITextViewer viewer = TestUtils.openTextViewer(TestUtils.createUniqueTestFile(project, content));
+
+ ICompletionProposal[] proposals = contentAssistProcessor.computeCompletionProposals(viewer, 24);
+ assertEquals(1, proposals.length);
+ ((LSIncompleteCompletionProposal) proposals[0]).apply(viewer.getDocument());
+
+ String newContent = viewer.getDocument().get();
+ assertEquals("this <addOnText1> is <addOnText2> the main < and foo> content of the file", newContent);
+ // TODO check link edit groups
+ }
+
+ @Test
public void testApplyCompletionWithPrefix() throws CoreException, InvocationTargetException {
Range range = new Range(new Position(0, 0), new Position(0, 5));
List<CompletionItem> items = Collections
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSIncompleteCompletionProposal.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSIncompleteCompletionProposal.java
index 998044e..b45d161 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSIncompleteCompletionProposal.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSIncompleteCompletionProposal.java
@@ -60,6 +60,8 @@
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
+import com.google.common.collect.ImmutableList;
+
public class LSIncompleteCompletionProposal
implements ICompletionProposal, ICompletionProposalExtension3, ICompletionProposalExtension4,
ICompletionProposalExtension5, ICompletionProposalExtension6, ICompletionProposalExtension7,
@@ -289,8 +291,9 @@
}
insertText = textEdit.getNewText();
LinkedHashMap<String, List<LinkedPosition>> regions = new LinkedHashMap<>();
+ int insertionOffset = LSPEclipseUtils.toOffset(textEdit.getRange().getStart(), document);
+ insertionOffset = computeNewOffset(item.getAdditionalTextEdits(), insertionOffset, document);
if (item.getInsertTextFormat() == InsertTextFormat.Snippet) {
- int insertionOffset = LSPEclipseUtils.toOffset(textEdit.getRange().getStart(), document);
int currentOffset = 0;
while ((currentOffset = insertText.indexOf('$', currentOffset)) != -1) {
StringBuilder keyBuilder = new StringBuilder();
@@ -335,7 +338,15 @@
}
}
textEdit.setNewText(insertText); // insertText now has placeholder removed
- LSPEclipseUtils.applyEdit(textEdit, document);
+ List<TextEdit> additionalEdits = item.getAdditionalTextEdits();
+ if (additionalEdits != null && !additionalEdits.isEmpty()) {
+ ImmutableList.Builder<TextEdit> allEdits = ImmutableList.builder();
+ allEdits.add(textEdit);
+ allEdits.addAll(additionalEdits);
+ LSPEclipseUtils.applyEdits(document, allEdits.build());
+ } else {
+ LSPEclipseUtils.applyEdit(textEdit, document);
+ }
if (viewer != null && !regions.isEmpty()) {
LinkedModeModel model = new LinkedModeModel();
@@ -355,13 +366,36 @@
ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER);
ui.enter();
} else {
- selection = new Region(LSPEclipseUtils.toOffset(textEdit.getRange().getStart(), document) + textEdit.getNewText().length(), 0);
+ selection = new Region(insertionOffset + textEdit.getNewText().length(), 0);
}
} catch (BadLocationException ex) {
LanguageServerPlugin.logError(ex);
}
}
+ private int computeNewOffset(List<TextEdit> additionalTextEdits, int insertionOffset, IDocument doc) {
+ if (additionalTextEdits != null && !additionalTextEdits.isEmpty()) {
+ int adjustment = 0;
+ for (TextEdit edit : additionalTextEdits) {
+ try {
+ Range rng = edit.getRange();
+ int start = LSPEclipseUtils.toOffset(rng.getStart(), doc);
+ if (start <= insertionOffset) {
+ int end = LSPEclipseUtils.toOffset(rng.getEnd(), doc);
+ int orgLen = end - start;
+ int newLeng = edit.getNewText().length();
+ int editChange = newLeng - orgLen;
+ adjustment += editChange;
+ }
+ } catch (BadLocationException e) {
+ LanguageServerPlugin.logError(e);
+ }
+ }
+ return insertionOffset + adjustment;
+ }
+ return insertionOffset;
+ }
+
protected String getInsertText() {
String insertText = this.item.getInsertText();
if (this.item.getTextEdit() != null) {