krz/orgstar

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

Commit e4003d118e

e4003d118e0bebe476c67fcaf17455fd815bd5cd

parent: aeb1840b38

Verified · cmc

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

C-c C-c in a table runs org-ctrl-c-ctrl-c

The Emacs preset (and Doom, built from it) bound C-c C-c in tables to org.table.align. org-ctrl-c-ctrl-c now evaluates a typed formula, then recalculates a row marked # or aligns, falling back to Emacs when the row needs it.

Layout: unified · split

Sources/OrgCore/Commands/CtrlCCtrlC.swift +13 −3
@@ -3,7 +3,8 @@ import Foundation
3// C-c C-c (`org-ctrl-c-ctrl-c`, Org 9.8.7) for the contexts no more specific binding takes: 3// C-c C-c (`org-ctrl-c-ctrl-c`, Org 9.8.7) for the contexts no more specific binding takes:
4// headings set tags, clock lines get their duration again, timestamps their weekday, cookies 4// headings set tags, clock lines get their duration again, timestamps their weekday, cookies
5// update, items toggle their checkbox, or without one have their list repaired, setup keywords 5// update, items toggle their checkbox, or without one have their list repaired, setup keywords
6// refresh, and elsewhere there's nothing to do. 6// refresh, tables evaluate a typed formula, then align or recalculate a row marked `#`, and
7// elsewhere there's nothing to do.
7 8
8/// `org-ctrl-c-ctrl-c`. 9/// `org-ctrl-c-ctrl-c`.
9public struct CtrlCCtrlC: OrgCommand { 10public struct CtrlCCtrlC: OrgCommand {
@@ -20,10 +21,19 @@ public struct CtrlCCtrlC: OrgCommand {
20 if KeyContext.holds("table", in: context) || TableRecalculate.tblfmLine(in: context) != nil { 21 if KeyContext.holds("table", in: context) || TableRecalculate.tblfmLine(in: context) != nil {
21 // At the table's very start the context is the table itself, which org recalculates. 22 // At the table's very start the context is the table itself, which org recalculates.
22 let atStart = buffer.saveExcursion { buffer.tableBegin() } == context.caret 23 let atStart = buffer.saveExcursion { buffer.tableBegin() } == context.caret
23 return TableRecalculate.tblfmLine(in: context) != nil || atStart ? TableRecalculate(all: true).run(in: context) : commitBuffer(context) { buffer in 24 if TableRecalculate.tblfmLine(in: context) != nil || atStart { return TableRecalculate(all: true).run(in: context) }
25 // `org-table-maybe-eval-formula`, then `org-table-maybe-recalculate-line` on a row
26 // marked `#`, which aligns, or else `org-table-align`.
27 let step = commitBuffer(context) { buffer in
24 try buffer.tableMaybeEvalFormula() 28 try buffer.tableMaybeEvalFormula()
25 buffer.tableAlign() 29 guard buffer.lookingAtLine("^[ \\t]*\\| *# *(\\||$)") else { return buffer.tableAlign() }
30 do {
31 try buffer.tableRecalculate(all: false)
32 } catch let error as Calc.Unsupported {
33 throw EmacsBuffer.UserError(message: TableRecalculate.emacsMessage + error.reason)
34 }
26 } 35 }
36 return TableRecalculate.inEmacs(step, form: "(org-ctrl-c-ctrl-c)", in: context)
27 } 37 }
28 if buffer.footnoteReferenceAtPoint() != nil 38 if buffer.footnoteReferenceAtPoint() != nil
29 || buffer.footnoteDefinitions().contains(where: { $0.begin <= context.caret && context.caret < ($0.contentsBegin ?? $0.end) }) { 39 || buffer.footnoteDefinitions().contains(where: { $0.begin <= context.caret && context.caret < ($0.contentsBegin ?? $0.end) }) {
Sources/OrgCore/Compute/TableFormulas.swift +11 −6
@@ -950,19 +950,24 @@ public struct TableRecalculate: OrgCommand {
950 throw EmacsBuffer.UserError(message: Self.emacsMessage + error.reason) 950 throw EmacsBuffer.UserError(message: Self.emacsMessage + error.reason)
951 } 951 }
952 } 952 }
953 guard case .failed(let message) = step, message.hasPrefix(Self.emacsMessage) else { return step } 953 let form = Self.tblfmLine(in: context) != nil ? "(org-table-calc-current-TBLFM)" : all ? "(org-table-recalculate t)" : "(org-table-recalculate nil)"
954 // Emacs recalculates the table; its text replaces the table here. 954 return Self.inEmacs(step, form: form, in: context)
955 }
956
957 /// A step that failed with `emacsMessage` as a request to run `form` on the table in
958 /// Emacs, whose text then replaces the table here; other steps unchanged.
959 static func inEmacs(_ step: CommandStep, form: String, in context: EditContext) -> CommandStep {
960 guard case .failed(let message) = step, message.hasPrefix(emacsMessage) else { return step }
955 let buffer = EmacsBuffer(context.text, point: context.caret, settings: context.tree.settings, options: context.options) 961 let buffer = EmacsBuffer(context.text, point: context.caret, settings: context.tree.settings, options: context.options)
956 let onTblfm = Self.tblfmLine(in: context) != nil 962 let onTblfm = tblfmLine(in: context) != nil
957 if onTblfm { 963 if onTblfm {
958 while buffer.forwardLine(-1) == 0, !buffer.lookingAtLine("^[ \\t]*\\|") {} 964 while buffer.forwardLine(-1) == 0, !buffer.lookingAtLine("^[ \\t]*\\|") {}
959 } 965 }
960 let table = buffer.tableBegin()..<buffer.tableEnd() 966 let table = buffer.tableBegin()..<buffer.tableEnd()
961 let tblfm = onTblfm ? Self.tblfmLine(in: context) ?? "" : buffer.firstTblfm(after: table.upperBound) ?? "" 967 let tblfm = onTblfm ? tblfmLine(in: context) ?? "" : buffer.firstTblfm(after: table.upperBound) ?? ""
962 let form = onTblfm ? "(org-table-calc-current-TBLFM)" : all ? "(org-table-recalculate t)" : "(org-table-recalculate nil)"
963 return .external(.emacsTable( 968 return .external(.emacsTable(
964 table: table, point: context.caret, form: form, 969 table: table, point: context.caret, form: form,
965 reason: String(message.dropFirst(Self.emacsMessage.count)), lisp: tblfm.contains("'(") 970 reason: String(message.dropFirst(emacsMessage.count)), lisp: tblfm.contains("'(")
966 )) 971 ))
967 } 972 }
968} 973}
Sources/OrgCore/Keymap/Presets.swift +1 −1
@@ -998,7 +998,7 @@ public enum KeymapPreset: String, Sendable, CaseIterable {
998 command = "org.fill-paragraph" 998 command = "org.fill-paragraph"
999 [[bind]] 999 [[bind]]
1000 keys = "C-c C-c" 1000 keys = "C-c C-c"
1001 command = "org.table.align" 1001 command = "org.ctrl-c-ctrl-c"
1002 when = "table" 1002 when = "table"
1003 [[bind]] 1003 [[bind]]
1004 keys = "C-c C-c" 1004 keys = "C-c C-c"
Tests/OrgCoreTests/KeymapTests.swift +1 −1
@@ -212,6 +212,6 @@ struct CtrlCCtrlCKeyTests {
212 } 212 }
213 #expect(command(at: 1) == "org.ctrl-c-ctrl-c") 213 #expect(command(at: 1) == "org.ctrl-c-ctrl-c")
214 #expect(command(at: 5) == "org.ctrl-c-ctrl-c") 214 #expect(command(at: 5) == "org.ctrl-c-ctrl-c")
215 #expect(command(at: 13) == "org.table.align") 215 #expect(command(at: 13) == "org.ctrl-c-ctrl-c")
216 } 216 }
217} 217}
Tests/OrgCoreTests/TableFormulaEntryTests.swift +5
@@ -13,6 +13,8 @@ struct TableFormulaEntryTests {
13 let replaced = "| a | b |\n| 1 | =$1*5 |\n#+TBLFM: $2=$1*2::@1$2=x\n" 13 let replaced = "| a | b |\n| 1 | =$1*5 |\n#+TBLFM: $2=$1*2::@1$2=x\n"
14 let reuse = "| a | b |\n| 2 | = |\n#+TBLFM: $2=$1*2\n\nText\n" 14 let reuse = "| a | b |\n| 2 | = |\n#+TBLFM: $2=$1*2\n\nText\n"
15 let last = "| 1 | =$1+1 |\n" 15 let last = "| 1 | =$1+1 |\n"
16 let marked = "| | x | y |\n|---+---+---|\n| # | 2 | |\n| | 3 | |\n#+TBLFM: $3=$2*10\n"
17 let markedTyped = "| # | 2 | =$2+1 | |\n| # | 5 | | |\n#+TBLFM: $4=$2*2\n"
16 let prompt = { (arg: String) in "(cl-letf (((symbol-function 'read-string) (lambda (&rest _) \"\"))) (org-table-eval-formula \(arg)))" } 18 let prompt = { (arg: String) in "(cl-letf (((symbol-function 'read-string) (lambda (&rest _) \"\"))) (org-table-eval-formula \(arg)))" }
17 let cases: [(String, String, any OrgCommand, [String: String], String)] = [ 19 let cases: [(String, String, any OrgCommand, [String: String], String)] = [
18 (plain, "*2", TableCommand(.nextField), [:], "(org-table-next-field)"), 20 (plain, "*2", TableCommand(.nextField), [:], "(org-table-next-field)"),
@@ -26,6 +28,9 @@ struct TableFormulaEntryTests {
26 (stored, "| 3", TableEvalFormula(named: false), ["formula": "$1*3"], "(org-table-eval-formula nil \"$1*3\")"), 28 (stored, "| 3", TableEvalFormula(named: false), ["formula": "$1*3"], "(org-table-eval-formula nil \"$1*3\")"),
27 (stored, "| 3", TableEvalFormula(named: true), ["formula": "99"], "(org-table-eval-formula '(4) \"99\")"), 29 (stored, "| 3", TableEvalFormula(named: true), ["formula": "99"], "(org-table-eval-formula '(4) \"99\")"),
28 (stored, "| 1 | 2", CtrlCCtrlC(), [:], "(org-ctrl-c-ctrl-c)"), 30 (stored, "| 1 | 2", CtrlCCtrlC(), [:], "(org-ctrl-c-ctrl-c)"),
31 (marked, "| 2", CtrlCCtrlC(), [:], "(org-ctrl-c-ctrl-c)"),
32 (marked, "| 3", CtrlCCtrlC(), [:], "(org-ctrl-c-ctrl-c)"),
33 (markedTyped, "+1", CtrlCCtrlC(), [:], "(org-ctrl-c-ctrl-c)"),
29 (stored, "| 3 | 4", TableEvalFormula(named: false), ["formula": ""], prompt("nil")), 34 (stored, "| 3 | 4", TableEvalFormula(named: false), ["formula": ""], prompt("nil")),
30 (stored, "| 1", TableEvalFormula(named: true), ["formula": ""], prompt("'(4)")), 35 (stored, "| 1", TableEvalFormula(named: true), ["formula": ""], prompt("'(4)")),
31 ] 36 ]