Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -510,9 +510,9 @@
var file = TestUtils.createFile(project, "dummy" + new Random().nextInt(), "");
var triggerChars = new char[] {':', '>'};
// When toCompletionParams get called with offset == 0 and document.getLength() == 0:
var param = LSPEclipseUtils.toCompletionParams(file.getLocationURI(), 0, LSPEclipseUtils.getDocument(file), triggerChars);

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note test

Invoking
LSPEclipseUtils.toCompletionParams
should be avoided because it has been deprecated.
// Then no context has been added to param:
assertNull(param.getContext());
assertEquals(param.getContext().getTriggerKind(), CompletionTriggerKind.Invoked);
}

@Test
Expand All @@ -521,7 +521,7 @@
var file = TestUtils.createFile(project, "dummy" + new Random().nextInt(), "std");
var triggerChars = new char[] {':', '>'};
// When toCompletionParams get called with offset == 0 and document.getLength() > 0:
var param = LSPEclipseUtils.toCompletionParams(file.getLocationURI(), 0, LSPEclipseUtils.getDocument(file), triggerChars);

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note test

Invoking
LSPEclipseUtils.toCompletionParams
should be avoided because it has been deprecated.
// Then the trigger kind is Invoked:
assertEquals(param.getContext().getTriggerKind(), CompletionTriggerKind.Invoked);
}
Expand All @@ -532,7 +532,7 @@
var file = TestUtils.createFile(project, "dummy" + new Random().nextInt(), "std:");
var triggerChars = new char[] {':', '>'};
// When toCompletionParams get called with offset > 0 and document.getLength() > 0:
var param = LSPEclipseUtils.toCompletionParams(file.getLocationURI(), 4, LSPEclipseUtils.getDocument(file), triggerChars);

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note test

Invoking
LSPEclipseUtils.toCompletionParams
should be avoided because it has been deprecated.
// Then the context has been added with a colon as trigger character:
assertEquals(param.getContext().getTriggerCharacter(), ":");
// And the trigger kind is TriggerCharacter:
Expand All @@ -545,7 +545,7 @@
var file = TestUtils.createFile(project, "dummy" + new Random().nextInt(), "std");
var triggerChars = new char[] {':', '>'};
// When toCompletionParams get called with offset > 0 and document.getLength() > 0:
var param = LSPEclipseUtils.toCompletionParams(file.getLocationURI(), 3, LSPEclipseUtils.getDocument(file), triggerChars);

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note test

Invoking
LSPEclipseUtils.toCompletionParams
should be avoided because it has been deprecated.
// Then the trigger kind is Invoked:
assertEquals(param.getContext().getTriggerKind(), CompletionTriggerKind.Invoked);
}
Expand Down
51 changes: 34 additions & 17 deletions org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,30 +240,47 @@ public static boolean isOffsetInRange(int offset, Range range, IDocument documen
}
}

