Skip to content

Commit 4020720

Browse files
author
jialan
committed
docs: add website column completions demo
1 parent c1b619c commit 4020720

File tree

1 file changed

+200
-21
lines changed

1 file changed

+200
-21
lines changed

website/src/languages/helpers/completionService.ts

Lines changed: 200 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { languages } from 'monaco-editor/esm/vs/editor/editor.api';
2-
import { CompletionService, ICompletionItem } from 'monaco-sql-languages/esm/languageService';
2+
import {
3+
CommonEntityContext,
4+
CompletionService,
5+
ICompletionItem,
6+
Suggestions,
7+
WordRange
8+
} from 'monaco-sql-languages/esm/languageService';
39
import { EntityContextType } from 'monaco-sql-languages/esm/main';
410

511
import { getCatalogs, getDataBases, getSchemas, getTables, getViews } from './dbMetaProvider';
12+
import { AttrName, EntityContext } from 'dt-sql-parser/dist/parser/common/entityCollector';
613

714
const haveCatalogSQLType = (languageId: string) => {
815
return ['flinksql', 'trinosql'].includes(languageId.toLowerCase());
@@ -12,28 +19,18 @@ const namedSchemaSQLType = (languageId: string) => {
1219
return ['trinosql', 'hivesql', 'sparksql'].includes(languageId);
1320
};
1421

15-
export const completionService: CompletionService = async function (
16-
model,
17-
_position,
18-
_completionContext,
19-
suggestions
20-
) {
21-
if (!suggestions) {
22-
return Promise.resolve([]);
23-
}
24-
const languageId = model.getLanguageId();
22+
const isWordRangesEndWithWhiteSpace = (wordRanges: WordRange[]) => {
23+
return wordRanges.length > 1 && wordRanges.at(-1)?.text === ' ';
24+
};
25+
26+
const getSyntaxCompletionItems = async (
27+
languageId: string,
28+
syntax: Suggestions['syntax'],
29+
entities: EntityContext[] | null
30+
): Promise<ICompletionItem[]> => {
2531
const haveCatalog = haveCatalogSQLType(languageId);
2632
const getDBOrSchema = namedSchemaSQLType(languageId) ? getSchemas : getDataBases;
2733

28-
const { keywords, syntax } = suggestions;
29-
30-
const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({
31-
label: kw,
32-
kind: languages.CompletionItemKind.Keyword,
33-
detail: '关键字',
34-
sortText: '2' + kw
35-
}));
36-
3734
let syntaxCompletionItems: ICompletionItem[] = [];
3835

3936
/** 是否已经存在 catalog 补全项 */
@@ -58,6 +55,13 @@ export const completionService: CompletionService = async function (
5855
const words = wordRanges.map((wr) => wr.text);
5956
const wordCount = words.length;
6057

58+
/**
59+
* 在做上下文判断时,如果已经键入了空格,则表示已经离开了该上下文。
60+
* 如: SELECT id | FROM t1
61+
* 光标所处位置在id后且键入了空格,虽然收集到的上下文信息中包含了`EntityContextType.COLUMN`,但不应该继续补全字段, table同理
62+
*/
63+
if (isWordRangesEndWithWhiteSpace(wordRanges)) continue;
64+
6165
if (
6266
syntaxContextType === EntityContextType.CATALOG ||
6367
syntaxContextType === EntityContextType.DATABASE_CREATE
@@ -108,8 +112,21 @@ export const completionService: CompletionService = async function (
108112
}
109113

110114
if (!existTableCompletions) {
115+
const createTables =
116+
entities
117+
?.filter(
118+
(entity) =>
119+
entity.entityContextType === EntityContextType.TABLE_CREATE
120+
)
121+
.map((tb) => ({
122+
label: tb.text,
123+
kind: languages.CompletionItemKind.Field,
124+
detail: 'table',
125+
sortText: '1' + tb.text
126+
})) || [];
111127
syntaxCompletionItems = syntaxCompletionItems.concat(
112-
await getTables(languageId)
128+
await getTables(languageId),
129+
createTables
113130
);
114131
existTableCompletions = true;
115132
}
@@ -182,6 +199,168 @@ export const completionService: CompletionService = async function (
182199
}
183200
}
184201
}
202+
203+
if (syntaxContextType === EntityContextType.COLUMN) {
204+
const inSelectStmtContext = entities?.some(
205+
(entity) =>
206+
entity.entityContextType === EntityContextType.TABLE &&
207+
entity.belongStmt.isContainCaret
208+
);
209+
// 上下文中建的所有表
210+
const allCreateTables =
211+
(entities?.filter(
212+
(entity) => entity.entityContextType === EntityContextType.TABLE_CREATE
213+
) as CommonEntityContext[]) || [];
214+
215+
if (inSelectStmtContext) {
216+
// select语句中的来源表
217+
// todo filter 子查询中的表
218+
const fromTables =
219+
entities?.filter(
220+
(entity) =>
221+
entity.entityContextType === EntityContextType.TABLE &&
222+
entity.belongStmt.isContainCaret
223+
) || [];
224+
// 从上下文中找到来源表的定义信息
225+
const fromTableDefinitionEntities = allCreateTables.filter((tb) =>
226+
fromTables?.some((ft) => ft.text === tb.text)
227+
);
228+
const tableNameAliasMap = fromTableDefinitionEntities.reduce(
229+
(acc: Record<string, string>, tb) => {
230+
acc[tb.text] =
231+
fromTables?.find((ft) => ft.text === tb.text)?.[AttrName.alias]?.text ||
232+
tb.text;
233+
return acc;
234+
},
235+
{}
236+
);
237+
238+
let fromTableColumns: (ICompletionItem & {
239+
_tableName?: string;
240+
_columnText?: string;
241+
})[] = [];
242+
243+
if (wordRanges.length <= 1) {
244+
const columnRepeatCountMap = new Map<string, number>();
245+
fromTableColumns = fromTableDefinitionEntities
246+
.map((tb) => {
247+
const displayTbName =
248+
tableNameAliasMap[tb.text] === tb.text
249+
? tb.text
250+
: tableNameAliasMap[tb.text];
251+
return (
252+
tb.columns?.map((column) => {
253+
const columnName = column.text;
254+
const repeatCount = columnRepeatCountMap.get(columnName) || 0;
255+
columnRepeatCountMap.set(columnName, repeatCount + 1);
256+
return {
257+
label:
258+
column.text +
259+
(column[AttrName.colType]?.text
260+
? `(${column[AttrName.colType].text})`
261+
: ''),
262+
insertText: column.text,
263+
kind: languages.CompletionItemKind.EnumMember,
264+
detail: `来源表 ${displayTbName} 的字段`,
265+
sortText: '0' + displayTbName + column.text + repeatCount,
266+
_tableName: displayTbName,
267+
_columnText: column.text
268+
};
269+
}) || []
270+
);
271+
})
272+
.flat();
273+
274+
// 如果有多个重名字段,则插入的字段自动包含表名
275+
fromTableColumns = fromTableColumns.map((column) => {
276+
const columnRepeatCount =
277+
columnRepeatCountMap.get(column.label as string) || 0;
278+
const isFromMultipleTables = fromTables.length > 1;
279+
return columnRepeatCount > 1 && isFromMultipleTables
280+
? {
281+
...column,
282+
insertText: `${column._tableName}.${column._columnText}`
283+
}
284+
: column;
285+
});
286+
287+
// 输入字段时提供可选表
288+
const tableOrAliasCompletionItems = fromTables.map((tb) => {
289+
const displayTbName = tableNameAliasMap[tb.text]
290+
? tableNameAliasMap[tb.text]
291+
: tb.text;
292+
return {
293+
label: displayTbName,
294+
kind: languages.CompletionItemKind.Field,
295+
detail: `table`,
296+
sortText: '1' + displayTbName
297+
};
298+
});
299+
300+
syntaxCompletionItems = syntaxCompletionItems.concat(
301+
tableOrAliasCompletionItems
302+
);
303+
} else if (wordRanges.length >= 2 && words[1] === '.') {
304+
const tbNameOrAlias = words[0];
305+
fromTableColumns = fromTableDefinitionEntities
306+
.filter(
307+
(tb) =>
308+
tb.text === tbNameOrAlias ||
309+
tableNameAliasMap[tb.text] === tbNameOrAlias
310+
)
311+
.map((tb) => {
312+
const displayTbName = tableNameAliasMap[tb.text]
313+
? tableNameAliasMap[tb.text]
314+
: tb.text;
315+
return (
316+
tb.columns?.map((column) => ({
317+
label:
318+
column.text +
319+
(column[AttrName.colType]?.text
320+
? `(${column[AttrName.colType].text})`
321+
: ''),
322+
insertText: column.text,
323+
kind: languages.CompletionItemKind.EnumMember,
324+
detail: `来源表 ${displayTbName} 的字段`,
325+
sortText: '0' + displayTbName + column.text
326+
})) || []
327+
);
328+
})
329+
.flat();
330+
}
331+
332+
syntaxCompletionItems = syntaxCompletionItems.concat(fromTableColumns);
333+
}
334+
}
185335
}
336+
337+
return syntaxCompletionItems;
338+
};
339+
340+
export const completionService: CompletionService = async function (
341+
model,
342+
_position,
343+
_completionContext,
344+
suggestions,
345+
entities
346+
) {
347+
if (!suggestions) {
348+
return Promise.resolve([]);
349+
}
350+
const languageId = model.getLanguageId();
351+
352+
const { keywords, syntax } = suggestions;
353+
console.log('syntax', syntax);
354+
console.log('entities', entities);
355+
356+
const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({
357+
label: kw,
358+
kind: languages.CompletionItemKind.Keyword,
359+
detail: '关键字',
360+
sortText: '2' + kw
361+
}));
362+
363+
const syntaxCompletionItems = await getSyntaxCompletionItems(languageId, syntax, entities);
364+
186365
return [...syntaxCompletionItems, ...keywordsCompletionItems];
187366
};

0 commit comments

Comments
 (0)