unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher
@ 2023-04-09  7:36 Tassilo Horn
  2023-04-09  8:08 ` Eli Zaretskii
  2023-04-09  8:56 ` Philip Kaludercic
  0 siblings, 2 replies; 6+ messages in thread
From: Tassilo Horn @ 2023-04-09  7:36 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 646 bytes --]

Hi all,

I've written a small utility called switchy.el which I'd like to add to
GNU ELPA.  I can do that on my own but wanted to give you a chance for
commenting first.  I'm attaching the file below.

In essence, the single command provided is `switchy-window' which is
similar to `other-window' except that it switches to other windows in
last-recently-used order instead of top-to-bottom-left-to-right order.
With quick consecutive invocations you can reach any window but if you
have a small delay between invocations (controlled by the single
defcustom `switchy-delay'), you toggle between the two last recently
used windows.

Bye,
Tassilo


[-- Attachment #2: switchy.el --]
[-- Type: application/emacs-lisp, Size: 8075 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher
  2023-04-09  7:36 [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher Tassilo Horn
@ 2023-04-09  8:08 ` Eli Zaretskii
  2023-04-09  8:56 ` Philip Kaludercic
  1 sibling, 0 replies; 6+ messages in thread
From: Eli Zaretskii @ 2023-04-09  8:08 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

> From: Tassilo Horn <tsdh@gnu.org>
> Date: Sun, 09 Apr 2023 09:36:27 +0200
> 
> I've written a small utility called switchy.el which I'd like to add to
> GNU ELPA.  I can do that on my own but wanted to give you a chance for
> commenting first.  I'm attaching the file below.
> 
> In essence, the single command provided is `switchy-window' which is
> similar to `other-window' except that it switches to other windows in
> last-recently-used order instead of top-to-bottom-left-to-right order.
> With quick consecutive invocations you can reach any window but if you
> have a small delay between invocations (controlled by the single
> defcustom `switchy-delay'), you toggle between the two last recently
> used windows.

Thanks.

Any chance of changing the name of the package to make the fact that
it switches windows more evident?  Like switchy-window.el, for
example?

Another minor nit: you say "last-recently-used order", but I believe
the correct terminology is "least-recently-used order".



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher
  2023-04-09  7:36 [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher Tassilo Horn
  2023-04-09  8:08 ` Eli Zaretskii
@ 2023-04-09  8:56 ` Philip Kaludercic
  2023-04-09  9:33   ` Tassilo Horn
  1 sibling, 1 reply; 6+ messages in thread
From: Philip Kaludercic @ 2023-04-09  8:56 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

Tassilo Horn <tsdh@gnu.org> writes:

> Hi all,
>
> I've written a small utility called switchy.el which I'd like to add to
> GNU ELPA.  I can do that on my own but wanted to give you a chance for
> commenting first.  I'm attaching the file below.
>
> In essence, the single command provided is `switchy-window' which is
> similar to `other-window' except that it switches to other windows in
> last-recently-used order instead of top-to-bottom-left-to-right order.
> With quick consecutive invocations you can reach any window but if you
> have a small delay between invocations (controlled by the single
> defcustom `switchy-delay'), you toggle between the two last recently
> used windows.
>
> Bye,
> Tassilo
>
> ;;; switchy.el --- A last-recently-used window switcher  -*- lexical-binding: t; -*-
> ;;
> ;; Copyright (C) 2023 Tassilo Horn
> ;;
> ;; Author: Tassilo Horn <tsdh@gnu.org>
> ;; Version: 1.0
> ;; Keywords: windows
> ;; Homepage: https://sr.ht/~tsdh/switchy/
> ;; Repository: https://git.sr.ht/~tsdh/switchy
> ;; Package-Requires: ((emacs "25.1") (compat "29.1.3.4"))

If you are to use compat 29.1.0.0 or newer then you also have to require
it!

> ;; SPDX-License-Identifier: GPL-3.0-or-later
> ;;
> ;; This file is NOT part of GNU Emacs.

If added to GNU ELPA, this should be change to "... is part of GNU
Emacs", right?

> ;;
> ;; 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 3
> ;; of the License, 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.
> ;;
> ;; 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:
> ;;
> ;; Switchy is a last-recently-used window switcher.  It suits my personal Emacs
> ;; layout and workflow where I usually have at most two editing windows but up
> ;; to three side-windows which I have to select only seldomly.
> ;;
> ;; The idea of switchy is simple: when you invoke `switchy-window' in quick
> ;; succession, it will switch to one window after the other in
> ;; last-recently-used order.  Once you stop switching for long enough time
> ;; (`switchy-delay', 1.5 seconds by default), the selected window gets locked
> ;; in, i.e., its LRU timestamp is updated and this switching sequence is ended.
> ;; Thusly, you can toggle between two windows simply by invoking
> ;; `switchy-window', waiting at least `switchy-delay', and then invoking
> ;; `switchy-window' again to switch back to the original window.
> ;;
> ;; Activate `switchy-minor-mode' which tracks window changes and bind
> ;; `switchy-window' to a key of your liking in `switchy-minor-mode-map' (or
> ;; globally, see the variable's docstring for examples).
> ;;
> ;; Hint: Since the order of window switching is not as obvious as it is with
> ;; `other-window', adding a bit visual feedback to window selection changes can
> ;; be helpful.  That can be done easily with the stock Emacs pulse.el, e.g.:
> ;;
> ;;  (add-hook 'window-selection-change-functions
> ;;            (lambda (frame)
> ;;              (when (eq frame (selected-frame))
> ;;                (pulse-momentary-highlight-one-line))))
>
>
> ;;; Code:
>
> (defgroup switchy nil
>   "Switchy is a last-recently-used window-switcher."
>   :group 'windows)
>
> (defvar switchy--tick-counter 0
>   "Values of this counter represent the last-recently-used order of windows.
> Only for internal use.")
>
> (defvar switchy--tick-alist nil
>   "An alist with entries (WINDOW . TICK).
> A higher TICK value means a window has more recently been visited.
> Only for internal use.")
>
> (defcustom switchy-delay 1.5
>   "Number of seconds before the current window gets locked in.
> If more time elapses between consecutive invocations of
> `switchy-window', the current window's tick (timestamp) is
> updated in `switchy--tick-alist' and the current switching cycle
> ends."
>   :type 'number)
>
> (defvar switchy--timer nil
>   "The timer locking in the current window after `switchy-delay' seconds.
> Only for internal use.")
>
> (defvar switchy--visited-windows nil
>   "The windows having already been visited in the current switching cycle.")
>
> (defun switchy--on-window-selection-change (&optional frame)
>   "Record the next `switchy--tick-counter' value for the selected window of FRAME.
> Meant to be used in `window-selection-change-functions' which is
> arranged by `switchy-minor-mode'."
>   (when (eq frame (selected-frame))
>     (when switchy--timer
>       (cancel-timer switchy--timer))
>     (setq switchy--timer (run-at-time
>                           switchy-delay nil
>                           (lambda ()
>                             (setf (alist-get (selected-window)
>                                              switchy--tick-alist)
>                                   (cl-incf switchy--tick-counter))
>                             (setq switchy--visited-windows nil))))))
>
> (defun switchy-window (&optional arg)
>   "Switch to other windows in last-recently-used order.
> If prefix ARG is given, use least-recently-used order.
>
> If the time between consecutive invocations is smaller than
> `switchy-delay' seconds, selects one after the other window in
> LRU order and cycles when all windows have been visited.  If
> `switchy-delay' has passed, the current switching cycle ends and
> the now selected window gets its tick updated (a kind of
> timestamp)."
>   (interactive)
>
>   (unless switchy-minor-mode
>     (user-error "switchy-window requires `switchy-minor-mode' being active"))
>
>   ;; Remove dead windows.
>   (setq switchy--tick-alist (seq-filter
>                              (lambda (e)
>                                (window-live-p (car e)))
>                              switchy--tick-alist))
>   ;; Add windows never selected.
>   (dolist (win (window-list (selected-frame)))
>     (unless (assq win switchy--tick-alist)
>       (setf (alist-get win switchy--tick-alist) 0)))

The setf is strictly speaking unnecessary here and causes an accidental
O(n^2) slowdown, since you traverse the list once to check if it has an
entry and then traverse it again to check if you can set 0 to an
existing entry.  You could also just push a cons-cell to the beginning.
Then again, this is all bounded by the maximal number of windows that
someone has open so it doesn't matter in practice.

>   ;; Ensure the current window is marked as visited.
>   (setq switchy--visited-windows (cons (selected-window)
>                                        switchy--visited-windows))
>
>   (let ((win-entries (seq-filter
>                       (lambda (e)
>                         (let ((win (car e)))
>                           (and (eq (window-frame win) (selected-frame))
>                                (or (minibuffer-window-active-p win)
>                                    (not (eq win (minibuffer-window
>                                                  (selected-frame)))))
>                                (not (memq win switchy--visited-windows)))))
>                       switchy--tick-alist)))
>     (if win-entries
>         (when-let ((win (car (seq-reduce (lambda (x e)
>                                            (if (and x (funcall (if arg #'< #'>)
>                                                                (cdr x) (cdr e)))
>                                                x
>                                              e))
>                                          win-entries nil))))
>
>           (progn

Why the progn if you are using when-let?

>             (setq switchy--visited-windows (cons win switchy--visited-windows))
>             (select-window win)))
>       ;; Start a new cycle if we're not at the start already, i.e., we visited
>       ;; just one (the current) window.
>       (when (> (length switchy--visited-windows) 1)

Or (length> switchy--visited-windows 1)?

>         (setq switchy--visited-windows nil)
>         (switchy-window)))))
>
> (defvar switchy-minor-mode-map (make-sparse-keymap)
>   "The mode map of `switchy-minor-mode'.
> No keys are bound by default.  Bind the main command
> `switchy-window' to a key of your liking, e.g.,
>
>   ;; That\\='s what I use.
>   (keymap-set switchy-minor-mode-map \"C-<\" #\\='switchy-window)

If you are already making use of keymap-set, you might as well define
the map itself using defvar-keymap (Compat provides it).

>   ;; Or as a substitute for `other-window'.
>   (add-hook \\='switchy-minor-mode-hook
>             (lambda ()
>               (if switchy-minor-mode
>                   (keymap-global-set \"<remap> <other-window>\"
>                                      #\\='switchy-window)
>                 (keymap-global-unset \"<remap> <other-window>\"))))")
>
> (define-minor-mode switchy-minor-mode
>   "Activates recording of window selection ticks.
> Those are the timestamps for figuring out the last-recently-used
> order of windows.
>
> The minor-mode provides the keymap `switchy-minor-mode-map',
> which see."
>   :global t
>   :keymap switchy-minor-mode-map

Isn't this the default anyway?

>   (if switchy-minor-mode
>       (add-hook 'window-selection-change-functions
>                 #'switchy--on-window-selection-change)
>     (remove-hook 'window-selection-change-functions
>                  #'switchy--on-window-selection-change)))
>
> (provide 'switchy)
>
> (provide 'switchy)

Accidentally duplicated?

> ;;; switchy.el ends here
>

-- 
Philip Kaludercic



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher
  2023-04-09  8:56 ` Philip Kaludercic
@ 2023-04-09  9:33   ` Tassilo Horn
  2023-04-09 10:30     ` Philip Kaludercic
  2023-04-09 11:14     ` Tassilo Horn
  0 siblings, 2 replies; 6+ messages in thread
From: Tassilo Horn @ 2023-04-09  9:33 UTC (permalink / raw)
  To: Philip Kaludercic; +Cc: emacs-devel

Thanks Philip,

I'll address your comments before adding the package.

Bye,
Tassilo

09.04.2023 10:56:28 Philip Kaludercic <philipk@posteo.net>:

> Tassilo Horn <tsdh@gnu.org> writes:
> 
>> Hi all,
>> 
>> I've written a small utility called switchy.el which I'd like to add to
>> GNU ELPA.  I can do that on my own but wanted to give you a chance for
>> commenting first.  I'm attaching the file below.
>> 
>> In essence, the single command provided is `switchy-window' which is
>> similar to `other-window' except that it switches to other windows in
>> last-recently-used order instead of top-to-bottom-left-to-right order.
>> With quick consecutive invocations you can reach any window but if you
>> have a small delay between invocations (controlled by the single
>> defcustom `switchy-delay'), you toggle between the two last recently
>> used windows.
>> 
>> Bye,
>> Tassilo
>> 
>> ;;; switchy.el --- A last-recently-used window switcher  -*- lexical-binding: t; -*-
>> ;;
>> ;; Copyright (C) 2023 Tassilo Horn
>> ;;
>> ;; Author: Tassilo Horn <tsdh@gnu.org>
>> ;; Version: 1.0
>> ;; Keywords: windows
>> ;; Homepage: https://sr.ht/~tsdh/switchy/
>> ;; Repository: https://git.sr.ht/~tsdh/switchy
>> ;; Package-Requires: ((emacs "25.1") (compat "29.1.3.4"))
> 
> If you are to use compat 29.1.0.0 or newer then you also have to require
> it!
> 
>> ;; SPDX-License-Identifier: GPL-3.0-or-later
>> ;;
>> ;; This file is NOT part of GNU Emacs.
> 
> If added to GNU ELPA, this should be change to "... is part of GNU
> Emacs", right?
> 
>> ;;
>> ;; 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 3
>> ;; of the License, 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.
>> ;;
>> ;; 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:
>> ;;
>> ;; Switchy is a last-recently-used window switcher.  It suits my personal Emacs
>> ;; layout and workflow where I usually have at most two editing windows but up
>> ;; to three side-windows which I have to select only seldomly.
>> ;;
>> ;; The idea of switchy is simple: when you invoke `switchy-window' in quick
>> ;; succession, it will switch to one window after the other in
>> ;; last-recently-used order.  Once you stop switching for long enough time
>> ;; (`switchy-delay', 1.5 seconds by default), the selected window gets locked
>> ;; in, i.e., its LRU timestamp is updated and this switching sequence is ended.
>> ;; Thusly, you can toggle between two windows simply by invoking
>> ;; `switchy-window', waiting at least `switchy-delay', and then invoking
>> ;; `switchy-window' again to switch back to the original window.
>> ;;
>> ;; Activate `switchy-minor-mode' which tracks window changes and bind
>> ;; `switchy-window' to a key of your liking in `switchy-minor-mode-map' (or
>> ;; globally, see the variable's docstring for examples).
>> ;;
>> ;; Hint: Since the order of window switching is not as obvious as it is with
>> ;; `other-window', adding a bit visual feedback to window selection changes can
>> ;; be helpful.  That can be done easily with the stock Emacs pulse.el, e.g.:
>> ;;
>> ;;  (add-hook 'window-selection-change-functions
>> ;;            (lambda (frame)
>> ;;              (when (eq frame (selected-frame))
>> ;;                (pulse-momentary-highlight-one-line))))
>> 
>> 
>> ;;; Code:
>> 
>> (defgroup switchy nil
>>   "Switchy is a last-recently-used window-switcher."
>>   :group 'windows)
>> 
>> (defvar switchy--tick-counter 0
>>   "Values of this counter represent the last-recently-used order of windows.
>> Only for internal use.")
>> 
>> (defvar switchy--tick-alist nil
>>   "An alist with entries (WINDOW . TICK).
>> A higher TICK value means a window has more recently been visited.
>> Only for internal use.")
>> 
>> (defcustom switchy-delay 1.5
>>   "Number of seconds before the current window gets locked in.
>> If more time elapses between consecutive invocations of
>> `switchy-window', the current window's tick (timestamp) is
>> updated in `switchy--tick-alist' and the current switching cycle
>> ends."
>>   :type 'number)
>> 
>> (defvar switchy--timer nil
>>   "The timer locking in the current window after `switchy-delay' seconds.
>> Only for internal use.")
>> 
>> (defvar switchy--visited-windows nil
>>   "The windows having already been visited in the current switching cycle.")
>> 
>> (defun switchy--on-window-selection-change (&optional frame)
>>   "Record the next `switchy--tick-counter' value for the selected window of FRAME.
>> Meant to be used in `window-selection-change-functions' which is
>> arranged by `switchy-minor-mode'."
>>   (when (eq frame (selected-frame))
>>     (when switchy--timer
>>       (cancel-timer switchy--timer))
>>     (setq switchy--timer (run-at-time
>>                           switchy-delay nil
>>                           (lambda ()
>>                             (setf (alist-get (selected-window)
>>                                              switchy--tick-alist)
>>                                   (cl-incf switchy--tick-counter))
>>                             (setq switchy--visited-windows nil))))))
>> 
>> (defun switchy-window (&optional arg)
>>   "Switch to other windows in last-recently-used order.
>> If prefix ARG is given, use least-recently-used order.
>> 
>> If the time between consecutive invocations is smaller than
>> `switchy-delay' seconds, selects one after the other window in
>> LRU order and cycles when all windows have been visited.  If
>> `switchy-delay' has passed, the current switching cycle ends and
>> the now selected window gets its tick updated (a kind of
>> timestamp)."
>>   (interactive)
>> 
>>   (unless switchy-minor-mode
>>     (user-error "switchy-window requires `switchy-minor-mode' being active"))
>> 
>>   ;; Remove dead windows.
>>   (setq switchy--tick-alist (seq-filter
>>                              (lambda (e)
>>                                (window-live-p (car e)))
>>                              switchy--tick-alist))
>>   ;; Add windows never selected.
>>   (dolist (win (window-list (selected-frame)))
>>     (unless (assq win switchy--tick-alist)
>>       (setf (alist-get win switchy--tick-alist) 0)))
> 
> The setf is strictly speaking unnecessary here and causes an accidental
> O(n^2) slowdown, since you traverse the list once to check if it has an
> entry and then traverse it again to check if you can set 0 to an
> existing entry.  You could also just push a cons-cell to the beginning.
> Then again, this is all bounded by the maximal number of windows that
> someone has open so it doesn't matter in practice.
> 
>>   ;; Ensure the current window is marked as visited.
>>   (setq switchy--visited-windows (cons (selected-window)
>>                                        switchy--visited-windows))
>> 
>>   (let ((win-entries (seq-filter
>>                       (lambda (e)
>>                         (let ((win (car e)))
>>                           (and (eq (window-frame win) (selected-frame))
>>                                (or (minibuffer-window-active-p win)
>>                                    (not (eq win (minibuffer-window
>>                                                  (selected-frame)))))
>>                                (not (memq win switchy--visited-windows)))))
>>                       switchy--tick-alist)))
>>     (if win-entries
>>         (when-let ((win (car (seq-reduce (lambda (x e)
>>                                            (if (and x (funcall (if arg #'< #'>)
>>                                                                (cdr x) (cdr e)))
>>                                                x
>>                                              e))
>>                                          win-entries nil))))
>> 
>>           (progn
> 
> Why the progn if you are using when-let?
> 
>>             (setq switchy--visited-windows (cons win switchy--visited-windows))
>>             (select-window win)))
>>       ;; Start a new cycle if we're not at the start already, i.e., we visited
>>       ;; just one (the current) window.
>>       (when (> (length switchy--visited-windows) 1)
> 
> Or (length> switchy--visited-windows 1)?
> 
>>         (setq switchy--visited-windows nil)
>>         (switchy-window)))))
>> 
>> (defvar switchy-minor-mode-map (make-sparse-keymap)
>>   "The mode map of `switchy-minor-mode'.
>> No keys are bound by default.  Bind the main command
>> `switchy-window' to a key of your liking, e.g.,
>> 
>>   ;; That\\='s what I use.
>>   (keymap-set switchy-minor-mode-map \"C-<\" #\\='switchy-window)
> 
> If you are already making use of keymap-set, you might as well define
> the map itself using defvar-keymap (Compat provides it).
> 
>>   ;; Or as a substitute for `other-window'.
>>   (add-hook \\='switchy-minor-mode-hook
>>             (lambda ()
>>               (if switchy-minor-mode
>>                   (keymap-global-set \"<remap> <other-window>\"
>>                                      #\\='switchy-window)
>>                 (keymap-global-unset \"<remap> <other-window>\"))))")
>> 
>> (define-minor-mode switchy-minor-mode
>>   "Activates recording of window selection ticks.
>> Those are the timestamps for figuring out the last-recently-used
>> order of windows.
>> 
>> The minor-mode provides the keymap `switchy-minor-mode-map',
>> which see."
>>   :global t
>>   :keymap switchy-minor-mode-map
> 
> Isn't this the default anyway?
> 
>>   (if switchy-minor-mode
>>       (add-hook 'window-selection-change-functions
>>                 #'switchy--on-window-selection-change)
>>     (remove-hook 'window-selection-change-functions
>>                  #'switchy--on-window-selection-change)))
>> 
>> (provide 'switchy)
>> 
>> (provide 'switchy)
> 
> Accidentally duplicated?
> 
>> ;;; switchy.el ends here
>> 
> 



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher
  2023-04-09  9:33   ` Tassilo Horn
@ 2023-04-09 10:30     ` Philip Kaludercic
  2023-04-09 11:14     ` Tassilo Horn
  1 sibling, 0 replies; 6+ messages in thread
From: Philip Kaludercic @ 2023-04-09 10:30 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

Tassilo Horn <tsdh@gnu.org> writes:

> Thanks Philip,
>
> I'll address your comments before adding the package.

Great, looking forward to seeing the package on ELPA :)

> Bye,
> Tassilo
>
> 09.04.2023 10:56:28 Philip Kaludercic <philipk@posteo.net>:
>
>> Tassilo Horn <tsdh@gnu.org> writes:
>> 
>>> Hi all,
>>> 
>>> I've written a small utility called switchy.el which I'd like to add to
>>> GNU ELPA.  I can do that on my own but wanted to give you a chance for
>>> commenting first.  I'm attaching the file below.
>>> 
>>> In essence, the single command provided is `switchy-window' which is
>>> similar to `other-window' except that it switches to other windows in
>>> last-recently-used order instead of top-to-bottom-left-to-right order.
>>> With quick consecutive invocations you can reach any window but if you
>>> have a small delay between invocations (controlled by the single
>>> defcustom `switchy-delay'), you toggle between the two last recently
>>> used windows.
>>> 
>>> Bye,
>>> Tassilo
>>> 
>>> ;;; switchy.el --- A last-recently-used window switcher  -*- lexical-binding: t; -*-
>>> ;;
>>> ;; Copyright (C) 2023 Tassilo Horn
>>> ;;
>>> ;; Author: Tassilo Horn <tsdh@gnu.org>
>>> ;; Version: 1.0
>>> ;; Keywords: windows
>>> ;; Homepage: https://sr.ht/~tsdh/switchy/
>>> ;; Repository: https://git.sr.ht/~tsdh/switchy
>>> ;; Package-Requires: ((emacs "25.1") (compat "29.1.3.4"))
>> 
>> If you are to use compat 29.1.0.0 or newer then you also have to require
>> it!
>> 
>>> ;; SPDX-License-Identifier: GPL-3.0-or-later
>>> ;;
>>> ;; This file is NOT part of GNU Emacs.
>> 
>> If added to GNU ELPA, this should be change to "... is part of GNU
>> Emacs", right?
>> 
>>> ;;
>>> ;; 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 3
>>> ;; of the License, 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.
>>> ;;
>>> ;; 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:
>>> ;;
>>> ;; Switchy is a last-recently-used window switcher.  It suits my personal Emacs
>>> ;; layout and workflow where I usually have at most two editing windows but up
>>> ;; to three side-windows which I have to select only seldomly.
>>> ;;
>>> ;; The idea of switchy is simple: when you invoke `switchy-window' in quick
>>> ;; succession, it will switch to one window after the other in
>>> ;; last-recently-used order.  Once you stop switching for long enough time
>>> ;; (`switchy-delay', 1.5 seconds by default), the selected window gets locked
>>> ;; in, i.e., its LRU timestamp is updated and this switching sequence is ended.
>>> ;; Thusly, you can toggle between two windows simply by invoking
>>> ;; `switchy-window', waiting at least `switchy-delay', and then invoking
>>> ;; `switchy-window' again to switch back to the original window.
>>> ;;
>>> ;; Activate `switchy-minor-mode' which tracks window changes and bind
>>> ;; `switchy-window' to a key of your liking in `switchy-minor-mode-map' (or
>>> ;; globally, see the variable's docstring for examples).
>>> ;;
>>> ;; Hint: Since the order of window switching is not as obvious as it is with
>>> ;; `other-window', adding a bit visual feedback to window selection changes can
>>> ;; be helpful.  That can be done easily with the stock Emacs pulse.el, e.g.:
>>> ;;
>>> ;;  (add-hook 'window-selection-change-functions
>>> ;;            (lambda (frame)
>>> ;;              (when (eq frame (selected-frame))
>>> ;;                (pulse-momentary-highlight-one-line))))
>>> 
>>> 
>>> ;;; Code:
>>> 
>>> (defgroup switchy nil
>>>   "Switchy is a last-recently-used window-switcher."
>>>   :group 'windows)
>>> 
>>> (defvar switchy--tick-counter 0
>>>   "Values of this counter represent the last-recently-used order of windows.
>>> Only for internal use.")
>>> 
>>> (defvar switchy--tick-alist nil
>>>   "An alist with entries (WINDOW . TICK).
>>> A higher TICK value means a window has more recently been visited.
>>> Only for internal use.")
>>> 
>>> (defcustom switchy-delay 1.5
>>>   "Number of seconds before the current window gets locked in.
>>> If more time elapses between consecutive invocations of
>>> `switchy-window', the current window's tick (timestamp) is
>>> updated in `switchy--tick-alist' and the current switching cycle
>>> ends."
>>>   :type 'number)
>>> 
>>> (defvar switchy--timer nil
>>>   "The timer locking in the current window after `switchy-delay' seconds.
>>> Only for internal use.")
>>> 
>>> (defvar switchy--visited-windows nil
>>>   "The windows having already been visited in the current switching cycle.")
>>> 
>>> (defun switchy--on-window-selection-change (&optional frame)
>>>   "Record the next `switchy--tick-counter' value for the selected window of FRAME.
>>> Meant to be used in `window-selection-change-functions' which is
>>> arranged by `switchy-minor-mode'."
>>>   (when (eq frame (selected-frame))
>>>     (when switchy--timer
>>>       (cancel-timer switchy--timer))
>>>     (setq switchy--timer (run-at-time
>>>                           switchy-delay nil
>>>                           (lambda ()
>>>                             (setf (alist-get (selected-window)
>>>                                              switchy--tick-alist)
>>>                                   (cl-incf switchy--tick-counter))
>>>                             (setq switchy--visited-windows nil))))))
>>> 
>>> (defun switchy-window (&optional arg)
>>>   "Switch to other windows in last-recently-used order.
>>> If prefix ARG is given, use least-recently-used order.
>>> 
>>> If the time between consecutive invocations is smaller than
>>> `switchy-delay' seconds, selects one after the other window in
>>> LRU order and cycles when all windows have been visited.  If
>>> `switchy-delay' has passed, the current switching cycle ends and
>>> the now selected window gets its tick updated (a kind of
>>> timestamp)."
>>>   (interactive)
>>> 
>>>   (unless switchy-minor-mode
>>>     (user-error "switchy-window requires `switchy-minor-mode' being active"))
>>> 
>>>   ;; Remove dead windows.
>>>   (setq switchy--tick-alist (seq-filter
>>>                              (lambda (e)
>>>                                (window-live-p (car e)))
>>>                              switchy--tick-alist))
>>>   ;; Add windows never selected.
>>>   (dolist (win (window-list (selected-frame)))
>>>     (unless (assq win switchy--tick-alist)
>>>       (setf (alist-get win switchy--tick-alist) 0)))
>> 
>> The setf is strictly speaking unnecessary here and causes an accidental
>> O(n^2) slowdown, since you traverse the list once to check if it has an
>> entry and then traverse it again to check if you can set 0 to an
>> existing entry.  You could also just push a cons-cell to the beginning.
>> Then again, this is all bounded by the maximal number of windows that
>> someone has open so it doesn't matter in practice.
>> 
>>>   ;; Ensure the current window is marked as visited.
>>>   (setq switchy--visited-windows (cons (selected-window)
>>>                                        switchy--visited-windows))
>>> 
>>>   (let ((win-entries (seq-filter
>>>                       (lambda (e)
>>>                         (let ((win (car e)))
>>>                           (and (eq (window-frame win) (selected-frame))
>>>                                (or (minibuffer-window-active-p win)
>>>                                    (not (eq win (minibuffer-window
>>>                                                  (selected-frame)))))
>>>                                (not (memq win switchy--visited-windows)))))
>>>                       switchy--tick-alist)))
>>>     (if win-entries
>>>         (when-let ((win (car (seq-reduce (lambda (x e)
>>>                                            (if (and x (funcall (if arg #'< #'>)
>>>                                                                (cdr x) (cdr e)))
>>>                                                x
>>>                                              e))
>>>                                          win-entries nil))))
>>> 
>>>           (progn
>> 
>> Why the progn if you are using when-let?
>> 
>>>             (setq switchy--visited-windows (cons win switchy--visited-windows))
>>>             (select-window win)))
>>>       ;; Start a new cycle if we're not at the start already, i.e., we visited
>>>       ;; just one (the current) window.
>>>       (when (> (length switchy--visited-windows) 1)
>> 
>> Or (length> switchy--visited-windows 1)?
>> 
>>>         (setq switchy--visited-windows nil)
>>>         (switchy-window)))))
>>> 
>>> (defvar switchy-minor-mode-map (make-sparse-keymap)
>>>   "The mode map of `switchy-minor-mode'.
>>> No keys are bound by default.  Bind the main command
>>> `switchy-window' to a key of your liking, e.g.,
>>> 
>>>   ;; That\\='s what I use.
>>>   (keymap-set switchy-minor-mode-map \"C-<\" #\\='switchy-window)
>> 
>> If you are already making use of keymap-set, you might as well define
>> the map itself using defvar-keymap (Compat provides it).
>> 
>>>   ;; Or as a substitute for `other-window'.
>>>   (add-hook \\='switchy-minor-mode-hook
>>>             (lambda ()
>>>               (if switchy-minor-mode
>>>                   (keymap-global-set \"<remap> <other-window>\"
>>>                                      #\\='switchy-window)
>>>                 (keymap-global-unset \"<remap> <other-window>\"))))")
>>> 
>>> (define-minor-mode switchy-minor-mode
>>>   "Activates recording of window selection ticks.
>>> Those are the timestamps for figuring out the last-recently-used
>>> order of windows.
>>> 
>>> The minor-mode provides the keymap `switchy-minor-mode-map',
>>> which see."
>>>   :global t
>>>   :keymap switchy-minor-mode-map
>> 
>> Isn't this the default anyway?
>> 
>>>   (if switchy-minor-mode
>>>       (add-hook 'window-selection-change-functions
>>>                 #'switchy--on-window-selection-change)
>>>     (remove-hook 'window-selection-change-functions
>>>                  #'switchy--on-window-selection-change)))
>>> 
>>> (provide 'switchy)
>>> 
>>> (provide 'switchy)
>> 
>> Accidentally duplicated?
>> 
>>> ;;; switchy.el ends here
>>> 
>> 

-- 
Philip Kaludercic



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher
  2023-04-09  9:33   ` Tassilo Horn
  2023-04-09 10:30     ` Philip Kaludercic
@ 2023-04-09 11:14     ` Tassilo Horn
  1 sibling, 0 replies; 6+ messages in thread
From: Tassilo Horn @ 2023-04-09 11:14 UTC (permalink / raw)
  Cc: Philip Kaludercic, emacs-devel

Tassilo Horn <tsdh@gnu.org> writes:

Hi Philip,

I've added the package to ELPA as switchy-window as suggested by Eli.

>>> ;; Package-Requires: ((emacs "25.1") (compat "29.1.3.4"))
>> 
>> If you are to use compat 29.1.0.0 or newer then you also have to
>> require it!

Ah, thanks.  I'm new to the compat game but it's a relief not to worry
about compatibility. :-)

>>> ;; SPDX-License-Identifier: GPL-3.0-or-later
>>> ;;
>>> ;; This file is NOT part of GNU Emacs.
>> 
>> If added to GNU ELPA, this should be change to "... is part of GNU
>> Emacs", right?

Yes, I've changed that now.

>>>   ;; Remove dead windows.
>>>   (setq switchy--tick-alist (seq-filter
>>>                              (lambda (e)
>>>                                (window-live-p (car e)))
>>>                              switchy--tick-alist))
>>>   ;; Add windows never selected.
>>>   (dolist (win (window-list (selected-frame)))
>>>     (unless (assq win switchy--tick-alist)
>>>       (setf (alist-get win switchy--tick-alist) 0)))
>> 
>> The setf is strictly speaking unnecessary here and causes an
>> accidental O(n^2) slowdown, since you traverse the list once to check
>> if it has an entry and then traverse it again to check if you can set
>> 0 to an existing entry.  You could also just push a cons-cell to the
>> beginning.  Then again, this is all bounded by the maximal number of
>> windows that someone has open so it doesn't matter in practice.

Yup, I've kept that as-is because the theoretical slowdown is not
practical and it's easier to debug when the alist ist free of
duplicates.

>>>   (let ((win-entries (seq-filter
>>>                       (lambda (e)
>>>                         (let ((win (car e)))
>>>                           (and (eq (window-frame win) (selected-frame))
>>>                                (or (minibuffer-window-active-p win)
>>>                                    (not (eq win (minibuffer-window
>>>                                                  (selected-frame)))))
>>>                                (not (memq win switchy--visited-windows)))))
>>>                       switchy--tick-alist)))
>>>     (if win-entries
>>>         (when-let ((win (car (seq-reduce (lambda (x e)
>>>                                            (if (and x (funcall (if arg #'<
>>> #'>)
>>>                                                                (cdr x) (cdr
>>> e)))
>>>                                                x
>>>                                              e))
>>>                                          win-entries nil))))
>>> 
>>>           (progn
>> 
>> Why the progn if you are using when-let?

A left-over of an earlier version.

>>>       ;; Start a new cycle if we're not at the start already, i.e., we
>>> visited
>>>       ;; just one (the current) window.
>>>       (when (> (length switchy--visited-windows) 1)
>> 
>> Or (length> switchy--visited-windows 1)?

Oh, I didn't know that.

>>> (defvar switchy-minor-mode-map (make-sparse-keymap)
>>>   "The mode map of `switchy-minor-mode'.
>>> No keys are bound by default.  Bind the main command
>>> `switchy-window' to a key of your liking, e.g.,
>>> 
>>>   ;; That\\='s what I use.
>>>   (keymap-set switchy-minor-mode-map \"C-<\" #\\='switchy-window)
>> 
>> If you are already making use of keymap-set, you might as well define
>> the map itself using defvar-keymap (Compat provides it).

Yes, thanks!

>>> (define-minor-mode switchy-minor-mode
>>>   "Activates recording of window selection ticks.
>>> Those are the timestamps for figuring out the last-recently-used
>>> order of windows.
>>> 
>>> The minor-mode provides the keymap `switchy-minor-mode-map',
>>> which see."
>>>   :global t
>>>   :keymap switchy-minor-mode-map
>> 
>> Isn't this the default anyway?

Yes, it is.  Removed.

>>> (provide 'switchy)
>>> 
>>> (provide 'switchy)
>> 
>> Accidentally duplicated?

Just to be on the safe side it is really provided! ;-)

Thanks,
Tassilo

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2023-04-09 11:14 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-04-09  7:36 [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher Tassilo Horn
2023-04-09  8:08 ` Eli Zaretskii
2023-04-09  8:56 ` Philip Kaludercic
2023-04-09  9:33   ` Tassilo Horn
2023-04-09 10:30     ` Philip Kaludercic
2023-04-09 11:14     ` Tassilo Horn

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).