From 42947150adc2639bf7456d40dedce09338cec303 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Wed, 6 Jul 2022 16:47:32 -0500 Subject: [PATCH] Rename +compat.el to avoid collision; add a thing --- lisp/+compat.el | 64 ++ lisp/compat.el | 34 - lisp/compat/keymap.el | 590 ---------- lisp/compat/tab-bar.el | 2424 ---------------------------------------- 4 files changed, 64 insertions(+), 3048 deletions(-) create mode 100644 lisp/+compat.el delete mode 100644 lisp/compat.el delete mode 100644 lisp/compat/keymap.el delete mode 100644 lisp/compat/tab-bar.el diff --git a/lisp/+compat.el b/lisp/+compat.el new file mode 100644 index 0000000..286d5da --- /dev/null +++ b/lisp/+compat.el @@ -0,0 +1,64 @@ +;;; +compat.el --- Thin backward-compatibility shim -*- lexical-binding: t; -*- + +;;; Commentary: + +;; I use different versionso of Emacs. Sometimes I have to copy-paste functions +;; from newer Emacs to make my customizations work. This is that file. + +;; This is probably ill-advised. + +;;; Code: + +;;; Load stuff in +compat/ subdirectory +(dolist (file (directory-files (locate-user-emacs-file "lisp/+compat") :full "\\.el\\'")) + (load file :noerror)) + +;;; Only define things if not already defined +(defmacro +compat-defun (name &rest args) + `(if (fboundp ',name) + (message "+compat: `%s' already bound." ',name) + (defun ,name ,@args))) + +(defmacro +compat-defmacro (name &rest args) + `(if (fboundp ',name) + (message "+compat: `%s' already bound." ',name) + (defmacro ,name ,@args))) + +;;; Single functions + +(+compat-defmacro dlet (binders &rest body) + "Like `let' but using dynamic scoping." + (declare (indent 1) (debug let)) + ;; (defvar FOO) only affects the current scope, but in order for + ;; this not to affect code after the main `let' we need to create a new scope, + ;; which is what the surrounding `let' is for. + ;; FIXME: (let () ...) currently doesn't actually create a new scope, + ;; which is why we use (let (_) ...). + `(let (_) + ,@(mapcar (lambda (binder) + `(defvar ,(if (consp binder) (car binder) binder))) + binders) + (let ,binders ,@body))) + +;; https://git.savannah.gnu.org/cgit/emacs.git/diff/?id=772b189143453745a8e014e21d4b6b78f855bba3 +(+compat-defun rename-visited-file (new-location) + "Rename the file visited by the current buffer to NEW-LOCATION. +This command also sets the visited file name. If the buffer +isn't visiting any file, that's all it does. + +Interactively, this prompts for NEW-LOCATION." + (interactive + (list (if buffer-file-name + (read-file-name "Rename visited file to: ") + (read-file-name "Set visited file name: " + default-directory + (expand-file-name + (file-name-nondirectory (buffer-name)) + default-directory))))) + (when (and buffer-file-name + (file-exists-p buffer-file-name)) + (rename-file buffer-file-name new-location)) + (set-visited-file-name new-location nil t)) + +(provide '+compat) +;;; +compat.el ends here diff --git a/lisp/compat.el b/lisp/compat.el deleted file mode 100644 index 4bb8706..0000000 --- a/lisp/compat.el +++ /dev/null @@ -1,34 +0,0 @@ -;;; compat.el --- Thin backward-compatibility shim -*- lexical-binding: t; -*- - -;;; Commentary: - -;; I use different versionso of Emacs. Sometimes I have to copy-paste functions -;; from newer Emacs to make my customizations work. This is that file. - -;; This is probably ill-advised. - -;;; Code: - -;; Load stuff in compat/ subdirectory -(dolist (file (directory-files (locate-user-emacs-file "lisp/compat") :full "\\.el\\'")) - (load file :noerror)) - -;; Other stuff... - -(unless (fboundp 'dlet) - (defmacro dlet (binders &rest body) - "Like `let' but using dynamic scoping." - (declare (indent 1) (debug let)) - ;; (defvar FOO) only affects the current scope, but in order for - ;; this not to affect code after the main `let' we need to create a new scope, - ;; which is what the surrounding `let' is for. - ;; FIXME: (let () ...) currently doesn't actually create a new scope, - ;; which is why we use (let (_) ...). - `(let (_) - ,@(mapcar (lambda (binder) - `(defvar ,(if (consp binder) (car binder) binder))) - binders) - (let ,binders ,@body)))) - -(provide 'compat) -;;; compat.el ends here diff --git a/lisp/compat/keymap.el b/lisp/compat/keymap.el deleted file mode 100644 index 3e9189f..0000000 --- a/lisp/compat/keymap.el +++ /dev/null @@ -1,590 +0,0 @@ -;;; keymap.el --- Keymap functions -*- lexical-binding: t; -*- - -;; Copyright (C) 2021-2022 Free Software Foundation, Inc. - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see . - -;;; Commentary: - -;; This library deals with the "new" keymap binding interface: The -;; only key syntax allowed by these functions is the `kbd' one. - -;;; Code: - - - -(defun keymap--check (key) - "Signal an error if KEY doesn't have a valid syntax." - (unless (key-valid-p key) - (error "%S is not a valid key definition; see `key-valid-p'" key))) - -(defun keymap--compile-check (&rest keys) - (dolist (key keys) - (when (or (vectorp key) - (and (stringp key) (not (key-valid-p key)))) - (byte-compile-warn "Invalid `kbd' syntax: %S" key)))) - -(defun keymap-set (keymap key definition) - "Set KEY to DEFINITION in KEYMAP. -KEY is a string that satisfies `key-valid-p'. - -DEFINITION is anything that can be a key's definition: - nil (means key is undefined in this keymap), - a command (a Lisp function suitable for interactive calling), - a string (treated as a keyboard macro), - a keymap (to define a prefix key), - a symbol (when the key is looked up, the symbol will stand for its - function definition, which should at that time be one of the above, - or another symbol whose function definition is used, etc.), - a cons (STRING . DEFN), meaning that DEFN is the definition - (DEFN should be a valid definition in its own right) and - STRING is the menu item name (which is used only if the containing - keymap has been created with a menu name, see `make-keymap'), - or a cons (MAP . CHAR), meaning use definition of CHAR in keymap MAP, - or an extended menu item definition. - (See info node `(elisp)Extended Menu Items'.)" - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (keymap--check key) - ;; If we're binding this key to another key, then parse that other - ;; key, too. - (when (stringp definition) - (keymap--check definition) - (setq definition (key-parse definition))) - (define-key keymap (key-parse key) definition)) - -(defun keymap-global-set (key command) - "Give KEY a global binding as COMMAND. -COMMAND is the command definition to use; usually it is -a symbol naming an interactively-callable function. - -KEY is a string that satisfies `key-valid-p'. - -Note that if KEY has a local binding in the current buffer, -that local binding will continue to shadow any global binding -that you make with this function." - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (interactive - (let* ((menu-prompting nil) - (key (read-key-sequence "Set key globally: " nil t))) - (list key - (read-command (format "Set key %s to command: " - (key-description key)))))) - (keymap-set (current-global-map) key command)) - -(defun keymap-local-set (key command) - "Give KEY a local binding as COMMAND. -COMMAND is the command definition to use; usually it is -a symbol naming an interactively-callable function. - -KEY is a string that satisfies `key-valid-p'. - -The binding goes in the current buffer's local map, which in most -cases is shared with all other buffers in the same major mode." - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (interactive "KSet key locally: \nCSet key %s locally to command: ") - (let ((map (current-local-map))) - (unless map - (use-local-map (setq map (make-sparse-keymap)))) - (keymap-set map key command))) - -(defun keymap-global-unset (key &optional remove) - "Remove global binding of KEY (if any). -KEY is a string that satisfies `key-valid-p'. - -If REMOVE (interactively, the prefix arg), remove the binding -instead of unsetting it. See `keymap-unset' for details." - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (interactive - (list (key-description (read-key-sequence "Set key locally: ")) - current-prefix-arg)) - (keymap-unset (current-global-map) key remove)) - -(defun keymap-local-unset (key &optional remove) - "Remove local binding of KEY (if any). -KEY is a string that satisfies `key-valid-p'. - -If REMOVE (interactively, the prefix arg), remove the binding -instead of unsetting it. See `keymap-unset' for details." - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (interactive - (list (key-description (read-key-sequence "Unset key locally: ")) - current-prefix-arg)) - (when (current-local-map) - (keymap-unset (current-local-map) key remove))) - -(defun keymap-unset (keymap key &optional remove) - "Remove key sequence KEY from KEYMAP. -KEY is a string that satisfies `key-valid-p'. - -If REMOVE, remove the binding instead of unsetting it. This only -makes a difference when there's a parent keymap. When unsetting -a key in a child map, it will still shadow the same key in the -parent keymap. Removing the binding will allow the key in the -parent keymap to be used." - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (keymap--check key) - (define-key keymap (key-parse key) nil remove)) - -(defun keymap-substitute (keymap olddef newdef &optional oldmap prefix) - "Replace OLDDEF with NEWDEF for any keys in KEYMAP now defined as OLDDEF. -In other words, OLDDEF is replaced with NEWDEF wherever it appears. -Alternatively, if optional fourth argument OLDMAP is specified, we redefine -in KEYMAP as NEWDEF those keys that are defined as OLDDEF in OLDMAP. - -If you don't specify OLDMAP, you can usually get the same results -in a cleaner way with command remapping, like this: - (define-key KEYMAP [remap OLDDEF] NEWDEF) -\n(fn OLDDEF NEWDEF KEYMAP &optional OLDMAP)" - ;; Don't document PREFIX in the doc string because we don't want to - ;; advertise it. It's meant for recursive calls only. Here's its - ;; meaning - - ;; If optional argument PREFIX is specified, it should be a key - ;; prefix, a string. Redefined bindings will then be bound to the - ;; original key, with PREFIX added at the front. - (unless prefix - (setq prefix "")) - (let* ((scan (or oldmap keymap)) - (prefix1 (vconcat prefix [nil])) - (key-substitution-in-progress - (cons scan key-substitution-in-progress))) - ;; Scan OLDMAP, finding each char or event-symbol that - ;; has any definition, and act on it with hack-key. - (map-keymap - (lambda (char defn) - (aset prefix1 (length prefix) char) - (substitute-key-definition-key defn olddef newdef prefix1 keymap)) - scan))) - -(defun keymap-set-after (keymap key definition &optional after) - "Add binding in KEYMAP for KEY => DEFINITION, right after AFTER's binding. -This is like `keymap-set' except that the binding for KEY is placed -just after the binding for the event AFTER, instead of at the beginning -of the map. Note that AFTER must be an event type (like KEY), NOT a command -\(like DEFINITION). - -If AFTER is t or omitted, the new binding goes at the end of the keymap. -AFTER should be a single event type--a symbol or a character, not a sequence. - -Bindings are always added before any inherited map. - -The order of bindings in a keymap matters only when it is used as -a menu, so this function is not useful for non-menu keymaps." - (declare (indent defun) - (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (keymap--check key) - (when after - (keymap--check after)) - (define-key-after keymap (key-parse key) definition - (and after (key-parse after)))) - -(defun key-parse (keys) - "Convert KEYS to the internal Emacs key representation. -See `kbd' for a descripion of KEYS." - (declare (pure t) (side-effect-free t)) - ;; A pure function is expected to preserve the match data. - (save-match-data - (let ((case-fold-search nil) - (len (length keys)) ; We won't alter keys in the loop below. - (pos 0) - (res [])) - (while (and (< pos len) - (string-match "[^ \t\n\f]+" keys pos)) - (let* ((word-beg (match-beginning 0)) - (word-end (match-end 0)) - (word (substring keys word-beg len)) - (times 1) - key) - ;; Try to catch events of the form "". - (if (string-match "\\`<[^ <>\t\n\f][^>\t\n\f]*>" word) - (setq word (match-string 0 word) - pos (+ word-beg (match-end 0))) - (setq word (substring keys word-beg word-end) - pos word-end)) - (when (string-match "\\([0-9]+\\)\\*." word) - (setq times (string-to-number (substring word 0 (match-end 1)))) - (setq word (substring word (1+ (match-end 1))))) - (cond ((string-match "^<<.+>>$" word) - (setq key (vconcat (if (eq (key-binding [?\M-x]) - 'execute-extended-command) - [?\M-x] - (or (car (where-is-internal - 'execute-extended-command)) - [?\M-x])) - (substring word 2 -2) "\r"))) - ((and (string-match "^\\(\\([ACHMsS]-\\)*\\)<\\(.+\\)>$" word) - (progn - (setq word (concat (match-string 1 word) - (match-string 3 word))) - (not (string-match - "\\<\\(NUL\\|RET\\|LFD\\|ESC\\|SPC\\|DEL\\)$" - word)))) - (setq key (list (intern word)))) - ((or (equal word "REM") (string-match "^;;" word)) - (setq pos (string-match "$" keys pos))) - (t - (let ((orig-word word) (prefix 0) (bits 0)) - (while (string-match "^[ACHMsS]-." word) - (setq bits (+ bits - (cdr - (assq (aref word 0) - '((?A . ?\A-\^@) (?C . ?\C-\^@) - (?H . ?\H-\^@) (?M . ?\M-\^@) - (?s . ?\s-\^@) (?S . ?\S-\^@)))))) - (setq prefix (+ prefix 2)) - (setq word (substring word 2))) - (when (string-match "^\\^.$" word) - (setq bits (+ bits ?\C-\^@)) - (setq prefix (1+ prefix)) - (setq word (substring word 1))) - (let ((found (assoc word '(("NUL" . "\0") ("RET" . "\r") - ("LFD" . "\n") ("TAB" . "\t") - ("ESC" . "\e") ("SPC" . " ") - ("DEL" . "\177"))))) - (when found (setq word (cdr found)))) - (when (string-match "^\\\\[0-7]+$" word) - (let ((n 0)) - (dolist (ch (cdr (string-to-list word))) - (setq n (+ (* n 8) ch -48))) - (setq word (vector n)))) - (cond ((= bits 0) - (setq key word)) - ((and (= bits ?\M-\^@) (stringp word) - (string-match "^-?[0-9]+$" word)) - (setq key (mapcar (lambda (x) (+ x bits)) - (append word nil)))) - ((/= (length word) 1) - (error "%s must prefix a single character, not %s" - (substring orig-word 0 prefix) word)) - ((and (/= (logand bits ?\C-\^@) 0) (stringp word) - ;; We used to accept . and ? here, - ;; but . is simply wrong, - ;; and C-? is not used (we use DEL instead). - (string-match "[@-_a-z]" word)) - (setq key (list (+ bits (- ?\C-\^@) - (logand (aref word 0) 31))))) - (t - (setq key (list (+ bits (aref word 0))))))))) - (when key - (dolist (_ (number-sequence 1 times)) - (setq res (vconcat res key)))))) - (if (and (>= (length res) 4) - (eq (aref res 0) ?\C-x) - (eq (aref res 1) ?\() - (eq (aref res (- (length res) 2)) ?\C-x) - (eq (aref res (- (length res) 1)) ?\))) - (apply #'vector (let ((lres (append res nil))) - ;; Remove the first and last two elements. - (setq lres (cdr (cdr lres))) - (nreverse lres) - (setq lres (cdr (cdr lres))) - (nreverse lres))) - res)))) - -(defun key-valid-p (keys) - "Say whether KEYS is a valid key. -A key is a string consisting of one or more key strokes. -The key strokes are separated by single space characters. - -Each key stroke is either a single character, or the name of an -event, surrounded by angle brackets. In addition, any key stroke -may be preceded by one or more modifier keys. Finally, a limited -number of characters have a special shorthand syntax. - -Here's some example key sequences. - - \"f\" (the key 'f') - \"S o m\" (a three key sequence of the keys 'S', 'o' and 'm') - \"C-c o\" (a two key sequence of the keys 'c' with the control modifier - and then the key 'o') - \"H-\" (the key named \"left\" with the hyper modifier) - \"M-RET\" (the \"return\" key with a meta modifier) - \"C-M-\" (the \"space\" key with both the control and meta modifiers) - -These are the characters that have shorthand syntax: -NUL, RET, TAB, LFD, ESC, SPC, DEL. - -Modifiers have to be specified in this order: - - A-C-H-M-S-s - -which is - - Alt-Control-Hyper-Meta-Shift-super" - (declare (pure t) (side-effect-free t)) - (and - (stringp keys) - (string-match-p "\\`[^ ]+\\( [^ ]+\\)*\\'" keys) - (save-match-data - (catch 'exit - (let ((prefixes - "\\(A-\\)?\\(C-\\)?\\(H-\\)?\\(M-\\)?\\(S-\\)?\\(s-\\)?") - (case-fold-search nil)) - (dolist (key (split-string keys " ")) - ;; Every key might have these modifiers, and they should be - ;; in this order. - (when (string-match (concat "\\`" prefixes) key) - (setq key (substring key (match-end 0)))) - (unless (or (and (= (length key) 1) - ;; Don't accept control characters as keys. - (not (< (aref key 0) ?\s)) - ;; Don't accept Meta'd characters as keys. - (or (multibyte-string-p key) - (not (<= 127 (aref key 0) 255)))) - (and (string-match-p "\\`<[-_A-Za-z0-9]+>\\'" key) - ;; Don't allow . - (= (progn - (string-match - (concat "\\`<" prefixes) key) - (match-end 0)) - 1)) - (string-match-p - "\\`\\(NUL\\|RET\\|TAB\\|LFD\\|ESC\\|SPC\\|DEL\\)\\'" - key)) - ;; Invalid. - (throw 'exit nil))) - t))))) - -(defun key-translate (from to) - "Translate character FROM to TO on the current terminal. -This function creates a `keyboard-translate-table' if necessary -and then modifies one entry in it. - -Both KEY and TO are strings that satisfy `key-valid-p'." - (declare (compiler-macro - (lambda (form) (keymap--compile-check from to) form))) - (keymap--check from) - (keymap--check to) - (or (char-table-p keyboard-translate-table) - (setq keyboard-translate-table - (make-char-table 'keyboard-translate-table nil))) - (aset keyboard-translate-table (key-parse from) (key-parse to))) - -(defun keymap-lookup (keymap key &optional accept-default no-remap position) - "Return the binding for command KEY. -KEY is a string that satisfies `key-valid-p'. - -If KEYMAP is nil, look up in the current keymaps. If non-nil, it -should either be a keymap or a list of keymaps, and only these -keymap(s) will be consulted. - -The binding is probably a symbol with a function definition. - -Normally, `keymap-lookup' ignores bindings for t, which act as -default bindings, used when nothing else in the keymap applies; -this makes it usable as a general function for probing keymaps. -However, if the optional second argument ACCEPT-DEFAULT is -non-nil, `keymap-lookup' does recognize the default bindings, -just as `read-key-sequence' does. - -Like the normal command loop, `keymap-lookup' will remap the -command resulting from looking up KEY by looking up the command -in the current keymaps. However, if the optional third argument -NO-REMAP is non-nil, `keymap-lookup' returns the unmapped -command. - -If KEY is a key sequence initiated with the mouse, the used keymaps -will depend on the clicked mouse position with regard to the buffer -and possible local keymaps on strings. - -If the optional argument POSITION is non-nil, it specifies a mouse -position as returned by `event-start' and `event-end', and the lookup -occurs in the keymaps associated with it instead of KEY. It can also -be a number or marker, in which case the keymap properties at the -specified buffer position instead of point are used." - (declare (compiler-macro (lambda (form) (keymap--compile-check key) form))) - (keymap--check key) - (when (and keymap position) - (error "Can't pass in both keymap and position")) - (if keymap - (let ((value (lookup-key keymap (key-parse key) accept-default))) - (if (and (not no-remap) - (symbolp value)) - (or (command-remapping value) value) - value)) - (key-binding (kbd key) accept-default no-remap position))) - -(defun keymap-local-lookup (keys &optional accept-default) - "Return the binding for command KEYS in current local keymap only. -KEY is a string that satisfies `key-valid-p'. - -The binding is probably a symbol with a function definition. - -If optional argument ACCEPT-DEFAULT is non-nil, recognize default -bindings; see the description of `keymap-lookup' for more details -about this." - (declare (compiler-macro (lambda (form) (keymap--compile-check keys) form))) - (when-let ((map (current-local-map))) - (keymap-lookup map keys accept-default))) - -(defun keymap-global-lookup (keys &optional accept-default message) - "Return the binding for command KEYS in current global keymap only. -KEY is a string that satisfies `key-valid-p'. - -The binding is probably a symbol with a function definition. -This function's return values are the same as those of `keymap-lookup' -\(which see). - -If optional argument ACCEPT-DEFAULT is non-nil, recognize default -bindings; see the description of `keymap-lookup' for more details -about this. - -If MESSAGE (and interactively), message the result." - (declare (compiler-macro (lambda (form) (keymap--compile-check keys) form))) - (interactive - (list (key-description (read-key-sequence "Look up key in global keymap: ")) - nil t)) - (let ((def (keymap-lookup (current-global-map) keys accept-default))) - (when message - (message "%s is bound to %s globally" keys def)) - def)) - - -;;; define-keymap and defvar-keymap - -(defun define-keymap--compile (form &rest args) - ;; This compiler macro is only there for compile-time - ;; error-checking; it does not change the call in any way. - (while (and args - (keywordp (car args)) - (not (eq (car args) :menu))) - (unless (memq (car args) '(:full :keymap :parent :suppress :name :prefix)) - (byte-compile-warn "Invalid keyword: %s" (car args))) - (setq args (cdr args)) - (when (null args) - (byte-compile-warn "Uneven number of keywords in %S" form)) - (setq args (cdr args))) - ;; Bindings. - (while args - (let ((key (pop args))) - (when (and (stringp key) (not (key-valid-p key))) - (byte-compile-warn "Invalid `kbd' syntax: %S" key))) - (when (null args) - (byte-compile-warn "Uneven number of key bindings in %S" form)) - (setq args (cdr args))) - form) - -(defun define-keymap (&rest definitions) - "Create a new keymap and define KEY/DEFINITION pairs as key bindings. -The new keymap is returned. - -Options can be given as keywords before the KEY/DEFINITION -pairs. Available keywords are: - -:full If non-nil, create a chartable alist (see `make-keymap'). - If nil (i.e., the default), create a sparse keymap (see - `make-sparse-keymap'). - -:suppress If non-nil, the keymap will be suppressed (see `suppress-keymap'). - If `nodigits', treat digits like other chars. - -:parent If non-nil, this should be a keymap to use as the parent - (see `set-keymap-parent'). - -:keymap If non-nil, instead of creating a new keymap, the given keymap - will be destructively modified instead. - -:name If non-nil, this should be a string to use as the menu for - the keymap in case you use it as a menu with `x-popup-menu'. - -:prefix If non-nil, this should be a symbol to be used as a prefix - command (see `define-prefix-command'). If this is the case, - this symbol is returned instead of the map itself. - -KEY/DEFINITION pairs are as KEY and DEF in `keymap-set'. KEY can -also be the special symbol `:menu', in which case DEFINITION -should be a MENU form as accepted by `easy-menu-define'. - -\(fn &key FULL PARENT SUPPRESS NAME PREFIX KEYMAP &rest [KEY DEFINITION]...)" - (declare (indent defun) - (compiler-macro define-keymap--compile)) - (let (full suppress parent name prefix keymap) - ;; Handle keywords. - (while (and definitions - (keywordp (car definitions)) - (not (eq (car definitions) :menu))) - (let ((keyword (pop definitions))) - (unless definitions - (error "Missing keyword value for %s" keyword)) - (let ((value (pop definitions))) - (pcase keyword - (:full (setq full value)) - (:keymap (setq keymap value)) - (:parent (setq parent value)) - (:suppress (setq suppress value)) - (:name (setq name value)) - (:prefix (setq prefix value)) - (_ (error "Invalid keyword: %s" keyword)))))) - - (when (and prefix - (or full parent suppress keymap)) - (error "A prefix keymap can't be defined with :full/:parent/:suppress/:keymap keywords")) - - (when (and keymap full) - (error "Invalid combination: :keymap with :full")) - - (let ((keymap (cond - (keymap keymap) - (prefix (define-prefix-command prefix nil name)) - (full (make-keymap name)) - (t (make-sparse-keymap name))))) - (when suppress - (suppress-keymap keymap (eq suppress 'nodigits))) - (when parent - (set-keymap-parent keymap parent)) - - ;; Do the bindings. - (while definitions - (let ((key (pop definitions))) - (unless definitions - (error "Uneven number of key/definition pairs")) - (let ((def (pop definitions))) - (if (eq key :menu) - (easy-menu-define nil keymap "" def) - (keymap-set keymap key def))))) - keymap))) - -(defmacro defvar-keymap (variable-name &rest defs) - "Define VARIABLE-NAME as a variable with a keymap definition. -See `define-keymap' for an explanation of the keywords and KEY/DEFINITION. - -In addition to the keywords accepted by `define-keymap', this -macro also accepts a `:doc' keyword, which (if present) is used -as the variable documentation string. - -\(fn VARIABLE-NAME &key DOC FULL PARENT SUPPRESS NAME PREFIX KEYMAP &rest [KEY DEFINITION]...)" - (declare (indent 1)) - (let ((opts nil) - doc) - (while (and defs - (keywordp (car defs)) - (not (eq (car defs) :menu))) - (let ((keyword (pop defs))) - (unless defs - (error "Uneven number of keywords")) - (if (eq keyword :doc) - (setq doc (pop defs)) - (push keyword opts) - (push (pop defs) opts)))) - (unless (zerop (% (length defs) 2)) - (error "Uneven number of key/definition pairs: %s" defs)) - `(defvar ,variable-name - (define-keymap ,@(nreverse opts) ,@defs) - ,@(and doc (list doc))))) - -(provide 'keymap) - -;;; keymap.el ends here diff --git a/lisp/compat/tab-bar.el b/lisp/compat/tab-bar.el deleted file mode 100644 index d49fc2e..0000000 --- a/lisp/compat/tab-bar.el +++ /dev/null @@ -1,2424 +0,0 @@ -;;; tab-bar.el --- frame-local tabs with named persistent window configurations -*- lexical-binding: t; -*- - -;; Copyright (C) 2019-2022 Free Software Foundation, Inc. - -;; Author: Juri Linkov -;; Keywords: frames tabs -;; Maintainer: emacs-devel@gnu.org - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see . - -;;; Commentary: - -;; Provides `tab-bar-mode' to control display of the tab bar and -;; bindings for the global tab bar. - -;; The normal global binding for [tab-bar] (below) uses the value of -;; `tab-bar-map' as the actual keymap to define the tab bar. - -;;; Code: - -(eval-when-compile - (require 'cl-lib) - (require 'seq)) - - -(defgroup tab-bar nil - "Frame-local tabs." - :group 'convenience - :version "27.1") - -(defgroup tab-bar-faces '((tab-bar custom-face)) ; tab-bar is defined in faces.el - "Faces used in the tab bar." - :group 'tab-bar - :group 'faces - :version "27.1") - -(defface tab-bar-tab - '((default - :inherit tab-bar) - (((class color) (min-colors 88)) - :box (:line-width 1 :style released-button)) - (t - :inverse-video nil)) - "Tab bar face for selected tab." - :version "27.1" - :group 'tab-bar-faces) - -(defface tab-bar-tab-inactive - '((default - :inherit tab-bar-tab) - (((class color) (min-colors 88)) - :background "grey75") - (t - :inverse-video t)) - "Tab bar face for non-selected tab." - :version "27.1" - :group 'tab-bar-faces) - -(defface tab-bar-tab-group-current - '((t :inherit tab-bar-tab :box nil :weight bold)) - "Tab bar face for current group tab." - :version "28.1" - :group 'tab-bar-faces) - -(defface tab-bar-tab-group-inactive - '((t :inherit (shadow tab-bar-tab-inactive))) - "Tab bar face for inactive group tab." - :version "28.1" - :group 'tab-bar-faces) - -(defface tab-bar-tab-ungrouped - '((t :inherit (shadow tab-bar-tab-inactive))) - "Tab bar face for ungrouped tab when tab groups are used." - :version "28.1" - :group 'tab-bar-faces) - - -(defcustom tab-bar-select-tab-modifiers '() - "List of modifier keys for selecting tab-bar tabs by their numbers. -Possible modifier keys are `control', `meta', `shift', `hyper', `super' and -`alt'. Pressing one of the modifiers in the list and a digit selects the -tab whose number equals the digit (see `tab-bar-select-tab'). -The digit 9 selects the last (rightmost) tab (see `tab-last'). -The digit 0 selects the most recently visited tab (see `tab-recent'). -For easier selection of tabs by their numbers, consider customizing -`tab-bar-tab-hints', which will show tab numbers alongside the tab name." - :type '(set :tag "Tab selection modifier keys" - (const control) - (const meta) - (const shift) - (const hyper) - (const super) - (const alt)) - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - ;; Reenable the tab-bar with new keybindings - (when tab-bar-mode - (tab-bar--undefine-keys) - (tab-bar--define-keys))) - :group 'tab-bar - :version "27.1") - -(defun tab-bar--define-keys () - "Install key bindings for switching between tabs if the user has configured them." - (when tab-bar-select-tab-modifiers - (global-set-key (vector (append tab-bar-select-tab-modifiers (list ?0))) - 'tab-recent) - (dotimes (i 8) - (global-set-key (vector (append tab-bar-select-tab-modifiers - (list (+ i 1 ?0)))) - 'tab-bar-select-tab)) - (global-set-key (vector (append tab-bar-select-tab-modifiers (list ?9))) - 'tab-last)) - ;; Don't override user customized key bindings - (unless (global-key-binding [(control tab)]) - (global-set-key [(control tab)] 'tab-next)) - (unless (global-key-binding [(control shift tab)]) - (global-set-key [(control shift tab)] 'tab-previous)) - (unless (global-key-binding [(control shift iso-lefttab)]) - (global-set-key [(control shift iso-lefttab)] 'tab-previous)) - - ;; Replace default value with a condition that supports displaying - ;; global-mode-string in the tab bar instead of the mode line. - (when (and (memq 'tab-bar-format-global tab-bar-format) - (member '(global-mode-string ("" global-mode-string)) - mode-line-misc-info)) - (setf (alist-get 'global-mode-string mode-line-misc-info) - '(("" (:eval (if (and tab-bar-mode - (memq 'tab-bar-format-global - tab-bar-format)) - "" global-mode-string))))))) - -(defun tab-bar--undefine-keys () - "Uninstall key bindings previously bound by `tab-bar--define-keys'." - (when (eq (global-key-binding [(control tab)]) 'tab-next) - (global-unset-key [(control tab)])) - (when (eq (global-key-binding [(control shift tab)]) 'tab-previous) - (global-unset-key [(control shift tab)])) - (when (eq (global-key-binding [(control shift iso-lefttab)]) 'tab-previous) - (global-unset-key [(control shift iso-lefttab)]))) - -(defun tab-bar--load-buttons () - "Load the icons for the tab buttons." - (when (and tab-bar-new-button - (not (get-text-property 0 'display tab-bar-new-button))) - ;; This file is pre-loaded so only here we can use the right data-directory: - (add-text-properties 0 (length tab-bar-new-button) - `(display (image :type xpm - :file "tabs/new.xpm" - :margin ,tab-bar-button-margin - :ascent center)) - tab-bar-new-button)) - - (when (and tab-bar-close-button - (not (get-text-property 0 'display tab-bar-close-button))) - ;; This file is pre-loaded so only here we can use the right data-directory: - (add-text-properties 0 (length tab-bar-close-button) - `(display (image :type xpm - :file "tabs/close.xpm" - :margin ,tab-bar-button-margin - :ascent center)) - tab-bar-close-button))) - -(defun tab-bar--tab-bar-lines-for-frame (frame) - "Determine and return the value of `tab-bar-lines' for FRAME. -Return 0 if `tab-bar-mode' is not enabled. Otherwise return -either 1 or 0 depending on the value of the customizable variable -`tab-bar-show', which see." - (cond - ((not tab-bar-mode) 0) - ((not tab-bar-show) 0) - ((eq tab-bar-show t) 1) - ((natnump tab-bar-show) - (if (> (length (funcall tab-bar-tabs-function frame)) tab-bar-show) 1 0)))) - -(defun tab-bar--update-tab-bar-lines (&optional frames) - "Update the `tab-bar-lines' frame parameter in FRAMES. -If the optional parameter FRAMES is omitted, update only -the currently selected frame. If it is t, update all frames -as well as the default for new frames. Otherwise FRAMES should be -a list of frames to update." - (let ((frame-lst (cond ((null frames) - (list (selected-frame))) - ((eq frames t) - (frame-list)) - (t frames)))) - ;; Loop over all frames and update `tab-bar-lines' - (dolist (frame frame-lst) - (unless (frame-parameter frame 'tab-bar-lines-keep-state) - (set-frame-parameter frame 'tab-bar-lines - (tab-bar--tab-bar-lines-for-frame frame))))) - ;; Update `default-frame-alist' - (when (eq frames t) - (setq default-frame-alist - (cons (cons 'tab-bar-lines (if (and tab-bar-mode (eq tab-bar-show t)) 1 0)) - (assq-delete-all 'tab-bar-lines default-frame-alist))))) - -(define-minor-mode tab-bar-mode - "Toggle the tab bar in all graphical frames (Tab Bar mode)." - :global t - ;; It's defined in C/cus-start, this stops the d-m-m macro defining it again. - :variable tab-bar-mode - - ;; Recalculate `tab-bar-lines' for all frames - (tab-bar--update-tab-bar-lines t) - - (when tab-bar-mode - (tab-bar--load-buttons)) - (if tab-bar-mode - (tab-bar--define-keys) - (tab-bar--undefine-keys))) - - -;;; Key bindings - -(defun tab-bar--key-to-number (key) - "Return the tab number represented by KEY. -If KEY is a symbol 'tab-N', where N is a tab number, the value is N. -If KEY is \\='current-tab, the value is nil. -For any other value of KEY, the value is t." - (cond - ((null key) t) - ((eq key 'current-tab) nil) - ((let ((key-name (format "%S" key))) - (when (string-prefix-p "tab-" key-name) - (string-to-number (string-replace "tab-" "" key-name))))) - (t t))) - -(defvar tab-bar--dragging-in-progress) - -(defun tab-bar--event-to-item (posn) - "This function extracts extra info from the mouse event at position POSN. -It returns a list of the form (KEY KEY-BINDING CLOSE-P), where: - KEY is a symbol representing a tab, such as \\='tab-1 or \\='current-tab; - KEY-BINDING is the binding of KEY; - CLOSE-P is non-nil if the mouse event was a click on the close button \"x\", - nil otherwise." - (setq tab-bar--dragging-in-progress nil) - (if (posn-window posn) - (let ((caption (car (posn-string posn)))) - (when caption - (get-text-property 0 'menu-item caption))) - ;; Text-mode emulation of switching tabs on the tab bar. - ;; This code is used when you click the mouse in the tab bar - ;; on a console which has no window system but does have a mouse. - (let* ((x-position (car (posn-x-y posn))) - (keymap (lookup-key (cons 'keymap (nreverse (current-active-maps))) [tab-bar])) - (column 0)) - (when x-position - (catch 'done - (map-keymap - (lambda (key binding) - (when (eq (car-safe binding) 'menu-item) - (when (> (+ column (length (nth 1 binding))) x-position) - (throw 'done (list key (nth 2 binding) - (get-text-property - (- x-position column) - 'close-tab (nth 1 binding))))) - (setq column (+ column (length (nth 1 binding)))))) - keymap)))))) - -(defun tab-bar-mouse-down-1 (event) - "Select the tab at mouse click, or add a new tab on the tab bar. -Whether this command adds a new tab or selects an existing tab -depends on whether the click is on the \"+\" button or on an -existing tab." - (interactive "e") - (let* ((item (tab-bar--event-to-item (event-start event))) - (tab-number (tab-bar--key-to-number (nth 0 item)))) - (setq tab-bar--dragging-in-progress t) - ;; Don't close the tab when clicked on the close button. Also - ;; don't add new tab on down-mouse. Let `tab-bar-mouse-1' do this. - (unless (or (memq (car item) '(add-tab history-back history-forward)) - (nth 2 item)) - (if (functionp (nth 1 item)) - (call-interactively (nth 1 item)) - (unless (eq tab-number t) - (tab-bar-select-tab tab-number)))))) - -(defun tab-bar-mouse-1 (event) - "Close the tab whose \"x\" close button you click. -See also `tab-bar-mouse-close-tab', which closes the tab -regardless of where you click on it. Also add a new tab." - (interactive "e") - (let* ((item (tab-bar--event-to-item (event-start event))) - (tab-number (tab-bar--key-to-number (nth 0 item)))) - (cond - ((and (memq (car item) '(add-tab history-back history-forward)) - (functionp (nth 1 item))) - (call-interactively (nth 1 item))) - ((and (nth 2 item) (not (eq tab-number t))) - (tab-bar-close-tab tab-number))))) - -(defun tab-bar-mouse-close-tab (event) - "Close the tab you click on. -This is in contrast with `tab-bar-mouse-1' that closes a tab -only when you click on its \"x\" close button." - (interactive "e") - (let* ((item (tab-bar--event-to-item (event-start event))) - (tab-number (tab-bar--key-to-number (nth 0 item)))) - (unless (eq tab-number t) - (tab-bar-close-tab tab-number)))) - -(defun tab-bar-mouse-context-menu (event) - "Pop up the context menu for the tab on which you click." - (interactive "e") - (let* ((item (tab-bar--event-to-item (event-start event))) - (tab-number (tab-bar--key-to-number (nth 0 item))) - (menu (make-sparse-keymap (propertize "Context Menu" 'hide t)))) - - (cond - ((eq tab-number t) - (define-key-after menu [new-tab] - '(menu-item "New tab" tab-bar-new-tab - :help "Create a new tab")) - (when tab-bar-closed-tabs - (define-key-after menu [undo-close] - '(menu-item "Reopen closed tab" tab-bar-undo-close-tab - :help "Undo closing the tab")))) - - (t - (define-key-after menu [duplicate-tab] - `(menu-item "Duplicate" (lambda () (interactive) - (tab-bar-duplicate-tab - nil ,tab-number)) - :help "Clone the tab")) - (define-key-after menu [detach-tab] - `(menu-item "Detach" (lambda () (interactive) - (tab-bar-detach-tab - ,tab-number)) - :help "Move the tab to new frame")) - (define-key-after menu [close] - `(menu-item "Close" (lambda () (interactive) - (tab-bar-close-tab ,tab-number)) - :help "Close the tab")) - (define-key-after menu [close-other] - `(menu-item "Close other tabs" - (lambda () (interactive) - (tab-bar-close-other-tabs ,tab-number)) - :help "Close all other tabs")))) - - (popup-menu menu event))) - -(defun tab-bar-mouse-move-tab (event) - "Move a tab to a different position on the tab bar. -This command should be bound to a drag event. It moves the tab -at the mouse-down event to the position at mouse-up event." - (interactive "e") - (setq tab-bar--dragging-in-progress nil) - (let ((from (tab-bar--key-to-number - (nth 0 (tab-bar--event-to-item - (event-start event))))) - (to (tab-bar--key-to-number - (nth 0 (tab-bar--event-to-item - (event-end event)))))) - (unless (or (eq from to) (eq from t) (eq to t)) - (tab-bar-move-tab-to - (if (null to) (1+ (tab-bar--current-tab-index)) to) from)))) - -(defvar tab-bar-map - (let ((map (make-sparse-keymap))) - (define-key map [down-mouse-1] 'tab-bar-mouse-down-1) - (define-key map [drag-mouse-1] 'tab-bar-mouse-move-tab) - (define-key map [mouse-1] 'tab-bar-mouse-1) - (define-key map [down-mouse-2] 'tab-bar-mouse-close-tab) - (define-key map [mouse-2] 'ignore) - (define-key map [down-mouse-3] 'tab-bar-mouse-context-menu) - - (define-key map [mouse-4] 'tab-previous) - (define-key map [mouse-5] 'tab-next) - (define-key map [wheel-up] 'tab-previous) - (define-key map [wheel-down] 'tab-next) - (define-key map [wheel-left] 'tab-previous) - (define-key map [wheel-right] 'tab-next) - - (define-key map [S-mouse-4] 'tab-bar-move-tab-backward) - (define-key map [S-mouse-5] 'tab-bar-move-tab) - (define-key map [S-wheel-up] 'tab-bar-move-tab-backward) - (define-key map [S-wheel-down] 'tab-bar-move-tab) - (define-key map [S-wheel-left] 'tab-bar-move-tab-backward) - (define-key map [S-wheel-right] 'tab-bar-move-tab) - - map) - "Keymap for the commands used on the tab bar.") - -(global-set-key [tab-bar] - `(menu-item ,(purecopy "tab bar") ignore - :filter tab-bar-make-keymap)) - -(defun tab-bar-make-keymap (&optional _ignore) - "Generate an actual keymap from `tab-bar-map'. -Its main job is to show tabs in the tab bar -and to bind mouse events to the commands." - (tab-bar-make-keymap-1)) - - -(defun toggle-tab-bar-mode-from-frame (&optional arg) - "Toggle tab bar on or off, based on the status of the current frame. -Used in the Show/Hide menu, to have the toggle reflect the current frame. -See `tab-bar-mode' for more information." - (interactive (list (or current-prefix-arg 'toggle))) - (if (eq arg 'toggle) - (tab-bar-mode (if (> (frame-parameter nil 'tab-bar-lines) 0) 0 1)) - (tab-bar-mode arg))) - -(defun toggle-frame-tab-bar (&optional frame) - "Toggle tab bar of the selected frame. -When calling from Lisp, use the optional argument FRAME to toggle -the tab bar on that frame. -This is useful if you want to enable the tab bar individually -on each new frame when the global `tab-bar-mode' is disabled, -or if you want to disable the tab bar individually on each -new frame when the global `tab-bar-mode' is enabled, by using - - (add-hook 'after-make-frame-functions 'toggle-frame-tab-bar)" - (interactive) - (set-frame-parameter frame 'tab-bar-lines - (if (> (frame-parameter frame 'tab-bar-lines) 0) 0 1)) - (set-frame-parameter frame 'tab-bar-lines-keep-state - (not (frame-parameter frame 'tab-bar-lines-keep-state)))) - - -(defcustom tab-bar-show t - "Defines when to show the tab bar. -If t, the default, enable `tab-bar-mode' automatically upon using -the commands that create new window configurations (e.g., `tab-new'). -If a non-negative integer, show the tab bar only if the number of -the tabs exceeds the value of this variable. In particular, -if the value is 1, hide the tab bar when it has only one tab, and -show it again once more tabs are created. A value that is a -non-negative integer also makes the tab bar appearance be different -on different frames: the tab bar can be shown on some frames and -hidden on others, depending on how many tab-bar tabs are on that -frame, and whether that number is greater than the numerical value -of this variable. -If nil, always keep the tab bar hidden. In this case it's still -possible to use persistent named window configurations by relying on -keyboard commands `tab-new', `tab-close', `tab-next', `tab-switcher', etc. - -Setting this variable directly does not take effect; please customize -it (see the info node `Easy Customization'), then it will automatically -update the tab bar on all frames according to the new value. - -To enable or disable the tab bar individually on each frame, -you can use the command `toggle-frame-tab-bar'." - :type '(choice (const :tag "Always" t) - (const :tag "When more than one tab" 1) - (const :tag "Never" nil)) - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (if val - (tab-bar-mode 1) - (tab-bar--update-tab-bar-lines t))) - :group 'tab-bar - :version "27.1") - -(defcustom tab-bar-new-tab-choice t - "Defines what to show in a new tab. -If t, start a new tab with the current buffer, i.e. the buffer -that was current before calling the command that adds a new tab -(this is the same what `make-frame' does by default). -If the value is a string, use it as a buffer name to switch to -if such buffer exists, or switch to a buffer visiting the file or -directory that the string specifies. If the value is a function, -call it with no arguments and switch to the buffer that it returns. -If nil, duplicate the contents of the tab that was active -before calling the command that adds a new tab." - :type '(choice (const :tag "Current buffer" t) - (string :tag "Buffer" "*scratch*") - (directory :tag "Directory" :value "~/") - (file :tag "File" :value "~/.emacs") - (function :tag "Function") - (const :tag "Duplicate tab" nil)) - :group 'tab-bar - :version "27.1") - -(defcustom tab-bar-new-tab-group t - "Defines what group to assign to a new tab. -If nil, don't set a default group automatically. -If t, inherit the group name from the previous tab. -If the value is a string, use it as the group name of a new tab. -If the value is a function, call it with no arguments -to get the group name." - :type '(choice (const :tag "No automatic group" nil) - (const :tag "Inherit group from previous tab" t) - (string :tag "Fixed group name") - (function :tag "Function that returns group name")) - :group 'tab-bar - :version "28.1") - -(defcustom tab-bar-new-button-show t - "If non-nil, show the \"New tab\" button in the tab bar. -When this is nil, you can create new tabs with \\[tab-new]." - :type 'boolean - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "27.1") -(make-obsolete-variable 'tab-bar-new-button-show 'tab-bar-format "28.1") - -(defvar tab-bar-new-button " + " - "Button for creating a new tab.") - -(defcustom tab-bar-close-button-show t - "Defines where to show the close tab button. -If t, show the close tab button on all tabs. -If `selected', show it only on the selected tab. -If `non-selected', show it only on non-selected tab. -If nil, don't show it at all." - :type '(choice (const :tag "On all tabs" t) - (const :tag "On selected tab" selected) - (const :tag "On non-selected tabs" non-selected) - (const :tag "None" nil)) - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "27.1") - -(defvar tab-bar-close-button - (propertize " x" - 'close-tab t - :help "Click to close tab") - "Button for closing the clicked tab.") - -(defvar tab-bar-back-button " < " - "Button for going back in tab history.") - -(defvar tab-bar-forward-button " > " - "Button for going forward in tab history.") - -(defcustom tab-bar-tab-hints nil - "Show absolute numbers on tabs in the tab bar before the tab name. -This helps to select the tab by its number using `tab-bar-select-tab' -and `tab-bar-select-tab-modifiers'." - :type 'boolean - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "27.1") - -(defvar tab-bar-separator nil - "String that delimits tabs.") - -(defun tab-bar-separator () - "Separator between tabs." - (or tab-bar-separator (if window-system " " "|"))) - - -(defcustom tab-bar-tab-name-function #'tab-bar-tab-name-current - "Function to get a tab name. -Function gets no arguments. -The choice is between displaying only the name of the current buffer -in the tab name (default), or displaying the names of all buffers -from all windows in the window configuration." - :type '(choice (const :tag "Selected window buffer" - tab-bar-tab-name-current) - (const :tag "Selected window buffer with window count" - tab-bar-tab-name-current-with-count) - (const :tag "Truncated buffer name" - tab-bar-tab-name-truncated) - (const :tag "All window buffers" - tab-bar-tab-name-all) - (function :tag "Function")) - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "27.1") - -(defun tab-bar-tab-name-current () - "Generate tab name from the buffer of the selected window." - (buffer-name (window-buffer (minibuffer-selected-window)))) - -(defun tab-bar-tab-name-current-with-count () - "Generate tab name from the buffer of the selected window. -Also add the number of windows in the window configuration." - (let ((count (length (window-list-1 nil 'nomini))) - (name (window-buffer (minibuffer-selected-window)))) - (if (> count 1) - (format "%s (%d)" name count) - (format "%s" name)))) - -(defun tab-bar-tab-name-all () - "Generate tab name from buffers of all windows." - (mapconcat #'buffer-name - (delete-dups (mapcar #'window-buffer - (window-list-1 (frame-first-window) - 'nomini))) - ", ")) - -(defcustom tab-bar-tab-name-truncated-max 20 - "Maximum length of the tab name from the current buffer. -Effective when `tab-bar-tab-name-function' is customized -to `tab-bar-tab-name-truncated'." - :type 'integer - :group 'tab-bar - :version "27.1") - -(defvar tab-bar-tab-name-ellipsis t) - -(defun tab-bar-tab-name-truncated () - "Generate tab name from the buffer of the selected window. -Truncate it to the length specified by `tab-bar-tab-name-truncated-max'. -Append ellipsis `tab-bar-tab-name-ellipsis' in this case." - (let ((tab-name (buffer-name (window-buffer (minibuffer-selected-window))))) - (if (< (length tab-name) tab-bar-tab-name-truncated-max) - tab-name - (propertize (truncate-string-to-width - tab-name tab-bar-tab-name-truncated-max nil nil - tab-bar-tab-name-ellipsis) - 'help-echo tab-name)))) - - -(defvar tab-bar-tabs-function #'tab-bar-tabs - "Function to get a list of tabs to display in the tab bar. -This function should have one optional argument FRAME, -defaulting to the selected frame when nil. -It should return a list of alists with parameters -that include at least the element (name . TAB-NAME). -For example, \\='((tab (name . \"Tab 1\")) (current-tab (name . \"Tab 2\"))) -By default, use function `tab-bar-tabs'.") - -(defun tab-bar-tabs (&optional frame) - "Return a list of tabs belonging to the FRAME. -Ensure the frame parameter `tabs' is pre-populated. -Update the current tab name when it exists. -Return its existing value or a new value." - (let ((tabs (frame-parameter frame 'tabs))) - (if tabs - (let* ((current-tab (tab-bar--current-tab-find tabs)) - (current-tab-name (assq 'name current-tab)) - (current-tab-explicit-name (assq 'explicit-name current-tab))) - (when (and current-tab-name - current-tab-explicit-name - (not (cdr current-tab-explicit-name))) - (setf (cdr current-tab-name) - (funcall tab-bar-tab-name-function)))) - ;; Create default tabs - (setq tabs (list (tab-bar--current-tab-make))) - (tab-bar-tabs-set tabs frame)) - tabs)) - -(defun tab-bar-tabs-set (tabs &optional frame) - "Set a list of TABS on the FRAME." - (set-frame-parameter frame 'tabs tabs)) - - -(defcustom tab-bar-tab-face-function #'tab-bar-tab-face-default - "Function to define a tab face. -Function gets one argument: a tab." - :type 'function - :group 'tab-bar - :version "28.1") - -(defun tab-bar-tab-face-default (tab) - (if (eq (car tab) 'current-tab) 'tab-bar-tab 'tab-bar-tab-inactive)) - -(defcustom tab-bar-tab-name-format-function #'tab-bar-tab-name-format-default - "Function to format a tab name. -Function gets two arguments, the tab and its number, and should return -the formatted tab name to display in the tab bar." - :type 'function - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "28.1") - -(defun tab-bar-tab-name-format-default (tab i) - (let ((current-p (eq (car tab) 'current-tab))) - (propertize - (concat (if tab-bar-tab-hints (format "%d " i) "") - (alist-get 'name tab) - (or (and tab-bar-close-button-show - (not (eq tab-bar-close-button-show - (if current-p 'non-selected 'selected))) - tab-bar-close-button) - "")) - 'face (funcall tab-bar-tab-face-function tab)))) - -(defcustom tab-bar-format '(tab-bar-format-history - tab-bar-format-tabs - tab-bar-separator - tab-bar-format-add-tab) - "Template for displaying tab bar items. -Every item in the list is a function that returns -a string, or a list of menu-item elements, or nil. -Adding a function to the list causes the tab bar to show -that string, or display a tab button which, when clicked, -will invoke the command that is the binding of the menu item. -The menu-item binding of nil will produce a tab clicking -on which will select that tab. The menu-item's title is -displayed as the label of the tab. -If a function returns nil, it doesn't directly affect the -tab bar appearance, but can do that by some side-effect. -If the list ends with `tab-bar-format-align-right' and -`tab-bar-format-global', then after enabling `display-time-mode' -(or any other mode that uses `global-mode-string'), -it will display time aligned to the right on the tab bar instead -of the mode line. Replacing `tab-bar-format-tabs' with -`tab-bar-format-tabs-groups' will group tabs on the tab bar." - :type 'hook - :options '(tab-bar-format-menu-bar - tab-bar-format-history - tab-bar-format-tabs - tab-bar-format-tabs-groups - tab-bar-separator - tab-bar-format-add-tab - tab-bar-format-align-right - tab-bar-format-global) - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "28.1") - -(defun tab-bar-menu-bar (event) - "Pop up the same menu as displayed by the menu bar. -Used by `tab-bar-format-menu-bar'." - (interactive "e") - (let ((menu (make-sparse-keymap (propertize "Menu Bar" 'hide t)))) - (run-hooks 'activate-menubar-hook 'menu-bar-update-hook) - (map-keymap (lambda (key binding) - (when (consp binding) - (define-key-after menu (vector key) - (copy-sequence binding)))) - (menu-bar-keymap)) - (popup-menu menu event))) - -(defun tab-bar-format-menu-bar () - "Produce the Menu button for the tab bar that shows the menu bar." - `((menu-bar menu-item (propertize "Menu" 'face 'tab-bar-tab-inactive) - tab-bar-menu-bar :help "Menu Bar"))) - -(defun tab-bar-format-history () - "Produce back and forward buttons for the tab bar. -These buttons will be shown when `tab-bar-history-mode' is enabled. -You can hide these buttons by customizing `tab-bar-format' and removing -`tab-bar-format-history' from it." - (when tab-bar-history-mode - `((sep-history-back menu-item ,(tab-bar-separator) ignore) - (history-back - menu-item ,tab-bar-back-button tab-bar-history-back - :help "Click to go back in tab history") - (sep-history-forward menu-item ,(tab-bar-separator) ignore) - (history-forward - menu-item ,tab-bar-forward-button tab-bar-history-forward - :help "Click to go forward in tab history")))) - -(defun tab-bar--format-tab (tab i) - "Format TAB using its index I and return the result as a keymap." - (append - `((,(intern (format "sep-%i" i)) menu-item ,(tab-bar-separator) ignore)) - (cond - ((eq (car tab) 'current-tab) - `((current-tab - menu-item - ,(funcall tab-bar-tab-name-format-function tab i) - ignore - :help "Current tab"))) - (t - `((,(intern (format "tab-%i" i)) - menu-item - ,(funcall tab-bar-tab-name-format-function tab i) - ,(alist-get 'binding tab) - :help "Click to visit tab")))) - (when (alist-get 'close-binding tab) - `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) - menu-item "" - ,(alist-get 'close-binding tab)))))) - -(defun tab-bar-format-tabs () - "Produce all the tabs for the tab bar." - (let ((i 0)) - (mapcan - (lambda (tab) - (setq i (1+ i)) - (tab-bar--format-tab tab i)) - (funcall tab-bar-tabs-function)))) - -(defcustom tab-bar-tab-group-function #'tab-bar-tab-group-default - "Function to get a tab group name. -Function gets one argument: a tab." - :type 'function - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "28.1") - -(defun tab-bar-tab-group-default (tab) - (alist-get 'group tab)) - -(defcustom tab-bar-tab-group-format-function #'tab-bar-tab-group-format-default - "Function to format a tab group name. -Function gets two arguments, a tab with a group name and its number, -and should return the formatted tab group name to display in the tab bar." - :type 'function - :initialize 'custom-initialize-default - :set (lambda (sym val) - (set-default sym val) - (force-mode-line-update)) - :group 'tab-bar - :version "28.1") - -(defun tab-bar-tab-group-format-default (tab i) - (propertize - (concat (if tab-bar-tab-hints (format "%d " i) "") - (funcall tab-bar-tab-group-function tab)) - 'face 'tab-bar-tab-group-inactive)) - -(defcustom tab-bar-tab-group-face-function #'tab-bar-tab-group-face-default - "Function to define a tab group face. -Function gets one argument: a tab." - :type 'function - :group 'tab-bar - :version "28.1") - -(defun tab-bar-tab-group-face-default (tab) - (if (not (or (eq (car tab) 'current-tab) - (funcall tab-bar-tab-group-function tab))) - 'tab-bar-tab-ungrouped - (tab-bar-tab-face-default tab))) - -(defun tab-bar--format-tab-group (tab i &optional current-p) - "Format TAB as a tab that represents a group of tabs. -The argument I is the tab index, and CURRENT-P is non-nil -when the tab is current. Return the result as a keymap." - (append - `((,(intern (format "sep-%i" i)) menu-item ,(tab-bar-separator) ignore)) - `((,(intern (format "group-%i" i)) - menu-item - ,(if current-p - (propertize (funcall tab-bar-tab-group-function tab) - 'face 'tab-bar-tab-group-current) - (funcall tab-bar-tab-group-format-function tab i)) - ,(if current-p 'ignore - (or - (alist-get 'binding tab) - `(lambda () - (interactive) - (tab-bar-select-tab ,i)))) - :help "Click to visit group")))) - -(defun tab-bar-format-tabs-groups () - "Produce tabs for the tab bar grouped according to their groups." - (let* ((tabs (funcall tab-bar-tabs-function)) - (current-group (funcall tab-bar-tab-group-function - (tab-bar--current-tab-find tabs))) - (previous-group nil) - (i 0)) - (mapcan - (lambda (tab) - (let ((tab-group (funcall tab-bar-tab-group-function tab))) - (setq i (1+ i)) - (prog1 (cond - ;; Show current group tabs and ungrouped tabs - ((or (equal tab-group current-group) (not tab-group)) - (append - ;; Prepend current group name before first tab - (when (and (not (equal previous-group tab-group)) tab-group) - (tab-bar--format-tab-group tab i t)) - ;; Override default tab faces to use group faces - (let ((tab-bar-tab-face-function tab-bar-tab-group-face-function)) - (tab-bar--format-tab tab i)))) - ;; Show first tab of other groups with a group name - ((not (equal previous-group tab-group)) - (tab-bar--format-tab-group tab i)) - ;; Hide other group tabs - (t nil)) - (setq previous-group tab-group)))) - tabs))) - -(defun tab-bar-format-add-tab () - "Button to add a new tab." - (when (and tab-bar-new-button-show tab-bar-new-button) - `((add-tab menu-item ,tab-bar-new-button tab-bar-new-tab - :help "New tab")))) - -(defun tab-bar-format-align-right () - "Align the rest of tab bar items to the right." - (let* ((rest (cdr (memq 'tab-bar-format-align-right tab-bar-format))) - (rest (tab-bar-format-list rest)) - (rest (mapconcat (lambda (item) (nth 2 item)) rest "")) - (hpos (length rest)) - (str (propertize " " 'display `(space :align-to (- right ,hpos))))) - `((align-right menu-item ,str ignore)))) - -(defun tab-bar-format-global () - "Produce display of `global-mode-string' in the tab bar. -When `tab-bar-format-global' is added to `tab-bar-format' -(possibly appended after `tab-bar-format-align-right'), -then modes that display information on the mode line -using `global-mode-string' will display the same text -on the tab bar instead." - `((global menu-item ,(string-trim-right (format-mode-line global-mode-string)) ignore))) - -(defun tab-bar-format-list (format-list) - (let ((i 0)) - (apply #'append - (mapcar - (lambda (format) - (setq i (1+ i)) - (cond - ((functionp format) - (let ((ret (funcall format))) - (when (stringp ret) - (setq ret `((,(intern (format "str-%i" i)) - menu-item ,ret ignore)))) - ret)))) - format-list)))) - -(defun tab-bar-make-keymap-1 () - "Generate an actual keymap from `tab-bar-map', without caching." - (append tab-bar-map (tab-bar-format-list tab-bar-format))) - - -;; Some window-configuration parameters don't need to be persistent. -;; Don't save to the desktop file such tab parameters that are saved -;; as "Unprintable entity" so can't be used after restoring the desktop. -;; Actually tab-bar-select-tab already can handle unprintable entities, -;; but it's better not to waste the desktop file with useless data. -(defun frameset-filter-tabs (current _filtered _parameters saving) - (if saving - (mapcar (lambda (current) - (if (consp current) - (seq-reduce (lambda (current param) - (assq-delete-all param current)) - '(wc wc-point wc-bl wc-bbl - wc-history-back wc-history-forward) - (copy-sequence current)) - current)) - current) - current)) - -(push '(tabs . frameset-filter-tabs) frameset-filter-alist) - -(defun tab-bar--tab (&optional frame) - "Make a new tab data structure that can be added to tabs on the FRAME." - (let* ((tab (tab-bar--current-tab-find nil frame)) - (tab-explicit-name (alist-get 'explicit-name tab)) - (tab-group (alist-get 'group tab)) - (bl (seq-filter #'buffer-live-p (frame-parameter - frame 'buffer-list))) - (bbl (seq-filter #'buffer-live-p (frame-parameter - frame 'buried-buffer-list)))) - `(tab - (name . ,(if tab-explicit-name - (alist-get 'name tab) - (funcall tab-bar-tab-name-function))) - (explicit-name . ,tab-explicit-name) - ,@(if tab-group `((group . ,tab-group))) - (time . ,(float-time)) - (ws . ,(window-state-get - (frame-root-window (or frame (selected-frame))) 'writable)) - (wc . ,(current-window-configuration)) - (wc-point . ,(point-marker)) - (wc-bl . ,bl) - (wc-bbl . ,bbl) - ,@(when tab-bar-history-mode - `((wc-history-back . ,(gethash (or frame (selected-frame)) - tab-bar-history-back)) - (wc-history-forward . ,(gethash (or frame (selected-frame)) - tab-bar-history-forward)))) - ;; Copy other possible parameters - ,@(mapcan (lambda (param) - (unless (memq (car param) - '(name explicit-name group time - ws wc wc-point wc-bl wc-bbl - wc-history-back wc-history-forward)) - (list param))) - (cdr tab))))) - -(defun tab-bar--current-tab (&optional tab frame) - "Make the current tab data structure from TAB on FRAME." - (tab-bar--current-tab-make (or tab (tab-bar--current-tab-find nil frame)))) - -(defun tab-bar--current-tab-make (&optional tab) - "Make the current tab data structure from TAB. -TAB here is an argument meaning \"use tab as template\", -i.e. the tab is created using data from TAB. This is -necessary when switching tabs, otherwise the destination tab -inherits the current tab's `explicit-name' parameter." - (let* ((tab-explicit-name (alist-get 'explicit-name tab)) - (tab-group (if tab - (alist-get 'group tab) - (pcase tab-bar-new-tab-group - ((pred stringp) tab-bar-new-tab-group) - ((pred functionp) (funcall tab-bar-new-tab-group)))))) - `(current-tab - (name . ,(if tab-explicit-name - (alist-get 'name tab) - (funcall tab-bar-tab-name-function))) - (explicit-name . ,tab-explicit-name) - ,@(if tab-group `((group . ,tab-group))) - ;; Copy other possible parameters - ,@(mapcan (lambda (param) - (unless (memq (car param) - '(name explicit-name group time - ws wc wc-point wc-bl wc-bbl - wc-history-back wc-history-forward)) - (list param))) - (cdr tab))))) - -(defun tab-bar--current-tab-find (&optional tabs frame) - ;; Find the current tab as a pointer to its data structure. - (assq 'current-tab (or tabs (funcall tab-bar-tabs-function frame)))) - -(defun tab-bar--current-tab-index (&optional tabs frame) - ;; Return the index of the current tab. - (seq-position (or tabs (funcall tab-bar-tabs-function frame)) - 'current-tab (lambda (a b) (eq (car a) b)))) - -(defun tab-bar--tab-index (tab &optional tabs frame) - ;; Return the index of TAB. - (seq-position (or tabs (funcall tab-bar-tabs-function frame)) - tab #'eq)) - -(defun tab-bar--tab-index-by-name (name &optional tabs frame) - ;; Return the index of TAB by the its NAME. - (seq-position (or tabs (funcall tab-bar-tabs-function frame)) - name (lambda (a b) (equal (alist-get 'name a) b)))) - -(defun tab-bar--tab-index-recent (nth &optional tabs frame) - ;; Return the index of NTH recent tab. - (let* ((tabs (or tabs (funcall tab-bar-tabs-function frame))) - (sorted-tabs (tab-bar--tabs-recent tabs frame)) - (tab (nth (1- nth) sorted-tabs))) - (tab-bar--tab-index tab tabs))) - -(defun tab-bar--tabs-recent (&optional tabs frame) - ;; Return the list of tabs sorted by recency. - (let* ((tabs (or tabs (funcall tab-bar-tabs-function frame)))) - (seq-sort-by (lambda (tab) (alist-get 'time tab)) #'> - (seq-remove (lambda (tab) - (eq (car tab) 'current-tab)) - tabs)))) - - -(defun tab-bar-select-tab (&optional tab-number) - "Switch to the tab by its absolute position TAB-NUMBER in the tab bar. -When this command is bound to a numeric key (with a key prefix or modifier key -using `tab-bar-select-tab-modifiers'), calling it without an argument -will translate its bound numeric key to the numeric argument. -Also the prefix argument TAB-NUMBER can be used to override -the numeric key, so it takes precedence over the bound digit key. -For example, `-2' will select the second tab, but `C-u 15 --2' will select the 15th tab. TAB-NUMBER counts from 1. -Negative TAB-NUMBER counts tabs from the end of the tab bar." - (interactive "P") - (unless (integerp tab-number) - (let ((key (event-basic-type last-command-event))) - (setq tab-number (if (and (characterp key) (>= key ?1) (<= key ?9)) - (- key ?0) - 0)))) - - (let* ((tabs (funcall tab-bar-tabs-function)) - (from-index (tab-bar--current-tab-index tabs)) - (to-number (cond ((< tab-number 0) (+ (length tabs) (1+ tab-number))) - ((zerop tab-number) (1+ from-index)) - (t tab-number))) - (to-index (1- (max 1 (min to-number (length tabs)))))) - - (unless (eq from-index to-index) - (let* ((from-tab (tab-bar--tab)) - (to-tab (nth to-index tabs)) - (wc (alist-get 'wc to-tab)) - (ws (alist-get 'ws to-tab))) - - ;; During the same session, use window-configuration to switch - ;; tabs, because window-configurations are more reliable - ;; (they keep references to live buffers) than window-states. - ;; But after restoring tabs from a previously saved session, - ;; its value of window-configuration is unreadable, - ;; so restore its saved window-state. - (cond - ((and (window-configuration-p wc) - ;; Check for such cases as cloning a frame with tabs. - ;; When tabs were cloned to another frame, then fall back - ;; to using `window-state-put' below. - (eq (window-configuration-frame wc) (selected-frame))) - (let ((wc-point (alist-get 'wc-point to-tab)) - (wc-bl (seq-filter #'buffer-live-p (alist-get 'wc-bl to-tab))) - (wc-bbl (seq-filter #'buffer-live-p (alist-get 'wc-bbl to-tab))) - (wc-history-back (alist-get 'wc-history-back to-tab)) - (wc-history-forward (alist-get 'wc-history-forward to-tab))) - - (set-window-configuration wc) - - ;; set-window-configuration does not restore the value of - ;; point in the current buffer, so restore it separately. - (when (and (markerp wc-point) - (marker-buffer wc-point) - ;; FIXME: After dired-revert, marker relocates to 1. - ;; window-configuration restores point to global point - ;; in this dired buffer, not to its window point, - ;; but this is slightly better than 1. - ;; Maybe better to save dired-filename in each window? - (not (eq 1 (marker-position wc-point)))) - (goto-char wc-point)) - - (when wc-bl (set-frame-parameter nil 'buffer-list wc-bl)) - (when wc-bbl (set-frame-parameter nil 'buried-buffer-list wc-bbl)) - - (when tab-bar-history-mode - (puthash (selected-frame) - (and (window-configuration-p (alist-get 'wc (car wc-history-back))) - wc-history-back) - tab-bar-history-back) - (puthash (selected-frame) - (and (window-configuration-p (alist-get 'wc (car wc-history-forward))) - wc-history-forward) - tab-bar-history-forward)))) - - (ws - (window-state-put ws nil 'safe))) - - (when tab-bar-history-mode - (setq tab-bar-history-omit t)) - - (when from-index - (setf (nth from-index tabs) from-tab)) - (setf (nth to-index tabs) (tab-bar--current-tab-make (nth to-index tabs))) - - (unless tab-bar-mode - (message "Selected tab '%s'" (alist-get 'name to-tab)))) - - (force-mode-line-update)))) - -(defun tab-bar-switch-to-next-tab (&optional arg) - "Switch to ARGth next tab. -Interactively, ARG is the prefix numeric argument and defaults to 1." - (interactive "p") - (unless (integerp arg) - (setq arg 1)) - (let* ((tabs (funcall tab-bar-tabs-function)) - (from-index (or (tab-bar--current-tab-index tabs) 0)) - (to-index (mod (+ from-index arg) (length tabs)))) - (tab-bar-select-tab (1+ to-index)))) - -(defun tab-bar-switch-to-prev-tab (&optional arg) - "Switch to ARGth previous tab. -Interactively, ARG is the prefix numeric argument and defaults to 1." - (interactive "p") - (unless (integerp arg) - (setq arg 1)) - (tab-bar-switch-to-next-tab (- arg))) - -(defun tab-bar-switch-to-last-tab (&optional arg) - "Switch to the last tab or ARGth tab from the end of the tab bar. -Interactively, ARG is the prefix numeric argument; it defaults to 1, -which means the last tab on the tab bar. For example, `C-u 2 --9' selects the tab before the last tab." - (interactive "p") - (tab-bar-select-tab (- (length (funcall tab-bar-tabs-function)) - (1- (abs (or arg 1)))))) - -(defun tab-bar-switch-to-recent-tab (&optional arg) - "Switch to ARGth most recently visited tab. -Interactively, ARG is the prefix numeric argument and defaults to 1." - (interactive "p") - (unless (integerp arg) - (setq arg 1)) - (let ((tab-index (tab-bar--tab-index-recent arg))) - (if tab-index - (tab-bar-select-tab (1+ tab-index)) - (message "No more recent tabs")))) - -(defun tab-bar-switch-to-tab (name) - "Switch to the tab by NAME. -Default values are tab names sorted by recency, so you can use \ -\\\\[next-history-element] -to get the name of the most recently visited tab, the second -most recent, and so on. -When the tab with that NAME doesn't exist, create a new tab -and rename it to NAME." - (interactive - (let* ((recent-tabs (mapcar (lambda (tab) - (alist-get 'name tab)) - (tab-bar--tabs-recent)))) - (list (completing-read (format-prompt "Switch to tab by name" - (car recent-tabs)) - recent-tabs nil nil nil nil recent-tabs)))) - (let ((tab-index (tab-bar--tab-index-by-name name))) - (if tab-index - (tab-bar-select-tab (1+ tab-index)) - (tab-bar-new-tab) - (tab-bar-rename-tab name)))) - -(defalias 'tab-bar-select-tab-by-name 'tab-bar-switch-to-tab) - - -(defun tab-bar-move-tab-to (to-number &optional from-number) - "Move tab from FROM-NUMBER position to new position at TO-NUMBER. -FROM-NUMBER defaults to the current tab number. -FROM-NUMBER and TO-NUMBER count from 1. -Negative TO-NUMBER counts tabs from the end of the tab bar. -Argument addressing is absolute in contrast to `tab-bar-move-tab' -where argument addressing is relative." - (interactive "P") - (let* ((tabs (funcall tab-bar-tabs-function)) - (from-number (or from-number (1+ (tab-bar--current-tab-index tabs)))) - (from-tab (nth (1- from-number) tabs)) - (to-number (if to-number (prefix-numeric-value to-number) 1)) - (to-number (if (< to-number 0) (+ (length tabs) (1+ to-number)) to-number)) - (to-index (max 0 (min (1- to-number) (1- (length tabs)))))) - (setq tabs (delq from-tab tabs)) - (cl-pushnew from-tab (nthcdr to-index tabs)) - (tab-bar-tabs-set tabs) - (force-mode-line-update))) - -(defun tab-bar-move-tab (&optional arg) - "Move the current tab ARG positions to the right. -Interactively, ARG is the prefix numeric argument and defaults to 1. -If ARG is negative, move the current tab ARG positions to the left. -Argument addressing is relative in contrast to `tab-bar-move-tab-to', -where argument addressing is absolute." - (interactive "p") - (let* ((tabs (funcall tab-bar-tabs-function)) - (from-index (or (tab-bar--current-tab-index tabs) 0)) - (to-index (mod (+ from-index arg) (length tabs)))) - (tab-bar-move-tab-to (1+ to-index) (1+ from-index)))) - -(defun tab-bar-move-tab-backward (&optional arg) - "Move the current tab ARG positions to the left. -Interactively, ARG is the prefix numeric argument and defaults to 1. -Like `tab-bar-move-tab', but moves in the opposite direction." - (interactive "p") - (tab-bar-move-tab (- (or arg 1)))) - -(defun tab-bar-move-tab-to-frame (arg &optional from-frame from-number to-frame to-number) - "Move tab from FROM-NUMBER position to new position at TO-NUMBER. -FROM-NUMBER defaults to the current tab number. -FROM-NUMBER and TO-NUMBER count from 1. -FROM-FRAME specifies the source frame and defaults to the selected frame. -TO-FRAME specifies the target frame and defaults the next frame. -Interactively, ARG selects the ARGth next frame on the same terminal, -to which to move the tab; ARG defaults to 1." - (interactive "P") - (unless from-frame - (setq from-frame (selected-frame))) - (unless to-frame - (dotimes (_ (prefix-numeric-value arg)) - (setq to-frame (next-frame to-frame)))) - (unless (eq from-frame to-frame) - (let* ((from-tabs (funcall tab-bar-tabs-function from-frame)) - (from-number (or from-number (1+ (tab-bar--current-tab-index from-tabs)))) - (from-tab (nth (1- from-number) from-tabs)) - (to-tabs (funcall tab-bar-tabs-function to-frame)) - (to-index (max 0 (min (1- (or to-number 1)) (1- (length to-tabs)))))) - (cl-pushnew (assq-delete-all - 'wc (if (eq (car from-tab) 'current-tab) - (tab-bar--tab from-frame) - from-tab)) - (nthcdr to-index to-tabs)) - (with-selected-frame from-frame - (let ((inhibit-message t) ; avoid message about deleted tab - (tab-bar-close-last-tab-choice 'delete-frame) - tab-bar-closed-tabs) - (tab-bar-close-tab from-number))) - (tab-bar-tabs-set to-tabs to-frame) - (force-mode-line-update t)))) - -(defun tab-bar-detach-tab (&optional from-number) - "Move tab number FROM-NUMBER to a new frame. -FROM-NUMBER defaults to the current tab (which happens interactively)." - (interactive (list (1+ (tab-bar--current-tab-index)))) - (let* ((tabs (funcall tab-bar-tabs-function)) - (tab-index (1- (or from-number (1+ (tab-bar--current-tab-index tabs))))) - (tab-name (alist-get 'name (nth tab-index tabs))) - ;; On some window managers, `make-frame' selects the new frame, - ;; so previously selected frame is saved to `from-frame'. - (from-frame (selected-frame)) - (new-frame (make-frame `((name . ,tab-name))))) - (tab-bar-move-tab-to-frame nil from-frame from-number new-frame nil) - (with-selected-frame new-frame - (tab-bar-close-tab)))) - -(defun tab-bar-move-window-to-tab () - "Move the selected window to a new tab. -This command removes the selected window from the configuration stored -on the current tab, and makes a new tab with that window in its -configuration." - (interactive) - (let ((tab-bar-new-tab-choice 'window)) - (tab-bar-new-tab)) - (tab-bar-switch-to-recent-tab) - (delete-window) - (tab-bar-switch-to-recent-tab)) - - -(defcustom tab-bar-new-tab-to 'right - "Where to create a new tab. -If `leftmost', create as the first tab. -If `left', create to the left of the current tab. -If `right', create to the right of the current tab. -If `rightmost', create as the last tab. -If the value is a function, it should return a number as a position -on the tab bar specifying where to add a new tab." - :type '(choice (const :tag "Add as First" leftmost) - (const :tag "Add to Left" left) - (const :tag "Add to Right" right) - (const :tag "Add as Last" rightmost) - (function :tag "Function")) - :group 'tab-bar - :version "27.1") - -(defcustom tab-bar-tab-post-open-functions nil - "List of functions to call after creating a new tab. -The current tab is supplied as an argument. Any modifications made -to the tab argument will be applied after all functions are called." - :type '(repeat function) - :group 'tab-bar - :version "27.1") - -(defun tab-bar-new-tab-to (&optional tab-number) - "Add a new tab at the absolute position TAB-NUMBER. -TAB-NUMBER counts from 1. If no TAB-NUMBER is specified, then add -a new tab at the position specified by `tab-bar-new-tab-to'. -Negative TAB-NUMBER counts tabs from the end of the tab bar, -and -1 means the new tab will become the last one. -Argument addressing is absolute in contrast to `tab-bar-new-tab', -where argument addressing is relative. -After the tab is created, the hooks in -`tab-bar-tab-post-open-functions' are run." - (interactive "P") - (let* ((tabs (funcall tab-bar-tabs-function)) - (from-index (tab-bar--current-tab-index tabs)) - (from-tab (tab-bar--tab))) - - (when tab-bar-new-tab-choice - ;; Handle the case when it's called in the active minibuffer. - (when (minibuffer-selected-window) - (select-window (minibuffer-selected-window))) - (let ((ignore-window-parameters t)) - (delete-other-windows)) - (unless (eq tab-bar-new-tab-choice 'window) - ;; Create a new window to get rid of old window parameters - ;; (e.g. prev/next buffers) of old window. - (split-window) (delete-window)) - (let ((buffer - (if (functionp tab-bar-new-tab-choice) - (funcall tab-bar-new-tab-choice) - (if (stringp tab-bar-new-tab-choice) - (or (get-buffer tab-bar-new-tab-choice) - (find-file-noselect tab-bar-new-tab-choice)))))) - (when (buffer-live-p buffer) - (switch-to-buffer buffer)))) - - (when from-index - (setf (nth from-index tabs) from-tab)) - - (let* ((to-tab (tab-bar--current-tab-make - (when (eq tab-bar-new-tab-group t) - `((group . ,(alist-get 'group from-tab)))))) - (to-number (and tab-number (prefix-numeric-value tab-number))) - (to-index (or (if to-number - (if (< to-number 0) - (+ (length tabs) (1+ to-number)) - (1- to-number))) - (pcase tab-bar-new-tab-to - ('leftmost 0) - ('rightmost (length tabs)) - ('left (or from-index 1)) - ('right (1+ (or from-index 0))) - ((pred functionp) - (funcall tab-bar-new-tab-to)))))) - (setq to-index (max 0 (min (or to-index 0) (length tabs)))) - (cl-pushnew to-tab (nthcdr to-index tabs)) - - (when (eq to-index 0) - ;; `pushnew' handles the head of tabs but not frame-parameter - (tab-bar-tabs-set tabs)) - - (when tab-bar-history-mode - (puthash (selected-frame) nil tab-bar-history-back) - (puthash (selected-frame) nil tab-bar-history-forward) - (setq tab-bar-history-omit t)) - - (run-hook-with-args 'tab-bar-tab-post-open-functions - (nth to-index tabs))) - - (when tab-bar-show - (if (not tab-bar-mode) - ;; Turn on `tab-bar-mode' since a tab was created. - ;; Note: this also updates `tab-bar-lines'. - (tab-bar-mode 1) - (tab-bar--update-tab-bar-lines))) - - (force-mode-line-update) - (unless tab-bar-mode - (message "Added new tab at %s" tab-bar-new-tab-to)))) - -(defun tab-bar-new-tab (&optional arg from-number) - "Create a new tab ARG positions to the right. -If a negative ARG, create a new tab ARG positions to the left. -If ARG is zero, create a new tab in place of the current tab. -If no ARG is specified, then add a new tab at the position -specified by `tab-bar-new-tab-to'. -Argument addressing is relative in contrast to `tab-bar-new-tab-to', -where argument addressing is absolute. -If FROM-NUMBER is a tab number, a new tab is created from that tab." - (interactive "P") - (when from-number - (let ((inhibit-message t)) - (tab-bar-select-tab from-number))) - (if arg - (let* ((tabs (funcall tab-bar-tabs-function)) - (from-index (or (tab-bar--current-tab-index tabs) 0)) - (to-index (+ from-index (prefix-numeric-value arg)))) - (tab-bar-new-tab-to (1+ to-index))) - (tab-bar-new-tab-to))) - -(defun tab-bar-duplicate-tab (&optional arg from-number) - "Clone the current tab to ARG positions to the right. -ARG and FROM-NUMBER have the same meaning as in `tab-bar-new-tab'." - (interactive "P") - (let ((tab-bar-new-tab-choice nil) - (tab-bar-new-tab-group t)) - (tab-bar-new-tab arg from-number))) - - -(defvar tab-bar-closed-tabs nil - "A list of closed tabs to be able to undo their closing.") - -(defcustom tab-bar-close-tab-select 'recent - "Which tab to make current after closing the specified tab. -If `left', select the adjacent left tab. -If `right', select the adjacent right tab. -If `recent', select the most recently visited tab." - :type '(choice (const :tag "Select left tab" left) - (const :tag "Select right tab" right) - (const :tag "Select recent tab" recent)) - :group 'tab-bar - :version "27.1") - -(defcustom tab-bar-close-last-tab-choice nil - "What to do when the last tab is closed. -If nil, do nothing and show a message, like closing the last window or frame. -If `delete-frame', delete the containing frame, as a web browser would do. -If `tab-bar-mode-disable', disable `tab-bar-mode' so that tabs no longer show -in the frame. -If the value is a function, call that function with the tab to be closed -as an argument." - :type '(choice (const :tag "Do nothing and show message" nil) - (const :tag "Close the containing frame" delete-frame) - (const :tag "Disable tab-bar-mode" tab-bar-mode-disable) - (function :tag "Function")) - :group 'tab-bar - :version "27.1") - -(defcustom tab-bar-tab-prevent-close-functions nil - "List of functions to call to determine whether to close a tab. -The tab to be closed and a boolean indicating whether or not it -is the only tab in the frame are supplied as arguments. If any -function returns a non-nil value, the tab will not be closed." - :type '(repeat function) - :group 'tab-bar - :version "27.1") - -(defcustom tab-bar-tab-pre-close-functions nil - "List of functions to call before closing a tab. -Each function is called with two arguments: the tab to be closed -and a boolean indicating whether or not it is the only tab on its frame." - :type '(repeat function) - :group 'tab-bar - :version "27.1") - -(defun tab-bar-close-tab (&optional tab-number to-number) - "Close the tab specified by its absolute position TAB-NUMBER. -If no TAB-NUMBER is specified, then close the current tab and switch -to the tab specified by `tab-bar-close-tab-select'. -Interactively, TAB-NUMBER is the prefix numeric argument, and defaults to 1. -TAB-NUMBER counts from 1. -Optional TO-NUMBER could be specified to override the value of -`tab-bar-close-tab-select' programmatically with a position -of an existing tab to select after closing the current tab. -TO-NUMBER counts from 1. - -The functions in `tab-bar-tab-prevent-close-functions' will be -run to determine whether or not to close the tab. -Just before the tab is closed, the functions in -`tab-bar-tab-pre-close-functions' will be run. The base behavior -for the last tab on a frame is determined by -`tab-bar-close-last-tab-choice'." - (interactive "P") - (let* ((tabs (funcall tab-bar-tabs-function)) - (current-index (tab-bar--current-tab-index tabs)) - (close-index (if (integerp tab-number) (1- tab-number) current-index)) - (last-tab-p (= 1 (length tabs))) - (prevent-close (run-hook-with-args-until-success - 'tab-bar-tab-prevent-close-functions - (nth close-index tabs) - last-tab-p))) - - (unless prevent-close - (run-hook-with-args 'tab-bar-tab-pre-close-functions - (nth close-index tabs) - last-tab-p) - - (if last-tab-p - (pcase tab-bar-close-last-tab-choice - ('nil - (user-error "Attempt to delete the sole tab in a frame")) - ('delete-frame - (delete-frame)) - ('tab-bar-mode-disable - (tab-bar-mode -1)) - ((pred functionp) - ;; Give the handler function the full extent of the tab's - ;; data, not just it's name and explicit-name flag. - (funcall tab-bar-close-last-tab-choice (tab-bar--tab)))) - - ;; More than one tab still open - (when (eq current-index close-index) - ;; Select another tab before deleting the current tab - (let ((to-index (or (if to-number (1- to-number)) - (pcase tab-bar-close-tab-select - ('left (1- (if (< current-index 1) 2 current-index))) - ('right (if (> (length tabs) (1+ current-index)) - (1+ current-index) - (1- current-index))) - ('recent (tab-bar--tab-index-recent 1 tabs)))))) - (setq to-index (max 0 (min (or to-index 0) (1- (length tabs))))) - (let ((inhibit-message t)) ; avoid message about selected tab - (tab-bar-select-tab (1+ to-index))) - ;; Re-read tabs after selecting another tab - (setq tabs (funcall tab-bar-tabs-function)))) - - (let ((close-tab (nth close-index tabs))) - (push `((frame . ,(selected-frame)) - (index . ,close-index) - (tab . ,(if (eq (car close-tab) 'current-tab) - (tab-bar--tab) - close-tab))) - tab-bar-closed-tabs) - (tab-bar-tabs-set (delq close-tab tabs))) - - ;; Recalculate `tab-bar-lines' and update frames - (tab-bar--update-tab-bar-lines) - - (force-mode-line-update) - (unless tab-bar-mode - (message "Deleted tab and switched to %s" tab-bar-close-tab-select)))))) - -(defun tab-bar-close-tab-by-name (name) - "Close the tab given its NAME. -Interactively, prompt for NAME." - (interactive - (list (completing-read "Close tab by name: " - (mapcar (lambda (tab) - (alist-get 'name tab)) - (funcall tab-bar-tabs-function))))) - (tab-bar-close-tab (1+ (tab-bar--tab-index-by-name name)))) - -(defun tab-bar-close-other-tabs (&optional tab-number) - "Close all tabs on the selected frame, except the tab TAB-NUMBER. -TAB-NUMBER counts from 1 and defaults to the current tab (which -happens interactively)." - (interactive) - (let* ((tabs (funcall tab-bar-tabs-function)) - (current-index (tab-bar--current-tab-index tabs)) - (keep-index (if (integerp tab-number) - (1- (max 1 (min tab-number (length tabs)))) - current-index)) - (index 0)) - - (when (nth keep-index tabs) - (unless (eq keep-index current-index) - (tab-bar-select-tab (1+ keep-index)) - (setq tabs (funcall tab-bar-tabs-function))) - - (dolist (tab tabs) - (unless (or (eq index keep-index) - (run-hook-with-args-until-success - 'tab-bar-tab-prevent-close-functions tab - ;; `last-tab-p' logically can't ever be true - ;; if we make it this far - nil)) - (push `((frame . ,(selected-frame)) - (index . ,index) - (tab . ,tab)) - tab-bar-closed-tabs) - (run-hook-with-args 'tab-bar-tab-pre-close-functions tab nil) - (setq tabs (delq tab tabs))) - (setq index (1+ index))) - (tab-bar-tabs-set tabs) - - ;; Recalculate tab-bar-lines and update frames - (tab-bar--update-tab-bar-lines) - - (force-mode-line-update) - (unless tab-bar-mode - (message "Deleted all other tabs"))))) - -(defun tab-bar-undo-close-tab () - "Restore the most recently closed tab." - (interactive) - ;; Pop out closed tabs that were on already deleted frames - (while (and tab-bar-closed-tabs - (not (frame-live-p (alist-get 'frame (car tab-bar-closed-tabs))))) - (pop tab-bar-closed-tabs)) - - (if tab-bar-closed-tabs - (let* ((closed (pop tab-bar-closed-tabs)) - (frame (alist-get 'frame closed)) - (index (alist-get 'index closed)) - (tab (alist-get 'tab closed))) - (unless (eq frame (selected-frame)) - (select-frame-set-input-focus frame)) - - (let ((tabs (funcall tab-bar-tabs-function))) - (setq index (max 0 (min index (length tabs)))) - (cl-pushnew tab (nthcdr index tabs)) - (when (eq index 0) - ;; pushnew handles the head of tabs but not frame-parameter - (tab-bar-tabs-set tabs)) - (tab-bar-select-tab (1+ index)))) - - (message "No more closed tabs to undo"))) - - -(defun tab-bar-rename-tab (name &optional tab-number) - "Give the tab specified by its absolute position TAB-NUMBER a new NAME. -If no TAB-NUMBER is specified, then rename the current tab. -Interactively, TAB-NUMBER is the prefix numeric argument, and defaults -to the current tab. -TAB-NUMBER counts from 1. -Interactively, prompt for the new NAME. -If NAME is the empty string, then use the automatic name -function `tab-bar-tab-name-function'." - (interactive - (let* ((tabs (funcall tab-bar-tabs-function)) - (tab-number (or current-prefix-arg (1+ (tab-bar--current-tab-index tabs)))) - (tab-name (alist-get 'name (nth (1- tab-number) tabs)))) - (list (read-from-minibuffer - "New name for tab (leave blank for automatic naming): " - nil nil nil nil tab-name) - current-prefix-arg))) - (let* ((tabs (funcall tab-bar-tabs-function)) - (tab-index (if (integerp tab-number) - (1- (max 0 (min tab-number (length tabs)))) - (tab-bar--current-tab-index tabs))) - (tab-to-rename (nth tab-index tabs)) - (tab-explicit-name (> (length name) 0)) - (tab-new-name (if tab-explicit-name - name - (funcall tab-bar-tab-name-function)))) - (setf (alist-get 'name tab-to-rename) tab-new-name - (alist-get 'explicit-name tab-to-rename) tab-explicit-name) - - (force-mode-line-update) - (unless tab-bar-mode - (message "Renamed tab to '%s'" tab-new-name)))) - -(defun tab-bar-rename-tab-by-name (tab-name new-name) - "Rename the tab named TAB-NAME to NEW-NAME. -Interactively, prompt for TAB-NAME and NEW-NAME. -If NEW-NAME is the empty string, then use the automatic name -function `tab-bar-tab-name-function'." - (interactive - (let ((tab-name (completing-read "Rename tab by name: " - (mapcar (lambda (tab) - (alist-get 'name tab)) - (funcall tab-bar-tabs-function))))) - (list tab-name (read-from-minibuffer - "New name for tab (leave blank for automatic naming): " - nil nil nil nil tab-name)))) - (tab-bar-rename-tab new-name (1+ (tab-bar--tab-index-by-name tab-name)))) - - -;;; Tab groups - -(defun tab-bar-move-tab-to-group (&optional tab) - "Relocate TAB (by default, the current tab) closer to its group." - (interactive) - (let* ((tabs (funcall tab-bar-tabs-function)) - (tab (or tab (tab-bar--current-tab-find tabs))) - (tab-index (tab-bar--tab-index tab)) - (group (alist-get 'group tab)) - ;; Beginning position of the same group - (beg (seq-position tabs group - (lambda (tb gr) - (and (not (eq tb tab)) - (equal (alist-get 'group tb) gr))))) - ;; Size of the same group - (len (when beg - (seq-position (nthcdr beg tabs) group - (lambda (tb gr) - (not (equal (alist-get 'group tb) gr)))))) - (pos (when beg - (cond - ;; Don't move tab when it's already inside group bounds - ((and len (>= tab-index beg) (<= tab-index (+ beg len))) nil) - ;; Move tab from the right to the group end - ((and len (> tab-index (+ beg len))) (+ beg len 1)) - ;; Move tab from the left to the group beginning - ((< tab-index beg) beg))))) - (when pos - (tab-bar-move-tab-to pos (1+ tab-index))))) - -(defcustom tab-bar-tab-post-change-group-functions nil - "List of functions to call after changing a tab group. -The current tab is supplied as an argument." - :type 'hook - :options '(tab-bar-move-tab-to-group) - :group 'tab-bar - :version "28.1") - -(defun tab-bar-change-tab-group (group-name &optional tab-number) - "Add the tab specified by its absolute position TAB-NUMBER to GROUP-NAME. -If no TAB-NUMBER is specified, then set the GROUP-NAME for the current tab. -Interactively, TAB-NUMBER is the prefix numeric argument, and the command -prompts for GROUP-NAME. -TAB-NUMBER counts from 1. -If GROUP-NAME is the empty string, then remove the tab from any group. -While using this command, you might also want to replace -`tab-bar-format-tabs' with `tab-bar-format-tabs-groups' in -`tab-bar-format' to group tabs on the tab bar." - (interactive - (let* ((tabs (funcall tab-bar-tabs-function)) - (tab-number (or current-prefix-arg - (1+ (tab-bar--current-tab-index tabs)))) - (group-name (funcall tab-bar-tab-group-function - (nth (1- tab-number) tabs)))) - (list (completing-read - "Group name for tab (leave blank to remove group): " - (delete-dups - (delq nil (cons group-name - (mapcar (lambda (tab) - (funcall tab-bar-tab-group-function tab)) - (funcall tab-bar-tabs-function)))))) - current-prefix-arg))) - (let* ((tabs (funcall tab-bar-tabs-function)) - (tab-index (if tab-number - (1- (max 0 (min tab-number (length tabs)))) - (tab-bar--current-tab-index tabs))) - (tab (nth tab-index tabs)) - (group (assq 'group tab)) - (group-new-name (and (> (length group-name) 0) group-name))) - (if group - (setcdr group group-new-name) - (nconc tab `((group . ,group-new-name)))) - - (run-hook-with-args 'tab-bar-tab-post-change-group-functions tab) - - (force-mode-line-update) - (unless tab-bar-mode - (message "Set tab group to '%s'" group-new-name)))) - -(defun tab-bar-close-group-tabs (group-name) - "Close all tabs that belong to GROUP-NAME on the selected frame. -Interactively, prompt for GROUP-NAME." - (interactive - (let ((group-name (funcall tab-bar-tab-group-function - (tab-bar--current-tab-find)))) - (list (completing-read - "Close all tabs with group name: " - (delete-dups - (delq nil (cons group-name - (mapcar (lambda (tab) - (funcall tab-bar-tab-group-function tab)) - (funcall tab-bar-tabs-function))))))))) - (let* ((close-group (and (> (length group-name) 0) group-name)) - (tab-bar-tab-prevent-close-functions - (cons (lambda (tab _last-tab-p) - (not (equal (funcall tab-bar-tab-group-function tab) - close-group))) - tab-bar-tab-prevent-close-functions))) - (tab-bar-close-other-tabs) - - (when (equal (funcall tab-bar-tab-group-function - (tab-bar--current-tab-find)) - close-group) - (tab-bar-close-tab)))) - - -;;; Tab history mode - -(defvar tab-bar-history-limit 10 - "The number of history elements to keep.") - -(defvar tab-bar-history-omit nil - "When non-nil, omit window-configuration changes from the current command.") - -(defvar tab-bar-history-back (make-hash-table) - "History of back changes in every tab per frame.") - -(defvar tab-bar-history-forward (make-hash-table) - "History of forward changes in every tab per frame.") - -(defvar tab-bar-history-old nil - "Window configuration before the current command.") - -(defvar tab-bar-history-pre-command nil - "Command set to `this-command' by `pre-command-hook'.") - -(defvar tab-bar-history-done-command nil - "Command handled by `window-configuration-change-hook'.") - -(defun tab-bar--history-pre-change () - ;; Reset before the command could set it - (setq tab-bar-history-omit nil) - (setq tab-bar-history-pre-command this-command) - (when (zerop (minibuffer-depth)) - (setq tab-bar-history-old - `((wc . ,(current-window-configuration)) - (wc-point . ,(point-marker)))))) - -(defun tab-bar--history-change () - (when (and (not tab-bar-history-omit) tab-bar-history-old - ;; Don't register changes performed by the same command - ;; repeated in sequence, such as incremental window resizing. - (not (eq tab-bar-history-done-command tab-bar-history-pre-command)) - (zerop (minibuffer-depth))) - (puthash (selected-frame) - (seq-take (cons tab-bar-history-old - (gethash (selected-frame) tab-bar-history-back)) - tab-bar-history-limit) - tab-bar-history-back) - (setq tab-bar-history-old nil)) - (setq tab-bar-history-done-command tab-bar-history-pre-command)) - -(defun tab-bar-history-back () - "Restore a previous window configuration used in the current tab. -This navigates back in the history of window configurations." - (interactive) - (setq tab-bar-history-omit t) - (let* ((history (pop (gethash (selected-frame) tab-bar-history-back))) - (wc (alist-get 'wc history)) - (wc-point (alist-get 'wc-point history))) - (if (window-configuration-p wc) - (progn - (puthash (selected-frame) - (cons tab-bar-history-old - (gethash (selected-frame) tab-bar-history-forward)) - tab-bar-history-forward) - (set-window-configuration wc) - (when (and (markerp wc-point) (marker-buffer wc-point)) - (goto-char wc-point))) - (message "No more tab back history")))) - -(defun tab-bar-history-forward () - "Cancel restoration of the previous window configuration. -This navigates forward in the history of window configurations." - (interactive) - (setq tab-bar-history-omit t) - (let* ((history (pop (gethash (selected-frame) tab-bar-history-forward))) - (wc (alist-get 'wc history)) - (wc-point (alist-get 'wc-point history))) - (if (window-configuration-p wc) - (progn - (puthash (selected-frame) - (cons tab-bar-history-old - (gethash (selected-frame) tab-bar-history-back)) - tab-bar-history-back) - (set-window-configuration wc) - (when (and (markerp wc-point) (marker-buffer wc-point)) - (goto-char wc-point))) - (message "No more tab forward history")))) - -(defvar-keymap tab-bar-history-mode-map - "C-c " #'tab-bar-history-back - "C-c " #'tab-bar-history-forward) - -(define-minor-mode tab-bar-history-mode - "Toggle tab history mode for the tab bar. -Tab history mode remembers window configurations used in every tab, -and can restore them." - :global t :group 'tab-bar - (if tab-bar-history-mode - (progn - (when (and tab-bar-mode (not (get-text-property 0 'display tab-bar-back-button))) - ;; This file is pre-loaded so only here we can use the right data-directory: - (add-text-properties 0 (length tab-bar-back-button) - `(display (image :type xpm - :file "tabs/left-arrow.xpm" - :margin ,tab-bar-button-margin - :ascent center)) - tab-bar-back-button)) - (when (and tab-bar-mode (not (get-text-property 0 'display tab-bar-forward-button))) - ;; This file is pre-loaded so only here we can use the right data-directory: - (add-text-properties 0 (length tab-bar-forward-button) - `(display (image :type xpm - :file "tabs/right-arrow.xpm" - :margin ,tab-bar-button-margin - :ascent center)) - tab-bar-forward-button)) - - (add-hook 'pre-command-hook 'tab-bar--history-pre-change) - (add-hook 'window-configuration-change-hook 'tab-bar--history-change)) - (remove-hook 'pre-command-hook 'tab-bar--history-pre-change) - (remove-hook 'window-configuration-change-hook 'tab-bar--history-change))) - - -;;; Non-graphical access to frame-local tabs (named window configurations) - -(defun tab-switcher () - "Display a list of named window configurations. -The list is displayed in the buffer `*Tabs*'. -It's placed in the center of the frame to resemble a window list -displayed by a window switcher in some window managers on Alt+Tab. - -In this list of window configurations you can delete or select them. -Type ? after invocation to get help on commands available. -Type q to remove the list of window configurations from the display. - -The first column shows `D' for a window configuration you have -marked for deletion." - (interactive) - (let ((dir default-directory)) - (let ((tab-bar-new-tab-choice t) - ;; Don't enable tab-bar-mode if it's disabled - (tab-bar-show nil)) - (tab-bar-new-tab)) - (let ((switch-to-buffer-preserve-window-point nil)) - (switch-to-buffer (tab-switcher-noselect))) - (setq default-directory dir)) - (message "Commands: d, x; RET; q to quit; ? for help.")) - -(defun tab-switcher-noselect () - "Create and return a buffer with a list of window configurations. -The list is displayed in a buffer named `*Tabs*'. - -For more information, see the function `tab-switcher'." - (let* ((tabs (seq-remove (lambda (tab) - (eq (car tab) 'current-tab)) - (funcall tab-bar-tabs-function))) - ;; Sort by recency - (tabs (sort tabs (lambda (a b) (< (alist-get 'time b) - (alist-get 'time a)))))) - (with-current-buffer (get-buffer-create - (format " *Tabs*<%s>" (or (frame-parameter nil 'window-id) - (frame-parameter nil 'name)))) - (setq buffer-read-only nil) - (erase-buffer) - (tab-switcher-mode) - ;; Vertical alignment to the center of the frame - (insert-char ?\n (/ (- (frame-height) (length tabs) 1) 2)) - ;; Horizontal alignment to the center of the frame - (setq tab-switcher-column (- (/ (frame-width) 2) 15)) - (dolist (tab tabs) - (insert (propertize - (format "%s %s\n" - (make-string tab-switcher-column ?\040) - (propertize - (alist-get 'name tab) - 'mouse-face 'highlight - 'help-echo "mouse-2: select this window configuration")) - 'tab tab))) - (goto-char (point-min)) - (goto-char (or (next-single-property-change (point) 'tab) (point-min))) - (when (> (length tabs) 1) - (tab-switcher-next-line)) - (move-to-column tab-switcher-column) - (set-buffer-modified-p nil) - (setq buffer-read-only t) - (current-buffer)))) - -(defvar-local tab-switcher-column 3) - -(defvar tab-switcher-mode-map - (let ((map (make-keymap))) - (suppress-keymap map t) - (define-key map "q" 'quit-window) - (define-key map "\C-m" 'tab-switcher-select) - (define-key map "d" 'tab-switcher-delete) - (define-key map "k" 'tab-switcher-delete) - (define-key map "\C-d" 'tab-switcher-delete-backwards) - (define-key map "\C-k" 'tab-switcher-delete) - (define-key map "x" 'tab-switcher-execute) - (define-key map " " 'tab-switcher-next-line) - (define-key map "n" 'tab-switcher-next-line) - (define-key map "p" 'tab-switcher-prev-line) - (define-key map "\177" 'tab-switcher-backup-unmark) - (define-key map "?" 'describe-mode) - (define-key map "u" 'tab-switcher-unmark) - (define-key map [mouse-2] 'tab-switcher-mouse-select) - (define-key map [follow-link] 'mouse-face) - map) - "Local keymap for `tab-switcher-mode' buffers.") - -(define-derived-mode tab-switcher-mode nil "Window Configurations" - "Major mode for selecting a window configuration. -Each line describes one window configuration in Emacs. -Letters do not insert themselves; instead, they are commands. -\\ -\\[tab-switcher-mouse-select] -- select window configuration you click on. -\\[tab-switcher-select] -- select current line's window configuration. -\\[tab-switcher-delete] -- mark that window configuration to be deleted, and move down. -\\[tab-switcher-delete-backwards] -- mark that window configuration to be deleted, and move up. -\\[tab-switcher-execute] -- delete marked window configurations. -\\[tab-switcher-unmark] -- remove all kinds of marks from current line. - With prefix argument, also move up one line. -\\[tab-switcher-backup-unmark] -- back up a line and remove marks." - (setq truncate-lines t)) - -(defun tab-switcher-current-tab (error-if-non-existent-p) - "Return window configuration described by this line of the list." - (let* ((where (save-excursion - (beginning-of-line) - (+ 2 (point) tab-switcher-column))) - (tab (and (not (eobp)) (get-text-property where 'tab)))) - (or tab - (if error-if-non-existent-p - (user-error "No window configuration on this line") - nil)))) - -(defun tab-switcher-next-line (&optional arg) - "Move to ARGth next line in the list of tabs. -Interactively, ARG is the prefix numeric argument and defaults to 1." - (interactive "p") - (forward-line arg) - (beginning-of-line) - (move-to-column tab-switcher-column)) - -(defun tab-switcher-prev-line (&optional arg) - "Move to ARGth previous line in the list of tabs. -Interactively, ARG is the prefix numeric argument and defaults to 1." - (interactive "p") - (forward-line (- arg)) - (beginning-of-line) - (move-to-column tab-switcher-column)) - -(defun tab-switcher-unmark (&optional backup) - "Cancel requested operations on window configuration on this line and move down. -With prefix arg, move up instead." - (interactive "P") - (beginning-of-line) - (move-to-column tab-switcher-column) - (let* ((buffer-read-only nil)) - (delete-char 1) - (insert " ")) - (forward-line (if backup -1 1)) - (move-to-column tab-switcher-column)) - -(defun tab-switcher-backup-unmark () - "Move up one line and cancel requested operations on window configuration there." - (interactive) - (forward-line -1) - (tab-switcher-unmark) - (forward-line -1) - (move-to-column tab-switcher-column)) - -(defun tab-switcher-delete (&optional arg) - "Mark window configuration on this line to be deleted by \\\\[tab-switcher-execute] command. -Prefix arg says how many window configurations to delete. -Negative arg means delete backwards." - (interactive "p") - (let ((buffer-read-only nil)) - (if (or (null arg) (= arg 0)) - (setq arg 1)) - (while (> arg 0) - (delete-char 1) - (insert ?D) - (forward-line 1) - (setq arg (1- arg))) - (while (< arg 0) - (delete-char 1) - (insert ?D) - (forward-line -1) - (setq arg (1+ arg))) - (move-to-column tab-switcher-column))) - -(defun tab-switcher-delete-backwards (&optional arg) - "Mark window configuration on this line to be deleted by \\\\[tab-switcher-execute] command. -Then move up one line. Prefix arg means move that many lines." - (interactive "p") - (tab-switcher-delete (- (or arg 1)))) - -(defun tab-switcher-delete-from-list (tab) - "Delete the window configuration from the list of tabs." - (push `((frame . ,(selected-frame)) - (index . ,(tab-bar--tab-index tab)) - (tab . ,tab)) - tab-bar-closed-tabs) - (tab-bar-tabs-set (delq tab (funcall tab-bar-tabs-function)))) - -(defun tab-switcher-execute () - "Delete window configurations marked with \\\\[tab-switcher-delete] commands." - (interactive) - (save-excursion - (goto-char (point-min)) - (let ((buffer-read-only nil)) - (while (re-search-forward - (format "^%sD" (make-string tab-switcher-column ?\040)) - nil t) - (forward-char -1) - (let ((tab (tab-switcher-current-tab nil))) - (when tab - (tab-switcher-delete-from-list tab) - (beginning-of-line) - (delete-region (point) (progn (forward-line 1) (point)))))))) - (beginning-of-line) - (move-to-column tab-switcher-column) - (force-mode-line-update)) - -(defun tab-switcher-select () - "Select this line's window configuration. -This command replaces all the existing windows in the selected frame -with those specified by the selected window configuration." - (interactive) - (let* ((to-tab (tab-switcher-current-tab t))) - (kill-buffer (current-buffer)) - ;; Delete the current window configuration of tab list - ;; without storing it in the undo list of closed tabs - (let ((inhibit-message t) ; avoid message about deleted tab - tab-bar-closed-tabs) - (tab-bar-close-tab nil (1+ (tab-bar--tab-index to-tab)))))) - -(defun tab-switcher-mouse-select (event) - "Select the window configuration whose line you click on." - (interactive "e") - (set-buffer (window-buffer (posn-window (event-end event)))) - (goto-char (posn-point (event-end event))) - (tab-switcher-select)) - - -(defun tab-bar--reusable-frames (all-frames) - (cond - ((eq all-frames t) (frame-list)) - ((eq all-frames 'visible) (visible-frame-list)) - ((framep all-frames) (list all-frames)) - (t (list (selected-frame))))) - -(defun tab-bar-get-buffer-tab (buffer-or-name &optional all-frames ignore-current-tab) - "Return the tab that owns the window whose buffer is BUFFER-OR-NAME. -BUFFER-OR-NAME may be a buffer or a buffer name, and defaults to -the current buffer. - -The optional argument ALL-FRAMES specifies the frames to consider: - -- t means consider all tabs on all existing frames. - -- `visible' means consider all tabs on all visible frames. - -- A frame means consider all tabs on that frame only. - -- Any other value of ALL-FRAMES means consider all tabs on the -selected frame and no others. - -When the optional argument IGNORE-CURRENT-TAB is non-nil, -don't take into account the buffers in the currently selected tab. -Otherwise, prefer buffers of the current tab." - (let ((buffer (if buffer-or-name - (get-buffer buffer-or-name) - (current-buffer)))) - (when (bufferp buffer) - (seq-some - (lambda (frame) - (seq-some - (lambda (tab) - (when (if (eq (car tab) 'current-tab) - (get-buffer-window buffer frame) - (let* ((state (alist-get 'ws tab)) - (buffers (when state - (window-state-buffers state)))) - (or - ;; non-writable window-state - (memq buffer buffers) - ;; writable window-state - (member (buffer-name buffer) buffers)))) - (append tab `((index . ,(tab-bar--tab-index tab nil frame)) - (frame . ,frame))))) - (let* ((tabs (funcall tab-bar-tabs-function frame)) - (current-tab (tab-bar--current-tab-find tabs))) - (setq tabs (remq current-tab tabs)) - (if ignore-current-tab - ;; Use tabs without current-tab. - tabs - ;; Make sure current-tab is at the beginning of tabs. - (cons current-tab tabs))))) - (tab-bar--reusable-frames all-frames))))) - -(defun display-buffer-in-tab (buffer alist) - "Display BUFFER in a tab using display actions in ALIST. -ALIST is an association list of action symbols and values. See -Info node `(elisp) Buffer Display Action Alists' for details of -such alists. - -If ALIST contains a `tab-name' entry, it creates a new tab with that name and -displays BUFFER in a new tab. If a tab with this name already exists, it -switches to that tab before displaying BUFFER. The `tab-name' entry can be -a function, in which case it is called with two arguments: BUFFER and ALIST, -and should return the tab name. When a `tab-name' entry is omitted, create -a new tab without an explicit name. - -The ALIST entry `tab-group' (string or function) defines the tab group. - -If ALIST contains a `reusable-frames' entry, its value determines -which frames to search for a reusable tab: - nil -- do not reuse any frames; - a frame -- just that frame; - `visible' -- all visible frames; - 0 -- all frames on the current terminal; - t -- all frames; - other non-nil values -- use the selected frame. - -If ALIST contains a non-nil `ignore-current-tab' entry, then the buffers -of the current tab are skipped when searching for a reusable tab. -Otherwise, prefer buffers of the current tab. - -This is an action function for buffer display, see Info -node `(elisp) Buffer Display Action Functions'. It should be -called only by `display-buffer' or a function directly or -indirectly called by the latter." - (let* ((reusable-frames (alist-get 'reusable-frames alist)) - (ignore-current-tab (alist-get 'ignore-current-tab alist)) - (reusable-tab (when reusable-frames - (tab-bar-get-buffer-tab buffer reusable-frames - ignore-current-tab)))) - (if reusable-tab - (let* ((frame (alist-get 'frame reusable-tab)) - (index (alist-get 'index reusable-tab))) - (when frame - (select-frame-set-input-focus frame)) - (when index - (tab-bar-select-tab (1+ index))) - (when (get-buffer-window buffer frame) - (select-window (get-buffer-window buffer frame)))) - (let ((tab-name (alist-get 'tab-name alist))) - (when (functionp tab-name) - (setq tab-name (funcall tab-name buffer alist))) - (if tab-name - (let ((tab-index (tab-bar--tab-index-by-name tab-name))) - (if tab-index - (progn - (tab-bar-select-tab (1+ tab-index)) - (when (get-buffer-window buffer) - (select-window (get-buffer-window buffer)))) - (display-buffer-in-new-tab buffer alist))) - (display-buffer-in-new-tab buffer alist)))))) - -(defun display-buffer-in-new-tab (buffer alist) - "Display BUFFER in a new tab using display actions in ALIST. -ALIST is an association list of action symbols and values. See -Info node `(elisp) Buffer Display Action Alists' for details of -such alists. - -Like `display-buffer-in-tab', but always creates a new tab unconditionally, -without checking if a suitable tab already exists. - -If ALIST contains a `tab-name' entry, it creates a new tab with that name -and displays BUFFER in a new tab. The `tab-name' entry can be a function, -in which case it is called with two arguments: BUFFER and ALIST, and should -return the tab name. When a `tab-name' entry is omitted, create a new tab -without an explicit name. - -The ALIST entry `tab-group' (string or function) defines the tab group. - -This is an action function for buffer display, see Info -node `(elisp) Buffer Display Action Functions'. It should be -called only by `display-buffer' or a function directly or -indirectly called by the latter." - (let ((tab-bar-new-tab-choice t)) - (tab-bar-new-tab) - (let ((tab-name (alist-get 'tab-name alist))) - (when (functionp tab-name) - (setq tab-name (funcall tab-name buffer alist))) - (when tab-name - (tab-bar-rename-tab tab-name))) - (let ((tab-group (alist-get 'tab-group alist))) - (when (functionp tab-group) - (setq tab-group (funcall tab-group buffer alist))) - (when tab-group - (tab-bar-change-tab-group tab-group))) - (window--display-buffer buffer (selected-window) 'tab alist))) - -(defun switch-to-buffer-other-tab (buffer-or-name &optional _norecord) - "Switch to buffer BUFFER-OR-NAME in another tab. -Like \\[switch-to-buffer-other-frame] (which see), but creates a new tab. -Interactively, prompt for the buffer to switch to." - (declare (advertised-calling-convention (buffer-or-name) "28.1")) - (interactive - (list (read-buffer-to-switch "Switch to buffer in other tab: "))) - (display-buffer (window-normalize-buffer-to-switch-to buffer-or-name) - '((display-buffer-in-tab) - (inhibit-same-window . nil)))) - -(defun find-file-other-tab (filename &optional wildcards) - "Edit file FILENAME, in another tab. -Like \\[find-file-other-frame] (which see), but creates a new tab. -Interactively, prompt for FILENAME. -If WILDCARDS is non-nil, FILENAME can include widcards, and all matching -files will be visited." - (interactive - (find-file-read-args "Find file in other tab: " - (confirm-nonexistent-file-or-buffer))) - (let ((value (find-file-noselect filename nil nil wildcards))) - (if (listp value) - (progn - (setq value (nreverse value)) - (switch-to-buffer-other-tab (car value)) - (mapc 'switch-to-buffer (cdr value)) - value) - (switch-to-buffer-other-tab value)))) - -(defun find-file-read-only-other-tab (filename &optional wildcards) - "Edit file FILENAME, in another tab, but don't allow changes. -Like \\[find-file-other-frame] (which see), but creates a new tab. -Like \\[find-file-other-tab], but marks buffer as read-only. -Use \\[read-only-mode] to permit editing. -Interactively, prompt for FILENAME. -If WILDCARDS is non-nil, FILENAME can include widcards, and all matching -files will be visited." - (interactive - (find-file-read-args "Find file read-only in other tab: " - (confirm-nonexistent-file-or-buffer))) - (find-file--read-only (lambda (filename wildcards) - (window-buffer - (find-file-other-tab filename wildcards))) - filename wildcards)) - -(defun other-tab-prefix () - "Display the buffer of the next command in a new tab. -The next buffer is the buffer displayed by the next command invoked -immediately after this command (ignoring reading from the minibuffer). -Creates a new tab before displaying the buffer, or switches to the tab -that already contains that buffer. -When `switch-to-buffer-obey-display-actions' is non-nil, -`switch-to-buffer' commands are also supported." - (interactive) - (display-buffer-override-next-command - (lambda (buffer alist) - (cons (progn - (display-buffer-in-tab - buffer (append alist '((inhibit-same-window . nil)))) - (selected-window)) - 'tab)) - nil "[other-tab]") - (message "Display next command buffer in a new tab...")) - - -;;; Short aliases and keybindings - -(defalias 'tab-new 'tab-bar-new-tab) -(defalias 'tab-new-to 'tab-bar-new-tab-to) -(defalias 'tab-duplicate 'tab-bar-duplicate-tab) -(defalias 'tab-detach 'tab-bar-detach-tab) -(defalias 'tab-window-detach 'tab-bar-move-window-to-tab) -(defalias 'tab-close 'tab-bar-close-tab) -(defalias 'tab-close-other 'tab-bar-close-other-tabs) -(defalias 'tab-close-group 'tab-bar-close-group-tabs) -(defalias 'tab-undo 'tab-bar-undo-close-tab) -(defalias 'tab-select 'tab-bar-select-tab) -(defalias 'tab-switch 'tab-bar-switch-to-tab) -(defalias 'tab-next 'tab-bar-switch-to-next-tab) -(defalias 'tab-previous 'tab-bar-switch-to-prev-tab) -(defalias 'tab-last 'tab-bar-switch-to-last-tab) -(defalias 'tab-recent 'tab-bar-switch-to-recent-tab) -(defalias 'tab-move 'tab-bar-move-tab) -(defalias 'tab-move-to 'tab-bar-move-tab-to) -(defalias 'tab-rename 'tab-bar-rename-tab) -(defalias 'tab-group 'tab-bar-change-tab-group) -(defalias 'tab-list 'tab-switcher) - -(define-key tab-prefix-map "n" 'tab-duplicate) -(define-key tab-prefix-map "N" 'tab-new-to) -(define-key tab-prefix-map "2" 'tab-new) -(define-key tab-prefix-map "1" 'tab-close-other) -(define-key tab-prefix-map "0" 'tab-close) -(define-key tab-prefix-map "u" 'tab-undo) -(define-key tab-prefix-map "o" 'tab-next) -(define-key tab-prefix-map "O" 'tab-previous) -(define-key tab-prefix-map "m" 'tab-move) -(define-key tab-prefix-map "M" 'tab-move-to) -(define-key tab-prefix-map "G" 'tab-group) -(define-key tab-prefix-map "r" 'tab-rename) -(define-key tab-prefix-map "\r" 'tab-switch) -(define-key tab-prefix-map "b" 'switch-to-buffer-other-tab) -(define-key tab-prefix-map "f" 'find-file-other-tab) -(define-key tab-prefix-map "\C-f" 'find-file-other-tab) -(define-key tab-prefix-map "\C-r" 'find-file-read-only-other-tab) -(define-key tab-prefix-map "t" 'other-tab-prefix) - -(defvar tab-bar-switch-repeat-map - (let ((map (make-sparse-keymap))) - (define-key map "o" 'tab-next) - (define-key map "O" 'tab-previous) - map) - "Keymap to repeat tab switch key sequences `C-x t o o O'. -Used in `repeat-mode'.") -(put 'tab-next 'repeat-map 'tab-bar-switch-repeat-map) -(put 'tab-previous 'repeat-map 'tab-bar-switch-repeat-map) - -(defvar tab-bar-move-repeat-map - (let ((map (make-sparse-keymap))) - (define-key map "m" 'tab-move) - (define-key map "M" 'tab-bar-move-tab-backward) - map) - "Keymap to repeat tab move key sequences `C-x t m m M'. -Used in `repeat-mode'.") -(put 'tab-move 'repeat-map 'tab-bar-move-repeat-map) -(put 'tab-bar-move-tab-backward 'repeat-map 'tab-bar-move-repeat-map) - - -(provide 'tab-bar) - -;;; tab-bar.el ends here