我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:修能
这是一段平平无奇的 SQL 语法- SELECT id, sum(name) FROM student GROUP BY id ORDER BY id;
复制代码 如果把这段代码放到 monaco-editor(@0.49.0) 中,一切也显得非常普通。- monaco.editor.create(ref.current!, {
- value: 'SELECT id, sum(name) FROM student GROUP BY id ORDER BY id;',
- language: "SparkSQL",
- });
复制代码 效果如下:
接下来我们通过 monaco-editor 提供的一些 Language Services 来针对 SparkSQL 的语言进行优化。
本文旨在提供相关思路以及 Demo,不可将相关代码用于生产环境
高亮
- const regex1 = /.../;
- const regex2 = /.../;
- const regex3 = /.../;
- const regex4 = /.../;
- // Register a new language
- monaco.languages.register({ id: "SparkSQL" });
- // Register a tokens provider for the language
- monaco.languages.setMonarchTokensProvider("SparkSQL", {
- tokenizer: {
- root: [
- [regex1, "keyword"],
- [regex2, "comment"],
- [regex3, "function"],
- [regex4, "string"],
- ],
- },
- });
- // Define a new theme that contains only rules that match this language
- monaco.editor.defineTheme("myCoolTheme", {
- base: "vs",
- inherit: false,
- rules: [
- { token: "keyword", foreground: "#0000ff" },
- { token: "function", foreground: "#795e26" },
- { token: "comment", foreground: "#008000" },
- { token: "string", foreground: "#a31515" },
- ],
- colors: {
- "editor.foreground": "#001080",
- },
- });
复制代码 不知道各位有没有疑惑,为什么 monaco-editor 的高亮和 VSCode 的高亮不太一样?
为什么使用 Monarch 而不是 textmate 的原因?
折叠
通过 registerFoldingRangeProvider可以自定义实现一些折叠代码块的逻辑- monaco.languages.registerFoldingRangeProvider("SparkSQL", {
- provideFoldingRanges: function (model) {
- const ranges: monaco.languages.FoldingRange[] = [];
- for (let i = 0; i < model.getLineCount(); ) {
- const lineContent = model.getLineContent(i + 1);
- const isValidLine = (content: string) =>
- content && !content.trim().startsWith("--");
- // 整段折叠
- if (isValidLine(lineContent) && !isValidLine(model.getLineContent(i))) {
- const start = i + 1;
- let end = start;
- while (end < model.getLineCount() && model.getLineContent(end + 1)) {
- end++;
- }
- if (end <= model.getLineCount()) {
- ranges.push({
- start: start,
- end: end,
- kind: monaco.languages.FoldingRangeKind.Region,
- });
- }
- }
- i++;
- }
- return ranges;
- },
- });
复制代码 悬浮提示
通过 registerHoverProvider实现悬浮后提示相关信息- monaco.languages.registerCompletionItemProvider("SparkSQL", {
- triggerCharacters: ["."],
- provideCompletionItems: function (model, position) {
- const word = model.getWordUntilPosition(position);
- const range: monaco.IRange = {
- startLineNumber: position.lineNumber,
- endLineNumber: position.lineNumber,
- startColumn: word.startColumn,
- endColumn: word.endColumn,
- };
- const offset = model.getOffsetAt(position);
- const prevIdentifier = model.getWordAtPosition(
- model.getPositionAt(offset - 1)
- );
- if (prevIdentifier?.word) {
- const regex = createRegExp(
- exactly("CREATE TABLE ")
- .and(exactly(`${prevIdentifier.word} `))
- .and(exactly("("))
- .and(oneOrMore(char).groupedAs("columns"))
- .and(exactly(")"))
- );
- const match = model.getValue().match(regex);
- if (match && match.groups.columns) {
- const columns = match.groups.columns;
- return {
- suggestions: columns.split(",").map((item) => {
- const [columnName, columnType] = item.trim().split(" ");
- return {
- label: `${columnName.trim()}(${columnType.trim()})`,
- kind: monaco.languages.CompletionItemKind.Field,
- documentation: `${columnName.trim()} ${columnType.trim()}`,
- insertText: columnName.trim(),
- range: range,
- };
- }),
- };
- }
- }
- return {
- suggestions: createDependencyProposals(range),
- };
- },
- });
复制代码 内嵌提示
通过 registerInlayHintsProvider可以实现插入提示代码- import * as monaco from "monaco-editor";
- monaco.languages.registerHoverProvider("SparkSQL", {
- provideHover: function (model, position) {
- const word = model.getWordAtPosition(position);
- if (!word) return null;
- const fullText = model.getValue();
- const offset = fullText.indexOf(`CREATE TABLE ${word.word}`);
- if (offset !== -1) {
- const lineNumber = model.getPositionAt(offset);
- const lineContent = model.getLineContent(lineNumber.lineNumber);
- return {
- range: new monaco.Range(
- position.lineNumber,
- word.startColumn,
- position.lineNumber,
- word.endColumn
- ),
- contents: [
- {
- value: lineContent,
- },
- ],
- };
- }
- },
- });
复制代码PS:需要配合 Markers 一起才能显示其效果
- monaco.languages.registerInlayHintsProvider("SparkSQL", {
- provideInlayHints(model, range) {
- const hints: monaco.languages.InlayHint[] = [];
- for (let i = range.startLineNumber; i <= range.endLineNumber; i++) {
- const lineContent = model.getLineContent(i);
- if (lineContent.includes("sum")) {
- hints.push({
- label: "expr: ",
- position: {
- lineNumber: i,
- column: lineContent.indexOf("sum") + 5,
- },
- kind: monaco.languages.InlayHintKind.Parameter,
- });
- }
- }
- return {
- hints: hints,
- dispose: function () {},
- };
- },
- });
复制代码 超链接
众所周知,在 monaco-editor 中,如果一段文本能匹配 http(s?):的话,会自动加上超链接的标识。而通过 registerLinkProvider这个 API,我们可以自定义一些文案进行超链接的跳跃。- monaco.languages.registerDefinitionProvider("SparkSQL", {
- provideDefinition: function (model, position) {
- const lineContent = model.getLineContent(position.lineNumber);
- if (lineContent.startsWith("--")) return null;
- const word = model.getWordAtPosition(position);
- const fullText = model.getValue();
- const offset = fullText.indexOf(`CREATE TABLE ${word?.word}`);
- if (offset !== -1) {
- const pos = model.getPositionAt(offset + 13);
- return {
- uri: model.uri,
- range: new monaco.Range(
- pos.lineNumber,
- pos.column,
- pos.lineNumber,
- pos.column + word!.word.length
- ),
- };
- }
- },
- });
复制代码 格式化
通过registerDocumentFormattingEditProviderAPI 可以实现文档格式化的功能。- monaco.languages.registerReferenceProvider("SparkSQL", {
- provideReferences: function (model, position) {
- const lineContent = model.getLineContent(position.lineNumber);
- if (!lineContent.startsWith("CREATE TABLE")) return null;
- const word = model.getWordAtPosition(position);
- if (word?.word) {
- const regex = createRegExp(
- exactly("SELECT").and(oneOrMore(char)).and(`FROM student`),
- ["g"]
- );
- const fullText = model.getValue();
- const array1: monaco.languages.Location[] = [];
- while (regex.exec(fullText) !== null) {
- console.log("regex:", regex.lastIndex);
- const pos = model.getPositionAt(regex.lastIndex);
- array1.push({
- uri: model.uri,
- range: new monaco.Range(
- pos.lineNumber,
- model.getLineMinColumn(pos.lineNumber),
- pos.lineNumber,
- model.getLineMaxColumn(pos.lineNumber)
- ),
- });
- }
- if (array1.length) return array1;
- }
- return null;
- },
- });
复制代码 其他
除了上述提到的这些 Language Services 的功能以外,还有很多其他的语言服务功能可以实现。这里只是抛砖引玉来提到一些 API,还有一些 API 可以关注 monaco-editor 的官方文档 API。
最后
欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star
- 大数据分布式任务调度系统——Taier
- 轻量级的 Web IDE UI 框架——Molecule
- 针对大数据领域的 SQL Parser 项目——dt-sql-parser
- 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
- 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
- 一个针对 antd 的组件测试工具库——ant-design-testing
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |