Skip to content

Commit 08415b4

Browse files
author
Keen Yee Liau
committed
feat: Backwards compat with new TS LS interface
angular/angular#31972 changes the Angular LS interface to return TS data types. This PR makes the extension compatible with both the old and new interface.
1 parent 86c10ed commit 08415b4

File tree

1 file changed

+97
-32
lines changed

1 file changed

+97
-32
lines changed

server/src/server.ts

Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,20 @@
55
* ------------------------------------------------------------------------------------------ */
66

77
import * as ng from '@angular/language-service';
8-
9-
import {
10-
createConnection, IConnection, InitializeResult, TextDocumentPositionParams,
11-
CompletionItem, CompletionItemKind, Definition, Location, TextDocumentIdentifier,
12-
Position, Range, TextEdit, Hover
13-
} from 'vscode-languageserver';
8+
import * as ts from 'typescript';
9+
import * as lsp from 'vscode-languageserver';
1410

1511
import {TextDocuments, TextDocumentEvent, fileNameToUri} from './documents';
1612
import {ErrorCollector} from './errors';
1713

1814
import {Completion} from '@angular/language-service';
1915

2016
// Create a connection for the server. The connection uses Node's IPC as a transport
21-
let connection: IConnection = createConnection();
17+
const connection: lsp.IConnection = lsp.createConnection();
2218

2319
// Create a simple text document manager. The text document manager
2420
// supports full document sync only
25-
let documents: TextDocuments = new TextDocuments(handleTextEvent);
21+
const documents: TextDocuments = new TextDocuments(handleTextEvent);
2622

2723

