1
1
/*
2
- * Copyright 2017 IBM Corporation
2
+ * Copyright 2017-18 IBM Corporation
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
@@ -20,49 +20,54 @@ const fs = require('fs'),
20
20
path = require ( 'path' ) ,
21
21
expandHomeDir = require ( 'expand-home-dir' )
22
22
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
+
23
33
/**
24
34
* We've found a match. Add this match to the given partial match,
25
35
* located in the given dirname'd directory, and update the given
26
36
* prompt, which is an <input>.
27
- *
28
- */
37
+ *
38
+ */
29
39
const complete = ( match , prompt , { temporaryContainer, partial= temporaryContainer . partial , dirname= temporaryContainer . dirname , addSpace= false } ) => {
30
40
debug ( 'completion' , match , partial , dirname )
31
41
32
42
// 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 )
35
44
36
45
if ( temporaryContainer ) {
37
46
temporaryContainer . cleanup ( )
38
47
}
39
48
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 + '/'
54
57
} else {
55
- console . error ( err )
58
+ // otherwise, dirname/match is not a directory
59
+ debug ( 'complete as scalar' )
60
+ prompt . value = prompt . value + completion
56
61
}
57
- } )
62
+ } else {
63
+ console . error ( err )
64
+ }
65
+ } )
58
66
59
- } else {
60
- // otherwise, just add the completion to the prompt
61
- debug ( 'complete as scalar (alt)' )
62
- prompt . value = prompt . value + completion
63
- }
64
67
} 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
66
71
}
67
72
}
68
73
@@ -80,6 +85,44 @@ const installKeyHandlers = prompt => {
80
85
}
81
86
}
82
87
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
+
83
126
/**
84
127
* Install keyboard up-arrow and down-arrow handlers in the given REPL
85
128
* prompt. This needs to be installed in the prompt, as ui.js installs
@@ -247,7 +290,7 @@ const makeCompletionContainer = (block, prompt, partial, dirname, lastIdx) => {
247
290
* Add a suggestion to the suggestion container
248
291
*
249
292
*/
250
- const addSuggestion = ( temporaryContainer , partial , dirname , prompt ) => ( match , idx ) => {
293
+ const addSuggestion = ( temporaryContainer , dirname , prompt ) => ( match , idx ) => {
251
294
const matchLabel = match . label || match ,
252
295
matchCompletion = match . completion || matchLabel
253
296
@@ -281,7 +324,7 @@ const addSuggestion = (temporaryContainer, partial, dirname, prompt) => (match,
281
324
282
325
// onclick, use this match as the completion
283
326
option . addEventListener ( 'click' , ( ) => {
284
- complete ( matchCompletion , prompt , { temporaryContainer, partial , dirname, addSpace : match . addSpace } )
327
+ complete ( matchCompletion , prompt , { temporaryContainer, dirname, addSpace : match . addSpace } )
285
328
} )
286
329
287
330
option . setAttribute ( 'data-match' , matchLabel )
@@ -341,9 +384,11 @@ const suggestLocalFile = (last, block, prompt, temporaryContainer, lastIdx) => {
341
384
temporaryContainer = makeCompletionContainer ( block , prompt , partial , dirname , lastIdx )
342
385
}
343
386
387
+ updateReplToReflectLongestPrefix ( prompt , matches , temporaryContainer )
388
+
344
389
// add each match to that temporary div
345
390
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 )
347
392
348
393
// see if the match is a directory, so that we add a trailing slash
349
394
fs . lstat ( expandHomeDir ( path . join ( dirname , match ) ) , ( err , stats ) => {
@@ -398,7 +443,9 @@ const filterAndPresentEntitySuggestions = (last, block, prompt, temporaryContain
398
443
temporaryContainer = makeCompletionContainer ( block , prompt , partial , dirname , lastIdx )
399
444
}
400
445
401
- filteredList . forEach ( addSuggestion ( temporaryContainer , partial , dirname , prompt ) )
446
+ updateReplToReflectLongestPrefix ( prompt , filteredList , temporaryContainer )
447
+
448
+ filteredList . forEach ( addSuggestion ( temporaryContainer , dirname , prompt ) )
402
449
}
403
450
}
404
451
@@ -429,7 +476,7 @@ const suggestCommandCompletions = (matches, partial, block, prompt, temporaryCon
429
476
}
430
477
431
478
// add suggestions to the container
432
- matches . forEach ( addSuggestion ( temporaryContainer , partial , undefined , prompt ) )
479
+ matches . forEach ( addSuggestion ( temporaryContainer , undefined , prompt ) )
433
480
}
434
481
}
435
482
0 commit comments