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+点击,把光标移到画出该对象的那条命令上。
- 在预览里拖拽,改变命令写下的坐标、或对象在邻居之间的位置,作为一次可撤销的编辑。
- Slides 视图工具栏上的 FrameSeq: Open in Studio:在独立窗口里用 FrameSeq Studio 打开这份演示。未保存的修改会先保存;同一份演示的 Studio 已在运行时只会重新显示,不会再启动一个;但如果它此后已经打开了另一份演示,就会针对这份演示重新启动。FrameSeq: Stop Studio 用来关闭它。
扩展通过 frameseq inspect --json 读取大纲,并调用 FrameSeq CLI 来预览、校验和导出:项目里装了就用项目自己的,否则用全局安装的,可以是 npm 装的,也可以是 FrameSeq 应用。应用自带 Node.js,扩展会改用它,所以只装了应用的电脑不需要别的东西。它不会再打包一份渲染运行时。
本地构建与安装#
在 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 可执行文件。没有自己项目的演示,也就是任意文件夹里的单个 .slides.ts 文件,会使用 npm install --global @pride7/frameseq 放到 PATH 上的 frameseq 命令,也就是终端会运行的那一个。两者都没有时,扩展会提示如何安装 FrameSeq。
运行 FrameSeq: Preview 会把入口文档留在第一个编辑器组,并在旁边打开预览。如果你更希望预览出现在当前编辑器组里,把 frameseq.previewBeside 设为 false。在 FrameSeq 大纲里选中某一页会同时更新源码选区和实时预览,这个行为由 frameseq.followOutline 控制。
frameseq.previewBeside 只决定新预览第一次创建在哪里。预览已经存在后,从 Slides 大纲切页会保留它当前的编辑器组:如果你把预览移到源码所在组、作为另一个标签页,它会继续留在标签页;如果原来是左右分屏,也会继续保持分屏。激活预览标签时,Current Slide 也会继续显示正在预览的页面,不再退回空白欢迎提示。
从大纲切页时,已有预览会一直保持可见。若源码本来就在另一栏可见,只会静默移动其中的光标;若 slides.ts 是藏在预览后面的同组标签页,扩展不会先激活它。因此点击每张 Slide 时不再出现“源码闪一下,再回预览”的过程。
预览当前显示页面的 slide() 调用会带有持续的源码标记:整行底色、左侧强调线、滚动条刻度,以及 Previewing slide N 行尾标签。它不依赖 VS Code 很淡的非活动选区,所以焦点留在预览时也很醒目。保存或刷新后会按最新源码重新计算行号,在页面前方增删代码也不会让标记留在旧位置。
预览页面始终保持演示文稿配置的比例(16:9、4:3 或显式画布尺寸)。狭窄或纵向的编辑器窗格会在页面周围留下舞台空间,而不会把圆角外框拉伸到整个窗格。固定画布随后在该外框内等比缩放,所以布局编辑和导出坐标仍使用文档的原生画布单位。
将鼠标放在交互式预览上并按住 Ctrl(macOS 为 Command)滚动即可缩放画布。底部会显示真实比例,并提供 −、+、Fit 和 1:1:Fit 会随编辑器窗格尺寸自动适应,1:1 会恢复画布原生的 100% 尺寸;Ctrl/Command+0 同样可以恢复 100%。预览缩放不会修改源码或导出尺寸。
入口文件的选择#
优先使用当前活动的 slides.ts、*.slides.ts 或 *.fsq 编辑器,其次是在其他编辑器组里可见的幻灯片编辑器。否则扩展使用 frameseq.entry 设置里的路径,该路径可以相对于工作区文件夹,也可以是绝对路径:
{
"frameseq.entry": "talks/kickoff/slides.ts"
}
如果该文件不存在,扩展会在整个工作区里搜索 slides.ts、*.slides.ts 与 *.fsq,并跳过 node_modules、dist、out、tmp、output。因此放在子目录里的演示文稿无需任何配置。若存在多个演示文稿,离当前编辑文件最近的优先,其次是路径最浅的。
运行 FrameSeq: Select Entry(命令面板、Slides 视图标题菜单,或空视图里的链接)可以自己选择演示文稿。选择器会列出找到的全部演示文稿,标注当前正在使用的那个,并按工作区记住这次选择:在你用 Follow the active editor 清除之前,它的优先级高于活动编辑器。Slides 视图标题会显示当前入口,固定选择时还会追加 (selected)。切换入口会清除上一份布局诊断,并把正在运行的预览切到新的演示文稿。
命令会在该演示文稿自己的项目根目录下运行:入口文件往上第一个带有 FrameSeq CLI 的目录,否则是最近的 package.json 所在目录,否则是工作区文件夹。因此放在 monorepo 子目录里的演示文稿会使用它自己的依赖。
.fsq 文件#
FrameSeq Studio 保存演示时用的 .fsq 是 FrameSeq 自己的后缀,内容是同样的 TypeScript。扩展把它当作 FrameSeq 文档打开,按 TypeScript 着色;预览、大纲、Current Slide、区域和对象的名字、代码片段以及每个命令,都和在 .slides.ts 文件里一样可用。
TypeScript 会像检查 .ts 文件一样边写边检查 .fsq 文件:编辑器和 Problems 面板里的类型错误、补全和悬停说明都有。扩展自带一个 TypeScript 插件,把文件交给 VS Code 内置的 TypeScript 支持,并给它提供 slide()、text() 等 FrameSeq 命令的声明:优先用文件所在项目里安装的 FrameSeq;文件在 FrameSeq 仓库的检出里时用仓库源码;否则用全局安装,即 FrameSeq 应用或 npm install --global @pride7/frameseq。VS Code 开着时才装好的全局安装,要在 Developer: Reload Window 之后才会用上。
没有哪个 tsconfig.json 会包含 .fsq 文件,所以 VS Code 用它给项目之外的文件准备的选项来检查,而不是项目的 compilerOptions;命令行上的 tsc 也不读它。frameseq check 和 FrameSeq Studio 仍然会报告它的类型错误。这项检查需要 VS Code 内置的 TypeScript and JavaScript Language Features 扩展,除非你停用了它,否则它一直开着。想让项目的 tsconfig.json 和 tsc 也检查演示,就把文件命名为 .slides.ts:内容一样,两种后缀随时可以互相改名。
命令#
打开命令面板运行:
FrameSeq: Refresh SlidesFrameSeq: Select EntryFrameSeq: PreviewFrameSeq: Preview Current SlideFrameSeq: Previous SlideFrameSeq: Next SlideFrameSeq: Insert Slide After CurrentFrameSeq: Go to Named RegionFrameSeq: Bind Selection to Named RegionFrameSeq: Stop PreviewFrameSeq: Check LayoutFrameSeq: Export HTMLFrameSeq: Export PDFFrameSeq: Export PPTXFrameSeq: Export Typst
命名区域#
区域状态项会显示下一条内容命令将被写到哪里:可以是 main、left、cell1,也可以是 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(),扩展会拒绝操作,因为移动这些边界会悄悄改变内容归属。
名字#
幻灯片文档用 .as() 和 at() 起的名字,在 TypeScript 看来只是字符串,所以扩展按照与 FrameSeq Studio 相同的规则来读它们:
- 补全:在
ref()、group()、.from()、.to()或.rightOf()这类摆放方法的字符串里,列出本页的名字;输入"encoder.之后列出锚点方位;在at()里列出本页的区域。 - 转到定义(
F12):打开给对象命名的.as(),或第一次创建该区域的at();查找所有引用(Shift+F12)列出本页上的每一处使用,光标停在其中一处时编辑器会把它们都高亮。 - 重命名符号(
F2):把名字、每一处引用,以及被改名区域里各子区域的路径,作为一次编辑一起改掉。 - 悬停:说明这个名字是什么、在哪里起的名,以及本页引用了它几次。
- 指向本页上不存在之物的名字、重复起的对象名,以及在没有分栏的页上写的
at("left"),会在输入时出现在 Problems 面板里。在运行时拼出名字、或调用了可能这样做的辅助函数的页面不做检查。
Current Slide 检查器#
紧凑的 Slides 视图仍然位于 FrameSeq 活动栏上方。Current Slide 填充它下面的空间并跟随源码光标。摘要显示页码、布局、对象数量和备注状态。组件按创作光标区域分组,例如 main、cell0 或命名 at() 路径;嵌套构建器在树里也保持父子层级。对象标签来自 text("Latency") 或 rect("Model") 这样的字面内容。视图标题栏提供预览、布局检查和 PPTX 导出。
展开组件可以查看由源码支撑的字面属性,例如 x、y、width、height、gap、fill 和 color。选择属性会打开带类型校验的输入框,并且只替换 TypeScript 中对应的字面量。扩展在编辑前确认原文没有变化,用一次 workspace edit 保证普通 Undo 可撤销,随后保存并刷新检查器和实时预览。计算表达式暂时只支持跳转源码,避免属性面板在不知情时改变其语义。
命名 at() 区域本身也是可编辑组件。展开区域标题,可以编辑该路径任意一次访问中写明的 position、尺寸、间距、内边距、填充色和对齐属性。在预览编辑模式中,可以直接拖动可见区域;如果区域没有容易抓取的空白,按住 Shift 从任意子对象开始拖动,就会选择最近的已定位命名区域。扩展只改写区域的 x 和 y 字面量,因此所有子对象一起移动,一次 Undo 即可恢复。没有字面 position({ x, y }) 的区域继续参与自动流式布局,不会被悄悄改成绝对定位。
预览编辑模式现在也是由源码支撑的选择界面。普通点击选择组件,Ctrl+点击或 Command+点击可以增减多选。预览顶部工具条显示命名区域层级和选择数量;选择两个以上组件后,点击 Bind region,扩展会确认它们属于同一页、同一创作区域,并且是连续的顶层兄弟组件,然后用一次可撤销编辑完成命名区域绑定。若中间漏选了组件,或者选中了嵌套子项、区域容器、跨区域对象,操作会被拒绝,不会悄悄扩大选区。
同一条多选工具条支持左对齐、水平居中、右对齐、顶部对齐、垂直居中和底部对齐。选择三个以上对象后,还可以水平或垂直平均分布;首尾对象保持不动,中间对象获得相等的视觉间距。只有所有对象在对应轴上都具有源码支持的字面坐标时,该轴的操作才会启用。计算坐标、流式布局对象,以及同时包含父项和子项的选区会保持禁用,不会被擅自转换或猜测。一次操作会把所有相关坐标作为一次可撤销的工作区编辑写回。
选中具有字面坐标的对象后,方向键会按画布单位微调 1 像素,Shift + 方向键微调 10 像素。持续按键会合并成一次源码编辑,多选会作为整体移动而不改变内部间距。单选旁边用紧凑浮签显示 x、y、宽度和高度。第一次按 Escape 只清除选区,再按一次才退出编辑模式。
按 Delete 或 Backspace 删除选中的对象:它们的源码行作为一次可撤销的工作区编辑被删掉,写在上方的注释也一起删掉,连到它们上面的连接线也会一起删除。别的对象按名字相对它定位的对象、写在另一条命令里面的对象以及区域会被拒绝,预览会说明原因。
在嵌入预览中点击对象后,键盘焦点会继续留在预览里。若 slides.ts 本来就在旁边可见,其选区会静默跟随;隐藏的源码标签不会被激活。紧凑浮签会明确显示 Selected。对于流式布局组件,它会显示 flow item · drag to reorder,因为源码里没有可供方向键修改的 x 或 y 字面量;按下不可用的方向键时,也会说明缺少哪个坐标。
已定位组件会先区分单击和拖动,再决定是否捕获指针。按下后直接松开会选中组件并聚焦画布,随后可以立即使用方向键;只有移动超过拖拽阈值后才会捕获指针。执行对齐或分布工具条操作后,焦点也会回到画布。
每次微调完成后都会保存源码并刷新实时预览。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
错误和警告会标注在写出该对象的那条命令所在的行上、出现在 Problems 面板里,同时也显示在大纲中该页的下方。无法运行的文档,会标在它停下时正在执行的语句上。FrameSeq 0.39 之前的版本不报告行号,这时结果会标在该页的 slide() 行上。判断溢出、裁切、空白页和最小字号的依据始终是浏览器渲染结果。
从预览里编辑#
预览运行期间,源码和页面是互相指向的。
在预览上按住 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 服务期间记录,构建出来的演示文稿不含这些信息。