跳转到内容

文件与文本

假设你已经完成文本阅读器,现在希望加一个按钮,把当前文本转换成大写并保存。本篇从这个修改开始,再说明什么时候应使用文件接口。

在文本阅读器的 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 入口不变。

把 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

先读取文本和版本,再提交编辑,最后保存到磁盘。这三个步骤不能合并成一次直接文件写入:用户可能正在其他界面编辑同一份文本。

重新加载插件,用它打开测试 .txt 文件。点击按钮,内容应变成大写,页面显示 Saved.。

如果编辑时版本已变化,edit 返回 stale,示例停止保存并提示重新检查。如果磁盘内容已变化,save 返回 conflict,草稿仍保留。实际编辑器应保留用户输入,不能在冲突后自动用旧内容覆盖新内容。连续输入产生的编辑请求应串行提交。

要持续显示文本,沿用入门教程中的 document.observe。把返回的订阅加入 ctx.subscriptions,关闭 View 后由宿主释放。撤销、重做也使用 document 接口。

document 适合编辑当前文本;fs 适合读取其他文件、列出目录或播放媒体。例如,在一个已有的媒体 file View 的 mount 内:

const fs = ctx.capabilities.fs
if (!fs) throw new Error("Filesystem access required")
const video = document.createElement("video")
video.controls = true
video.src = await fs.getUrl()
root.append(video)

这里省略路径表示当前绑定文件。大文件播放使用 getUrl 的流地址,不要先把整个视频读入内存。readText 上限为 2 MiB,二进制读写上限为 16 MiB。

默认文件访问受绑定文件及目录范围限制。浏览整个 Space 时,在清单中声明 workspace.files.read;写文件还需要相应写权限和贡献项的 access: "write"。如果文件已有受管理的工作副本,直接写入、删除或重命名可能返回 BUSY;编辑当前文本继续使用 document。

文件属性独立于文件内容。例如,你可以给笔记写入评分,而不修改 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.filemeta
if (!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、符号链接和非普通文件会报错。多个写入方需要自行协调。

完整参数与错误约定见 Document、文件访问和文件属性。