summaryrefslogtreecommitdiff
path: root/elpa/js2-refactor-20210306.2003/js2r-paredit.el
blob: 39075584c13a201fafc7c2fb681db505236aa6ca (plain)
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
;;; js2r-paredit.el --- Paredit-like extensions for js2-refactor    -*- lexical-binding: t; -*-

;; Copyright (C) 2012-2014 Magnar Sveen
;; Copyright (C) 2015-2016 Magnar Sveen and Nicolas Petton

;; Author: Magnar Sveen <magnars@gmail.com>,
;;         Nicolas Petton <nicolas@petton.fr>
;; Keywords: conveniences

;; 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 <http://www.gnu.org/licenses/>.

;;; Code:

(require 'dash)

(require 'js2r-helpers)

(defun js2r--nesting-node-p (node)
  (or (js2-function-node-p node)
      (js2-if-node-p node)
      (js2-for-node-p node)
      (js2-while-node-p node)))

(defun js2r--standalone-node-p (node)
  (or (js2-stmt-node-p node)
      (and (js2-function-node-p node)
           (eq 'FUNCTION_STATEMENT (js2-function-node-form node)))))

(defun js2r-kill ()
  "Kill a line like `kill-line' but tries to respect node boundaries.
Falls back to `kill-line' if the buffer has parse errors.

if(|foo) {bar();}       -> if() {bar();}

function foo() {|2 + 3} -> function foo() {}

// some |comment        -> // some

'this is a| string'     -> 'this is a'
"
  (interactive)
  (if js2-parsed-errors
      (progn
        (message "Buffer has parse errors.  Killing the line")
        (kill-line))
    (condition-case error
	(js2r--kill-line)
      (progn
	(message "Error occured while trying to kill AST node.  Killing the line.")
	(kill-line)))))

(defun js2r--kill-line ()
  "Kill a line, but respecting node boundaries."
  (let ((node (js2r--next-node)))
    (cond
     ((js2-comment-node-p node) (kill-line))
     ((js2-string-node-p node) (js2r--kill-line-in-string))
     (t (js2r--kill-line-in-sexp)))))

(defun js2r--next-node ()
  "Return the node at point, or the node after the point if the
  point is at the exact end of a node."
  (save-excursion
    (when (= (js2-node-abs-end (js2-node-at-point))
             (point))
      (forward-char 1))
    (js2-node-at-point)))

(defun js2r--kill-line-in-sexp ()
  "Kill a line, but only kills until the closest outer sexp on
  the current line, delimited with \")}]\". If no sexp is found
  on the current line, falls back to
  `js2r--kill-line-with-inner-sexp'."
  (condition-case error
      (let* ((beg (point))
             (end (save-excursion
                    (up-list)
                    (forward-char -1)
                    (point))))
        (if (js2-same-line end)
            (kill-region beg end)
          (js2r--kill-line-with-inner-sexp)))
    (scan-error
     (js2r--kill-line-with-inner-sexp))))

(defun js2r--kill-line-with-inner-sexp ()
  "Kill a line, but respecting inner killed sexps, ensuring that
we kill up to the end to the next inner sexp if it starts in
the current line.

If the parentheses are unbalanced, fallback to `kill-line' and
warn the user."
  (condition-case error
      (let* ((beg (point))
             (end (save-excursion
                    (forward-visible-line 1)
                    (point)))
             (beg-of-sexp (save-excursion
                            (js2r--goto-last-sexp-on-line)
                            (point)))
             (end-of-sexp (save-excursion
                            (goto-char beg-of-sexp)
                            (forward-list)
                            ;; Kill all remaining semi-colons as well
                            (while (looking-at ";")
                              (forward-char))
                            (point))))
        (if (js2-same-line beg-of-sexp)
            (kill-region beg (max end end-of-sexp))
          (kill-line)))
    (scan-error
     (message "Unbalanced parentheses. Killing the line.")
     (kill-line))))

(defun js2r--goto-last-sexp-on-line ()
  "Move the cursor to the opening of the last sexp on the current
line, or to the end of the line if no sexp is found."
  (let ((pos (point)))
    (down-list)
    (backward-char 1)
    (forward-list)
    (if (js2-same-line pos)
        (js2r--goto-last-sexp-on-line)
      (backward-list))))

(defun js2r--kill-line-in-string ()
  "Kill a line in a string node, respecting the node boundaries.
When at the beginning of the node, kill from outside of it."
  (let* ((node (js2-node-at-point))
         (beg (point))
         (node-start (js2-node-abs-pos node))
         (node-end (js2-node-abs-end node)))
    (if (= beg node-start)
        (js2r--kill-line-in-sexp)
      (kill-region beg (1- node-end)))))

(defun js2r-forward-slurp (&optional arg)
  "Add the expression following the current function into it.

The addition is performed by moving the closing brace of the
function down.

When called with a prefix argument ARG, slurp ARG expressions
following the current function."
  (interactive "p")
  (js2r--guard)
  (js2r--wait-for-parse
   (let* ((nesting (js2r--closest 'js2r--nesting-node-p))
	  (standalone (if (js2r--standalone-node-p nesting)
			  nesting
			(js2-node-parent-stmt nesting)))
	  (next-sibling (js2-node-next-sibling standalone))
	  (beg (js2-node-abs-pos next-sibling))
	  (last-sibling (if (wholenump arg)
			    (let ((num arg)
				  (iter-sibling next-sibling))
			      (while (> num 1) ;; Do next-sibling arg nbr of times
				(setq iter-sibling (js2-node-next-sibling iter-sibling))
				(setq num (1- num)))
			      iter-sibling)
			  next-sibling)) ;; No optional arg. Just use next-sibling
	  (end (js2-node-abs-end last-sibling))
	  (text (buffer-substring beg end)))
     (save-excursion
       (delete-region beg end)
       ;; Delete newline character if the deleted AST node was at the end of the line
       (goto-char beg)
       (when (and (eolp)
		  (not (eobp)))
	 (delete-char 1))
       (goto-char (js2-node-abs-end nesting))
       (forward-char -1)
       (when (looking-back "{ *")
	 (newline))
       (setq beg (point))
       (insert text)
       (when (looking-at " *}")
	 (newline))
       (setq end (point))
       (indent-region beg end)))))

(defun js2r-forward-barf (&optional arg)
  (interactive "p")
  (js2r--guard)
  (js2r--wait-for-parse
   (let* ((nesting (js2r--closest 'js2r--nesting-node-p))
	  (standalone (if (js2r--standalone-node-p nesting)
			  nesting
			(js2-node-parent-stmt nesting)))
	  (standalone-end (js2-node-abs-end standalone))
	  (last-child (car (last (if (js2-if-node-p nesting)
				     (js2-scope-kids (js2r--closest 'js2-scope-p))
				   (js2r--node-kids nesting)))))
	  (first-barf-child (if (wholenump arg)
				(let ((num arg)
				      (iter-child last-child))
				  (while (> num 1) ;; Do prev-sibling arg nbr of times
				    (setq iter-child (js2-node-prev-sibling iter-child))
				    (setq num (1- num)))
				  iter-child)
			      last-child)) ; No optional arg. Just use last-child
	  (last-child-beg (save-excursion
			    (goto-char (js2-node-abs-pos first-barf-child))
			    (skip-syntax-backward " ")
			    (while (looking-back "\n") (backward-char))
			    (point)))
	  (last-child-end (js2-node-abs-end last-child))
	  (text (buffer-substring last-child-beg last-child-end)))
     (save-excursion
       (js2r--execute-changes
	(list
	 (list :beg last-child-beg :end last-child-end :contents "")
	 (list :beg standalone-end :end standalone-end :contents text)))))))

(provide 'js2r-paredit)
;;; js2r-paredit.el ends here