FrameSeq

Visual Studio Code 扩展#

FrameSeq 扩展给 VS Code 加上了理解演示结构的导航和命令,同时不替换 FrameSeq 的渲染器。

当前功能#

  • 活动栏里的 FrameSeq 视图,按源码顺序列出每个 slide() 及其命名 at() 区域。
  • 跟随编辑器光标的 Current Slide 检查器,用活动栏下半部分展开区域和带标签的对象导航,而不是留下空白。
  • 显示页面标签、布局、对象数量和备注标记。
  • 从大纲条目或布局问题一键跳到对应的 slide() 调用。
  • 左边 slides.ts、右边实时预览的分屏工作区。
  • 大纲与预览联动,以及当前页/上一页/下一页命令。
  • 状态栏显示光标所在的页面和当前创作区域。
  • 区域感知编辑:在当前页的命名区域间跳转,或把选中的内容行绑定成新的可整体移动区域。
  • 插入新页的命令,以及常见 FrameSeq 结构的 TypeScript 代码片段。
  • 实时预览的启动与停止控制。
  • 布局检查结果显示在 VS Code 的 Problems 面板。
  • Slides 视图工具栏上的导出按钮,可选 HTML、PDF、PPTX 和可编辑 Typst。
  • 在预览里 Alt+点击,把光标移到画出该对象的那条命令上。
  • 在预览里拖拽,改变命令写下的坐标、或对象在邻居之间的位置,作为一次可撤销的编辑。

扩展通过 frameseq inspect --json 读取大纲,并调用项目里已安装的 FrameSeq CLI 来预览、校验和导出。它不会再打包一份渲染运行时。

本地构建与安装#

在 FrameSeq 仓库里:

npm install
npm run vscode:package

打包好的扩展写入:

output/vscode/frameseq-vscode.vsix

在 VS Code 扩展视图里用 Install from VSIX... 安装,或者用命令行:

code --install-extension output/vscode/frameseq-vscode.vsix

安装之后打开一个 FrameSeq 项目。该项目必须已经装好 npm 依赖,扩展才能调用本地的 frameseq 可执行文件。

运行 FrameSeq: Preview 会把入口文档留在第一个编辑器组,并在旁边打开预览。如果你更希望预览出现在当前编辑器组里,把 frameseq.previewBeside 设为 false。在 FrameSeq 大纲里选中某一页会同时更新源码选区和实时预览,这个行为由 frameseq.followOutline 控制。

frameseq.previewBeside 只决定新预览第一次创建在哪里。预览已经存在后,从 Slides 大纲切页会保留它当前的编辑器组:如果你把预览移到源码所在组、作为另一个标签页,它会继续留在标签页;如果原来是左右分屏,也会继续保持分屏。激活预览标签时,Current Slide 也会继续显示正在预览的页面,不再退回空白欢迎提示。

从大纲切页时,已有预览会一直保持可见。若源码本来就在另一栏可见,只会静默移动其中的光标;若 slides.ts 是藏在预览后面的同组标签页,扩展不会先激活它。因此点击每张 Slide 时不再出现“源码闪一下,再回预览”的过程。

预览当前显示页面的 slide() 调用会带有持续的源码标记:整行底色、左侧强调线、滚动条刻度,以及 Previewing slide N 行尾标签。它不依赖 VS Code 很淡的非活动选区,所以焦点留在预览时也很醒目。保存或刷新后会按最新源码重新计算行号,在页面前方增删代码也不会让标记留在旧位置。

预览页面始终保持演示文稿配置的比例(16:94:3 或显式画布尺寸)。狭窄或纵向的编辑器窗格会在页面周围留下舞台空间,而不会把圆角外框拉伸到整个窗格。固定画布随后在该外框内等比缩放,所以布局编辑和导出坐标仍使用文档的原生画布单位。

将鼠标放在交互式预览上并按住 Ctrl(macOS 为 Command)滚动即可缩放画布。底部会显示真实比例,并提供 +Fit1:1Fit 会随编辑器窗格尺寸自动适应,1:1 会恢复画布原生的 100% 尺寸;Ctrl/Command+0 同样可以恢复 100%。预览缩放不会修改源码或导出尺寸。

入口文件的选择#

优先使用当前活动的 slides.ts*.slides.ts 编辑器,其次是在其他编辑器组里可见的幻灯片编辑器。否则扩展使用 frameseq.entry 设置里的路径,该路径可以相对于工作区文件夹,也可以是绝对路径:

{
  "frameseq.entry": "decks/kickoff/slides.ts"
}

如果该文件不存在,扩展会在整个工作区里搜索 slides.ts*.slides.ts,并跳过 node_modulesdistouttmpoutput。因此放在子目录里的演示文稿无需任何配置。若存在多个演示文稿,离当前编辑文件最近的优先,其次是路径最浅的。

