Skip to content

The Explorer shows files that are gone and misses ones that appeared #292

Description

@jamescrosswell

Opportunity

The Explorer shows the folder as it was when you expanded it, not as it is now, and gives no sign that anything has changed.

The vision's user spends most of their day navigating codebases and reviewing PRs, and a lot happens to their files outside the editor:

  • git switch / git checkout to review a branch adds and removes files.
  • A build writes bin/, obj/ and generated sources, and a test run leaves snapshots.
  • An agent or a script in the terminal next door creates and deletes files.

Afterwards the tree still lists deleted files and leaves out new ones. Nothing marks it as stale, and you can't refresh it, so the workarounds are collapsing and re-expanding the folder or restarting TuiCode. The reviewer reported it on #270 (comment): "these files aren't added/removed from the Explorer automatically (and there's no way to manually refresh the Explorer)."

Evidence from the code (origin/main f1ac508):

  • FileExplorerView reads a folder's children once, when it's expanded. After that it only refreshes them for its own actions (create, delete, move, paste, via RefreshKeepingExpansion).
  • Notice when open files change on disk #133 added DiskWatcher, which watches the files behind open tabs. It doesn't watch the tree, and ShowChangedOnDisk only recolours rows that already exist.
  • There's no refresh command. None of the ~70 commands re-reads the tree.

Most editors the user comes from refresh on their own. VS Code watches the workspace and also has a Refresh Explorer button for when watching fails. Watching has a known cost on Linux: VS Code documents the ENOSPC "unable to watch for file changes" failure on large trees. DiskWatcher's own notes flag the same limit (fs.inotify.max_user_instances, shared by the whole login session).

Options considered

  1. A manual Refresh explorer command only. Cheap and dependable, and uses no watchers. But you have to suspect the tree is stale before you'd refresh it, and the whole problem is that you can't tell.
  2. Re-read the expanded folders whenever the Explorer gets focus. No watchers, and a branch switch shows up the next time you look at the tree. But the tree stays wrong while it's visible and the editor has focus, which is how you usually see it.
  3. Watch every expanded folder, one non-recursive watcher each, and refresh just that folder when it changes. Keep a manual refresh as the fallback. (Proposed.) Covers exactly what the reviewer asked for ("folders that are expanded in the tree"), and what's collapsed costs nothing. It uses the same debounced, degrade-on-failure pattern DiskWatcher already uses. The cost is one watcher per expanded folder, which is usually a handful and can be capped.
  4. One recursive watcher on the workspace root (VS Code's approach). Hears everything with one instance. But on Linux, .NET adds an inotify watch for every directory underneath, including node_modules, bin and obj. That's the ENOSPC trap, and it would need an exclude list and settings. Over the top for a tree that only shows expanded folders anyway.
  5. Poll expanded folders on a timer. Simple, and works on file systems where watchers don't, such as some network mounts. But it's either slow to notice or does a lot of needless reads.

Option 3 includes option 1 as its first slice, so there's a dependable way to refresh before the automatic one lands, and it's still there as a fallback whenever a watcher can't be set up.

Proposal

The Explorer keeps itself up to date, and you can refresh it yourself.

  • Refresh explorer: a new command in the palette (Ctrl+E), with mnemonic re. It re-reads every expanded folder.
  • Automatic: each expanded folder is watched. When files or folders appear, disappear or get renamed in it, that folder refreshes on its own, after the same short settling delay DiskWatcher uses so that a git switch becomes one refresh rather than hundreds. Collapsing a folder stops watching it.
  • The tree doesn't jump. Expanded folders stay expanded, and the selection stays on the same path. If the selected entry is gone, the selection moves to its neighbour, as it does after Delete. The scroll position only moves if it has to.
  • Failure is quiet and safe. A folder that can't be watched (out of inotify instances, no permission) is logged, and Refresh still works for it. Watchers are capped. Past the cap, the most recently expanded folders are watched and the rest rely on Refresh.

Mockup

Before: git switch pr-branch in another terminal adds Parser.cs and removes Legacy.cs.

┌ Explorer │ Find │ Review ─────┐
│ ▾ src/TuiCode                 │
│   ▾ Syntax                    │
│       Grammar.cs              │
│       Legacy.cs            ◀  │
│       Tokenizer.cs            │
│   ▸ Workbench                 │
└───────────────────────────────┘

About a quarter of a second later, with no keypress (Legacy.cs was selected, so the selection moves to its neighbour):

┌ Explorer │ Find │ Review ─────┐
│ ▾ src/TuiCode                 │
│   ▾ Syntax                    │
│       Grammar.cs              │
│       Parser.cs               │
│       Tokenizer.cs         ◀  │
│   ▸ Workbench                 │
└───────────────────────────────┘

The manual route in the command palette:

┌ Commands ──────────────────────────────────────┐
│ > refresh                                      │
│ Refresh explorer                               │
└────────────────────────────────────────────────┘

No new controls, and no status bar message for automatic refreshes. They should just be right.

Scope

In

  • A Refresh explorer command (palette, mnemonic re, rebindable in Settings, no default key).
  • Watching expanded folders, non-recursively, with a debounced refresh of the folder that changed.
  • Keeping expansion, selection and scroll stable across a refresh.
  • A cap on watchers, and logging when a folder can't be watched.

Out

Rough breakdown

  1. I can refresh the Explorer when it's out of date. Refresh explorer re-reads every expanded folder, keeping expansion and selection. Small, dependable, and useful straight away after a branch switch.
  2. The Explorer notices new, removed and renamed files by itself. Watchers on expanded folders, a debounced per-folder refresh, a cap, and quiet degradation. Depends on 1 for the refresh it triggers.

Settled with the reviewer

  1. Cap on watchers: 64 expanded folders, half of Linux's default 128 max_user_instances. Past that, folders rely on Refresh.
  2. No default key for Refresh explorer. Mnemonic re and the palette.
  3. Refresh is only for the tree. Open tabs already notice disk changes by themselves (A tab tells you the moment its file changes on disk #268, A clean tab shows what is actually on disk #269, A deleted file doesn't vanish from under you #271).

Original idea

Recorded from the reviewer's comment on #270 (#270 (comment)), as a follow-up under #133.

If I have a directory open in TuiCode and files are created or removed from that directory by an external process, these files aren't added/removed from the Explorer automatically (and there's no way to manually refresh the Explorer).

Ideally the Explorer would automatically detect changes to the presence of files in any folders that are expanded in the tree.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pitchAn a-team pitch: Lead shapes it, reviewer approves it

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions