# Changelog

本文件记录 `dsh-ide-workbench` 的每个发布版本。发布页（GitHub Releases）的
说明直接取自这里对应版本的小节；没有对应小节的 tag 会退回 GitHub 自动生成的
提交说明。

## 0.5.1

这一版的重点是**让语义分析（代码索引）在真实机器上站得住**：内存的账算对了、
工作区之间不再互相拖累、状态栏说的和实际发生的一致，外加 Java 语言服务器真正可用。

### 内存的账重新算了一遍

- **不再把同一份内存算两遍**：此前闸门把运行中服务器的**上限**当占用扣了一遍，而整机可用
  内存里已经扣过它的真实占用 —— 同一个服务器被记了两次账。现在只扣它**还可能再涨**的部分
  （上限 − 实测占用）。实测场景：可用 2648 MB、某索引实测 621 MB 的机器上，可用量从
  600 MB 修正为 1206 MB，原本被拒的索引现在能正常启动。
- **实测占用是真的量出来的**：服务器启动前后各读一次系统可用内存，差值即占用；测不出
  （同期有别的进程在抢内存）时作废并退回该语言的基线估算，同时在日志里留痕。存活期内
  每 60 秒复测一次。
- **界面上的「可用内存」是同一个数**：内存紧张弹窗、宿主提示、诊断面板、设置页此前
  各自报各自的数（同一个故障在「首次启动」和「崩溃后重启」时显示的数字都不一样）。
  现在四处同源，并加了回归测试守着。
- **机器级总预算**：所有语言服务器的上限之和不超过 `min(总内存 × 25%, 4 GB)`。这条闸门
  在硬地板之后、内存紧张之前生效，且**用户的「仍然启动」不能突破它** —— 用户回答的是
  「这台机器能不能开这一台」，不是「能不能突破整机预算」。
- **上限按项目规模分档**：源码文件 ≤ 400 的项目上限 1024 MB、≤ 1000 的 1536 MB、更大的
  保持 2048 MB；规模数不出来时按大项目处理（数不出文件不等于项目小）。按语言手动设置的
  上限仍然优先。
- **内存压力释放阀默认开启**：机器真的紧张时，宿主会主动释放一台语言服务器，而不是等
  10 分钟空闲超时。释放顺序可预测：没有在途请求、不是当前焦点、没在构建索引、空闲最久
  的**先**让位；**正在构建 program 的项目永不被让位**（它看起来最闲，却最不该被踢）。
- **「让出内存」不是故障**：被释放的服务器显示灰色「已让出内存」，而不是黄色的故障态；
  下一个语义请求会自动把它唤醒。
- **「仍然启动」不再传染**：此前点过一次「内存紧张也照样启动」，该项目此后每次紧张都
  直接强启、跨工作区跨进程都强启。现在这个记忆只作用于当次启动，下次会重新询问。

### 工作区与项目之间不再互相拖累

- **切换工作区会真正关掉旧工作区的语言服务器**：此前客户端从头到尾没有告诉宿主
  「这个项目我不要了」，旧服务器一直跑到 10 分钟空闲超时，还带着一笔幽灵预留额去压住
  新工作区的索引。现在关闭会**先于**新开发生，闸门读到的是旧服务器退出、内存归还之后
  的真实快照。
- **被换下的工作区不再留下残留**：它的轮询、连接与宿主侧会话一起释放，不会出现「旧
  工作区的状态栏还显示着『语义分析：就绪』」这种无主的就绪。
- **新工作区默认走基础模式，并说明原因**：状态栏显示「本项目未开启」，切换时给一句
  解释，不再是默默不工作。
- **用户的决定会被记住**：手动开启语义分析后，跨工作区切换、跨 DSH 重启都还记得。
- **「总是索引」不再压过用户明确的选择**：一个你明确选过基础模式的项目，即使全局策略是
  「总是索引」也不会被自动拉起；诊断面板的【重新建立索引】随时能让它回来。

### 空闲策略与自动唤醒

- **空闲策略可以选**：永不退出 / N 分钟后退出 / 仅内存紧张时退出；选「永不」会提示大
  项目上约 1.8–2.2 GB 的常驻占用。
- **空闲后第一次语义操作自动把服务器拉起来**，不必再手工重建索引；唤醒走的是和手动
  启动一样的内存预检，机器吃不消时仍然会问。
