Skip to content

Commit 418b8a0

Browse files
committed
port autocomplete direct commands and browser-runtime-core
1 parent c8941c1 commit 418b8a0

File tree

13 files changed

+307
-117
lines changed

13 files changed

+307
-117
lines changed

package-lock.json

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/autocomplete/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"dependencies": {
4646
"@mongodb-js/mongodb-constants": "^0.10.1",
4747
"@mongosh/shell-api": "^3.11.0",
48+
"@mongodb-js/mongodb-ts-autocomplete": "^0.2.4",
4849
"semver": "^7.5.4"
4950
}
5051
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete';
2+
import type { TypeSignature } from '@mongosh/shell-api';
3+
import { signatures as shellSignatures } from '@mongosh/shell-api';
4+
5+
type TypeSignatureAttributes = { [key: string]: TypeSignature };
6+
7+
export async function directCommandCompleter(
8+
context: AutocompletionContext,
9+
line: string
10+
): Promise<string[]> {
11+
const SHELL_COMPLETIONS = shellSignatures.ShellApi
12+
.attributes as TypeSignatureAttributes;
13+
14+
// Split at space-to-non-space transitions when looking at this as a command,
15+
// because multiple spaces (e.g. 'show collections') are valid in commands.
16+
// This split keeps the spaces intact so we can join them back later.
17+
const splitLineWhitespace = line.split(/(?<!\S)(?=\S)/);
18+
const command = splitLineWhitespace[0].trim();
19+
if (!SHELL_COMPLETIONS[command]?.isDirectShellCommand) {
20+
return [];
21+
}
22+
23+
// If the shell API provides us with a completer, use it.
24+
// examples: use, show, snippet
25+
const completer = SHELL_COMPLETIONS[command].newShellCommandCompleter;
26+
if (!completer) {
27+
// examples without a custom completer: exit, quit, it, cls
28+
return [line];
29+
}
30+
31+
if (splitLineWhitespace.length === 1) {
32+
if (splitLineWhitespace[0].trimEnd() === splitLineWhitespace[0]) {
33+
// Treat e.g. 'show' like 'show '.
34+
splitLineWhitespace[0] += ' ';
35+
}
36+
37+
// Complete the first argument after the command.
38+
splitLineWhitespace.push('');
39+
}
40+
const hits =
41+
(await completer(
42+
context,
43+
splitLineWhitespace.map((item) => item.trim())
44+
)) || [];
45+
// Adjust to full input, because `completer` only completed the last item
46+
// in the line, e.g. ['profile'] -> ['show profile']
47+
const fullLineHits = hits.map((hit) =>
48+
[...splitLineWhitespace.slice(0, -1), hit].join('')
49+
);
50+
51+
return fullLineHits;
52+
}