运行 FrameSeq: Select Entry(命令面板、Slides 视图标题菜单,或空视图里的链接)可以自己选择演示文稿。选择器会列出找到的全部演示文稿,标注当前正在使用的那个,并按工作区记住这次选择:在你用 Follow the active editor 清除之前,它的优先级高于活动编辑器。Slides 视图标题会显示当前入口,固定选择时还会追加 (selected)。切换入口会清除上一份布局诊断,并把正在运行的预览切到新的演示文稿。

命令会在该演示文稿自己的项目根目录下运行:入口文件往上第一个带有 FrameSeq CLI 的目录,否则是最近的 package.json 所在目录,否则是工作区文件夹。因此放在 monorepo 子目录里的演示文稿会使用它自己的依赖。

命令#

打开命令面板运行:

  • FrameSeq: Refresh Slides
  • FrameSeq: Select Entry
  • FrameSeq: Preview
  • FrameSeq: Preview Current Slide
  • FrameSeq: Previous Slide
  • FrameSeq: Next Slide
  • FrameSeq: Insert Slide After Current
  • FrameSeq: Go to Named Region
  • FrameSeq: Bind Selection to Named Region
  • FrameSeq: Stop Preview
  • FrameSeq: Check Layout
  • FrameSeq: Export HTML
  • FrameSeq: Export PDF
  • FrameSeq: Export PPTX
  • FrameSeq: Export Typst

命名区域#

区域状态项会显示下一条内容命令将被写到哪里:可以是 mainleftcell1,也可以是 diagram/legend 这样的 at() 路径。选择 FrameSeq: Go to Named Region 可以跳到当前页任意命名区域的第一次 at() 调用。重复访问的路径会在 Slides 大纲和区域选择器中显示访问次数。

要让连续的对象成为可整体移动的单元,选中它们的完整源码行,然后从编辑器右键菜单运行 FrameSeq: Bind Selection to Named Region。扩展会在选区前插入命名列容器,并在选区后恢复之前的创作光标:

at("pipeline").column();
rect("Parse");
rect("Build");
main();

整个改动只占一次撤销。若选区包含 slide()at()main()left()right()cell(),扩展会拒绝操作,因为移动这些边界会悄悄改变内容归属。

Current Slide 检查器#

紧凑的 Slides 视图仍然位于 FrameSeq 活动栏上方。Current Slide 填充它下面的空间并跟随源码光标。摘要显示页码、布局、对象数量和备注状态。组件按创作光标区域分组,例如 maincell0 或命名 at() 路径;嵌套构建器在树里也保持父子层级。对象标签来自 text("Latency")rect("Model") 这样的字面内容。视图标题栏提供预览、布局检查和 PPTX 导出。

展开组件可以查看由源码支撑的字面属性,例如 xywidthheightgapfillcolor。选择属性会打开带类型校验的输入框,并且只替换 TypeScript 中对应的字面量。扩展在编辑前确认原文没有变化,用一次 workspace edit 保证普通 Undo 可撤销,随后保存并刷新检查器和实时预览。计算表达式暂时只支持跳转源码,避免属性面板在不知情时改变其语义。

命名 at() 区域本身也是可编辑组件。展开区域标题,可以编辑该路径任意一次访问中写明的 position、尺寸、间距、内边距、填充色和对齐属性。在预览编辑模式中,可以直接拖动可见区域;如果区域没有容易抓取的空白,按住 Shift 从任意子对象开始拖动,就会选择最近的已定位命名区域。扩展只改写区域的 xy 字面量,因此所有子对象一起移动,一次 Undo 即可恢复。没有字面 position({ x, y }) 的区域继续参与自动流式布局,不会被悄悄改成绝对定位。

预览编辑模式现在也是由源码支撑的选择界面。普通点击选择组件,Ctrl+点击或 Command+点击可以增减多选。预览顶部工具条显示命名区域层级和选择数量;选择两个以上组件后,点击 Bind region,扩展会确认它们属于同一页、同一创作区域,并且是连续的顶层兄弟组件,然后用一次可撤销编辑完成命名区域绑定。若中间漏选了组件,或者选中了嵌套子项、区域容器、跨区域对象,操作会被拒绝,不会悄悄扩大选区。

同一条多选工具条支持左对齐、水平居中、右对齐、顶部对齐、垂直居中和底部对齐。选择三个以上对象后,还可以水平或垂直平均分布;首尾对象保持不动,中间对象获得相等的视觉间距。只有所有对象在对应轴上都具有源码支持的字面坐标时,该轴的操作才会启用。计算坐标、流式布局对象,以及同时包含父项和子项的选区会保持禁用,不会被擅自转换或猜测。一次操作会把所有相关坐标作为一次可撤销的工作区编辑写回。

选中具有字面坐标的对象后,方向键会按画布单位微调 1 像素,Shift + 方向键微调 10 像素。持续按键会合并成一次源码编辑,多选会作为整体移动而不改变内部间距。单选旁边用紧凑浮签显示 xy、宽度和高度。第一次按 Escape 只清除选区,再按一次才退出编辑模式。

