CodeJar 在中文输入法下 Backspace 误删大片文本的根因与修复实践

在为项目实现一个轻量级代码编辑器时,我选择了 CodeJar。它体积小、依赖少、基于 contenteditable 即可完成语法高亮,集成成本极低。然而在真实使用中遇到了一个非常棘手的问题:
在中文输入法环境下,按一次 Backspace,偶发性删除一大片文本。
这个问题最初看起来像是浏览器 bug,甚至一度怀疑是输入法行为异常。但经过定位后,发现问题来自 CodeJar 的高亮重渲染机制与浏览器 IME(输入法组合输入)事件的冲突。
现象复现
环境:
- Chrome / Edge
- 任意中文输入法
- CodeJar 3.x
操作过程:
- 在编辑器中输入中文(处于组合输入状态)
- 输入法提交文字后立即按 Backspace
- 偶发性触发整段文本被删除
问题并非必现,而是随机出现,增加了排查难度。
代码结构分析
查看 CodeJar 源码后发现,新版已经不再使用 beforeinput 进行高亮同步,而是改为在 keyup 事件后触发延迟高亮:
on('keyup', event => {
if (event.defaultPrevented) return;
if (event.isComposing) return;
if (prev !== toString()) debounceHighlight();
});而高亮逻辑会执行:
const pos = save();
highlight(editor, pos);
restore(pos);即:
- 保存当前 selection
- 重绘 DOM(插入语法高亮 HTML)
- 再恢复 selection
问题恰好出在 IME 组合输入刚结束时浏览器 selection 尚未稳定 ,而 CodeJar 在此时触发重绘并恢复 selection,会造成 selection 错位。下一次 Backspace 时,浏览器认为当前存在大范围选区,于是删除整片内容。
关键点
IME 组合输入期间不能触发高亮重渲染。
否则 selection 恢复的坐标会与浏览器内部状态不一致。
修复方案实现
CodeJar 内部已经提供统一事件注册封装:
const on = (type, fn) => editor.addEventListener(type, fn);因此直接引入原生 compositionstart / compositionend 即可。
新增 IME 状态锁:
let isComposingIME = false;监听输入法事件:
on('compositionstart', () => {
isComposingIME = true;
});
on('compositionend', () => {
isComposingIME = false;
requestAnimationFrame(() => {
const pos = save();
doHighlight(editor, pos);
restore(pos);
});
});并在 keyup 阶段阻止高亮触发:
on('keyup', event => {
if (event.defaultPrevented) return;
if (event.isComposing) return;
if (isComposingIME) return;
if (prev !== toString()) debounceHighlight();
});最终逻辑变为:
- IME 组合输入时不触发 DOM 重绘
- 组合结束后在下一帧统一执行一次高亮
- selection 全程保持稳定
修复结果
- 中文输入正常
- Backspace 精确删除单字符
- 高亮行为保持一致
- Undo / History 不受影响
- 无需改动 CodeJar 内部核心结构
问题彻底消失。
结语
这类问题的本质是:
contenteditable + IME + DOM 重绘 + selection 恢复 四者同时存在时产生的状态竞争。
CodeJar 本身足够轻量,但在复杂输入环境下需要补足浏览器输入法事件处理。完成这一步后,它仍然是一个非常干净、可控、适合嵌入式场景的编辑器内核。
发布评论
评论列表 0




