diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/CompleteCompletionTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/CompleteCompletionTest.java index 9bce60114..7c91c3c51 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/CompleteCompletionTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/CompleteCompletionTest.java @@ -429,7 +429,7 @@ record Test(String completion, String expected, String fileContent, int caretPos for (Test test : tests) { CompletionItem completionItem = createCompletionItem( // test.completion, // - CompletionItemKind.Class, // + CompletionItemKind.Snippet, // new Range(new Position(0, test.caretPos()), new Position(0, test.caretPos() + test.selectionLen()))); completionItem.setInsertTextFormat(InsertTextFormat.Snippet); @@ -611,4 +611,33 @@ public void testContextInformationPositionUsesEditRangeStart() throws CoreExcept // TextEdit starts at (0,2) so offset should be 0 assertEquals(editOffset, pos); } + + /** + * Ensures TM line-context variables are resolved from the viewer caret line, + * even when the TextEdit range is on a different line. + */ + @Test + public void testVariablesUseViewerLineNotEditRange() throws CoreException { + final CompletionItem completionItem = createCompletionItem( // + "$TM_LINE_INDEX $TM_LINE_NUMBER $TM_CURRENT_LINE", // + CompletionItemKind.Snippet, // + // Replace the entire 3rd line (line2) to avoid trailing leftovers + new Range(new Position(2, 0), new Position(2, 5))); + completionItem.setInsertTextFormat(InsertTextFormat.Snippet); + MockLanguageServer.INSTANCE.setCompletionList(new CompletionList(true, List.of(completionItem))); + + ITextViewer viewer = TestUtils.openTextViewer(TestUtils.createUniqueTestFile(project, "line0\nline1\nline2")); + + // Place caret on the first line (line 0) to drive variable resolution + viewer.setSelectedRange(0, 0); + + // Invoke completion at the edit range line (start of line 2) + int editOffset = viewer.getDocument().get().indexOf("line2"); + ICompletionProposal[] proposals = contentAssistProcessor.computeCompletionProposals(viewer, editOffset); + assertEquals(1, proposals.length); + + ((LSCompletionProposal) proposals[0]).apply(viewer, '\n', 0, editOffset); + // Variables should reflect caret line (line 0) + assertEquals("0 1 line0", viewer.getDocument().get().split("\n")[2]); + } } 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 4a1386067..b966eac25 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 @@ -662,7 +662,13 @@ private String getVariableValue(String variableName) { } case TM_LINE_INDEX -> { try { - yield Integer.toString(getTextEditRange().getStart().getLine()); // TODO probably wrong, should use viewer state + final var v = this.viewer; + if (v != null) { + int caretOffset = v.getSelectedRange().x; + yield Integer.toString(document.getLineOfOffset(caretOffset)); + } + // fallback to text edit range if viewer not available + yield Integer.toString(getTextEditRange().getStart().getLine()); } catch (BadLocationException e) { LanguageServerPlugin.logWarning(e.getMessage(), e); yield ""; //$NON-NLS-1$ @@ -670,15 +676,28 @@ private String getVariableValue(String variableName) { } case TM_LINE_NUMBER -> { try { - yield Integer.toString(getTextEditRange().getStart().getLine() + 1); // TODO probably wrong, should use viewer state + final var v = this.viewer; + if (v != null) { + int caretOffset = v.getSelectedRange().x; + yield Integer.toString(document.getLineOfOffset(caretOffset) + 1); + } + // fallback to text edit range if viewer not available + yield Integer.toString(getTextEditRange().getStart().getLine() + 1); } catch (BadLocationException e) { LanguageServerPlugin.logWarning(e.getMessage(), e); yield ""; //$NON-NLS-1$ } } - case TM_CURRENT_LINE -> { // TODO probably wrong, should use viewer state + case TM_CURRENT_LINE -> { try { - int currentLineIndex = getTextEditRange().getStart().getLine(); + int currentLineIndex; + final var v = this.viewer; + if (v != null) { + int caretOffset = v.getSelectedRange().x; + currentLineIndex = document.getLineOfOffset(caretOffset); + } else { + currentLineIndex = getTextEditRange().getStart().getLine(); + } IRegion lineInformation = document.getLineInformation(currentLineIndex); String line = document.get(lineInformation.getOffset(), lineInformation.getLength()); yield line;