branch: externals/bufferlo commit 973841c4face62ebc9193f357ee744c2bd046247 Author: shipmints <shipmi...@gmail.com> Commit: shipmints <shipmi...@gmail.com>
Misc changes and an important fix. No tabs. Command-line arg to inhibit loading bookmarks at startup. Fixed bufferlo-bookmarks-save to ensure frames are selected. Rename bufferlo-bookmark-frame-load-policy policies. mode-line-lighter-prefix now defcustom. mode-line-format visual improvement. Verbiage changed for bufferlo-bookmark-frame-clone-policy. Bookmark auto save message user-settable options. --- bufferlo.el | 272 +++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 158 insertions(+), 114 deletions(-) diff --git a/bufferlo.el b/bufferlo.el index aea7e03716..d79f8f9e93 100644 --- a/bufferlo.el +++ b/bufferlo.el @@ -140,7 +140,7 @@ This is a list of regular expressions to filter buffer names." "If non-nil, confirm before closing the tab and killing buffers." :type 'boolean) -(defcustom bufferlo-bookmark-frame-load-policy 'current +(defcustom bufferlo-bookmark-frame-load-policy 'replace-frame-retain-current-bookmark "Control loading a frame bookmark into a already-bookmarked frame. \\='prompt allows you to select a policy interactively. @@ -149,11 +149,11 @@ This is a list of regular expressions to filter buffer names." frames, with the exception that a bookmarked frame may be reloaded to restore its state. -\\='current replaces the frame content using the existing frame -bookmark name. +\\='replace-frame-retain-current-bookmark replaces the frame +content using the existing frame bookmark name. -\\='replace replaces the frame content and adopts the new -bookmark name. +\\='replace-frame-adopt-loaded-bookmark replaces the frame content +and adopts the loaded bookmark name. \\='merge adds new frame bookmark tabs to the existing frame, retaining the existing bookmark name. @@ -164,8 +164,8 @@ loading is not overridden with a prefix argument that suppresses making a new frame." :type '(radio (const :tag "Prompt" prompt) (const :tag "Disallow" disallow) - (const :tag "Current bookmark name" current) - (const :tag "Replace bookmark name" replace) + (const :tag "Replace frame, retain current bookmark name" replace-frame-retain-current-bookmark) + (const :tag "Replace frame, adopt loaded bookmark name" replace-frame-adopt-loaded-bookmark) (const :tag "Merge" merge))) (defcustom bufferlo-bookmark-frame-duplicate-policy 'allow @@ -196,10 +196,11 @@ conditions. \\='allow allows duplicates. -\\='clear will clear the bookmark on the cloned frame." +\\='disassociate will clear the bookmark on the newly cloned or +undeleted frame." :type '(radio (const :tag "Prompt" prompt) (const :tag "Allow" allow) - (const :tag "Clear" clear))) + (const :tag "Disassociate" disassociate))) (defcustom bufferlo-bookmark-tab-overwrite-policy 'overwrite "Control whether loaded tabs overwrite current tabs or occupy new tabs. @@ -288,14 +289,15 @@ advance that prevent duplicate frame and tab bookmarks." (const :tag "Other frames" other) (const :tag "All frames" all))) -(defcustom bufferlo-bookmarks-save-predicate-functions nil +(defcustom bufferlo-bookmarks-save-predicate-functions (list #'bufferlo-bookmarks-save-all-p) "Functions to filter active bufferlo bookmarks to save. These are applied when `bufferlo-bookmarks-auto-save-idle-interval' is > 0, or manually via `bufferlo-bookmarks-save'. Functions are passed the bufferlo bookmark name and invoked until the first positive result. Set to `#'bufferlo-bookmarks-save-all-p' to save all bookmarks or -provide your own predicates." +provide your own predicates (note: be sure to remove +`#'bufferlo-bookmarks-save-all-p' from the list)." :type 'hook) (defcustom bufferlo-bookmarks-load-predicate-functions nil @@ -434,15 +436,31 @@ This is controlled by `bufferlo-bookmarks-auto-save-idle-interval'.") (defcustom bufferlo-bookmarks-auto-save-idle-interval 0 "Save bufferlo bookmarks when Emacs has been idle this many seconds. -Set to 0 to disable the timer." +Set to 0 to disable the timer. Units are whole integer seconds." :type 'natnum :set (lambda (sym val) (set-default sym val) (bufferlo--bookmarks-auto-save-timer-maybe-start))) +(defcustom bufferlo-bookmarks-auto-save-messages nil + "Control messages from the interval auto saver. + +\\=nil inhibits all messages. + +\\=t shows all messages. + +\\='saved shows a message only when bookmarks have been saved. + +\\='notsaved shows a message only when bookmarks have not been saved." + :type '(radio (const :tag "None" nil) + (const :tag "All" t) + (const :tag "Saved only" saved) + (const :tag "Not-saved only" notsaved))) + ;; Yes, it's a playful cow, but the water buffalo "🐃" is dark and hard to see. -(defvar bufferlo-mode-line-lighter-prefix " 🐮" - "Bufferlo mode-line lighter prefix.") +(defcustom bufferlo-mode-line-lighter-prefix " 🐮" + "Bufferlo mode-line lighter prefix." + :type 'string) (defvar bufferlo-mode) ; byte compiler (defun bufferlo-mode-line-format () @@ -451,15 +469,35 @@ Set to 0 to disable the timer." (let ((fbm (frame-parameter nil 'bufferlo-bookmark-frame-name)) (tbm (alist-get 'bufferlo-bookmark-tab-name (tab-bar--current-tab-find)))) (concat bufferlo-mode-line-lighter-prefix - (if fbm (concat "f:" fbm)) - (if (and fbm tbm) "/") - (if tbm (concat "t:" tbm)))))) + "[" + (if fbm (concat "Ⓕ" fbm)) + (if (and fbm tbm) " ") + (if tbm (concat "Ⓣ" tbm)) + "]")))) (defcustom bufferlo-mode-line-lighter '(:eval (bufferlo-mode-line-format)) "Bufferlo mode line definition." :type 'sexp :risky t) +(defconst bufferlo--command-line-noload-prefix "--bufferlo-noload") +(defvar bufferlo--command-line-noload nil) + +(defun bufferlo--parse-command-line () + "Process bufferlo Emacs command-line arguments." + (when-let (pos (seq-position command-line-args bufferlo--command-line-noload-prefix #'string-equal)) + (setq bufferlo--command-line-noload pos) + (setq command-line-args (seq-remove-at-position command-line-args pos)))) + +(defun -bufferlo--parse-command-line-test () "." + (let ((command-line-args (list "/usr/bin/emacs" "--name" "foobar" bufferlo--command-line-noload-prefix "-T" "title"))) + (setq bufferlo--command-line-noload nil) + (message "command-line-args=%s" command-line-args) + (message "bufferlo--command-line-noload=%s" bufferlo--command-line-noload) + (bufferlo--parse-command-line) + (message "bufferlo--command-line-noload=%s" bufferlo--command-line-noload) + (message "command-line-args=%s" command-line-args))) + ;;;###autoload (define-minor-mode bufferlo-mode "Manage frame/tab-local buffers." @@ -470,6 +508,7 @@ Set to 0 to disable the timer." :keymap nil (if bufferlo-mode (progn + (bufferlo--parse-command-line) ; parse user-provided settings first ;; Prefer local buffers (when bufferlo-prefer-local-buffers (dolist (frame (frame-list)) @@ -499,7 +538,8 @@ Set to 0 to disable the timer." (when (not (eq bufferlo-bookmarks-save-at-emacs-exit 'nosave)) (add-hook 'kill-emacs-hook #'bufferlo--bookmarks-save-at-emacs-exit)) ;; load bookmarks at startup option - (when (not (eq bufferlo-bookmarks-load-at-emacs-startup 'noload)) + (when (and (not bufferlo--command-line-noload) + (not (eq bufferlo-bookmarks-load-at-emacs-startup 'noload))) (add-hook 'window-setup-hook #'bufferlo-bookmarks-load)) ;; bookmark advice (advice-add 'bookmark-rename :around #'bufferlo--bookmark-rename-advice) @@ -759,16 +799,16 @@ the adviced functions. Honors `bufferlo-bookmark-frame-clone-policy'." (when fbm (when (eq clone-policy 'prompt) (pcase (let ((read-answer-short t)) - (read-answer "Cloned/undeleted frame bookmark: Allow, Clear cloned/undeleted bookmark " - '(("allow" ?a "Allow duplicate bookmark") - ("clear" ?c "Clear bookmark") + (read-answer "Disassociate cloned/undeleted frame bookmark: Allow, Disassociate " + '(("allow" ?a "Allow bookmark") + ("disassociate" ?d "Disassociate bookmark") ("help" ?h "Help") ("quit" ?q "Quit--retains the bookmark")))) - ("clear" (setq clone-policy 'clear)) + ("disassociate" (setq clone-policy 'disassociate)) (_ (setq clone-policy 'allow)))) ; allow, quit cases (pcase clone-policy ('allow) - ('clear + ('disassociate (set-frame-parameter nil 'bufferlo-bookmark-frame-name nil)))))) (defsubst bufferlo--warn () @@ -960,14 +1000,14 @@ argument INTERNAL-TOO is non-nil." (concat "Kill frame and its buffers? ")))) (when kill (bufferlo-kill-buffers nil frame 'all internal-too) - ;; TODO: Emacs 30 frame-deletable-p - ;; account for top-level, non-child frames - (setq frame (or frame (selected-frame))) - (when (= 1 (length (seq-filter - (lambda (x) (null (frame-parameter x 'parent-frame))) - (frame-list)))) - (make-frame)) ; leave one for the user - (delete-frame frame)))) + ;; TODO: Emacs 30 frame-deletable-p + ;; account for top-level, non-child frames + (setq frame (or frame (selected-frame))) + (when (= 1 (length (seq-filter + (lambda (x) (null (frame-parameter x 'parent-frame))) + (frame-list)))) + (make-frame)) ; leave one for the user + (delete-frame frame)))) (defun bufferlo-tab-close-kill-buffers (&optional killall internal-too) "Close the current tab and kill the local buffers. @@ -975,7 +1015,7 @@ The optional arguments KILLALL and INTERNAL-TOO are passed to `bufferlo-kill-buffers'." (interactive "P") (bufferlo--warn) - (let ((kill t) + (let ((kill t) (tbm (alist-get 'bufferlo-bookmark-tab-name (tab-bar--current-tab-find)))) (when (and tbm bufferlo-close-tab-kill-buffers-save-bookmark-prompt) @@ -985,9 +1025,9 @@ The optional arguments KILLALL and INTERNAL-TOO are passed to (when bufferlo-close-tab-kill-buffers-prompt (setq kill (y-or-n-p (concat "Kill tab and its buffers? ")))) - (when kill - (bufferlo-kill-buffers killall nil nil internal-too) - (tab-bar-close-tab)))) + (when kill + (bufferlo-kill-buffers killall nil nil internal-too) + (tab-bar-close-tab)))) (defun bufferlo-isolate-project (&optional file-buffers-only) "Isolate a project in the frame or tab. @@ -1105,7 +1145,7 @@ If the buffer is already visible in a non-selected window, select it." (generate-new-buffer-name bufferlo-local-scratch-buffer-name))) (with-current-buffer buffer (when (eq major-mode 'fundamental-mode) - (funcall (or bufferlo-local-scratch-buffer-initial-major-mode + (funcall (or bufferlo-local-scratch-buffer-initial-major-mode initial-major-mode #'ignore))))) buffer)) @@ -1179,7 +1219,7 @@ If the prefix argument is given, include all buffers." (buffer-name b))) (bufferlo-buffer-list)) (generate-new-buffer-name "*Local Buffer List*"))) - (buffer (get-buffer-create name))) + (buffer (get-buffer-create name))) (with-current-buffer buffer (Buffer-menu-mode) (setq bufferlo--buffer-menu-this-frame (selected-frame)) @@ -1198,7 +1238,7 @@ If the prefix argument is given, include all buffers." (display-buffer (let* ((old-buffer (current-buffer)) (name "*Orphan Buffer List*") - (buffer (get-buffer-create name))) + (buffer (get-buffer-create name))) (with-current-buffer buffer (Buffer-menu-mode) (setq bufferlo--buffer-menu-this-frame (selected-frame)) @@ -1344,8 +1384,8 @@ Has no effect if the next command does not query for a buffer." (lambda () (unless (or ;; from window.el:display-buffer-override-next-command - (> (minibuffer-depth) minibuffer-depth) - (eq this-command command)) + (> (minibuffer-depth) minibuffer-depth) + (eq this-command command)) (setq bufferlo--anywhere-tmp-disabled nil) (remove-hook 'post-command-hook postfun)))) (setq bufferlo--anywhere-tmp-disabled t) @@ -1367,8 +1407,8 @@ In contrast to `bufferlo-anywhere-mode', this does not adhere to (lambda () (unless (or ;; from window.el:display-buffer-override-next-command - (> (minibuffer-depth) minibuffer-depth) - (eq this-command command)) + (> (minibuffer-depth) minibuffer-depth) + (eq this-command command)) (setq bufferlo--anywhere-tmp-enabled nil) (unless bufferlo-anywhere-mode (advice-remove #'call-interactively @@ -1615,14 +1655,14 @@ the message after successfully restoring the bookmark." (progn (when (eq load-policy 'prompt) (pcase (let ((read-answer-short t)) - (read-answer "Frame already bookmarked: use Current, Replace with new, Merge with existing " - '(("current" ?c "Use the existing bookmark") - ("replace" ?r "Replace the bookmark with the selected bookmark") + (read-answer "Frame already bookmarked: load and retain Current, Replace with new, Merge with existing " + '(("current" ?c "Replace frame, retain the current bookmark") + ("replace" ?r "Replace frame, adopt the loaded bookmark") ("merge" ?m "Merge the new tab content with the existing bookmark") ("help" ?h "Help") ("quit" ?q "Quit with no changes")))) - ("current" (setq load-policy 'current)) - ("replace" (setq load-policy 'replace)) + ("current" (setq load-policy 'replace-frame-retain-current-bookmark)) + ("replace" (setq load-policy 'replace-frame-adopt-loaded-bookmark)) ("merge" (setq load-policy 'merge)) (_ (throw :noload t)))) (pcase load-policy @@ -1630,13 +1670,13 @@ the message after successfully restoring the bookmark." (when (not (equal fbm bookmark-name)) ; allow reloads of existing bookmark (unless no-message (message "Frame already bookmarked as %s; not loaded." fbm)) (throw :noload t))) - ('current - (setq msg (concat msg (format "; merged with existing bookmark %s." fbm)))) - ('replace - (setq msg (concat msg (format "; replaced bookmark %s." fbm))) + ('replace-frame-retain-current-bookmark + (setq msg (concat msg (format "; retained existing bookmark %s." fbm)))) + ('replace-frame-adopt-loaded-bookmark + (setq msg (concat msg (format "; adopted loaded bookmark %s." fbm))) (setq fbm bookmark-name)) ('merge - (setq msg (concat msg (format "; merged bookmark %s." bookmark-name)))))) + (setq msg (concat msg (format "; merged tabs from bookmark %s." bookmark-name)))))) (setq fbm bookmark-name)) ; not already bookmarked (unless (eq load-policy 'merge) (if (>= emacs-major-version 28) @@ -1875,28 +1915,32 @@ It is intended to be used in `bufferlo-bookmarks-load-predicate-functions'." (defun bufferlo--bookmarks-save (active-bookmark-names active-bookmarks &optional no-message) "Save the bookmarks in ACTIVE-BOOKMARK-NAMES indexed by ACTIVE-BOOKMARKS. Specify NO-MESSAGE to inhibit the bookmark save status message." - (let ((bookmarks-saved nil) - (start-time (current-time))) - (let ((bookmark-save-flag nil)) ; inhibit built-in bookmark file saving until we're done - (dolist (abm-name active-bookmark-names) - (when-let* ((abm (assoc abm-name active-bookmarks)) - (abm-type (alist-get 'type (cadr abm)))) + (let ((bookmarks-saved nil) + (start-time (current-time))) + (let ((bookmark-save-flag nil)) ; inhibit built-in bookmark file saving until we're done + (dolist (abm-name active-bookmark-names) + (when-let* ((abm (assoc abm-name active-bookmarks)) + (abm-type (alist-get 'type (cadr abm))) + (abm-frame (alist-get 'frame (cadr abm)))) + (with-selected-frame abm-frame (cond ((eq abm-type 'fbm) (bufferlo-bookmark-frame-save abm-name nil t)) ((eq abm-type 'tbm) (bufferlo-bookmark-tab-save abm-name nil t))) - (push abm-name bookmarks-saved)))) - (cond - (bookmarks-saved + (push abm-name bookmarks-saved))))) + (cond + (bookmarks-saved + (let ((inhibit-message (or no-message + (not (memq bufferlo-bookmarks-auto-save-messages (list 'saved t)))))) (bookmark-save) - (unless no-message - (message "Saved bufferlo bookmarks: %s, in %.2f second(s)" - (mapconcat 'identity bookmarks-saved " ") - (float-time (time-subtract (current-time) start-time))))) - (t - (unless no-message - (message "No bufferlo bookmarks saved.")))))) + (message "Saved bufferlo bookmarks: %s, in %.2f second(s)" + (mapconcat 'identity bookmarks-saved " ") + (float-time (time-subtract (current-time) start-time))))) + (t + (when (and (not no-message) + (memq bufferlo-bookmarks-auto-save-messages (list 'notsaved t))) + (message "No bufferlo bookmarks saved.")))))) (defun bufferlo-bookmarks-save (&optional all) "Save active bufferlo bookmarks. @@ -2121,31 +2165,31 @@ transient work." (defun bufferlo--close-active-bookmarks (active-bookmark-names active-bookmarks) "Close the bookmarks in ACTIVE-BOOKMARK-NAMES indexed by ACTIVE-BOOKMARKS." - (let* ((abms (seq-filter - (lambda (x) (member (car x) active-bookmark-names)) - active-bookmarks)) - (tbms (seq-filter - (lambda (x) (eq 'tbm (alist-get 'type (cadr x)))) - abms)) - (fbms (seq-filter - (lambda (x) (eq 'fbm (alist-get 'type (cadr x)))) - abms))) - ;; do tab bookmarks first, then frame bookmarks - (dolist (abm tbms) - (let ((abm-frame (alist-get 'frame (cadr abm))) - (abm-tab (alist-get 'tab (cadr abm)))) - (with-selected-frame abm-frame - (tab-bar-select-tab - (1+ (tab-bar--tab-index abm-tab))) - (let ((bufferlo-close-tab-kill-buffers-save-bookmark-prompt nil) - (bufferlo-close-tab-kill-buffers-prompt nil)) - (bufferlo-tab-close-kill-buffers))))) - (dolist (abm fbms) - (let ((abm-frame (alist-get 'frame (cadr abm)))) - (with-selected-frame abm-frame - (let ((bufferlo-delete-frame-kill-buffers-save-bookmark-prompt nil) - (bufferlo-delete-frame-kill-buffers-prompt nil)) - (bufferlo-delete-frame-kill-buffers))))))) + (let* ((abms (seq-filter + (lambda (x) (member (car x) active-bookmark-names)) + active-bookmarks)) + (tbms (seq-filter + (lambda (x) (eq 'tbm (alist-get 'type (cadr x)))) + abms)) + (fbms (seq-filter + (lambda (x) (eq 'fbm (alist-get 'type (cadr x)))) + abms))) + ;; do tab bookmarks first, then frame bookmarks + (dolist (abm tbms) + (let ((abm-frame (alist-get 'frame (cadr abm))) + (abm-tab (alist-get 'tab (cadr abm)))) + (with-selected-frame abm-frame + (tab-bar-select-tab + (1+ (tab-bar--tab-index abm-tab))) + (let ((bufferlo-close-tab-kill-buffers-save-bookmark-prompt nil) + (bufferlo-close-tab-kill-buffers-prompt nil)) + (bufferlo-tab-close-kill-buffers))))) + (dolist (abm fbms) + (let ((abm-frame (alist-get 'frame (cadr abm)))) + (with-selected-frame abm-frame + (let ((bufferlo-delete-frame-kill-buffers-save-bookmark-prompt nil) + (bufferlo-delete-frame-kill-buffers-prompt nil)) + (bufferlo-delete-frame-kill-buffers))))))) (defun bufferlo-bookmarks-close () "Close all active bufferlo frame and tab bookmarks and kill their buffers. @@ -2154,26 +2198,26 @@ You will be offered to save bookmarks using filter predicates or all unless a prefix argument is specified." (interactive) (let* ((close t) - (abms (bufferlo--active-bookmarks)) - (abm-names (mapcar #'car abms))) - (if (null abms) - (message "No active bufferlo bookmarks") - (unless current-prefix-arg - (pcase (let ((read-answer-short t)) - (read-answer "Save bookmarks before closing them: All, Predicate, No save " - '(("all" ?a "Save all active bookmarks") - ("pred" ?p "Save predicate-filtered bookmarks, if set") - ("nosave" ?n "Don't save") - ("help" ?h "Help") - ("quit" ?q "Quit")))) - ("all" - (bufferlo-bookmarks-save 'all)) - ("pred" - (bufferlo-bookmarks-save)) - ("nosave") - (_ (setq close nil)))) - (when close - (bufferlo--close-active-bookmarks abm-names abms))))) + (abms (bufferlo--active-bookmarks)) + (abm-names (mapcar #'car abms))) + (if (null abms) + (message "No active bufferlo bookmarks") + (unless current-prefix-arg + (pcase (let ((read-answer-short t)) + (read-answer "Save bookmarks before closing them: All, Predicate, No save " + '(("all" ?a "Save all active bookmarks") + ("pred" ?p "Save predicate-filtered bookmarks, if set") + ("nosave" ?n "Don't save") + ("help" ?h "Help") + ("quit" ?q "Quit")))) + ("all" + (bufferlo-bookmarks-save 'all)) + ("pred" + (bufferlo-bookmarks-save)) + ("nosave") + (_ (setq close nil)))) + (when close + (bufferlo--close-active-bookmarks abm-names abms))))) (defun bufferlo--bookmark-raise (abm) "Raise ABM's frame/tab." @@ -2237,7 +2281,7 @@ OLDFN BOOKMARK-NAME BATCH" (interactive) (if (called-interactively-p 'interactive) (setq bookmark-name (bookmark-completing-read "Delete bookmark" - bookmark-current-bookmark))) + bookmark-current-bookmark))) (if-let ((abm (assoc bookmark-name (bufferlo--active-bookmarks)))) (user-error "%s is an active bufferlo bookmark--close its frame/tab, or clear it before deleting" bookmark-name) (if (called-interactively-p 'interactive)