kimnTai
## Symptom In VS Code, when an **external tool rewrites an open file on disk** (e.g. an AI coding agent or a code generator), semantic highlighting on the reloaded buffer is applied at the **old file's offsets**. Colors and bold then land on random characters, including inside comments. Typing in the editor works fine; only external writes trigger it. `TypeScript: Restart TS Server` fixes it until the next external write. This looks like a residual of #49 (same-as-disk host content skips the overlay push, and Go keeps the text it read first), now on the tsserver `updateOpen` / `reload` path. Since Go keeps the stale text, diagnostics, hover and so on are probably stale as well. ## Environment - `typescript` → `npm:[email protected]` (latest at time of writing) - Control: `[email protected]` - macOS 26.6.2 arm64, Node v24.19.0, VS Code 1.139.1 ## Repro Put `run3.mjs` in an empty dir next to `tsconfig.json` (`{"compilerOptions":{"strict":true}}`), then run `node run3.mjs <path-to>/lib/tsserver.js updateOpen` (or `reload`). ```js import { spawn } from 'node:child_process'; import fs from 'node:fs'; const file = process.cwd() + '/b.ts'; const v1 = '// 中文\nconst fooBar = 1;\nconst x = fooBar;\n'; const v2 = '// 中文中文中文中文中文中文 上傳成功後把後端路徑\nfunction useThing() {}\nconst fooBar = 1;\nconst x = fooBar;\nuseThing();\n'; fs.writeFileSync(file, v1); let text = v1; const p = spawn('node', [process.argv[2]], { stdio: ['pipe', 'pipe', 'ignore'] }); let seq = 0, buf = '', round = 0; const send = (command, args) => p.stdin.write(JSON.stringify({ seq: ++seq, type: 'request', command, arguments: args }) + '\n'); const ask = () => send('encodedSemanticClassifications-full', { file, start: 0, length: text.length, format: '2020' }); p.stdout.on('data', (d) => { buf += d; const lines = buf.split('\n'); buf = lines.pop(); for (const l of lines) { if (!l.startsWith('{')) continue; const j = JSON.parse(l); if (j.command !== 'encodedSemanticClassifications-full') continue; const s = j.body.spans; console.log('--- round', round, 'mode', process.argv[3]); for (let i = 0; i < s.length; i += 3) console.log(s[i], s[i + 1], JSON.stringify(text.slice(s[i], s[i] + s[i + 1]))); round++; if (round === 1) { fs.writeFileSync(file, v2); // external tool writes the file text = v2; if (process.argv[3] === 'updateOpen') { // what VS Code sends after reloading the buffer from disk send('updateOpen', { changedFiles: [{ fileName: file, textChanges: [{ start: { line: 1, offset: 1 }, end: { line: 4, offset: 1 }, newText: v2 }] }] }); } else { send('reload', { file, tmpfile: file }); } setTimeout(ask, 500); } else { p.kill(); process.exit(0); } } }); send('open', { file, fileContent: v1 }); ask(); setTimeout(() => { console.log('timeout'); p.kill(); process.exit(1); }, 20000); ``` ## Result (identical for `updateOpen` and `reload`) Round 1 = after the external write. **TNB bridge.17**: spans still match `v1` offsets: ``` --- round 1 12 6 "文中文 上傳" 30 1 "c" 34 6 "n useT" ``` **stock [email protected]**: correct: ``` --- round 1 36 8 "useThing" 56 6 "fooBar" 74 1 "x" 78 6 "fooBar" 86 8 "useThing" ``` Control: incremental `change` requests (the user typing, while buffer ≠ disk) return correct offsets on TNB. Only the "buffer becomes equal to the new disk content" case goes stale.