fix: renumber sibling sequences when list indentation changes

This commit is contained in:
2026-10-19 19:00:16 +02:00
parent afa27b941f
commit 662200e684
3 changed files with 104 additions and 4 deletions
+71 -3
View File
@@ -345,9 +345,77 @@ final class NotesController: NSViewController, NSTextViewDelegate {
guard indenting || removed > 0 else { return true }
let replacement = indenting ? " " : ""
let delta = replacement.utf16.count - removed
let selectedStart = max(start, selection.location + delta)
let selectedEnd = max(start, NSMaxRange(selection) + delta)
replaceListMarkdown(replacement, range: NSRange(location: start, length: removed),
var selectedStart = max(start, selection.location + delta)
var selectedEnd = max(start, NSMaxRange(selection) + delta)
func isListLine(_ range: NSRange) -> Bool {
Self.listMatch(in: source.substring(with: range) as NSString) != nil
&& !codeBlocks.contains { NSIntersectionRange($0.range, range).length > 0 }
}
var block = source.lineRange(for: NSRange(location: start, length: 0))
while block.location > 0 {
let previous = source.lineRange(for: NSRange(location: block.location - 1, length: 0))
guard isListLine(previous) else { break }
block = NSUnionRange(previous, block)
}
while NSMaxRange(block) < source.length {
let next = source.lineRange(for: NSRange(location: NSMaxRange(block), length: 0))
guard isListLine(next) else { break }
block = NSUnionRange(block, next)
}
let body = NSMutableAttributedString(attributedString: editor.markdownContents.attributedSubstring(from: block))
body.replaceCharacters(in: NSRange(location: start - block.location, length: removed), with: replacement)
let changed = body.string as NSString
let oldDepth = Self.indentationWidth(indent)
let newDepth = Self.indentationWidth(replacement + (indent as NSString).substring(from: removed))
var ancestors: [(depth: Int, number: UInt64?, delimiter: String)] = []
var edits: [(range: NSRange, number: String)] = []
var oldLevel = true, newLevel = true
var offset = 0
while offset < changed.length {
let row = changed.lineRange(for: NSRange(location: offset, length: 0))
let text = changed.substring(with: row) as NSString
guard let match = Self.listMatch(in: text) else { break }
let depth = Self.indentationWidth(text.substring(with: match.range(at: 1)))
if offset + block.location > start {
if depth < oldDepth { oldLevel = false }
if depth < newDepth { newLevel = false }
}
while let last = ancestors.last, last.depth > depth { ancestors.removeLast() }
let peer = ancestors.last?.depth == depth ? ancestors.removeLast() : nil
var number: UInt64?
var delimiter = ""
if match.range(at: 3).location != NSNotFound {
number = UInt64(text.substring(with: match.range(at: 3)))
delimiter = text.substring(with: match.range(at: 4))
if offset + block.location >= start,
(oldLevel && depth == oldDepth) || (newLevel && depth == newDepth) {
if let peer, peer.delimiter == delimiter, let previous = peer.number, previous < UInt64.max {
number = previous + 1
} else if !ancestors.isEmpty || offset + block.location > start {
number = 1
}
if let number, UInt64(text.substring(with: match.range(at: 3))) != number {
let digits = match.range(at: 3)
edits.append((NSRange(location: offset + digits.location, length: digits.length), String(number)))
}
}
}
ancestors.append((depth, number, delimiter))
offset = NSMaxRange(row)
}
for edit in edits.reversed() {
body.replaceCharacters(in: edit.range, with: edit.number)
let location = block.location + edit.range.location
let difference = edit.number.utf16.count - edit.range.length
func adjusted(_ position: Int) -> Int {
if position <= location { return position }
if position < location + edit.range.length { return location + min(position - location, edit.number.utf16.count) }
return position + difference
}
selectedStart = adjusted(selectedStart)
selectedEnd = adjusted(selectedEnd)
}
replaceMarkdown(body, range: block,
selection: NSRange(location: selectedStart, length: selectedEnd - selectedStart))
return true
}