Conversation
Only the `:` dropdown converted a shortcode. Somebody who knows the name and types it through — or pastes a line out of a chat log — kept `:smile:` in the text, and the page then showed the shortcode where every other client shows the character. docs/13 left this open with the reason to be careful named: it "risks firing inside things like a:b:c". Three guards answer that, and they are the whole substance of the new module: - The opening colon must sit on a word boundary — start of line, whitespace or an opening bracket. That is the dropdown's own rule, and it is what leaves a:b:c, 12:30:45 and host:8080/x:y: as typed: in each of them a word character stands in front of the colon that would open a shortcode. - The name must be one of ours. An unknown :foo: stays text; the curated list being small is the feature here, not the limitation. - Never inside code, where a shortcode is a string literal, a Ruby symbol or a YAML key. Known limit, written down: an inline span still being typed has no closing backtick, so the parser sees no span and the replacement does fire inside it. Closed code is caught. It runs as an inputHandler, so the typed colon never reaches the document and the replacement is one change — one Ctrl+Z brings :smile: back whole instead of peeling off a colon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…the window
An inline code span that is still being typed has no closing backtick, so lezer
sees a plain paragraph and the replacement fired inside it — swallowing a code
literal somebody meant to keep. Closed code is still caught by the syntax tree;
an open span is caught by counting the backticks on the line, which is the only
thing that can answer while the span is incomplete.
The look-back is a 64-character slice, not a line start, so a word running past
it came out looking like a word boundary and `a:b:c` was replaced again on a
long line. The character in front of the colon is read from the line now.
The boundary rule also admits `[` and `{`, and the tests no longer lean on
unknown names: with `a:b` the name guard answers first, so every one of those
rows would have passed with the boundary check deleted.
docs/13-editing.md said a pasted chat log stops showing `:tada:` and that one
Ctrl+Z brings `:smile:` back whole. Neither is true — it is the typed colon
that converts, and that colon never enters the document.
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.
Closes CON-20.
A shortcode typed out in full is replaced on its closing colon:
:smile:becomes 😄 without touching the:dropdown.What is in it
Three guards keep it from firing where a colon is not a shortcode:
:sits on a word boundary — start of line, whitespace, or an opening bracket ((,[,{). That leavesa:b:c,12:30:45andhost:8080/x:y:exactly as typed.:foo:stays text.`a :smilehas no closing backtick, so lezer sees a plain paragraph — is caught by counting the backticks on the line. Before this, the replacement fired inside it and swallowed a code literal.The look-back is a 64-character slice rather than a line start, so a word running past it used to look like a word boundary and
a:b:cwas replaced again on long lines; the character in front of the colon is read from the line now.It runs as an
inputHandler, so the typed colon never enters the document and the replacement is a single change. Note that one Ctrl+Z therefore removes the emoji together with the shortcode — it cannot put:smile:back whole, and docs/13 now says so instead of the opposite.How to test
In the editor:
ship it :rocket:— converts on the last colon. Ctrl+Z once removes the whole thing (documented behaviour, not a bug).a:b:c,12:30:45,host:8080/x:y:— unchanged. Repeat on a line longer than 64 characters before the colon: still unchanged.:foo:— stays text.:smile:— unchanged. Then type`a :smile:while the span is still open: unchanged (this is the regression fixed here).(:smile:),[:smile:],{:smile:}— converts.:dropdown still works as before.:tada:— it stays as pasted; only a retyped closing colon converts.Automated: typecheck, lint, tests and build are green.