-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdelimiter.el
131 lines (114 loc) · 4.19 KB
/
delimiter.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
;;; delimiter.el --- Add, change and delete delimiters around point -*- lexical-binding: t; -*-
;; Copyright (c) 2017, 2019 fmdkdd
;;
;; Author: fmdkdd
;; This file is not part of GNU Emacs.
;;; Commentary:
;; I wanted the functionality of evil-surround. There is `insert-pair'
;; built-in, but the code looks too complex for what I need it to do. And I
;; don't understand how you pass the delimiter char in the first place.
;;
;; I've looked at embrace.el ( https://github.com/cute-jumper/embrace.el ),
;; which is again, too complex for what I need, and does something weird with
;; overlays I don't understand.
;;
;; The following works for me, and uses only basic Elisp functions.
;;; Code:
(defvar delimiter-pair-alist
'((?\( . ?\))
(?\[ . ?\])
(?\{ . ?\})
(?\< . ?\>)
(?\" . ?\")
(?\' . ?\')
(?\` . ((emacs-lisp-mode . ?\')
(t . ?\`))))
"Alist of known delimiter pairs.
The value is either a single char corresponding to the closing
delimiter, or a list of pairs ((MODE . CHAR) ...) to specify a
mode-specific closing delimiter.")
(defun delimiter--get-closing (char)
"The closing delimiter for CHAR."
(let ((match (alist-get char delimiter-pair-alist)))
(cond
;; If a list, try to match the mode
((and match (listp match))
(cdr (seq-find
(pcase-lambda (`(,mode . _))
(or (eq mode t) (derived-mode-p mode)))
match)))
;; A single char, so return that
(match)
;; Unknown delimiter: assume it's symmetric
(t char))))
;;;###autoload
(defun delimiter-dwim (char)
"Surround or delete region or word with CHAR.
When CHAR is 'd', call `delimiter-delete' instead."
(interactive "cChar: ")
(cond
((eq char ?d) (call-interactively #'delimiter-delete))
((eq char ?c) (call-interactively #'delimiter-change))
(t (delimiter-surround char))))
;;;###autoload
(defun delimiter-surround (char)
"Surround active region or word with CHAR.
If region is active, surround that. Otherwise, surround the sexp
under point.
If CHAR is an opening delimiter in `insert-pair-alist', use the
closing delimiter of the pair to surround the region; use CHAR
otherwise."
(interactive "cChar: ")
;; goto-char may move the region when it is active, so save it
(let ((begin)
(end)
(closing (delimiter--get-closing char)))
(save-excursion
;; Get the boundaries of the sexp under point
(if (region-active-p)
(setq begin (region-beginning)
end (region-end))
(setq begin (progn
(unless (or (eq (point) (line-beginning-position))
(eq ? (preceding-char)))
(backward-sexp))
(point))
end (progn (forward-sexp) (point))))
(goto-char end)
(insert-char closing)
(goto-char begin)
(insert-char char))))
;;;###autoload
(defun delimiter-delete (char)
"Delete the nearest surrounding delimiters opening with CHAR."
(interactive "cChar to zap: ")
(let* ((opening-regex (concat "^" (char-to-string char)))
(closing (delimiter--get-closing char))
(closing-regex (concat "^" (char-to-string closing))))
(save-excursion
;; skip-chars-* over search-backward because the former has a bytecode op
(skip-chars-backward opening-regex)
(delete-char -1)
(skip-chars-forward closing-regex)
(delete-char 1))))
;;;###autoload
(defun delimiter-change (char newchar)
"Replace the surrounding CHAR delimiters with NEWCHAR."
(interactive "cChar to replace:\ncReplace with: ")
(let* ((opening-regex (concat "^" (char-to-string char)))
(closing (delimiter--get-closing char))
(closing-regex (concat "^" (char-to-string closing)))
(new-closing (delimiter--get-closing newchar)))
(save-excursion
;; skip-chars-* over search-backward because the former has a bytecode op
(skip-chars-backward opening-regex)
(delete-char -1)
(insert-char newchar)
(skip-chars-forward closing-regex)
(delete-char 1)
(insert-char new-closing))))
(provide 'delimiter)
;;; delimiter.el ends here
;; Local Variables:
;; eval: (fmdkdd/byte-compile-on-save)
;; End: