(viewUUID: string, filePath: string)
| 453 | const FILE_WATCH_DEBOUNCE_MS = 150; |
| 454 | |
| 455 | export function startFileWatch(viewUUID: string, filePath: string): void { |
| 456 | const resolved = path.resolve(filePath); |
| 457 | let stat: fs.Stats; |
| 458 | try { |
| 459 | stat = fs.statSync(resolved); |
| 460 | } catch { |
| 461 | return; // vanished between validation and here |
| 462 | } |
| 463 | |
| 464 | // Replace any existing watcher for this view |
| 465 | stopFileWatch(viewUUID); |
| 466 | |
| 467 | const entry: ViewFileWatch = { |
| 468 | filePath: resolved, |
| 469 | watcher: null as unknown as fs.FSWatcher, |
| 470 | lastMtimeMs: stat.mtimeMs, |
| 471 | debounce: null, |
| 472 | }; |
| 473 | |
| 474 | const onEvent = (eventType: string): void => { |
| 475 | if (entry.debounce) clearTimeout(entry.debounce); |
| 476 | entry.debounce = setTimeout(() => { |
| 477 | entry.debounce = null; |
| 478 | let s: fs.Stats; |
| 479 | try { |
| 480 | s = fs.statSync(resolved); |
| 481 | } catch { |
| 482 | return; // gone mid-atomic-write; next rename will re-attach |
| 483 | } |
| 484 | if (s.mtimeMs === entry.lastMtimeMs) return; // spurious / already sent |
| 485 | entry.lastMtimeMs = s.mtimeMs; |
| 486 | enqueueCommand(viewUUID, { type: "file_changed", mtimeMs: s.mtimeMs }); |
| 487 | }, FILE_WATCH_DEBOUNCE_MS); |
| 488 | |
| 489 | // Atomic saves replace the inode — old watcher stops firing. Re-attach. |
| 490 | if (eventType === "rename") { |
| 491 | try { |
| 492 | entry.watcher.close(); |
| 493 | } catch { |
| 494 | /* already closed */ |
| 495 | } |
| 496 | try { |
| 497 | entry.watcher = fs.watch(resolved, onEvent); |
| 498 | } catch { |
| 499 | // File removed, not replaced. Leave closed; pruneStaleQueues cleans up. |
| 500 | } |
| 501 | } |
| 502 | }; |
| 503 | |
| 504 | try { |
| 505 | entry.watcher = fs.watch(resolved, onEvent); |
| 506 | } catch { |
| 507 | return; // fs.watch unsupported (e.g. some network filesystems) |
| 508 | } |
| 509 | viewFileWatches.set(viewUUID, entry); |
| 510 | } |
| 511 | |
| 512 | export function stopFileWatch(viewUUID: string): void { |
no test coverage detected
searching dependent graphs…