Как пересчитать сумму столбцов в режиме emacs org? - PullRequest
0 голосов
/ 21 апреля 2019

Допустим, у нас есть следующая структура:

|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 голосов
/ 25 апреля 2019

Org-mode имеет ограниченную возможность автоматического пересчета таблиц : первый столбец можно использовать для добавления специального символа (#), который выполняет то, что вы хотите, но только когда TAB , S-TAB или RET вводятся в то время, как в строке, которую вы хотите пересчитать (обратите внимание, что это также добавляет эту строку к глобальному пересчету для буфера, что также может быть полезно).

Проблема в том, что режим org не знает, когда ему следует пересчитать таблицу, если вы не скажете это (например, с помощью команды C-c * (org-ctrl-c-star)). Одним из вариантов может быть повторное связывание некоторых или всех комбинаций клавиш перемещения в org-mode, чтобы увидеть, начался ли курсор и вышел ли он из записи таблицы, и если да, то пересчитать всю таблицу, но я бы рекомендовал не делать этого.

На мой взгляд, лучшее и более простое решение состоит в том, чтобы в режиме org автоматически обновлять таблицу при вставке новой строки (но не обязательно при каждом изменении записи). Вот функция Emacs Lisp (my-org-table-insert-row-and-recalculate-table), которая создает новую строку над текущей, запрашивая пользователя для каждого столбца, а затем пересчитывает все формулы для таблицы. Несколько замечаний об этой функции:

  • Эту функцию необходимо запустить в первой строке, к которой применяется формула, или под ней, а также над последней строкой, к которой применяется формула. Если нет, формула может не обновляться, как вы ожидаете (например, используемая нижняя строка может выпасть из диапазона формулы). Это основано на том, как в режиме org обновляются формулы для вставленных строк.
  • Если какие-либо формулы являются недопустимыми, процесс обновления может не завершиться (например, формула #+TBLFM: $2=vsum(@2..@-1) не будет работать для любой вставки, поскольку режим org пытается применить ее к строке 1, столбцу 2, для которого ссылка @-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)
...