From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Tassilo Horn Newsgroups: gmane.emacs.devel Subject: [GNU ELPA] I'd like to add switchy.el: a last-recently-used window switcher Date: Sun, 09 Apr 2023 09:36:27 +0200 Message-ID: <87cz4dpk0n.fsf@gnu.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="31171"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: mu4e 1.11.0; emacs 30.0.50 To: emacs-devel Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Apr 09 09:56:21 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 1plPuP-0007u0-8M for ged-emacs-devel@m.gmane-mx.org; Sun, 09 Apr 2023 09:56:21 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1plPtX-00039k-N5; Sun, 09 Apr 2023 03:55: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 1plPtW-00039U-BW for emacs-devel@gnu.org; Sun, 09 Apr 2023 03:55:26 -0400 Original-Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1plPtW-0001hl-38 for emacs-devel@gnu.org; Sun, 09 Apr 2023 03:55:26 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-Version:Date:Subject:To:From:in-reply-to: references; bh=PSMEX/Xg4OqOSGhifHAqVqpE9g+wgi4iei85RiYNOs4=; b=M79c/EyiAMnc63 iAeYtY4DA5dImzUJmbbXu1IGBcc+MBPXMLSOWie1tPoLmZzrPx5i2IGhNkRxv8qr/59AtU2KQ/ecx Gx3ICW47hjS7GjB1iP7zre/MusdQflsP2pj4P1h4dhEQ3+SlppDYK+e1P7YCkwUWeT1jZcsyoQTRi 9tk45dypHq9XofqLT83arG8uEObEm+ud4UkX91Y1EnTnHbSXYFi99aa/ey4I735PnP7qnAJnuU/Yq YL8AvLTthbuEfmXxX3TUfgdAPjt3QrBbVDNddMY5jQMLo+Rr8zlYL1wW3wA4dmqgJave2DWNt8s6h fDuBslwbeP+QG0e7vKyw==; Original-Received: from auth1-smtp.messagingengine.com ([66.111.4.227]) by fencepost.gnu.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1plPtV-0006Dj-6n for emacs-devel@gnu.org; Sun, 09 Apr 2023 03:55:25 -0400 Original-Received: from compute6.internal (compute6.nyi.internal [10.202.2.47]) by mailauth.nyi.internal (Postfix) with ESMTP id B99E127C0054 for ; Sun, 9 Apr 2023 03:55:24 -0400 (EDT) Original-Received: from mailfrontend2 ([10.202.2.163]) by compute6.internal (MEProxy); Sun, 09 Apr 2023 03:55:24 -0400 X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvhedrvdejledgvdduucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucenucfjughrpegfhffvufffkfggtgesmhdtreertd ertdenucfhrhhomhepvfgrshhsihhlohcujfhorhhnuceothhsughhsehgnhhurdhorhhg qeenucggtffrrghtthgvrhhnpeehffekheejheegueekhfevheffvdeghfffvdfggeffhe ekfffhueeiffdutefhudenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgr ihhlfhhrohhmpehthhhorhhnodhmvghsmhhtphgruhhthhhpvghrshhonhgrlhhithihqd ekieejfeekjeekgedqieefhedvleekqdhtshguhheppehgnhhurdhorhhgsehfrghsthhm rghilhdrfhhm X-ME-Proxy: Feedback-ID: ib2b94485:Fastmail Original-Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Sun, 9 Apr 2023 03:55:23 -0400 (EDT) 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:305195 Archived-At: --=-=-= Content-Type: text/plain 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 --=-=-= Content-Type: application/emacs-lisp Content-Disposition: inline; filename=switchy.el Content-Transfer-Encoding: quoted-printable ;;; switchy.el --- A last-recently-used window switcher -*- lexical-bindin= g: 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")) ;; SPDX-License-Identifier: GPL-3.0-or-later ;; ;; This file is NOT part of GNU Emacs. ;; ;; 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 E= macs ;; 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 lock= ed ;; in, i.e., its LRU timestamp is updated and this switching sequence is en= ded. ;; 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))) ;; 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 (setq switchy--visited-windows (cons win switchy--visited-windo= ws)) (select-window win))) ;; Start a new cycle if we're not at the start already, i.e., we visi= ted ;; just one (the current) window. (when (> (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\\=3D's what I use. (keymap-set switchy-minor-mode-map \"C-<\" #\\=3D'switchy-window) ;; Or as a substitute for `other-window'. (add-hook \\=3D'switchy-minor-mode-hook (lambda () (if switchy-minor-mode (keymap-global-set \" \" #\\=3D'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 (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) ;;; switchy.el ends here --=-=-=--