forked from emacs-evil/evil-cleverparens
-
Notifications
You must be signed in to change notification settings - Fork 0
/
evil-cleverparens-util.el
408 lines (356 loc) · 13.6 KB
/
evil-cleverparens-util.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
;;; evil-cleverparens-util.el --- Utility functions for evil-cleverparens
;;
;; Copyright (C) 2015 Olli Piepponen
;;
;; Author: Olli Piepponen <opieppo@gmail.com>
;; URL: https://github.com/luxbock/evil-cleverparens
;; Keywords: cleverparens, parentheses, evil, paredit, smartparens
;; Version: 0.1.0
;; Package-Requires: ((evil "1.0") (smartparens "1.6.1"))
;;
;; This file is NOT part of GNU Emacs.
;;
;; This file is free software (MIT License)
;;; Commentary:
;; Helpers and utility functions for evil-cleverparens.
;;; Code:
(require 'cl-lib)
(require 'dash)
(require 'evil)
(require 'paredit)
(require 'smartparens)
(require 'subr-x)
(defvar evil-cp-pair-list
'(("(" . ")") ("[" . "]") ("{" . "}") ("\"" . "\""))
"List of parentheses pairs recognized by evil-cleverparens.")
(defvar evil-cp-sp-pair-list
(-filter (lambda (pair)
(not (string-equal (car pair) "`")))
sp-pair-list))
(defvar evil-cp--ws-regexp "[ \n\r\t]")
(defun evil-cp--looking-at-string-delimiter-p ()
"Predicate for checking if the point is on a string delimiter."
(and (looking-at (sp--get-stringlike-regexp))
(not (paredit-in-string-escape-p))))
(defun evil-cp--looking-at-whitespace-p (&optional pos)
(save-excursion
(when pos (goto-char pos))
(looking-at-p evil-cp--ws-regexp)))
(defun evil-cp--pair-for (pair pairs)
(cond
((not pairs)
(message "Pair for %s not found." pair))
((string= pair (caar pairs))
(car pairs))
(t
(evil-cp--pair-for pair (cdr pairs)))))
(defun evil-cp-pair-for (pair)
(evil-cp--pair-for pair evil-cp-pair-list))
(defun evil-cp--get-opening-regexp ()
(sp--strict-regexp-opt (--map (car it) evil-cp-pair-list)))
(defun evil-cp--get-closing-regexp ()
(sp--strict-regexp-opt (--map (cdr it) evil-cp-pair-list)))
(defun evil-cp--looking-at-opening-p (&optional pos)
"Predicate that returns true if point is looking at an opening
parentheses as defined by smartparens for the major mode in
question. Ignores parentheses inside strings."
(save-excursion
(when pos (goto-char pos))
(and (sp--looking-at-p (evil-cp--get-opening-regexp))
(not (evil-cp--inside-string-p)))))
(defun evil-cp--looking-at-closing-p (&optional pos)
"Predicate that returns true if point is looking at an closing
paren as defined by smartparens for the major mode in
question. Ignores parentheses inside strings."
(save-excursion
(when pos (goto-char pos))
(and (sp--looking-at-p (evil-cp--get-closing-regexp))
(not (evil-cp--inside-string-p)))))
(defun evil-cp--looking-at-paren-p (&optional pos)
"Predicate that returns true if point is looking at a
parentheses as defined by smartparens for the major mode in
question. Ignores parentheses inside strings."
(save-excursion
(when pos (goto-char pos))
(and (sp--looking-at-p (sp--get-allowed-regexp))
(not (evil-cp--inside-string-p)))))
(defun evil-cp--looking-at-any-delimiter (&optional pos)
"Predicate that returns true if point is on top of a
parentheses or a string delimiter as defined by smartparens for
the major mode in question."
(save-excursion
(when pos (goto-char pos))
(or (sp--looking-at-p (sp--get-stringlike-regexp))
(evil-cp--looking-at-paren-p))))
(defun evil-cp--looking-at-string-opening-p (&optional pos)
"Predicate for checking if point is on a opening string delimiter."
(save-excursion
(when pos (goto-char pos))
(and (evil-cp--looking-at-string-delimiter-p)
(progn
(forward-char)
(nth 3 (syntax-ppss))))))
(defun evil-cp--looking-at-string-closing-p (&optional pos)
"Predicate for checking if point is on a closing delimiter."
(save-excursion
(when pos (goto-char pos))
(and (evil-cp--looking-at-string-delimiter-p)
(not (paredit-in-string-escape-p))
(nth 3 (syntax-ppss)))))
(defun evil-cp--looking-at-any-opening-p (&optional pos)
"Predicate to check if point (or POS) is on an opening
parentheses or a string delimiter."
(or (evil-cp--looking-at-opening-p pos)
(evil-cp--looking-at-string-opening-p pos)))
(defun evil-cp--looking-at-any-closing-p (&optional pos)
"Predicate to check if point (or POS) is on an opening
parentheses or a string delimiter."
(or (evil-cp--looking-at-closing-p pos)
(evil-cp--looking-at-string-closing-p pos)))
(defun evil-cp-region-ok-p (beg end)
"The same as `sp-region-ok-p' where `' are not treated as a
valid pair."
(let ((sp-pair-list evil-cp-sp-pair-list))
(sp-region-ok-p beg end)))
(defmacro evil-cp--guard-point (&rest body)
"Evil/Vim and Emacs have different opinions on where the point
is with respect to the visible cursor. This macro is used to make
sure that commands that are used to the Emacs view still work
when the cursor in evil is on top of an opening parentheses or a
string delimiter."
`(if (evil-cp--looking-at-any-opening-p)
(save-excursion
(forward-char 1)
,@body)
,@body))
(defmacro evil-cp--point-after (&rest body)
"Return location of point after performing body."
`(save-excursion
,@body
(point)))
(defmacro evil-cp--movement-bounds (movement)
"Returns a cons-pair containing the range of motion that
executing MOVEMENT performs or nil if point wasn't moved."
(let ((beg (cl-gensym))
(end (cl-gensym)))
`(let ((,beg (point))
(,end (evil-cp--point-after ,movement)))
(when (not (= ,beg ,end))
(cons ,beg ,end)))))
(defmacro evil-cp--safe-line-bounds (&rest body)
"Performs the actions in BODY and checks if the line where
point lands is a safe region, in which case its bounds are
returned."
`(save-excursion
,@body
(let ((pbol (point-at-bol))
(peol (point-at-eol)))
(when (and (sp-region-ok-p pbol peol))
(cons pbol peol)))))
(defmacro evil-cp--next-thing-bounds (&optional movement by-line-p)
"Fetches the bounds for the next hing which may be a symbol, a
form, a line or a comment block."
`(save-excursion
,movement
(evil-cp--next-thing-bounds* ,by-line-p)))
(defmacro evil-cp--previous-thing-bounds (&optional movement by-line-p)
"Fetches the bounds for the previous thing which may be a
symbol, a form, a line or a comment block."
`(save-excursion
,movement
(evil-cp--previous-thing-bounds* ,by-line-p)))
(defun evil-cp--looking-at-empty-form ()
"A predicate for checking if the point is currently looking at
an empty form."
(evil-cp--guard-point
(or (sp-point-in-empty-sexp)
(sp-point-in-empty-string))))
(defun evil-cp--inside-form-p (&optional pos)
"Predicate for checking if point is inside a sexp."
(save-excursion
(when pos (goto-char pos))
(when (evil-cp--looking-at-opening-p) (forward-char))
(not (zerop (nth 0 (syntax-ppss))))))
(defun evil-cp--inside-string-p (&optional pos)
"Predicate that returns true if point is inside a string."
(save-excursion
(when pos (goto-char pos))
(when (not (or (eobp) (bobp)))
(let ((string-ppss (nth 3 (syntax-ppss))))
(or string-ppss
(progn
(forward-char)
(nth 3 (syntax-ppss))))))))
(defun evil-cp--inside-any-form-p (&optional pos)
"Predicate that returns true if point is either inside a sexp
or a string."
(or (evil-cp--inside-form-p pos)
(evil-cp--inside-string-p pos)))
(defun evil-cp--top-level-form-p (&optional pos)
"Predicate that returns true if point is inside a top-level sexp."
(save-excursion
(when pos (goto-char pos))
(evil-cp--guard-point
(let* ((ppss (syntax-ppss))
(n0 (nth 0 ppss))
(n3 (nth 3 ppss)))
(or (and (eq n0 1) (not n3)) ; top-level sexp
(and (eq n0 0) n3)))))) ; top-level string
(defun evil-cp--outside-form-p (&optional pos)
"Prediacate for checking if the point is outside any form or
string."
(let ((sppss (syntax-ppss pos)))
(and (zerop (car sppss))
(not (nth 3 sppss)))))
(defun evil-cp--string-bounds (&optional pos)
"Returns the location of the beginning and ending positions for
a string form. Accepts an optional argument POS for moving the
point to that location."
(let ((pos (or pos (point))))
(save-excursion
(goto-char pos)
(or (sp-get-quoted-string-bounds)
(progn
(forward-char)
(sp-get-quoted-string-bounds))))))
(defun evil-cp--point-in-comment (&optional pos)
"Slightly cheaper to check version of `sp-point-in-comment'. Does not
support comments with open/closing delimiters."
(setq pos (or pos (point)))
(or (looking-at "\\s<")
(save-excursion
(nth 4 (syntax-ppss pos)))))
(defun evil-cp--point-in-string-or-comment (&optional pos)
"Slightly cheaper to check version of
`sp-point-in-string-or-comment'. Does not support comments with
open/closing delimiters."
(or (evil-cp--point-in-comment pos)
(sp-point-in-string pos)))
(defun evil-cp--skip-whitespace-and-comments (&optional reversep)
"Skips whitespace and comments forward."
(catch 'stop
(if reversep
(while (or (looking-back evil-cp--ws-regexp)
(evil-cp--point-in-comment (1- (point))))
(backward-char)
(when (bobp) (throw 'stop :bobp)))
(while (or (looking-at evil-cp--ws-regexp)
(evil-cp--point-in-comment))
(forward-char)
(when (looking-at sp-comment-char)
(forward-line))
(when (eobp) (throw 'stop :eobp))))))
(defun evil-cp--backward-up-list (&optional ignore-strings-p)
"Workaround for `backward-up-list' not working inside strings.
If IGNORE-STRINGS-P is t then strings are ignored when moving up.
Otherwise they are treated as lists. Returns the location of
point when the operation was successful."
(interactive)
(when (let ((sppss (syntax-ppss)))
(or (not (zerop (car sppss)))
(nth 3 sppss)))
(if (not (in-string-p))
(progn
(backward-up-list)
(point))
(when (not (and ignore-strings-p (evil-cp--top-level-form-p)))
(while (in-string-p)
(backward-char))
(when ignore-strings-p
(backward-up-list))
(point)))))
(defun evil-cp--up-list (&optional ignore-strings-p)
"Workaround for `up-list' not working inside strings. If
IGNORE-STRINGS-P is t then strings are ignored when moving up.
Otherwise they are treated as lists. Returns the location of
point when the operation was successful."
(interactive)
(when (let ((sppss (syntax-ppss)))
(or (not (zerop (car sppss)))
(nth 3 sppss)))
(if (not (in-string-p))
(progn
(up-list)
(point))
(when (not (and ignore-strings-p (evil-cp--top-level-form-p)))
(while (in-string-p)
(forward-char))
(when ignore-strings-p
(up-list))
(point)))))
(defun evil-cp--top-level-bounds (&optional pos)
"Returns the bounds for the top-level form point is in, or POS
if given. Note that this is different from defun-bounds, as defun
would ignore top-level forms that are on the same line."
(save-excursion
(when pos (goto-char pos))
(when (evil-cp--inside-form-p)
(catch 'done
(while t
(evil-cp--backward-up-list)
(when (evil-cp--top-level-form-p)
(throw 'done (cons (point) (forward-list)))))))))
(defun evil-cp--beginning-of-top-level ()
(when (evil-cp--inside-form-p)
(goto-char (car (evil-cp--top-level-bounds)))))
(defun evil-cp--end-of-top-level ()
(when (evil-cp--inside-form-p)
(goto-char (cdr (evil-cp--top-level-bounds)))))
(defun evil-cp--matching-paren-pos (&optional pos)
"Finds the location of the matching paren for where point is
located. Also works for strings. Takes an optional POS argument
for temporarily moving the point to before proceeding.
Assumes that the parentheses in the buffer are balanced."
(save-excursion
(cond ((evil-cp--looking-at-opening-p)
(progn
(sp-forward-sexp 1)
(backward-char)
(point)))
((evil-cp--looking-at-closing-p)
(progn
(forward-char 1)
(sp-backward-sexp 1)
(point)))
((looking-at "\"")
(let* ((bounds (evil-cp--string-bounds)))
(if (= (car bounds) (point))
(1- (cdr bounds))
(car bounds)))))))
(defun evil-cp--text-balanced-p (text)
"Checks if the string TEXT is balanced or not."
(with-temp-buffer
(insert text)
(sp-region-ok-p (point-min) (point-max))))
(defun evil-cp--balanced-block-p (beg end)
"Checks whether the block defined by BEG and END contains
balanced parentheses."
(let ((region (evil-yank-rectangle beg end)))
(with-temp-buffer
(insert region)
(sp-region-ok-p (point-min) (point-max)))))
(defun evil-cp--sp-obj-bounds (thing)
"Helper for turning a smartparens text object into a cons-pair."
(sp-get thing
(when :beg (cons :beg :end))))
(defun evil-cp--get-enclosing-bounds (&optional prefixp)
"Returns a tuple of start/end positions for the surrounding
sexp. If PREFIXP is true, then the beginning bound starts from
the beginning of the prefix as defined by `sp-sexp-prefix'."
(when (evil-cp--inside-any-form-p)
(save-excursion
(evil-cp--guard-point
(sp-get (if (sp-point-in-string (point))
(sp-get-string t)
(sp-get-enclosing-sexp))
(cons (if (and prefixp (not (string-empty-p :prefix)))
(- :beg (length :prefix))
:beg)
:end))))))
(defun evil-cp--next-sexp-bounds (&optional pos)
(save-excursion
(when pos (goto-char pos))
(when (not (evil-cp--looking-at-whitespace-p))
(evil-cp--movement-bounds (forward-sexp)))))
(provide 'evil-cleverparens-util)
;;; evil-cleverparens-util.el ends here