diff --git a/mayfly_go_web/src/views/ops/db/db.ts b/mayfly_go_web/src/views/ops/db/db.ts index be672436..8461adef 100644 --- a/mayfly_go_web/src/views/ops/db/db.ts +++ b/mayfly_go_web/src/views/ops/db/db.ts @@ -93,6 +93,52 @@ export class DbInst { return tables; } + async loadTableSuggestions(dbName: string, range: any, reload?: boolean) { + const tables = await this.loadTables(dbName, reload); + // 表名联想 + let suggestions: languages.CompletionItem[] = []; + tables?.forEach((tableMeta: any, index: any) => { + const { tableName, tableComment } = tableMeta; + suggestions.push({ + label: { + label: tableName + ' - ' + tableComment, + description: 'table', + }, + kind: monaco.languages.CompletionItemKind.File, + detail: tableComment, + insertText: tableName + ' ', + range, + sortText: 300 + index + '', + }); + }); + return { suggestions }; + } + + /** 加载列信息提示 */ + async loadTableColumnSuggestions(db: string, tableName: string, range: any) { + let dbHits = await this.loadDbHints(db); + let columns = dbHits[tableName]; + let suggestions: languages.CompletionItem[] = []; + columns?.forEach((a: string, index: any) => { + // 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间] + const nameAndComment = a.split(' '); + const fieldName = nameAndComment[0]; + suggestions.push({ + label: { + label: a, + description: 'column', + }, + kind: monaco.languages.CompletionItemKind.Property, + detail: '', // 不显示detail, 否则选中时备注等会被遮挡 + insertText: fieldName, // create_time + range, + sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型 + }); + }); + + return { suggestions }; + } + /** * 获取表的所有列信息 * @param table 表名 @@ -482,7 +528,7 @@ export type FieldsMeta = { * @param db 库名 * @param dbs 该库所有库名 */ -export function registerDbCompletionItemProvider(language: string, dbId: number, db: string, dbs: [] = []) { +export function registerDbCompletionItemProvider(language: string, dbId: number, db: string, dbs: any[] = []) { registerCompletionItemProvider(language, { triggerCharacters: ['.', ' '], provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise => { @@ -518,29 +564,6 @@ export function registerDbCompletionItemProvider(language: string, dbId: number, let lastToken = tokens[tokens.length - 1].toLowerCase(); const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || ''; - // console.log("光标前文本:=>" + textBeforePointerMulti) - // console.log("最后输入的:=>" + lastToken) - - let suggestions: languages.CompletionItem[] = []; - - let alias = ''; - if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) { - // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】 - alias = lastToken.substring(0, lastToken.lastIndexOf('.')); - if (lastToken.trim().startsWith('.')) { - alias = secondToken; - } - // 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名 - let aliasArr = lastToken.split(','); - if (aliasArr.length > 1) { - lastToken = aliasArr[aliasArr.length - 1]; - alias = lastToken.substring(0, lastToken.lastIndexOf('.')); - if (lastToken.trim().startsWith('.')) { - alias = secondToken; - } - } - } - // 获取光标所在行之前的所有文本内容 const textBeforeCursor = model.getValueInRange({ startLineNumber: 1, @@ -579,39 +602,62 @@ export function registerDbCompletionItemProvider(language: string, dbId: number, sqlStatement = textBeforeCursor + textAfterCursor; } - const tableName = getTableName4SqlCtx(sqlStatement, alias); - // 提出到表名,则将表对应的字段也添加进提示建议 - if (tableName) { - let dbHits = await dbInst.loadDbHints(db); - let columns = dbHits[tableName]; - columns?.forEach((a: string, index: any) => { - // 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间] - const nameAndComment = a.split(' '); - const fieldName = nameAndComment[0]; + let suggestions: languages.CompletionItem[] = []; + + // 库名提示 + if (dbs && dbs.length > 0) { + dbs.forEach((a: any) => { suggestions.push({ label: { label: a, - description: 'column', + description: 'schema', }, - kind: monaco.languages.CompletionItemKind.Property, - detail: '', // 不显示detail, 否则选中时备注等会被遮挡 - insertText: fieldName, // create_time + kind: monaco.languages.CompletionItemKind.Folder, + insertText: a, range, - sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型 }); }); + } - // 若存在字段提示,并且有别名,则提示字段即可,不完善后续的表名以及函数等 - if (suggestions.length > 0 && alias) { - return { - suggestions: suggestions, - }; + let alias = ''; + if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) { + // 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】 + alias = lastToken.substring(0, lastToken.lastIndexOf('.')); + if (lastToken.trim().startsWith('.')) { + alias = secondToken; + } + + // 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名 + let aliasArr = lastToken.split(','); + if (aliasArr.length > 1) { + lastToken = aliasArr[aliasArr.length - 1]; + alias = lastToken.substring(0, lastToken.lastIndexOf('.')); + if (lastToken.trim().startsWith('.')) { + alias = secondToken; + } + } + + // 如果是【库.表名联想】.前的字符串是库名 + if (dbs.indexOf(alias) >= 0) { + return await dbInst.loadTableSuggestions(alias, range); + } + // 表下列名联想 .前的字符串是表名或表别名 + const sqlInfo = getTableName4SqlCtx(sqlStatement, alias, db); + // 提出到表名,则将表对应的字段也添加进提示建议 + if (sqlInfo) { + return await dbInst.loadTableColumnSuggestions(sqlInfo.db, sqlInfo.tableName, range); } } - const tables = await dbInst.loadTables(db); + // 空格触发也会提示字段信息 + const sqlInfo = getTableName4SqlCtx(sqlStatement, alias, db); + if (sqlInfo) { + const columnSuggestions = await dbInst.loadTableColumnSuggestions(sqlInfo.db, sqlInfo.tableName, range); + suggestions.push(...columnSuggestions.suggestions); + } - // 表名联想 + // 当前库的表名联想 + const tables = await dbInst.loadTables(db); tables.forEach((tableMeta: any, index: any) => { const { tableName, tableComment } = tableMeta; suggestions.push({ @@ -695,21 +741,6 @@ export function registerDbCompletionItemProvider(language: string, dbId: number, }); }); - // 库名提示 - if (dbs && dbs.length > 0) { - dbs.forEach((a: any) => { - suggestions.push({ - label: { - label: a, - description: 'schema', - }, - kind: monaco.languages.CompletionItemKind.Folder, - insertText: a, - range, - }); - }); - } - // 默认提示 return { suggestions: suggestions, @@ -718,31 +749,33 @@ export function registerDbCompletionItemProvider(language: string, dbId: number, }); } -function getTableName4SqlCtx(sql: string, alias: string = '') { +function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string): { tableName: string; tableAlias: string; db: string } | undefined { // 去除多余的换行、空格和制表符 sql = sql.replace(/[\r\n\s\t]+/g, ' '); // 提取所有可能的表名和别名 - const regex = /(?:(?:FROM|JOIN|UPDATE)\s+(\S+)\s+(?:AS\s+)?(\S+))/gi; + const regex = /(?:FROM|JOIN|UPDATE)\s+(\S+)\s+(?:AS\s+)?(\S+)/gi; let matches; const tables = []; // 使用正则表达式匹配所有的表和别名 while ((matches = regex.exec(sql)) !== null) { - const tableName = matches[1].replace(/[`"]/g, ''); + let tableName = matches[1].replace(/[`"]/g, ''); + let db = defaultDb; + if (tableName.indexOf('.') >= 0) { + let info = tableName.split('.'); + db = info[0]; + tableName = info[1]; + } const tableAlias = matches[2] ? matches[2].replace(/[`"]/g, '') : tableName; - tables.push({ tableName, tableAlias }); + tables.push({ tableName, tableAlias, db }); } - // console.log('sql....', sql); - // console.log('alias....', alias); - // console.log('parset tables...', tables); if (alias) { // 如果指定了别名参数,则返回对应的表名 - const table = tables.find((t) => t.tableAlias === alias); - return table ? table.tableName : ''; + return tables.find((t) => t.tableAlias === alias); } else { // 如果未指定别名参数,则返回第一个表名 - return tables.length > 0 ? tables[0].tableName : ''; + return tables.length > 0 ? tables[0] : undefined; } }