diff --git a/packages/cli-repl/src/mongosh-repl.spec.ts b/packages/cli-repl/src/mongosh-repl.spec.ts index 657e7e2d31..738f626469 100644 --- a/packages/cli-repl/src/mongosh-repl.spec.ts +++ b/packages/cli-repl/src/mongosh-repl.spec.ts @@ -9,6 +9,7 @@ import { StubbedInstance, stubInterface } from 'ts-sinon'; import { promisify } from 'util'; import { expect, fakeTTYProps, tick, useTmpdir, waitEval } from '../test/repl-helpers'; import MongoshNodeRepl, { MongoshConfigProvider, MongoshNodeReplOptions } from './mongosh-repl'; +import stripAnsi from 'strip-ansi'; const delay = promisify(setTimeout); @@ -234,6 +235,19 @@ describe('MongoshNodeRepl', () => { expect(output).to.include('65537'); }); + it('does not stop input when autocompleting during .editor', async() => { + input.write('.editor\n'); + await tick(); + expect(output).to.include('Entering editor mode'); + output = ''; + input.write('db.\u0009\u0009'); + await tick(); + input.write('version()\n'); + input.write('\u0004'); // Ctrl+D + await waitEval(bus); + expect(output).to.include('Error running command serverBuildInfo'); + }); + it('can enter multiline code', async() => { for (const line of multilineCode.split('\n')) { input.write(line + '\n'); @@ -297,6 +311,20 @@ describe('MongoshNodeRepl', () => { await tick(); expect(output).to.include('somelongvariable'); }); + it('autocompletion during .editor does not reset the prompt', async() => { + input.write('.editor\n'); + await tick(); + output = ''; + expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal(''); + input.write('db.\u0009\u0009'); + await tick(); + input.write('foo\nbar\n'); + expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal(''); + input.write('\u0003'); // Ctrl+C for abort + await tick(); + expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal('> '); + expect(stripAnsi(output)).to.equal('ddbdb.db.\tdb.\tfdb.\tfodb.\tfoo\r\nbbabar\r\n\r\n> '); + }); }); context('history support', () => { diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index 9b93b7fec4..8513b38881 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -65,6 +65,7 @@ class MongoshNodeRepl implements EvaluationListener { shellCliOptions: Partial; configProvider: MongoshConfigProvider; onClearCommand?: EvaluationListener['onClearCommand']; + insideAutoComplete: boolean; constructor(options: MongoshNodeReplOptions) { this.input = options.input; @@ -74,6 +75,7 @@ class MongoshNodeRepl implements EvaluationListener { this.nodeReplOptions = options.nodeReplOptions || {}; this.shellCliOptions = options.shellCliOptions || {}; this.configProvider = options.configProvider; + this.insideAutoComplete = false; this._runtimeState = null; } @@ -123,16 +125,21 @@ class MongoshNodeRepl implements EvaluationListener { completer.bind(null, internalState.getAutocompleteParameters()); (repl as Mutable).completer = callbackify(async(text: string): Promise<[string[], string]> => { - // Merge the results from the repl completer and the mongosh completer. - const [ [replResults], [mongoshResults] ] = await Promise.all([ - (async() => await origReplCompleter(text) || [[]])(), - (async() => await mongoshCompleter(text))() - ]); - this.bus.emit('mongosh:autocompletion-complete'); // For testing. - // Remove duplicates, because shell API methods might otherwise show - // up in both completions. - const deduped = [...new Set([...replResults, ...mongoshResults])]; - return [deduped, text]; + this.insideAutoComplete = true; + try { + // Merge the results from the repl completer and the mongosh completer. + const [ [replResults], [mongoshResults] ] = await Promise.all([ + (async() => await origReplCompleter(text) || [[]])(), + (async() => await mongoshCompleter(text))() + ]); + this.bus.emit('mongosh:autocompletion-complete'); // For testing. + // Remove duplicates, because shell API methods might otherwise show + // up in both completions. + const deduped = [...new Set([...replResults, ...mongoshResults])]; + return [deduped, text]; + } finally { + this.insideAutoComplete = false; + } }); const originalDisplayPrompt = repl.displayPrompt.bind(repl); @@ -283,13 +290,18 @@ class MongoshNodeRepl implements EvaluationListener { } async eval(originalEval: asyncRepl.OriginalEvalFunction, input: string, context: any, filename: string): Promise { - this.lineByLineInput.enableBlockOnNewLine(); + if (!this.insideAutoComplete) { + this.lineByLineInput.enableBlockOnNewLine(); + } + const { internalState, repl, shellEvaluator } = this.runtimeState(); try { return await shellEvaluator.customEval(originalEval, input, context, filename); } finally { - repl.setPrompt(await this.getShellPrompt(internalState)); + if (!this.insideAutoComplete) { + repl.setPrompt(await this.getShellPrompt(internalState)); + } this.bus.emit('mongosh:eval-complete'); // For testing purposes. } }