]> crepu.dev Git - config.git/blob - djavu-asus/elpa/elpy-20230803.1455/elpy-refactor.el
ActualizaciĆ³n de Readme
[config.git] / djavu-asus / elpa / elpy-20230803.1455 / elpy-refactor.el
1 ;;; elpy-refactor.el --- Refactoring mode for Elpy
2
3 ;; Copyright (C) 2020 Gaby Launay
4
5 ;; Author: Gaby Launay <gaby.launay@protonmail.com>
6 ;; URL: https://github.com/jorgenschaefer/elpy
7
8 ;; This program is free software; you can redistribute it and/or
9 ;; modify it under the terms of the GNU General Public License
10 ;; as published by the Free Software Foundation; either version 3
11 ;; of the License, or (at your option) any later version.
12
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 ;;; Commentary:
22
23 ;; This file provides an interface, including a major mode, to use
24 ;; refactoring options provided by the Jedi library.
25
26 ;;; Code:
27
28 ;; We require elpy, but elpy loads us, so we shouldn't load it back.
29 ;; (require 'elpy)
30 (require 'diff-mode)
31
32
33 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
34 ;;; Refactor mode (for applying diffs)
35
36 (defvar elpy-refactor--saved-window-configuration nil
37 "Saved windows configuration, so that we can restore it after `elpy-refactor' has done its thing.")
38
39 (defvar elpy-refactor--saved-pos nil
40 "Line and column number of the position we were at before starting refactoring.")
41
42 (defvar elpy-refactor--modified-buffers '()
43 "Keep track of the buffers modified by the current refactoring sessions.")
44
45 (defun elpy-refactor--apply-diff (proj-path diff)
46 "Apply DIFF, looking for the files in PROJ-PATH."
47 (let ((current-line (line-number-at-pos (point)))
48 (current-col (- (point) (line-beginning-position))))
49 (with-current-buffer (get-buffer-create " *Elpy Refactor*")
50 (elpy-refactor-mode)
51 (let ((inhibit-read-only t))
52 (erase-buffer)
53 (insert diff))
54 (setq default-directory proj-path)
55 (goto-char (point-min))
56 (elpy-refactor--apply-whole-diff))
57 (condition-case nil
58 (progn
59 (goto-char (point-min))
60 (forward-line (- current-line 1))
61 (beginning-of-line)
62 (forward-char current-col))
63 (error))
64 ))
65
66 (defun elpy-refactor--display-diff (proj-path diff)
67 "Display DIFF in a `diff-mode' window.
68
69 DIFF files should be relative to PROJ-PATH."
70 (setq elpy-refactor--saved-window-configuration (current-window-configuration)
71 elpy-refactor--saved-pos (list (line-number-at-pos (point) t)
72 (- (point) (line-beginning-position)))
73 elpy-refactor--modified-buffers '())
74 (with-current-buffer (get-buffer-create "*Elpy Refactor*")
75 (elpy-refactor-mode)
76 (let ((inhibit-read-only t))
77 (erase-buffer)
78 (insert (propertize
79 (substitute-command-keys
80 (concat
81 "\\[diff-file-next] and \\[diff-file-prev] -- Move between files\n"
82 "\\[diff-hunk-next] and \\[diff-hunk-prev] -- Move between hunks\n"
83 "\\[diff-split-hunk] -- Split the current hunk at point\n"
84 "\\[elpy-refactor--apply-hunk] -- Apply the current hunk\n"
85 "\\[diff-kill-hunk] -- Kill the current hunk\n"
86 "\\[elpy-refactor--apply-whole-diff] -- Apply the whole diff\n"
87 "\\[elpy-refactor--quit] -- Quit\n"))
88 'face 'bold)
89 "\n\n")
90 (align-regexp (point-min) (point-max) "\\(\\s-*\\) -- ")
91 (goto-char (point-min))
92 (while (search-forward " -- " nil t)
93 (replace-match " " nil t))
94 (goto-char (point-max))
95 (insert diff))
96 (setq default-directory proj-path)
97 (goto-char (point-min))
98 (if (diff--some-hunks-p)
99 (progn
100 (select-window (display-buffer (current-buffer)))
101 (diff-hunk-next))
102 ;; quit if not diff at all...
103 (message "No differences to validate")
104 (kill-buffer (current-buffer)))))
105
106 (defvar elpy-refactor-mode-map
107 (let ((map (make-sparse-keymap)))
108 (define-key map (kbd "C-c C-c") 'elpy-refactor--apply-hunk)
109 (define-key map (kbd "C-c C-a") 'elpy-refactor--apply-whole-diff)
110 (define-key map (kbd "C-c C-x") 'diff-kill-hunk)
111 (define-key map (kbd "q") 'elpy-refactor--quit)
112 (define-key map (kbd "C-c C-k") 'elpy-refactor--quit)
113 (define-key map (kbd "h") 'describe-mode)
114 (define-key map (kbd "?") 'describe-mode)
115 map)
116 "The key map for `elpy-refactor-mode'.")
117
118 (define-derived-mode elpy-refactor-mode diff-mode "Elpy Refactor"
119 "Mode to display refactoring actions and ask confirmation from the user.
120
121 \\{elpy-refactor-mode-map}"
122 :group 'elpy
123 (view-mode 1))
124
125 (defun elpy-refactor--apply-hunk ()
126 "Apply the current hunk."
127 (interactive)
128 (save-excursion
129 (diff-apply-hunk))
130 ;; keep track of modified buffers
131 (let ((buf (find-buffer-visiting (diff-find-file-name))))
132 (when buf
133 (add-to-list 'elpy-refactor--modified-buffers buf)))
134 ;;
135 (diff-hunk-kill)
136 (unless (diff--some-hunks-p)
137 (elpy-refactor--quit)))
138
139 (defun elpy-refactor--apply-whole-diff ()
140 "Apply the whole diff and quit."
141 (interactive)
142 (goto-char (point-min))
143 (diff-hunk-next)
144 (while (diff--some-hunks-p)
145 (let ((buf (find-buffer-visiting (diff-find-file-name))))
146 (when buf
147 (add-to-list 'elpy-refactor--modified-buffers buf)))
148 (condition-case nil
149 (progn
150 (save-excursion
151 (diff-apply-hunk))
152 (diff-hunk-kill))
153 (error (diff-hunk-next)))) ;; if a hunk fail, switch to the next one
154 ;; quit
155 (elpy-refactor--quit))
156
157 (defun elpy-refactor--quit ()
158 "Quit the refactoring session."
159 (interactive)
160 ;; save modified buffers
161 (dolist (buf elpy-refactor--modified-buffers)
162 (with-current-buffer buf
163 (basic-save-buffer)))
164 (setq elpy-refactor--modified-buffers '())
165 ;; kill refactoring buffer
166 (kill-buffer (current-buffer))
167 ;; Restore window configuration
168 (when elpy-refactor--saved-window-configuration
169 (set-window-configuration elpy-refactor--saved-window-configuration)
170 (setq elpy-refactor--saved-window-configuration nil))
171 ;; Restore cursor position
172 (when elpy-refactor--saved-pos
173 (goto-char (point-min))
174 (forward-line (- (car elpy-refactor--saved-pos) 1))
175 (forward-char (car (cdr elpy-refactor--saved-pos)))
176 (setq elpy-refactor--saved-pos nil)))
177
178
179
180 ;;;;;;;;;;;;;;;;;
181 ;; User functions
182
183 (defun elpy-refactor-rename (new-name &optional dontask)
184 "Rename the symbol at point to NEW-NAME.
185
186 With a prefix argument (or if DONTASK is non-nil),
187 do not display the diff before applying."
188 (interactive (list
189 (let ((old-name (thing-at-point 'symbol)))
190 (if (or (not old-name)
191 (not (elpy-refactor--is-valid-symbol-p old-name)))
192 (error "No symbol at point")
193 (read-string
194 (format "New name for '%s': "
195 (thing-at-point 'symbol))
196 (thing-at-point 'symbol))))))
197 (unless (and new-name
198 (elpy-refactor--is-valid-symbol-p new-name))
199 (error "'%s' is not a valid python symbol"))
200 (message "Gathering occurences of '%s'..."
201 (thing-at-point 'symbol))
202 (let* ((elpy-rpc-timeout 10) ;; refactoring can be long...
203 (diff (elpy-rpc-get-rename-diff new-name))
204 (proj-path (alist-get 'project_path diff))
205 (success (alist-get 'success diff))
206 (diff (alist-get 'diff diff)))
207 (cond ((not success)
208 (error "Refactoring failed for some reason"))
209 ((string= success "Not available")
210 (error "This functionnality needs jedi > 0.17.0, please update"))
211 ((or dontask current-prefix-arg)
212 (message "Replacing '%s' with '%s'..."
213 (thing-at-point 'symbol)
214 new-name)
215 (elpy-refactor--apply-diff proj-path diff)
216 (message "Done"))
217 (t
218 (elpy-refactor--display-diff proj-path diff)))))
219
220 (defun elpy-refactor-extract-variable (new-name)
221 "Extract the current region to a new variable NEW-NAME."
222 (interactive "sNew name: ")
223 (let ((beg (if (region-active-p)
224 (region-beginning)
225 (car (or (bounds-of-thing-at-point 'symbol)
226 (error "No symbol at point")))))
227 (end (if (region-active-p)
228 (region-end)
229 (cdr (bounds-of-thing-at-point 'symbol)))))
230 (when (or (elpy-refactor--is-valid-symbol-p new-name)
231 (y-or-n-p "'%s' does not appear to be a valid python symbol. Are you sure you want to use it? "))
232 (let* ((line-beg (save-excursion
233 (goto-char beg)
234 (line-number-at-pos)))
235 (line-end (save-excursion
236 (goto-char end)
237 (line-number-at-pos)))
238 (col-beg (save-excursion
239 (goto-char beg)
240 (- (point) (line-beginning-position))))
241 (col-end (save-excursion
242 (goto-char end)
243 (- (point) (line-beginning-position))))
244 (diff (elpy-rpc-get-extract-variable-diff
245 new-name line-beg line-end col-beg col-end))
246 (proj-path (alist-get 'project_path diff))
247 (success (alist-get 'success diff))
248 (diff (alist-get 'diff diff)))
249 (cond ((not success)
250 (error "We could not extract the selection as a variable"))
251 ((string= success "Not available")
252 (error "This functionnality needs jedi > 0.17.0, please update"))
253 (t
254 (deactivate-mark)
255 (elpy-refactor--apply-diff proj-path diff)))))))
256
257 (defun elpy-refactor-extract-function (new-name)
258 "Extract the current region to a new function NEW-NAME."
259 (interactive "sNew function name: ")
260 (unless (region-active-p)
261 (error "No selection"))
262 (when (or (elpy-refactor--is-valid-symbol-p new-name)
263 (y-or-n-p "'%s' does not appear to be a valid python symbol. Are you sure you want to use it? "))
264 (let* ((line-beg (save-excursion
265 (goto-char (region-beginning))
266 (line-number-at-pos)))
267 (line-end (save-excursion
268 (goto-char (region-end))
269 (line-number-at-pos)))
270 (col-beg (save-excursion
271 (goto-char (region-beginning))
272 (- (point) (line-beginning-position))))
273 (col-end (save-excursion
274 (goto-char (region-end))
275 (- (point) (line-beginning-position))))
276 (diff (elpy-rpc-get-extract-function-diff
277 new-name line-beg line-end col-beg col-end))
278 (proj-path (alist-get 'project_path diff))
279 (success (alist-get 'success diff))
280 (diff (alist-get 'diff diff)))
281 (cond ((not success)
282 (error "We could not extract the selection as a function"))
283 ((string= success "Not available")
284 (error "This functionnality needs jedi > 0.17.0, please update"))
285 (t
286 (deactivate-mark)
287 (elpy-refactor--apply-diff proj-path diff))))))
288
289 (defun elpy-refactor-inline ()
290 "Inline the variable at point."
291 (interactive)
292 (let* ((diff (elpy-rpc-get-inline-diff))
293 (proj-path (alist-get 'project_path diff))
294 (success (alist-get 'success diff))
295 (diff (alist-get 'diff diff)))
296 (cond ((not success)
297 (error "We could not inline the variable '%s'"
298 (thing-at-point 'symbol)))
299 ((string= success "Not available")
300 (error "This functionnality needs jedi > 0.17.0, please update"))
301 (t
302 (elpy-refactor--apply-diff proj-path diff)))))
303
304
305 ;;;;;;;;;;;;
306 ;; Utilities
307
308 (defun elpy-refactor--is-valid-symbol-p (symbol)
309 "Return t if SYMBOL is a valid python symbol."
310 (eq 0 (string-match "^[a-zA-Z_][a-zA-Z0-9_]*$" symbol)))
311
312 ;;;;;;;;;;;;
313 ;; Compatibility
314 (unless (fboundp 'diff--some-hunks-p)
315 (defun diff--some-hunks-p ()
316 (save-excursion
317 (goto-char (point-min))
318 (re-search-forward diff-hunk-header-re nil t))))
319
320 (provide 'elpy-refactor)
321 ;;; elpy-refactor.el ends here