- **休眠不是故障**：空闲回收后状态栏显示灰色「语义分析：已休眠（省内存）」并给出
  【重新建立索引】。此前同一句「保守版」同时代表没装、启动失败、被降级、空闲回收四种
  完全不同的状态。
- **休眠后文档表仍在**：唤醒时重新打开给新进程，每份文档恰好一次 `didOpen`，不用从头
  再来一遍。
- **空闲定时器不再丢失**，构建 program 期间也不会被空闲定时器误杀。
- **明确选过基础模式的项目不会被自动拉起**。

### 状态栏说的和实际发生的一致

- **四种状态四种文案四种颜色**：降级（黄）、启动失败（红）、未启用（灰）、已休眠（灰）。
- **状态栏不再冻结在旧值**：工作台失活后语义分析状态继续轮询，此前后台两秒就停，状态栏
  可以无限期停在「语义分析：启动中…」。
- **轮询失败显示「状态未知（正在重试…）」**，不再保留一个可能已经过时几分钟的值。
- **启动时能看到等了多久**：「语义分析：启动中…（已等待 2 分 14 秒）」，60 秒后出现
  【取消索引】。
- **会话死了，状态栏就说会话死了**：此前状态栏只读宿主的状态（语言服务器**进程**活着），
  而右键菜单的灰掉读的是浏览器与服务器之间那条**链路**，两者从不互相校验，于是出现过
  「就绪」但悬浮无提示、语义菜单全灰而界面上零提示零按钮。现在客户端会话有自己的状态，
  死掉时显示红色「语义分析：会话未就绪」。
- **【重新连接】是真的能用的出口**：它重做握手并重启宿主的服务器。此前【重新建立索引】
  管的是宿主的服务器，重建一个健康服务器的索引并不能修好浏览器与它之间的链路。
- **坏掉的握手十分钟才暴露**：握手本该毫秒级应答，却一直用着「首个语义请求」的预算
  （最长 10 分钟，用于构建 program）。现在握手有独立的 30 秒预算，失败立刻可见。
- **灰掉的菜单说明原因**：会话没就绪时说「语义分析会话尚未就绪，可在状态栏重新连接」，
  而不是那句在会话死掉时属于撒谎的「该语言服务器不支持查找引用」。
- **「已索引」徽章分两态**：「已索引」与灰色的「已索引（已回收）」可以区分。

### 崩溃与降级

- **被 OOM 杀掉也会真的重启一次**：此前 stdout 关闭与进程退出两个信号同时到达时，第二个
  信号会立刻降级，白白浪费掉那一次自动重启。
- **降级会说明原因**：状态栏提示与诊断面板能直接回答「为什么降级了」。
- **降级弹一次 toast**：崩溃 → 重启 → 降级在同一个瞬间走完，2 秒轮询采不到中间态，此前
  连崩溃提示都会被跳过。休眠**不**弹。
- **宿主侧不再静默**：握手被丢弃时写进宿主日志。

### 索引范围与多仓

- **索引范围可以收敛到项目根**（新增 `lsp.indexScope`）：此前宿主只把作用域根当作
  语言服务器的 `cwd`，而 `cwd` 并不决定 TypeScript 的 program 范围 —— 决定它的是握手里的
  `rootUri`。现在客户端按作用域根发送 `rootUri`，作用域外的文件不再通报给服务器（但仍
  可正常打开）。
- **作用域不会在下一次重启时悄悄退回旧值**：宿主保留最新的一次握手，服务器在运行时收到
  的新握手也会被记下。
- **共享同一个 tsconfig 的多包只建一份索引**：pnpm workspace / tsconfig `references` 的
  多个包，在语言服务器看来本来就是同一个 program，此前会起两个服务器、把同一批文件
  索引两遍。现在合并成一个程序，判定保守（拿不准就合并）。
- **仓库有了 `tsconfig.json`**：tsserver 不再为整个工作区建推断项目（实测 2,335 个文件 /
  84 MB），改用 194 个文件的配置项目。

### Java 语言服务器（jdtls）

- **Java 语义分析真的能起来了**：jdtls 现在由插件用 **JDK 直接启动成 JVM**，而不是把它
  那个无扩展名的 Python 启动脚本当成可执行文件 spawn —— 后者在任何机器上都是 `ENOENT`。
  附带的好处是 JVM 就是插件直接管理的子进程，停服务器时内存真的会还回来，内存上限也能
  落到 JVM 上。
