Skip to content

Commit a5d68bb

Browse files
JackWang032jialan
and
jialan
authored
feat: support built-in sql snippets (#154)
* feat: support built-in sql snippets * docs: update docs for snippets * chore: update dt-sql-parser to v4.1.0-beta.8 --------- Co-authored-by: JackWang032 <--global> Co-authored-by: jialan <jialan@dtstack.com>
1 parent e5be2bb commit a5d68bb

22 files changed

+1253
-26
lines changed

README-zh_CN.md

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Monaco SQL Languages 是一个基于 Monaco Editor 的 SQL 语言项目,从 [m
1717
- 代码高亮
1818
- 语法校验
1919
- 自动补全
20+
- 内置SQL代码片段
2021

2122
> [dt-sql-parser](https://github.com/DTStack/dt-sql-parser) 提供语法解析功能。
2223
@@ -91,7 +92,7 @@ npm install monaco-sql-languages
9192
});
9293
```
9394

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

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

161163
<br/>
162164

165+
## 代码片段
166+
我们为每种SQL语言内置了一部分代码片段, 帮助我们快速编写SQL
167+
168+
**如何自定义代码片段?**
169+
170+
在进行设置语言功能时, 通过配置`snippets`实现, 当`snippets`传入空数组时, 则关闭内置代码片段。
171+
172+
```typescript
173+
import { snippets, CompletionSnippetOption } from 'monaco-sql-languages/esm/main.js';
174+
175+
const customSnippets: CompletionSnippetOption[] = [
176+
{
177+
label: 'INSERT',
178+
prefix: 'insert',
179+
// Will join the line with `\n`
180+
body: [
181+
'INSERT INTO ${1:table_name}',
182+
'SELECT ${3:column1}, ${4:column2}',
183+
'FROM ${2:source_table}',
184+
'WHERE ${5:conditions};\n$6'
185+
],
186+
description: "This is an 'insert into select' snippet"
187+
}
188+
];
189+
190+
setupLanguageFeatures(LanguageIdEnum.MYSQL, {
191+
completionItems: {
192+
enable: true,
193+
snippets: [...snippets.mysqlSnippets, ...customSnippets],
194+
completionService
195+
},
196+
preprocessCode
197+
});
198+
```
199+
代码片段详细语法可以参考[vscode-snippet](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax), 不过与 vscode 代码片段不同的是, 我们仅会在**SQL语句开头**提供 snippets 补全项。
200+
201+
还需要注意的是,如果您提供了自定义的`completionService`方法, 您需要将`snippets`作为补全项手动返回, 以下是一个简单示例:
202+
203+
```typescript
204+
const completionService: CompletionService = async function (
205+
model,
206+
position,
207+
completionContext,
208+
suggestions,
209+
entities,
210+
snippets
211+
) {
212+
const { keywords } = suggestions;
213+
214+
const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({
215+
label: kw,
216+
kind: languages.CompletionItemKind.Keyword,
217+
detail: 'keyword',
218+
sortText: '2' + kw
219+
}));
220+
221+
const snippetCompletionItems: ICompletionItem[] =
222+
snippets?.map((item) => ({
223+
label: item.label || item.prefix,
224+
kind: languages.CompletionItemKind.Snippet,
225+
filterText: item.prefix,
226+
insertText: item.insertText,
227+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
228+
sortText: '3' + item.prefix,
229+
detail: item.description !== undefined ? item.description : 'SQL Snippet',
230+
documentation: item.insertText
231+
})) || [];
232+
233+
return [...keywordsCompletionItems, ...snippetCompletionItems];
234+
};
235+
```
236+
237+
**其他注意事项**
238+
239+
当处于代码片段中时, 可以通过`Tab`键移动到下一个输入位置, 但普通的关键字补全功能也是通过`Tab`键接受补全的,这会产生快捷键冲突, 所以 Monaco-Editor 规定, 当处于代码片段上下文时, 不会触发补全功能。
240+
![snippet-prevent-completion](./documents/images/snippet-prevent-completion.gif)
241+
如果想要在代码片段中仍能支持智能补全, 可以通过设置 Monaco-Editor 配置项`suggest.snippetsPreventQuickSuggestions``false`来实现。
242+
```typescript
243+
editor.create(editorElement, {
244+
suggest: {
245+
snippetsPreventQuickSuggestions: false
246+
}
247+
})
248+
```
249+
![snippet-no-prevent-completion](./documents/images/snippet-no-prevent-completion.gif)
250+
163251
## Monaco Theme
164252

165253
> Monaco SQL Languages 计划在未来支持更多的 Monaco Theme.

README.md

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This project is based on the SQL language project of Monaco Editor, which was fo
1717
- Code Highlighting
1818
- Syntax Validation
1919
- Code Completion
20+
- Built-in SQL Snippets
2021

2122
> Powered By [dt-sql-parser](https://github.com/DTStack/dt-sql-parser)
2223
@@ -91,7 +92,7 @@ npm install monaco-sql-languages
9192
});
9293
```
9394

