Codemirror345读源码
之前读了codemirror2的代码, 继续读3-5的代码.
v3.0beta1
- 果然有重大修改, 已经没有charFromX了. 不过posFromMouse还在
function posFromMouse(cm, e, liberal) {
var display = cm.display;//这个是显示区域
if (!liberal) {
var target = e_target(e);
if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
target == display.scrollbarV || target == display.scrollbarV.firstChild ||
target == display.scrollbarFiller) return null;
}
var x, y, space = display.lineSpace.getBoundingClientRect(); //linespace 是初始值, 一个行div, 为了wrap用的.
// Fails unpredictably on IE[67] when mouse is dragged around quickly.
try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
return coordsChar(cm, x - space.left, y - space.top);//后面两个参数就是块内的偏移量
}
这里调用了coordsChar
// Coords must be lineSpace-local
function coordsChar(cm, x, y) {
var display = cm.display, doc = cm.view.doc;
var cw = charWidth(display), heightPos = display.viewOffset + y;
if (heightPos < 0) return {line: 0, ch: 0};
var lineNo = lineAtHeight(doc, heightPos);//一行一行的计算高度来找到行.
if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length};
var lineObj = getLine(doc, lineNo);//拿到行dom对象
if (!lineObj.text.length) return {line: lineNo, ch: 0};
var tw = cm.options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;//开了wrap就要一通算.
if (x < 0) x = 0;
var wrongLine = false;
//这个函数变化了. 返回的是某个元素的left, 也是像素.
function getX(ch) {
//这个函数关键了, 拿到合适的元素.
var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line", lineObj);
if (tw) { //这个地方是自动换行wrap的修正.
wrongLine = true;
if (innerOff > sp.bottom) return Math.max(0, sp.left - display.wrapper.clientWidth);
else if (innerOff < sp.top) return sp.left + display.wrapper.clientWidth;
else wrongLine = false;
}
return sp.left;
}
var bidi = getOrder(lineObj), dist = lineObj.text.length;
var from = lineLeft(lineObj), fromX = 0, to = lineRight(lineObj), toX;
if (!bidi) {
// Guess a suitable upper bound for our search.
var estimated = Math.min(to, Math.ceil((x + Math.floor(innerOff / textHeight(display)) *
display.wrapper.clientWidth * .9) / cw));
for (;;) {
var estX = getX(estimated);
if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
else {toX = estX; to = estimated; break;}
}
// Try to guess a suitable lower bound as well.
estimated = Math.floor(to * 0.8); estX = getX(estimated);
if (estX < x) {from = estimated; fromX = estX;}
dist = to - from;
} else toX = getX(to);
if (x > toX) return {line: lineNo, ch: to};
// Do a binary search between these bounds.
for (;;) {
if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
var after = x - fromX < toX - x, ch = after ? from : to;
while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
return {line: lineNo, ch: ch, after: after}; //核心: 返回行数和字符数
}
var step = Math.ceil(dist / 2), middle = from + step;
if (bidi) {
middle = from;
for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
}
var middleX = getX(middle);
if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
else {from = middle; fromX = middleX; dist = step;}
}
}
相关函数cursorcoords
function cursorCoords(cm, pos, context, lineObj) {
lineObj = lineObj || getLine(cm.view.doc, pos.line);
function get(ch, right) {
var m = measureLine(cm, lineObj, ch);
if (right) m.left = m.right; else m.right = m.left;
return intoCoordSystem(cm, pos, m, context);
}
var order = getOrder(lineObj), ch = pos.ch;
if (!order) return get(ch);
var main, other, linedir = order[0].level;
for (var i = 0; i < order.length; ++i) {
var part = order[i], rtl = part.level % 2, nb, here;
if (part.from < ch && part.to > ch) return get(ch, rtl);
var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
if (left == ch) {
// Opera and IE return bogus offsets and widths for edges
// where the direction flips, but only for the side with the
// lower level. So we try to use the side with the higher
// level.
if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
else here = get(rtl && part.from != part.to ? ch - 1 : ch);
if (rtl == linedir) main = here; else other = here;
} else if (right == ch) {
var nb = i < order.length - 1 && order[i+1];
if (!rtl && nb && nb.from == nb.to) continue;
if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
else here = get(rtl ? ch : ch - 1, true);
if (rtl == linedir) main = here; else other = here;
}
}
if (linedir && !ch) other = get(order[0].to - 1);
if (!main) return other;
if (other) main.other = other;
return main;
}
3.21.0 主线的最后一个3.x
简化了
// Coords must be lineSpace-local
function coordsChar(cm, x, y) {
var doc = cm.doc;
y += cm.display.viewOffset;
if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
if (lineNo > last)
return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
if (x < 0) x = 0;
for (;;) {
var lineObj = getLine(doc, lineNo);
var found = coordsCharInner(cm, lineObj, lineNo, x, y);
var merged = collapsedSpanAtEnd(lineObj);
var mergedPos = merged && merged.find();
if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
lineNo = mergedPos.to.line;
else
return found;
}
}
其实不是了, 后面还有
function coordsCharInner(cm, lineObj, lineNo, x, y) {
var innerOff = y - heightAtLine(cm, lineObj);
var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
var measurement = measureLine(cm, lineObj);
function getX(ch) {
var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
lineObj, measurement);
wrongLine = true;
if (innerOff > sp.bottom) return sp.left - adjust;
else if (innerOff < sp.top) return sp.left + adjust;
else wrongLine = false;
return sp.left;
}
var bidi = getOrder(lineObj), dist = lineObj.text.length;
var from = lineLeft(lineObj), to = lineRight(lineObj);
var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
// Do a binary search between these bounds.
for (;;) {
if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
var ch = x < fromX || x - fromX <= toX - x ? from : to;
var xDiff = x - (ch == from ? fromX : toX);
while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
xDiff < 0 ? -1 : xDiff ? 1 : 0);
return pos;
}
var step = Math.ceil(dist / 2), middle = from + step;
if (bidi) {
middle = from;
for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
}
var middleX = getX(middle);
if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
}
}
v4-beta1
和v3没区别
4.13
没区别
5.0.0
依旧没变化
5.20.0
从这个版本开始依赖node了. codemirror.js是npm出来的,
在这个文件了: position_measurement
5.42.0
完全不知道这个是啥了, 需要跟踪一下才行
- 两个关键元素
- display就是显示的内容.
- doc就是内部实际的文档.
// Compute the character position closest to the given coordinates.
// Input must be lineSpace-local ("div" coordinate system).
export function coordsChar(cm, x, y) {
let doc = cm.doc
y += cm.display.viewOffset
if (y < 0) return PosWithInfo(doc.first, 0, null, true, -1)
let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1
//lineatheight这个函数就是一行一行的减去高度, 减到高度小于行高然后拿到行数, 简单的说就是一行一行试出来的.
if (lineN > last)
return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1)
if (x < 0) x = 0
let lineObj = getLine(doc, lineN) //doc里面有全部的文档按照一行一行的存储再数组里面.
for (;;) {
let found = coordsCharInner(cm, lineObj, lineN, x, y) //就是这里拿的最终结果.
let collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 ? 1 : 0))
if (!collapsed) return found
let rangeEnd = collapsed.find(1)
if (rangeEnd.line == lineN) return rangeEnd
lineObj = getLine(doc, lineN = rangeEnd.line)
}
}
function coordsCharInner(cm, lineObj, lineNo, x, y) {
// Move y into line-local coordinate space
y -= heightAtLine(lineObj) //lineobj是doc, 这里减掉了前面的行高.
let preparedMeasure = prepareMeasureForLine(cm, lineObj) //这里拿到了当前行.
// When directly calling `measureCharPrepared`, we have to adjust
// for the widgets at this line.
let widgetHeight = widgetTopHeight(lineObj)
let begin = 0, end = lineObj.text.length, ltr = true //end就是line的长度
let order = getOrder(lineObj, cm.doc.direction) //这个是判断文本方向, 从右往左....
// If the line isn't plain left-to-right text, first figure out
// which bidi section the coordinates fall into.
if (order) { //难为marijn了, 幸亏他不知道中文和日本本来是从上往下写的.
let part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)
(cm, lineObj, lineNo, preparedMeasure, order, x, y)
ltr = part.level != 1
// The awkward -1 offsets are needed because findFirst (called
// on these below) will treat its first bound as inclusive,
// second as exclusive, but we want to actually address the
// characters in the part's range
begin = ltr ? part.from : part.to - 1
end = ltr ? part.to : part.from - 1
}
// A binary search to find the first character whose bounding box
// starts after the coordinates. If we run across any whose box wrap
// the coordinates, store that.
let chAround = null, boxAround = null
let ch = findFirst(ch => { //这个地方是关键, findfirst就是持续的找到合适的位置的那个死循环. 他的第一个参数是一个函数. 这个函数的参数是一个位置值. 核心就在这里, 虽然隔了那么多个版本, 但是本质是一样的, 就是一个一个试过去的.
let box = measureCharPrepared(cm, preparedMeasure, ch)
box.top += widgetHeight; box.bottom += widgetHeight
if (!boxIsAfter(box, x, y, false)) return false
if (box.top <= y && box.left <= x) {
chAround = ch
boxAround = box
}
return true
}, begin, end)
//下面是修正, 是对计算结果的修正.
let baseX, sticky, outside = false
// If a box around the coordinates was found, use that
if (boxAround) {
// Distinguish coordinates nearer to the left or right side of the box
let atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr
ch = chAround + (atStart ? 0 : 1)
sticky = atStart ? "after" : "before"
baseX = atLeft ? boxAround.left : boxAround.right
} else {
// (Adjust for extended bound, if necessary.)
if (!ltr && (ch == end || ch == begin)) ch++
// To determine which side to associate with, get the box to the
// left of the character and compare it's vertical position to the
// coordinates
sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" :
(measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ?
"after" : "before"
// Now get accurate coordinates for this place, in order to get a
// base X position
let coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure)
baseX = coords.left
outside = y < coords.top || y >= coords.bottom
}
ch = skipExtendingChars(lineObj.text, ch, 1)
return PosWithInfo(lineNo, ch, sticky, outside, x - baseX)
}
结论: codemirror虽然从2一路开到了5, 但是, 在计算光标位置这件事上并没有进步, 都是一路算过去的. 或许, 我想的直接硬插的方式并不可行:
- 我想的是用一个隐藏的textarea在dom节点位置硬插入内容,
- 然后更新这个dom当前这一行的内容.
- 保存的时候再保存整个dom的内容.