如何在emacs org模式下重新计算列总和?

时间:2019-04-21 18:40:07

标签: emacs org-mode

可以说我们具有以下结构:

|Element    |Price          |  
|first      |1              |  
|second     |2              |  
|Total      |:=vsum(@2..@-1)|  -> this will render: 3
#+TBLFM: @4$2=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$2=vsum(@2..@-1)  

那么当插入带有新值的新行时如何自动改变总和?

1 个答案:

答案 0 :(得分:0)

Org-mode has limited ability to automatically recalculate tables:第一列可用于添加特殊字符(#),该特殊字符可以实现您想要的功能,但是仅当TABS-TAB,在要重新计算的行中键入RETC-c *(请注意,这还会将此行添加到缓冲区的全局重新计算中,这也可能有用)。

这里的问题是,除非您告知org-mode(例如,使用命令org-ctrl-c-starorg-mode),否则org-mode不知道何时应该重新计算该表。一种选择是重新绑定my-org-table-insert-row-and-recalculate-table中的某些或所有移动键绑定,以查看光标是否已开始并已移出表条目,如果是,则重新计算整个表,但是我建议不要这样做。

在我看来,更好和更轻松的解决方案是让org-mode在插入新行时自动更新表(但不必每次条目更改时都自动更新)。这是一个Emacs Lisp函数(#+TBLFM: $2=vsum(@2..@-1)),它通过提示用户输入每一列,在当前行上方创建新行,然后重新计算该表的所有公式。有关此功能的一些注意事项:

  • 此函数需要在公式适用的第一行或以下以及公式适用的最后一行或以下运行。如果不是,则公式可能不会按您期望的那样更新(例如,正在使用的最下面一行可能会超出公式范围)。这基于组织模式如何更新插入行的公式。
  • 如果任何公式无效,则更新过程可能无法完成(例如,公式@-1不能用于任何插入,因为org-mode试图将其应用于引用{ {1}}无效)。
  • 如果手动更新表条目,则仍然需要手动调用表公式的重新计算过程。
(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$2=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$2=vsum(@2..@-1)