← 返回列表
未验证
在侧边栏直接预览并编辑 Typst 文档
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/14 · 已提供中文文档
在 DeepSeek Harness Web UI 的原生右侧边栏中实时预览 Typst,由按文件运行的 tinymist 预览服务器通过应用源代理进行渲染。
综合分
29.8
GitHub 分
29.8
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add Limbo-137/dsh-typst-preview该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-ui-primitives用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-typst-preview
DeepSeek Harness Web UI 原生右侧边栏里的 Typst 实时预览:在右侧边栏里点开一个 .typ 文件,就是一个带「预览 / 源码」开关的 tab,形态与内置的 Markdown 预览一致。
English: README.md。
它长什么样
右侧边栏 → Files → 点一个 .typ:
- 预览:tinymist preview 渲染的页面,走 WebSocket 推送,改文件即重编译。工具栏三个按钮:重新载入、配色(原始 / 跟随系统 / 反色,记在 localStorage)、在新标签页打开。
- 源码:同一 tab 内切成源码,语法高亮取自 tinymist 自己的语义 token(标题、关键字、函数、字符串、公式、标签、注释,以及 加粗/_斜体_ 标记),带行号、复制,大文件带「加载更多」。配色直接用应用自身的代码块色板(--shiki-),所以 .typ 源码与 Markdown 代码块看起来是一套。取不到高亮时——没有 tinymist、文件超过体积上限、或 highlight: false——退回原生代码渲染器 + 宿主分页读取,与加高亮之前完全一致。
两个面始终挂载,切到源码再切回来不会销毁预览进程:后台仍在编译。但预览文档只为你正在看的那一个 tab 挂载:一个活的预览页就是一整份 WebKit 文档(带编译好的渲染器和 socket),给每个打开的 tab 都留一份正是浏览器标签页涨到几个 GB 的原因。切回某个 tab 会重新加载它的页面(约 1 秒),并且先重新 open 一次:这个 tab 手里的 token 可能属于一个在隐藏期间被回收的实例,而拿失效 token 去请求,过去会换来一张错误页而不是文档。
默认预览面来自类型注册的 priority: 'extension'(高于随包的纯文本 fallback 查看器)。想让 .typ 回到原生文本预览,把 src/client/index.tsx 里 typstTabDefinition() 的 priority 改成 'fallback',再从别处显式 openResource(address, { kind: 'typst-preview' })。
依赖
- DSH >= 0.1.5-rc.1:本插件在原生右侧边栏上注册 tab 类型(ctx.sidebarRightTabs),正文挂进 sidebar.right.pane.tab 席位。
- tinymist:先查 PATH,再查 ~/.local/bin、/opt/homebrew/bin、/usr/local/bin、~/.cargo/bin;用 tinymistPath 可覆盖。
- 本插件的前身(~/.dsh/plugins/dsh-typst-preview,挂在 dsh-better-sidebar 的文件查看器上)与 0.1.5 不兼容,已被本插件取代。
权限、依赖与失败边界
直说,因为这个插件要驱动外部编译器,而 DSH STORE(合理地)拒绝替作者猜:它是高权限插件,所以在商城里是 user-reviewed——由商城把变更摆给你看、你逐次确认——而不是自动放行。
| 能力 | 确切边界 |
|---|---|
| 文件 | 只读你打开过的那些 .typ,不碰其它。从不写入:源码面是只读的,改文件只可能来自 agent 自己的工具。 |
| 网络 | 无对外流量、无遥测。每个请求要么同源(应用自己主机上的 /api/typst-preview/),要么是回环到本插件启动的 tinymist 进程。 |
| 命令 | 只 spawn 本机 tinymist(preview,以及源码高亮用的 lsp),argv 显式给出;不经 shell、不远程安装、不调用其它可执行文件。tinymistPath 可换二进制。 |
| 凭据 | 无。不读任何 token/key/cookie;唯一的"环境"用途是定位 tinymist 与用户家目录。 |
| 生命周期脚本 | 没有 preinstall/install/postinstall/prepare,安装与更新时不执行任何代码(这也是 lib/ 入库的原因)。 |
外部依赖:tinymist 需在 PATH(或用 tinymistPath 指定),实测版本 v0.15.0-rc1。
缺东西或出错时:没有 tinymist → 预览面报错、源码面退回纯文本;文件超过 highlightMaxBytes(4 MiB)→ 不高亮但仍可读;预览进程崩溃/被杀 → 回收器清理,下次按需重启;进程数有上限(maxInstances,外加两倍硬闸),闲置与游离的子进程都会被回收,所以丢掉的子进程活不过启动它的那个 tab。
声明的兼容范围:Node >=22、DSH >=0.1.5-rc.1 /…,外加每实例一条精确升级路由 GET /api/typst-preview/ws/。同源围栏:拒绝 sec-fetch-site: cross-site 与 Origin 主机与 Host 不一致的请求。 |
| src/host/tinymist.ts | 每个(会话 × 绝对路径 × 配色)一个预览进程;端口对用「同时 bind 0 再释放」的办法挑,避开 tinymist 默认固定端口 23625/23626;按 typst.toml → 会话 workspace(当它包含该文件)→ 文件目录的顺序定根;就绪后才向 tab 报成功。 |
| src/host/highlight.ts | 用 tinymist lsp 做 Typst 语法高亮:把 semanticTokens/full 的答案解成逐行的 [start, end, classIndex, styleBits] 游程(UTF-16 偏移),浏览器端只负责画 span,不用往包里塞语法文件。每个项目根一个语言服务器,文件按内容哈希缓存,所以翻页不再请求 token;位置编码是被断言为 UTF-16 而不是假设的——游程索引的是 JS 字符串,UTF-8 偏移会把中文行切错字。应用自带的静态高亮器做不了这件事:它的 shiki 只带一张固定语法表,里面没有 Typst。 |
| src/host/proxy.ts | HTTP 透传(前缀剥离,强制 accept-encoding: identity 以保证改写 HTML 安全),只改页面里一处:new URL("/", window.location.href)——那是页面里唯一的绝对地址——改写成该实例的 WebSocket 路由。WebSocket 握手与帧双向原样中继。 |
| src/client/index.tsx(浏览器) | 两阶段注册(类型进 ctx.sidebarRightTabs,正文进 keyed 席位 sidebar.right.pane.tab)。源码面先问 POST …/source,取不到高亮时退回 ctx.remote.workspaceFiles.read(分页纯文本)+ 自带代码渲染器,所以这一面永远有内容可看。token 类名映射到外壳自己的 --shiki- 变量,明暗两套主题都由应用提供。文案中英双语。 |
因为宿主半与浏览器半都通过 DSH 自己的 origin 访问 tinymist,本机、局域网、隧道访问都能用,不需要额外暴露端口。
一个写这类插件要记住的 DSH 细节:workspaceFiles.read 这类 Remote 方法返回的是结果信封({ ok: true, value } / { ok: false, error }),不是裸 payload;浏览器半在 inject face 里拆封,并把失败抛成异常。
验证
两个脚本都对着真 tinymist 跑,不 mock:
宿主半:起 apply() → 假 web server(同 exact/prefix/upgrade 派发语义)→ 真进程
node scripts/smoke.mjs
open 起进程 / 页面代理且 WS 地址被重写 / WebSocket 中继收到真实帧 /
重复 open 复用实例 / source 返回文本 + token 游程(含分页窗口与失败回退)/
close 回收 / 未知页面 token 回一张可读的页面而不是 JSON / 跨站请求被拒 —— 20/20
宿主半:预览进程池 vs 操作系统进程表
node scripts/leak-check.mjs
同一文件并发两次 open 共享同一 token 与同一个子进程 / LRU 淘汰真的杀掉了进程 /
从实例表里被移除的子进程仍可 close、仍被计入 / 回收器收走游离子进程、
但放过仍持有 socket 的预览 / dispose 之后一个不剩 —— 15/15
浏览器半:按外壳的方式加载构建好的客户端包,用 React 静态渲染真高亮结果
node scripts/render-check.mjs
真 tinymist 页面 → 每行一行、带行号;#set 是 keyword span;加粗* 带 style 位;
中文注释逐字对上;文件每一行逐字符还原 —— 8/8
浏览器半:无头 Chrome + CDP 驱动一个跑着的 DSH Web
printf '= Probe\nHello $x^2$\n' > /path/to/workspace/probe-typst.typ
node scripts/gui-check.mjs "http://127.0.0.1:3080/?token=…" probe-typst.typ
客户端包被加载、右侧栏打开、Files 里点 .typ 落到本插件的 tab、
iframe 指向 /api/typst-preview/p//、宿主返回带重写 WS 的真页面、
源码面读到文本、切回预览 iframe 仍在、无 typst 相关 console 错误 —— 14/14
gui-check.mjs 会在失败时打印诊断(面板文本、只读快照 globalThis.__dshTypstDebug、一次直接 open 的往返结果)。
已知边界
- 只读:源码面是查看器,不是编辑器;编辑仍由 agent 的写入工具或外部编辑器完成,预览会自动跟上。
- 右侧栏 tab 状态只在内存:刷新后回到折叠态(这是原生侧边栏本身的行为)。
- 每个文件一个 tinymist 进程:上限 maxInstances,超出按最近使用淘汰;端口由宿主挑,不占固定 23625。
- --invert-colors 的 auto 由 tinymist 自己解释;DSH 深色主题下若不合意,用工具栏切到「反色」。
- 只认 dsh-resource://file/** 里的 .typ(大小写不敏感);工作区外的文件走 absolute 地址同样可用。
许可
MIT扫码进群