start new lines at indent

This commit is contained in:
TC
2026-06-08 22:28:06 +10:00
parent cc1511230a
commit 88c78f9005

View File

@@ -441,36 +441,24 @@ const hrPlugin = ViewPlugin.fromClass(
// Paragraphs in the manuscript use single newlines (no blank separators), // Paragraphs in the manuscript use single newlines (no blank separators),
// so every text line is a paragraph. We indent all of them except the first // so every text line is a paragraph. We indent all of them except the first
// paragraph after a heading, HR, or document start (traditional typography). // paragraph after a heading, HR, or document start (traditional typography).
const blankLineDeco = Decoration.line({ class: 'cm-blank-line' }) const noIndentDeco = Decoration.line({ class: 'cm-no-indent' })
const paragraphStartDeco = Decoration.line({ class: 'cm-paragraph-start' })
function buildParagraphDecos(view: EditorView): DecorationSet { function buildParagraphDecos(view: EditorView): DecorationSet {
const builder = new RangeSetBuilder<Decoration>() const builder = new RangeSetBuilder<Decoration>()
const doc = view.state.doc const doc = view.state.doc
// Start true so the very first text paragraph gets no indent
let skipNextIndent = true
for (let n = 1; n <= doc.lines; n++) { for (let n = 1; n <= doc.lines; n++) {
const line = doc.line(n) const line = doc.line(n)
const text = line.text const text = line.text
const isBlank = text.trim() === ''
const isHeading = /^#{1,6} /.test(text) const isHeading = /^#{1,6} /.test(text)
const isHR = /^---+$/.test(text.trim()) const isHR = /^---+$/.test(text.trim())
const isSpecial = /^[-*>]/.test(text) // list / blockquote const isSpecial = /^[-*>]/.test(text) // list / blockquote / blank
if (isBlank) { // Only suppress indent for structural lines — never for regular text.
builder.add(line.from, line.from, blankLineDeco) // This ensures no text line ever changes its decoration status based on
// blank lines don't affect the skip flag // neighboring lines, eliminating cascade-induced layout shifts.
} else if (isHeading || isHR) { if (isHeading || isHR || isSpecial) {
skipNextIndent = true builder.add(line.from, line.from, noIndentDeco)
} else if (isSpecial) {
skipNextIndent = false
} else {
// Regular text paragraph
if (!skipNextIndent) {
builder.add(line.from, line.from, paragraphStartDeco)
}
skipNextIndent = false
} }
} }
return builder.finish() return builder.finish()
@@ -481,7 +469,7 @@ const paragraphIndentPlugin = ViewPlugin.fromClass(
decorations: DecorationSet decorations: DecorationSet
constructor(view: EditorView) { this.decorations = buildParagraphDecos(view) } constructor(view: EditorView) { this.decorations = buildParagraphDecos(view) }
update(update: ViewUpdate) { update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) { if (update.docChanged) {
this.decorations = buildParagraphDecos(update.view) this.decorations = buildParagraphDecos(update.view)
} }
} }
@@ -516,8 +504,8 @@ function buildTheme(fontSize: number, dark: boolean, focusMode = false): ReturnT
'.cm-cursor': { borderLeftColor: 'var(--accent)' }, '.cm-cursor': { borderLeftColor: 'var(--accent)' },
'.cm-selectionBackground': { backgroundColor: 'var(--active-bg)' }, '.cm-selectionBackground': { backgroundColor: 'var(--active-bg)' },
'&.cm-focused .cm-selectionBackground': { backgroundColor: 'var(--active-bg)' }, '&.cm-focused .cm-selectionBackground': { backgroundColor: 'var(--active-bg)' },
'.cm-blank-line': { fontSize: '0.45em', lineHeight: '1' }, '.cm-line': { textIndent: '2em' },
'.cm-paragraph-start': { textIndent: '2em' }, '.cm-no-indent': { textIndent: '0' },
'.cm-gutters': { display: 'none' }, '.cm-gutters': { display: 'none' },
'.cm-activeLine': { backgroundColor: 'transparent' }, '.cm-activeLine': { backgroundColor: 'transparent' },
'.cm-activeLineGutter': { backgroundColor: 'transparent' }, '.cm-activeLineGutter': { backgroundColor: 'transparent' },