1 ;;; ace-window.el --- Quickly switch windows. -*- lexical-binding: t -*-
3 ;; Copyright (C) 2014-2015 Oleh Krehel
5 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
6 ;; URL: https://github.com/abo-abo/ace-window
8 ;; Keywords: window, location
10 ;; This file is not part of GNU Emacs
12 ;; This file is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 3, or (at your option)
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; For a full copy of the GNU General Public License
23 ;; see <http://www.gnu.org/licenses/>.
27 ;; The main function, `ace-window' is meant to replace `other-window'.
28 ;; If fact, when there are only two windows present, `other-window' is
29 ;; called. If there are more, each window will have its first
30 ;; character highlighted. Pressing that character will switch to that
33 ;; To setup this package, just add to your .emacs:
35 ;; (global-set-key (kbd "M-p") 'ace-window)
37 ;; replacing "M-p" with an appropriate shortcut.
39 ;; Depending on your window usage patterns, you might want to set
41 ;; (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
43 ;; This way they're all on the home row, although the intuitive
46 ;; If you don't want the gray background that makes the red selection
47 ;; characters stand out more, set this:
49 ;; (setq aw-background nil)
51 ;; When prefixed with one `universal-argument', instead of switching
52 ;; to selected window, the selected window is swapped with current one.
54 ;; When prefixed with two `universal-argument', the selected window is
61 (defgroup ace-window nil
62 "Quickly switch current window."
66 (defcustom aw-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)
67 "Keys for selecting window.")
69 (defcustom aw-scope 'global
70 "The scope used by `ace-window'."
72 (const :tag "global" global)
73 (const :tag "frame" frame)))
75 (defcustom aw-ignored-buffers '("*Calc Trail*" "*LV*")
76 "List of buffers to ignore when selecting window."
77 :type '(repeat string))
79 (defcustom aw-ignore-on t
80 "When t, `ace-window' will ignore `aw-ignored-buffers'.
81 Use M-0 `ace-window' to toggle this value."
84 (defcustom aw-background t
85 "When t, `ace-window' will dim out all buffers temporarily when used.'."
88 (defcustom aw-leading-char-style 'char
89 "Style of the leading char overlay."
91 (const :tag "single char" 'char)
92 (const :tag "full path" 'path)))
94 (defface aw-leading-char-face
95 '((((class color)) (:foreground "red"))
96 (((background dark)) (:foreground "gray100"))
97 (((background light)) (:foreground "gray0"))
98 (t (:foreground "gray100" :underline nil)))
99 "Face for each window's leading char.")
101 (defface aw-background-face
102 '((t (:foreground "gray40")))
103 "Face for whole window background during selection.")
105 (defface aw-mode-line-face
106 '((t (:inherit mode-line-buffer-id)))
107 "Face used for displaying the ace window key in the mode-line.")
110 (defun aw-ignored-p (window)
111 "Return t if WINDOW should be ignored."
113 (member (buffer-name (window-buffer window))
114 aw-ignored-buffers)))
116 (defun aw-window-list ()
117 "Return the list of interesting windows."
121 (let ((f (window-frame w))
122 (b (window-buffer w)))
123 (or (not (and (frame-live-p f)
124 (frame-visible-p f)))
125 (string= "initial_terminal" (terminal-name f))
127 (with-current-buffer b
128 (and buffer-read-only
129 (= 0 (buffer-size b)))))))
132 (cl-mapcan #'window-list (frame-list)))
136 (error "Invalid `aw-scope': %S" aw-scope))))
139 (defvar aw-overlays-lead nil
140 "Hold overlays for leading chars.")
142 (defvar aw-overlays-back nil
143 "Hold overlays for when `aw-background' is t.")
145 (defvar ace-window-mode nil
146 "Minor mode during the selection process.")
148 ;; register minor mode
149 (or (assq 'ace-window-mode minor-mode-alist)
150 (nconc minor-mode-alist
151 (list '(ace-window-mode ace-window-mode))))
154 "Clean up mode line and overlays."
156 (setq ace-window-mode nil)
157 (force-mode-line-update)
159 (mapc #'delete-overlay aw-overlays-back)
160 (setq aw-overlays-back nil)
161 (aw--remove-leading-chars))
163 (defun aw--lead-overlay (path leaf)
164 "Create an overlay using PATH at LEAF.
166 (let* ((pt (car leaf))
168 (ol (make-overlay pt (1+ pt) (window-buffer wnd)))
169 (old-str (with-selected-window wnd
170 (buffer-substring pt (1+ pt))))
173 (cl-case aw-leading-char-style
175 (apply #'string (last path)))
177 (apply #'string (reverse path)))
179 (error "Bad `aw-leading-char-style': %S"
180 aw-leading-char-style)))
181 (cond ((string-equal old-str "\t")
182 (make-string (1- tab-width) ?\ ))
183 ((string-equal old-str "\n")
187 (max 0 (1- (string-width old-str)))
189 (overlay-put ol 'face 'aw-leading-char-face)
190 (overlay-put ol 'window wnd)
191 (overlay-put ol 'display new-str)
192 (push ol aw-overlays-lead)))
194 (defun aw--remove-leading-chars ()
195 "Remove leading char overlays."
196 (mapc #'delete-overlay aw-overlays-lead)
197 (setq aw-overlays-lead nil))
199 (defun aw--make-backgrounds (wnd-list)
200 "Create a dim background overlay for each window on WND-LIST."
202 (setq aw-overlays-back
204 (let ((ol (make-overlay
208 (overlay-put ol 'face 'aw-background-face)
212 (defvar aw--flip-keys nil
213 "Pre-processed `aw-flip-keys'.")
215 (defcustom aw-flip-keys '("n")
216 "Keys which should select the last window."
217 :set (lambda (sym val)
220 (mapcar (lambda (x) (aref (kbd x) 0)) val))))
222 (defun aw-select (mode-line)
223 "Return a selected other window.
224 Amend MODE-LINE to the mode line for the duration of the selection."
225 (let ((start-window (selected-window))
226 (next-window-scope (cl-case aw-scope
229 (wnd-list (aw-window-list))
231 (cl-case (length wnd-list)
237 (setq final-window (next-window nil nil next-window-scope))
238 (while (and (aw-ignored-p final-window)
239 (not (equal final-window start-window)))
240 (setq final-window (next-window final-window nil next-window-scope)))
243 (let ((candidate-list
244 (mapcar (lambda (wnd)
245 ;; can't jump if the buffer is empty
246 (with-current-buffer (window-buffer wnd)
247 (when (= 0 (buffer-size))
249 (cons (aw-offset wnd) wnd))
251 (aw--make-backgrounds wnd-list)
252 (setq ace-window-mode mode-line)
253 (force-mode-line-update)
254 ;; turn off helm transient map
255 (remove-hook 'post-command-hook 'helm--maybe-update-keymap)
258 (or (cdr (avy-read (avy-tree candidate-list aw-keys)
260 #'aw--remove-leading-chars))
263 (if (memq (caddr err) aw--flip-keys)
265 (signal (car err) (cdr err)))))
270 (defun ace-select-window ()
274 (aw-select " Ace - Window")))
277 (defun ace-delete-window ()
281 (aw-select " Ace - Delete Window")))
284 (defun ace-swap-window ()
288 (aw-select " Ace - Swap Window")))
291 (defun ace-maximize-window ()
292 "Ace maximize window."
295 (aw-select " Ace - Maximize Window"))
296 (delete-other-windows))
299 (defun ace-window (arg)
301 Perform an action based on ARG described below.
303 By default, behaves like extended `other-window'.
305 Prefixed with one \\[universal-argument], does a swap between the
306 selected window and the current window, so that the selected
307 buffer moves to current window (and current buffer moves to
310 Prefixed with two \\[universal-argument]'s, deletes the selected
318 (4 (ace-swap-window))
319 (16 (ace-delete-window))
320 (t (ace-select-window))))
323 (defun aw-window< (wnd1 wnd2)
324 "Return true if WND1 is less than WND2.
325 This is determined by their respective window coordinates.
326 Windows are numbered top down, left to right."
327 (let ((f1 (window-frame wnd1))
328 (f2 (window-frame wnd2))
329 (e1 (window-edges wnd1))
330 (e2 (window-edges wnd2)))
331 (cond ((string< (frame-parameter f1 'window-id)
332 (frame-parameter f2 'window-id))
334 ((< (car e1) (car e2))
336 ((> (car e1) (car e2))
338 ((< (cadr e1) (cadr e2))
341 (defvar aw--window-ring (make-ring 10)
342 "Hold the window switching history.")
344 (defun aw--push-window (window)
345 "Store WINDOW to `aw--window-ring'."
346 (when (or (zerop (ring-length aw--window-ring))
348 (ring-ref aw--window-ring 0)
350 (ring-insert aw--window-ring (selected-window))))
352 (defun aw--pop-window ()
353 "Return the removed top of `aw--window-ring'."
356 (while (not (window-live-p
357 (setq res (ring-remove aw--window-ring 0)))))
359 (error "No previous windows stored")))
362 (defun aw-switch-to-window (window)
363 "Switch to the window WINDOW."
364 (let ((frame (window-frame window)))
365 (when (and (frame-live-p frame)
366 (not (eq frame (selected-frame))))
367 (select-frame-set-input-focus frame))
368 (if (window-live-p window)
370 (aw--push-window (selected-window))
371 (select-window window))
372 (error "Got a dead window %S" window))))
374 (defun aw-flip-window ()
375 "Switch to the window you were previously in."
377 (aw-switch-to-window (aw--pop-window)))
379 (defun aw-delete-window (window)
380 "Delete window WINDOW."
381 (let ((frame (window-frame window)))
382 (when (and (frame-live-p frame)
383 (not (eq frame (selected-frame))))
384 (select-frame-set-input-focus (window-frame window)))
385 (if (= 1 (length (window-list)))
387 (if (window-live-p window)
388 (delete-window window)
389 (error "Got a dead window %S" window)))))
391 (defun aw-swap-window (window)
392 "Swap buffers of current window and WINDOW."
393 (cl-labels ((swap-windows (window1 window2)
394 "Swap the buffers of WINDOW1 and WINDOW2."
395 (let ((buffer1 (window-buffer window1))
396 (buffer2 (window-buffer window2)))
397 (set-window-buffer window1 buffer2)
398 (set-window-buffer window2 buffer1)
399 (select-window window2))))
400 (let ((frame (window-frame window))
401 (this-window (selected-window)))
402 (when (and (frame-live-p frame)
403 (not (eq frame (selected-frame))))
404 (select-frame-set-input-focus (window-frame window)))
405 (when (and (window-live-p window)
406 (not (eq window this-window)))
407 (aw--push-window this-window)
408 (swap-windows this-window window)))))
410 (defun aw-offset (window)
411 "Return point in WINDOW that's closest to top left corner.
412 The point is writable, i.e. it's not part of space after newline."
413 (let ((h (window-hscroll window))
414 (beg (window-start window))
415 (end (window-end window))
416 (inhibit-field-text-motion t))
418 (window-buffer window)
421 (while (and (< (point) end)
422 (< (- (line-end-position)
423 (line-beginning-position))
430 (define-minor-mode ace-window-display-mode
431 "Minor mode for showing the ace window key in the mode line."
433 (if ace-window-display-mode
438 `((ace-window-display-mode
439 (:eval (window-parameter (selected-window) 'ace-window-path)))
441 'ace-window-display-mode
442 (default-value 'mode-line-format))))
443 (force-mode-line-update t)
444 (add-hook 'window-configuration-change-hook 'aw-update))
448 'ace-window-display-mode
449 (default-value 'mode-line-format)))
450 (remove-hook 'window-configuration-change-hook 'aw-update)))
453 "Update ace-window-path window parameter for all windows."
455 (avy-tree (aw-window-list) aw-keys)
457 (set-window-parameter
458 leaf 'ace-window-path
460 (apply #'string (reverse path))
461 'face 'aw-mode-line-face)))))
463 (provide 'ace-window)
465 ;;; ace-window.el ends here