Skip to content

Commit 1dbc271

Browse files
FlorianKroissrubenporras
authored andcommitted
fix: Abort completion computations when encountering a BadLocationException
1 parent d8b551a commit 1dbc271

4 files changed

Lines changed: 68 additions & 40 deletions

File tree

org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/LSPEclipseUtilsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ public void testToCompletionParams_EmptyDocument() throws Exception {
512512
// When toCompletionParams get called with offset == 0 and document.getLength() == 0:
513513
var param = LSPEclipseUtils.toCompletionParams(file.getLocationURI(), 0, LSPEclipseUtils.getDocument(file), triggerChars);
514514
// Then no context has been added to param:
515-
assertNull(param.getContext());
515+
assertEquals(param.getContext().getTriggerKind(), CompletionTriggerKind.Invoked);
516516
}
517517

518518
@Test

org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,30 +240,47 @@ public static boolean isOffsetInRange(int offset, Range range, IDocument documen
240240
}
241241
}
242242

243-
public static CompletionParams toCompletionParams(URI fileUri, int offset, IDocument document, char[] completionTriggerChars)
244-
throws BadLocationException {
245-
Position start = toPosition(offset, document);
246-
final var param = new CompletionParams();
243+
/**
244+
* Provides the character at the given offset or {@code null} if the document is empty.
245+
*/
246+
public static @Nullable Character getCharacterAtPosition(IDocument document, int offset) throws BadLocationException {
247247
if (document.getLength() > 0) {
248-
try {
249-
int positionCharacterOffset = offset > 0 ? offset-1 : offset;
250-
String positionCharacter = document.get(positionCharacterOffset, 1);
251-
if (Chars.contains(completionTriggerChars, positionCharacter.charAt(0))) {
252-
param.setContext(new CompletionContext(CompletionTriggerKind.TriggerCharacter, positionCharacter));
253-
} else {
254-
// According to LSP 3.17 specification: the triggerCharacter in CompletionContext is undefined if
255-
// triggerKind != CompletionTriggerKind.TriggerCharacter
256-
param.setContext(new CompletionContext(CompletionTriggerKind.Invoked));
257-
}
258-
} catch (BadLocationException e) {
259-
LanguageServerPlugin.logError(e);
260-
}
248+
int positionCharacterOffset = offset > 0 ? offset - 1 : offset;
249+
return document.getChar(positionCharacterOffset);
250+
}
251+
return null;
252+
}
253+
254+
public static CompletionContext toCompletionContext(@Nullable Character triggerChar, char[] completionTriggerChars) {
255+
if (triggerChar == null || !Chars.contains(completionTriggerChars, triggerChar.charValue())) {
256+
// According to LSP 3.17 specification: the triggerCharacter in
257+
// CompletionContext is undefined if
258+
// triggerKind != CompletionTriggerKind.TriggerCharacter
259+
return new CompletionContext(CompletionTriggerKind.Invoked);
260+
} else {
261+
return new CompletionContext(CompletionTriggerKind.TriggerCharacter, triggerChar.toString());
261262
}
263+
}
264+
265+
public static CompletionParams toCompletionParams(URI fileUri, Position start, CompletionContext context) {
266+
final var param = new CompletionParams();
267+
param.setContext(context);
262268
param.setPosition(start);
263269
param.setTextDocument(toTextDocumentIdentifier(fileUri));
264270
return param;
265271
}
266272

273+
/**
274+
* Use {@link #toCompletionParams(URI, Position, CompletionContext)} instead.
275+
*/
276+
@Deprecated(forRemoval = true)
277+
public static CompletionParams toCompletionParams(URI fileUri, int offset, IDocument document, char[] completionTriggerChars)
278+
throws BadLocationException {
279+
Position start = toPosition(offset, document);
280+
@Nullable Character triggerChar = getCharacterAtPosition(document, offset);
281+
return toCompletionParams(fileUri, start, toCompletionContext(triggerChar, completionTriggerChars));
282+
}
283+
267284
public static @Nullable ITextSelection toSelection(Range range, IDocument document) {
268285
try {
269286
int offset = toOffset(range.getStart(), document);

org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSCompletionProposal.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,9 @@ public int getRankScore() {
234234
rankScore = CompletionProposalTools.getScoreOfFilterMatch(getDocumentFilter(),
235235
getFilterString());
236236
} catch (BadLocationException e) {
237-
LanguageServerPlugin.logError(e);
237+
// Document was changed while we computed completion proposals, which made the
238+
// offset invalid. We can stop any further computation as the result will be
239+
// discarded anyway.
238240
rankScore = -1;
239241
}
240242
this.rankScore = rankScore;
@@ -257,7 +259,9 @@ public int getRankCategory() {
257259
rankCategory = CompletionProposalTools.getCategoryOfFilterMatch(getDocumentFilter(),
258260
getFilterString());
259261
} catch (BadLocationException e) {
260-
LanguageServerPlugin.logError(e);
262+
// Document was changed while we computed completion proposals, which made the
263+
// offset invalid. We can stop any further computation as the result will be
264+
// discarded anyway.
261265
rankCategory = CompletionProposalTools.CATEGORY_NO_MATCH;
262266
}
263267
this.rankCategory = rankCategory;
@@ -442,27 +446,25 @@ private void updateCompletionItem(@Nullable CompletionItem resolvedItem) {
442446

443447
@Override
444448
public int getPrefixCompletionStart(IDocument document, int completionOffset) {
445-
Either<TextEdit, InsertReplaceEdit> textEdit = item.getTextEdit();
446-
if (textEdit != null) {
447-
try {
449+
try {
450+
Either<TextEdit, InsertReplaceEdit> textEdit = item.getTextEdit();
451+
if (textEdit != null) {
448452
return LSPEclipseUtils.toOffset(getTextEditRange().getStart(), document);
449-
} catch (BadLocationException e) {
450-
LanguageServerPlugin.logError(e);
451453
}
452-
}
453-
final String insertText = getInsertText();
454-
final int insertTextLength = insertText.length();
455-
try {
456-
String subDoc = document.get(
457-
Math.max(0, completionOffset - insertTextLength),
454+
455+
final String insertText = getInsertText();
456+
final int insertTextLength = insertText.length();
457+
String subDoc = document.get(Math.max(0, completionOffset - insertTextLength),
458458
Math.min(insertTextLength, completionOffset));
459459
for (int i = 0; i < insertTextLength && i < completionOffset; i++) {
460460
if (insertText.regionMatches(true, 0, subDoc, i, subDoc.length() - i)) {
461461
return completionOffset - subDoc.substring(i).length();
462462
}
463463
}
464464
} catch (BadLocationException e) {
465-
LanguageServerPlugin.logError(e);
465+
// Document was changed while we computed completion proposals, which made the
466+
// offset invalid. We can stop any further computation as the result will be
467+
// discarded anyway.
466468
}
467469
return completionOffset;
468470
}

org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@
5151
import org.eclipse.lsp4e.internal.CancellationUtil;
5252
import org.eclipse.lsp4e.ui.Messages;
5353
import org.eclipse.lsp4e.ui.UI;
54+
import org.eclipse.lsp4j.CompletionContext;
5455
import org.eclipse.lsp4j.CompletionItem;
5556
import org.eclipse.lsp4j.CompletionItemDefaults;
5657
import org.eclipse.lsp4j.CompletionList;
5758
import org.eclipse.lsp4j.CompletionParams;
59+
import org.eclipse.lsp4j.Position;
5860
import org.eclipse.lsp4j.SignatureHelpParams;
5961
import org.eclipse.lsp4j.SignatureInformation;
6062
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
@@ -116,21 +118,28 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
116118
return NO_COMPLETION_PROPOSALS;
117119
}
118120

121+
final @Nullable Character triggerChar;
122+
final Position completionPosition;
123+
try {
124+
// Eagerly to try to access the position, so we can fail fast.
125+
triggerChar = LSPEclipseUtils.getCharacterAtPosition(document, offset);
126+
completionPosition = LSPEclipseUtils.toPosition(offset, document);
127+
} catch (BadLocationException e) {
128+
// Document was changed while we computed completion proposals, which made the
129+
// offset invalid. We can stop any further computation as the result will be
130+
// discarded anyway.
131+
return NO_COMPLETION_PROPOSALS;
132+
}
133+
119134
URI uri = LSPEclipseUtils.toUri(document);
120135
if (uri == null) {
121136
return NO_COMPLETION_PROPOSALS;
122137
}
123138

124139
initiateLanguageServers(document);
125-
CompletionParams param;
126140

127-
try {
128-
param = LSPEclipseUtils.toCompletionParams(uri, offset, document, this.completionTriggerChars);
129-
} catch (BadLocationException e) {
130-
LanguageServerPlugin.logError(e);
131-
this.errorMessage = createErrorMessage(offset, e);
132-
return createErrorProposal(offset, e);
133-
}
141+
final CompletionContext context = LSPEclipseUtils.toCompletionContext(triggerChar, completionTriggerChars);
142+
final CompletionParams param = LSPEclipseUtils.toCompletionParams(uri, completionPosition, context);
134143

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

0 commit comments

Comments
 (0)