Repository navigation
How Recent Files Work
This chapter explains how the Recent Files popup (Cmd+E) keeps its list, saves it per workspace and shows it. The user side is in Recent Files.
People press Cmd+E all day to jump between the files they work on, and the user asked for the same popup. Before it, Quick Open and Search Everywhere built their "recent" sections from the Back and Forward history. That history is a list of places, not of files: going back reorders it, and it is empty after a restart. A real most recently used list fixes both and gives the three popups one source.
The list is an array of RecentFile (filePath, edited), newest first. The rules live in the pure module src/lib/stores/recentFiles.ts:
-
touchRecentmoves a file to the top, or adds it there, keeping itseditedflag. Pseudo tabs (terminal, commit, Git and compare tabs) are ignored. The list is capped atMAX_RECENT_FILES(50). -
markEditedsetseditedwithout moving the file. -
removeRecentdrops a file. - Each returns the same array when nothing changed, so the store can skip a save.
recentFilesStore.follow() in src/lib/recentFiles/recentFilesStore.svelte.ts is called once from runWorkspace() (views/workspaceLife.svelte.ts), so it lives exactly as long as a workspace is open. It runs three effects:
flowchart LR
W["repoStore.workspace.id"] -->|load| L["settings.recentFileLists[id]"]
O["repoStore.openFilePath"] -->|touchRecent| S["recentFilesStore.files"]
D["repoStore.dirtyPaths"] -->|markEdited| S
S -->|rememberRecentFiles| J["state.json recentFileLists"]
S --> P["RecentFiles.svelte"]
S --> Q["Quick Open, Search Everywhere"]
- A new workspace id loads that workspace's saved list and closes the popup.
- A change of
openFilePath(the focused group's active tab) touches that file. A file outside the workspace folders is skipped, so a tab left over from the previous workspace never lands in the new list. - Every path in
dirtyPaths(tabs with unsaved edits) is marked edited. That is what "Show edited only" filters on.
Each change calls settings.rememberRecentFiles(workspaceId, files), which stores the list under the recentFileLists key of state.json (see How Settings Work). withRecentFiles moves the workspace to the end and keeps the newest MAX_TAB_SESSIONS (30) workspaces, like the saved tabs. On load, parseRecentFiles keeps only absolute paths that isSavablePath accepts, drops repeats and pseudo tabs, and caps each list. The settings store's own debounce turns a burst of tab switches into one write.
RecentFiles.svelte is loaded with a dynamic import the first time it opens, and unmounted when it closes. The pure part is recentFilesModel.ts:
-
recentFileRows(files, folders, query, editedOnly)turns paths into rows withpathRows(so files outside the workspace drop out and multi-folder workspaces lead with the folder name), then filters withmatchRecentRowfrom Quick Open's model. Unlike Quick Open, the rows keep their recent order instead of sorting by score. -
initialRecentSelectionpicks row 1 when row 0 is the file on screen and there is no query, so Cmd+E, Enter goes to the previous file.
The component adds the git tones of views/files/tones.ts (shared with the Files panel), the unsaved dot from repoStore.isDirty, and the keys: arrows and Ctrl+N/P, Enter and Cmd+Enter (to the side), Esc, the Recent Files shortcut again to toggle edited only, and Delete (Cmd+Backspace on a Mac with an empty filter) to remove a row.
Opening a row first checks navigation.fileExists. A file that is gone is removed from the list with a toast instead of opening an error tab. Otherwise navigation.openFileAt(filePath, null, null, { pin: true, toSide }) opens it and focuses its editor.
Settings > Editor > Recent Files (recentFiles in settings.json, on by default) turns the feature off. The store's effects read it, so with it off load(null) empties the list, nothing is touched or marked, and nothing is written. The lists in state.json stay untouched, so turning it on again loads them back. The saved lists use their own key, recentFileLists, because the settings store applies settings.json and state.json to the same object and the two names must not meet.
With it off, openRecentFiles in workspaceActions.ts shows an info toast with an Open Settings button (settings.openDialog("editor")) instead of the popup, so Cmd+E never seems to do nothing. Quick Open and Search Everywhere get an empty list and fall back to the open tabs.
edit.recentFiles is an Edit menu item with CmdOrCtrl+E and a window command (WINDOW_COMMANDS in workspaceShortcuts.ts), so it works from the editor and, on macOS, the terminal. The commit message box handles Cmd+E first for its message history and calls preventDefault, so windowCommand skips the key there. shortcutsBlocked and overlayOpen in workspaceActions.ts include the popup, so it never opens over another popup and other window shortcuts wait while it is up. The MCP close_dialog tool knows it as recentFiles.
| File | What it does |
|---|---|
src/lib/stores/recentFiles.ts |
The list rules, parsing and saving per workspace |
src/lib/recentFiles/recentFilesStore.svelte.ts |
The open workspace's list, following the editor, popup open state |
src/lib/recentFiles/recentFilesModel.ts |
Popup rows, filtering, the first selected row |
src/lib/recentFiles/RecentFiles.svelte |
The popup, on the UI library's CommandPalette (preferred row, Remove buttons) |
src/lib/stores/settingsData.ts, settings.svelte.ts
|
The recentFiles setting and the recentFileLists key of state.json
|
src/lib/views/settings/EditorFeatureSettings.svelte, views/settings/memoryCost.ts
|
The Settings row and its memory mark |
src/lib/menu/menuSpec.ts, menuState.ts, menuActions.ts
|
Edit > Recent Files |
src/lib/views/workspaceShortcuts.ts, workspaceActions.ts
|
The window key and the open guard |
src/lib/quickOpen/QuickOpen.svelte, src/lib/search/FileSearch.svelte
|
Their recent sections read the same list |
A list of files, not of places. Back and Forward stay a history of places (How Navigation Works). Recent Files only cares which files, in which order, which is simpler to keep and to save.
Saved per workspace. Recent files belong to a project. Keying by workspace id matches the saved tabs, and 50 paths per workspace is a few kilobytes at most.
Recent order while filtering. The list is short and you usually want a file you just saw, so the order you know beats a fuzzy score.
Edited means unsaved edits seen. The editor already reports dirty tabs, so marking from dirtyPaths needs no new hook into the editors.
-
src/lib/stores/recentFiles.test.ts: moving to the top, the cap, edited marks, removal, same-array returns, parsing badstate.jsonvalues, and the per-workspace cap. -
src/lib/recentFiles/recentFilesModel.test.ts: rows in order with folders, edited only, filtering with highlights, multi-folder names, and the first selected row. -
settingsData.test.ts,registry.test.ts,menuState.test.tsandworkspaceShortcuts.test.ts: thestate.jsonkey, the menu item and Cmd+E, also that a key the commit box handled is skipped.
- A new kind of non-file tab must be covered by
isPseudoTab, or it would join the list. - Changing the popup's keys: update
help/shortcuts.tsand the Recent Files page. - Another popup that lists recent files should read
recentFilesStore.filePaths().
These pages are generated from docs/wiki. To correct something, open a pull request against the source file; edits made here are overwritten.
Using Nibtree
- Getting Started
- Welcome Screen
- Workspaces
- Files Panel
- File Operations
- File Icons
- Rounded Panels
- Editor and Tabs
- Pin Reorder and Wrap Tabs
- Single Tab Title
- Split Editor
- New File and Unsaved Changes
- Unload Hidden Tabs
- Editing Code
- Code Appearance
- Markdown Editor
- Image and PDF Preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and Replace
- Changes and Commits
- Sign In Prompts
- Commit Box Layout
- Repository Actions
- Commit Options
- Ignoring Files
- Diffs
- Inline Diffs
- Staging View
- Revert a Change
- Resolving Conflicts
- Merge Tool
- Blame
- History and Log
- Merge Commits
- Branches and Tags
- Branches Popup
- Remotes
- Stashes
- Shelf
- Git Menu
- Git Dialogs
- Interactive Rebase
- Worktrees
- AI Agents
- AI Tasks
- AI Usage
- Keep Computer Awake
- Submodules
- Git LFS
- GitHub
- Git Console
- Terminal
- Terminal Features
- Scripts
- Navigation
- Menus
- Settings
- Settings Search
- Color Themes
- Settings Terminal and Automation
- Settings Files
- Updates
- Status Bar and Help
- Notifications
- Memory Use
- Clear Cache
- MCP and CLI
- Git Mergetool
- Keyboard Shortcuts
- Keyboard Shortcuts Editor
- Troubleshooting
- Troubleshooting Git
Developers
- Developer Guide
- Architecture
- Project Layout
- Backend
- How Folder Watching Works
- Folder Watching Bugs We Fixed
- How Workspace Files Work
- How the Preview Scheme Works
- Backend Services
- How Terminal Features Work
- Terminal Bugs We Fixed
- Frontend
- How the Rich Markdown Editor Works
- Rich Markdown Bugs We Fixed
- Git Menu Links
- Menu Keys and Routing
- Frontend Modules
- Blame Bugs We Fixed
- Project Color And Title Bar
- One Diff View
- Diff Bugs We Fixed
- Merge Tool Layouts
- Merge Tool Bugs We Fixed
- Color Theme Bugs We Fixed
- How the Connected Diff Works
- Rounded Panels Bugs We Fixed
- Update Bugs We Fixed
- Status Bar Bugs We Fixed
- MCP and CLI Bugs We Fixed
- Repository Actions Bugs We Fixed
- How the Changes Tab Works
- CodeMirror Patterns
- How the Cursor Works
- How Editor Features Work
- How the Path Bar Works
- Commands and Events
- Settings Reference
- Commands Workspace and Changes
- Commands Branches and Remotes
- Commands History and Extras
- Commands App and Tools
- Testing
- Test Suites
- Debugging
- Debugging with MCP
- Measuring Setting Memory
- How Terminal Memory Is Measured
- How Markdown Diagrams Work
- Releases and CI
- Publishing the Wiki
- Versioning and Changelog
- Browser Version Packages
- Browser Release Checks
- Docker Image
- Platforms and Signing
- Windows Support
- Windows Bugs We Fixed
- Windows MCP and CLI
- Docs and Screenshots
- UI Library
- UI Library MCP
- UI Library Bugs
- UI Library Migration
- Contributing
How features work
- Welcome screen
- Workspaces and folders
- Files panel
- File operations
- File icons
- Editor and tabs
- Pin, reorder and wrap tabs
- Single tab title
- Split editor
- New File and unsaved changes
- Unload hidden tabs
- Editing code
- Code appearance
- Markdown editor
- Image and PDF preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and replace
- Changes and commits
- Sign-in prompts
- Commit box layout
- Repository actions
- Commit options
- Ignoring files
- Diffs
- Inline diffs
- Staging view
- Revert a change
- Resolving conflicts
- Merge tool
- Blame
- History and log
- Merge commits
- Commit tabs
- Branches and tags
- Branches popup
- Remotes
- Stashes
- Shelf
- Git menu
- Git dialogs
- Interactive rebase
- Worktrees
- AI agents
- AI usage
- Keep computer awake
- AI tasks
- Submodules
- Git LFS
- GitHub account
- Git Console
- Back and Forward
- Integrated terminal
- Scripts tool window
- Menu bar
- Settings
- Search in Settings
- Color themes
- Rounded panels
- Updates and release channels
- Status bar and help links
- Notifications
- Memory readout and memory log
- Clear Cache
- MCP server and command line tool
- git mergetool mode