emacs-winum/window-numbering.el

362 lines
14 KiB
EmacsLisp
Raw Normal View History

;;; window-numbering.el --- Numbered window shortcuts
;;
2015-02-28 15:47:03 -05:00
;; Copyright (C) 2006-2007, 2013, 2015 Nikolaj Schumacher <bugs * nschum , de>
;;
;; Author: Nikolaj Schumacher <bugs * nschum de>
2013-03-23 04:28:51 -04:00
;; Version: 1.1.2
;; Keywords: faces, matching
;; URL: http://nschum.de/src/emacs/window-numbering-mode/
;; Compatibility: GNU Emacs 22.x, GNU Emacs 23.x, GNU Emacs 24.x
;;
;; This file is NOT part of GNU Emacs.
;;
2008-04-11 15:22:15 -04:00
;; This program 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 2
;; of the License, or (at your option) any later version.
;;
2008-04-11 15:22:15 -04:00
;; 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.
;;
2008-04-11 15:22:15 -04:00
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Commentary:
;;
2008-04-11 15:24:04 -04:00
;; Enable window-numbering-mode and use M-1 through M-0 to navigate.
;;
;; If you want to affect the numbers, use window-numbering-before-hook or
;; window-numbering-assign-func.
;; For instance, to always assign the calculator window the number 9, add the
;; following to your .emacs:
;;
;; (setq window-numbering-assign-func
;; (lambda () (when (equal (buffer-name) "*Calculator*") 9)))
;;
;;; Changes Log:
;;
;; Fix numbering of minibuffer for recent Emacs versions.
;;
2013-03-23 04:28:51 -04:00
;; 2013-03-23 (1.1.2)
;; Fix numbering in terminal mode with menu bar visible.
2013-01-27 06:49:51 -05:00
;; Add face for window number. (thanks to Chen Bin)
;;
;; 2008-04-11 (1.1.1)
;; Added possibility to delete window with prefix arg.
;; Cleaned up code and migrated to `defcustom'.
;;
2008-04-11 15:24:04 -04:00
;; 2007-02-18 (1.1)
;; Added window-numbering-before-hook, window-numbering-assign-func.
;;
;;; Code:
2008-04-11 15:24:04 -04:00
(eval-when-compile (require 'cl))
2008-04-11 15:22:15 -04:00
(push "^No window numbered .$" debug-ignored-errors)
2008-04-11 15:22:15 -04:00
(defgroup window-numbering nil
2016-12-08 13:36:57 -05:00
"Navigate and manage windows using numbers."
:group 'convenience)
2008-04-11 15:22:15 -04:00
(defcustom window-numbering-auto-assign-0-to-minibuffer t
2016-12-08 13:36:57 -05:00
"If non-nil, `window-numbering-mode' assigns 0 to the minibuffer if active."
:group 'window-numbering
:type '(choice (const :tag "Off" nil)
(const :tag "On" t)))
2008-04-11 15:24:04 -04:00
(defcustom window-numbering-before-hook nil
2016-12-08 13:36:57 -05:00
"Hook called before `window-numbering-mode' starts assigning numbers.
The list of windows to be numbered is passed as a parameter.
2008-04-11 15:24:04 -04:00
Use `window-numbering-assign' to manually assign some of them a number.
If you want to assign a number to just one buffer, use
`window-numbering-assign-func' instead."
:group 'window-numbering
:type 'hook)
2008-04-11 15:24:04 -04:00
(defcustom window-numbering-assign-func nil
2016-12-08 13:36:57 -05:00
"Function called for each window by `window-numbering-mode'.
This is called before automatic assignment begins. The function should
return a number to have it assigned to the current-window, nil otherwise."
:group 'window-numbering
:type 'function)
2008-04-11 15:24:04 -04:00
(defconst window-numbering-mode-line-position 1
"The position in the mode-line `window-numbering-mode' displays the number.")
2008-04-11 15:24:04 -04:00
2016-12-08 13:42:43 -05:00
;; TODO when changed from frame-local to non-local in customize, need to force
;; update, or `window-numbering-get-number' fails and crashes the modeline
;; until next update.
(defcustom window-numbering-frame-scope 'global
"The scope of number sets."
:group 'window-numbering
:type '(choice
(const :tag "frame local" frame-local)
(const :tag "visible frames" visible)
(const :tag "global" global)))
(defcustom window-numbering-reverse-frame-list nil
"If t, order frames by reverse order of creation.
Has effect only when `window-numbering-frame-scope' is not 'frame-local."
:group 'window-numbering
:type 'boolean)
2016-08-09 14:10:09 -04:00
(defface window-numbering-face
'()
2013-01-27 06:49:51 -05:00
"Face used for the number in the mode-line."
:group 'window-numbering)
(defvar window-numbering--frames-table nil
2016-12-08 13:36:57 -05:00
"Bidirectional table between windows and window numbers.
Used when `window-numbering-frame-scope' is 'frame-local to keep
track of separate window numbers sets in every frame.
It is a hash table using emacs frames as keys and cons of the form
\(`window-numbering--windows' . `window-numbering--numbers') as values.
To get a window given a number, use the `car' of a value.
To get a number given a window, use the `cdr' of a value.
Such a structure allows for per-frame bidirectional fast access.")
;;;###autoload
(defun get-window-by-number (i)
"Return window numbered I if exists."
(let ((windows (if (eq window-numbering-frame-scope 'frame-local)
(car (gethash (selected-frame)
window-numbering--frames-table))
window-numbering--windows))
window)
(if (and (>= i 0) (< i 10)
(setq window (aref windows i)))
window
(error "No window numbered %s" i))))
;;;###autoload
(defun select-window-by-number (i &optional arg)
"Select window given number I by `window-numbering-mode'.
If prefix ARG is given, delete the window instead of selecting it."
(interactive "P")
(let ((w (get-window-by-number i)))
(if arg
(delete-window w)
(window-numbering-switch-to-window w))))
2016-12-08 13:42:43 -05:00
;; (defun window-numbering-select-window-[0-9] ())
(dotimes (i 10)
(eval `(defun ,(intern (format "select-window-%s" i)) (&optional arg)
,(format "Select the window with number %i." i)
(interactive "P")
(select-window-by-number ,i arg))))
2008-04-11 15:24:04 -04:00
(defun window-numbering-calculate-left (windows)
2016-12-08 13:36:57 -05:00
"Return a list of numbers currently available for assignment.
WINDOWS: a vector of currently assigned windows."
(let ((i 9)
left)
2008-04-11 15:24:04 -04:00
(while (>= i 0)
(let ((window (aref windows i)))
(unless window
(push (% (1+ i) 10) left)))
(decf i))
left))
(defvar window-numbering--windows nil
2016-12-08 13:36:57 -05:00
"Vector of windows indexed by their number.
Used internally by window-numbering to get a window provided a number.")
(defvar window-numbering--numbers nil
2016-12-08 13:36:57 -05:00
"Hash table of numbers indexed by their window.
Used internally by window-numbering to get a number provided a window.")
(defvar window-numbering--left nil
"A list of unused window numbers.")
2008-04-11 15:24:04 -04:00
(defun window-numbering-assign (window &optional number)
2016-12-08 13:36:57 -05:00
"Assign to window WINDOW the number NUMBER.
If NUMBER is not specified, determine it first based on
`window-numbering--left'.
Returns the assigned number, or nil on error."
2008-04-11 15:24:04 -04:00
(if number
(if (aref window-numbering--windows number)
2008-04-11 15:24:04 -04:00
(progn (message "Number %s assigned to two buffers (%s and %s)"
number window (aref window-numbering--windows number))
2008-04-11 15:24:04 -04:00
nil)
(setf (aref window-numbering--windows number) window)
(puthash window number window-numbering--numbers)
(setq window-numbering--left (delq number window-numbering--left))
number)
;; else determine number and assign
(when window-numbering--left
(unless (gethash window window-numbering--numbers)
(let ((number (car window-numbering--left)))
(window-numbering-assign window number))))))
2008-04-11 15:24:04 -04:00
2016-12-08 13:42:43 -05:00
;; TODO update mode-line in all frames
2008-04-11 15:24:04 -04:00
(defun window-numbering-update ()
"Update window numbers."
(setq window-numbering--windows (make-vector 10 nil)
window-numbering--numbers (make-hash-table :size 10)
window-numbering--left (window-numbering-calculate-left
window-numbering--windows))
(when (eq window-numbering-frame-scope 'frame-local)
(puthash (selected-frame)
(cons window-numbering--windows window-numbering--numbers)
window-numbering--frames-table))
(when (and window-numbering-auto-assign-0-to-minibuffer
(active-minibuffer-window))
(window-numbering-assign (active-minibuffer-window) 0))
(let ((windows (window-numbering-window-list)))
(run-hook-with-args 'window-numbering-before-hook windows)
(when window-numbering-assign-func
(mapc (lambda (window)
(with-selected-window window
(with-current-buffer (window-buffer window)
(let ((num (funcall window-numbering-assign-func)))
(when num
(window-numbering-assign window num))))))
windows))
(dolist (window windows)
(window-numbering-assign window))))
2008-04-11 15:22:15 -04:00
(defun window-numbering-list-windows-in-frame (&optional f)
"List windows in frame F using natural Emacs ordering."
(window-list f 0 (frame-first-window f)))
(defun window-numbering-window-list ()
"Return a list of interesting windows."
(cl-remove-if
(lambda (w)
(let ((f (window-frame w)))
(or (not (and (frame-live-p f)
(frame-visible-p f)))
(string= "initial_terminal" (terminal-name f))
;; (window-numbering-ignored-p w) ;; TODO implement
)))
(cl-case window-numbering-frame-scope
(global
(cl-mapcan 'window-numbering-list-windows-in-frame
(if window-numbering-reverse-frame-list
(frame-list)
(nreverse (frame-list)))))
(visible
(cl-mapcan 'window-numbering-list-windows-in-frame
(if window-numbering-reverse-frame-list
(visible-frame-list)
(nreverse (visible-frame-list)))))
(frame-local
(window-numbering-list-windows-in-frame))
(t
(error "Invalid `window-numbering-frame-scope': %S"
window-numbering-frame-scope)))))
(defun window-numbering-switch-to-window (window)
"Switch to the window WINDOW and switch input focus if on a different frame."
(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)
(select-window window)
(error "Got a dead window %S" window))))
;;;###autoload
2008-04-11 15:24:04 -04:00
(defun window-numbering-get-number-string (&optional window)
2016-12-08 13:36:57 -05:00
"Get the current or specified window's current number as a propertized string.
WINDOW: if specified, the window of which we want to know the number.
If not specified, the number of the currently selected window is
returned."
2013-01-27 06:49:51 -05:00
(let ((s (int-to-string (window-numbering-get-number window))))
(propertize s 'face 'window-numbering-face)))
2008-04-11 15:22:15 -04:00
;;;###autoload
2008-04-11 15:22:15 -04:00
(defun window-numbering-get-number (&optional window)
2016-12-08 13:36:57 -05:00
"Get the current or specified window's current number.
WINDOW: if specified, the window of which we want to know the number.
If not specified, the number of the currently selected window is
returned."
(let ((w (or window (selected-window))))
(if (eq window-numbering-frame-scope 'frame-local)
(gethash w (cdr (gethash (selected-frame)
window-numbering--frames-table)))
(gethash w window-numbering--numbers))))
2008-04-11 15:22:15 -04:00
(defvar window-numbering-keymap
(let ((map (make-sparse-keymap)))
(define-key map "\M-0" 'select-window-0)
(define-key map "\M-1" 'select-window-1)
(define-key map "\M-2" 'select-window-2)
(define-key map "\M-3" 'select-window-3)
(define-key map "\M-4" 'select-window-4)
(define-key map "\M-5" 'select-window-5)
(define-key map "\M-6" 'select-window-6)
(define-key map "\M-7" 'select-window-7)
(define-key map "\M-8" 'select-window-8)
(define-key map "\M-9" 'select-window-9)
map)
"Keymap used in by `window-numbering-mode'.")
;;;###autoload
2008-04-11 15:22:15 -04:00
(define-minor-mode window-numbering-mode
2016-12-08 13:36:57 -05:00
"A minor mode that allows for managing windows based on window numbers."
nil
nil
window-numbering-keymap
:global t
2008-04-11 15:22:15 -04:00
(if window-numbering-mode
(unless window-numbering--frames-table
2008-04-11 15:24:04 -04:00
(save-excursion
(setq window-numbering--frames-table (make-hash-table :size 16))
2008-04-11 15:24:04 -04:00
(window-numbering-install-mode-line)
(add-hook 'minibuffer-setup-hook 'window-numbering-update)
2008-04-11 15:24:04 -04:00
(add-hook 'window-configuration-change-hook
'window-numbering-update)
(dolist (frame (frame-list))
(select-frame frame)
(window-numbering-update))))
2008-04-11 15:22:15 -04:00
(window-numbering-clear-mode-line)
(remove-hook 'minibuffer-setup-hook 'window-numbering-update)
2008-04-11 15:22:15 -04:00
(remove-hook 'window-configuration-change-hook
2008-04-11 15:24:04 -04:00
'window-numbering-update)
(setq window-numbering--frames-table nil)))
2008-04-11 15:22:15 -04:00
;;;###autoload
2008-04-11 15:22:15 -04:00
(defun window-numbering-install-mode-line (&optional position)
2016-12-08 13:36:57 -05:00
"Install the window number from `window-numbering-mode' to the mode-line.
POSITION: position in the mode-line."
2008-04-11 15:22:15 -04:00
(let ((mode-line (default-value 'mode-line-format))
(res))
(dotimes (i (min (or position window-numbering-mode-line-position)
(length mode-line)))
(push (car mode-line) res)
(pop mode-line))
2008-04-11 15:24:04 -04:00
(push '(:eval (window-numbering-get-number-string)) res)
2008-04-11 15:22:15 -04:00
(while mode-line
(push (car mode-line) res)
(pop mode-line))
(setq-default mode-line-format (nreverse res)))
(force-mode-line-update t))
;;;###autoload
2008-04-11 15:22:15 -04:00
(defun window-numbering-clear-mode-line ()
2008-04-11 15:24:04 -04:00
"Remove the window number of `window-numbering-mode' from the mode-line."
2008-04-11 15:22:15 -04:00
(let ((mode-line (default-value 'mode-line-format))
(res))
(while mode-line
(let ((item (car mode-line)))
2008-04-11 15:24:04 -04:00
(unless (equal item '(:eval (window-numbering-get-number-string)))
2008-04-11 15:22:15 -04:00
(push item res)))
(pop mode-line))
(setq-default mode-line-format (nreverse res)))
(force-mode-line-update t))
2016-12-08 13:42:43 -05:00
;; TODO select window of unlimited input number:
;; - prefix argument
;; - read-from-minibuffer
2008-04-11 15:22:15 -04:00
(provide 'window-numbering)
;;; window-numbering.el ends here