From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Philip Kaludercic Newsgroups: gmane.emacs.devel Subject: Re: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher Date: Sun, 09 Apr 2023 08:56:16 +0000 Message-ID: <87r0stqvrj.fsf@posteo.net> References: <87cz4dpk0n.fsf@gnu.org> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="4746"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel To: Tassilo Horn Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Apr 09 10:57:20 2023 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1plQrP-00013Q-KV for ged-emacs-devel@m.gmane-mx.org; Sun, 09 Apr 2023 10:57:19 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1plQqZ-0004Gl-0u; Sun, 09 Apr 2023 04:56:27 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1plQqV-0004GO-Je for emacs-devel@gnu.org; Sun, 09 Apr 2023 04:56:23 -0400 Original-Received: from mout02.posteo.de ([185.67.36.66]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1plQqT-0005M6-6b for emacs-devel@gnu.org; Sun, 09 Apr 2023 04:56:23 -0400 Original-Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id D08DC2401B4 for ; Sun, 9 Apr 2023 10:56:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1681030577; bh=WY4d0zxrf1ccegLnlhbqhUg3UUpLZKNTvRn2wqtEjyY=; h=From:To:Cc:Subject:Autocrypt:Date:From; b=qBIRF4ctLusT95XUniVtG4qtNyOSU+ZMQQTFqw8Z/gTBlAWmLeA48odWDNQhwoIfG 9CFTJ2VBNsQ41YBNDi7q2T8djpOQ5K3HX/tHgAjQG8/yys23BQdhOGTEq+tIB2NnaL mrp7ESE0Pn8l4LOIKn7f3QNYYFexThfSj2CHY4qDLWUPfkXCZx3ORHKmUeZdaZvUeg OgI0ZLh2O4Dj7XaEZc3fGgiIf1tIsSxnPsblbp87vGR5/dFSKnx/xg0p33eoGJF1tY btBfv4kTUe6Tr8iLBOeTqv8SX0y+e9UTLJf3WytSyFQVIHGclra6Bm4bK6Y7bnypMI VPuLtleNXvhDw== Original-Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4PvQv52YJDz9rxD; Sun, 9 Apr 2023 10:56:17 +0200 (CEST) In-Reply-To: <87cz4dpk0n.fsf@gnu.org> (Tassilo Horn's message of "Sun, 09 Apr 2023 09:36:27 +0200") Autocrypt: addr=philipk@posteo.net; keydata= mDMEZBBQQhYJKwYBBAHaRw8BAQdAHJuofBrfqFh12uQu0Yi7mrl525F28eTmwUDflFNmdui0QlBo aWxpcCBLYWx1ZGVyY2ljIChnZW5lcmF0ZWQgYnkgYXV0b2NyeXB0LmVsKSA8cGhpbGlwa0Bwb3N0 ZW8ubmV0PoiWBBMWCAA+FiEEDg7HY17ghYlni8XN8xYDWXahwukFAmQQUEICGwMFCQHhM4AFCwkI BwIGFQoJCAsCBBYCAwECHgECF4AACgkQ8xYDWXahwulikAEA77hloUiSrXgFkUVJhlKBpLCHUjA0 mWZ9j9w5d08+jVwBAK6c4iGP7j+/PhbkxaEKa4V3MzIl7zJkcNNjHCXmvFcEuDgEZBBQQhIKKwYB BAGXVQEFAQEHQI5NLiLRjZy3OfSt1dhCmFyn+fN/QKELUYQetiaoe+MMAwEIB4h+BBgWCAAmFiEE Dg7HY17ghYlni8XN8xYDWXahwukFAmQQUEICGwwFCQHhM4AACgkQ8xYDWXahwukm+wEA8cml4JpK NeAu65rg+auKrPOP6TP/4YWRCTIvuYDm0joBALw98AMz7/qMHvSCeU/hw9PL6u6R2EScxtpKnWof z4oM Received-SPF: pass client-ip=185.67.36.66; envelope-from=philipk@posteo.net; helo=mout02.posteo.de X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:305197 Archived-At: Tassilo Horn 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 > ;; 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 . > ;; > ;;; 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 \" \" > #\\='switchy-window) > (keymap-global-unset \" \"))))") > > (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