Skip to content

Commit 052118d

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

File tree

16 files changed

+344
-134
lines changed

16 files changed

+344
-134
lines changed

package-lock.json

Lines changed: 50 additions & 31 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/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@mongodb-js/eslint-config-mongosh": "^1.0.0",
4242
"@mongodb-js/prettier-config-devtools": "^1.0.1",
4343
"@mongodb-js/tsconfig-mongosh": "^1.0.0",
44+
"@mongodb-js/mongodb-ts-autocomplete": "^0.2.4",
4445
"@mongosh/types": "3.6.2",
4546
"bson": "^6.10.3",
4647
"depcheck": "^1.4.7",

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 [];

0 commit comments

Comments
 (0)