Repository navigation
feat(windows): add an "Ask me" option for which window a file opened from outside goes to - #1040
Conversation
…from outside goes to Settings > Files gets "Open external files in": "Last window" (the default, today's behaviour) or "Ask me". With "Ask me" and more than one window open, a file opened from Explorer, Finder, "Open with" or a second launch shows a picker in the window Rust delivered it to, which is offered first. Open sends the files to the chosen window, and Cancel opens nothing. A file that some window already has open never reaches the picker: Rust sends it to that window first (sftwrdotdev#1038). A file this window has open switches to its tab without asking. Explorer starts one process per selected file, so files that arrive within 250 ms are asked about together, and files that arrive while the picker is open join it. Launch files are asked about too, but only once every window of the launch has restored, so the list is complete and the files that a reopened window has open have already gone to it. Rust broadcasts `windows-restored` once every open window has asked `send_markdown_path`, and `windows_restored` answers a window that starts later. A window that never finishes loading would hold the event back, so each window stops waiting after 10 seconds. `Modal` gains `confirmLabel`, `children` and `onkeydown` for the list and its arrow keys. The picker opens files one after another through `openFiles`, as the launch's files always have. Refs sftwrdotdev#1029 Assisted-By: Claude Opus 5.5
At launch the reopened windows come up over the launch window after Rust has delivered the launch files to it, so the picker opened hidden behind them, and the click that brought it forward landed on the dialog's backdrop and cancelled it. While its picker is on screen, a window is now held in front of the other Markpad windows (`hold_front`): it is brought forward when the picker opens, and a viewer window focused meanwhile hands the focus straight back, as an app-modal dialog's owner does. Other apps are not affected. The hold ends when the picker is answered or cancelled, or the window closes. Refs sftwrdotdev#1029 Assisted-By: Claude Opus 5.5
…ry window has restored The launch files open while sftwrdotdev#990 is still reopening the other windows, and a window that finishes restoring later comes up over the one that took them. With "Last window" set, the file was there but hidden. Rust now remembers the last window that took launch files (the launch window, or a reopened window that has one open, sftwrdotdev#1038) and brings it back to the front when it sends `windows-restored`, which is the moment the last window has finished. No delay is involved. Refs sftwrdotdev#1029 Assisted-By: Claude Opus 5.5
|
Thanks, this reads well. The picker logic and the launch-file fix both look right to me, and keeping the launch-file fix in this PR is fine. I ran everything except One question about It also has a side effect: with the picker open in A, opening a file from Explorer that B already has open sends it to B, but B is pushed straight back behind A. If dropping |
…anches request() joins whichever list is collecting the path in one place, and confirm() opens here unless the selected window is another one that is still open, so each outcome is written once.
|
Thanks for the review and for running the checks. You're right about the B side effect. The third commit covers launch with a single file, but not a mixed batch. Say
Clicking the launch window to get back to the picker lands on the dialog's background, which cancels it. I hit that in testing with a single file, before adding the hold and before the third commit. The hold also covers clicking another Markpad window while the picker is open, which otherwise leaves it behind that window. Options I see:
I'd go with 2, since it keeps the picker in front of you without fighting the window Rust just raised, though it's the most code of the three. Which do you prefer? |
|
Good catch on the mixed batch, I missed that Let's go with 1, and I've pushed it myself, so there's nothing for you to change:
The remaining case, clicking another Markpad window and then clicking back onto the dialog's background, is how every Everything except |
…ront With a mixed launch batch, a reopened window that takes its own launch file is raised at `windows-restored` and can cover the launch window's picker. Raising the asking window when the picker opens covers that. The hold also pushed back any window Rust had just raised for a file, so `hold_front` and its focus hand-back go.
It works on windows with the test build from run 40658097535 |
What this is
The "Ask me" half of #1029, on top of #1038 as @PathGao suggested. Settings
Finder, "Open with" or a second launch shows a picker listing the open
windows. Each is named as the tab's "Move to window" menu names it, with
its tag colour.
The window Rust delivered the file to, the focused or last focused one, is
the one that asks. It's listed first and selected, so Enter keeps the old
result. The picker brings its window to the front when it opens. Open sends the
files to the chosen window, and Cancel (or Escape, or a click outside)
opens nothing. The keys are the existing dialog's: Enter/y, Escape/n, plus
↑/↓ and Home/End for the list.
A file that some window already has open never reaches the picker. #1038
sends it to that window, so the picker only ever asks about files that are
open nowhere.
The hover hint says it only asks with more than one window open, and that
files opened from inside Markpad always open where you are.
Closes #1029
Mechanism
Rust picks the receiving window and sends it
file-path, and the windowloaded the file at once. Now
file-pathgoes throughcreateOpenFilePicker(
src/lib/sessions/openFilePicker.svelte.ts), which loads the file at onceunless all of these hold:
Multi-select. Explorer starts one process per selected file, so a
multi-select arrives as separate deliveries a few milliseconds apart. Files
that arrive within 250 ms are asked about in one picker ("Open 3 files in…"),
and any that arrive while it's open join it. I checked this on Windows with 30
files at once, and with 60 where 30 arrived late: all of them landed in one
picker.
Launch files. Since #990 a launch reopens every window, and the launch
files reach the main window before the reopened windows have restored. Asking
then would list windows that haven't registered yet. It would also ask about
files a reopened window is about to take itself (#1038). So nothing is asked
until every window of the launch has restored:
windows-restoredevent once every open viewerwindow, the launch window included, has asked
send_markdown_path. Eachwindow asks only after its restore and its first
set_window_meta, so bythen the window list is complete.
mid-startup never asks.
windows_restoredand doesn'twait.
finishes loading, each window stops waiting after 10 seconds.
Keeping the picker in sight. At launch, a reopened window that takes its
own launch files is raised when every window has restored (see below), and
can come up over the launch window, which is the one asking. So the picker
raises its window once, with the existing
focusWindow, when it opens.Clicking another Markpad window while the picker is open still leaves it
behind that window, and clicking back onto the dialog's background cancels
it. That is how every
Modalbehaves today, so it is left toModal.So with "Ask me" set, a launch that reopens several windows and is given
a.md(open in a reopened window) andb.md(open nowhere) bringsa.md'swindow forward and then asks about
b.mdalone.The readiness signal comes from Rust because the frontend can't tell the
launch window apart:
windows-are-equalineslint.config.jsforbids it.Smaller pieces:
ModalgainsconfirmLabel(a named primary button instead ofSave/Discard),
children(content under the message) andonkeydown(seeskeys first, so the list can take the arrow keys).
documentSession.openFiles, one afteranother, as launch files always were.
ViewerWindowEntrygainstag_color, whichlist_viewer_windowsalreadyreturns.
open-files-hereis the event that hands a chosen window its files. It'sdeclared in
windowEvents.ts.bindings.tsis regenerated, withwindowsRestoredadded as a command andas an event.
Also fixed: launch files opened behind a reopened window
Found while testing this PR, and it happens with either setting, including
the default.
To reproduce on current
master:finishes restoring after that and comes up over it, so the file is
hidden.
The launch files are handed out while the other windows are still being
reopened, and nothing brought the window that took them back to the front
afterwards.
Fix: Rust remembers the last window that took launch files. That's the
launch window, or a reopened window that has one of them open (#1038). When
it sends
windows-restored, the moment the last window has finished, itbrings that window to the front. I used that event rather than a delay,
since any delay would be a guess: too short on a slow machine, visible lag
on a fast one.
Checked by hand: with "Last window" set, the main window now ends up in
front showing the file.
Scope
tags.
Open dialog, the folder sidebar, a link, Recent Files) already says which
window it's for, so it never asks.
a pinned tag's window for a file would be a separate decision.
Tests
scripts/windows.spec.ts, 14 new under "a file opened fromoutside asks which window it goes to". Under vitest, so
$stateis the realrune, with every dependency a recording fake:
markReady, then are asked about togetherModalscripts/settings.spec.ts, 1 new: the default, surviving arestart, rejecting an unknown value, and the options matching what the
store accepts.
window_runtime.rs, 1 new:the_launch_is_restored_once_every_open_window_has_askedI broke the picker in three ways in a copy, and each break failed a test:
The wiring is checked by hand, not by a test: the
file-pathandwindows-restoredlisteners, the event broadcast, raising the launch files'window, and the
Modalmarkup.MarkdownViewer.svelteisn't mounted in the suite.Verification
test.yml:npm run check: 0 errorsnpm run lintnpm test: 946npm run test:vitest: 730cargo fmt --check, andcargo clippy --locked --all-targets -- -D warningscargo test: 248npm auditfails on thedeepmergeadvisory (GHSA-gcqx-w48m-w2h4),the same as on fix(windows): switch to the window that already has a file instead of opening it twice #1038. It isn't from this change.
a background click) each do what they say.
the chosen window.
switches to its tab, both with no picker.
windows.
a.mdopen in a reopened window, openinga.mdandb.mdtogether brings that window forward and asks about
b.mdonly.a.mdalone goes to its window with no picker.made before 620411a, which replaced the hold on the asking window with a
one-time raise. The launch with
a.mdandb.mdhasn't been rechecked byhand since.