monaco-editor 的 Language Services
我们是袋鼠云数栈 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;
},
});
PS:如果不设置的话,monaco-editor 会根据缩紧注册默认的折叠块逻辑
补全
通过 registerCompletionItemProvider
可以实现自定义补全代码
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),
};
},
});
悬浮提示
通过 registerHoverProvider
实现悬浮后提示相关信息
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,
},
],
};
}
},
});
内嵌提示
通过 registerInlayHintsProvider
可以实现插入提示代码
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 () {},
};
},
});
跳转定义/引用
跳转定义/引用是一对相辅相成的 API。如果实现了跳转定义而不实现跳转引用,会让用户感到困惑。
这里我们分别registerDefinitionProvider
和 registerReferenceProvider
两个 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
),
};
}
},
});
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;
},
});
CodeAction
可以基于 CodeAction 实现如快速修复等功能。
monaco.languages.registerCodeActionProvider("SparkSQL", {
provideCodeActions: function (model, range, context) {
const actions: monaco.languages.CodeAction[] = [];
const diagnostics = context.markers;
diagnostics.forEach((marker) => {
if (marker.code === "no-function") {
actions.push({
title: "Correct function",
diagnostics: [marker],
kind: "quickfix",
edit: {
edits: [
{
resource: model.uri,
textEdit: {
range: marker,
text: "sum",
},
versionId: model.getVersionId(),
},
],
},
isPreferred: true,
});
}
});
return {
actions: actions,
dispose: function () {},
};
},
});
PS:需要配合 Markers 一起才能显示其效果
instance.onDidChangeModelContent(() => {
setModelMarkers(instance.getModel());
});
超链接
众所周知,在 monaco-editor 中,如果一段文本能匹配 http(s?):
的话,会自动加上超链接的标识。而通过 registerLinkProvider
这个 API,我们可以自定义一些文案进行超链接的跳跃。
monaco.languages.registerLinkProvider("SparkSQL", {
provideLinks: function (model) {
const links: monaco.languages.ILink[] = [];
const lines = model.getLinesContent();
lines.forEach((line, lineIndex) => {
const idx = line.toLowerCase().indexOf("sum");
if (line.startsWith("--") && idx !== -1) {
links.push({
range: new monaco.Range(
lineIndex + 1,
idx + 1,
lineIndex + 1,
idx + 4
),
url: "https://spark.apache.org/docs/latest/api/sql/#sum",
});
}
});
return {
links: links,
};
},
});
格式化
通过registerDocumentFormattingEditProvider
API 可以实现文档格式化的功能。
import * as monaco from "monaco-editor";
monaco.languages.registerDocumentFormattingEditProvider("SparkSQL", {
provideDocumentFormattingEdits: function (model) {
const edits: monaco.languages.TextEdit[] = [];
const lines = model.getLinesContent();
lines.forEach((line, lineNumber) => {
const trimmedLine = line.trim();
if (trimmedLine.length > 0) {
const range = new monaco.Range(
lineNumber + 1,
1,
lineNumber + 1,
line.length + 1
);
edits.push({
range: range,
text: trimmedLine,
});
}
});
return edits;
},
});
其他
除了上述提到的这些 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
monaco-editor 的 Language Services的更多相关文章
- js 在浏览器中使用 monaco editor
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- Monaco Editor 使用入门
以前项目是用ace编辑器的,但是总有些不敬人意的地方.前端事件看见的VS Code编辑器Monaco Editor准备更换下,下面介绍一些使用中遇到的一点问题.代码提示 1.项目引用 import * ...
- Monaco Editor 中的 Keybinding 机制
一.前言 前段时间碰到了一个 Keybinding 相关的问题,于是探究了一番,首先大家可能会有两个问题:Monaco Editor 是啥?Keybinding 又是啥? Monaco Editor: ...
- monaco editor 实现自定义提示(sql为例)
monaco editor :https://www.cnblogs.com/XHappyness/p/9414177.html 这里实现自己定义的提示: .vue <template> ...
- Vue cli2.0 项目中使用Monaco Editor编辑器
monaco-editor 是微软出的一条开源web在线编辑器支持多种语言,代码高亮,代码提示等功能,与Visual Studio Code 功能几乎相同. 在项目中可能会用带代码编辑功能,或者展示代 ...
- 【软工】[技术博客] 用Monaco Editor打造接近vscode体验的浏览器IDE
[技术博客] 用Monaco Editor打造接近vscode体验的浏览器IDE 官方文档与重要参考资料 官方demo 官方API调用样例 Playground 官方API Doc,但其搜索框不支持模 ...
- 使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)
译文来源 欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScrip ...
- Asp.Net Core 使用Monaco Editor 实现代码编辑器
在项目中经常有代码在线编辑的需求,比如修改基于Xml的配置文件,编辑Json格式的测试数据等.我们可以使用微软开源的在线代码编辑器Monaco Editor实现这些功能.Monaco Editor是著 ...
- 手把手教你实现在Monaco Editor中使用VSCode主题
背景 笔者开源了一个小项目code-run,类似codepen的一个工具,其中代码编辑器使用的是微软的Monaco Editor,这个库是直接从VSCode的源码中生成的,只不过是做了一点修改让它支持 ...
- monaco editor + vue的配置
monaco editor是vscode的御用编辑器. 功能非常强大,使用方便轻巧,对js\ts等等语言支持都良好,能方便的扩展以支持其他语言或者自定义的特性. 夸了这么多,这里只说它一个问题: 这货 ...
随机推荐
- 为 Serverless Devs 插上 Terraform 的翅膀,实现企业级多环境部署(上)
简介: Serverless Devs 离不开对云资源的操作,但支持新资源时需要开发相应的组件代码:如果将环境模板的定义通过 Terraform IaC 来完成,在 Serverless Devs ...
- 10个Bug环环相扣,你能解开几个?
简介:由阿里云云效主办的2021年第3届83行代码挑战赛已经收官.超2万人围观,近4000人参赛,85个团队组团来战.大赛采用游戏闯关玩儿法,融合元宇宙科幻和剧本杀元素,让一众开发者玩得不亦乐乎. ...
- 技术干货 | 闲鱼:一个优秀的 Push 平台,需要经历怎样的前世今生
简介: mPaaS 消息推送服务,快速集成多家厂商 Push 通道,有效提高用户留存率,提升用户体验. 编者荐语: 点击这里,了解 mPaaS 消息推送服务,快速集成多家厂商 Push 通道,有效提 ...
- C#的基于.net framework的Dll模块编程(三) - 编程手把手系列文章
继续这个系列的博文: 一.设置DLL类库信息: 在接解决方案资源管理器中选择该Dll程序集项目,鼠标右键,选择属性,打开窗口. 点击"程序集信息",打开并编辑该Dll程序集的相关信 ...
- Raft 共识算法4-选举限制
Raft 共识算法4-选举限制 Raft算法中译版地址:https://object.redisant.com/doc/raft中译版-2023年4月23日.pdf 英原论文地址:https://ra ...
- Radius 现在是云原生计算基金会(CNCF)的沙箱项目
在数字化时代,云原生计算技术逐渐成为企业转型的关键.2024-04-25,备受瞩目的开源项目 Radius 已正式加入云原生计算基金会(CNCF)的沙箱项目![Sandbox] Radius · Is ...
- Plumed分子模拟后分析
技术背景 在前面的几篇博客中,我们分别介绍过Histogram算法的使用.Plumed安装与简单使用.Plumed一般就是两种用法:要么在运行分子动力学模拟的过程中实时的对接,要么就是把分子模拟的相关 ...
- Oracle细粒度审计策略
场景:经常需要查看某些表做了哪些操作. Oracle中,可以添加细粒度策略来获取,如下: begin dbms_fga.add_policy(object_schema => 'portxx', ...
- layui.js
目录 用法: 1.在base.js里导入layui插件 2.在使用的html页面里引入 base.js lucky.js index.html 用法: 1.在base.js里导入layui插件 2.在 ...
- NASM语法
NASM汇编语言的语法很简单,由4部分组成: label:instruction operands; comment 这4部分都是可选的.一条语句可以没有label,没有comment,甚至连inst ...