Own JS/CSS options

Thursday, March 15, 2012

Emacs Snippets: Calculation Helpers

If there is one thing that absolutely highlights horrible user interface design to me, it is when a user is sitting in front of a computer and picks up a pocket calculator. That’s just wrong in so many ways.

You have to copy any data already on the computer, and then copy the result back. Then the calculator in your hand is missing all sorts of editing functionality to fix up mistakes you do. And well, you are sitting in front of an enormously powerful calculator, but have to use a separate hardware gadget for a simple calculation. Why?

Well, mostly, because our enormously powerful calculators suck at the task of simply calculating. The best they have to offer is to hunt through a hierarchy of menus to start a program that … looks exactly like the gadget you could use, with exactly the same horrible user interface.

Emacs to the rescue.

Calculate Region

This command will replace the region with the result of piping that region through bc(1), meaning you can use quite a powerful mathematical notation there. Multiple lines are no problem, either. While editing any kind of text, you can simply start typing a mathematical expression, select it, and replace it with its result.

A prefix argument means to retain the expression in the buffer, and instead display the result in the minibuffer and put it into your kill ring for C-y to insert it.

(global-set-key (kbd "C-c m") 'fc-calculate-region)
(defun fc-calculate-region (start end &optional prefix)
  "Evaluate the mathematical expression within the region, and
replace it with its result.

With a prefix arg, do not replace the region, but instead put the
result into the kill ring."
  (interactive "r\nP")
  (let* ((expr (buffer-substring start end))
         (result (fc-bc-calculate-expression expr))
         (ends-with-newline (string-match "\n$" expr)))
    (if prefix
        (progn
          (kill-new result)
          (message "%s" result))
      (delete-region start end)
      (insert result)
      (when ends-with-newline
        (insert "\n")))))

(defun fc-bc-calculate-expression (expr)
  "Evaluate `expr' as a mathematical expression, and return its result.

This actually pipes `expr' through bc(1), replacing newlines with
spaces first. If bc(1) encounters an error, an error is
signalled."
  (with-temp-buffer
    (insert expr)
    (goto-char (point-min))
    (while (search-forward "\n" nil t)
      (replace-match " " nil t))
    (goto-char (point-max))
    (insert "\n")
    (call-process-region (point-min)
                          (point-max)
                         "bc" t t nil "-lq")
    (goto-char (point-min))
    (when (search-forward "error" nil t)
      (error "Bad expression"))
    (while (search-forward "\n" nil t)
      (replace-match "" nil t))
    (buffer-string)))

Evaluate SEXPR

Infix math is all well and dandy, but I sometimes like more powerful syntax. And I actually do think in Lisp’s prefix notation a lot. So, the following is a small enhancement over the standard C-x C-e which evaluates the expression before point. This will actually replace it with the result of evaluating that expression.

(global-set-key (kbd "C-c e") 'fc-eval-and-replace)
(defun fc-eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (prin1 (eval (read (current-kill 0)))
         (current-buffer)))

Sum Rectangle

Finally, I quite often find myself facing a tabular list of numbers that I’d like to sum up. For example something like this:

   128.00 EUR Train ticket
    10.00 EUR Reservation
     8.23 EUR Lunch
     6.99 EUR Book

I’d like to sum those numbers up. With the following in your .emacs, you just select the numbers with rectangle selection, and use C-x r a (mnemonic: rectangle add). Your minibuffer now shows 153.22 and you can insert that sum using C-y.

(global-set-key (kbd "C-x r a") 'fc-add-rectangle)
(defun fc-add-rectangle (start end)
  "Add all the lines in the region-rectangle and put the result in the
kill ring."
  (interactive "r")
  (let ((sum 0))
    (mapc (lambda (line)
            (string-match "-?[0-9.]+" line)
            (setq sum (+ sum (string-to-number (match-string 0 line)))))
          (extract-rectangle start end))
    (kill-new (number-to-string sum))
    (message "%s" sum)))
I probably should make a mode out of these some day.