如何为CodeMirror实现带参数的交互式悬浮提示组件?
CodeMirror自定义标记隐藏与悬浮交互实现方案
核心思路
先剥离输入内容中的自定义标记,只保留纯文本显示在编辑器中;同时记录标记包裹文本的位置和参数,通过CodeMirror的状态管理和hover扩展,实现鼠标悬停时显示参数弹窗。
步骤实现
解析原始内容,提取纯文本与标记元数据
编写解析函数,匹配自定义标记(比如<foo>),提取中间文本、标记参数,以及该文本在纯文本中的位置范围。function parseMarkedContent(content) { // 匹配自定义foo标记,可根据实际需求修改正则规则 const markRegex = /<foo\s+param1="([^"]+)">([^<]+)<\/foo>/g; const marks = []; let plainText = content; let offset = 0; let match; while ((match = markRegex.exec(content)) !== null) { const [fullMatch, paramVal, targetText] = match; // 计算目标文本在纯文本中的起始/结束位置 const start = match.index - offset; const end = start + targetText.length; // 存储标记的位置和参数信息 marks.push({ from: start, to: end, params: { param1: paramVal } }); // 替换标记为纯文本,更新偏移量 plainText = plainText.replace(fullMatch, targetText); offset += fullMatch.length - targetText.length; } return { plainText, marks }; }用StateField管理标记状态
利用CodeMirror的StateField存储标记的位置和参数,确保文档内容变更时,标记位置能自动调整;同时通过该字段提供hover提示扩展。import { StateField, StateEffect, EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; // 定义添加标记的状态副作用 const addMarksEffect = StateEffect.define(); // 创建标记状态字段 const markStateField = StateField.define({ create() { return []; }, update(marks, transaction) { // 文档变更时,自动映射标记的位置 marks = marks.map(mark => ({ ...mark, from: transaction.changes.mapPos(mark.from), to: transaction.changes.mapPos(mark.to) })).filter(mark => mark.from < mark.to); // 处理添加标记的副作用 for (const effect of transaction.effects) { if (effect.is(addMarksEffect)) { marks = [...marks, ...effect.value]; } } return marks; }, // 提供hover提示扩展 provide(field) { return EditorView.hoverTooltip.from(field, marks => ({ create(view, pos) { // 找到当前位置对应的标记 const targetMark = marks.find(mark => pos >= mark.from && pos <= mark.to); if (!targetMark) return null; // 构建悬浮弹窗DOM const tooltipDom = document.createElement("div"); tooltipDom.style.padding = "8px 12px"; tooltipDom.style.backgroundColor = "#f5f5f5"; tooltipDom.style.border = "1px solid #ddd"; tooltipDom.style.borderRadius = "4px"; tooltipDom.textContent = `参数:param1 = ${targetMark.params.param1}`; return { dom: tooltipDom, pos: targetMark.from, end: targetMark.to }; } })); } });初始化编辑器
解析原始内容后,将纯文本传入编辑器,并通过状态副作用把标记数据注入到StateField中。import { basicSetup } from "codemirror"; // 原始输入内容 const rawInput = 'This is a <foo param1="bar">test</foo> sentence'; const { plainText, marks } = parseMarkedContent(rawInput); // 初始化编辑器 const editorView = new EditorView({ doc: plainText, extensions: [ basicSetup, markStateField, // 初始化时添加标记数据 EditorState.transaction.of({ effects: addMarksEffect.of(marks) }) ], parent: document.getElementById("editor-container") });
关键细节
- 自动位置调整:StateField的update方法中通过
transaction.changes.mapPos处理了文档变更时的标记位置映射,用户编辑文本时,标记会自动跟随目标文本移动。 - 正则灵活性:如果有多种自定义标记,可修改解析函数的正则表达式,扩展参数提取逻辑。
- 弹窗自定义:悬浮弹窗的DOM结构、样式可根据需求自由调整,比如添加更多参数展示、自定义样式等。
内容的提问来源于stack exchange,提问作者Nick Jonas
相关产品推荐
相关产品推荐

