一、前言
Monaco Editor 是一款微软开源的运行在浏览器环境中的代码编辑器,其不仅提供了基本的代码编辑功能,还支持自定义功能的扩展,使得开发者能够根据项目需求定制编辑器的行为,为编辑器注入新的生命力。本文将深入探讨在实际开发过程中,我们如何通过 Monaco Editor 原生的 API 进行自定义扩展功能,将 Monaco Editor 从一个简单的文本编辑工具,转变为一个具备代码上下文理解、智能提示、格式化以及错误检测等高级功能的智能编辑器。无论您是 Monaco Editor 的新手,还是已经有一定经验的老手,本文都将为您提供宝贵的信息和见解。文章分为上下两篇,当前为"上篇"内容:
二、编辑器介绍
Monaco Editor 提供了一个轻量级的代码编辑器,可以在任何基于 Web 技术构建的项目中使用。它不仅支持多种编程语言,还提供了丰富的 API 和事件系统,使得开发者可以轻松地与编辑器交互。编辑器功能特性请点击 👉 链接,编辑器使用文档请点击 👉 链接,编辑器在线体验请点击 👉 链接
2.1 常用配置项
js
{
value: '', // 编辑器初始文本
language: 'javascript', // 语言
theme: 'vs', // 主题
readOnly: false, // 是否只读
minimap: { enabled: false }, // 是否启用小地图
fontSize: 14, // 字体大小
tabSize: 2, // tab缩进长度
automaticLayout: true, // 自动布局
lineNumbers: 'off', // 是否启用行号
contextmenu: true, // 是否启用上下文菜单
folding: true, // 是否启用代码折叠
foldingStrategy: 'auto', // 代码折叠策略
wordWrap: 'on', // 自动换行设置
wrappingIndent: 'indent', // 换行缩进
formatOnPaste: true, // 粘贴时是否自动格式化
formatOnType: true, // 输入时是否自动格式化
dragAndDrop: true, // 是否允许拖放
cursorStyle: 'line', // 光标样式
cursorBlinking: 'blink', // 光标闪烁方式
scrollbar: {
vertical: 'auto', // 垂直滚动条的显示方式
horizontal: 'auto', // 水平滚动条的显示方式
verticalScrollbarSize: 2, // 垂直滚动条的宽
horizontalScrollbarSize: 2, // 水平滚动条的高度
}
}
2.2 常用API
js
// 获取 model 实例
editor.getModel();
// 获取代码内容(字符串)
editor.getModel().getValue();
// 获取代码长度
editor.getModel().getValueLength();
// 获取光标位置
editor.getPosition();
// 光标跳到给定位置
editor.setPosition({ lineNumber: 1, column: 1 });
// 编辑器聚焦
editor.focus();
// 销毁编辑器
editor.dispose();
// 设置代码
editor.setValue('console.log("Hello world!");');
// 指定位置插入代码
editor.executeEdits();
// 获取选中的行信息
editor.getSelection();
// 获取某一行的内容,不传值则返回全部
editor.getModel().getLineContent(1);
// 编辑器滚到指定行
editor.revealLine(1);
// 编辑器视图滚到指定位置
editor.revealPositionInCenter({ lineNumber: 1, column: 1});
// 手动触发 action
editor.trigger('actionName', args)
// 内容改变事件
editor.onDidChangeModelContent((e)=>console.log(e)});
三、自定义功能
3.1 代码提示
Monaco Editor 提供了 monaco.languages.registerCompletionItemProvider API 来自定义代码提示。
利用registerCompletionItemProvider
可以在需要结合上下文进行提示或提供全局提示等场景中自定义代码提示,从而显著提升编辑器的开发效率。
js
monaco.languages.registerCompletionItemProvider("javascript", {
provideCompletionItems: function (model, position) {
// ... 这里可以进行一些逻辑处理决定应该返回什么提示选项
return {
suggestions: [
{
label: "swDemo",
insertText: "sw-demo",
detail: "this is detail",
sortText: `0_swDemo`,
kind: monaco.languages.CompletionItemKind.Function,
filterText: "swDemo",
documentation: "this is documentation",
},
],
};
},
});
以下介绍返回的 suggestion 项常用的配置参数:
- label:每个选项的展示文本
- detail:每个选项的详情介绍
- documentation:每个选项的详细介绍,支持字符串或markdown
- kind:类型,决定了图标的样式,monaco 提供了二十多项类型以供选择 👉 链接
- filterText:筛选选项的关键词
- insertText:点击回车后插入的文本,可以自定义不一定就是 label 展示的内容
- range:文本要替换的区间
- sortText:选项会根据这个属性值进行结果排序,如果期望排在前面可自定义设置,例如以0开头
有时候我们的代码提示内容需要通过接口等异步获取,可参考如下内容:
js
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: function (model, position) {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ suggestions })
}, 1000)
})
return promise;
},
});
3.2 悬浮提示
Monaco Editor 提供了 monaco.languages.registerHoverProvider API 来自定义悬浮提示。
当我们期望鼠标悬浮在某些特定代码上时显示自定义提示,可以通过 registerHoverProvider 注册对应的提示,可参考如下内容:
js
monaco.languages.registerHoverProvider("javascript", {
provideHover: function (model: any, position: any) {
const content = model.getLineContent(position.lineNumber); // 当前hover的行内容
const word = model.getWordAtPosition(position).word; // 当前hover的单词
// ...进行一些逻辑判断
return {
// 每一项就是一行内容
contents: [
{ value: `第一行内容:${word}` },
{
value: "```javascript\n" + "console.log(123)" + "\n```",
},
],
};
},
});
通过设置自定义 Marker 也能实现相似的功能,具体实现可以参考下篇-3.1 语法检查。
3.3 点击跳转
Monaco Editor 提供了 editor.onMouseDown API 来支持监听点击事件。
当我们需要实现 cmd + click 时执行某些操作,例如跳转对应的内容,除了创建多个 model 使得编辑器默认支持跳转对应的定义之外,还可以通过监听编辑器的点击事件并判断 cmd 按键是否按下来实现,可参考如下内容:
js
editor.onMouseDown((e) => {
// 检查是否同时按下了cmd键(在MacOS上)或ctrl键(在Windows上)
if (e.event.metaKey || e.event.ctrlKey) {
// ... 执行某些操作
}
});
3.4 插入代码
Monaco Editor 提供了 editor.ICodeEditor.executeEdits API 来实现自定义插入代码。
假设我们存在一个变量或方法列表,当点击列表项时能够快速插入相应的代码内容,此时就可以通过 executeEdits 来实现,可参考如下内容:
js
editor.executeEdits("", [
{
range: { startLineNumber, startColumn, endLineNumber, endColumn }, // 要修改的范围
text: '待插入的内容', // 要插入的内容
forceMoveMarkers: true, // 是否强制移动光标
},
]);
// 如果需要在光标后插入内容,获取光标位置,将range设置为相关值
const position = editor.getPosition();
// 如果将选中替换为新内容,获取选中内容位置,将range设置为相关值
const selection = editor.getSelection();
💡 通过 editor.ITextModel.applyEdits 也能实现代码的插入和替换,editor.setValue() 也能更改代码,但是 executeEdits 的好处是通过其插入的代码可以通过 "⌘ + Z" 撤销插入。
四、总结
Monaco Editor 以其强大的功能和灵活性成为了前端开发中不可或缺的一部分,本文总结了我们在对代码编辑器在 Javascript 语言下进行自定义功能扩展的经验,通过自定义代码提示、悬浮提示、点击跳转、插入代码等功能,极大地增强了编辑器的交互性和用户体验,使得我们使用 JS 代码编辑器能够更加高效(语法检查、代码格式化、自定义菜单、单行编辑器模式等功能请跳转下篇查看)。总的来说,Monaco Editor 的自定义功能为开发者提供了无限的可能性,使得开发者能够根据具体需求定制出最适合自己项目的编辑器。
参考资料