Sources/OrgCore/Keymap/VimExtras.swift
661 lines · 35081 bytes
1import Foundation
2
3// Doom's evil beyond the basics: marks and the jump list, macros, `gv`, block visual state,
4// evil-surround (`ys`, `cs`, `ds`, visual `S`), commenting (`gc`) and evil-org's text objects
5// (`e` object, `E` element, `r` greater element, `R` subtree).
6
7extension Vim {
8 /// Marks from the editor, over the ones set in this engine.
9 var currentMarks: [Character: Int] { marks.merging(contextMarks) { $1 } }
10
11 /// Motions evil records in the jump list.
12 static func isJump(_ keys: [KeyChord]) -> Bool {
13 guard let key = keys.first, key.modifiers.isEmpty else { return false }
14 if ["G", "%", "{", "}", "n", "N", "*", "#", "'", "`", "H", "M", "L", "s", "S", "f", "F", "t", "T"].contains(key.key) { return true }
15 return key.key == "g" && keys.count > 1 && keys[1].key == "g"
16 }
17
18 /// Keys evil handles here before operators and motions, or nil.
19 mutating func parseExtras(_ rest: ArraySlice<KeyChord>, _ buffer: inout Buffer, _ context: Context, register: Character?, count: Int?, keys: [KeyChord]) -> Parse? {
20 guard let first = rest.first else { return .incomplete }
21 let visual = mode == .visual || mode == .visualLine
22 let n = count ?? 1
23 let caret = context.caret
24 let second = rest.dropFirst().first
25 func needSecond() -> Parse? { second == nil ? .incomplete : nil }
26
27 if first.modifiers == [.control] {
28 switch first.key {
29 case "o" where !visual: return .done([.jumpBack(n)])
30 case "i" where !visual: return .done([.jumpForward(n)])
31 case "v":
32 if visual { lastVisual = (anchor, caret, mode) }
33 mode = .visualBlock
34 if !visual { anchor = caret }
35 return .done(blockActions(buffer, caret))
36 default: return nil
37 }
38 }
39 guard first.modifiers.isEmpty else { return nil }
40
41 if visual, first.key == "S" {
42 if let wait = needSecond() { return wait }
43 let range = visualRange(buffer, caret)
44 lastVisual = (anchor, caret, mode)
45 mode = .normal
46 return .done(surround(range.range, with: second!, linewise: range.linewise, buffer, keys: []))
47 }
48 guard !visual else { return nil }
49
50 switch first.key {
51 case "m":
52 if let wait = needSecond() { return wait }
53 guard let name = second!.key.first, second!.key.count == 1, name.isLetter else { return .invalid }
54 marks[name] = caret
55 return .done([.setMark(name, caret)])
56 case "q":
57 if recording {
58 recording = false
59 return .done([.recordMacro(nil)])
60 }
61 if let wait = needSecond() { return wait }
62 guard let name = second!.key.first, second!.key.count == 1, name.isLetter || name.isNumber else { return .invalid }
63 recording = true
64 return .done([.recordMacro(name)])
65 case "@":
66 if let wait = needSecond() { return wait }
67 let name: Character
68 if second!.key == "@" {
69 guard let last = lastMacro else { return .done([.message("No previous macro")]) }
70 name = last
71 } else {
72 guard let c = second!.key.first, second!.key.count == 1 else { return .invalid }
73 name = c
74 }
75 lastMacro = name
76 return .done([.playMacro(name, n)])
77 case "g":
78 if let wait = needSecond() { return wait }
79 guard second!.key == "v", second!.modifiers.isEmpty else { return nil }
80 guard let last = lastVisual else { return .done([]) }
81 mode = last.mode
82 anchor = min(last.anchor, buffer.length)
83 let to = min(last.caret, buffer.length)
84 return .done(mode == .visualBlock ? blockActions(buffer, to) : visualActions(buffer, to))
85 case "y", "c", "d":
86 guard second?.key == "s", second?.modifiers.isEmpty == true else { return nil }
87 return surroundCommand(first.key, Array(rest.dropFirst(2)), &buffer, caret: caret, count: n, keys: keys)
88 default:
89 return nil
90 }
91 }
92
93 // MARK: - Surround
94
95 /// The delimiters evil-surround puts around text for `key`: an opening bracket adds a space
96 /// inside, a closing one doesn't.
97 static func delimiters(_ key: KeyChord) -> (open: String, close: String)? {
98 let pairs: [String: (String, String)] = [
99 "(": ("( ", " )"), ")": ("(", ")"), "b": ("(", ")"), "[": ("[ ", " ]"), "]": ("[", "]"), "r": ("[", "]"),
100 "{": ("{ ", " }"), "}": ("{", "}"), "B": ("{", "}"), "<": ("< ", " >"), ">": ("<", ">"), "a": ("<", ">"), "SPC": (" ", " "),
101 ]
102 if let pair = pairs[key.key] { return pair }
103 guard key.modifiers.isEmpty, key.key.count == 1 else { return nil }
104 return (key.key, key.key)
105 }
106
107 /// `ys{motion}{char}`, `yss{char}`, `cs{old}{new}` and `ds{char}`.
108 mutating func surroundCommand(_ op: String, _ after: [KeyChord], _ buffer: inout Buffer, caret: Int, count: Int, keys: [KeyChord]) -> Parse {
109 switch op {
110 case "y":
111 guard let (_, count2, motionKeys) = Self.prefix(after), let motionFirst = motionKeys.first else { return .incomplete }
112 let total = count * (count2 ?? 1)
113 if motionFirst.key == "s", motionFirst.modifiers.isEmpty {
114 guard motionKeys.count >= 2 else { return .incomplete }
115 let line = buffer.lineStart(caret)
116 let range = buffer.firstNonBlank(line)..<buffer.lineEnd(line)
117 return .done(surround(range, with: motionKeys[motionKeys.startIndex + 1], linewise: false, buffer, keys: keys))
118 }
119 if motionFirst.key == "i" || motionFirst.key == "a", motionFirst.modifiers.isEmpty {
120 guard motionKeys.count >= 3 else { return .incomplete }
121 guard let object = textObject(motionFirst.key == "i", motionKeys[motionKeys.startIndex + 1], buffer, caret, count: total) else { return .invalid }
122 return .done(surround(object.range, with: motionKeys[motionKeys.startIndex + 2], linewise: object.linewise, buffer, keys: keys))
123 }
124 var target = caret
125 switch motion(Array(motionKeys), &buffer, &target, count: total, operatorPending: true) {
126 case .incomplete: return .incomplete
127 case .invalid: return .invalid
128 case .moved(let to, let kind):
129 // The surround character follows the motion's keys.
130 let used = Self.motionLength(Array(motionKeys))
131 guard motionKeys.count > used else { return .incomplete }
132 let char = motionKeys[motionKeys.startIndex + used]
133 var range: Range<Int>
134 switch kind {
135 case .linewise: range = buffer.lineStart(min(caret, to))..<buffer.lineEnd(max(caret, to))
136 case .inclusive: range = min(caret, to)..<min(buffer.length, max(caret, to) + 1)
137 case .exclusive: range = min(caret, to)..<max(caret, to)
138 }
139 // ysw leaves the space after the word outside.
140 while range.upperBound > range.lowerBound, let c = buffer.char(range.upperBound - 1), Buffer.isBlank(c) { range = range.lowerBound..<(range.upperBound - 1) }
141 return .done(surround(range, with: char, linewise: kind == .linewise, buffer, keys: keys))
142 }
143 case "d", "c":
144 guard let target = after.first else { return .incomplete }
145 if op == "c", after.count < 2 { return .incomplete }
146 guard let found = surrounding(target, buffer, caret) else { return .invalid }
147 var edits: [TextEdit]
148 if op == "d" {
149 edits = [TextEdit(range: found.close, replacement: ""), TextEdit(range: found.open, replacement: "")]
150 } else {
151 guard let (open, close) = Self.delimiters(after[after.startIndex + 1]) else { return .invalid }
152 edits = [TextEdit(range: found.close, replacement: close), TextEdit(range: found.open, replacement: open)]
153 }
154 lastChange = (keys, nil)
155 return .done([.edit(edits, caret: found.open.lowerBound)])
156 default:
157 return .invalid
158 }
159 }
160
161 /// How many keys a motion takes: two for `f`, `t`, `'`, `` ` `` and `g`, otherwise one.
162 static func motionLength(_ keys: [KeyChord]) -> Int {
163 guard let first = keys.first, first.modifiers.isEmpty else { return 1 }
164 return ["f", "F", "t", "T", "'", "`", "g"].contains(first.key) ? 2 : 1
165 }
166
167 /// Wraps `range` in the delimiters for `key`; linewise, on lines of their own.
168 mutating func surround(_ range: Range<Int>, with key: KeyChord, linewise: Bool, _ buffer: Buffer, keys: [KeyChord]) -> [Action] {
169 guard let (open, close) = Self.delimiters(key) else { return [] }
170 var edits: [TextEdit]
171 if linewise {
172 let start = buffer.lineStart(range.lowerBound)
173 let end = buffer.lineEnd(max(range.lowerBound, range.upperBound - 1))
174 edits = [TextEdit(range: end..<end, replacement: "\n" + close.trimmingCharacters(in: .whitespaces)),
175 TextEdit(range: start..<start, replacement: open.trimmingCharacters(in: .whitespaces) + "\n")]
176 } else {
177 edits = [TextEdit(range: range.upperBound..<range.upperBound, replacement: close), TextEdit(range: range.lowerBound..<range.lowerBound, replacement: open)]
178 }
179 if !keys.isEmpty { lastChange = (keys, nil) }
180 return [.edit(edits, caret: range.lowerBound)]
181 }
182
183 /// The delimiters around `caret` for `key`, as `ds` and `cs` find them; with an opening
184 /// bracket, the blanks inside go too.
185 func surrounding(_ key: KeyChord, _ buffer: Buffer, _ caret: Int) -> (open: Range<Int>, close: Range<Int>)? {
186 let pairs: [String: (unichar, unichar)] = ["(": (40, 41), ")": (40, 41), "b": (40, 41), "[": (91, 93), "]": (91, 93), "r": (91, 93),
187 "{": (123, 125), "}": (123, 125), "B": (123, 125), "<": (60, 62), ">": (60, 62), "a": (60, 62)]
188 let outer: Range<Int>
189 if let (open, close) = pairs[key.key] {
190 guard let r = buffer.bracketObject(caret, open: open, close: close, inner: false) else { return nil }
191 outer = r
192 } else if key.key.count == 1, let quote = key.key.utf16.first {
193 guard let r = buffer.quoteObject(caret, quote: quote, inner: false) else { return nil }
194 outer = r
195 // `a"` takes blanks after the closing quote; the delimiter is the quote itself.
196 var end = r.upperBound
197 while end > r.lowerBound, buffer.char(end - 1) != quote { end -= 1 }
198 return (r.lowerBound..<(r.lowerBound + 1), (end - 1)..<end)
199 } else {
200 return nil
201 }
202 var open = outer.lowerBound..<(outer.lowerBound + 1)
203 var close = (outer.upperBound - 1)..<outer.upperBound
204 if ["(", "[", "{", "<"].contains(key.key) {
205 while open.upperBound < close.lowerBound, let c = buffer.char(open.upperBound), Buffer.isBlank(c) { open = open.lowerBound..<(open.upperBound + 1) }
206 while close.lowerBound > open.upperBound, let c = buffer.char(close.lowerBound - 1), Buffer.isBlank(c) { close = (close.lowerBound - 1)..<close.upperBound }
207 }
208 return (open, close)
209 }
210
211 // MARK: - Commenting
212
213 /// `comment-or-uncomment-region` in Org: `# ` at the shallowest indentation of the lines'
214 /// text, blank lines left alone; when every line is a comment, the comments come off.
215 static func toggleComments(_ buffer: Buffer, _ range: Range<Int>) -> [TextEdit] {
216 var lines: [Int] = []
217 var line = buffer.lineStart(range.lowerBound)
218 let last = buffer.lineStart(max(range.lowerBound, range.upperBound - 1))
219 while true {
220 lines.append(line)
221 let end = buffer.lineEnd(line)
222 guard line < last, end < buffer.length else { break }
223 line = end + 1
224 }
225 let content = lines.filter { buffer.firstNonBlankOrEnd($0) < buffer.lineEnd($0) }
226 guard !content.isEmpty else { return [] }
227 let commented = content.allSatisfy { start in
228 let text = buffer.substring(buffer.firstNonBlankOrEnd(start)..<buffer.lineEnd(start))
229 return text == "#" || text.hasPrefix("# ")
230 }
231 if commented {
232 return content.reversed().map { start in
233 let at = buffer.firstNonBlankOrEnd(start)
234 let length = buffer.lineEnd(start) - at >= 2 && buffer.char(at + 1) == 32 ? 2 : 1
235 return TextEdit(range: at..<(at + length), replacement: "")
236 }
237 }
238 let column = content.map { buffer.firstNonBlankOrEnd($0) - $0 }.min() ?? 0
239 return content.reversed().map { TextEdit(range: ($0 + column)..<($0 + column), replacement: "# ") }
240 }
241
242 // MARK: - Org text objects
243
244 /// evil-org's text objects: `e` the object at point, `E` the element, `r` the greater element
245 /// around it, `R` the subtree. `a` takes the blanks after; `i` the contents.
246 /// An org-element: `end` takes the blanks after it, `contents` is nil for elements without any.
247 struct OrgElement {
248 var begin: Int
249 var end: Int
250 var contents: Range<Int>?
251 var greater: Bool
252 var block = false
253 var node: SyntaxNode?
254 }
255
256 /// `j` and `k` are evil-org's `gj` and `gk` (`org-forward-element`, `org-backward-element`):
257 /// an empty range where they go, nil where they can't.
258 static func orgObject(_ key: String, inner: Bool, _ buffer: Buffer, _ position: Int) -> (range: Range<Int>, linewise: Bool)? {
259 let tree = OrgParser.parse(buffer.ns as String)
260 /// The point the element functions look at, and the nodes containing it.
261 var c = position
262 var chain: [SyntaxNode] = []
263 func look(at p: Int) {
264 c = p
265 chain = []
266 var node = tree.root
267 while let child = node.child(containing: c) {
268 chain.append(child)
269 node = child
270 }
271 }
272 look(at: position)
273 /// The end of the line ending at or after `p`, without trailing blank lines.
274 func trimmed(_ lower: Int, _ upper: Int) -> Int {
275 var end = upper
276 while end > lower, let ch = buffer.char(end - 1), ch == 10 || Buffer.isBlank(ch) { end -= 1 }
277 if end > lower, buffer.char(end) == 10 { end += 1 }
278 return max(lower, end)
279 }
280 /// The org-element for a node of ours; a `section` here is Org's headline.
281 func element(_ n: SyntaxNode) -> OrgElement {
282 let r = n.range
283 var post = r.upperBound
284 let limit = n.parent?.range.upperBound ?? buffer.length
285 while post < limit {
286 let lineEnd = buffer.lineEnd(post)
287 guard (post..<lineEnd).allSatisfy({ buffer.char($0).map(Buffer.isBlank) ?? true }), lineEnd < buffer.length else { break }
288 post = min(limit, lineEnd + 1)
289 }
290 switch n.kind {
291 case .section:
292 let body = min(r.upperBound, buffer.lineEnd(r.lowerBound) + 1)
293 let contentsEnd = trimmed(body, r.upperBound)
294 return OrgElement(begin: r.lowerBound, end: r.upperBound, contents: body < contentsEnd ? body..<contentsEnd : nil, greater: true, node: n)
295 case .plainList, .table, .drawer, .propertyDrawer, .dynamicBlock, .footnoteDefinition, .inlineTask:
296 return OrgElement(begin: r.lowerBound, end: post, contents: r.lowerBound..<trimmed(r.lowerBound, r.upperBound), greater: true, node: n)
297 case .item:
298 let first = n.children.first { ![.bullet, .checkbox, .itemTag].contains($0.kind) }?.range.lowerBound ?? r.upperBound
299 return OrgElement(begin: r.lowerBound, end: post, contents: min(first, r.upperBound)..<trimmed(first, r.upperBound), greater: true, node: n)
300 case .block:
301 let name = buffer.substring(r.lowerBound..<buffer.lineEnd(r.lowerBound)).lowercased()
302 let greater = !["src", "example", "export", "verse", "comment"].contains { name.hasPrefix("#+begin_\($0)") }
303 return OrgElement(begin: r.lowerBound, end: post, contents: nil, greater: greater, block: true, node: n)
304 case .latexEnvironment:
305 return OrgElement(begin: r.lowerBound, end: post, contents: nil, greater: false, block: true, node: n)
306 case .paragraph:
307 return OrgElement(begin: r.lowerBound, end: post, contents: r.lowerBound..<trimmed(r.lowerBound, r.upperBound), greater: false, node: n)
308 default:
309 return OrgElement(begin: r.lowerBound, end: post, contents: nil, greater: false, node: n)
310 }
311 }
312 /// Org's section: the text under a heading before its first child, or before the first heading.
313 func section(_ n: SyntaxNode) -> OrgElement? {
314 let begin = n.kind == .zerothSection ? n.range.lowerBound : min(n.range.upperBound, buffer.lineEnd(n.range.lowerBound) + 1)
315 let end = n.children.first { $0.kind == .section && $0.range.lowerBound >= begin }?.range.lowerBound ?? n.range.upperBound
316 guard begin < end else { return nil }
317 return OrgElement(begin: begin, end: end, contents: begin..<trimmed(begin, end), greater: true)
318 }
319 func parent(_ e: OrgElement) -> OrgElement? {
320 guard let n = e.node else {
321 // A section's parent is its headline.
322 return chain.last { $0.kind == .section && $0.range.lowerBound < e.begin }.map(element)
323 }
324 guard let up = n.parent else { return nil }
325 switch up.kind {
326 case .section where n.kind != .section: return section(up)
327 case .zerothSection: return section(up)
328 case .document: return nil
329 default: return element(up)
330 }
331 }
332 /// org-element-at-point.
333 func atPoint() -> OrgElement? {
334 let objects: Set<SyntaxKind> = [.title, .heading, .bold, .italic, .underline, .strikeThrough, .verbatim, .code, .link, .linkDescription,
335 .timestamp, .footnoteReference, .statisticsCookie, .target, .radioTarget, .macro, .inlineSourceBlock,
336 .latexFragment, .lineBreak, .superscript, .subscript, .entity, .tableCell, .nodeProperty,
337 .citation, .citationReference, .exportSnippet, .inlineBabelCall, .itemTag]
338 var elements = chain.filter { !objects.contains($0.kind) }
339 if chain.contains(where: { $0.kind == .heading }), let i = elements.lastIndex(where: { $0.kind == .section }) {
340 elements = Array(elements[...i])
341 }
342 guard var n = elements.last else { return nil }
343 if [.section, .zerothSection, .plainList, .item].contains(n.kind), !chain.contains(where: { $0.kind == .heading }) {
344 // On a blank line: the element before it, whose post-blank it is.
345 if let before = n.children.last(where: { $0.range.upperBound <= c && $0.kind != .heading && $0.kind != .bullet }), !n.children.contains(where: { $0.range.contains(c) }) {
346 n = before
347 } else if n.kind == .zerothSection || n.kind == .section {
348 return section(n)
349 }
350 }
351 if let up = n.parent, up.range.lowerBound == n.range.lowerBound, (n.kind == .item && up.kind == .plainList) || (n.kind == .tableRow && up.kind == .table) {
352 n = up
353 }
354 return element(n)
355 }
356 func innerRange(_ e: OrgElement) -> Range<Int> {
357 if e.block {
358 let first = min(e.end, buffer.lineEnd(e.begin) + 1)
359 let closing = buffer.lineStart(max(e.begin, trimmed(e.begin, e.end) - 1))
360 return min(first, closing)..<closing
361 }
362 if let contents = e.contents { return contents }
363 return e.begin..<max(e.begin, trimmed(e.begin, e.end) - (buffer.char(trimmed(e.begin, e.end) - 1) == 10 ? 1 : 0))
364 }
365 /// The stars of the heading on the line holding `p`, or nil.
366 func headingLevel(_ p: Int) -> Int? {
367 let line = buffer.substring(buffer.lineStart(p)..<buffer.lineEnd(p))
368 guard let stars = line.range(of: "^\\*+(?=[ \\t]|$)", options: .regularExpression) else { return nil }
369 return line.distance(from: stars.lowerBound, to: stars.upperBound)
370 }
371 func at(_ p: Int) -> (range: Range<Int>, linewise: Bool) { (p..<p, false) }
372 switch key {
373 case "j":
374 guard position < buffer.length else { return nil }
375 if headingLevel(position) != nil {
376 // To the end of the subtree, when a heading is there.
377 guard let section = chain.last(where: { $0.kind == .section }) else { return nil }
378 let end = section.range.upperBound
379 return end < buffer.length && headingLevel(end) != nil ? at(end) : nil
380 }
381 guard let e = atPoint() else { return nil }
382 if let up = parent(e), up.contents?.upperBound == e.end { return at(up.end) }
383 return at(e.end)
384 case "k":
385 guard position > 0 else { return nil }
386 if let level = headingLevel(position) {
387 // The previous heading at this level, or else the parent.
388 var line = buffer.lineStart(position)
389 while line > 0 {
390 line = buffer.lineStart(line - 1)
391 if let other = headingLevel(line), other <= level { return at(line) }
392 }
393 return nil
394 }
395 guard let e = atPoint() else { return nil }
396 if position != e.begin { return at(e.begin) }
397 var p = e.begin
398 while p > 0, let ch = buffer.char(p - 1), Buffer.isSpace(ch) { p -= 1 }
399 guard p > 0 else { return at(0) }
400 look(at: p)
401 guard var prev = atPoint() else { return at(e.begin) }
402 var target = prev.begin
403 while let up = parent(prev), up.end <= e.begin {
404 target = up.begin
405 prev = up
406 }
407 return at(target)
408 case "R":
409 guard let headline = chain.last(where: { $0.kind == .section }).map(element) else { return nil }
410 return (inner ? (headline.contents ?? headline.begin..<headline.begin) : headline.begin..<headline.end, true)
411 case "E":
412 guard let e = atPoint() else { return nil }
413 return (inner ? innerRange(e) : e.begin..<e.end, false)
414 case "r":
415 guard var e = atPoint() else { return nil }
416 if !e.greater, let up = parent(e) { e = up }
417 return (inner ? innerRange(e) : e.begin..<e.end, !inner)
418 default:
419 let objects: Set<SyntaxKind> = [.bold, .italic, .underline, .strikeThrough, .verbatim, .code, .link, .timestamp, .footnoteReference,
420 .statisticsCookie, .target, .radioTarget, .macro, .inlineSourceBlock, .latexFragment, .entity, .superscript, .subscript,
421 .citation, .citationReference, .exportSnippet, .inlineBabelCall]
422 guard let found = chain.last(where: { objects.contains($0.kind) }) else {
423 guard let e = atPoint() else { return nil }
424 return (inner ? innerRange(e) : e.begin..<e.end, false)
425 }
426 var range = found.range
427 var end = range.upperBound
428 while let ch = buffer.char(end), Buffer.isBlank(ch) { end += 1 }
429 if inner {
430 switch found.kind {
431 case .bold, .italic, .underline, .strikeThrough, .verbatim, .code:
432 range = (range.lowerBound + 1)..<(range.upperBound - 1)
433 case .link:
434 if let description = found.firstChild(.linkDescription) { range = description.range }
435 default:
436 break
437 }
438 return (range, false)
439 }
440 return (range.lowerBound..<end, false)
441 }
442 }
443
444 // MARK: - Block visual
445
446 /// The block's columns and line starts, from `anchor` to `caret`; `$` stretches it to each
447 /// line's end.
448 func block(_ buffer: Buffer, _ caret: Int) -> (lines: [Int], left: Int, right: Int) {
449 let a = min(anchor, caret), b = max(anchor, caret)
450 let left = min(anchor - buffer.lineStart(anchor), caret - buffer.lineStart(caret))
451 let right = blockToEnd ? Int.max : max(anchor - buffer.lineStart(anchor), caret - buffer.lineStart(caret))
452 var lines: [Int] = []
453 var line = buffer.lineStart(a)
454 while true {
455 lines.append(line)
456 let end = buffer.lineEnd(line)
457 guard end < buffer.length, line < buffer.lineStart(b) else { break }
458 line = end + 1
459 }
460 return (lines, left, right)
461 }
462
463 /// Each line's part of the block.
464 func blockRanges(_ buffer: Buffer, _ caret: Int) -> [Range<Int>] {
465 let (lines, left, right) = block(buffer, caret)
466 return lines.map { line in
467 let end = buffer.lineEnd(line)
468 let lower = min(line + left, end)
469 let upper = right == Int.max ? end : min(line + right + 1, end)
470 return lower..<max(lower, upper)
471 }
472 }
473
474 func blockActions(_ buffer: Buffer, _ caret: Int) -> [Action] {
475 [.move(caret: caret), .selectBlock(blockRanges(buffer, caret))]
476 }
477
478 /// Keys in block visual state.
479 mutating func parseBlock(_ rest: ArraySlice<KeyChord>, _ buffer: inout Buffer, _ context: Context, register: Character?, count n: Int, keys: [KeyChord]) -> Parse {
480 guard let first = rest.first else { return .incomplete }
481 let caret = context.caret
482 let ranges = blockRanges(buffer, caret)
483 func leave() {
484 lastVisual = (anchor, caret, .visualBlock)
485 mode = .normal
486 blockToEnd = false
487 }
488 if first == KeyChord("v", .control) || (first.modifiers.isEmpty && first.key == "ESC") {
489 leave()
490 return .done([.move(caret: buffer.clampNormal(caret))])
491 }
492 guard first.modifiers.isEmpty else { return .invalid }
493 switch first.key {
494 case "v", "V":
495 lastVisual = (anchor, caret, .visualBlock)
496 mode = first.key == "v" ? .visual : .visualLine
497 blockToEnd = false
498 return .done(visualActions(buffer, caret))
499 case "o", "O":
500 let old = anchor
501 anchor = caret
502 return .done(blockActions(buffer, old))
503 case "$":
504 blockToEnd = true
505 return .done(blockActions(buffer, buffer.lineEnd(caret) > buffer.lineStart(caret) ? buffer.lineEnd(caret) - 1 : caret))
506 case "d", "x", "y", "c":
507 let removed = ranges.map { buffer.substring($0) }.joined(separator: "\n")
508 leave()
509 var actions = store(removed, linewise: false, register: register, yank: first.key == "y")
510 let top = ranges.first?.lowerBound ?? caret
511 if first.key == "y" { return .done(actions + [.move(caret: top)]) }
512 let edits = ranges.reversed().map { TextEdit(range: $0, replacement: "") }
513 if first.key == "c" {
514 let (lines, left, _) = block(buffer, caret)
515 blockInsert = (lines.dropFirst().map { buffer.lineNumber($0) }, left, false)
516 actions.insert(.edit(edits, caret: top), at: 0)
517 enterInsert(at: top, keys: [])
518 return .done(actions)
519 }
520 actions.insert(.edit(edits, caret: buffer.removing(top..<top).clampNormal(top)), at: 0)
521 return .done(actions)
522 case "I", "A":
523 let (lines, left, right) = block(buffer, caret)
524 leave()
525 let column = first.key == "I" ? left : (right == Int.max ? Int.max : right + 1)
526 let top = lines.first ?? caret
527 let at: Int
528 if column == Int.max {
529 at = buffer.lineEnd(top)
530 } else {
531 at = min(top + column, buffer.lineEnd(top))
532 }
533 blockInsert = (lines.dropFirst().map { buffer.lineNumber($0) }, column, first.key == "A")
534 var actions: [Action] = []
535 // `A` past a short first line pads it with spaces.
536 if first.key == "A", column != Int.max, buffer.lineEnd(top) < top + column {
537 let pad = top + column - buffer.lineEnd(top)
538 actions.append(.edit([TextEdit(range: buffer.lineEnd(top)..<buffer.lineEnd(top), replacement: String(repeating: " ", count: pad))], caret: top + column))
539 enterInsert(at: top + column, keys: [])
540 return .done(actions)
541 }
542 enterInsert(at: at, keys: [])
543 return .done([.move(caret: at)])
544 case "r":
545 guard rest.count >= 2 else { return .incomplete }
546 let char = rest[rest.startIndex + 1].key == "SPC" ? " " : rest[rest.startIndex + 1].key
547 guard char.count == 1 else { return .invalid }
548 leave()
549 let edits = ranges.reversed().map { TextEdit(range: $0, replacement: String(repeating: char, count: $0.count)) }
550 return .done([.edit(edits, caret: ranges.first?.lowerBound ?? caret)])
551 case "~", "u", "U":
552 leave()
553 let edits = ranges.reversed().map { r -> TextEdit in
554 let s = buffer.substring(r)
555 let t = first.key == "u" ? s.lowercased() : first.key == "U" ? s.uppercased() : String(s.map { $0.isUppercase ? Character($0.lowercased()) : Character($0.uppercased()) })
556 return TextEdit(range: r, replacement: t)
557 }
558 return .done([.edit(edits, caret: ranges.first?.lowerBound ?? caret)])
559 case ">", "<":
560 leave()
561 let lines = block(buffer, caret).lines
562 let range = (lines.first ?? caret)..<min(buffer.length, buffer.lineEnd(lines.last ?? caret) + 1)
563 mode = .normal
564 return .done(apply(first.key, range: range, linewise: true, buffer, register: register, keys: [], count: n))
565 default:
566 var target = caret
567 switch motion(Array(rest), &buffer, &target, count: n, operatorPending: false) {
568 case .incomplete: return .incomplete
569 case .invalid: return .invalid
570 case .moved(let to, _): return .done(blockActions(buffer, to))
571 }
572 }
573 }
574
575 /// What block `I`, `A` or `c` typed, put on the block's other lines at its column.
576 func replicateBlockInsert(_ block: (lines: [Int], column: Int, pad: Bool), _ buffer: Buffer, caret: Int) -> [Action] {
577 var edits: [TextEdit] = []
578 for number in block.lines.reversed() {
579 let line = buffer.lineOffset(number)
580 let end = buffer.lineEnd(line)
581 if block.column == Int.max {
582 edits.append(TextEdit(range: end..<end, replacement: insertedText))
583 } else if line + block.column <= end {
584 edits.append(TextEdit(range: (line + block.column)..<(line + block.column), replacement: insertedText))
585 } else if block.pad {
586 edits.append(TextEdit(range: end..<end, replacement: String(repeating: " ", count: line + block.column - end) + insertedText))
587 }
588 }
589 let lineStart = buffer.lineStart(caret)
590 let back = caret > lineStart ? caret - 1 : caret
591 return edits.isEmpty ? [.move(caret: back)] : [.edit(edits, caret: back)]
592 }
593}
594
595/// `char-fold-to-regexp` for one snipe key, as evil-snipe's `evil-snipe--process-key` uses it
596/// with `evil-snipe-char-fold`: a letter also matches its accented and other variants.
597enum CharFold {
598 /// `char-fold-table` by code point.
599 static let regexps: [UInt32: String] = {
600 var regexps: [UInt32: String] = [:]
601 // Scalars, not characters: a tab before a combining mark is one character.
602 for line in table.unicodeScalars.split(separator: "\n") {
603 guard let tab = line.firstIndex(of: "\t"), let code = UInt32(String(line[..<tab]), radix: 16) else { continue }
604 regexps[code] = String(line[line.index(after: tab)...])
605 }
606 return regexps
607 }()
608
609 /// The Emacs regexp `key` matches: its table entry, else the key quoted.
610 static func regexp(_ key: Character) -> String {
611 let scalars = key.unicodeScalars
612 if scalars.count == 1, let entry = regexps[scalars.first!.value] { return entry }
613 return scalars.map { "[*.\\?+^$".unicodeScalars.contains($0) ? "\\" + String($0) : String($0) }.joined()
614 }
615
616 /// An Emacs regexp of shy groups, alternatives, bracket expressions and literals, which is
617 /// all `char-fold-table` and `regexp-quote` produce, as an ICU pattern.
618 static func icu(_ emacs: String) -> String {
619 let s = Array(emacs.unicodeScalars)
620 func literal(_ c: Unicode.Scalar) -> String { String(format: "\\x{%X}", c.value) }
621 var out = ""
622 var i = 0
623 while i < s.count {
624 if s[i] == "\\", i + 1 < s.count {
625 switch s[i + 1] {
626 case "(":
627 out += "(?:"
628 i += i + 3 < s.count && s[i + 2] == "?" && s[i + 3] == ":" ? 4 : 2
629 case "|":
630 out += "|"
631 i += 2
632 case ")":
633 out += ")"
634 i += 2
635 default:
636 out += literal(s[i + 1])
637 i += 2
638 }
639 } else if s[i] == "[", let close = s[(i + 2)...].firstIndex(of: "]") {
640 // A `]` right after `[` is a member; `-` between two members is a range.
641 var j = i + 1
642 out += "["
643 while j < close {
644 if j + 2 < close, s[j + 1] == "-" {
645 out += literal(s[j]) + "-" + literal(s[j + 2])
646 j += 3
647 } else {
648 out += literal(s[j])
649 j += 1
650 }
651 }
652 out += "]"
653 i = close + 1
654 } else {
655 out += literal(s[i])
656 i += 1
657 }
658 }
659 return out
660 }
661}