diff options
author | Lev Lamberov <dogsleg@debian.org> | 2016-10-06 18:03:29 -0300 |
---|---|---|
committer | Lev Lamberov <dogsleg@debian.org> | 2016-10-06 18:03:29 -0300 |
commit | 29a19f983878888bd212bc30c1abbe43a074eeb3 (patch) | |
tree | 39cce3d75cdf8d19078a087fb5a7735b1680a1f8 |
Import ace-window_0.9.0.orig.tar.gz
[dgit import orig ace-window_0.9.0.orig.tar.gz]
-rw-r--r-- | Cask | 8 | ||||
-rw-r--r-- | Makefile | 15 | ||||
-rw-r--r-- | README.md | 120 | ||||
-rw-r--r-- | ace-window.el | 525 |
4 files changed, 668 insertions, 0 deletions
@@ -0,0 +1,8 @@ +(source gnu) +(source melpa) + +(package-file "ace-window.el") + +(development + (depends-on "avy")) + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8dff696 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +emacs ?= emacs +CASK = ~/.cask/bin/cask + +.PHONY: all clean + +all: compile + +cask: + $(shell EMACS=$(emacs) $(CASK) --verbose --debug) + +compile: + $(CASK) exec $(emacs) -batch --eval "(byte-compile-file \"ace-window.el\")" + +clean: + rm -f *.elc diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdd1452 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# ace-window + +**GNU Emacs package for selecting a window to switch to** + +## What and why + +I'm sure you're aware of `other-window` command. While it's great for +two windows, it quickly loses it's value when there are more windows: +you need to call it many times, and since it's not easily predictable, +you have to check each time if you're in the window that you wanted. + +Another approach is to use `windmove-left`, `windmove-up` etc. These +are fast and predictable. Their disadvantage is that they need 4 key +bindings. The default ones are shift+arrows, which are hard to reach. + +This package aims to take the speed and predictability of `windmove` +and pack it into a single key binding, similar to `other-window`. + +## Setup + +Just assign `ace-window` to a short key binding, as switching windows +is a common task. I suggest <kbd>M-p</kbd>, as it's short and not +bound in the default Emacs. + +## Usage + +When there are two windows, `ace-window` will call `other-window`. If +there are more, each window will have its first character highlighted. +Pressing that character will switch to that window. Note that, unlike +`ace-jump-mode`, the point position will not be changed: it's the same +behavior as that of `other-window`. + +The windows are ordered top-down, left-to-right. This means that if +you remember your window layouts, you can switch windows without even +looking at the leading char. For instance, the top left window will +always be `1`. + +`ace-window` works across multiple frames, as you can see from the +[in-action gif](http://oremacs.com/download/ace-window.gif). + +## Swap and delete window + +- You can swap windows by calling `ace-window` with a prefix argument <kbd>C-u</kbd>. + +- You can delete the selected window by calling `ace-window` with a double prefix argument, i.e. <kbd>C-u C-u</kbd>. + +## Change the action midway + +You can also start by calling `ace-window` and then decide to switch the action to `delete` or `swap` etc. By default the bindings are: + +- <kbd>x</kbd> - delete window +- <kbd>m</kbd> - swap (move) window +- <kbd>v</kbd> - split window vertically +- <kbd>b</kbd> - split window horizontally +- <kbd>n</kbd> - select the previous window +- <kbd>i</kbd> - maximize window (select which window) +- <kbd>o</kbd> - maximize current window + +In order for it to work, these keys *must not* be in `aw-keys` and you have to have `aw-dispatch-always` set to `t`. + +## Customization +Aside from binding `ace-window`: + + (global-set-key (kbd "M-p") 'ace-window) + +maybe you'd like the following customizations: + +### `aw-keys` +`aw-keys` - the sequence of leading characters for each window: + + (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)) + +`aw-keys` are 0-9 by default, which is reasonable, but in the setup +above, the keys are on the home row. + +### `aw-scope` +The default one is `global`, which means that `ace-window` will work +across frames. If you set this to `frame`, `ace-window` will offer you +the windows only on current frame. + +### `aw-background` + +By default, `ace-window` temporarily sets a gray background and +removes color from available windows in order to make the +window-switching characters more visible. This is the behavior +inherited from `ace-jump-mode`. + +This behavior might not be necessary, as you already know the locations +where to look, i.e. the top-left corners of each window. +So you can turn off the gray background with: + + (setq aw-background nil) + +### `aw-dispatch-always` + +When non-nil, `ace-window` will issue a `read-char` event for one window. +This will make `ace-window` act different from `other-window` for one +or two windows. This is useful to be able to change the action midway +and execute other action other than the *jump* default. +By default is set to `nil` + +### `aw-dispatch-alist` + +This is the list of actions that you can trigger from `ace-window` other than the +*jump* default. +By default is: + + (defvar aw-dispatch-alist + '((?x aw-delete-window " Ace - Delete Window") + (?m aw-swap-window " Ace - Swap Window") + (?n aw-flip-window) + (?v aw-split-window-vert " Ace - Split Vert Window") + (?b aw-split-window-horz " Ace - Split Horz Window") + (?i delete-other-windows " Ace - Maximize Window") + (?o delete-other-windows)) + "List of actions for `aw-dispatch-default'.") + +If the pair key-action is followed by a string, then `ace-window` will be +invoked again to be able to select on which window you want to select the +action. Otherwise the current window is selected. diff --git a/ace-window.el b/ace-window.el new file mode 100644 index 0000000..20fe1e4 --- /dev/null +++ b/ace-window.el @@ -0,0 +1,525 @@ +;;; ace-window.el --- Quickly switch windows. -*- lexical-binding: t -*- + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel <ohwoeowho@gmail.com> +;; Maintainer: Oleh Krehel <ohwoeowho@gmail.com> +;; URL: https://github.com/abo-abo/ace-window +;; Version: 0.8.1 +;; Package-Requires: ((avy "0.2.0")) +;; Keywords: window, location + +;; This file is part of GNU Emacs. + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; For a full copy of the GNU General Public License +;; see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; The main function, `ace-window' is meant to replace `other-window'. +;; In fact, when there are only two windows present, `other-window' is +;; called. If there are more, each window will have its first +;; character highlighted. Pressing that character will switch to that +;; window. +;; +;; To setup this package, just add to your .emacs: +;; +;; (global-set-key (kbd "M-p") 'ace-window) +;; +;; replacing "M-p" with an appropriate shortcut. +;; +;; Depending on your window usage patterns, you might want to set +;; +;; (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)) +;; +;; This way they are all on the home row, although the intuitive +;; ordering is lost. +;; +;; If you don't want the gray background that makes the red selection +;; characters stand out more, set this: +;; +;; (setq aw-background nil) +;; +;; If you want to know the selection characters ahead of time, you can +;; turn on `ace-window-display-mode'. +;; +;; When prefixed with one `universal-argument', instead of switching +;; to selected window, the selected window is swapped with current one. +;; +;; When prefixed with two `universal-argument', the selected window is +;; deleted instead. + +;;; Code: +(require 'avy) +(require 'ring) + +;;* Customization +(defgroup ace-window nil + "Quickly switch current window." + :group 'convenience + :prefix "aw-") + +(defcustom aw-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9) + "Keys for selecting window.") + +(defcustom aw-scope 'global + "The scope used by `ace-window'." + :type '(choice + (const :tag "global" global) + (const :tag "frame" frame))) + +(defcustom aw-ignored-buffers '("*Calc Trail*" "*LV*") + "List of buffers to ignore when selecting window." + :type '(repeat string)) + +(defcustom aw-ignore-on t + "When t, `ace-window' will ignore `aw-ignored-buffers'. +Use M-0 `ace-window' to toggle this value." + :type 'boolean) + +(defcustom aw-ignore-current nil + "When t, `ace-window' will ignore `selected-window'." + :type 'boolean) + +(defcustom aw-background t + "When t, `ace-window' will dim out all buffers temporarily when used.'." + :type 'boolean) + +(defcustom aw-leading-char-style 'char + "Style of the leading char overlay." + :type '(choice + (const :tag "single char" 'char) + (const :tag "full path" 'path))) + +(defcustom aw-dispatch-always nil + "When non-nil, `ace-window' will issue a `read-char' even for one window. +This will make `ace-window' act different from `other-window' for + one or two windows." + :type 'boolean) + +(defface aw-leading-char-face + '((((class color)) (:foreground "red")) + (((background dark)) (:foreground "gray100")) + (((background light)) (:foreground "gray0")) + (t (:foreground "gray100" :underline nil))) + "Face for each window's leading char.") + +(defface aw-background-face + '((t (:foreground "gray40"))) + "Face for whole window background during selection.") + +(defface aw-mode-line-face + '((t (:inherit mode-line-buffer-id))) + "Face used for displaying the ace window key in the mode-line.") + +;;* Implementation +(defun aw-ignored-p (window) + "Return t if WINDOW should be ignored." + (or (and aw-ignore-on + (member (buffer-name (window-buffer window)) + aw-ignored-buffers)) + (and aw-ignore-current + (equal window (selected-window))))) + +(defun aw-window-list () + "Return the list of interesting windows." + (sort + (cl-remove-if + (lambda (w) + (let ((f (window-frame w)) + (b (window-buffer w))) + (or (not (and (frame-live-p f) + (frame-visible-p f))) + (string= "initial_terminal" (terminal-name f)) + (aw-ignored-p w) + (with-current-buffer b + (and buffer-read-only + (= 0 (buffer-size b))))))) + (cl-case aw-scope + (global + (cl-mapcan #'window-list (frame-list))) + (frame + (window-list)) + (t + (error "Invalid `aw-scope': %S" aw-scope)))) + 'aw-window<)) + +(defvar aw-overlays-back nil + "Hold overlays for when `aw-background' is t.") + +(defvar ace-window-mode nil + "Minor mode during the selection process.") + +;; register minor mode +(or (assq 'ace-window-mode minor-mode-alist) + (nconc minor-mode-alist + (list '(ace-window-mode ace-window-mode)))) + +(defun aw--done () + "Clean up mode line and overlays." + ;; mode line + (aw-set-mode-line nil) + ;; background + (mapc #'delete-overlay aw-overlays-back) + (setq aw-overlays-back nil) + (avy--remove-leading-chars)) + +(defun aw--lead-overlay (path leaf) + "Create an overlay using PATH at LEAF. +LEAF is (PT . WND)." + (let* ((pt (car leaf)) + (wnd (cdr leaf)) + (ol (make-overlay pt (1+ pt) (window-buffer wnd))) + (old-str (or + (ignore-errors + (with-selected-window wnd + (buffer-substring pt (1+ pt)))) + "")) + (new-str + (concat + (cl-case aw-leading-char-style + (char + (apply #'string (last path))) + (path + (apply #'string (reverse path))) + (t + (error "Bad `aw-leading-char-style': %S" + aw-leading-char-style))) + (cond ((string-equal old-str "\t") + (make-string (1- tab-width) ?\ )) + ((string-equal old-str "\n") + "\n") + (t + (make-string + (max 0 (1- (string-width old-str))) + ?\ )))))) + (overlay-put ol 'face 'aw-leading-char-face) + (overlay-put ol 'window wnd) + (overlay-put ol 'display new-str) + (push ol avy--overlays-lead))) + +(defun aw--make-backgrounds (wnd-list) + "Create a dim background overlay for each window on WND-LIST." + (when aw-background + (setq aw-overlays-back + (mapcar (lambda (w) + (let ((ol (make-overlay + (window-start w) + (window-end w) + (window-buffer w)))) + (overlay-put ol 'face 'aw-background-face) + ol)) + wnd-list)))) + +(define-obsolete-variable-alias + 'aw-flip-keys 'aw--flip-keys "0.1.0" + "Use `aw-dispatch-alist' instead.") + +(defvar aw-dispatch-function 'aw-dispatch-default + "Function to call when a character not in `aw-keys' is pressed.") + +(defvar aw-action nil + "Function to call at the end of `aw-select'.") + +(defun aw-set-mode-line (str) + "Set mode line indicator to STR." + (setq ace-window-mode str) + (force-mode-line-update)) + +(defvar aw-dispatch-alist + '((?x aw-delete-window " Ace - Delete Window") + (?m aw-swap-window " Ace - Swap Window") + (?n aw-flip-window) + (?v aw-split-window-vert " Ace - Split Vert Window") + (?b aw-split-window-horz " Ace - Split Horz Window") + (?i delete-other-windows " Ace - Maximize Window") + (?o delete-other-windows)) + "List of actions for `aw-dispatch-default'.") + +(defun aw-dispatch-default (char) + "Perform an action depending on CHAR." + (let ((val (cdr (assoc char aw-dispatch-alist)))) + (if val + (if (and (car val) (cadr val)) + (prog1 (setq aw-action (car val)) + (aw-set-mode-line (cadr val))) + (funcall (car val)) + (throw 'done 'exit)) + (avy-handler-default char)))) + +(defun aw-select (mode-line &optional action) + "Return a selected other window. +Amend MODE-LINE to the mode line for the duration of the selection." + (setq aw-action action) + (let ((start-window (selected-window)) + (next-window-scope (cl-case aw-scope + ('global 'visible) + ('frame 'frame))) + (wnd-list (aw-window-list)) + window) + (setq window + (cond ((<= (length wnd-list) 1) + (when aw-dispatch-always + (setq aw-action + (unwind-protect + (catch 'done + (funcall aw-dispatch-function (read-char))) + (aw--done))) + (when (eq aw-action 'exit) + (setq aw-action nil))) + (or (car wnd-list) start-window)) + ((and (= (length wnd-list) 2) (not aw-dispatch-always)) + (let ((wnd (next-window nil nil next-window-scope))) + (while (and (aw-ignored-p wnd) + (not (equal wnd start-window))) + (setq wnd (next-window wnd nil next-window-scope))) + wnd)) + (t + (let ((candidate-list + (mapcar (lambda (wnd) + ;; can't jump if the buffer is empty + (with-current-buffer (window-buffer wnd) + (when (= 0 (buffer-size)) + (insert " "))) + (cons (aw-offset wnd) wnd)) + wnd-list))) + (aw--make-backgrounds wnd-list) + (aw-set-mode-line mode-line) + ;; turn off helm transient map + (remove-hook 'post-command-hook 'helm--maybe-update-keymap) + (unwind-protect + (let* ((avy-handler-function aw-dispatch-function) + (res (avy-read (avy-tree candidate-list aw-keys) + #'aw--lead-overlay + #'avy--remove-leading-chars))) + (if (eq res 'exit) + (setq aw-action nil) + (or (cdr res) + start-window))) + (aw--done)))))) + (if aw-action + (funcall aw-action window) + window))) + +;;* Interactive +;;;###autoload +(defun ace-select-window () + "Ace select window." + (interactive) + (aw-select " Ace - Window" + #'aw-switch-to-window)) + +;;;###autoload +(defun ace-delete-window () + "Ace delete window." + (interactive) + (aw-select " Ace - Delete Window" + #'aw-delete-window)) + +;;;###autoload +(defun ace-swap-window () + "Ace swap window." + (interactive) + (aw-select " Ace - Swap Window" + #'aw-swap-window)) + +;;;###autoload +(defun ace-maximize-window () + "Ace maximize window." + (interactive) + (aw-select " Ace - Maximize Window" + #'delete-other-windows)) + +;;;###autoload +(defun ace-window (arg) + "Select a window. +Perform an action based on ARG described below. + +By default, behaves like extended `other-window'. + +Prefixed with one \\[universal-argument], does a swap between the +selected window and the current window, so that the selected +buffer moves to current window (and current buffer moves to +selected window). + +Prefixed with two \\[universal-argument]'s, deletes the selected +window." + (interactive "p") + (cl-case arg + (0 + (setq aw-ignore-on + (not aw-ignore-on)) + (ace-select-window)) + (4 (ace-swap-window)) + (16 (ace-delete-window)) + (t (ace-select-window)))) + +;;* Utility +(defun aw-window< (wnd1 wnd2) + "Return true if WND1 is less than WND2. +This is determined by their respective window coordinates. +Windows are numbered top down, left to right." + (let ((f1 (window-frame wnd1)) + (f2 (window-frame wnd2)) + (e1 (window-edges wnd1)) + (e2 (window-edges wnd2))) + (cond ((string< (frame-parameter f1 'window-id) + (frame-parameter f2 'window-id)) + t) + ((< (car e1) (car e2)) + t) + ((> (car e1) (car e2)) + nil) + ((< (cadr e1) (cadr e2)) + t)))) + +(defvar aw--window-ring (make-ring 10) + "Hold the window switching history.") + +(defun aw--push-window (window) + "Store WINDOW to `aw--window-ring'." + (when (or (zerop (ring-length aw--window-ring)) + (not (equal + (ring-ref aw--window-ring 0) + window))) + (ring-insert aw--window-ring (selected-window)))) + +(defun aw--pop-window () + "Return the removed top of `aw--window-ring'." + (let (res) + (condition-case nil + (while (or (not (window-live-p + (setq res (ring-remove aw--window-ring 0)))) + (equal res (selected-window)))) + (error + (if (= (length (aw-window-list)) 2) + (progn + (other-window 1) + (setq res (selected-window))) + (error "No previous windows stored")))) + res)) + +(defun aw-switch-to-window (window) + "Switch to the window WINDOW." + (let ((frame (window-frame window))) + (when (and (frame-live-p frame) + (not (eq frame (selected-frame)))) + (select-frame-set-input-focus frame)) + (if (window-live-p window) + (progn + (aw--push-window (selected-window)) + (select-window window)) + (error "Got a dead window %S" window)))) + +(defun aw-flip-window () + "Switch to the window you were previously in." + (interactive) + (aw-switch-to-window (aw--pop-window))) + +(defun aw-delete-window (window) + "Delete window WINDOW." + (let ((frame (window-frame window))) + (when (and (frame-live-p frame) + (not (eq frame (selected-frame)))) + (select-frame-set-input-focus (window-frame window))) + (if (= 1 (length (window-list))) + (delete-frame frame) + (if (window-live-p window) + (delete-window window) + (error "Got a dead window %S" window))))) + +(defun aw-swap-window (window) + "Swap buffers of current window and WINDOW." + (cl-labels ((swap-windows (window1 window2) + "Swap the buffers of WINDOW1 and WINDOW2." + (let ((buffer1 (window-buffer window1)) + (buffer2 (window-buffer window2))) + (set-window-buffer window1 buffer2) + (set-window-buffer window2 buffer1) + (select-window window2)))) + (let ((frame (window-frame window)) + (this-window (selected-window))) + (when (and (frame-live-p frame) + (not (eq frame (selected-frame)))) + (select-frame-set-input-focus (window-frame window))) + (when (and (window-live-p window) + (not (eq window this-window))) + (aw--push-window this-window) + (swap-windows this-window window))))) + +(defun aw-split-window-vert (window) + "Split WINDOW vertically." + (select-window window) + (split-window-vertically)) + +(defun aw-split-window-horz (window) + "Split WINDOW horizontally." + (select-window window) + (split-window-horizontally)) + +(defun aw-offset (window) + "Return point in WINDOW that's closest to top left corner. +The point is writable, i.e. it's not part of space after newline." + (let ((h (window-hscroll window)) + (beg (window-start window)) + (end (window-end window)) + (inhibit-field-text-motion t)) + (with-current-buffer + (window-buffer window) + (save-excursion + (goto-char beg) + (while (and (< (point) end) + (< (- (line-end-position) + (line-beginning-position)) + h)) + (forward-line)) + (+ (point) h))))) + +;;* Mode line +;;;###autoload +(define-minor-mode ace-window-display-mode + "Minor mode for showing the ace window key in the mode line." + :global t + (if ace-window-display-mode + (progn + (aw-update) + (set-default + 'mode-line-format + `((ace-window-display-mode + (:eval (window-parameter (selected-window) 'ace-window-path))) + ,@(assq-delete-all + 'ace-window-display-mode + (default-value 'mode-line-format)))) + (force-mode-line-update t) + (add-hook 'window-configuration-change-hook 'aw-update)) + (set-default + 'mode-line-format + (assq-delete-all + 'ace-window-display-mode + (default-value 'mode-line-format))) + (remove-hook 'window-configuration-change-hook 'aw-update))) + +(defun aw-update () + "Update ace-window-path window parameter for all windows." + (avy-traverse + (avy-tree (aw-window-list) aw-keys) + (lambda (path leaf) + (set-window-parameter + leaf 'ace-window-path + (propertize + (apply #'string (reverse path)) + 'face 'aw-mode-line-face))))) + +(provide 'ace-window) + +;;; ace-window.el ends here |