krz/orgstar

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

Commit 3f1506d171

3f1506d1716dccbce092bb4304aff844922f2f71

parent: 7f4beb62cf

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-08 03:29 UTC

Agenda prefix %T shows the item's last tag

Org's field regexp matches T under case-fold-search; it was left as
literal text here.

Layout: unified · split

Sources/OrgCore/Agenda/PrefixFormat.swift +4 −3
@@ -1,8 +1,9 @@
1import Foundation 1import Foundation
2 2
3/// `org-agenda-prefix-format` for one view, compiled as `org-compile-prefix-format` does: 3/// `org-agenda-prefix-format` for one view, compiled as `org-compile-prefix-format` does:
4/// `%c` category, `%t` time, `%l` level, `%s` extra, `%e` effort, `%b` breadcrumbs, `%i` icon 4/// `%c` category, `%t` time, `%l` level, `%s` extra, `%e` effort, `%b` breadcrumbs, `%T` last
5/// (none in Orgstar), each with an optional `?` (empty when the value is), a `%-12`-style 5/// tag (Org's regexp lists only lower-case keys but matches `T` under `case-fold-search`),
6/// `%i` icon (none in Orgstar), each with an optional `?` (empty when the value is), a `%-12`-style
6/// width with an optional `.N` limit on categories, and a punctuation character added after a 7/// width with an optional `.N` limit on categories, and a punctuation character added after a
7/// non-empty value. `%(…)` forms run Lisp; they are empty here. 8/// non-empty value. `%(…)` forms run Lisp; they are empty here.
8public struct PrefixFormat: Sendable, Equatable { 9public struct PrefixFormat: Sendable, Equatable {
@@ -18,7 +19,7 @@ public struct PrefixFormat: Sendable, Equatable {
18 let categoryMaxLength: Int? 19 let categoryMaxLength: Int?
19 20
20 public init(_ format: String) { 21 public init(_ format: String) {
21 let regex = try! NSRegularExpression(pattern: "%(\\?)?([-+]?[0-9.]*)([ .;,:!?=|/<>]?)([cltseib]|\\(.+?\\))") 22 let regex = try! NSRegularExpression(pattern: "%(\\?)?([-+]?[0-9.]*)([ .;,:!?=|/<>]?)([cltseibT]|\\(.+?\\))")
22 let ns = format as NSString 23 let ns = format as NSString
23 var pieces: [Piece] = [] 24 var pieces: [Piece] = []
24 var hasTime = false 25 var hasTime = false
Tests/OrgCoreTests/AgendaTests.swift +18
@@ -437,6 +437,24 @@ struct AgendaPrefixFormatTests {
437 } 437 }
438 } 438 }
439 439
440 /// `%T` is the last tag: Org's field regexp lists only lower-case keys, but matches it
441 /// under `case-fold-search`.
442 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
443 func lastTagMatchesEmacs() throws {
444 let today = Days.absolute(year: 2026, month: 10, day: 5)
445 let formats = ["agenda": " %-10:c%-8T%?-12t% s", "todo": " %?-6T|", "tags": "%c %-6:T", "search": " %i %-12:c"]
446 try AgendaOracle.compare(files: [("p.org", Self.file)], start: today, span: 2, today: today, prefix: formats)
447 var options = AgendaOptions()
448 options.prefixFormat = formats
449 let lisp = AgendaOracle.prefixLisp(formats)
450 try AgendaOracle.compareList(files: [("p.org", Self.file)], form: "(let ((org-agenda-prefix-format \(lisp))) (org-todo-list))", today: today) {
451 Agenda.todoList($0, options: options)
452 }
453 try AgendaOracle.compareList(files: [("p.org", Self.file)], form: "(let ((org-agenda-prefix-format \(lisp))) (org-tags-view nil \"work|proj\"))", today: today) {
454 Agenda.tagsMatch($0, match: "work|proj", options: options)
455 }
456 }
457
440 @Test func compilesFields() { 458 @Test func compilesFields() {
441 let format = PrefixFormat(" %-8.6c|%?-4t%e") 459 let format = PrefixFormat(" %-8.6c|%?-4t%e")
442 #expect(format.render(["c": "category", "e": "1:00"]) == " categ |1:00") 460 #expect(format.render(["c": "category", "e": "1:00"]) == " categ |1:00")