-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmc-calc.el
204 lines (171 loc) · 6.74 KB
/
mc-calc.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
;;; mc-calc.el --- Combine multiple-cursors and calc -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Frank Roland
;; Author: Frank Roland hatheroldev@fgmail.com>
;; Keywords: convenience
;; Package-Requires: ((emacs "24.4") (multiple-cursors "1.2.1"))
;; Version: 0.0.1
;; URL: https://github.com/hatheroldev/mc-calc
;; This program 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 of the License, or
;; (at your option) any later version.
;; This program 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 <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This package allows the use of calc in regions with multiple-cursors.
;;
;; Use ~M-x package-install mc-calc~ to install this package.
;;
;; Use ~M-x mc-calc-eval~ to replace multiple regions with result from
;; evaluating in calc.
;;
;; Use ~M-x mc-calc-grab~ to grab multiple regions as vector in calc and
;; open calc (returnable to current buffer).
;;
;; Use ~M-x mc-calc~ to open calc (returnable to current buffer).
;;
;; Use ~M-x mc-calc-copy-to-buffer~ to replace multiple regions
;; from top level vector and return to buffer.
;;
;; You can customize used calc options in `mc-calc-eval-options'
;; and `mc-calc-major-mode-eval-options-alist'.
;;; Code:
(require 'multiple-cursors)
(require 'subr-x) ; string-join
(require 'calc) ; calc-quit, math-format-value
(require 'calc-vec) ; mat-map-vec
(defgroup mc-calc nil
"Combine multiple-cursors and calc."
:prefix "mc-calc-"
:tag "mc-calc"
:group 'convenience)
(defcustom mc-calc-major-mode-eval-options-alist nil
"Alist of major modes with `calc-eval' options.
Note: calc automatically sets `calc-language' from `major-mode'."
:group 'mc-calc
:type '(alist :key-type (symbol :tag "Major mode")
:value-type (list :tag "Key/value pairs")))
(defcustom mc-calc-eval-options nil
"Control calc settings.
This variable defines how to build first argument to `calc-eval' in
`mc-calc-eval'.
You can
- set it to t to use the current calc settings, this also disables
automatic options,
- set it to nil to reset all calc settings to default,
- set key value pairs to be used as calc settings,
e.g. '(calc-number-radix 16)."
:group 'mc-calc
:type '(list :tag "Key/value pairs"))
(defvar mc-calc-from-buffer nil
"The buffer to return to.")
(defvar mc-calc-was-started-p nil
"Non-nil if calc buffer was already visible.")
(defun mc-calc--eval-first-param (val)
"Build fist `calc-eval' parameter and prepend VAL."
(if (listp mc-calc-eval-options)
;; Explicit calc setting.
(let ((lang (cl-assoc-if
#'derived-mode-p
mc-calc-major-mode-eval-options-alist)))
(append (cons val mc-calc-eval-options)
(when lang
(cdr lang))))
;; Use calc as currently set.
val))
(defun mc-calc--eval (val &rest rest)
"Call `calc-eval' with VAL plus options and REST."
(apply #'calc-eval (mc-calc--eval-first-param val) rest))
;;;###autoload
(defun mc-calc-eval ()
"Eval each cursor region in calc.
You can use $ and $$ in the region:
- $ will be substituted by the cursor number (starting with 0) and
- $$ will be substituted by the number of cursors.
Set `mc-calc-eval-options' to configure calc options."
(interactive)
(let* ((vals (mc--ordered-region-strings))
(num (length vals))
(i -1)) ; Cursor number, usable as $ in formula.
(setq mc--strings-to-replace
(mapcar
(lambda (val)
(setq i (1+ i))
(mc-calc--eval
val
nil
i ; Available as $ in calc-eval.
num)) ; Available as $$ in calc-eval.
vals)))
(mc--replace-region-strings))
(defun mc-calc--set-values ()
"Set values in order to use `mc-calc-copy-to-buffer'."
(setq mc-calc-from-buffer (current-buffer))
(setq mc-calc-was-started-p (get-buffer-window "*Calculator*" 'visible))
(with-no-warnings
(setq var-mccursors (mc/num-cursors))))
(defun mc-calc--quit ()
"Quit calc, if it was not already visible, and return to previous buffer."
(when mc-calc-from-buffer
(if mc-calc-was-started-p
(pop-to-buffer mc-calc-from-buffer)
(calc-quit t)
(switch-to-buffer mc-calc-from-buffer)))
(setq mc-calc-from-buffer nil))
;;;###autoload
(defun mc-calc ()
"Open calc and set values in order to use `mc-calc-copy-to-buffer'."
(interactive)
(mc-calc--set-values)
(calc))
;;;###autoload
(defun mc-calc-grab ()
"Collect each cursor region into a vector and push it to calc.
After some operations are performed on the vector
the result can be copied back with `mc-calc-copy-to-buffer'."
(interactive)
(mc-calc-vec--grab (mc--ordered-region-strings)))
(defun mc-calc-vec--grab (data)
"Interpret DATA as list of strings and grab them into a calc vector.
The variables `mc-calc-from-buffer' and `mc-calc-was-started-p' are set so that
after some operations are performed on the vector the result can be copied
back with `mc-calc-copy-to-buffer'."
(mc-calc--set-values)
(calc)
(mc-calc--eval
(concat "[" (string-join (mapcar #'calc-eval data) ", ") "]")
'push))
(defun mc-calc-vec--top-get ()
"Get calc top vector and return it as list of strings."
(let ((vals (mc-calc--eval 1 'rawtop))
data)
(math-map-vec
(lambda (val)
(setq data (cons (math-format-value val) data)))
vals)
(calc-eval 1 'pop) ; Remove top element from calc stack.
(nreverse data)))
;;; Do not autoload
(defun mc-calc-copy-to-buffer ()
"Copy the top of stack (vector or single element) into an editing buffer.
You must have used `mc-calc-grab' or `mc-calc' before you can use this function
from within the calc buffer."
(interactive)
(when (memq major-mode '(calc-mode calc-trail-mode))
(pop-to-buffer mc-calc-from-buffer))
(let ((data (mc-calc-vec--top-get))
(num-cursors (mc/num-cursors)))
(when (eq (length data) 1)
;; Extend single element to number of cursors.
(setq data (make-list num-cursors (car data))))
(unless (eq (length data) num-cursors)
(user-error "Vector length does not match number of multiple cursors"))
(setq mc--strings-to-replace data))
(mc--replace-region-strings)
(mc-calc--quit))
(provide 'mc-calc)
;;; mc-calc.el ends here