packages/autocomplete/src/index.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Topologies, TypeSignature } from '@mongosh/shell-api';
22
import { signatures as shellSignatures } from '@mongosh/shell-api';
3+
import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete';
34
import semver from 'semver';
45
import {
56
CONVERSION_OPERATORS,
@@ -13,6 +14,7 @@ import {
1314
ON_PREM,
1415
DATABASE,
1516
} from '@mongodb-js/mongodb-constants';
17+
import { directCommandCompleter } from './direct-command-completer';
1618

1719
type TypeSignatureAttributes = { [key: string]: TypeSignature };
1820

@@ -78,7 +80,7 @@ const GROUP = '$group';
7880
*
7981
* @returns {array} Matching Completions, Current User Input.
8082
*/
81-
async function completer(
83+
export async function completer(
8284
params: AutocompleteParameters,
8385
line: string
8486
): Promise<[string[], string, 'exclusive'] | [string[], string]> {
@@ -398,4 +400,51 @@ function filterShellAPI(
398400
return hits;
399401
}
400402

401-
export default completer;
403+
type AutocompleteShellInstanceState = {
404+
getAutocompleteParameters: () => AutocompleteParameters;
405+
getAutocompletionContext: () => AutocompletionContext;
406+
};
407+
408+
function transformAutocompleteResults(
409+
line: string,
410+
results: { result: string }[]
411+
): [string[], string] {
412+
return [results.map((result) => result.result), line];
413+
}
414+
415+
export type CompletionResults =
416+
| [string[], string, 'exclusive']
417+
| [string[], string];
418+
419+
export async function initNewAutocompleter(
420+
instanceState: Pick<
421+
AutocompleteShellInstanceState,
422+
'getAutocompletionContext'
423+
>
424+
): Promise<(text: string) => Promise<CompletionResults>> {
425+
// only import the autocompleter code the first time we need it to
426+
// hide the time it takes from the initial startup time
427+
const { MongoDBAutocompleter } = await import(
428+
'@mongodb-js/mongodb-ts-autocomplete'
429+
);
430+
431+
const autocompletionContext = instanceState.getAutocompletionContext();
432+
const mongoDBCompleter = new MongoDBAutocompleter({
433+
context: autocompletionContext,
434+
});
435+
436+
return async (text: string): Promise<CompletionResults> => {
437+
const directResults = await directCommandCompleter(
438+
autocompletionContext,
439+
text
440+
);
441+
442+
if (directResults.length) {
443+
return [directResults, text, 'exclusive'];
444+
}
445+
446+
const results = await mongoDBCompleter.autocomplete(text);
447+
const transformed = transformAutocompleteResults(text, results);
448+
return transformed;
449+
};
450+
}

packages/browser-runtime-core/src/autocompleter/shell-api-autocompleter.spec.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { ShellApiAutocompleter } from './shell-api-autocompleter';
22
import { expect } from 'chai';
33
import { Topologies } from '@mongosh/shell-api';
4+
import type { AutocompleteParameters } from '@mongosh/autocomplete';
5+
import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete';
46

5-
const standalone440 = {
7+
const standalone440Parameters: AutocompleteParameters = {
68
topology: () => Topologies.Standalone,
79
apiVersionInfo: () => undefined,
810
connectionInfo: () => ({
@@ -11,16 +13,31 @@ const standalone440 = {
1113
server_version: '4.4.0',
1214
is_local_atlas: false,
1315
}),
14-
getCollectionCompletionsForCurrentDb: () => ['bananas'],
15-
getDatabaseCompletions: () => ['databaseOne'],
16+
getCollectionCompletionsForCurrentDb: () => Promise.resolve(['bananas']),
17+
getDatabaseCompletions: () => Promise.resolve(['databaseOne']),
18+
};
19+
20+
const standalone440Context: AutocompletionContext = {
21+
currentDatabaseAndConnection: () => ({
22+
connectionId: 'connection-1',
23+
databaseName: 'databaseOne',
24+
}),
25+
databasesForConnection: () => Promise.resolve(['databaseOne']),
26+
collectionsForDatabase: () => Promise.resolve(['bananas', 'coll1']),
27+
schemaInformationForCollection: () => Promise.resolve({}),
28+
};
29+
30+
const shellInstanceState = {
31+
getAutocompleteParameters: () => standalone440Parameters,
32+
getAutocompletionContext: () => standalone440Context,
1633
};
1734

1835
describe('Autocompleter', function () {
1936
describe('getCompletions', function () {
2037
let autocompleter: ShellApiAutocompleter;
2138

2239
beforeEach(function () {
23-
autocompleter = new ShellApiAutocompleter(standalone440);
40+
autocompleter = new ShellApiAutocompleter(shellInstanceState);
2441
});
2542

2643
it('returns completions for text before cursor', async function () {

packages/browser-runtime-core/src/autocompleter/shell-api-autocompleter.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,52 @@
1-
import type { AutocompleteParameters } from '@mongosh/autocomplete';
2-
import cliReplCompleter from '@mongosh/autocomplete';
1+
import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete';
2+
import type {
3+
AutocompleteParameters,
4+
CompletionResults,
5+
} from '@mongosh/autocomplete';
6+
import { completer, initNewAutocompleter } from '@mongosh/autocomplete';
37
import type { Autocompleter, Completion } from './autocompleter';
48

9+
type AutocompleteShellInstanceState = {
10+
getAutocompleteParameters: () => AutocompleteParameters;
11+
getAutocompletionContext: () => AutocompletionContext;
12+
};
13+
514
export class ShellApiAutocompleter implements Autocompleter {
6-
private parameters: AutocompleteParameters;
15+
private shellInstanceState: AutocompleteShellInstanceState;
16+
17+
// old autocomplete only:
18+
private parameters: AutocompleteParameters | undefined;
19+
20+
// new autocomplete only:
21+
private newMongoshCompleter:
22+
| ((line: string) => Promise<CompletionResults>)
23+
| undefined;
724

8-
constructor(parameters: AutocompleteParameters) {
9-
this.parameters = parameters;
25+
constructor(shellInstanceState: AutocompleteShellInstanceState) {
26+
this.shellInstanceState = shellInstanceState;
1027
}
1128

1229
async getCompletions(code: string): Promise<Completion[]> {
1330
if (!code) {
1431
return [];
1532
}
1633

17-
const completions = await cliReplCompleter(this.parameters, code);
34+
let completions: CompletionResults;
35+
36+
if (process.env.USE_NEW_AUTOCOMPLETE) {
37+
if (!this.newMongoshCompleter) {
38+
this.newMongoshCompleter = await initNewAutocompleter(
39+
this.shellInstanceState
40+
);
41+
}
42+
43+
completions = await this.newMongoshCompleter(code);
44+
} else {
45+
if (!this.parameters) {
46+
this.parameters = this.shellInstanceState.getAutocompleteParameters();
47+
}
48+
completions = await completer(this.parameters, code);
49+
}
1850

1951
if (!completions || !completions.length) {
2052
return [];

packages/browser-runtime-core/src/open-context-runtime.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface InterpreterEnvironment {
2929
*/
3030
export class OpenContextRuntime implements Runtime {
3131
private interpreterEnvironment: InterpreterEnvironment;
32-
// TODO(MONGOSH-2205): we have to also port this to the new autocomplete
3332
private autocompleter: ShellApiAutocompleter | null = null;
3433
private shellEvaluator: ShellEvaluator;
3534
private instanceState: ShellInstanceState;
@@ -53,9 +52,7 @@ export class OpenContextRuntime implements Runtime {
5352

5453
async getCompletions(code: string): Promise<Completion[]> {
5554
if (!this.autocompleter) {
56-
this.autocompleter = new ShellApiAutocompleter(
57-
this.instanceState.getAutocompleteParameters()
58-
);
55+
this.autocompleter = new ShellApiAutocompleter(this.instanceState);
5956
this.updatedConnectionInfoPromise ??=
6057
this.instanceState.fetchConnectionInfo();
6158
await this.updatedConnectionInfoPromise;

packages/cli-repl/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
"@mongosh/shell-evaluator": "^3.11.0",
7878
"@mongosh/snippet-manager": "^3.11.0",
7979
"@mongosh/types": "3.6.2",
80-
"@mongodb-js/mongodb-ts-autocomplete": "^0.2.4",
8180
"@segment/analytics-node": "^1.3.0",
8281
"ansi-escape-sequences": "^5.1.2",
8382
"askcharacter": "^2.0.4",

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,11 +2620,6 @@ describe('CliRepl', function () {
26202620
});
26212621

26222622
it('completes shell commands', async function () {
2623-
if (process.env.USE_NEW_AUTOCOMPLETE) {
2624-
// TODO(MONGOSH-2035): not supported yet
2625-
this.skip();
2626-
}
2627-
26282623
input.write('const dSomeVariableStartingWithD = 10;\n');
26292624
await waitEval(cliRepl.bus);
26302625

@@ -2636,11 +2631,6 @@ describe('CliRepl', function () {
26362631
});
26372632

26382633
it('completes use <db>', async function () {
2639-
if (process.env.USE_NEW_AUTOCOMPLETE) {
2640-
// TODO(MONGOSH-2035): not supported yet
2641-
this.skip();
2642-
}
2643-
26442634
if (!hasDatabaseNames) return this.skip();
26452635
input.write('db.getMongo()._listDatabases()\n'); // populate database cache
26462636
await waitEval(cliRepl.bus);

0 commit comments

Comments
 (0)