@@ -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 ) {
0 commit comments