mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-21 e75a20334813452c6912c090d70a0de2c805f94d
commit | author | age
5cb5f7 1 ;;; srefactor-ui.el --- A refactoring tool based on Semantic parser framework
C 2 ;;
3 ;; Filename: srefactor-ui.el
4 ;; Description: A refactoring tool based on Semantic parser framework
5 ;; Author: Tu, Do Hoang <tuhdo1710@gmail.com
6 ;; Maintainer: Tu, Do Hoang
7 ;; Created: Wed Feb 11 21:25:51 2015 (+0700)
8 ;; Version: 0.1
9 ;; Package-Requires: ()
10 ;; Last-Updated: Wed Feb 11 21:25:51 2015 (+0700)
11 ;;           By: Tu, Do Hoang
12 ;;     Update #: 1
13 ;; URL:
14 ;; Doc URL:
15 ;; Keywords: c, languages, tools
16 ;; Compatibility: GNU Emacs: 24.3+
17 ;;
18 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
19 ;;
20 ;;; Commentary:
21 ;;
22 ;; This package provides a UI to interact with users of Srefactor
23 ;; package.
24 ;;
25 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
26 ;;
27 ;; This program is free software: you can redistribute it and/or modify
28 ;; it under the terms of the GNU General Public License as published by
29 ;; the Free Software Foundation, either version 3 of the License, or (at
30 ;; your option) any later version.
31 ;;
32 ;; This program is distributed in the hope that it will be useful, but
33 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
34 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
35 ;; General Public License for more details.
36 ;;
37 ;; You should have received a copy of the GNU General Public License
38 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
39 ;;
40 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
41 ;;
42 ;;; Code:
43 (with-no-warnings
44   (require 'cl))
45 (require 'recentf)
46 (require 'eieio)
47 (require 'semantic/format)
48 (autoload 'srefactor--refactor-based-on-tag-class "srefactor")
49 (autoload 'srefactor--insert-tag "srefactor")
50
51 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
52 ;; Variables
53 ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
54
55 (defvar srefactor-ui--current-active-window nil
56   "Store the current active window where the menu is invoked.")
57
58 (defvar srefactor-ui--current-active-region-start nil
59   "Store the start of an active region of current window if any.")
60
61 (defvar srefactor-ui--current-active-region-end nil
62   "Store the end of an active region of current window if any.")
63
64 (defvar srefactor-ui--current-active-menu nil
65   "Current menu object biing used.")
66
67 (defvar srefactor-ui--func-type nil
68   "What type of refactoring to perform.")
69
70 (defvar srefactor-ui--current-active-tag-overlay nil
71   "Overlay of tag in srefactor-ui--current-active-window.")
72
73 (defcustom srefactor-ui-menu-show-help t
74   "Turn on/off help message."
75   :group 'srefactor-ui
76   :type 'boolean)
77
78 (defsubst srefactor-ui--menu-label (e)
79   (car e))
80
81 (defsubst srefactor-ui--menu-value-item (e)
82   (cdr e))
83
84 (defsubst srefactor-ui--digit-shortcut-command-name (n)
85   "Return a command name to open the Nth most recent file.
86 See also the command `recentf-open-most-recent-file'."
87   (intern (format "srefactor-ui--refactor-based-on-tag-class-%d" n)))
88
89 (defsubst srefactor-ui--make-menu-element (menu-item menu-value)
90   "Create a new menu-element.
91 A menu element is a pair (MENU-ITEM . MENU-VALUE), where MENU-ITEM is
92 the menu item string displayed.  MENU-VALUE is the file to be open
93 when the corresponding MENU-ITEM is selected."
94   (cons menu-item menu-value))
95
96 (defclass srefactor-ui-menu ()
97   ((name
98     :initarg :name
99     :initform "*Srefactor Menu*"
100     :accessor name
101     :type string
102     :documentation
103     "Name of the menu to be displayed in the modeline.")
104    (items
105     :initarg :items
106     :initform nil
107     :accessor items
108     :type list
109     :documentation
110     "Item list to be displayed in a menu. Item is a list
111           '(DISPLAY REAL OPTIONS).")
112    (action
113     :initarg :action
114     :initform nil
115     :accessor action
116     :documentation
117     "An action to run when a menu item is selected.")
118    (context
119     :initarg :context
120     :initform nil
121     :accessor context
122     :documentation
123     "Current Semantic tag in scope, used as context to
124      select appropriate refactor actions.")
125    (shortcut-p
126     :initarg :shortcut-p
127     :initform nil
128     :accessor shortcut-p
129     :type boolean
130     :documentation
131     "If t, first 9 actions can be executed by digit keys 1-9.")
132    (persistent-action
133     :initarg :persistent-action
134     :initform nil
135     :accessor persistent-action
136     :documentation
137     "An action to execute without exiting the menu.")
138    (keymap
139     :initarg :keymap
140     :initform nil
141     :accessor keymap
142     :documentation
143     "A function that set define keys in srefactor-ui-menu-mode-map.")
144    (post-handler
145     :initarg :post-handler
146     :initform nil
147     :accessor post-handler
148     :documentation
149     "A function to be executed after the menu is created."))
150   "Class srefactor-ui-menu ")
151
152 (defmacro srefactor-ui--menu (name &rest forms)
153   "Show a dialog buffer with NAME, setup with FORMS."
154   (declare (indent 1) (debug t))
155   `(with-current-buffer (get-buffer-create ,name)
156      ;; Cleanup buffer
157      (let ((inhibit-read-only t)
158            (ol (overlay-lists)))
159        (mapc 'delete-overlay (car ol))
160        (mapc 'delete-overlay (cdr ol))
161        (erase-buffer))
162      (srefactor-ui-menu-mode)
163      ,@forms
164      (widget-setup)
165      (switch-to-buffer (current-buffer))
166      (hl-line-mode 1)))
167
168 (defun srefactor-ui-create-menu (menu)
169   (interactive)
170   (unless (items menu)
171     (error "No available action."))
172   (setq srefactor-ui--current-active-window (car (window-list)))
173   (setq srefactor-ui--current-active-menu menu)
174   (if (region-active-p)
175       (progn
176         (setq srefactor-ui--current-active-region-start (region-beginning))
177         (setq srefactor-ui--current-active-region-end (region-end)))
178     (setq srefactor-ui--current-active-region-start nil)
179     (setq srefactor-ui--current-active-region-end nil))
180   (condition-case nil
181       (with-selected-window (select-window (split-window-below))
182         (srefactor-ui--menu
183             (or (name srefactor-ui--current-active-menu)
184                 (format "*%s*" "*Srefactor Menu*"))
185           (let ((major-mode 'c++-mode))
186             (widget-insert (if (context srefactor-ui--current-active-menu)
187                                (concat (semantic-format-tag-summarize (context srefactor-ui--current-active-menu) nil t) "\n")
188                              "")
189                            (if srefactor-ui-menu-show-help
190                                (concat  (if (shortcut-p srefactor-ui--current-active-menu)
191                                             (concat "Press "
192                                                     (propertize "1-9" 'face  'font-lock-preprocessor-face)
193                                                     " or click on an action to execute.\n")
194                                           "Click on an action to execute.\n")
195                                         "Press "
196                                         (propertize "o" 'face  'bold)
197                                         " or "
198                                         (propertize "O" 'face  'bold)
199                                         " to switch to next/previous option."
200                                         "\n"
201                                         "Click on "
202                                         (propertize "[Cancel]" 'face 'bold)
203                                         " or press "
204                                         (propertize "q" 'face 'bold)
205                                         " to quit.\n")
206                              "")))
207           (apply 'widget-create
208                  `(group
209                    :indent 2
210                    :format "\n%v\n"
211                    ,@(srefactor-ui--generate-items
212                       (items srefactor-ui--current-active-menu)
213                       (action srefactor-ui--current-active-menu)
214                       (shortcut-p srefactor-ui--current-active-menu))))
215           (widget-create
216            'push-button
217            :notify 'srefactor-ui--menu-quit
218            (propertize  "Cancel" 'face 'bold))
219           (recentf-dialog-goto-first 'link)
220           (when (post-handler menu)
221             (funcall (post-handler menu)))
222           (when (keymap menu)
223             (funcall (keymap menu))))
224         (fit-window-to-buffer (car (window-list))
225                               (/ (* (frame-height) 50)
226                                  100)
227                               (/ (* (frame-height) 10)
228                                  100))
229         (when (and (fboundp 'evil-mode)
230                    evil-mode)
231           (evil-local-mode)))
232     (error (srefactor-ui--clean-up-menu-window)
233            (message "Error when creating menu."))))
234
235 (defun srefactor-ui--return-option-list (type)
236   (let (options)
237     (cond
238      ((eq type 'file)
239       (push "(Current file)" options)
240       (push "(Other file)" options)
241       (when (featurep 'projectile)
242         (push "(Project file)" options))
243       (push "(File)" options))
244      ((eq type 'tag)
245       '("(Before)" "(Inside)" "(After)"))
246      (t))))
247
248 (defun srefactor-ui--generate-items (commands action &optional add-shortcut)
249   "Return a list of widgets to display FILES in a dialog buffer."
250   (mapcar (lambda (w)
251             (srefactor-ui--create-menu-widget w action))
252           (if add-shortcut
253               (srefactor-ui--show-digit-shortcut (mapcar 'srefactor-ui--make-default-menu-element
254                                                          commands))
255             (mapcar 'srefactor-ui--make-default-menu-element
256                     commands))))
257
258 (defun srefactor-ui--show-digit-shortcut (l)
259   "Filter the list of menu-elements L to show digit shortcuts."
260   (let ((i 0))
261     (dolist (e l)
262       (setq i (1+ i))
263       (setcar e (format (if (< i 10)
264                             "[%s] %s"
265                           " %s  %s")
266                         (if (< i 10 )
267                             (propertize (number-to-string (% i 10))
268                                         'face 'font-lock-preprocessor-face
269                                         'mouse-face 'italic)
270                           " ")
271                         (srefactor-ui--menu-label e))))
272     l))
273
274 (defun srefactor-ui--make-default-menu-element (command)
275   (srefactor-ui--make-menu-element (srefactor-ui--menu-label command)
276                                    (srefactor-ui--menu-value-item command)))
277
278 (defun srefactor-ui--create-menu-widget (menu-element action)
279   "Return a widget to display MENU-ELEMENT in a dialog buffer."
280   `(link :tag ,(srefactor-ui--menu-label menu-element)
281          :button-prefix ""
282          :button-suffix ""
283          :button-face nil
284          :format "%[%t\n%]"
285          :help-echo ""
286          :action ,action
287          ,(srefactor-ui--menu-value-item menu-element)))
288
289 (defun srefactor-ui--clean-up-menu-window (&optional kill-buffer)
290   (interactive)
291   (when kill-buffer
292     (kill-buffer (current-buffer)))
293   (delete-window (car (window-list)))
294   (select-window srefactor-ui--current-active-window)
295   (when (and srefactor-ui--current-active-region-start
296              srefactor-ui--current-active-region-end)
297     (goto-char srefactor-ui--current-active-region-start)
298     (set-mark-command nil)
299     (goto-char srefactor-ui--current-active-region-end)
300     (setq deactivate-mark nil))
301   (when srefactor-ui--current-active-tag-overlay
302     (delete-overlay srefactor-ui--current-active-tag-overlay)))
303
304 (defun srefactor-ui--refactor-action (widget &rest _ignore)
305   "Open the file stored in WIDGET's value when notified.
306 -IGNORE other arguments."
307   (interactive)
308   (srefactor-ui--clean-up-menu-window t)
309   (srefactor--refactor-based-on-tag-class (car (widget-value widget))
310                                           (srefactor-ui--get-current-menu-option (widget-get widget :tag))))
311
312 (defun srefactor-ui--tag-action (widget &rest _ignore)
313   (interactive)
314   (srefactor-ui--clean-up-menu-window t)
315   (srefactor--insert-tag (context srefactor-ui--current-active-menu)
316                          (car (widget-value widget))
317                          srefactor-ui--func-type
318                          (srefactor-ui--get-current-menu-option (widget-get widget :tag))))
319
320 (defun srefactor-ui--menu-quit (&rest ignored)
321   (interactive)
322   (srefactor-ui--clean-up-menu-window t))
323
324 (defvar srefactor-ui--shortcuts-keymap
325   (let ((km (make-sparse-keymap)))
326     (dolist (k '(9 8 7 6 5 4 3 2 1))
327       (let ((cmd (srefactor-ui--digit-shortcut-command-name k)))
328         ;; Define a shortcut command.
329         (defalias cmd
330           `(lambda ()
331              (interactive)
332              (unless (search-forward (number-to-string ,k) nil t)
333                  (search-backward (number-to-string ,k)) nil t)
334              (srefactor-ui--refactor-action (get-char-property (point) 'button))))
335         ;; Bind it to a digit key.
336         (define-key km (vector (+ k ?0)) cmd)))
337     km)
338   "Digit shortcuts keymap.")
339
340 (defun srefactor-ui--previous-page-target-window ()
341   (interactive)
342   (let ((menu-window (car (window-list))))
343     (select-window srefactor-ui--current-active-window)
344     (condition-case nil
345         (scroll-down)
346       (error nil))
347     (select-window menu-window)))
348
349 (defun srefactor-ui--next-page-target-window ()
350   (interactive)
351   (let ((menu-window (car (window-list))))
352     (select-window srefactor-ui--current-active-window)
353     (condition-case nil
354         (scroll-up)
355       (error nil))
356     (select-window menu-window)))
357
358 (defun srefactor-ui--cycle-option (direction current-option options)
359   (let* ((options options)
360          (pos (position current-option options :test #'string-equal))
361          (l (length options)))
362     (if (eq direction 'next)
363         (if (< pos (1- l))
364             (nth (1+ pos) options)
365           (car options))
366       (if (> pos 0)
367           (nth (1- pos) options)
368         (nth (1- l) options)))))
369
370 (defun srefactor-ui--get-current-menu-option (menu-string)
371   (condition-case nil
372       (progn
373         (string-match "(\\(.*\\))" menu-string)
374         (match-string 0 menu-string))
375     (error nil)))
376
377 (defun srefactor-ui--cycle (direction)
378   (let* ((pos (point))
379          (link (get-char-property pos 'button))
380          (current-opt (srefactor-ui--get-current-menu-option (widget-get link :tag)))
381          (options (cadr (widget-value-value-get link)))
382          (check (unless current-opt (throw 'option-not-available "No option is available for this tag.")))
383          (next-opt (srefactor-ui--cycle-option direction current-opt options))
384          (next-tag (replace-regexp-in-string "(\\(.*\\))" "" (widget-get link :tag))))
385     (when link
386       (widget-put link :tag (concat next-tag next-opt))
387       (widget-delete (get-char-property pos 'button))
388       (widget-create link)
389       (forward-line -1)
390       (widget-forward 1))))
391
392 (defvar srefactor-ui-menu-mode-map
393   (let ((km (copy-keymap srefactor-ui--shortcuts-keymap)))
394     (set-keymap-parent km widget-keymap)
395     (define-key km "q" 'srefactor-ui--menu-quit)
396     (define-key km "n" 'widget-forward)
397     (define-key km "p" 'widget-backward)
398     (define-key km "j" 'widget-forward)
399     (define-key km "k" 'widget-backward)
400     (define-key km (kbd "TAB") (lambda ()
401                                  (interactive)
402                                  (when (persistent-action srefactor-ui--current-active-menu)
403                                    (funcall (persistent-action srefactor-ui--current-active-menu)))))
404     (define-key km "o" (lambda ()
405                          (interactive)
406                          (message "%s"
407                                   (catch 'option-not-available
408                                     (srefactor-ui--cycle 'next)))))
409     (define-key km "O" (lambda ()
410                          (interactive)
411                          (message "%s"
412                                   (catch 'option-not-available
413                                     (srefactor-ui--cycle 'prev)))))
414     (define-key km (kbd "M-<next>") 'srefactor-ui--next-page-target-window)
415     (define-key km (kbd "M-<prior>") 'srefactor-ui--previous-page-target-window)
416     (when (featurep 'evil)
417       (define-key km (kbd "/") 'evil-search-forward)
418       (define-key km (kbd "?") 'evil-search-backward))
419     (define-key km (kbd "C-g") 'srefactor-ui--menu-quit)
420     (define-key km [follow-link] "\C-m")
421     km)
422   "Keymap used in recentf dialogs.")
423
424 (define-derived-mode srefactor-ui-menu-mode nil "srefactor-ui-menu"
425   "Major mode of recentf dialogs.
426  "
427   :syntax-table nil
428   :abbrev-table nil
429   (setq truncate-lines t))
430
431 (provide 'srefactor-ui)
432
433 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
434 ;;; srefactor-ui.el ends here
435 ;; Local Variables:
436 ;; byte-compile-warnings: (not cl-functions)
437 ;; End: