Skip to content

A backslash typed into projected link source becomes literal text instead of an escape #265

Description

@Azganoth

Summary

Typing a backslash into projected link source turns the link literal, which is what the specification describes, but the backslash lands in the document as an ordinary character rather than as the escape it spells. The run then saves with three backslashes where one is enough, and the resulting document differs from the same file authored by hand.

This is the gesture #245 read from the specification as the inverse of #255's, and recorded as inferred rather than observed. It is reachable today with no other change in place.

Steps to reproduce

  1. Open a document containing [a](b) tail.
  2. Place the caret in the link so its source projects, and type \ at the start of [a](b).
  3. Move the caret out of the run and save.

Expected behavior

The same state a file holding \[a](b) tail produces: the document holds [a](b) tail as literal text with no backslash character in it, and the file saves as \[a](b) tail.

The link must still become literal, which it already does. A backslash the author means as ordinary text must still survive as one.

Actual behavior

Measured against f409cdbf by driving the editor mount used by the plugin tests, comparing the gesture against the same run authored with the escape. The documents are compared as ProseMirror documents rather than as strings, so the comparison sees literalness rather than form.

Typing \ into projected source Authored \[a](b) tail
Links after the caret leaves 0 0
Document text \[a](b) tail [a](b) tail
Saved \\\[a](b) tail \[a](b) tail

The same holds on a realistic link. see [test link](./test.html) tail gives document text see \[test link](./test.html) tail and saves as see \\\[test link](./test.html) tail, against see [test link](./test.html) tail and see \[test link](./test.html) tail for the authored form.

The wrong state is stable rather than self-correcting: reopening \\\[a](b) tail returns the document holding the backslash and saves it back unchanged, so nothing later recovers the escape the author meant.

Related context

Done when

  • Typing a backslash before projected link source produces the same document as opening a file that already holds the escaped run.
  • The run saves with the escape its text needs rather than with an escaped backslash.
  • The link still becomes literal, and a backslash the author intends as ordinary text is still preserved.
  • Regression coverage asserts the resulting document, not only its serialization, so a fix cannot pass by writing the right bytes from the wrong state.

Notes, logs, screenshots

Diagnosis

sourceProjectionLinkAdapter.ts falls back to createLiteralSourceProjectionSlice(state, source) when parseLinkSource rejects the edited source, and that slice is built from the raw source string. The backslash the author typed is part of that string, so it enters the document as text like any other character.

The serializer is then correct: a document holding a literal backslash followed by a bracket needs \\ for the backslash and \[ for the bracket, which is where the third one comes from. The defect is in the commit, not in escaping, so it is not one of the classes under #251.

Verified with a throwaway probe under src/features/editor/tests/, run with vitest and deleted. To reproduce: mount the source, enter projection through the rendered a element, place the selection at the start of the source run, type \, move the selection to the document end, then read doc.textContent and getMarkdown() and compare view.state.doc against a mount of the hand-authored escape.

Implementation direction

One constraint and one open decision.

The constraint is that both directions have to keep working. A backslash typed into projected link source has to mean "escape this run", while a backslash the author wants as text still has to reach the document as one, and only the projection context separates them.

The open decision is where the exception lives. docs/specification.md says an invalid edit to projected source becomes exact literal text, and consuming a leading backslash is an exception to that rule rather than an application of it. Whether it belongs in the adapter's parseSource, in the projection commit ahead of the adapter, or in the specification's wording is not settled here.

Out of scope

Metadata

Metadata

Assignees

Labels

BugSomething isn't working

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions