跳转到内容

树查询

树建成、挂载好之后,程序通过查询把它读回来:谁是根、某个节点有哪些子节点、某个 box 在哪里、某个节点画出了哪些字符、某个缓冲区位置属于哪个节点。大多数查询是纯树导航,对 detached 树与挂载中的树同样可用;位置与区间查询需要布局,缓冲区区间还需要活着的挂载。本章记录全部查询。节点构造建出节点,挂载与生命周期把它们挂起来;渲染模型定义几何查询所报告的坐标系,增量更新与发布解释这些值何时变化。

节点在树中的位置由它的父节点与它在该父节点子序中的位置决定。根没有父节点,其它每个节点恰好有一个;文档序就是深度优先、从左到右的遍历顺序。节点知道自己的根、父节点、经父节点知道的兄弟、以及按序的子节点;查询从不改动树,返回的列表是调用者拥有的新副本。

这里有两套坐标系。几何是布局的坐标系:横向像素、纵向 root 行,相对根的边框盒;它只对已布局的节点存在——已挂载,或用 ebox-render 渲染过(见挂载与生命周期)。缓冲区位置是 Emacs 的坐标系:挂载文本中的字符位置,取决于定位内容的宽度,因此属于某一个具体的挂载。从未布局的节点对几何查询回答 nil;不在活挂载里的节点对缓冲区区间与命中测试回答 nil;只走树的查询——父、子、次序、祖先——两者都不需要。

返回 NODE 所在树的根。

语义。 查询沿父链上溯到顶,所以对 detached 节点返回节点自身。与 ebox-node-parent 一起,它让从任意节点(例如 ebox-node-at 找到的节点)出发的查询能到达拥有它的整棵树。

返回。 根节点。

错误。 无。

参见。 ebox-node-parent、ebox-node-buffer。

返回显示 NODE 的缓冲区,或 nil。

语义。 查询问 NODE 的根由哪个挂载喂给,若该挂载仍活着则返回它的缓冲区。detached 树、渲染过但从未挂载的树、以及挂载释放之后都返回 nil。它回答「这个节点现在在哪里」,供只拿到节点、没拿到缓冲区的代码使用。

返回。 缓冲区,或 nil。

错误。 无。

参见。 ebox-node-root、ebox-buffer-mount。

返回 NODE 的父节点;根或 detached 节点返回 nil。

语义。 父链是树遍历与祖先测试所走的路。被从树上摘下的节点重新报告 nil,之后到达它的查询把它当作根。

返回。 父节点,或 nil。

错误。 无。

参见。 ebox-node-root、ebox-node-children、ebox-node-index。

返回 NODE 的子节点,新列表。

语义。 列表是副本:往里加、从里删、或改动其单元都不影响树。叶子——文本节点或空容器——返回 nil。顺序是文档序,因此结果与布局和绘制使用的顺序一致。

返回。 子节点列表。

错误。 无。

参见。 ebox-node-child、ebox-node-child-count、ebox-node-walk。

返回 NODE 在 INDEX 处的子节点;INDEX 越界时返回 nil。

语义。 INDEX 从零开始;负索引与等于或超过子节点数的索引都返回 nil。它是直接到达已知位置的办法,例如手写表单的第二个子节点。

返回。 子节点,或 nil。

错误。 无。

参见。 ebox-node-children、ebox-node-child-count。

返回 NODE 的子节点数。

语义。 叶子回答 0。计数直接读保留序列,所以只关心数量时比取 ebox-node-children 的长度便宜。

返回。 子节点数。

错误。 无。

参见。 ebox-node-children、ebox-node-child。

返回 NODE 在其父节点子序中的位置;根返回 0。

语义。 索引从零开始,并随结构变化跟随节点:ebox-node-move 之后报告新位置,被摘下之后重新报告 0。对已经拿到手的节点,它是 ebox-node-child 的逆查询。

返回。 索引;根或 detached 节点为 0。

错误。 无。

参见。 ebox-node-child、ebox-node-move。

Function: ebox-node-ancestor-p (ancestor node)

Section titled “Function: ebox-node-ancestor-p (ancestor node)”

ANCESTOR 是 NODE 本身或它的祖先之一时返回非 nil。

语义。 测试包含节点自身,所以「这个 box 是否在那个 panel 里」这类问题中,panel 等于节点本身也算在里面。它从 NODE 沿父链上走,是 overlay 命中测试所做的事在树层面的对应物。

返回。 关系成立时非 nil。

错误。 无。

参见。 ebox-node-parent、ebox-node-root。

NODE 是文本节点时返回非 nil。

语义。 测试针对节点的 tag,不需要布局。它把携带可见字符串的叶子与容器、图像节点区分开,也是遍历给访问过的节点贴标签的方式。

