From bdd3e1e9a4adeb77cf911804f3576ff088bd7534 Mon Sep 17 00:00:00 2001 From: Ethan Graf Date: Sat, 18 Jul 2026 23:41:18 -0400 Subject: [PATCH] Hide inline markers as soon as cursor leaves the span Proximity reveal used a 2-char window, so after typing a closing marker (e.g. the second *) the markers stayed visible until the cursor moved 3 characters past them. Set PROXIMITY_CHARS to 0 so markers reveal only while the cursor is within/touching the span and hide the moment it moves one character past the closing marker. Affects bold, italic, strikethrough, inline code, and links. Co-Authored-By: Claude Opus 4.8 --- src/editors/automerge/livePreview/context.ts | 22 ++++++++++++++----- .../automerge/livePreview/livePreview.test.ts | 21 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/editors/automerge/livePreview/context.ts b/src/editors/automerge/livePreview/context.ts index c0c9342..961e194 100644 --- a/src/editors/automerge/livePreview/context.ts +++ b/src/editors/automerge/livePreview/context.ts @@ -28,8 +28,16 @@ import { } from '@codemirror/view'; import { syntaxTree } from '@codemirror/language'; -/** How many characters away from an inline span still reveals its markers. */ -export const PROXIMITY_CHARS = 2; +/** + * How far past a span's boundaries the cursor still reveals its markers. + * + * 0 means markers are shown only while the cursor is *within* the span + * (boundaries inclusive), and hide as soon as it moves one character past the + * closing marker. A wider window would keep e.g. `*abc*` markers visible for N + * more characters after the closing `*` is typed, which reads as the markers + * "sticking" after the text is already formatted. + */ +export const PROXIMITY_CHARS = 0; export interface Range { from: number; @@ -76,10 +84,12 @@ export function iterRanges(view: EditorView): readonly Range[] { /** * True iff the cursor is on the SAME LINE as the span [from, to] AND within - * `slack` chars of its boundaries. Same-line gating stops proximity-reveal - * from leaking onto neighbouring lines. Correct for bold/italic/strike/links, - * which cannot cross newlines per the @lezer/markdown grammar. Inline code is - * the exception (it may span newlines) — use `isCursorInRange` there. + * `slack` chars of its boundaries (slack defaults to 0 → boundaries inclusive, + * so the cursor must be within/touching the span). Same-line gating stops + * proximity-reveal from leaking onto neighbouring lines. Correct for + * bold/italic/strike/links, which cannot cross newlines per the + * @lezer/markdown grammar. Inline code is the exception (it may span newlines) + * — use `isCursorInRange` there. */ export function isCursorNearSameLine( state: EditorState, diff --git a/src/editors/automerge/livePreview/livePreview.test.ts b/src/editors/automerge/livePreview/livePreview.test.ts index 3b100d8..961a2d7 100644 --- a/src/editors/automerge/livePreview/livePreview.test.ts +++ b/src/editors/automerge/livePreview/livePreview.test.ts @@ -108,6 +108,27 @@ describe('inlineFormatDecoration', () => { expect(withClass(list, 'cm-inline-formatting-hidden')).toHaveLength(0); }); + it('hides markers as soon as the cursor moves one char past the close', () => { + // "*abc*def": Emphasis node is [0,5]; the closing "*" ends at offset 5. + // At the boundary (just typed the closing marker) markers stay visible; + // one character later they hide. + const atClose = makeState('*abc*def', 5); // cursor right after closing "*" + expect( + withClass( + decos(atClose, buildInlineFormatDecorations(atClose)), + 'cm-inline-formatting-hidden', + ), + ).toHaveLength(0); + + const pastClose = makeState('*abc*def', 6); // one character past the "*" + expect( + withClass( + decos(pastClose, buildInlineFormatDecorations(pastClose)), + 'cm-inline-formatting-hidden', + ).map((h) => h.text), + ).toEqual(['*', '*']); + }); + it('handles italic and strikethrough', () => { const italic = makeState('hello world *it* end', 0); expect( -- 2.51.2