资讯动态

Code Hike 实战:用浏览器 Selection API 实现代码 Token 的 Occurrences 高亮

发布时间:2026/10/13 12:29:08 来源:尧图企业网站定制
前端开发工具【免费下载链接】codehikeBuild rich content websites with Markdown and React项目地址https://gitcode.com/gh_mirrors/co/codehike点击查看免费下载导读在网页上阅读代码时我们常常怀念代码编辑器的能力选中一个标识符所有同名出现处被同时高亮。Code Hike 的 occurrences 演示demo 说明文档实现了这一体验——不仅代码块内选中 Token 可以高亮全部出现位置从页面正文段落中选中一个单词例如button或Carousel也能联动高亮代码里的同名 Token。本文将逐层拆解该 demo 的实现原理并深入到 codehike 的 Token 渲染管线说明为什么这套方案只需几十行代码即可落地以及如何扩展出多代码块、正则匹配等更强大的能力。一、功能定义像代码编辑器一样的选中高亮原 demo 文档用一句话点明了它的动机One thing I miss while reading code on websites is the ability to highlight all occurrences of a particular piece of code. This works similar to a code editor, you can select a token and all occurrences of that token will be highlighted.也就是说这个 demo 的目标不是 Code Hike 的注释体系!mark、!focus等而是运行时、由用户交互驱动的动态高亮用户用鼠标选中任意 Token页面自动把代码中所有文本相同的 Token 标亮交互模型与 VS Code 等编辑器的Highlight Occurrences完全一致。demo 页面使用了一段典型的 React 组件代码作为演示素材use client import { useState } from react import { Carousel } from acme-carousel export default function Gallery() { let [isOpen, setIsOpen] useState(false) return ( div button onClick{() setIsOpen(true)}Show/button {isOpen Carousel /} /div ) }选中这段代码中的任意标识符如isOpen、setIsOpen、Carousel所有同名出现处都会高亮。更妙的是它同样支持从代码块之外的普通段落文字中选择单词——demo 文档特意提示读者try: button orCarousel因为正文里恰好出现了这两个词选中它们同样能高亮代码中的对应 Token。这是因为实现基于document.getSelection()全局选区选区并不限定在pre内部。二、页面装配MDX 到高亮代码的管线demo 由三个文件构成职责清晰content.mdMarkdown 内容与演示代码块本体page.tsx服务端装配负责把 Markdown 中的代码块高亮成HighlightedCodecode.tsx客户端交互组件CodeWithOccurrences承载全部选中-高亮逻辑。页面入口page.tsx展示了 Code Hike 的标准两段式用法import { RawCode, Pre, highlight } from codehike/code import Content from ./content.md import { CodeWithOccurrences } from ./code export default function Page() { return Content components{{ Code }} / } async function Code({ codeblock }: { codeblock: RawCode }) { const info await highlight(codeblock, github-dark) return CodeWithOccurrences code{info} / }关键点MDX 编译Content是从content.md导入的编译产物代码块会被解析成RawCode{ value, lang, meta }见 types.ts并通过components{{ Code }}映射到自定义渲染组件高亮highlight(codeblock, github-dark)调用 highlight.ts 中的异步函数经code-hike/lighter生成带主题色的 Token 序列产出HighlightedCode包含tokens、annotations、style、themeName等字段渲染CodeWithOccurrences把HighlightedCode交给 Code Hike 的Pre组件渲染。由此可以看出occurrences 功能建立在 Code Hike 的Token 即叶子span这一渲染结构之上——这正是下一节要剖析的核心。三、核心实现几十行代码完成选中即高亮交互逻辑全部集中在 code.tsx。我们先看完整源码再逐段拆解use client import { HighlightedCode, Pre } from codehike/code import React from react export function CodeWithOccurrences({ code }: { code: HighlightedCode }) { const ref React.useRefHTMLPreElement(null) React.useEffect(() { const handler: EventListener (e) { const selected document.getSelection()!.toString().trim() ref.current!.querySelectorAll(span:not(:has(*))).forEach((element) { if (element.textContent selected) { element.setAttribute(data-selected, true) } else { element.removeAttribute(data-selected) } }) } document.addEventListener(selectionchange, handler) return () { document.removeEventListener(selectionchange, handler) } }, []) return ( Pre ref{ref} code{code} // components2{{ Token }} / ) }3.1 拿到pre的引用Pre组件本身是forwardRef实现的见 pre.tsx所以ref会被正确转发到真实的preDOM 节点最终由 inner.tsx 中的InnerPre渲染pre {...props} ref{_ref}>const selected document.getSelection()!.toString().trim()document.getSelection()是全局选区对象因此从段落文字中选中的单词同样能参与匹配——这正是 demo 文档所说select a word from a paragraph能生效的原因。toString()拿到纯文本后trim()去掉首尾空白防止选区边缘带入多余空格导致匹配失败。3.4 遍历叶子 Token 并打标记ref.current!.querySelectorAll(span:not(:has(*))).forEach((element) { if (element.textContent selected) { element.setAttribute(data-selected, true) } else { element.removeAttribute(data-selected) } })span:not(:has(*))选择所有没有子元素的span——在 Code Hike 的渲染结构中这正是高亮后的最小 Token 单位下文 3.5 有源码佐证用element.textContent selected做严格文本相等比较命中则写入data-selectedtrue未命中则移除实现每次选区变化后全量重算全量重算虽然简单直接但在超大代码块中会有遍历开销从代码结构看若追求性能可以改为先收集匹配集合再统一更新不过 demo 场景下现有实现已足够。3.5 为什么叶子 span就是 Token这是整套方案成立的地基。Code Hike 的高亮管线保证每个语法 Token 渲染为一个独立span证据链如下Token 数据模型Token [string, Color?, React.CSSProperties?]见 types.ts每个 Token 携带文本、颜色与样式高亮阶段highlight.ts 中splitWhitespace/joinWhitespace会把 Token 与周围空白含换行分离空白直接以字符串形式存在于tokens数组不生成 span渲染阶段inner.tsx 的InnerToken最终输出span {...props}{value}/span而 inline.tsx 中空白字符串直接作为文本节点输出。因此 DOM 结构里最内层、不含子元素的 span恰好一一对应语法 Token。选中isOpen时遍历到的所有textContent isOpen的叶子 span 就是它的全部 occurrences——包括字符串字面量中的同名文本。若想更精确比如只匹配标识符、不匹配注释和字符串可以在比较时叠加语言语义判断但那是后话。四、高亮样式data 属性与自定义 Token 组件的两条路线4.1 路线 Adata 属性 CSSdemo 采用的方式实现只负责写data-selected属性样式完全交给 CSS。配合 Tailwind 的 data 变体一行即可完成视觉反馈/* 普通 CSS */ span[data-selected] { background-color: rgba(59, 130, 246, 0.4); border-radius: 4px; } /* 或 Tailwind 变体写法 */>// const Token: TokenComponent ({ value, lineNumber, ...props }) { // return ( // span // {...props} // classNamedata-[selected]:bg-blue-500/40>赞分享前端开发工具【免费下载链接】codehikeBuild rich content websites with Markdown and React项目地址https://gitcode.com/gh_mirrors/co/codehike点击查看免费下载相关推荐Code Hike 中 Mark 代码高亮注释Annotation的完整实战指南Code Hike 中 Mark 代码高亮注释Annotation的完整实战指南 导读 本文围绕 Code Hike 官方 Demo 中的 mark 注释前端开发工具Code Hike 深入指南用 token-transitions 实现代码块切换的 Token 级平滑过渡Code Hike 深入指南用 token transitions 实现代码块切换的 Token 级平滑过渡 Code Hike 仓库根目录 https:/前端开发工具Code Hike 幻灯片布局实战用 !!steps 与 Selection 构建可交互的多步代码演示Code Hike 幻灯片布局实战用 !!steps 与 Selection 构建可交互的多步代码演示 本文以 Code Hike 仓库中 slideshow前端开发工具上一篇XXMI启动器一站式游戏模型管理工具告别多游戏切换烦恼下一篇如何一键永久备份你的QQ空间青春记忆GetQzonehistory完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