zh CN Structural Query - chiba233/yume-dsl-token-walker GitHub Wiki

结构查询

解释 API | Lint | 首页

StructuralNode[] 树里搜索和定位节点。这些工具操作的是 parseStructural 生成的结构树,不是 TextToken[]

什么时候用:

  • 按名字或位置找一个特定标签
  • 收集某种类型的所有标签
  • 带 parent/depth 上下文遍历整棵树
  • 在编辑器里判断"光标在哪个标签里面"

Demo:编辑器"光标在哪个标签里"

最常见的实战场景——给定源码中的光标偏移量,找出包围它的标签:

import {parseStructural} from "yume-dsl-rich-text";
import {enclosingNode} from "yume-dsl-token-walker";

const source = "Hello $$bold($$italic(world)$$)$$!";
const tree = parseStructural(source, {trackPositions: true});

// 用户的光标在 offset 22——"world" 里面
const tag = enclosingNode(tree, 22);

if (tag) {
    console.log(tag.type); // "inline"
    console.log(tag.tag);  // "italic"  ← 最深的包围标签
}

enclosingNode 跳过 text/escape/separator 节点,只返回标签节点(inline / raw / block)——你可以直接访问 .tag.children.args,不需要额外的类型守卫。 当上游启用 implicitInlineShorthand 时,它还会跳过标记为 implicitInlineShorthand: true 的 inline 节点,让光标命中优先落到可独立切片的外层标签。


findFirst——第一个匹配

深度优先先序搜索。返回第一个让 predicate 返回 true 的节点,或 undefined。找到后立即停止(早退出)。

import {parseStructural} from "yume-dsl-rich-text";
import {findFirst} from "yume-dsl-token-walker";

const tree = parseStructural("Hello $$bold($$italic(world)$$)$$");

const italic = findFirst(tree, (node) =>
    node.type === "inline" && node.tag === "italic",
);
// italic.type === "inline", italic.tag === "italic"

predicate 接收 (node, ctx),其中 ctx 包含 parentdepthindex

// 找 bold 标签里面的第一个文本节点
const textInBold = findFirst(tree, (node, ctx) =>
    node.type === "text" &&
    ctx.parent?.type === "inline" &&
    (ctx.parent as { tag: string }).tag === "bold",
);

findAll——所有匹配

遍历顺序和 findFirst 一样,但收集所有匹配:

import {parseStructural} from "yume-dsl-rich-text";
import {findAll} from "yume-dsl-token-walker";

const tree = parseStructural("$$bold(a)$$ then $$bold(b)$$");
const bolds = findAll(tree, (node) =>
    node.type === "inline" && node.tag === "bold",
);
// bolds.length === 2

nodeAtOffset——最深节点定位

找到源码 span 包含给定偏移量的最深(最具体的)节点。需要 trackPositions: true

import {parseStructural} from "yume-dsl-rich-text";
import {nodeAtOffset} from "yume-dsl-token-walker";

const input = "Hello $$bold(world)$$";
const tree = parseStructural(input, {trackPositions: true});

const node = nodeAtOffset(tree, 14); // offset 14 在 "world" 里面
// node.type === "text", node.value === "world"

没有节点包含该 offset 或没有追踪位置时返回 undefined

enclosingNode 的区别

nodeAtOffset enclosingNode
返回 任何节点(text、escape、separator、inline、raw、block) 只返回标签节点(inline、raw、block)
用途 "这个 offset 处是什么东西?" "这个 offset 被哪个标签包围?"
返回类型 StructuralNode | undefined StructuralTagNode | undefined

nodePathAtOffset——完整路径

返回从最外层到最深命中节点的完整路径(StructuralNode[])。最后一个元素和 nodeAtOffset 的返回值相同。

适用于编辑器面包屑、上下文补全、嵌套层级显示。

import {parseStructural} from "yume-dsl-rich-text";
import {nodePathAtOffset} from "yume-dsl-token-walker";

const source = "Hello $$bold($$italic(world)$$)$$!";
const tree = parseStructural(source, {trackPositions: true});

const path = nodePathAtOffset(tree, 22);
// path[0].type === "inline", path[0].tag === "bold"   — 最外层
// path[1].type === "inline", path[1].tag === "italic"  — 中间层
// path[2].type === "text",   path[2].value === "world" — 最深层

没有节点包含该偏移量或缺少 position 时,返回空数组。


enclosingNode——最深标签定位

找到包围给定偏移量的最深标签节点(inline / raw / block)。跳过 text、escape、separator。 同时跳过 implicitInlineShorthand === true 的 inline 节点。

返回类型是 StructuralTagNode | undefined——已经收窄过了,.tag.children.args 直接可用,不需要类型守卫。

import {parseStructural} from "yume-dsl-rich-text";
import {enclosingNode} from "yume-dsl-token-walker";

const input = "Hello $$bold(world)$$";
const tree = parseStructural(input, {trackPositions: true});

const tag = enclosingNode(tree, 14);
if (tag) {
    console.log(tag.tag); // "bold"
    // tag.children 直接可用——不需要类型守卫
}

offset 不在任何标签内部或没有追踪位置时返回 undefined

offset 语义: offset 必须是传给 parseStructural原始源码文本的字符串索引——不是渲染后、print 后、或显示文本的索引。 nodeAtOffset 也是如此。


walkStructural——遍历每个节点

纯副作用遍历。和 findFirst/findAll 不同,它不收集也不返回任何东西——当你需要检查每个节点的完整上下文时使用。

import {parseStructural} from "yume-dsl-rich-text";
import {walkStructural} from "yume-dsl-token-walker";

const tree = parseStructural("$$bold(hello $$italic(world)$$)$$");

walkStructural(tree, (node, ctx) => {
    console.log(`${"  ".repeat(ctx.depth)}${node.type}`);
});
// inline
//   text
//   inline
//     text

StructuralVisitContext

每个 predicate 和 visitor 回调都会收到这个上下文:

interface StructuralVisitContext {
    parent: StructuralNode | null;  // 顶层节点为 null
    depth: number;                   // 顶层为 0
    index: number;                   // 在 parent 的子数组中的位置
}

子节点遍历规则

所有查询函数共享同一个 DFS 引擎。哪些子节点会被遍历取决于节点类型:

节点类型 遍历的子节点
text
escape
separator
inline children
raw args(content 是原始字符串,不遍历)
block args,然后 children

这意味着 findFirst/findAll/walkStructural 能找到 raw/block 标签的 args 里的节点,但不会进入 raw 的 content 字符串。


类型参考

类型 说明
StructuralPredicate (node: StructuralNode, ctx: StructuralVisitContext) => boolean
StructuralVisitor (node: StructuralNode, ctx: StructuralVisitContext) => void
StructuralTagNode Extract<StructuralNode, { type: "inline" | "raw" | "block" }>
StructuralVisitContext { parent, depth, index }