Enter continues a block quote, as it already continues a list (#700) - #705
Merged
Conversation
…a list (#700) Writing a quote meant typing `>` on every line: Enter continued lists and task items and left `> quoted` alone, because `listEnter` recognised `>` only as the PREFIX a list item can be nested inside, never as a marker of its own. A line with no `-`, `*` or `1.` therefore parsed as no block at all. `listEditing.ts` now answers for both — `blockEnter`, since what it decides is no longer only about lists — and the two share one rule for where the caret joins the block: after the marker's CHARACTERS, not after the space behind them. `>|text` and `> |text` are the same gesture in the same pixel and now get the same answer; the separator each new marker writes is cut back to the half the caret has passed, so the half riding down with the text is not doubled, and pressing Enter there repeatedly no longer widens the gap a space at a time. Lists answer to that rule too, which is a behaviour change: `-| item` used to drop out of the list. An empty quote clears in one keystroke, at any depth — the reporter asked for the list's own "twice to cancel", and `> > ` should not need one Enter per level to escape.
This was referenced Aug 22, 2026
Open
PathGao
added a commit
that referenced
this pull request
Aug 25, 2026
Four things shipped since 2.7.4 that the file which documents what Markpad can do never heard about. It carries the editing behaviour around each construct, not only the spellings, so each one belongs to a section that already exists. - Lists: `Tab` moved a line by tabSize and left the marker alone. #713 makes it a level change -- the parent's content column, and both numbered lists renumbered -- which is what the file already claimed and now describes accurately. - Quotes: `Enter` continues a block quote (#705), so the section gets the paragraph Lists has had. Including that one keystroke clears an empty quoted line at any depth, which is the way out. - Images: where a pasted or dropped image lands, and `${filename}` in that setting (#716). It expands to a folder name, not a path, and the note says so -- `./images/${filename}/` is not a thing you can write here. - Not-syntax: copying from the preview keeps its formatting (#680), and the split panes can trade sides (#693). Tests: 984 pass.
alecdotdev
pushed a commit
that referenced
this pull request
Aug 25, 2026
…own again (#719) * chore: bump version to 2.7.5 * docs(syntax): bring the reference up to 2.7.5, in both languages Four things shipped since 2.7.4 that the file which documents what Markpad can do never heard about. It carries the editing behaviour around each construct, not only the spellings, so each one belongs to a section that already exists. - Lists: `Tab` moved a line by tabSize and left the marker alone. #713 makes it a level change -- the parent's content column, and both numbered lists renumbered -- which is what the file already claimed and now describes accurately. - Quotes: `Enter` continues a block quote (#705), so the section gets the paragraph Lists has had. Including that one keystroke clears an empty quoted line at any depth, which is the way out. - Images: where a pasted or dropped image lands, and `${filename}` in that setting (#716). It expands to a folder name, not a path, and the note says so -- `./images/${filename}/` is not a thing you can write here. - Not-syntax: copying from the preview keeps its formatting (#680), and the split panes can trade sides (#693). Tests: 984 pass. * docs(release): the download table's two warnings describe 2.7.5, not 2.7.4 The table is composed in `build.yml` and printed on every release page, so both notes ship with whatever master holds when the workflow is dispatched. Both are about to be wrong. macOS: the last paragraph told users the app grants file access per prompt and that self-signing in Keychain Access is the way out, redone after every update. #707 is what that paragraph asks for, so it would print the workaround on the first release that no longer needs it. Replaced with what is now true, worded so it stays true for 2.7.6: the grant survives an update, and only a user coming from a release older than 2.7.5 is asked once more, because the signature changes the identity the old grants belonged to. The Gatekeeper paragraph above it is untouched -- signing is not notarization, and the first-launch dialog is unaffected. Windows: the "false positive Trojan" half is the stale one -- VirusTotal no longer flags the portable `.exe`, which is what #334 and #466 were. The SmartScreen half is not: it fires on an unsigned binary regardless of what any scanner says, and stays true until an Authenticode certificate exists (#562). Dropping the whole note would leave the release page silent about a dialog every Windows user still meets. So the antivirus claim goes and the unrecognized-app one stays, in one shorter sentence. Tests: 984 pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Enter on a block quote writes the next line's
>, and Enter on a quote withnothing after the marker takes it away. Closes #700, reported by @17Archangel,
who asked for the behaviour lists already have — "回车两次再取消掉".
It also changes one thing about lists, deliberately: see Scope.
Mechanism
utils/listSyntax.tshas always spelled the list-marker prefix[ \t]*(?:>[ \t]*)*, so>was understood — as the thing a list item can benested inside.
listEnteraskedparseListItemand nothing else, and thatpattern needs a
-,*or1.to match at all.> quotedis a line with aprefix and no marker, so it parsed as no block, and Enter fell through to
Monaco's own.
The module answers for both blocks now and is named for it (
blockEnter), withQUOTE_MARKERfactored out beside the fragments the prefix already used.Where the caret joins the block
The interesting decision is not "does a quote continue" but "from which
column". Measuring to where the marker's TEXT starts — the obvious rule, and
the one the list branch used — answers two opposite things for one gesture:
Same pixel, same keystroke, and the user cannot see which spelling they are in
without counting spaces. So the boundary is the end of the marker's
characters instead, and both answer "continue". That is CodeMirror's rule,
reached for the same reason —
@codemirror/lang-markdown'scommands.tsdeclines only when
inner.to - inner.spaceAfter.length > pos, subtractingexactly that whitespace.
The separator, once instead of twice
A caret parked inside the separator splits it. The half after the caret rides
down with the text, because that is what a line break does, so a new marker
carrying a whole separator lands one space more than was typed — and another on
every Enter after that, which is what the reporter would have hit first:
markerUpTorations the separator to the half already behind the caret, so thetwo halves add back up to what was there. The marker's characters are never
rationed: an ordered marker still counts up (
9.->10.) and a task box stillcomes back unchecked.
Alternatives considered
Suppressing continuation inside fenced code. Built and then removed.
- iteminside a
```diffblock is prose-shaped text in somebody's code andcontinuing it is wrong, and
utils/pasteContext.tsalready answers "is thecaret in code" for Ctrl+V, so the guard was four lines. It costs
monaco.editor.tokenize()over every line above the caret: 5.5 ms at 500lines, 27 ms at 2 000, 275 ms at 20 000 (Monarch's own markdown grammar,
realistic prose). Ctrl+V has paid that since it shipped because a paste is one
keystroke in a while; Enter is every second keystroke of a paragraph. Nothing
public in Monaco is cheaper —
ITextModel.getLineTokensis internal, and theencoded tokens it holds have lost the type strings this needs (a bare fence's
body encodes as
Other, indistinguishable from prose), while this app's ownsemantic layer arrives a tick late inside Monaco's sparse store. CodeMirror
does bail on
FencedCode, cheaply, because it has a Lezer tree to resolveagainst; we do not. So lists and quotes are both still continued inside a fence,
as lists always have been, and
listEditing.tssays so with the numbers.Declining
>text, as VS Code's Markdown All in One does(
/^> /.test(textBeforeCursor)). It sidesteps the caret problem above ratherthan answering it, and it cannot be copied here: comrak renders
>textas aquote —
semantic_spansreturnsquote.markerfor it, exactly as for> text— so Enter would be disagreeing with the preview drawn beside it.Normalising the separator. CodeMirror collapses it to one space, Markdown
All in One writes a hard-coded
>. Neither preserves a hand-aligned listeither, and this app does (
- itemcontinues as-), so> quotedkeeps its spacing for the same reason.
Trimming the whitespace behind the caret, which CodeMirror does before
breaking (
while (from > line.from && /\s/...) from--). It would make theabandoned line byte-identical whichever side of the separator Enter is pressed,
and
render.hardbreaksmeans trailing spaces carry no meaning here, so nothingwould break. Left out anyway: the line it cleans is one nobody can see the
difference in, and Enter quietly deleting characters to the left of the caret is
more than this key was asked to do.
Scope
Lists change too. The boundary and the separator rule are one implementation
for both blocks, so
-| itemnow continues instead of dropping out of thelist, and
- [x]| taskcontinues from after the box. That is the same defectone marker over; fixing it only for quotes would leave quotes one column more
forgiving than lists with nothing to point at for why.
Not touched: Tab and Shift+Tab, which read the same lines through
parseListItemand have their own reasons to; the fence blind spot above; andthe empty-quote ladder, which stays at one Enter per block rather than
CodeMirror's and Markdown All in One's two empty
>lines — the reporter askedfor the list's two-Enter exit and this app's lists give it.
Tests
scripts/listContinuation.test.ts: 968 -> 969 node tests, all green, plusnpm run check(815 files, 0 errors),npm run test:vitest(398),npm audit(0),
cargo test.The pure function is CALLED, once per shape: every quote depth and spelling,
the caret at each column across the marker of a quote, a bullet, an ordered
item and a task item, and the split-doubling case as a before/after pair. The
two
Editor.sveltehandlers are lifted out and run against the stub editor asbefore, so the edits asserted are the real ones.
Reverting each half with the tests left in place: the quote branch -> 5 red;
the boundary -> the caret and doubling tests red; both restored -> green.
Verification
npm audit,npm run check,npm test,npm run test:vitest,cd src-tauri && cargo test— all pass locally.Driven by hand in a release build on macOS 15 (Apple silicon): every case above,
plus Enter held down on a quote and on a list to watch the separator stay one
space wide.
Not verified: Windows and Linux — the reporter is on Windows 10 — and that
Monaco delivers Enter to this handler at runtime, which no test here can
establish. What is pinned instead is the
whenclause the app declares.