public static CompletionParams toCompletionParams(URI fileUri, int offset, IDocument document, char[] completionTriggerChars)
throws BadLocationException {
Position start = toPosition(offset, document);
final var param = new CompletionParams();
/**
* Provides the character at the given offset or {@code null} if the document is empty.
*/
public static @Nullable Character getCharacterAtPosition(IDocument document, int offset) throws BadLocationException {
if (document.getLength() > 0) {
try {
int positionCharacterOffset = offset > 0 ? offset-1 : offset;
String positionCharacter = document.get(positionCharacterOffset, 1);
if (Chars.contains(completionTriggerChars, positionCharacter.charAt(0))) {
param.setContext(new CompletionContext(CompletionTriggerKind.TriggerCharacter, positionCharacter));
} else {
// According to LSP 3.17 specification: the triggerCharacter in CompletionContext is undefined if
// triggerKind != CompletionTriggerKind.TriggerCharacter
param.setContext(new CompletionContext(CompletionTriggerKind.Invoked));
}
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
}
int positionCharacterOffset = offset > 0 ? offset - 1 : offset;
return document.getChar(positionCharacterOffset);
}
return null;
}

public static CompletionContext toCompletionContext(@Nullable Character triggerChar, char[] completionTriggerChars) {
if (triggerChar == null || !Chars.contains(completionTriggerChars, triggerChar.charValue())) {
// According to LSP 3.17 specification: the triggerCharacter in
// CompletionContext is undefined if
// triggerKind != CompletionTriggerKind.TriggerCharacter
return new CompletionContext(CompletionTriggerKind.Invoked);
} else {
return new CompletionContext(CompletionTriggerKind.TriggerCharacter, triggerChar.toString());
}
}

public static CompletionParams toCompletionParams(URI fileUri, Position start, CompletionContext context) {
final var param = new CompletionParams();
param.setContext(context);
param.setPosition(start);
param.setTextDocument(toTextDocumentIdentifier(fileUri));
return param;
}

/**
* Use {@link #toCompletionParams(URI, Position, CompletionContext)} instead.
*/
@Deprecated(forRemoval = true)
public static CompletionParams toCompletionParams(URI fileUri, int offset, IDocument document, char[] completionTriggerChars)
throws BadLocationException {
Position start = toPosition(offset, document);
@Nullable Character triggerChar = getCharacterAtPosition(document, offset);
return toCompletionParams(fileUri, start, toCompletionContext(triggerChar, completionTriggerChars));
}

public static @Nullable ITextSelection toSelection(Range range, IDocument document) {
try {
int offset = toOffset(range.getStart(), document);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ public int getRankScore() {
rankScore = CompletionProposalTools.getScoreOfFilterMatch(getDocumentFilter(),
getFilterString());
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
// Document was changed while we computed completion proposals, which made the
// offset invalid. We can stop any further computation as the result will be
// discarded anyway.
rankScore = -1;
}
this.rankScore = rankScore;
Expand All @@ -257,7 +259,9 @@ public int getRankCategory() {
rankCategory = CompletionProposalTools.getCategoryOfFilterMatch(getDocumentFilter(),
getFilterString());
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
// Document was changed while we computed completion proposals, which made the
// offset invalid. We can stop any further computation as the result will be
// discarded anyway.
rankCategory = CompletionProposalTools.CATEGORY_NO_MATCH;
}
this.rankCategory = rankCategory;
Expand Down Expand Up @@ -442,27 +446,25 @@ private void updateCompletionItem(@Nullable CompletionItem resolvedItem) {

@Override
public int getPrefixCompletionStart(IDocument document, int completionOffset) {
Either<TextEdit, InsertReplaceEdit> textEdit = item.getTextEdit();
if (textEdit != null) {
try {
try {
Either<TextEdit, InsertReplaceEdit> textEdit = item.getTextEdit();
if (textEdit != null) {
return LSPEclipseUtils.toOffset(getTextEditRange().getStart(), document);
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
}
}
final String insertText = getInsertText();
final int insertTextLength = insertText.length();
try {
String subDoc = document.get(
Math.max(0, completionOffset - insertTextLength),

final String insertText = getInsertText();
final int insertTextLength = insertText.length();
String subDoc = document.get(Math.max(0, completionOffset - insertTextLength),
Math.min(insertTextLength, completionOffset));
for (int i = 0; i < insertTextLength && i < completionOffset; i++) {
if (insertText.regionMatches(true, 0, subDoc, i, subDoc.length() - i)) {
return completionOffset - subDoc.substring(i).length();
}
}
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
// Document was changed while we computed completion proposals, which made the
// offset invalid. We can stop any further computation as the result will be
// discarded anyway.
}
return completionOffset;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@
import org.eclipse.lsp4e.internal.CancellationUtil;
import org.eclipse.lsp4e.ui.Messages;
import org.eclipse.lsp4e.ui.UI;
import org.eclipse.lsp4j.CompletionContext;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemDefaults;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.SignatureHelpParams;
import org.eclipse.lsp4j.SignatureInformation;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
Expand Down Expand Up @@ -116,21 +118,28 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
return NO_COMPLETION_PROPOSALS;
}

final @Nullable Character triggerChar;
final Position completionPosition;
try {
// Eagerly to try to access the position, so we can fail fast.
triggerChar = LSPEclipseUtils.getCharacterAtPosition(document, offset);
completionPosition = LSPEclipseUtils.toPosition(offset, document);
} catch (BadLocationException e) {
// Document was changed while we computed completion proposals, which made the
// offset invalid. We can stop any further computation as the result will be
// discarded anyway.
return NO_COMPLETION_PROPOSALS;
}

URI uri = LSPEclipseUtils.toUri(document);
if (uri == null) {
return NO_COMPLETION_PROPOSALS;
}

initiateLanguageServers(document);
CompletionParams param;

try {
param = LSPEclipseUtils.toCompletionParams(uri, offset, document, this.completionTriggerChars);
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
this.errorMessage = createErrorMessage(offset, e);
return createErrorProposal(offset, e);
}
final CompletionContext context = LSPEclipseUtils.toCompletionContext(triggerChar, completionTriggerChars);
final CompletionParams param = LSPEclipseUtils.toCompletionParams(uri, completionPosition, context);

final var proposals = Collections.synchronizedList(new ArrayList<ICompletionProposal>());
final var anyIncomplete = new AtomicBoolean(false);
Expand Down