啰嗦话不说直接开始我们的正文🚀🚀🚀
什么是块状变量?
本文所指的块状变量是由一个或者多个字符串组成,能在编辑器中能高亮显示,且为一个整体的元素(整体是指,编辑器光标不能出现在块状变量内部)。
可能有些同学的第一反应是:这不就是注册一个setMonarchTokensProvider嘛。 的确,它是可以满足我们的高亮效果,但是笔者在使用它的时候发现了以下几个缺陷(不知道是不是使用的姿势不对😰)
1.它只能控制字体样式;如字体颜色,字体加粗等,设置背景颜色不生效。
2.只要在编辑内输入相同的字符串,都会被高亮显示,无法区别用户真正需要块状的内容。
所以基于上面的原因我们无法用setMonarchTokensProvider来实现。
一图胜千言,效果如下图
如何实现任意高亮样式
翻阅monaco-editor官网发现createDecorationsCollection(笔者称它为文字装饰器)很符合我们的预期效果。它可以给某个范围的值添加class,能添加class那样式要什么样都可以随心所欲的写了😂。而且这个api会返回一个IEditorDecorationsCollection对象,通过这个对象getRanges()方法可以获取所有高亮字符的范围;最主要的是这个返回的范围是编辑后的(例如你开始在第二行二列设置了高亮,然后在第一行后面输入了回车,getRanges返回的是第三行二列)。
ps:使用官网提供的dome,我们可以发现在装饰后的文字前后输入的文字一样会被视为装饰内容。我们需要在options参数中设置一个stickiness:monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges就能解决该问题
遇见问题&&解决方案
Q1:我们现在有了实现高亮的方法,那如何让编辑器识别我们块状变量为一个整体,让光标不在块状变量内部停留?
A1:这里笔者主要通过onDidChangeCursorSelection去监听光标改变,然后判断光变位置是否在getRanges()然后的范围中,如在则根据光标移动方向,使用setPosition改变光标位置、或者使用setSelection改变选中范围。
let editerInstance = monaco.editor.create(dom, options);
// 设置光标位置
editerInstance.setPosition({
lineNumber: 1,
column: 1
});
// 设置选中
editerInstance.setSelection({
endColumn: 1, // select区域结束行号
endLineNumber: 5, // select区域结束列号
startColumn: 1, // select区域开始列号
startLineNumber: 1, // select区域开始行号
});
Q2:在使用编辑器删除功能时,如何在遇见块状变量整体删除?
A2:笔者使用addCommand去重写了backspace和delete两个键的逻辑,实现思路跟光标移动大同小异,这里就不然多赘述。不过删除代码笔者推荐使用executeEdits这个方法。改方法会在undostack保留操作记录,可以在ctrl+z回退,非常好用。
// addCommand用法
let editerInstance = monaco.editor.create(dom, options);
// 重写backspace键
editerInstance.addCommand(monaco.KeyCode.Backspace, () => {
let range = new monaco.Range(
1, // range区域开始行号
1, // range区域开始列号
1, // range区域结束行号
5, // range区域结束列号
);
let op = {
range: range, // 删除的范围
text: ‘’,
forceMoveMarkers: true, // 取消选中状态
};
// 删除代码
editerInstance.executeEdits("", [op]);
});
// 重写ctrl+z
editerInstance.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyZ,
() => {
// todo
}
);
Q3:在删除块状元素后,若使用回撤(ctrl+z),删除的块状元素无法被恢复。
A3:首先肯定的是也会像删除功能一样去重写ctrl+z事件。后面的问题比较棘手,因为在删除块状元素的同时我们会把改元素的位置信息也删除(随着块状元素的增多,如果不删除位置信息,在重写光标移动和选中事件时会对程序带来额外的负担)。
没有了位置信息后就很难复原被删除的块状元素。既然没有了位置信息,那我们就干脆把块状元素重新渲染一遍?
这个思路最关键的问题是如何在编辑器中找出块状元素;
这时就想如果块状元素字符串如果都是 $$块状元素代码$$ 这样的形式出现就好了,就可以利用正则去匹配出它的
位置信息然后渲染成以前的样式。
但是这样形式在编辑器中会很怪异,用户看不懂,也不会理解为什么要做成这样。多半会否决这个方案。
既然他们不喜欢看见额外的$符号包裹块状元素,那我们换一种即存在但在视觉上又看不见的东西来替换$符号不就
行了? 这里想到了几种方式
1.空格符号\t来替换$,但是这个符号没有特殊性,很容易造成误匹配。所以行不通
2.通过零宽字符来代替$,足够特殊,又看不见。完美方案
所以笔者在添加块状元素时在前后都加了零宽字符。比如插入的块状元素字符为"康康小黑团",实际上编辑器添加的内容为" \u200d康康小黑团\u200d "。然后通过findMatches匹配位置信息,在重新调用createDecorationsCollection方法就能复现所有的块状元素。
这里提醒下不要用\u200b这个零宽字符,因为在使用monaco-editor格式化后这个字符会被删除。
\u200e之后的零宽字符都会被看见。具体为什么暂时还没有找到原因,求大佬赐教。
至此所有的难点都已经有相应的解决方案了,后面的就是具体逻辑实现,在这儿也不多说了。
完结,撒花。🥳🥳🥳
补充:
需要该功能的朋友可以到GitHub上自行下载。