Skip to content

feat: search from selected text and copy document as Markdown - #231

Closed
junglesub wants to merge 1 commit into
sftwrdotdev:masterfrom
junglesub:feat/selected-text-search-and-copy-markdown
Closed

feat: search from selected text and copy document as Markdown#231
junglesub wants to merge 1 commit into
sftwrdotdev:masterfrom
junglesub:feat/selected-text-search-and-copy-markdown

Conversation

@junglesub

Copy link
Copy Markdown
Contributor

Summary

Adds selected-text search and a command to copy the current document's full Markdown source.

Changes

  • Seed Ctrl/Cmd+F with the selected text in the editor and rendered preview
  • Start from the selected occurrence instead of scrolling to the first match
  • Add “Copy as Markdown” to the application menu
  • Add Ctrl/Cmd+Shift+C as the copy Markdown shortcut
  • Add translations and regression coverage

Copy As Markdown (Ctrl/Cmd + Shift + C)

image

Find selected text (Ctrl/Cmd+F)

download

Testing

  • pnpm run test:workflows
  • pnpm run check
  • Manually verified on Windows

@PathGao

PathGao commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this — the find half is a real gap, and it's landing with you as the author in #586. The copy half I'm dropping; reasoning for both below.

Taking: seeding the find bar from the selection

Reworked before landing, on three points.

The editor half needs no code. Monaco's seedSearchStringFromSelection defaults to seeding, so Cmd/Ctrl+F in the editor has behaved this way all along — which is what the assert.doesNotMatch(editor, /seedSearchStringFromSelection/) test was really observing.

The preview half was off in the commonest case. getPreviewSelection() returned null unless the selection began and ended in the same text node, so selecting across **bold**, a link or inline code produced no seed at all — not even the query text. It now reads selection.toString(), and seeds single-line selections only, which is Monaco's own rule in the other pane; two panes disagreeing about one keystroke was the thing worth fixing.

"Start from the selected occurrence" is not landing. That part carried ~110 of the 120 lines: the pendingActiveRange / getRangeMatchIndex / getClosestMarkIndex fallback chain, the untrack change to the sanitizedHtml effect, and the extra reapply() in the render path. That is the most delicate code in the app — a tree walk that mutates the nodes it is walking, plus scroll restoration — and the payoff is which match is active at the moment the bar opens. It can come back later on its own merits if anyone asks for it.

What's left in FindBar.svelte is setQuery(value) { query = value }; the existing query effect schedules the search, so a seed goes through the same debounce and the same not-yet-open guard as typing, and the highlight walk is untouched. The decision of whether to seed is six lines in utils/findSeed.ts, which the test imports and runs.

Not taking: Copy as Markdown

In reading mode, Cmd/Ctrl+ECmd/Ctrl+ACmd/Ctrl+C already produces exactly this. And it turns out to be the more correct route: Markpad reads only the first 5MB of a file over that size (tab.isTruncated), and documentSession.ensureFullContent() is what completes the buffer. Entering the editor awaits it — copyMarkdownDocument reads tab.rawContent directly, so on a large file it would put the first 5MB on the clipboard and then toast "Markdown copied". Silently truncated, reported as success.

That is fixable in a line. But it means the feature carries a standing contract with the large-file state machine in order to save three keystrokes, and "Copy" that ignores your selection is a surprise every user gets to have once. Not a good trade for a reader that advertises itself as restrained.

One note for next time

New chords belong in src/lib/utils/shortcuts.ts rather than bound by hand. scripts/shortcutRegistry.test.ts fires every registered chord at the real handleKeyDown and the real registerLocalizedActions across all three platforms, checks the command that actually runs, and refuses to let two entries advertise the same chord — none of which can see a hand-rolled binding.

Thanks again — the find change is the better half and it's yours.

@PathGao PathGao closed this Aug 11, 2026
PathGao added a commit that referenced this pull request Aug 11, 2026
Reworked from #231.

Select a word in the rendered document, press Cmd/Ctrl+F, and the bar opens
with that word already in it. The editor pane has done this for as long as it
has used Monaco -- `seedSearchStringFromSelection` defaults to seeding -- so
the two panes disagreed about the same keystroke, and the preview was the one
that made you type the word you were already pointing at.

The seed is `selection.toString()`, not the text of a single node. A selection
that crosses `**bold**`, a link or inline code spans several nodes, and those
are the words a reader is most likely to have highlighted; reading one node
would have left the feature off in the commonest case.

Single-line selections only, which is Monaco's rule in the other pane. A
paragraph in the search box matches nothing and buries the query the user was
about to type. Everything else is left alone rather than cleared: a caret with
nothing selected, a selection in a modal or the tab strip, whitespace. A
repeated Cmd/Ctrl+F with no selection still just re-focuses the box (#559).

`setQuery` is the assignment and nothing else -- the existing `query` effect
schedules the search, so a seed goes through the same debounce and the same
not-yet-open guard as typing does, and the highlight walk is untouched.

The decision of whether to seed is the whole feature, so it is a pure function
in `utils/findSeed.ts` that the test imports and runs -- the behaviour kind
AGENTS.md asks for, rather than a harness that lifts it back out of the
component. One source-shape line covers the wiring, since a pure function
nothing calls is dead code. Checked by mutation: dropping the single-line rule
or the containment guard each fails it. 977 tests pass, svelte-check clean.

Co-authored-by: junglesub <junglesubmarine@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@junglesub

Copy link
Copy Markdown
Contributor Author

Thank you so much for taking the time to think through the PR in such depth and for explaining the reasoning behind your decisions.

I originally made these changes for my own use and decided to share them, but your response helped me understand how differently you have to think about a change when looking at it from the perspective of a larger project, especially the trade-offs between a feature's value, complexity, and long-term maintenance.

Thanks again for the great project and for giving me an opportunity to learn and grow through contributing to it.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants