Markdown editor adds 4th backtick when closing a fenced code block

Problem

When typing three backticks to close a fenced code block in the Markdown editor, a spurious 4th backtick is inserted. This only happens when no language is specified at the opening fence (e.g. ``` with no sql, js, etc. after it).
Issue #12569

Root Cause

The editor uses CodeMirror's closeBrackets extension for auto-pairing characters including backtick. This extension works correctly for a single backtick (inline code), but has no awareness of sequences. When the 3rd backtick is typed, closeBrackets sees it as a new opening bracket and inserts a closing one, producing 4 backticks instead of 3.

Fix

packages/editor/CodeMirror/editorCommands/handleBacktick.ts — new file,

import { EditorSelection, StateCommand } from '@codemirror/state';

const handleBacktick: StateCommand = ({ state, dispatch }) => {
	const changes = state.changeByRange(range => {
		if (!range.empty) return { range };

		const pos = range.from;
		const textBefore = state.doc.sliceString(Math.max(0, pos - 2), pos);
		const charAfter = state.doc.sliceString(pos, pos + 1);
		const backticksBefore = textBefore.length - textBefore.replace(/`+$/, '').length;

		if (backticksBefore >= 2) {
			return {
				range: EditorSelection.cursor(pos + 1),
				changes: { from: pos, to: pos, insert: '`' },
			};
		}

		if (backticksBefore === 0) {
			return {
				range: EditorSelection.cursor(pos + 1),
				changes: { from: pos, to: pos, insert: '``' },
			};
		}

		if (backticksBefore === 1 && charAfter === '`') {
			return {
				range: EditorSelection.cursor(pos + 1),
			};
		}

		return { range };
	});

	if (changes.changes.empty && changes.selection.eq(state.selection)) return false;

	dispatch(state.update(changes, { scrollIntoView: true, userEvent: 'input' }));
	return true;
};
export default handleBacktick;

All other characters continue to use closeBrackets unchanged. The deleteMarkupBackward backspace behavior is also unaffected. The fix is gated behind the existing "Auto-match braces" setting — disabling that setting disables this behavior too.

What is not changed

  • No changes to behavior for (), [], "", '' or any other auto-paired character

  • Plugin behavior is unaffected

Files changed

  • packages/editor/CodeMirror/editorCommands/handleBacktick.ts — new file, the command

  • packages/editor/CodeMirror/configFromSettings.ts — remove backtick from openingBrackets, import and register the new command

2 Likes

Hi! The fix works correctly on my local setup.
Since the change is a bit over ~50 lines, I just wanted to check if the approach is OK. The scope is limited to the bug fix and doesn’t introduce architectural changes, but I’m happy to adjust if needed.

Thanks

I have opened a PR which solves the issue

1 Like