Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
11 KiB

пре 4 година
  1. ;;; elpy-refactor.el --- Refactoring mode for Elpy
  2. ;; Copyright (C) 2013-2016 Jorgen Schaefer
  3. ;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>
  4. ;; URL: https://github.com/jorgenschaefer/elpy
  5. ;; This program is free software; you can redistribute it and/or
  6. ;; modify it under the terms of the GNU General Public License
  7. ;; as published by the Free Software Foundation; either version 3
  8. ;; of the License, or (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; This file provides an interface, including a major mode, to use
  17. ;; refactoring options provided by the Rope library.
  18. ;;; Code:
  19. ;; We require elpy, but elpy loads us, so we shouldn't load it back.
  20. ;; (require 'elpy)
  21. (defvar elpy-refactor-changes nil
  22. "Changes that will be commited on \\[elpy-refactor-commit].")
  23. (make-variable-buffer-local 'elpy-refactor-current-changes)
  24. (defvar elpy-refactor-window-configuration nil
  25. "The old window configuration. Will be restored after commit.")
  26. (make-variable-buffer-local 'elpy-refactor-window-configuration)
  27. (make-obsolete
  28. 'elpy-refactor
  29. "Refactoring has been unstable and flakey, support will be dropped in the future."
  30. "elpy 1.5.0")
  31. (defun elpy-refactor ()
  32. "Run the Elpy refactoring interface for Python code."
  33. (interactive)
  34. (save-some-buffers)
  35. (let* ((selection (elpy-refactor-select
  36. (elpy-refactor-rpc-get-options)))
  37. (method (car selection))
  38. (args (cdr selection)))
  39. (when method
  40. (elpy-refactor-create-change-buffer
  41. (elpy-refactor-rpc-get-changes method args)))))
  42. (defun elpy-refactor-select (options)
  43. "Show the user the refactoring options and let her choose one.
  44. Depending on the chosen option, ask the user for further
  45. arguments and build the argument.
  46. Return a cons cell of the name of the option and the arg list
  47. created."
  48. (let ((buf (get-buffer-create "*Elpy Refactor*"))
  49. (pos (vector (1- (point))
  50. (ignore-errors
  51. (1- (region-beginning)))
  52. (ignore-errors
  53. (1- (region-end)))))
  54. (inhibit-read-only t)
  55. (options (sort options
  56. (lambda (a b)
  57. (let ((cata (cdr (assq 'category a)))
  58. (catb (cdr (assq 'category b))))
  59. (if (equal cata catb)
  60. (string< (cdr (assq 'description a))
  61. (cdr (assq 'description b)))
  62. (string< cata catb))))))
  63. (key ?a)
  64. last-category
  65. option-alist)
  66. (with-current-buffer buf
  67. (erase-buffer)
  68. (dolist (option options)
  69. (let ((category (cdr (assq 'category option)))
  70. (description (cdr (assq 'description option)))
  71. (name (cdr (assq 'name option)))
  72. (args (cdr (assq 'args option))))
  73. (when (not (equal category last-category))
  74. (when last-category
  75. (insert "\n"))
  76. (insert (propertize category 'face 'bold) "\n")
  77. (setq last-category category))
  78. (insert " (" key ") " description "\n")
  79. (setq option-alist (cons (list key name args)
  80. option-alist))
  81. (setq key (1+ key))))
  82. (let ((window-conf (current-window-configuration)))
  83. (unwind-protect
  84. (progn
  85. (with-selected-window (display-buffer buf)
  86. (goto-char (point-min)))
  87. (fit-window-to-buffer (get-buffer-window buf))
  88. (let* ((key (read-key "Refactoring action? "))
  89. (entry (cdr (assoc key option-alist))))
  90. (kill-buffer buf)
  91. (cons (car entry) ; name
  92. (elpy-refactor-build-arguments (cadr entry)
  93. pos))))
  94. (set-window-configuration window-conf))))))
  95. (defun elpy-refactor-build-arguments (args pos)
  96. "Translate an argument list specification to an argument list.
  97. POS is a vector of three elements, the current offset, the offset
  98. of the beginning of the region, and the offset of the end of the
  99. region.
  100. ARGS is a list of triples, each triple containing the name of an
  101. argument (ignored), the type of the argument, and a possible
  102. prompt string.
  103. Available types:
  104. offset - The offset in the buffer, (1- (point))
  105. start_offset - Offset of the beginning of the region
  106. end_offset - Offset of the end of the region
  107. string - A free-form string
  108. filename - A non-existing file name
  109. directory - An existing directory name
  110. boolean - A boolean question"
  111. (mapcar (lambda (arg)
  112. (let ((type (cadr arg))
  113. (prompt (cl-caddr arg)))
  114. (cond
  115. ((equal type "offset")
  116. (aref pos 0))
  117. ((equal type "start_offset")
  118. (aref pos 1))
  119. ((equal type "end_offset")
  120. (aref pos 2))
  121. ((equal type "string")
  122. (read-from-minibuffer prompt))
  123. ((equal type "filename")
  124. (expand-file-name
  125. (read-file-name prompt)))
  126. ((equal type "directory")
  127. (expand-file-name
  128. (read-directory-name prompt)))
  129. ((equal type "boolean")
  130. (y-or-n-p prompt)))))
  131. args))
  132. (defun elpy-refactor-create-change-buffer (changes)
  133. "Show the user a buffer of changes.
  134. The user can review the changes and confirm them with
  135. \\[elpy-refactor-commit]."
  136. (when (not changes)
  137. (error "No changes for this refactoring action."))
  138. (with-current-buffer (get-buffer-create "*Elpy Refactor*")
  139. (elpy-refactor-mode)
  140. (setq elpy-refactor-changes changes
  141. elpy-refactor-window-configuration (current-window-configuration))
  142. (let ((inhibit-read-only t))
  143. (erase-buffer)
  144. (elpy-refactor-insert-changes changes))
  145. (select-window (display-buffer (current-buffer)))
  146. (goto-char (point-min))))
  147. (defun elpy-refactor-insert-changes (changes)
  148. "Format and display the changes described in CHANGES."
  149. (insert (propertize "Use C-c C-c to apply the following changes."
  150. 'face 'bold)
  151. "\n\n")
  152. (dolist (change changes)
  153. (let ((action (cdr (assq 'action change))))
  154. (cond
  155. ((equal action "change")
  156. (insert (cdr (assq 'diff change))
  157. "\n"))
  158. ((equal action "create")
  159. (let ((type (cdr (assq 'type change))))
  160. (if (equal type "file")
  161. (insert "+++ " (cdr (assq 'file change)) "\n"
  162. "Create file " (cdr (assq 'file change)) "\n"
  163. "\n")
  164. (insert "+++ " (cdr (assq 'path change)) "\n"
  165. "Create directory " (cdr (assq 'path change)) "\n"
  166. "\n"))))
  167. ((equal action "move")
  168. (insert "--- " (cdr (assq 'source change)) "\n"
  169. "+++ " (cdr (assq 'destination change)) "\n"
  170. "Rename " (cdr (assq 'type change)) "\n"
  171. "\n"))
  172. ((equal action "delete")
  173. (let ((type (cdr (assq 'type change))))
  174. (if (equal type "file")
  175. (insert "--- " (cdr (assq 'file change)) "\n"
  176. "Delete file " (cdr (assq 'file change)) "\n"
  177. "\n")
  178. (insert "--- " (cdr (assq 'path change)) "\n"
  179. "Delete directory " (cdr (assq 'path change)) "\n"
  180. "\n"))))))))
  181. (defvar elpy-refactor-mode-map
  182. (let ((map (make-sparse-keymap)))
  183. (define-key map (kbd "C-c C-c") 'elpy-refactor-commit)
  184. (define-key map (kbd "q") 'bury-buffer)
  185. (define-key map (kbd "h") 'describe-mode)
  186. (define-key map (kbd "?") 'describe-mode)
  187. map)
  188. "The key map for `elpy-refactor-mode'.")
  189. (define-derived-mode elpy-refactor-mode diff-mode "Elpy Refactor"
  190. "Mode to display refactoring actions and ask confirmation from the user.
  191. \\{elpy-refactor-mode-map}"
  192. :group 'elpy
  193. (view-mode 1))
  194. (defun elpy-refactor-commit ()
  195. "Commit the changes in the current buffer."
  196. (interactive)
  197. (when (not elpy-refactor-changes)
  198. (error "No changes to commit."))
  199. ;; Restore the window configuration as the first thing so that
  200. ;; changes below are visible to the user. Especially the point
  201. ;; change in possible buffer changes.
  202. (set-window-configuration elpy-refactor-window-configuration)
  203. (dolist (change elpy-refactor-changes)
  204. (let ((action (cdr (assq 'action change))))
  205. (cond
  206. ((equal action "change")
  207. (with-current-buffer (find-file-noselect (cdr (assq 'file change)))
  208. ;; This would break for save-excursion as the buffer is
  209. ;; truncated, so all markets now point to position 1.
  210. (let ((old-point (point)))
  211. (undo-boundary)
  212. (erase-buffer)
  213. (insert (cdr (assq 'contents change)))
  214. (undo-boundary)
  215. (goto-char old-point))))
  216. ((equal action "create")
  217. (if (equal (cdr (assq 'type change))
  218. "file")
  219. (find-file-noselect (cdr (assq 'file change)))
  220. (make-directory (cdr (assq 'path change)))))
  221. ((equal action "move")
  222. (let* ((source (cdr (assq 'source change)))
  223. (dest (cdr (assq 'destination change)))
  224. (buf (get-file-buffer source)))
  225. (when buf
  226. (with-current-buffer buf
  227. (setq buffer-file-name dest)
  228. (rename-buffer (file-name-nondirectory dest) t)))
  229. (rename-file source dest)))
  230. ((equal action "delete")
  231. (if (equal (cdr (assq 'type change)) "file")
  232. (let ((name (cdr (assq 'file change))))
  233. (when (y-or-n-p (format "Really delete %s? " name))
  234. (delete-file name t)))
  235. (let ((name (cdr (assq 'directory change))))
  236. (when (y-or-n-p (format "Really delete %s? " name))
  237. (delete-directory name nil t))))))))
  238. (kill-buffer (current-buffer)))
  239. (defun elpy-refactor-rpc-get-options ()
  240. "Get a list of refactoring options from the Elpy RPC."
  241. (if (use-region-p)
  242. (elpy-rpc "get_refactor_options"
  243. (list (buffer-file-name)
  244. (1- (region-beginning))
  245. (1- (region-end))))
  246. (elpy-rpc "get_refactor_options"
  247. (list (buffer-file-name)
  248. (1- (point))))))
  249. (defun elpy-refactor-rpc-get-changes (method args)
  250. "Get a list of changes from the Elpy RPC after applying METHOD with ARGS."
  251. (elpy-rpc "refactor"
  252. (list (buffer-file-name)
  253. method args)))
  254. (defun elpy-refactor-options (option)
  255. "Show available refactor options and let user choose one."
  256. (interactive "c[i]: importmagic-fixup [p]: autopep8-fix-code [r]: refactor")
  257. (let ((choice (char-to-string option)))
  258. (cond
  259. ((string-equal choice "i")
  260. (elpy-importmagic-fixup))
  261. ((string-equal choice "p")
  262. (elpy-autopep8-fix-code))
  263. ((string-equal choice "r")
  264. (elpy-refactor)))))
  265. (provide 'elpy-refactor)
  266. ;;; elpy-refactor.el ends here