在嵌入预览中点击对象后,键盘焦点会继续留在预览里。若 slides.ts 本来就在旁边可见,其选区会静默跟随;隐藏的源码标签不会被激活。紧凑浮签会明确显示 Selected。对于流式布局组件,它会显示 flow item · drag to reorder,因为源码里没有可供方向键修改的 xy 字面量;按下不可用的方向键时,也会说明缺少哪个坐标。

已定位组件会先区分单击和拖动,再决定是否捕获指针。按下后直接松开会选中组件并聚焦画布,随后可以立即使用方向键;只有移动超过拖拽阈值后才会捕获指针。执行对齐或分布工具条操作后,焦点也会回到画布。

每次微调完成后都会保存源码并刷新实时预览。FrameSeq 会在旧画布继续显示时,于屏幕外完成新画布的渲染和缩放,再一次性替换,因此松开拖动时不会露出空白或未经缩放的画面。选区会按源码位置恢复,画布也会重新获得焦点,所以连续按方向键不需要重复点击。只有编辑被拒绝或宿主没有响应时,才会把整页刷新作为安全回退。

拖动已定位对象时,其左侧、水平中心、右侧,以及顶部、垂直中心、底部会自动吸附到附近可编辑对象。锚点进入 6 个屏幕像素的阈值时,细参考线会贯穿幻灯片;离开阈值或松开指针后立即消失。拖动时按住 Ctrl 或 Command 可以临时绕过吸附,进行完全自由的移动。

编辑状态会持续清晰但保持紧凑:E 按钮保持强调色填充和光晕,再次按 E 或 Escape 后退出。单选只显示画布上的对象轮廓;只有多选时顶部才出现工具条,因为这时才需要数量、区域上下文和 Bind region 操作。

源码编辑器、检查器和预览共用同一套映射。光标在组件调用链中移动时,Current Slide 会选中该组件,预览也会高亮它;光标落在可编辑字面量上时,则会精确选中对应属性。移动到另一页的对象时,已打开的预览也会切换页面;移动到空白源码会清除旧高亮。把 frameseq.followCursor 设为 false 可以关闭自动跟随。

在 Current Slide 中选择区域或对象会打开对应命令,并用明显的强调轮廓标出预览中的相应组件。在预览里 Alt+点击对象会执行反向操作:打开源码并选中匹配的 Current Slide 条目。区域选择使用它的 at() 名字,所以被标出的是整个命名容器,而不只是第一个子对象。

这个轮廓表示当前检查目标,不是永久标注。点击画布空白或普通预览位置、按 Escape、在编辑模式中直接选择对象,或者切换页面,都会清除它。

保存当前幻灯片文档时默认会刷新大纲。如果有别的工具在频繁改写源码,可以关掉 frameseq.autoRefresh

布局诊断#

FrameSeq: Check Layout 跑的是和下面这条命令相同的渲染检查:

frameseq check slides.ts --json

错误和警告会标注在相关的 slide() 行上、出现在 Problems 面板里,同时也显示在大纲中该页的下方。判断溢出、裁切、空白页和最小字号的依据始终是浏览器渲染结果。

从预览里编辑#

预览运行期间,源码和页面是互相指向的。

在预览上按住 Alt,指针下的对象会描出轮廓;Alt+点击它,光标就移到写出它的那条命令上。

预览里的 E 控件开启布局编辑:

  • position({ x, y }) 放置的对象可以拖动,写了 width()height() 的可以从右下角缩放。松手即把这些数字写回。
  • 处在文档流里的对象没有坐标可改,拖动它改变的是它在邻居之间的位置。预览会画出落点,该命令占据的整行会被搬过去,连同写在它上面或旁边的注释。
  • Ctrl+Z 撤销上一次拖拽。在 VS Code 预览里,改动是通过工作区应用的,所以由编辑器自己的撤销覆盖;在浏览器里则由开发服务器保存历史。
  • Escape 退出该模式。

只有拖拽真正改变的那几个字符会被重写,所以注释和格式都能留存。当数字和文件对不上时,拖拽会被拒绝而不是猜测——预览被拖动期间文档在编辑器里被改过,就是这种情况。

只有当源码里存在唯一一处改动能代表这次拖拽时,才会提供编辑:

  • position({ x: cursor, y: 90 }) 的 x 是算出来的,没有数字可以重写。
  • 循环或辅助函数里的命令会从一行渲染出多个对象,拖拽指不到其中哪一个。
  • 重排只在文档的同一个运行段内进行。left()cell(1)、新的 slide(),以及会收拢上方对象的 group(a, b),各自结束一段;把行搬过这些调用,改变的就不只是顺序,而是对象归属哪个区域。

不能拖拽的对象仍然可以点击,依旧能跳回它所在的行。源码位置只在 frameseq dev 服务期间记录,构建出来的演示文稿不含这些信息。