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
33// C-c C-c (`org-ctrl-c-ctrl-c`, Org 9.8.7) for the contexts no more specific binding takes:
44// headings set tags, clock lines get their duration again, timestamps their weekday, cookies
55// 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.
78
89/// `org-ctrl-c-ctrl-c`.
910public struct CtrlCCtrlC: OrgCommand {
@@ -20,10 +21,19 @@ public struct CtrlCCtrlC: OrgCommand {
2021 if KeyContext.holds("table", in: context) || TableRecalculate.tblfmLine(in: context) != nil {
2122 // At the table's very start the context is the table itself, which org recalculates.
2223 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
2428 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 }
2635 }
36 return TableRecalculate.inEmacs(step, form: "(org-ctrl-c-ctrl-c)", in: context)
2737 }
2838 if buffer.footnoteReferenceAtPoint() != nil
2939 || 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 {
950950 throw EmacsBuffer.UserError(message: Self.emacsMessage + error.reason)
951951 }
952952 }
953 guard case .failed(let message) = step, message.hasPrefix(Self.emacsMessage) else { return step }
954 // Emacs recalculates the table; its text replaces the table here.
953 let form = Self.tblfmLine(in: context) != nil ? "(org-table-calc-current-TBLFM)" : all ? "(org-table-recalculate t)" : "(org-table-recalculate nil)"
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 }
955961 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
957963 if onTblfm {
958964 while buffer.forwardLine(-1) == 0, !buffer.lookingAtLine("^[ \\t]*\\|") {}
959965 }
960966 let table = buffer.tableBegin()..<buffer.tableEnd()
961 let tblfm = onTblfm ? Self.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)"
967 let tblfm = onTblfm ? tblfmLine(in: context) ?? "" : buffer.firstTblfm(after: table.upperBound) ?? ""
963968 return .external(.emacsTable(
964969 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("'(")
966971 ))
967972 }
968973}
Sources/OrgCore/Keymap/Presets.swift +1 −1
@@ -998,7 +998,7 @@ public enum KeymapPreset: String, Sendable, CaseIterable {
998998 command = "org.fill-paragraph"
999999 [[bind]]
10001000 keys = "C-c C-c"
1001 command = "org.table.align"
1001 command = "org.ctrl-c-ctrl-c"
10021002 when = "table"
10031003 [[bind]]
10041004 keys = "C-c C-c"
Tests/OrgCoreTests/KeymapTests.swift +1 −1
@@ -212,6 +212,6 @@ struct CtrlCCtrlCKeyTests {
212212 }
213213 #expect(command(at: 1) == "org.ctrl-c-ctrl-c")
214214 #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")
216216 }
217217}
Tests/OrgCoreTests/TableFormulaEntryTests.swift +5
@@ -13,6 +13,8 @@ struct TableFormulaEntryTests {
1313 let replaced = "| a | b |\n| 1 | =$1*5 |\n#+TBLFM: $2=$1*2::@1$2=x\n"
1414 let reuse = "| a | b |\n| 2 | = |\n#+TBLFM: $2=$1*2\n\nText\n"
1515 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"
1618 let prompt = { (arg: String) in "(cl-letf (((symbol-function 'read-string) (lambda (&rest _) \"\"))) (org-table-eval-formula \(arg)))" }
1719 let cases: [(String, String, any OrgCommand, [String: String], String)] = [
1820 (plain, "*2", TableCommand(.nextField), [:], "(org-table-next-field)"),
@@ -26,6 +28,9 @@ struct TableFormulaEntryTests {
2628 (stored, "| 3", TableEvalFormula(named: false), ["formula": "$1*3"], "(org-table-eval-formula nil \"$1*3\")"),
2729 (stored, "| 3", TableEvalFormula(named: true), ["formula": "99"], "(org-table-eval-formula '(4) \"99\")"),
2830 (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)"),
2934 (stored, "| 3 | 4", TableEvalFormula(named: false), ["formula": ""], prompt("nil")),
3035 (stored, "| 1", TableEvalFormula(named: true), ["formula": ""], prompt("'(4)")),
3136 ]