Skip to content

[WIP DO NOT MERGE] Autocomplete2 demo #2445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cfbafad
[Do Not Merge] PoC for shell-api autocomplete type definitions
addaleax Jan 24, 2024
0e17bc2
fixup: add index signature support
addaleax Jan 24, 2024
4d72278
fixup
addaleax Jan 24, 2024
6a931fa
Initial feature-flagged, draft new autocompleter.
lerouxb Mar 31, 2025
88edcd3
use the specified connection and db name
lerouxb Apr 2, 2025
70168e6
port some tests to autocomplete
lerouxb Apr 2, 2025
fd845e6
this one just worked
lerouxb Apr 2, 2025
b1ccc97
also just works
lerouxb Apr 2, 2025
7fecfac
use the new mongodb-schema
lerouxb Apr 4, 2025
256fc60
more bundled packages
lerouxb Apr 15, 2025
d81ff54
Merge branch 'main' into shell-api-ac-type-definitions
lerouxb Apr 15, 2025
198fb06
add rs, sh, sp generics
lerouxb Apr 17, 2025
59c04f3
fix tests
lerouxb Apr 17, 2025
3545fd8
hopefully fix the api export
lerouxb Apr 17, 2025
f34e213
something went wrong with the package-lock.json file
lerouxb Apr 17, 2025
51946ea
don't include the REPLACEME boilerplate which will be added on the ou…
lerouxb Apr 22, 2025
41a4560
workaround possible false positive error
lerouxb Apr 22, 2025
33ee0b5
no idea why it added that
lerouxb Apr 22, 2025
ae48313
we should probably run api-generate automatically
lerouxb Apr 22, 2025
880135f
add missing deps
lerouxb Apr 22, 2025
25240df
add the ShellApi and ShellBson globals to the shell api
lerouxb Apr 24, 2025
5511582
remove comment
lerouxb Apr 24, 2025
c6d8219
linebreak
lerouxb Apr 28, 2025
8f11992
add help comments to the extracted/generated shell-api
lerouxb Apr 28, 2025
e6833d1
Merge branch 'shell-api-ac-type-definitions-fork' into autocomplete2-…
lerouxb May 1, 2025
663e042
Merge branch 'use-new-autocomplete' into autocomplete2-demo
lerouxb May 2, 2025
1b2bb2d
strip node.js repl results for now
lerouxb May 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
467 changes: 464 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/autocomplete/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ describe('completer.completer', function () {

it('returns all suggestions', async function () {
const i = 'db.';
const attr = shellSignatures.Database.attributes as any;
const attr = shellSignatures.DatabaseImpl.attributes as any;
const dbComplete = Object.keys(attr);
const adjusted = dbComplete
.filter((c) => !attr[c].deprecated)
Expand Down Expand Up @@ -347,7 +347,7 @@ describe('completer.completer', function () {
it('returns all suggestions', async function () {
const i = 'db.shipwrecks.';
const collComplete = Object.keys(
shellSignatures.Collection.attributes as any
shellSignatures.CollectionImpl.attributes as any
);
const adjusted = collComplete
.filter(
Expand Down
4 changes: 2 additions & 2 deletions packages/autocomplete/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ async function completer(
): Promise<[string[], string, 'exclusive'] | [string[], string]> {
const SHELL_COMPLETIONS = shellSignatures.ShellApi
.attributes as TypeSignatureAttributes;
const COLL_COMPLETIONS = shellSignatures.Collection
const COLL_COMPLETIONS = shellSignatures.CollectionImpl
.attributes as TypeSignatureAttributes;
const DB_COMPLETIONS = shellSignatures.Database
const DB_COMPLETIONS = shellSignatures.DatabaseImpl
.attributes as TypeSignatureAttributes;
const AGG_CURSOR_COMPLETIONS = shellSignatures.AggregationCursor
.attributes as TypeSignatureAttributes;
Expand Down
1 change: 1 addition & 0 deletions packages/browser-runtime-core/src/open-context-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface InterpreterEnvironment {
*/
export class OpenContextRuntime implements Runtime {
private interpreterEnvironment: InterpreterEnvironment;
// TODO: we have to also port this to the new autocomplete
private autocompleter: ShellApiAutocompleter | null = null;
private shellEvaluator: ShellEvaluator;
private instanceState: ShellInstanceState;
Expand Down
4 changes: 3 additions & 1 deletion packages/build/src/packaging/package/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export async function execFile(
): Promise<ReturnType<typeof execFileWithoutLogging>> {
const joinedCommand = [args[0], ...(args[1] ?? [])].join(' ');
console.info(
'Running "' + joinedCommand + '" in ' + args[2]?.cwd ?? process.cwd()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS2869 Right operand of ?? is unreachable because the left operand is never nullish.
`Running "${joinedCommand}" in ${args[2]?.cwd ?? process.cwd()}`
);
const result = await execFileWithoutLogging(...args);
console.info('"' + joinedCommand + '" resulted in:', {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli-repl/src/cli-repl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1667,7 +1667,7 @@ describe('CliRepl', function () {
)
.flat();
expect(apiEvents).to.have.lengthOf(1);
expect(apiEvents[0].properties.class).to.equal('Database');
expect(apiEvents[0].properties.class).to.equal('DatabaseImpl');
expect(apiEvents[0].properties.method).to.equal(
'printShardingStatus'
);
Expand Down Expand Up @@ -1738,8 +1738,8 @@ describe('CliRepl', function () {
e.properties.count,
])
).to.deep.equal([
['Database', 'hello', 2],
['Database', 'hello', 1],
['DatabaseImpl', 'hello', 2],
['DatabaseImpl', 'hello', 1],
]);
});

Expand Down Expand Up @@ -2466,8 +2466,8 @@ describe('CliRepl', function () {
});

afterEach(async function () {
expect(output).not.to.include('Tab completion error');
expect(output).not.to.include(
expect(output, output).not.to.include('Tab completion error');
expect(output, output).not.to.include(
'listCollections requires authentication'
);
await cliRepl.mongoshRepl.close();
Expand Down
205 changes: 123 additions & 82 deletions packages/cli-repl/src/mongosh-repl.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* eslint-disable no-control-regex */
import { MongoshCommandFailed } from '@mongosh/errors';
import type { ServiceProvider } from '@mongosh/service-provider-core';
import type {
AggregationCursor,
ServiceProvider,
} from '@mongosh/service-provider-core';
import { bson } from '@mongosh/service-provider-core';
import { ADMIN_DB } from '@mongosh/shell-api/lib/enums';
import { ADMIN_DB } from '../../shell-api/lib/enums';
import { CliUserConfig } from '@mongosh/types';
import { EventEmitter, once } from 'events';
import path from 'path';
Expand Down Expand Up @@ -89,6 +92,14 @@ describe('MongoshNodeRepl', function () {
},
});
sp.runCommandWithCheck.resolves({ ok: 1 });

if (process.env.USE_NEW_AUTOCOMPLETE) {
sp.listCollections.resolves([{ name: 'coll' }]);
const aggCursor = stubInterface<AggregationCursor>();
aggCursor.toArray.resolves([{ foo: 1, bar: 2 }]);
sp.aggregate.returns(aggCursor);
}

serviceProvider = sp;
calledServiceProviderFunctions = () =>
Object.fromEntries(
Expand Down Expand Up @@ -352,6 +363,14 @@ describe('MongoshNodeRepl', function () {
};
const tabtab = async () => {
await tab();
if (process.env.USE_NEW_AUTOCOMPLETE) {
// TODO: This is because autocomplete() is async and will either list
// databases or collections or sample documents, any of which takes time
// to complete and can time out. There is probably a better way.
await new Promise((resolve) => {
setTimeout(resolve, 210);
});
}
await tab();
};

Expand Down Expand Up @@ -379,19 +398,28 @@ describe('MongoshNodeRepl', function () {
expect(output).to.include('65537');
});

it('does not stop input when autocompleting during .editor', async function () {
input.write('.editor\n');
await tick();
expect(output).to.include('Entering editor mode');
output = '';
input.write('db.');
await tabtab();
await tick();
input.write('version()\n');
input.write('\u0004'); // Ctrl+D
await waitEval(bus);
expect(output).to.include('Error running command serverBuildInfo');
});
context(
`autocompleting during .editor [${
process.env.USE_NEW_AUTOCOMPLETE ?? 'old'
}]`,
function () {
it('does not stop input when autocompleting during .editor', async function () {
input.write('.editor\n');
await tick();
expect(output).to.include('Entering editor mode');
output = '';
input.write('db.');
await tabtab();
await tick();
input.write('version()\n');
input.write('\u0004'); // Ctrl+D
await waitEval(bus);
expect(output, output).to.include(
'Error running command serverBuildInfo'
);
});
}
);

it('can enter multiline code', async function () {
for (const line of multilineCode.split('\n')) {
Expand Down Expand Up @@ -449,73 +477,86 @@ describe('MongoshNodeRepl', function () {
expect(code).to.equal(undefined);
});

context('autocompletion', function () {
it('autocompletes collection methods', async function () {
input.write('db.coll.');
await tabtab();
await tick();
expect(output).to.include('db.coll.updateOne');
});
it('autocompletes shell-api methods (once)', async function () {
input.write('vers');
await tabtab();
await tick();
expect(output).to.include('version');
expect(output).to.not.match(/version[ \t]+version/);
});
it('autocompletes async shell api methods', async function () {
input.write('db.coll.find().');
await tabtab();
await tick();
expect(output).to.include('db.coll.find().close');
});
it('autocompletes local variables', async function () {
input.write('let somelongvariable = 0\n');
await waitEval(bus);
output = '';
input.write('somelong');
await tabtab();
await tick();
expect(output).to.include('somelongvariable');
});
it('autocompletes partial repl commands', async function () {
input.write('.e');
await tabtab();
await tick();
expect(output).to.include('editor');
expect(output).to.include('exit');
});
it('autocompletes full repl commands', async function () {
input.write('.ed');
await tabtab();
await tick();
expect(output).to.include('.editor');
expect(output).not.to.include('exit');
});
it('autocompletion during .editor does not reset the prompt', async function () {
input.write('.editor\n');
await tick();
output = '';
expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal('');
input.write('db.');
await tabtab();
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(
'test> '
);
expect(stripAnsi(output)).to.equal('db.foo\r\nbar\r\n\r\ntest> ');
});
it('does not autocomplete tab-indented code', async function () {
output = '';
input.write('\t\tfoo');
await tick();
expect(output).to.equal('\t\tfoo');
});
});
context(
`autocompletion [${process.env.USE_NEW_AUTOCOMPLETE ?? 'old'}]`,
function () {
it('autocompletes collection methods', async function () {
input.write('db.coll.');
await tabtab();
await tick();
expect(output, output).to.include('db.coll.updateOne');
});
it('autocompletes collection schema fields', async function () {
if (!process.env.USE_NEW_AUTOCOMPLETE) {
// not supported in the old autocomplete
this.skip();
}
input.write('db.coll.find({');
await tabtab();
await tick();
expect(output, output).to.include('db.coll.find({foo');
});
it('autocompletes shell-api methods (once)', async function () {
input.write('vers');
await tabtab();
await tick();
expect(output, output).to.include('version');
expect(output, output).to.not.match(/version[ \t]+version/);
});
it('autocompletes async shell api methods', async function () {
input.write('db.coll.find().');
await tabtab();
await tick();
expect(output, output).to.include('db.coll.find().toArray');
});
it('autocompletes local variables', async function () {
input.write('let somelongvariable = 0\n');
await waitEval(bus);
output = '';
input.write('somelong');
await tabtab();
await tick();
expect(output, output).to.include('somelongvariable');
});
it('autocompletes partial repl commands', async function () {
input.write('.e');
await tabtab();
await tick();
expect(output, output).to.include('editor');
expect(output, output).to.include('exit');
});
it('autocompletes full repl commands', async function () {
input.write('.ed');
await tabtab();
await tick();
expect(output, output).to.include('.editor');
expect(output, output).not.to.include('exit');
});
it('autocompletion during .editor does not reset the prompt', async function () {
input.write('.editor\n');
await tick();
output = '';
expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal('');
input.write('db.');
await tabtab();
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(
'test> '
);
expect(stripAnsi(output)).to.equal('db.foo\r\nbar\r\n\r\ntest> ');
});
it('does not autocomplete tab-indented code', async function () {
output = '';
input.write('\t\tfoo');
await tick();
expect(output, output).to.equal('\t\tfoo');
});
}
);

context('history support', function () {
const arrowUp = '\x1b[A';
Expand Down
Loading