11/*
2- * Copyright 2017 IBM Corporation
2+ * Copyright 2017-18 IBM Corporation
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
@@ -20,49 +20,54 @@ const fs = require('fs'),
2020 path = require ( 'path' ) ,
2121 expandHomeDir = require ( 'expand-home-dir' )
2222
23+ /**
24+ * Return partial updated with the given match; there may be some
25+ * overlap at the beginning.
26+ *
27+ */
28+ const completeWith = ( partial , match , addSpace = false ) => {
29+ const partialIdx = match . indexOf ( partial )
30+ return ( partialIdx >= 0 ? match . substring ( partialIdx + partial . length ) : match ) + ( addSpace ? ' ' : '' )
31+ }
32+
2333/**
2434 * We've found a match. Add this match to the given partial match,
2535 * located in the given dirname'd directory, and update the given
2636 * prompt, which is an <input>.
27- *
28- */
37+ *
38+ */
2939const complete = ( match , prompt , { temporaryContainer, partial= temporaryContainer . partial , dirname= temporaryContainer . dirname , addSpace= false } ) => {
3040 debug ( 'completion' , match , partial , dirname )
3141
3242 // in case match includes partial as a prefix
33- const partialIdx = match . indexOf ( partial ) ,
34- completion = ( partialIdx >= 0 ? match . substring ( partialIdx + partial . length ) : match ) + ( addSpace ? ' ' : '' )
43+ const completion = completeWith ( partial , match , addSpace )
3544
3645 if ( temporaryContainer ) {
3746 temporaryContainer . cleanup ( )
3847 }
3948
40- if ( completion ) {
41- if ( dirname ) {
42- // see if we need to add a trailing slash
43- fs . lstat ( expandHomeDir ( path . join ( dirname , match ) ) , ( err , stats ) => {
44- if ( ! err ) {
45- if ( stats . isDirectory ( ) ) {
46- // add a trailing slash if the dirname/match is a directory
47- debug ( 'complete as directory' )
48- prompt . value = prompt . value + completion + '/'
49- } else {
50- // otherwise, dirname/match is not a directory
51- debug ( 'complete as scalar' )
52- prompt . value = prompt . value + completion
53- }
49+ if ( dirname ) {
50+ // see if we need to add a trailing slash
51+ fs . lstat ( expandHomeDir ( path . join ( dirname , match ) ) , ( err , stats ) => {
52+ if ( ! err ) {
53+ if ( stats . isDirectory ( ) ) {
54+ // add a trailing slash if the dirname/match is a directory
55+ debug ( 'complete as directory' )
56+ prompt . value = prompt . value + completion + '/'
5457 } else {
55- console . error ( err )
58+ // otherwise, dirname/match is not a directory
59+ debug ( 'complete as scalar' )
60+ prompt . value = prompt . value + completion
5661 }
57- } )
62+ } else {
63+ console . error ( err )
64+ }
65+ } )
5866
59- } else {
60- // otherwise, just add the completion to the prompt
61- debug ( 'complete as scalar (alt)' )
62- prompt . value = prompt . value + completion
63- }
6467 } else {
65- debug ( 'no completion string' )
68+ // otherwise, just add the completion to the prompt
69+ debug ( 'complete as scalar (alt)' )
70+ prompt . value = prompt . value + completion
6671 }
6772}
6873
@@ -80,6 +85,44 @@ const installKeyHandlers = prompt => {
8085 }
8186}
8287
88+ /**
89+ * Given a list of matches to the partial that is in the
90+ * prompt.value, update prompt.value so that it contains the longest
91+ * common prefix of the matches
92+ *
93+ */
94+ const updateReplToReflectLongestPrefix = ( prompt , matches , temporaryContainer , partial = temporaryContainer . partial ) => {
95+ if ( matches . length > 0 ) {
96+ const shortest = matches . reduce ( ( minLength , match ) => ! minLength ? match . length : Math . min ( minLength , match . length ) , false )
97+ let idx = 0
98+
99+ const partialComplete = idx => {
100+ const completion = completeWith ( partial , matches [ 0 ] . substring ( 0 , idx ) )
101+ temporaryContainer . partial = temporaryContainer . partial + completion
102+ prompt . value = prompt . value + completion
103+ }
104+
105+ for ( idx = 0 ; idx < shortest ; idx ++ ) {
106+ const char = matches [ 0 ] . charAt ( idx )
107+
108+ for ( let jdx = 1 ; jdx < matches . length ; jdx ++ ) {
109+ const other = matches [ jdx ] . charAt ( idx )
110+ if ( char != other ) {
111+ if ( idx > 0 ) {
112+ // then we found some common prefix
113+ return partialComplete ( idx )
114+ }
115+ }
116+ }
117+ }
118+
119+ if ( idx > 0 ) {
120+ partialComplete ( idx )
121+ }
122+ }
123+ }
124+
125+
83126/**
84127 * Install keyboard up-arrow and down-arrow handlers in the given REPL
85128 * prompt. This needs to be installed in the prompt, as ui.js installs
@@ -247,7 +290,7 @@ const makeCompletionContainer = (block, prompt, partial, dirname, lastIdx) => {
247290 * Add a suggestion to the suggestion container
248291 *
249292 */
250- const addSuggestion = ( temporaryContainer , partial , dirname , prompt ) => ( match , idx ) => {
293+ const addSuggestion = ( temporaryContainer , dirname , prompt ) => ( match , idx ) => {
251294 const matchLabel = match . label || match ,
252295 matchCompletion = match . completion || matchLabel
253296
@@ -281,7 +324,7 @@ const addSuggestion = (temporaryContainer, partial, dirname, prompt) => (match,
281324
282325 // onclick, use this match as the completion
283326 option . addEventListener ( 'click' , ( ) => {
284- complete ( matchCompletion , prompt , { temporaryContainer, partial , dirname, addSpace : match . addSpace } )
327+ complete ( matchCompletion , prompt , { temporaryContainer, dirname, addSpace : match . addSpace } )
285328 } )
286329
287330 option . setAttribute ( 'data-match' , matchLabel )
@@ -341,9 +384,11 @@ const suggestLocalFile = (last, block, prompt, temporaryContainer, lastIdx) => {
341384 temporaryContainer = makeCompletionContainer ( block , prompt , partial , dirname , lastIdx )
342385 }
343386
387+ updateReplToReflectLongestPrefix ( prompt , matches , temporaryContainer )
388+
344389 // add each match to that temporary div
345390 matches . forEach ( ( match , idx ) => {
346- const { option, optionInner } = addSuggestion ( temporaryContainer , partial , dirname , prompt ) ( match , idx )
391+ const { option, optionInner } = addSuggestion ( temporaryContainer , dirname , prompt ) ( match , idx )
347392
348393 // see if the match is a directory, so that we add a trailing slash
349394 fs . lstat ( expandHomeDir ( path . join ( dirname , match ) ) , ( err , stats ) => {
@@ -398,7 +443,9 @@ const filterAndPresentEntitySuggestions = (last, block, prompt, temporaryContain
398443 temporaryContainer = makeCompletionContainer ( block , prompt , partial , dirname , lastIdx )
399444 }
400445
401- filteredList . forEach ( addSuggestion ( temporaryContainer , partial , dirname , prompt ) )
446+ updateReplToReflectLongestPrefix ( prompt , filteredList , temporaryContainer )
447+
448+ filteredList . forEach ( addSuggestion ( temporaryContainer , dirname , prompt ) )
402449 }
403450}
404451
@@ -429,7 +476,7 @@ const suggestCommandCompletions = (matches, partial, block, prompt, temporaryCon
429476 }
430477
431478 // add suggestions to the container
432- matches . forEach ( addSuggestion ( temporaryContainer , partial , undefined , prompt ) )
479+ matches . forEach ( addSuggestion ( temporaryContainer , undefined , prompt ) )
433480 }
434481}
435482
0 commit comments