Skip to content

Commit

Permalink
feat: support built-in sql snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
jialan committed Nov 4, 2024
1 parent a8f6ea8 commit 0f29fa6
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 13 deletions.
22 changes: 19 additions & 3 deletions src/baseSQLWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Check failure on line 5 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / check (18.x)

Module '"dt-sql-parser/dist/parser/common/types"' has no exported member 'SemanticContext'.

Check failure on line 5 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Module '"dt-sql-parser/dist/parser/common/types"' has no exported member 'SemanticContext'.

Check failure on line 5 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / check (20.x)

Module '"dt-sql-parser/dist/parser/common/types"' has no exported member 'SemanticContext'.

Check failure on line 5 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / test (18.x)

Module '"dt-sql-parser/dist/parser/common/types"' has no exported member 'SemanticContext'.

Check failure on line 5 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Module '"dt-sql-parser/dist/parser/common/types"' has no exported member 'SemanticContext'.

Check failure on line 5 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / test (20.x)

Module '"dt-sql-parser/dist/parser/common/types"' has no exported member 'SemanticContext'.

export interface ICreateData {
languageId: string;
Expand Down Expand Up @@ -45,17 +46,32 @@ 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);
let allEntities = null;
if (suggestions?.syntax?.length) {
allEntities = this.parser.getAllEntities(code, position);
}
return Promise.resolve([suggestions, allEntities]);
const semanticContext = this.parser.getSemanticContextAtCaretPosition(code, position);

Check failure on line 61 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / check (18.x)

Property 'getSemanticContextAtCaretPosition' does not exist on type 'BasicSQL<Lexer, ParserRuleContext, SQLParserBase<ParserRuleContext>>'. Did you mean 'getSuggestionAtCaretPosition'?

Check failure on line 61 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Property 'getSemanticContextAtCaretPosition' does not exist on type 'BasicSQL<Lexer, ParserRuleContext, SQLParserBase<ParserRuleContext>>'. Did you mean 'getSuggestionAtCaretPosition'?

Check failure on line 61 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / check (20.x)

Property 'getSemanticContextAtCaretPosition' does not exist on type 'BasicSQL<Lexer, ParserRuleContext, SQLParserBase<ParserRuleContext>>'. Did you mean 'getSuggestionAtCaretPosition'?

Check failure on line 61 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / test (18.x)

Property 'getSemanticContextAtCaretPosition' does not exist on type 'BasicSQL<Lexer, ParserRuleContext, SQLParserBase<ParserRuleContext>>'. Did you mean 'getSuggestionAtCaretPosition'?

Check failure on line 61 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Property 'getSemanticContextAtCaretPosition' does not exist on type 'BasicSQL<Lexer, ParserRuleContext, SQLParserBase<ParserRuleContext>>'. Did you mean 'getSuggestionAtCaretPosition'?

Check failure on line 61 in src/baseSQLWorker.ts

View workflow job for this annotation

GitHub Actions / test (20.x)

Property 'getSemanticContextAtCaretPosition' does not exist on type 'BasicSQL<Lexer, ParserRuleContext, SQLParserBase<ParserRuleContext>>'. Did you mean 'getSuggestionAtCaretPosition'?

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<EntityContext[] | null> {
Expand Down
15 changes: 12 additions & 3 deletions src/languageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { debounce } from './common/utils';
import { BaseSQLWorker } from './baseSQLWorker';
import type { ParseError } from 'dt-sql-parser';
import type { LanguageServiceDefaults } from './monaco.contribution';
import type { CompletionSnippet, LanguageServiceDefaults } from './monaco.contribution';

export interface WorkerAccessor<T extends BaseSQLWorker> {
(...uris: Uri[]): Promise<T>;
Expand Down Expand Up @@ -159,13 +159,22 @@ export class CompletionAdapter<T extends BaseSQLWorker>
}
return worker.doCompletionWithEntities(code, position);
})
.then(([suggestions, allEntities]) => {
.then(async ({ suggestions, allEntities, context: semanticContext }) => {
let snippets: CompletionSnippet[] = [];
if (semanticContext?.isNewStatement) {
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) => {
Expand Down
14 changes: 14 additions & 0 deletions src/languages/flink/flink.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CompletionSnippetOption } from 'src/monaco.contribution';

export const flinkSnippets: CompletionSnippetOption[] = [
{
prefix: 'INSERT',
label: 'INSERT代码片段',
body: ['insert', 'into', '\t`${1:table1}`', 'values', '\t(`$2`);', '${3}']
},
{
prefix: 'SELECT',
label: 'SELECT 通用模板',
body: ['select', '\t${1:id}', 'from', '\t${2:table1};', '${3}']
}
];
117 changes: 117 additions & 0 deletions src/languages/hive/hive.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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',
body: [
'INSERT INTO ${1:table_name} (${2:column1}, ${3:column2}) VALUES (${4:value1}, ${5:value2});\n$6'
]
},
{
label: 'insert into select',
prefix: 'INSERT',
body: [
'INSERT INTO TABLE ${1:table_name}',
'SELECT ${2:column1}, ${3:column3}',
'FROM ${4: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_alias}'",
'ROW FORMAT ${7:DELIMITE}',
"FIELDS TERMINATED BY '${8:\\t}'",
'STORED AS ${9:TEXTFILE};\n$10'
]
},
{
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 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_alias}'",
'PARTITIONED BY (${7:part_column_name} STRING)',
'ROW FORMAT ${8:DELIMITED}',
"FIELDS TERMINATED BY '${9:\\t}'",
'STORED AS ${10:TEXTFILE};\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_alias}'",
'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:TEXTFILE};\n$13'
]
},
{
label: 'alter table partition',
prefix: 'ALTER-TABLE-PARTITION',
body: [
'ALTER TABLE ${1:table_name}',
"${2:ADD} PARTITION (${3:partition_column}='${4:partition_value}');\n$5"
]
}
];
14 changes: 14 additions & 0 deletions src/languages/impala/impala.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CompletionSnippetOption } from 'src/monaco.contribution';

