krz/orgstar

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

Commit 8847b010a7

8847b010a7b834c174ec4eaa9c7ab0c5ffa1ca46

parent: 9518e3948b

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-05 04:55 UTC

Org options in Settings

Layout: unified · split

Sources/Orgstar/ContentView.swift +10 −1
@@ -12,6 +12,14 @@ struct ContentView: View {
12 @AppStorage("keymap") private var preset = KeymapPreset.emacs 12 @AppStorage("keymap") private var preset = KeymapPreset.emacs
13 @AppStorage("optionAsMeta") private var optionAsMeta = OptionAsMeta.left.rawValue 13 @AppStorage("optionAsMeta") private var optionAsMeta = OptionAsMeta.left.rawValue
14 @State private var keymap = KeymapPreset.emacs.keymap 14 @State private var keymap = KeymapPreset.emacs.keymap
15 @AppStorage("tagsColumn") private var tagsColumn = -77
16 @AppStorage("headingAfterSubtree") private var headingAfterSubtree = true
17 @AppStorage("splitLine") private var splitLine = false
18 @AppStorage("alphabeticalLists") private var alphabeticalLists = true
19
20 private var options: EditingOptions {
21 Preferences.options(tagsColumn: tagsColumn, headingAfterSubtree: headingAfterSubtree, splitLine: splitLine, alphabeticalLists: alphabeticalLists)
22 }
15 @State private var selectedFile: String? 23 @State private var selectedFile: String?
16 @State private var query = "" 24 @State private var query = ""
17 @FocusState private var searchFocused: Bool 25 @FocusState private var searchFocused: Bool
@@ -35,7 +43,7 @@ struct ContentView: View {
35 .navigationSplitViewColumnWidth(min: 160, ideal: 220, max: 360) 43 .navigationSplitViewColumnWidth(min: 160, ideal: 220, max: 360)
36 } detail: { 44 } detail: {
37 if session.path != nil { 45 if session.path != nil {
38 EditorView(session: session, commands: commands, keymap: keymap, optionAsMeta: OptionAsMeta(rawValue: optionAsMeta), onAppCommand: perform) 46 EditorView(session: session, commands: commands, keymap: keymap, optionAsMeta: OptionAsMeta(rawValue: optionAsMeta), options: options, onAppCommand: perform)
39 .safeAreaInset(edge: .bottom, spacing: 0) { EchoArea(commands: commands) } 47 .safeAreaInset(edge: .bottom, spacing: 0) { EchoArea(commands: commands) }
40 } else { 48 } else {
41 ContentUnavailableView( 49 ContentUnavailableView(
@@ -65,6 +73,7 @@ struct ContentView: View {
65 } 73 }
66 } 74 }
67 .onChange(of: saveMode, initial: true) { _, mode in session.saveMode = mode } 75 .onChange(of: saveMode, initial: true) { _, mode in session.saveMode = mode }
76 .onChange(of: options, initial: true) { _, options in session.options = options }
68 .onChange(of: preset, initial: true) { loadKeymap() } 77 .onChange(of: preset, initial: true) { loadKeymap() }
69 .sheet(isPresented: $commands.showingPalette) { 78 .sheet(isPresented: $commands.showingPalette) {
70 PaletteView(keymap: keymap) { id in 79 PaletteView(keymap: keymap) { id in
Sources/Orgstar/EditorView.swift +2
@@ -11,6 +11,7 @@ struct EditorView: NSViewRepresentable {
11 let commands: AppCommands 11 let commands: AppCommands
12 let keymap: Keymap 12 let keymap: Keymap
13 let optionAsMeta: OptionAsMeta 13 let optionAsMeta: OptionAsMeta
14 let options: EditingOptions
14 let onAppCommand: (String) -> Void 15 let onAppCommand: (String) -> Void
15 16
16 final class Coordinator { 17 final class Coordinator {
@@ -54,6 +55,7 @@ struct EditorView: NSViewRepresentable {
54 if let editor = coordinator.editor { 55 if let editor = coordinator.editor {
55 if editor.keymap != keymap { editor.keymap = keymap } 56 if editor.keymap != keymap { editor.keymap = keymap }
56 editor.optionAsMeta = optionAsMeta 57 editor.optionAsMeta = optionAsMeta
58 editor.options = options
57 } 59 }
58 container.window?.isDocumentEdited = session.isDirty 60 container.window?.isDocumentEdited = session.isDirty
59 if let request = session.revealRequest, request.id != coordinator.revealID { 61 if let request = session.revealRequest, request.id != coordinator.revealID {
Sources/Orgstar/OrgstarApp.swift +26 −3
@@ -90,9 +90,15 @@ enum UnsavedChanges {
90@main 90@main
91struct OrgstarApp: App { 91struct OrgstarApp: App {
92 @NSApplicationDelegateAdaptor private var delegate: AppDelegate 92 @NSApplicationDelegateAdaptor private var delegate: AppDelegate
93 @State private var workspace = Result { try WorkspaceModel(directory: WorkspaceModel.defaultDirectory) } 93 @State private var workspace = Result {
94 try WorkspaceModel(directory: WorkspaceModel.defaultDirectory, settings: .with(todoKeywords: Preferences.orgSettings))
95 }
94 @State private var commands = AppCommands() 96 @State private var commands = AppCommands()
95 @State private var session = DocumentSession() 97 @State private var session: DocumentSession = {
98 let session = DocumentSession()
99 session.defaults = Preferences.orgSettings
100 return session
101 }()
96 102
97 var body: some Scene { 103 var body: some Scene {
98 Window("Orgstar", id: "main") { 104 Window("Orgstar", id: "main") {
@@ -144,6 +150,11 @@ struct SettingsView: View {
144 @AppStorage("saveMode") private var saveMode = SaveMode.automatic 150 @AppStorage("saveMode") private var saveMode = SaveMode.automatic
145 @AppStorage("keymap") private var preset = KeymapPreset.emacs 151 @AppStorage("keymap") private var preset = KeymapPreset.emacs
146 @AppStorage("optionAsMeta") private var optionAsMeta = OptionAsMeta.left.rawValue 152 @AppStorage("optionAsMeta") private var optionAsMeta = OptionAsMeta.left.rawValue
153 @AppStorage("tagsColumn") private var tagsColumn = -77
154 @AppStorage("headingAfterSubtree") private var headingAfterSubtree = true
155 @AppStorage("splitLine") private var splitLine = false
156 @AppStorage("alphabeticalLists") private var alphabeticalLists = true
157 @AppStorage("todoKeywords") private var todoKeywords = Preferences.defaultTodoKeywords
147 158
148 var body: some View { 159 var body: some View {
149 Form { 160 Form {
@@ -165,8 +176,20 @@ struct SettingsView: View {
165 .font(.caption) 176 .font(.caption)
166 .foregroundStyle(.secondary) 177 .foregroundStyle(.secondary)
167 .textSelection(.enabled) 178 .textSelection(.enabled)
179 Divider()
180 Picker("Tags:", selection: $tagsColumn) {
181 Text("Aligned to end at column 77").tag(-77)
182 Text("One space after the title").tag(0)
183 }
184 Toggle("M-RET adds the new heading after the subtree", isOn: $headingAfterSubtree)
185 Toggle("M-RET splits the line at the caret", isOn: $splitLine)
186 Toggle("Lists can use letters (a. b. c.)", isOn: $alphabeticalLists)
187 TextField("Default TODO keywords:", text: $todoKeywords)
188 Text("For files without #+TODO, in #+TODO syntax. Takes effect when Orgstar restarts.")
189 .font(.caption)
190 .foregroundStyle(.secondary)
168 } 191 }
169 .padding(20) 192 .padding(20)
170 .frame(width: 420) 193 .frame(width: 480)
171 } 194 }
172} 195}
Sources/Orgstar/Preferences.swift added +20
@@ -0,0 +1,20 @@
1import Foundation
2import OrgCore
3
4/// Org preferences, stored in user defaults by the Settings window.
5enum Preferences {
6 static let defaultTodoKeywords = "TODO PROJ LOOP STRT WAIT HOLD IDEA | DONE KILL"
7
8 /// TODO keywords for files without `#+TODO`, read at launch.
9 static var orgSettings: OrgSettings {
10 let line = UserDefaults.standard.string(forKey: "todoKeywords") ?? defaultTodoKeywords
11 return OrgSettings.default.withTodoKeywords(line) ?? .default
12 }
13
14 static func options(tagsColumn: Int, headingAfterSubtree: Bool, splitLine: Bool, alphabeticalLists: Bool) -> EditingOptions {
15 EditingOptions(
16 tagsColumn: tagsColumn, insertHeadingRespectContent: headingAfterSubtree,
17 metaReturnMaySplitLine: splitLine, listAllowAlphabetical: alphabeticalLists
18 )
19 }
20}