views — 视图模型与视图 API

创建并管理插件的原生视图模型(ViewModel)与类型化视图。

self.views 提供视图(View)的创建与管理能力。一个视图对应一个在 IDE 中渲染的实例,插件通过 ViewModel 同步其数据模型。

view = self.views.create("my_view")
view.open()

常量与协议限制

每次快照、每个补丁事务以及整个视图负载都有硬性上限,超过上限会在发送到宿主之前抛出异常。

常量说明
MAX_SNAPSHOT_NODES20_000单次快照最多节点数
MAX_PATCH_OPS2_000单个补丁最多操作数
MAX_VIEW_PAYLOAD_BYTES2MB单个快照/补丁负载的最大序列化大小

所有越界操作会在本地抛出 ViewProtocolError,不会发送到宿主。常见触发场景:视图已关闭、快照超过节点数上限、单次补丁负载超过字节上限。

创建视图模型

create(view_id, instance_id=None) -> ViewModel

创建一个本地视图模型。view_id 必须是插件贡献的视图 ID。

instance_id 标识视图的具体挂载实例,宿主按 (view_id, instance_id) 定位要渲染的模型,两边必须一致:

  • 不传(侧边栏场景):SDK 从 manifest 读取该视图声明的 container,默认成 container:<containerId>,与宿主侧边栏的挂载点对齐。绝大多数 UI 插件直接调用 self.views.create(view_id).open() 即可。
  • 显式传入(标签页等场景):使用宿主分配的实例 ID,例如 self.tabs.create_view() 回调里拿到的 instance.instance_id(形如 tab:<n>)。显式值始终优先。
  • 兜底:当视图不在 manifest 中,或以 standalone 方式运行(无宿主上下文)时,回退为随机生成。

若手动传入的 instance_id 与宿主实际挂载的实例不一致,快照会被送到一个无人渲染的实例,视图会一直停在加载指示器上。侧边栏视图通常直接省略该参数,交给 SDK 默认对齐。

# 侧边栏:省略 instance_id,自动对齐到 container:<containerId>
view = self.views.create("dashboard")
view.open()

revision 与同步模型

ViewModel 追踪 revision。首次发送为完整快照,之后为增量补丁;同一时刻最多有一个补丁在途,等待确认期间产生的操作会合并进下一个补丁,因此快速生产者不会超出宿主处理速度。

视图模型的两类形态

ViewModel 承载的数据节点决定视图在 IDE 中的渲染路径,详见 UI 插件开发

  • 纯数据节点:快照中的节点是普通字典(行、树节点、日志条目)。宿主根据 manifest 的 renderernative.tree 等)选择对应渲染器绘制。
  • 组件树:快照中的第一个节点是一个 Component(或其 to_json() 结果,或带 component 映射的字典)。宿主直接渲染这棵组件树,忽略 renderer
# 组件树形态
view.snapshot([
    Column(
        Text("Hello"),
        Button(id="go", label="提交", on_press=lambda e: print("pressed")),
    ),
])

两类形态互斥,以第一个节点为准。混用会产生未定义行为。

同步协议

ViewModel 与宿主之间使用快照 + 增量补丁 + 修订号协议(sdk.view.snapshot / sdk.view.patch / ide.view.ack / ide.view.nack / ide.view.resync):

插件                              IDE
 │  snapshot(revision=1)          │
 │ ───────────────────────────────>│  首次:完整快照
 │  patch(baseRevision=1→2)        │
 │ ───────────────────────────────>│  增量补丁
 │<─────────────────────────────── ide.view.ack(revision=2)
 │  patch(baseRevision=2→3)        │
 │ ───────────────────────────────>│
 │<─────────────────────────────── ide.view.ack(revision=3)
 │  ...若宿主发现修订号断层/异常...│
 │<─────────────────────────────── ide.view.nack / ide.view.resync
 │  snapshot(重发完整快照)          │
 │ ───────────────────────────────>│
  • insert / update / remove / move 默认立即入队,batch() 内操作合并为一个补丁事务发送。
  • 每次补丁携带 baseRevision 与目标 revision;宿主确认后 revision 前移。
  • 同一时刻最多有一个补丁在途。等待确认时产生的操作继续排队,并在确认后合并到下一批补丁。
  • revisionGap 表示 baseRevision 与宿主当前修订不一致,noSnapshot 表示该实例没有快照。这两种拒绝会产生 ide.view.nack,随后宿主发送 ide.view.resync,SDK 重发完整快照。
  • invalidOperationclosed 只产生 ide.view.nack。SDK 收到补丁错误响应或 nack 后也会用当前本地模型执行 resync(),使两端重新收敛。
  • 性能保护返回 delivery_paused 时,SDK 保留已经应用到本地模型的状态,清除在途标记并等待宿主恢复。宿主恢复投递时发送 ide.view.resync,SDK 再提交完整快照。
  • 视图隐藏时,变更只应用到本地模型并设置 _needs_snapshot。恢复可见后发送一份新快照,不逐条补发隐藏期间的操作。

snapshot() 在视图已关闭、节点数超过 MAX_SNAPSHOT_NODES 或负载超过 MAX_VIEW_PAYLOAD_BYTES 时,会在发送前本地抛出 ViewProtocolError。补丁负载同样受 MAX_VIEW_PAYLOAD_BYTES 限制,超限时也会抛 ViewProtocolError

