文件与文本
假设你已经完成文本阅读器,现在希望加一个按钮,把当前文本转换成大写并保存。本篇从这个修改开始,再说明什么时候应使用文件接口。
1. 允许 View 编辑文本
Section titled “1. 允许 View 编辑文本”在文本阅读器的 plugin.json 中,给原来的 View 添加 access: "write":
{ "id": "main", "title": "Text Reader", "kind": "file", "entry": "./src/main.ts", "capabilities": ["document"], "access": "write"}这是 views 数组中的一项,不是完整清单。document 提供当前打开文本的工作副本;write 允许修改。原来的 file/open 入口不变。
2. 编辑后再保存
Section titled “2. 编辑后再保存”把 src/main.ts 替换为:
import type { Mount } from "@eidos.space/plugin-sdk"
const mount: Mount = (ctx, root) => { const textDocument = ctx.capabilities.document if (!textDocument) throw new Error("Document capability required") const button = document.createElement("button") const status = document.createElement("p") button.textContent = "Convert to uppercase and save" root.append(button, status)
button.onclick = () => { button.disabled = true void (async () => { const current = await textDocument.read() const edited = await textDocument.edit({ text: current.text.toUpperCase(), expectedVersion: current.version, }) if (edited.status === "stale") { status.textContent = "The text changed. Review it and try again." return } const saved = await textDocument.save() status.textContent = saved.status === "conflict" ? "The file changed on disk. Your draft is still available." : "Saved." })() .catch((error) => { status.textContent = String(error) }) .finally(() => { button.disabled = false }) }}export default mount先读取文本和版本,再提交编辑,最后保存到磁盘。这三个步骤不能合并成一次直接文件写入:用户可能正在其他界面编辑同一份文本。
3. 验证保存与冲突
Section titled “3. 验证保存与冲突”重新加载插件,用它打开测试 .txt 文件。点击按钮,内容应变成大写,页面显示 Saved.。
如果编辑时版本已变化,edit 返回 stale,示例停止保存并提示重新检查。如果磁盘内容已变化,save 返回 conflict,草稿仍保留。实际编辑器应保留用户输入,不能在冲突后自动用旧内容覆盖新内容。连续输入产生的编辑请求应串行提交。
要持续显示文本,沿用入门教程中的 document.observe。把返回的订阅加入 ctx.subscriptions,关闭 View 后由宿主释放。撤销、重做也使用 document 接口。
什么时候使用 fs?
Section titled “什么时候使用 fs?”document 适合编辑当前文本;fs 适合读取其他文件、列出目录或播放媒体。例如,在一个已有的媒体 file View 的 mount 内:
const fs = ctx.capabilities.fsif (!fs) throw new Error("Filesystem access required")const video = document.createElement("video")video.controls = truevideo.src = await fs.getUrl()root.append(video)这里省略路径表示当前绑定文件。大文件播放使用 getUrl 的流地址,不要先把整个视频读入内存。readText 上限为 2 MiB,二进制读写上限为 16 MiB。
默认文件访问受绑定文件及目录范围限制。浏览整个 Space 时,在清单中声明 workspace.files.read;写文件还需要相应写权限和贡献项的 access: "write"。如果文件已有受管理的工作副本,直接写入、删除或重命名可能返回 BUSY;编辑当前文本继续使用 document。
给文件保存评分或标签
Section titled “给文件保存评分或标签”文件属性独立于文件内容。例如,你可以给笔记写入评分,而不修改 Markdown 正文。Lite 的此项能力要求 API 3.1.0。
在清单中添加:
{ "workspace": { "files": { "read": true }, "filemeta": { "namespaces": ["space.eidos.meta"], "write": true } }}给执行写入的 View 或 Action 设置 access: "write"。在其处理函数内:
const filemeta = ctx.capabilities.filemetaif (!filemeta) throw new Error("File metadata unavailable")await filemeta.patch("notes/example.md", "space.eidos.meta", { set: { rating: 4, tags: ["review"] },})const metadata = await filemeta.read("notes/example.md", "space.eidos.meta")先在测试 Space 中准备该文件。路径相对 Space;没有 workspace 文件读权限时只能访问绑定文件。属性写权限不会授予正文写权限。
patch 保留未提到的键;删除键使用 remove: ["rating"],设置 null 则保留值为 null 的键。修改立即作用于文件系统,读取返回完整 namespace JSON,不受表格字段定义限制。namespace 不存在时返回空对象。属性最多 1 MiB,文件系统可能有更小限制;损坏 JSON、符号链接和非普通文件会报错。多个写入方需要自行协调。