@@ -12,7 +12,7 @@ import {
12
12
KernelKind
13
13
} from '@krassowski/completion-theme/lib/types' ;
14
14
import { JSONArray , JSONObject } from '@lumino/coreutils' ;
15
- import * as lsProtocol from 'vscode-languageserver-types' ;
15
+ import type * as lsProtocol from 'vscode-languageserver-types' ;
16
16
17
17
import { CodeCompletion as LSPCompletionSettings } from '../../_completion' ;
18
18
import { LSPConnection } from '../../connection' ;
@@ -53,6 +53,99 @@ export interface ICompletionsReply
53
53
items : IExtendedCompletionItem [ ] ;
54
54
}
55
55
56
+ export function transformLSPCompletions < T > (
57
+ token : CodeEditor . IToken ,
58
+ position_in_token : number ,
59
+ lspCompletionItems : lsProtocol . CompletionItem [ ] ,
60
+ createCompletionItem : ( kind : string , match : lsProtocol . CompletionItem ) => T ,
61
+ console : ILSPLogConsole
62
+ ) {
63
+ let prefix = token . value . slice ( 0 , position_in_token + 1 ) ;
64
+ let all_non_prefixed = true ;
65
+ let items : T [ ] = [ ] ;
66
+ lspCompletionItems . forEach ( match => {
67
+ let kind = match . kind ? CompletionItemKind [ match . kind ] : '' ;
68
+
69
+ // Update prefix values
70
+ let text = match . insertText ? match . insertText : match . label ;
71
+
72
+ // declare prefix presence if needed and update it
73
+ if ( text . toLowerCase ( ) . startsWith ( prefix . toLowerCase ( ) ) ) {
74
+ all_non_prefixed = false ;
75
+ if ( prefix !== token . value ) {
76
+ if ( text . toLowerCase ( ) . startsWith ( token . value . toLowerCase ( ) ) ) {
77
+ // given a completion insert text "display_table" and two test cases:
78
+ // disp<tab>data → display_table<cursor>data
79
+ // disp<tab>lay → display_table<cursor>
80
+ // we have to adjust the prefix for the latter (otherwise we would get display_table<cursor>lay),
81
+ // as we are constrained NOT to replace after the prefix (which would be "disp" otherwise)
82
+ prefix = token . value ;
83
+ }
84
+ }
85
+ }
86
+ // add prefix if needed
87
+ else if ( token . type === 'string' && prefix . includes ( '/' ) ) {
88
+ // special case for path completion in strings, ensuring that:
89
+ // '/Com<tab> → '/Completion.ipynb
90
+ // when the returned insert text is `Completion.ipynb` (the token here is `'/Com`)
91
+ // developed against pyls and pylsp server, may not work well in other cases
92
+ const parts = prefix . split ( '/' ) ;
93
+ if (
94
+ text . toLowerCase ( ) . startsWith ( parts [ parts . length - 1 ] . toLowerCase ( ) )
95
+ ) {
96
+ let pathPrefix = parts . slice ( 0 , - 1 ) . join ( '/' ) + '/' ;
97
+ match . insertText = pathPrefix + text ;
98
+ // for label removing the prefix quote if present
99
+ if ( pathPrefix . startsWith ( "'" ) || pathPrefix . startsWith ( '"' ) ) {
100
+ pathPrefix = pathPrefix . substr ( 1 ) ;
101
+ }
102
+ match . label = pathPrefix + match . label ;
103
+ all_non_prefixed = false ;
104
+ }
105
+ }
106
+
107
+ let completionItem = createCompletionItem ( kind , match ) ;
108
+
109
+ items . push ( completionItem ) ;
110
+ } ) ;
111
+ console . debug ( 'Transformed' ) ;
112
+ // required to make the repetitive trigger characters like :: or ::: work for R with R languageserver,
113
+ // see https://github.com/jupyter-lsp/jupyterlab-lsp/issues/436
114
+ let prefix_offset = token . value . length ;
115
+ // completion of dictionaries for Python with jedi-language-server was
116
+ // causing an issue for dic['<tab>'] case; to avoid this let's make
117
+ // sure that prefix.length >= prefix.offset
118
+ if ( all_non_prefixed && prefix_offset > prefix . length ) {
119
+ prefix_offset = prefix . length ;
120
+ }
121
+
122
+ let response = {
123
+ // note in the ContextCompleter it was:
124
+ // start: token.offset,
125
+ // end: token.offset + token.value.length,
126
+ // which does not work with "from statistics import <tab>" as the last token ends at "t" of "import",
127
+ // so the completer would append "mean" as "from statistics importmean" (without space!);
128
+ // (in such a case the typedCharacters is undefined as we are out of range)
129
+ // a different workaround would be to prepend the token.value prefix:
130
+ // text = token.value + text;
131
+ // but it did not work for "from statistics <tab>" and lead to "from statisticsimport" (no space)
132
+ start : token . offset + ( all_non_prefixed ? prefix_offset : 0 ) ,
133
+ end : token . offset + prefix . length ,
134
+ items : items ,
135
+ source : {
136
+ name : 'LSP' ,
137
+ priority : 2
138
+ }
139
+ } ;
140
+ if ( response . start > response . end ) {
141
+ console . warn (
142
+ 'Response contains start beyond end; this should not happen!' ,
143
+ response
144
+ ) ;
145
+ }
146
+ return response ;
147
+ }
148
+
56
149
/**
57
150
* A LSP connector for completion handlers.
58
151
*/
@@ -376,97 +469,21 @@ export class LSPConnector
376
469
) ) || [ ] ) as lsProtocol . CompletionItem [ ] ;
377
470
378
471
this . console . debug ( 'Transforming' ) ;
379
- let prefix = token . value . slice ( 0 , position_in_token + 1 ) ;
380
- let all_non_prefixed = true ;
381
- let items : IExtendedCompletionItem [ ] = [ ] ;
382
- lspCompletionItems . forEach ( match => {
383
- let kind = match . kind ? CompletionItemKind [ match . kind ] : '' ;
384
-
385
- // Update prefix values
386
- let text = match . insertText ? match . insertText : match . label ;
387
-
388
- // declare prefix presence if needed and update it
389
- if ( text . toLowerCase ( ) . startsWith ( prefix . toLowerCase ( ) ) ) {
390
- all_non_prefixed = false ;
391
- if ( prefix !== token . value ) {
392
- if ( text . toLowerCase ( ) . startsWith ( token . value . toLowerCase ( ) ) ) {
393
- // given a completion insert text "display_table" and two test cases:
394
- // disp<tab>data → display_table<cursor>data
395
- // disp<tab>lay → display_table<cursor>
396
- // we have to adjust the prefix for the latter (otherwise we would get display_table<cursor>lay),
397
- // as we are constrained NOT to replace after the prefix (which would be "disp" otherwise)
398
- prefix = token . value ;
399
- }
400
- }
401
- }
402
- // add prefix if needed
403
- else if ( token . type === 'string' && prefix . includes ( '/' ) ) {
404
- // special case for path completion in strings, ensuring that:
405
- // '/Com<tab> → '/Completion.ipynb
406
- // when the returned insert text is `Completion.ipynb` (the token here is `'/Com`)
407
- // developed against pyls and pylsp server, may not work well in other cases
408
- const parts = prefix . split ( '/' ) ;
409
- if (
410
- text . toLowerCase ( ) . startsWith ( parts [ parts . length - 1 ] . toLowerCase ( ) )
411
- ) {
412
- let pathPrefix = parts . slice ( 0 , - 1 ) . join ( '/' ) + '/' ;
413
- match . insertText = pathPrefix + match . insertText ;
414
- // for label removing the prefix quote if present
415
- if ( pathPrefix . startsWith ( "'" ) || pathPrefix . startsWith ( '"' ) ) {
416
- pathPrefix = pathPrefix . substr ( 1 ) ;
417
- }
418
- match . label = pathPrefix + match . label ;
419
- all_non_prefixed = false ;
420
- }
421
- }
422
-
423
- let completionItem = new LazyCompletionItem (
424
- kind ,
425
- this . icon_for ( kind ) ,
426
- match ,
427
- this ,
428
- document . uri
429
- ) ;
430
472
431
- items . push ( completionItem ) ;
432
- } ) ;
433
- this . console . debug ( 'Transformed' ) ;
434
- // required to make the repetitive trigger characters like :: or ::: work for R with R languageserver,
435
- // see https://github.com/jupyter-lsp/jupyterlab-lsp/issues/436
436
- let prefix_offset = token . value . length ;
437
- // completion of dictionaries for Python with jedi-language-server was
438
- // causing an issue for dic['<tab>'] case; to avoid this let's make
439
- // sure that prefix.length >= prefix.offset
440
- if ( all_non_prefixed && prefix_offset > prefix . length ) {
441
- prefix_offset = prefix . length ;
442
- }
443
-
444
- let response = {
445
- // note in the ContextCompleter it was:
446
- // start: token.offset,
447
- // end: token.offset + token.value.length,
448
- // which does not work with "from statistics import <tab>" as the last token ends at "t" of "import",
449
- // so the completer would append "mean" as "from statistics importmean" (without space!);
450
- // (in such a case the typedCharacters is undefined as we are out of range)
451
- // a different workaround would be to prepend the token.value prefix:
452
- // text = token.value + text;
453
- // but it did not work for "from statistics <tab>" and lead to "from statisticsimport" (no space)
454
- start : token . offset + ( all_non_prefixed ? prefix_offset : 0 ) ,
455
- end : token . offset + prefix . length ,
456
- items : items ,
457
- source : {
458
- name : 'LSP' ,
459
- priority : 2
460
- }
461
- } ;
462
- if ( response . start > response . end ) {
463
- console . warn (
464
- 'Response contains start beyond end; this should not happen!' ,
465
- response
466
- ) ;
467
- }
468
-
469
- return response ;
473
+ return transformLSPCompletions (
474
+ token ,
475
+ position_in_token ,
476
+ lspCompletionItems ,
477
+ ( kind , match ) =>
478
+ new LazyCompletionItem (
479
+ kind ,
480
+ this . icon_for ( kind ) ,
481
+ match ,
482
+ this ,
483
+ document . uri
484
+ ) ,
485
+ this . console
486
+ ) ;
470
487
}
471
488
472
489
protected icon_for ( type : string ) : LabIcon {
0 commit comments