From f67a53883b5f338f64c8c4de0a7ed137cbb0ab8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20Jos=C3=A9=20Ferreira=20J=C3=BAnior?= Date: Sat, 10 Oct 2026 15:45:11 -0300 Subject: [PATCH] Find a list item's line start without scanning past its line --- CHANGELOG.md | 1 + src/features/editor/plugins/listForm.ts | 7 +++++-- .../editor/tests/markdownCompatibility.test.ts | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dac45763..a6d460c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Leafdown uses lightweight [Keep a Changelog](https://keepachangelog.com/en/1.1.0 ### Fixed +- Open documents with many list items faster. Reading each item's indentation searched back toward the start of the file, so opening took longer with every item and with the file's size, adding about a second to a 1 MB document and much more to larger ones. - Draw the scrollbars of wide code blocks, tables, and display math in the same thin, rounded style as the rest of Leafdown instead of the system default. Help code samples, Find in folder's skipped-file lists, the code block language and wiki link suggestion lists, and menus cut short by a small window match it too. - Show a file opened from File Explorer, File > Open, recent files, or a drop as soon as it is read instead of after its whole folder has been scanned. A file in a large folder such as Documents could leave the window on the welcome screen for several seconds, looking as if the file had not opened. The article navigator shows placeholder rows until the folder is ready, and a folder that cannot be scanned reports its error without closing the document. - Show a heading's caret-based marker only while the editor has focus, so opening a heading-first document does not show the marker before you enter the editor. Hover markers and section folding remain available. diff --git a/src/features/editor/plugins/listForm.ts b/src/features/editor/plugins/listForm.ts index 22ad4987..d81504d4 100644 --- a/src/features/editor/plugins/listForm.ts +++ b/src/features/editor/plugins/listForm.ts @@ -58,8 +58,11 @@ const readListItemIndent = (item: MarkdownNode, source: string) => { return undefined; } - const lineStart = - Math.max(source.lastIndexOf("\n", start - 1), source.lastIndexOf("\r", start - 1)) + 1; + let lineStart = start; + + while (lineStart > 0 && source[lineStart - 1] !== "\n" && source[lineStart - 1] !== "\r") { + lineStart -= 1; + } return source.slice(lineStart, start); }; diff --git a/src/features/editor/tests/markdownCompatibility.test.ts b/src/features/editor/tests/markdownCompatibility.test.ts index b5c3f146..431d1808 100644 --- a/src/features/editor/tests/markdownCompatibility.test.ts +++ b/src/features/editor/tests/markdownCompatibility.test.ts @@ -3151,6 +3151,24 @@ describe("List item indentation", () => { expect(reopened.view.state.doc.toJSON()).toEqual(mounted.view.state.doc.toJSON()); }); + it.each([ + { lineEnding: "\n", name: "line feeds" }, + { lineEnding: "\r\n", name: "carriage returns and line feeds" }, + { lineEnding: "\r", name: "carriage returns" }, + ])( + "reads each item's prefix off its own line in a file ending lines with $name", + async ({ lineEnding }) => { + const authored = + "- parent\n\n\t- child\n\t - sibling\n\nText\n\n> - quoted\n>\n> \t- nested\n"; + const { mounted, reopened, saved } = await saveAndReopen( + authored.replaceAll("\n", lineEnding), + ); + + expect(saved).toBe(authored); + expect(reopened.view.state.doc.toJSON()).toEqual(mounted.view.state.doc.toJSON()); + }, + ); + // A marker written even one column past the item before it falls inside that item, so a list // standing right after another gives way: the file the record came from cannot have held that, // and the columns the containers write are no longer the ones the file wrote.