Skip to content

Commit ce643e7

Browse files
lividclaude
andcommitted
Fix list item rendering glitch on Enter by combining newline and prefix into single insertText call
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bf21364 commit ce643e7

4 files changed

Lines changed: 89 additions & 23 deletions

File tree

Planet/Helper/MarkdownListAutocomplete.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,68 @@ enum MarkdownListAutocomplete {
8686
return .none
8787
}
8888

89+
/// Evaluate what autocomplete action to take **before** the newline is inserted.
90+
///
91+
/// Call this from `insertNewline` **before** calling `super.insertNewline`.
92+
/// The returned action can be combined with the newline into a single `insertText` call.
93+
///
94+
/// - Parameters:
95+
/// - text: The full text content (before the newline is inserted).
96+
/// - cursorUTF16Offset: The cursor position in UTF-16 units (`selectedRange().location`).
97+
/// - Returns: The autocomplete action to apply.
98+
static func evaluateBeforeNewline(text: String, cursorUTF16Offset: Int) -> Result {
99+
let ns = text as NSString
100+
let cursorPos = cursorUTF16Offset
101+
guard cursorPos >= 0, ns.length > 0 else { return .none }
102+
103+
// If cursor is at end of text after a \n, it's on an empty trailing line → no list context
104+
if cursorPos >= ns.length, cursorPos > 0,
105+
ns.character(at: ns.length - 1) == unichar(0x0A) {
106+
return .none
107+
}
108+
109+
// Find the line the cursor is on
110+
var lineStart = 0
111+
var contentsEnd = 0
112+
let searchPos = min(cursorPos, ns.length - 1)
113+
ns.getLineStart(&lineStart, end: nil, contentsEnd: &contentsEnd,
114+
for: NSRange(location: searchPos, length: 0))
115+
116+
let lineRange = NSRange(location: lineStart, length: contentsEnd - lineStart)
117+
let currentLine = ns.substring(with: lineRange)
118+
let trimmed = currentLine.trimmingCharacters(in: .whitespaces)
119+
120+
// Check if current line is an empty list marker
121+
let isEmptyNumberedItem = trimmed.range(of: #"^\d+\.$"#, options: .regularExpression) != nil
122+
if trimmed == "*" || trimmed == "+" || trimmed == "-"
123+
|| trimmed == "- [ ]" || trimmed == "- [x]" || trimmed == "- [X]"
124+
|| isEmptyNumberedItem {
125+
return .removeEmptyMarker(utf16Range: lineRange)
126+
}
127+
128+
// List continuation: insert prefix
129+
if trimmed.hasPrefix("- [ ] ") || trimmed.hasPrefix("- [x] ") || trimmed.hasPrefix("- [X] ") {
130+
return .insertPrefix("- [ ] ", atUTF16Offset: cursorPos)
131+
}
132+
if trimmed.hasPrefix("* ") {
133+
return .insertPrefix("* ", atUTF16Offset: cursorPos)
134+
}
135+
if trimmed.hasPrefix("+ ") {
136+
return .insertPrefix("+ ", atUTF16Offset: cursorPos)
137+
}
138+
if trimmed.hasPrefix("- ") {
139+
return .insertPrefix("- ", atUTF16Offset: cursorPos)
140+
}
141+
if let match = trimmed.range(of: #"^(\d+)\. "#, options: .regularExpression) {
142+
let numberStr = trimmed[match].dropLast(2)
143+
if let number = Int(numberStr) {
144+
return .insertPrefix("\(number + 1). ", atUTF16Offset: cursorPos)
145+
}
146+
}
147+
148+
return .none
149+
}
150+
89151
/// Apply the result to an NSTextView via `insertText`.
90152
@MainActor
91153
static func apply(_ result: Result, to textView: NSTextView) {

Planet/Labs/Quick Post/QuickPostView.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -569,16 +569,18 @@ final class QuickPostEditorTextView: NSTextView {
569569

570570
// MARK: - List autocomplete on Enter
571571

572-
override func keyDown(with event: NSEvent) {
573-
super.keyDown(with: event)
574-
switch event.keyCode {
575-
case 36, 76: // Return, Enter
576-
let result = MarkdownListAutocomplete.evaluate(
577-
text: self.string, cursorUTF16Offset: self.selectedRange().location
578-
)
579-
MarkdownListAutocomplete.apply(result, to: self)
580-
default:
581-
break
572+
override func insertNewline(_ sender: Any?) {
573+
let result = MarkdownListAutocomplete.evaluateBeforeNewline(
574+
text: self.string,
575+
cursorUTF16Offset: self.selectedRange().location
576+
)
577+
switch result {
578+
case .removeEmptyMarker(let range):
579+
insertText("", replacementRange: range)
580+
case .insertPrefix(let prefix, _):
581+
insertText("\n" + prefix, replacementRange: self.selectedRange())
582+
case .none:
583+
super.insertNewline(sender)
582584
}
583585
}
584586
}

Planet/Writer/WriterTextView.swift

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -782,18 +782,20 @@ class WriterEditorTextView: NSTextView {
782782
)
783783
}
784784

785-
// MARK: - Process enter / return key event
786-
787-
override func keyDown(with event: NSEvent) {
788-
super.keyDown(with: event)
789-
switch event.keyCode {
790-
case 36, 76: // Return, Enter
791-
let result = MarkdownListAutocomplete.evaluate(
792-
text: self.string, cursorUTF16Offset: self.selectedRange().location
793-
)
794-
MarkdownListAutocomplete.apply(result, to: self)
795-
default:
796-
break
785+
// MARK: - List autocomplete on Enter
786+
787+
override func insertNewline(_ sender: Any?) {
788+
let result = MarkdownListAutocomplete.evaluateBeforeNewline(
789+
text: self.string,
790+
cursorUTF16Offset: self.selectedRange().location
791+
)
792+
switch result {
793+
case .removeEmptyMarker(let range):
794+
insertText("", replacementRange: range)
795+
case .insertPrefix(let prefix, _):
796+
insertText("\n" + prefix, replacementRange: self.selectedRange())
797+
case .none:
798+
super.insertNewline(sender)
797799
}
798800
}
799801
}

Planet/versioning.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
CURRENT_PROJECT_VERSION = 2658
1+
CURRENT_PROJECT_VERSION = 2659

0 commit comments

Comments
 (0)