Commit 15dd64c4f2
Verified · cmc
Layout: unified · split
Sources/OrgCore/Commands/HeadingCommands.swift +7 −4
| @@ -186,8 +186,10 @@ public struct TodoCycle: OrgCommand { | |||
| 186 | entryHeading(at: context.caret, in: context.tree) != nil | 186 | entryHeading(at: context.caret, in: context.tree) != nil |
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | /// No keyword, then each keyword of its sequence in order, then no keyword again. | 189 | /// No keyword, then each keyword of its sequence in order, then no keyword again. A |
| 190 | static func next(after current: String?, in settings: OrgSettings) -> String? { | 190 | /// `#+TYP_TODO` keyword goes to the sequence's first done keyword unless `repeated` (the |
| 191 | /// previous command was also this one, `this-command` eq `last-command` in `org-todo`). | ||
| 192 | static func next(after current: String?, in settings: OrgSettings, repeated: Bool) -> String? { | ||
| 191 | guard let current else { | 193 | guard let current else { |
| 192 | guard let first = settings.todoSequences.first else { return nil } | 194 | guard let first = settings.todoSequences.first else { return nil } |
| 193 | return (first.active + first.done).first?.name | 195 | return (first.active + first.done).first?.name |
| @@ -195,7 +197,8 @@ public struct TodoCycle: OrgCommand { | |||
| 195 | for sequence in settings.todoSequences { | 197 | for sequence in settings.todoSequences { |
| 196 | let names = (sequence.active + sequence.done).map(\.name) | 198 | let names = (sequence.active + sequence.done).map(\.name) |
| 197 | if let index = names.firstIndex(of: current) { | 199 | if let index = names.firstIndex(of: current) { |
| 198 | return index + 1 < names.count ? names[index + 1] : nil | 200 | guard index + 1 < names.count else { return nil } |
| 201 | return sequence.kind == .type && !repeated ? sequence.done.first?.name : names[index + 1] | ||
| 199 | } | 202 | } |
| 200 | } | 203 | } |
| 201 | return nil | 204 | return nil |
| @@ -229,7 +232,7 @@ public struct TodoCycle: OrgCommand { | |||
| 229 | return .failed("Before first headline") | 232 | return .failed("Before first headline") |
| 230 | } | 233 | } |
| 231 | let line = HeadingLine(heading) | 234 | let line = HeadingLine(heading) |
| 232 | let next = Self.next(after: line.todo?.text, in: context.tree.settings) | 235 | let next = Self.next(after: line.todo?.text, in: context.tree.settings, repeated: context.lastCommand == id) |
| 233 | return line.commit(context) { buffer in | 236 | return line.commit(context) { buffer in |
| 234 | let text = buffer.string as NSString | 237 | let text = buffer.string as NSString |
| 235 | let regionStart = line.stars.upperBound - line.start | 238 | let regionStart = line.stars.upperBound - line.start |
Tests/OrgCoreTests/HeadingCommandTests.swift +29
| @@ -13,6 +13,35 @@ struct HeadingCommandTests { | |||
| 13 | #expect(runCommand(cycle, "text\n", caret: 1).failure != nil) | 13 | #expect(runCommand(cycle, "text\n", caret: 1).failure != nil) |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | /// `#+TYP_TODO`: a first C-c C-t goes to the done keyword, a repeated one to the next type. | ||
| 17 | @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil)) | ||
| 18 | func typeKeywordsMatchEmacs() throws { | ||
| 19 | let texts = [ | ||
| 20 | "#+TYP_TODO: Fred Sara Lucy | DONE\n* Fred a\n", "#+TYP_TODO: Fred Sara Lucy | DONE\n* Sara a\n", | ||
| 21 | "#+TYP_TODO: Fred Sara Lucy | DONE\n* Lucy a\n", "#+TYP_TODO: Fred Sara Lucy | DONE\n* DONE a\n", | ||
| 22 | "#+TYP_TODO: Fred Sara | DONE GONE\n* Sara a\n", "#+TYP_TODO: Fred Sara | DONE GONE\n* DONE a\n", | ||
| 23 | "#+TYP_TODO: Fred Sara | DONE\n* a\n", "#+TODO: TODO | DONE\n#+TYP_TODO: Fred Sara | OK\n* Fred a\n", | ||
| 24 | ] | ||
| 25 | var cases: [EmacsOracle.Case] = [] | ||
| 26 | var ours: [String] = [] | ||
| 27 | for text in texts { | ||
| 28 | let caret = (text as NSString).range(of: "\n*").location + 2 | ||
| 29 | for repeated in [false, true] { | ||
| 30 | let last = repeated ? "'org-todo" : "'other" | ||
| 31 | cases.append(EmacsOracle.Case(text: text, point: EmacsOracle.point(caret, in: text), form: "(let ((this-command 'org-todo) (last-command \(last))) (org-todo))")) | ||
| 32 | let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [caret..<caret], lastCommand: repeated ? TodoCycle().id : "org.other") | ||
| 33 | guard case .commit(let result) = TodoCycle().run(in: context) else { ours.append("failed"); continue } | ||
| 34 | ours.append(result.edits.sorted { $0.range.lowerBound > $1.range.lowerBound }.reduce(text) { $1.apply(to: $0) }) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | let emacs = try EmacsOracle.run(cases) | ||
| 38 | #expect(emacs.count == ours.count) | ||
| 39 | for (mine, emacs) in zip(ours, emacs) { | ||
| 40 | #expect(mine == emacs.text) | ||
| 41 | } | ||
| 42 | #expect(ours[0].hasSuffix("* DONE a\n") && ours[1].hasSuffix("* Sara a\n")) | ||
| 43 | } | ||
| 44 | |||
| 16 | @Test func caretMovesToTheTitleWhenBeforeIt() { | 45 | @Test func caretMovesToTheTitleWhenBeforeIt() { |
| 17 | #expect(runCommand(TodoCycle(), "* a\n", caret: 2).caret == 7) | 46 | #expect(runCommand(TodoCycle(), "* a\n", caret: 2).caret == 7) |
| 18 | #expect(runCommand(TodoCycle(), "* TODO abc\nbody\n", caret: 13).caret == 13) | 47 | #expect(runCommand(TodoCycle(), "* TODO abc\nbody\n", caret: 13).caret == 13) |