Commit f08ff89622
Verified · cmc
Layout: unified · split
Sources/OrgApp/EmacsImport.swift +1 −1
| @@ -448,7 +448,7 @@ public struct EmacsImport: Sendable { | |||
| 448 | guard let n = value.integer else { return note("not a number") } | 448 | guard let n = value.integer else { return note("not a number") } |
| 449 | add(name, .integer(n)) | 449 | add(name, .integer(n)) |
| 450 | case "org-insert-heading-respect-content", "org-list-allow-alphabetical", "org-hide-emphasis-markers", "org-pretty-entities", | 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 | add(name, .bool(truthy)) | 452 | add(name, .bool(truthy)) |
| 453 | case "org-hide-drawer-startup", "org-hide-block-startup": | 453 | case "org-hide-drawer-startup", "org-hide-block-startup": |
| 454 | add(name == "org-hide-drawer-startup" ? "org-cycle-hide-drawer-startup" : "org-cycle-hide-block-startup", .bool(truthy)) | 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 | fallback: .string("nil"), comment: "nil, time or note: log changing or removing a DEADLINE", legacy: ""), | 82 | fallback: .string("nil"), comment: "nil, time or note: log changing or removing a DEADLINE", legacy: ""), |
| 83 | Setting(section: "", key: "org-log-into-drawer", defaultsKey: "logIntoDrawer", kind: .string, fallback: .string(""), | 83 | Setting(section: "", key: "org-log-into-drawer", defaultsKey: "logIntoDrawer", kind: .string, fallback: .string(""), |
| 84 | comment: "the drawer state notes go in (\"LOGBOOK\" is Emacs's t); \"\" for none", legacy: ""), | 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 | Setting(section: "", key: "org-cycle-hide-drawer-startup", defaultsKey: "hideDrawerStartup", kind: .bool, fallback: .bool(true), | 87 | Setting(section: "", key: "org-cycle-hide-drawer-startup", defaultsKey: "hideDrawerStartup", kind: .bool, fallback: .bool(true), |
| 86 | comment: "fold drawers when a file opens; #+STARTUP: nohidedrawers overrides", legacy: ""), | 88 | comment: "fold drawers when a file opens; #+STARTUP: nohidedrawers overrides", legacy: ""), |
| 87 | Setting(section: "", key: "org-cycle-hide-block-startup", defaultsKey: "hideBlockStartup", kind: .bool, fallback: .bool(false), | 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 | public var logRedeadline: LogHow? | 22 | public var logRedeadline: LogHow? |
| 23 | /// `org-use-fast-todo-selection` `auto`: with keys in the keywords, C-c C-t asks for one. | 23 | /// `org-use-fast-todo-selection` `auto`: with keys in the keywords, C-c C-t asks for one. |
| 24 | public var fastTodoSelection: Bool | 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 | /// `org-cycle-hide-drawer-startup`: drawers are folded when a file opens. | 27 | /// `org-cycle-hide-drawer-startup`: drawers are folded when a file opens. |
| 26 | public var hideDrawerStartup: Bool | 28 | public var hideDrawerStartup: Bool |
| 27 | /// `org-cycle-hide-block-startup`: blocks are folded when a file opens. | 29 | /// `org-cycle-hide-block-startup`: blocks are folded when a file opens. |
| @@ -40,8 +42,10 @@ public struct EditingOptions: Sendable, Equatable, Codable { | |||
| 40 | listAllowAlphabetical: Bool = false, logRepeat: Bool = true, fillColumn: Int = 70, | 42 | listAllowAlphabetical: Bool = false, logRepeat: Bool = true, fillColumn: Int = 70, |
| 41 | hideEmphasisMarkers: Bool = false, prettyEntities: Bool = false, subSuperscriptsNeedBraces: Bool = false, | 43 | hideEmphasisMarkers: Bool = false, prettyEntities: Bool = false, subSuperscriptsNeedBraces: Bool = false, |
| 42 | logDone: LogHow? = nil, logIntoDrawer: String? = nil, fastTodoSelection: Bool = true, | 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 | self.logReschedule = logReschedule | 49 | self.logReschedule = logReschedule |
| 46 | self.logRedeadline = logRedeadline | 50 | self.logRedeadline = logRedeadline |
| 47 | self.hideDrawerStartup = hideDrawerStartup | 51 | self.hideDrawerStartup = hideDrawerStartup |
| @@ -235,7 +239,7 @@ public enum Commands { | |||
| 235 | ShiftTimestamp(up: true), ShiftTimestamp(up: false), ShiftTimestampDay(later: true), ShiftTimestampDay(later: false), | 239 | ShiftTimestamp(up: true), ShiftTimestamp(up: false), ShiftTimestampDay(later: true), ShiftTimestampDay(later: false), |
| 236 | OpenLinkAtPoint(), CreateID(), ToggleComment(), ToggleHeading(), ToggleItem(), MarkSubtree(), | 240 | OpenLinkAtPoint(), CreateID(), ToggleComment(), ToggleHeading(), ToggleItem(), MarkSubtree(), |
| 237 | CopySubtree(cut: false), CopySubtree(cut: true), CloneSubtree(), SortEntries(), InsertStructureTemplate(), EditSpecial(), | 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 | public static var everything: [any OrgCommand] { all + TableCommand.Action.allCases.map(TableCommand.init) } | 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 | } else { | 296 | } else { |
| 297 | new = Self.value(priorities.default) | 297 | new = Self.value(priorities.default) |
| 298 | } | 298 | } |
| 299 | let cookie = new.map { "[#\(Self.label($0, numeric: numeric))]" } | 299 | return Self.write(new.map { "[#\(Self.label($0, numeric: numeric))]" }, line: line, context: context) |
| 300 | return line.commit(context) { buffer in | 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 | if let existing = line.priority { | 305 | if let existing = line.priority { |
| 302 | let range = line.local(existing.range) | 306 | let range = line.local(existing.range) |
| 303 | if let cookie { | 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 | public struct PriorityUp: OrgCommand { | 394 | public struct PriorityUp: OrgCommand { |
| 335 | public init() {} | 395 | public init() {} |
| 336 | public var id: String { "org.priority.up" } | 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 | public func keyLabels(for command: String, modes: [String?] = [nil], limit: Int = 2) -> [String] { | 101 | public func keyLabels(for command: String, modes: [String?] = [nil], limit: Int = 2) -> [String] { |
| 102 | var found: [(keys: [KeyChord], rank: [Int])] = [] | 102 | var found: [(keys: [KeyChord], rank: [Int])] = [] |
| 103 | for (order, mode) in modes.enumerated() { | 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 | found.append((binding.keys, [order, binding.when == nil ? 0 : 1, binding.keys.count, found.count])) | 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 | /// Context names a binding's `when` can use. | 132 | /// Context names a binding's `when` can use. |
| 132 | public enum KeyContext { | 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 | public static func holds(_ name: String, in context: EditContext) -> Bool { | 136 | public static func holds(_ name: String, in context: EditContext) -> Bool { |
| 136 | switch name { | 137 | switch name { |
| @@ -147,6 +148,13 @@ public enum KeyContext { | |||
| 147 | case "dblock": | 148 | case "dblock": |
| 148 | let buffer = EmacsBuffer(context.text, point: context.caret, settings: context.tree.settings) | 149 | let buffer = EmacsBuffer(context.text, point: context.caret, settings: context.tree.settings) |
| 149 | return buffer.lookingAtLine(EmacsBuffer.dblockStart) | 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 | default: return false | 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 | public var toml: String { | 23 | public var toml: String { |
| 24 | switch self { | 24 | switch self { |
| 25 | case .emacs: Self.common + Self.emacsBindings | 25 | case .emacs: Self.common + Self.speedBindings(mode: nil) + Self.emacsBindings |
| 26 | case .mac: Self.common + Self.macBindings | 26 | case .mac: Self.common + Self.speedBindings(mode: nil) + Self.macBindings |
| 27 | case .doom: Self.doomToml | 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 | /// Doom: the Emacs preset's org bindings in every evil state (as org-mode-map's are), with | 51 | /// Doom: the Emacs preset's org bindings in every evil state (as org-mode-map's are), with |
| 32 | /// evil-org's `M-h/j/k/l` forms, then Doom's leader, localleader and evil-org keys. | 52 | /// evil-org's `M-h/j/k/l` forms, then Doom's leader, localleader and evil-org keys. |
| 33 | static var doomToml: String { | 53 | static var doomToml: String { |
| @@ -346,6 +366,10 @@ public enum KeymapPreset: String, Sendable, CaseIterable { | |||
| 346 | command = "org.effort.set" | 366 | command = "org.effort.set" |
| 347 | mode = "normal" | 367 | mode = "normal" |
| 348 | [[bind]] | 368 | [[bind]] |
| 369 | keys = "SPC m p p" | ||
| 370 | command = "org.priority.set" | ||
| 371 | mode = "normal" | ||
| 372 | [[bind]] | ||
| 349 | keys = "SPC m p u" | 373 | keys = "SPC m p u" |
| 350 | command = "org.priority.up" | 374 | command = "org.priority.up" |
| 351 | mode = "normal" | 375 | mode = "normal" |
| @@ -682,6 +706,9 @@ public enum KeymapPreset: String, Sendable, CaseIterable { | |||
| 682 | keys = "C-c /" | 706 | keys = "C-c /" |
| 683 | command = "org.sparse-tree" | 707 | command = "org.sparse-tree" |
| 684 | [[bind]] | 708 | [[bind]] |
| 709 | keys = "C-c ," | ||
| 710 | command = "org.priority.set" | ||
| 711 | [[bind]] | ||
| 685 | keys = "C-c C-x C-r" | 712 | keys = "C-c C-x C-r" |
| 686 | command = "org.clock.report" | 713 | command = "org.clock.report" |
| 687 | [[bind]] | 714 | [[bind]] |
Sources/OrgEditorAppKit/EditorCommands.swift +1
| @@ -40,6 +40,7 @@ public enum EditorCommands { | |||
| 40 | EditorCommand(id: "org.narrow.subtree", title: "Narrow to Subtree", action: .view), | 40 | EditorCommand(id: "org.narrow.subtree", title: "Narrow to Subtree", action: .view), |
| 41 | EditorCommand(id: "org.narrow.block", title: "Narrow to Block", action: .view), | 41 | EditorCommand(id: "org.narrow.block", title: "Narrow to Block", action: .view), |
| 42 | EditorCommand(id: "org.widen", title: "Widen", action: .view), | 42 | EditorCommand(id: "org.widen", title: "Widen", action: .view), |
| 43 | EditorCommand(id: "org.narrow.toggle", title: "Narrow to Subtree or Widen", action: .view), | ||
| 43 | EditorCommand(id: "org.table.toggle-column-width", title: "Shrink or Expand Table Column", action: .view), | 44 | EditorCommand(id: "org.table.toggle-column-width", title: "Shrink or Expand Table Column", action: .view), |
| 44 | EditorCommand(id: "org.table.shrink", title: "Shrink Table Columns with Widths", action: .view), | 45 | EditorCommand(id: "org.table.shrink", title: "Shrink Table Columns with Widths", action: .view), |
| 45 | EditorCommand(id: "org.table.expand", title: "Expand Table Columns", action: .view), | 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 | switch id { | 405 | switch id { |
| 406 | case "org.cycle": cycleAtCaret() | 406 | case "org.cycle": cycleAtCaret() |
| 407 | case "org.cycle-global": cycleGlobal() | 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 | default: tableColumnWidth(id) | 409 | default: tableColumnWidth(id) |
| 410 | } | 410 | } |
| 411 | case .edit(let selector, let extending): | 411 | case .edit(let selector, let extending): |
| @@ -615,6 +615,8 @@ public final class OrgEditor: NSObject { | |||
| 615 | do { | 615 | do { |
| 616 | switch id { | 616 | switch id { |
| 617 | case "org.narrow.subtree": narrowed = try Narrowing.subtree(at: caret, in: textView.string) | 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 | case "org.narrow.block": narrowed = try Narrowing.block(at: caret, in: textView.string) | 620 | case "org.narrow.block": narrowed = try Narrowing.block(at: caret, in: textView.string) |
| 619 | default: narrowed = nil | 621 | default: narrowed = nil |
| 620 | } | 622 | } |
Sources/Orgstar/ContentView.swift +3 −1
| @@ -38,6 +38,7 @@ struct ContentView: View { | |||
| 38 | @AppStorage("logIntoDrawer") private var logIntoDrawer = "" | 38 | @AppStorage("logIntoDrawer") private var logIntoDrawer = "" |
| 39 | @AppStorage("logReschedule") private var logReschedule = "nil" | 39 | @AppStorage("logReschedule") private var logReschedule = "nil" |
| 40 | @AppStorage("logRedeadline") private var logRedeadline = "nil" | 40 | @AppStorage("logRedeadline") private var logRedeadline = "nil" |
| 41 | @AppStorage("useSpeedCommands") private var useSpeedCommands = false | ||
| 41 | @AppStorage("hideDrawerStartup") private var hideDrawerStartup = true | 42 | @AppStorage("hideDrawerStartup") private var hideDrawerStartup = true |
| 42 | @AppStorage("hideBlockStartup") private var hideBlockStartup = false | 43 | @AppStorage("hideBlockStartup") private var hideBlockStartup = false |
| 43 | 44 | ||
| @@ -45,7 +46,8 @@ struct ContentView: View { | |||
| 45 | Preferences.options( | 46 | Preferences.options( |
| 46 | tagsColumn: tagsColumn, headingAfterSubtree: headingAfterSubtree, splitLine: splitLine, alphabeticalLists: alphabeticalLists, | 47 | tagsColumn: tagsColumn, headingAfterSubtree: headingAfterSubtree, splitLine: splitLine, alphabeticalLists: alphabeticalLists, |
| 47 | fillColumn: fillColumn, hiddenMarkers: hiddenMarkers, prettyEntities: prettyEntities, logDone: logDone, logIntoDrawer: logIntoDrawer, | 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 | /// A block being edited apart (`org-edit-special`). | 53 | /// A block being edited apart (`org-edit-special`). |
Sources/Orgstar/Preferences.swift +3 −2
| @@ -18,7 +18,7 @@ enum Preferences { | |||
| 18 | static func options( | 18 | static func options( |
| 19 | tagsColumn: Int, headingAfterSubtree: Bool, splitLine: Bool, alphabeticalLists: Bool, fillColumn: Int, | 19 | tagsColumn: Int, headingAfterSubtree: Bool, splitLine: Bool, alphabeticalLists: Bool, fillColumn: Int, |
| 20 | hiddenMarkers: Bool, prettyEntities: Bool, logDone: String, logIntoDrawer: String, | 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 | ) -> EditingOptions { | 22 | ) -> EditingOptions { |
| 23 | EditingOptions( | 23 | EditingOptions( |
| 24 | tagsColumn: tagsColumn, insertHeadingRespectContent: headingAfterSubtree, | 24 | tagsColumn: tagsColumn, insertHeadingRespectContent: headingAfterSubtree, |
| @@ -26,7 +26,8 @@ enum Preferences { | |||
| 26 | hideEmphasisMarkers: hiddenMarkers, prettyEntities: prettyEntities, subSuperscriptsNeedBraces: true, | 26 | hideEmphasisMarkers: hiddenMarkers, prettyEntities: prettyEntities, subSuperscriptsNeedBraces: true, |
| 27 | logDone: LogHow(rawValue: logDone), logIntoDrawer: logIntoDrawer.isEmpty ? nil : logIntoDrawer, | 27 | logDone: LogHow(rawValue: logDone), logIntoDrawer: logIntoDrawer.isEmpty ? nil : logIntoDrawer, |
| 28 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, | 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 | #expect(dispatcher.feed(z) == .undefined([c, z])) | 135 | #expect(dispatcher.feed(z) == .undefined([c, z])) |
| 136 | #expect(dispatcher.feed(c) == .pending([c])) | 136 | #expect(dispatcher.feed(c) == .pending([c])) |
| 137 | #expect(dispatcher.feed(KeyDispatcher.quit) == .cancelled) | 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 | @Test func continuationsListNextKeys() { | 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 | } | ||