Personal emacs config
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

  1. ;;; number.el --- Working with numbers at point.
  2. ;; Package-Version: 20170901.1312
  3. ;; Package-Commit: bbc278d34dbcca83e70e3be855ec98b23debfb99
  4. ;; Copyright (c) 2014 Chris Done. All rights reserved.
  5. ;; This file is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation; either version 3, or (at your option)
  8. ;; any later version.
  9. ;; This file is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; Do trivial arithmetic on the numbers at point. Attempts to preserve
  17. ;; padding when it can. Examples:
  18. ;; M-x number/add 1 RET
  19. ;; 1 -> 2
  20. ;; 05 -> 06
  21. ;; 6.30 -> 7.30
  22. ;; 07.30 -> 08.30
  23. ;; -08.30 -> -07.30
  24. ;; M-x number/pad 2 RET
  25. ;; 5 -> 05
  26. ;; M-x number/pad 2 RET 6 RET
  27. ;; 3.141 -> 03.141000
  28. ;; The "guessing" where the number is isn't yet quite awesome, e.g. it
  29. ;; doesn't know that the 05 in "2014-05-01" is a month and not,
  30. ;; e.g. the number -05. But you can use the region to explicitly
  31. ;; denote the start and end of the number.
  32. ;; The following keybindings might be nice to use:
  33. ;;
  34. ;; (global-set-key (kbd "C-c C-+") 'number/add)
  35. ;; (global-set-key (kbd "C-c C--") 'number/sub)
  36. ;; (global-set-key (kbd "C-c C-*") 'number/multiply)
  37. ;; (global-set-key (kbd "C-c C-/") 'number/divide)
  38. ;; (global-set-key (kbd "C-c C-0") 'number/pad)
  39. ;; (global-set-key (kbd "C-c C-=") 'number/eval)
  40. ;;; Code:
  41. (defun number/add (n)
  42. "Add to the number at point."
  43. (interactive (list (number-read-from-minibuffer)))
  44. (number-arith-op n '+))
  45. (defun number/sub (n)
  46. "Subtract to the number at point."
  47. (interactive (list (number-read-from-minibuffer)))
  48. (number-arith-op n '-))
  49. (defun number/multiply (n)
  50. "Multiply the number at point."
  51. (interactive (list (number-read-from-minibuffer)))
  52. (number-arith-op n '*))
  53. (defun number/divide (n)
  54. "Divide the number at point."
  55. (interactive (list (number-read-from-minibuffer)))
  56. (number-arith-op n '/))
  57. (defun number/eval ()
  58. (interactive)
  59. (number-transform
  60. (lambda (number)
  61. (number-modify
  62. :number
  63. (lambda (x)
  64. (funcall (eval
  65. `(lambda (n)
  66. ,(read-from-minibuffer "Eval (e.g. (* n 2)): " "" nil t)))
  67. x))
  68. number))))
  69. (defun number/pad ()
  70. "Pad the number at point."
  71. (interactive)
  72. (number-transform
  73. (lambda (number)
  74. (ecase (number-get number :type)
  75. (integral
  76. (number-modify
  77. :padding
  78. (lambda (p)
  79. (number-read-padding
  80. number
  81. :padding
  82. "Pad (default: %d): "))
  83. number))
  84. (decimal
  85. (number-modify
  86. :decimal-padding
  87. (lambda (p)
  88. (number-read-padding
  89. number
  90. :decimal-padding
  91. "Decimal precision (default: %d): "))
  92. (number-modify
  93. :padding
  94. (lambda (p)
  95. (number-read-padding
  96. number
  97. :padding
  98. "Pad (default: %d): "))
  99. number)))))))
  100. (defun number-read-padding (number key caption)
  101. "Read a padding value for a number."
  102. (or (let ((str (read-from-minibuffer
  103. (format caption
  104. (number-get number key)))))
  105. (unless (string= "" str)
  106. (string-to-number str)))
  107. (number-get number key)))
  108. (defun number/mark ()
  109. "Mark the number at point."
  110. (interactive)
  111. (skip-chars-backward "0-9.")
  112. (when (looking-back "[+\\-]")
  113. (goto-char (1- (point))))
  114. (let ((point (point)))
  115. (skip-chars-forward "+-")
  116. (skip-chars-forward "0-9")
  117. (when (looking-at "\\.[0-9]")
  118. (skip-chars-forward ".")
  119. (skip-chars-forward "0-9"))
  120. (set-mark (point))
  121. (goto-char point)))
  122. (defun number-arith-op (n op)
  123. "Apply the arithmetic operation to the current point."
  124. (number-transform
  125. (lambda (number)
  126. (number-modify
  127. :number
  128. (lambda (x) (funcall op x (number-get n :number)))
  129. number))))
  130. (defun number-transform (f)
  131. "Transform the number at point in some way."
  132. (let ((point (point)))
  133. (let* ((beg-end (prog2 (unless (region-active-p)
  134. (number/mark))
  135. (list (region-beginning)
  136. (region-end))
  137. (deactivate-mark)))
  138. (string (apply 'buffer-substring-no-properties beg-end)))
  139. (let ((new (number-format (funcall f (number-read string)))))
  140. (apply 'delete-region beg-end)
  141. (insert new)))
  142. (goto-char point)))
  143. (defun number-modify (key f number)
  144. "Modify the number contained in a number specifier."
  145. (mapcar (lambda (entry)
  146. (if (eq (car entry) key)
  147. (cons key (funcall f (cdr entry)))
  148. entry))
  149. number))
  150. (defun number-format (number)
  151. "Format the given number specifier to a string."
  152. (ecase (number-get number :type)
  153. (decimal
  154. (if (= 0 (number-get number :padding))
  155. (format (format "%%.%df" (number-get number :decimal-padding))
  156. (number-get number :number))
  157. (number-pad-decimal (number-get number :padding)
  158. (number-get number :decimal-padding)
  159. (number-get number :number))))
  160. (integral
  161. (if (= 0 (number-get number :number))
  162. "0"
  163. (format (format "%%0.%dd" (number-get number :padding))
  164. (number-get number :number))))))
  165. (defun number-pad-decimal (left-pad right-pad n)
  166. "Pad a decimal on the left- and right-hand side of the decimal
  167. place."
  168. (let ((precision right-pad)
  169. (total (+ left-pad 1 right-pad (if (< n 0) 1 0))))
  170. (format (format "%%0%d.%df"
  171. total
  172. precision)
  173. n)))
  174. (defun number-get (number key)
  175. "Get the KEY value from NUMBER."
  176. (cdr (assoc key number)))
  177. (defun number-read-from-minibuffer ()
  178. "Read a number from the minibuffer."
  179. (number-read (read-from-minibuffer "Number: ")))
  180. (defun number-read (string)
  181. "Read a number from a string."
  182. (cond
  183. ((string-match "\\." string)
  184. `((:string . ,string)
  185. (:number . ,(string-to-number string))
  186. (:type . decimal)
  187. (:padding . ,(number-padding string))
  188. (:decimal-padding . ,(number-decimal-padding string))))
  189. ((string-match "[-+]?[0-9]+" string)
  190. `((:string . ,string)
  191. (:number . ,(string-to-number string))
  192. (:type . integral)
  193. (:padding . ,(number-padding string))))
  194. (t (error "Unable to parse a number."))))
  195. (defun number-padding (string)
  196. "Calculate the padding a number has."
  197. (if (string-match "[-+]?\\(\\(0+\\)[^\\.]*\\)" string)
  198. (length (match-string 1 string))
  199. 0))
  200. (defun number-decimal-padding (string)
  201. "Calculate the padding a number has."
  202. (if (string-match "\\.\\([0-9]+\\)$" string)
  203. (length (match-string 1 string))
  204. 0))
  205. (defun number-guess-padding (string)
  206. "Guess the padding a number has."
  207. (if (string-match "[-+]?\\(\\([0-9]+\\)[^\\.]*\\)" string)
  208. (length (match-string 1 string))
  209. 0))
  210. (provide 'number)
  211. ;;; number.el ends here