- **JDK 自动发现**：按「设置 → `JAVA_HOME` → PATH → IDE 自己的 JDK 登记表 → IDE 自带
  运行时（JBR）→ 兜底扫描」的顺序找 JDK，取版本最高的一个；启动失败时把找到的全部 JDK
  及版本列出来。路径全部是**发现**出来的，IDE 装在哪个盘、哪个目录都不影响；也可以手动
  填 `lsp.servers.java.javaHome`。找不到时提示明确指向该设置项。
- **每个工程一个 jdtls 工作区**：jdtls 的 `-data` 按工程根隔离。此前它默认按「当前目录的
  文件夹名」做哈希，两个同名工程会共用同一个工作区，索引互相污染，表现为「跳转定义跳到
  另一个工程的同名类」。
- **打开 `.java` 文件就起 Java 服务器**：客户端现在把**当前文件的语言**报给宿主。此前
  宿主按标记文件判定语言，混合仓库（同时有 `package.json` 与 `pom.xml`）恒定判成
  TypeScript，Java 服务器根本不会启动。
- **java 的内存估算按实测修正**：512 MB → 2048 MB。jdtls 起步就是 1 GB 堆，加上 Eclipse
  框架与索引，典型占用 1.5–2.5 GB；此前的估算低了一个量级，内存闸门会放行一台机器养不起
  的服务器。Java 工程因此会比 YAML 工程更早看到「内存紧张」的询问 —— 这是修正后的正常
  行为。
- **设置**：新增 `lsp.servers.java.javaHome`（JDK）、`jdtlsHome`（jdtls 安装根）、
  `javaSearchRoots`（追加的 JDK 扫描根，默认空）与 `javaArgs`（追加的 JVM 参数，默认空）。

### 新增设置

- `lsp.budgetRatio`（默认 `0.25`）：机器级总预算占总内存的比例。
- `lsp.budgetCapMB`（默认 `4096`）：机器级总预算的绝对上限。
- `lsp.maxConcurrentIndexes`（默认 `0`）：0 表示由预算决定；> 0 时与预算取更严者。
- `lsp.pressureYield`（默认 `true`）：内存压力释放阀的总开关。
- `lsp.idlePolicy` 新增取值 `always`（空闲退出**且**压力退让）。
- `lsp.memoryLimitMB` 的语义扩展：由「所有语言的无条件上限」改为「单语言上限的全局上限」。
- `lsp.indexScope`：索引范围收敛到项目根。

### 性能

- **2 秒轮询不再每 2 秒重渲染面板**：仓库没变时状态对象保持同一身份。
- **文件索引排序不再用 `localeCompare`**（实测慢 6–8 倍）。
- **CodeMirror 语言包按需加载**：13 个语言包与语法包不再在页面加载时求值（实测省下约
  291 ms CPU 与 8.3 MB 堆）。
- **120 KB 的样式表不再每帧拼接**。
- **窗口最小化不再误报「状态未知」**：隐藏时只是跳过轮询，回到前台立即刷新。

### 设置页：一个开关就够了

- **取消勾选的语言还能再勾回去**：此前取消勾选会把那个语言从「语言服务器」列表拿走，
  挪进「已关闭的语言服务器」——一个只读的清单，于是它**永远开不回来**。宿主列出语言
  服务器时不再因为它被关掉就跳过它：被关掉的语言照样被探测、照样列在「语言服务器」里，
  只是开关是空的。
- **「已关闭的语言服务器」这一栏删掉了**：勾上就是开、取消就是关，一个开关就够了。
  「未找到的语言服务器」（没装）留着 —— 那是机器的事实，不是设置。
- **状态栏不再把被关掉的语言说成「未安装」**：没装是机器的事实，被关掉是用户的决定，
  两者不再混为一谈。设置页的「未找到」那一行也按同一把尺子过滤了（它列的是**想要、
  但这台机器上没有**的语言）。
- **降级提示的正文是本地化的**：此前中文标题配一段英文技术串。超时、内存、其它三种
  原因各有本地化文案，技术原文移到诊断面板的「原因」一行。
