krz/orgstar

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

Commit 5d3019a3c9

5d3019a3c99437780e1c4a6c913a9afbaca3b13f

parent: ecf09db5d3

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-05 03:30 UTC

Match org's heading and keyword rules

Layout: unified · split

Sources/OrgCore/Parser/Lines.swift +3 −2
@@ -73,10 +73,11 @@ func classifyLine(_ line: Substring) -> ClassifiedLine {
7373private func lineClass(_ rest: Substring, columnZero: Bool) -> LineClass {
7474 let trimmed = rest.trimmingTrailingWhitespace
7575
76 // As org's `org-outline-regexp`: stars and then a space. A lone `*`, or stars before a tab,
77 // is not a heading.
7678 if columnZero, rest.first == "*" {
7779 let stars = rest.prefix { $0 == "*" }
78 let after = rest.dropFirst(stars.count)
79 if after.isEmpty || after.first == " " || after.first == "\t" {
80 if rest.dropFirst(stars.count).first == " " {
8081 return .heading(level: stars.count)
8182 }
8283 }
Sources/OrgCore/Parser/Parser.swift +3 −1
@@ -390,8 +390,10 @@ struct Parser {
390390 builder.token(.stars, stars)
391391 rest = whitespace(rest.dropFirst(stars.count))
392392
393 // As org: a keyword counts only before a space or the end of the line, not a tab.
393394 let word = rest.prefix { $0 != " " && $0 != "\t" }
394 if !word.isEmpty, settings.todoKeywordNames.contains(String(word)) {
395 let afterWord = rest.dropFirst(word.count).first
396 if !word.isEmpty, afterWord == nil || afterWord == " ", settings.todoKeywordNames.contains(String(word)) {
395397 builder.token(.todoKeyword, word)
396398 rest = whitespace(rest.dropFirst(word.count))
397399 }
Tests/OrgCoreTests/LinesTests.swift +3 −1
@@ -20,7 +20,9 @@ struct LinesTests {
2020 (" \t", .blank),
2121 ("* a", .heading(level: 1)),
2222 ("*** ", .heading(level: 3)),
23 ("*", .heading(level: 1)),
23 ("*", .plain),
24 ("*\ttab", .plain),
25 ("* ", .heading(level: 1)),
2426 ("*bold* text", .plain),
2527 (" * a", .listItem),
2628 ("#+BEGIN_SRC sh :results output", .blockBegin(name: "src")),
Tests/OrgCoreTests/ParserSectionTests.swift +6
@@ -43,6 +43,12 @@ struct ParserSectionTests {
4343 #expect(todo == ["NEXT"])
4444 }
4545
46 @Test func todoKeywordNeedsASpaceAfterIt() {
47 #expect(tokens(of: .heading, in: "* TODO a\n").contains { $0.kind == .todoKeyword })
48 #expect(tokens(of: .heading, in: "* TODO\n").contains { $0.kind == .todoKeyword })
49 #expect(!tokens(of: .heading, in: "* TODO\ta\n").contains { $0.kind == .todoKeyword })
50 }
51
4652 @Test func priorityNeedsValidValueAndSpace() {
4753 #expect(tokens(of: .heading, in: "* [#B] x\n").contains { $0.kind == .priority })
4854 #expect(tokens(of: .heading, in: "* [#10] x\n").contains { $0.kind == .priority })