fix: render additional spaces after list markers

This commit is contained in:
Valdemar Lorenzen
2026-10-19 13:40:17 +02:00
parent bbee699cc8
commit efabd3ae18
3 changed files with 76 additions and 18 deletions
+69 -12
View File
@@ -537,11 +537,11 @@ func listNavigationSkipsHiddenSeparatorsAndKeepsSelectionAnchorsAndDeletionUndo(
let body = "🦊e\u{301} z"
// Prefix source, rendered prefix, marker, separator, backward/forward expansions.
let fixtures = [
("", "", "-", " \t", "", ""),
("\t", "\t", "+", "\t ", "", ""),
(" ", " ", "009)", "\t \t", "009", "09)"),
("- parent\n ", "\u{fffc} parent\n ", "- [X]", " \t ", "- [X", " [X]"),
("\t", "\t", "12)\t[]", "\t \t", "12)\t[", "2)\t[]"),
("", "", "-", " ", "", ""),
("\t", "\t", "+", "\t", "", ""),
(" ", " ", "009)", "\t", "009", "09)"),
("- parent\n ", "\u{fffc} parent\n ", "- [X]", " ", "- [X", " [X]"),
("\t", "\t", "12)\t[]", "\t", "12)\t[", "2)\t[]"),
]
for (prefix, renderedPrefix, marker, separator, backwardExpansion, forwardExpansion) in fixtures {
let source = prefix + marker + separator + body
@@ -838,10 +838,10 @@ func mixedListMarkersAlignBodyGlyphsAndWrappedLinesWithoutChangingMarkdown() thr
let continuation = String(repeating: " wrapped words", count: 24)
let rows = [
("", "-", " ", "bullet"), ("", "*", "\t", "asterisk"),
("", "+", " ", "plus"), ("", "1.", " ", "ordered"),
("", "123456789)", " ", "long"), ("", "- [ ]", " ", "task"),
("", "+", " ", "plus"), ("", "1.", " ", "ordered"),
("", "123456789)", " ", "long"), ("", "- [ ]", " ", "task"),
(" ", "+", " ", "nestedBullet"), (" ", "00012.", "\t", "nestedNumber"),
(" ", "* [X]", " ", "nestedTask"),
(" ", "* [X]", " ", "nestedTask"),
]
let source = rows.map { $0.0 + $0.1 + $0.2 + $0.3 + continuation }.joined(separator: "\n")
editor.insertText(source, replacementRange: editor.selectedRange())
@@ -1005,20 +1005,22 @@ func listBodyInsertionUsesVisiblePositionForTypingPasteAndReplacementRanges() th
editor.breakUndoCoalescing()
editor.undoManager?.removeAllActions()
editor.insertText("🦊", replacementRange: explicitRange ? insertion : NSRange(location: NSNotFound, length: 0))
#expect(editor.markdownString == marker + separator + "🦊tail")
let position = max(1, offset)
#expect(editor.markdownString == marker + String(separator.prefix(position)) + "🦊" + String(separator.dropFirst(position)) + "tail")
#expect((editor.string as NSString).range(of: "\u{fffc}").location != NSNotFound)
#expect(editor.selectedRange().location == NSMaxRange(token) + separator.utf16.count + 2)
#expect(editor.selectedRange().location == NSMaxRange(token) + position + 2)
editor.breakUndoCoalescing()
editor.undoManager?.undo()
#expect(editor.markdownString == original)
editor.undoManager?.redo()
#expect(editor.markdownString == marker + separator + "🦊tail")
#expect(editor.markdownString == marker + String(separator.prefix(position)) + "🦊" + String(separator.dropFirst(position)) + "tail")
}
editor.setMarkdownContents(NSAttributedString(string: original))
let token = (editor.string as NSString).range(of: "\u{fffc}")
editor.setSelectedRange(NSRange(location: NSMaxRange(token) + offset, length: 0))
#expect(editor.readSelection(from: board))
#expect(editor.markdownString == marker + separator + "pastedtail")
let position = max(1, offset)
#expect(editor.markdownString == marker + String(separator.prefix(position)) + "pasted" + String(separator.dropFirst(position)) + "tail")
}
editor.setMarkdownContents(NSAttributedString(string: ""))
editor.setSelectedRange(NSRange(location: 0, length: 0))
@@ -1038,3 +1040,58 @@ func listBodyInsertionUsesVisiblePositionForTypingPasteAndReplacementRanges() th
editor.insertText("!", replacementRange: separator)
#expect(editor.markdownString == "-!abXcd")
}
@Test @MainActor
func extraListBodyWhitespaceIsVisibleAndHasNormalCursorStops() throws {
_ = NSApplication.shared
let editor = NotesTextView(frame: NSRect(x: 0, y: 0, width: 520, height: 200))
editor.isRichText = true
editor.allowsUndo = true
let window = NSWindow(contentRect: editor.frame, styleMask: .borderless, backing: .buffered, defer: false)
window.contentView = editor
defer { window.orderOut(nil) }
window.makeFirstResponder(editor)
let layout = try #require(editor.layoutManager)
let container = try #require(editor.textContainer)
func bodyX() -> CGFloat {
layout.ensureLayout(for: container)
let range = (editor.string as NSString).range(of: "tail")
let glyph = layout.glyphIndexForCharacter(at: range.location)
return layout.lineFragmentRect(forGlyphAt: glyph, effectiveRange: nil).minX + layout.location(forGlyphAt: glyph).x
}
for marker in ["-", "+", "*", "12.", "99)", "- []", "- [x]", " - [ ]"] {
for whitespace in [" ", " ", "\t", "\t "] {
let source = marker + " tail"
editor.setMarkdownContents(NSAttributedString(string: source))
let token = (editor.string as NSString).range(of: "\u{fffc}")
let start = NSMaxRange(token) + 1
let originalX = bodyX()
editor.setSelectedRange(NSRange(location: start, length: 0))
editor.breakUndoCoalescing()
editor.undoManager?.removeAllActions()
editor.insertText(whitespace, replacementRange: editor.selectedRange())
#expect(editor.markdownString == marker + " " + whitespace + "tail")
#expect(bodyX() > originalX, "Additional body whitespace must advance the rendered text")
#expect(editor.selectedRange().location == start + whitespace.utf16.count)
editor.setSelectedRange(NSRange(location: token.location, length: 0))
editor.moveRight(nil)
#expect(editor.selectedRange().location == start)
for index in 1...whitespace.utf16.count {
editor.moveRight(nil)
#expect(editor.selectedRange().location == start + index)
}
for index in stride(from: whitespace.utf16.count - 1, through: 0, by: -1) {
editor.moveLeft(nil)
#expect(editor.selectedRange().location == start + index)
}
editor.moveLeft(nil)
#expect(editor.selectedRange().location == token.location)
editor.breakUndoCoalescing()
editor.undoManager?.undo()
#expect(editor.markdownString == source)
#expect(abs(bodyX() - originalX) < 0.5)
editor.undoManager?.redo()
#expect(bodyX() > originalX)
}
}
}