Skip to content

Commit

Permalink
Support completion insert replace capability
Browse files Browse the repository at this point in the history
Signed-off-by: sheche <[email protected]>
  • Loading branch information
jdneo authored and rgrunber committed Apr 12, 2022
1 parent e3f5c74 commit 67063e4
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.InsertReplaceEdit;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
Expand Down Expand Up @@ -126,7 +127,7 @@ private void updateReplacement(CompletionProposal proposal, CompletionItem item,
List<org.eclipse.lsp4j.TextEdit> additionalTextEdits = new ArrayList<>();

StringBuilder completionBuffer = new StringBuilder();
Range range = null;
InsertReplaceEdit insertReplaceEdit = new InsertReplaceEdit();
if (isSupportingRequiredProposals(proposal)) {
CompletionProposal[] requiredProposals = proposal.getRequiredProposals();
if (requiredProposals != null) {
Expand All @@ -143,7 +144,7 @@ private void updateReplacement(CompletionProposal proposal, CompletionItem item,
|| proposal.getKind() == CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION
|| proposal.getKind() == CompletionProposal.ANONYMOUS_CLASS_DECLARATION) {
completionBuffer.append(edit.getNewText());
range = edit.getRange();
setInsertReplaceRange(requiredProposal, insertReplaceEdit);
} else {
additionalTextEdits.add(edit);
}
Expand All @@ -159,17 +160,7 @@ private void updateReplacement(CompletionProposal proposal, CompletionItem item,
}
}

if (range == null) {
boolean completionOverwrite = preferences.isCompletionOverwrite();
if (!completionOverwrite && (proposal.getKind() == CompletionProposal.METHOD_REF || proposal.getKind() == CompletionProposal.LOCAL_VARIABLE_REF || proposal.getKind() == CompletionProposal.FIELD_REF)) {
// See https:/redhat-developer/vscode-java/issues/462
int end = proposal.getReplaceEnd();
if (end > offset) {
proposal.setReplaceRange(proposal.getReplaceStart(), offset);
}
}
range = toReplacementRange(proposal);
}
setInsertReplaceRange(proposal, insertReplaceEdit);

switch (proposal.getKind()) {
case CompletionProposal.METHOD_DECLARATION:
Expand All @@ -184,7 +175,7 @@ private void updateReplacement(CompletionProposal proposal, CompletionItem item,
break;
case CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION:
case CompletionProposal.ANONYMOUS_CLASS_DECLARATION:
appendAnonymousClass(completionBuffer, proposal, range);
appendAnonymousClass(completionBuffer, proposal, insertReplaceEdit);
break;
case CompletionProposal.LAMBDA_EXPRESSION:
appendLambdaExpressionReplacement(completionBuffer, proposal);
Expand All @@ -201,11 +192,16 @@ private void updateReplacement(CompletionProposal proposal, CompletionItem item,
item.setInsertTextFormat(InsertTextFormat.PlainText);
}
String text = completionBuffer.toString();
if (range != null) {
item.setTextEdit(Either.forLeft(new org.eclipse.lsp4j.TextEdit(range, text)));
} else {
if (insertReplaceEdit.getReplace() == null || insertReplaceEdit.getInsert() == null) {
// fallback
item.setInsertText(text);
} else if (client.isCompletionInsertReplaceSupport()) {
insertReplaceEdit.setNewText(text);
item.setTextEdit(Either.forRight(insertReplaceEdit));
} else if (preferences.isCompletionOverwrite()) {
item.setTextEdit(Either.forLeft(new org.eclipse.lsp4j.TextEdit(insertReplaceEdit.getReplace(), text)));
} else {
item.setTextEdit(Either.forLeft(new org.eclipse.lsp4j.TextEdit(insertReplaceEdit.getInsert(), text)));
}

if (!isImportCompletion(proposal) && (!client.isResolveAdditionalTextEditsSupport() || isResolving)) {
Expand All @@ -226,7 +222,7 @@ private void appendLambdaExpressionReplacement(StringBuilder completionBuffer, C
}
}

private void appendAnonymousClass(StringBuilder completionBuffer, CompletionProposal proposal, Range range) {
private void appendAnonymousClass(StringBuilder completionBuffer, CompletionProposal proposal, InsertReplaceEdit edit) {
IDocument document;
try {
IBuffer buffer = this.compilationUnit.getBuffer();
Expand All @@ -249,7 +245,7 @@ private void appendAnonymousClass(StringBuilder completionBuffer, CompletionProp
}
String replacement = this.anonymousTypeNewBody;

if (document.getLength() > offset && range != null) {
if (document.getLength() > offset) {
if (proposal.getKind() == CompletionProposal.ANONYMOUS_CLASS_DECLARATION) {
// update replacement range
int length = 0;
Expand All @@ -262,7 +258,12 @@ private void appendAnonymousClass(StringBuilder completionBuffer, CompletionProp
pos++;
ch = document.getChar(pos);
}
range.getEnd().setCharacter(range.getEnd().getCharacter() + length);
if (edit.getReplace() != null) {
edit.getReplace().getEnd().setCharacter(edit.getReplace().getEnd().getCharacter() + length);
}
if (edit.getInsert() != null) {
edit.getInsert().getEnd().setCharacter(edit.getInsert().getEnd().getCharacter() + length);
}
length = 1;
pos = offset - 1;
if (pos < document.getLength()) {
Expand All @@ -274,7 +275,12 @@ private void appendAnonymousClass(StringBuilder completionBuffer, CompletionProp
ch = document.getChar(pos);
}
}
range.getStart().setCharacter(range.getStart().getCharacter() - length);
if (edit.getReplace() != null) {
edit.getReplace().getStart().setCharacter(edit.getReplace().getStart().getCharacter() - length);
}
if (edit.getInsert() != null) {
edit.getInsert().getStart().setCharacter(edit.getInsert().getStart().getCharacter() - length);
}
replacement = checkReplacementEnd(document, replacement, offset);
} else if (proposal.getKind() == CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) {
// update replacement range
Expand Down Expand Up @@ -304,7 +310,12 @@ private void appendAnonymousClass(StringBuilder completionBuffer, CompletionProp
pos--;
}
if (length > 0) {
range.getEnd().setCharacter(range.getEnd().getCharacter() + length);
if (edit.getReplace() != null) {
edit.getReplace().getEnd().setCharacter(edit.getReplace().getEnd().getCharacter() + length);
}
if (edit.getInsert() != null) {
edit.getInsert().getEnd().setCharacter(edit.getInsert().getEnd().getCharacter() + length);
}
}
}
int next = (pos > 0) ? pos + 1 : offset;
Expand Down Expand Up @@ -397,6 +408,25 @@ private Range toReplacementRange(CompletionProposal proposal){
return null;
}

private void setInsertReplaceRange(CompletionProposal proposal, InsertReplaceEdit edit){
try {
int start = proposal.getReplaceStart();
int end = proposal.getReplaceEnd();
if (edit.getReplace() == null) {
Range replaceRange = JDTUtils.toRange(compilationUnit, start, end - start);
edit.setReplace(replaceRange);
}

if (edit.getInsert() == null) {
end = Math.min(end, offset);
Range insertRange = JDTUtils.toRange(compilationUnit, start, end - start);
edit.setInsert(insertRange);
}
} catch (JavaModelException e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
}

/**
* Adds imports collected by importRewrite to item
* @param item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,11 @@ public boolean isSymbolTagSupported() {
&& capabilities.getTextDocument().getDocumentSymbol().getTagSupport() != null;
}

public boolean isCompletionInsertReplaceSupport() {
return v3supported
&& capabilities.getTextDocument().getCompletion() != null
&& capabilities.getTextDocument().getCompletion().getCompletionItem() != null
&& capabilities.getTextDocument().getCompletion().getCompletionItem().getInsertReplaceSupport().booleanValue();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2022 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import static org.eclipse.jdt.ls.core.internal.Lsp4jAssertions.assertPosition;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JsonMessageHelper;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.InsertReplaceEdit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

/**
* @author Gorkem Ercan
*
*/
@RunWith(MockitoJUnitRunner.class)
public class CompletionInsertReplaceCapabilityTest extends AbstractCompilationUnitBasedTest {

private static String COMPLETION_TEMPLATE =
"{\n" +
" \"id\": \"1\",\n" +
" \"method\": \"textDocument/completion\",\n" +
" \"params\": {\n" +
" \"textDocument\": {\n" +
" \"uri\": \"${file}\"\n" +
" },\n" +
" \"position\": {\n" +
" \"line\": ${line},\n" +
" \"character\": ${char}\n" +
" }\n" +
" },\n" +
" \"jsonrpc\": \"2.0\"\n" +
"}";

@Before
public void setUp() {
mockClient();
CoreASTProvider sharedASTProvider = CoreASTProvider.getInstance();
sharedASTProvider.disposeAST();
}

@Test
public void testCompletion_InsertReplaceEdit() throws Exception {
when(preferenceManager.getClientPreferences().isCompletionInsertReplaceSupport()).thenReturn(true);
ICompilationUnit unit = getWorkingCopy("src/org/sample/Test.java", String.join("\n",
"public class Test {",
" public static void main(String[] args) {",
" if (\"foo\".equSystem.getProperty(\"bar\")) {}",
" }",
"}"
));
CompletionItem item = requestCompletions(unit, ".equ").getItems().get(0);
InsertReplaceEdit edit = item.getTextEdit().getRight();
assertNotNull(edit);
assertEquals("equals(${1:arg0})", edit.getNewText());
// check insert range
assertPosition(2, 12, edit.getInsert().getStart());
assertPosition(2, 15, edit.getInsert().getEnd());
// check replace range
assertPosition(2, 12, edit.getReplace().getStart());
assertPosition(2, 21, edit.getReplace().getEnd());
}

private CompletionList requestCompletions(ICompilationUnit unit, String completeBehind) throws JavaModelException {
int[] loc = findCompletionLocation(unit, completeBehind);
return server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight();
}

private String createCompletionRequest(ICompilationUnit unit, int line, int kar) {
return COMPLETION_TEMPLATE.replace("${file}", JDTUtils.toURI(unit))
.replace("${line}", String.valueOf(line))
.replace("${char}", String.valueOf(kar));
}

private void mockClient() {
// Mock the preference manager to use LSP v3 support.
when(preferenceManager.getClientPreferences().isCompletionSnippetsSupported()).thenReturn(true);
when(preferenceManager.getClientPreferences().isSignatureHelpSupported()).thenReturn(true);
when(preferenceManager.getClientPreferences().isCompletionInsertReplaceSupport()).thenReturn(true);
}
}

0 comments on commit 67063e4

Please sign in to comment.