Skip to content

Commit

Permalink
fix(completions): fix completions for inputs / outputs
Browse files Browse the repository at this point in the history
The `TextEdit` is not meant to replace text to the right of the
position. Instead, `InsesrtReplaceEdit` should be used.

Reference discussion in LSP issue for examples on replacement vs
insertion range for `InsertReplaceEdit`:
microsoft/language-server-protocol#846

Fixes angular#1399

Note that the issue did not appear in the VE version of the extension
because it was only providing completions as inserts (they had
no `replacementSpan`).
  • Loading branch information
atscott committed Jun 8, 2021
1 parent 7fc2118 commit 2e7745d
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 4 deletions.
5 changes: 5 additions & 0 deletions integration/lsp/ivy_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ describe('Angular Ivy language server', () => {
}) as lsp.CompletionItem[];
const outputCompletion = response.find(i => i.label === '(appOutput)')!;
expect(outputCompletion.kind).toEqual(lsp.CompletionItemKind.Property);
expect((outputCompletion.textEdit as lsp.InsertReplaceEdit).insert)
.toEqual({start: {line: 0, character: 8}, end: {line: 0, character: 9}});
// replace range includes the closing )
expect((outputCompletion.textEdit as lsp.InsertReplaceEdit).replace)
.toEqual({start: {line: 0, character: 8}, end: {line: 0, character: 10}});
});
});

Expand Down
15 changes: 11 additions & 4 deletions server/src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import * as ts from 'typescript/lib/tsserverlibrary';
import * as lsp from 'vscode-languageserver';

import {tsTextSpanToLspRange} from './utils';
import {lspPositionToTsPosition, tsTextSpanToLspRange} from './utils';

// TODO: Move this to `@angular/language-service`.
enum CompletionKind {
Expand Down Expand Up @@ -118,9 +118,16 @@ export function tsCompletionEntryToLspCompletionItem(
// from 'entry.name'. For example, a method name could be 'greet', but the
// insertText is 'greet()'.
const insertText = entry.insertText || entry.name;
item.textEdit = entry.replacementSpan ?
lsp.TextEdit.replace(tsTextSpanToLspRange(scriptInfo, entry.replacementSpan), insertText) :
lsp.TextEdit.insert(position, insertText);
if (entry.replacementSpan) {
const replacementRange = tsTextSpanToLspRange(scriptInfo, entry.replacementSpan);
const tsPosition = lspPositionToTsPosition(scriptInfo, position);
const insertLength = tsPosition - entry.replacementSpan.start;
const insertionRange =
tsTextSpanToLspRange(scriptInfo, {...entry.replacementSpan, length: insertLength})
item.textEdit = lsp.InsertReplaceEdit.create(insertText, insertionRange, replacementRange);
} else {
item.textEdit = lsp.TextEdit.insert(position, insertText);
}
item.data = {
kind: 'ngCompletionOriginData',
filePath: scriptInfo.fileName,
Expand Down

0 comments on commit 2e7745d

Please sign in to comment.