diff --git a/README-zh_CN.md b/README-zh_CN.md index 9e3e08a6..35f67510 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -17,6 +17,7 @@ Monaco SQL Languages 是一个基于 Monaco Editor 的 SQL 语言项目,从 [m - 代码高亮 - 语法校验 - 自动补全 +- 内置SQL代码片段 > 由 [dt-sql-parser](https://github.com/DTStack/dt-sql-parser) 提供语法解析功能。 @@ -91,7 +92,7 @@ npm install monaco-sql-languages }); ``` - 默认情况下,自动补全功能只提供关键字自动补全, 但你可以通过设置 `completionService` 自定义自动补全项。 + 默认情况下,自动补全功能只提供关键字自动补全与内置SQL代码片段补全, 但你可以通过设置 `completionService` 自定义自动补全项。 ```typescript import { languages } from 'monaco-editor/esm/vs/editor/editor.api'; @@ -108,7 +109,8 @@ npm install monaco-sql-languages position, completionContext, suggestions, // 语法推荐信息 - entities // 当前编辑器文本的语法上下文中出现的表名、字段名等 + entities, // 当前编辑器文本的语法上下文中出现的表名、字段名等 + snippets // 代码片段 ) { return new Promise((resolve, reject) => { if (!suggestions) { @@ -160,6 +162,92 @@ npm install monaco-sql-languages
+## 代码片段 +我们为每种SQL语言内置了一部分代码片段, 帮助我们快速编写SQL。 + +**如何自定义代码片段?** + +在进行设置语言功能时, 通过配置`snippets`实现, 当`snippets`传入空数组时, 则关闭内置代码片段。 + +```typescript +import { snippets, CompletionSnippetOption } from 'monaco-sql-languages/esm/main.js'; + +const customSnippets: CompletionSnippetOption[] = [ + { + label: 'INSERT', + prefix: 'insert', + // Will join the line with `\n` + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ], + description: "This is an 'insert into select' snippet" + } +]; + +setupLanguageFeatures(LanguageIdEnum.MYSQL, { + completionItems: { + enable: true, + snippets: [...snippets.mysqlSnippets, ...customSnippets], + completionService + }, + preprocessCode +}); +``` +代码片段详细语法可以参考[vscode-snippet](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax), 不过与 vscode 代码片段不同的是, 我们仅会在**SQL语句开头**提供 snippets 补全项。 + +还需要注意的是,如果您提供了自定义的`completionService`方法, 您需要将`snippets`作为补全项手动返回, 以下是一个简单示例: + +```typescript +const completionService: CompletionService = async function ( + model, + position, + completionContext, + suggestions, + entities, + snippets +) { + const { keywords } = suggestions; + + const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({ + label: kw, + kind: languages.CompletionItemKind.Keyword, + detail: 'keyword', + sortText: '2' + kw + })); + + const snippetCompletionItems: ICompletionItem[] = + snippets?.map((item) => ({ + label: item.label || item.prefix, + kind: languages.CompletionItemKind.Snippet, + filterText: item.prefix, + insertText: item.insertText, + insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet, + sortText: '3' + item.prefix, + detail: item.description !== undefined ? item.description : 'SQL Snippet', + documentation: item.insertText + })) || []; + + return [...keywordsCompletionItems, ...snippetCompletionItems]; +}; +``` + +**其他注意事项** + +当处于代码片段中时, 可以通过`Tab`键移动到下一个输入位置, 但普通的关键字补全功能也是通过`Tab`键接受补全的,这会产生快捷键冲突, 所以 Monaco-Editor 规定, 当处于代码片段上下文时, 不会触发补全功能。 +![snippet-prevent-completion](./documents/images/snippet-prevent-completion.gif) +如果想要在代码片段中仍能支持智能补全, 可以通过设置 Monaco-Editor 配置项`suggest.snippetsPreventQuickSuggestions`为`false`来实现。 +```typescript +editor.create(editorElement, { + suggest: { + snippetsPreventQuickSuggestions: false + } +}) +``` +![snippet-no-prevent-completion](./documents/images/snippet-no-prevent-completion.gif) + ## Monaco Theme > Monaco SQL Languages 计划在未来支持更多的 Monaco Theme. diff --git a/README.md b/README.md index e346ed38..03f15121 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This project is based on the SQL language project of Monaco Editor, which was fo - Code Highlighting - Syntax Validation - Code Completion +- Built-in SQL Snippets > Powered By [dt-sql-parser](https://github.com/DTStack/dt-sql-parser) @@ -91,7 +92,7 @@ npm install monaco-sql-languages }); ``` - By default, Monaco SQL Languages only provides keyword autocompletion, and you can customize your completionItem list via `completionService`. + By default, Monaco SQL Languages only provides keyword autocompletion and built-in SQL snippets, and you can customize your completionItem list via `completionService`. ```typescript import { languages } from 'monaco-editor/esm/vs/editor/editor.api'; @@ -108,7 +109,8 @@ npm install monaco-sql-languages position, completionContext, suggestions, // syntax context info at caretPosition - entities // tables, columns in the syntax context of the editor text + entities, // tables, columns in the syntax context of the editor text + snippets // SQL snippets ) { return new Promise((resolve, reject) => { if (!suggestions) { @@ -160,6 +162,86 @@ npm install monaco-sql-languages
+## SQL Snippets + +We provide some built-in SQL snippets for each SQL language, which helps us to write SQL quickly. + +**How to customize SQL snippets?** + +When setting language features, you can customize SQL snippets via `snippets` configuration. When `snippets` is passed in as an empty array, the built-in SQL snippets are disabled. + +```typescript +import { snippets, CompletionSnippetOption } from 'monaco-sql-languages/esm/main.js'; + +const customSnippets: CompletionSnippetOption[] = [ + { + label: 'INSERT', + prefix: 'insert', + // Will join the line with `\n` + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ], + description: "This is an 'insert into select' snippet" + } +]; +``` + +Snippets syntax can refer to [vscode-snippet](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax). +But it is different from vscode code snippets, we only provide snippets completions **at the beginning of the SQL statement**. + +Note: If you provide a custom `completionService` method, you need to manually return the `snippets` as completions, as shown in the following example: + +```typescript +const completionService: CompletionService = async function ( + model, + position, + completionContext, + suggestions, + entities, + snippets +) { + const { keywords } = suggestions; + + const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({ + label: kw, + kind: languages.CompletionItemKind.Keyword, + detail: 'keyword', + sortText: '2' + kw + })); + + const snippetCompletionItems: ICompletionItem[] = + snippets?.map((item) => ({ + label: item.label || item.prefix, + kind: languages.CompletionItemKind.Snippet, + filterText: item.prefix, + insertText: item.insertText, + insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet, + sortText: '3' + item.prefix, + detail: item.description !== undefined ? item.description : 'SQL Snippet', + documentation: item.insertText + })) || []; + + return [...keywordsCompletionItems, ...snippetCompletionItems]; +}; +``` + +Other Notes: + +When in code snippet context, you can use `Tab` key to move to the next input position, but the keywords completions is also triggered by `Tab` key, which will cause a shortcut key conflict. So Monaco-Editor stipulates that when in code snippet context, it will not trigger completion. +![snippet-prevent-completion](./documents/images/snippet-prevent-completion.gif) +If you want to still support intelligent completion in code snippet context, you can set the Monaco-Editor configuration item `suggest.snippetsPreventQuickSuggestions` to `false` to achieve it. +```typescript +editor.create(editorElement, { + suggest: { + snippetsPreventQuickSuggestions: false + } +}) +``` +![snippet-no-prevent-completion](./documents/images/snippet-no-prevent-completion.gif) + ## Monaco Theme > Monaco SQL Languages plan to support more themes in the future. diff --git a/documents/images/snippet-no-prevent-completion.gif b/documents/images/snippet-no-prevent-completion.gif new file mode 100644 index 00000000..6570c94c Binary files /dev/null and b/documents/images/snippet-no-prevent-completion.gif differ diff --git a/documents/images/snippet-prevent-completion.gif b/documents/images/snippet-prevent-completion.gif new file mode 100644 index 00000000..2e574ff2 Binary files /dev/null and b/documents/images/snippet-prevent-completion.gif differ diff --git a/package.json b/package.json index dc75bc76..4ed60883 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "pre-commit": "npx pretty-quick --staged" }, "dependencies": { - "dt-sql-parser": "4.1.0-beta.4" + "dt-sql-parser": "4.2.0" }, "peerDependencies": { "monaco-editor": ">=0.31.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c3b1e83..986d2d9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: dt-sql-parser: - specifier: 4.1.0-beta.4 - version: 4.1.0-beta.4(antlr4ng-cli@1.0.7) + specifier: 4.2.0 + version: 4.2.0(antlr4ng-cli@1.0.7) devDependencies: '@commitlint/cli': specifier: ^17.7.2 @@ -308,6 +308,7 @@ packages: antlr4ng-cli@1.0.7: resolution: {integrity: sha512-qN2FsDBmLvsQcA5CWTrPz8I8gNXeS1fgXBBhI78VyxBSBV/EJgqy8ks6IDTC9jyugpl40csCQ4sL5K4i2YZ/2w==} + deprecated: 'This package is deprecated and will no longer be updated. Please use the new antlr-ng package instead: https://github.com/mike-lischke/antlr-ng' hasBin: true antlr4ng@2.0.11: @@ -714,8 +715,8 @@ packages: resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} engines: {node: '>=6'} - dt-sql-parser@4.1.0-beta.4: - resolution: {integrity: sha512-L+Qsw+lv7enkMuhy0XXOm7H63gaajwX7X0RUGCNU8h5xw9Pj5DEWvLcKTS0R+YmO4FzVXOpEzH9e1KkqQaKFaQ==} + dt-sql-parser@4.2.0: + resolution: {integrity: sha512-tsTHGNGIeTd7xACh8FNzSCaQHYyITJeSTMZPxGFkROiccPxj82uEqOeUgJ+bYof9hEacf4h61LsiMyxPy+tl7g==} engines: {node: '>=18'} email-addresses@3.1.0: @@ -2703,7 +2704,7 @@ snapshots: find-up: 3.0.0 minimatch: 3.1.2 - dt-sql-parser@4.1.0-beta.4(antlr4ng-cli@1.0.7): + dt-sql-parser@4.2.0(antlr4ng-cli@1.0.7): dependencies: antlr4-c3: 3.3.7(antlr4ng-cli@1.0.7) antlr4ng: 2.0.11(antlr4ng-cli@1.0.7) diff --git a/src/baseSQLWorker.ts b/src/baseSQLWorker.ts index 3d3029cf..4fe0166c 100644 --- a/src/baseSQLWorker.ts +++ b/src/baseSQLWorker.ts @@ -2,6 +2,7 @@ import { BasicSQL } from 'dt-sql-parser/dist/parser/common/basicSQL'; import { worker } from './fillers/monaco-editor-core'; import { Suggestions, ParseError, EntityContext } from 'dt-sql-parser'; import { Position } from './fillers/monaco-editor-core'; +import { SemanticContext } from 'dt-sql-parser/dist/parser/common/types'; export interface ICreateData { languageId: string; @@ -45,7 +46,11 @@ export abstract class BaseSQLWorker { async doCompletionWithEntities( code: string, position: Position - ): Promise<[Suggestions | null, EntityContext[] | null]> { + ): Promise<{ + suggestions: Suggestions | null; + allEntities: EntityContext[] | null; + context: SemanticContext | null; + }> { code = code || this.getTextDocument(); if (code) { const suggestions = this.parser.getSuggestionAtCaretPosition(code, position); @@ -53,9 +58,20 @@ export abstract class BaseSQLWorker { if (suggestions?.syntax?.length) { allEntities = this.parser.getAllEntities(code, position); } - return Promise.resolve([suggestions, allEntities]); + const semanticContext = this.parser.getSemanticContextAtCaretPosition(code, position); + + return Promise.resolve({ + suggestions, + allEntities, + context: semanticContext + }); } - return Promise.resolve([null, null]); + + return Promise.resolve({ + suggestions: null, + allEntities: null, + context: null + }); } async getAllEntities(code: string, position?: Position): Promise { diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index e1325c70..fa684269 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -15,7 +15,7 @@ import { Range, Uri } from './fillers/monaco-editor-core'; -import type { LanguageServiceDefaults } from './monaco.contribution'; +import type { CompletionSnippet, LanguageServiceDefaults } from './monaco.contribution'; export interface WorkerAccessor { (...uris: Uri[]): Promise; @@ -163,13 +163,22 @@ export class CompletionAdapter } return worker.doCompletionWithEntities(code, position); }) - .then(([suggestions, allEntities]) => { + .then(({ suggestions, allEntities, context: semanticContext }) => { + let snippets: CompletionSnippet[] = []; + if (semanticContext?.isStatementBeginning) { + snippets = this._defaults.completionSnippets.map((item) => ({ + ...item, + insertText: typeof item.body === 'string' ? item.body : item.body.join('\n') + })); + } + return this._defaults.completionService( model, position, context, suggestions, - allEntities + allEntities, + snippets ); }) .then((completions) => { diff --git a/src/languages/flink/flink.snippet.ts b/src/languages/flink/flink.snippet.ts new file mode 100644 index 00000000..d9b25eca --- /dev/null +++ b/src/languages/flink/flink.snippet.ts @@ -0,0 +1,115 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const flinkSnippets: CompletionSnippetOption[] = [ + { + label: 'create-source-table', + prefix: 'CREATE-SOURCE-TABLE', + body: [ + 'CREATE TABLE ${1:source_table} (', + '\tid STRING,', + '\tval BIGINT,', + '\tts TIMESTAMP(3),', + "\tWATERMARK FOR ts AS ts - INTERVAL '5' SECOND", + ') WITH (', + "\t'connector' = 'kafka',", + "\t'topic' = 'input_topic',", + "\t'properties.bootstrap.servers' = 'localhost:9092',", + "\t'format' = 'json',", + "\t'scan.startup.mode' = 'earliest-offset'", + ');' + ] + }, + { + label: 'create-sink-table', + prefix: 'CREATE-SINK-TABLE', + body: [ + 'CREATE TABLE ${1:sink_table} (', + '\tid STRING,', + '\tcnt BIGINT', + ') WITH (', + "\t'connector' = 'jdbc',", + "\t'url' = 'jdbc:mysql://localhost:3306/test',", + "\t'table-name' = 'output_table',", + "\t'username' = 'root',", + "\t'password' = 'password'", + ');' + ] + }, + { + label: 'tumble-window', + prefix: 'TUMBLE-WINDOW', + body: [ + 'SELECT', + '\twindow_start,', + '\twindow_end,', + '\tSUM(price) as total_price', + 'FROM', + '\tTABLE(TUMBLE(TABLE table_name2,', + '\tDESCRIPTOR(create_time),', + "\tINTERVAL '1' MINUTE))", + 'GROUP BY', + '\twindow_start,', + '\twindow_end;' + ] + }, + { + label: 'hop-window', + prefix: 'HOP-WINDOW', + body: [ + 'SELECT', + '\twindow_start,', + '\twindow_end,', + '\tSUM(price) as total_price', + 'FROM', + '\tTABLE(HOP(TABLE table_name2,', + '\tDESCRIPTOR(create_time),', + "\tINTERVAL '30' SECONDS,", + "\tINTERVAL '1' MINUTE))", + 'GROUP BY', + '\twindow_start,', + '\twindow_end;' + ] + }, + { + label: 'comulate-window', + prefix: 'CUMULATE-WINDOW', + body: [ + 'SELECT', + '\twindow_start,', + '\twindow_end,', + '\tSUM(price) as total_price', + 'FROM', + '\tTABLE(CUMULATE(TABLE table_name2,', + '\tDESCRIPTOR(create_time),', + "\tINTERVAL '30' SECONDS,", + "\tINTERVAL '1' MINUTE))", + 'GROUP BY', + '\twindow_start,', + '\twindow_end;' + ] + }, + { + label: 'session-window', + prefix: 'SESSION-WINDOW', + body: [ + 'SELECT', + "\tSESSION_START(create_time, INTERVAL '10' SECOND) AS session_beg,", + "\tSESSION_ROWTIME(create_time, INTERVAL '10' SECOND) AS session_end,", + '\tSUM(price) AS total_price', + 'FROM', + '\ttable_name', + 'GROUP BY', + "\tSESSION(create_time, INTERVAL '10' SECOND);" + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${2:column1}, ${3:column2}', + 'FROM ${4:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + } +]; diff --git a/src/languages/hive/hive.snippet.ts b/src/languages/hive/hive.snippet.ts new file mode 100644 index 00000000..a1fe4ef6 --- /dev/null +++ b/src/languages/hive/hive.snippet.ts @@ -0,0 +1,144 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const hiveSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:table_name} ${2:t1}', + '${3:LEFT} JOIN ${4:table2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDERBY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO TABLE ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'insert-overwrite-table', + prefix: 'INSERT-OVERWRITE-TABLE', + body: [ + 'INSERT OVERWRITE TABLE ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: ['DELETE FROM ${1:table_name}', 'WHERE ${2:column1} = ${3:value1};\n$4'] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + "COMMENT '${6:table_comment}'", + 'ROW FORMAT ${7:DELIMITED}', + "FIELDS TERMINATED BY '${8:\\t}'", + 'STORED AS ${9:PARQUET};\n$10' + ] + }, + { + label: 'create-table-as-select', + prefix: 'CREATE-TABLE-AS-SELECT', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name}', + 'AS', + 'SELECT ${2:column1}, ${3:column2}', + 'FROM ${4:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'create-partition-table', + prefix: 'CREATE-PARTITION-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + "COMMENT '${6:table_comment}'", + 'PARTITIONED BY (${7:part_column_name} STRING)', + 'ROW FORMAT ${8:DELIMITED}', + "FIELDS TERMINATED BY '${9:\\t}'", + 'STORED AS ${10:PARQUET};\n$11' + ] + }, + { + label: 'create-bucket-table', + prefix: 'CREATE-BUCKET-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + "COMMENT '${6:table_comment}'", + 'PARTITIONED BY (${7:part_column_name} STRING)', + 'CLUSTERED BY (${8:bucket_column_name}) INTO ${9:1} BUCKETS', + 'ROW FORMAT ${10:DELIMITED}', + "FIELDS TERMINATED BY '${11:\\t}'", + 'STORED AS ${12:PARQUET};\n$13' + ] + }, + { + label: 'alter-table-partition', + prefix: 'ALTER-TABLE-PARTITION', + body: [ + "ALTER TABLE ${1:table_name} ${2:ADD} PARTITION (${3:part_column}='${4:part_value}');\n$5" + ] + }, + { + label: 'alter-table-properties', + prefix: 'ALTER-TABLE-PROPERTIES', + body: [ + "ALTER TABLE ${1:table_name} SET TBLPROPERTIES ('${2:property_name}'='${3:property_value}');\n$4" + ] + }, + { + label: 'alter-table-columns', + prefix: 'ALTER-TABLE-COLUMNS', + body: [ + 'ALTER TABLE ${1:table_name} ADD COLUMNS (', + "\t${2:column_name} ${3:STRING} COMMENT '${4:desc}'", + ');\n$5' + ] + } +]; diff --git a/src/languages/impala/impala.snippet.ts b/src/languages/impala/impala.snippet.ts new file mode 100644 index 00000000..d0aec317 --- /dev/null +++ b/src/languages/impala/impala.snippet.ts @@ -0,0 +1,122 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const impalaSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:table_name} ${2:t1}', + '${3:LEFT} JOIN ${4:table2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDER-BY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'insert-overwrite-table', + prefix: 'INSERT-OVERWRITE-TABLE', + body: [ + 'INSERT OVERWRITE TABLE ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: ['DELETE FROM ${1:table_name}', 'WHERE ${2:column1} = ${3:value1};\n$4'] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + "COMMENT '${6:table_comment}'", + 'STORED AS ${7:PARQUET};\n$8' + ] + }, + { + label: 'create-table-as-select', + prefix: 'CREATE-TABLE-AS-SELECT', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name}', + 'AS', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'create-partition-table', + prefix: 'CREATE-PARTITION-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + 'PARTITIONED BY (${6:part_column_name} ${7:STRING})', + "COMMENT '${8:table_comment}'", + 'STORED AS ${9:PARQUET};\n$10' + ] + }, + { + label: 'alter-table-partition', + prefix: 'ALTER-TABLE-PARTITION', + body: [ + "ALTER TABLE ${1:table_name} ${2:ADD} PARTITION (${3:part_column} = '${4:part_value}');\n$5" + ] + }, + { + label: 'alter-table-properties', + prefix: 'ALTER-TABLE-PROPERTIES', + body: [ + "ALTER TABLE ${1:table_name} SET TBLPROPERTIES ('${2:property_name}' = '${3:property_value}');\n$4" + ] + }, + { + label: 'alter-table-column', + prefix: 'ALTER-TABLE-COLUMN', + body: [ + "ALTER TABLE ${1:table_name} ADD COLUMN ${2:column_name} ${3:STRING} COMMENT '${4:desc}';\n$5" + ] + } +]; diff --git a/src/languages/mysql/mysql.snippet.ts b/src/languages/mysql/mysql.snippet.ts new file mode 100644 index 00000000..b931b909 --- /dev/null +++ b/src/languages/mysql/mysql.snippet.ts @@ -0,0 +1,169 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const mysqlSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:table_name} ${2:t1}', + '${3:LEFT} JOIN ${4:table2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDER-BY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'replace-into-table', + prefix: 'REPLACE-INTO-TABLE', + body: [ + 'REPLACE INTO ${1:table_name} (${2:column1}, ${3:column2})', + 'VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: ['DELETE FROM ${1:table_name}', 'WHERE ${2:column1} = ${3:value1};\n$4'] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT},', + '\tPRIMARY KEY (${2:column1})', + ')', + "COMMENT '${6:table_comment}';\n$7" + ] + }, + { + label: 'create-table-as-select', + prefix: 'CREATE-TABLE-AS-SELECT', + body: [ + 'CREATE TABLE ${1:table_name}', + 'AS', + 'SELECT ${2:column1}, ${3:column2}', + 'FROM ${4:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'create-table-partitioned-by-range', + prefix: 'CREATE-TABLE-PARTITIONED-BY-RANGE', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT},', + '\tPRIMARY KEY (${2:column1})', + ')', + 'PARTITION BY RANGE (${2:column1}) (', + '\tPARTITION ${6:p0} VALUES LESS THAN ($7)', + ');\n$8' + ] + }, + { + label: 'create-table-partitioned-by-list', + prefix: 'CREATE-TABLE-PARTITIONED-BY-LIST', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT},', + '\tPRIMARY KEY (${2:column1})', + ')', + 'PARTITION BY LIST (${2:column1}) (', + '\tPARTITION ${6:p0} VALUES IN ($7)', + ');\n$8' + ] + }, + { + label: 'create-table-partitioned-by-hash', + prefix: 'CREATE-TABLE-PARTITIONED-BY-HASH', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT},', + '\tPRIMARY KEY (${2:column1})', + ')', + 'PARTITION BY HASH (${2:column1})', + 'PARTITIONS ${6:4};\n$7' + ] + }, + { + label: 'create-table-partitioned-by-key', + prefix: 'CREATE-TABLE-PARTITIONED-BY-KEY', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT},', + '\tPRIMARY KEY (${2:column1})', + ')', + 'PARTITION BY KEY (${2:column1})', + 'PARTITIONS ${6:4};\n$7' + ] + }, + { + label: 'alter-table-add-column', + prefix: 'ALTER-TABLE-ADD-COLUMN', + body: ["ALTER TABLE ${1:table_name} ADD ${2:column_name} ${3:INT} COMMENT '${4:desc}';\n$5"] + }, + { + label: 'alter-table-add-partition', + prefix: 'ALTER-TABLE-ADD-PARTITION', + body: ['ALTER TABLE ${1:table_name} ADD PARTITION (', '\t$2', ');\n$4'] + }, + { + label: 'alter-table-add-index', + prefix: 'ALTER-TABLE-ADD-INDEX', + body: ['ALTER TABLE ${1:table_name} ADD INDEX ${2:index_name} (${3:column_name});\n$4'] + }, + { + label: 'alter-table-add-primary-key', + prefix: 'ALTER-TABLE-ADD-PRIMARY-KEY', + body: ['ALTER TABLE ${1:table_name} ADD PRIMARY KEY (${2:column_name});\n$3'] + }, + { + label: 'alter-table-add-constraint', + prefix: 'ALTER-TABLE-ADD-CONSTRAINT', + body: [ + 'ALTER TABLE ${1:table_name}', + 'ADD CONSTRAINT ${2:constraint_name}', + 'FOREIGN KEY (${3:foreign_column}) REFERENCES ${4:ref_table} (${5:ref_column});\n$6' + ] + } +]; diff --git a/src/languages/pgsql/pgsql.snippet.ts b/src/languages/pgsql/pgsql.snippet.ts new file mode 100644 index 00000000..5591f6d3 --- /dev/null +++ b/src/languages/pgsql/pgsql.snippet.ts @@ -0,0 +1,137 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const pgsqlSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:table_name} ${2:t1}', + '${3:LEFT} JOIN ${4:table2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDER-BY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: ['DELETE FROM ${1:table_name}', 'WHERE ${2:column1} = ${3:value1};\n$4'] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT},', + '\tPRIMARY KEY (${2:column1})', + ');\n$6' + ] + }, + { + label: 'create-table-as-select', + prefix: 'CREATE-TABLE-AS-SELECT', + body: [ + 'CREATE TABLE ${1:table_name}', + 'AS', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'create-table-partitioned-by-range', + prefix: 'CREATE-TABLE-PARTITIONED-BY-RANGE', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT}', + ')', + 'PARTITION BY RANGE (${2:column1});\n', + 'CREATE TABLE ${6:p0} PARTITION OF ${1:table_name}', + '\tFOR VALUES FROM (${7:start_value}) TO (${8:end_value});\n$9' + ] + }, + { + label: 'create-table-partitioned-by-list', + prefix: 'CREATE-TABLE-PARTITIONED-BY-LIST', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT}', + ')', + 'PARTITION BY LIST (${2:column1});\n', + 'CREATE TABLE ${6:p0} PARTITION OF ${1:table_name}', + '\tFOR VALUES IN (${7:range_values});\n$8' + ] + }, + { + label: 'create-table-partitioned-by-hash', + prefix: 'CREATE-TABLE-PARTITIONED-BY-HASH', + body: [ + 'CREATE TABLE ${1:table_name} (', + '\t${2:column1} ${3:INT},', + '\t${4:column2} ${5:INT}', + ')', + 'PARTITION BY HASH (${2:column1});\n', + 'CREATE TABLE ${6:p0} PARTITION OF ${1:table_name}', + '\tFOR VALUES WITH (MODULUS ${7:4}, REMAINDER ${8:0});\n$9' + ] + }, + { + label: 'alter-table-add-column', + prefix: 'ALTER-TABLE-ADD-COLUMN', + body: ['ALTER TABLE ${1:table_name} ADD COLUMN ${2:column_name} ${3:INT};\n$4'] + }, + { + label: 'alter-table-add-primary-key', + prefix: 'ALTER-TABLE-ADD-PRIMARY-KEY', + body: [ + 'ALTER TABLE ${1:table_name} ADD CONSTRAINT ${2:pk_name} PRIMARY KEY (${3:column_name});\n$4' + ] + }, + { + label: 'alter-table-add-foreign-key', + prefix: 'ALTER-TABLE-ADD-FOREIGN-KEY', + body: [ + 'ALTER TABLE ${1:table_name}', + 'ADD CONSTRAINT ${2:fk_name}', + 'FOREIGN KEY (${3:foreign_column}) REFERENCES ${4:ref_table} (${5:ref_column});\n$6' + ] + } +]; diff --git a/src/languages/spark/spark.snippet.ts b/src/languages/spark/spark.snippet.ts new file mode 100644 index 00000000..251e4445 --- /dev/null +++ b/src/languages/spark/spark.snippet.ts @@ -0,0 +1,135 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const sparkSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:table_name} ${2:t1}', + '${3:LEFT} JOIN ${4:table2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDER-BY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO ${1:table_name}', + 'SELECT ${2:column1}, ${3:column2}', + 'FROM ${4:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'insert-overwrite-table', + prefix: 'INSERT-OVERWRITE-TABLE', + body: [ + 'INSERT OVERWRITE TABLE ${1:table_name}', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: ['DELETE FROM ${1:table_name}', 'WHERE ${2:column1} = ${3:value1};\n$4'] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + 'USING ${6:parquet}', + "COMMENT '${7:table_comment}';\n$8" + ] + }, + { + label: 'create-table-as-select', + prefix: 'CREATE-TABLE-AS-SELECT', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name}', + 'AS', + 'SELECT ${2:column1}, ${3:column2}', + 'FROM ${4:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'create-partition-table', + prefix: 'CREATE-PARTITION-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + 'USING ${6:parquet}', + 'PARTITIONED BY (${7:part_column_name} ${8:STRING})', + "COMMENT '${9:table_comment}';\n$10" + ] + }, + { + label: 'create-bucket-table', + prefix: 'CREATE-BUCKET-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + 'USING ${6:parquet}', + 'CLUSTERED BY (${7:bucket_column}) INTO ${8:1} BUCKETS', + "COMMENT '${9:table_comment}';\n$10" + ] + }, + { + label: 'alter-table-partition', + prefix: 'ALTER-TABLE-PARTITION', + body: [ + "ALTER TABLE ${1:table_name} ${2:ADD} PARTITION (${3:part_column}='${4:part_value}');\n$5" + ] + }, + { + label: 'alter-table-properties', + prefix: 'ALTER-TABLE-PROPERTIES', + body: [ + "ALTER TABLE ${1:table_name} SET TBLPROPERTIES ('${2:property_name}'='${3:property_value}');\n$4" + ] + }, + { + label: 'alter-table-column', + prefix: 'ALTER-TABLE-COLUMN', + body: [ + "ALTER TABLE ${1:table_name} ADD COLUMN ${2:column_name} ${3:STRING} COMMENT '${4:desc}';\n$5" + ] + } +]; diff --git a/src/languages/trino/trino.snippet.ts b/src/languages/trino/trino.snippet.ts new file mode 100644 index 00000000..0d82d8ad --- /dev/null +++ b/src/languages/trino/trino.snippet.ts @@ -0,0 +1,112 @@ +import type { CompletionSnippetOption } from 'src/monaco.contribution'; + +export const trinoSnippets: CompletionSnippetOption[] = [ + { + label: 'select', + prefix: 'SELECT', + body: ['SELECT ${2:column1}, ${3:column2} FROM ${1:hive.schema_name.table_name};\n$4'] + }, + { + label: 'select-join', + prefix: 'SELECT-JOIN', + body: [ + 'SELECT ${8:column1} FROM ${1:hive.schema_name.table_name1} ${2:t1}', + '${3:LEFT} JOIN ${4:hive.schema_name.table_name2} ${5:t2} ON ${2:t1}.${6:column1} = ${5:t2}.${7:column2};\n$9' + ] + }, + { + label: 'select-order-by', + prefix: 'SELECT-ORDER-BY', + body: [ + 'SELECT ${2:column1}, ${3:column2} FROM ${1:hive.schema_name.table_name} ORDER BY ${4:column1} ${5:desc};\n$6' + ] + }, + { + label: 'insert', + prefix: 'INSERT-INTO', + body: [ + 'INSERT INTO ${1:hive.schema_name.table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6' + ] + }, + { + label: 'insert-into-select', + prefix: 'INSERT-INTO-SELECT', + body: [ + 'INSERT INTO ${1:hive.schema_name.table_name}', + 'SELECT ${2:column1}, ${3:column2}', + 'FROM ${4:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'update', + prefix: 'UPDATE', + body: [ + 'UPDATE ${1:hive.schema_name.table_name}', + 'SET ${2:column1} = ${3:value1}', + 'WHERE ${4:column2} = ${5:value2};\n$6' + ] + }, + { + label: 'delete', + prefix: 'DELETE', + body: [ + 'DELETE FROM ${1:hive.schema_name.table_name}', + 'WHERE ${2:column1} = ${3:value1};\n$4' + ] + }, + { + label: 'create-catalog', + prefix: 'CREATE-CATALOG', + body: [ + 'CREATE CATALOG ${1:catalog_name} USING ${2:hive}', + 'WITH (', + "\t${3:property_name} = '${4:property_value}'", + ');\n$5' + ] + }, + { + label: 'create-schema', + prefix: 'CREATE-SCHEMA', + body: ["CREATE SCHEMA ${1:hive.schema_name} WITH (LOCATION = '${2:/hive/data/web}');\n$3"] + }, + { + label: 'create-table', + prefix: 'CREATE-TABLE', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:hive.schema_name.table_name} (', + '\t${2:column1} ${3:STRING},', + '\t${4:column2} ${5:STRING}', + ')', + "COMMENT '${6:table_comment}'", + 'WITH (', + "\tformat = '${7:PARQUET}'", + ');\n$8' + ] + }, + { + label: 'create-table-as-select', + prefix: 'CREATE-TABLE-AS-SELECT', + body: [ + 'CREATE TABLE IF NOT EXISTS ${1:hive.schema_name.table_name}', + 'AS', + 'SELECT ${3:column1}, ${4:column2}', + 'FROM ${2:source_table}', + 'WHERE ${5:conditions};\n$6' + ] + }, + { + label: 'alter-table-properties', + prefix: 'ALTER-TABLE-PROPERTIES', + body: [ + "ALTER TABLE ${1:hive.schema_name.table_name} SET PROPERTIES ${2:property_name} = '${3:property_value}';\n$4" + ] + }, + { + label: 'alter-table-columns', + prefix: 'ALTER-TABLE-COLUMNS', + body: [ + "ALTER TABLE ${1:hive.schema_name.table_name} ADD COLUMN ${2:column_name} ${3:STRING} COMMENT '${4:desc}';\n$5" + ] + } +]; diff --git a/src/main.ts b/src/main.ts index 45036c2a..900243c0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ export * from './languageService'; export * from './setupLanguageFeatures'; export * from './common/constants'; export * from './theme'; +export * as snippets from './snippets'; export { EntityContextType, StmtContextType } from 'dt-sql-parser'; diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index c1a2cdc2..bcb2c6ea 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -32,7 +32,8 @@ export type CompletionService = ( position: Position, completionContext: languages.CompletionContext, suggestions: Suggestions | null, - entities: EntityContext[] | null + entities: EntityContext[] | null, + snippets?: CompletionSnippet[] ) => Promise; export interface CompletionOptions { @@ -43,8 +44,20 @@ export interface CompletionOptions { */ completionService: CompletionService; triggerCharacters: string[]; + snippets: CompletionSnippetOption[]; } +export interface CompletionSnippet { + prefix: string; + label: string; + body: string | string[]; + // generated by body + insertText?: string; + description?: string; +} + +export type CompletionSnippetOption = Omit; + export interface ModeConfiguration { /** * Defines whether the built-in completionItemProvider is enabled. @@ -91,6 +104,7 @@ export interface LanguageServiceDefaults { readonly modeConfiguration: ModeConfiguration; preprocessCode: PreprocessCode | null; completionService: CompletionService; + completionSnippets: CompletionSnippet[]; triggerCharacters: string[]; setModeConfiguration(modeConfiguration: ModeConfiguration): void; } @@ -127,6 +141,10 @@ export class LanguageServiceDefaultsImpl implements LanguageServiceDefaults { return this._modeConfiguration.completionItems.completionService; } + get completionSnippets(): CompletionSnippet[] { + return this._modeConfiguration.completionItems.snippets; + } + get triggerCharacters(): string[] { return this._modeConfiguration.completionItems.triggerCharacters; } @@ -150,7 +168,9 @@ export const defaultCompletionService: CompletionService = function ( _model, _position, _context, - suggestions + suggestions, + _entities, + snippets ) { if (!suggestions) { return Promise.resolve([]); @@ -163,11 +183,23 @@ export const defaultCompletionService: CompletionService = function ( detail: 'keyword' })); - return Promise.resolve(keywordsCompletionItems); + const snippetCompletionItems: ICompletionItem[] = + snippets?.map((item) => ({ + label: item.label || item.prefix, + kind: languages.CompletionItemKind.Snippet, + filterText: item.prefix, + insertText: item.insertText, + insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet, + detail: item.description || 'snippet', + documentation: item.insertText + })) || []; + + return Promise.resolve([...keywordsCompletionItems, ...snippetCompletionItems]); }; export const modeConfigurationDefault: Required = { completionItems: { + snippets: [], enable: true, completionService: defaultCompletionService, triggerCharacters: ['.', ' '] diff --git a/src/setupLanguageFeatures.ts b/src/setupLanguageFeatures.ts index 44cb3f6e..cd3fc5dd 100644 --- a/src/setupLanguageFeatures.ts +++ b/src/setupLanguageFeatures.ts @@ -8,6 +8,7 @@ import { modeConfigurationDefault, PreprocessCode } from './monaco.contribution'; +import * as snippets from './snippets'; export interface FeatureConfiguration { /** @@ -88,6 +89,27 @@ export function setupLanguageFeatures( } } +function getDefaultSnippets(languageId: LanguageIdEnum) { + switch (languageId) { + case LanguageIdEnum.HIVE: + return snippets.hiveSnippets; + case LanguageIdEnum.FLINK: + return snippets.flinkSnippets; + case LanguageIdEnum.IMPALA: + return snippets.impalaSnippets; + case LanguageIdEnum.MYSQL: + return snippets.mysqlSnippets; + case LanguageIdEnum.PG: + return snippets.pgsqlSnippets; + case LanguageIdEnum.SPARK: + return snippets.sparkSnippets; + case LanguageIdEnum.TRINO: + return snippets.trinoSnippets; + default: + return []; + } +} + function processConfiguration( languageId: LanguageIdEnum, configuration: FeatureConfiguration @@ -127,12 +149,20 @@ function processConfiguration( ? configuration.definitions : (defaults?.modeConfiguration.definitions ?? modeConfigurationDefault.definitions); + const snippets = + typeof configuration.completionItems !== 'boolean' && + Array.isArray(configuration.completionItems?.snippets) + ? configuration.completionItems!.snippets + : (defaults?.modeConfiguration.completionItems.snippets ?? + getDefaultSnippets(languageId)); + return { diagnostics, completionItems: { enable: completionEnable, completionService, - triggerCharacters + triggerCharacters, + snippets }, references, definitions diff --git a/src/snippets.ts b/src/snippets.ts new file mode 100644 index 00000000..997e8f28 --- /dev/null +++ b/src/snippets.ts @@ -0,0 +1,7 @@ +export { hiveSnippets } from './languages/hive/hive.snippet'; +export { flinkSnippets } from './languages/flink/flink.snippet'; +export { trinoSnippets } from './languages/trino/trino.snippet'; +export { pgsqlSnippets } from './languages/pgsql/pgsql.snippet'; +export { sparkSnippets } from './languages/spark/spark.snippet'; +export { mysqlSnippets } from './languages/mysql/mysql.snippet'; +export { impalaSnippets } from './languages/impala/impala.snippet'; diff --git a/website/src/App.tsx b/website/src/App.tsx index 062d598e..ab5cfeac 100644 --- a/website/src/App.tsx +++ b/website/src/App.tsx @@ -4,12 +4,26 @@ import { create, Workbench } from '@dtinsight/molecule'; import InstanceService from '@dtinsight/molecule/esm/services/instanceService'; import { ExtendsWorkbench } from './extensions/workbench'; import { version, dependencies } from '../../package.json'; - +import { editor } from 'monaco-editor'; import './languages'; import '@dtinsight/molecule/esm/style/mo.css'; + import './App.css'; +/** + * Allow code completion when typing in snippets. + * + * You can also set configurations when creating monaco-editor instance + */ +editor.onDidCreateEditor((editor) => { + editor.updateOptions({ + suggest: { + snippetsPreventQuickSuggestions: false + } + }); +}); + function App(): React.ReactElement { const refMoInstance = useRef(); const [MyWorkbench, setMyWorkbench] = useState(); diff --git a/website/src/languages/helpers/completionService.ts b/website/src/languages/helpers/completionService.ts index c6fde960..c060536c 100644 --- a/website/src/languages/helpers/completionService.ts +++ b/website/src/languages/helpers/completionService.ts @@ -16,7 +16,9 @@ export const completionService: CompletionService = async function ( model, _position, _completionContext, - suggestions + suggestions, + _entities, + snippets ) { if (!suggestions) { return Promise.resolve([]); @@ -183,5 +185,18 @@ export const completionService: CompletionService = async function ( } } } - return [...syntaxCompletionItems, ...keywordsCompletionItems]; + + const snippetCompletionItems: ICompletionItem[] = + snippets?.map((item) => ({ + label: item.label || item.prefix, + kind: languages.CompletionItemKind.Snippet, + filterText: item.prefix, + insertText: item.insertText, + insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet, + sortText: '3' + item.prefix, + detail: item.description !== undefined ? item.description : 'SQL模板', + documentation: item.insertText + })) || []; + + return [...syntaxCompletionItems, ...keywordsCompletionItems, ...snippetCompletionItems]; }; diff --git a/website/src/languages/index.ts b/website/src/languages/index.ts index 88c0ccbb..5f64c296 100644 --- a/website/src/languages/index.ts +++ b/website/src/languages/index.ts @@ -2,7 +2,6 @@ import 'monaco-sql-languages/esm/all.contributions.js'; import './languageWorker'; import './theme'; import { setupLanguageFeatures, LanguageIdEnum } from 'monaco-sql-languages/esm/main.js'; - import { completionService } from './helpers/completionService'; /**