Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【WIP】feat: support built-in sql snippets #154

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions README-zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Monaco SQL Languages 是一个基于 Monaco Editor 的 SQL 语言项目,从 [m
- 代码高亮
- 语法校验
- 自动补全
- 内置SQL代码片段

> 由 [dt-sql-parser](https://github.com/DTStack/dt-sql-parser) 提供语法解析功能。

Expand Down Expand Up @@ -91,7 +92,7 @@ npm install monaco-sql-languages
});
```

默认情况下,自动补全功能只提供关键字自动补全, 但你可以通过设置 `completionService` 自定义自动补全项。
默认情况下,自动补全功能只提供关键字自动补全与内置SQL代码片段补全, 但你可以通过设置 `completionService` 自定义自动补全项。

```typescript
import { languages } from 'monaco-editor/esm/vs/editor/editor.api';
Expand All @@ -108,7 +109,8 @@ npm install monaco-sql-languages
position,
completionContext,
suggestions, // 语法推荐信息
entities // 当前编辑器文本的语法上下文中出现的表名、字段名等
entities, // 当前编辑器文本的语法上下文中出现的表名、字段名等
snippets // 代码片段
) {
return new Promise((resolve, reject) => {
if (!suggestions) {
Expand Down Expand Up @@ -160,6 +162,92 @@ npm install monaco-sql-languages

<br/>

## 代码片段
我们为每种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.
Expand Down
86 changes: 84 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -160,6 +162,86 @@ npm install monaco-sql-languages

<br/>

## 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.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documents/images/snippet-prevent-completion.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"pre-commit": "npx pretty-quick --staged"
},
"dependencies": {
"dt-sql-parser": "4.0.2"
"dt-sql-parser": "4.1.0-beta.4"
},
"peerDependencies": {
"monaco-editor": ">=0.31.0"
Expand Down
15 changes: 8 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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 { 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 / 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 (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 / test (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 / 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 (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 / check (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 @@
async doCompletionWithEntities(
code: string,
position: Position
): Promise<[Suggestions | null, EntityContext[] | null]> {
): Promise<{
JackWang032 marked this conversation as resolved.
Show resolved Hide resolved
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 / 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 (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 / test (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 / 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 (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 / check (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(({ 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) => {
Expand Down
Loading
Loading