ViewModel 属性

属性说明
view_id视图 ID
instance_id实例 ID
revision当前已确认的修订号
nodes当前模型节点列表(副本)
visible视图当前是否可见
has_in_flight是否有补丁在途
pending_count排队等待发送的操作数
route当前路由
route_stack路由栈(副本)
route_params当前路由参数(副本)

生命周期

方法说明
open(callback=None)打开视图
close(callback=None)关闭视图
snapshot(nodes, revision=None, callback=None)发送完整快照并重置补丁状态
view.open()
view.snapshot([
    {"id": "row_1", "label": "Hello"},
    {"id": "row_2", "label": "World"},
])

数据变更

方法说明
insert(id, data, index=None)插入节点
update(id, data)更新节点
remove(id)删除节点
move(id, index)移动节点到指定索引
batch()上下文管理器,将内部操作合并为单个补丁事务
flush()立即发送排队操作(除非已有补丁在途),返回是否已发送
with view.batch():
    view.insert("a", {"label": "A"})
    view.insert("b", {"label": "B"})
    view.move("a", 1)

batch() 中的操作在退出时作为一个补丁事务发送。当补丁在途时,操作会保持排队,并在确认后合并进下一个补丁。

事件与组件调用

方法说明
on_event(component_id, event, handler)注册组件事件处理函数,返回 handler
off_event(component_id, event)注销组件事件处理函数
dispatch_event(component_id, event, payload)分发事件,返回是否有处理函数执行
invoke_component(component_id, method, arguments=None, callback=None)调用已挂载组件的类型化操作

可见性与路由

方法说明
on_visibility(handler)注册可见性变化回调(handler(visible)
on_route(handler)注册路由同步回调,handler 接收 (route, params)
push_route(route, params=None, callback=None)入栈路由
replace_route(route, params=None, callback=None)替换当前路由
goto_route(route, params=None, callback=None)跳转到指定路由
pop_route(callback=None)弹出当前路由

路由以实例为单位:宿主持有权威路由栈,并通过路由同步帧推回,本地属性只是最近一次同步的镜像。

类型化视图 Facade

self.views 还提供类型化视图的快速入口,返回对应的 RendererView 子类(见 native_views):

方法说明
outline(view_id, title="Outline", actions=())大纲视图
tree(view_id, title="Tree", actions=(), expanded_ids=None, selected_id=None, indent=None, searchable=None, empty_label=None)树形视图
virtual_list(view_id, title="List", actions=(), item_count=None, item_height=None, selected_id=None, empty_label=None)虚拟列表
table(view_id, title="Table", columns=(), actions=(), row_count=None, row_height=None, show_header=None, selected_id=None, sort_column=None, sort_ascending=None, empty_label=None)表格视图
form(view_id, title="Form", actions=())表单视图
markdown(view_id, title="Markdown", actions=())Markdown 视图
log(view_id, title="Log", actions=(), item_height=None, empty_label=None)日志视图
variable_inspector(view_id, title="Device Variables", actions=())变量检查器
tree = self.views.tree("file_tree", searchable=True)
log = self.views.log("console", empty_label="暂无输出")

RendererView.set_items() 使用节点 id 计算带顺序的增量差异。同一个业务对象在连续刷新中必须保持相同 ID;重复 ID 或随刷新变化的 ID 会破坏选择、展开状态和补丁定位。事件回调接收对应的类型化节点,因此可直接访问 node.idnode.label 等属性。

Facade 在普通业务节点之外还会生成带语义角色的协议节点。appBarActionappBarMenu 描述标题栏操作,placeholder 描述空状态,viewConfig 携带渲染器配置,contextMenuProvider 表示视图支持动态上下文菜单。IDE 渲染器必须先识别这些角色,再处理普通数据节点;内部开发时不能把它们当作用户数据过滤掉。

查找视图模型

get(instance_id, view_id=None) -> ViewModel?

按实例 ID 查找已创建的视图模型;传入 view_id 时按 (view_id, instance_id) 精确匹配,否则要求实例 ID 唯一。

model = self.views.get(instance_id, view_id="dashboard")

侧边栏挂载点使用 container:<containerId>。标签页由宿主创建,回调返回形如 tab:<n> 的实例 ID;插件必须使用该 ID 创建或查找模型。view_id 相同但 instance_id 不同的视图拥有独立的修订号、路由栈、可见性和组件状态。

使用示例

class MyPlugin(UiPlugin):
    def on_start(self):
        self.view = self.views.create("my_view")
        self.view.open()
        self.view.snapshot([
            {"id": "greeting", "label": "PyriteSDK"},
        ])
        self.view.on_visibility(self._on_visibility)

    def _on_visibility(self, visible):
        print(f"视图可见性: {visible}")

实现位置

  • SDK src/pyrite_sdk/api/view.py 维护本地模型、修订号、在途补丁、路由和恢复状态。
  • SDK src/pyrite_sdk/api/native_views.py 实现类型化节点、稳定 ID 差异和渲染器协议角色。
  • IDE lib/core/sdk/view_model_store.dart 以插件、会话和视图实例为键保存权威模型,并以事务方式应用补丁。
  • IDE lib/core/sdk/api/view_api.dart 校验消息、发送 ack、nack 与 resync。
  • IDE lib/features/plugin_view/ 包含视图表面、渲染器注册表和通用组件构建器。

On this page