在《搭建Selenium在线执行环境》中,基于CodeMirror我做了一个界面简单的在线代码编辑器,界面大概就长这个样子:

输入输出均是利用CodeMirror完成的,只不过设计了不同的显示风格而已。输入框支持语法高亮、行号等特性,而输出框则模拟终端的黑白输出以及简单的行号显示。
前后端通过WebSocket进行简单的消息发送,后来在《让WebSocket支持Oauth2-TOKEN验证的尝试》中,通过改造后端,让前后端的通信不仅变更安全,也能传送更多类型的数据了。那么当前的输出界面就变得有些简陋了,毕竟当前无法显示除了字符串之外的其它内容,前端的改造势在必行。不仅要显示文本内容,还要让它显示更多富媒体的信息。
CodeMirror使用replaceSelection
将文本内容指定插入的位置,但是这个接口仅支持插入文本内容,并不支持插入其它数据。
replaceSelection(text: string | Text) → TransactionSpec
Create a transaction spec that replaces every selection range with the given content.
如果要插入别的内容得想想其它的办法,这里顺道提一下初次使用CodeMirror的API时,不会那么容易理解每个API的作用,甚至不太容易找到需要的API接口,这与近代编程语言易于理解的API写法存在不小的区别。比如,通常使用insertXXX
表示插入某种类型、使用append
表示在末尾追加,但是在CodeMirror中很难直接找到对应的API。
如何解决
既然从API中一时半会也没找到可直接使用的API,那么不妨先看看文本内容在CodeMirror中是如何显示的吧。

如上图,在CodeMirror中显示一行文本,并在HTML代码中查看这行文本是如何显示的,很容易地能看出文本在CodeMirror中就是一个div块,看来上文中用到的API,最终是创建了这么一个块呀,这就有意思了。
如果我能自定义div的内容,比如包装一张图片,岂不是也能在CodeMirror中显示了?那么我们想在CodeMirror中显示富文本信息的诉求就变成如何在CodeMirror中插入HTML自定义元素了。
幸运的是,CodeMirror提供了一个添加Widget的接口addLineWidget
。换句话讲我们可以通过这个接口可以向CodeMirror添加任意一个HTML对象。
doc.addLineWidget(line: integer|LineHandle, node: Element, ?options: object) → LineWidget
Adds a line widget, an element shown below a line, spanning the whole of the editor’s width, and moving the lines below it downwards.
line
should be either an integer or a line handle, andnode
should be a DOM node, which will be displayed below the given line.options
, when given, should be an object that configures the behavior of the widget. The following options are supported (all default to false):
coverGutter: boolean
Whether the widget should cover the gutter.
noHScroll: boolean
Whether the widget should stay fixed in the face of horizontal scrolling.
above: boolean
Causes the widget to be placed above instead of below the text of the line.
handleMouseEvents: boolean
Determines whether the editor will capture mouse and drag events occurring in this widget. Default is false—the events will be left alone for the default browser handler, or specific handlers on the widget, to capture.
insertAt: integer
By default, the widget is added below other widgets for the line. This option can be used to place it at a different position (zero for the top, N to put it after the Nth other widget). Note that this only has effect once, when the widget is created.
className: string
Add an extra CSS class name to the wrapper element created for the widget.
Note that the widget node will become a descendant of nodes with CodeMirror-specific CSS classes, and those classes might in some cases affect it. This method returns an object that represents the widget placement. It’ll have a
line
property pointing at the line handle that it is associated with, and the following methods:
clear()
Removes the widget.
changed()
Call this if you made some change to the widget’s DOM node that might affect its height. It’ll force CodeMirror to update the height of the line that contains the widget.
实现方式
既然找到了解决办法,剩下的就好办多了,结合我自己网站的实际情况,将该接口进行了二次封装,这样更方便调用。
function createElement(type, src) {
let node;
switch (type) {
case 1:
node = document.createElement("p");
node.innerHTML = src;
break;
case 2:
node = document.createElement("img");
node.src = "data:image/png;base64, " + src;
break;
case 3:
break;
}
return node;
}
function append(cm, type, msg, mark) {
if(msg.length == 0){
return;
}
let lastline = cm.lineCount();
cm.setCursor(lastline, 0);
if (type.isElement) {
cm.addLineWidget(lastline, createElement(type.node, msg), { above: true });
} else {
// cm.replaceRange(msg, { line: lastline, ch: 0 }, { line: lastline });
cm.replaceSelection(msg);
}
if (mark) {
cm.markText({ line: lastline - 1, ch: 0 }, { line: lastline - 1, ch: msg.length }, { css: "color: #FF6347" });
}
}
函数逻辑相对简单,如果需要添加Widget,则根据传入的Widget类型,自行判断是创建p还是img对象(当然这里可以任意扩展其它的类型对象),然后调用addLineWidget将组件添加进指定行即可。
如果是普通的文本,则还是按以前的replaceSelection进行添加。为了满足对特定文本内容突出显示,这里单独使用了markText接口,它可以对指定文本设置其css样式。
注意:使用每次调用addLineWidget,仅能添加一个网页控件(对象),也就是说所添加的内容是独占一行的。
效果
前端将输入的指令通过WebSocket(带OAUTH2 TOKEN)传递给后端,并在后端解析运行。运行结果再通过WebSocket发回前端。虽然后端服务是以Docker运行,但是从安全角度考虑,后端对可执行的Shell命令做了限制(包括且不限于禁用重定向、高危命令、长时执行等)。