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 {
1212 @AppStorage("keymap") private var preset = KeymapPreset.emacs
1313 @AppStorage("optionAsMeta") private var optionAsMeta = OptionAsMeta.left.rawValue
1414 @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 }
1523 @State private var selectedFile: String?
1624 @State private var query = ""
1725 @FocusState private var searchFocused: Bool
@@ -35,7 +43,7 @@ struct ContentView: View {
3543 .navigationSplitViewColumnWidth(min: 160, ideal: 220, max: 360)
3644 } detail: {
3745 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)
3947 .safeAreaInset(edge: .bottom, spacing: 0) { EchoArea(commands: commands) }
4048 } else {
4149 ContentUnavailableView(
@@ -65,6 +73,7 @@ struct ContentView: View {
6573 }
6674 }
6775 .onChange(of: saveMode, initial: true) { _, mode in session.saveMode = mode }
76 .onChange(of: options, initial: true) { _, options in session.options = options }
6877 .onChange(of: preset, initial: true) { loadKeymap() }
6978 .sheet(isPresented: $commands.showingPalette) {
7079 PaletteView(keymap: keymap) { id in
Sources/Orgstar/EditorView.swift +2
@@ -11,6 +11,7 @@ struct EditorView: NSViewRepresentable {
1111 let commands: AppCommands
1212 let keymap: Keymap
1313 let optionAsMeta: OptionAsMeta
14 let options: EditingOptions
1415 let onAppCommand: (String) -> Void
1516
1617 final class Coordinator {
@@ -54,6 +55,7 @@ struct EditorView: NSViewRepresentable {
5455 if let editor = coordinator.editor {
5556 if editor.keymap != keymap { editor.keymap = keymap }
5657 editor.optionAsMeta = optionAsMeta
58 editor.options = options
5759 }
5860 container.window?.isDocumentEdited = session.isDirty
5961 if let request = session.revealRequest, request.id != coordinator.revealID {
Sources/Orgstar/OrgstarApp.swift +26 −3
@@ -90,9 +90,15 @@ enum UnsavedChanges {
9090@main
9191struct OrgstarApp: App {
9292 @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 }
9496 @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 }()
96102
97103 var body: some Scene {
98104 Window("Orgstar", id: "main") {
@@ -144,6 +150,11 @@ struct SettingsView: View {
144150 @AppStorage("saveMode") private var saveMode = SaveMode.automatic
145151 @AppStorage("keymap") private var preset = KeymapPreset.emacs
146152 @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
147158
148159 var body: some View {
149160 Form {
@@ -165,8 +176,20 @@ struct SettingsView: View {
165176 .font(.caption)
166177 .foregroundStyle(.secondary)
167178 .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)
168191 }
169192 .padding(20)
170 .frame(width: 420)
193 .frame(width: 480)
171194 }
172195}
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}