krz/orgstar

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

Commit d8a3718c32

d8a3718c3265dc2024016bdf179e00d633ce47ea

parent: 2794492110

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-08 04:56 UTC

Read header-args properties for calls where the call is

org-babel-lob-get-info merges the header-args properties at the #+CALL line or
inline call after org-babel-default-lob-header-args, for evaluation and :exports.
An inline call's {{{results(...)}}} stays in export whatever :exports says.

Layout: unified · split

Sources/OrgCore/Compute/Babel.swift +32 −19
@@ -190,7 +190,7 @@ public enum Babel {
190190 guard let m = line.firstMatch(of: #/(?i)^[ \t]*#\+CALL:[ \t]*([^\s(\[]+)(?:\[([^\]]*)\])?\((.*?)\)[ \t]*(.*?)[ \t]*\n?$/#) else { return nil }
191191 let name = String(m.1)
192192 guard let block = model.srcBlocks.first(where: { $0.name == name }) else { throw Failure.message("Reference '\(name)' not found in this buffer") }
193 var extra = [lobDefaults]
193 var extra = [lobDefaults] + callProperties(block, at: lineRange.location, model: model)
194194 if let inside = m.2 { extra.append(Params.parse(String(inside))) }
195195 extra.append(splitArguments(String(m.3)).map { (":var", $0) })
196196 if !m.4.isEmpty { extra.append(Params.parse(String(m.4))) }
@@ -216,13 +216,20 @@ public enum Babel {
216216 guard let object = found, let m = object.text.firstMatch(of: /^call_([^()\[\]\s]+)(?:\[([^\]]*)\])?\((.*?)\)(?:\[([^\]]*)\])?$/) else { return nil }
217217 let name = String(m.1)
218218 guard let block = model.srcBlocks.first(where: { $0.name == name }) else { throw Failure.message("Reference '\(name)' not found in this buffer") }
219 var extra = [lobDefaults]
219 var extra = [lobDefaults] + callProperties(block, at: object.range.lowerBound, model: model)
220220 if let inside = m.2 { extra.append(Params.parse(String(inside))) }
221221 extra.append(splitArguments(String(m.3)).map { (":var", $0) })
222222 if let end = m.4 { extra.append(Params.parse(String(end))) }
223223 return (block, try Params(block: block, model: model, text: text, extra: extra), object.range)
224224 }
225225
226 /// The `header-args` properties where a call is, in the called block's language, which
227 /// `org-babel-lob-get-info` puts after `lobDefaults`.
228 static func callProperties(_ block: SrcBlockInfo, at location: Int, model: DocumentModel) -> [[(String, String)]] {
229 let heading = model.headings.lastIndex { $0.headingRange.lowerBound <= location }
230 return Params.propertyLayers(language: block.language ?? "", heading: heading, model: model)
231 }
232
226233 /// `org-babel-default-lob-header-args`, over the called block's own header arguments.
227234 static let lobDefaults = [(":exports", "results")]
228235
@@ -246,7 +253,9 @@ public enum Babel {
246253 if line.range(of: "(?i)^[ \\t]*#\\+CALL:", options: .regularExpression) != nil {
247254 call = (try? callAt(offset, model: model, text: text))?.params
248255 } else if tree.root.descendants().contains(where: { $0.kind == .inlineBabelCall && $0.range.lowerBound == offset }) {
249 call = (try? inlineCallAt(offset, tree: tree, model: model, text: text))?.params
256 // `org-babel-remove-result` only takes away `#+RESULTS:`, so an inline call's
257 // `{{{results(...)}}}` stays whatever `:exports` says.
258 return "results"
250259 } else if let inline = try? inlineAt(offset, tree: tree, model: model, text: text), inline.block.range.lowerBound == offset {
251260 return value(inline.params, language: inline.block.language)
252261 } else {
@@ -924,22 +933,7 @@ public enum Babel {
924933 init(block: SrcBlockInfo, model: DocumentModel, text: NSString, defaults: [(String, String)] = [], extra: [[(String, String)]] = [],
925934 inline: Bool = false) throws {
926935 var layers: [[(String, String)]] = [defaults]
927 let language = block.language ?? ""
928 // `org-babel-params-from-properties`: the inherited `header-args`, then the inherited
929 // `header-args:LANG`, each the nearest value with `+` additions after it.
930 var levels = [model.fileProperties]
931 if let heading = block.heading {
932 levels += (model.ancestors(of: heading).reversed() + [heading]).map { model.headings[$0].properties }
933 }
934 for key in ["header-args", "header-args:\(language)"] {
935 var value: String?
936 for entries in levels {
937 for entry in entries where entry.key.lowercased() == key {
938 value = entry.additive ? [value, entry.value].compactMap { $0 }.joined(separator: " ") : entry.value
939 }
940 }
941 if let value { layers.append(Params.parse(value)) }
942 }
936 layers += Params.propertyLayers(language: block.language ?? "", heading: block.heading, model: model)
943937 let firstLine = inline ? "" : text.substring(with: text.lineRange(for: NSRange(location: block.range.lowerBound, length: 0)))
944938 if let r = firstLine.range(of: "(?i)#\\+begin_src[ \\t]+\\S+", options: .regularExpression) {
945939 var rest = String(firstLine[r.upperBound...]).trimmingCharacters(in: .whitespacesAndNewlines)
@@ -993,6 +987,25 @@ public enum Babel {
993987 }
994988 }
995989
990 /// `org-babel-params-from-properties` under `heading`, or before any: the inherited
991 /// `header-args`, then the inherited `header-args:LANG`, each the nearest value with `+`
992 /// additions after it.
993 static func propertyLayers(language: String, heading: Int?, model: DocumentModel) -> [[(String, String)]] {
994 var levels = [model.fileProperties]
995 if let heading {
996 levels += (model.ancestors(of: heading).reversed() + [heading]).map { model.headings[$0].properties }
997 }
998 return ["header-args", "header-args:\(language)"].compactMap { key in
999 var value: String?
1000 for entries in levels {
1001 for entry in entries where entry.key.lowercased() == key {
1002 value = entry.additive ? [value, entry.value].compactMap { $0 }.joined(separator: " ") : entry.value
1003 }
1004 }
1005 return value.map(Params.parse)
1006 }
1007 }
1008
9961009 /// `org-babel-parse-header-arguments`: split before each `:key` outside quotes and
9971010 /// parentheses.
9981011 /// A Lisp string's contents as `read` gives them.
Tests/OrgCoreTests/BabelTests.swift +20
@@ -368,7 +368,27 @@ struct BabelCallTests {
368368 (named + "Inline call_double[:results verbatim](n=1) here.\n", "call_"),
369369 (named + "Inline call_double(n=2) {{{results(=0=)}}} here.\n", "call_"),
370370 (named + "Inline call_double(n=2) and call_double(n=3) here.\n", "call_double(n=3"),
371 // Properties where the call is, not where the block is.
372 (underHeadings + "#+CALL: double()\n", "#+CALL"),
373 (underHeadings + "#+CALL: double() :var n=1\n", "#+CALL"),
374 (underHeadings + "Inline call_double() here.\n", "call_"),
371375 ]
376 static let underHeadings = """
377 * Library
378 :PROPERTIES:
379 :header-args: :results verbatim
380 :END:
381 #+NAME: double
382 #+begin_src sh :var n=2
383 echo $((n*2))
384 #+end_src
385 * Use
386 :PROPERTIES:
387 :header-args:sh: :var n=7
388 :header-args: :results raw
389 :END:
390
391 """
372392
373393 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
374394 func matchesEmacs() throws {
Tests/OrgCoreTests/ExportTests.swift +13
@@ -320,6 +320,19 @@ struct ExportResultsTests {
320320 Inline src_sh[:exports both]{echo codeinlineboth} {{{results(=resinlineboth=)}}} three.
321321
322322 Inline call_named() {{{results(=resinlinecall=)}}} four.
323
324 * Other
325 :PROPERTIES:
326 :header-args: :exports none
327 :END:
328 #+CALL: named()
329
330 #+RESULTS:
331 : rescallother
332
333 Inline call_named() {{{results(=resinlinecallother=)}}} five.
334
335 Inline call_named[:exports code]() {{{results(=resinlinecallcode=)}}} six.
323336 """ + "\n"
324337
325338 /// Which code and results the HTML and Markdown exports keep, against ox-html with nothing