From 7b0a70b4499f38eb291d84a3ac03ff7884aca829 Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Sat, 18 Feb 2023 16:46:47 +0300 Subject: [PATCH] feat: add cycling for all buffers --- perspective-exwm.el | 118 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 19 deletions(-) diff --git a/perspective-exwm.el b/perspective-exwm.el index eb18ce6..4f86a0a 100644 --- a/perspective-exwm.el +++ b/perspective-exwm.el @@ -36,6 +36,8 @@ ;; Other useful functions are: ;; - `perspective-exwm-cycle-exwm-buffers-backward' and ;; `perspective-exwm-cycle-exwm-buffers-backward' +;; - `perspective-exwm-cycle-all-buffers-backward' and +;; `perspective-exwm-cycle-all-buffers-forward' ;; - `perspective-exwm-switch-perspective' ;; - `perspective-exwm-copy-to-workspace' and ;; `perspective-exwm-move-to-workspace' @@ -62,8 +64,8 @@ "A function to return the current EXWM window title." exwm-title) -(defcustom perspective-exwm-get-exwm-buffer-name #'perspective-exwm--get-class - "A function to get the EXWM buffer title. +(defcustom perspective-exwm-get-buffer-name #'perspective-exwm--get-class + "Retrieve buffer name for the cycle commands. Meant to be ran in the context of the target buffer, e.g. with `with-current-buffer'. @@ -75,6 +77,12 @@ The two default options are: :type 'function :options '(perspective-exwm--get-class perspective-exwm--get-title)) +(defcustom perspective-exwm-cycle-max-message-length + (- (frame-width) 10) + "Maximum length of the message displayed by the cycle commands." + :group 'perspective-exwm + :type 'integer) + (defcustom perspective-exwm-override-initial-name nil "Set initial perspective name for a particular EXWM workspace." :group 'perspective-exwm @@ -105,13 +113,84 @@ Used in `perspective-exwm-switch-perspective'." Used in `perspective-exwm-switch-perspective'." :group 'perspective-exwm) -(defun perspective-exwm--cycle-exwm-buffers (dir) - "Cycle EXWM buffers in the current perspective. +(defun perspective-exwm--cycle-get-message (all-buffers cycle-buffers) + "Return the display message for the buffer cycling commands. + +ALL-BUFFERS is the list of all buffers in the current perspective. +CYCLE-BUFFERS are the buffers to cycle through." + ;; Iterate over all buffers + (cl-loop with seen-current = nil + for buf in all-buffers + for name = (with-current-buffer buf + (or (funcall perspective-exwm-get-buffer-name) + (buffer-name))) + for is-current = (eq (current-buffer) buf) + for is-skip = (not (member buf cycle-buffers)) + if is-current do (setq seen-current t) + if is-current + collect (concat "[" (propertize name 'face 'perspective-exwm-cycle-current-face) "] ") into current-list + else if is-skip + collect (concat "[" (propertize name 'face 'perspective-exwm-cycle-skip-face) "] ") into skip-list + else if seen-current + collect (format " %s " name) into after-list + else + collect (format " %s " name) into before-list + ;; 4 list: + ;; - current-list - current buffers + ;; - skip-list - buffers displayed in other windows + ;; - before-list - buffers before current + ;; - after-list - buffers after current + ;; We want to display them in the following order: + ;; skip-list before-list current-list after-list + ;; And trim before-list and after-list to fit the message + ;; length; that means trimming the end of before-list and + ;; the beginning of after-list. + finally return + (let* ((skip-msg (mapconcat #'identity skip-list "")) + (current-msg (mapconcat #'identity current-list "")) + (len (+ (length skip-msg) (length current-msg) 8)) + (before-stack (reverse before-list)) + (after-stack after-list)) + ;; Length of nil is 0 :'( + (cl-loop for before-elem-len = (if before-stack (length (car before-stack)) 10000) + for after-elem-len = (if after-stack (length (car after-stack)) 10000) + while (and (or before-stack after-stack) + (< (+ len (min before-elem-len after-elem-len)) perspective-exwm-cycle-max-message-length)) + for before = (when (and before-stack + (< (+ len before-elem-len) perspective-exwm-cycle-max-message-length)) + (pop before-stack)) + if before collect before into before-msg-list + if before do (setq len (+ len before-elem-len)) + for after = (when (and after-stack + (< (+ len after-elem-len) perspective-exwm-cycle-max-message-length)) + (pop after-stack)) + if after concat after into after-msg + if after do (setq len (+ len after-elem-len)) + finally return + (concat + skip-msg + (when before-stack + (format " (%s) " + (propertize (number-to-string (length before-stack)) + 'face 'perspective-exwm-cycle-skip-face))) + (mapconcat #'identity (reverse before-msg-list) "") + current-msg + after-msg + (when after-stack + (format " (%s) " + (propertize (number-to-string (length after-stack)) + 'face 'perspective-exwm-cycle-skip-face)))))))) + +(defun perspective-exwm--cycle-exwm-buffers (dir &optional all) + "Cycle buffers in the current perspective. DIR is either 'forward or 'backward. A buffer is skipped if it is already displayed in some other window of the current perspective. The buffer name comes from -`perspective-exwm-get-exwm-buffer-name'. +`perspective-exwm-get-buffer-name'. + +If ALL is nil, then cycle only EXWM buffers. Otherwise, cycle +all. The function prints out the state to the messages. The current buffer after the switch is highlighted with `warning', skipped @@ -122,7 +201,7 @@ buffer is highlighted with `persp-selected-face'" (cl-loop for buf in (persp-current-buffers) for is-another = (and (get-buffer-window buf) (not (eq current buf))) if (and (buffer-live-p buf) - (eq 'exwm-mode (buffer-local-value 'major-mode buf)) + (or all (eq 'exwm-mode (buffer-local-value 'major-mode buf))) (not (string-match-p ignore-rx (buffer-name buf)))) collect buf into all-buffers and if (not is-another) collect buf into cycle-buffers @@ -137,19 +216,8 @@ buffer is highlighted with `persp-selected-face'" (length cycle-buffers))) (next-buffer (nth next-pos cycle-buffers))) (switch-to-buffer next-buffer) - (message - "%s" - (cl-loop for buf in all-buffers - for name = (with-current-buffer buf (funcall perspective-exwm-get-exwm-buffer-name)) - for is-current = (eq (current-buffer) buf) - for is-skip = (not (member buf cycle-buffers)) - if is-current - concat (concat "[" (propertize name 'face 'perspective-exwm-cycle-current-face) "] ") into res - else if is-skip - concat (concat "[" (propertize name 'face 'perspective-exwm-cycle-skip-face) "] ") into res - else - concat (format " %s " name) into res - finally return res)))))) + (let ((msg (perspective-exwm--cycle-get-message all-buffers cycle-buffers))) + (message msg)))))) ;;;###autoload (defun perspective-exwm-cycle-exwm-buffers-forward () @@ -169,6 +237,18 @@ detail." (interactive) (perspective-exwm--cycle-exwm-buffers 'backward)) +;;;###autoload +(defun perspective-exwm-cycle-all-buffers-forward () + "Cycle all buffers in the current perspective forward." + (interactive) + (perspective-exwm--cycle-exwm-buffers 'forward t)) + +;;;###autoload +(defun perspective-exwm-cycle-all-buffers-backward () + "Cycle all buffers in the current perspective backward." + (interactive) + (perspective-exwm--cycle-exwm-buffers 'backward t)) + ;;;###autoload (defun perspective-exwm-switch-perspective () "Switch to a perspective on any workspace."