如何在 emacs org 模式下重新计算列的总和?
How to recalculate sum of columns in emacs org mode?
假设我们有以下结构:
|Element |Price |
|first |1 |
|second |2 |
|Total |:=vsum(@2..@-1)| -> this will render: 3
#+TBLFM: @4=vsum(@2..@-1)
现在假设插入了一个新行:
|Element |Price |
|first |1 |
|inserted |10 |
|second |2 |
|Total |:=vsum(@2..@-1)| -> this will still render 3, but it should render 13
#+TBLFM: @5=vsum(@2..@-1)
那么如何在插入新行时自动改变总和呢?
Org-mode has limited ability to automatically recalculate tables:第一列可以用来添加一个特殊字符(#
),它可以做你想做的事,但只有当TAB
,S-TAB
,或 RET
在您要重新计算的行中键入(请注意,这还将此行添加到缓冲区的全局重新计算中,这也可能很有用)。
这里的问题是 org-mode 不知道什么时候应该重新计算 table 除非你告诉它(例如使用命令 C-c *
(org-ctrl-c-star
)) .一种选择是重新绑定 org-mode
中的部分或所有移动键绑定,以查看光标是否从 table 条目开始并移出,如果是,则重新计算整个 table,但是我建议不要这样做。
在我看来,更好更简单的解决方案是让 org-mode 在插入新行时自动更新 table(但不一定每次条目更改时)。这是一个 Emacs Lisp 函数 (my-org-table-insert-row-and-recalculate-table
),它通过提示用户输入每一列在当前行上方创建一个新行,然后重新计算 table 的所有公式。关于这个函数的几点说明:
- 此函数需要 运行 在公式适用的第一行或以下,以及公式适用的最后一行之上。否则,公式可能不会按预期更新(例如,正在使用的底行可能会超出公式范围)。这基于组织模式如何更新插入行的公式。
- 如果任何公式无效,更新过程可能无法完成(例如,公式
#+TBLFM: =vsum(@2..@-1)
对任何插入都不起作用,因为 org-mode 会尝试将其应用于第 1 行第 2 列,其中引用 @-1
无效)。
- 如果您手动更新 table 条目,您仍然需要手动调用 table 公式的重新计算过程。
(require 'subr-x)
(defun remove-empty-strings-list (list)
(if (null list)
'()
(let ((rest (remove-empty-strings-list (cdr list))))
(if (string= (car list) "")
rest
(cons (car list) rest)))))
(defun my-org-table--get-row-as-list ()
(unless (org-at-table-p) (user-error "Not at a table"))
(let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
(mapcar 'string-trim (remove-empty-strings-list (split-string line "|")))))
(defun my-org-table--get-top-row-as-list ()
(save-excursion
(org-table-goto-line 1)
(my-org-table--get-row-as-list)))
(defun my-org-table--insert-string-row-and-recalculate-table (string)
(org-table-with-shrunk-columns
(beginning-of-line 1)
(insert-before-markers string "\n")
(org-table-align)
(org-table-fix-formulas "@" nil (1- (org-table-current-dline)) 1)
(org-table-iterate)))
(defun my-org-table--prompt-row ()
(unless (org-at-table-p) (user-error "Not at a table"))
(let ((top-row-list (my-org-table--get-top-row-as-list))
(new-str "|")
(cur-col 1))
(dolist (top-item top-row-list)
(let ((new-entry
(read-string (concat "Enter entry (column #"
(number-to-string cur-col)
" - first entry: "
top-item
"): "))))
(setq new-str (concat new-str new-entry "|")) ;; test placement
(setq cur-col (+ 1 cur-col))))
new-str))
(defun my-org-table-insert-row-and-recalculate-table ()
"Interactively inserts row above point and then recalculates table"
(interactive)
(unless (org-at-table-p) (user-error "Not at a table"))
(my-org-table--insert-string-row-and-recalculate-table (my-org-table--prompt-row)))
示例用法('!!' 指示缓冲区中的点,'>>' 是迷你缓冲区提示):
| Element | Price |
| first | 1 |
| second | 2 |!!
| Total | 3 |
#+TBLFM: @4=vsum(@2..@-1)
运行 M-x my-org-table-insert-row-and-recalculate-table
.
>> Enter entry (column #1 - first entry: Element):
inserted
>> Enter entry (column #2 - first entry: Price):
100
输出:
| Element | Price |
| first | 1 |
| inserted | 100 |
| second | 2 |
| Total | 103 |
#+TBLFM: @5=vsum(@2..@-1)
假设我们有以下结构:
|Element |Price |
|first |1 |
|second |2 |
|Total |:=vsum(@2..@-1)| -> this will render: 3
#+TBLFM: @4=vsum(@2..@-1)
现在假设插入了一个新行:
|Element |Price |
|first |1 |
|inserted |10 |
|second |2 |
|Total |:=vsum(@2..@-1)| -> this will still render 3, but it should render 13
#+TBLFM: @5=vsum(@2..@-1)
那么如何在插入新行时自动改变总和呢?
Org-mode has limited ability to automatically recalculate tables:第一列可以用来添加一个特殊字符(#
),它可以做你想做的事,但只有当TAB
,S-TAB
,或 RET
在您要重新计算的行中键入(请注意,这还将此行添加到缓冲区的全局重新计算中,这也可能很有用)。
这里的问题是 org-mode 不知道什么时候应该重新计算 table 除非你告诉它(例如使用命令 C-c *
(org-ctrl-c-star
)) .一种选择是重新绑定 org-mode
中的部分或所有移动键绑定,以查看光标是否从 table 条目开始并移出,如果是,则重新计算整个 table,但是我建议不要这样做。
在我看来,更好更简单的解决方案是让 org-mode 在插入新行时自动更新 table(但不一定每次条目更改时)。这是一个 Emacs Lisp 函数 (my-org-table-insert-row-and-recalculate-table
),它通过提示用户输入每一列在当前行上方创建一个新行,然后重新计算 table 的所有公式。关于这个函数的几点说明:
- 此函数需要 运行 在公式适用的第一行或以下,以及公式适用的最后一行之上。否则,公式可能不会按预期更新(例如,正在使用的底行可能会超出公式范围)。这基于组织模式如何更新插入行的公式。
- 如果任何公式无效,更新过程可能无法完成(例如,公式
#+TBLFM: =vsum(@2..@-1)
对任何插入都不起作用,因为 org-mode 会尝试将其应用于第 1 行第 2 列,其中引用@-1
无效)。 - 如果您手动更新 table 条目,您仍然需要手动调用 table 公式的重新计算过程。
(require 'subr-x)
(defun remove-empty-strings-list (list)
(if (null list)
'()
(let ((rest (remove-empty-strings-list (cdr list))))
(if (string= (car list) "")
rest
(cons (car list) rest)))))
(defun my-org-table--get-row-as-list ()
(unless (org-at-table-p) (user-error "Not at a table"))
(let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
(mapcar 'string-trim (remove-empty-strings-list (split-string line "|")))))
(defun my-org-table--get-top-row-as-list ()
(save-excursion
(org-table-goto-line 1)
(my-org-table--get-row-as-list)))
(defun my-org-table--insert-string-row-and-recalculate-table (string)
(org-table-with-shrunk-columns
(beginning-of-line 1)
(insert-before-markers string "\n")
(org-table-align)
(org-table-fix-formulas "@" nil (1- (org-table-current-dline)) 1)
(org-table-iterate)))
(defun my-org-table--prompt-row ()
(unless (org-at-table-p) (user-error "Not at a table"))
(let ((top-row-list (my-org-table--get-top-row-as-list))
(new-str "|")
(cur-col 1))
(dolist (top-item top-row-list)
(let ((new-entry
(read-string (concat "Enter entry (column #"
(number-to-string cur-col)
" - first entry: "
top-item
"): "))))
(setq new-str (concat new-str new-entry "|")) ;; test placement
(setq cur-col (+ 1 cur-col))))
new-str))
(defun my-org-table-insert-row-and-recalculate-table ()
"Interactively inserts row above point and then recalculates table"
(interactive)
(unless (org-at-table-p) (user-error "Not at a table"))
(my-org-table--insert-string-row-and-recalculate-table (my-org-table--prompt-row)))
示例用法('!!' 指示缓冲区中的点,'>>' 是迷你缓冲区提示):
| Element | Price |
| first | 1 |
| second | 2 |!!
| Total | 3 |
#+TBLFM: @4=vsum(@2..@-1)
运行 M-x my-org-table-insert-row-and-recalculate-table
.
>> Enter entry (column #1 - first entry: Element):
inserted
>> Enter entry (column #2 - first entry: Price):
100
输出:
| Element | Price |
| first | 1 |
| inserted | 100 |
| second | 2 |
| Total | 103 |
#+TBLFM: @5=vsum(@2..@-1)