5
5
* ------------------------------------------------------------------------------------------ */
6
6
7
7
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' ;
14
10
15
11
import { TextDocuments , TextDocumentEvent , fileNameToUri } from './documents' ;
16
12
import { ErrorCollector } from './errors' ;
17
13
18
14
import { Completion } from '@angular/language-service' ;
19
15
20
16
// 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 ( ) ;
22
18
23
19
// Create a simple text document manager. The text document manager
24
20
// supports full document sync only
25
- let documents : TextDocuments = new TextDocuments ( handleTextEvent ) ;
21
+ const documents : TextDocuments = new TextDocuments ( handleTextEvent ) ;
26
22
27
23
28
24
// Setup the error collector that watches for document events and requests errors
@@ -45,7 +41,7 @@ documents.listen(connection);
45
41
// After the server has started the client sends an initilize request. The server receives
46
42
// in the passed params the rootPath of the workspace plus the client capabilites.
47
43
let workspaceRoot : string ;
48
- connection . onInitialize ( ( params ) : InitializeResult => {
44
+ connection . onInitialize ( ( params ) : lsp . InitializeResult => {
49
45
workspaceRoot = params . rootPath ;
50
46
return {
51
47
capabilities : {
@@ -62,7 +58,8 @@ connection.onInitialize((params): InitializeResult => {
62
58
}
63
59
} ) ;
64
60
65
- function compiletionKindToCompletionItemKind ( kind : string ) : CompletionItemKind {
61
+ function compiletionKindToCompletionItemKind ( kind : string ) : lsp . CompletionItemKind {
62
+ const { CompletionItemKind} = lsp ;
66
63
switch ( kind ) {
67
64
case 'attribute' : return CompletionItemKind . Property ;
68
65
case 'html attribute' : return CompletionItemKind . Property ;
@@ -84,13 +81,13 @@ const wordRe = /(\w|\(|\)|\[|\]|\*|\-|\_|\.)+/g;
84
81
const special = / \( | \) | \[ | \] | \* | \- | \_ | \. / ;
85
82
86
83
// 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 {
88
85
if ( insertText . match ( special ) && range ) {
89
- return TextEdit . replace ( range , insertText ) ;
86
+ return lsp . TextEdit . replace ( range , insertText ) ;
90
87
}
91
88
}
92
89
93
- function getReplaceRange ( document : TextDocumentIdentifier , offset : number ) : Range {
90
+ function getReplaceRange ( document : lsp . TextDocumentIdentifier , offset : number ) : lsp . Range {
94
91
const line = documents . getDocumentLine ( document , offset ) ;
95
92
if ( line && line . text && line . start <= offset && line . start + line . text . length >= offset ) {
96
93
const lineOffset = offset - line . start - 1 ;
@@ -104,7 +101,7 @@ function getReplaceRange(document: TextDocumentIdentifier, offset: number): Rang
104
101
}
105
102
} ) ) ;
106
103
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 ) ;
108
105
}
109
106
}
110
107
}
@@ -119,7 +116,7 @@ function insertTextOf(completion: Completion): string {
119
116
}
120
117
121
118
// This handler provides the initial list of the completion items.
122
- connection . onCompletion ( ( textDocumentPosition : TextDocumentPositionParams ) : CompletionItem [ ] => {
119
+ connection . onCompletion ( ( textDocumentPosition : lsp . TextDocumentPositionParams ) : lsp . CompletionItem [ ] => {
123
120
const { fileName, service, offset, languageId} = documents . getServiceInfo ( textDocumentPosition . textDocument ,
124
121
textDocumentPosition . position )
125
122
if ( fileName && service && offset != null ) {
@@ -142,14 +139,14 @@ connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): Comp
142
139
}
143
140
} ) ;
144
141
145
- function ngDefintionToDefintion ( definition : ng . Definition ) : Definition {
142
+ function ngDefinitionToDefinition ( definition : ng . Definition ) : lsp . Definition {
146
143
const locations = definition . map ( d => {
147
- const document = TextDocumentIdentifier . create ( fileNameToUri ( d . fileName ) ) ;
144
+ const document = lsp . TextDocumentIdentifier . create ( fileNameToUri ( d . fileName ) ) ;
148
145
const positions = documents . offsetsToPositions ( document , [ d . span . start , d . span . end ] ) ;
149
146
return { document, positions}
150
147
} ) . 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 ) ;
153
150
} ) ;
154
151
if ( locations && locations . length ) {
155
152
return locations ;
@@ -165,22 +162,51 @@ function logErrors<T>(f: () => T): T {
165
162
}
166
163
}
167
164
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 ) ;
171
168
if ( fileName && service && offset != null ) {
172
169
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 ) ;
175
176
}
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
+ } ) ;
176
202
}
177
203
} ) ) ;
178
204
179
- function ngHoverToHover ( hover : ng . Hover , document : TextDocumentIdentifier ) : Hover {
205
+ function ngHoverToHover ( hover : ng . Hover , document : lsp . TextDocumentIdentifier ) : lsp . Hover {
180
206
if ( hover ) {
181
207
const positions = documents . offsetsToPositions ( document , [ hover . span . start , hover . span . end ] ) ;
182
208
if ( positions ) {
183
- const range = Range . create ( positions [ 0 ] , positions [ 1 ] ) ;
209
+ const range = lsp . Range . create ( positions [ 0 ] , positions [ 1 ] ) ;
184
210
return {
185
211
contents : { language : 'typescript' , value : hover . text . map ( t => t . text ) . join ( '' ) } ,
186
212
range
@@ -189,16 +215,55 @@ function ngHoverToHover(hover: ng.Hover, document: TextDocumentIdentifier): Hove
189
215
}
190
216
}
191
217
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 ) ;
195
221
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 ( '' ) ;
199
236
}
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
+ } ;
200
258
}
201
259
} ) ) ;
202
260
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
+
203
268
// Listen on the connection
204
269
connection . listen ( ) ;
0 commit comments