Skip to content

Commit 66d956c

Browse files
gribnoysupdanielleadams
authored andcommitted
repl: make autocomplete case-insensitive
This changes autocomplete suggestion filter to ignore input case allowing for more autosuggest results shown on the screen Fixes: #41631 PR-URL: #41632 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 931ecfa commit 66d956c

File tree

4 files changed

+138
-64
lines changed

4 files changed

+138
-64
lines changed

lib/repl.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const {
9494
StringPrototypeStartsWith,
9595
StringPrototypeTrim,
9696
StringPrototypeTrimLeft,
97+
StringPrototypeToLocaleLowerCase,
9798
Symbol,
9899
SyntaxError,
99100
SyntaxErrorPrototype,
@@ -1301,8 +1302,8 @@ function complete(line, callback) {
13011302
// Ignore right whitespace. It could change the outcome.
13021303
line = StringPrototypeTrimLeft(line);
13031304

1304-
// REPL commands (e.g. ".break").
13051305
let filter = '';
1306+
// REPL commands (e.g. ".break").
13061307
if (RegExpPrototypeTest(/^\s*\.(\w*)$/, line)) {
13071308
ArrayPrototypePush(completionGroups, ObjectKeys(this.commands));
13081309
completeOn = StringPrototypeMatch(line, /^\s*\.(\w*)$/)[1];
@@ -1545,11 +1546,16 @@ function complete(line, callback) {
15451546
// Filter, sort (within each group), uniq and merge the completion groups.
15461547
if (completionGroups.length && filter) {
15471548
const newCompletionGroups = [];
1549+
const lowerCaseFilter = StringPrototypeToLocaleLowerCase(filter);
15481550
ArrayPrototypeForEach(completionGroups, (group) => {
1549-
const filteredGroup = ArrayPrototypeFilter(
1550-
group,
1551-
(str) => StringPrototypeStartsWith(str, filter)
1552-
);
1551+
const filteredGroup = ArrayPrototypeFilter(group, (str) => {
1552+
// Filter is always case-insensitive following chromium autocomplete
1553+
// behavior.
1554+
return StringPrototypeStartsWith(
1555+
StringPrototypeToLocaleLowerCase(str),
1556+
lowerCaseFilter
1557+
);
1558+
});
15531559
if (filteredGroup.length) {
15541560
ArrayPrototypePush(newCompletionGroups, filteredGroup);
15551561
}

test/parallel/test-repl-history-navigation.js

+74-51
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const tests = [
7474
env: { NODE_REPL_HISTORY: defaultHistoryPath },
7575
test: [ 'let ab = 45', ENTER,
7676
'555 + 909', ENTER,
77+
'let autocompleteMe = 123', ENTER,
7778
'{key : {key2 :[] }}', ENTER,
7879
'Array(100).fill(1).map((e, i) => i ** i)', LEFT, LEFT, DELETE,
7980
'2', ENTER],
@@ -82,7 +83,7 @@ const tests = [
8283
},
8384
{
8485
env: { NODE_REPL_HISTORY: defaultHistoryPath },
85-
test: [UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN, DOWN],
86+
test: [UP, UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN],
8687
expected: [prompt,
8788
`${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
8889
prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
@@ -92,13 +93,15 @@ const tests = [
9293
' 2025, 2116, 2209,...',
9394
`${prompt}{key : {key2 :[] }}`,
9495
prev && '\n// { key: { key2: [] } }',
96+
`${prompt}let autocompleteMe = 123`,
9597
`${prompt}555 + 909`,
9698
prev && '\n// 1464',
9799
`${prompt}let ab = 45`,
98100
prompt,
99101
`${prompt}let ab = 45`,
100102
`${prompt}555 + 909`,
101103
prev && '\n// 1464',
104+
`${prompt}let autocompleteMe = 123`,
102105
`${prompt}{key : {key2 :[] }}`,
103106
prev && '\n// { key: { key2: [] } }',
104107
`${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
@@ -128,7 +131,7 @@ const tests = [
128131
preview: false,
129132
showEscapeCodes: true,
130133
test: [
131-
'55', UP, UP, UP, UP, UP, UP, ENTER,
134+
'55', UP, UP, UP, UP, UP, UP, UP, ENTER,
132135
],
133136
expected: [
134137
'\x1B[1G', '\x1B[0J', prompt, '\x1B[3G',
@@ -185,10 +188,10 @@ const tests = [
185188
ENTER,
186189
'veryLongName'.repeat(30),
187190
ENTER,
188-
`${'\x1B[90m \x1B[39m'.repeat(235)} fun`,
191+
`${'\x1B[90m \x1B[39m'.repeat(229)} aut`,
189192
ESCAPE,
190193
ENTER,
191-
`${' '.repeat(236)} fun`,
194+
`${' '.repeat(230)} aut`,
192195
ESCAPE,
193196
ENTER,
194197
],
@@ -236,19 +239,20 @@ const tests = [
236239
prompt, '\x1B[3G',
237240
// 1. UP
238241
// This exceeds the maximum columns (250):
239-
// Whitespace + prompt + ' // '.length + 'function'.length
240-
// 236 + 2 + 4 + 8
242+
// Whitespace + prompt + ' // '.length + 'autocompleteMe'.length
243+
// 230 + 2 + 4 + 14
241244
'\x1B[1G', '\x1B[0J',
242-
`${prompt}${' '.repeat(236)} fun`, '\x1B[243G',
243-
' // ction', '\x1B[243G',
244-
' // ction', '\x1B[243G',
245+
`${prompt}${' '.repeat(230)} aut`, '\x1B[237G',
246+
' // ocompleteMe', '\x1B[237G',
247+
'\n// 123', '\x1B[237G',
248+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
245249
'\x1B[0K',
246250
// 2. UP
247251
'\x1B[1G', '\x1B[0J',
248-
`${prompt}${' '.repeat(235)} fun`, '\x1B[242G',
249-
// TODO(BridgeAR): Investigate why the preview is generated twice.
250-
' // ction', '\x1B[242G',
251-
' // ction', '\x1B[242G',
252+
`${prompt}${' '.repeat(229)} aut`, '\x1B[236G',
253+
' // ocompleteMe', '\x1B[236G',
254+
'\n// 123', '\x1B[236G',
255+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
252256
// Preview cleanup
253257
'\x1B[0K',
254258
// 3. UP
@@ -326,8 +330,8 @@ const tests = [
326330
skip: !process.features.inspector,
327331
checkTotal: true,
328332
test: [
329-
'fu',
330-
'n',
333+
'au',
334+
't',
331335
RIGHT,
332336
BACKSPACE,
333337
LEFT,
@@ -353,74 +357,93 @@ const tests = [
353357
// K = Erase in line; 0 = right; 1 = left; 2 = total
354358
expected: [
355359
// 0.
356-
// 'f'
357-
'\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'f',
360+
// 'a'
361+
'\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'a',
358362
// 'u'
359-
'u', ' // nction', '\x1B[5G',
360-
// 'n' - Cleanup
363+
'u', ' // tocompleteMe', '\x1B[5G',
364+
'\n// 123', '\x1B[5G',
365+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
366+
// 't' - Cleanup
361367
'\x1B[0K',
362-
'n', ' // ction', '\x1B[6G',
368+
't', ' // ocompleteMe', '\x1B[6G',
369+
'\n// 123', '\x1B[6G',
370+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
363371
// 1. Right. Cleanup
364372
'\x1B[0K',
365-
'ction',
373+
'ocompleteMe',
374+
'\n// 123', '\x1B[17G',
375+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
366376
// 2. Backspace. Refresh
367-
'\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G',
377+
'\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, '\x1B[16G',
368378
// Autocomplete and refresh?
369-
' // n', '\x1B[10G', ' // n', '\x1B[10G',
379+
' // e', '\x1B[16G',
380+
'\n// 123', '\x1B[16G',
381+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
370382
// 3. Left. Cleanup
371383
'\x1B[0K',
372-
'\x1B[1D', '\x1B[10G', ' // n', '\x1B[9G',
384+
'\x1B[1D', '\x1B[16G', ' // e', '\x1B[15G',
373385
// 4. Left. Cleanup
374-
'\x1B[10G', '\x1B[0K', '\x1B[9G',
375-
'\x1B[1D', '\x1B[10G', ' // n', '\x1B[8G',
386+
'\x1B[16G', '\x1B[0K', '\x1B[15G',
387+
'\x1B[1D', '\x1B[16G', ' // e', '\x1B[14G',
376388
// 5. 'A' - Cleanup
377-
'\x1B[10G', '\x1B[0K', '\x1B[8G',
389+
'\x1B[16G', '\x1B[0K', '\x1B[14G',
378390
// Refresh
379-
'\x1B[1G', '\x1B[0J', `${prompt}functAio`, '\x1B[9G',
391+
'\x1B[1G', '\x1B[0J', `${prompt}autocompletAeM`, '\x1B[15G',
380392
// 6. Backspace. Refresh
381-
'\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[8G', '\x1B[10G', ' // n',
382-
'\x1B[8G', '\x1B[10G', ' // n',
383-
'\x1B[8G', '\x1B[10G',
393+
'\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`,
394+
'\x1B[14G', '\x1B[16G', ' // e',
395+
'\x1B[14G', '\x1B[16G', ' // e',
396+
'\x1B[14G', '\x1B[16G',
384397
// 7. Go to end. Cleanup
385-
'\x1B[0K', '\x1B[8G', '\x1B[2C',
386-
'n',
398+
'\x1B[0K', '\x1B[14G', '\x1B[2C',
399+
'e',
400+
'\n// 123', '\x1B[17G',
401+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
387402
// 8. Backspace. Refresh
388-
'\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G',
403+
'\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, '\x1B[16G',
389404
// Autocomplete
390-
' // n', '\x1B[10G', ' // n', '\x1B[10G',
405+
' // e', '\x1B[16G',
406+
'\n// 123', '\x1B[16G',
407+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
391408
// 9. Word left. Cleanup
392-
'\x1B[0K', '\x1B[7D', '\x1B[10G', ' // n', '\x1B[3G', '\x1B[10G',
409+
'\x1B[0K', '\x1B[13D', '\x1B[16G', ' // e', '\x1B[3G', '\x1B[16G',
393410
// 10. Word right. Cleanup
394-
'\x1B[0K', '\x1B[3G', '\x1B[7C', ' // n', '\x1B[10G',
411+
'\x1B[0K', '\x1B[3G', '\x1B[13C', ' // e', '\x1B[16G',
412+
'\n// 123', '\x1B[16G',
413+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
395414
// 11. ESCAPE
396415
'\x1B[0K',
397416
// 12. ENTER
398417
'\r\n',
399-
'Uncaught ReferenceError: functio is not defined\n',
418+
'Uncaught ReferenceError: autocompleteM is not defined\n',
400419
'\x1B[1G', '\x1B[0J',
401420
// 13. UP
402421
prompt, '\x1B[3G', '\x1B[1G', '\x1B[0J',
403-
`${prompt}functio`, '\x1B[10G',
404-
' // n', '\x1B[10G',
405-
' // n', '\x1B[10G',
422+
`${prompt}autocompleteM`, '\x1B[16G',
423+
' // e', '\x1B[16G',
424+
'\n// 123', '\x1B[16G',
425+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
406426
// 14. LEFT
407-
'\x1B[0K', '\x1B[1D',
408-
'\x1B[10G', ' // n', '\x1B[9G', '\x1B[10G',
427+
'\x1B[0K', '\x1B[1D', '\x1B[16G',
428+
' // e', '\x1B[15G', '\x1B[16G',
409429
// 15. ENTER
410-
'\x1B[0K', '\x1B[9G', '\x1B[1C',
430+
'\x1B[0K', '\x1B[15G', '\x1B[1C',
411431
'\r\n',
412-
'Uncaught ReferenceError: functio is not defined\n',
432+
'Uncaught ReferenceError: autocompleteM is not defined\n',
413433
'\x1B[1G', '\x1B[0J',
414-
'> ', '\x1B[3G',
434+
prompt, '\x1B[3G',
415435
// 16. UP
416436
'\x1B[1G', '\x1B[0J',
417-
'> functio', '\x1B[10G',
418-
' // n', '\x1B[10G',
419-
' // n', '\x1B[10G', '\x1B[0K',
437+
`${prompt}autocompleteM`, '\x1B[16G',
438+
' // e', '\x1B[16G',
439+
'\n// 123', '\x1B[16G',
440+
'\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
441+
'\x1B[0K',
420442
// 17. ENTER
421-
'n', '\r\n',
443+
'e', '\r\n',
444+
'123\n',
422445
'\x1B[1G', '\x1B[0J',
423-
'... ', '\x1B[5G',
446+
prompt, '\x1B[3G',
424447
'\r\n',
425448
],
426449
clean: true

test/parallel/test-repl-reverse-search.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,7 @@ const tests = [
212212
expected: [
213213
'\x1B[1G', '\x1B[0J',
214214
prompt, '\x1B[3G',
215-
'f', 'u', ' // nction',
216-
'\x1B[5G', '\x1B[0K',
217-
'\nbck-i-search: _', '\x1B[1A', '\x1B[5G',
215+
'f', 'u', '\nbck-i-search: _', '\x1B[1A', '\x1B[5G',
218216
'\x1B[3G', '\x1B[0J',
219217
'{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[21G',
220218
'\x1B[3G', '\x1B[0J',

test/parallel/test-repl-tab-complete.js

+52-5
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,38 @@ testMe.complete('str.len', common.mustCall(function(error, data) {
205205

206206
putIn.run(['.clear']);
207207

208+
// Tab completion should be case-insensitive if member part is lower-case
209+
putIn.run([
210+
'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };',
211+
]);
212+
testMe.complete(
213+
'foo.b',
214+
common.mustCall(function(error, data) {
215+
assert.deepStrictEqual(data, [
216+
['foo.BARbuz', 'foo.barBLA', 'foo.barBar'],
217+
'foo.b',
218+
]);
219+
})
220+
);
221+
222+
putIn.run(['.clear']);
223+
224+
// Tab completion should be case-insensitive if member part is upper-case
225+
putIn.run([
226+
'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };',
227+
]);
228+
testMe.complete(
229+
'foo.B',
230+
common.mustCall(function(error, data) {
231+
assert.deepStrictEqual(data, [
232+
['foo.BARbuz', 'foo.barBLA', 'foo.barBar'],
233+
'foo.B',
234+
]);
235+
})
236+
);
237+
238+
putIn.run(['.clear']);
239+
208240
// Tab completion should not break on spaces
209241
const spaceTimeout = setTimeout(function() {
210242
throw new Error('timeout');
@@ -588,12 +620,27 @@ const testNonGlobal = repl.start({
588620
useGlobal: false
589621
});
590622

591-
const builtins = [['Infinity', 'Int16Array', 'Int32Array',
592-
'Int8Array'], 'I'];
623+
const builtins = [
624+
[
625+
'if',
626+
'import',
627+
'in',
628+
'instanceof',
629+
'',
630+
'Infinity',
631+
'Int16Array',
632+
'Int32Array',
633+
'Int8Array',
634+
...(common.hasIntl ? ['Intl'] : []),
635+
'inspector',
636+
'isFinite',
637+
'isNaN',
638+
'',
639+
'isPrototypeOf',
640+
],
641+
'I',
642+
];
593643

594-
if (common.hasIntl) {
595-
builtins[0].push('Intl');
596-
}
597644
testNonGlobal.complete('I', common.mustCall((error, data) => {
598645
assert.deepStrictEqual(data, builtins);
599646
}));

0 commit comments

Comments
 (0)