2824
// Setup the error collector that watches for document events and requests errors
@@ -45,7 +41,7 @@ documents.listen(connection);
4541
// After the server has started the client sends an initilize request. The server receives
4642
// in the passed params the rootPath of the workspace plus the client capabilites.
4743
let workspaceRoot: string;
48-
connection.onInitialize((params): InitializeResult => {
44+
connection.onInitialize((params): lsp.InitializeResult => {
4945
workspaceRoot = params.rootPath;
5046
return {
5147
capabilities: {
@@ -62,7 +58,8 @@ connection.onInitialize((params): InitializeResult => {
6258
}
6359
});
6460

65-
function compiletionKindToCompletionItemKind(kind: string): CompletionItemKind {
61+
function compiletionKindToCompletionItemKind(kind: string): lsp.CompletionItemKind {
62+
const {CompletionItemKind} = lsp;
6663
switch (kind) {
6764
case 'attribute': return CompletionItemKind.Property;
6865
case 'html attribute': return CompletionItemKind.Property;
@@ -84,13 +81,13 @@ const wordRe = /(\w|\(|\)|\[|\]|\*|\-|\_|\.)+/g;
8481
const special = /\(|\)|\[|\]|\*|\-|\_|\./;
8582

8683
// Convert attribute names with non-\w chracters into a text edit.
87-
function insertionToEdit(range: Range, insertText: string): TextEdit {
84+
function insertionToEdit(range: lsp.Range, insertText: string): lsp.TextEdit {
8885
if (insertText.match(special) && range) {
89-
return TextEdit.replace(range, insertText);
86+
return lsp.TextEdit.replace(range, insertText);
9087
}
9188
}
9289

93-
function getReplaceRange(document: TextDocumentIdentifier, offset: number): Range {
90+
function getReplaceRange(document: lsp.TextDocumentIdentifier, offset: number): lsp.Range {
9491
const line = documents.getDocumentLine(document, offset);
9592
if (line && line.text && line.start <= offset && line.start + line.text.length >= offset) {
9693
const lineOffset = offset - line.start - 1;
@@ -104,7 +101,7 @@ function getReplaceRange(document: TextDocumentIdentifier, offset: number): Rang
104101
}
105102
}));
106103
if (found != null) {
107-
return Range.create(Position.create(line.line - 1, found), Position.create(line.line - 1, found + len));
104+
return lsp.Range.create(line.line - 1, found, line.line - 1, found + len);
108105
}
109106
}
110107
}
@@ -119,7 +116,7 @@ function insertTextOf(completion: Completion): string {
119116
}
120117

121118
// This handler provides the initial list of the completion items.
122-
connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
119+
connection.onCompletion((textDocumentPosition: lsp.TextDocumentPositionParams): lsp.CompletionItem[] => {
123120
const {fileName, service, offset, languageId} = documents.getServiceInfo(textDocumentPosition.textDocument,
124121
textDocumentPosition.position)
125122
if (fileName && service && offset != null) {
@@ -142,14 +139,14 @@ connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): Comp
142139
}
143140
});
144141

145-
function ngDefintionToDefintion(definition: ng.Definition): Definition {
142+
function ngDefinitionToDefinition(definition: ng.Definition): lsp.Definition {
146143
const locations = definition.map(d => {
147-
const document = TextDocumentIdentifier.create(fileNameToUri(d.fileName));
144+
const document = lsp.TextDocumentIdentifier.create(fileNameToUri(d.fileName));
148145
const positions = documents.offsetsToPositions(document, [d.span.start, d.span.end]);
149146
return {document, positions}
150147
}).filter(d => d.positions.length > 0).map(d => {
151-
const range = Range.create(d.positions[0], d.positions[1]);
152-
return Location.create(d.document.uri, range);
148+
const range = lsp.Range.create(d.positions[0], d.positions[1]);
149+
return lsp.Location.create(d.document.uri, range);
153150
});
154151
if (locations && locations.length) {
155152
return locations;
@@ -165,22 +162,51 @@ function logErrors<T>(f: () => T): T {
165162
}
166163
}
167164

168-
connection.onDefinition((textDocumentPosition: TextDocumentPositionParams): Definition => logErrors(() => {
169-
const {fileName, service, offset, languageId} = documents.getServiceInfo(textDocumentPosition.textDocument,
170-
textDocumentPosition.position)
165+
connection.onDefinition((params: lsp.TextDocumentPositionParams) => logErrors(() => {
166+
const {textDocument, position} = params;
167+
const {fileName, service, offset} = documents.getServiceInfo(textDocument, position);
171168
if (fileName && service && offset != null) {
172169
let result = service.getDefinitionAt(fileName, offset);
173-
if (result) {
174-
return ngDefintionToDefintion(result);
170+
if (!result) {
171+
return;
172+
}
173+
if (Array.isArray(result)) {
174+
// Backwards compatibility with old ng.Location[] (ng.Definition)
175+
return ngDefinitionToDefinition(result);
175176
}
177+
const {textSpan, definitions} = result as ts.DefinitionInfoAndBoundSpan;
178+
if (!definitions || !definitions.length) {
179+
return;
180+
}
181+
const [start, end] = documents.offsetsToPositions(textDocument, [
182+
textSpan.start,
183+
textSpan.start + textSpan.length,
184+
]);
185+
const originSelectionRange = lsp.Range.create(
186+
start.line - 1, start.character - 1, end.line - 1, end.character - 1);
187+
return definitions.map((d) => {
188+
const targetUri = lsp.TextDocumentIdentifier.create(fileNameToUri(d.fileName));
189+
const [start, end] = documents.offsetsToPositions(targetUri, [
190+
d.textSpan.start,
191+
d.textSpan.start + d.textSpan.length,
192+
]);
193+
const targetRange = lsp.Range.create(
194+
start.line - 1, start.character - 1, end.line -1, end.character - 1);
195+
return {
196+
originSelectionRange,
197+
targetUri : targetUri.uri,
198+
targetRange,
199+
targetSelectionRange: targetRange,
200+
};
201+
});
176202
}
177203
}));
178204

179-
function ngHoverToHover(hover: ng.Hover, document: TextDocumentIdentifier): Hover {
205+
function ngHoverToHover(hover: ng.Hover, document: lsp.TextDocumentIdentifier): lsp.Hover {
180206
if (hover) {
181207
const positions = documents.offsetsToPositions(document, [hover.span.start, hover.span.end]);
182208
if (positions) {
183-
const range = Range.create(positions[0], positions[1]);
209+
const range = lsp.Range.create(positions[0], positions[1]);
184210
return {
185211
contents: {language: 'typescript', value: hover.text.map(t => t.text).join('')},
186212
range
@@ -189,16 +215,55 @@ function ngHoverToHover(hover: ng.Hover, document: TextDocumentIdentifier): Hove
189215
}
190216
}
191217

192-
connection.onHover((textDocumentPosition: TextDocumentPositionParams): Hover => logErrors(() => {
193-
const {fileName, service, offset, languageId} = documents.getServiceInfo(textDocumentPosition.textDocument,
194-
textDocumentPosition.position)
218+
connection.onHover((params: lsp.TextDocumentPositionParams): lsp.Hover => logErrors(() => {
219+
const {position, textDocument} = params;
220+
const {fileName, service, offset} = documents.getServiceInfo(textDocument, position);
195221
if (fileName && service && offset != null) {
196-
let result = service.getHoverAt(fileName, offset);
197-
if (result) {
198-
return ngHoverToHover(result, textDocumentPosition.textDocument);
222+
const result = service.getHoverAt(fileName, offset);
223+
if (!result) {
224+
return;
225+
}
226+
if (isNgHover(result)) {
227+
// Backwards compatibility with old ng.Hover
228+
return ngHoverToHover(result, params.textDocument);
229+
}
230+
const {kind, kindModifiers, textSpan, displayParts, documentation} = result as ts.QuickInfo;
231+
let desc = kindModifiers ? kindModifiers + ' ' : '';
232+
if (displayParts) {
233+
// displayParts does not contain info about kindModifiers
234+
// but displayParts does contain info about kind
235+
desc += displayParts.map(dp => dp.text).join('');
199236
}
237+
else {
238+
desc += kind;
239+
}
240+
const contents: lsp.MarkedString[] = [{
241+
language: 'typescript',
242+
value: desc,
243+
}];
244+
if (documentation) {
245+
for (const d of documentation) {
246+
contents.push(d.text);
247+
}
248+
}
249+
const [start, end] = documents.offsetsToPositions(textDocument, [
250+
textSpan.start,
251+
textSpan.start + textSpan.length,
252+
]);
253+
return {
254+
contents,
255+
range: lsp.Range.create(
256+
start.line - 1, start.character - 1, end.line - 1, end.character - 1),
257+
};
200258
}
201259
}));
202260

261+
function isNgHover(result: ng.Hover): result is ng.Hover {
262+
if (result.span && Array.isArray(result.text)) {
263+
return true;
264+
}
265+
return false;
266+
}
267+
203268
// Listen on the connection
204269
connection.listen();

0 commit comments

Comments
 (0)