1 ;;; ace-window.el --- Quickly switch windows using `ace-jump-mode'. -*- lexical-binding: t -*-
3 ;; Copyright (C) 2014 Oleh Krehel
5 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
6 ;; URL: https://github.com/abo-abo/ace-window
8 ;; Package-Requires: ((ace-jump-mode "2.0"))
9 ;; Keywords: cursor, window, location
11 ;; This file is not part of GNU Emacs
13 ;; This file is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 3, or (at your option)
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; For a full copy of the GNU General Public License
24 ;; see <http://www.gnu.org/licenses/>.
28 ;; This package uses `ace-jump-mode' machinery to switch between
31 ;; The main function, `ace-window' is meant to replace `other-window'.
32 ;; If fact, when there are only two windows present, `other-window' is
33 ;; called. If there are more, each window will have its first
34 ;; character highlighted. Pressing that character will switch to that
35 ;; window. Note that unlike `ace-jump-mode', the point position will
36 ;; not be changed: only current window focus changes.
38 ;; To setup this package, just add to your ~.emacs:
40 ;; (global-set-key (kbd "M-p") 'ace-window)
42 ;; replacing "M-p" with an appropriate shortcut.
44 ;; Depending on your window usage patterns, you might want to set
46 ;; (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
48 ;; This way they're all on the home row, although the intuitive
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
58 (require 'ace-jump-mode)
60 ;; ——— Customization ———————————————————————————————————————————————————————————
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."
70 (defcustom aw-scope 'global
71 "The scope used by `ace-window'."
74 (const :tag "global" global)
75 (const :tag "frame" frame)))
77 (defcustom aw-ignored-buffers '("*Calc Trail*")
78 "List of buffers to ignore when selecting window."
81 (defcustom aw-ignore-on t
82 "When t, `ace-window' will ignore `aw-ignored-buffers'.
83 Use M-0 `ace-window' to toggle this value."
87 (defcustom aw-background t
88 "When t, `ace-window' will dim out all buffers temporarily when used.'."
92 (defun aw-ignored-p (window)
93 "Return t if WINDOW should be ignored."
95 (member (buffer-name (window-buffer window))
98 (defun aw-list-visual-area ()
99 "Forward to `ace-jump-list-visual-area', removing invisible frames."
102 (let ((f (aj-visual-area-frame x)))
103 (or (not (and (frame-live-p f)
104 (frame-visible-p f)))
105 (string= "initial_terminal" (terminal-name f))
106 (aw-ignored-p (aj-visual-area-window x)))))
107 (ace-jump-list-visual-area)))
109 (defvar aw--current-op nil
110 "A function of one argument to call.")
112 (defun aw--callback ()
113 "Call `aw--current-op' for the window selected by ace-jump."
115 (let* ((index (or (cl-position (aref (this-command-keys) 0)
118 (node (nth index (cdr ace-jump-search-tree))))
120 (message "No such position candidate.")
123 ((eq (car node) 'branch)
124 (let ((old-tree ace-jump-search-tree))
125 (setq ace-jump-search-tree (cons 'branch (cdr node)))
126 (ace-jump-update-overlay-in-search-tree
127 ace-jump-search-tree aw-keys)
128 (setf (cdr node) nil)
129 (ace-jump-delete-overlay-in-search-tree old-tree)))
131 ((eq (car node) 'leaf)
132 (let ((aj-data (overlay-get (cdr node) 'aj-data)))
135 (run-hooks 'ace-jump-mode-before-jump-hook)
136 (funcall aw--current-op aj-data))
137 (run-hooks 'ace-jump-mode-end-hook))
141 (error "[AceJump] Internal error: tree node type is invalid")))))
143 (defun aw--doit (mode-line)
144 "Select a window and eventually call `aw--current-op' for it.
145 Set mode line to MODE-LINE during the selection process."
146 (let* ((ace-jump-mode-scope aw-scope)
152 (sort (aw-list-visual-area)
155 (if (<= (length visual-area-list) 2)
159 (let ((b (aj-visual-area-buffer va)))
160 (with-current-buffer b
161 (and buffer-read-only
162 (= 0 (buffer-size b))))))
164 (cl-case (length visual-area-list)
167 (if (aw-ignored-p (selected-window))
169 ;; don't get stuck in an empty read-only buffer
170 (select-window (aj-visual-area-window (car visual-area-list)))))
172 (if (aw-ignored-p (selected-window))
174 (let ((sw (selected-window))
175 (w (next-window nil nil next-window-scope)))
176 (while (aw-ignored-p w)
178 (setq w (next-window nil nil next-window-scope)))
180 (funcall aw--current-op
183 :visual-area (make-aj-visual-area
184 :buffer (window-buffer w)
186 :frame (window-frame w)))))))
188 (let ((candidate-list
190 (let ((b (aj-visual-area-buffer va)))
191 ;; ace-jump-mode can't jump if the buffer is empty
192 (when (= 0 (buffer-size b))
193 (with-current-buffer b
197 (aw-offset (aj-visual-area-window va))
200 ;; create background for each visual area
202 (setq ace-jump-background-overlay-list
203 (loop for va in visual-area-list
204 collect (let* ((w (aj-visual-area-window va))
205 (b (aj-visual-area-buffer va))
206 (ol (make-overlay (window-start w)
209 (overlay-put ol 'face 'ace-jump-face-background)
211 ;; construct search tree and populate overlay into tree
212 (setq ace-jump-search-tree
213 (ace-jump-tree-breadth-first-construct
214 (length candidate-list)
216 (ace-jump-populate-overlay-to-search-tree
217 ace-jump-search-tree candidate-list)
218 (ace-jump-update-overlay-in-search-tree
219 ace-jump-search-tree aw-keys)
220 (setq ace-jump-mode mode-line)
221 (force-mode-line-update)
222 ;; turn off helm transient map
223 (remove-hook 'post-command-hook 'helm--maybe-update-keymap)
224 ;; override the local key map
225 (let ((map (make-keymap)))
226 (dolist (key-code aw-keys)
227 (define-key map (make-string 1 key-code) 'aw--callback))
228 (define-key map [t] 'ace-jump-done)
229 (if (fboundp 'set-transient-map)
230 (set-transient-map map)
231 (set-temporary-overlay-map map)))
233 (add-hook 'mouse-leave-buffer-hook 'ace-jump-done)
234 (add-hook 'kbd-macro-termination-hook 'ace-jump-done))))))
236 ;; ——— Interactive —————————————————————————————————————————————————————————————
238 (defun ace-select-window ()
241 (setq aw--current-op 'aw-switch-to-window)
242 (aw--doit " Ace - Window"))
245 (defun ace-delete-window ()
248 (setq aw--current-op 'aw-delete-window)
249 (aw--doit " Ace - Delete Window"))
252 (defun ace-swap-window ()
255 (setq aw--current-op 'aw-swap-window)
256 (aw--doit " Ace - Swap Window"))
259 (defun ace-window (arg)
260 "Select a window with function `ace-jump-mode'.
261 Perform an action based on ARG described below.
263 By default, behaves like extended `other-window'.
265 Prefixed with one \\[universal-argument], does a swap between the
266 selected window and the current window, so that the selected
267 buffer moves to current window (and current buffer moves to
270 Prefixed with two \\[universal-argument]'s, deletes the selected
278 (4 (ace-swap-window))
279 (16 (ace-delete-window))
280 (t (ace-select-window))))
282 ;; ——— Utility —————————————————————————————————————————————————————————————————
283 (defun aw-visual-area< (va1 va2)
284 "Return true if visual area VA1 is less than VA2.
285 This is determined by their respective window coordinates.
286 Windows are numbered top down, left to right."
287 (let ((f1 (aj-visual-area-frame va1))
288 (f2 (aj-visual-area-frame va2))
289 (e1 (window-edges (aj-visual-area-window va1)))
290 (e2 (window-edges (aj-visual-area-window va2))))
291 (cond ((string< (frame-parameter f1 'window-id)
292 (frame-parameter f2 'window-id))
294 ((< (car e1) (car e2))
296 ((> (car e1) (car e2))
298 ((< (cadr e1) (cadr e2))
301 (defun aw-switch-to-window (aj-data)
302 "Switch to the window of `aj-position' structure AJ-DATA."
303 (let ((frame (aj-position-frame aj-data))
304 (window (aj-position-window aj-data)))
305 (when (and (frame-live-p frame)
306 (not (eq frame (selected-frame))))
307 (select-frame-set-input-focus frame))
308 (if (window-live-p window)
309 (select-window window)
310 (error "Bad aj-data, aw-delete-window: %S" aj-data))))
312 (defun aw-delete-window (aj-data)
313 "Delete window of `aj-position' structure AJ-DATA."
314 (let ((frame (aj-position-frame aj-data))
315 (window (aj-position-window aj-data)))
316 (when (and (frame-live-p frame)
317 (not (eq frame (selected-frame))))
318 (select-frame-set-input-focus (window-frame window)))
319 (if (= 1 (length (window-list)))
321 (if (window-live-p window)
322 (delete-window window)
323 (error "Bad aj-data, aw-delete-window: %S" aj-data)))))
325 (defun aw-swap-window (aj-data)
326 "Swap buffers of current window and that of `aj-position' structure AJ-DATA."
327 (cl-labels ((swap-windows (window1 window2)
328 "Swap the buffers of WINDOW1 and WINDOW2."
329 (let ((buffer1 (window-buffer window1))
330 (buffer2 (window-buffer window2)))
331 (set-window-buffer window1 buffer2)
332 (set-window-buffer window2 buffer1)
333 (select-window window2))))
334 (let ((frame (aj-position-frame aj-data))
335 (window (aj-position-window aj-data))
336 (this-window (selected-window)))
337 (when (and (frame-live-p frame)
338 (not (eq frame (selected-frame))))
339 (select-frame-set-input-focus (window-frame window)))
340 (when (and (window-live-p window)
341 (not (eq window this-window)))
342 (swap-windows this-window window)))))
344 (defun aw-offset (window)
345 "Return point in WINDOW that's closest to top left corner.
346 The point is writable, i.e. it's not part of space after newline."
347 (let ((h (window-hscroll window))
348 (beg (window-start window))
349 (end (window-end window))
350 (inhibit-field-text-motion t))
352 (window-buffer window)
355 (while (and (< (point) end)
356 (< (- (line-end-position)
357 (line-beginning-position))
362 (provide 'ace-window)
364 ;;; ace-window.el ends here