File Manager

Browse, view and edit the open project in the right work panel: a full directory tree with per-file-type icons, syntax-highlighted editing, a Markdown preview, image and media viewing, CSV/JSON structured views, read-only SQLite browsing with a SQL query box, a context menu for creating and renaming, and filename search.

by Tioit-Wang·v0.3.1 Official catalog

About this plugin

文件管理器(File Manager)

在 PI-Desktop 的右侧工作面板里浏览、查看并编辑当前项目的文件:完整目录树(图标随文件类型变化)、左右分栏、代码高亮编辑与保存、图片与音视频查看、CSV/JSON 结构化查看,以及新建、重命名/移动、按文件名搜索。

功能

  • 完整目录树:懒加载、可按层展开、目录在前。每一行的图标随文件类型变化——文件夹、代码、JSON、Markdown、样式、HTML/Vue/Svelte、YAML/TOML、Shell、SQL、图片、压缩包、字体、锁文件等各有自己的字形与配色;package.json、Dockerfile、LICENSE、.gitignore 这类特殊文件名优先于扩展名判定。
  • 选中态与悬停态明确区分:悬停是淡淡的中性底色(只表示鼠标位置);选中是强调色底 + 左侧强调色竖条 + 前景色文字 + 中等字重,鼠标移开也保持,悬停到选中行上还会再加深一档,不会让人误以为选中丢了。
  • 右键菜单:在文件/文件夹上右键可新建文件、新建文件夹、重命名、移动、打开、刷新;在列表空白处右键则只提供「在此新建」与刷新。支持 ↑↓ / Home / End / Enter / Esc。
  • 左右分栏:左侧文件列表,右侧内容。中间分隔条可拖拽,宽度会记住。
  • 代码高亮编辑:CodeMirror 6,行号、查找替换、多光标、括号匹配、代码折叠。按文件名加载对应语言的语法高亮,覆盖 TypeScript/TSX、JavaScript/JSX、JSON、Markdown、CSS/SCSS/Sass/Less、HTML、Vue、XML、YAML、TOML、Properties、Python、Go、Rust、Java、C/C++、C#、Kotlin、Scala、Dart、Objective-C、PHP、Ruby、Lua、Shell、PowerShell、SQL、Dockerfile、Diff。全部随包内置、离线可用,不请求网络。Ctrl/Cmd+S 或工具栏按钮保存;没有改动时不写盘,保存后光标与滚动位置留在原处(不会跳回开头),界面显示的始终是磁盘上那份内容。焦点不在编辑器里(刚点过工具栏、或停在预览 / 表格 / 树视图)时 Ctrl/Cmd+S 同样有效。
  • Markdown 预览 / 编辑切换:打开 .md / .markdown / .mdx 时,工具栏出现「编辑 / 预览」切换。预览支持标题、围栏代码块(带同样的语法高亮)、引用、分隔线、有序/无序/任务列表(按缩进嵌套)、表格(含对齐)、行内 code / 粗体 / 斜体 / 删除线 / 链接 / 图片。切换会记住。
  • 图片查看:png / jpg / jpeg / gif / webp / avif / bmp / ico / svg。默认适应窗口,可切「实际大小」;底部显示像素尺寸与文件体积,舞台铺棋盘格,透明区域一眼可见。
  • 音视频播放:mp4 / m4v / mov / webm / ogv / mkv 用播放器,mp3 / m4a / aac / wav / flac / ogg / opus / weba 用音频条。不自动播放;编码或封装不被支持时给明确提示,而不是留一块黑框。
  • CSV / TSV 表格(分页):打开 .csv / .tsv 就是表格,默认每页 1000 行;底部工具条可以翻页(首页/上一页/下一页/末页)、看「当前范围 / 总行数」,也能改每页行数(100–5000,选择会记住)。首行当表头且吸顶,最左一列是行号且吸左,整行悬停高亮,整列都是数字时自动右对齐,被省略号截断的值悬停可看全。点表头排序:升序 → 降序 → 取消(回到文件原序);整列都是数字按数值比,否则按自然序文本比,空值永远沉底。解析支持引号包裹、"" 转义、字段内换行、CRLF 与参差的行;列数上限 60、解析上限 20 万行,超出会在底部说明。工具栏可切回源码编辑。
  • JSON 折叠树:.json / .jsonc / .json5 可切树视图,默认展开两层、单节点最多直接铺 100 个键,长字符串截断(完整值在悬停提示里)。// 注释、尾逗号这类非严格 JSON 不会炸面板,只显示「解析失败 + 原因」,源码视图照常可编辑。
  • SQLite 数据库(只读):.db / .sqlite / .sqlite3 / .db3 打开即用——顶部概览(体积、页大小 × 页数、编码、SQLite 库版本、WAL 提示),「浏览」页签里选表或视图看数据(分页、点表头排序、最左 rowid、NULL 灰显、BLOB 只显示体积)、「结构」按钮看列与类型/索引/触发器/CREATE 原文,以及「SQL」页签里的只读查询框(Ctrl/Cmd+Enter 运行,可选行数上限,显示耗时与截断提示)。 这是插件里唯一能打开大文件的预览类型:数据库的字节根本不进视图,主进程每次只取一页。
  • 二进制说清楚:zip / apk / exe 这类没有可预览形态的文件,提示「二进制文件(zip、apk、exe 等),暂不支持预览」,不做魔法识别,也不假装能解压。
  • 文件操作:新建文件、新建文件夹、重命名、移动到指定目录。
  • 用默认应用打开 / 在文件夹中显示:文件右键即可(fs.openDefault / fs.reveal)。这两个动作由宿主执行,权限与范围同内置的 Files 视图。
  • 按文件名搜索:整个项目范围内分页搜索,点击结果直接跳转并展开到该文件。
  • 忽略规则:项目里有 .gitignore / .ignore 就按规则隐藏条目(可用工具栏的眼睛按钮切回显示);一个规则文件都没有时,目录树展示全部条目。
  • 跟随宿主外观:亮/暗配色与界面语言(中文 / English)跟随 PI-Desktop 主体,编辑器与预览的高亮配色一并跟随。

