Skip to content

Commit 7b10092

Browse files
fix: collection with dots in the name disappears from the suggestions list VSCODE-407 (#514)
* fix: collection with . in the namespace disappears from the suggestions list VSCODE-407 * docs: update comment * refactor: clean up * refactor: n
1 parent 6fb3732 commit 7b10092

File tree

4 files changed

+740
-589
lines changed

4 files changed

+740
-589
lines changed

src/language/mongoDBService.ts

Lines changed: 109 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
InsertTextFormat,
55
MarkupKind,
66
DiagnosticSeverity,
7+
Range,
8+
Position,
79
} from 'vscode-languageserver/node';
810
import type {
911
CancellationToken,
@@ -60,9 +62,9 @@ export default class MongoDBService {
6062
_connectionOptions?: MongoClientOptions;
6163

6264
_databaseCompletionItems: CompletionItem[] = [];
63-
_collectionCompletionItems: { [database: string]: CompletionItem[] } = {};
6465
_shellSymbolCompletionItems: { [symbol: string]: CompletionItem[] } = {};
6566
_globalSymbolCompletionItems: CompletionItem[] = [];
67+
_collections: { [database: string]: string[] } = {};
6668
_fields: { [namespace: string]: string[] } = {};
6769

6870
_visitor: Visitor;
@@ -487,25 +489,15 @@ export default class MongoDBService {
487489
* Get and cache collection and field names based on the namespace.
488490
*/
489491
async _getCompletionValuesAndUpdateCache(
490-
textFromEditor: string,
491-
position: { line: number; character: number },
492492
currentDatabaseName: string | null,
493493
currentCollectionName: string | null
494494
) {
495-
if (
496-
currentDatabaseName &&
497-
!this._collectionCompletionItems[currentDatabaseName]
498-
) {
495+
if (currentDatabaseName && !this._collections[currentDatabaseName]) {
499496
// Get collection names for the current database.
500497
const collections = await this._getCollections(currentDatabaseName);
501498

502499
// Create and cache collection completion items.
503-
this._cacheCollectionCompletionItems(
504-
textFromEditor,
505-
position,
506-
currentDatabaseName,
507-
collections
508-
);
500+
this._cacheCollections(currentDatabaseName, collections);
509501
}
510502

511503
if (currentDatabaseName && currentCollectionName) {
@@ -750,18 +742,76 @@ export default class MongoDBService {
750742
}
751743
}
752744

745+
/**
746+
* Convert cached collection names into completion items.
747+
* We do not cache completion items as we do for other entities,
748+
* because some of the collection names have special characters
749+
* and must be edited to the bracket notation based on the current line content.
750+
*/
751+
_getCollectionCompletionItems({
752+
databaseName,
753+
currentLineText,
754+
position,
755+
}: {
756+
databaseName: string;
757+
currentLineText: string;
758+
position: { line: number; character: number };
759+
}) {
760+
return this._collections[databaseName].map((collectionName) => {
761+
if (this._isValidPropertyName(collectionName)) {
762+
return {
763+
label: collectionName,
764+
kind: CompletionItemKind.Folder,
765+
preselect: true,
766+
};
767+
}
768+
769+
return {
770+
label: collectionName,
771+
kind: CompletionItemKind.Folder,
772+
// The current line text, e.g. `{ db. } // Comment`.
773+
filterText: currentLineText,
774+
textEdit: {
775+
range: {
776+
start: { line: position.line, character: 0 },
777+
end: {
778+
line: position.line,
779+
character: currentLineText.length,
780+
},
781+
},
782+
// The completion item with the collection name converted into the bracket notation.
783+
newText: [
784+
currentLineText.slice(0, position.character - 1),
785+
`['${collectionName}']`,
786+
currentLineText.slice(position.character, currentLineText.length),
787+
].join(''),
788+
},
789+
preselect: true,
790+
};
791+
});
792+
}
793+
753794
/**
754795
* If the current node is 'db.<trigger>'.
755796
*/
756-
_provideDbSymbolCompletionItems(state: CompletionState) {
797+
_provideDbSymbolCompletionItems(
798+
state: CompletionState,
799+
currentLineText: string,
800+
position: { line: number; character: number }
801+
) {
757802
// If we found 'use("db")' and the current node is 'db.<trigger>'.
758803
if (state.isDbSymbol && state.databaseName) {
759804
this._connection.console.log(
760805
'VISITOR found db symbol and collection name completions'
761806
);
807+
762808
return [
763809
...this._shellSymbolCompletionItems.Database,
764-
...this._collectionCompletionItems[state.databaseName],
810+
...this._getCollectionCompletionItems({
811+
databaseName: state.databaseName,
812+
currentLineText,
813+
position,
814+
}),
765815
];
766816
}
767817

@@ -776,10 +826,18 @@ export default class MongoDBService {
776826
* If the current node can be used as a collection name
777827
* e.g. 'db.<trigger>.find()' or 'let a = db.<trigger>'.
778828
*/
779-
_provideCollectionNameCompletionItems(state: CompletionState) {
829+
_provideCollectionNameCompletionItems(
830+
state: CompletionState,
831+
currentLineText: string,
832+
position: { line: number; character: number }
833+
) {
780834
if (state.isCollectionName && state.databaseName) {
781835
this._connection.console.log('VISITOR found collection name completions');
782-
return this._collectionCompletionItems[state.databaseName];
836+
return this._getCollectionCompletionItems({
837+
databaseName: state.databaseName,
838+
currentLineText,
839+
position,
840+
});
783841
}
784842
}
785843

@@ -797,26 +855,37 @@ export default class MongoDBService {
797855
* Parse code from a playground to identify
798856
* where the cursor is and suggests only suitable completion items.
799857
*/
800-
async provideCompletionItems(
801-
textFromEditor: string,
802-
position: { line: number; character: number }
803-
): Promise<CompletionItem[]> {
858+
async provideCompletionItems({
859+
document,
860+
position,
861+
}: {
862+
document?: Document;
863+
position: { line: number; character: number };
864+
}): Promise<CompletionItem[]> {
804865
this._connection.console.log(
805866
`Provide completion items for a position: ${util.inspect(position)}`
806867
);
807868

808-
const state = this._visitor.parseASTForCompletion(textFromEditor, position);
869+
const state = this._visitor.parseASTForCompletion(
870+
document?.getText(),
871+
position
872+
);
809873
this._connection.console.log(
810874
`VISITOR completion state: ${util.inspect(state)}`
811875
);
812876

813877
await this._getCompletionValuesAndUpdateCache(
814-
textFromEditor,
815-
position,
816878
state.databaseName,
817879
state.collectionName
818880
);
819881

882+
const currentLineText = document?.getText(
883+
Range.create(
884+
Position.create(position.line, 0),
885+
Position.create(position.line + 1, 0)
886+
)
887+
);
888+
820889
const completionOptions = [
821890
this._provideStageCompletionItems.bind(this, state),
822891
this._provideQueryOperatorCompletionItems.bind(this, state),
@@ -827,8 +896,18 @@ export default class MongoDBService {
827896
this._provideFindCursorCompletionItems.bind(this, state),
828897
this._provideAggregationCursorCompletionItems.bind(this, state),
829898
this._provideGlobalSymbolCompletionItems.bind(this, state),
830-
this._provideDbSymbolCompletionItems.bind(this, state),
831-
this._provideCollectionNameCompletionItems.bind(this, state),
899+
this._provideDbSymbolCompletionItems.bind(
900+
this,
901+
state,
902+
currentLineText,
903+
position
904+
),
905+
this._provideCollectionNameCompletionItems.bind(
906+
this,
907+
state,
908+
currentLineText,
909+
position
910+
),
832911
this._provideDbNameCompletionItems.bind(this, state),
833912
];
834913

@@ -960,53 +1039,10 @@ export default class MongoDBService {
9601039
}
9611040

9621041
/**
963-
* Convert collection names to Completion Items and cache them.
1042+
* Cache collection names.
9641043
*/
965-
_cacheCollectionCompletionItems(
966-
textFromEditor: string,
967-
position: { line: number; character: number },
968-
database: string,
969-
collections: Document[]
970-
): void {
971-
this._collectionCompletionItems[database] = collections.map((item) => {
972-
if (this._isValidPropertyName(item.name)) {
973-
return {
974-
label: item.name,
975-
kind: CompletionItemKind.Folder,
976-
preselect: true,
977-
};
978-
}
979-
980-
// Convert invalid property names to array-like format.
981-
const filterText = textFromEditor.split('\n')[position.line];
982-
983-
return {
984-
label: item.name,
985-
kind: CompletionItemKind.Folder,
986-
// Find the line with invalid property name.
987-
filterText: [
988-
filterText.slice(0, position.character),
989-
`.${item.name}`,
990-
filterText.slice(position.character, filterText.length),
991-
].join(''),
992-
textEdit: {
993-
range: {
994-
start: { line: position.line, character: 0 },
995-
end: {
996-
line: position.line,
997-
character: filterText.length,
998-
},
999-
},
1000-
// Replace with array-like format.
1001-
newText: [
1002-
filterText.slice(0, position.character - 1),
1003-
`['${item.name}']`,
1004-
filterText.slice(position.character, filterText.length),
1005-
].join(''),
1006-
preselect: true,
1007-
},
1008-
};
1009-
});
1044+
_cacheCollections(database: string, collections: Document[]): void {
1045+
this._collections[database] = collections.map((item) => item.name);
10101046
}
10111047

10121048
_clearCachedFields(): void {
@@ -1018,7 +1054,7 @@ export default class MongoDBService {
10181054
}
10191055

10201056
_clearCachedCollections(): void {
1021-
this._collectionCompletionItems = {};
1057+
this._collections = {};
10221058
}
10231059

10241060
async _clearCurrentConnection(): Promise<void> {

src/language/server.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,12 @@ connection.onRequest(
202202

203203
// Provide MongoDB completion items.
204204
connection.onCompletion((params: TextDocumentPositionParams) => {
205-
const textFromEditor = documents.get(params.textDocument.uri)?.getText();
205+
const document = documents.get(params.textDocument.uri);
206206

207-
return mongoDBService.provideCompletionItems(
208-
textFromEditor ? textFromEditor : '',
209-
params.position
210-
);
207+
return mongoDBService.provideCompletionItems({
208+
document,
209+
position: params.position,
210+
});
211211
});
212212

213213
// This handler resolves additional information for the item selected in

src/language/visitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class Visitor {
142142
}
143143

144144
parseASTForCompletion(
145-
textFromEditor: string,
145+
textFromEditor = '',
146146
position: { line: number; character: number }
147147
): CompletionState {
148148
const selection: VisitorSelection = {

0 commit comments

Comments
 (0)