UP | HOME

▼ 本文更新于 [2025-06-09 周一 16:17]

emacs-让beancount插件自动对齐适配中文

[2025-06-09 周一 16:13]

emacs上的 beancount 插件,由于开发者是外国人原因,对CJK字符支持一直很烂,对齐就是其中一例。

查看相关代码源码,会发现直接使用的 length 函数,正确考虑CJK方式的对齐应为 string-width 函数。

我们将 beancount-align-numbers 的函数中相关宽度判断函数修改,然后用 advice 宏在不修改原函数的情况下进行替换:

(defun adv/beancount-align-numbers (begin end &optional requested-currency-column)
  "Align all numbers in the given region. CURRENCY-COLUMN is the character
at which to align the beginning of the amount's currency. If not specified, use
the smallest columns that will align all the numbers.  With a prefix argument,
align with the fill-column."
  (interactive "r")

  ;; With a prefix argument, align with the fill-column.
  (when current-prefix-arg
    (setq requested-currency-column fill-column))

  ;; Loop once in the region to find the length of the longest string before the
  ;; number.
  (let (prefix-widths
        number-widths
        (number-padding "  "))
    (beancount-for-line-in-region
     begin end
     (let ((line (thing-at-point 'line)))
       (when (string-match (concat "\\(.*?\\)"
                                   "[ \t]+"
                                   "\\(" beancount-number-regexp "\\)"
                                   "[ \t]+"
                                   beancount-currency-regexp)
                           line)
         (push (string-width (match-string 1 line)) prefix-widths)
         (push (string-width (match-string 2 line)) number-widths)
         )))

    (when prefix-widths
      ;; Loop again to make the adjustments to the numbers.
      (let* ((number-width (apply 'max number-widths))
             (number-format (format "%%%ss" number-width))
             ;; Compute rightmost column of prefix.
             (max-prefix-width (apply 'max prefix-widths))
             (max-prefix-width
              (if requested-currency-column
                  (max (- requested-currency-column (string-width number-padding) number-width 1)
                       max-prefix-width)
                max-prefix-width))
             (prefix-format (format "%%-%ss" max-prefix-width))
             )

        (beancount-for-line-in-region
         begin end
         (let ((line (thing-at-point 'line)))
           (when (string-match (concat "^\\([^\"]*?\\)"
                                       "[ \t]+"
                                       "\\(" beancount-number-regexp "\\)"
                                       "[ \t]+"
                                       "\\(.*\\)$")
                               line)
             (delete-region (line-beginning-position) (line-end-position))
             (let* ((prefix (match-string 1 line))
                    (number (match-string 2 line))
                    (rest (match-string 3 line)) )
               (insert (format prefix-format prefix))
               (insert number-padding)
               (insert (format number-format number))
               (insert " ")
               (insert rest)))))))))
(advice-add #'beancount-align-numbers :override #'adv/beancount-align-numbers)

为了排版方便,我们还可以覆盖掉 C-c ; 的默认函数,改为按一下对齐整个 buffer

(use-package beancount
  :bind
  (:map beancount-mode-map
        ("C-c ;" . my/beancount-align-buffer)))

(defun my/beancount-align-buffer ()
  "Align postings under the point's paragraph.
This function looks for a posting in the previous transaction to
determine the column at which to align the transaction, or otherwise
the fill column, and align all the postings of this transaction to
this column."
  (interactive)
  (let* ((begin (save-excursion
                  (beginning-of-buffer)
                  (point)))
         (end (save-excursion
                (end-of-buffer)
                (point)))
         (currency-column (or (beancount-find-previous-alignment-column)
                              fill-column)))
    (beancount-align-numbers begin end currency-column)))

© Published by Emacs 31.0.50 (Org mode 9.8-pre) | RSS 评论