安装

在 PI-Desktop 的 Plugins 页面:

  1. Load development plugin → 选择本目录;或
  2. 用 dist/pi.file-manager-0.3.1.piplug 走 Install plugin package。

装好后打开一个项目,在右侧工作面板头部的切换菜单 → “Plugin views / 插件视图” 分组里点击「文件管理器」。

这个插件没有命令面板入口,也不注册面板窗口——它只提供一个停靠在右侧栏的视图(contributes.views)。宿主没有提供「用命令打开视图」的通道,所以这里也不声明误导性的命令。

开发

cd views-src
pnpm install
pnpm typecheck
pnpm build      # 产物输出到 ../views(入库,宿主加载 views/index.html)

views-src 是源码(React + TypeScript + Tailwind + CodeMirror 6);views 是构建产物,直接入库。构建为 IIFE 单文件并内联全部动态 import——宿主的视图用 file:// 加载页面,type="module" 与 crossorigin 会被 Chromium 拦掉,所以 vite.config.ts 里有一个 fileProtocolCompat 插件负责改写 index.html。

main.js 是手写、零依赖的 CommonJS,插件的依赖不会被安装,所以不要往它里面 require 第三方包。

校验

cd views-src
pnpm typecheck
pnpm verify        # 语法高亮 + Markdown 预览 + 查看器的离线校验

pnpm verify 跑三个不需要 DOM 的校验脚本,用于覆盖「静默失败」的三条链路:

  • verify-highlight.mjs:对 17 种真实文件走完整的 文件名 → 语言解析 → 载入 → 语法树 → 高亮 token 链路,断言每种都产出足够多的 不同高亮 class,并反向断言 .txt / .bin / .zip / .png / LICENSE 不会被误认领。 CodeMirror 的高亮是「解析不出来就什么都不报」的典型——只 grep 产物字符串会漏掉 运行时问题(本插件第一版就踩过:basicSetup 自带的 fallback 高亮器把自己的样式盖住了)。
  • verify-markdown.mjs:断言预览真实产出的元素树(标题/列表/表格/任务列表/嵌套 强调),链接协议白名单,以及不存在 dangerouslySetInnerHTML、原始 HTML 被转义。 其中一个回归用例专门盯住「行内解析递归时共享带 g 的正则」——那会让预览卡死并吃光内存。
  • verify-viewers.mjs:断言 CSV 解析的 12 个用例(引号包裹、"" 转义、字段内换行、 CRLF、参差的行、截断与列数封顶)、分页切片(页码越界夹紧、末页不满、空表也有 1 页)、 数字列识别、JSON 合法/非法两条路径,以及「哪个文件有哪几种看法、默认落在哪一侧」的判定。 表格与树都是「把文件内容变成界面」的路径,解析器错了不会抛异常、只会静默显示错。

冒烟测试(在仓库根目录运行):

node ../pi-file-manager-smoke-test.cjs

数据与安全

这个插件用自己的 Node fs 读写当前项目里的文件,而不是宿主的 pi.fs 网关。

为什么必须这样:宿主的 manifest.fs 在语法上就禁止整树写入(写入 scope 不能是 **),任何一个能通过校验的窄 scope 都会让「保存」变成每次都弹权限确认;而且 pi.fs.* 没有创建/重命名/移动,fs.glob 与 fs.list 还有条数上限、会跳过 node_modules、屏蔽凭据路径。与之对应的代价是:宿主的权限网关不介入这些调用,所以安全责任由插件自己承担。manifest 里因此没有申报 fs.write(写权限根本无法诚实申报),只申报了 ui.view 与 fs.read——后者仅用于「用默认应用打开」与「在文件夹中显示」这两个只能由宿主代为执行的动作(fs.openDefault / fs.reveal,权限与范围同内置的 Files 视图:root workspace、scope **)。这两个通道由宿主按声明的范围校验,越界会被宿主自己拒掉。

