diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/LSPEclipseUtilsTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/LSPEclipseUtilsTest.java index ef9393e54..8ef6c570d 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/LSPEclipseUtilsTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/LSPEclipseUtilsTest.java @@ -512,7 +512,7 @@ public void testToCompletionParams_EmptyDocument() throws Exception { // When toCompletionParams get called with offset == 0 and document.getLength() == 0: var param = LSPEclipseUtils.toCompletionParams(file.getLocationURI(), 0, LSPEclipseUtils.getDocument(file), triggerChars); // Then no context has been added to param: - assertNull(param.getContext()); + assertEquals(param.getContext().getTriggerKind(), CompletionTriggerKind.Invoked); } @Test diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java index 7270449ba..582156af8 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java @@ -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); diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSCompletionProposal.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSCompletionProposal.java index 442eef939..641156893 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSCompletionProposal.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSCompletionProposal.java @@ -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; @@ -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; @@ -442,19 +446,15 @@ private void updateCompletionItem(@Nullable CompletionItem resolvedItem) { @Override public int getPrefixCompletionStart(IDocument document, int completionOffset) { - Either textEdit = item.getTextEdit(); - if (textEdit != null) { - try { + try { + Either 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)) { @@ -462,7 +462,9 @@ public int getPrefixCompletionStart(IDocument document, int completionOffset) { } } } 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; } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java index 4b5a75cca..21f4c7764 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java @@ -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; @@ -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()); final var anyIncomplete = new AtomicBoolean(false);