You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为CodeMirror实现带参数的交互式悬浮提示组件?

CodeMirror自定义标记隐藏与悬浮交互实现方案

核心思路

先剥离输入内容中的自定义标记,只保留纯文本显示在编辑器中;同时记录标记包裹文本的位置和参数,通过CodeMirror的状态管理和hover扩展,实现鼠标悬停时显示参数弹窗。

步骤实现

  1. 解析原始内容,提取纯文本与标记元数据
    编写解析函数,匹配自定义标记(比如<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 };
    }
    
  2. 用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
            };
          }
        }));
      }
    });
    
  3. 初始化编辑器
    解析原始内容后,将纯文本传入编辑器,并通过状态副作用把标记数据注入到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:25:35