comparison elpa/elpy-1.25.0/elpy-refactor.el @ 182:c3bd84985977

upgrade elpy to 1.25
author Jordi Gutiérrez Hermoso <jordigh@octave.org>
date Thu, 11 Oct 2018 15:38:33 -0400
parents elpa/elpy-1.18.0/elpy-refactor.el@56ea66d76309
children
comparison
equal deleted inserted replaced
181:5ff62f07dd47 182:c3bd84985977
1 ;;; elpy-refactor.el --- Refactoring mode for Elpy
2
3 ;; Copyright (C) 2013-2016 Jorgen Schaefer
4
5 ;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>
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 Rope library.
25
26 ;;; Code:
27
28 ;; We require elpy, but elpy loads us, so we shouldn't load it back.
29 ;; (require 'elpy)
30
31 (defvar elpy-refactor-changes nil
32 "Changes that will be commited on \\[elpy-refactor-commit].")
33 (make-variable-buffer-local 'elpy-refactor-current-changes)
34
35 (defvar elpy-refactor-window-configuration nil
36 "The old window configuration. Will be restored after commit.")
37 (make-variable-buffer-local 'elpy-refactor-window-configuration)
38
39 (make-obsolete
40 'elpy-refactor
41 "Refactoring has been unstable and flakey, support will be dropped in the future."
42 "elpy 1.5.0")
43 (defun elpy-refactor ()
44 "Run the Elpy refactoring interface for Python code."
45 (interactive)
46 (save-some-buffers)
47 (let* ((selection (elpy-refactor-select
48 (elpy-refactor-rpc-get-options)))
49 (method (car selection))
50 (args (cdr selection)))
51 (when method
52 (elpy-refactor-create-change-buffer
53 (elpy-refactor-rpc-get-changes method args)))))
54
55 (defun elpy-refactor-select (options)
56 "Show the user the refactoring options and let her choose one.
57
58 Depending on the chosen option, ask the user for further
59 arguments and build the argument.
60
61 Return a cons cell of the name of the option and the arg list
62 created."
63 (let ((buf (get-buffer-create "*Elpy Refactor*"))
64 (pos (vector (1- (point))
65 (ignore-errors
66 (1- (region-beginning)))
67 (ignore-errors
68 (1- (region-end)))))
69 (inhibit-read-only t)
70 (options (sort options
71 (lambda (a b)
72 (let ((cata (cdr (assq 'category a)))
73 (catb (cdr (assq 'category b))))
74 (if (equal cata catb)
75 (string< (cdr (assq 'description a))
76 (cdr (assq 'description b)))
77 (string< cata catb))))))
78 (key ?a)
79 last-category
80 option-alist)
81 (with-current-buffer buf
82 (erase-buffer)
83 (dolist (option options)
84 (let ((category (cdr (assq 'category option)))
85 (description (cdr (assq 'description option)))
86 (name (cdr (assq 'name option)))
87 (args (cdr (assq 'args option))))
88 (when (not (equal category last-category))
89 (when last-category
90 (insert "\n"))
91 (insert (propertize category 'face 'bold) "\n")
92 (setq last-category category))
93 (insert " (" key ") " description "\n")
94 (setq option-alist (cons (list key name args)
95 option-alist))
96 (setq key (1+ key))))
97 (let ((window-conf (current-window-configuration)))
98 (unwind-protect
99 (progn
100 (with-selected-window (display-buffer buf)
101 (goto-char (point-min)))
102 (fit-window-to-buffer (get-buffer-window buf))
103 (let* ((key (read-key "Refactoring action? "))
104 (entry (cdr (assoc key option-alist))))
105 (kill-buffer buf)
106 (cons (car entry) ; name
107 (elpy-refactor-build-arguments (cadr entry)
108 pos))))
109 (set-window-configuration window-conf))))))
110
111 (defun elpy-refactor-build-arguments (args pos)
112 "Translate an argument list specification to an argument list.
113
114 POS is a vector of three elements, the current offset, the offset
115 of the beginning of the region, and the offset of the end of the
116 region.
117
118 ARGS is a list of triples, each triple containing the name of an
119 argument (ignored), the type of the argument, and a possible
120 prompt string.
121
122 Available types:
123
124 offset - The offset in the buffer, (1- (point))
125 start_offset - Offset of the beginning of the region
126 end_offset - Offset of the end of the region
127 string - A free-form string
128 filename - A non-existing file name
129 directory - An existing directory name
130 boolean - A boolean question"
131 (mapcar (lambda (arg)
132 (let ((type (cadr arg))
133 (prompt (cl-caddr arg)))
134 (cond
135 ((equal type "offset")
136 (aref pos 0))
137 ((equal type "start_offset")
138 (aref pos 1))
139 ((equal type "end_offset")
140 (aref pos 2))
141 ((equal type "string")
142 (read-from-minibuffer prompt))
143 ((equal type "filename")
144 (expand-file-name
145 (read-file-name prompt)))
146 ((equal type "directory")
147 (expand-file-name
148 (read-directory-name prompt)))
149 ((equal type "boolean")
150 (y-or-n-p prompt)))))
151 args))
152
153 (defun elpy-refactor-create-change-buffer (changes)
154 "Show the user a buffer of changes.
155
156 The user can review the changes and confirm them with
157 \\[elpy-refactor-commit]."
158 (when (not changes)
159 (error "No changes for this refactoring action."))
160 (with-current-buffer (get-buffer-create "*Elpy Refactor*")
161 (elpy-refactor-mode)
162 (setq elpy-refactor-changes changes
163 elpy-refactor-window-configuration (current-window-configuration))
164 (let ((inhibit-read-only t))
165 (erase-buffer)
166 (elpy-refactor-insert-changes changes))
167 (select-window (display-buffer (current-buffer)))
168 (goto-char (point-min))))
169
170 (defun elpy-refactor-insert-changes (changes)
171 "Format and display the changes described in CHANGES."
172 (insert (propertize "Use C-c C-c to apply the following changes."
173 'face 'bold)
174 "\n\n")
175 (dolist (change changes)
176 (let ((action (cdr (assq 'action change))))
177 (cond
178 ((equal action "change")
179 (insert (cdr (assq 'diff change))
180 "\n"))
181 ((equal action "create")
182 (let ((type (cdr (assq 'type change))))
183 (if (equal type "file")
184 (insert "+++ " (cdr (assq 'file change)) "\n"
185 "Create file " (cdr (assq 'file change)) "\n"
186 "\n")
187 (insert "+++ " (cdr (assq 'path change)) "\n"
188 "Create directory " (cdr (assq 'path change)) "\n"
189 "\n"))))
190 ((equal action "move")
191 (insert "--- " (cdr (assq 'source change)) "\n"
192 "+++ " (cdr (assq 'destination change)) "\n"
193 "Rename " (cdr (assq 'type change)) "\n"
194 "\n"))
195 ((equal action "delete")
196 (let ((type (cdr (assq 'type change))))
197 (if (equal type "file")
198 (insert "--- " (cdr (assq 'file change)) "\n"
199 "Delete file " (cdr (assq 'file change)) "\n"
200 "\n")
201 (insert "--- " (cdr (assq 'path change)) "\n"
202 "Delete directory " (cdr (assq 'path change)) "\n"
203 "\n"))))))))
204
205 (defvar elpy-refactor-mode-map
206 (let ((map (make-sparse-keymap)))
207 (define-key map (kbd "C-c C-c") 'elpy-refactor-commit)
208 (define-key map (kbd "q") 'bury-buffer)
209 (define-key map (kbd "h") 'describe-mode)
210 (define-key map (kbd "?") 'describe-mode)
211 map)
212 "The key map for `elpy-refactor-mode'.")
213
214 (define-derived-mode elpy-refactor-mode diff-mode "Elpy Refactor"
215 "Mode to display refactoring actions and ask confirmation from the user.
216
217 \\{elpy-refactor-mode-map}"
218 :group 'elpy
219 (view-mode 1))
220
221 (defun elpy-refactor-commit ()
222 "Commit the changes in the current buffer."
223 (interactive)
224 (when (not elpy-refactor-changes)
225 (error "No changes to commit."))
226 ;; Restore the window configuration as the first thing so that
227 ;; changes below are visible to the user. Especially the point
228 ;; change in possible buffer changes.
229 (set-window-configuration elpy-refactor-window-configuration)
230 (dolist (change elpy-refactor-changes)
231 (let ((action (cdr (assq 'action change))))
232 (cond
233 ((equal action "change")
234 (with-current-buffer (find-file-noselect (cdr (assq 'file change)))
235 ;; This would break for save-excursion as the buffer is
236 ;; truncated, so all markets now point to position 1.
237 (let ((old-point (point)))
238 (undo-boundary)
239 (erase-buffer)
240 (insert (cdr (assq 'contents change)))
241 (undo-boundary)
242 (goto-char old-point))))
243 ((equal action "create")
244 (if (equal (cdr (assq 'type change))
245 "file")
246 (find-file-noselect (cdr (assq 'file change)))
247 (make-directory (cdr (assq 'path change)))))
248 ((equal action "move")
249 (let* ((source (cdr (assq 'source change)))
250 (dest (cdr (assq 'destination change)))
251 (buf (get-file-buffer source)))
252 (when buf
253 (with-current-buffer buf
254 (setq buffer-file-name dest)
255 (rename-buffer (file-name-nondirectory dest) t)))
256 (rename-file source dest)))
257 ((equal action "delete")
258 (if (equal (cdr (assq 'type change)) "file")
259 (let ((name (cdr (assq 'file change))))
260 (when (y-or-n-p (format "Really delete %s? " name))
261 (delete-file name t)))
262 (let ((name (cdr (assq 'directory change))))
263 (when (y-or-n-p (format "Really delete %s? " name))
264 (delete-directory name nil t))))))))
265 (kill-buffer (current-buffer)))
266
267 (defun elpy-refactor-rpc-get-options ()
268 "Get a list of refactoring options from the Elpy RPC."
269 (if (use-region-p)
270 (elpy-rpc "get_refactor_options"
271 (list (buffer-file-name)
272 (1- (region-beginning))
273 (1- (region-end))))
274 (elpy-rpc "get_refactor_options"
275 (list (buffer-file-name)
276 (1- (point))))))
277
278 (defun elpy-refactor-rpc-get-changes (method args)
279 "Get a list of changes from the Elpy RPC after applying METHOD with ARGS."
280 (elpy-rpc "refactor"
281 (list (buffer-file-name)
282 method args)))
283
284 (defun elpy-refactor-options (option)
285 "Show available refactor options and let user choose one."
286 (interactive "c[i]: importmagic-fixup [p]: autopep8-fix-code [r]: refactor")
287 (let ((choice (char-to-string option)))
288 (cond
289 ((string-equal choice "i")
290 (elpy-importmagic-fixup))
291 ((string-equal choice "p")
292 (elpy-autopep8-fix-code))
293 ((string-equal choice "r")
294 (elpy-refactor)))))
295
296 (provide 'elpy-refactor)
297 ;;; elpy-refactor.el ends here