文章目录
在之前的文章《4 步为 Monaco Editor 添加自定义语言支持》中,我们已经学会了如何使用 monaco-editor 来实现一个支持自定义编程语言的编辑器,但是作为一个标准的代码编辑器,我们还需要它支持代码块折叠功能,这样在编辑内容比较多的代码文件时,可以方便的将其中一些代码块折叠起来,使得在阅读代码时更容易理解整体代码结构。
这一次我们就来看看如何给 Monaco Editor 增加代码块折叠功能。
官方示例
在 Monaco Editor Playground 的 Folding Provider Example 示例中,可以看到如何给一个自定义语言添加代码折叠支持:
monaco.languages.registerFoldingRangeProvider("foldLanguage", {
provideFoldingRanges: function (model, context, token) {
return [
// comment1
{
start: 5,
end: 7,
kind: monaco.languages.FoldingRangeKind.Comment,
},
// ...
]
}
});
可以看到,只需要在注册自定义语言后,再注册一个 foldingRangeProvider 即可,在 provicdeFoldingRanges callback 中,将可以折叠的代码区块,以 start 和 end 行号圈定范围,返回给到 Monaco Editor 即可。
在示例中,它是通过硬编码起始和结束行号的方式来提供可折叠范围的,但是在实际使用过程中,我们不可能预见可以支持代码折叠的范围,这里需要使用编码的方式来动态生成可折叠范围。
需求定义
在开发 MermaidEditor 的代码编辑器时,在部分图表中,代码块会有一些层级关系,为了有更好的编辑体验,我们就需要给代码编辑器加上代码块折叠功能。
目前 Monaco Editor 并没有提供官方的 LSP(Language Server Protocol) 支持,我们需要使用其他方式来实现代码折叠的逻辑。
通过观察,我们可以发现,对于不同层级的代码块来说,它们通常拥有不同数量的缩进,因此我们可以通过缩进来判断代码行是否属于同一个代码块。
代码实现
获取编辑区内容
在 provideFoldingRanges 函数中有一个参数 model,通过这个变量我们可以获得当前编辑区中的所有文本内容,通过遍历每一行的内容,可以获取到每一行的前置空白字符数量,从而根据空白字符数量来判定代码块折叠范围。
// 用于匹配每一行空白字符的正则表达式
const pattern = /^(\s*)(.+)/;
// 遍历编辑区内容中的每一行代码
for (var i = 1, count = model.getLineCount(); i <= count; i++) {
const line = model.getLineContent(i);
// 获取每一行代码中的前置空白字符
const matches = pattern.exec(line);
if (matches) {
// 获取当前行的空白长度,用于匹配后续代码行是否属于这一区块
const indentLen = matches[1].length;
}
}
匹配代码折叠区域
在遍历每一行代码后,我们需要判断它后续的代码行,是否属于可折叠代码区块,这里判断的标准是,后续代码行的前置空白字符长度大于当前行的前置空白字符数量,因此所有缩进长度大于当前行的代码区域,都可以被包含进来 。
// 从下一行开始搜索
let endLine = i + 1;
let lastNotEmptyLine = i;
// 遍历到编辑区内容末尾
while (endLine <= count) {
const lineContent = model.getLineContent(endLine);
// 获取后续行的前置空白字符长度
const subMatches = pattern.exec(lineContent);
if (subMatches != null) {
// 如果前置空白字符长度大于起始行,就将它包含进折叠区域
if (subMatches[1].length > indentLen) {
lastNotEmptyLine = endLine;
} else {
break;
}
}
endLine++;
}
请注意,这里额外忽略了空白行,是为了防止空白行会中断折叠区域的判断,这样在处理同等缩进的代码块中,如果出于代码格式考虑添加的空白也不会导致折叠区域判断错误,提前结束折叠区域的判定。
返回代码块折叠区域
if (lastNotEmptyLine > i) {
ranges.push({
start: i,
end: lastNotEmptyLine,
kind: monaco.languages.FoldingRangeKind.Region,
});
}
这里返回 Monaco Editor 可以使用的代码块折叠区域数据结构。
完整代码
monacoEditor.languages.registerFoldingRangeProvider("foldLanguage", {
provideFoldingRanges: function (model, context, token) {
const ranges = [];
const pattern = /^(\s*)(.+)/;
for (var i = 1, count = model.getLineCount(); i <= count; i++) {
const line = model.getLineContent(i);
const matches = pattern.exec(line);
if (matches) {
const indentLen = matches[1].length;
let endLine = i + 1;
let lastNotEmptyLine = i;
while (endLine <= count) {
const lineContent = model.getLineContent(endLine);
const subMatches = pattern.exec(lineContent);
if (subMatches != null) {
if (subMatches[1].length > indentLen) {
lastNotEmptyLine = endLine;
} else {
break;
}
}
endLine++;
}
if (lastNotEmptyLine > i) {
ranges.push({
start: i,
end: lastNotEmptyLine,
kind: monaco.languages.FoldingRangeKind.Region,
});
}
}
}
return ranges;
},
});
最终效果
可以看到,最终 Monaco Editor 正确显示了代码块折叠标记,并且有嵌套的折叠,也能正确识别。
0 条评论。