- **诊断面板多一行「最坏情况」**：除了「可用 / 需要 / 已预留」，还给出「若该服务器吃到
  上限，机器将剩多少」，让你自己判断要不要冒险。

## 0.5.0

### 代码智能与索引

- **本地补全 + 语义能力**：所有语言都有**本地补全**（零依赖，Tab / Enter 接受，Esc
  关闭）；配置语言服务器后再加上**语义补全**、**悬停文档**、**签名帮助**、
  **跳转定义**与**语义查找引用**。
- **查找用法现在能列出跨文件引用**：此前只有已经在编辑器里打开的文件才会被计入，
  引用面板几乎总是空的。
- **索引的内存按语言估算**：启动门槛从一刀切的 2 GB 改为按语言估算（YAML 约
  96 MB、TypeScript 约 384 MB），硬下限 256 MB。内存紧张时先弹窗询问「仍然
  启动 / 使用基础模式」，同一项目不再重复询问；低于硬下限时才直接降级。
- **内存上限可以按语言单独设置**：全局上限是**所有**语言服务器的上限，而按语言的
  设置会**替换**它 —— 设置页现在给每个语言一个上限输入框（留空即用全局上限），
  提示文案也讲明「调低它会让每个语言都用更小的上限，调高它也无法突破某个语言
  自身的内置上限」。
- **降级状态看得见**：连续 3 次语义请求失败（或连续 2 次超时）时状态栏从「就绪」
  变为「不稳定」；保守版不再是灰色圆点而是警告色。点状态栏打开诊断面板，可
  查看最近一次失败、语言服务器输出与内存，一键「重新建立索引」或「使用基础
  模式」——不必再靠切换项目恢复。
- **索引总开关**：设置里的开关改名为「构建代码索引」（字段不变，已有配置继续
  生效），关闭后立即结束正在运行的索引进程并释放内存，不再等 10 分钟空闲
  退出。切换项目时旧项目的语言服务器也会停掉（此前它继续占着内存直到 10 分钟
  空闲超时）。
- **内存数字是实时的**：估算值与机器可用内存跟着状态轮询一起回来，诊断面板与
  设置页里的数字不再是打开那一刻的快照。
- **内存判定收敛到宿主一处**：同一份快照由宿主给出结论（可启动 / 紧张 / 不足），
  客户端只渲染；低内存提示说的是**门槛**而不是估算值，不再出现「需要 96 MB、
  可用 200 MB」这种自相矛盾的句子。

### 大项目上的语义请求不再被杀

- **第一个语义请求可以等十分钟**：大型项目里，语言服务器要先为整个项目建好
  program（实测可超过 10 分钟）。此前 30 秒的请求上限在这一次请求上必然触发 ——
  客户端弹「Request timed out」，宿主每 30 秒记一次超时、三次就把正在干活的
  服务器杀掉，重启后又从头构建，形成「构建 → 被杀 → 重建」的自触发循环。现在
  请求预算跟着**「服务器是否已经回答过一个真实请求」**走：在回答之前给 10 分钟，
  之后回到你配置的值。握手 `initialize` 毫秒级回答，不算数。
- **服务器崩溃重启后，客户端与宿主用同一个预算**：宿主在每个新进程上复位自己的
  计数，客户端此前只在通道重开时复位 —— 而语言服务器崩溃并不会关掉浏览器与
  宿主之间的通道。现在宿主在状态里发布一个进程代号，客户端发现代号变了就复位
  自己的预算，两侧不会再一边耐心一边急躁。
- **状态栏在等待索引时说实话**：会话状态在服务器回答 `initialize` 之后 10 ms 就
  变成「就绪」，而 program 还要建好几分钟 —— 状态栏此前说「就绪」，编辑器却
  挂着。现在显示「语义分析：正在构建索引…（首次语义请求可能需要数分钟）」。
  等待没有被缩短（缩短只会把「Request timed out」的噪声请回来），只是变得可见。
- **通知不再被当成超时**：此前一条通知之后只要语言服务器安静 30 秒（首次构建时
  可达数分钟）就算一次「请求超时」。现在只有**真正的请求**超时才算数。
- **握手失败后会话能自己恢复**：此前握手一旦失败，之后每个语义请求都失败，只能
  刷新页面。现在客户端会断开重连并重新握手。
