@@ -12,7 +12,7 @@ import {
1212 KernelKind
1313} from '@krassowski/completion-theme/lib/types' ;
1414import { JSONArray , JSONObject } from '@lumino/coreutils' ;
15- import * as lsProtocol from 'vscode-languageserver-types' ;
15+ import type * as lsProtocol from 'vscode-languageserver-types' ;
1616
1717import { CodeCompletion as LSPCompletionSettings } from '../../_completion' ;
1818import { LSPConnection } from '../../connection' ;
@@ -53,6 +53,99 @@ export interface ICompletionsReply
5353 items : IExtendedCompletionItem [ ] ;
5454}
5555
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+
56149/**
57150 * A LSP connector for completion handlers.
58151 */
@@ -376,97 +469,21 @@ export class LSPConnector
376469 ) ) || [ ] ) as lsProtocol . CompletionItem [ ] ;
377470
378471 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- ) ;
430472
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+ ) ;
470487 }
471488
472489 protected icon_for ( type : string ) : LabIcon {
0 commit comments