Sources/OrgCore/Keymap/Vim.swift
1353 lines · 66016 bytes
67 symbols in this file
VimModeActionContextfeedinserteddeletedBackwardinsertKeypassesThroughenterInsertParseCommandindentationdigitprefixparseMotionKindMotionResultmotioncolumnsnipetextObjectvisualRangevisualActionsstorepastechangeapplyenterInsertjoinrepeatChangesearchresetBuffercharsubstringlineStartlineEndremovingapplyingisBlankisSpaceisWordkindclampNormalclampNormalfirstNonBlankfirstNonBlankOrEndfirstNonBlankInlineOffsetlineNumberlineCountwordForwardwordBackwardwordEndwordEndBackwardwordStartwordAtmatchingBracketparagraphblanksearchwordObjectquoteObjectbracketObjectparagraphObjectblank
1import Foundation
2
3/// Modal editing as Doom's evil gives it: normal, insert, visual and visual-line states;
4/// counts, registers, operators with motions and text objects, `.` repeat, evil-snipe's `s`
5/// and `S`, `Y` to the end of the line, and the unnamed register on the system clipboard.
6/// Keys go in; edits, moves and commands come out for the editor to apply.
7public struct Vim: Sendable {
8 public enum Mode: String, Sendable {
9 case normal, insert, visual
10 case visualLine = "visual-line"
11 case visualBlock = "visual-block"
12
13 /// The keymap mode name for bindings.
14 public var keymapMode: String {
15 switch self {
16 case .normal: "normal"
17 case .insert: "insert"
18 case .visual, .visualLine, .visualBlock: "visual"
19 }
20 }
21 }
22
23 public enum Action: Equatable, Sendable {
24 /// Replace text and put the caret (and, in visual states, the selection) somewhere.
25 case edit([TextEdit], caret: Int)
26 case move(caret: Int)
27 /// The selection to show in a visual state, `lowerBound..<upperBound`.
28 case select(Range<Int>)
29 case command(String)
30 case undo(Int)
31 case redo(Int)
32 case setClipboard(String)
33 /// Ask for a search pattern (`/`, `?`) or an ex command (`:`).
34 case prompt(String)
35 /// `gq` and `gw`: fill these lines' paragraphs (`FillRegion`); the caret stays at
36 /// `keep`, or without it goes to the last line's first non-blank.
37 case fill(Range<Int>, keep: Int?)
38 /// An ex command to run, as if typed after `:` (`ZZ` is `:wq`, `ZQ` is `:q!`).
39 case ex(String)
40 case message(String)
41 /// `m`: a mark at this offset, for the editor to keep through edits.
42 case setMark(Character, Int)
43 /// A jump left this offset: the editor adds it to the jump list.
44 case jumped(from: Int)
45 /// C-o and C-i through the jump list.
46 case jumpBack(Int)
47 case jumpForward(Int)
48 /// `q`: start recording keys into a register, or with nil stop.
49 case recordMacro(Character?)
50 /// `@`: play a register's keys, this many times.
51 case playMacro(Character, Int)
52 /// A block selection: one range per line.
53 case selectBlock([Range<Int>])
54 /// `gj`, `gk`: down (or, negative, up) by screen lines, which only the editor knows.
55 case visualLines(Int)
56 }
57
58 public struct Context: Sendable {
59 public var text: String
60 public var caret: Int
61 public var clipboard: String?
62 /// Lines in a half page, for C-d and C-u.
63 public var halfPage: Int
64 /// Marks where the editor keeps them now; `'` and `` ` `` are the last jump's start.
65 public var marks: [Character: Int]
66 /// The text the window shows, for H, M and L; nil when unknown.
67 public var visible: Range<Int>?
68 /// An Org buffer, where evil-org's keys apply.
69 public var org: Bool
70
71 public init(text: String, caret: Int, clipboard: String? = nil, halfPage: Int = 15, marks: [Character: Int] = [:], visible: Range<Int>? = nil, org: Bool = false) {
72 self.org = org
73 self.text = text
74 self.caret = caret
75 self.clipboard = clipboard
76 self.halfPage = halfPage
77 self.marks = marks
78 self.visible = visible
79 }
80 }
81
82 public internal(set) var mode: Mode = .normal
83 /// Keys of the command being typed.
84 public private(set) var pending: [KeyChord] = []
85 /// Named registers. Evil's are global: an editor with several engines shares them.
86 public var registers: [Character: (text: String, linewise: Bool)] = [:]
87 /// What the clipboard held when this engine last wrote it, and whether that was whole lines.
88 public var clipboardRecord: (text: String, linewise: Bool)?
89 var anchor = 0
90 var goalColumn: Int?
91 /// The last snipe: s, S, f, F, t or T. `consume` lands on the match (s, f, F) rather
92 /// than next to it (t, T); `keys` repeat it right after it, forward and back.
93 var lastSnipe: (forward: Bool, chars: String, count: Int, consume: Bool, keys: (forward: KeyChord, backward: KeyChord))?
94 /// Set after a snipe: its keys repeat it as the next key, as evil-snipe's transient map has it.
95 var snipeRepeatKeys: (forward: KeyChord, backward: KeyChord)?
96 var lastSearch: (pattern: String, forward: Bool)?
97 /// The keys and inserted text of the last change, for `.`.
98 var lastChange: (keys: [KeyChord], inserted: String?)?
99 var changeKeys: [KeyChord]?
100 var insertStart = 0
101 /// The caret when the command began, for commands that keep its column.
102 var caretForApply = 0
103 /// Typed in insert state since it began, kept by `inserted(_:)`.
104 var insertedText = ""
105 /// The last visual selection, for `gv`.
106 var lastVisual: (anchor: Int, caret: Int, mode: Mode)?
107 /// Marks the engine set this run, for a context without the editor's.
108 var marks: [Character: Int] = [:]
109 /// Where block `I`, `A` and `c` copy what was typed when insert state ends: the other lines,
110 /// by number from 1, the column, and whether short lines are padded to it.
111 var blockInsert: (lines: [Int], column: Int, pad: Bool)?
112 /// The editor's marks for this key.
113 var contextMarks: [Character: Int] = [:]
114 /// The context's half page and visible text for this key.
115 var contextHalfPage = 15
116 var contextVisible: Range<Int>?
117 var contextOrg = false
118 /// Keys are going into a macro register.
119 public internal(set) var recording = false
120 var lastMacro: Character?
121 /// `$` in block visual state: every line to its end.
122 var blockToEnd = false
123
124 public init() {}
125
126 // MARK: - Feeding keys
127
128 public mutating func feed(_ key: KeyChord, _ context: Context) -> [Action] {
129 switch mode {
130 case .insert:
131 return insertKey(key, context)
132 case .normal, .visual, .visualLine, .visualBlock:
133 var key = key
134 if pending.isEmpty, let repeatKeys = snipeRepeatKeys {
135 if key == repeatKeys.forward { key = KeyChord(";") } else if key == repeatKeys.backward { key = KeyChord(",") }
136 }
137 if pending.isEmpty { snipeRepeatKeys = nil }
138 pending.append(key)
139 let keys = pending
140 caretForApply = context.caret
141 contextMarks = context.marks
142 contextHalfPage = context.halfPage
143 contextVisible = context.visible
144 contextOrg = context.org
145 var buffer = Buffer(context)
146 switch parse(keys, &buffer, context) {
147 case .incomplete:
148 return []
149 case .invalid:
150 pending = []
151 return []
152 case .done(let actions):
153 pending = []
154 return actions
155 }
156 }
157 }
158
159 /// Text typed in insert state, recorded for `.`.
160 public mutating func inserted(_ text: String) {
161 insertedText += text
162 }
163
164 public mutating func deletedBackward() {
165 if !insertedText.isEmpty { insertedText.removeLast() }
166 }
167
168 private mutating func insertKey(_ key: KeyChord, _ context: Context) -> [Action] {
169 let buffer = Buffer(context)
170 if key == KeyChord("ESC") || key == KeyChord("[", .control) || key == KeyChord("g", .control) {
171 mode = .normal
172 if var keys = changeKeys {
173 keys.append(KeyChord("ESC"))
174 lastChange = (keys, insertedText)
175 changeKeys = nil
176 }
177 let caret = context.caret
178 let lineStart = buffer.lineStart(caret)
179 if let block = blockInsert {
180 blockInsert = nil
181 if !insertedText.isEmpty, !insertedText.contains("\n") { return replicateBlockInsert(block, buffer, caret: caret) }
182 }
183 return [.move(caret: caret > lineStart ? caret - 1 : caret)]
184 }
185 if key == KeyChord("w", .control) {
186 let start = buffer.wordBackward(context.caret, bigWord: false, stopAtLine: true)
187 guard start < context.caret else { return [] }
188 return [.edit([TextEdit(range: start..<context.caret, replacement: "")], caret: start)]
189 }
190 if key == KeyChord("u", .control) {
191 let start = buffer.firstNonBlank(buffer.lineStart(context.caret))
192 let from = context.caret > start ? start : buffer.lineStart(context.caret)
193 guard from < context.caret else { return [] }
194 return [.edit([TextEdit(range: from..<context.caret, replacement: "")], caret: from)]
195 }
196 return []
197 }
198
199 /// Whether a key in insert state belongs to the text system.
200 public func passesThrough(_ key: KeyChord) -> Bool {
201 guard mode == .insert else { return false }
202 return !(key == KeyChord("ESC") || key == KeyChord("[", .control) || key == KeyChord("g", .control)
203 || key == KeyChord("w", .control) || key == KeyChord("u", .control))
204 }
205
206 public mutating func enterInsert(at caret: Int, keys: [KeyChord]) {
207 mode = .insert
208 insertStart = caret
209 insertedText = ""
210 changeKeys = keys
211 }
212
213 // MARK: - Parsing
214
215 enum Parse {
216 case incomplete
217 case invalid
218 case done([Action])
219 }
220
221 struct Command {
222 var register: Character?
223 var count: Int?
224 var keys: ArraySlice<KeyChord>
225 }
226
227 /// `evil-shift-width`, which Doom keeps equal to `tab-width`: 8 in Org.
228 static let shiftWidth = 8
229
230 /// What `indent-according-to-mode` gives a line after `line` in Org: under a list item,
231 /// its text's column; under a heading, none; otherwise `line`'s own indentation.
232 static func indentation(for buffer: Buffer, line: Int) -> String {
233 let text = buffer.substring(buffer.lineStart(line)..<buffer.lineEnd(line))
234 let item = try! NSRegularExpression(pattern: "^[ \\t]*([-+*]|[0-9]+[.)])[ \\t]+")
235 let ns = text as NSString
236 if text.hasPrefix("*") { return "" }
237 if let m = item.firstMatch(in: text, range: NSRange(location: 0, length: ns.length)) {
238 return String(repeating: " ", count: m.range.length)
239 }
240 return String(text.prefix { $0 == " " || $0 == "\t" })
241 }
242
243 static func digit(_ key: KeyChord) -> Int? {
244 guard key.modifiers.isEmpty, key.key.count == 1, let d = Int(key.key) else { return nil }
245 return d
246 }
247
248 /// Reads `"r` and a count off the front of `keys`.
249 static func prefix(_ keys: [KeyChord]) -> (register: Character?, count: Int?, rest: ArraySlice<KeyChord>)? {
250 var rest = keys[...]
251 var register: Character?
252 if rest.first?.key == "\"", rest.first?.modifiers.isEmpty == true {
253 guard rest.count >= 2 else { return nil }
254 register = rest[rest.startIndex + 1].key == "SPC" ? " " : rest[rest.startIndex + 1].key.first
255 rest = rest.dropFirst(2)
256 }
257 var count: Int?
258 while let key = rest.first, let d = digit(key), !(d == 0 && count == nil) {
259 count = (count ?? 0) * 10 + d
260 rest = rest.dropFirst()
261 }
262 return (register, count, rest)
263 }
264
265 mutating func parse(_ keys: [KeyChord], _ buffer: inout Buffer, _ context: Context) -> Parse {
266 guard let (register, count, rest) = Self.prefix(keys) else { return .incomplete }
267 guard let first = rest.first else { return .incomplete }
268 let visual = mode == .visual || mode == .visualLine
269 let n = count ?? 1
270 let caret = context.caret
271
272 if mode == .visualBlock { return parseBlock(rest, &buffer, context, register: register, count: n, keys: keys) }
273 if let result = parseExtras(rest, &buffer, context, register: register, count: count, keys: keys) { return result }
274
275 // Operators.
276 let operatorKeys: [String: String] = ["d": "d", "c": "c", "y": "y", ">": ">", "<": "<"]
277 var op: String?
278 var afterOp = rest.dropFirst()
279 if first.modifiers.isEmpty, let o = operatorKeys[first.key] {
280 op = o
281 } else if first.key == "g", first.modifiers.isEmpty {
282 guard let second = rest.dropFirst().first else { return .incomplete }
283 if ["~", "u", "U", "c", "q", "w"].contains(second.key), second.modifiers.isEmpty {
284 op = "g" + second.key
285 afterOp = rest.dropFirst(2)
286 }
287 }
288 if let op {
289 if visual {
290 let range = visualRange(buffer, caret)
291 return .done(apply(op, range: range.range, linewise: range.linewise, buffer, register: register, keys: keys, count: n))
292 }
293 // A second count and the motion.
294 guard let (_, count2, motionKeys) = Self.prefix(Array(afterOp)), let motionFirst = motionKeys.first else { return .incomplete }
295 let total = n * (count2 ?? 1)
296 let doubled = op.count == 1 ? op : String(op.last!)
297 if motionFirst.key == doubled, motionFirst.modifiers.isEmpty, motionKeys.count == 1 || (op.count == 2 && motionFirst.key == String(op.last!)) {
298 // dd, cc, yy, >>, <<, g~~, guu, gUU: whole lines.
299 let startLine = buffer.lineStart(caret)
300 var end = startLine
301 for _ in 0..<total {
302 let next = buffer.lineEnd(end)
303 end = next < buffer.length ? next + 1 : next
304 if end >= buffer.length { break }
305 }
306 return .done(apply(op, range: startLine..<max(end, buffer.lineEnd(startLine)), linewise: true, buffer, register: register, keys: keys, count: total))
307 }
308 if motionFirst.key == "i" || motionFirst.key == "a", motionFirst.modifiers.isEmpty {
309 guard motionKeys.count >= 2 else { return .incomplete }
310 guard let object = textObject(motionFirst.key == "i", motionKeys[motionKeys.startIndex + 1], buffer, caret, count: total) else { return .invalid }
311 // evil-org's subtree objects move point to the heading first.
312 if motionKeys[motionKeys.startIndex + 1].key == "R" { caretForApply = buffer.lineStart(object.range.lowerBound) }
313 return .done(apply(op, range: object.range, linewise: object.linewise, buffer, register: register, keys: keys, count: total))
314 }
315 var motionCaret = caret
316 if op == "c", motionFirst.key == "w" || motionFirst.key == "W", motionFirst.modifiers.isEmpty,
317 let c = buffer.char(caret), !Buffer.isBlank(c) {
318 // cw acts as ce on a word.
319 var end = caret
320 for _ in 0..<total { end = buffer.wordEnd(end, bigWord: motionFirst.key == "W", fromInside: true) }
321 return .done(apply(op, range: caret..<min(buffer.length, end + 1), linewise: false, buffer, register: register, keys: keys, count: total))
322 }
323 switch motion(Array(motionKeys), &buffer, &motionCaret, count: total, operatorPending: true) {
324 case .incomplete: return .incomplete
325 case .invalid: return .invalid
326 case .moved(let target, let kind):
327 var range: Range<Int>
328 var linewise = false
329 switch kind {
330 case .linewise:
331 let a = buffer.lineStart(min(caret, target))
332 let b = buffer.lineEnd(max(caret, target))
333 range = a..<min(buffer.length, b + 1)
334 linewise = true
335 case .inclusive:
336 range = min(caret, target)..<min(buffer.length, max(caret, target) + 1)
337 case .exclusive:
338 range = min(caret, target)..<max(caret, target)
339 // dw at the end of a line stops at the line's end.
340 if motionFirst.key == "w" || motionFirst.key == "W", buffer.lineStart(range.upperBound) != buffer.lineStart(range.lowerBound), range.upperBound > range.lowerBound {
341 var end = buffer.lineEnd(range.lowerBound)
342 if end == range.lowerBound, total == 1 { end = min(buffer.length, end + 1) }
343 let upper = buffer.firstNonBlankOrEnd(buffer.lineStart(range.upperBound)) == range.upperBound
344 ? max(range.lowerBound, min(end, range.upperBound)) : range.upperBound
345 range = range.lowerBound..<(upper == range.lowerBound ? end : upper)
346 if buffer.lineStart(range.upperBound) != buffer.lineStart(range.lowerBound) {
347 range = range.lowerBound..<buffer.lineEnd(range.lowerBound)
348 }
349 }
350 }
351 return .done(apply(op, range: range, linewise: linewise, buffer, register: register, keys: keys, count: total))
352 }
353 }
354
355 if visual, first.modifiers.isEmpty {
356 switch first.key {
357 case "ESC":
358 lastVisual = (anchor, caret, mode)
359 mode = .normal
360 return .done([.move(caret: buffer.clampNormal(caret))])
361 case "o":
362 let old = anchor
363 anchor = caret
364 return .done(visualActions(buffer, old))
365 case "x":
366 let range = visualRange(buffer, caret)
367 return .done(apply("d", range: range.range, linewise: range.linewise, buffer, register: register, keys: keys, count: n))
368 case "~", "u", "U":
369 let range = visualRange(buffer, caret)
370 return .done(apply(first.key == "~" ? "g~" : "g" + first.key, range: range.range, linewise: range.linewise, buffer, register: register, keys: keys, count: n))
371 case "J":
372 let range = visualRange(buffer, caret)
373 let lines = max(2, buffer.lineCount(range.range))
374 mode = .normal
375 return .done(join(buffer, at: range.range.lowerBound, lines: lines, keys: keys))
376 case "p", "P":
377 let range = visualRange(buffer, caret)
378 guard let (text, linewise) = paste(register, context) else { return .done([]) }
379 mode = .normal
380 var replacement = text
381 if linewise, !range.linewise { replacement = "\n" + text }
382 if !linewise, range.linewise { replacement += "\n" }
383 let removed = buffer.substring(range.range)
384 var actions: [Action] = [.edit([TextEdit(range: range.range, replacement: replacement)], caret: range.range.lowerBound + (linewise ? 0 : max(0, replacement.utf16.count - 1)))]
385 actions += store(removed, linewise: range.linewise, register: nil, yank: false)
386 return .done(actions)
387 case "v":
388 if mode == .visual {
389 mode = .normal
390 return .done([.move(caret: buffer.clampNormal(caret))])
391 }
392 mode = .visual
393 return .done(visualActions(buffer, caret))
394 case "V":
395 if mode == .visualLine {
396 mode = .normal
397 return .done([.move(caret: buffer.clampNormal(caret))])
398 }
399 mode = .visualLine
400 return .done(visualActions(buffer, caret))
401 case "i", "a":
402 guard rest.count >= 2 else { return .incomplete }
403 guard let object = textObject(first.key == "i", rest[rest.startIndex + 1], buffer, caret, count: n) else { return .invalid }
404 if object.linewise { mode = .visualLine }
405 anchor = object.range.lowerBound
406 let end = max(object.range.lowerBound, object.range.upperBound - 1)
407 return .done(visualActions(buffer, end))
408 default:
409 break
410 }
411 }
412
413 if !visual {
414 switch (first.key, first.modifiers) {
415 case ("i", []), ("a", []), ("I", []), ("A", []), ("o", []), ("O", []):
416 return .done(enterInsert(first.key, buffer, caret, keys: keys))
417 case ("v", []):
418 mode = .visual
419 anchor = caret
420 return .done(visualActions(buffer, caret))
421 case ("V", []):
422 mode = .visualLine
423 anchor = caret
424 return .done(visualActions(buffer, caret))
425 case ("x", []), ("<delete>", []):
426 guard let c = buffer.char(caret), c != 10 else { return .done([]) }
427 let end = min(buffer.lineEnd(caret), caret + n)
428 return .done(apply("d", range: caret..<end, linewise: false, buffer, register: register, keys: keys, count: n))
429 case ("X", []):
430 let start = max(buffer.lineStart(caret), caret - n)
431 guard start < caret else { return .done([]) }
432 return .done(apply("d", range: start..<caret, linewise: false, buffer, register: register, keys: keys, count: n))
433 case ("D", []), ("C", []):
434 var end = buffer.lineEnd(caret)
435 for _ in 1..<max(1, n) where end < buffer.length { end = buffer.lineEnd(end + 1) }
436 return .done(apply(first.key == "D" ? "d" : "c", range: caret..<end, linewise: false, buffer, register: register, keys: keys, count: n))
437 case ("Y", []):
438 // `evil-want-Y-yank-to-eol`.
439 return .done(apply("y", range: caret..<buffer.lineEnd(caret), linewise: false, buffer, register: register, keys: keys, count: n))
440 case ("p", []), ("P", []):
441 guard let (text, linewise) = paste(register, context) else { return .done([]) }
442 let repeated = String(repeating: text, count: n)
443 if linewise {
444 let body = repeated.hasSuffix("\n") ? repeated : repeated + "\n"
445 if first.key == "p" {
446 let end = buffer.lineEnd(caret)
447 if end >= buffer.length {
448 let insertion = "\n" + String(body.dropLast())
449 return .done(change([TextEdit(range: end..<end, replacement: insertion)], caret: buffer.firstNonBlankIn(insertion, at: end + 1), keys: keys))
450 }
451 return .done(change([TextEdit(range: (end + 1)..<(end + 1), replacement: body)], caret: buffer.firstNonBlankIn(body, at: end + 1), keys: keys))
452 }
453 let start = buffer.lineStart(caret)
454 return .done(change([TextEdit(range: start..<start, replacement: body)], caret: buffer.firstNonBlankIn(body, at: start), keys: keys))
455 }
456 let at = first.key == "p" && buffer.char(caret).map({ $0 != 10 }) == true ? caret + 1 : caret
457 return .done(change([TextEdit(range: at..<at, replacement: repeated)], caret: at + max(0, repeated.utf16.count - 1), keys: keys))
458 case ("J", []):
459 return .done(join(buffer, at: caret, lines: max(2, n), keys: keys))
460 case ("~", []):
461 let end = min(buffer.lineEnd(caret), caret + n)
462 guard end > caret else { return .done([]) }
463 let flipped = String(buffer.substring(caret..<end).map { $0.isUppercase ? Character($0.lowercased()) : Character($0.uppercased()) })
464 return .done(change([TextEdit(range: caret..<end, replacement: flipped)], caret: buffer.clampNormal(end, in: buffer.lineStart(caret)), keys: keys))
465 case ("r", []):
466 guard rest.count >= 2 else { return .incomplete }
467 let target = rest[rest.startIndex + 1]
468 let char = target.key == "SPC" ? " " : target.key == "RET" ? "\n" : target.key
469 guard char.count == 1, caret + n <= buffer.lineEnd(caret) else { return .done([]) }
470 let replacement = char == "\n" ? "\n" : String(repeating: char, count: n)
471 return .done(change([TextEdit(range: caret..<(caret + n), replacement: replacement)], caret: char == "\n" ? caret + 1 : caret + n - 1, keys: keys))
472 case ("u", []):
473 return .done([.undo(n)])
474 case ("r", [.control]):
475 return .done([.redo(n)])
476 case (".", []):
477 return .done(repeatChange(n, context))
478 case (":", []):
479 return .done([.prompt(":")])
480 case ("/", []), ("?", []):
481 return .done([.prompt(first.key)])
482 case ("Z", []):
483 guard rest.count >= 2 else { return .incomplete }
484 switch rest[rest.startIndex + 1].key {
485 case "Z": return .done([.ex("wq")])
486 case "Q": return .done([.ex("q!")])
487 default: return .invalid
488 }
489 default:
490 break
491 }
492 }
493
494 if mode == .normal, !contextOrg, rest.count == 2, first == KeyChord("g"), rest.last == KeyChord("j") || rest.last == KeyChord("k") {
495 return .done([.visualLines(rest.last == KeyChord("j") ? n : -n)])
496 }
497
498 // Motions move the caret, or extend the selection in visual states.
499 var target = caret
500 switch motion(Array(rest), &buffer, &target, count: n, operatorPending: false) {
501 case .incomplete: return .incomplete
502 case .invalid: return .invalid
503 case .moved(let to, _):
504 if visual { return .done(visualActions(buffer, to)) }
505 let jump: [Action] = Self.isJump(Array(rest)) && to != caret ? [.jumped(from: caret)] : []
506 return .done(jump + [.move(caret: buffer.clampNormal(to))])
507 }
508 }
509
510 // MARK: - Motions
511
512 enum MotionKind { case exclusive, inclusive, linewise }
513
514 enum MotionResult {
515 case incomplete
516 case invalid
517 case moved(Int, MotionKind)
518 }
519
520 mutating func motion(_ keys: [KeyChord], _ buffer: inout Buffer, _ caret: inout Int, count n: Int, operatorPending: Bool) -> MotionResult {
521 guard let key = keys.first else { return .incomplete }
522 let c = caret
523 func column(_ position: Int) -> Int { position - buffer.lineStart(position) }
524 switch (key.key, key.modifiers) {
525 case ("h", []), ("<left>", []), ("DEL", []):
526 goalColumn = nil
527 return .moved(max(buffer.lineStart(c), c - n), .exclusive)
528 case ("l", []), ("<right>", []), ("SPC", []):
529 goalColumn = nil
530 let end = buffer.lineEnd(c)
531 let limit = operatorPending ? end : max(buffer.lineStart(c), end - 1)
532 return .moved(min(limit, c + n), .exclusive)
533 case ("j", []), ("k", []), ("<down>", []), ("<up>", []), ("d", [.control]), ("u", [.control]), ("+", []), ("-", []), ("RET", []):
534 let down = ["j", "<down>", "+", "RET"].contains(key.key) || key == KeyChord("d", .control)
535 let lines = key.modifiers.contains(.control) ? (n == 1 ? contextHalfPage : n) : n
536 let goal = goalColumn ?? column(c)
537 var line = buffer.lineStart(c)
538 var moved = 0
539 for _ in 0..<lines {
540 if down {
541 let end = buffer.lineEnd(line)
542 guard end < buffer.length, end + 1 <= buffer.length else { break }
543 if end + 1 == buffer.length { break }
544 line = end + 1
545 } else {
546 guard line > 0 else { break }
547 line = buffer.lineStart(line - 1)
548 }
549 moved += 1
550 }
551 if moved == 0, !key.modifiers.contains(.control) { return .invalid }
552 if ["+", "-", "RET"].contains(key.key) { return .moved(buffer.firstNonBlank(line), .linewise) }
553 goalColumn = goal
554 let length = buffer.lineEnd(line) - line
555 return .moved(line + min(goal, operatorPending ? length : max(0, length - 1)), .linewise)
556 case ("w", []), ("W", []):
557 goalColumn = nil
558 var p = c
559 for _ in 0..<n { p = buffer.wordForward(p, bigWord: key.key == "W") }
560 return .moved(p, .exclusive)
561 case ("b", []), ("B", []):
562 goalColumn = nil
563 var p = c
564 for _ in 0..<n { p = buffer.wordBackward(p, bigWord: key.key == "B", stopAtLine: false) }
565 return .moved(p, .exclusive)
566 case ("e", []), ("E", []):
567 goalColumn = nil
568 var p = c
569 for _ in 0..<n { p = buffer.wordEnd(p, bigWord: key.key == "E", fromInside: false) }
570 return .moved(p, .inclusive)
571 case ("0", []), ("<home>", []):
572 goalColumn = nil
573 return .moved(buffer.lineStart(c), .exclusive)
574 case ("^", []):
575 goalColumn = nil
576 return .moved(buffer.firstNonBlank(buffer.lineStart(c)), .exclusive)
577 case ("$", []), ("<end>", []):
578 var line = buffer.lineStart(c)
579 for _ in 1..<max(1, n) where buffer.lineEnd(line) < buffer.length { line = buffer.lineEnd(line) + 1 }
580 goalColumn = Int.max
581 let end = buffer.lineEnd(line)
582 if mode == .visual { return .moved(end, .inclusive) }
583 return .moved(max(line, end - 1), .inclusive)
584 case ("G", []):
585 // `evil-start-of-line` nil: the column stays.
586 let line = n > 1 || keys.count > 1 ? buffer.lineOffset(n) : buffer.lineStart(buffer.length)
587 let column = c - buffer.lineStart(c)
588 let length = buffer.lineEnd(line) - line
589 return .moved(line + min(column, operatorPending ? length : max(0, length - 1)), .linewise)
590 case ("g", []):
591 guard keys.count >= 2 else { return .incomplete }
592 switch keys[1].key {
593 case "g":
594 let line = buffer.lineOffset(n)
595 let column = c - buffer.lineStart(c)
596 let length = buffer.lineEnd(line) - line
597 return .moved(line + min(column, operatorPending ? length : max(0, length - 1)), .linewise)
598 case "e", "E":
599 var p = c
600 for _ in 0..<n { p = buffer.wordEndBackward(p, bigWord: keys[1].key == "E") }
601 return .moved(p, .inclusive)
602 case "j" where contextOrg, "k" where contextOrg:
603 // evil-org: org-forward-element and org-backward-element, which take no count.
604 guard let p = Self.orgObject(keys[1].key, inner: false, buffer, c)?.range.lowerBound, p != c else { return .invalid }
605 goalColumn = nil
606 return .moved(p, .exclusive)
607 case "j":
608 return motion([KeyChord("j")], &buffer, &caret, count: n, operatorPending: operatorPending)
609 case "k":
610 return motion([KeyChord("k")], &buffer, &caret, count: n, operatorPending: operatorPending)
611 case "_":
612 let end = buffer.lineEnd(c)
613 var p = end
614 while p > buffer.lineStart(c), let ch = buffer.char(p - 1), Buffer.isBlank(ch) { p -= 1 }
615 return .moved(max(buffer.lineStart(c), p - 1), .inclusive)
616 default:
617 return .invalid
618 }
619 case ("f", []), ("F", []), ("t", []), ("T", []):
620 // evil-snipe-override-mode: one-character snipes.
621 guard keys.count >= 2 else { return .incomplete }
622 let char = keys[1].key == "SPC" ? " " : keys[1].key == "TAB" ? "\t" : keys[1].key
623 guard char.count == 1 else { return .invalid }
624 let forward = key.key == "f" || key.key == "t"
625 lastSnipe = (forward, char, n, key.key == "f" || key.key == "F", (KeyChord(key.key.lowercased()), KeyChord(key.key.uppercased())))
626 return snipe(forward: forward, count: n, buffer, c, operatorPending: operatorPending, repeating: false)
627 case (";", []), (",", []):
628 // evil-snipe-repeat, with Doom's `evil-snipe-repeat-scope` visible.
629 guard let last = lastSnipe else { return .invalid }
630 let forward = key.key == ";" ? last.forward : !last.forward
631 return snipe(forward: forward, count: n * last.count, buffer, c, operatorPending: operatorPending, repeating: true)
632 case ("s", []) where !operatorPending, ("S", []) where !operatorPending:
633 guard keys.count >= 3 else { return .incomplete }
634 let chars = keys[1...2].map { $0.key == "SPC" ? " " : $0.key == "TAB" ? "\t" : $0.key }.joined()
635 lastSnipe = (key.key == "s", chars, n, true, (KeyChord("s"), KeyChord("S")))
636 return snipe(forward: key.key == "s", count: n, buffer, c, operatorPending: operatorPending, repeating: false)
637 case ("%", []):
638 guard let p = buffer.matchingBracket(c) else { return .invalid }
639 return .moved(p, .inclusive)
640 case ("'", []), ("`", []):
641 guard keys.count >= 2 else { return .incomplete }
642 let name = keys[1].key == "'" || keys[1].key == "`" ? Character("'") : keys[1].key.first
643 guard let name, let mark = currentMarks[name] else { return .invalid }
644 goalColumn = nil
645 let p = min(mark, buffer.length)
646 return key.key == "'" ? .moved(buffer.firstNonBlank(buffer.lineStart(p)), .linewise) : .moved(p, .exclusive)
647 case ("}", []), ("{", []):
648 var p = c
649 for _ in 0..<n { p = buffer.paragraph(p, forward: key.key == "}") }
650 return .moved(p, .exclusive)
651 case ("n", []), ("N", []):
652 guard let search = lastSearch else { return .invalid }
653 let forward = key.key == "n" ? search.forward : !search.forward
654 guard let p = buffer.search(search.pattern, from: c, forward: forward, count: n) else { return .invalid }
655 return .moved(p, .exclusive)
656 case ("*", []), ("#", []):
657 guard let word = buffer.wordAt(c) else { return .invalid }
658 let pattern = "\\b" + NSRegularExpression.escapedPattern(for: word) + "\\b"
659 lastSearch = (pattern, key.key == "*")
660 guard let p = buffer.search(pattern, from: buffer.wordStart(c), forward: key.key == "*", count: n) else { return .invalid }
661 return .moved(p, .exclusive)
662 case ("H", []), ("M", []), ("L", []):
663 // The visible lines: H the count-th from the top, L from the bottom, M the middle.
664 guard let visible = contextVisible else { return .invalid }
665 goalColumn = nil
666 let lower = min(visible.lowerBound, buffer.length)
667 let last = buffer.lineStart(max(lower, min(visible.upperBound, buffer.length) - 1))
668 var lines: [Int] = []
669 var line = buffer.lineStart(lower)
670 while true {
671 lines.append(line)
672 guard line < last, buffer.lineEnd(line) < buffer.length else { break }
673 line = buffer.lineEnd(line) + 1
674 }
675 let index = key.key == "H" ? min(n, lines.count) - 1 : key.key == "L" ? max(0, lines.count - n) : (lines.count - 1) / 2
676 return .moved(buffer.firstNonBlank(lines[index]), .linewise)
677 default:
678 return .invalid
679 }
680 }
681
682 /// `evil-snipe--seek` for the last snipe: the `n`th match from `c` in the line after (or
683 /// before) the search start, or for a repeat in the visible text, with smart case,
684 /// Doom's `evil-snipe-char-fold` and `evil-snipe-skip-leading-whitespace`.
685 mutating func snipe(forward: Bool, count n: Int, _ buffer: Buffer, _ c: Int, operatorPending: Bool, repeating: Bool) -> MotionResult {
686 guard let last = lastSnipe else { return .invalid }
687 // f, s and F search from the next or this character, t and T one further.
688 let start = forward ? c + (last.consume ? 1 : 2) : c - (last.consume ? 0 : 1)
689 guard start >= 0, start <= buffer.length else { return .invalid }
690 var bound = forward ? buffer.lineEnd(start) : buffer.lineStart(start)
691 if repeating, let visible = contextVisible {
692 bound = forward ? min(visible.upperBound, buffer.length) - 1 : min(visible.lowerBound, start)
693 }
694 let blankKeys = last.chars.first == " " || last.chars.first == "\t"
695 let regexps = last.chars.map(CharFold.regexp)
696 var pattern = regexps.map(CharFold.icu).joined()
697 if blankKeys, let ch = buffer.char(start), Buffer.isBlank(ch) { pattern += "[^ \t]" }
698 // `evil-snipe--case-p`: case matters when the regexps hold a capital letter.
699 let caseFold = !regexps.joined().unicodeScalars.contains { ("A"..."Z").contains($0) }
700 let regex = try! NSRegularExpression(pattern: pattern, options: caseFold ? .caseInsensitive : [])
701 var match: NSRange?
702 if forward, start <= bound {
703 var from = start
704 for _ in 0..<n {
705 match = regex.firstMatch(in: buffer.ns as String, range: NSRange(location: from, length: bound - from))?.range
706 guard let found = match else { break }
707 from = NSMaxRange(found)
708 }
709 } else if !forward {
710 var from = start
711 for _ in 0..<n {
712 match = nil
713 var p = from - 1
714 while p >= bound, match == nil {
715 match = regex.firstMatch(in: buffer.ns as String, options: .anchored, range: NSRange(location: p, length: from - p))?.range
716 p -= 1
717 }
718 guard let found = match else { break }
719 from = found.location
720 }
721 }
722 guard let found = match else {
723 if repeating { snipeRepeatKeys = last.keys }
724 return .invalid
725 }
726 if !operatorPending { snipeRepeatKeys = last.keys }
727 var end = NSMaxRange(found)
728 var length = found.length
729 if blankKeys {
730 end -= 1
731 length -= 1
732 }
733 let visual = mode == .visual || mode == .visualLine || mode == .visualBlock
734 var p: Int
735 if !forward {
736 p = last.consume ? found.location : end
737 } else {
738 p = visual ? (last.consume ? end : found.location) : operatorPending ? end : found.location
739 if last.consume {
740 if visual { p -= 1 }
741 } else {
742 p -= length
743 if length > 1, !operatorPending { p += 1 }
744 }
745 }
746 return .moved(p, .exclusive)
747 }
748
749 // MARK: - Text objects
750
751 func textObject(_ inner: Bool, _ key: KeyChord, _ buffer: Buffer, _ c: Int, count: Int) -> (range: Range<Int>, linewise: Bool)? {
752 switch key.key {
753 case "w", "W":
754 return buffer.wordObject(c, inner: inner, bigWord: key.key == "W", count: count).map { ($0, false) }
755 case "\"", "'", "`":
756 return buffer.quoteObject(c, quote: key.key.utf16.first!, inner: inner).map { ($0, false) }
757 case "(", ")", "b":
758 return buffer.bracketObject(c, open: 40, close: 41, inner: inner).map { ($0, false) }
759 case "[", "]":
760 return buffer.bracketObject(c, open: 91, close: 93, inner: inner).map { ($0, false) }
761 case "{", "}", "B":
762 return buffer.bracketObject(c, open: 123, close: 125, inner: inner).map { ($0, false) }
763 case "<", ">":
764 return buffer.bracketObject(c, open: 60, close: 62, inner: inner).map { ($0, false) }
765 case "p":
766 return buffer.paragraphObject(c, inner: inner).map { ($0, true) }
767 case "e", "E", "r", "R":
768 return Self.orgObject(key.key, inner: inner, buffer, c)
769 default:
770 return nil
771 }
772 }
773
774 // MARK: - Applying
775
776 func visualRange(_ buffer: Buffer, _ caret: Int) -> (range: Range<Int>, linewise: Bool) {
777 let a = min(anchor, caret)
778 let b = max(anchor, caret)
779 if mode == .visualLine {
780 let end = buffer.lineEnd(b)
781 return (buffer.lineStart(a)..<min(buffer.length, end + 1), true)
782 }
783 return (a..<min(buffer.length, b + 1), false)
784 }
785
786 func visualActions(_ buffer: Buffer, _ caret: Int) -> [Action] {
787 let a = min(anchor, caret)
788 let b = max(anchor, caret)
789 let range = mode == .visualLine ? buffer.lineStart(a)..<buffer.lineEnd(b) : a..<min(buffer.length, b + 1)
790 return [.move(caret: caret), .select(range)]
791 }
792
793 mutating func store(_ text: String, linewise: Bool, register: Character?, yank: Bool) -> [Action] {
794 if register == "_" { return [] }
795 if let register, register.isLetter {
796 let key = Character(register.lowercased())
797 if register.isUppercase, let existing = registers[key] {
798 registers[key] = (existing.text + text, existing.linewise || linewise)
799 } else {
800 registers[key] = (text, linewise)
801 }
802 return []
803 }
804 if yank { registers["0"] = (text, linewise) }
805 clipboardRecord = (text, linewise)
806 return [.setClipboard(text)]
807 }
808
809 func paste(_ register: Character?, _ context: Context) -> (String, Bool)? {
810 if let register, register != "\"", register != "+", register != "*" {
811 return registers[Character(register.lowercased())]
812 }
813 guard let clip = context.clipboard ?? clipboardRecord?.text else { return nil }
814 if let record = clipboardRecord, record.text == clip { return (clip, record.linewise) }
815 return (clip, false)
816 }
817
818 mutating func change(_ edits: [TextEdit], caret: Int, keys: [KeyChord]) -> [Action] {
819 lastChange = (keys, nil)
820 return [.edit(edits, caret: caret)]
821 }
822
823 mutating func apply(_ op: String, range: Range<Int>, linewise: Bool, _ buffer: Buffer, register: Character?, keys: [KeyChord], count: Int) -> [Action] {
824 let wasVisual = mode == .visual || mode == .visualLine
825 if wasVisual { lastVisual = (anchor, caretForApply, mode) }
826 let context0Column = max(0, caretForApply - buffer.lineStart(caretForApply))
827 mode = .normal
828 let text = buffer.substring(range)
829 switch op {
830 case "y":
831 var actions = store(text, linewise: linewise, register: register, yank: true)
832 let caret = linewise && !wasVisual ? min(range.lowerBound, buffer.length) : range.lowerBound
833 actions.append(.move(caret: linewise && wasVisual ? buffer.lineStart(range.lowerBound) : caret))
834 return actions
835 case "d":
836 var actions = store(text, linewise: linewise, register: register, yank: false)
837 var deletion = range
838 if linewise, range.upperBound >= buffer.length, range.lowerBound > 0, buffer.char(range.upperBound - 1) != 10 || range.upperBound == buffer.length {
839 // The last line takes the newline before it.
840 if buffer.char(range.lowerBound - 1) == 10, range.upperBound == buffer.length, !(text.hasSuffix("\n")) {
841 deletion = (range.lowerBound - 1)..<range.upperBound
842 }
843 }
844 let after = buffer.removing(deletion)
845 var caret = deletion.lowerBound
846 if linewise {
847 let column = context0Column
848 let line = after.lineStart(min(deletion.lowerBound, after.length))
849 let length = after.lineEnd(line) - line
850 caret = line + min(column, max(0, length - 1))
851 } else {
852 caret = after.clampNormal(caret)
853 }
854 lastChange = (wasVisual ? [] : keys, nil)
855 actions.insert(.edit([TextEdit(range: deletion, replacement: "")], caret: caret), at: 0)
856 return actions
857 case "c":
858 var actions = store(text, linewise: linewise, register: register, yank: false)
859 var deletion = range
860 var replacement = ""
861 if linewise {
862 // cc keeps the newline and indents as the line before it calls for.
863 replacement = range.lowerBound == 0 ? "" : Self.indentation(for: buffer, line: range.lowerBound - 1)
864 if buffer.char(range.upperBound - 1) == 10 { deletion = range.lowerBound..<(range.upperBound - 1) }
865 }
866 let caret = deletion.lowerBound + replacement.utf16.count
867 actions.insert(.edit([TextEdit(range: deletion, replacement: replacement)], caret: caret), at: 0)
868 enterInsert(at: caret, keys: wasVisual ? [] : keys)
869 return actions
870 case ">", "<":
871 let start = buffer.lineStart(range.lowerBound)
872 var edits: [TextEdit] = []
873 var line = start
874 let end = linewise ? range.upperBound : max(range.upperBound, buffer.lineEnd(max(range.lowerBound, range.upperBound - 1)))
875 while line < end || (line == start && line <= buffer.length) {
876 let lineEnd = buffer.lineEnd(line)
877 if lineEnd > line {
878 if op == ">" {
879 edits.append(TextEdit(range: line..<line, replacement: String(repeating: " ", count: Self.shiftWidth)))
880 } else {
881 let indent = buffer.substring(line..<buffer.firstNonBlankOrEnd(line))
882 let remove = min(Self.shiftWidth, indent.count)
883 if remove > 0 { edits.append(TextEdit(range: line..<(line + remove), replacement: "")) }
884 }
885 }
886 guard lineEnd < buffer.length else { break }
887 line = lineEnd + 1
888 if line >= end { break }
889 }
890 lastChange = (wasVisual ? [] : keys, nil)
891 return [.edit(edits, caret: start)]
892 case "gc":
893 lastChange = (wasVisual ? [] : keys, nil)
894 return [.edit(Self.toggleComments(buffer, range), caret: buffer.lineStart(range.lowerBound))]
895 case "gq", "gw":
896 // Whole lines, as evil's fill operators take them.
897 lastChange = (wasVisual ? [] : keys, nil)
898 let end = buffer.lineEnd(max(range.lowerBound, range.upperBound - 1))
899 let lines = buffer.lineStart(range.lowerBound)..<min(buffer.length, end + 1)
900 return [.fill(lines, keep: op == "gw" ? caretForApply : nil)]
901 default:
902 let transformed: String
903 switch op {
904 case "gu": transformed = text.lowercased()
905 case "gU": transformed = text.uppercased()
906 default: transformed = String(text.map { $0.isUppercase ? Character($0.lowercased()) : Character($0.uppercased()) })
907 }
908 lastChange = (wasVisual ? [] : keys, nil)
909 return [.edit([TextEdit(range: range, replacement: transformed)], caret: range.lowerBound)]
910 }
911 }
912
913 mutating func enterInsert(_ key: String, _ buffer: Buffer, _ caret: Int, keys: [KeyChord]) -> [Action] {
914 switch key {
915 case "i":
916 enterInsert(at: caret, keys: keys)
917 return [.move(caret: caret)]
918 case "a":
919 let p = buffer.char(caret).map { $0 != 10 } == true ? caret + 1 : caret
920 enterInsert(at: p, keys: keys)
921 return [.move(caret: p)]
922 case "I":
923 let p = buffer.firstNonBlank(buffer.lineStart(caret))
924 enterInsert(at: p, keys: keys)
925 return [.move(caret: p)]
926 case "A":
927 let p = buffer.lineEnd(caret)
928 enterInsert(at: p, keys: keys)
929 return [.move(caret: p)]
930 case "o":
931 let end = buffer.lineEnd(caret)
932 let indent = Self.indentation(for: buffer, line: caret)
933 enterInsert(at: end + 1 + indent.utf16.count, keys: keys)
934 return [.edit([TextEdit(range: end..<end, replacement: "\n" + indent)], caret: end + 1 + indent.utf16.count)]
935 default:
936 let start = buffer.lineStart(caret)
937 let indent = Self.indentation(for: buffer, line: max(0, start - 1)) == "" || start == 0 ? "" : Self.indentation(for: buffer, line: start - 1)
938 enterInsert(at: start + indent.utf16.count, keys: keys)
939 return [.edit([TextEdit(range: start..<start, replacement: indent + "\n")], caret: start + indent.utf16.count)]
940 }
941 }
942
943 mutating func join(_ buffer: Buffer, at caret: Int, lines: Int, keys: [KeyChord]) -> [Action] {
944 var edits: [TextEdit] = []
945 var line = buffer.lineStart(caret)
946 var position = caret
947 for _ in 1..<lines {
948 let end = buffer.lineEnd(line)
949 guard end < buffer.length else { break }
950 let nextStart = end + 1
951 let nextText = buffer.firstNonBlankOrEnd(nextStart)
952 let joinsEmpty = buffer.lineEnd(nextStart) == nextText
953 let separator = joinsEmpty || buffer.char(nextText) == 41 ? "" : (end > line && buffer.char(end - 1) == 32 ? "" : " ")
954 var from = end
955 while from > line, let c = buffer.char(from - 1), c == 32 || c == 9 { from -= 1 }
956 if !separator.isEmpty || from == end { from = end }
957 edits.append(TextEdit(range: from..<nextText, replacement: separator))
958 position = from
959 line = nextStart
960 }
961 guard !edits.isEmpty else { return [] }
962 lastChange = (keys, nil)
963 // Positions in edits refer to the original text; apply back to front.
964 let shift = edits.dropLast().reduce(0) { $0 + $1.replacement.utf16.count - $1.range.count }
965 return [.edit(edits, caret: position + shift)]
966 }
967
968 mutating func repeatChange(_ count: Int, _ context: Context) -> [Action] {
969 guard let change = lastChange, !change.keys.isEmpty else { return [] }
970 var copy = self
971 copy.lastChange = nil
972 copy.pending = []
973 var keys = change.keys
974 if count > 1, let (_, _, rest) = Self.prefix(keys) {
975 keys = Array("\(count)".map { KeyChord(String($0)) }) + Array(rest)
976 }
977 var actions: [Action] = []
978 var text = context.text
979 var caret = context.caret
980 for key in keys {
981 if copy.mode == .insert, key == KeyChord("ESC") {
982 if let inserted = change.inserted, !inserted.isEmpty {
983 let edit = TextEdit(range: caret..<caret, replacement: inserted)
984 actions.append(.edit([edit], caret: caret + inserted.utf16.count))
985 text = edit.apply(to: text)
986 caret += inserted.utf16.count
987 }
988 }
989 let produced = copy.feed(key, Context(text: text, caret: caret, clipboard: context.clipboard, halfPage: context.halfPage, visible: context.visible, org: context.org))
990 for action in produced {
991 switch action {
992 case .edit(let edits, let to):
993 var new = text
994 for edit in edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { new = edit.apply(to: new) }
995 text = new
996 caret = to
997 case .move(let to):
998 caret = to
999 default:
1000 break
1001 }
1002 actions.append(action)
1003 }
1004 }
1005 let preserved = (registers, clipboardRecord)
1006 self = copy
1007 registers = preserved.0
1008 clipboardRecord = copy.clipboardRecord ?? preserved.1
1009 lastChange = change
1010 return actions
1011 }
1012
1013 /// `/pattern` or `?pattern` from the prompt: the caret's new place.
1014 public mutating func search(_ pattern: String, forward: Bool, _ context: Context) -> [Action] {
1015 lastSearch = (pattern, forward)
1016 let buffer = Buffer(context)
1017 guard let p = buffer.search(pattern, from: context.caret, forward: forward, count: 1) else { return [.message("Search failed: \(pattern)")] }
1018 if mode == .visual || mode == .visualLine { return visualActions(buffer, p) }
1019 return [.jumped(from: context.caret), .move(caret: p)]
1020 }
1021
1022 /// Leave a visual state without acting, as `ESC` does.
1023 public mutating func reset() {
1024 pending = []
1025 if mode == .visual || mode == .visualLine || mode == .visualBlock { mode = .normal }
1026 }
1027
1028 // MARK: - Text
1029
1030 struct Buffer {
1031 let ns: NSString
1032 var length: Int { ns.length }
1033
1034 init(_ context: Context) { ns = context.text as NSString }
1035 init(_ text: String) { ns = text as NSString }
1036
1037 func char(_ i: Int) -> unichar? { i >= 0 && i < ns.length ? ns.character(at: i) : nil }
1038 func substring(_ r: Range<Int>) -> String { ns.substring(with: NSRange(location: r.lowerBound, length: r.count)) }
1039 func lineStart(_ i: Int) -> Int { ns.lineRange(for: NSRange(location: min(i, ns.length), length: 0)).location }
1040 func lineEnd(_ i: Int) -> Int {
1041 var end = 0
1042 ns.getLineStart(nil, end: nil, contentsEnd: &end, for: NSRange(location: min(i, ns.length), length: 0))
1043 return end
1044 }
1045 func removing(_ r: Range<Int>) -> Buffer { Buffer(ns.replacingCharacters(in: NSRange(location: r.lowerBound, length: r.count), with: "")) }
1046 func applying(_ edits: [TextEdit]) -> Buffer {
1047 var text = ns as String
1048 for edit in edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { text = edit.apply(to: text) }
1049 return Buffer(text)
1050 }
1051
1052 static func isBlank(_ c: unichar) -> Bool { c == 32 || c == 9 }
1053 static func isSpace(_ c: unichar) -> Bool { c == 32 || c == 9 || c == 10 || c == 13 }
1054 static func isWord(_ c: unichar) -> Bool {
1055 guard let scalar = Unicode.Scalar(c) else { return true }
1056 return CharacterSet.alphanumerics.contains(scalar) || c == 95
1057 }
1058
1059 /// 0 blank, 1 word, 2 other.
1060 func kind(_ i: Int, bigWord: Bool) -> Int {
1061 guard let c = char(i) else { return 0 }
1062 if Self.isSpace(c) { return 0 }
1063 if bigWord { return 1 }
1064 return Self.isWord(c) ? 1 : 2
1065 }
1066
1067 func clampNormal(_ i: Int) -> Int { clampNormal(i, in: lineStart(i)) }
1068 func clampNormal(_ i: Int, in line: Int) -> Int {
1069 let end = lineEnd(line)
1070 return max(line, min(i, end > line ? end - 1 : line))
1071 }
1072
1073 func firstNonBlank(_ line: Int) -> Int {
1074 var p = line
1075 let end = lineEnd(line)
1076 while p < end, let c = char(p), Self.isBlank(c) { p += 1 }
1077 return p == end && end > line ? end - 1 : (p == end ? line : p)
1078 }
1079
1080 func firstNonBlankOrEnd(_ line: Int) -> Int {
1081 var p = line
1082 let end = lineEnd(line)
1083 while p < end, let c = char(p), Self.isBlank(c) { p += 1 }
1084 return p
1085 }
1086
1087 func firstNonBlankIn(_ inserted: String, at start: Int) -> Int {
1088 start + inserted.prefix { $0 == " " || $0 == "\t" }.utf16.count
1089 }
1090
1091 func lineOffset(_ number: Int) -> Int {
1092 var line = 0
1093 for _ in 1..<max(1, number) {
1094 let end = lineEnd(line)
1095 guard end < length, end + 1 < length else { break }
1096 line = end + 1
1097 }
1098 return line
1099 }
1100
1101 /// The number, from 1, of the line holding `i`.
1102 func lineNumber(_ i: Int) -> Int {
1103 var count = 1
1104 var p = 0
1105 while p < min(i, length) {
1106 if char(p) == 10 { count += 1 }
1107 p += 1
1108 }
1109 return count
1110 }
1111
1112 func lineCount(_ r: Range<Int>) -> Int {
1113 substring(r).filter { $0 == "\n" }.count + (r.upperBound > 0 && char(r.upperBound - 1) == 10 ? 0 : 1)
1114 }
1115
1116 func wordForward(_ c: Int, bigWord: Bool) -> Int {
1117 var p = c
1118 let k = kind(p, bigWord: bigWord)
1119 if k != 0 { while p < length, kind(p, bigWord: bigWord) == k { p += 1 } }
1120 // Skip blanks; an empty line is a word.
1121 while p < length, kind(p, bigWord: bigWord) == 0 {
1122 if char(p) == 10, p > c, char(p - 1) == 10 { return p }
1123 if char(p) == 10, p + 1 < length, char(p + 1) == 10, p + 1 > c { return p + 1 }
1124 p += 1
1125 }
1126 return min(p, length)
1127 }
1128
1129 func wordBackward(_ c: Int, bigWord: Bool, stopAtLine: Bool) -> Int {
1130 var p = c
1131 guard p > 0 else { return 0 }
1132 p -= 1
1133 while p > 0, kind(p, bigWord: bigWord) == 0 {
1134 if char(p) == 10, char(p - 1) == 10, !stopAtLine { return p }
1135 if stopAtLine, char(p) == 10 { return p + 1 }
1136 p -= 1
1137 }
1138 let k = kind(p, bigWord: bigWord)
1139 while p > 0, kind(p - 1, bigWord: bigWord) == k, k != 0 { p -= 1 }
1140 return p
1141 }
1142
1143 func wordEnd(_ c: Int, bigWord: Bool, fromInside: Bool) -> Int {
1144 var p = fromInside ? c : c + 1
1145 while p < length, kind(p, bigWord: bigWord) == 0 { p += 1 }
1146 let k = kind(p, bigWord: bigWord)
1147 while p + 1 < length, kind(p + 1, bigWord: bigWord) == k, k != 0 { p += 1 }
1148 return min(p, max(0, length - 1))
1149 }
1150
1151 func wordEndBackward(_ c: Int, bigWord: Bool) -> Int {
1152 var p = c
1153 let k = kind(p, bigWord: bigWord)
1154 while p > 0, kind(p, bigWord: bigWord) == k, k != 0 { p -= 1 }
1155 while p > 0, kind(p, bigWord: bigWord) == 0 { p -= 1 }
1156 return p
1157 }
1158
1159 func wordStart(_ c: Int) -> Int {
1160 var p = c
1161 while p > 0, let ch = char(p - 1), Self.isWord(ch) { p -= 1 }
1162 return p
1163 }
1164
1165 func wordAt(_ c: Int) -> String? {
1166 var p = c
1167 while p < lineEnd(c), let ch = char(p), !Self.isWord(ch) { p += 1 }
1168 guard let ch = char(p), Self.isWord(ch) else { return nil }
1169 let start = wordStart(p)
1170 var end = p
1171 while end < length, let e = char(end), Self.isWord(e) { end += 1 }
1172 return substring(start..<end)
1173 }
1174
1175 func matchingBracket(_ c: Int) -> Int? {
1176 let pairs: [unichar: (unichar, Bool)] = [40: (41, true), 91: (93, true), 123: (125, true), 41: (40, false), 93: (91, false), 125: (123, false)]
1177 var p = c
1178 let end = lineEnd(c)
1179 while p < end, let ch = char(p), pairs[ch] == nil { p += 1 }
1180 guard let open = char(p), let (close, forward) = pairs[open] else { return nil }
1181 var depth = 0
1182 var q = p
1183 while q >= 0, q < length {
1184 if char(q) == open { depth += 1 }
1185 if char(q) == close {
1186 depth -= 1
1187 if depth == 0 { return q }
1188 }
1189 q += forward ? 1 : -1
1190 }
1191 return nil
1192 }
1193
1194 func paragraph(_ c: Int, forward: Bool) -> Int {
1195 func blank(_ line: Int) -> Bool { substring(line..<lineEnd(line)).allSatisfy { $0 == " " || $0 == "\t" } }
1196 var line = lineStart(c)
1197 if forward {
1198 while lineEnd(line) < length, blank(line) { line = lineEnd(line) + 1 }
1199 while lineEnd(line) < length {
1200 line = lineEnd(line) + 1
1201 if blank(line) { return line }
1202 }
1203 return max(0, length - (length > 0 && char(length - 1) == 10 ? 1 : 0))
1204 }
1205 while line > 0, blank(line) { line = lineStart(line - 1) }
1206 while line > 0 {
1207 line = lineStart(line - 1)
1208 if blank(line) { return line }
1209 }
1210 return 0
1211 }
1212
1213 func search(_ pattern: String, from c: Int, forward: Bool, count: Int) -> Int? {
1214 guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }
1215 let all = regex.matches(in: ns as String, range: NSRange(location: 0, length: length)).map(\.range.location)
1216 guard !all.isEmpty else { return nil }
1217 var p = c
1218 for _ in 0..<count {
1219 if forward {
1220 p = all.first { $0 > p } ?? all[0]
1221 } else {
1222 p = all.last { $0 < p } ?? all[all.count - 1]
1223 }
1224 }
1225 return p
1226 }
1227
1228 func wordObject(_ c: Int, inner: Bool, bigWord: Bool, count: Int) -> Range<Int>? {
1229 guard c < length else { return nil }
1230 var start = c
1231 var end = c
1232 let k = kind(c, bigWord: bigWord)
1233 let lineS = lineStart(c)
1234 let lineE = lineEnd(c)
1235 while start > lineS, kind(start - 1, bigWord: bigWord) == k, char(start - 1) != 10 { start -= 1 }
1236 while end < lineE, kind(end, bigWord: bigWord) == k { end += 1 }
1237 if inner {
1238 for _ in 1..<max(1, count) {
1239 let k2 = kind(end, bigWord: bigWord)
1240 while end < lineE, kind(end, bigWord: bigWord) == k2 { end += 1 }
1241 }
1242 return start..<end
1243 }
1244 for _ in 1..<max(1, count) {
1245 while end < lineE, let ch = char(end), Self.isBlank(ch) { end += 1 }
1246 let k2 = kind(end, bigWord: bigWord)
1247 while end < lineE, kind(end, bigWord: bigWord) == k2 { end += 1 }
1248 }
1249 if k == 0 {
1250 let k2 = kind(end, bigWord: bigWord)
1251 while end < lineE, kind(end, bigWord: bigWord) == k2 { end += 1 }
1252 return start..<end
1253 }
1254 var trailing = end
1255 while trailing < lineE, let ch = char(trailing), Self.isBlank(ch) { trailing += 1 }
1256 if trailing > end { return start..<trailing }
1257 while start > lineS, let ch = char(start - 1), Self.isBlank(ch) { start -= 1 }
1258 return start..<end
1259 }
1260
1261 func quoteObject(_ c: Int, quote: unichar, inner: Bool) -> Range<Int>? {
1262 let s = lineStart(c)
1263 let e = lineEnd(c)
1264 var positions: [Int] = []
1265 var p = s
1266 while p < e {
1267 if char(p) == quote, p == s || char(p - 1) != 92 { positions.append(p) }
1268 p += 1
1269 }
1270 var pair: (Int, Int)?
1271 var i = 0
1272 while i + 1 < positions.count {
1273 if positions[i] <= c, c <= positions[i + 1] { pair = (positions[i], positions[i + 1]); break }
1274 i += 2
1275 }
1276 guard let (open, close) = pair else { return nil }
1277 if inner { return (open + 1)..<close }
1278 var end = close + 1
1279 while end < e, let ch = char(end), Self.isBlank(ch) { end += 1 }
1280 var start = open
1281 if end == close + 1 { while start > s, let ch = char(start - 1), Self.isBlank(ch) { start -= 1 } }
1282 return start..<end
1283 }
1284
1285 func bracketObject(_ c: Int, open: unichar, close: unichar, inner: Bool) -> Range<Int>? {
1286 var depth = 0
1287 var p = c
1288 var start: Int?
1289 if char(c) == open {
1290 start = c
1291 } else {
1292 if char(c) == close { p = c - 1 }
1293 while p >= 0 {
1294 if char(p) == close { depth += 1 }
1295 if char(p) == open {
1296 if depth == 0 { start = p; break }
1297 depth -= 1
1298 }
1299 p -= 1
1300 }
1301 }
1302 if start == nil {
1303 var f = c
1304 while f < lineEnd(c), char(f) != open { f += 1 }
1305 if f < lineEnd(c) { start = f }
1306 }
1307 guard let s = start else { return nil }
1308 depth = 0
1309 var q = s
1310 var end: Int?
1311 while q < length {
1312 if char(q) == open { depth += 1 }
1313 if char(q) == close {
1314 depth -= 1
1315 if depth == 0 { end = q; break }
1316 }
1317 q += 1
1318 }
1319 guard let e = end else { return nil }
1320 if !inner { return s..<(e + 1) }
1321 var innerStart = s + 1
1322 var innerEnd = e
1323 // A block that starts and ends on its own lines keeps them.
1324 if char(innerStart) == 10, lineStart(innerEnd) == innerEnd || substring(lineStart(innerEnd)..<innerEnd).allSatisfy({ $0 == " " || $0 == "\t" }) {
1325 innerStart += 1
1326 innerEnd = lineStart(innerEnd)
1327 }
1328 return innerStart..<innerEnd
1329 }
1330
1331 func paragraphObject(_ c: Int, inner: Bool) -> Range<Int>? {
1332 func blank(_ line: Int) -> Bool { substring(line..<lineEnd(line)).allSatisfy { $0 == " " || $0 == "\t" } }
1333 var start = lineStart(c)
1334 let isBlank = blank(start)
1335 while start > 0, blank(lineStart(start - 1)) == isBlank { start = lineStart(start - 1) }
1336 var end = lineEnd(c)
1337 while end < length, end + 1 <= length, end + 1 < length || !isBlank, blank(end + 1) == isBlank, end + 1 < length {
1338 end = lineEnd(end + 1)
1339 }
1340 var upper = min(length, end + 1)
1341 if !inner {
1342 var after = upper
1343 while after < length, blank(after) {
1344 let next = lineEnd(after)
1345 after = min(length, next + 1)
1346 if next >= length { break }
1347 }
1348 upper = after
1349 }
1350 return start..<upper
1351 }
1352 }
1353}