- **崩溃提示按原因说话**：超时不会再被说成「内存不足」，而是提示调高「单次请求的
  上限」；真正是内存不足时才提示关闭其它程序或改用保守版。

### 稳定性：一个语言服务器不再能带走整个 IDE

- **修掉了把整个 DSH 进程带走的崩溃**：宿主把 JSON-RPC 帧写进语言服务器的 stdin 时
  没有给这个流挂 `'error'` 监听器（stdout / stderr 都有，唯独 stdin 没有）。管道
  断裂产生的 `EPIPE` 是**异步**经由流的 `'error'` 事件抛出的，写操作外面的
  `try/catch` 永远抓不到；事件无人接收 → 未捕获异常 → 整个宿主进程死亡（表现为
  「应用无法启动或已意外停止」），触发路径正是切换 Project / 选择构建。现在这条
  路径被彻底封住，并且不止是挂一个监听器：旧 worker 会在新进程起来之前被停掉、
  worker 的消息回调加上了 generation 栅栏（旧代的帧不再写进新服务器的 stdin）、
  进程被杀之前先清空句柄（在途帧变成空操作而不是写进断管）。
- **插件有了错误边界**：插件接管宿主的 `uncaughtException` / `unhandledRejection`
  事件 —— 属于插件自己的错误被拦下并降级语义分析，不属于插件的错误原样交还给
  宿主，宿主「致命错误即退出」的策略一点没变。
- **每个 socket 都有了错误监听器**：终端的 WebSocket 服务器、终端 socket、语言
  服务器中继的 WebSocket 服务器此前完全没有 `'error'` 监听器 —— 一个终端打不开
  不该带走整个 IDE。
- **与语言服务器无关的故障不再牵连语义分析**：终端或文件操作出错时，状态栏不再
  把所有语言服务器降级成「保守版」，真实原因留在日志里。

### 查找与引用

- **全项目查找改为弹窗**：`Ctrl+Shift+R` 在编辑器上方弹出结果浮层，不再占用
  代码标签页；上半区是匹配到的**文件列表**，下半区是所选文件的**预览**（每一处
  命中带前后各 5 行上下文）。单击文件只预览、双击文件（或预览里的某处命中）
  打开并定位。「查找用法（文本版）」同样走弹窗，两者不再互相覆盖查询。
- 弹窗里单击文件行后键盘仍然可用（Esc / ↑↓ / F8）；两个入口不再互相继承查询词；
  跳转带上**列号**，光标落在命中处而不是行首。

### Git 与提交

- **提交后可推送**：提交面板新增独立「推送」按钮，顶栏 `↑N` 徽标可点并说明
  「领先 N 个提交」；无上游分支时给出中文提示与可复制的命令。
- **文件树 Git 着色**：未跟踪目录下的每个文件都按文件着色（此前整个目录只有
  一条记录，目录下的文件不着色）；目录色由子文件聚合而来。双击目录条目不再
  出现英文的 `path is a directory`。
- 状态条目有上限（20000）：超大仓库的状态列表会被截断并提示，不再无限增长。

### 视觉与主题

- **配色跟随宿主**：悬停卡、签名帮助、补全弹窗、引用面板与编辑器语法高亮不再
  写死黑白与暗色系色板，全部改由宿主的 `--dsw-alias-*` 令牌驱动。浅色主题下
  悬停卡里不再出现纯黑代码块；选中行使用「品牌色上的文字色」，浅色品牌色也
  读得清。
- 语法色板改成**随主题**：十支语法色由「色相 + 主题文字色」混合而来，浅色与深色
  底上的对比度都在 5:1 以上（旧色板在深底上只有 2.58:1）。
- 文件树、编辑器页签、提交面板的文件图标改用宿主的 `FileTypeIcon`：与 DSH
  自带文件浏览器、`dsh-better-sidebar` 同一套品牌 logo（React / Java /
  TypeScript / C / C++ 等 48 种）与分类色 sheet 图标，随宿主皮肤自动切换。
- 删除本地手绘的 `FILE_TINTS` 调色板与约 190 行类型字形，以及本地文件夹图标；
  终端工具栏与项目选择器也改用宿主文件夹，全插件只剩一套文件夹字形。

### 设置与交互

