From 23d850c256b71af5bb62ecbbe197a7c93b974309 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 4 Sep 2018 17:32:42 +0200 Subject: [PATCH 01/30] Formalize docstrings Add missing docstrings and fix typos/meanings. --- resize-window.el | 52 +++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/resize-window.el b/resize-window.el index 20253f1..2bd3dbc 100644 --- a/resize-window.el +++ b/resize-window.el @@ -61,7 +61,7 @@ (require 'cl-lib) (defgroup resize-window nil - "Quickly resize current window" + "Quickly resize windows." :group 'convenience :prefix "rw-") @@ -74,7 +74,7 @@ :type 'integer) (defcustom resize-window-allow-backgrounds t - "Allow resize mode to set a background. + "Allow resize mode to set backgrounds. This is also valuable to see that you are in resize mode." :type 'boolean) @@ -128,9 +128,9 @@ should return the fine adjustment (default 1)." (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) (?y resize-window--restore-windows " (when state) Restore window configuration" nil) (?? resize-window--display-menu " Resize - display menu" nil)) - "List of actions for `resize-window-dispatch-default. + "List of resize mode bindings. Main data structure of the dispatcher with the form: -\(char function documentation match-capitals\)") +\(key function documentation allows-capitals\)") (defvar resize-window-alias-list '((right ?f) @@ -141,16 +141,16 @@ Main data structure of the dispatcher with the form: Rather than have to use n, etc, you can alias keys for others.") (defun resize-window--notify (&rest info) - "Notify with INFO, a string. + "Notify with INFO, a string or list (format-string object...). This is just a pass through to message usually. However, it can be overridden in tests to test the output of message." (when resize-window-notify-with-messages (apply #'message info))) (defun resize-window--match-alias (key) - "Taken the KEY or keyboard selection from `read-key` check for alias. + "Taken the KEY or keyboard selection from `read-key' check for alias. Match the KEY against the alias table. If found, return the value that it -points to, which should be a key in the ‘resize-window-dispatch-alist’. -Otherwise, return the key." +points to, which should be a key in the `resize-window-dispatch-alist'. +Otherwise, return the KEY." (let ((alias (assoc key resize-window-alias-list))) (if alias (car (cdr alias)) @@ -177,7 +177,7 @@ nil." (defun resize-window--display-choice (choice) "Formats screen message about CHOICE. -CHOICE is a \(key function description allows-capital\)." +CHOICE is a \(key function documentation allows-capitals\)." (let ((key (resize-window--choice-keybinding choice))) (format "%s: %s " (if (resize-window--allows-capitals choice) (format "%s|%s" @@ -187,6 +187,7 @@ CHOICE is a \(key function description allows-capital\)." (resize-window--choice-documentation choice)))) (defun resize-window--get-documentation-strings () + "Return documented keybindings as a multiline string." (mapconcat #'identity (mapcar 'resize-window--display-choice resize-window-dispatch-alist) "\n")) @@ -203,8 +204,8 @@ CHOICE is a \(key function description allows-capital\)." (defun resize-window--execute-action (choice &optional scaled) "Given a CHOICE, grab values out of the alist. -If SCALED, then call action with the resize-window-capital-argument." - ;; (char function description) +CHOICE is a \(key function documentation allows-capitals\). +If SCALED, then call action with the `resize-window-uppercase-argument'." (let ((action (resize-window--choice-lambda choice)) (description (resize-window--choice-documentation choice))) (unless (equal (resize-window--choice-keybinding choice) ??) @@ -250,22 +251,25 @@ to enlarge right." ;;; Function Handlers (defun resize-window--enlarge-down (&optional size) "Extend the current window downwards by optional SIZE. -If no SIZE is given, extend by `resize-window-default-argument`" +If no SIZE is given, extend by `resize-window-lowercase-argument'." (let ((size (or size (resize-window-lowercase-argument)))) (enlarge-window size))) (defun resize-window--enlarge-up (&optional size) - "Bring bottom edge back up by one or optional SIZE." + "Bring bottom edge back up by one or optional SIZE. +If no SIZE is given, extend by `resize-window-lowercase-argument'." (let ((size (or size (resize-window-lowercase-argument)))) (enlarge-window (- size)))) (defun resize-window--enlarge-horizontally (&optional size) - "Enlarge the window horizontally by one or optional SIZE." + "Enlarge the window horizontally by one or optional SIZE. +If no SIZE is given, extend by `resize-window-lowercase-argument'." (let ((size (or size (resize-window-lowercase-argument)))) (enlarge-window size t))) (defun resize-window--shrink-horizontally (&optional size) - "Shrink the window horizontally by one or optional SIZE." + "Shrink the window horizontally by one or optional SIZE. +If no SIZE is given, extend by `resize-window-lowercase-argument'." (let ((size (or size (resize-window-lowercase-argument)))) (enlarge-window (- size) t))) @@ -274,9 +278,11 @@ If no SIZE is given, extend by `resize-window-default-argument`" (balance-windows)) (defun resize-window--delete-overlays () + "Remove overlays." (delete-overlay resize-window--background-overlay)) (defun resize-window--create-overlay () + "Add overlay." (setq resize-window--background-overlay (resize-window--make-background))) (defun resize-window--cycle-window-positive () @@ -296,38 +302,48 @@ If no SIZE is given, extend by `resize-window-default-argument`" (resize-window--notify "%s" (resize-window--get-documentation-strings))) (defun resize-window--delete-window () + "Delete the current window." (delete-overlay resize-window--background-overlay) (delete-window) (setq resize-window--background-overlay (resize-window--make-background))) (defun resize-window--window-push () + "Save the current state in the stack." (push (current-window-configuration) resize-window--window-stack)) (defun resize-window--window-pop () + "Return the first element and remove it from the stack." (pop resize-window--window-stack)) (defun resize-window--kill-other-windows () + "Delete other windows." (resize-window--delete-overlays) (resize-window--window-push) (delete-other-windows) (resize-window--create-overlay)) (defun resize-window--restore-windows () + "Restore the previous state." (let ((config (resize-window--window-pop))) (when config (resize-window--delete-overlays) (set-window-configuration config) (resize-window--create-overlay)))) -(defvar resize-window--capital-letters (number-sequence ?A ?Z)) -(defvar resize-window--lower-letters (number-sequence ?a ?z)) +(defvar resize-window--capital-letters (number-sequence ?A ?Z) + "List of uppercase letters as characters.") +(defvar resize-window--lower-letters (number-sequence ?a ?z) + "List of lowercase letters as characters.") (defun resize-window--key-available? (key) + "Return non-nil if KEY is bound, otherwise return nil." (let ((keys (mapcar #'resize-window--choice-keybinding resize-window-dispatch-alist))) (not (member key keys)))) (defun resize-window-add-choice (key func doc &optional allows-capitals) - "Register a function for resize-window. + "Register a new binding for `resize-window'. +Refuses to replace an already taken key. + KEY is the char (eg ?c) that should invoke the FUNC. DOC is a doc string for the help menu, and optional ALLOWS-CAPITALS should be t or nil. Functions should be of zero arity if they do not allow From 9209cf13c093046b386c8879f789506780a88e3c Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 21 Sep 2018 09:00:38 +0200 Subject: [PATCH 02/30] Fix readme.md typos --- readme.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 15f64ba..26da7b5 100644 --- a/readme.md +++ b/readme.md @@ -7,6 +7,7 @@ [![Tag Version](https://img.shields.io/github/tag/dpsutton/resize-window.svg)](https://github.com/dpsutton/resize-window/tags) ## What it is ## + Resizing windows is a pain in emacs. You have to do an uncomfortable chord `C-x {`, `C-x ^`. Giving prefixes to make the jump larger than 1 line means remembering `C-u `. I always @@ -15,9 +16,10 @@ minor mode to easily adjust window sizes with familiar keys and without chords. ## How to use it ## + As soon as it gets polished a little further, it will hopefully be accepted to ELPA or something (I'm not too sure about all of the -differences, ELPA, MELPA, marmalade, etc. +differences, ELPA, MELPA, marmalade, etc.) I've submitted for elpa and have a pending request to have copyright assigned. Until then, just drop it into your load path. I've found the @@ -56,16 +58,20 @@ motions and cycle windows until everything is adjusted to how you like it. ## How to extend it ## + There are a few things that you can do. There are customizable variables: - resize-window-coarse-argument (default: 5) - resize-window-fine-argument (default: 1) -- resizewindow-allow-backgrounds (default: t) +- resize-window-allow-backgrounds (default: t) +- resize-window-swap-capital-and-lowercase-behavior (default: nil) +- resize-window-notify-with-messages (default: t) Any of these can be customized by using `customize-group RET resize-window` or by setting the appropriate variable in your init.el file as normal: `(setq )`. ## What's even cooler ## + At the end of the day, this is really just a function dispatcher listening for key presses. So i've found a really nice workflow. I've bound resize-window to `C-c ;` and i've also added a new dispatch: @@ -78,7 +84,9 @@ run helm-mini. Its trivial now to bounce around, resize windows and reset their sources. And since the help menu is dynamically generated, pressing ? displays this new choice automatically. -For convenience sake, you can use the helper method `resize-window-add-choice` to register your function without having to remember the structure of the list it will end up in. For example: +For convenience sake, you can use the helper method `resize-window-add-choice` +to register your function without having to remember the structure of the list +it will end up in. For example: (push '(?h (lambda () (dired "~/projects/clojure")) "Clojure home" nil)) ; is equivalent to @@ -87,10 +95,9 @@ For convenience sake, you can use the helper method `resize-window-add-choice` t ; the allows-capitals argument is optional. -Further, there are alias, held in the `resize-window-alias-list` alist. It is +Further, there are aliases, held in the `resize-window-alias-list` alist. It is currently defined as - (defvar resize-windown-alias-list '((right ?f) (up ?n) @@ -125,6 +132,7 @@ resize these buffers. ![usage gif](images/navigate.gif) ## Bugs ## + - When in resize mode, there's a visual overlay over the current buffer to give some visual indication what's going on. However, if you have two copies of the same buffer open, both are @@ -132,19 +140,21 @@ overlayed. Would like to reduce this so that only the buffer you're working in is overlaid, regardless of how many copies are open ## Hopeful upcoming features ## + I can't promise any of these but these are the things I'm hoping to do: - put overlays over *other* buffers rather than current one. This greys out other workspaces and leaves yours with full color, bringing your eye there. Just seems like a nice ergonomic touch. -- DONE:allow customization to invert capital/non-capital behavior. Right +- DONE: allow customization to invert capital/non-capital behavior. Right now, lower case selections move the window size by 1 and upper-case moves by 5. These should both be easy to customize and easy to *flip*. Ie, make the lowercase `n` make it bigger by 5 and the upper-case `N` increase by 1. ## Shout out ## + This is my first attempt at really doing anything in elisp and to get there, I drew lots of inspiration and organizational ideas and almost verbatim code from `ace-mode`. Ace mode is super super nice and my aim @@ -153,11 +163,12 @@ than jumping. In fact, this might actually be better as a pull request to their package. ## How it works ## + Super simple architecture. There's an associative list called -`resize-window-dispatch-alist` (the rm is prefix for resize-mode) that holds a -simple data structure with the invocation-key, the function to call, -documentation string to be shown as a message, and whether it should -accept a capital letter as well as the lower-case letter. +`resize-window-dispatch-alist` (the rm is prefix for resize-mode) that +holds a simple data structure with the invocation-key, the function to +call, documentation string to be shown as a message, and whether it +should accept a capital letter as well as the lower-case letter. The function `resize-window` is just a while loop listening for characters, checking if that character is associated to anything in @@ -165,5 +176,5 @@ the list and checking if `(+ char 32)` is associated to anything in the list (which is just the uppercase version (or is it? now its a bug)). If lower case matches, do it, if uppercase matches something, then make sure that's ok and do it but with the -`resize-window-capital-argument` rather than -`resize-window-default-argument`. +`resize-window-uppercase-argument` rather than +`resize-window-lowercase-argument`. From bac717a58b43189fa3fe664ecc628cd6e4fd22c5 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 14 Sep 2018 09:23:37 +0200 Subject: [PATCH 03/30] Fix indentation --- resize-window.el | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/resize-window.el b/resize-window.el index 2bd3dbc..bf04329 100644 --- a/resize-window.el +++ b/resize-window.el @@ -189,7 +189,7 @@ CHOICE is a \(key function documentation allows-capitals\)." (defun resize-window--get-documentation-strings () "Return documented keybindings as a multiline string." (mapconcat #'identity (mapcar 'resize-window--display-choice - resize-window-dispatch-alist) + resize-window-dispatch-alist) "\n")) (defun resize-window--make-background () @@ -211,14 +211,14 @@ If SCALED, then call action with the `resize-window-uppercase-argument'." (unless (equal (resize-window--choice-keybinding choice) ??) (resize-window--notify "%s" description)) (condition-case nil - (if scaled - (funcall action (resize-window-uppercase-argument)) - (funcall action)) - - (wrong-number-of-arguments - (resize-window--notify "Invalid arity in function for %s" - (char-to-string - (resize-window--choice-keybinding choice))))))) + (if scaled + (funcall action (resize-window-uppercase-argument)) + (funcall action)) + (wrong-number-of-arguments + (resize-window--notify + "Invalid arity in function for %s" + (char-to-string + (resize-window--choice-keybinding choice))))))) ;;;###autoload (defun resize-window () @@ -337,7 +337,8 @@ If no SIZE is given, extend by `resize-window-lowercase-argument'." (defun resize-window--key-available? (key) "Return non-nil if KEY is bound, otherwise return nil." - (let ((keys (mapcar #'resize-window--choice-keybinding resize-window-dispatch-alist))) + (let ((keys (mapcar #'resize-window--choice-keybinding + resize-window-dispatch-alist))) (not (member key keys)))) (defun resize-window-add-choice (key func doc &optional allows-capitals) @@ -351,8 +352,10 @@ capitals, and should be of optional single arity if they allow capitals. Invoking with the capital will pass the capital argument." (if (resize-window--key-available? key) - (push (list key func doc allows-capitals) resize-window-dispatch-alist) - (message "The `%s` key is already taken for resize-window." (char-to-string key)))) + (push (list key func doc allows-capitals) + resize-window-dispatch-alist) + (message "The `%s` key is already taken for resize-window." + (char-to-string key)))) (provide 'resize-window) ;;; resize-window.el ends here From bea596a78396ca12e5c65ed6ad8acacd60f0146e Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 14:50:17 +0200 Subject: [PATCH 04/30] Populate customization group --- resize-window.el | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/resize-window.el b/resize-window.el index bf04329..debfbee 100644 --- a/resize-window.el +++ b/resize-window.el @@ -67,24 +67,29 @@ (defcustom resize-window-coarse-argument 5 "Set how big a capital letter movement is." - :type 'integer) + :type 'integer + :group 'resize-window) (defcustom resize-window-fine-argument 1 "Set how big the default movement should be." - :type 'integer) + :type 'integer + :group 'resize-window) (defcustom resize-window-allow-backgrounds t "Allow resize mode to set backgrounds. This is also valuable to see that you are in resize mode." - :type 'boolean) + :type 'boolean + :group 'resize-window) (defcustom resize-window-swap-capital-and-lowercase-behavior nil "Reverse default behavior of lower case and uppercase arguments." - :type 'boolean) + :type 'boolean + :group 'resize-window) (defcustom resize-window-notify-with-messages t "Show notifications in message bar." - :type 'boolean) + :type 'boolean + :group 'resize-window) (defvar resize-window--background-overlay () "Holder for background overlay.") From 573f400b9eea7e85ce0f681e666d6c0f4b8a0101 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 14 Sep 2018 09:30:23 +0200 Subject: [PATCH 05/30] Verify if keybinding is free both in aliases and action bindings --- resize-window.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resize-window.el b/resize-window.el index debfbee..240b06a 100644 --- a/resize-window.el +++ b/resize-window.el @@ -342,9 +342,8 @@ If no SIZE is given, extend by `resize-window-lowercase-argument'." (defun resize-window--key-available? (key) "Return non-nil if KEY is bound, otherwise return nil." - (let ((keys (mapcar #'resize-window--choice-keybinding - resize-window-dispatch-alist))) - (not (member key keys)))) + (and (not (assoc key resize-window-alias-list)) + (not (assoc key resize-window-dispatch-alist)))) (defun resize-window-add-choice (key func doc &optional allows-capitals) "Register a new binding for `resize-window'. From 2b8db3ac78b05702aac6485f201bf3a9823ffce2 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Sat, 8 Sep 2018 17:32:09 +0200 Subject: [PATCH 06/30] Do not delete the main window --- resize-window.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resize-window.el b/resize-window.el index 240b06a..45d86d1 100644 --- a/resize-window.el +++ b/resize-window.el @@ -308,9 +308,10 @@ If no SIZE is given, extend by `resize-window-lowercase-argument'." (defun resize-window--delete-window () "Delete the current window." - (delete-overlay resize-window--background-overlay) - (delete-window) - (setq resize-window--background-overlay (resize-window--make-background))) + (unless (eq (selected-window) (window-main-window)) + (delete-overlay resize-window--background-overlay) + (delete-window) + (setq resize-window--background-overlay (resize-window--make-background)))) (defun resize-window--window-push () "Save the current state in the stack." From 85e04fa42fe3c9863b03b743f89db17c7fea23e0 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 09:46:25 +0200 Subject: [PATCH 07/30] Do not resize the root window --- resize-window.el | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/resize-window.el b/resize-window.el index 45d86d1..f7087a9 100644 --- a/resize-window.el +++ b/resize-window.el @@ -257,26 +257,30 @@ to enlarge right." (defun resize-window--enlarge-down (&optional size) "Extend the current window downwards by optional SIZE. If no SIZE is given, extend by `resize-window-lowercase-argument'." - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window size))) + (unless (frame-root-window-p (selected-window)) + (let ((size (or size (resize-window-lowercase-argument)))) + (enlarge-window size)))) (defun resize-window--enlarge-up (&optional size) "Bring bottom edge back up by one or optional SIZE. If no SIZE is given, extend by `resize-window-lowercase-argument'." - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window (- size)))) + (unless (frame-root-window-p (selected-window)) + (let ((size (or size (resize-window-lowercase-argument)))) + (enlarge-window (- size))))) (defun resize-window--enlarge-horizontally (&optional size) "Enlarge the window horizontally by one or optional SIZE. If no SIZE is given, extend by `resize-window-lowercase-argument'." - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window size t))) + (unless (frame-root-window-p (selected-window)) + (let ((size (or size (resize-window-lowercase-argument)))) + (enlarge-window size t)))) (defun resize-window--shrink-horizontally (&optional size) "Shrink the window horizontally by one or optional SIZE. If no SIZE is given, extend by `resize-window-lowercase-argument'." - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window (- size) t))) + (unless (frame-root-window-p (selected-window)) + (let ((size (or size (resize-window-lowercase-argument)))) + (enlarge-window (- size) t)))) (defun resize-window--reset-windows () "Reset window layout to even spread." From c5ba4ef3d0aa23230be7d9987ce374db05f42e06 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 09:50:07 +0200 Subject: [PATCH 08/30] Resize by pushing/pulling the window divider f/n key pushes forward/down the current window right/below divider. If there is no right/below divider, it pulls forward/down the left/above. b/p key pulls backward/up the current window right/below divider. If there is no right/below divider, it pushes backward/up the left/above. Update: - readme.md --- readme.md | 15 +++++----- resize-window.el | 77 ++++++++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/readme.md b/readme.md index 26da7b5..278b9f7 100644 --- a/readme.md +++ b/readme.md @@ -31,13 +31,14 @@ the caps lock key to control. But, just run `M-x resize-window`. There are only a few commands to learn, and they mimic the normal motions in emacs. -- `n`: Makes the window vertically bigger, think scrolling down. Use -`N` to enlarge 5 lines at once. -- `p`: Makes the window vertically smaller, again, like scrolling. Use -`P` to shrink 5 lines at once. -- `f`: Makes the window horizontally bigger, like scrolling forward; -`F` for five lines at once. -- `b`: window horizontally smaller, `B` for five lines at once. +- `n`: Resize the window vertically like scrolling down. Use `N` for 5 +lines at once. +- `p`: Resize the window vertically like scrolling up. Use `P` for 5 +lines at once. +- `f`: Resize the window horizontally like scrolling forward. Use `F` +for 5 lines at once. +- `b`: Resize the window horizontally like scrolling backward. Use `B` +for 5 lines at once. - `r`: reset window layout to standard - `w`: cycle through windows so that you can adjust other window panes. `W` cycles in the opposite direction. diff --git a/resize-window.el b/resize-window.el index f7087a9..65c49f7 100644 --- a/resize-window.el +++ b/resize-window.el @@ -26,7 +26,7 @@ ;;; Commentary: ;; Easily allows you to resize windows. Rather than guessing that you -;; want `C-u 17 C-x {`, you could just press FFff, which enlarges 5 +;; want `C-u 17 C-x {`, you could just press FFff, which resizes 5 ;; lines, then 5 lines, then one and then one. The idea is that the ;; normal motions n,p,f,b along with r for reset and w for cycling ;; windows allows for super simple resizing of windows. All of this is @@ -37,13 +37,14 @@ ;; But, just run `M-x resize-window`. There are only a few commands to learn, ;; and they mimic the normal motions in emacs. -;; n : Makes the window vertically bigger, think scrolling down. Use -;; N to enlarge 5 lines at once. -;; p : Makes the window vertically smaller, again, like scrolling. Use -;; P to shrink 5 lines at once. -;; f : Makes the window horizontally bigger, like scrolling forward; -;; F for five lines at once. -;; b : window horizontally smaller, B for five lines at once. +;; n : Resize the window vertically like scrolling down. +;; N for 5 lines at once. +;; p : Resize the window vertically like scrolling up. +;; P for 5 lines at once. +;; f : Resize the window horizontally like scrolling forward. +;; F for 5 lines at once. +;; b : Resize the window horizontally like scrolling backward. +;; B for 5 lines at once. ;; r : reset window layout to standard ;; w : cycle through windows so that you can adjust other window ;; panes. W cycles in the opposite direction. @@ -103,7 +104,7 @@ This is also valuable to see that you are in resize mode." (defun resize-window-lowercase-argument () "Return the behavior for lowercase entries. -Example, normally n maps to enlarge vertically by 1. However, +Example, normally n maps to resize vertically by 1. However, if you have swapped capital and lowercase behavior, then this should return the coarse adjustment." (if resize-window-swap-capital-and-lowercase-behavior @@ -112,7 +113,7 @@ this should return the coarse adjustment." (defun resize-window-uppercase-argument () "Return the behavior for uppercase entries. -Example, normally N maps to enlarge vertically by 5. However, +Example, normally N maps to resize vertically by 5. However, if you have swapped capital and lowercase behavior, then this should return the fine adjustment (default 1)." (if resize-window-swap-capital-and-lowercase-behavior @@ -120,10 +121,10 @@ should return the fine adjustment (default 1)." resize-window-coarse-argument)) (defvar resize-window-dispatch-alist - '((?n resize-window--enlarge-down " Resize - Expand down" t) - (?p resize-window--enlarge-up " Resize - Expand up" t) - (?f resize-window--enlarge-horizontally " Resize - horizontally" t) - (?b resize-window--shrink-horizontally " Resize - shrink horizontally" t) + '((?n resize-window--resize-downward " Resize - downward" t) + (?p resize-window--resize-upward " Resize - upward" t) + (?f resize-window--resize-forward " Resize - forward" t) + (?b resize-window--resize-backward " Resize - backward" t) (?r resize-window--reset-windows " Resize - reset window layout" nil) (?w resize-window--cycle-window-positive " Resize - cycle window" nil) (?W resize-window--cycle-window-negative " Resize - cycle window" nil) @@ -228,8 +229,8 @@ If SCALED, then call action with the `resize-window-uppercase-argument'." ;;;###autoload (defun resize-window () "Resize the window. -Press n to enlarge down, p to enlarge up, b to enlarge left and f -to enlarge right." +Press n to resize down, p to resize up, b to resize left and f +to resize right." (interactive) (setq resize-window--background-overlay (resize-window--make-background)) (resize-window--notify "Resize mode: enter character, ? for help") @@ -254,33 +255,37 @@ to enlarge right." (quit (resize-window--delete-overlays)))) ;;; Function Handlers -(defun resize-window--enlarge-down (&optional size) - "Extend the current window downwards by optional SIZE. -If no SIZE is given, extend by `resize-window-lowercase-argument'." +(defun resize-window--resize-downward (&optional size) + "Resize the window vertically downward by optional SIZE. +If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window size)))) + (let ((size (or size (resize-window-lowercase-argument))) + (direction (if (window-in-direction 'below) 1 -1))) + (enlarge-window (* size direction))))) -(defun resize-window--enlarge-up (&optional size) - "Bring bottom edge back up by one or optional SIZE. -If no SIZE is given, extend by `resize-window-lowercase-argument'." +(defun resize-window--resize-upward (&optional size) + "Resize the window vertically upward by optional SIZE. +If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window (- size))))) + (let ((size (or size (resize-window-lowercase-argument))) + (direction (if (window-in-direction 'below) -1 1))) + (enlarge-window (* size direction))))) -(defun resize-window--enlarge-horizontally (&optional size) - "Enlarge the window horizontally by one or optional SIZE. -If no SIZE is given, extend by `resize-window-lowercase-argument'." +(defun resize-window--resize-forward (&optional size) + "Resize the window horizontally forward by optional SIZE. +If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window size t)))) + (let ((size (or size (resize-window-lowercase-argument))) + (direction (if (window-in-direction 'right) 1 -1))) + (enlarge-window (* size direction) t)))) -(defun resize-window--shrink-horizontally (&optional size) - "Shrink the window horizontally by one or optional SIZE. -If no SIZE is given, extend by `resize-window-lowercase-argument'." +(defun resize-window--resize-backward (&optional size) + "Resize the window horizontally backward by optional SIZE. +If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) - (let ((size (or size (resize-window-lowercase-argument)))) - (enlarge-window (- size) t)))) + (let ((size (or size (resize-window-lowercase-argument))) + (direction (if (window-in-direction 'right) -1 1))) + (enlarge-window (* size direction) t)))) (defun resize-window--reset-windows () "Reset window layout to even spread." From 584511f44843e20b42df7f2cec883c4b5dbb0f30 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 08:34:20 +0200 Subject: [PATCH 09/30] Overlay other windows Refresh overlays after window split. Update: - readme.md --- readme.md | 8 +--- resize-window.el | 106 +++++++++++++++++++++++++---------------------- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/readme.md b/readme.md index 278b9f7..07d47bf 100644 --- a/readme.md +++ b/readme.md @@ -134,18 +134,14 @@ resize these buffers. ## Bugs ## -- When in resize mode, there's a visual overlay over the current -buffer to give some visual indication what's going on. However, if -you have two copies of the same buffer open, both are -overlayed. Would like to reduce this so that only the buffer you're -working in is overlaid, regardless of how many copies are open +Working to spot one to fix...! ## Hopeful upcoming features ## I can't promise any of these but these are the things I'm hoping to do: -- put overlays over *other* buffers rather than current one. This +- DONE: put overlays over *other* buffers rather than current one. This greys out other workspaces and leaves yours with full color, bringing your eye there. Just seems like a nice ergonomic touch. - DONE: allow customization to invert capital/non-capital behavior. Right diff --git a/resize-window.el b/resize-window.el index 65c49f7..6daee0e 100644 --- a/resize-window.el +++ b/resize-window.el @@ -92,8 +92,8 @@ This is also valuable to see that you are in resize mode." :type 'boolean :group 'resize-window) -(defvar resize-window--background-overlay () - "Holder for background overlay.") +(defvar resize-window--background-overlays () + "List of background overlays.") (defvar resize-window--window-stack () "Stack for holding window configurations.") @@ -128,8 +128,8 @@ should return the fine adjustment (default 1)." (?r resize-window--reset-windows " Resize - reset window layout" nil) (?w resize-window--cycle-window-positive " Resize - cycle window" nil) (?W resize-window--cycle-window-negative " Resize - cycle window" nil) - (?2 split-window-below " Split window horizontally" nil) - (?3 split-window-right " Slit window vertically" nil) + (?2 resize-window--split-window-below " Split window horizontally" nil) + (?3 resize-window--split-window-right " Slit window vertically" nil) (?0 resize-window--delete-window " Delete window" nil) (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) (?y resize-window--restore-windows " (when state) Restore window configuration" nil) @@ -198,15 +198,25 @@ CHOICE is a \(key function documentation allows-capitals\)." resize-window-dispatch-alist) "\n")) -(defun resize-window--make-background () - "Place a background over the current window." +(defun resize-window--add-backgrounds () + "Place an overlay background over other windows." + (resize-window--remove-backgrounds) (when resize-window-allow-backgrounds - (let ((ol (make-overlay - (point-min) - (point-max) - (window-buffer)))) - (overlay-put ol 'face 'resize-window-background) - ol))) + (let ((windows (remq (selected-window) (window-list nil -1)))) + (dolist (window windows) + (with-current-buffer (window-buffer window) + (let ((ol (make-overlay + (point-min) + (point-max) + (current-buffer)))) + (overlay-put ol 'face 'resize-window-background) + (overlay-put ol 'window window) + (push ol resize-window--background-overlays))))))) + +(defun resize-window--remove-backgrounds () + "Remove the overlay backgrounds." + (mapc 'delete-overlay resize-window--background-overlays) + (setq resize-window--background-overlays nil)) (defun resize-window--execute-action (choice &optional scaled) "Given a CHOICE, grab values out of the alist. @@ -232,27 +242,28 @@ If SCALED, then call action with the `resize-window-uppercase-argument'." Press n to resize down, p to resize up, b to resize left and f to resize right." (interactive) - (setq resize-window--background-overlay (resize-window--make-background)) + (resize-window--add-backgrounds) (resize-window--notify "Resize mode: enter character, ? for help") (condition-case nil - (let ((reading-characters t) - ;; allow mini-buffer to collapse after displaying menu - (resize-mini-windows t)) - (while reading-characters - (let* ((char (resize-window--match-alias (read-key))) - (choice (assoc char resize-window-dispatch-alist)) - (capital (when (numberp char) - (assoc (+ char 32) resize-window-dispatch-alist)))) - (cond - (choice (resize-window--execute-action choice)) - ((and capital (resize-window--allows-capitals capital)) - ;; rather than pass an argument, we tell it to "scale" it - ;; with t and that method can worry about how to get that - ;; action - (resize-window--execute-action capital t)) - (t (setq reading-characters nil) - (delete-overlay resize-window--background-overlay)))))) - (quit (resize-window--delete-overlays)))) + (let ((reading-characters t) + ;; allow mini-buffer to collapse after displaying menu + (resize-mini-windows t)) + (while reading-characters + (let* ((char (resize-window--match-alias (read-key))) + (choice (assoc char resize-window-dispatch-alist)) + (capital (when (numberp char) + (assoc (+ char 32) resize-window-dispatch-alist)))) + (cond + (choice (resize-window--execute-action choice)) + ((and capital (resize-window--allows-capitals capital)) + ;; rather than pass an argument, we tell it to "scale" it + ;; with t and that method can worry about how to get that + ;; action + (resize-window--execute-action capital t)) + (t (setq reading-characters nil) + (resize-window--remove-backgrounds)))))) + (quit + (resize-window--remove-backgrounds)))) ;;; Function Handlers (defun resize-window--resize-downward (&optional size) @@ -291,36 +302,35 @@ If no SIZE is given, modify by `resize-window-default-argument'" "Reset window layout to even spread." (balance-windows)) -(defun resize-window--delete-overlays () - "Remove overlays." - (delete-overlay resize-window--background-overlay)) - -(defun resize-window--create-overlay () - "Add overlay." - (setq resize-window--background-overlay (resize-window--make-background))) - (defun resize-window--cycle-window-positive () "Cycle windows." - (delete-overlay resize-window--background-overlay) (other-window 1) - (setq resize-window--background-overlay (resize-window--make-background))) + (resize-window--add-backgrounds)) (defun resize-window--cycle-window-negative () "Cycle windows negative." - (delete-overlay resize-window--background-overlay) (other-window -1) - (setq resize-window--background-overlay (resize-window--make-background))) + (resize-window--add-backgrounds)) (defun resize-window--display-menu () "Display menu in minibuffer." (resize-window--notify "%s" (resize-window--get-documentation-strings))) +(defun resize-window--split-window-below () + "Split the window vertically." + (split-window-below) + (resize-window--add-backgrounds)) + +(defun resize-window--split-window-right () + "Split the window horizontally." + (split-window-right) + (resize-window--add-backgrounds)) + (defun resize-window--delete-window () "Delete the current window." (unless (eq (selected-window) (window-main-window)) - (delete-overlay resize-window--background-overlay) (delete-window) - (setq resize-window--background-overlay (resize-window--make-background)))) + (resize-window--add-backgrounds))) (defun resize-window--window-push () "Save the current state in the stack." @@ -332,18 +342,16 @@ If no SIZE is given, modify by `resize-window-default-argument'" (defun resize-window--kill-other-windows () "Delete other windows." - (resize-window--delete-overlays) (resize-window--window-push) (delete-other-windows) - (resize-window--create-overlay)) + (resize-window--add-backgrounds)) (defun resize-window--restore-windows () "Restore the previous state." (let ((config (resize-window--window-pop))) (when config - (resize-window--delete-overlays) (set-window-configuration config) - (resize-window--create-overlay)))) + (resize-window--add-backgrounds)))) (defvar resize-window--capital-letters (number-sequence ?A ?Z) "List of uppercase letters as characters.") From 207df838576451a4301958dea726a376d77e2ee8 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 11:11:36 +0200 Subject: [PATCH 10/30] Display the help menu in a side window Exclude the help menu when saving a configuration. When restoring a configuration display the help menu only if it was currently open. Workaround to force update the current buffer values. Soon after `ivy-switch-buffer`, both `current-buffer` and `get-buffer-window` still reference the old buffer, not the one switched to. --- resize-window.el | 77 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/resize-window.el b/resize-window.el index 6daee0e..78e9e9a 100644 --- a/resize-window.el +++ b/resize-window.el @@ -133,7 +133,7 @@ should return the fine adjustment (default 1)." (?0 resize-window--delete-window " Delete window" nil) (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) (?y resize-window--restore-windows " (when state) Restore window configuration" nil) - (?? resize-window--display-menu " Resize - display menu" nil)) + (?? resize-window--display-menu " Resize - toggle help menu" nil)) "List of resize mode bindings. Main data structure of the dispatcher with the form: \(key function documentation allows-capitals\)") @@ -260,9 +260,12 @@ to resize right." ;; with t and that method can worry about how to get that ;; action (resize-window--execute-action capital t)) - (t (setq reading-characters nil) - (resize-window--remove-backgrounds)))))) + (t + (setq reading-characters nil) + (resize-window--display-menu 'kill) + (resize-window--remove-backgrounds)))))) (quit + (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)))) ;;; Function Handlers @@ -312,9 +315,33 @@ If no SIZE is given, modify by `resize-window-default-argument'" (other-window -1) (resize-window--add-backgrounds)) -(defun resize-window--display-menu () - "Display menu in minibuffer." - (resize-window--notify "%s" (resize-window--get-documentation-strings))) +(defun resize-window--display-menu (&optional action) + "Toggle help menu side window or perform ACTION if non-nil. +ACTION is a symbol of value 'kill or 'open." + (let* ((buffer (get-buffer-create "*Resize-Window-Help*")) + (window (get-buffer-window buffer)) + (add-backgrounds nil)) + (cond + ((and window (or (not action) (eq action 'kill))) + (quit-window t window) + (setq add-backgrounds t)) + ((and (not window) (or (not action) (eq action 'open))) + (setq window (display-buffer-in-side-window buffer nil)) + (set-window-parameter window 'no-other-window t) + (set-window-parameter window 'no-delete-other-windows t) + (with-current-buffer buffer + (setq buffer-read-only t) + (setq window-size-fixed t) + (let ((inhibit-read-only t) + (window-size-fixed nil)) + (erase-buffer) + (insert (resize-window--get-documentation-strings)) + (fit-window-to-buffer window))) + (setq add-backgrounds t))) + ;; NOTE: Just in case the help menu was selected (it shouldn't) + ;; refresh the backgrounds even when the help menu is killed. + (when add-backgrounds + (resize-window--add-backgrounds)))) (defun resize-window--split-window-below () "Split the window vertically." @@ -332,9 +359,42 @@ If no SIZE is given, modify by `resize-window-default-argument'" (delete-window) (resize-window--add-backgrounds))) +(defun resize-window--window-config () + "Return the current window configuration. +Exclude the help menu from the configuration." + (let ((display-menu (get-buffer-window "*Resize-Window-Help*"))) + (resize-window--display-menu 'kill) + (prog2 + ;; WORKAROUND: Calling `current-buffer' or `get-buffer-window' + ;; soon after `ivy-switch-buffer' references the old buffer. + ;; This forces to update to the buffer switched to. It also + ;; allows `current-window-configuration' to capture a proper + ;; configuration updating the values of all current buffers. + ;; See also https://github.com/abo-abo/swiper/issues/1766 + (let ((curr-window (selected-window))) + (mapc (lambda (w) (select-window w)) (window-list)) + (select-window curr-window)) + (current-window-configuration) + (when display-menu + (resize-window--display-menu 'open))))) + +(defun resize-window--restore-config (config) + "Restore the window configuration CONFIG then return it. +Restore the help menu only if it is currently open." + (let ((display-menu (get-buffer-window "*Resize-Window-Help*"))) + (set-window-configuration config) + ;; NOTE: If `resize-window--window-config' was used to save the + ;; CONFIG there is no help menu to kill. Keep this just in case. + (resize-window--display-menu 'kill) + (prog1 + (current-window-configuration) + (if display-menu + (resize-window--display-menu 'open) + (resize-window--add-backgrounds))))) + (defun resize-window--window-push () "Save the current state in the stack." - (push (current-window-configuration) resize-window--window-stack)) + (push (resize-window--window-config) resize-window--window-stack)) (defun resize-window--window-pop () "Return the first element and remove it from the stack." @@ -350,8 +410,7 @@ If no SIZE is given, modify by `resize-window-default-argument'" "Restore the previous state." (let ((config (resize-window--window-pop))) (when config - (set-window-configuration config) - (resize-window--add-backgrounds)))) + (resize-window--restore-config config)))) (defvar resize-window--capital-letters (number-sequence ?A ?Z) "List of uppercase letters as characters.") From 4bb07f4ff7967909ac3e4b2e7594ce8dcf0f278c Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 4 Sep 2018 19:06:03 +0200 Subject: [PATCH 11/30] Align documentation for help menu Update: - test/resize-window-test.el --- resize-window.el | 9 ++++----- test/resize-window-test.el | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/resize-window.el b/resize-window.el index 78e9e9a..eb54f72 100644 --- a/resize-window.el +++ b/resize-window.el @@ -185,11 +185,10 @@ nil." "Formats screen message about CHOICE. CHOICE is a \(key function documentation allows-capitals\)." (let ((key (resize-window--choice-keybinding choice))) - (format "%s: %s " (if (resize-window--allows-capitals choice) - (format "%s|%s" - (string key) - (string (- key 32))) - (string key)) + (concat (if (resize-window--allows-capitals choice) + (format "%s|%s" (string key) (string (- key 32))) + (format " %s " (string key))) + " : " (resize-window--choice-documentation choice)))) (defun resize-window--get-documentation-strings () diff --git a/test/resize-window-test.el b/test/resize-window-test.el index 7786b98..8185fd2 100644 --- a/test/resize-window-test.el +++ b/test/resize-window-test.el @@ -21,9 +21,9 @@ (defvar choice-capital '(?n 'function "documentation" t)) (ert-deftest should-create-documentation-from-alist () - (should (equal "n: documentation " + (should (equal " n : documentation" (resize-window--display-choice choice-no-capital))) - (should (equal "n|N: documentation " + (should (equal "n|N : documentation" (resize-window--display-choice choice-capital)))) (ert-deftest should-execute-and-display-message () From 075efc9b6a55878309169d33554bd0cec3669f26 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 11:19:22 +0200 Subject: [PATCH 12/30] Quit resize mode only by pressing "q", "Q", or space --- resize-window.el | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/resize-window.el b/resize-window.el index eb54f72..178454b 100644 --- a/resize-window.el +++ b/resize-window.el @@ -259,10 +259,19 @@ to resize right." ;; with t and that method can worry about how to get that ;; action (resize-window--execute-action capital t)) - (t + (;; NOTE: Don't use `=', if `char' is a symbol like + ;; 'insertchar it will fail. Use `equal' instead. + (or (equal char ?q) + (equal char ?Q) + (equal char (string-to-char " "))) (setq reading-characters nil) (resize-window--display-menu 'kill) - (resize-window--remove-backgrounds)))))) + (resize-window--remove-backgrounds)) + (t + (resize-window--notify + (format + "Unregistered key: (%s) %s" + char (single-key-description char)))))))) (quit (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)))) From c1640c9ef424e2a963f1ac4b1c76721d985053ae Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 21 Sep 2018 08:07:38 +0200 Subject: [PATCH 13/30] Customizable option to allow to quit on unregistered key Update: - readme.md --- readme.md | 1 + resize-window.el | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 07d47bf..fb9f3bc 100644 --- a/readme.md +++ b/readme.md @@ -64,6 +64,7 @@ There are a few things that you can do. There are customizable variables: - resize-window-coarse-argument (default: 5) - resize-window-fine-argument (default: 1) - resize-window-allow-backgrounds (default: t) +- resize-window-unregistered-key-quit (default: nil) - resize-window-swap-capital-and-lowercase-behavior (default: nil) - resize-window-notify-with-messages (default: t) diff --git a/resize-window.el b/resize-window.el index 178454b..c8827ba 100644 --- a/resize-window.el +++ b/resize-window.el @@ -82,6 +82,12 @@ This is also valuable to see that you are in resize mode." :type 'boolean :group 'resize-window) +(defcustom resize-window-unregistered-key-quit nil + "Quit when an unregistered key is pressed. +If nil do not quit and notify the unregistered key pressed." + :type 'boolean + :group 'resize-window) + (defcustom resize-window-swap-capital-and-lowercase-behavior nil "Reverse default behavior of lower case and uppercase arguments." :type 'boolean @@ -261,7 +267,8 @@ to resize right." (resize-window--execute-action capital t)) (;; NOTE: Don't use `=', if `char' is a symbol like ;; 'insertchar it will fail. Use `equal' instead. - (or (equal char ?q) + (or resize-window-unregistered-key-quit + (equal char ?q) (equal char ?Q) (equal char (string-to-char " "))) (setq reading-characters nil) From 176ccdc381ce6ffbe2a42a0b1804f18506feeac8 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 25 Sep 2018 02:39:42 +0200 Subject: [PATCH 14/30] Clear minibuffer on quit --- resize-window.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resize-window.el b/resize-window.el index c8827ba..37195ce 100644 --- a/resize-window.el +++ b/resize-window.el @@ -272,6 +272,7 @@ to resize right." (equal char ?Q) (equal char (string-to-char " "))) (setq reading-characters nil) + (message nil) (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)) (t @@ -280,6 +281,7 @@ to resize right." "Unregistered key: (%s) %s" char (single-key-description char)))))))) (quit + (message nil) (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)))) From 01bde96efa9fc7b6a024b4bdd94a046e42c21270 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 14:46:19 +0200 Subject: [PATCH 15/30] Window configurations stack management The stack size is now customizable. Always remove the oldest window configurations in excess to respect the stack size. Avoid stack duplicates and add the current window configuration to the tail of the stack before reverting to a previous configuration. Update: - readme.md --- readme.md | 1 + resize-window.el | 129 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index fb9f3bc..c382f05 100644 --- a/readme.md +++ b/readme.md @@ -65,6 +65,7 @@ There are a few things that you can do. There are customizable variables: - resize-window-fine-argument (default: 1) - resize-window-allow-backgrounds (default: t) - resize-window-unregistered-key-quit (default: nil) +- resize-window-stack-size (default: 16) - resize-window-swap-capital-and-lowercase-behavior (default: nil) - resize-window-notify-with-messages (default: t) diff --git a/resize-window.el b/resize-window.el index 37195ce..93b3310 100644 --- a/resize-window.el +++ b/resize-window.el @@ -88,6 +88,11 @@ If nil do not quit and notify the unregistered key pressed." :type 'boolean :group 'resize-window) +(defcustom resize-window-stack-size 16 + "Size of the stack for holding window configurations." + :type 'integer + :group 'resize-window) + (defcustom resize-window-swap-capital-and-lowercase-behavior nil "Reverse default behavior of lower case and uppercase arguments." :type 'boolean @@ -102,7 +107,10 @@ If nil do not quit and notify the unregistered key pressed." "List of background overlays.") (defvar resize-window--window-stack () - "Stack for holding window configurations.") + "Stack for holding window configurations. + +It is an alist of format ((configuration . time)...), where time +is the time when the configuration was saved/visited.") (defface resize-window-background '((t (:foreground "gray40"))) @@ -247,6 +255,9 @@ If SCALED, then call action with the `resize-window-uppercase-argument'." Press n to resize down, p to resize up, b to resize left and f to resize right." (interactive) + (resize-window--refresh-stack) + ;; NOTE: Do not trim the stack here. Let stack requests to handle + ;; window configurations in excess. (resize-window--add-backgrounds) (resize-window--notify "Resize mode: enter character, ? for help") (condition-case nil @@ -409,13 +420,121 @@ Restore the help menu only if it is currently open." (resize-window--display-menu 'open) (resize-window--add-backgrounds))))) +(defun resize-window--apply-config (config) + "Return the window configuration CONFIG after applying it. +Return nil if CONFIG isn't a proper window configuration. +Do not change the current window configuration." + (when (window-configuration-p config) + (let ((curr-frame (selected-frame)) + (some-frame (window-configuration-frame config)) + (some-config config)) + (when (frame-live-p some-frame) + (select-frame some-frame) + (save-excursion + (save-window-excursion + (set-window-configuration config) + (setq some-config (resize-window--window-config)))) + (select-frame curr-frame)) + some-config))) + +(defun resize-window--refresh-stack () + "Refresh the stack and remove adjacent duplicates. +Each window configuration is restored and saved again. + +The configurations saved time is not changed. Always remove the +older configuration when a duplicate is found. + +A refresh reveals duplicate configurations. When a configuration +is restored that takes account of the current state of the frame. +Since killed buffers cannot be dug up, applying a state will use +what it finds, and so two configurations may end up the same." + (let (stack-buffer) + (dotimes (n (length resize-window--window-stack)) + (let* ((this-member (nth n resize-window--window-stack)) + (this-config (resize-window--apply-config (car this-member))) + (this-svtime (cdr this-member)) + (prev-config (caar stack-buffer)) + (prev-svtime (cdar stack-buffer))) + (if (and this-config prev-config + (compare-window-configurations this-config prev-config)) + (when (time-less-p prev-svtime this-svtime) + (setcar stack-buffer this-member)) + (when this-config + (push this-member stack-buffer))))) + (setq resize-window--window-stack (nreverse stack-buffer)))) + +(defun resize-window--window-trim () + "Trim the oldest window configurations in excess from the stack. +Return the removed stack members." + (let* ((size (length resize-window--window-stack)) + (trim (- size resize-window-stack-size))) + (when (> trim 0) + (let ((oldest-members + (sort (copy-sequence resize-window--window-stack) + (lambda (a b) + (time-less-p (cdr a) (cdr b)))))) + (setq oldest-members + (nbutlast oldest-members (- size trim))) + (dotimes (n (length oldest-members)) + (let ((old-member (nth n oldest-members))) + (setq resize-window--window-stack + (delq old-member resize-window--window-stack)))) + oldest-members)))) + (defun resize-window--window-push () - "Save the current state in the stack." - (push (resize-window--window-config) resize-window--window-stack)) + "Save the current window configuration in the stack. +Return its stack member if the configuration is saved. + +Trim adjacent duplicates and old configurations when necessary. + +See also `resize-window-stack-size'." + (let* ((curr-config (resize-window--window-config)) + (curr-member (cons curr-config (current-time))) + (prev-config nil)) + ;; trim duplicates from the tail + (while (and (setq prev-config (caar (last resize-window--window-stack))) + (setq prev-config (resize-window--apply-config prev-config)) + (compare-window-configurations curr-config prev-config) + (setq resize-window--window-stack + (nbutlast resize-window--window-stack)))) + ;; trim duplicates from the head + (while (and (setq prev-config (caar resize-window--window-stack)) + (setq prev-config (resize-window--apply-config prev-config)) + (compare-window-configurations curr-config prev-config) + (pop resize-window--window-stack))) + (push curr-member resize-window--window-stack) + (resize-window--window-trim) + (when resize-window--window-stack + curr-member))) (defun resize-window--window-pop () - "Return the first element and remove it from the stack." - (pop resize-window--window-stack)) + "Shift to a previous window configuration. +Return the configuration shifted to if any. + +Save the current configuration to the tail of the stack. + +Trim adjacent duplicates and old configurations when necessary. + +See also `resize-window-stack-size'." + (resize-window--window-trim) + (let* ((curr-config (resize-window--window-config)) + (curr-member (cons curr-config (current-time))) + (prev-config nil)) + ;; trim duplicates from the tail + (while (and (setq prev-config (caar (last resize-window--window-stack))) + (setq prev-config (resize-window--apply-config prev-config)) + (compare-window-configurations curr-config prev-config) + (setq resize-window--window-stack + (nbutlast resize-window--window-stack)))) + ;; trim duplicates from the head + (while (and (setq prev-config (car (pop resize-window--window-stack))) + (setq prev-config (resize-window--apply-config prev-config)) + (compare-window-configurations curr-config prev-config))) + (when prev-config + (setq resize-window--window-stack + (append resize-window--window-stack (list curr-member))) + (resize-window--restore-config prev-config) + prev-config))) (defun resize-window--kill-other-windows () "Delete other windows." From d218de17e3f8c9c5e853ec44f460dbc47d5f9a32 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 4 Sep 2018 19:47:17 +0200 Subject: [PATCH 16/30] Save state before layout reset --- resize-window.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resize-window.el b/resize-window.el index 93b3310..aa64fb9 100644 --- a/resize-window.el +++ b/resize-window.el @@ -139,7 +139,7 @@ should return the fine adjustment (default 1)." (?p resize-window--resize-upward " Resize - upward" t) (?f resize-window--resize-forward " Resize - forward" t) (?b resize-window--resize-backward " Resize - backward" t) - (?r resize-window--reset-windows " Resize - reset window layout" nil) + (?r resize-window--reset-windows " Resize - reset window layout (save state)" nil) (?w resize-window--cycle-window-positive " Resize - cycle window" nil) (?W resize-window--cycle-window-negative " Resize - cycle window" nil) (?2 resize-window--split-window-below " Split window horizontally" nil) @@ -331,6 +331,7 @@ If no SIZE is given, modify by `resize-window-default-argument'" (defun resize-window--reset-windows () "Reset window layout to even spread." + (resize-window--window-push) (balance-windows)) (defun resize-window--cycle-window-positive () From e262003a0a63ef10b33481277ef04d44e86f050d Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 4 Sep 2018 19:49:23 +0200 Subject: [PATCH 17/30] Save state before window delete --- resize-window.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resize-window.el b/resize-window.el index aa64fb9..a12dc29 100644 --- a/resize-window.el +++ b/resize-window.el @@ -144,7 +144,7 @@ should return the fine adjustment (default 1)." (?W resize-window--cycle-window-negative " Resize - cycle window" nil) (?2 resize-window--split-window-below " Split window horizontally" nil) (?3 resize-window--split-window-right " Slit window vertically" nil) - (?0 resize-window--delete-window " Delete window" nil) + (?0 resize-window--delete-window " Delete window (save state)" nil) (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) (?y resize-window--restore-windows " (when state) Restore window configuration" nil) (?? resize-window--display-menu " Resize - toggle help menu" nil)) @@ -385,6 +385,7 @@ ACTION is a symbol of value 'kill or 'open." (defun resize-window--delete-window () "Delete the current window." (unless (eq (selected-window) (window-main-window)) + (resize-window--window-push) (delete-window) (resize-window--add-backgrounds))) From ca098fa48ae303ec219f5871d518fd3a305b3f75 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Wed, 5 Sep 2018 17:32:22 +0200 Subject: [PATCH 18/30] Save state before window split --- resize-window.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resize-window.el b/resize-window.el index a12dc29..db65b0e 100644 --- a/resize-window.el +++ b/resize-window.el @@ -142,8 +142,8 @@ should return the fine adjustment (default 1)." (?r resize-window--reset-windows " Resize - reset window layout (save state)" nil) (?w resize-window--cycle-window-positive " Resize - cycle window" nil) (?W resize-window--cycle-window-negative " Resize - cycle window" nil) - (?2 resize-window--split-window-below " Split window horizontally" nil) - (?3 resize-window--split-window-right " Slit window vertically" nil) + (?2 resize-window--split-window-below " Split window horizontally (save state)" nil) + (?3 resize-window--split-window-right " Slit window vertically (save state)" nil) (?0 resize-window--delete-window " Delete window (save state)" nil) (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) (?y resize-window--restore-windows " (when state) Restore window configuration" nil) @@ -374,11 +374,13 @@ ACTION is a symbol of value 'kill or 'open." (defun resize-window--split-window-below () "Split the window vertically." + (resize-window--window-push) (split-window-below) (resize-window--add-backgrounds)) (defun resize-window--split-window-right () "Split the window horizontally." + (resize-window--window-push) (split-window-right) (resize-window--add-backgrounds)) From a863c5a9821e279e3bc4547c9d976292cb395923 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 11:23:00 +0200 Subject: [PATCH 19/30] Save state command Update: - readme.md --- readme.md | 1 + resize-window.el | 2 ++ 2 files changed, 3 insertions(+) diff --git a/readme.md b/readme.md index c382f05..9a1ad69 100644 --- a/readme.md +++ b/readme.md @@ -46,6 +46,7 @@ panes. `W` cycles in the opposite direction. - `3`: create a new vertical split - `0`: delete the current window - `k`: kill all buffers and put window config on the stack +- `s`: Save the state on the stack so you may restore it later. - `y`: make the window configuration according to the last config pushed onto the stack - `?`: Display menu listing commands diff --git a/resize-window.el b/resize-window.el index db65b0e..08bc6dd 100644 --- a/resize-window.el +++ b/resize-window.el @@ -52,6 +52,7 @@ ;; 3 : create a new vertical split ;; 0 : delete the current window ;; k : kill all buffers and put window config on the stack +;; s : Save the state on the stack so you may restore it later. ;; y : make the window configuration according to the last config ;; pushed onto the stack ;; ? : Display menu listing commands @@ -146,6 +147,7 @@ should return the fine adjustment (default 1)." (?3 resize-window--split-window-right " Slit window vertically (save state)" nil) (?0 resize-window--delete-window " Delete window (save state)" nil) (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) + (?s resize-window--window-push " Save state" nil) (?y resize-window--restore-windows " (when state) Restore window configuration" nil) (?? resize-window--display-menu " Resize - toggle help menu" nil)) "List of resize mode bindings. From 33b47693f7fa5ce13f468608efd9d886c224f053 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 11:34:10 +0200 Subject: [PATCH 20/30] Reorganize the list of commands Update: - readme.md - test/resize-window-test.el --- readme.md | 19 +++++++------- resize-window.el | 53 +++++++++++++++++++------------------- test/resize-window-test.el | 2 +- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/readme.md b/readme.md index 9a1ad69..2715c9a 100644 --- a/readme.md +++ b/readme.md @@ -39,17 +39,16 @@ lines at once. for 5 lines at once. - `b`: Resize the window horizontally like scrolling backward. Use `B` for 5 lines at once. -- `r`: reset window layout to standard -- `w`: cycle through windows so that you can adjust other window -panes. `W` cycles in the opposite direction. -- `2`: create a new horizontal split -- `3`: create a new vertical split -- `0`: delete the current window -- `k`: kill all buffers and put window config on the stack +- `w`: Cycle through windows so that you can adjust other window +panes. Use `W` to cycle in the opposite direction. +- `e`: Even the size of the windows. +- `2`: Split the window horizontally. +- `3`: Split the window vertically. +- `0`: Delete the current window. +- `k`: Delete other windows and save the state on the stack. - `s`: Save the state on the stack so you may restore it later. -- `y`: make the window configuration according to the last config - pushed onto the stack -- `?`: Display menu listing commands +- `y`: Restore to a previous saved state. +- `?`: Display the help menu listing commands. The best part of this is that resize-window keeps listening for more keystrokes until it doesn't recognize input. So you don't have to make diff --git a/resize-window.el b/resize-window.el index 08bc6dd..65e272d 100644 --- a/resize-window.el +++ b/resize-window.el @@ -1,4 +1,4 @@ -;;; resize-window.el --- easily resize windows -*- lexical-binding: t; -*- +;;; resize-window.el --- Easily resize windows -*- lexical-binding: t; -*- ;; Copyright (C) 2015 Free Software Foundation, Inc. @@ -28,13 +28,13 @@ ;; Easily allows you to resize windows. Rather than guessing that you ;; want `C-u 17 C-x {`, you could just press FFff, which resizes 5 ;; lines, then 5 lines, then one and then one. The idea is that the -;; normal motions n,p,f,b along with r for reset and w for cycling +;; normal motions n,p,f,b along with e for even and w for cycling ;; windows allows for super simple resizing of windows. All of this is ;; inside of a while loop so that you don't have to invoke more chords ;; to resize again, but just keep using standard motions until you are ;; happy. -;; But, just run `M-x resize-window`. There are only a few commands to learn, +;; Just run `M-x resize-window`. There are only a few commands to learn, ;; and they mimic the normal motions in emacs. ;; n : Resize the window vertically like scrolling down. @@ -45,17 +45,16 @@ ;; F for 5 lines at once. ;; b : Resize the window horizontally like scrolling backward. ;; B for 5 lines at once. -;; r : reset window layout to standard -;; w : cycle through windows so that you can adjust other window -;; panes. W cycles in the opposite direction. -;; 2 : create a new horizontal split -;; 3 : create a new vertical split -;; 0 : delete the current window -;; k : kill all buffers and put window config on the stack +;; w : Cycle through windows so that you can adjust other window panes. +;; W cycle in the opposite direction. +;; e : Even the size of the windows. +;; 2 : Split the window horizontally. +;; 3 : Split the window vertically. +;; 0 : Delete the current window. +;; k : Delete other windows and save the state on the stack. ;; s : Save the state on the stack so you may restore it later. -;; y : make the window configuration according to the last config -;; pushed onto the stack -;; ? : Display menu listing commands +;; y : Restore to a previous saved state. +;; ? : Display the help menu listing commands. ;;; Code: @@ -136,20 +135,20 @@ should return the fine adjustment (default 1)." resize-window-coarse-argument)) (defvar resize-window-dispatch-alist - '((?n resize-window--resize-downward " Resize - downward" t) - (?p resize-window--resize-upward " Resize - upward" t) - (?f resize-window--resize-forward " Resize - forward" t) - (?b resize-window--resize-backward " Resize - backward" t) - (?r resize-window--reset-windows " Resize - reset window layout (save state)" nil) - (?w resize-window--cycle-window-positive " Resize - cycle window" nil) - (?W resize-window--cycle-window-negative " Resize - cycle window" nil) - (?2 resize-window--split-window-below " Split window horizontally (save state)" nil) - (?3 resize-window--split-window-right " Slit window vertically (save state)" nil) - (?0 resize-window--delete-window " Delete window (save state)" nil) - (?k resize-window--kill-other-windows " Kill other windows (save state)" nil) - (?s resize-window--window-push " Save state" nil) - (?y resize-window--restore-windows " (when state) Restore window configuration" nil) - (?? resize-window--display-menu " Resize - toggle help menu" nil)) + '((?n resize-window--resize-downward "Resize downward" t) + (?p resize-window--resize-upward "Resize upward" t) + (?f resize-window--resize-forward "Resize forward" t) + (?b resize-window--resize-backward "Resize backward" t) + (?w resize-window--cycle-window-positive "Next window" nil) + (?W resize-window--cycle-window-negative "Previous window" nil) + (?e resize-window--reset-windows "Even layout (save state)" nil) + (?2 resize-window--split-window-below "Split below (save state)" nil) + (?3 resize-window--split-window-right "Split right (save state)" nil) + (?0 resize-window--delete-window "Delete window (save state)" nil) + (?k resize-window--kill-other-windows "Delete other windows (save state)" nil) + (?s resize-window--window-push "Save state" nil) + (?y resize-window--restore-windows "Restore state (save state)" nil) + (?? resize-window--display-menu "Toggle help menu" nil)) "List of resize mode bindings. Main data structure of the dispatcher with the form: \(key function documentation allows-capitals\)") diff --git a/test/resize-window-test.el b/test/resize-window-test.el index 8185fd2..dc7f949 100644 --- a/test/resize-window-test.el +++ b/test/resize-window-test.el @@ -58,5 +58,5 @@ (resize-window-lowercase-argument))))) (ert-deftest resize-window--key-already-used-tests () - (should (resize-window--key-available? ?e)) + (should (resize-window--key-available? ?v)) (should-not (resize-window--key-available? ?n))) From ae07d0bde2b2ef43a7429bf7c4095f43dbe93865 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 14 Sep 2018 09:04:50 +0200 Subject: [PATCH 21/30] Capture key combinations like "M-w" Support symbol, character (integer), key text, or key sequence as key. NOTE: `read-key' cannot capture "M-w", use `read-key-sequence-vector'. --- resize-window.el | 132 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 31 deletions(-) diff --git a/resize-window.el b/resize-window.el index 65e272d..9234855 100644 --- a/resize-window.el +++ b/resize-window.el @@ -167,16 +167,78 @@ This is just a pass through to message usually. However, it can be overridden in tests to test the output of message." (when resize-window-notify-with-messages (apply #'message info))) +(defun resize-window--key-str (key) + "Return the string representation of KEY. +KEY is a symbol, character (integer), key text, or key sequence. + +For instance, ?n \"n\" [?n] [(?n)] are considered the same, and +?\\C-n \"C-n\" \"\\C-n\" [?\\C-n] [(?\\C-n)] [(control ?n)] too." + ;; NOTE: Fail loudly when KEY is wrong to help debugging. + (key-description + (cond + ((and (not (booleanp key)) + (or (symbolp key) (integerp key))) + (vector key)) + ((stringp key) + (kbd key)) + ((vectorp key) + key) + (t + (signal 'wrong-type-argument + `((symbolp integerp stringp vectorp) ,key)))))) + +(defun resize-window--keys-equal (&rest keys) + "Return non-nil if KEYS are considered equal. +If there is only one key return non-nil." + (let ((key-str (resize-window--key-str (car keys)))) + (not (cl-find-if-not + (lambda (k) + (string= key-str (resize-window--key-str k))) + (cdr keys))))) + +(defun resize-window--key-to-lower (key) + "Return the lowercase key sequence of KEY. +Return nil if KEY isn't an uppercase letter." + (let* ((key-str (resize-window--key-str key)) + (char (if (= (length key-str) 1) (string-to-char key-str)))) + (and char + (member char resize-window--capital-letters) + (vector (+ char 32))))) + +(defun resize-window--key-to-upper (key) + "Return the uppercase key sequence of KEY. +Return nil if KEY isn't an lowercase letter." + (let* ((key-str (resize-window--key-str key)) + (char (if (= (length key-str) 1) (string-to-char key-str)))) + (and char + (member char resize-window--lower-letters) + (vector (- char 32))))) + +(defun resize-window--key-element (key sequence) + "Return the first element in SEQUENCE whose car equals KEY." + (let ((key-str (resize-window--key-str key))) + (cl-assoc-if + (lambda (k) + (string= key-str (resize-window--key-str k))) + sequence))) + (defun resize-window--match-alias (key) - "Taken the KEY or keyboard selection from `read-key' check for alias. + "Taken the KEY or keyboard selection check for alias. Match the KEY against the alias table. If found, return the value that it points to, which should be a key in the `resize-window-dispatch-alist'. Otherwise, return the KEY." - (let ((alias (assoc key resize-window-alias-list))) + (let ((alias (resize-window--key-element + key resize-window-alias-list))) (if alias (car (cdr alias)) key))) +(defun resize-window--match-dispatch (key) + "Taken the KEY or keyboard selection check for an action. +Match the KEY against the alias table `resize-window-dispatch-alist'." + (resize-window--key-element + key resize-window-dispatch-alist)) + (defun resize-window--choice-keybinding (choice) "Get the keybinding associated with CHOICE." (car choice)) @@ -201,8 +263,12 @@ nil." CHOICE is a \(key function documentation allows-capitals\)." (let ((key (resize-window--choice-keybinding choice))) (concat (if (resize-window--allows-capitals choice) - (format "%s|%s" (string key) (string (- key 32))) - (format " %s " (string key))) + (format "%s|%s" + (resize-window--key-str key) + (resize-window--key-str + (resize-window--key-to-upper key))) + (format " %s " + (resize-window--key-str key))) " : " (resize-window--choice-documentation choice)))) @@ -238,7 +304,8 @@ CHOICE is a \(key function documentation allows-capitals\). If SCALED, then call action with the `resize-window-uppercase-argument'." (let ((action (resize-window--choice-lambda choice)) (description (resize-window--choice-documentation choice))) - (unless (equal (resize-window--choice-keybinding choice) ??) + (unless (resize-window--keys-equal + (resize-window--choice-keybinding choice) [??]) (resize-window--notify "%s" description)) (condition-case nil (if scaled @@ -247,7 +314,7 @@ If SCALED, then call action with the `resize-window-uppercase-argument'." (wrong-number-of-arguments (resize-window--notify "Invalid arity in function for %s" - (char-to-string + (resize-window--key-str (resize-window--choice-keybinding choice))))))) ;;;###autoload @@ -260,16 +327,17 @@ to resize right." ;; NOTE: Do not trim the stack here. Let stack requests to handle ;; window configurations in excess. (resize-window--add-backgrounds) - (resize-window--notify "Resize mode: enter character, ? for help") + (resize-window--notify "Resize mode: insert KEY, ? for help") (condition-case nil - (let ((reading-characters t) + (let ((reading-keys t) ;; allow mini-buffer to collapse after displaying menu (resize-mini-windows t)) - (while reading-characters - (let* ((char (resize-window--match-alias (read-key))) - (choice (assoc char resize-window-dispatch-alist)) - (capital (when (numberp char) - (assoc (+ char 32) resize-window-dispatch-alist)))) + (while reading-keys + (let* ((kin (read-key-sequence-vector nil nil t)) + (key (and kin (resize-window--match-alias kin))) + (choice (and key (resize-window--match-dispatch key))) + (lower (and key (resize-window--key-to-lower key))) + (capital (and lower (resize-window--match-dispatch lower)))) (cond (choice (resize-window--execute-action choice)) ((and capital (resize-window--allows-capitals capital)) @@ -277,21 +345,20 @@ to resize right." ;; with t and that method can worry about how to get that ;; action (resize-window--execute-action capital t)) - (;; NOTE: Don't use `=', if `char' is a symbol like - ;; 'insertchar it will fail. Use `equal' instead. - (or resize-window-unregistered-key-quit - (equal char ?q) - (equal char ?Q) - (equal char (string-to-char " "))) - (setq reading-characters nil) + ((or resize-window-unregistered-key-quit + (resize-window--keys-equal key [?q]) + (resize-window--keys-equal key [?Q]) + (resize-window--keys-equal key [? ]) + (resize-window--keys-equal key "C-g")) + (setq reading-keys nil) (message nil) (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)) (t (resize-window--notify (format - "Unregistered key: (%s) %s" - char (single-key-description char)))))))) + "Unregistered key: %s -> %s" + key (resize-window--key-str key)))))))) (quit (message nil) (resize-window--display-menu 'kill) @@ -560,24 +627,27 @@ See also `resize-window-stack-size'." (defun resize-window--key-available? (key) "Return non-nil if KEY is bound, otherwise return nil." - (and (not (assoc key resize-window-alias-list)) - (not (assoc key resize-window-dispatch-alist)))) + (and (not (resize-window--key-element + key resize-window-alias-list)) + (not (resize-window--key-element + key resize-window-dispatch-alist)))) (defun resize-window-add-choice (key func doc &optional allows-capitals) "Register a new binding for `resize-window'. Refuses to replace an already taken key. -KEY is the char (eg ?c) that should invoke the FUNC. DOC is a doc -string for the help menu, and optional ALLOWS-CAPITALS should be -t or nil. Functions should be of zero arity if they do not allow -capitals, and should be of optional single arity if they allow -capitals. Invoking with the capital will pass the capital -argument." +KEY is the key (e.g. ?c) that invokes the function FUNC. DOC is a +docstring for the help menu. A non-nil ALLOWS-CAPITALS tells FUNC +accepts capital letters. FUNC should be of zero arity if does not +allow capitals, otherwise to allow capitals should be of optional +single arity so a capital KEY may be passed to FUNC when pressed. + +See also `resize-window--key-str'." (if (resize-window--key-available? key) (push (list key func doc allows-capitals) resize-window-dispatch-alist) (message "The `%s` key is already taken for resize-window." - (char-to-string key)))) + (resize-window--key-str key)))) (provide 'resize-window) ;;; resize-window.el ends here From f9ce04c6f77be5d18166ad71b698c8a3cc41b93d Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 25 Sep 2018 13:31:34 +0200 Subject: [PATCH 22/30] Allow to forcibly replace a key binding --- resize-window.el | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/resize-window.el b/resize-window.el index 9234855..4899150 100644 --- a/resize-window.el +++ b/resize-window.el @@ -632,9 +632,9 @@ See also `resize-window-stack-size'." (not (resize-window--key-element key resize-window-dispatch-alist)))) -(defun resize-window-add-choice (key func doc &optional allows-capitals) +(defun resize-window-add-choice (key func doc &optional allows-capitals force) "Register a new binding for `resize-window'. -Refuses to replace an already taken key. +Refuses to replace an already taken key unless FORCE is non-nil. KEY is the key (e.g. ?c) that invokes the function FUNC. DOC is a docstring for the help menu. A non-nil ALLOWS-CAPITALS tells FUNC @@ -643,6 +643,15 @@ allow capitals, otherwise to allow capitals should be of optional single arity so a capital KEY may be passed to FUNC when pressed. See also `resize-window--key-str'." + (when force + (setq resize-window-alias-list + (delq (resize-window--key-element + key resize-window-alias-list) + resize-window-alias-list)) + (setq resize-window-dispatch-alist + (delq (resize-window--key-element + key resize-window-dispatch-alist) + resize-window-dispatch-alist))) (if (resize-window--key-available? key) (push (list key func doc allows-capitals) resize-window-dispatch-alist) From f4d625a0c05ff3f5807522b96a6f19fc21cbb5b7 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 3 Sep 2018 12:32:33 +0200 Subject: [PATCH 23/30] Persistent status message by timeout Spinloop the status notification if the minibuffer is active by suggestion of Dan Sutton (see `resize-window--notify-status'). --- resize-window.el | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/resize-window.el b/resize-window.el index 4899150..d518f91 100644 --- a/resize-window.el +++ b/resize-window.el @@ -161,11 +161,31 @@ Main data structure of the dispatcher with the form: "List of aliases for commands. Rather than have to use n, etc, you can alias keys for others.") +(defvar resize-window--notify-timers nil + "Notify callback timers.") + +(defun resize-window--cancel-notify () + "Cancel all the notify callback timers." + (mapc 'cancel-timer resize-window--notify-timers) + (setq resize-window--notify-timers nil)) + (defun resize-window--notify (&rest info) "Notify with INFO, a string or list (format-string object...). -This is just a pass through to message usually. However, it can be -overridden in tests to test the output of message." - (when resize-window-notify-with-messages (apply #'message info))) +Display the status message again after a timeout. +Can be overridden in tests to test the output." + (resize-window--cancel-notify) + (when resize-window-notify-with-messages + (message "Resize mode: %s" (apply #'format info)) + (push (run-with-timer 1.5 nil #'resize-window--notify-status) + resize-window--notify-timers))) + +(defun resize-window--notify-status () + "Display status message." + (when resize-window-notify-with-messages + (if (minibuffer-window-active-p (selected-window)) + (push (run-with-timer 1.5 nil #'resize-window--notify-status) + resize-window--notify-timers) + (message "Resize mode: insert KEY, ? for help, q or SPACE to quit")))) (defun resize-window--key-str (key) "Return the string representation of KEY. @@ -304,9 +324,7 @@ CHOICE is a \(key function documentation allows-capitals\). If SCALED, then call action with the `resize-window-uppercase-argument'." (let ((action (resize-window--choice-lambda choice)) (description (resize-window--choice-documentation choice))) - (unless (resize-window--keys-equal - (resize-window--choice-keybinding choice) [??]) - (resize-window--notify "%s" description)) + (resize-window--notify "%s" description) (condition-case nil (if scaled (funcall action (resize-window-uppercase-argument)) @@ -327,7 +345,7 @@ to resize right." ;; NOTE: Do not trim the stack here. Let stack requests to handle ;; window configurations in excess. (resize-window--add-backgrounds) - (resize-window--notify "Resize mode: insert KEY, ? for help") + (resize-window--notify-status) (condition-case nil (let ((reading-keys t) ;; allow mini-buffer to collapse after displaying menu @@ -352,6 +370,7 @@ to resize right." (resize-window--keys-equal key "C-g")) (setq reading-keys nil) (message nil) + (resize-window--cancel-notify) (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)) (t @@ -361,6 +380,7 @@ to resize right." key (resize-window--key-str key)))))))) (quit (message nil) + (resize-window--cancel-notify) (resize-window--display-menu 'kill) (resize-window--remove-backgrounds)))) From 05397fc8cbfbc3a695a7918539df6764257bc1b4 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 21 Sep 2018 09:32:25 +0200 Subject: [PATCH 24/30] Update copyright notice --- resize-window.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resize-window.el b/resize-window.el index d518f91..539dba4 100644 --- a/resize-window.el +++ b/resize-window.el @@ -1,8 +1,9 @@ ;;; resize-window.el --- Easily resize windows -*- lexical-binding: t; -*- -;; Copyright (C) 2015 Free Software Foundation, Inc. +;; Copyright (C) 2015-2018 Free Software Foundation, Inc. ;; Author: Dan Sutton +;; Author: Matthew White ;; Maintainer: Dan Sutton ;; URL: https://github.com/dpsutton/resize-mode From 133b1c6abe1a3638052a7ecd68ea8d8df61f9a1f Mon Sep 17 00:00:00 2001 From: Matthew White Date: Fri, 21 Sep 2018 09:41:21 +0200 Subject: [PATCH 25/30] Update license link in readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 2715c9a..5c57aee 100644 --- a/readme.md +++ b/readme.md @@ -5,6 +5,7 @@ [![MELPA](http://melpa.org/packages/resize-window-badge.svg)](http://melpa.org/#/resize-window) [![MELPA stable](http://stable.melpa.org/packages/resize-window-badge.svg)](http://stable.melpa.org/#/resize-window) [![Tag Version](https://img.shields.io/github/tag/dpsutton/resize-window.svg)](https://github.com/dpsutton/resize-window/tags) +[![License](https://img.shields.io/:license-gpl3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) ## What it is ## From 0f7c4fdf971c1c52b62918eab7128f97a04d1119 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Sat, 22 Sep 2018 05:50:06 +0200 Subject: [PATCH 26/30] Update .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c135d12..0e5b7bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,8 @@ matrix: sudo: required before_install: - curl -fsSkL https://gist.github.com/rejeep/7736123/raw > travis.sh && source ./travis.sh + - sudo apt-get update + - sudo apt-get install -y texinfo - evm install $EVM_EMACS --use --skip - cask From 79a29b5ca52eebd9f98db3be3535c055cb421523 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Sat, 22 Sep 2018 11:08:06 +0200 Subject: [PATCH 27/30] Stack forward/backward movements Window configurations stack utility functions. Suspend the notification if the minibuffer is active by suggestion of Dan Sutton (see `resize-window--notify'). Offers better compatibility with `ivy-switch-buffer' and commands which employ the minibuffer. Update: - readme.md --- readme.md | 33 ++- resize-window.el | 556 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 516 insertions(+), 73 deletions(-) diff --git a/readme.md b/readme.md index 5c57aee..b345a86 100644 --- a/readme.md +++ b/readme.md @@ -48,7 +48,8 @@ panes. Use `W` to cycle in the opposite direction. - `0`: Delete the current window. - `k`: Delete other windows and save the state on the stack. - `s`: Save the state on the stack so you may restore it later. -- `y`: Restore to a previous saved state. +- `>`: Restore to a previous saved state. Use `<` to restore in the +opposite direction. - `?`: Display the help menu listing commands. The best part of this is that resize-window keeps listening for more @@ -121,17 +122,37 @@ In this example, we can bounce back and forth between the test and code of resize-window. When we want to work in one exclusively, we call up resize-window (bound with `C-c ;` and then hit `k` for kill all the other windows. We edit our tests and then call up -resize-window and hit `y` for yank. Think that we just put them into a -ring buffer, but they are actually in a stack. +resize-window and hit `>` restore to a succeding saved state or `<` +for a preceding one. Think that we just put them into a ring buffer, +but they are actually in a stack. + +## The window configurations stack ## + +The stack is a customizable size holder for window configurations. It +folds over. Moving after the end restarts from the beginning and vice +versa. Old configurations are dropped due to a chosen reduction in its +size or an exceding number of configurations saved. + +Move forward/backward via `>` and `<` (to avoid pressing a modifier +key, you may consider `,` and `.` as possible alternatives). +Originally I was using `r` and `R` to move in the stack... + +Special flags give hints about the direction followed, forward `>` or +backward `<`, and if the current window configuration is modified `*` +or not `=` (aka saved in the stack at the current position). + +When a configuration is modified, adjacent positions in the stack are +considered to see if such new configuration is already there. In such +a case, modification flag and direction followed are set accordingly. ## Create windows ## Here, we want to create a bunch of windows. We can use `2` and `3` to make splits like their native emacs commands `C-x 2` and `C-x 3`. Use `0` to kill the split. If you want to go down to a single, use the -example above to hit `k` to kill all and then `y` to restore. Again, -all of the buffer resizing commands work (`f`, `p`, `b`, `n`) to -resize these buffers. +example above to hit `k` to kill all and then `>` or `<` to restore. +Again, all of the buffer resizing commands work (`f`, `p`, `b`, `n`) +to resize these buffers. ![usage gif](images/navigate.gif) diff --git a/resize-window.el b/resize-window.el index 539dba4..41a2ed8 100644 --- a/resize-window.el +++ b/resize-window.el @@ -54,7 +54,8 @@ ;; 0 : Delete the current window. ;; k : Delete other windows and save the state on the stack. ;; s : Save the state on the stack so you may restore it later. -;; y : Restore to a previous saved state. +;; > : Restore to a previous saved state. +;; < Restore in the opposite direction. ;; ? : Display the help menu listing commands. @@ -113,6 +114,18 @@ If nil do not quit and notify the unregistered key pressed." It is an alist of format ((configuration . time)...), where time is the time when the configuration was saved/visited.") +(defvar resize-window--config-modified nil + "Current window configuration modification flag. +It is non-nil if the configuration is new/modified. + +Use `resize-window--seek-config' to initialize.") + +(defvar resize-window--restore-forward t + "Current restore movement. +It is non-nil if restoring forward, otherwise restoring backward. + +Use `resize-window--seek-config' to initialize.") + (defface resize-window-background '((t (:foreground "gray40"))) "Face for when resizing window.") @@ -147,8 +160,9 @@ should return the fine adjustment (default 1)." (?3 resize-window--split-window-right "Split right (save state)" nil) (?0 resize-window--delete-window "Delete window (save state)" nil) (?k resize-window--kill-other-windows "Delete other windows (save state)" nil) - (?s resize-window--window-push "Save state" nil) - (?y resize-window--restore-windows "Restore state (save state)" nil) + (?s resize-window--window-save "Save state" nil) + (?> resize-window--restore-head "Restore succeding (save state)" nil) + (?< resize-window--restore-tail "Restore preceding (save state)" nil) (?? resize-window--display-menu "Toggle help menu" nil)) "List of resize mode bindings. Main data structure of the dispatcher with the form: @@ -165,6 +179,23 @@ Rather than have to use n, etc, you can alias keys for others.") (defvar resize-window--notify-timers nil "Notify callback timers.") +(defun resize-window--config-info () + "Return a string about the current window configuration. + +Combines >, <, *, =, to express respectively restoring forward, +backward, new/modified and unmodified current configuration. + +See also: +- `resize-window--restore-forward' +- `resize-window--config-modified'" + (let ((a (if resize-window--restore-forward ?> ?<)) + (b (if resize-window--config-modified ?* ?=))) + (when resize-window--restore-forward + (let ((tmp a)) + (setq a b) + (setq b tmp))) + (format "%c%c" a b))) + (defun resize-window--cancel-notify () "Cancel all the notify callback timers." (mapc 'cancel-timer resize-window--notify-timers) @@ -176,9 +207,19 @@ Display the status message again after a timeout. Can be overridden in tests to test the output." (resize-window--cancel-notify) (when resize-window-notify-with-messages - (message "Resize mode: %s" (apply #'format info)) - (push (run-with-timer 1.5 nil #'resize-window--notify-status) - resize-window--notify-timers))) + (let ((status (lambda (args) + (unless (minibuffer-window-active-p (selected-window)) + (message " [%s] Resize mode: %s" + (resize-window--config-info) + (apply #'format args)))))) + (funcall status info) + ;; FIXME: Subtle trick to update the status after side effects + ;; have been executed (i.e. a function has run). Using a timed + ;; callback for this is not ideal though. + (push (run-with-timer 0.2 nil status info) + resize-window--notify-timers) + (push (run-with-timer 1.5 nil #'resize-window--notify-status) + resize-window--notify-timers)))) (defun resize-window--notify-status () "Display status message." @@ -186,7 +227,8 @@ Can be overridden in tests to test the output." (if (minibuffer-window-active-p (selected-window)) (push (run-with-timer 1.5 nil #'resize-window--notify-status) resize-window--notify-timers) - (message "Resize mode: insert KEY, ? for help, q or SPACE to quit")))) + (message " [%s] Resize mode: insert KEY, ? for help, q or SPACE to quit" + (resize-window--config-info))))) (defun resize-window--key-str (key) "Return the string representation of KEY. @@ -343,6 +385,7 @@ Press n to resize down, p to resize up, b to resize left and f to resize right." (interactive) (resize-window--refresh-stack) + (resize-window--seek-config) ;; NOTE: Do not trim the stack here. Let stack requests to handle ;; window configurations in excess. (resize-window--add-backgrounds) @@ -392,7 +435,8 @@ If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) (let ((size (or size (resize-window-lowercase-argument))) (direction (if (window-in-direction 'below) 1 -1))) - (enlarge-window (* size direction))))) + (enlarge-window (* size direction)) + (resize-window--window-modified)))) (defun resize-window--resize-upward (&optional size) "Resize the window vertically upward by optional SIZE. @@ -400,7 +444,8 @@ If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) (let ((size (or size (resize-window-lowercase-argument))) (direction (if (window-in-direction 'below) -1 1))) - (enlarge-window (* size direction))))) + (enlarge-window (* size direction)) + (resize-window--window-modified)))) (defun resize-window--resize-forward (&optional size) "Resize the window horizontally forward by optional SIZE. @@ -408,7 +453,8 @@ If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) (let ((size (or size (resize-window-lowercase-argument))) (direction (if (window-in-direction 'right) 1 -1))) - (enlarge-window (* size direction) t)))) + (enlarge-window (* size direction) t) + (resize-window--window-modified)))) (defun resize-window--resize-backward (&optional size) "Resize the window horizontally backward by optional SIZE. @@ -416,21 +462,26 @@ If no SIZE is given, modify by `resize-window-default-argument'" (unless (frame-root-window-p (selected-window)) (let ((size (or size (resize-window-lowercase-argument))) (direction (if (window-in-direction 'right) -1 1))) - (enlarge-window (* size direction) t)))) + (enlarge-window (* size direction) t) + (resize-window--window-modified)))) (defun resize-window--reset-windows () "Reset window layout to even spread." - (resize-window--window-push) - (balance-windows)) + (when resize-window--config-modified + (resize-window--window-save)) + (balance-windows) + (resize-window--window-modified)) (defun resize-window--cycle-window-positive () "Cycle windows." (other-window 1) + (resize-window--window-modified) (resize-window--add-backgrounds)) (defun resize-window--cycle-window-negative () "Cycle windows negative." (other-window -1) + (resize-window--window-modified) (resize-window--add-backgrounds)) (defun resize-window--display-menu (&optional action) @@ -463,21 +514,27 @@ ACTION is a symbol of value 'kill or 'open." (defun resize-window--split-window-below () "Split the window vertically." - (resize-window--window-push) + (when resize-window--config-modified + (resize-window--window-save)) (split-window-below) + (resize-window--window-modified) (resize-window--add-backgrounds)) (defun resize-window--split-window-right () "Split the window horizontally." - (resize-window--window-push) + (when resize-window--config-modified + (resize-window--window-save)) (split-window-right) + (resize-window--window-modified) (resize-window--add-backgrounds)) (defun resize-window--delete-window () "Delete the current window." (unless (eq (selected-window) (window-main-window)) - (resize-window--window-push) + (when resize-window--config-modified + (resize-window--window-save)) (delete-window) + (resize-window--window-modified) (resize-window--add-backgrounds))) (defun resize-window--window-config () @@ -530,6 +587,40 @@ Do not change the current window configuration." (select-frame curr-frame)) some-config))) +(defun resize-window--seek-config (&optional config) + "Seek the most recent version of CONFIG in the stack. +If CONFIG is nil use the current window configuration. +If CONFIG is found return its stack member, otherwise return nil. + +If CONFIG is found, unset the `resize-window--config-modified' +flag and reorganize the stack with CONFIG as its first element +and all the elements before CONFIG as part of its tail. + +If CONFIG isn't found, set `resize-window--config-modified'. + +See also `resize-window--refresh-stack'." + (let ((curr-config (or config (resize-window--window-config))) + (curr-member nil) + (curr-svtime 0) + (head nil) + (tail nil)) + (dolist (this-member resize-window--window-stack) + (let ((this-config (car this-member)) + (this-svtime (cdr this-member))) + (when (and (compare-window-configurations curr-config this-config) + (time-less-p curr-svtime this-svtime)) + (setq curr-svtime this-svtime) + (setq curr-member this-member) + (setq tail (append tail head)) + (setq head nil)) + (setq head (append head (list this-member))))) + (when curr-member + (setq curr-member (cons curr-config (current-time))) + (setq resize-window--window-stack (append head tail)) + (setcar resize-window--window-stack curr-member)) + (setq resize-window--config-modified (not curr-member)) + curr-member)) + (defun resize-window--refresh-stack () "Refresh the stack and remove adjacent duplicates. Each window configuration is restored and saved again. @@ -556,11 +647,268 @@ what it finds, and so two configurations may end up the same." (push this-member stack-buffer))))) (setq resize-window--window-stack (nreverse stack-buffer)))) -(defun resize-window--window-trim () - "Trim the oldest window configurations in excess from the stack. -Return the removed stack members." +(defun resize-window--get-stack-head () + "Return the first member in the window configurations stack." + (car resize-window--window-stack)) + +(defun resize-window--pop-stack-head () + "Remove the first member from the window configurations stack. +Return the removed member." + (prog1 + (resize-window--get-stack-head) + (setq resize-window--window-stack + (cdr resize-window--window-stack)))) + +(defun resize-window--push-stack-head (element) + "Push ELEMENT to the begin of the window configurations stack. +Return ELEMENT." + (when element + (setq resize-window--window-stack + (append (list element) resize-window--window-stack)) + element)) + +(defun resize-window--set-stack-head (element) + "Replace the first member in the window configurations stack +with ELEMENT then return ELEMENT. +Push ELEMENT in the stack if the stack is empty." + (when element + (if resize-window--window-stack + (setcar resize-window--window-stack element) + (resize-window--push-stack-head element)))) + +(defun resize-window--stack-head-config (config) + "Replace the first configuration in the window configurations +stack with CONFIG then return CONFIG." + (when (and config resize-window--window-stack) + (setcar (resize-window--get-stack-head) config))) + +(defun resize-window--stack-head-svtime (save-time) + "Replace the first configuration's save time in the window +configurations stack to SAVE-TIME then return SAVE-TIME." + (when (and save-time resize-window--window-stack) + (setcdr (resize-window--get-stack-head) save-time))) + +(defun resize-window--get-stack-tail () + "Return the last member in the window configurations stack." + (car (last resize-window--window-stack))) + +(defun resize-window--pop-stack-tail () + "Remove the last member from the window configurations stack. +Return the removed member." + (prog1 + (resize-window--get-stack-tail) + (setq resize-window--window-stack + (nbutlast resize-window--window-stack)))) + +(defun resize-window--push-stack-tail (element) + "Push ELEMENT to the end of the window configurations stack. +Return ELEMENT." + (when element + (setq resize-window--window-stack + (append resize-window--window-stack (list element))) + element)) + +(defun resize-window--set-stack-tail (element) + "Replace the last member in the window configurations stack +with ELEMENT then return ELEMENT. +Push ELEMENT in the stack if the stack is empty." + (when element + (if resize-window--window-stack + (setcar (last resize-window--window-stack) element) + (resize-window--push-stack-tail element)))) + +(defun resize-window--stack-tail-config (config) + "Replace the last configuration in the window configurations +stack with CONFIG then return CONFIG." + (when (and config resize-window--window-stack) + (setcar (resize-window--get-stack-tail) config))) + +(defun resize-window--stack-tail-svtime (save-time) + "Replace the last configuration's save time in the window +configurations stack with SAVE-TIME then return SAVE-TIME." + (when (and save-time resize-window--window-stack) + (setcdr (resize-window--get-stack-tail) save-time))) + +(defun resize-window--get-stack-member (&optional from-tail) + "Return the first member in the window configurations stack. +If FROM-TAIL is non-nil return the last member intead." + (if from-tail + (resize-window--get-stack-tail) + (resize-window--get-stack-head))) + +(defun resize-window--pop-stack-member (&optional from-tail) + "Remove the first member from the window configurations stack. +If FROM-TAIL is non-nil remove the last member instead. +Return the removed member." + (if from-tail + (resize-window--pop-stack-tail) + (resize-window--pop-stack-head))) + +(defun resize-window--push-stack-member (element &optional to-tail) + "Push ELEMENT to the begin of the window configurations stack. +If TO-TAIL is non-nil push to the end. +Return ELEMENT." + (if to-tail + (resize-window--push-stack-tail element) + (resize-window--push-stack-head element))) + +(defun resize-window--set-stack-member (element &optional to-tail) + "Replace the first member in the window configurations stack +with ELEMENT then return ELEMENT. +If TO-TAIL is non-nil replace the last." + (if to-tail + (resize-window--set-stack-tail element) + (resize-window--set-stack-head element))) + +(defun resize-window--stack-member-config (config &optional to-tail) + "Replace the first configuration in the window configurations +stack with CONFIG then return CONFIG. +If TO-TAIL is non-nil replace the last." + (if to-tail + (resize-window--stack-tail-config config) + (resize-window--stack-head-config config))) + +(defun resize-window--stack-member-svtime (save-time &optional to-tail) + "Replace the first configuration's save time in the window +configurations stack with SAVE-TIME then return SAVE-TIME. +If TO-TAIL is non-nil replace the last." + (if to-tail + (resize-window--stack-tail-svtime save-time) + (resize-window--stack-head-svtime save-time))) + +(defun resize-window--get-stack-nth (n) + "Return the Nth member in the window configurations stack. +If N is negative count from the end, where -1 is the end. +Return nil if N is out of bound." + (let* ((stack-size (length resize-window--window-stack)) + (stack-last (1- stack-size)) + (x (if (< n 0) (+ stack-size n) n))) + (and (>= x 0) + (<= x stack-last) + (nth x resize-window--window-stack)))) + +(defun resize-window--pop-stack-nth (n) + "Remove the Nth member from the window configurations stack. +If N is negative count from the end, where -1 is the end. +Return the removed member or nil if N is out of bound." + (let* ((stack-size (length resize-window--window-stack)) + (stack-last (1- stack-size)) + (x (if (< n 0) (+ stack-size n) n))) + (and (>= x 0) + (<= x stack-last) + (pop (nthcdr x resize-window--window-stack))))) + +(defun resize-window--push-stack-nth (element n) + "Push ELEMENT as the Nth member in the window configurations +stack then return ELEMENT or nil if N is out of bound. +If N is negative count from the end, where -1 is the end." + (let* ((stack-size (length resize-window--window-stack)) + (stack-last (1- stack-size)) + (x (if (< n 0) (+ stack-size n 1) n))) + (when (and element (>= x 0) (<= x stack-size)) + (let ((head (butlast resize-window--window-stack (- stack-size x))) + (tail (nthcdr x resize-window--window-stack))) + (setq resize-window--window-stack + (append head (list element) tail)) + element)))) + +(defun resize-window--set-stack-nth (element n) + "Replace the Nth member in the window configurations stack +with ELEMENT then return ELEMENT or nil if N is out of bound. +Push ELEMENT in the stack if the stack is empty with N 0 or -1. +If N is negative count from the end, where -1 is the end." + (let* ((stack-size (length resize-window--window-stack)) + (stack-last (if (> stack-size 0) (1- stack-size) 0)) + (x (if (< n 0) (+ stack-size n (if (> stack-size 0) 0 1)) n))) + (when (and element (>= x 0) (<= x stack-last)) + (let ((head (butlast resize-window--window-stack (- stack-size x))) + (tail (nthcdr (1+ x) resize-window--window-stack))) + (setq resize-window--window-stack + (append head (list element) tail)) + element)))) + +(defun resize-window--stack-nth-config (config n) + "Replace the Nth configuration in the window configurations stack +with CONFIG then return CONFIG or nil if N is out of bound. +If N is negative count from the end, where -1 is the end." + (let ((element (resize-window--get-stack-nth n))) + (when element + (setcar element config)))) + +(defun resize-window--stack-nth-svtime (save-time n) + "Replace the Nth configuration's save time in the window configurations +stack with SAVE-TIME then return SAVE-TIME or nil if N is out of bound. +If N is negative count from the end, where -1 is the end." + (let ((element (resize-window--get-stack-nth n))) + (when element + (setcdr element save-time)))) + +(defun resize-window--stack-shift-head () + "Shift right in the window configurations stack. +Return the member shifted to if any. + +Pop the head and push it to the tail in the stack. It is like +scrolling right in the stack. The next member becomes the first." + (let ((element (resize-window--pop-stack-head))) + (when element + (resize-window--push-stack-tail element)))) + +(defun resize-window--stack-shift-tail () + "Shift left in the window configurations stack. +Return the member shifted to if any. + +Pop the tail and push it to the head in the stack. It is like +scrolling left in the stack. The last member becomes the first." + (let ((element (resize-window--pop-stack-tail))) + (when element + (resize-window--push-stack-head element)))) + +(defun resize-window--stack-shift-member (&optional to-tail) + "Shitf right in the window configurations stack. +If TO-TAIL is non-nil shift left. +Return the member shifted to if any. + +Move the head to the tail like scrolling right, or move the tail +to the head like scrolling left if TO-TAIL is non-nil." + (if to-tail + (resize-window--stack-shift-tail) + (resize-window--stack-shift-head))) + +(defun resize-window--stack-config-modified (&optional config from-tail) + "Return non-nil if CONFIG differs from the stack first configuration. +If CONFIG is nil use the current window configuration. +If FROM-TAIL is non-nil check the last configuration." + (let* ((curr-config (or config (resize-window--window-config))) + (this-member (if from-tail + (resize-window--get-stack-tail) + (resize-window--get-stack-head))) + (this-config (car this-member))) + (or (not (setq this-config (resize-window--apply-config this-config))) + (not (compare-window-configurations curr-config this-config))))) + +(defun resize-window--trim-stack-dups (&optional config from-tail) + "Trim consecutive duplicates of CONFIG from the beginning of +the stack or from the end if FROM-TAIL is non-nil. +If CONFIG is nil use the current window configuration. +Return the first stack member that differs from CONFIG." + (let ((curr-config (or config (resize-window--window-config))) + (this-member nil) + (this-config nil)) + (while + (and (setq this-member + (resize-window--get-stack-member from-tail)) + (setq this-config (car this-member)) + (setq this-config (resize-window--apply-config this-config)) + (compare-window-configurations curr-config this-config) + (resize-window--pop-stack-member from-tail))) + this-member)) + +(defun resize-window--window-trim (&optional stack-size) + "Trim the oldest window configurations from the stack in +excess of STACK-SIZE then return the removed stack members. +If STACK-SIZE is nil use `resize-window-stack-size'." (let* ((size (length resize-window--window-stack)) - (trim (- size resize-window-stack-size))) + (trim (- size (or stack-size resize-window-stack-size)))) (when (> trim 0) (let ((oldest-members (sort (copy-sequence resize-window--window-stack) @@ -574,72 +922,146 @@ Return the removed stack members." (delq old-member resize-window--window-stack)))) oldest-members)))) -(defun resize-window--window-push () +(defun resize-window--window-save () "Save the current window configuration in the stack. -Return its stack member if the configuration is saved. +If the configuration is saved return its stack member. + +If the configuration isn't modified, replace the previously saved +configuration, otherwise save the configuration in respect to the +`resize-window--restore-forward' flag, either after or before the +first element of the stack. -Trim adjacent duplicates and old configurations when necessary. +Set `resize-window--config-modified' to the configuration state. -See also `resize-window-stack-size'." +Trim adjacent duplicates and old configurations when necessary to +fit `resize-window-stack-size'." (let* ((curr-config (resize-window--window-config)) (curr-member (cons curr-config (current-time))) - (prev-config nil)) - ;; trim duplicates from the tail - (while (and (setq prev-config (caar (last resize-window--window-stack))) - (setq prev-config (resize-window--apply-config prev-config)) - (compare-window-configurations curr-config prev-config) - (setq resize-window--window-stack - (nbutlast resize-window--window-stack)))) - ;; trim duplicates from the head - (while (and (setq prev-config (caar resize-window--window-stack)) - (setq prev-config (resize-window--apply-config prev-config)) - (compare-window-configurations curr-config prev-config) - (pop resize-window--window-stack))) - (push curr-member resize-window--window-stack) + (head-change (resize-window--stack-config-modified curr-config))) + (when (and head-change resize-window--restore-forward) + (let* ((this-member (resize-window--pop-stack-member)) + (this-config (car this-member))) + (resize-window--trim-stack-dups this-config) + (resize-window--trim-stack-dups this-config t) + (resize-window--push-stack-member this-member t))) + (resize-window--trim-stack-dups curr-config) + (resize-window--trim-stack-dups curr-config t) + (resize-window--push-stack-member curr-member) (resize-window--window-trim) - (when resize-window--window-stack + (setq resize-window--config-modified + (resize-window--stack-config-modified)) + (when (eq curr-member (resize-window--get-stack-member)) curr-member))) -(defun resize-window--window-pop () - "Shift to a previous window configuration. -Return the configuration shifted to if any. +(defun resize-window--window-shift (&optional to-tail) + "Shift to the next window configuration in the stack. +If TO-TAIL is non-nil shift to the left, otherwise to the right. +Return the stack member of the configuration shifted to if any. + +If the current configuration is modified, save the configuration +via `resize-window--window-save' before shifting. + +Set `resize-window--restore-forward' accordingly to the shift and +set `resize-window--config-modified' to the configuration state. + +Trim adjacent duplicates and old configurations when necessary to +fit `resize-window-stack-size'." + (when (resize-window--stack-config-modified) + (resize-window--window-save)) + (let* ((curr-member (resize-window--get-stack-member)) + (this-member (resize-window--pop-stack-member to-tail)) + (this-config (resize-window--apply-config (car this-member))) + (head-member (resize-window--trim-stack-dups this-config)) + (tail-member (resize-window--trim-stack-dups this-config t)) + (next-member (if to-tail + this-member + head-member)) + (next-config (if to-tail + this-config + (resize-window--apply-config (car next-member))))) + (resize-window--push-stack-member this-member (not to-tail)) + (if (eq curr-member next-member) + (setq next-member nil) + (resize-window--stack-member-config next-config) + (resize-window--stack-member-svtime (current-time))) + (resize-window--window-trim) + (setq resize-window--restore-forward (not to-tail)) + (setq resize-window--config-modified + (resize-window--stack-config-modified)) + next-member)) + +(defun resize-window--window-modified () + "Seek the adjacent window configuration equal to the current. +Return the matched stack member if found, otherwise return nil. -Save the current configuration to the tail of the stack. +Configurations adjacent to the current are the first, next, or +last in the stack. -Trim adjacent duplicates and old configurations when necessary. +If a match is found unset `resize-window--config-modified' and +reorganize the stack with the match as its first element. If a +match isn't found set `resize-window--config-modified'. -See also `resize-window-stack-size'." - (resize-window--window-trim) +Also, if a match is found set `resize-window--restore-forward' +accordingly in respect to where the match was found, before or +after the first configuration in the stack." (let* ((curr-config (resize-window--window-config)) (curr-member (cons curr-config (current-time))) - (prev-config nil)) - ;; trim duplicates from the tail - (while (and (setq prev-config (caar (last resize-window--window-stack))) - (setq prev-config (resize-window--apply-config prev-config)) - (compare-window-configurations curr-config prev-config) - (setq resize-window--window-stack - (nbutlast resize-window--window-stack)))) - ;; trim duplicates from the head - (while (and (setq prev-config (car (pop resize-window--window-stack))) - (setq prev-config (resize-window--apply-config prev-config)) - (compare-window-configurations curr-config prev-config))) - (when prev-config - (setq resize-window--window-stack - (append resize-window--window-stack (list curr-member))) - (resize-window--restore-config prev-config) - prev-config))) + (head-member (resize-window--get-stack-nth 0)) + (next-member (resize-window--get-stack-nth 1)) + (tail-member (resize-window--get-stack-nth -1)) + (find-config + (lambda (this-member) + (let ((this-config (car this-member))) + (when this-config + (setq this-config (resize-window--apply-config this-config)) + (compare-window-configurations curr-config this-config))))) + head-equals next-equals tail-equals) + (when (eq next-member tail-member) + (if resize-window--restore-forward + (setq tail-member nil) + (setq next-member nil))) + (if resize-window--restore-forward + (or (setq head-equals (funcall find-config head-member)) + (setq next-equals (funcall find-config next-member)) + (setq tail-equals (funcall find-config tail-member))) + (or (setq head-equals (funcall find-config head-member)) + (setq tail-equals (funcall find-config tail-member)) + (setq next-equals (funcall find-config next-member)))) + (setq resize-window--config-modified + (not (or head-equals next-equals tail-equals))) + (unless resize-window--config-modified + (unless head-equals + (setq resize-window--restore-forward next-equals) + (resize-window--stack-shift-member (not next-equals))) + (resize-window--set-stack-member curr-member)))) (defun resize-window--kill-other-windows () "Delete other windows." - (resize-window--window-push) + (when resize-window--config-modified + (resize-window--window-save)) (delete-other-windows) + (resize-window--window-modified) (resize-window--add-backgrounds)) -(defun resize-window--restore-windows () - "Restore the previous state." - (let ((config (resize-window--window-pop))) - (when config - (resize-window--restore-config config)))) +(defun resize-window--restore-head () + "Restore a succeding state. +Set `resize-window--config-modified' to the configuration state." + (let* ((prev-member (resize-window--window-shift)) + (prev-config (car prev-member))) + (when prev-config + (resize-window--restore-config prev-config) + (setq resize-window--config-modified + (resize-window--stack-config-modified))))) + +(defun resize-window--restore-tail () + "Restore a preceding state. +Set `resize-window--config-modified' to the configuration state." + (let* ((prev-member (resize-window--window-shift t)) + (prev-config (car prev-member))) + (when prev-config + (resize-window--restore-config prev-config) + (setq resize-window--config-modified + (resize-window--stack-config-modified))))) (defvar resize-window--capital-letters (number-sequence ?A ?Z) "List of uppercase letters as characters.") From 2b2c821bfe15026c77569fa710ed073e7ddc34b2 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Mon, 24 Sep 2018 19:28:23 +0200 Subject: [PATCH 28/30] Drop current configuration from the stack --- readme.md | 1 + resize-window.el | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b345a86..c970bc1 100644 --- a/readme.md +++ b/readme.md @@ -47,6 +47,7 @@ panes. Use `W` to cycle in the opposite direction. - `3`: Split the window vertically. - `0`: Delete the current window. - `k`: Delete other windows and save the state on the stack. +- `x`: Drop the current saved state. Switch to another one. - `s`: Save the state on the stack so you may restore it later. - `>`: Restore to a previous saved state. Use `<` to restore in the opposite direction. diff --git a/resize-window.el b/resize-window.el index 41a2ed8..1adb4ab 100644 --- a/resize-window.el +++ b/resize-window.el @@ -53,6 +53,7 @@ ;; 3 : Split the window vertically. ;; 0 : Delete the current window. ;; k : Delete other windows and save the state on the stack. +;; x : Drop the current saved state. Switch to another one. ;; s : Save the state on the stack so you may restore it later. ;; > : Restore to a previous saved state. ;; < Restore in the opposite direction. @@ -160,6 +161,7 @@ should return the fine adjustment (default 1)." (?3 resize-window--split-window-right "Split right (save state)" nil) (?0 resize-window--delete-window "Delete window (save state)" nil) (?k resize-window--kill-other-windows "Delete other windows (save state)" nil) + (?x resize-window--window-drop "Drop state" nil) (?s resize-window--window-save "Save state" nil) (?> resize-window--restore-head "Restore succeding (save state)" nil) (?< resize-window--restore-tail "Restore preceding (save state)" nil) @@ -367,7 +369,10 @@ CHOICE is a \(key function documentation allows-capitals\). If SCALED, then call action with the `resize-window-uppercase-argument'." (let ((action (resize-window--choice-lambda choice)) (description (resize-window--choice-documentation choice))) - (resize-window--notify "%s" description) + (if (resize-window--keys-equal + (resize-window--choice-keybinding choice) [?x]) + (resize-window--cancel-notify) + (resize-window--notify "%s" description)) (condition-case nil (if scaled (funcall action (resize-window-uppercase-argument)) @@ -922,6 +927,33 @@ If STACK-SIZE is nil use `resize-window-stack-size'." (delq old-member resize-window--window-stack)))) oldest-members)))) +(defun resize-window--window-drop () + "Drop the current window configuration from the stack. +Ask the user for confirmation then return the removed member. + +Abort if the configuration isn't in the stack or the user decided +otherwise. If the configuration is dropped, switch to another one +in respect to `resize-window--restore-forward' direction flag." + (if (or (resize-window--stack-config-modified) + (not (let ((query-replace-map (copy-keymap query-replace-map))) + (define-key query-replace-map [? ] 'skip) + (y-or-n-p "Drop saved state? ")))) + (resize-window--notify-status) + (prog1 + (resize-window--pop-stack-member) + (unless resize-window--restore-forward + (resize-window--stack-shift-member t)) + (let* ((curr-member (resize-window--get-stack-member)) + (curr-config (car curr-member))) + (when curr-config + (resize-window--restore-config curr-config) + (setq curr-config (resize-window--window-config)) + (resize-window--stack-member-config curr-config) + (resize-window--stack-member-svtime (current-time)))) + (setq resize-window--config-modified + (resize-window--stack-config-modified)) + (resize-window--notify "Drop saved state")))) + (defun resize-window--window-save () "Save the current window configuration in the stack. If the configuration is saved return its stack member. From f94e216517104a20ce908f38668c8a0d29ea2ba0 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Tue, 25 Sep 2018 09:33:59 +0200 Subject: [PATCH 29/30] Update readme.md with stack details --- readme.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/readme.md b/readme.md index c970bc1..b0d8058 100644 --- a/readme.md +++ b/readme.md @@ -200,3 +200,117 @@ bug)). If lower case matches, do it, if uppercase matches something, then make sure that's ok and do it but with the `resize-window-uppercase-argument` rather than `resize-window-lowercase-argument`. + +### More about the window configurations stack ## + +Some explanations are due about the stack. Here are some details you +may find interesting and clarifying. + +## Current window configuration ## + +The current window configuration is what you are looking at in the +screen. Think of it like a windows layout. It may be or may be not +equal to the first element of the stack. The former means it is +unmodified, hence it is equal to a saved configuration in the +stack. The latter means that it is modified, and not saved in the +stack yet. The current window configuration is modified when a window +is switched, split, resized, or deleted. + +A modified current window configuration prints the modification flag +`*` to the notification area and an unmodified one prints `=`. + +## Stack navigation: shifting elements ## + +The stack is a spinning wheel, with no real beginning or end. The +active element is the first one. Movements along the stack consist +into shifting the elements. Hitting `<` (shift left) pops the last +element and pushes it to the beginning of the stack, making it the +first. Hitting `>` (shift right) pops the first element and pushes +it to the end of the stack, making the second element the first. + +Shifting left prints the direction flag `<` to the notification area +and shifting right prints `>`. + +## Stack update: auto shifting elements ## + +When the current window configuration is modified, a smart system +checks the elements in the stack adjacent to the first to see if they +are equal to the modified configuration. The direction flag suggests +the order of comparison, i.e. start from the last element when it is +`<` or from the second when it is `>`, until a match is found or both +elements are tested. If a match is found, the stack is shifted in the +direction of the match (like you hit the corresponding key binding), +the modification flag is unset, and the direction flag is set to the +shifting direction. + +## Stack update: saving a window configuration ## + +When the smart system did not find a match, a modified configuration +may be automatically saved in the stack if trying to modify it again. +This also triggers the smart system to search in the stack the newly +modification obtained. Please note that resizing a window is exempted +from automatically saving a modified configuration. + +Pressing `s`, `<`, or `>` will also save the modified configuration. + +The direction flag dictates where configurations are saved. If it is +`<`, save the configuration as the first element. If it is `>`, save +the configuration as the second element, then shift right. This is to +give you an insight on how to step back to the previous configuration. +For instance, if there is only a single window and the direction flag +is `>`, when you split the window and hit `<` you will be back to the +single window... suppose there was more than one configuration in the +stack to begin with... If the direction flag was `<`, a split and hit +on `>` will still get you back to the single window, but hitting `<` +instead would have shifted to the configuration expected to be found +on the left of the single window. + +``` + config A config C + ----- ----- + | A | |-C-| initial condition, unmodified configuration A + ----- ----- + + notification area: [=>] (modified flag is =, direction flag is >) + + + hit 3 and split A into + modified configuration + (set modified flag: *) + / | + / | + 3 planned + | insertion + | / hit s to save the modified configuration or + ----- / ----- you may even hit < to save and step back to A + | A | ----- |-C-| + ----- | | | ----- here (silent right shift [*>] from A on save) + \ ----- / + \ / + saving pushes it + + notification area: [*>] (modified flag is *, direction flag is >) + + + final result + ----- ----- ----- + | | | |-C-| | A | to step back to A hit < + ----- ----- ----- + + notification area: [=>] (modified flag is =, direction flag is >) + + + shifting left + ----- ----- ----- + | A | | | | |-C-| stepped back to A, the direction flag changed + ----- ----- ----- + + notification area: [<=] (modified flag is =, direction flag is <) + +``` + +## Stack update: dropping a window configuration ## + +When a configuration is dropped from the stack, the stack is shifted +in the direction dictated by the direction flag, like pressing either +`<` or `>`. From c02a3c8e8afa4f17f1cad4e31d522ed7e3d959cc Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 27 Sep 2018 19:23:44 +0200 Subject: [PATCH 30/30] Frame aware stack management Save window configurations per frame in the stack, which allows to scroll the saved window configurations of the selected frame only. Update: - readme.md --- readme.md | 3 + resize-window.el | 507 +++++++++++++++++++++++++++++------------------ 2 files changed, 319 insertions(+), 191 deletions(-) diff --git a/readme.md b/readme.md index b0d8058..2ef0dbb 100644 --- a/readme.md +++ b/readme.md @@ -134,6 +134,9 @@ folds over. Moving after the end restarts from the beginning and vice versa. Old configurations are dropped due to a chosen reduction in its size or an exceding number of configurations saved. +Each frame has its own personalized stack. Scrolling the stack or one +frame will not alter the window configuration of other frames. + Move forward/backward via `>` and `<` (to avoid pressing a modifier key, you may consider `,` and `.` as possible alternatives). Originally I was using `r` and `R` to move in the stack... diff --git a/resize-window.el b/resize-window.el index 1adb4ab..65bdc18 100644 --- a/resize-window.el +++ b/resize-window.el @@ -109,11 +109,11 @@ If nil do not quit and notify the unregistered key pressed." (defvar resize-window--background-overlays () "List of background overlays.") -(defvar resize-window--window-stack () - "Stack for holding window configurations. +(defvar resize-window--window-stacks () + "List of frame stacks for holding window configurations. -It is an alist of format ((configuration . time)...), where time -is the time when the configuration was saved/visited.") +List of alists of format ((frame . ((configuration . time)...))), +time is the time when the configuration was saved/visited.") (defvar resize-window--config-modified nil "Current window configuration modification flag. @@ -389,7 +389,7 @@ If SCALED, then call action with the `resize-window-uppercase-argument'." Press n to resize down, p to resize up, b to resize left and f to resize right." (interactive) - (resize-window--refresh-stack) + (resize-window--refresh-stacks) (resize-window--seek-config) ;; NOTE: Do not trim the stack here. Let stack requests to handle ;; window configurations in excess. @@ -592,9 +592,10 @@ Do not change the current window configuration." (select-frame curr-frame)) some-config))) -(defun resize-window--seek-config (&optional config) - "Seek the most recent version of CONFIG in the stack. +(defun resize-window--seek-config (&optional config frame) + "Seek the most recent version of CONFIG in FRAME's stack. If CONFIG is nil use the current window configuration. +If FRAME is nil use the current frame. If CONFIG is found return its stack member, otherwise return nil. If CONFIG is found, unset the `resize-window--config-modified' @@ -603,13 +604,15 @@ and all the elements before CONFIG as part of its tail. If CONFIG isn't found, set `resize-window--config-modified'. -See also `resize-window--refresh-stack'." - (let ((curr-config (or config (resize-window--window-config))) - (curr-member nil) - (curr-svtime 0) - (head nil) - (tail nil)) - (dolist (this-member resize-window--window-stack) +See also `resize-window--refresh-stacks'." + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (curr-config (or config (resize-window--window-config))) + (curr-member nil) + (curr-svtime 0) + (head nil) + (tail nil)) + (dolist (this-member stack) (let ((this-config (car this-member)) (this-svtime (cdr this-member))) (when (and (compare-window-configurations curr-config this-config) @@ -621,13 +624,22 @@ See also `resize-window--refresh-stack'." (setq head (append head (list this-member))))) (when curr-member (setq curr-member (cons curr-config (current-time))) - (setq resize-window--window-stack (append head tail)) - (setcar resize-window--window-stack curr-member)) + (setq stack (append head tail)) + (setcar stack curr-member) + (setcdr frame-stack stack)) (setq resize-window--config-modified (not curr-member)) curr-member)) -(defun resize-window--refresh-stack () - "Refresh the stack and remove adjacent duplicates. +(defun resize-window--refresh-stacks () + "Refresh the stack for all the frames. + +See also `resize-window--refresh-frame-stack'." + (dolist (frame-stack resize-window--window-stacks) + (let ((frame (car frame-stack))) + (resize-window--refresh-frame-stack frame)))) + +(defun resize-window--refresh-frame-stack (&optional frame) + "Refresh FRAME's stack and remove adjacent duplicates. Each window configuration is restored and saved again. The configurations saved time is not changed. Always remove the @@ -637,9 +649,11 @@ A refresh reveals duplicate configurations. When a configuration is restored that takes account of the current state of the frame. Since killed buffers cannot be dug up, applying a state will use what it finds, and so two configurations may end up the same." - (let (stack-buffer) - (dotimes (n (length resize-window--window-stack)) - (let* ((this-member (nth n resize-window--window-stack)) + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (stack-buffer nil)) + (dotimes (n (length stack)) + (let* ((this-member (nth n stack)) (this-config (resize-window--apply-config (car this-member))) (this-svtime (cdr this-member)) (prev-config (caar stack-buffer)) @@ -650,281 +664,392 @@ what it finds, and so two configurations may end up the same." (setcar stack-buffer this-member)) (when this-config (push this-member stack-buffer))))) - (setq resize-window--window-stack (nreverse stack-buffer)))) + (setq stack (nreverse stack-buffer)) + (if stack + (setcdr frame-stack stack) + (resize-window--del-frame-stack frame)))) + +(defun resize-window--frame-stack (&optional frame) + "Return the FRAME's window configurations stack. +If FRAME is nil use the current frame." + (when (setq frame (or frame (selected-frame))) + (assq frame resize-window--window-stacks))) + +(defun resize-window--del-frame-stack (&optional frame) + "Remove FRAME's window configurations stack. +If FRAME is nil use the current frame. +Return the removed FRAME's stack." + (let ((frame-stack (resize-window--frame-stack frame))) + (when frame-stack + (setq resize-window--window-stacks + (delq frame-stack resize-window--window-stacks)) + frame-stack))) + +(defun resize-window--get-stack-head (&optional frame) + "Return the first member in FRAME's window configurations stack. +If FRAME is nil use the current frame." + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack))) + (car stack))) + +(defun resize-window--pop-stack-head (&optional frame) + "Remove the first member from FRAME's window configurations stack. +If FRAME is nil use the current frame. +Return the removed member." + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (this-member (car stack)) + (rest-members (cdr stack))) + (when (consp frame-stack) + (if rest-members + (setcdr frame-stack rest-members) + (resize-window--del-frame-stack frame)) + this-member))) + +(defun resize-window--push-stack-head (element &optional frame) + "Push ELEMENT to the begin of FRAME's window configurations stack. +If FRAME is nil use the current frame. +Return ELEMENT." + (when (and element (setq frame (or frame (selected-frame)))) + (let* ((frame-stack + (or (resize-window--frame-stack frame) + (car (push (list frame) + resize-window--window-stacks)))) + (stack (cdr frame-stack))) + (car (setcdr frame-stack + (append (list element) stack)))))) + +(defun resize-window--set-stack-head (element &optional frame) + "Replace the first member in FAME's window configurations stack +with ELEMENT then return ELEMENT. +Push ELEMENT in the stack if the stack is empty. +If FRAME is nil use the current frame." + (when (and element (setq frame (or frame (selected-frame)))) + (let* ((frame-stack + (or (resize-window--frame-stack frame) + (car (push (list frame) + resize-window--window-stacks)))) + (stack (cdr frame-stack))) + (if (consp stack) + (setcar stack element) + (car (setcdr frame-stack (list element))))))) + +(defun resize-window--stack-head-config (config &optional frame) + "Replace the first configuration in FRAME's window configurations +stack with CONFIG then return CONFIG. +If FRAME is nil use the current frame." + (let ((element (resize-window--get-stack-head frame))) + (when (and config element) + (setcar element config)))) + +(defun resize-window--stack-head-svtime (save-time &optional frame) + "Replace the first configuration's save time in FRAME's window +configurations stack to SAVE-TIME then return SAVE-TIME. +If FRAME is nil use the current frame." + (let ((element (resize-window--get-stack-head frame))) + (when (and save-time element) + (setcdr element save-time)))) -(defun resize-window--get-stack-head () - "Return the first member in the window configurations stack." - (car resize-window--window-stack)) +(defun resize-window--get-stack-tail (&optional frame) + "Return the last member in FRAME's window configurations stack. +If FRAME is nil use the current frame." + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack))) + (car (last stack)))) -(defun resize-window--pop-stack-head () - "Remove the first member from the window configurations stack. +(defun resize-window--pop-stack-tail (&optional frame) + "Remove the last member from FRAME's window configurations stack. +If FRAME is nil use the current frame. Return the removed member." - (prog1 - (resize-window--get-stack-head) - (setq resize-window--window-stack - (cdr resize-window--window-stack)))) - -(defun resize-window--push-stack-head (element) - "Push ELEMENT to the begin of the window configurations stack. + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (this-member (car (last stack))) + (rest-members (nbutlast stack))) + (when (consp frame-stack) + (if rest-members + (setcdr frame-stack rest-members) + (resize-window--del-frame-stack frame)) + this-member))) + +(defun resize-window--push-stack-tail (element &optional frame) + "Push ELEMENT to the end of FRAME's window configurations stack. +If FRAME is nil use the current frame. Return ELEMENT." - (when element - (setq resize-window--window-stack - (append (list element) resize-window--window-stack)) - element)) - -(defun resize-window--set-stack-head (element) - "Replace the first member in the window configurations stack + (when (and element (setq frame (or frame (selected-frame)))) + (let* ((frame-stack + (or (resize-window--frame-stack frame) + (car (push (list frame) + resize-window--window-stacks)))) + (stack (cdr frame-stack))) + (car (last (setcdr frame-stack + (append stack (list element)))))))) + +(defun resize-window--set-stack-tail (element &optional frame) + "Replace the last member in FRAME's window configurations stack with ELEMENT then return ELEMENT. -Push ELEMENT in the stack if the stack is empty." - (when element - (if resize-window--window-stack - (setcar resize-window--window-stack element) - (resize-window--push-stack-head element)))) - -(defun resize-window--stack-head-config (config) - "Replace the first configuration in the window configurations -stack with CONFIG then return CONFIG." - (when (and config resize-window--window-stack) - (setcar (resize-window--get-stack-head) config))) - -(defun resize-window--stack-head-svtime (save-time) - "Replace the first configuration's save time in the window -configurations stack to SAVE-TIME then return SAVE-TIME." - (when (and save-time resize-window--window-stack) - (setcdr (resize-window--get-stack-head) save-time))) - -(defun resize-window--get-stack-tail () - "Return the last member in the window configurations stack." - (car (last resize-window--window-stack))) - -(defun resize-window--pop-stack-tail () - "Remove the last member from the window configurations stack. -Return the removed member." - (prog1 - (resize-window--get-stack-tail) - (setq resize-window--window-stack - (nbutlast resize-window--window-stack)))) +Push ELEMENT in the stack if the stack is empty. +If FRAME is nil use the current frame." + (when (and element (setq frame (or frame (selected-frame)))) + (let* ((frame-stack + (or (resize-window--frame-stack frame) + (car (push (list frame) + resize-window--window-stacks)))) + (stack (cdr frame-stack))) + (if (consp stack) + (setcar (last stack) element) + (car (setcdr frame-stack (list element))))))) + +(defun resize-window--stack-tail-config (config &optional frame) + "Replace the last configuration in FRAME's window configurations +stack with CONFIG then return CONFIG. +If FRAME is nil use the current frame." + (let ((element (resize-window--get-stack-tail frame))) + (when (and config element) + (setcar element config)))) -(defun resize-window--push-stack-tail (element) - "Push ELEMENT to the end of the window configurations stack. -Return ELEMENT." - (when element - (setq resize-window--window-stack - (append resize-window--window-stack (list element))) - element)) +(defun resize-window--stack-tail-svtime (save-time &optional frame) + "Replace the last configuration's save time in FRAME's window +configurations stack with SAVE-TIME then return SAVE-TIME. +If FRAME is nil use the current frame." + (let ((element (resize-window--get-stack-tail frame))) + (when (and save-time element) + (setcdr element save-time)))) -(defun resize-window--set-stack-tail (element) - "Replace the last member in the window configurations stack -with ELEMENT then return ELEMENT. -Push ELEMENT in the stack if the stack is empty." - (when element - (if resize-window--window-stack - (setcar (last resize-window--window-stack) element) - (resize-window--push-stack-tail element)))) - -(defun resize-window--stack-tail-config (config) - "Replace the last configuration in the window configurations -stack with CONFIG then return CONFIG." - (when (and config resize-window--window-stack) - (setcar (resize-window--get-stack-tail) config))) - -(defun resize-window--stack-tail-svtime (save-time) - "Replace the last configuration's save time in the window -configurations stack with SAVE-TIME then return SAVE-TIME." - (when (and save-time resize-window--window-stack) - (setcdr (resize-window--get-stack-tail) save-time))) - -(defun resize-window--get-stack-member (&optional from-tail) - "Return the first member in the window configurations stack. -If FROM-TAIL is non-nil return the last member intead." +(defun resize-window--get-stack-member (&optional from-tail frame) + "Return the first member in FRAME's window configurations stack. +If FROM-TAIL is non-nil return the last member intead. +If FRAME is nil use the current frame." (if from-tail - (resize-window--get-stack-tail) - (resize-window--get-stack-head))) + (resize-window--get-stack-tail frame) + (resize-window--get-stack-head frame))) -(defun resize-window--pop-stack-member (&optional from-tail) - "Remove the first member from the window configurations stack. +(defun resize-window--pop-stack-member (&optional from-tail frame) + "Remove the first member from FRAME's window configurations stack. If FROM-TAIL is non-nil remove the last member instead. +If FRAME is nil use the current frame. Return the removed member." (if from-tail - (resize-window--pop-stack-tail) - (resize-window--pop-stack-head))) + (resize-window--pop-stack-tail frame) + (resize-window--pop-stack-head frame))) -(defun resize-window--push-stack-member (element &optional to-tail) - "Push ELEMENT to the begin of the window configurations stack. +(defun resize-window--push-stack-member (element &optional to-tail frame) + "Push ELEMENT to the begin of FRAME's window configurations stack. If TO-TAIL is non-nil push to the end. +If FRAME is nil use the current frame. Return ELEMENT." (if to-tail - (resize-window--push-stack-tail element) - (resize-window--push-stack-head element))) + (resize-window--push-stack-tail element frame) + (resize-window--push-stack-head element frame))) -(defun resize-window--set-stack-member (element &optional to-tail) - "Replace the first member in the window configurations stack +(defun resize-window--set-stack-member (element &optional to-tail frame) + "Replace the first member in FRAME's window configurations stack with ELEMENT then return ELEMENT. -If TO-TAIL is non-nil replace the last." +If TO-TAIL is non-nil replace the last. +If FRAME is nil use the current frame." (if to-tail - (resize-window--set-stack-tail element) - (resize-window--set-stack-head element))) + (resize-window--set-stack-tail element frame) + (resize-window--set-stack-head element frame))) -(defun resize-window--stack-member-config (config &optional to-tail) - "Replace the first configuration in the window configurations +(defun resize-window--stack-member-config (config &optional to-tail frame) + "Replace the first configuration in FRAME's window configurations stack with CONFIG then return CONFIG. -If TO-TAIL is non-nil replace the last." +If TO-TAIL is non-nil replace the last. +If FRAME is nil use the current frame." (if to-tail - (resize-window--stack-tail-config config) - (resize-window--stack-head-config config))) + (resize-window--stack-tail-config config frame) + (resize-window--stack-head-config config frame))) -(defun resize-window--stack-member-svtime (save-time &optional to-tail) - "Replace the first configuration's save time in the window +(defun resize-window--stack-member-svtime (save-time &optional to-tail frame) + "Replace the first configuration's save time in FRAME's window configurations stack with SAVE-TIME then return SAVE-TIME. -If TO-TAIL is non-nil replace the last." +If TO-TAIL is non-nil replace the last. +If FRAME is nil use the current frame." (if to-tail - (resize-window--stack-tail-svtime save-time) - (resize-window--stack-head-svtime save-time))) + (resize-window--stack-tail-svtime save-time frame) + (resize-window--stack-head-svtime save-time frame))) -(defun resize-window--get-stack-nth (n) - "Return the Nth member in the window configurations stack. +(defun resize-window--get-stack-nth (n &optional frame) + "Return the Nth member in FRAME's window configurations stack. If N is negative count from the end, where -1 is the end. +If FRAME is nil use the current frame. Return nil if N is out of bound." - (let* ((stack-size (length resize-window--window-stack)) + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (stack-size (length stack)) (stack-last (1- stack-size)) (x (if (< n 0) (+ stack-size n) n))) (and (>= x 0) (<= x stack-last) - (nth x resize-window--window-stack)))) + (nth x stack)))) -(defun resize-window--pop-stack-nth (n) - "Remove the Nth member from the window configurations stack. +(defun resize-window--pop-stack-nth (n &optional frame) + "Remove the Nth member from FRAME's window configurations stack. If N is negative count from the end, where -1 is the end. -Return the removed member or nil if N is out of bound." - (let* ((stack-size (length resize-window--window-stack)) +Return the removed member or nil if N is out of bound. +If FRAME is nil use the current frame." + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (stack-size (length stack)) (stack-last (1- stack-size)) (x (if (< n 0) (+ stack-size n) n))) (and (>= x 0) (<= x stack-last) - (pop (nthcdr x resize-window--window-stack))))) - -(defun resize-window--push-stack-nth (element n) - "Push ELEMENT as the Nth member in the window configurations + (prog1 + (pop (nthcdr x stack)) + (if stack + (setcdr frame-stack stack) + (resize-window--del-frame-stack frame)))))) + +(defun resize-window--push-stack-nth (element n &optional frame) + "Push ELEMENT as the Nth member in FRAME's window configurations stack then return ELEMENT or nil if N is out of bound. -If N is negative count from the end, where -1 is the end." - (let* ((stack-size (length resize-window--window-stack)) +If N is negative count from the end, where -1 is the end. +If FRAME is nil use the current frame." + (setq frame (or frame (selected-frame))) + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (stack-size (length stack)) (stack-last (1- stack-size)) (x (if (< n 0) (+ stack-size n 1) n))) (when (and element (>= x 0) (<= x stack-size)) - (let ((head (butlast resize-window--window-stack (- stack-size x))) - (tail (nthcdr x resize-window--window-stack))) - (setq resize-window--window-stack - (append head (list element) tail)) + (let ((head (butlast stack (- stack-size x))) + (tail (nthcdr x stack))) + (setq stack (append head (list element) tail)) + (unless frame-stack + (setq frame-stack + (car (push (list frame) + resize-window--window-stacks)))) + (setcdr frame-stack stack) element)))) -(defun resize-window--set-stack-nth (element n) - "Replace the Nth member in the window configurations stack +(defun resize-window--set-stack-nth (element n &optional frame) + "Replace the Nth member in FRAME's window configurations stack with ELEMENT then return ELEMENT or nil if N is out of bound. Push ELEMENT in the stack if the stack is empty with N 0 or -1. -If N is negative count from the end, where -1 is the end." - (let* ((stack-size (length resize-window--window-stack)) +If N is negative count from the end, where -1 is the end. +If FRAME is nil use the current frame." + (setq frame (or frame (selected-frame))) + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (stack-size (length stack)) (stack-last (if (> stack-size 0) (1- stack-size) 0)) (x (if (< n 0) (+ stack-size n (if (> stack-size 0) 0 1)) n))) (when (and element (>= x 0) (<= x stack-last)) - (let ((head (butlast resize-window--window-stack (- stack-size x))) - (tail (nthcdr (1+ x) resize-window--window-stack))) - (setq resize-window--window-stack - (append head (list element) tail)) + (let ((head (butlast stack (- stack-size x))) + (tail (nthcdr (1+ x) stack))) + (setq stack (append head (list element) tail)) + (unless frame-stack + (setq frame-stack + (car (push (list frame) + resize-window--window-stacks)))) + (setcdr frame-stack stack) element)))) -(defun resize-window--stack-nth-config (config n) - "Replace the Nth configuration in the window configurations stack -with CONFIG then return CONFIG or nil if N is out of bound. -If N is negative count from the end, where -1 is the end." - (let ((element (resize-window--get-stack-nth n))) +(defun resize-window--stack-nth-config (config n &optional frame) + "Replace the Nth configuration in FRAME's window configurations +stack with CONFIG then return CONFIG or nil if N is out of bound. +If N is negative count from the end, where -1 is the end. +If FRAME is nil use the current frame." + (let ((element (resize-window--get-stack-nth n frame))) (when element (setcar element config)))) -(defun resize-window--stack-nth-svtime (save-time n) - "Replace the Nth configuration's save time in the window configurations -stack with SAVE-TIME then return SAVE-TIME or nil if N is out of bound. -If N is negative count from the end, where -1 is the end." - (let ((element (resize-window--get-stack-nth n))) +(defun resize-window--stack-nth-svtime (save-time n &optional frame) + "Replace the Nth configuration's save time in FRAME's window +configurations stack with SAVE-TIME then return SAVE-TIME or nil +if N is out of bound. +If N is negative count from the end, where -1 is the end. +If FRAME is nil use the current frame." + (let ((element (resize-window--get-stack-nth n frame))) (when element (setcdr element save-time)))) -(defun resize-window--stack-shift-head () - "Shift right in the window configurations stack. +(defun resize-window--stack-shift-head (&optional frame) + "Shift right in FRAME's stack. Return the member shifted to if any. +If FRAME is nil use the current frame. -Pop the head and push it to the tail in the stack. It is like +Pop the head and push it to the tail in FRAME's stack. It is like scrolling right in the stack. The next member becomes the first." - (let ((element (resize-window--pop-stack-head))) + (let ((element (resize-window--pop-stack-head frame))) (when element - (resize-window--push-stack-tail element)))) + (resize-window--push-stack-tail element frame)))) -(defun resize-window--stack-shift-tail () - "Shift left in the window configurations stack. +(defun resize-window--stack-shift-tail (&optional frame) + "Shift left in FRAME's stack. Return the member shifted to if any. +If FRAME is nil use the current frame. -Pop the tail and push it to the head in the stack. It is like +Pop the tail and push it to the head in FRAME's stack. It is like scrolling left in the stack. The last member becomes the first." - (let ((element (resize-window--pop-stack-tail))) + (let ((element (resize-window--pop-stack-tail frame))) (when element - (resize-window--push-stack-head element)))) + (resize-window--push-stack-head element frame)))) -(defun resize-window--stack-shift-member (&optional to-tail) - "Shitf right in the window configurations stack. +(defun resize-window--stack-shift-member (&optional to-tail frame) + "Shitf right in FRAME's stack. If TO-TAIL is non-nil shift left. Return the member shifted to if any. Move the head to the tail like scrolling right, or move the tail to the head like scrolling left if TO-TAIL is non-nil." (if to-tail - (resize-window--stack-shift-tail) - (resize-window--stack-shift-head))) + (resize-window--stack-shift-tail frame) + (resize-window--stack-shift-head frame))) -(defun resize-window--stack-config-modified (&optional config from-tail) - "Return non-nil if CONFIG differs from the stack first configuration. +(defun resize-window--stack-config-modified (&optional config from-tail frame) + "Return non-nil if CONFIG differs from FRAME's stack first configuration. If CONFIG is nil use the current window configuration. -If FROM-TAIL is non-nil check the last configuration." +If FROM-TAIL is non-nil check the last configuration. +If FRAME is nil use the current frame." (let* ((curr-config (or config (resize-window--window-config))) (this-member (if from-tail - (resize-window--get-stack-tail) - (resize-window--get-stack-head))) + (resize-window--get-stack-tail frame) + (resize-window--get-stack-head frame))) (this-config (car this-member))) (or (not (setq this-config (resize-window--apply-config this-config))) (not (compare-window-configurations curr-config this-config))))) -(defun resize-window--trim-stack-dups (&optional config from-tail) +(defun resize-window--trim-stack-dups (&optional config from-tail frame) "Trim consecutive duplicates of CONFIG from the beginning of -the stack or from the end if FROM-TAIL is non-nil. +FRAME's stack or from the end if FROM-TAIL is non-nil. If CONFIG is nil use the current window configuration. +If FRAME is nil use the current frame. Return the first stack member that differs from CONFIG." (let ((curr-config (or config (resize-window--window-config))) (this-member nil) (this-config nil)) (while (and (setq this-member - (resize-window--get-stack-member from-tail)) + (resize-window--get-stack-member from-tail frame)) (setq this-config (car this-member)) (setq this-config (resize-window--apply-config this-config)) (compare-window-configurations curr-config this-config) - (resize-window--pop-stack-member from-tail))) + (resize-window--pop-stack-member from-tail frame))) this-member)) -(defun resize-window--window-trim (&optional stack-size) - "Trim the oldest window configurations from the stack in +(defun resize-window--window-trim (&optional frame stack-size) + "Trim the oldest window configurations from FRAME's stack in excess of STACK-SIZE then return the removed stack members. +If FRAME is nil use the current frame. If STACK-SIZE is nil use `resize-window-stack-size'." - (let* ((size (length resize-window--window-stack)) + (let* ((frame-stack (resize-window--frame-stack frame)) + (stack (cdr frame-stack)) + (size (length stack)) (trim (- size (or stack-size resize-window-stack-size)))) (when (> trim 0) (let ((oldest-members - (sort (copy-sequence resize-window--window-stack) + (sort (copy-sequence stack) (lambda (a b) (time-less-p (cdr a) (cdr b)))))) (setq oldest-members (nbutlast oldest-members (- size trim))) (dotimes (n (length oldest-members)) (let ((old-member (nth n oldest-members))) - (setq resize-window--window-stack - (delq old-member resize-window--window-stack)))) + (setq stack (delq old-member stack)))) + (if stack + (setcdr frame-stack stack) + (resize-window--del-frame-stack frame)) oldest-members)))) (defun resize-window--window-drop ()