Skip to content

Commit 6287fd4

Browse files
authored
fix(cli-repl): disregard autocomplete eval for input stream MONGOSH-586 (#660)
This is the second half of MONGOSH-586. The REPL’s built-in autocompleter uses `repl.eval()` to figure out what JS object’s properties it would be autocompleting. These `.eval()` calls don’t represent a state change as far as input handling is concerned, so they should not affect the prompt and not enable block-on-newline.
1 parent 2c7d950 commit 6287fd4

File tree

2 files changed

+52
-12
lines changed

2 files changed

+52
-12
lines changed

packages/cli-repl/src/mongosh-repl.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { StubbedInstance, stubInterface } from 'ts-sinon';
99
import { promisify } from 'util';
1010
import { expect, fakeTTYProps, tick, useTmpdir, waitEval } from '../test/repl-helpers';
1111
import MongoshNodeRepl, { MongoshConfigProvider, MongoshNodeReplOptions } from './mongosh-repl';
12+
import stripAnsi from 'strip-ansi';
1213

1314
const delay = promisify(setTimeout);
1415

@@ -235,6 +236,19 @@ describe('MongoshNodeRepl', () => {
235236
expect(output).to.include('65537');
236237
});
237238

239+
it('does not stop input when autocompleting during .editor', async() => {
240+
input.write('.editor\n');
241+
await tick();
242+
expect(output).to.include('Entering editor mode');
243+
output = '';
244+
input.write('db.\u0009\u0009');
245+
await tick();
246+
input.write('version()\n');
247+
input.write('\u0004'); // Ctrl+D
248+
await waitEval(bus);
249+
expect(output).to.include('Error running command serverBuildInfo');
250+
});
251+
238252
it('can enter multiline code', async() => {
239253
for (const line of multilineCode.split('\n')) {
240254
input.write(line + '\n');
@@ -298,6 +312,20 @@ describe('MongoshNodeRepl', () => {
298312
await tick();
299313
expect(output).to.include('somelongvariable');
300314
});
315+
it('autocompletion during .editor does not reset the prompt', async() => {
316+
input.write('.editor\n');
317+
await tick();
318+
output = '';
319+
expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal('');
320+
input.write('db.\u0009\u0009');
321+
await tick();
322+
input.write('foo\nbar\n');
323+
expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal('');
324+
input.write('\u0003'); // Ctrl+C for abort
325+
await tick();
326+
expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal('> ');
327+
expect(stripAnsi(output)).to.equal('ddbdb.db.\tdb.\tfdb.\tfodb.\tfoo\r\nbbabar\r\n\r\n> ');
328+
});
301329
});
302330

303331
context('history support', () => {

packages/cli-repl/src/mongosh-repl.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class MongoshNodeRepl implements EvaluationListener {
6565
shellCliOptions: Partial<MongoshCliOptions>;
6666
configProvider: MongoshConfigProvider;
6767
onClearCommand?: EvaluationListener['onClearCommand'];
68+
insideAutoComplete: boolean;
6869

6970
constructor(options: MongoshNodeReplOptions) {
7071
this.input = options.input;
@@ -74,6 +75,7 @@ class MongoshNodeRepl implements EvaluationListener {
7475
this.nodeReplOptions = options.nodeReplOptions || {};
7576
this.shellCliOptions = options.shellCliOptions || {};
7677
this.configProvider = options.configProvider;
78+
this.insideAutoComplete = false;
7779
this._runtimeState = null;
7880
}
7981

@@ -123,16 +125,21 @@ class MongoshNodeRepl implements EvaluationListener {
123125
completer.bind(null, internalState.getAutocompleteParameters());
124126
(repl as Mutable<typeof repl>).completer =
125127
callbackify(async(text: string): Promise<[string[], string]> => {
126-
// Merge the results from the repl completer and the mongosh completer.
127-
const [ [replResults], [mongoshResults] ] = await Promise.all([
128-
(async() => await origReplCompleter(text) || [[]])(),
129-
(async() => await mongoshCompleter(text))()
130-
]);
131-
this.bus.emit('mongosh:autocompletion-complete'); // For testing.
132-
// Remove duplicates, because shell API methods might otherwise show
133-
// up in both completions.
134-
const deduped = [...new Set([...replResults, ...mongoshResults])];
135-
return [deduped, text];
128+
this.insideAutoComplete = true;
129+
try {
130+
// Merge the results from the repl completer and the mongosh completer.
131+
const [ [replResults], [mongoshResults] ] = await Promise.all([
132+
(async() => await origReplCompleter(text) || [[]])(),
133+
(async() => await mongoshCompleter(text))()
134+
]);
135+
this.bus.emit('mongosh:autocompletion-complete'); // For testing.
136+
// Remove duplicates, because shell API methods might otherwise show
137+
// up in both completions.
138+
const deduped = [...new Set([...replResults, ...mongoshResults])];
139+
return [deduped, text];
140+
} finally {
141+
this.insideAutoComplete = false;
142+
}
136143
});
137144

138145
const originalDisplayPrompt = repl.displayPrompt.bind(repl);
@@ -284,13 +291,18 @@ class MongoshNodeRepl implements EvaluationListener {
284291
}
285292

286293
async eval(originalEval: asyncRepl.OriginalEvalFunction, input: string, context: any, filename: string): Promise<any> {
287-
this.lineByLineInput.enableBlockOnNewLine();
294+
if (!this.insideAutoComplete) {
295+
this.lineByLineInput.enableBlockOnNewLine();
296+
}
297+
288298
const { internalState, repl, shellEvaluator } = this.runtimeState();
289299

290300
try {
291301
return await shellEvaluator.customEval(originalEval, input, context, filename);
292302
} finally {
293-
repl.setPrompt(await this.getShellPrompt(internalState));
303+
if (!this.insideAutoComplete) {
304+
repl.setPrompt(await this.getShellPrompt(internalState));
305+
}
294306
this.bus.emit('mongosh:eval-complete'); // For testing purposes.
295307
}
296308
}

0 commit comments

Comments
 (0)