Sources/OrgCore/Keymap/Vim.swift
1350 lines · 65738 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].key + keys[2].key
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 and
684 /// `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 var pattern = NSRegularExpression.escapedPattern(for: last.chars)
696 if blankKeys, let ch = buffer.char(start), Buffer.isBlank(ch) { pattern += "[^ \t]" }
697 let regex = try! NSRegularExpression(pattern: pattern, options: last.chars == last.chars.lowercased() ? .caseInsensitive : [])
698 var match: NSRange?
699 if forward, start <= bound {
700 var from = start
701 for _ in 0..<n {
702 match = regex.firstMatch(in: buffer.ns as String, range: NSRange(location: from, length: bound - from))?.range
703 guard let found = match else { break }
704 from = NSMaxRange(found)
705 }
706 } else if !forward {
707 var from = start
708 for _ in 0..<n {
709 match = nil
710 var p = from - 1
711 while p >= bound, match == nil {
712 match = regex.firstMatch(in: buffer.ns as String, options: .anchored, range: NSRange(location: p, length: from - p))?.range
713 p -= 1
714 }
715 guard let found = match else { break }
716 from = found.location
717 }
718 }
719 guard let found = match else {
720 if repeating { snipeRepeatKeys = last.keys }
721 return .invalid
722 }
723 if !operatorPending { snipeRepeatKeys = last.keys }
724 var end = NSMaxRange(found)
725 var length = found.length
726 if blankKeys {
727 end -= 1
728 length -= 1
729 }
730 let visual = mode == .visual || mode == .visualLine || mode == .visualBlock
731 var p: Int
732 if !forward {
733 p = last.consume ? found.location : end
734 } else {
735 p = visual ? (last.consume ? end : found.location) : operatorPending ? end : found.location
736 if last.consume {
737 if visual { p -= 1 }
738 } else {
739 p -= length
740 if length > 1, !operatorPending { p += 1 }
741 }
742 }
743 return .moved(p, .exclusive)
744 }
745
746 // MARK: - Text objects
747
748 func textObject(_ inner: Bool, _ key: KeyChord, _ buffer: Buffer, _ c: Int, count: Int) -> (range: Range<Int>, linewise: Bool)? {
749 switch key.key {
750 case "w", "W":
751 return buffer.wordObject(c, inner: inner, bigWord: key.key == "W", count: count).map { ($0, false) }
752 case "\"", "'", "`":
753 return buffer.quoteObject(c, quote: key.key.utf16.first!, inner: inner).map { ($0, false) }
754 case "(", ")", "b":
755 return buffer.bracketObject(c, open: 40, close: 41, inner: inner).map { ($0, false) }
756 case "[", "]":
757 return buffer.bracketObject(c, open: 91, close: 93, inner: inner).map { ($0, false) }
758 case "{", "}", "B":
759 return buffer.bracketObject(c, open: 123, close: 125, inner: inner).map { ($0, false) }
760 case "<", ">":
761 return buffer.bracketObject(c, open: 60, close: 62, inner: inner).map { ($0, false) }
762 case "p":
763 return buffer.paragraphObject(c, inner: inner).map { ($0, true) }
764 case "e", "E", "r", "R":
765 return Self.orgObject(key.key, inner: inner, buffer, c)
766 default:
767 return nil
768 }
769 }
770
771 // MARK: - Applying
772
773 func visualRange(_ buffer: Buffer, _ caret: Int) -> (range: Range<Int>, linewise: Bool) {
774 let a = min(anchor, caret)
775 let b = max(anchor, caret)
776 if mode == .visualLine {
777 let end = buffer.lineEnd(b)
778 return (buffer.lineStart(a)..<min(buffer.length, end + 1), true)
779 }
780 return (a..<min(buffer.length, b + 1), false)
781 }
782
783 func visualActions(_ buffer: Buffer, _ caret: Int) -> [Action] {
784 let a = min(anchor, caret)
785 let b = max(anchor, caret)
786 let range = mode == .visualLine ? buffer.lineStart(a)..<buffer.lineEnd(b) : a..<min(buffer.length, b + 1)
787 return [.move(caret: caret), .select(range)]
788 }
789
790 mutating func store(_ text: String, linewise: Bool, register: Character?, yank: Bool) -> [Action] {
791 if register == "_" { return [] }
792 if let register, register.isLetter {
793 let key = Character(register.lowercased())
794 if register.isUppercase, let existing = registers[key] {
795 registers[key] = (existing.text + text, existing.linewise || linewise)
796 } else {
797 registers[key] = (text, linewise)
798 }
799 return []
800 }
801 if yank { registers["0"] = (text, linewise) }
802 clipboardRecord = (text, linewise)
803 return [.setClipboard(text)]
804 }
805
806 func paste(_ register: Character?, _ context: Context) -> (String, Bool)? {
807 if let register, register != "\"", register != "+", register != "*" {
808 return registers[Character(register.lowercased())]
809 }
810 guard let clip = context.clipboard ?? clipboardRecord?.text else { return nil }
811 if let record = clipboardRecord, record.text == clip { return (clip, record.linewise) }
812 return (clip, false)
813 }
814
815 mutating func change(_ edits: [TextEdit], caret: Int, keys: [KeyChord]) -> [Action] {
816 lastChange = (keys, nil)
817 return [.edit(edits, caret: caret)]
818 }
819
820 mutating func apply(_ op: String, range: Range<Int>, linewise: Bool, _ buffer: Buffer, register: Character?, keys: [KeyChord], count: Int) -> [Action] {
821 let wasVisual = mode == .visual || mode == .visualLine
822 if wasVisual { lastVisual = (anchor, caretForApply, mode) }
823 let context0Column = max(0, caretForApply - buffer.lineStart(caretForApply))
824 mode = .normal
825 let text = buffer.substring(range)
826 switch op {
827 case "y":
828 var actions = store(text, linewise: linewise, register: register, yank: true)
829 let caret = linewise && !wasVisual ? min(range.lowerBound, buffer.length) : range.lowerBound
830 actions.append(.move(caret: linewise && wasVisual ? buffer.lineStart(range.lowerBound) : caret))
831 return actions
832 case "d":
833 var actions = store(text, linewise: linewise, register: register, yank: false)
834 var deletion = range
835 if linewise, range.upperBound >= buffer.length, range.lowerBound > 0, buffer.char(range.upperBound - 1) != 10 || range.upperBound == buffer.length {
836 // The last line takes the newline before it.
837 if buffer.char(range.lowerBound - 1) == 10, range.upperBound == buffer.length, !(text.hasSuffix("\n")) {
838 deletion = (range.lowerBound - 1)..<range.upperBound
839 }
840 }
841 let after = buffer.removing(deletion)
842 var caret = deletion.lowerBound
843 if linewise {
844 let column = context0Column
845 let line = after.lineStart(min(deletion.lowerBound, after.length))
846 let length = after.lineEnd(line) - line
847 caret = line + min(column, max(0, length - 1))
848 } else {
849 caret = after.clampNormal(caret)
850 }
851 lastChange = (wasVisual ? [] : keys, nil)
852 actions.insert(.edit([TextEdit(range: deletion, replacement: "")], caret: caret), at: 0)
853 return actions
854 case "c":
855 var actions = store(text, linewise: linewise, register: register, yank: false)
856 var deletion = range
857 var replacement = ""
858 if linewise {
859 // cc keeps the newline and indents as the line before it calls for.
860 replacement = range.lowerBound == 0 ? "" : Self.indentation(for: buffer, line: range.lowerBound - 1)
861 if buffer.char(range.upperBound - 1) == 10 { deletion = range.lowerBound..<(range.upperBound - 1) }
862 }
863 let caret = deletion.lowerBound + replacement.utf16.count
864 actions.insert(.edit([TextEdit(range: deletion, replacement: replacement)], caret: caret), at: 0)
865 enterInsert(at: caret, keys: wasVisual ? [] : keys)
866 return actions
867 case ">", "<":
868 let start = buffer.lineStart(range.lowerBound)
869 var edits: [TextEdit] = []
870 var line = start
871 let end = linewise ? range.upperBound : max(range.upperBound, buffer.lineEnd(max(range.lowerBound, range.upperBound - 1)))
872 while line < end || (line == start && line <= buffer.length) {
873 let lineEnd = buffer.lineEnd(line)
874 if lineEnd > line {
875 if op == ">" {
876 edits.append(TextEdit(range: line..<line, replacement: String(repeating: " ", count: Self.shiftWidth)))
877 } else {
878 let indent = buffer.substring(line..<buffer.firstNonBlankOrEnd(line))
879 let remove = min(Self.shiftWidth, indent.count)
880 if remove > 0 { edits.append(TextEdit(range: line..<(line + remove), replacement: "")) }
881 }
882 }
883 guard lineEnd < buffer.length else { break }
884 line = lineEnd + 1
885 if line >= end { break }
886 }
887 lastChange = (wasVisual ? [] : keys, nil)
888 return [.edit(edits, caret: start)]
889 case "gc":
890 lastChange = (wasVisual ? [] : keys, nil)
891 return [.edit(Self.toggleComments(buffer, range), caret: buffer.lineStart(range.lowerBound))]
892 case "gq", "gw":
893 // Whole lines, as evil's fill operators take them.
894 lastChange = (wasVisual ? [] : keys, nil)
895 let end = buffer.lineEnd(max(range.lowerBound, range.upperBound - 1))
896 let lines = buffer.lineStart(range.lowerBound)..<min(buffer.length, end + 1)
897 return [.fill(lines, keep: op == "gw" ? caretForApply : nil)]
898 default:
899 let transformed: String
900 switch op {
901 case "gu": transformed = text.lowercased()
902 case "gU": transformed = text.uppercased()
903 default: transformed = String(text.map { $0.isUppercase ? Character($0.lowercased()) : Character($0.uppercased()) })
904 }
905 lastChange = (wasVisual ? [] : keys, nil)
906 return [.edit([TextEdit(range: range, replacement: transformed)], caret: range.lowerBound)]
907 }
908 }
909
910 mutating func enterInsert(_ key: String, _ buffer: Buffer, _ caret: Int, keys: [KeyChord]) -> [Action] {
911 switch key {
912 case "i":
913 enterInsert(at: caret, keys: keys)
914 return [.move(caret: caret)]
915 case "a":
916 let p = buffer.char(caret).map { $0 != 10 } == true ? caret + 1 : caret
917 enterInsert(at: p, keys: keys)
918 return [.move(caret: p)]
919 case "I":
920 let p = buffer.firstNonBlank(buffer.lineStart(caret))
921 enterInsert(at: p, keys: keys)
922 return [.move(caret: p)]
923 case "A":
924 let p = buffer.lineEnd(caret)
925 enterInsert(at: p, keys: keys)
926 return [.move(caret: p)]
927 case "o":
928 let end = buffer.lineEnd(caret)
929 let indent = Self.indentation(for: buffer, line: caret)
930 enterInsert(at: end + 1 + indent.utf16.count, keys: keys)
931 return [.edit([TextEdit(range: end..<end, replacement: "\n" + indent)], caret: end + 1 + indent.utf16.count)]
932 default:
933 let start = buffer.lineStart(caret)
934 let indent = Self.indentation(for: buffer, line: max(0, start - 1)) == "" || start == 0 ? "" : Self.indentation(for: buffer, line: start - 1)
935 enterInsert(at: start + indent.utf16.count, keys: keys)
936 return [.edit([TextEdit(range: start..<start, replacement: indent + "\n")], caret: start + indent.utf16.count)]
937 }
938 }
939
940 mutating func join(_ buffer: Buffer, at caret: Int, lines: Int, keys: [KeyChord]) -> [Action] {
941 var edits: [TextEdit] = []
942 var line = buffer.lineStart(caret)
943 var position = caret
944 for _ in 1..<lines {
945 let end = buffer.lineEnd(line)
946 guard end < buffer.length else { break }
947 let nextStart = end + 1
948 let nextText = buffer.firstNonBlankOrEnd(nextStart)
949 let joinsEmpty = buffer.lineEnd(nextStart) == nextText
950 let separator = joinsEmpty || buffer.char(nextText) == 41 ? "" : (end > line && buffer.char(end - 1) == 32 ? "" : " ")
951 var from = end
952 while from > line, let c = buffer.char(from - 1), c == 32 || c == 9 { from -= 1 }
953 if !separator.isEmpty || from == end { from = end }
954 edits.append(TextEdit(range: from..<nextText, replacement: separator))
955 position = from
956 line = nextStart
957 }
958 guard !edits.isEmpty else { return [] }
959 lastChange = (keys, nil)
960 // Positions in edits refer to the original text; apply back to front.
961 let shift = edits.dropLast().reduce(0) { $0 + $1.replacement.utf16.count - $1.range.count }
962 return [.edit(edits, caret: position + shift)]
963 }
964
965 mutating func repeatChange(_ count: Int, _ context: Context) -> [Action] {
966 guard let change = lastChange, !change.keys.isEmpty else { return [] }
967 var copy = self
968 copy.lastChange = nil
969 copy.pending = []
970 var keys = change.keys
971 if count > 1, let (_, _, rest) = Self.prefix(keys) {
972 keys = Array("\(count)".map { KeyChord(String($0)) }) + Array(rest)
973 }
974 var actions: [Action] = []
975 var text = context.text
976 var caret = context.caret
977 for key in keys {
978 if copy.mode == .insert, key == KeyChord("ESC") {
979 if let inserted = change.inserted, !inserted.isEmpty {
980 let edit = TextEdit(range: caret..<caret, replacement: inserted)
981 actions.append(.edit([edit], caret: caret + inserted.utf16.count))
982 text = edit.apply(to: text)
983 caret += inserted.utf16.count
984 }
985 }
986 let produced = copy.feed(key, Context(text: text, caret: caret, clipboard: context.clipboard, halfPage: context.halfPage, visible: context.visible, org: context.org))
987 for action in produced {
988 switch action {
989 case .edit(let edits, let to):
990 var new = text
991 for edit in edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { new = edit.apply(to: new) }
992 text = new
993 caret = to
994 case .move(let to):
995 caret = to
996 default:
997 break
998 }
999 actions.append(action)
1000 }
1001 }
1002 let preserved = (registers, clipboardRecord)
1003 self = copy
1004 registers = preserved.0
1005 clipboardRecord = copy.clipboardRecord ?? preserved.1
1006 lastChange = change
1007 return actions
1008 }
1009
1010 /// `/pattern` or `?pattern` from the prompt: the caret's new place.
1011 public mutating func search(_ pattern: String, forward: Bool, _ context: Context) -> [Action] {
1012 lastSearch = (pattern, forward)
1013 let buffer = Buffer(context)
1014 guard let p = buffer.search(pattern, from: context.caret, forward: forward, count: 1) else { return [.message("Search failed: \(pattern)")] }
1015 if mode == .visual || mode == .visualLine { return visualActions(buffer, p) }
1016 return [.jumped(from: context.caret), .move(caret: p)]
1017 }
1018
1019 /// Leave a visual state without acting, as `ESC` does.
1020 public mutating func reset() {
1021 pending = []
1022 if mode == .visual || mode == .visualLine || mode == .visualBlock { mode = .normal }
1023 }
1024
1025 // MARK: - Text
1026
1027 struct Buffer {
1028 let ns: NSString
1029 var length: Int { ns.length }
1030
1031 init(_ context: Context) { ns = context.text as NSString }
1032 init(_ text: String) { ns = text as NSString }
1033
1034 func char(_ i: Int) -> unichar? { i >= 0 && i < ns.length ? ns.character(at: i) : nil }
1035 func substring(_ r: Range<Int>) -> String { ns.substring(with: NSRange(location: r.lowerBound, length: r.count)) }
1036 func lineStart(_ i: Int) -> Int { ns.lineRange(for: NSRange(location: min(i, ns.length), length: 0)).location }
1037 func lineEnd(_ i: Int) -> Int {
1038 var end = 0
1039 ns.getLineStart(nil, end: nil, contentsEnd: &end, for: NSRange(location: min(i, ns.length), length: 0))
1040 return end
1041 }
1042 func removing(_ r: Range<Int>) -> Buffer { Buffer(ns.replacingCharacters(in: NSRange(location: r.lowerBound, length: r.count), with: "")) }
1043 func applying(_ edits: [TextEdit]) -> Buffer {
1044 var text = ns as String
1045 for edit in edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { text = edit.apply(to: text) }
1046 return Buffer(text)
1047 }
1048
1049 static func isBlank(_ c: unichar) -> Bool { c == 32 || c == 9 }
1050 static func isSpace(_ c: unichar) -> Bool { c == 32 || c == 9 || c == 10 || c == 13 }
1051 static func isWord(_ c: unichar) -> Bool {
1052 guard let scalar = Unicode.Scalar(c) else { return true }
1053 return CharacterSet.alphanumerics.contains(scalar) || c == 95
1054 }
1055
1056 /// 0 blank, 1 word, 2 other.
1057 func kind(_ i: Int, bigWord: Bool) -> Int {
1058 guard let c = char(i) else { return 0 }
1059 if Self.isSpace(c) { return 0 }
1060 if bigWord { return 1 }
1061 return Self.isWord(c) ? 1 : 2
1062 }
1063
1064 func clampNormal(_ i: Int) -> Int { clampNormal(i, in: lineStart(i)) }
1065 func clampNormal(_ i: Int, in line: Int) -> Int {
1066 let end = lineEnd(line)
1067 return max(line, min(i, end > line ? end - 1 : line))
1068 }
1069
1070 func firstNonBlank(_ line: Int) -> Int {
1071 var p = line
1072 let end = lineEnd(line)
1073 while p < end, let c = char(p), Self.isBlank(c) { p += 1 }
1074 return p == end && end > line ? end - 1 : (p == end ? line : p)
1075 }
1076
1077 func firstNonBlankOrEnd(_ line: Int) -> Int {
1078 var p = line
1079 let end = lineEnd(line)
1080 while p < end, let c = char(p), Self.isBlank(c) { p += 1 }
1081 return p
1082 }
1083
1084 func firstNonBlankIn(_ inserted: String, at start: Int) -> Int {
1085 start + inserted.prefix { $0 == " " || $0 == "\t" }.utf16.count
1086 }
1087
1088 func lineOffset(_ number: Int) -> Int {
1089 var line = 0
1090 for _ in 1..<max(1, number) {
1091 let end = lineEnd(line)
1092 guard end < length, end + 1 < length else { break }
1093 line = end + 1
1094 }
1095 return line
1096 }
1097
1098 /// The number, from 1, of the line holding `i`.
1099 func lineNumber(_ i: Int) -> Int {
1100 var count = 1
1101 var p = 0
1102 while p < min(i, length) {
1103 if char(p) == 10 { count += 1 }
1104 p += 1
1105 }
1106 return count
1107 }
1108
1109 func lineCount(_ r: Range<Int>) -> Int {
1110 substring(r).filter { $0 == "\n" }.count + (r.upperBound > 0 && char(r.upperBound - 1) == 10 ? 0 : 1)
1111 }
1112
1113 func wordForward(_ c: Int, bigWord: Bool) -> Int {
1114 var p = c
1115 let k = kind(p, bigWord: bigWord)
1116 if k != 0 { while p < length, kind(p, bigWord: bigWord) == k { p += 1 } }
1117 // Skip blanks; an empty line is a word.
1118 while p < length, kind(p, bigWord: bigWord) == 0 {
1119 if char(p) == 10, p > c, char(p - 1) == 10 { return p }
1120 if char(p) == 10, p + 1 < length, char(p + 1) == 10, p + 1 > c { return p + 1 }
1121 p += 1
1122 }
1123 return min(p, length)
1124 }
1125
1126 func wordBackward(_ c: Int, bigWord: Bool, stopAtLine: Bool) -> Int {
1127 var p = c
1128 guard p > 0 else { return 0 }
1129 p -= 1
1130 while p > 0, kind(p, bigWord: bigWord) == 0 {
1131 if char(p) == 10, char(p - 1) == 10, !stopAtLine { return p }
1132 if stopAtLine, char(p) == 10 { return p + 1 }
1133 p -= 1
1134 }
1135 let k = kind(p, bigWord: bigWord)
1136 while p > 0, kind(p - 1, bigWord: bigWord) == k, k != 0 { p -= 1 }
1137 return p
1138 }
1139
1140 func wordEnd(_ c: Int, bigWord: Bool, fromInside: Bool) -> Int {
1141 var p = fromInside ? c : c + 1
1142 while p < length, kind(p, bigWord: bigWord) == 0 { p += 1 }
1143 let k = kind(p, bigWord: bigWord)
1144 while p + 1 < length, kind(p + 1, bigWord: bigWord) == k, k != 0 { p += 1 }
1145 return min(p, max(0, length - 1))
1146 }
1147
1148 func wordEndBackward(_ c: Int, bigWord: Bool) -> Int {
1149 var p = c
1150 let k = kind(p, bigWord: bigWord)
1151 while p > 0, kind(p, bigWord: bigWord) == k, k != 0 { p -= 1 }
1152 while p > 0, kind(p, bigWord: bigWord) == 0 { p -= 1 }
1153 return p
1154 }
1155
1156 func wordStart(_ c: Int) -> Int {
1157 var p = c
1158 while p > 0, let ch = char(p - 1), Self.isWord(ch) { p -= 1 }
1159 return p
1160 }
1161
1162 func wordAt(_ c: Int) -> String? {
1163 var p = c
1164 while p < lineEnd(c), let ch = char(p), !Self.isWord(ch) { p += 1 }
1165 guard let ch = char(p), Self.isWord(ch) else { return nil }
1166 let start = wordStart(p)
1167 var end = p
1168 while end < length, let e = char(end), Self.isWord(e) { end += 1 }
1169 return substring(start..<end)
1170 }
1171
1172 func matchingBracket(_ c: Int) -> Int? {
1173 let pairs: [unichar: (unichar, Bool)] = [40: (41, true), 91: (93, true), 123: (125, true), 41: (40, false), 93: (91, false), 125: (123, false)]
1174 var p = c
1175 let end = lineEnd(c)
1176 while p < end, let ch = char(p), pairs[ch] == nil { p += 1 }
1177 guard let open = char(p), let (close, forward) = pairs[open] else { return nil }
1178 var depth = 0
1179 var q = p
1180 while q >= 0, q < length {
1181 if char(q) == open { depth += 1 }
1182 if char(q) == close {
1183 depth -= 1
1184 if depth == 0 { return q }
1185 }
1186 q += forward ? 1 : -1
1187 }
1188 return nil
1189 }
1190
1191 func paragraph(_ c: Int, forward: Bool) -> Int {
1192 func blank(_ line: Int) -> Bool { substring(line..<lineEnd(line)).allSatisfy { $0 == " " || $0 == "\t" } }
1193 var line = lineStart(c)
1194 if forward {
1195 while lineEnd(line) < length, blank(line) { line = lineEnd(line) + 1 }
1196 while lineEnd(line) < length {
1197 line = lineEnd(line) + 1
1198 if blank(line) { return line }
1199 }
1200 return max(0, length - (length > 0 && char(length - 1) == 10 ? 1 : 0))
1201 }
1202 while line > 0, blank(line) { line = lineStart(line - 1) }
1203 while line > 0 {
1204 line = lineStart(line - 1)
1205 if blank(line) { return line }
1206 }
1207 return 0
1208 }
1209
1210 func search(_ pattern: String, from c: Int, forward: Bool, count: Int) -> Int? {
1211 guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }
1212 let all = regex.matches(in: ns as String, range: NSRange(location: 0, length: length)).map(\.range.location)
1213 guard !all.isEmpty else { return nil }
1214 var p = c
1215 for _ in 0..<count {
1216 if forward {
1217 p = all.first { $0 > p } ?? all[0]
1218 } else {
1219 p = all.last { $0 < p } ?? all[all.count - 1]
1220 }
1221 }
1222 return p
1223 }
1224
1225 func wordObject(_ c: Int, inner: Bool, bigWord: Bool, count: Int) -> Range<Int>? {
1226 guard c < length else { return nil }
1227 var start = c
1228 var end = c
1229 let k = kind(c, bigWord: bigWord)
1230 let lineS = lineStart(c)
1231 let lineE = lineEnd(c)
1232 while start > lineS, kind(start - 1, bigWord: bigWord) == k, char(start - 1) != 10 { start -= 1 }
1233 while end < lineE, kind(end, bigWord: bigWord) == k { end += 1 }
1234 if inner {
1235 for _ in 1..<max(1, count) {
1236 let k2 = kind(end, bigWord: bigWord)
1237 while end < lineE, kind(end, bigWord: bigWord) == k2 { end += 1 }
1238 }
1239 return start..<end
1240 }
1241 for _ in 1..<max(1, count) {
1242 while end < lineE, let ch = char(end), Self.isBlank(ch) { end += 1 }
1243 let k2 = kind(end, bigWord: bigWord)
1244 while end < lineE, kind(end, bigWord: bigWord) == k2 { end += 1 }
1245 }
1246 if k == 0 {
1247 let k2 = kind(end, bigWord: bigWord)
1248 while end < lineE, kind(end, bigWord: bigWord) == k2 { end += 1 }
1249 return start..<end
1250 }
1251 var trailing = end
1252 while trailing < lineE, let ch = char(trailing), Self.isBlank(ch) { trailing += 1 }
1253 if trailing > end { return start..<trailing }
1254 while start > lineS, let ch = char(start - 1), Self.isBlank(ch) { start -= 1 }
1255 return start..<end
1256 }
1257
1258 func quoteObject(_ c: Int, quote: unichar, inner: Bool) -> Range<Int>? {
1259 let s = lineStart(c)
1260 let e = lineEnd(c)
1261 var positions: [Int] = []
1262 var p = s
1263 while p < e {
1264 if char(p) == quote, p == s || char(p - 1) != 92 { positions.append(p) }
1265 p += 1
1266 }
1267 var pair: (Int, Int)?
1268 var i = 0
1269 while i + 1 < positions.count {
1270 if positions[i] <= c, c <= positions[i + 1] { pair = (positions[i], positions[i + 1]); break }
1271 i += 2
1272 }
1273 guard let (open, close) = pair else { return nil }
1274 if inner { return (open + 1)..<close }
1275 var end = close + 1
1276 while end < e, let ch = char(end), Self.isBlank(ch) { end += 1 }
1277 var start = open
1278 if end == close + 1 { while start > s, let ch = char(start - 1), Self.isBlank(ch) { start -= 1 } }
1279 return start..<end
1280 }
1281
1282 func bracketObject(_ c: Int, open: unichar, close: unichar, inner: Bool) -> Range<Int>? {
1283 var depth = 0
1284 var p = c
1285 var start: Int?
1286 if char(c) == open {
1287 start = c
1288 } else {
1289 if char(c) == close { p = c - 1 }
1290 while p >= 0 {
1291 if char(p) == close { depth += 1 }
1292 if char(p) == open {
1293 if depth == 0 { start = p; break }
1294 depth -= 1
1295 }
1296 p -= 1
1297 }
1298 }
1299 if start == nil {
1300 var f = c
1301 while f < lineEnd(c), char(f) != open { f += 1 }
1302 if f < lineEnd(c) { start = f }
1303 }
1304 guard let s = start else { return nil }
1305 depth = 0
1306 var q = s
1307 var end: Int?
1308 while q < length {
1309 if char(q) == open { depth += 1 }
1310 if char(q) == close {
1311 depth -= 1
1312 if depth == 0 { end = q; break }
1313 }
1314 q += 1
1315 }
1316 guard let e = end else { return nil }
1317 if !inner { return s..<(e + 1) }
1318 var innerStart = s + 1
1319 var innerEnd = e
1320 // A block that starts and ends on its own lines keeps them.
1321 if char(innerStart) == 10, lineStart(innerEnd) == innerEnd || substring(lineStart(innerEnd)..<innerEnd).allSatisfy({ $0 == " " || $0 == "\t" }) {
1322 innerStart += 1
1323 innerEnd = lineStart(innerEnd)
1324 }
1325 return innerStart..<innerEnd
1326 }
1327
1328 func paragraphObject(_ c: Int, inner: Bool) -> Range<Int>? {
1329 func blank(_ line: Int) -> Bool { substring(line..<lineEnd(line)).allSatisfy { $0 == " " || $0 == "\t" } }
1330 var start = lineStart(c)
1331 let isBlank = blank(start)
1332 while start > 0, blank(lineStart(start - 1)) == isBlank { start = lineStart(start - 1) }
1333 var end = lineEnd(c)
1334 while end < length, end + 1 <= length, end + 1 < length || !isBlank, blank(end + 1) == isBlank, end + 1 < length {
1335 end = lineEnd(end + 1)
1336 }
1337 var upper = min(length, end + 1)
1338 if !inner {
1339 var after = upper
1340 while after < length, blank(after) {
1341 let next = lineEnd(after)
1342 after = min(length, next + 1)
1343 if next >= length { break }
1344 }
1345 upper = after
1346 }
1347 return start..<upper
1348 }
1349 }
1350}