export const impalaSnippets: CompletionSnippetOption[] = [
{
prefix: 'INSERT',
label: 'INSERT代码片段',
body: ['insert', 'into', '\t`${1:table1}`', 'values', '\t(`$2`);', '${3}']
},
{
prefix: 'SELECT',
label: 'SELECT 通用模板',
body: ['select', '\t${1:id}', 'from', '\t${2:table1};', '${3}']
}
];
14 changes: 14 additions & 0 deletions src/languages/mysql/mysql.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CompletionSnippetOption } from 'src/monaco.contribution';

export const mysqlSnippets: CompletionSnippetOption[] = [
{
prefix: 'INSERT',
label: 'INSERT代码片段',
body: ['insert', 'into', '\t`${1:table1}`', 'values', '\t(`$2`);', '${3}']
},
{
prefix: 'SELECT',
label: 'SELECT 通用模板',
body: ['select', '\t${1:id}', 'from', '\t${2:table1};', '${3}']
}
];
14 changes: 14 additions & 0 deletions src/languages/pgsql/pgsql.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CompletionSnippetOption } from 'src/monaco.contribution';

export const pgsqlSnippets: CompletionSnippetOption[] = [
{
prefix: 'INSERT',
label: 'INSERT代码片段',
body: ['insert', 'into', '\t`${1:table1}`', 'values', '\t(`$2`);', '${3}']
},
{
prefix: 'SELECT',
label: 'SELECT 通用模板',
body: ['select', '\t${1:id}', 'from', '\t${2:table1};', '${3}']
}
];
14 changes: 14 additions & 0 deletions src/languages/spark/spark.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CompletionSnippetOption } from 'src/monaco.contribution';

export const sparkSnippets: CompletionSnippetOption[] = [
{
prefix: 'INSERT',
label: 'INSERT代码片段',
body: ['insert', 'into', '\t`${1:table1}`', 'values', '\t(`$2`);', '${3}']
},
{
prefix: 'SELECT',
label: 'SELECT 通用模板',
body: ['select', '\t${1:id}', 'from', '\t${2:table1};', '${3}']
}
];
14 changes: 14 additions & 0 deletions src/languages/trino/trino.snippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CompletionSnippetOption } from 'src/monaco.contribution';

export const trinoSnippets: CompletionSnippetOption[] = [
{
prefix: 'INSERT',
label: 'INSERT代码片段',
body: ['insert', 'into', '\t`${1:table1}`', 'values', '\t(`$2`);', '${3}']
},
{
prefix: 'SELECT',
label: 'SELECT 通用模板',
body: ['select', '\t${1:id}', 'from', '\t${2:table1};', '${3}']
}
];
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
37 changes: 34 additions & 3 deletions src/monaco.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export type CompletionService = (
position: Position,
completionContext: languages.CompletionContext,
suggestions: Suggestions | null,
entities: EntityContext[] | null
entities: EntityContext[] | null,
snippets?: CompletionSnippet[]
) => Promise<ICompletionItem[] | ICompletionList>;

export interface CompletionOptions {
Expand All @@ -42,8 +43,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<CompletionSnippet, 'insertText'>;

export interface ModeConfiguration {
/**
* Defines whether the built-in completionItemProvider is enabled.
Expand Down Expand Up @@ -90,6 +103,7 @@ export interface LanguageServiceDefaults {
readonly modeConfiguration: ModeConfiguration;
preprocessCode: PreprocessCode | null;
completionService: CompletionService;
completionSnippets: CompletionSnippet[];
triggerCharacters: string[];
setModeConfiguration(modeConfiguration: ModeConfiguration): void;
}
Expand Down Expand Up @@ -126,6 +140,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;
}
Expand All @@ -149,7 +167,9 @@ export const defaultCompletionService: CompletionService = function (
_model,
_position,
_context,
suggestions
suggestions,
_entities,
snippets
) {
if (!suggestions) {
return Promise.resolve([]);
Expand All @@ -162,11 +182,22 @@ 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'
})) || [];

return Promise.resolve([...keywordsCompletionItems, ...snippetCompletionItems]);
};

export const modeConfigurationDefault: Required<ModeConfiguration> = {
completionItems: {
snippets: [],
enable: true,
completionService: defaultCompletionService,
triggerCharacters: ['.', ' ']
Expand Down
Loading

0 comments on commit 0f29fa6

Please sign in to comment.