返回。 文本节点时非 nil。

错误。 无。

参见。 ebox-node-walk、ebox-node-children。

按文档序对 NODE 及其后代调用 FUNCTION。

语义。 遍历是前序的:每个节点先于它的子节点被访问,某个节点的整棵子树先于它的下一个兄弟。FUNCTION 每次调用收到一个节点,返回值被忽略。遍历从 NODE 开始而不是从根开始,所以它访问的是一棵子树;由于按树递归,其深度跟随树的嵌套深度。它是通用的「访问一切」查询——收集 tag、数文本节点、对子树施加测试。

返回。 nil。

错误。 遍历本身不报错;FUNCTION 中的错误原样传播。

参见。 ebox-node-children、ebox-node-text-p。

把 NODE 的边框盒返回为 plist (:x :y :width :height)。

语义。 plist 按布局坐标报告边框盒::x 与 :width 是像素,:y 与 :height 是 root 行。节点被布局之后它才可用,而布局发生在渲染或挂载时;在那之前、以及从未布局过的树,回答 nil。文本节点作为自己的布局项布局时报告几何,例如 row 或 column 的直接子节点;行布局由所属 box 的匿名段落拥有的文本节点没有自己的边框盒,回答 nil,它画出的区间改由 ebox-node-bounds 报告。这些值是对当前布局的快照:改变布局的修改会在下次 flush 时反映出来。

返回。 plist;NODE 未布局,或文本节点由匿名段落承载时为 nil。

错误。 无。

示例。

(let* ((root (ebox-build '(column :width (px 40)
(box :height (lh 1) "head")
(box :height (lh 2) "body"))))
(first (ebox-node-child root 0))
(second (ebox-node-child root 1)))
(let ((ebox-viewport-width 40))
(ebox-render root))
(list (ebox-node-geometry first)
(ebox-node-geometry second)))
⇒ ((:x 0 :y 0 :width 40 :height 1) (:x 0 :y 1 :width 40 :height 2))

参见。 ebox-node-root-line、ebox-node-bounds。

返回 NODE 边框盒顶部的 root 行;未布局时返回 nil。

语义。 这是只取纵向位置,值与 ebox-node-geometry 的 :y 相同,只是不构造 plist。用它按纵向位置排序节点,或判断两个节点是否从同一行开始。与几何一样,未布局的节点,以及行布局由匿名段落承载的文本节点,回答 nil。

返回。 root 行号整数,或 nil。

错误。 无。

示例。

(let* ((ebox-viewport-width 40)
(root (ebox-build '(column :width (px 40)
(box :height (lh 1) "head")
(box :height (lh 2) "body"))))
(second (ebox-node-child root 1)))
(ebox-render root)
(ebox-node-root-line second))
⇒ 1

参见。 ebox-node-geometry。

Function: ebox-node-bounds (node &optional all-lines)

Section titled “Function: ebox-node-bounds (node &optional all-lines)”

返回 NODE 第一条绘制行的缓冲区位置 (START . END)。

参数。 NODE 是 box 或文本节点。ALL-LINES 非 nil 时,END 改为 NODE 最后一条绘制行的行尾。

语义。 查询需要活着的挂载:detached 节点、或挂载已释放的节点没有缓冲区位置,回答 nil。文本节点的区间是它第一段被绘制的字符;box 的区间是它第一条边框盒行,从 overlay 未覆盖的第一个单元格到该行上的右缘——可能包含可见字形之后的空白填充。带 ALL-LINES 时,区间覆盖多行 box 的每一条绘制行。第一条行被 overlay 完全盖住的 box 回答 nil,流内 :visibility hidden 的 box 仍报告它的空白槽位,隐藏或未放置的 absolute box 没有区间。这些位置对当前文本有效;之后的 flush 会移动它们,所以要在更新后仍可靠的代码存节点、不存位置。

返回。 (START . END) 点对;NODE 在活挂载中不拥有任何单元格时为 nil。

错误。 无。

示例。

(let ((ebox-viewport-width 40))
(let* ((buffer (ebox-render-to-buffer
"*panel*" '(column :width (px 40)
(box :height (lh 1) "head")
(box :height (lh 2) "body long"))))
(first (ebox-node-child (ebox-buffer-root buffer) 0))
(text (ebox-node-child first 0)))
(list (ebox-node-bounds first) (ebox-node-bounds text)
(ebox-node-bounds first t))))
⇒ ((1 . 6) (1 . 5) (1 . 6))

参见。 ebox-node-position、ebox-node-at、ebox-node-geometry。

返回 NODE 第一个被绘制字符的缓冲区位置,或 nil。