插件自己实施的全部限制:

限制说明
路径包含只接受相对项目根的路径;绝对路径、..、以及 realpath 后落到根目录之外的符号链接 / junction 一律拒绝
敏感路径.env*、.ssh、.aws、.gnupg、.git/**、*.pem、*.key、*.p12 等读写全拒;node_modules 可读不可写
原子写先写同目录临时文件 → fsync → 保留原文件权限位 → rename 覆盖,中断不会留下半写文件
冲突检测以 mtimeMs + 文件大小作为乐观锁;文件在编辑器之外被改动过时不会静默覆盖,会先弹对话框让你选择覆盖 / 放弃 / 重新加载
写入审计每次写入追加一行到插件数据目录的 write-audit.jsonl(约 1 MiB 后轮转),因为宿主审计不到这条路径
数据库只读以 readOnly 打开 + PRAGMA query_only = 1(实测 DROP/INSERT 会被 SQLite 自己拒绝),再叠一层语句白名单:只放行 select / with / values / explain 与只读 pragma,且必须是单条语句——node:sqlite 的 prepare 对多语句是放行的(实测 select 1; select 2 只执行第一条、不报错),所以必须自己拦。表名/列名先与 sqlite_schema 核对再拼引号,绝不直接插值
体积上限文本预览 2 MiB、图片 8 MiB、音视频 24 MiB、写入 8 MiB。图片与音视频单独设限是因为它们的字节要以 base64 随响应发给视图(膨胀约 1/3);超限时提示里会带上具体上限

行为上的两点说明:

  • .git 目录不会出现在树里(体量巨大且永远无用)。
  • 忽略规则来自 .gitignore 与 .ignore(支持 ! 取反、末尾 /、前导 / 锚定、* / ? / **)。这是 gitignore 的一个子集,不支持 \ 转义和 [a-z] 字符类;偏差只影响「显不显示」,且随时可以用工具栏的眼睛按钮翻盘。

Markdown 预览的安全做法:预览不使用 dangerouslySetInnerHTML,而是把 Markdown 解析成 React 元素——节点由 React 转义,结构上不存在注入面,因此也不需要额外的 sanitizer。原因很直接:这个视图的 bridge 通向一个能读写项目文件的插件主进程,预览里的 XSS 就等于任意文件写入。预览中的链接也不会导航(点一下就离开插件页面回不来了),点击只会把地址显示出来。

不访问网络:没有 net.domains,运行时不发起任何请求。插件数据目录里只放设置(分栏宽度、显示开关、Markdown 默认视图)和上面那份审计日志。

已知限制

  • 不删除文件/目录。删除是破坏性操作,而且宿主的 fs.remove 并没有开放给视图桥,只能走原生 rm——本期不做。
  • 「用默认应用打开」与「在文件夹中显示」只对文件有效(宿主对目录返回 INVALID_ARGUMENT),入口在文件树的右键菜单里。
  • Markdown 预览里的图片仍是占位块:![](path) 显示成带文件名的占位符。文件级的图片查看走 data URI 已经可用,但预览里的图片要逐张按相对路径去读,本轮没做。原因还是那条:面板以 file:// 加载,相对路径指向视图自身而不是项目。
  • 文件监听:宿主没有「插件 → 视图」的推送通道(视图只能发起请求、拿响应),所以外部改动不是实时的;保存后会刷新目录,工具栏的「从磁盘重新加载」随时可点,用来把别处改动拉进来(当前有未保存改动时会先确认)。
  • 二进制不可预览:zip / apk / exe / tiff 只给提示——不解压、不列归档内容、不做十六进制查看。TIFF 单独说明:Chromium 解不了它,所以按二进制处理,而不是显示一张破图。
  • 图片与音视频只读:只做查看,不进编辑器、不能改(不影响其它文件的编辑)。
  • 大文件不预览:图片超过 8 MiB、音视频超过 24 MiB 只提示上限。
  • .avi 这类按二进制处理(Chromium 放不了);.mkv / .mov 能不能播放取决于里面的编码,放不了会明确提示。
  • SQLite 只读:不写入、不 VACUUM、不建 -shm。WAL 模式下尚未合并回主文件的内容看不到(顶部有提示);加密库(SQLCipher)与损坏文件都明确报「读不了」,不会静默空白。
  • 数据库深翻页有上限(OFFSET ≤ 10 万行):node:sqlite 是同步 API、没有中断接口,只能让查询天生有界——要看更远就在 SQL 框里加条件收窄。
  • 表清单里的行数是估算(max(rowid)):精确 COUNT(*) 在大表上是全表扫描,请按需在 SQL 框里执行。
  • 需要宿主运行时带 node:sqlite(Node ≥ 22.5;当前宿主 Electron 43 / Node 24 满足)。探测不到时插件不会崩,只显示概览并说明原因。

许可

MIT。