1 ;;; ace-window.el --- Quickly switch windows. -*- lexical-binding: t -*-
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
5 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
6 ;; Maintainer: Oleh Krehel <ohwoeowho@gmail.com>
7 ;; URL: https://github.com/abo-abo/ace-window
9 ;; Package-Requires: ((avy "0.1.0"))
10 ;; Keywords: window, location
12 ;; This file is part of GNU Emacs.
14 ;; This file is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 3, or (at your option)
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; For a full copy of the GNU General Public License
25 ;; see <http://www.gnu.org/licenses/>.
29 ;; The main function, `ace-window' is meant to replace `other-window'.
30 ;; In fact, when there are only two windows present, `other-window' is
31 ;; called. If there are more, each window will have its first
32 ;; character highlighted. Pressing that character will switch to that
35 ;; To setup this package, just add to your .emacs:
37 ;; (global-set-key (kbd "M-p") 'ace-window)
39 ;; replacing "M-p" with an appropriate shortcut.
41 ;; Depending on your window usage patterns, you might want to set
43 ;; (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
45 ;; This way they are all on the home row, although the intuitive
48 ;; If you don't want the gray background that makes the red selection
49 ;; characters stand out more, set this:
51 ;; (setq aw-background nil)
53 ;; If you want to know the selection characters ahead of time, you can
54 ;; turn on `ace-window-display-mode'.
56 ;; When prefixed with one `universal-argument', instead of switching
57 ;; to selected window, the selected window is swapped with current one.
59 ;; When prefixed with two `universal-argument', the selected window is
67 (defgroup ace-window nil
68 "Quickly switch current window."
72 (defcustom aw-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)
73 "Keys for selecting window.")
75 (defcustom aw-scope 'global
76 "The scope used by `ace-window'."
78 (const :tag "global" global)
79 (const :tag "frame" frame)))
81 (defcustom aw-ignored-buffers '("*Calc Trail*" "*LV*")
82 "List of buffers to ignore when selecting window."
83 :type '(repeat string))
85 (defcustom aw-ignore-on t
86 "When t, `ace-window' will ignore `aw-ignored-buffers'.
87 Use M-0 `ace-window' to toggle this value."
90 (defcustom aw-background t
91 "When t, `ace-window' will dim out all buffers temporarily when used.'."
94 (defcustom aw-leading-char-style 'char
95 "Style of the leading char overlay."
97 (const :tag "single char" 'char)
98 (const :tag "full path" 'path)))
100 (defface aw-leading-char-face
101 '((((class color)) (:foreground "red"))
102 (((background dark)) (:foreground "gray100"))
103 (((background light)) (:foreground "gray0"))
104 (t (:foreground "gray100" :underline nil)))
105 "Face for each window's leading char.")
107 (defface aw-background-face
108 '((t (:foreground "gray40")))
109 "Face for whole window background during selection.")
111 (defface aw-mode-line-face
112 '((t (:inherit mode-line-buffer-id)))
113 "Face used for displaying the ace window key in the mode-line.")
116 (defun aw-ignored-p (window)
117 "Return t if WINDOW should be ignored."
119 (member (buffer-name (window-buffer window))
120 aw-ignored-buffers)))
122 (defun aw-window-list ()
123 "Return the list of interesting windows."
127 (let ((f (window-frame w))
128 (b (window-buffer w)))
129 (or (not (and (frame-live-p f)
130 (frame-visible-p f)))
131 (string= "initial_terminal" (terminal-name f))
133 (with-current-buffer b
134 (and buffer-read-only
135 (= 0 (buffer-size b)))))))
138 (cl-mapcan #'window-list (frame-list)))
142 (error "Invalid `aw-scope': %S" aw-scope))))
145 (defvar aw-overlays-back nil
146 "Hold overlays for when `aw-background' is t.")
148 (defvar ace-window-mode nil
149 "Minor mode during the selection process.")
151 ;; register minor mode
152 (or (assq 'ace-window-mode minor-mode-alist)
153 (nconc minor-mode-alist
154 (list '(ace-window-mode ace-window-mode))))
157 "Clean up mode line and overlays."
159 (setq ace-window-mode nil)
160 (force-mode-line-update)
162 (mapc #'delete-overlay aw-overlays-back)
163 (setq aw-overlays-back nil)
164 (avy--remove-leading-chars))
166 (defun aw--lead-overlay (path leaf)
167 "Create an overlay using PATH at LEAF.
169 (let* ((pt (car leaf))
171 (ol (make-overlay pt (1+ pt) (window-buffer wnd)))
174 (with-selected-window wnd
175 (buffer-substring pt (1+ pt))))
179 (cl-case aw-leading-char-style
181 (apply #'string (last path)))
183 (apply #'string (reverse path)))
185 (error "Bad `aw-leading-char-style': %S"
186 aw-leading-char-style)))
187 (cond ((string-equal old-str "\t")
188 (make-string (1- tab-width) ?\ ))
189 ((string-equal old-str "\n")
193 (max 0 (1- (string-width old-str)))
195 (overlay-put ol 'face 'aw-leading-char-face)
196 (overlay-put ol 'window wnd)
197 (overlay-put ol 'display new-str)
198 (push ol avy--overlays-lead)))
200 (defun aw--make-backgrounds (wnd-list)
201 "Create a dim background overlay for each window on WND-LIST."
203 (setq aw-overlays-back
205 (let ((ol (make-overlay
209 (overlay-put ol 'face 'aw-background-face)
213 (defvar aw--flip-keys nil
214 "Pre-processed `aw-flip-keys'.")
216 (defcustom aw-flip-keys '("n")
217 "Keys which should select the last window."
218 :set (lambda (sym val)
221 (mapcar (lambda (x) (aref (kbd x) 0)) val))))
223 (defun aw-select (mode-line)
224 "Return a selected other window.
225 Amend MODE-LINE to the mode line for the duration of the selection."
226 (let ((start-window (selected-window))
227 (next-window-scope (cl-case aw-scope
230 (wnd-list (aw-window-list))
232 (cl-case (length wnd-list)
238 (setq final-window (next-window nil nil next-window-scope))
239 (while (and (aw-ignored-p final-window)
240 (not (equal final-window start-window)))
241 (setq final-window (next-window final-window nil next-window-scope)))
244 (let ((candidate-list
245 (mapcar (lambda (wnd)
246 ;; can't jump if the buffer is empty
247 (with-current-buffer (window-buffer wnd)
248 (when (= 0 (buffer-size))
250 (cons (aw-offset wnd) wnd))
252 (aw--make-backgrounds wnd-list)
253 (setq ace-window-mode mode-line)
254 (force-mode-line-update)
255 ;; turn off helm transient map
256 (remove-hook 'post-command-hook 'helm--maybe-update-keymap)
259 (or (cdr (avy-read (avy-tree candidate-list aw-keys)
261 #'avy--remove-leading-chars))
264 (if (memq (nth 2 err) aw--flip-keys)
266 (signal (car err) (cdr err)))))
271 (defun ace-select-window ()
275 (aw-select " Ace - Window")))
278 (defun ace-delete-window ()
282 (aw-select " Ace - Delete Window")))
285 (defun ace-swap-window ()
289 (aw-select " Ace - Swap Window")))
292 (defun ace-maximize-window ()
293 "Ace maximize window."
296 (aw-select " Ace - Maximize Window"))
297 (delete-other-windows))
300 (defun ace-window (arg)
302 Perform an action based on ARG described below.
304 By default, behaves like extended `other-window'.
306 Prefixed with one \\[universal-argument], does a swap between the
307 selected window and the current window, so that the selected
308 buffer moves to current window (and current buffer moves to
311 Prefixed with two \\[universal-argument]'s, deletes the selected
319 (4 (ace-swap-window))
320 (16 (ace-delete-window))
321 (t (ace-select-window))))
324 (defun aw-window< (wnd1 wnd2)
325 "Return true if WND1 is less than WND2.
326 This is determined by their respective window coordinates.
327 Windows are numbered top down, left to right."
328 (let ((f1 (window-frame wnd1))
329 (f2 (window-frame wnd2))
330 (e1 (window-edges wnd1))
331 (e2 (window-edges wnd2)))
332 (cond ((string< (frame-parameter f1 'window-id)
333 (frame-parameter f2 'window-id))
335 ((< (car e1) (car e2))
337 ((> (car e1) (car e2))
339 ((< (cadr e1) (cadr e2))
342 (defvar aw--window-ring (make-ring 10)
343 "Hold the window switching history.")
345 (defun aw--push-window (window)
346 "Store WINDOW to `aw--window-ring'."
347 (when (or (zerop (ring-length aw--window-ring))
349 (ring-ref aw--window-ring 0)
351 (ring-insert aw--window-ring (selected-window))))
353 (defun aw--pop-window ()
354 "Return the removed top of `aw--window-ring'."
357 (while (not (window-live-p
358 (setq res (ring-remove aw--window-ring 0)))))
360 (error "No previous windows stored")))
363 (defun aw-switch-to-window (window)
364 "Switch to the window WINDOW."
365 (let ((frame (window-frame window)))
366 (when (and (frame-live-p frame)
367 (not (eq frame (selected-frame))))
368 (select-frame-set-input-focus frame))
369 (if (window-live-p window)
371 (aw--push-window (selected-window))
372 (select-window window))
373 (error "Got a dead window %S" window))))
375 (defun aw-flip-window ()
376 "Switch to the window you were previously in."
378 (aw-switch-to-window (aw--pop-window)))
380 (defun aw-delete-window (window)
381 "Delete window WINDOW."
382 (let ((frame (window-frame window)))
383 (when (and (frame-live-p frame)
384 (not (eq frame (selected-frame))))
385 (select-frame-set-input-focus (window-frame window)))
386 (if (= 1 (length (window-list)))
388 (if (window-live-p window)
389 (delete-window window)
390 (error "Got a dead window %S" window)))))
392 (defun aw-swap-window (window)
393 "Swap buffers of current window and WINDOW."
394 (cl-labels ((swap-windows (window1 window2)
395 "Swap the buffers of WINDOW1 and WINDOW2."
396 (let ((buffer1 (window-buffer window1))
397 (buffer2 (window-buffer window2)))
398 (set-window-buffer window1 buffer2)
399 (set-window-buffer window2 buffer1)
400 (select-window window2))))
401 (let ((frame (window-frame window))
402 (this-window (selected-window)))
403 (when (and (frame-live-p frame)
404 (not (eq frame (selected-frame))))
405 (select-frame-set-input-focus (window-frame window)))
406 (when (and (window-live-p window)
407 (not (eq window this-window)))
408 (aw--push-window this-window)
409 (swap-windows this-window window)))))
411 (defun aw-offset (window)
412 "Return point in WINDOW that's closest to top left corner.
413 The point is writable, i.e. it's not part of space after newline."
414 (let ((h (window-hscroll window))
415 (beg (window-start window))
416 (end (window-end window))
417 (inhibit-field-text-motion t))
419 (window-buffer window)
422 (while (and (< (point) end)
423 (< (- (line-end-position)
424 (line-beginning-position))
431 (define-minor-mode ace-window-display-mode
432 "Minor mode for showing the ace window key in the mode line."
434 (if ace-window-display-mode
439 `((ace-window-display-mode
440 (:eval (window-parameter (selected-window) 'ace-window-path)))
442 'ace-window-display-mode
443 (default-value 'mode-line-format))))
444 (force-mode-line-update t)
445 (add-hook 'window-configuration-change-hook 'aw-update))
449 'ace-window-display-mode
450 (default-value 'mode-line-format)))
451 (remove-hook 'window-configuration-change-hook 'aw-update)))
454 "Update ace-window-path window parameter for all windows."
456 (avy-tree (aw-window-list) aw-keys)
458 (set-window-parameter
459 leaf 'ace-window-path
461 (apply #'string (reverse path))
462 'face 'aw-mode-line-face)))))
464 (provide 'ace-window)
466 ;;; ace-window.el ends here