Monaco Editor 内容渲染

monaco中有三个情况是需要获取dom内容尺寸和位置的,本文记录下monaco处理这三种情况的核心原理:

  • 响应click事件,获取被点击的行号和列号
  • 渲染光标
  • 文本自动换行

本文先不看一些特殊情况处理,假定点击的位置就在某一个字符的上方,所有情况都是理想的。这样做方便查看核心逻辑,不被错误处理和边缘处理影响。

获取元素尺寸和位置的原理

使用Range可以获取文档的一部分区域并且获取这部分区域的尺寸信息(DOMRects)。需要注意的是,如果想选定部分文字,需要指定element为文本节点,而不是外面的span或者div。在线示例

1
2
3
4
const range = document.createRange()
range.setStart(startElement, startOffset)
range.setEnd(endElement, endOffset)
cosnt rects = range.getClientRects()

还有一种情况,从位置获取元素,再获取元素尺寸和位置:

1
2
const eles = document.elementsFromPoint(e.clientX, e.clientY);
// Then get rects

还可以用Canvas获取某个字符的尺寸(没有位置):

1
2
3
4
const context = canvas.getContext('2d')!;
context.font = font;
const metrics = context.measureText(char);
const width = metrics.width;

Click事件处理

在click事件中,用于点击一个位置,编辑器获取行号和列号。首先查找必须用到的document.createRange(),可以找到在mouseTarget.ts

  • 获取新的range:document.createRange()
  • 获取点击位置所对应的元素:shadowRoot.elementFromPoint(x, y)
  • 获取元素内的文本节点
  • 计算文本节点的尺寸和位置

到目前为止已经获得了这个文本节点的位置,接下来需要获取节点中文字的位置。当时看到这里以为又要用range去量了,但是并没有,是用的Canvas。猜想可能是这里只需要尺寸不需要位置,canvas也许快点?

  • 对于文本中的字符,确定宽度
  • 比较点击位置和字符中心位置,
    • 如果点击位置在字符中心偏左,直接拿到offset然后结束循环
    • 如果偏右,看下一个字符,知道看完所有的
  • 用新拿到的元素和偏移,计算当前字符的尺寸和位置

渲染光标

用于根据指定的行号和列号渲染光标。在viewCursor.ts中。主要逻辑是从position获取dom本身,然后调用range的方法获取光标所在位置字符的位置和尺寸信息,然后绘制光标

1
position -> dom & offset -> range.getClientRects() -> rects -> render

monaco的大部分都在处理如何从position获取dom和offset:

  • viewLines.visibleRangeForPosition:得到位置对应的Range查看代码
  • viewLine.getVisibleRangesForRange:得到对应行的Range查看代码
  • viewLine._actualReadPixelOffset:获取对应位置的Part(最小的一个<span><span/>单元)和字符偏移量。这时已经满足了使用range.getClientRects()的条件了,剩下的的只是一层层调用。

附monaco中viewlines的结构:

1
2
3
4
5
6
div.view-lines
div.view-line
span // 这是一个wrapper
span // 这里是每一行实际的字符 ...
...
span // ... 一直到这里

文本自动换行

// TODO