Personal emacs config
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.

954 lines
34 KiB

  1. ;;; ace-window.el --- Quickly switch windows. -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2015-2020 Free Software Foundation, Inc.
  3. ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
  4. ;; Maintainer: Oleh Krehel <ohwoeowho@gmail.com>
  5. ;; URL: https://github.com/abo-abo/ace-window
  6. ;; Package-Version: 0.10.0
  7. ;; Package-Commit: 7003c88cd9cad58dc35c7cd13ebc61c355fb5be7
  8. ;; Version: 0.10.0
  9. ;; Package-Requires: ((avy "0.5.0"))
  10. ;; Keywords: window, location
  11. ;; This file is 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)
  15. ;; any later version.
  16. ;; This program is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. ;; GNU General Public License for more details.
  20. ;; For a full copy of the GNU General Public License
  21. ;; see <http://www.gnu.org/licenses/>.
  22. ;;; Commentary:
  23. ;;
  24. ;; The main function, `ace-window' is meant to replace `other-window'
  25. ;; by assigning each window a short, unique label. When there are only
  26. ;; two windows present, `other-window' is called (unless
  27. ;; aw-dispatch-always is set non-nil). If there are more, each
  28. ;; window will have its first label character highlighted. Once a
  29. ;; unique label is typed, ace-window will switch to that window.
  30. ;;
  31. ;; To setup this package, just add to your .emacs:
  32. ;;
  33. ;; (global-set-key (kbd "M-o") 'ace-window)
  34. ;;
  35. ;; replacing "M-o" with an appropriate shortcut.
  36. ;;
  37. ;; By default, ace-window uses numbers for window labels so the window
  38. ;; labeling is intuitively ordered. But if you prefer to type keys on
  39. ;; your home row for quicker access, use this setting:
  40. ;;
  41. ;; (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  42. ;;
  43. ;; Whenever ace-window prompts for a window selection, it grays out
  44. ;; all the window characters, highlighting window labels in red. To
  45. ;; disable this behavior, set this:
  46. ;;
  47. ;; (setq aw-background nil)
  48. ;;
  49. ;; If you want to know the selection characters ahead of time, turn on
  50. ;; `ace-window-display-mode'.
  51. ;;
  52. ;; When prefixed with one `universal-argument', instead of switching
  53. ;; to the selected window, the selected window is swapped with the
  54. ;; current one.
  55. ;;
  56. ;; When prefixed with two `universal-argument', the selected window is
  57. ;; deleted instead.
  58. ;;; Code:
  59. (require 'avy)
  60. (require 'ring)
  61. (require 'subr-x)
  62. ;;* Customization
  63. (defgroup ace-window nil
  64. "Quickly switch current window."
  65. :group 'convenience
  66. :prefix "aw-")
  67. (defcustom aw-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)
  68. "Keys for selecting window."
  69. :type '(repeat character))
  70. (defcustom aw-scope 'global
  71. "The scope used by `ace-window'."
  72. :type '(choice
  73. (const :tag "visible frames" visible)
  74. (const :tag "global" global)
  75. (const :tag "frame" frame)))
  76. (defcustom aw-translate-char-function #'identity
  77. "Function to translate user input key into another key.
  78. For example, to make SPC do the same as ?a, use
  79. \(lambda (c) (if (= c 32) ?a c))."
  80. :type '(choice
  81. (const :tag "Off" #'identity)
  82. (const :tag "Ignore Case" #'downcase)
  83. (function :tag "Custom")))
  84. (defcustom aw-minibuffer-flag nil
  85. "When non-nil, also display `ace-window-mode' string in the minibuffer when ace-window is active."
  86. :type 'boolean)
  87. (defcustom aw-ignored-buffers '("*Calc Trail*" " *LV*")
  88. "List of buffers and major-modes to ignore when choosing a window from the window list.
  89. Active only when `aw-ignore-on' is non-nil."
  90. :type '(repeat string))
  91. (defcustom aw-ignore-on t
  92. "When t, `ace-window' will ignore buffers and major-modes in `aw-ignored-buffers'.
  93. Use M-0 `ace-window' to toggle this value."
  94. :type 'boolean)
  95. (defcustom aw-ignore-current nil
  96. "When t, `ace-window' will ignore `selected-window'."
  97. :type 'boolean)
  98. (defcustom aw-background t
  99. "When t, `ace-window' will dim out all buffers temporarily when used."
  100. :type 'boolean)
  101. (defcustom aw-leading-char-style 'char
  102. "Style of the leading char overlay."
  103. :type '(choice
  104. (const :tag "single char" 'char)
  105. (const :tag "full path" 'path)))
  106. (defcustom aw-dispatch-always nil
  107. "When non-nil, `ace-window' will issue a `read-char' even for one window.
  108. This will make `ace-window' act different from `other-window' for
  109. one or two windows."
  110. :type 'boolean)
  111. (defcustom aw-dispatch-when-more-than 2
  112. "If the number of windows is more than this, activate ace-window-ness."
  113. :type 'integer)
  114. (defcustom aw-reverse-frame-list nil
  115. "When non-nil `ace-window' will order frames for selection in
  116. the reverse of `frame-list'"
  117. :type 'boolean)
  118. (defcustom aw-frame-offset '(13 . 23)
  119. "Increase in pixel offset for new ace-window frames relative to the selected frame.
  120. Its value is an (x-offset . y-offset) pair in pixels."
  121. :type '(cons integer integer))
  122. (defcustom aw-frame-size nil
  123. "Frame size to make new ace-window frames.
  124. Its value is a (width . height) pair in pixels or nil for the default frame size.
  125. (0 . 0) is special and means make the frame size the same as the last selected frame size."
  126. :type '(cons integer integer))
  127. (defcustom aw-char-position 'top-left
  128. "Window positions of the character overlay.
  129. Consider changing this if the overlay tends to overlap with other things."
  130. :type '(choice
  131. (const :tag "top left corner only" 'top-left)
  132. (const :tag "both left corners" 'left)))
  133. ;; Must be defined before `aw-make-frame-char' since its :set function references this.
  134. (defvar aw-dispatch-alist
  135. '((?x aw-delete-window "Delete Window")
  136. (?m aw-swap-window "Swap Windows")
  137. (?M aw-move-window "Move Window")
  138. (?c aw-copy-window "Copy Window")
  139. (?j aw-switch-buffer-in-window "Select Buffer")
  140. (?n aw-flip-window)
  141. (?u aw-switch-buffer-other-window "Switch Buffer Other Window")
  142. (?e aw-execute-command-other-window "Execute Command Other Window")
  143. (?F aw-split-window-fair "Split Fair Window")
  144. (?v aw-split-window-vert "Split Vert Window")
  145. (?b aw-split-window-horz "Split Horz Window")
  146. (?o delete-other-windows "Delete Other Windows")
  147. (?T aw-transpose-frame "Transpose Frame")
  148. ;; ?i ?r ?t are used by hyperbole.el
  149. (?? aw-show-dispatch-help))
  150. "List of actions for `aw-dispatch-default'.
  151. Each action is a list of either:
  152. (char function description) where function takes a single window argument
  153. or
  154. (char function) where function takes no argument and the description is omitted.")
  155. (defun aw-set-make-frame-char (option value)
  156. ;; Signal an error if `aw-make-frame-char' is ever set to an invalid
  157. ;; or conflicting value.
  158. (when value
  159. (cond ((not (characterp value))
  160. (user-error "`aw-make-frame-char' must be a character, not `%s'" value))
  161. ((memq value aw-keys)
  162. (user-error "`aw-make-frame-char' is `%c'; this conflicts with the same character in `aw-keys'" value))
  163. ((assq value aw-dispatch-alist)
  164. (user-error "`aw-make-frame-char' is `%c'; this conflicts with the same character in `aw-dispatch-alist'" value))))
  165. (set option value))
  166. (defcustom aw-make-frame-char ?z
  167. "Non-existing ace window label character that triggers creation of a new single-window frame for display."
  168. :set 'aw-set-make-frame-char
  169. :type 'character)
  170. (defface aw-leading-char-face
  171. '((((class color)) (:foreground "red"))
  172. (((background dark)) (:foreground "gray100"))
  173. (((background light)) (:foreground "gray0"))
  174. (t (:foreground "gray100" :underline nil)))
  175. "Face for each window's leading char.")
  176. (defface aw-minibuffer-leading-char-face
  177. '((t :inherit aw-leading-char-face))
  178. "Face for minibuffer leading char.")
  179. (defface aw-background-face
  180. '((t (:foreground "gray40")))
  181. "Face for whole window background during selection.")
  182. (defface aw-mode-line-face
  183. '((t (:inherit mode-line-buffer-id)))
  184. "Face used for displaying the ace window key in the mode-line.")
  185. (defface aw-key-face
  186. '((t :inherit font-lock-builtin-face))
  187. "Face used by `aw-show-dispatch-help'.")
  188. ;;* Implementation
  189. (defun aw-ignored-p (window)
  190. "Return t if WINDOW should be ignored when choosing from the window list."
  191. (or (and aw-ignore-on
  192. ;; Ignore major-modes and buffer-names in `aw-ignored-buffers'.
  193. (or (memq (buffer-local-value 'major-mode (window-buffer window))
  194. aw-ignored-buffers)
  195. (member (buffer-name (window-buffer window)) aw-ignored-buffers)))
  196. ;; ignore child frames
  197. (and (fboundp 'frame-parent) (frame-parent (window-frame window)))
  198. ;; Ignore selected window if `aw-ignore-current' is non-nil.
  199. (and aw-ignore-current
  200. (equal window (selected-window)))
  201. ;; When `ignore-window-parameters' is nil, ignore windows whose
  202. ;; `no-other-window’ or `no-delete-other-windows' parameter is non-nil.
  203. (unless ignore-window-parameters
  204. (cl-case this-command
  205. (ace-select-window (window-parameter window 'no-other-window))
  206. (ace-delete-window (window-parameter window 'no-delete-other-windows))
  207. (ace-delete-other-windows (window-parameter
  208. window 'no-delete-other-windows))))))
  209. (defun aw-window-list ()
  210. "Return the list of interesting windows."
  211. (sort
  212. (cl-remove-if
  213. (lambda (w)
  214. (let ((f (window-frame w)))
  215. (or (not (and (frame-live-p f)
  216. (frame-visible-p f)))
  217. (string= "initial_terminal" (terminal-name f))
  218. (aw-ignored-p w))))
  219. (cl-case aw-scope
  220. (visible
  221. (cl-mapcan #'window-list (visible-frame-list)))
  222. (global
  223. (cl-mapcan #'window-list (frame-list)))
  224. (frame
  225. (window-list))
  226. (t
  227. (error "Invalid `aw-scope': %S" aw-scope))))
  228. 'aw-window<))
  229. (defvar aw-overlays-back nil
  230. "Hold overlays for when `aw-background' is t.")
  231. (defvar ace-window-mode nil
  232. "Minor mode during the selection process.")
  233. ;; register minor mode
  234. (or (assq 'ace-window-mode minor-mode-alist)
  235. (nconc minor-mode-alist
  236. (list '(ace-window-mode ace-window-mode))))
  237. (defvar aw-empty-buffers-list nil
  238. "Store the read-only empty buffers which had to be modified.
  239. Modify them back eventually.")
  240. (defvar aw--windows-hscroll nil
  241. "List of (window . hscroll-columns) items, each listing a window whose
  242. horizontal scroll will be restored upon ace-window action completion.")
  243. (defvar aw--windows-points nil
  244. "List of (window . point) items. The point position had to be
  245. moved in order to display the overlay.")
  246. (defun aw--done ()
  247. "Clean up mode line and overlays."
  248. ;; mode line
  249. (aw-set-mode-line nil)
  250. ;; background
  251. (mapc #'delete-overlay aw-overlays-back)
  252. (setq aw-overlays-back nil)
  253. (avy--remove-leading-chars)
  254. (dolist (b aw-empty-buffers-list)
  255. (with-current-buffer b
  256. (when (string= (buffer-string) " ")
  257. (let ((inhibit-read-only t))
  258. (delete-region (point-min) (point-max))))))
  259. (setq aw-empty-buffers-list nil)
  260. (aw--restore-windows-hscroll)
  261. (let (c)
  262. (while (setq c (pop aw--windows-points))
  263. (with-selected-window (car c)
  264. (goto-char (cdr c))))))
  265. (defun aw--restore-windows-hscroll ()
  266. "Restore horizontal scroll of windows from `aw--windows-hscroll' list."
  267. (let (wnd hscroll)
  268. (mapc (lambda (wnd-and-hscroll)
  269. (setq wnd (car wnd-and-hscroll)
  270. hscroll (cdr wnd-and-hscroll))
  271. (when (window-live-p wnd)
  272. (set-window-hscroll wnd hscroll)))
  273. aw--windows-hscroll))
  274. (setq aw--windows-hscroll nil))
  275. (defun aw--overlay-str (wnd pos path)
  276. "Return the replacement text for an overlay in WND at POS,
  277. accessible by typing PATH."
  278. (let ((old-str (or
  279. (ignore-errors
  280. (with-selected-window wnd
  281. (buffer-substring pos (1+ pos))))
  282. "")))
  283. (concat
  284. (cl-case aw-leading-char-style
  285. (char
  286. (string (avy--key-to-char (car (last path)))))
  287. (path
  288. (mapconcat
  289. (lambda (x) (string (avy--key-to-char x)))
  290. (reverse path)
  291. ""))
  292. (t
  293. (error "Bad `aw-leading-char-style': %S"
  294. aw-leading-char-style)))
  295. (cond ((string-equal old-str "\t")
  296. (make-string (1- tab-width) ?\ ))
  297. ((string-equal old-str "\n")
  298. "\n")
  299. (t
  300. (make-string
  301. (max 0 (1- (string-width old-str)))
  302. ?\ ))))))
  303. (defun aw--point-visible-p ()
  304. "Return non-nil if point is visible in the selected window.
  305. Return nil when horizontal scrolling has moved it off screen."
  306. (and (>= (- (current-column) (window-hscroll)) 0)
  307. (< (- (current-column) (window-hscroll))
  308. (window-width))))
  309. (defun aw--lead-overlay (path leaf)
  310. "Create an overlay using PATH at LEAF.
  311. LEAF is (PT . WND)."
  312. ;; Properly adds overlay in visible region of most windows except for any one
  313. ;; receiving output while this function is executing, since that moves point,
  314. ;; potentially shifting the added overlay outside the window's visible region.
  315. (let ((wnd (cdr leaf))
  316. ;; Prevent temporary movement of point from scrolling any window.
  317. (scroll-margin 0))
  318. (with-selected-window wnd
  319. (when (= 0 (buffer-size))
  320. (push (current-buffer) aw-empty-buffers-list)
  321. (let ((inhibit-read-only t))
  322. (insert " ")))
  323. ;; If point is not visible due to horizontal scrolling of the
  324. ;; window, this next expression temporarily scrolls the window
  325. ;; right until point is visible, so that the leading-char can be
  326. ;; seen when it is inserted. When ace-window's action finishes,
  327. ;; the horizontal scroll is restored by (aw--done).
  328. (while (and (not (aw--point-visible-p))
  329. (not (zerop (window-hscroll)))
  330. (progn (push (cons (selected-window) (window-hscroll)) aw--windows-hscroll) t)
  331. (not (zerop (scroll-right)))))
  332. (let* ((ws (window-start))
  333. (prev nil)
  334. (vertical-pos (if (eq aw-char-position 'left) -1 0))
  335. (horizontal-pos (if (zerop (window-hscroll)) 0 (1+ (window-hscroll))))
  336. (old-pt (point))
  337. (pt
  338. (progn
  339. ;; If leading-char is to be displayed at the top-left, move
  340. ;; to the first visible line in the window, otherwise, move
  341. ;; to the last visible line.
  342. (move-to-window-line vertical-pos)
  343. (move-to-column horizontal-pos)
  344. ;; Find a nearby point that is not at the end-of-line but
  345. ;; is visible so have space for the overlay.
  346. (setq prev (1- (point)))
  347. (while (and (>= prev ws) (/= prev (point)) (eolp))
  348. (setq prev (point))
  349. (unless (bobp)
  350. (line-move -1 t)
  351. (move-to-column horizontal-pos)))
  352. (recenter vertical-pos)
  353. (point)))
  354. (ol (make-overlay pt (1+ pt) (window-buffer wnd))))
  355. (if (= (aw--face-rel-height) 1)
  356. (goto-char old-pt)
  357. (when (/= pt old-pt)
  358. (goto-char (+ pt 1))
  359. (push (cons wnd old-pt) aw--windows-points)))
  360. (overlay-put ol 'display (aw--overlay-str wnd pt path))
  361. (if (window-minibuffer-p wnd)
  362. (overlay-put ol 'face 'aw-minibuffer-leading-char-face)
  363. (overlay-put ol 'face 'aw-leading-char-face))
  364. (overlay-put ol 'window wnd)
  365. (push ol avy--overlays-lead)))))
  366. (defun aw--make-backgrounds (wnd-list)
  367. "Create a dim background overlay for each window on WND-LIST."
  368. (when aw-background
  369. (setq aw-overlays-back
  370. (mapcar (lambda (w)
  371. (let ((ol (make-overlay
  372. (window-start w)
  373. (window-end w)
  374. (window-buffer w))))
  375. (overlay-put ol 'face 'aw-background-face)
  376. ol))
  377. wnd-list))))
  378. (defvar aw-dispatch-function 'aw-dispatch-default
  379. "Function to call when a character not in `aw-keys' is pressed.")
  380. (defvar aw-action nil
  381. "Function to call at the end of `aw-select'.")
  382. (defun aw-set-mode-line (str)
  383. "Set mode line indicator to STR."
  384. (setq ace-window-mode str)
  385. (when (and aw-minibuffer-flag ace-window-mode)
  386. (message "%s" (string-trim-left str)))
  387. (force-mode-line-update))
  388. (defun aw--dispatch-action (char)
  389. "Return item from `aw-dispatch-alist' matching CHAR."
  390. (assoc char aw-dispatch-alist))
  391. (defun aw-make-frame ()
  392. "Make a new Emacs frame using the values of `aw-frame-size' and `aw-frame-offset'."
  393. (make-frame
  394. (delq nil
  395. (list
  396. ;; This first parameter is important because an
  397. ;; aw-dispatch-alist command may not want to leave this
  398. ;; frame with input focus. If it is given focus, the
  399. ;; command may not be able to return focus to a different
  400. ;; frame since this is done asynchronously by the window
  401. ;; manager.
  402. '(no-focus-on-map . t)
  403. (when aw-frame-size
  404. (cons 'width
  405. (if (zerop (car aw-frame-size))
  406. (frame-width)
  407. (car aw-frame-size))))
  408. (when aw-frame-size
  409. (cons 'height
  410. (if (zerop (cdr aw-frame-size))
  411. (frame-height)
  412. (car aw-frame-size))))
  413. (cons 'left (+ (car aw-frame-offset)
  414. (car (frame-position))))
  415. (cons 'top (+ (cdr aw-frame-offset)
  416. (cdr (frame-position))))))))
  417. (defun aw-use-frame (window)
  418. "Create a new frame using the contents of WINDOW.
  419. The new frame is set to the same size as the previous frame, offset by
  420. `aw-frame-offset' (x . y) pixels."
  421. (aw-switch-to-window window)
  422. (aw-make-frame))
  423. (defun aw-clean-up-avy-current-path ()
  424. "Edit `avy-current-path' so only window label characters remain."
  425. ;; Remove any possible ace-window command char that may
  426. ;; precede the last specified window label, so
  427. ;; functions can use `avy-current-path' as the chosen
  428. ;; window label.
  429. (when (and (> (length avy-current-path) 0)
  430. (assq (aref avy-current-path 0) aw-dispatch-alist))
  431. (setq avy-current-path (substring avy-current-path 1))))
  432. (defun aw-dispatch-default (char)
  433. "Perform an action depending on CHAR."
  434. (cond ((and (fboundp 'avy-mouse-event-window)
  435. (avy-mouse-event-window char)))
  436. ((= char (aref (kbd "C-g") 0))
  437. (throw 'done 'exit))
  438. ((and aw-make-frame-char (= char aw-make-frame-char))
  439. ;; Make a new frame and perform any action on its window.
  440. (let ((start-win (selected-window))
  441. (end-win (frame-selected-window (aw-make-frame))))
  442. (if aw-action
  443. ;; Action must be called from the start-win. The action
  444. ;; determines which window to leave selected.
  445. (progn (select-frame-set-input-focus (window-frame start-win))
  446. (funcall aw-action end-win))
  447. ;; Select end-win when no action
  448. (aw-switch-to-window end-win)))
  449. (throw 'done 'exit))
  450. (t
  451. (let ((action (aw--dispatch-action char)))
  452. (if action
  453. (cl-destructuring-bind (_key fn &optional description) action
  454. (if (and fn description)
  455. (prog1 (setq aw-action fn)
  456. (aw-set-mode-line (format " Ace - %s" description)))
  457. (if (commandp fn)
  458. (call-interactively fn)
  459. (funcall fn))
  460. (throw 'done 'exit)))
  461. (aw-clean-up-avy-current-path)
  462. ;; Prevent any char from triggering an avy dispatch command.
  463. (let ((avy-dispatch-alist))
  464. (avy-handler-default char)))))))
  465. (defcustom aw-display-mode-overlay t
  466. "When nil, don't display overlays. Rely on the mode line instead."
  467. :type 'boolean)
  468. (defvar ace-window-display-mode)
  469. (defun aw-select (mode-line &optional action)
  470. "Return a selected other window.
  471. Amend MODE-LINE to the mode line for the duration of the selection."
  472. (setq aw-action action)
  473. (let ((start-window (selected-window))
  474. (next-window-scope (cl-case aw-scope
  475. ('visible 'visible)
  476. ('global 'visible)
  477. ('frame 'frame)))
  478. (wnd-list (aw-window-list))
  479. window)
  480. (setq window
  481. (cond ((<= (length wnd-list) 1)
  482. (when aw-dispatch-always
  483. (setq aw-action
  484. (unwind-protect
  485. (catch 'done
  486. (funcall aw-dispatch-function (read-char)))
  487. (aw--done)))
  488. (when (eq aw-action 'exit)
  489. (setq aw-action nil)))
  490. (or (car wnd-list) start-window))
  491. ((and (<= (+ (length wnd-list) (if (aw-ignored-p start-window) 1 0))
  492. aw-dispatch-when-more-than)
  493. (not aw-dispatch-always)
  494. (not aw-ignore-current))
  495. (let ((wnd (next-window nil nil next-window-scope)))
  496. (while (and (or (not (memq wnd wnd-list))
  497. (aw-ignored-p wnd))
  498. (not (equal wnd start-window)))
  499. (setq wnd (next-window wnd nil next-window-scope)))
  500. wnd))
  501. (t
  502. (let ((candidate-list
  503. (mapcar (lambda (wnd)
  504. (cons (aw-offset wnd) wnd))
  505. wnd-list)))
  506. (aw--make-backgrounds wnd-list)
  507. (aw-set-mode-line mode-line)
  508. ;; turn off helm transient map
  509. (remove-hook 'post-command-hook 'helm--maybe-update-keymap)
  510. (unwind-protect
  511. (let* ((avy-handler-function aw-dispatch-function)
  512. (avy-translate-char-function aw-translate-char-function)
  513. (transient-mark-mode nil)
  514. (res (avy-read (avy-tree candidate-list aw-keys)
  515. (if (and ace-window-display-mode
  516. (null aw-display-mode-overlay))
  517. (lambda (_path _leaf))
  518. #'aw--lead-overlay)
  519. #'avy--remove-leading-chars)))
  520. (if (eq res 'exit)
  521. (setq aw-action nil)
  522. (or (cdr res)
  523. start-window)))
  524. (aw--done))))))
  525. (if aw-action
  526. (funcall aw-action window)
  527. window)))
  528. ;;* Interactive
  529. ;;;###autoload
  530. (defun ace-select-window ()
  531. "Ace select window."
  532. (interactive)
  533. (aw-select " Ace - Window"
  534. #'aw-switch-to-window))
  535. ;;;###autoload
  536. (defun ace-delete-window ()
  537. "Ace delete window."
  538. (interactive)
  539. (aw-select " Ace - Delete Window"
  540. #'aw-delete-window))
  541. ;;;###autoload
  542. (defun ace-swap-window ()
  543. "Ace swap window."
  544. (interactive)
  545. (aw-select " Ace - Swap Window"
  546. #'aw-swap-window))
  547. ;;;###autoload
  548. (defun ace-delete-other-windows ()
  549. "Ace delete other windows."
  550. (interactive)
  551. (aw-select " Ace - Delete Other Windows"
  552. #'delete-other-windows))
  553. ;;;###autoload
  554. (defun ace-display-buffer (buffer alist)
  555. "Make `display-buffer' and `pop-to-buffer' select using `ace-window'.
  556. See sample config for `display-buffer-base-action' and `display-buffer-alist':
  557. https://github.com/abo-abo/ace-window/wiki/display-buffer."
  558. (let* ((aw-ignore-current (cdr (assq 'inhibit-same-window alist)))
  559. (rf (cdr (assq 'reusable-frames alist)))
  560. (aw-scope (cl-case rf
  561. ((nil) 'frame)
  562. (visible 'visible)
  563. ((0 t) 'global))))
  564. (unless (or (<= (length (aw-window-list)) 1)
  565. (not aw-scope))
  566. (window--display-buffer
  567. buffer (aw-select "Ace - Display Buffer") 'reuse))))
  568. (declare-function transpose-frame "ext:transpose-frame")
  569. (defun aw-transpose-frame (w)
  570. "Select any window on frame and `tranpose-frame'."
  571. (transpose-frame (window-frame w)))
  572. ;;;###autoload
  573. (defun ace-window (arg)
  574. "Select a window.
  575. Perform an action based on ARG described below.
  576. By default, behaves like extended `other-window'.
  577. See `aw-scope' which extends it to work with frames.
  578. Prefixed with one \\[universal-argument], does a swap between the
  579. selected window and the current window, so that the selected
  580. buffer moves to current window (and current buffer moves to
  581. selected window).
  582. Prefixed with two \\[universal-argument]'s, deletes the selected
  583. window."
  584. (interactive "p")
  585. (setq avy-current-path "")
  586. (cl-case arg
  587. (0
  588. (let ((aw-ignore-on (not aw-ignore-on)))
  589. (ace-select-window)))
  590. (4 (ace-swap-window))
  591. (16 (ace-delete-window))
  592. (t (ace-select-window))))
  593. ;;* Utility
  594. (unless (fboundp 'frame-position)
  595. (defun frame-position (&optional frame)
  596. (let ((pl (frame-parameter frame 'left))
  597. (pt (frame-parameter frame 'top)))
  598. (when (consp pl)
  599. (setq pl (eval pl)))
  600. (when (consp pt)
  601. (setq pt (eval pt)))
  602. (cons pl pt))))
  603. (defun aw-window< (wnd1 wnd2)
  604. "Return true if WND1 is less than WND2.
  605. This is determined by their respective window coordinates.
  606. Windows are numbered top down, left to right."
  607. (let* ((f1 (window-frame wnd1))
  608. (f2 (window-frame wnd2))
  609. (e1 (window-edges wnd1))
  610. (e2 (window-edges wnd2))
  611. (p1 (frame-position f1))
  612. (p2 (frame-position f2))
  613. (nl (or (null (car p1)) (null (car p2)))))
  614. (cond ((and (not nl) (< (car p1) (car p2)))
  615. (not aw-reverse-frame-list))
  616. ((and (not nl) (> (car p1) (car p2)))
  617. aw-reverse-frame-list)
  618. ((< (car e1) (car e2))
  619. t)
  620. ((> (car e1) (car e2))
  621. nil)
  622. ((< (cadr e1) (cadr e2))
  623. t))))
  624. (defvar aw--window-ring (make-ring 10)
  625. "Hold the window switching history.")
  626. (defun aw--push-window (window)
  627. "Store WINDOW to `aw--window-ring'."
  628. (when (or (zerop (ring-length aw--window-ring))
  629. (not (equal
  630. (ring-ref aw--window-ring 0)
  631. window)))
  632. (ring-insert aw--window-ring (selected-window))))
  633. (defun aw--pop-window ()
  634. "Return the removed top of `aw--window-ring'."
  635. (let (res)
  636. (condition-case nil
  637. (while (or (not (window-live-p
  638. (setq res (ring-remove aw--window-ring 0))))
  639. (equal res (selected-window))))
  640. (error
  641. (if (= (length (aw-window-list)) 2)
  642. (progn
  643. (other-window 1)
  644. (setq res (selected-window)))
  645. (error "No previous windows stored"))))
  646. res))
  647. (defun aw-switch-to-window (window)
  648. "Switch to the window WINDOW."
  649. (let ((frame (window-frame window)))
  650. (aw--push-window (selected-window))
  651. (when (and (frame-live-p frame)
  652. (not (eq frame (selected-frame))))
  653. (select-frame-set-input-focus frame))
  654. (if (window-live-p window)
  655. (select-window window)
  656. (error "Got a dead window %S" window))))
  657. (defun aw-flip-window ()
  658. "Switch to the window you were previously in."
  659. (interactive)
  660. (aw-switch-to-window (aw--pop-window)))
  661. (defun aw-show-dispatch-help ()
  662. "Display action shortucts in echo area."
  663. (interactive)
  664. (message "%s" (mapconcat
  665. (lambda (action)
  666. (cl-destructuring-bind (key fn &optional description) action
  667. (format "%s: %s"
  668. (propertize
  669. (char-to-string key)
  670. 'face 'aw-key-face)
  671. (or description fn))))
  672. aw-dispatch-alist
  673. "\n"))
  674. ;; Prevent this from replacing any help display
  675. ;; in the minibuffer.
  676. (let (aw-minibuffer-flag)
  677. (mapc #'delete-overlay aw-overlays-back)
  678. (call-interactively 'ace-window)))
  679. (defun aw-delete-window (window &optional kill-buffer)
  680. "Delete window WINDOW.
  681. When KILL-BUFFER is non-nil, also kill the buffer."
  682. (let ((frame (window-frame window)))
  683. (when (and (frame-live-p frame)
  684. (not (eq frame (selected-frame))))
  685. (select-frame-set-input-focus (window-frame window)))
  686. (if (= 1 (length (window-list)))
  687. (delete-frame frame)
  688. (if (window-live-p window)
  689. (let ((buffer (window-buffer window)))
  690. (delete-window window)
  691. (when kill-buffer
  692. (kill-buffer buffer)))
  693. (error "Got a dead window %S" window)))))
  694. (defun aw-switch-buffer-in-window (window)
  695. "Select buffer in WINDOW."
  696. (aw-switch-to-window window)
  697. (aw--switch-buffer))
  698. (declare-function ivy-switch-buffer "ext:ivy")
  699. (defun aw--switch-buffer ()
  700. (cond ((bound-and-true-p ivy-mode)
  701. (ivy-switch-buffer))
  702. ((bound-and-true-p ido-mode)
  703. (ido-switch-buffer))
  704. (t
  705. (call-interactively 'switch-to-buffer))))
  706. (defcustom aw-swap-invert nil
  707. "When non-nil, the other of the two swapped windows gets the point."
  708. :type 'boolean)
  709. (defun aw-swap-window (window)
  710. "Swap buffers of current window and WINDOW."
  711. (cl-labels ((swap-windows (window1 window2)
  712. "Swap the buffers of WINDOW1 and WINDOW2."
  713. (let ((buffer1 (window-buffer window1))
  714. (buffer2 (window-buffer window2)))
  715. (set-window-buffer window1 buffer2)
  716. (set-window-buffer window2 buffer1)
  717. (select-window window2))))
  718. (let ((frame (window-frame window))
  719. (this-window (selected-window)))
  720. (when (and (frame-live-p frame)
  721. (not (eq frame (selected-frame))))
  722. (select-frame-set-input-focus (window-frame window)))
  723. (when (and (window-live-p window)
  724. (not (eq window this-window)))
  725. (aw--push-window this-window)
  726. (if aw-swap-invert
  727. (swap-windows window this-window)
  728. (swap-windows this-window window))))))
  729. (defun aw-move-window (window)
  730. "Move the current buffer to WINDOW.
  731. Switch the current window to the previous buffer."
  732. (let ((buffer (current-buffer)))
  733. (switch-to-buffer (other-buffer))
  734. (aw-switch-to-window window)
  735. (switch-to-buffer buffer)))
  736. (defun aw-copy-window (window)
  737. "Copy the current buffer to WINDOW."
  738. (let ((buffer (current-buffer)))
  739. (aw-switch-to-window window)
  740. (switch-to-buffer buffer)))
  741. (defun aw-split-window-vert (window)
  742. "Split WINDOW vertically."
  743. (select-window window)
  744. (split-window-vertically))
  745. (defun aw-split-window-horz (window)
  746. "Split WINDOW horizontally."
  747. (select-window window)
  748. (split-window-horizontally))
  749. (defcustom aw-fair-aspect-ratio 2
  750. "The aspect ratio to aim for when splitting windows.
  751. Sizes are based on the number of characters, not pixels.
  752. Increase to prefer wider windows, or decrease for taller windows."
  753. :type 'number)
  754. (defun aw-split-window-fair (window)
  755. "Split WINDOW vertically or horizontally, based on its current dimensions.
  756. Modify `aw-fair-aspect-ratio' to tweak behavior."
  757. (let ((w (window-body-width window))
  758. (h (window-body-height window)))
  759. (if (< (* h aw-fair-aspect-ratio) w)
  760. (aw-split-window-horz window)
  761. (aw-split-window-vert window))))
  762. (defun aw-switch-buffer-other-window (window)
  763. "Switch buffer in WINDOW."
  764. (aw-switch-to-window window)
  765. (unwind-protect
  766. (aw--switch-buffer)
  767. (aw-flip-window)))
  768. (defun aw-execute-command-other-window (window)
  769. "Execute a command in WINDOW."
  770. (aw-switch-to-window window)
  771. (unwind-protect
  772. (funcall
  773. (key-binding
  774. (read-key-sequence
  775. "Enter key sequence: ")))
  776. (aw-flip-window)))
  777. (defun aw--face-rel-height ()
  778. (let ((h (face-attribute 'aw-leading-char-face :height)))
  779. (cond
  780. ((eq h 'unspecified)
  781. 1)
  782. ((floatp h)
  783. (max (floor h) 1))
  784. ((integerp h)
  785. 1)
  786. (t
  787. (error "unexpected: %s" h)))))
  788. (defun aw-offset (window)
  789. "Return point in WINDOW that's closest to top left corner.
  790. The point is writable, i.e. it's not part of space after newline."
  791. (let ((h (window-hscroll window))
  792. (beg (window-start window))
  793. (end (window-end window))
  794. (inhibit-field-text-motion t))
  795. (with-current-buffer (window-buffer window)
  796. (save-excursion
  797. (goto-char beg)
  798. (forward-line (1-
  799. (min
  800. (count-lines
  801. (point)
  802. (point-max))
  803. (aw--face-rel-height))))
  804. (while (and (< (point) end)
  805. (< (- (line-end-position)
  806. (line-beginning-position))
  807. h))
  808. (forward-line))
  809. (+ (point) h)))))
  810. (defun aw--after-make-frame (f)
  811. (aw-update)
  812. (make-frame-visible f))
  813. ;;* Mode line
  814. ;;;###autoload
  815. (define-minor-mode ace-window-display-mode
  816. "Minor mode for showing the ace window key in the mode line."
  817. :global t
  818. (if ace-window-display-mode
  819. (progn
  820. (aw-update)
  821. (set-default
  822. 'mode-line-format
  823. `((ace-window-display-mode
  824. (:eval (window-parameter (selected-window) 'ace-window-path)))
  825. ,@(assq-delete-all
  826. 'ace-window-display-mode
  827. (default-value 'mode-line-format))))
  828. (force-mode-line-update t)
  829. (add-hook 'window-configuration-change-hook 'aw-update)
  830. ;; Add at the end so does not precede select-frame call.
  831. (add-hook 'after-make-frame-functions #'aw--after-make-frame t))
  832. (set-default
  833. 'mode-line-format
  834. (assq-delete-all
  835. 'ace-window-display-mode
  836. (default-value 'mode-line-format)))
  837. (remove-hook 'window-configuration-change-hook 'aw-update)
  838. (remove-hook 'after-make-frame-functions 'aw--after-make-frame)))
  839. (defun aw-update ()
  840. "Update ace-window-path window parameter for all windows.
  841. Ensure all windows are labeled so the user can select a specific
  842. one, even from the set of windows typically ignored when making a
  843. window list."
  844. (let ((aw-ignore-on)
  845. (aw-ignore-current)
  846. (ignore-window-parameters t))
  847. (avy-traverse
  848. (avy-tree (aw-window-list) aw-keys)
  849. (lambda (path leaf)
  850. (set-window-parameter
  851. leaf 'ace-window-path
  852. (propertize
  853. (apply #'string (reverse path))
  854. 'face 'aw-mode-line-face))))))
  855. (provide 'ace-window)
  856. ;;; ace-window.el ends here