语义。 该值就是 ebox-node-bounds 的 CAR,因此继承那条查询的全部规则:只在活挂载里存在、指向节点绘制的第一个单元格、文本变化时随之移动。

返回。 缓冲区位置,或 nil。

错误。 无。

示例。

(let ((ebox-viewport-width 40))
(let* ((buffer (ebox-render-to-buffer
"*panel*" '(column :width (px 40)
(box :height (lh 1) "head")
(box :height (lh 2) "body long"))))
(first (ebox-node-child (ebox-buffer-root buffer) 0)))
(ebox-node-position first)))
⇒ 1

参见。 ebox-node-bounds、ebox-node-at。

Function: ebox-node-at (&optional position buffer-or-name)

Section titled “Function: ebox-node-at (&optional position buffer-or-name)”

返回在 BUFFER-OR-NAME 中 POSITION 处被绘制的最内层节点。

参数。 POSITION 缺省为 point;BUFFER-OR-NAME 缺省为当前缓冲区。

语义。 查询把缓冲区位置变回用户以为自己点到的那个节点。文本节点内的位置解析到该文本节点;box 内空白单元格——内边距、槽位、填充——上的位置解析到覆盖它的最内层 box,按绘制顺序进行,因此 overlay 压过被它盖住的东西。它是命中测试背后的判定,也是 ebox-node-bounds 的对应物:bounds 把节点变成位置,它把位置变回节点。缓冲区必须持有挂载中的树,答案是当前布局的快照。

返回。 POSITION 处的最内层节点,或可得的最佳匹配;无物覆盖时为 nil。

错误。 缓冲区内的位置无错误;没有挂载的缓冲区返回 nil。

示例。

(let ((ebox-viewport-width 40))
(let* ((buffer (ebox-render-to-buffer
"*panel*" '(box :height (lh 1) "head")))
(root (ebox-buffer-root buffer))
(text (ebox-node-child root 0)))
(eq (ebox-node-at 2 buffer) text)))
⇒ t

参见。 ebox-node-bounds、ebox-node-position、ebox-node-text-p。

结构查询把任意节点变成程序需要的、关于它位置的事实:它属于哪个根(ebox-node-root)、哪个缓冲区在显示它(ebox-node-buffer)、父、子、索引与祖先、是否是文本、以及遍历它的子树的走法。它们是纯树读取——不布局、不挂载、不改动——而新鲜子节点列表意味着调用者可以在树变化时继续持有结果。

几何查询为 box 补上布局事实,区间查询把这些事实接到缓冲区上。ebox-node-geometry 与 ebox-node-root-line 报告 box 在哪里;ebox-node-bounds 报告它画了哪些字符,ebox-node-position 报告区间的起点,ebox-node-at 从位置走回节点。合起来它们支撑常见任务:找到 point 下的 box、把具名节点滚进视野、高亮某一行的文本,或检查一次修改确实移动了它该移动的东西。

树查询便宜且少分配:父与索引是常数时间,子节点访问穿过保留序列、除请求的节点外不多复制,ebox-node-walk 是唯一触碰整棵子树每个节点的查询。它的递归使用 Lisp 栈,所以深度是树的深度。

几何与区间是快照。几何反映最近一次布局;改变它的修改在下次 flush 生效,在那之前读到的是旧值。缓冲区区间与命中测试反映当前文本,而每次更新都可能移动位置:布局挪动行、绘制重写行,某个原本指向节点首字符的位置可能指到别处或指不到任何东西。持有节点、更新后重新查询 bounds 的代码是稳的;跨更新缓存位置的代码不是。

box 区间与文本区间有意不同。文本节点的区间恰好是它画出的字符,止于最后一个字形。box 的第一行区间到达它的右缘,在被拉伸的 box 上会包含空白填充,可能在可见内容之后很远才结束;目标若是「这一行的字符」,查文本节点,或用 box 的 all-lines。overlay 规则跟随绘制:被更高 overlay 完全盖住的 box 不拥有可见单元格、也不报告 bounds;而流内隐藏的 box 仍拥有它的空白槽位并报告它。

查询本身不报错;它们对自己无法描述的状态回答 nil。nil 答案意味着以下之一:没有父节点(根)、没有子节点(叶子)、越界(索引)、未布局(几何、root-line)、不在活挂载中(buffer、bounds、position、at)。在本该给节点的地方传 nil 或非节点是类型错误,不是 ebox-error,所以调用者应当检查自己持有的节点。

恢复办法是重新读。几何为 nil 时,渲染或挂载这棵树,或等把它布局出来的那次 flush。预期可见的节点 bounds 为 nil 时,检查是否有 overlay 盖住它,或挂载是否已经释放。结构或文本更新之后,重新询问查询,而不是复用存下的位置——节点是稳定的句柄,数字不是。