6
6
CompletionConnector
7
7
} from '@jupyterlab/completer' ;
8
8
import { CodeEditor } from '@jupyterlab/codeeditor' ;
9
- import { ReadonlyJSONObject } from '@lumino/coreutils' ;
9
+ import { JSONArray , JSONObject } from '@lumino/coreutils' ;
10
10
import { completionItemKindNames , CompletionTriggerKind } from '../../../lsp' ;
11
11
import * as lsProtocol from 'vscode-languageserver-protocol' ;
12
12
import { PositionConverter } from '../../../converter' ;
@@ -156,7 +156,7 @@ export class LSPConnector extends DataConnector<
156
156
if ( document . language === kernelLanguage ) {
157
157
return Promise . all ( [
158
158
this . _kernel_connector . fetch ( request ) ,
159
- this . hint (
159
+ this . fetch_lsp (
160
160
token ,
161
161
typed_character ,
162
162
virtual_start ,
@@ -166,12 +166,12 @@ export class LSPConnector extends DataConnector<
166
166
position_in_token
167
167
)
168
168
] ) . then ( ( [ kernel , lsp ] ) =>
169
- this . merge_replies ( kernel , lsp , this . _editor )
169
+ this . merge_replies ( this . transform_reply ( kernel ) , lsp , this . _editor )
170
170
) ;
171
171
}
172
172
}
173
173
174
- return this . hint (
174
+ return this . fetch_lsp (
175
175
token ,
176
176
typed_character ,
177
177
virtual_start ,
@@ -189,7 +189,7 @@ export class LSPConnector extends DataConnector<
189
189
}
190
190
}
191
191
192
- async hint (
192
+ async fetch_lsp (
193
193
token : CodeEditor . IToken ,
194
194
typed_character : string ,
195
195
start : IVirtualPosition ,
@@ -200,15 +200,10 @@ export class LSPConnector extends DataConnector<
200
200
) : Promise < CompletionHandler . IReply > {
201
201
let connection = this . _connections . get ( document . id_path ) ;
202
202
203
- // nope - do not do this; we need to get the signature (yes)
204
- // but only in order to bump the priority of the parameters!
205
- // unfortunately there is no abstraction of scores exposed
206
- // to the matches...
207
- // Suggested in https://github.com/jupyterlab/jupyterlab/issues/7044, TODO PR
208
-
203
+ console . log ( '[LSP][Completer] Fetching and Transforming' ) ;
209
204
console . log ( '[LSP][Completer] Token:' , token ) ;
210
205
211
- let completion_items = ( ( await connection . getCompletion (
206
+ let lspCompletionItems = ( ( await connection . getCompletion (
212
207
cursor ,
213
208
{
214
209
start,
@@ -222,22 +217,23 @@ export class LSPConnector extends DataConnector<
222
217
) ) || [ ] ) as lsProtocol . CompletionItem [ ] ;
223
218
224
219
let prefix = token . value . slice ( 0 , position_in_token + 1 ) ;
225
-
226
- let matches : Array < string > = [ ] ;
227
- const types : Array < IItemType > = [ ] ;
228
220
let all_non_prefixed = true ;
229
- for ( let match of completion_items ) {
230
- // there are more interesting things to be extracted and passed to the metadata:
231
- // detail: "__main__"
232
- // documentation: "mean(data)↵↵Return the sample arithmetic mean of data.↵↵>>> mean([1, 2, 3, 4, 4])↵2.8↵↵>>> from fractions import Fraction as F↵>>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)])↵Fraction(13, 21)↵↵>>> from decimal import Decimal as D↵>>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")])↵Decimal('0.5625')↵↵If ``data`` is empty, StatisticsError will be raised."
233
- // insertText: "mean"
234
- // kind: 3
235
- // label: "mean(data)"
236
- // sortText: "amean"
237
-
238
- // TODO: add support for match.textEdit
221
+ let items : CompletionHandler . ICompletionItem [ ] = [ ] ;
222
+ lspCompletionItems . forEach ( match => {
223
+ let completionItem = {
224
+ label : match . label ,
225
+ insertText : match . insertText ,
226
+ type : match . kind ? completionItemKindNames [ match . kind ] : '' ,
227
+ documentation : lsProtocol . MarkupContent . is ( match . documentation )
228
+ ? match . documentation . value
229
+ : match . documentation ,
230
+ filterText : match . filterText ,
231
+ deprecated : match . deprecated ,
232
+ data : { ...match }
233
+ } ;
234
+
235
+ // Update prefix values
239
236
let text = match . insertText ? match . insertText : match . label ;
240
-
241
237
if ( text . toLowerCase ( ) . startsWith ( prefix . toLowerCase ( ) ) ) {
242
238
all_non_prefixed = false ;
243
239
if ( prefix !== token . value ) {
@@ -252,12 +248,8 @@ export class LSPConnector extends DataConnector<
252
248
}
253
249
}
254
250
255
- matches . push ( text ) ;
256
- types . push ( {
257
- text : text ,
258
- type : match . kind ? completionItemKindNames [ match . kind ] : ''
259
- } ) ;
260
- }
251
+ items . push ( completionItem ) ;
252
+ } ) ;
261
253
262
254
return {
263
255
// note in the ContextCompleter it was:
@@ -271,102 +263,161 @@ export class LSPConnector extends DataConnector<
271
263
// but it did not work for "from statistics <tab>" and lead to "from statisticsimport" (no space)
272
264
start : token . offset + ( all_non_prefixed ? 1 : 0 ) ,
273
265
end : token . offset + prefix . length ,
274
- matches : matches ,
275
- metadata : {
276
- _jupyter_types_experimental : types
277
- }
266
+ matches : [ ] ,
267
+ metadata : { } ,
268
+ items
278
269
} ;
279
270
}
280
271
272
+ private transform_reply (
273
+ reply : CompletionHandler . IReply
274
+ ) : CompletionHandler . IReply {
275
+ console . log ( '[LSP][Completer] Transforming kernel reply:' , reply ) ;
276
+ const items = new Array < CompletionHandler . ICompletionItem > ( ) ;
277
+ const metadata = reply . metadata || { } ;
278
+ const types = metadata . _jupyter_types_experimental as JSONArray ;
279
+
280
+ if ( types ) {
281
+ types . forEach ( ( item : JSONObject ) => {
282
+ // For some reason the _jupyter_types_experimental list has two entries
283
+ // for each match, with one having a type of "<unknown>". Discard those
284
+ // and use undefined to indicate an unknown type.
285
+ const text = item . text as string ;
286
+ const type = item . type as string ;
287
+ items . push ( { label : text , type } ) ;
288
+ } ) ;
289
+ } else {
290
+ const matches = reply . matches ;
291
+ matches . forEach ( match => {
292
+ items . push ( { label : match } ) ;
293
+ } ) ;
294
+ }
295
+ return { ...reply , items } ;
296
+ }
297
+
281
298
private merge_replies (
282
299
kernel : CompletionHandler . IReply ,
283
300
lsp : CompletionHandler . IReply ,
284
301
editor : CodeEditor . IEditor
285
- ) {
286
- // This is based on https://github.com/jupyterlab/jupyterlab/blob/f1bc02ced61881df94c49929837c49c022f5b115/packages/completer/src/connector.ts#L78
287
- // Copyright (c) Jupyter Development Team.
288
- // Distributed under the terms of the Modified BSD License.
289
-
290
- // If one is empty, return the other.
291
- if ( kernel . matches . length === 0 ) {
292
- return lsp ;
293
- } else if ( lsp . matches . length === 0 ) {
294
- return kernel ;
295
- }
302
+ ) : CompletionHandler . IReply {
296
303
console . log ( '[LSP][Completer] Merging completions:' , lsp , kernel ) ;
297
-
298
- // Populate the result with a copy of the lsp matches.
299
- const matches = lsp . matches . slice ( ) ;
300
- const types = lsp . metadata . _jupyter_types_experimental as Array < IItemType > ;
301
-
302
- // Cache all the lsp matches in a memo.
303
- const memo = new Set < string > ( matches ) ;
304
- const memo_types = new Map < string , string > (
305
- types . map ( v => [ v . text , v . type ] )
306
- ) ;
307
-
308
- let prefix = '' ;
309
-
310
- // if the kernel used a wider range, get the previous characters to strip the prefix off,
311
- // so that both use the same range
312
- if ( lsp . start > kernel . start ) {
313
- const cursor = editor . getCursorPosition ( ) ;
314
- const line = editor . getLine ( cursor . line ) ;
315
- prefix = line . substring ( kernel . start , lsp . start ) ;
316
- console . log ( '[LSP][Completer] Removing kernel prefix: ' , prefix ) ;
317
- } else if ( lsp . start < kernel . start ) {
318
- console . warn ( '[LSP][Completer] Kernel start > LSP start' ) ;
304
+ if ( ! kernel . items . length ) {
305
+ return lsp ;
319
306
}
320
-
321
- let remove_prefix = ( value : string ) => {
322
- if ( value . startsWith ( prefix ) ) {
323
- return value . substr ( prefix . length ) ;
324
- }
325
- return value ;
326
- } ;
327
-
328
- // TODO push the CompletionItem suggestion with proper sorting, this is a mess
329
- let priority_matches = new Set < string > ( ) ;
330
-
331
- if ( kernel . metadata . _jupyter_types_experimental == null ) {
332
- let kernel_types = kernel . metadata . _jupyter_types_experimental as Array <
333
- IItemType
334
- > ;
335
- kernel_types . forEach ( itemType => {
336
- let text = remove_prefix ( itemType . text ) ;
337
- if ( ! memo_types . has ( text ) ) {
338
- memo_types . set ( text , itemType . type ) ;
339
- if ( itemType . type !== '<unknown>' ) {
340
- priority_matches . add ( text ) ;
341
- }
342
- }
343
- } ) ;
307
+ if ( ! lsp . items . length ) {
308
+ return kernel ;
344
309
}
345
-
346
- // Add each context match that is not in the memo to the result.
347
- kernel . matches . forEach ( match => {
348
- match = remove_prefix ( match ) ;
349
- if ( ! memo . has ( match ) && ! priority_matches . has ( match ) ) {
350
- matches . push ( match ) ;
310
+ // Combine ICompletionItems across multiple IReply objects
311
+ const aggregatedItems = lsp . items . concat ( kernel . items ) ;
312
+ // De-dupe and filter items
313
+ const labelSet = new Set < String > ( ) ;
314
+ const processedItems = new Array < CompletionHandler . ICompletionItem > ( ) ;
315
+ // TODO: Integrate prefix stripping?
316
+ aggregatedItems . forEach ( item => {
317
+ if (
318
+ labelSet . has ( item . label ) ||
319
+ ( item . type && item . type === '<unknown>' )
320
+ ) {
321
+ return ;
351
322
}
323
+ labelSet . add ( item . label ) ;
324
+ processedItems . push ( item ) ;
352
325
} ) ;
353
-
354
- let final_matches : Array < string > = Array . from ( priority_matches ) . concat (
355
- matches
356
- ) ;
357
- let merged_types : Array < IItemType > = Array . from (
358
- memo_types . entries ( )
359
- ) . map ( ( [ key , value ] ) => ( { text : key , type : value } ) ) ;
360
-
361
- return {
362
- ...lsp ,
363
- matches : final_matches ,
364
- metadata : {
365
- _jupyter_types_experimental : merged_types
366
- }
367
- } ;
326
+ // TODO: Sort items
327
+ // Return reply with processed items.
328
+ return { ...lsp , items : processedItems } ;
368
329
}
369
330
331
+ // TODO: Remove this
332
+ // private merge_replies_old(
333
+ // kernel: CompletionHandler.IReply,
334
+ // lsp: CompletionHandler.IReply,
335
+ // editor: CodeEditor.IEditor
336
+ // ) {
337
+ // // This is based on https://github.com/jupyterlab/jupyterlab/blob/f1bc02ced61881df94c49929837c49c022f5b115/packages/completer/src/connector.ts#L78
338
+ // // Copyright (c) Jupyter Development Team.
339
+ // // Distributed under the terms of the Modified BSD License.
340
+
341
+ // // If one is empty, return the other.
342
+ // if (kernel.matches.length === 0) {
343
+ // return lsp;
344
+ // } else if (lsp.matches.length === 0) {
345
+ // return kernel;
346
+ // }
347
+ // console.log('[LSP][Completer] Merging completions:', lsp, kernel);
348
+
349
+ // // Populate the result with a copy of the lsp matches.
350
+ // const matches = lsp.matches.slice();
351
+ // const types = lsp.metadata._jupyter_types_experimental as Array<IItemType>;
352
+
353
+ // // Cache all the lsp matches in a memo.
354
+ // const memo = new Set<string>(matches);
355
+ // const memo_types = new Map<string, string>(
356
+ // types.map(v => [v.text, v.type])
357
+ // );
358
+
359
+ // let prefix = '';
360
+
361
+ // // if the kernel used a wider range, get the previous characters to strip the prefix off,
362
+ // // so that both use the same range
363
+ // if (lsp.start > kernel.start) {
364
+ // const cursor = editor.getCursorPosition();
365
+ // const line = editor.getLine(cursor.line);
366
+ // prefix = line.substring(kernel.start, lsp.start);
367
+ // console.log('[LSP][Completer] Removing kernel prefix: ', prefix);
368
+ // } else if (lsp.start < kernel.start) {
369
+ // console.warn('[LSP][Completer] Kernel start > LSP start');
370
+ // }
371
+
372
+ // let remove_prefix = (value: string) => {
373
+ // if (value.startsWith(prefix)) {
374
+ // return value.substr(prefix.length);
375
+ // }
376
+ // return value;
377
+ // };
378
+
379
+ // // TODO push the CompletionItem suggestion with proper sorting, this is a mess
380
+ // let priority_matches = new Set<string>();
381
+
382
+ // if (kernel.metadata._jupyter_types_experimental == null) {
383
+ // let kernel_types = kernel.metadata._jupyter_types_experimental as Array<
384
+ // IItemType
385
+ // >;
386
+ // kernel_types.forEach(itemType => {
387
+ // let text = remove_prefix(itemType.text);
388
+ // if (!memo_types.has(text)) {
389
+ // memo_types.set(text, itemType.type);
390
+ // if (itemType.type !== '<unknown>') {
391
+ // priority_matches.add(text);
392
+ // }
393
+ // }
394
+ // });
395
+ // }
396
+
397
+ // // Add each context match that is not in the memo to the result.
398
+ // kernel.matches.forEach(match => {
399
+ // match = remove_prefix(match);
400
+ // if (!memo.has(match) && !priority_matches.has(match)) {
401
+ // matches.push(match);
402
+ // }
403
+ // });
404
+
405
+ // let final_matches: Array<string> = Array.from(priority_matches).concat(
406
+ // matches
407
+ // );
408
+ // let merged_types: Array<IItemType> = Array.from(
409
+ // memo_types.entries()
410
+ // ).map(([key, value]) => ({ text: key, type: value }));
411
+
412
+ // return {
413
+ // ...lsp,
414
+ // matches: final_matches,
415
+ // metadata: {
416
+ // _jupyter_types_experimental: merged_types
417
+ // }
418
+ // };
419
+ // }
420
+
370
421
with_trigger_kind ( kind : CompletionTriggerKind , fn : Function ) {
371
422
try {
372
423
this . trigger_kind = kind ;
@@ -399,10 +450,3 @@ export namespace LSPConnector {
399
450
session ?: Session . ISessionConnection ;
400
451
}
401
452
}
402
-
403
- interface IItemType extends ReadonlyJSONObject {
404
- // the item value
405
- text : string ;
406
- // the item type
407
- type : string ;
408
- }
0 commit comments