Repository navigation
feat(folder): open a folder in a sidebar that stacks with the tab column - #964
ryanhoggpm wants to merge 6 commits into
Conversation
Adds a Tab Position setting (Top, Left Column, Right Column). With a column, the open documents are listed down one side of the window under the title bar, reusing TabList and Tab so the context menu, middle-click close and drag-to-reorder behave the same. The title bar shows the window title instead of the strip, the column is resizable, and the window tag colour runs along its inner edge. The default stays Top, so nothing changes for existing installs.
Tab Position gains Bottom: the same horizontal strip, docked along the bottom edge of the window instead of the title bar. TabColumn becomes TabDock, which handles the bottom strip and the side columns. Only a strip inside the title bar is a window drag region.
This reverts commit e1ab278.
- external-change-bar sits inside .content-area, so it spans the document instead of covering the column's first rows. - .content-area beside the column is absolute, not fixed: with no z-index it opens no stacking context, so the home page's context menu stacks above the column and title bar again. - the column resizer is a focusable separator with the outline handle's keys and its range in aria-valuemin/max. - settings.tabPlacement is the one answer the title bar and the column read.
Adds Open Folder to the main menu. The folder appears as a tree in a side column. With the tabs in a side column, the folder and the open files stack in one column, in the order chosen by Sidebar Stack Order, with a draggable divider; otherwise the folder takes its own side. The tree reads one level per expand and watches only expanded folders, non-recursively, so a large folder costs one read_dir of what is on screen. By default it lists only the files Markpad opens; Show All Files lists the rest dimmed and hands them to the system's default app through the same path document links already use. On Windows the tree does not list symlinks or junctions. Following one to a UNC path would make Windows offer the user's NTLM credentials to that host as soon as its parent folder was expanded (the attack sftwrdotdev#876 closed for documents); entries are typed from read_dir's own records, so a listing never touches a link's target.
…d folder - folder.rs keeps one RecommendedWatcher per window and watches or unwatches each expanded folder on it, instead of a watcher (an inotify instance and thread, or an FSEvents stream) per folder. Events map back to the folder the frontend named through its named and canonical paths, since FSEvents reports resolved ones, and are coalesced per window. - a subfolder whose read fails is collapsed the way toggle collapses it, so its listing, its descendants and their watches go too. - folder-changed is listened for on the current window only. - the module comment no longer claims a followed link cannot reach the network outside Windows.
fcf2301 to
3ae7628
Compare
|
Rebased onto the updated #963 (force-pushed, so please reset your local branch) and added one commit. The conflict resolution is folded into your commit: TabDock and TabColumn are gone in favour of Sidebar. |
What this is
A proposal, opened knowing #149 declined a folder tree. It adds Open Folder to the main menu and shows the folder as a tree in a side column. When the tabs from #963 are in a side column, the open files and the folder stack in one column, with a draggable divider and a setting for which goes on top. Otherwise the folder takes a side of its own.
It is stacked on #963 (vertical tabs, #884), so the first two commits here are that PR. Only the last commit,
fcf2301, is new. If this isn't something you want, no hard feelings; it will live on in my fork.New settings, all defaulting to off or to today's layout:
The divider position and the column width are remembered. The last opened folder reopens on launch in the main window. A folder deleted since then is dropped quietly rather than shown as an error.
Mechanism
#149 named two costs. This is how the PR answers each one.
Keeping a tree in sync with the disk. Nothing walks a tree.
read_folder_entrieslists one level, and the sidebar asks for a folder's children only when the reader expands it. Opening a folder withnode_modulesin it costs oneread_dirof its top level. Only expanded folders are watched, each with a non-recursivenotifywatcher, and the watch is dropped on collapse, on closing the folder, and when the window is destroyed. A change emitsfolder-changedwith that folder's path, coalesced over 200 ms through the existingcoalescedhelper, and the frontend re-reads that one level. The Rust side keeps no tree state, so there is nothing on that side to drift.Folders full of files Markpad doesn't open. By default the tree lists only folders and the files Markpad opens, decided by the same
MARKDOWN_LINK_EXTENSIONSlist the Open dialog filters on. There's no third copy of the list. With Show All Files on, the other files appear dimmed. Clicking one hands it to the system's default app throughopenWithSystem, which is the existing document-link path factored out ofhandleDocumentClick. Theis_launchable_pathguard therefore applies to the tree too: a.exeor.appis revealed in its folder, never run.Links on Windows. The tree does not list symlinks or junctions on Windows. If a folder someone unzipped or cloned contained a link to
\\host\share, following it would make Windows offer the user's NTLM credentials to that host as soon as the parent folder was expanded, with no click. That is the attack #876 closed for documents. Entry types come fromread_dir's own records, so a listing never touches a link's target. I first tried checking where each link pointed, but deciding which UNC spellings, link chains and check-then-use races are safe is a losing game. Leaving links out needs none of it. On macOS and Linux links are listed and followed, because a network share there is a mount the user made.Scope
Ctrl+Shift+Ois taken by Monaco's Go to Symbol, and I didn't want to pick a new chord for you.i18nCoverageexpects.Tests
src-tauri/src/folder.rs: folders sort first and by name ignoring case; dot entries are hidden and only one level is read; a file or a missing path is not a folder. On Unix, a link to a folder lists as a folder and a dangling link is dropped. On Windows, a link to\\unreachable.invalid\shareis left out of the listing. That test skips itself when the runner can't create symlinks.scripts/folderSidebar.spec.ts(vitest, fake disk behind a stubbedinvoke): opening reads and watches only the top level; collapsing unwatches the folder and its descendants;folder-changedre-reads only a loaded folder; closing forgets everything; restore drops a folder that no longer exists; the path helpers handle both separators.scripts/settingsPersistence.spec.ts: the four new settings round-trip, and invalid values fall back.scripts/localFileLinks.test.ts: retargeted fromhandleDocumentClicktoopenWithSystem, where theopenPathcall, the launchable check and the failure toast now live. It asserts the same order and the same try/catch. It also asserts the click handler still resolves the rawhrefbefore callingopenWithSystem, and still falls back toopenUrl.Verification
The vitest timeout is
diagramThemeRefresh.spec.tshitting the 5 s limit under full-suite load. It passes on its own, and it times out on unmodifiedmastertoo (noted in #963).Run by hand on Linux (WSLg, WebKitGTK 2.52): opened this repo as a folder; expanded
samplesand opened files from it with the tabs at each position; swapped the stack order, dragged the divider and collapsed each section; turned on Show All Files and clickedpackage.json, which opened in the system app;touch samples/new-note.mdfrom a shell appeared in the tree.Not verified: Windows and macOS at runtime. The Windows link test has only been reasoned about, not run, so CI's Windows job will be its first run.