@@ -322,53 +322,66 @@ export function transformLSPCompletions<T>(
322322 createCompletionItem : ( kind : string , match : lsProtocol . CompletionItem ) => T ,
323323 console : ILSPLogConsole
324324) {
325- let prefix = token . value . slice ( 0 , positionInToken + 1 ) ;
326- let allNonPrefixed = true ;
325+ let prefix = token . value . slice ( 0 , positionInToken ) ;
326+ let suffix = token . value . slice ( positionInToken , token . value . length ) ;
327327 let items : T [ ] = [ ] ;
328- let prefixLength = prefix . length ;
328+ // If there are no prefixes, we will just insert the text without replacing the token,
329+ // which is the case for example in R for `stats::<tab>` which returns module members
330+ // without `::` prefix.
331+ // If there are prefixes, we will replace the token so we may need to prepend/append to,
332+ // or otherwise modify the insert text of individual completion items.
333+ let anyPrefixed = false ;
334+
329335 lspCompletionItems . forEach ( match => {
330336 let kind = match . kind ? CompletionItemKind [ match . kind ] : '' ;
331337
332- // Update prefix values
333338 let text = match . insertText ? match . insertText : match . label ;
339+ let intendedText = match . insertText ? match . insertText : match . label ;
334340
335- // declare prefix presence if needed and update it
336- if ( text . toLowerCase ( ) . startsWith ( prefix . toLowerCase ( ) ) ) {
337- allNonPrefixed = false ;
338- if ( prefix !== token . value ) {
339- if ( text . toLowerCase ( ) . startsWith ( token . value . toLowerCase ( ) ) ) {
340- // given a completion insert text "display_table" and two test cases:
341- // disp<tab>data → display_table<cursor>data
342- // disp<tab>lay → display_table<cursor>
343- // we have to adjust the prefix for the latter (otherwise we would get display_table<cursor>lay),
344- // as we are constrained NOT to replace after the prefix (which would be "disp" otherwise)
345- prefix = token . value ;
346- }
341+ if ( intendedText . toLowerCase ( ) . startsWith ( prefix . toLowerCase ( ) ) ) {
342+ anyPrefixed = true ;
343+ }
344+
345+ // Add overlap with token prefix
346+ if ( intendedText . toLowerCase ( ) . startsWith ( token . value . toLowerCase ( ) ) ) {
347+ anyPrefixed = true ;
348+ // remove overlap with prefix before expanding it
349+ if ( intendedText . toLowerCase ( ) . startsWith ( prefix . toLowerCase ( ) ) ) {
350+ text = text . substring ( prefix . length , text . length ) ;
351+ match . insertText = text ;
347352 }
353+ text = token . value + text ;
354+ match . insertText = text ;
348355 }
349- // add prefix if needed
350- else if ( token . type === 'String' && prefix . includes ( '/' ) ) {
351- // special case for path completion in strings, ensuring that:
352- // '/Com<tab> → '/Completion.ipynb
353- // when the returned insert text is `Completion.ipynb` (the token here is `'/Com`)
354- // developed against pyls and pylsp server, may not work well in other cases
355356
357+ // special handling for paths
358+ if ( token . type === 'String' && prefix . includes ( '/' ) ) {
356359 const parts = stripQuotes ( prefix ) . split ( '/' ) ;
357360 if (
358361 text . toLowerCase ( ) . startsWith ( parts [ parts . length - 1 ] . toLowerCase ( ) )
359362 ) {
360363 let pathPrefix = parts . slice ( 0 , - 1 ) . join ( '/' ) + '/' ;
361- const withStartingQuote =
364+ text =
362365 ( prefix . startsWith ( "'" ) || prefix . startsWith ( '"' ) ? prefix [ 0 ] : '' ) +
363366 pathPrefix +
364367 text ;
365- match . insertText = withStartingQuote ;
368+ match . insertText = text ;
366369 // for label without quotes
367370 match . label = pathPrefix + match . label ;
368- if ( prefix . endsWith ( "'" ) || prefix . endsWith ( '"' ) ) {
369- prefixLength = prefix . length - 1 ;
371+ anyPrefixed = true ;
372+ }
373+ } else {
374+ // harmonise end to token
375+ if ( text . toLowerCase ( ) . endsWith ( suffix . toLowerCase ( ) ) ) {
376+ text = text . substring ( 0 , text . length - suffix . length ) ;
377+ match . insertText = text ;
378+ } else if ( token . type === 'String' ) {
379+ // special case for completion in strings to preserve the closing quote;
380+ // there is an issue that this gives opposing results in Notebook vs File editor
381+ // probably due to reconciliator logic
382+ if ( suffix . startsWith ( "'" ) || suffix . startsWith ( '"' ) ) {
383+ match . insertText = text + suffix [ 0 ] ;
370384 }
371- allNonPrefixed = false ;
372385 }
373386 }
374387
@@ -377,29 +390,17 @@ export function transformLSPCompletions<T>(
377390 items . push ( completionItem ) ;
378391 } ) ;
379392 console . debug ( 'Transformed' ) ;
380- // required to make the repetitive trigger characters like :: or ::: work for R with R languageserver,
381- // see https://github.com/jupyter-lsp/jupyterlab-lsp/issues/436
382- let prefixOffset = token . value . length ;
383- // completion of dictionaries for Python with jedi-language-server was
384- // causing an issue for dic['<tab>'] case; to avoid this let's make
385- // sure that prefix.length >= prefix.offset
386- if ( allNonPrefixed && prefixOffset > prefixLength ) {
387- prefixOffset = prefixLength ;
393+
394+ let start = token . offset ;
395+ let end = token . offset + token . value . length ;
396+ if ( ! anyPrefixed ) {
397+ start = end ;
388398 }
389399
390400 let response = {
391- // note in the ContextCompleter it was:
392- // start: token.offset,
393- // end: token.offset + token.value.length,
394- // which does not work with "from statistics import <tab>" as the last token ends at "t" of "import",
395- // so the completer would append "mean" as "from statistics importmean" (without space!);
396- // (in such a case the typedCharacters is undefined as we are out of range)
397- // a different workaround would be to prepend the token.value prefix:
398- // text = token.value + text;
399- // but it did not work for "from statistics <tab>" and lead to "from statisticsimport" (no space)
400- start : token . offset + ( allNonPrefixed ? prefixOffset : 0 ) ,
401- end : token . offset + prefixLength ,
402- items : items ,
401+ start,
402+ end,
403+ items,
403404 source : 'LSP'
404405 } ;
405406 if ( response . start > response . end ) {
0 commit comments