Commit f08ff89622
Verified · cmc
Layout: unified · split
Sources/OrgApp/EmacsImport.swift +1 −1
| @@ -448,7 +448,7 @@ public struct EmacsImport: Sendable { | ||
| 448 | 448 | guard let n = value.integer else { return note("not a number") } |
| 449 | 449 | add(name, .integer(n)) |
| 450 | 450 | case "org-insert-heading-respect-content", "org-list-allow-alphabetical", "org-hide-emphasis-markers", "org-pretty-entities", |
| 451 | "org-cycle-hide-drawer-startup", "org-cycle-hide-block-startup": | |
| 451 | "org-cycle-hide-drawer-startup", "org-cycle-hide-block-startup", "org-use-speed-commands": | |
| 452 | 452 | add(name, .bool(truthy)) |
| 453 | 453 | case "org-hide-drawer-startup", "org-hide-block-startup": |
| 454 | 454 | add(name == "org-hide-drawer-startup" ? "org-cycle-hide-drawer-startup" : "org-cycle-hide-block-startup", .bool(truthy)) |
Sources/OrgApp/UserConfig.swift +2
| @@ -82,6 +82,8 @@ public enum UserConfig { | ||
| 82 | 82 | fallback: .string("nil"), comment: "nil, time or note: log changing or removing a DEADLINE", legacy: ""), |
| 83 | 83 | Setting(section: "", key: "org-log-into-drawer", defaultsKey: "logIntoDrawer", kind: .string, fallback: .string(""), |
| 84 | 84 | comment: "the drawer state notes go in (\"LOGBOOK\" is Emacs's t); \"\" for none", legacy: ""), |
| 85 | Setting(section: "", key: "org-use-speed-commands", defaultsKey: "useSpeedCommands", kind: .bool, fallback: .bool(false), | |
| 86 | comment: "single keys at the start of a heading line run commands (n, p, t, c, …)", legacy: ""), | |
| 85 | 87 | Setting(section: "", key: "org-cycle-hide-drawer-startup", defaultsKey: "hideDrawerStartup", kind: .bool, fallback: .bool(true), |
| 86 | 88 | comment: "fold drawers when a file opens; #+STARTUP: nohidedrawers overrides", legacy: ""), |
| 87 | 89 | Setting(section: "", key: "org-cycle-hide-block-startup", defaultsKey: "hideBlockStartup", kind: .bool, fallback: .bool(false), |
Sources/OrgCore/Commands/Command.swift +6 −2
| @@ -22,6 +22,8 @@ public struct EditingOptions: Sendable, Equatable, Codable { | ||
| 22 | 22 | public var logRedeadline: LogHow? |
| 23 | 23 | /// `org-use-fast-todo-selection` `auto`: with keys in the keywords, C-c C-t asks for one. |
| 24 | 24 | public var fastTodoSelection: Bool |
| 25 | /// `org-use-speed-commands`: single keys at the start of a heading line run commands. | |
| 26 | public var useSpeedCommands: Bool | |
| 25 | 27 | /// `org-cycle-hide-drawer-startup`: drawers are folded when a file opens. |
| 26 | 28 | public var hideDrawerStartup: Bool |
| 27 | 29 | /// `org-cycle-hide-block-startup`: blocks are folded when a file opens. |
| @@ -40,8 +42,10 @@ public struct EditingOptions: Sendable, Equatable, Codable { | ||
| 40 | 42 | listAllowAlphabetical: Bool = false, logRepeat: Bool = true, fillColumn: Int = 70, |
| 41 | 43 | hideEmphasisMarkers: Bool = false, prettyEntities: Bool = false, subSuperscriptsNeedBraces: Bool = false, |
| 42 | 44 | logDone: LogHow? = nil, logIntoDrawer: String? = nil, fastTodoSelection: Bool = true, |
| 43 | hideDrawerStartup: Bool = true, hideBlockStartup: Bool = false, logReschedule: LogHow? = nil, logRedeadline: LogHow? = nil | |
| 45 | hideDrawerStartup: Bool = true, hideBlockStartup: Bool = false, logReschedule: LogHow? = nil, logRedeadline: LogHow? = nil, | |
| 46 | useSpeedCommands: Bool = false | |
| 44 | 47 | ) { |
| 48 | self.useSpeedCommands = useSpeedCommands | |
| 45 | 49 | self.logReschedule = logReschedule |
| 46 | 50 | self.logRedeadline = logRedeadline |
| 47 | 51 | self.hideDrawerStartup = hideDrawerStartup |
| @@ -235,7 +239,7 @@ public enum Commands { | ||
| 235 | 239 | ShiftTimestamp(up: true), ShiftTimestamp(up: false), ShiftTimestampDay(later: true), ShiftTimestampDay(later: false), |
| 236 | 240 | OpenLinkAtPoint(), CreateID(), ToggleComment(), ToggleHeading(), ToggleItem(), MarkSubtree(), |
| 237 | 241 | CopySubtree(cut: false), CopySubtree(cut: true), CloneSubtree(), SortEntries(), InsertStructureTemplate(), EditSpecial(), |
| 238 | UpdateDynamicBlock(), ClockReportTable(), SetEffort(), CtrlCCtrlC(), EvaluateTimeRange(), FootnoteAction(), FootnoteAction(special: true), TableEvalFormula(named: false), TableEvalFormula(named: true), SortTableLines(), TransposeTable(), GotoHeading(), SparseTreeCommand(), | |
| 242 | UpdateDynamicBlock(), ClockReportTable(), SetEffort(), CtrlCCtrlC(), EvaluateTimeRange(), FootnoteAction(), FootnoteAction(special: true), TableEvalFormula(named: false), TableEvalFormula(named: true), SortTableLines(), TransposeTable(), GotoHeading(), SparseTreeCommand(), SetPriority(), SetPriority(value: "A"), SetPriority(value: "B"), SetPriority(value: "C"), SetPriority(value: " "), | |
| 239 | 243 | ] |
| 240 | 244 | |
| 241 | 245 | public static var everything: [any OrgCommand] { all + TableCommand.Action.allCases.map(TableCommand.init) } |
Sources/OrgCore/Commands/HeadingCommands.swift +62 −2
| @@ -296,8 +296,12 @@ struct PriorityChange { | ||
| 296 | 296 | } else { |
| 297 | 297 | new = Self.value(priorities.default) |
| 298 | 298 | } |
| 299 | let cookie = new.map { "[#\(Self.label($0, numeric: numeric))]" } | |
| 300 | return line.commit(context) { buffer in | |
| 299 | return Self.write(new.map { "[#\(Self.label($0, numeric: numeric))]" }, line: line, context: context) | |
| 300 | } | |
| 301 | ||
| 302 | /// Puts `cookie` on the heading, or with nil removes it. | |
| 303 | static func write(_ cookie: String?, line: HeadingLine, context: EditContext) -> CommandStep { | |
| 304 | line.commit(context) { buffer in | |
| 301 | 305 | if let existing = line.priority { |
| 302 | 306 | let range = line.local(existing.range) |
| 303 | 307 | if let cookie { |
| @@ -331,6 +335,62 @@ struct PriorityChange { | ||
| 331 | 335 | } |
| 332 | 336 | } |
| 333 | 337 | |
| 338 | /// C-c ,: `org-priority` with `set`, asking for the priority (SPC removes it); with `value`, | |
| 339 | /// that priority, or with " " none, as the speed keys 1, 2, 3 and 0 do. | |
| 340 | public struct SetPriority: OrgCommand { | |
| 341 | let value: Character? | |
| 342 | public init(value: Character? = nil) { self.value = value } | |
| 343 | public var id: String { | |
| 344 | switch value { | |
| 345 | case nil: "org.priority.set" | |
| 346 | case " ": "org.priority.remove" | |
| 347 | case let v?: "org.priority.set-\(v.lowercased())" | |
| 348 | } | |
| 349 | } | |
| 350 | public var title: String { | |
| 351 | switch value { | |
| 352 | case nil: "Set Priority…" | |
| 353 | case " ": "Remove Priority" | |
| 354 | case let v?: "Set Priority \(v)" | |
| 355 | } | |
| 356 | } | |
| 357 | public func applies(in context: EditContext) -> Bool { headingOnLine(at: context.caret, in: context.tree) != nil } | |
| 358 | ||
| 359 | public func run(in context: EditContext) -> CommandStep { | |
| 360 | guard let heading = headingOnLine(at: context.caret, in: context.tree) else { return .failed("Not on a heading") } | |
| 361 | let priorities = context.tree.settings.priorities | |
| 362 | let numeric = Int(priorities.lowest) != nil | |
| 363 | let answer: String | |
| 364 | if let value { | |
| 365 | answer = String(value) | |
| 366 | } else if let given = context.answers["priority"] { | |
| 367 | answer = given | |
| 368 | } else { | |
| 369 | let message = "Priority \(priorities.highest)-\(priorities.lowest), SPC to remove:" | |
| 370 | if numeric, (Int(priorities.lowest) ?? 0) >= 10 { return .prompt(Prompt(key: "priority", message: message)) } | |
| 371 | guard let high = PriorityChange.value(priorities.highest), let low = PriorityChange.value(priorities.lowest), high <= low else { | |
| 372 | return .failed("Unsupported priority range") | |
| 373 | } | |
| 374 | let items: [FastSelection.Item] = (high...low).map { v in | |
| 375 | let label = PriorityChange.label(v, numeric: numeric) | |
| 376 | return .option(key: Character(label.lowercased()), name: label) | |
| 377 | } + [.option(key: " ", name: "remove")] | |
| 378 | return .prompt(Prompt(key: "priority", message: message, fast: FastSelection(items: items, multiple: false))) | |
| 379 | } | |
| 380 | let line = HeadingLine(heading) | |
| 381 | if answer == " " { | |
| 382 | guard line.priority != nil else { return .failed("No priority cookie found in line") } | |
| 383 | return PriorityChange.write(nil, line: line, context: context) | |
| 384 | } | |
| 385 | let new = numeric ? Int(answer.trimmingCharacters(in: .whitespaces)) : PriorityChange.value(answer.uppercased()) | |
| 386 | guard let new, let high = PriorityChange.value(priorities.highest), let low = PriorityChange.value(priorities.lowest), new >= high, new <= low else { | |
| 387 | return .failed(numeric ? "Priority must be a number between `\(priorities.highest)' and `\(priorities.lowest)'" | |
| 388 | : "Priority must be between `\(priorities.highest)' and `\(priorities.lowest)'") | |
| 389 | } | |
| 390 | return PriorityChange.write("[#\(PriorityChange.label(new, numeric: numeric))]", line: line, context: context) | |
| 391 | } | |
| 392 | } | |
| 393 | ||
| 334 | 394 | public struct PriorityUp: OrgCommand { |
| 335 | 395 | public init() {} |
| 336 | 396 | public var id: String { "org.priority.up" } |
Sources/OrgCore/Keymap/Keymap.swift +10 −2
| @@ -101,7 +101,8 @@ public struct Keymap: Sendable, Equatable { | ||
| 101 | 101 | public func keyLabels(for command: String, modes: [String?] = [nil], limit: Int = 2) -> [String] { |
| 102 | 102 | var found: [(keys: [KeyChord], rank: [Int])] = [] |
| 103 | 103 | for (order, mode) in modes.enumerated() { |
| 104 | for binding in bindings.reversed() where binding.mode == mode && binding.command == command && isLive(binding) { | |
| 104 | // Speed keys are labels for nothing: they act only at a heading's start. | |
| 105 | for binding in bindings.reversed() where binding.mode == mode && binding.command == command && isLive(binding) && binding.when != "speed" { | |
| 105 | 106 | found.append((binding.keys, [order, binding.when == nil ? 0 : 1, binding.keys.count, found.count])) |
| 106 | 107 | } |
| 107 | 108 | } |
| @@ -130,7 +131,7 @@ public struct Keymap: Sendable, Equatable { | ||
| 130 | 131 | |
| 131 | 132 | /// Context names a binding's `when` can use. |
| 132 | 133 | public enum KeyContext { |
| 133 | public static let names: Set<String> = ["heading", "table", "item", "region", "timestamp", "tblfm", "src", "fold-line", "dblock"] | |
| 134 | public static let names: Set<String> = ["heading", "table", "item", "region", "timestamp", "tblfm", "src", "fold-line", "dblock", "speed"] | |
| 134 | 135 | |
| 135 | 136 | public static func holds(_ name: String, in context: EditContext) -> Bool { |
| 136 | 137 | switch name { |
| @@ -147,6 +148,13 @@ public enum KeyContext { | ||
| 147 | 148 | case "dblock": |
| 148 | 149 | let buffer = EmacsBuffer(context.text, point: context.caret, settings: context.tree.settings) |
| 149 | 150 | return buffer.lookingAtLine(EmacsBuffer.dblockStart) |
| 151 | // `org-use-speed-commands`: at the very start of a heading line. | |
| 152 | case "speed": | |
| 153 | guard context.options.useSpeedCommands, context.selection.allSatisfy(\.isEmpty) else { return false } | |
| 154 | let ns = context.text as NSString | |
| 155 | let caret = context.caret | |
| 156 | guard caret == 0 || (caret <= ns.length && ns.character(at: caret - 1) == 10) else { return false } | |
| 157 | return ns.substring(from: caret).range(of: "^\\*+ ", options: .regularExpression) != nil | |
| 150 | 158 | default: return false |
| 151 | 159 | } |
| 152 | 160 | } |
Sources/OrgCore/Keymap/Presets.swift +30 −3
| @@ -22,12 +22,32 @@ public enum KeymapPreset: String, Sendable, CaseIterable { | ||
| 22 | 22 | |
| 23 | 23 | public var toml: String { |
| 24 | 24 | switch self { |
| 25 | case .emacs: Self.common + Self.emacsBindings | |
| 26 | case .mac: Self.common + Self.macBindings | |
| 27 | case .doom: Self.doomToml | |
| 25 | case .emacs: Self.common + Self.speedBindings(mode: nil) + Self.emacsBindings | |
| 26 | case .mac: Self.common + Self.speedBindings(mode: nil) + Self.macBindings | |
| 27 | case .doom: Self.doomToml + Self.speedBindings(mode: "insert") | |
| 28 | 28 | } |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | /// `org-speed-commands`, for the start of a heading line when `org-use-speed-commands` is on. | |
| 32 | static let speedCommands: [(key: String, command: String)] = [ | |
| 33 | ("n", "org.heading.next"), ("p", "org.heading.previous"), ("f", "org.heading.forward-same-level"), | |
| 34 | ("b", "org.heading.backward-same-level"), ("u", "org.heading.up"), ("j", "org.goto"), | |
| 35 | ("c", "org.cycle"), ("C", "org.cycle-global"), ("s", "org.narrow.toggle"), ("k", "org.subtree.cut"), | |
| 36 | ("U", "org.subtree.up"), ("D", "org.subtree.down"), ("r", "org.heading.demote"), ("l", "org.heading.promote"), | |
| 37 | ("R", "org.subtree.demote"), ("L", "org.subtree.promote"), ("i", "org.heading.insert-after-subtree"), | |
| 38 | ("^", "org.sort"), ("w", "app.refile"), ("a", "app.archive"), ("@", "org.subtree.mark"), | |
| 39 | ("#", "org.heading.toggle-comment"), ("I", "app.clock.in"), ("O", "app.clock.out"), ("t", "org.todo.cycle"), | |
| 40 | (",", "org.priority.set"), ("0", "org.priority.remove"), ("1", "org.priority.set-a"), ("2", "org.priority.set-b"), | |
| 41 | ("3", "org.priority.set-c"), (":", "org.tags.set"), ("e", "org.effort.set"), ("v", "app.agenda"), | |
| 42 | ("/", "org.sparse-tree"), ("o", "org.link.open"), | |
| 43 | ] | |
| 44 | ||
| 45 | static func speedBindings(mode: String?) -> String { | |
| 46 | speedCommands.map { key, command in | |
| 47 | "[[bind]]\nkeys = \"\(key)\"\ncommand = \"\(command)\"\nwhen = \"speed\"\n" + (mode.map { "mode = \"\($0)\"\n" } ?? "") | |
| 48 | }.joined() | |
| 49 | } | |
| 50 | ||
| 31 | 51 | /// Doom: the Emacs preset's org bindings in every evil state (as org-mode-map's are), with |
| 32 | 52 | /// evil-org's `M-h/j/k/l` forms, then Doom's leader, localleader and evil-org keys. |
| 33 | 53 | static var doomToml: String { |
| @@ -346,6 +366,10 @@ public enum KeymapPreset: String, Sendable, CaseIterable { | ||
| 346 | 366 | command = "org.effort.set" |
| 347 | 367 | mode = "normal" |
| 348 | 368 | [[bind]] |
| 369 | keys = "SPC m p p" | |
| 370 | command = "org.priority.set" | |
| 371 | mode = "normal" | |
| 372 | [[bind]] | |
| 349 | 373 | keys = "SPC m p u" |
| 350 | 374 | command = "org.priority.up" |
| 351 | 375 | mode = "normal" |
| @@ -682,6 +706,9 @@ public enum KeymapPreset: String, Sendable, CaseIterable { | ||
| 682 | 706 | keys = "C-c /" |
| 683 | 707 | command = "org.sparse-tree" |
| 684 | 708 | [[bind]] |
| 709 | keys = "C-c ," | |
| 710 | command = "org.priority.set" | |
| 711 | [[bind]] | |
| 685 | 712 | keys = "C-c C-x C-r" |
| 686 | 713 | command = "org.clock.report" |
| 687 | 714 | [[bind]] |
Sources/OrgEditorAppKit/EditorCommands.swift +1
| @@ -40,6 +40,7 @@ public enum EditorCommands { | ||
| 40 | 40 | EditorCommand(id: "org.narrow.subtree", title: "Narrow to Subtree", action: .view), |
| 41 | 41 | EditorCommand(id: "org.narrow.block", title: "Narrow to Block", action: .view), |
| 42 | 42 | EditorCommand(id: "org.widen", title: "Widen", action: .view), |
| 43 | EditorCommand(id: "org.narrow.toggle", title: "Narrow to Subtree or Widen", action: .view), | |
| 43 | 44 | EditorCommand(id: "org.table.toggle-column-width", title: "Shrink or Expand Table Column", action: .view), |
| 44 | 45 | EditorCommand(id: "org.table.shrink", title: "Shrink Table Columns with Widths", action: .view), |
| 45 | 46 | EditorCommand(id: "org.table.expand", title: "Expand Table Columns", action: .view), |
Sources/OrgEditorAppKit/OrgEditor.swift +3 −1
| @@ -405,7 +405,7 @@ public final class OrgEditor: NSObject { | ||
| 405 | 405 | switch id { |
| 406 | 406 | case "org.cycle": cycleAtCaret() |
| 407 | 407 | case "org.cycle-global": cycleGlobal() |
| 408 | case "org.narrow.subtree", "org.narrow.block", "org.widen": narrow(id) | |
| 408 | case "org.narrow.subtree", "org.narrow.block", "org.narrow.toggle", "org.widen": narrow(id) | |
| 409 | 409 | default: tableColumnWidth(id) |
| 410 | 410 | } |
| 411 | 411 | case .edit(let selector, let extending): |
| @@ -615,6 +615,8 @@ public final class OrgEditor: NSObject { | ||
| 615 | 615 | do { |
| 616 | 616 | switch id { |
| 617 | 617 | case "org.narrow.subtree": narrowed = try Narrowing.subtree(at: caret, in: textView.string) |
| 618 | // `org-toggle-narrow-to-subtree`. | |
| 619 | case "org.narrow.toggle": narrowed = narrowed == nil ? try Narrowing.subtree(at: caret, in: textView.string) : nil | |
| 618 | 620 | case "org.narrow.block": narrowed = try Narrowing.block(at: caret, in: textView.string) |
| 619 | 621 | default: narrowed = nil |
| 620 | 622 | } |
Sources/Orgstar/ContentView.swift +3 −1
| @@ -38,6 +38,7 @@ struct ContentView: View { | ||
| 38 | 38 | @AppStorage("logIntoDrawer") private var logIntoDrawer = "" |
| 39 | 39 | @AppStorage("logReschedule") private var logReschedule = "nil" |
| 40 | 40 | @AppStorage("logRedeadline") private var logRedeadline = "nil" |
| 41 | @AppStorage("useSpeedCommands") private var useSpeedCommands = false | |
| 41 | 42 | @AppStorage("hideDrawerStartup") private var hideDrawerStartup = true |
| 42 | 43 | @AppStorage("hideBlockStartup") private var hideBlockStartup = false |
| 43 | 44 | |
| @@ -45,7 +46,8 @@ struct ContentView: View { | ||
| 45 | 46 | Preferences.options( |
| 46 | 47 | tagsColumn: tagsColumn, headingAfterSubtree: headingAfterSubtree, splitLine: splitLine, alphabeticalLists: alphabeticalLists, |
| 47 | 48 | fillColumn: fillColumn, hiddenMarkers: hiddenMarkers, prettyEntities: prettyEntities, logDone: logDone, logIntoDrawer: logIntoDrawer, |
| 48 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, logReschedule: logReschedule, logRedeadline: logRedeadline | |
| 49 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, logReschedule: logReschedule, logRedeadline: logRedeadline, | |
| 50 | useSpeedCommands: useSpeedCommands | |
| 49 | 51 | ) |
| 50 | 52 | } |
| 51 | 53 | /// A block being edited apart (`org-edit-special`). |
Sources/Orgstar/Preferences.swift +3 −2
| @@ -18,7 +18,7 @@ enum Preferences { | ||
| 18 | 18 | static func options( |
| 19 | 19 | tagsColumn: Int, headingAfterSubtree: Bool, splitLine: Bool, alphabeticalLists: Bool, fillColumn: Int, |
| 20 | 20 | hiddenMarkers: Bool, prettyEntities: Bool, logDone: String, logIntoDrawer: String, |
| 21 | hideDrawerStartup: Bool, hideBlockStartup: Bool, logReschedule: String, logRedeadline: String | |
| 21 | hideDrawerStartup: Bool, hideBlockStartup: Bool, logReschedule: String, logRedeadline: String, useSpeedCommands: Bool | |
| 22 | 22 | ) -> EditingOptions { |
| 23 | 23 | EditingOptions( |
| 24 | 24 | tagsColumn: tagsColumn, insertHeadingRespectContent: headingAfterSubtree, |
| @@ -26,7 +26,8 @@ enum Preferences { | ||
| 26 | 26 | hideEmphasisMarkers: hiddenMarkers, prettyEntities: prettyEntities, subSuperscriptsNeedBraces: true, |
| 27 | 27 | logDone: LogHow(rawValue: logDone), logIntoDrawer: logIntoDrawer.isEmpty ? nil : logIntoDrawer, |
| 28 | 28 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, |
| 29 | logReschedule: LogHow(rawValue: logReschedule), logRedeadline: LogHow(rawValue: logRedeadline) | |
| 29 | logReschedule: LogHow(rawValue: logReschedule), logRedeadline: LogHow(rawValue: logRedeadline), | |
| 30 | useSpeedCommands: useSpeedCommands | |
| 30 | 31 | ) |
| 31 | 32 | } |
| 32 | 33 | } |
Tests/OrgCoreTests/KeymapTests.swift +1 −1
| @@ -135,7 +135,7 @@ struct KeymapTests { | ||
| 135 | 135 | #expect(dispatcher.feed(z) == .undefined([c, z])) |
| 136 | 136 | #expect(dispatcher.feed(c) == .pending([c])) |
| 137 | 137 | #expect(dispatcher.feed(KeyDispatcher.quit) == .cancelled) |
| 138 | #expect(dispatcher.feed(KeyChord("a")) == .unbound) | |
| 138 | #expect(dispatcher.feed(KeyChord("z")) == .unbound) | |
| 139 | 139 | } |
| 140 | 140 | |
| 141 | 141 | @Test func continuationsListNextKeys() { |
Tests/OrgCoreTests/PrioritySetTests.swift added +44
| @@ -0,0 +1,44 @@ | ||
| 1 | import Foundation | |
| 2 | import Testing | |
| 3 | @testable import OrgCore | |
| 4 | ||
| 5 | struct PrioritySetTests { | |
| 6 | static let oracle = ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil | |
| 7 | ||
| 8 | @Test(.enabled(if: oracle)) | |
| 9 | func setsPrioritiesLikeOrg() throws { | |
| 10 | let texts = ["* TODO Task :tag:\nbody\n", "* [#B] Plain\n", "* DONE [#A] Done one :x:y:\n", "* Heading\n"] | |
| 11 | // Org reads SPC and lower case only at its prompt. | |
| 12 | let values: [(Character, String)] = [("A", "(org-priority ?A)"), ("C", "(org-priority ?C)"), | |
| 13 | (" ", "(cl-letf (((symbol-function 'read-char-exclusive) (lambda (&rest _) ?\\s))) (org-priority))"), | |
| 14 | ("b", "(cl-letf (((symbol-function 'read-char-exclusive) (lambda (&rest _) ?b))) (org-priority))")] | |
| 15 | var cases: [EmacsOracle.Case] = [] | |
| 16 | var ours: [(String, String?)] = [] | |
| 17 | for text in texts { | |
| 18 | for (value, form) in values { | |
| 19 | cases.append(EmacsOracle.Case(text: text, point: 3, form: form)) | |
| 20 | let result = runCommand(SetPriority(), text, caret: 2, answers: ["priority": String(value)]) | |
| 21 | ours.append((result.text, result.failure)) | |
| 22 | } | |
| 23 | cases.append(EmacsOracle.Case(text: text, point: 3, form: "(org-priority ?D)")) | |
| 24 | let result = runCommand(SetPriority(), text, caret: 2, answers: ["priority": "D"]) | |
| 25 | ours.append((result.text, result.failure)) | |
| 26 | } | |
| 27 | for (i, theirs) in try EmacsOracle.run(cases).enumerated() { | |
| 28 | #expect(ours[i].0 == theirs.text, "\(cases[i].form) on \(cases[i].text.debugDescription)") | |
| 29 | #expect((ours[i].1 != nil) == !theirs.error.isEmpty, "\(cases[i].form): \(ours[i].1 ?? "") vs \(theirs.error)") | |
| 30 | } | |
| 31 | } | |
| 32 | ||
| 33 | @Test func speedKeysHoldAtHeadingStarts() { | |
| 34 | let text = "* One\nbody\n** Two\n" | |
| 35 | func holds(_ caret: Int, _ on: Bool) -> Bool { | |
| 36 | KeyContext.holds("speed", in: EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [caret..<caret], options: EditingOptions(useSpeedCommands: on))) | |
| 37 | } | |
| 38 | #expect(holds(0, true)) | |
| 39 | #expect(holds(11, true)) | |
| 40 | #expect(!holds(1, true)) | |
| 41 | #expect(!holds(6, true)) | |
| 42 | #expect(!holds(0, false)) | |
| 43 | } | |
| 44 | } | |