- **索引决策弹窗**：文案改为讲清代价与收益（「启用语义分析」而非「构建代码
  索引」），显示预计耗时与内存；非根目录新增「取消切换」（点了不切项目），
  根目录下三个按钮权重相同。
- **弹窗层级**：切换项目时的「未保存修改」警告现在盖在「选择 IDE 项目」之上
  （此前两者同层，警告被完全遮住），并给出「保存并切换 / 放弃修改并切换 /
  取消」三个明确出口。
- **弹窗的按钮看不出是按钮**：边框是一根发丝线，和弹窗表面几乎同色。现在弹窗里的
  按钮有更清楚的边界、更大的点击区域和与表面分开的底色，话术也从三句话减到
  一句（不再和按钮重复预计时间，语言显示 `TypeScript` 而不是 `typescript`）。
- **设置页此前每一行都没有标签**：组件名被写成了字符串，React 渲染成未知元素，
  标签和说明变成了不可见的 HTML 属性，只剩下几个孤零零的数字框。九处已改回
  组件。
- **设置页的字段块输入框此前是黑框白底**（浏览器默认外观）：给输入框上色的
  那条样式规则只覆盖了行输入框，字段块不在选择器里。现在两者共用同一条规则，
  输入框与同一页的其它输入框完全一致。
- 设置页新增按语言启停、内存余量系数、硬下限等参数，并显示当前可用内存与估算
  需求。

### 修复

- 机器繁忙时 `Ctrl+点击` 跳转偶尔静默失效：语法树是按时间预算在后台解析的，
  光标附近还没解析完时标识符取不到。现在改为向解析器索要**覆盖该位置**的
  语法树，答案不再取决于负载。
- 终端底部常驻黑色条：xterm 的 viewport 铺满整个终端且自带硬编码黑底，而
  内部 screen 只有 `rows * cellHeight` 高，FitAddon 取整后必然剩下一条 1–14px
  的黑边（任何窗口尺寸都在）。现在 viewport 让出终端自己的背景色。

### 发布

- 打 tag 发布时同时创建 GitHub Release：附带变更说明、源码，以及一个自带
  `node_modules` 的离线 npm 构建包。该 `.tgz` 地址也能直接填进 **设置 → 插件 →
  添加插件**，装的时候完全不需要访问 npm registry。

## 0.4.1

### 性能

- `store.set()` 跳过全等补丁：等值写入不再替换状态、也不唤醒订阅者
  （50 次等值写入 → 0 次通知）。
- 切片订阅：ProjectPanel、EditorArea、GitPanel、CommitPanel、TerminalPanel、
  SearchView、DiffView 各自通过新的 `useStoreSlice()` 订阅自己的切片，无关写入
  （toast、终端分块、git 轮询）不再触发这些面板重渲染。
- 文件树 flatten memo 只依赖它真正读取的字段；树行是带稳定 `useCallback`
  处理器的 memo 化 `TreeRow`。
- LRU `StoreCache` 最多保留 3 个工作区 store，且绝不淘汰仍有 Session 挂载的
  那个。
- 热工作区复用已缓存的状态（0 次 git 子进程）；每个文件的行数只在提交窗口可见
  时统计；`--ignored=matching` 跟随"显示被忽略文件"设置。
- 冷工作区只列根目录、只读取聚焦页签。
- 目录列表改为一次 `readdir(withFileTypes)`，不再逐项 `lstat`。
- 宿主会话表做缓存，不再每次渲染都读。
- 开发用的 `window.__iwbPerf` 计数器不出现在生产构建里。

### 交互

- 树形 twisty 点击展开目录且不选中该行；"定位文件"按钮可在树中显示当前文件。
- Git 仓库选择器只挑选仓库，不动文件树；IDE 项目不会因此改变。

### 修复

- 3 个严重、1 个验收缺口、9 个中等、7 个轻微问题已修复。

## 0.4.0

### 功能

- 发送到聊天：文件树与代码编辑器都能把文件/文件夹作为 @path 引用插入；代码
  选区会带上行号范围与围栏代码块。
- 发布前 QA 修复：终端 refit/resize、感知 CRLF 的冲突处理、关闭时刷新设置、
  查找文件选项开关、Markdown 预览默认开启、恢复的树加载、编辑器右键菜单。
- 新图标与一行徽标条；README/README_EN 重写为纯 GFM，图片改用绝对地址，特性
  改成两列表格。
