krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/Keymap/Vim.swift

1350 lines · 65738 bytes

67 symbols in this file
   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}