• 现一个智能的SQL编辑器


    补给资料    管注公众号:码农补给站     

    前言

    目前我司的多个产品中都支持在线编辑 SQL 来生成对应的任务。为了优化用户体验,在使用 MonacoEditor 为编辑器的基础上,我们还支持了如下几个重要功能:

    • 多种 SQL 的语法高亮
    • 多种 SQL 的报错提示(错误位置飘红)
    • 多种 SQL 的自动补全(智能提示)

    本文旨在讲解上述功能的实现思路,对于技术细节,由于篇幅原因不会阐述的太详细。

    Monaco Languages

    Monaco Editor 内置的 languages

    Monaco Editor 内置了相当多的 languages,比如 javaScriptCSSShell 等。

    Monaco Editor 依赖包的 ESM 入口文件为 ./esm/vs/editor/editor.main.ts

    而在入口文件中,Monaco Editor 引入了所有内置的 Languages。

    这里 languages 文件可以分为两类,一类是../language文件夹下的,支持自动补全和飘红提示等高级功能;另一类则是../basic-languages文件夹下的,只支持一些基本功能。

    使用内置的 Language 功能

    以使用 typescript language 为例:

     
    
    1. import { editor } from 'monaco-editor';
    2. const container = document.getElementById('container');
    3. editor.create(container, {
    4. language: 'typescript'
    5. })

    此时我们会发现,编辑器已经有语法高亮的功能了,但是浏览器控制台会抛异常,另外也没有自动补全功能和飘红提示功能,

    这其实是因为,Monaco Editor 无法加载到 language 对应的 worker,对应的解决办法看这里: Monaco integrate-esm

    这里我们使用 Using plain webpack的方式,首先将对应的 worker 文件设置为 webpack entry:

     
    
    1. module.exports = {
    2. entry: {
    3. index: path.resolve( __dirname, './src/index.ts'),
    4. 'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
    5. 'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker.js'
    6. },
    7. }

    另外还需要设置 Monaco Editor 的全局环境变量,这主要是为了告诉 Monaco Editor 对应的 worker 文件的路径

     
    
    1. import { editor } from 'monaco-editor';
    2. (window as any).MonacoEnvironment = {
    3. getWorkerUrl: function (_moduleId, label) {
    4. switch (label) {
    5. case 'flink': {
    6. return './flink.worker.js';
    7. }
    8. case 'typescript': {
    9. return './ts.worker.js'
    10. }
    11. default: {
    12. return './editor.worker.js';
    13. }
    14. }
    15. }
    16. };
    17. const container = document.getElementById('container');
    18. editor.create(container, {
    19. language: 'typescript'
    20. })

    这样一个具有语法高亮自动补全飘红提示 功能的 typescript 编辑器就设置好了

    小结分析

    首先上文中提到了当我们直接从 Monaco Editor 的入口文件中导入时,会自动的引入所有内置的 Languages,但是实际上这其中绝大都是我们不需要的,而由于其导入方式,很显然我们不需要的 languages 也无法被 treeShaking。要解决这个问题我们可以选择从 monaco-editor/esm/vs/editor/editor.api 文件中导入Monaco Editor 核心 API,然后通过 monaco-editor-webpack-plugin 来按需导入所需要的功能。另外这个插件也可以自动处理Monaco Editor 内置的 worker 文件的打包问题,以及自动注入 MonacoEnvironment全局环境变量。

    自定义 Language

    注册Language

    Monaco Editor 提供了 monaco.languages.register方法,用来自定义 language

     
    
    1. /**
    2. * Register information about a new language.
    3. */
    4. export function register(language: ILanguageExtensionPoint): void;
    5. export interface ILanguageExtensionPoint {
    6. id: string;
    7. extensions?: string[];
    8. filenames?: string[];
    9. filenamePatterns?: string[];
    10. firstLine?: string;
    11. aliases?: string[];
    12. mimetypes?: string[];
    13. configuration?: Uri;
    14. }

    第一步,我们需要注册一个 language, 配置项中 id 对应的就是语言名称(其他配置项可以暂时不填),这里自定义的 language 名为 myLang

     
    
    1. import { editor, languages } from 'monaco-editor';
    2. languages.register({
    3. id: "myLang"
    4. });
    5. const container = document.getElementById('container');
    6. editor.create(container, {
    7. language: 'myLang'
    8. })

    此时可以发现,页面上的编辑器没有任何其他附加功能,就是普通的文本编辑器

    设置 Language

    通过 monaco.languages.setLanguageConfiguration,可以对 language 进行配置

     
    
    1. /**
    2. * Set the editing configuration for a language.
    3. */
    4. export function setLanguageConfiguration(
    5. languageId: string,
    6. configuration: LanguageConfiguration
    7. ): IDisposable;
    8. /**
    9. * The language configuration interface defines the contract between extensions and
    10. * various editor features, like automatic bracket insertion, automatic indentation etc.
    11. */
    12. export interface LanguageConfiguration {
    13. comments?: CommentRule;
    14. brackets?: CharacterPair[];
    15. wordPattern?: RegExp;
    16. indentationRules?: IndentationRule;
    17. onEnterRules?: OnEnterRule[];
    18. autoClosingPairs?: IAutoClosingPairConditional[];
    19. surroundingPairs?: IAutoClosingPair[];
    20. colorizedBracketPairs?: CharacterPair[];
    21. autoCloseBefore?: string;
    22. folding?: FoldingRules;
    23. }

    这些配置会影响 Monaco Editor 的一些默认行为,比如设置 autoClosingPairs中有一项为一对圆括号,那么当输入左圆括号后,会自动补全右圆括号。

     
    
    1. import { languages } from "monaco-editor";
    2. const conf: languages.LanguageConfiguration = {
    3. comments: {
    4. lineComment: "--",
    5. blockComment: ["/*", "*/"],
    6. },
    7. brackets: [
    8. ["(", ")"],
    9. ],
    10. autoClosingPairs: [
    11. { open: "(", close: ")" },
    12. { open: '"', close: '"' },
    13. { open: "'", close: "'" },
    14. ],
    15. surroundingPairs: [
    16. { open: "(", close: ")" },
    17. { open: '"', close: '"' },
    18. { open: "'", close: "'" },
    19. ],
    20. };
    21. languages.setLanguageConfiguration('myLang', conf)

    高亮功能

    Monarch

    Moanco Editor 内置了 Monarch,用于实现语法高亮功能,它本质上是一个有限状态机,我们可以通过JSON的形式来配置其状态流转逻辑,并通过monaco.languages.setMonarchTokensProvider API 应用该配置。关于Monarch 的具体用法可以看一下这篇文章 以及 Monarch Document

    配置中最重要的是 tokenizer属性,意思是分词器,分词器会自动对编辑器内部的文本进行分词处理,每个分词器都有一个 root state,在 root state 中可以有多条规则,规则内部可以引用其他 state。

    下面是一个简单的配置示例

     
    
    1. import { languages } from "monaco-editor";
    2. export const language: languages.IMonarchLanguage = {
    3. ignoreCase: true,
    4. tokenizer: {
    5. root: [
    6. { include: '@comments' }, // 引用下面的 comments 规则
    7. { include: '@whitespace' }, // 引用下面的 whiteSpace 规则
    8. { include: '@strings' },// 引用下面的 strings 规则
    9. ],
    10. whitespace: [[/\s+/, 'white']],
    11. comments: [
    12. [/--+.*/, 'comment'],
    13. [//*/, { token: 'comment.quote', next: '@comment' }]
    14. ],
    15. comment: [
    16. [/[^*/]+/, 'comment'],
    17. [/*//, { token: 'comment.quote', next: '@pop' }],
    18. [/./, 'comment']
    19. ],
    20. strings: [
    21. [/'/, { token: 'string', next: '@string' }]
    22. ],
    23. string: [
    24. [/[^']+/, 'string'],
    25. [/''/, 'string'],
    26. [/'/, { token: 'string', next: '@pop' }]
    27. ],
    28. }
    29. };
    30. languages.setMonarchTokensProvider("myLang", language);

    上面的配置中 root 下面有三条规则分别匹配 注释(comments)字符串(strings) 以及空白字符(whiteSpace), 每条规则可以大体分为两部分:

    • 匹配方式,比如说正则
    • 对应的 token 类型(任意字符串)

    比如上述配置中 tokenizer.comments 规则

     
    
    1. comments: [
    2. [/--+.*/, 'comment'], // 左边是正则表达式用来匹配文本,右边是该规则对应的 token 名称
    3. [//*/, { token: 'comment.quote', next: '@comment' }] // 左边是正则表达式用来匹配文本,右边显示声明对应的 token 名称
    4. ],

    配置了如上 Monarch 之后,在编辑器内部输入注释或者字符串,那么Monaco editor 就会根据输入的内容进行分词处理

    可以看到目前字符串和注释已经被高亮了。这里有一个新的问题,不同类型的分词的颜色是怎么设置的

    Monaco Theme

    从上图中右侧的 Elements 面板中可以看到,不同类型的分词,对应的标签的 className 不同,它们是由 Monarch 配置中的 token 映射而来的。MonacoEditor 内置了一些 Theme,默认的 Theme 是 vs,而默认的 theme 中已经设置了上述 Monarch 中的 token 对应的颜色,所以我们应用上述配置后,对应的分词直接就有了高亮颜色。

    我们可以通过 monaco.editor.defineTheme 来定义一种新的 theme,如下例所示:

     
    
    1. editor.defineTheme('myTheme', {
    2. base: 'vs',
    3. inherit: true,
    4. rules: [
    5. { token: 'comment', foreground: 'ff4400' },
    6. { token: 'string', foreground: '0000ff' }
    7. ],
    8. colors: {
    9. },
    10. });
    11. // xxxx
    12. editor.create(container, {
    13. language: "myLang",
    14. theme: "myTheme"
    15. });

    这里将注释设置为红色,字符串设置为蓝色,显示效果如下图所示

    飘红提示

    飘红提示的功能就是在代码错误的位置打上标记(一般是红色波浪线),可以通过 monaco.editor.setModelMarkers API 来实现。比如我们想为 第1行的第1个字符到第2行的第2个字符 之间打上错误标记:

     
    
    1. const editorIns = editor.create(container, {
    2. language: "myLang",
    3. theme: "myTheme",
    4. value: `hello
    5. world`
    6. });
    7. const model = editorIns.getModel();
    8. editor.setModelMarkers(model, 'myLang', [
    9. {
    10. startLineNumber: 1,
    11. startColumn: 1,
    12. endLineNumber: 2,
    13. endColumn: 2,
    14. message: "语法错误",
    15. severity: MarkerSeverity.Error
    16. }
    17. ])

    severity 是标记类型,message 是提示信息,效果如下所示。

    到此为止,实现了飘红的功能,但是没有实现在语法错误处飘红的功能,这需要额外的语法解析器支持,会在下文中讲到。

    自动补全功能

    Monaco Editor 提供了 monaco.languages.registerCompletionItemProvider API 来实现自动补全功能

     
    
    1. import { editor, languages, MarkerSeverity, Position, CancellationToken, Range } from "monaco-editor";
    2. languages.registerCompletionItemProvider('myLang', {
    3. triggerCharacters: ['.', '*'],
    4. provideCompletionItems(
    5. model: editor.IReadOnlyModel,
    6. position: Position,
    7. context: languages.CompletionContext,
    8. token: CancellationToken
    9. ){
    10. const wordInfo = model.getWordUntilPosition(position);
    11. const wordRange = new Range(
    12. position.lineNumber,
    13. wordInfo.startColumn,
    14. position.lineNumber,
    15. wordInfo.endColumn
    16. );
    17. return new Promise((resolve) => {
    18. resolve({
    19. suggestions: [
    20. {
    21. label: "SELECT",
    22. kind: languages.CompletionItemKind.Keyword,
    23. insertText: "SELECT",
    24. range: wordRange,
    25. detail: '关键字',
    26. },
    27. {
    28. label: "SET",
    29. kind: languages.CompletionItemKind.Keyword,
    30. insertText: "SET",
    31. range: wordRange,
    32. detail: '关键字',
    33. },
    34. {
    35. label: "SHOW",
    36. kind: languages.CompletionItemKind.Keyword,
    37. insertText: "SHOW",
    38. range: wordRange,
    39. detail: '关键字',
    40. },
    41. ]
    42. })
    43. })
    44. }
    45. })

    registerCompletionItemProvider 接受两个参数,第一个参数是 languageId 也就是 language 名称,

    第二个参数是一个 CompletionItemProviderCompletionItemProvidertriggerCharacters用来配置触发自动补全的字符有哪些,而 provideCompletionItems则是一个函数,它接收 Monaco Editor 提供的当前的上下文信息,返回自动补全项列表。如上例中返回了三个自动补全项,那么当我们在编辑器中输入 S时,就会出现配置的自动补全项候选菜单。

    通过这个 API 我们可以实现一种语言的关键字自动补全,只需要在CompletionItemProvider中返回该语言所有的关键字对应的自动补全项即可。

    但是registerCompletionItemProvider目前做不到根据语义进行自动补全。

    比如用户写一段 flinkSQL,当用户输入完 CREATE 关键字并按下空格后,应该出现的自动补全项应该是只有TABLECATALOGDATABASEFUNCTIONVIEW

    再比如当用户输入 SELECT * FROM 时,后面应该提示表名而不是其他无关的关键字。与上文中的飘红提示一样,这些语义信息需要单独的语法解析器来分析。

    小结分析

    到此为止,在自定义 language 这一节中,我们已经了解了,在 Monaco Editor 中如何实现自定义语言的 语法高亮错误处飘红提示自动补全

    在数栈产品中,本节讲到的功能都通过引入 monaco-sql-languages 依赖来实现,这是我们数栈 UED 团队自研的开源项目,目前已经支持多种 SQL Languages。

    由于目前为止没有实现自定义 language 的语义分析功能,导致目前实现的编辑器不够智能。 另外,对于第一节中提到的 web worker ,在第二节中也没有有提到,实际上 Monaco Editor 自带的 web worker,也都是为了实现 language 的语义分析功能,下一节将阐述这一部分内容。

    SQL Parser

    要实现语义分析功能,很显然我们需要一个语法解析器。除了基本的语法解析的基础功能以外,我们还需要

    • 语法错误收集,收集编辑器中文本的语法错误信息,用于错误飘红提示功能。
    • 推断文本中指定位置的候选项列表,对于编辑器来说,指定位置一般就是光标所在位置。候选项是指在光标所在的位置应该要写什么。比如 SQL 中 SELECT 关键字后面可以跟字段或者函数,那么我们所要实现的 sql parser 就应该提示出在 SELECT 关键字后面的候选项应该是字段或者函数。

    实现基础的 SQL Parser

    Antlr4 语法文件

    我们使用 Antlr4 来实现一个基本的 SQL Parser。Antlr4 是一个强大的解析器生成器,它能根据用户自定义的语法文件来生成对应的解析器。Antlr4 的语法文件为 .g4文件,内部可以包含多条规则,规则可以分为词法规则和语法规则,词法规则用于生成词法分析器,语法规则用于生成语法解析器。

    例,我们现在写一份语法规则,匹配最简单的 SELECT 语句(不包括子查询、别名等规则),比如

     
    
    1. SELECT * FROM table1; -- eg1
    2. SELECT table2.name, age FROM schema2.table2; -- eg2

    那么在antlr4中这份语法文件应该这样写:

     
    
    1. grammar SelectStatement;
    2. /** 语法规则 begin */
    3. program: selectStatement? EOF;
    4. // 声明 语句的匹配规则
    5. selectStatement: KW_SELECT columnGroup KW_FROM tablePath SEMICOLON?;
    6. // 声明 语句中字段部分的匹配规则,字段部分可能为 col1, col2 的形式
    7. columnGroup: columnPath (COMMA columnPath)*;
    8. // 声明 字段名匹配规则,字段名有可能为 db.table.col 或者 * 的形式
    9. columnPath: dot_id | OP_STAR;
    10. // 声明 表名匹配规则,表名有可能为 db.table 的形式
    11. tablePath: dot_id;
    12. // 匹配 id.id 形式的标识符号
    13. dot_id: IDENTIFIER_LITERAL (DOT IDENTIFIER_LITERAL)*;
    14. /** 语法规则 end */
    15. /** 词法规则 begin */
    16. KW_SELECT: 'SELECT'; // 匹配 SELECT 关键字
    17. KW_FROM: 'FROM'; // 匹配 FROM 关键字
    18. OP_STAR: '*'; // 匹配 *
    19. DOT: '.'; // 匹配 .
    20. COMMA: ','; // 匹配 ,
    21. SEMICOLON: ';'; // 匹配 ;
    22. IDENTIFIER_LITERAL: [A-Z_a-z][A-Z_0-9a-z]*; // 匹配标识符
    23. WS: [ \t\n\r]+ -> skip ; // 忽略空格换行等空白字符
    24. /** 词法规则 end */

    语法规则的编写格式类似于 EBNF

    然后运行 antlr4 命令,根据所写的语法文件生成对应的解析器。可以直接使用官方文档中提供的方式 antlr4 typescript-target doc ,或者直接使用社区提供的 antlr4ts 包,这里以使用 antlr4ts 为例。

    生成的文件结果如下所示:

    使用 Antlr4 生成的 Parser

    在使用Antlr4 的生成的 Parser 之前我们需要安装,Antlr4 的运行时包。你可以将 Antlr4 的运行时包通过语法文件生成的parser文件之间的关系,类比为 react 和 react-dom之间的关系。这里以使用 antlr4ts 为运行时

     
     
    1. import { CommonTokenStream, CharStreams } from 'antlr4ts';
    2. import { SelectStatementLexer } from '../lib/selectStatement/SelectStatementLexer';
    3. import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';
    4. class SelectParser {
    5. private createLexer(input: string) {
    6. const inputStream = CharStreams.fromString(input);
    7. const lexer = new SelectStatementLexer(inputStream);
    8. return lexer
    9. }
    10. private createParser (input: string) {
    11. const lexer = this.createLexer(input);
    12. const tokens = new CommonTokenStream(lexer);
    13. const parser = new SelectStatementParser(tokens);
    14. return parser
    15. }
    16. parse (sql: string) {
    17. const parser = this.createParser(sql)
    18. const parseTree = parser.selectStatement();
    19. return parseTree;
    20. }
    21. }
    22. // 试一下效果
    23. const selectParser = new SelectParser();
    24. const parseTree = selectParser.parse('SELECT * FROM table1');

    获取文本中的错误信息

    当解析一个含有错误的文本时,Antlr4 会输出错误信息,例如输入

     
    
    selectParser.parse('SELECT id FRO');
    

    控制台打印

    可以看到错误信息中包含了文本中的错误所处的位置,我们可以通过使用 Antlr4 ParserErrorListener 来获取错误信息。

    声明一个 ParserErrorListener

     
    
    1. import { ParserErrorListener } from 'antlr4ts';
    2. export class SelectErrorListener implements ParserErrorListener {
    3. private _parserErrorSet: Set<any> = new Set();
    4. syntaxError(_rec, _ofSym, line, charPosInLine, msg) {
    5. let endCol = charPosInLine + 1;
    6. this._parserErrorSet.add({
    7. startLine: line,
    8. endLine: line,
    9. startCol: charPosInLine,
    10. endCol: endCol,
    11. message: msg,
    12. })
    13. }
    14. clear () {
    15. this._parserErrorSet.clear();
    16. }
    17. get parserErrors () {
    18. return Array.from(this._parserErrorSet)
    19. }
    20. }

    使用 ParserErrorListener 收集错误信息

     
    
    1. import { CommonTokenStream, CharStreams } from 'antlr4ts';
    2. import { SelectStatementLexer } from '../lib/selectStatement/SelectStatementLexer';
    3. import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';
    4. class SelectParser {
    5. private _errorListener = new SelectErrorListener();
    6. createLexer(input: string) {
    7. const inputStream = CharStreams.fromString(input);
    8. const lexer = new SelectStatementLexer(inputStream);
    9. this._errorListener.clear();
    10. lexer.removeErrorListeners(); // 移除 Antlr4 内置的 ErrorListener
    11. lexer.addErrorListener(this._errorListener)
    12. return lexer
    13. }
    14. createParser (input: string) {
    15. const lexer = this.createLexer(input);
    16. const tokens = new CommonTokenStream(lexer);
    17. const parser = new SelectStatementParser(tokens);
    18. parser.removeErrorListeners(); // 移除 Antlr4 内置的 ErrorListener
    19. parser.addErrorListener(this._errorListener);
    20. return parser
    21. }
    22. parse (sql: string) {
    23. const parser = this.createParser(sql)
    24. const parseTree = parser.selectStatement();
    25. console.log(this._errorListener.parserErrors);
    26. return {
    27. parseTree,
    28. errors: this._errorListener.parserErrors,
    29. };
    30. }
    31. }
    32. // 试一下效果
    33. const selectParser = new SelectParser();
    34. const { errors } = selectParser.parse('SELECT id FRO');
    35. console.log(errors);

    打印结果

    这样我们就获取到了文本中的语法错误出现的位置,以及错误信息。

    到此为止上文中遗留的第一个问题就已经差不多解决了,我们只需要在合适的时机将编辑器的内容进行解析,拿到错误信息并且通过 editor.setModelMarkers这个 API 让错误的位置飘红就大功告成了。

    自动补全功能

    对于自动补全功能,Antlr4 并没有直接提供,但是社区已经有了比较优秀的解决方案 - antlr-c3 。它的作用是根据Antlr4 Parser 的解析结果,分析指定位置填哪些词法/语法规则是合法的

    antlr4-c3 的使用方式比较简单。

     
    
    1. import { CodeCompletionCore } from "antlr4-c3";
    2. // 这里 parser 是 parser 实例
    3. let core = new CodeCompletionCore(parser);
    4. // tokenIndex 是想要自动补全的位置,对应由编辑器的光标位置转换而来
    5. // parserContext 则是解析完之后的返回的 ParserTree 或者 ParserTree 的子节点(传入子节点可以更高效)
    6. let candidates = core.collectCandidates(tokenIndex, parserContext);

    那么结合上文中写的 SelectParser,代码应该是这样

     
    
    1. import { CodeCompletionCore } from "antlr4-c3";
    2. import { SelectParser } from "./selectParser";
    3. /**
    4. * input 源文本
    5. * caretPosition 编辑器光标位置
    6. */
    7. function getSuggestions(input: string, caretPosition) {
    8. const selectParser = new SelectParser();
    9. const parserIns = selectParser.createParser(input)
    10. let core = new CodeCompletionCore(parserIns);
    11. const parserContext = parserIns.selectStatement();
    12. // 伪代码
    13. const tokenIndex = caretPosition2TokenIndex(caretPosition)
    14. let candidates = core.collectCandidates(tokenIndex, parserContext);
    15. }

    core.collectCandidates 的返回值的数据类型如下:

     
    
    1. interface CandidatesCollection {
    2. tokens: Map<number, TokenList>;
    3. rules: Map<number, CandidateRule>;
    4. }

    tokens 对应的是词法规则提示,比如关键字等,rules 对应的是语法规则,比如上述语法文件中的 columnPathtablePath等。

    需要注意的是,antlr4-c3 默认不收集语法规则,需要我们手动设置需要收集的语法规则

     
    
    1. import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';
    2. let core = new CodeCompletionCore(parserIns);
    3. core.preferredRules= new Set([
    4. SelectStatementParser.RULE_tablePath,
    5. SelectStatementParser.RULE_columnPath
    6. ])
    7. // 设置需要收集 tablePath 和 columnPath

    这样我们就收集到了在指定位置的可以填什么。接下来我们需要将结果进行转换成我们需要的数据结果

     
    
    1. import { CodeCompletionCore } from "antlr4-c3";
    2. import { SelectParser } from "./selectParser";
    3. import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';
    4. /**
    5. * input 源文本
    6. * caretPosition 编辑器光标位置
    7. */
    8. export function getSuggestions(input: string, caretPosition?: any) {
    9. const selectParser = new SelectParser();
    10. const parserIns = selectParser.createParser(input)
    11. let core = new CodeCompletionCore(parserIns);
    12. core.preferredRules= new Set([
    13. SelectStatementParser.RULE_tablePath,
    14. SelectStatementParser.RULE_columnPath
    15. ])
    16. const parserContext = parserIns.selectStatement();
    17. const tokenIndex = caretPosition2TokenIndex(caretPosition);
    18. let candidates = core.collectCandidates(tokenIndex, parserContext);
    19. const rule = [];
    20. const keywords = []
    21. for (let candidate of candidates.rules) {
    22. const [ruleType] = candidate;
    23. let syntaxContextType;
    24. switch (ruleType) {
    25. case SelectStatementParser.RULE_tablePath: {
    26. syntaxContextType = 'table';
    27. break;
    28. }
    29. case SelectStatementParser.RULE_columnPath: {
    30. syntaxContextType = 'column';
    31. break;
    32. }
    33. default:
    34. break;
    35. }
    36. if (syntaxContextType) {
    37. rule.push(syntaxContextType)
    38. }
    39. }
    40. for (let candidate of candidates.tokens) {
    41. const symbolicName = parserIns.vocabulary.getSymbolicName(candidate[0]);
    42. const displayName = parserIns.vocabulary.getDisplayName(candidate[0]);
    43. if(symbolicName && symbolicName.startsWith('KW_')) {
    44. const keyword = displayName.startsWith("'") && displayName.endsWith("'")
    45. ? displayName.slice(1, -1)
    46. : displayName
    47. keywords.push(keyword);
    48. }
    49. }
    50. console.log('===== suggest keywords: ',keywords);
    51. console.log('===== suggest rules:', rule);
    52. }

    这样我们就拿到了要提示的关键字和语法规则。关键字可以直接用于生成自动补全项,语法规则可以用于提示表名、字段名等。

    小结分析

    在这一节中,我们已经了解了,如何使用 Antlr4 和 antlr4-c3 来实现更加智能的飘红提示以及自动补全功能。

    这一部分功能,在 monaco-sql-languages 中通过引入数栈前端团队自研的开源项目 dt-sql-parser 实现。

    前文中提到的 worker 文件也正是用于运行 sql parser,因为dt-sql-parser 的解析可能会比较耗时,为了避免阻塞用户交互,将 sql parser 放到 web worker 中运行显然是更明智的选择。


    原文链接:https://juejin.cn/post/7297917491794984972
     

  • 相关阅读:
    学校图书馆管理系统
    简易Tomcat服务器
    框架外的PHP读取.env文件(php5.6、7.3可用版)
    零食食品经营小程序商城的作用是什么
    如何设置cpolar开机自启动(Linux版)
    【VMware虚拟机中ubuntu系列】—— 在虚拟机中使用本机摄像头的详细教程与常见问题分析及解决
    Python当前进程信息 (os包)分享
    30 位学者合力发表 Nature 综述,10 年回顾解构 AI 如何重塑科研范式
    Web基础与HTTP协议
    SpringCloud-微服务CAP原则
  • 原文地址:https://blog.csdn.net/2301_79105024/article/details/134267577