94-
By default, Monaco SQL Languages only provides keyword autocompletion, and you can customize your completionItem list via `completionService`.
95+
By default, Monaco SQL Languages only provides keyword autocompletion and built-in SQL snippets, and you can customize your completionItem list via `completionService`.
9596

9697
```typescript
9798
import { languages } from 'monaco-editor/esm/vs/editor/editor.api';
@@ -108,7 +109,8 @@ npm install monaco-sql-languages
108109
position,
109110
completionContext,
110111
suggestions, // syntax context info at caretPosition
111-
entities // tables, columns in the syntax context of the editor text
112+
entities, // tables, columns in the syntax context of the editor text
113+
snippets // SQL snippets
112114
) {
113115
return new Promise((resolve, reject) => {
114116
if (!suggestions) {
@@ -160,6 +162,86 @@ npm install monaco-sql-languages
160162

161163
<br/>
162164

165+
## SQL Snippets
166+
167+
We provide some built-in SQL snippets for each SQL language, which helps us to write SQL quickly.
168+
169+
**How to customize SQL snippets?**
170+
171+
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.
172+
173+
```typescript
174+
import { snippets, CompletionSnippetOption } from 'monaco-sql-languages/esm/main.js';
175+
176+
const customSnippets: CompletionSnippetOption[] = [
177+
{
178+
label: 'INSERT',
179+
prefix: 'insert',
180+
// Will join the line with `\n`
181+
body: [
182+
'INSERT INTO ${1:table_name}',
183+
'SELECT ${3:column1}, ${4:column2}',
184+
'FROM ${2:source_table}',
185+
'WHERE ${5:conditions};\n$6'
186+
],
187+
description: "This is an 'insert into select' snippet"
188+
}
189+
];
190+
```
191+
192+
Snippets syntax can refer to [vscode-snippet](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax).
193+
But it is different from vscode code snippets, we only provide snippets completions **at the beginning of the SQL statement**.
194+
195+
Note: If you provide a custom `completionService` method, you need to manually return the `snippets` as completions, as shown in the following example:
196+
197+
```typescript
198+
const completionService: CompletionService = async function (
199+
model,
200+
position,
201+
completionContext,
202+
suggestions,
203+
entities,
204+
snippets
205+
) {
206+
const { keywords } = suggestions;
207+
208+
const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({
209+
label: kw,
210+
kind: languages.CompletionItemKind.Keyword,
211+
detail: 'keyword',
212+
sortText: '2' + kw
213+
}));
214+
215+
const snippetCompletionItems: ICompletionItem[] =
216+
snippets?.map((item) => ({
217+
label: item.label || item.prefix,
218+
kind: languages.CompletionItemKind.Snippet,
219+
filterText: item.prefix,
220+
insertText: item.insertText,
221+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
222+
sortText: '3' + item.prefix,
223+
detail: item.description !== undefined ? item.description : 'SQL Snippet',
224+
documentation: item.insertText
225+
})) || [];
226+
227+
return [...keywordsCompletionItems, ...snippetCompletionItems];
228+
};
229+
```
230+
231+
Other Notes:
232+
233+
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.
234+
![snippet-prevent-completion](./documents/images/snippet-prevent-completion.gif)
235+
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.
236+
```typescript
237+
editor.create(editorElement, {
238+
suggest: {
239+
snippetsPreventQuickSuggestions: false
240+
}
241+
})
242+
```
243+
![snippet-no-prevent-completion](./documents/images/snippet-no-prevent-completion.gif)
244+
163245
## Monaco Theme
164246

165247
> Monaco SQL Languages plan to support more themes in the future.
Loading
1.08 MB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"pre-commit": "npx pretty-quick --staged"
6868
},
6969
"dependencies": {
70-
"dt-sql-parser": "4.1.0-beta.4"
70+
"dt-sql-parser": "4.1.0-beta.8"
7171
},
7272
"peerDependencies": {
7373
"monaco-editor": ">=0.31.0"

pnpm-lock.yaml

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/baseSQLWorker.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BasicSQL } from 'dt-sql-parser/dist/parser/common/basicSQL';
22
import { worker } from './fillers/monaco-editor-core';
33
import { Suggestions, ParseError, EntityContext } from 'dt-sql-parser';
44
import { Position } from './fillers/monaco-editor-core';
5+
import { SemanticContext } from 'dt-sql-parser/dist/parser/common/types';
56

67
export interface ICreateData {
78
languageId: string;
@@ -45,17 +46,32 @@ export abstract class BaseSQLWorker {
4546
async doCompletionWithEntities(
4647
code: string,
4748
position: Position
48-
): Promise<[Suggestions | null, EntityContext[] | null]> {
49+
): Promise<{
50+
suggestions: Suggestions | null;
51+
allEntities: EntityContext[] | null;
52+
context: SemanticContext | null;
53+
}> {
4954
code = code || this.getTextDocument();
5055
if (code) {
5156
const suggestions = this.parser.getSuggestionAtCaretPosition(code, position);
5257
let allEntities = null;
5358
if (suggestions?.syntax?.length) {
5459
allEntities = this.parser.getAllEntities(code, position);
5560
}
56-
return Promise.resolve([suggestions, allEntities]);
61+
const semanticContext = this.parser.getSemanticContextAtCaretPosition(code, position);
62+
63+
return Promise.resolve({
64+
suggestions,
65+
allEntities,
66+
context: semanticContext
67+
});
5768
}
58-
return Promise.resolve([null, null]);
69+
70+
return Promise.resolve({
71+
suggestions: null,
72+
allEntities: null,
73+
context: null
74+
});
5975
}
6076

6177
async getAllEntities(code: string, position?: Position): Promise<EntityContext[] | null> {

src/languageFeatures.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
Range,
1616
Uri
1717
} from './fillers/monaco-editor-core';
18-
import type { LanguageServiceDefaults } from './monaco.contribution';
18+
import type { CompletionSnippet, LanguageServiceDefaults } from './monaco.contribution';
1919

2020
export interface WorkerAccessor<T extends BaseSQLWorker> {
2121
(...uris: Uri[]): Promise<T>;
@@ -163,13 +163,22 @@ export class CompletionAdapter<T extends BaseSQLWorker>
163163
}
164164
return worker.doCompletionWithEntities(code, position);
165165
})
166-
.then(([suggestions, allEntities]) => {
166+
.then(({ suggestions, allEntities, context: semanticContext }) => {
167+
let snippets: CompletionSnippet[] = [];
168+
if (semanticContext?.isStatementBeginning) {
169+
snippets = this._defaults.completionSnippets.map((item) => ({
170+
...item,
171+
insertText: typeof item.body === 'string' ? item.body : item.body.join('\n')
172+
}));
173+
}
174+
167175
return this._defaults.completionService(
168176
model,
169177
position,
170178
context,
171179
suggestions,
172-
allEntities
180+
allEntities,
181+
snippets
173182
);
174183
})
175184
.then((completions) => {

0 commit comments

Comments
 (0)