You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
7.2 KiB
244 lines
7.2 KiB
;;; number.el --- Working with numbers at point.
|
|
;; Package-Version: 20170901.1312
|
|
;; Package-Commit: bbc278d34dbcca83e70e3be855ec98b23debfb99
|
|
|
|
;; Copyright (c) 2014 Chris Done. All rights reserved.
|
|
|
|
;; This file is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation; either version 3, or (at your option)
|
|
;; any later version.
|
|
|
|
;; This file is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Do trivial arithmetic on the numbers at point. Attempts to preserve
|
|
;; padding when it can. Examples:
|
|
|
|
;; M-x number/add 1 RET
|
|
;; 1 -> 2
|
|
;; 05 -> 06
|
|
;; 6.30 -> 7.30
|
|
;; 07.30 -> 08.30
|
|
;; -08.30 -> -07.30
|
|
|
|
;; M-x number/pad 2 RET
|
|
;; 5 -> 05
|
|
|
|
;; M-x number/pad 2 RET 6 RET
|
|
;; 3.141 -> 03.141000
|
|
|
|
;; The "guessing" where the number is isn't yet quite awesome, e.g. it
|
|
;; doesn't know that the 05 in "2014-05-01" is a month and not,
|
|
;; e.g. the number -05. But you can use the region to explicitly
|
|
;; denote the start and end of the number.
|
|
|
|
;; The following keybindings might be nice to use:
|
|
;;
|
|
;; (global-set-key (kbd "C-c C-+") 'number/add)
|
|
;; (global-set-key (kbd "C-c C--") 'number/sub)
|
|
;; (global-set-key (kbd "C-c C-*") 'number/multiply)
|
|
;; (global-set-key (kbd "C-c C-/") 'number/divide)
|
|
;; (global-set-key (kbd "C-c C-0") 'number/pad)
|
|
;; (global-set-key (kbd "C-c C-=") 'number/eval)
|
|
|
|
;;; Code:
|
|
|
|
(defun number/add (n)
|
|
"Add to the number at point."
|
|
(interactive (list (number-read-from-minibuffer)))
|
|
(number-arith-op n '+))
|
|
|
|
(defun number/sub (n)
|
|
"Subtract to the number at point."
|
|
(interactive (list (number-read-from-minibuffer)))
|
|
(number-arith-op n '-))
|
|
|
|
(defun number/multiply (n)
|
|
"Multiply the number at point."
|
|
(interactive (list (number-read-from-minibuffer)))
|
|
(number-arith-op n '*))
|
|
|
|
(defun number/divide (n)
|
|
"Divide the number at point."
|
|
(interactive (list (number-read-from-minibuffer)))
|
|
(number-arith-op n '/))
|
|
|
|
(defun number/eval ()
|
|
(interactive)
|
|
(number-transform
|
|
(lambda (number)
|
|
(number-modify
|
|
:number
|
|
(lambda (x)
|
|
(funcall (eval
|
|
`(lambda (n)
|
|
,(read-from-minibuffer "Eval (e.g. (* n 2)): " "" nil t)))
|
|
x))
|
|
number))))
|
|
|
|
(defun number/pad ()
|
|
"Pad the number at point."
|
|
(interactive)
|
|
(number-transform
|
|
(lambda (number)
|
|
(ecase (number-get number :type)
|
|
(integral
|
|
(number-modify
|
|
:padding
|
|
(lambda (p)
|
|
(number-read-padding
|
|
number
|
|
:padding
|
|
"Pad (default: %d): "))
|
|
number))
|
|
(decimal
|
|
(number-modify
|
|
:decimal-padding
|
|
(lambda (p)
|
|
(number-read-padding
|
|
number
|
|
:decimal-padding
|
|
"Decimal precision (default: %d): "))
|
|
(number-modify
|
|
:padding
|
|
(lambda (p)
|
|
(number-read-padding
|
|
number
|
|
:padding
|
|
"Pad (default: %d): "))
|
|
number)))))))
|
|
|
|
(defun number-read-padding (number key caption)
|
|
"Read a padding value for a number."
|
|
(or (let ((str (read-from-minibuffer
|
|
(format caption
|
|
(number-get number key)))))
|
|
(unless (string= "" str)
|
|
(string-to-number str)))
|
|
(number-get number key)))
|
|
|
|
(defun number/mark ()
|
|
"Mark the number at point."
|
|
(interactive)
|
|
(skip-chars-backward "0-9.")
|
|
(when (looking-back "[+\\-]")
|
|
(goto-char (1- (point))))
|
|
(let ((point (point)))
|
|
(skip-chars-forward "+-")
|
|
(skip-chars-forward "0-9")
|
|
(when (looking-at "\\.[0-9]")
|
|
(skip-chars-forward ".")
|
|
(skip-chars-forward "0-9"))
|
|
(set-mark (point))
|
|
(goto-char point)))
|
|
|
|
(defun number-arith-op (n op)
|
|
"Apply the arithmetic operation to the current point."
|
|
(number-transform
|
|
(lambda (number)
|
|
(number-modify
|
|
:number
|
|
(lambda (x) (funcall op x (number-get n :number)))
|
|
number))))
|
|
|
|
(defun number-transform (f)
|
|
"Transform the number at point in some way."
|
|
(let ((point (point)))
|
|
(let* ((beg-end (prog2 (unless (region-active-p)
|
|
(number/mark))
|
|
(list (region-beginning)
|
|
(region-end))
|
|
(deactivate-mark)))
|
|
(string (apply 'buffer-substring-no-properties beg-end)))
|
|
(let ((new (number-format (funcall f (number-read string)))))
|
|
(apply 'delete-region beg-end)
|
|
(insert new)))
|
|
(goto-char point)))
|
|
|
|
(defun number-modify (key f number)
|
|
"Modify the number contained in a number specifier."
|
|
(mapcar (lambda (entry)
|
|
(if (eq (car entry) key)
|
|
(cons key (funcall f (cdr entry)))
|
|
entry))
|
|
number))
|
|
|
|
(defun number-format (number)
|
|
"Format the given number specifier to a string."
|
|
(ecase (number-get number :type)
|
|
(decimal
|
|
(if (= 0 (number-get number :padding))
|
|
(format (format "%%.%df" (number-get number :decimal-padding))
|
|
(number-get number :number))
|
|
(number-pad-decimal (number-get number :padding)
|
|
(number-get number :decimal-padding)
|
|
(number-get number :number))))
|
|
(integral
|
|
(if (= 0 (number-get number :number))
|
|
"0"
|
|
(format (format "%%0.%dd" (number-get number :padding))
|
|
(number-get number :number))))))
|
|
|
|
(defun number-pad-decimal (left-pad right-pad n)
|
|
"Pad a decimal on the left- and right-hand side of the decimal
|
|
place."
|
|
(let ((precision right-pad)
|
|
(total (+ left-pad 1 right-pad (if (< n 0) 1 0))))
|
|
(format (format "%%0%d.%df"
|
|
total
|
|
precision)
|
|
n)))
|
|
|
|
(defun number-get (number key)
|
|
"Get the KEY value from NUMBER."
|
|
(cdr (assoc key number)))
|
|
|
|
(defun number-read-from-minibuffer ()
|
|
"Read a number from the minibuffer."
|
|
(number-read (read-from-minibuffer "Number: ")))
|
|
|
|
(defun number-read (string)
|
|
"Read a number from a string."
|
|
(cond
|
|
((string-match "\\." string)
|
|
`((:string . ,string)
|
|
(:number . ,(string-to-number string))
|
|
(:type . decimal)
|
|
(:padding . ,(number-padding string))
|
|
(:decimal-padding . ,(number-decimal-padding string))))
|
|
((string-match "[-+]?[0-9]+" string)
|
|
`((:string . ,string)
|
|
(:number . ,(string-to-number string))
|
|
(:type . integral)
|
|
(:padding . ,(number-padding string))))
|
|
(t (error "Unable to parse a number."))))
|
|
|
|
(defun number-padding (string)
|
|
"Calculate the padding a number has."
|
|
(if (string-match "[-+]?\\(\\(0+\\)[^\\.]*\\)" string)
|
|
(length (match-string 1 string))
|
|
0))
|
|
|
|
(defun number-decimal-padding (string)
|
|
"Calculate the padding a number has."
|
|
(if (string-match "\\.\\([0-9]+\\)$" string)
|
|
(length (match-string 1 string))
|
|
0))
|
|
|
|
(defun number-guess-padding (string)
|
|
"Guess the padding a number has."
|
|
(if (string-match "[-+]?\\(\\([0-9]+\\)[^\\.]*\\)" string)
|
|
(length (match-string 1 string))
|
|
0))
|
|
|
|
(provide 'number)
|
|
|
|
;;; number.el ends here
|