diff options
| author | Juri Linkov | 2019-10-01 23:15:03 +0300 |
|---|---|---|
| committer | Juri Linkov | 2019-10-01 23:15:03 +0300 |
| commit | 2698d3dba2e9858b026ed127d4de3f86810a5ef3 (patch) | |
| tree | 8e9b8f194cfcad8af83a4174a0105bbc691f06d6 /lisp | |
| parent | 25f45d710e91a7c1049f056ff27bc3e6968f5624 (diff) | |
| parent | 3f981a0a89bca47a207fb362485f07e7322bb145 (diff) | |
| download | emacs-2698d3dba2e9858b026ed127d4de3f86810a5ef3.tar.gz emacs-2698d3dba2e9858b026ed127d4de3f86810a5ef3.zip | |
Merge branch 'feature/tabs'
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/cus-start.el | 5 | ||||
| -rw-r--r-- | lisp/frame.el | 42 | ||||
| -rw-r--r-- | lisp/loadup.el | 1 | ||||
| -rw-r--r-- | lisp/menu-bar.el | 10 | ||||
| -rw-r--r-- | lisp/mouse.el | 1 | ||||
| -rw-r--r-- | lisp/startup.el | 3 | ||||
| -rw-r--r-- | lisp/subr.el | 6 | ||||
| -rw-r--r-- | lisp/tab-bar.el | 764 | ||||
| -rw-r--r-- | lisp/tab-line.el | 362 | ||||
| -rw-r--r-- | lisp/window.el | 5 | ||||
| -rw-r--r-- | lisp/xt-mouse.el | 8 |
11 files changed, 1203 insertions, 4 deletions
diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 15d33b43c01..e61c1954a1f 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el | |||
| @@ -324,6 +324,9 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of | |||
| 324 | ;; FIXME? | 324 | ;; FIXME? |
| 325 | ;; :initialize custom-initialize-default | 325 | ;; :initialize custom-initialize-default |
| 326 | :set custom-set-minor-mode) | 326 | :set custom-set-minor-mode) |
| 327 | (tab-bar-mode (frames mouse) boolean nil | ||
| 328 | ;; :initialize custom-initialize-default | ||
| 329 | :set custom-set-minor-mode) | ||
| 327 | (tool-bar-mode (frames mouse) boolean nil | 330 | (tool-bar-mode (frames mouse) boolean nil |
| 328 | ;; :initialize custom-initialize-default | 331 | ;; :initialize custom-initialize-default |
| 329 | :set custom-set-minor-mode) | 332 | :set custom-set-minor-mode) |
| @@ -726,6 +729,8 @@ since it could result in memory overflow and make Emacs crash." | |||
| 726 | ;; the condition for loadup.el to preload tool-bar.el. | 729 | ;; the condition for loadup.el to preload tool-bar.el. |
| 727 | ((string-match "tool-bar-" (symbol-name symbol)) | 730 | ((string-match "tool-bar-" (symbol-name symbol)) |
| 728 | (fboundp 'x-create-frame)) | 731 | (fboundp 'x-create-frame)) |
| 732 | ((string-match "tab-bar-" (symbol-name symbol)) | ||
| 733 | (fboundp 'x-create-frame)) | ||
| 729 | ((equal "vertical-centering-font-regexp" | 734 | ((equal "vertical-centering-font-regexp" |
| 730 | (symbol-name symbol)) | 735 | (symbol-name symbol)) |
| 731 | ;; Any function from fontset.c will do. | 736 | ;; Any function from fontset.c will do. |
diff --git a/lisp/frame.el b/lisp/frame.el index e9d4b2ebe4c..0c68fc378b9 100644 --- a/lisp/frame.el +++ b/lisp/frame.el | |||
| @@ -363,6 +363,47 @@ there (in decreasing order of priority)." | |||
| 363 | ;; If the initial frame is still around, apply initial-frame-alist | 363 | ;; If the initial frame is still around, apply initial-frame-alist |
| 364 | ;; and default-frame-alist to it. | 364 | ;; and default-frame-alist to it. |
| 365 | (when (frame-live-p frame-initial-frame) | 365 | (when (frame-live-p frame-initial-frame) |
| 366 | ;; When tab-bar has been switched off, correct the frame size | ||
| 367 | ;; by the lines added in x-create-frame for the tab-bar and | ||
| 368 | ;; switch `tab-bar-mode' off. | ||
| 369 | (when (display-graphic-p) | ||
| 370 | (let* ((init-lines | ||
| 371 | (assq 'tab-bar-lines initial-frame-alist)) | ||
| 372 | (other-lines | ||
| 373 | (or (assq 'tab-bar-lines window-system-frame-alist) | ||
| 374 | (assq 'tab-bar-lines default-frame-alist))) | ||
| 375 | (lines (or init-lines other-lines)) | ||
| 376 | (height (tab-bar-height frame-initial-frame t))) | ||
| 377 | ;; Adjust frame top if either zero (nil) tab bar lines have | ||
| 378 | ;; been requested in the most relevant of the frame's alists | ||
| 379 | ;; or tab bar mode has been explicitly turned off in the | ||
| 380 | ;; user's init file. | ||
| 381 | (when (and (> height 0) | ||
| 382 | (or (and lines | ||
| 383 | (or (null (cdr lines)) | ||
| 384 | (eq 0 (cdr lines)))) | ||
| 385 | (not tab-bar-mode))) | ||
| 386 | (let* ((initial-top | ||
| 387 | (cdr (assq 'top frame-initial-geometry-arguments))) | ||
| 388 | (top (frame-parameter frame-initial-frame 'top))) | ||
| 389 | (when (and (consp initial-top) (eq '- (car initial-top))) | ||
| 390 | (let ((adjusted-top | ||
| 391 | (cond | ||
| 392 | ((and (consp top) (eq '+ (car top))) | ||
| 393 | (list '+ (+ (cadr top) height))) | ||
| 394 | ((and (consp top) (eq '- (car top))) | ||
| 395 | (list '- (- (cadr top) height))) | ||
| 396 | (t (+ top height))))) | ||
| 397 | (modify-frame-parameters | ||
| 398 | frame-initial-frame `((top . ,adjusted-top)))))) | ||
| 399 | ;; Reset `tab-bar-mode' when zero tab bar lines have been | ||
| 400 | ;; requested for the window-system or default frame alists. | ||
| 401 | (when (and tab-bar-mode | ||
| 402 | (and other-lines | ||
| 403 | (or (null (cdr other-lines)) | ||
| 404 | (eq 0 (cdr other-lines))))) | ||
| 405 | (tab-bar-mode -1))))) | ||
| 406 | |||
| 366 | ;; When tool-bar has been switched off, correct the frame size | 407 | ;; When tool-bar has been switched off, correct the frame size |
| 367 | ;; by the lines added in x-create-frame for the tool-bar and | 408 | ;; by the lines added in x-create-frame for the tool-bar and |
| 368 | ;; switch `tool-bar-mode' off. | 409 | ;; switch `tool-bar-mode' off. |
| @@ -1593,6 +1634,7 @@ and width values are in pixels. | |||
| 1593 | '(tool-bar-external . nil) | 1634 | '(tool-bar-external . nil) |
| 1594 | '(tool-bar-position . nil) | 1635 | '(tool-bar-position . nil) |
| 1595 | '(tool-bar-size 0 . 0) | 1636 | '(tool-bar-size 0 . 0) |
| 1637 | '(tab-bar-size 0 . 0) | ||
| 1596 | (cons 'internal-border-width | 1638 | (cons 'internal-border-width |
| 1597 | (frame-parameter frame 'internal-border-width))))))) | 1639 | (frame-parameter frame 'internal-border-width))))))) |
| 1598 | 1640 | ||
diff --git a/lisp/loadup.el b/lisp/loadup.el index 67e8aa7d40a..e60922e380a 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el | |||
| @@ -267,6 +267,7 @@ | |||
| 267 | (load "rfn-eshadow") | 267 | (load "rfn-eshadow") |
| 268 | 268 | ||
| 269 | (load "menu-bar") | 269 | (load "menu-bar") |
| 270 | (load "tab-bar") | ||
| 270 | (load "emacs-lisp/lisp") | 271 | (load "emacs-lisp/lisp") |
| 271 | (load "textmodes/page") | 272 | (load "textmodes/page") |
| 272 | (load "register") | 273 | (load "register") |
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el index 19122125c53..b7967b858ae 100644 --- a/lisp/menu-bar.el +++ b/lisp/menu-bar.el | |||
| @@ -687,7 +687,7 @@ The selected font will be the default on both the existing and future frames." | |||
| 687 | ;; side-effect that turning them off via X | 687 | ;; side-effect that turning them off via X |
| 688 | ;; resources acts like having customized them, but | 688 | ;; resources acts like having customized them, but |
| 689 | ;; that seems harmless. | 689 | ;; that seems harmless. |
| 690 | menu-bar-mode tool-bar-mode)) | 690 | menu-bar-mode tab-bar-mode tool-bar-mode)) |
| 691 | ;; FIXME ? It's a little annoying that running this command | 691 | ;; FIXME ? It's a little annoying that running this command |
| 692 | ;; always loads cua-base, paren, time, and battery, even if they | 692 | ;; always loads cua-base, paren, time, and battery, even if they |
| 693 | ;; have not been customized in any way. (Due to custom-load-symbol.) | 693 | ;; have not been customized in any way. (Due to custom-load-symbol.) |
| @@ -1242,6 +1242,14 @@ mail status in mode line")) | |||
| 1242 | (frame-parameter (menu-bar-frame-for-menubar) | 1242 | (frame-parameter (menu-bar-frame-for-menubar) |
| 1243 | 'menu-bar-lines))))) | 1243 | 'menu-bar-lines))))) |
| 1244 | 1244 | ||
| 1245 | (bindings--define-key menu [showhide-tab-bar] | ||
| 1246 | '(menu-item "Tab Bar" toggle-tab-bar-mode-from-frame | ||
| 1247 | :help "Turn tab bar on/off" | ||
| 1248 | :button | ||
| 1249 | (:toggle . (menu-bar-positive-p | ||
| 1250 | (frame-parameter (menu-bar-frame-for-menubar) | ||
| 1251 | 'tab-bar-lines))))) | ||
| 1252 | |||
| 1245 | (if (and (boundp 'menu-bar-showhide-tool-bar-menu) | 1253 | (if (and (boundp 'menu-bar-showhide-tool-bar-menu) |
| 1246 | (keymapp menu-bar-showhide-tool-bar-menu)) | 1254 | (keymapp menu-bar-showhide-tool-bar-menu)) |
| 1247 | (bindings--define-key menu [showhide-tool-bar] | 1255 | (bindings--define-key menu [showhide-tool-bar] |
diff --git a/lisp/mouse.el b/lisp/mouse.el index 123ce2ca154..76fec507e71 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el | |||
| @@ -2734,6 +2734,7 @@ is copied instead of being cut." | |||
| 2734 | ;; versions. | 2734 | ;; versions. |
| 2735 | (global-set-key [header-line down-mouse-1] 'mouse-drag-header-line) | 2735 | (global-set-key [header-line down-mouse-1] 'mouse-drag-header-line) |
| 2736 | (global-set-key [header-line mouse-1] 'mouse-select-window) | 2736 | (global-set-key [header-line mouse-1] 'mouse-select-window) |
| 2737 | (global-set-key [tab-line mouse-1] 'mouse-select-window) | ||
| 2737 | ;; (global-set-key [mode-line drag-mouse-1] 'mouse-select-window) | 2738 | ;; (global-set-key [mode-line drag-mouse-1] 'mouse-select-window) |
| 2738 | (global-set-key [mode-line down-mouse-1] 'mouse-drag-mode-line) | 2739 | (global-set-key [mode-line down-mouse-1] 'mouse-drag-mode-line) |
| 2739 | (global-set-key [mode-line mouse-1] 'mouse-select-window) | 2740 | (global-set-key [mode-line mouse-1] 'mouse-select-window) |
diff --git a/lisp/startup.el b/lisp/startup.el index 52d4dbb05c8..393d7872560 100644 --- a/lisp/startup.el +++ b/lisp/startup.el | |||
| @@ -769,6 +769,7 @@ It is the default value of the variable `top-level'." | |||
| 769 | ("--background-color" . "-bg") | 769 | ("--background-color" . "-bg") |
| 770 | ("--color" . "-color"))) | 770 | ("--color" . "-color"))) |
| 771 | 771 | ||
| 772 | ;; FIXME: this var unused? | ||
| 772 | (defconst tool-bar-images-pixel-height 24 | 773 | (defconst tool-bar-images-pixel-height 24 |
| 773 | "Height in pixels of images in the tool-bar.") | 774 | "Height in pixels of images in the tool-bar.") |
| 774 | 775 | ||
| @@ -1300,6 +1301,7 @@ please check its value") | |||
| 1300 | (unless (daemonp) | 1301 | (unless (daemonp) |
| 1301 | (if (or noninteractive emacs-basic-display) | 1302 | (if (or noninteractive emacs-basic-display) |
| 1302 | (setq menu-bar-mode nil | 1303 | (setq menu-bar-mode nil |
| 1304 | tab-bar-mode nil | ||
| 1303 | tool-bar-mode nil | 1305 | tool-bar-mode nil |
| 1304 | no-blinking-cursor t)) | 1306 | no-blinking-cursor t)) |
| 1305 | (frame-initialize)) | 1307 | (frame-initialize)) |
| @@ -1515,6 +1517,7 @@ This can set the values of `menu-bar-mode', `tool-bar-mode', and | |||
| 1515 | settings will be marked as \"CHANGED outside of Customize\"." | 1517 | settings will be marked as \"CHANGED outside of Customize\"." |
| 1516 | (let ((no-vals '("no" "off" "false" "0")) | 1518 | (let ((no-vals '("no" "off" "false" "0")) |
| 1517 | (settings '(("menuBar" "MenuBar" menu-bar-mode nil) | 1519 | (settings '(("menuBar" "MenuBar" menu-bar-mode nil) |
| 1520 | ("tabBar" "TabBar" tab-bar-mode nil) | ||
| 1518 | ("toolBar" "ToolBar" tool-bar-mode nil) | 1521 | ("toolBar" "ToolBar" tool-bar-mode nil) |
| 1519 | ("scrollBar" "ScrollBar" scroll-bar-mode nil) | 1522 | ("scrollBar" "ScrollBar" scroll-bar-mode nil) |
| 1520 | ("cursorBlink" "CursorBlink" no-blinking-cursor t)))) | 1523 | ("cursorBlink" "CursorBlink" no-blinking-cursor t)))) |
diff --git a/lisp/subr.el b/lisp/subr.el index 45b99a82d2b..da619fef147 100644 --- a/lisp/subr.el +++ b/lisp/subr.el | |||
| @@ -2395,8 +2395,12 @@ some sort of escape sequence, the ambiguity is resolved via `read-key-delay'." | |||
| 2395 | (progn | 2395 | (progn |
| 2396 | (use-global-map | 2396 | (use-global-map |
| 2397 | (let ((map (make-sparse-keymap))) | 2397 | (let ((map (make-sparse-keymap))) |
| 2398 | ;; Don't hide the menu-bar and tool-bar entries. | 2398 | ;; Don't hide the menu-bar, tab-bar and tool-bar entries. |
| 2399 | (define-key map [menu-bar] (lookup-key global-map [menu-bar])) | 2399 | (define-key map [menu-bar] (lookup-key global-map [menu-bar])) |
| 2400 | (define-key map [tab-bar] | ||
| 2401 | ;; This hack avoids evaluating the :filter (Bug#9922). | ||
| 2402 | (or (cdr (assq 'tab-bar global-map)) | ||
| 2403 | (lookup-key global-map [tab-bar]))) | ||
| 2400 | (define-key map [tool-bar] | 2404 | (define-key map [tool-bar] |
| 2401 | ;; This hack avoids evaluating the :filter (Bug#9922). | 2405 | ;; This hack avoids evaluating the :filter (Bug#9922). |
| 2402 | (or (cdr (assq 'tool-bar global-map)) | 2406 | (or (cdr (assq 'tool-bar global-map)) |
diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el new file mode 100644 index 00000000000..42d40a96543 --- /dev/null +++ b/lisp/tab-bar.el | |||
| @@ -0,0 +1,764 @@ | |||
| 1 | ;;; tab-bar.el --- frame-local tabs with named persistent window configurations -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2019 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Juri Linkov <juri@linkov.net> | ||
| 6 | ;; Keywords: frames tabs | ||
| 7 | ;; Maintainer: emacs-devel@gnu.org | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; Provides `tab-bar-mode' to control display of the tab bar and | ||
| 27 | ;; bindings for the global tab bar. | ||
| 28 | |||
| 29 | ;; The normal global binding for [tab-bar] (below) uses the value of | ||
| 30 | ;; `tab-bar-map' as the actual keymap to define the tab bar. Modes | ||
| 31 | ;; may either bind items under the [tab-bar] prefix key of the local | ||
| 32 | ;; map to add to the global bar or may set `tab-bar-map' | ||
| 33 | ;; buffer-locally to override it. | ||
| 34 | |||
| 35 | ;;; Code: | ||
| 36 | |||
| 37 | |||
| 38 | (defgroup tab-bar nil | ||
| 39 | "Frame-local tabs." | ||
| 40 | :group 'convenience | ||
| 41 | :version "27.1") | ||
| 42 | |||
| 43 | (defgroup tab-bar-faces nil | ||
| 44 | "Faces used in the tab bar." | ||
| 45 | :group 'tab-bar | ||
| 46 | :group 'faces | ||
| 47 | :version "27.1") | ||
| 48 | |||
| 49 | (defface tab-bar | ||
| 50 | '((((type x w32 ns) (class color)) | ||
| 51 | :height 1.1 | ||
| 52 | :background "grey85" | ||
| 53 | :foreground "black") | ||
| 54 | (((type x) (class mono)) | ||
| 55 | :background "grey") | ||
| 56 | (t | ||
| 57 | :inverse-video t)) | ||
| 58 | "Tab bar face." | ||
| 59 | :version "27.1" | ||
| 60 | :group 'tab-bar-faces) | ||
| 61 | |||
| 62 | (defface tab-bar-tab | ||
| 63 | '((((class color) (min-colors 88)) | ||
| 64 | :box (:line-width 1 :style released-button)) | ||
| 65 | (t | ||
| 66 | :inverse-video nil)) | ||
| 67 | "Tab bar face for selected tab." | ||
| 68 | :version "27.1" | ||
| 69 | :group 'tab-bar-faces) | ||
| 70 | |||
| 71 | (defface tab-bar-tab-inactive | ||
| 72 | '((default | ||
| 73 | :inherit tab-bar-tab) | ||
| 74 | (((class color) (min-colors 88)) | ||
| 75 | :background "grey75") | ||
| 76 | (t | ||
| 77 | :inverse-video t)) | ||
| 78 | "Tab bar face for non-selected tab." | ||
| 79 | :version "27.1" | ||
| 80 | :group 'tab-bar-faces) | ||
| 81 | |||
| 82 | |||
| 83 | (define-minor-mode tab-bar-mode | ||
| 84 | "Toggle the tab bar in all graphical frames (Tab Bar mode)." | ||
| 85 | :global t | ||
| 86 | ;; It's defined in C/cus-start, this stops the d-m-m macro defining it again. | ||
| 87 | :variable tab-bar-mode | ||
| 88 | (let ((val (if tab-bar-mode 1 0))) | ||
| 89 | (dolist (frame (frame-list)) | ||
| 90 | (set-frame-parameter frame 'tab-bar-lines val)) | ||
| 91 | ;; If the user has given `default-frame-alist' a `tab-bar-lines' | ||
| 92 | ;; parameter, replace it. | ||
| 93 | (if (assq 'tab-bar-lines default-frame-alist) | ||
| 94 | (setq default-frame-alist | ||
| 95 | (cons (cons 'tab-bar-lines val) | ||
| 96 | (assq-delete-all 'tab-bar-lines | ||
| 97 | default-frame-alist))))) | ||
| 98 | (when tab-bar-mode | ||
| 99 | (global-set-key [(control shift iso-lefttab)] 'tab-bar-switch-to-prev-tab) | ||
| 100 | (global-set-key [(control shift tab)] 'tab-bar-switch-to-prev-tab) | ||
| 101 | (global-set-key [(control tab)] 'tab-bar-switch-to-next-tab))) | ||
| 102 | |||
| 103 | (defun tab-bar-handle-mouse (event) | ||
| 104 | "Text-mode emulation of switching tabs on the tab bar. | ||
| 105 | This command is used when you click the mouse in the tab bar | ||
| 106 | on a console which has no window system but does have a mouse." | ||
| 107 | (interactive "e") | ||
| 108 | (let* ((x-position (car (posn-x-y (event-start event)))) | ||
| 109 | (keymap (lookup-key (cons 'keymap (nreverse (current-active-maps))) [tab-bar])) | ||
| 110 | (column 0)) | ||
| 111 | (when x-position | ||
| 112 | (unless (catch 'done | ||
| 113 | (map-keymap | ||
| 114 | (lambda (_key binding) | ||
| 115 | (when (eq (car-safe binding) 'menu-item) | ||
| 116 | (when (> (+ column (length (nth 1 binding))) x-position) | ||
| 117 | ;; TODO: handle close | ||
| 118 | (unless (get-text-property (- x-position column) 'close-tab (nth 1 binding)) | ||
| 119 | (call-interactively (nth 2 binding))) | ||
| 120 | (throw 'done t)) | ||
| 121 | (setq column (+ column (length (nth 1 binding)))))) | ||
| 122 | keymap)) | ||
| 123 | ;; Clicking anywhere outside existing tabs will add a new tab | ||
| 124 | (tab-bar-new-tab))))) | ||
| 125 | |||
| 126 | ;; Used in the Show/Hide menu, to have the toggle reflect the current frame. | ||
| 127 | (defun toggle-tab-bar-mode-from-frame (&optional arg) | ||
| 128 | "Toggle tab bar on or off, based on the status of the current frame. | ||
| 129 | See `tab-bar-mode' for more information." | ||
| 130 | (interactive (list (or current-prefix-arg 'toggle))) | ||
| 131 | (if (eq arg 'toggle) | ||
| 132 | (tab-bar-mode (if (> (frame-parameter nil 'tab-bar-lines) 0) 0 1)) | ||
| 133 | (tab-bar-mode arg))) | ||
| 134 | |||
| 135 | (defvar tab-bar-map (make-sparse-keymap) | ||
| 136 | "Keymap for the tab bar. | ||
| 137 | Define this locally to override the global tab bar.") | ||
| 138 | |||
| 139 | (global-set-key [tab-bar] | ||
| 140 | `(menu-item ,(purecopy "tab bar") ignore | ||
| 141 | :filter tab-bar-make-keymap)) | ||
| 142 | |||
| 143 | (defconst tab-bar-keymap-cache (make-hash-table :weakness t :test 'equal)) | ||
| 144 | |||
| 145 | (defun tab-bar-make-keymap (&optional _ignore) | ||
| 146 | "Generate an actual keymap from `tab-bar-map'. | ||
| 147 | Its main job is to show tabs in the tab bar." | ||
| 148 | (if (= 1 (length tab-bar-map)) | ||
| 149 | (tab-bar-make-keymap-1) | ||
| 150 | (let ((key (cons (frame-terminal) tab-bar-map))) | ||
| 151 | (or (gethash key tab-bar-keymap-cache) | ||
| 152 | (puthash key tab-bar-map tab-bar-keymap-cache))))) | ||
| 153 | |||
| 154 | |||
| 155 | (defcustom tab-bar-new-tab-choice t | ||
| 156 | "Defines what to show in a new tab. | ||
| 157 | If t, start a new tab with the current buffer, i.e. the buffer | ||
| 158 | that was current before calling the command that adds a new tab | ||
| 159 | (this is the same what `make-frame' does by default). | ||
| 160 | If the value is a string, switch to a buffer if it exists, or switch | ||
| 161 | to a buffer visiting the file or directory that the string specifies. | ||
| 162 | If the value is a function, call it with no arguments and switch to | ||
| 163 | the buffer that it returns. | ||
| 164 | If nil, duplicate the contents of the tab that was active | ||
| 165 | before calling the command that adds a new tab." | ||
| 166 | :type '(choice (const :tag "Current buffer" t) | ||
| 167 | (directory :tag "Directory" :value "~/") | ||
| 168 | (file :tag "File" :value "~/.emacs") | ||
| 169 | (string :tag "Buffer" "*scratch*") | ||
| 170 | (function :tag "Function") | ||
| 171 | (const :tag "Duplicate tab" nil)) | ||
| 172 | :group 'tab-bar | ||
| 173 | :version "27.1") | ||
| 174 | |||
| 175 | (defvar tab-bar-new-button | ||
| 176 | (propertize " + " | ||
| 177 | 'display `(image :type xpm | ||
| 178 | :file ,(expand-file-name | ||
| 179 | "images/tabs/new.xpm" | ||
| 180 | data-directory) | ||
| 181 | :margin (2 . 0) | ||
| 182 | :ascent center)) | ||
| 183 | "Button for creating a new tab.") | ||
| 184 | |||
| 185 | (defcustom tab-bar-close-button-show t | ||
| 186 | "Defines where to show the close tab button. | ||
| 187 | If t, show the close tab button on all tabs. | ||
| 188 | If `selected', show it only on the selected tab. | ||
| 189 | If `non-selected', show it only on non-selected tab. | ||
| 190 | If nil, don't show it at all." | ||
| 191 | :type '(choice (const :tag "On all tabs" t) | ||
| 192 | (const :tag "On selected tab" selected) | ||
| 193 | (const :tag "On non-selected tabs" non-selected) | ||
| 194 | (const :tag "None" nil)) | ||
| 195 | :set (lambda (sym val) | ||
| 196 | (set sym val) | ||
| 197 | (force-mode-line-update)) | ||
| 198 | :group 'tab-bar | ||
| 199 | :version "27.1") | ||
| 200 | |||
| 201 | (defvar tab-bar-close-button | ||
| 202 | (propertize " x" | ||
| 203 | 'display `(image :type xpm | ||
| 204 | :file ,(expand-file-name | ||
| 205 | "images/tabs/close.xpm" | ||
| 206 | data-directory) | ||
| 207 | :margin (2 . 0) | ||
| 208 | :ascent center) | ||
| 209 | 'close-tab t | ||
| 210 | :help "Click to close tab") | ||
| 211 | "Button for closing the clicked tab.") | ||
| 212 | |||
| 213 | (defvar tab-bar-separator nil) | ||
| 214 | |||
| 215 | |||
| 216 | (defvar tab-bar-tab-name-function #'tab-bar-tab-name | ||
| 217 | "Function to get a tab name. | ||
| 218 | Function gets no arguments. | ||
| 219 | By default, use function `tab-bar-tab-name'.") | ||
| 220 | |||
| 221 | (defun tab-bar-tab-name () | ||
| 222 | "Generate tab name in the context of the selected frame." | ||
| 223 | (mapconcat #'buffer-name | ||
| 224 | (delete-dups (mapcar #'window-buffer | ||
| 225 | (window-list-1 (frame-first-window) | ||
| 226 | 'nomini))) | ||
| 227 | ", ")) | ||
| 228 | |||
| 229 | (defvar tab-bar-tabs-function #'tab-bar-tabs | ||
| 230 | "Function to get a list of tabs to display in the tab bar. | ||
| 231 | This function should return a list of alists with parameters | ||
| 232 | that include at least the element (name . TAB-NAME). | ||
| 233 | For example, '((tab (name . \"Tab 1\")) (current-tab (name . \"Tab 2\"))) | ||
| 234 | By default, use function `tab-bar-tabs'.") | ||
| 235 | |||
| 236 | (defun tab-bar-tabs () | ||
| 237 | "Return a list of tabs belonging to the selected frame. | ||
| 238 | Ensure the frame parameter `tabs' is pre-populated. | ||
| 239 | Return its existing value or a new value." | ||
| 240 | (let ((tabs (frame-parameter nil 'tabs))) | ||
| 241 | (if tabs | ||
| 242 | ;; Update current tab name | ||
| 243 | (let ((name (assq 'name (assq 'current-tab tabs)))) | ||
| 244 | (when name (setcdr name (funcall tab-bar-tab-name-function)))) | ||
| 245 | ;; Create default tabs | ||
| 246 | (setq tabs `((current-tab (name . ,(funcall tab-bar-tab-name-function))))) | ||
| 247 | (set-frame-parameter nil 'tabs tabs)) | ||
| 248 | tabs)) | ||
| 249 | |||
| 250 | (defun tab-bar-make-keymap-1 () | ||
| 251 | "Generate an actual keymap from `tab-bar-map', without caching." | ||
| 252 | (let ((separator (or tab-bar-separator (if window-system " " "|"))) | ||
| 253 | (i 0)) | ||
| 254 | (append | ||
| 255 | '(keymap (mouse-1 . tab-bar-handle-mouse)) | ||
| 256 | (mapcan | ||
| 257 | (lambda (tab) | ||
| 258 | (setq i (1+ i)) | ||
| 259 | (append | ||
| 260 | `((,(intern (format "sep-%i" i)) menu-item ,separator ignore)) | ||
| 261 | (cond | ||
| 262 | ((eq (car tab) 'current-tab) | ||
| 263 | `((current-tab | ||
| 264 | menu-item | ||
| 265 | ,(propertize (concat (cdr (assq 'name tab)) | ||
| 266 | (or (and tab-bar-close-button-show | ||
| 267 | (not (eq tab-bar-close-button-show | ||
| 268 | 'non-selected)) | ||
| 269 | tab-bar-close-button) "")) | ||
| 270 | 'face 'tab-bar-tab) | ||
| 271 | ignore | ||
| 272 | :help "Current tab"))) | ||
| 273 | (t | ||
| 274 | `((,(intern (format "tab-%i" i)) | ||
| 275 | menu-item | ||
| 276 | ,(propertize (concat (cdr (assq 'name tab)) | ||
| 277 | (or (and tab-bar-close-button-show | ||
| 278 | (not (eq tab-bar-close-button-show | ||
| 279 | 'selected)) | ||
| 280 | tab-bar-close-button) "")) | ||
| 281 | 'face 'tab-bar-tab-inactive) | ||
| 282 | ,(or | ||
| 283 | (cdr (assq 'binding tab)) | ||
| 284 | (lambda () | ||
| 285 | (interactive) | ||
| 286 | (tab-bar-select-tab tab))) | ||
| 287 | :help "Click to visit tab")))) | ||
| 288 | `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) | ||
| 289 | menu-item "" | ||
| 290 | ,(or | ||
| 291 | (cdr (assq 'close-binding tab)) | ||
| 292 | (lambda () | ||
| 293 | (interactive) | ||
| 294 | (tab-bar-close-tab tab))))))) | ||
| 295 | (funcall tab-bar-tabs-function)) | ||
| 296 | (when tab-bar-new-button | ||
| 297 | `((sep-add-tab menu-item ,separator ignore) | ||
| 298 | (add-tab menu-item ,tab-bar-new-button tab-bar-new-tab | ||
| 299 | :help "New tab")))))) | ||
| 300 | |||
| 301 | |||
| 302 | (defun tab-bar-read-tab-name (prompt) | ||
| 303 | (let* ((tabs (tab-bar-tabs)) | ||
| 304 | (tab-name | ||
| 305 | (completing-read prompt | ||
| 306 | (or (delq nil (mapcar (lambda (tab) | ||
| 307 | (cdr (assq 'name tab))) | ||
| 308 | tabs)) | ||
| 309 | '(""))))) | ||
| 310 | (catch 'done | ||
| 311 | (dolist (tab tabs) | ||
| 312 | (when (equal (cdr (assq 'name tab)) tab-name) | ||
| 313 | (throw 'done tab)))))) | ||
| 314 | |||
| 315 | (defun tab-bar-tab-default () | ||
| 316 | (let ((tab `(tab | ||
| 317 | (name . ,(funcall tab-bar-tab-name-function)) | ||
| 318 | (time . ,(time-convert nil 'integer)) | ||
| 319 | (wc . ,(current-window-configuration)) | ||
| 320 | (ws . ,(window-state-get | ||
| 321 | (frame-root-window (selected-frame)) 'writable))))) | ||
| 322 | tab)) | ||
| 323 | |||
| 324 | (defun tab-bar-find-prev-tab (&optional tabs) | ||
| 325 | (unless tabs | ||
| 326 | (setq tabs (tab-bar-tabs))) | ||
| 327 | (unless (eq (car (car tabs)) 'current-tab) | ||
| 328 | (while (and tabs (not (eq (car (car (cdr tabs))) 'current-tab))) | ||
| 329 | (setq tabs (cdr tabs))) | ||
| 330 | tabs)) | ||
| 331 | |||
| 332 | |||
| 333 | (defun tab-bar-select-tab (tab) | ||
| 334 | "Switch to the specified TAB." | ||
| 335 | (interactive (list (tab-bar-read-tab-name "Select tab by name: "))) | ||
| 336 | (when (and tab (not (eq (car tab) 'current-tab))) | ||
| 337 | (let* ((tabs (tab-bar-tabs)) | ||
| 338 | (new-tab (tab-bar-tab-default)) | ||
| 339 | (wc (cdr (assq 'wc tab)))) | ||
| 340 | ;; During the same session, use window-configuration to switch | ||
| 341 | ;; tabs, because window-configurations are more reliable | ||
| 342 | ;; (they keep references to live buffers) than window-states. | ||
| 343 | ;; But after restoring tabs from a previously saved session, | ||
| 344 | ;; its value of window-configuration is unreadable, | ||
| 345 | ;; so restore its saved window-state. | ||
| 346 | (if (window-configuration-p wc) | ||
| 347 | (set-window-configuration wc) | ||
| 348 | (window-state-put (cdr (assq 'ws tab)) | ||
| 349 | (frame-root-window (selected-frame)) 'safe)) | ||
| 350 | (while tabs | ||
| 351 | (cond | ||
| 352 | ((eq (car tabs) tab) | ||
| 353 | (setcar tabs `(current-tab (name . ,(funcall tab-bar-tab-name-function))))) | ||
| 354 | ((eq (car (car tabs)) 'current-tab) | ||
| 355 | (setcar tabs new-tab))) | ||
| 356 | (setq tabs (cdr tabs))) | ||
| 357 | (force-mode-line-update)))) | ||
| 358 | |||
| 359 | (defun tab-bar-switch-to-prev-tab (&optional _arg) | ||
| 360 | "Switch to ARGth previous tab." | ||
| 361 | (interactive "p") | ||
| 362 | (let ((prev-tab (tab-bar-find-prev-tab))) | ||
| 363 | (when prev-tab | ||
| 364 | (tab-bar-select-tab (car prev-tab))))) | ||
| 365 | |||
| 366 | (defun tab-bar-switch-to-next-tab (&optional _arg) | ||
| 367 | "Switch to ARGth next tab." | ||
| 368 | (interactive "p") | ||
| 369 | (let* ((tabs (tab-bar-tabs)) | ||
| 370 | (prev-tab (tab-bar-find-prev-tab tabs))) | ||
| 371 | (if prev-tab | ||
| 372 | (tab-bar-select-tab (car (cdr (cdr prev-tab)))) | ||
| 373 | (tab-bar-select-tab (car (cdr tabs)))))) | ||
| 374 | |||
| 375 | |||
| 376 | (defcustom tab-bar-new-tab-to 'right | ||
| 377 | "Defines where to create a new tab. | ||
| 378 | If `leftmost', create as the first tab. | ||
| 379 | If `left', create to the left from the current tab. | ||
| 380 | If `right', create to the right from the current tab. | ||
| 381 | If `rightmost', create as the last tab." | ||
| 382 | :type '(choice (const :tag "First tab" leftmost) | ||
| 383 | (const :tag "To the left" left) | ||
| 384 | (const :tag "To the right" right) | ||
| 385 | (const :tag "Last tab" rightmost)) | ||
| 386 | :group 'tab-bar | ||
| 387 | :version "27.1") | ||
| 388 | |||
| 389 | (defun tab-bar-new-tab () | ||
| 390 | "Clone the current tab to the position specified by `tab-bar-new-tab-to'." | ||
| 391 | (interactive) | ||
| 392 | (unless tab-bar-mode | ||
| 393 | (tab-bar-mode 1)) | ||
| 394 | (let* ((tabs (tab-bar-tabs)) | ||
| 395 | ;; (i-tab (- (length tabs) (length (memq tab tabs)))) | ||
| 396 | (new-tab (tab-bar-tab-default))) | ||
| 397 | (cond | ||
| 398 | ((eq tab-bar-new-tab-to 'leftmost) | ||
| 399 | (setq tabs (cons new-tab tabs))) | ||
| 400 | ((eq tab-bar-new-tab-to 'rightmost) | ||
| 401 | (setq tabs (append tabs (list new-tab)))) | ||
| 402 | (t | ||
| 403 | (let ((prev-tab (tab-bar-find-prev-tab tabs))) | ||
| 404 | (cond | ||
| 405 | ((eq tab-bar-new-tab-to 'left) | ||
| 406 | (if prev-tab | ||
| 407 | (setcdr prev-tab (cons new-tab (cdr prev-tab))) | ||
| 408 | (setq tabs (cons new-tab tabs)))) | ||
| 409 | ((eq tab-bar-new-tab-to 'right) | ||
| 410 | (if prev-tab | ||
| 411 | (setq prev-tab (cdr prev-tab)) | ||
| 412 | (setq prev-tab tabs)) | ||
| 413 | (setcdr prev-tab (cons new-tab (cdr prev-tab)))))))) | ||
| 414 | (set-frame-parameter nil 'tabs tabs) | ||
| 415 | (tab-bar-select-tab new-tab) | ||
| 416 | (when tab-bar-new-tab-choice | ||
| 417 | (delete-other-windows) | ||
| 418 | (let ((buffer | ||
| 419 | (if (functionp tab-bar-new-tab-choice) | ||
| 420 | (funcall tab-bar-new-tab-choice) | ||
| 421 | (if (stringp tab-bar-new-tab-choice) | ||
| 422 | (or (get-buffer tab-bar-new-tab-choice) | ||
| 423 | (find-file-noselect tab-bar-new-tab-choice)))))) | ||
| 424 | (when (buffer-live-p buffer) | ||
| 425 | (switch-to-buffer buffer)))) | ||
| 426 | (unless tab-bar-mode | ||
| 427 | (message "Added new tab with the current window configuration")))) | ||
| 428 | |||
| 429 | |||
| 430 | (defcustom tab-bar-close-tab-select 'right | ||
| 431 | "Defines what tab to select after closing the specified tab. | ||
| 432 | If `left', select the adjacent left tab. | ||
| 433 | If `right', select the adjacent right tab." | ||
| 434 | :type '(choice (const :tag "Select left tab" left) | ||
| 435 | (const :tag "Select right tab" right)) | ||
| 436 | :group 'tab-bar | ||
| 437 | :version "27.1") | ||
| 438 | |||
| 439 | (defun tab-bar-close-current-tab (&optional tab select-tab) | ||
| 440 | "Close the current TAB. | ||
| 441 | After closing the current tab switch to the tab | ||
| 442 | specified by `tab-bar-close-tab-select', or to `select-tab' | ||
| 443 | if its value is provided." | ||
| 444 | (interactive) | ||
| 445 | (let ((tabs (tab-bar-tabs))) | ||
| 446 | (unless tab | ||
| 447 | (let ((prev-tab (tab-bar-find-prev-tab tabs))) | ||
| 448 | (setq tab (if prev-tab | ||
| 449 | (car (cdr prev-tab)) | ||
| 450 | (car tabs))))) | ||
| 451 | (if select-tab | ||
| 452 | (setq tabs (delq tab tabs)) | ||
| 453 | (let* ((i-tab (- (length tabs) (length (memq tab tabs)))) | ||
| 454 | (i-select | ||
| 455 | (cond | ||
| 456 | ((eq tab-bar-close-tab-select 'left) | ||
| 457 | (1- i-tab)) | ||
| 458 | ((eq tab-bar-close-tab-select 'right) | ||
| 459 | ;; Do nothing: the next tab will take | ||
| 460 | ;; the index of the closed tab | ||
| 461 | i-tab) | ||
| 462 | (t 0)))) | ||
| 463 | (setq tabs (delq tab tabs) | ||
| 464 | i-select (max 0 (min (1- (length tabs)) i-select)) | ||
| 465 | select-tab (nth i-select tabs)))) | ||
| 466 | (set-frame-parameter nil 'tabs tabs) | ||
| 467 | (tab-bar-select-tab select-tab))) | ||
| 468 | |||
| 469 | (defun tab-bar-close-tab (tab) | ||
| 470 | "Close the specified TAB. | ||
| 471 | After closing the current tab switch to the tab | ||
| 472 | specified by `tab-bar-close-tab-select'." | ||
| 473 | (interactive (list (tab-bar-read-tab-name "Close tab by name: "))) | ||
| 474 | (when tab | ||
| 475 | (if (eq (car tab) 'current-tab) | ||
| 476 | (tab-bar-close-current-tab tab) | ||
| 477 | ;; Close non-current tab, no need to switch to another tab | ||
| 478 | (set-frame-parameter nil 'tabs (delq tab (tab-bar-tabs))) | ||
| 479 | (force-mode-line-update)))) | ||
| 480 | |||
| 481 | |||
| 482 | ;;; Non-graphical access to frame-local tabs (named window configurations) | ||
| 483 | |||
| 484 | (defun tab-new () | ||
| 485 | "Create a new named window configuration without having to click a tab." | ||
| 486 | (interactive) | ||
| 487 | (tab-bar-new-tab) | ||
| 488 | (unless tab-bar-mode | ||
| 489 | (message "Added new tab with the current window configuration"))) | ||
| 490 | |||
| 491 | (defun tab-close () | ||
| 492 | "Delete the current window configuration without clicking a close button." | ||
| 493 | (interactive) | ||
| 494 | (tab-bar-close-current-tab) | ||
| 495 | (unless tab-bar-mode | ||
| 496 | (message "Deleted the current tab"))) | ||
| 497 | |||
| 498 | ;; Short aliases | ||
| 499 | ;; (defalias 'tab-switch 'tab-bar-switch-to-next-tab) | ||
| 500 | (defalias 'tab-select 'tab-bar-select-tab) | ||
| 501 | (defalias 'tab-previous 'tab-bar-switch-to-prev-tab) | ||
| 502 | (defalias 'tab-next 'tab-bar-switch-to-next-tab) | ||
| 503 | (defalias 'tab-list 'tab-bar-list) | ||
| 504 | |||
| 505 | (defun tab-bar-list () | ||
| 506 | "Display a list of named window configurations. | ||
| 507 | The list is displayed in the buffer `*Tabs*'. | ||
| 508 | |||
| 509 | In this list of window configurations you can delete or select them. | ||
| 510 | Type ? after invocation to get help on commands available. | ||
| 511 | Type q to remove the list of window configurations from the display. | ||
| 512 | |||
| 513 | The first column shows `D' for for a window configuration you have | ||
| 514 | marked for deletion." | ||
| 515 | (interactive) | ||
| 516 | (let ((dir default-directory) | ||
| 517 | (minibuf (minibuffer-selected-window))) | ||
| 518 | (let ((tab-bar-mode t)) ; don't enable tab-bar-mode if it's disabled | ||
| 519 | (tab-bar-new-tab)) | ||
| 520 | ;; Handle the case when it's called in the active minibuffer. | ||
| 521 | (when minibuf (select-window (minibuffer-selected-window))) | ||
| 522 | (delete-other-windows) | ||
| 523 | ;; Create a new window to replace the existing one, to not break the | ||
| 524 | ;; window parameters (e.g. prev/next buffers) of the window just saved | ||
| 525 | ;; to the window configuration. So when a saved window is restored, | ||
| 526 | ;; its parameters left intact. | ||
| 527 | (split-window) (delete-window) | ||
| 528 | (let ((switch-to-buffer-preserve-window-point nil)) | ||
| 529 | (switch-to-buffer (tab-bar-list-noselect))) | ||
| 530 | (setq default-directory dir)) | ||
| 531 | (message "Commands: d, x; RET; q to quit; ? for help.")) | ||
| 532 | |||
| 533 | (defun tab-bar-list-noselect () | ||
| 534 | "Create and return a buffer with a list of window configurations. | ||
| 535 | The list is displayed in a buffer named `*Tabs*'. | ||
| 536 | |||
| 537 | For more information, see the function `tab-bar-list'." | ||
| 538 | (let* ((tabs (delq nil (mapcar (lambda (tab) ; remove current tab | ||
| 539 | (unless (eq (car tab) 'current-tab) | ||
| 540 | tab)) | ||
| 541 | (tab-bar-tabs)))) | ||
| 542 | ;; Sort by recency | ||
| 543 | (tabs (sort tabs (lambda (a b) (< (cdr (assq 'time b)) | ||
| 544 | (cdr (assq 'time a))))))) | ||
| 545 | (with-current-buffer (get-buffer-create | ||
| 546 | (format " *Tabs*<%s>" (or (frame-parameter nil 'window-id) | ||
| 547 | (frame-parameter nil 'name)))) | ||
| 548 | (erase-buffer) | ||
| 549 | (tab-bar-list-mode) | ||
| 550 | (setq buffer-read-only nil) | ||
| 551 | ;; Vertical alignment to the center of the frame | ||
| 552 | (insert-char ?\n (/ (- (frame-height) (length tabs) 1) 2)) | ||
| 553 | ;; Horizontal alignment to the center of the frame | ||
| 554 | (setq tab-bar-list-column (- (/ (frame-width) 2) 15)) | ||
| 555 | (dolist (tab tabs) | ||
| 556 | (insert (propertize | ||
| 557 | (format "%s %s\n" | ||
| 558 | (make-string tab-bar-list-column ?\040) | ||
| 559 | (propertize | ||
| 560 | (cdr (assq 'name tab)) | ||
| 561 | 'mouse-face 'highlight | ||
| 562 | 'help-echo "mouse-2: select this window configuration")) | ||
| 563 | 'tab tab))) | ||
| 564 | (goto-char (point-min)) | ||
| 565 | (goto-char (or (next-single-property-change (point) 'tab) (point-min))) | ||
| 566 | (when (> (length tabs) 1) | ||
| 567 | (tab-bar-list-next-line)) | ||
| 568 | (move-to-column tab-bar-list-column) | ||
| 569 | (set-buffer-modified-p nil) | ||
| 570 | (current-buffer)))) | ||
| 571 | |||
| 572 | (defvar tab-bar-list-column 3) | ||
| 573 | (make-variable-buffer-local 'tab-bar-list-column) | ||
| 574 | |||
| 575 | (defvar tab-bar-list-mode-map | ||
| 576 | (let ((map (make-keymap))) | ||
| 577 | (suppress-keymap map t) | ||
| 578 | (define-key map "q" 'quit-window) | ||
| 579 | (define-key map "\C-m" 'tab-bar-list-select) | ||
| 580 | (define-key map "d" 'tab-bar-list-delete) | ||
| 581 | (define-key map "k" 'tab-bar-list-delete) | ||
| 582 | (define-key map "\C-d" 'tab-bar-list-delete-backwards) | ||
| 583 | (define-key map "\C-k" 'tab-bar-list-delete) | ||
| 584 | (define-key map "x" 'tab-bar-list-execute) | ||
| 585 | (define-key map " " 'tab-bar-list-next-line) | ||
| 586 | (define-key map "n" 'tab-bar-list-next-line) | ||
| 587 | (define-key map "p" 'tab-bar-list-prev-line) | ||
| 588 | (define-key map "\177" 'tab-bar-list-backup-unmark) | ||
| 589 | (define-key map "?" 'describe-mode) | ||
| 590 | (define-key map "u" 'tab-bar-list-unmark) | ||
| 591 | (define-key map [mouse-2] 'tab-bar-list-mouse-select) | ||
| 592 | (define-key map [follow-link] 'mouse-face) | ||
| 593 | map) | ||
| 594 | "Local keymap for `tab-bar-list-mode' buffers.") | ||
| 595 | |||
| 596 | (define-derived-mode tab-bar-list-mode nil "Window Configurations" | ||
| 597 | "Major mode for selecting a window configuration. | ||
| 598 | Each line describes one window configuration in Emacs. | ||
| 599 | Letters do not insert themselves; instead, they are commands. | ||
| 600 | \\<tab-bar-list-mode-map> | ||
| 601 | \\[tab-bar-list-mouse-select] -- select window configuration you click on. | ||
| 602 | \\[tab-bar-list-select] -- select current line's window configuration. | ||
| 603 | \\[tab-bar-list-delete] -- mark that window configuration to be deleted, and move down. | ||
| 604 | \\[tab-bar-list-delete-backwards] -- mark that window configuration to be deleted, and move up. | ||
| 605 | \\[tab-bar-list-execute] -- delete marked window configurations. | ||
| 606 | \\[tab-bar-list-unmark] -- remove all kinds of marks from current line. | ||
| 607 | With prefix argument, also move up one line. | ||
| 608 | \\[tab-bar-list-backup-unmark] -- back up a line and remove marks." | ||
| 609 | (setq truncate-lines t) | ||
| 610 | (setq buffer-read-only t)) | ||
| 611 | |||
| 612 | (defun tab-bar-list-current-tab (error-if-non-existent-p) | ||
| 613 | "Return window configuration described by this line of the list." | ||
| 614 | (let* ((where (save-excursion | ||
| 615 | (beginning-of-line) | ||
| 616 | (+ 2 (point) tab-bar-list-column))) | ||
| 617 | (tab (and (not (eobp)) (get-text-property where 'tab)))) | ||
| 618 | (or tab | ||
| 619 | (if error-if-non-existent-p | ||
| 620 | (user-error "No window configuration on this line") | ||
| 621 | nil)))) | ||
| 622 | |||
| 623 | |||
| 624 | (defun tab-bar-list-next-line (&optional arg) | ||
| 625 | (interactive) | ||
| 626 | (forward-line arg) | ||
| 627 | (beginning-of-line) | ||
| 628 | (move-to-column tab-bar-list-column)) | ||
| 629 | |||
| 630 | (defun tab-bar-list-prev-line (&optional arg) | ||
| 631 | (interactive) | ||
| 632 | (forward-line (- arg)) | ||
| 633 | (beginning-of-line) | ||
| 634 | (move-to-column tab-bar-list-column)) | ||
| 635 | |||
| 636 | (defun tab-bar-list-unmark (&optional backup) | ||
| 637 | "Cancel all requested operations on window configuration on this line and move down. | ||
| 638 | Optional prefix arg means move up." | ||
| 639 | (interactive "P") | ||
| 640 | (beginning-of-line) | ||
| 641 | (move-to-column tab-bar-list-column) | ||
| 642 | (let* ((buffer-read-only nil)) | ||
| 643 | (delete-char 1) | ||
| 644 | (insert " ")) | ||
| 645 | (forward-line (if backup -1 1)) | ||
| 646 | (move-to-column tab-bar-list-column)) | ||
| 647 | |||
| 648 | (defun tab-bar-list-backup-unmark () | ||
| 649 | "Move up and cancel all requested operations on window configuration on line above." | ||
| 650 | (interactive) | ||
| 651 | (forward-line -1) | ||
| 652 | (tab-bar-list-unmark) | ||
| 653 | (forward-line -1) | ||
| 654 | (move-to-column tab-bar-list-column)) | ||
| 655 | |||
| 656 | (defun tab-bar-list-delete (&optional arg) | ||
| 657 | "Mark window configuration on this line to be deleted by \\<tab-bar-list-mode-map>\\[tab-bar-list-execute] command. | ||
| 658 | Prefix arg is how many window configurations to delete. | ||
| 659 | Negative arg means delete backwards." | ||
| 660 | (interactive "p") | ||
| 661 | (let ((buffer-read-only nil)) | ||
| 662 | (if (or (null arg) (= arg 0)) | ||
| 663 | (setq arg 1)) | ||
| 664 | (while (> arg 0) | ||
| 665 | (delete-char 1) | ||
| 666 | (insert ?D) | ||
| 667 | (forward-line 1) | ||
| 668 | (setq arg (1- arg))) | ||
| 669 | (while (< arg 0) | ||
| 670 | (delete-char 1) | ||
| 671 | (insert ?D) | ||
| 672 | (forward-line -1) | ||
| 673 | (setq arg (1+ arg))) | ||
| 674 | (move-to-column tab-bar-list-column))) | ||
| 675 | |||
| 676 | (defun tab-bar-list-delete-backwards (&optional arg) | ||
| 677 | "Mark window configuration on this line to be deleted by \\<tab-bar-list-mode-map>\\[tab-bar-list-execute] command. | ||
| 678 | Then move up one line. Prefix arg means move that many lines." | ||
| 679 | (interactive "p") | ||
| 680 | (tab-bar-list-delete (- (or arg 1)))) | ||
| 681 | |||
| 682 | (defun tab-bar-list-delete-from-list (tab) | ||
| 683 | "Delete the window configuration from both lists." | ||
| 684 | (set-frame-parameter nil 'tabs (delq tab (tab-bar-tabs)))) | ||
| 685 | |||
| 686 | (defun tab-bar-list-execute () | ||
| 687 | "Delete window configurations marked with \\<tab-bar-list-mode-map>\\[tab-bar-list-delete] commands." | ||
| 688 | (interactive) | ||
| 689 | (save-excursion | ||
| 690 | (goto-char (point-min)) | ||
| 691 | (let ((buffer-read-only nil)) | ||
| 692 | (while (re-search-forward | ||
| 693 | (format "^%sD" (make-string tab-bar-list-column ?\040)) | ||
| 694 | nil t) | ||
| 695 | (forward-char -1) | ||
| 696 | (let ((tab (tab-bar-list-current-tab nil))) | ||
| 697 | (when tab | ||
| 698 | (tab-bar-list-delete-from-list tab) | ||
| 699 | (beginning-of-line) | ||
| 700 | (delete-region (point) (progn (forward-line 1) (point)))))))) | ||
| 701 | (beginning-of-line) | ||
| 702 | (move-to-column tab-bar-list-column) | ||
| 703 | (when tab-bar-mode | ||
| 704 | (force-mode-line-update))) | ||
| 705 | |||
| 706 | (defun tab-bar-list-select () | ||
| 707 | "Select this line's window configuration. | ||
| 708 | This command deletes and replaces all the previously existing windows | ||
| 709 | in the selected frame." | ||
| 710 | (interactive) | ||
| 711 | (let* ((select-tab (tab-bar-list-current-tab t))) | ||
| 712 | (kill-buffer (current-buffer)) | ||
| 713 | ;; Delete the current window configuration | ||
| 714 | (tab-bar-close-current-tab nil select-tab) | ||
| 715 | ;; (tab-bar-select-tab select-tab) | ||
| 716 | )) | ||
| 717 | |||
| 718 | (defun tab-bar-list-mouse-select (event) | ||
| 719 | "Select the window configuration whose line you click on." | ||
| 720 | (interactive "e") | ||
| 721 | (set-buffer (window-buffer (posn-window (event-end event)))) | ||
| 722 | (goto-char (posn-point (event-end event))) | ||
| 723 | (tab-bar-list-select)) | ||
| 724 | |||
| 725 | |||
| 726 | (defvar ctl-x-6-map (make-sparse-keymap) | ||
| 727 | "Keymap for tab commands.") | ||
| 728 | (defalias 'ctl-x-6-prefix ctl-x-6-map) | ||
| 729 | (define-key ctl-x-map "6" 'ctl-x-6-prefix) | ||
| 730 | |||
| 731 | (defun switch-to-buffer-other-tab (buffer-or-name &optional norecord) | ||
| 732 | "Switch to buffer BUFFER-OR-NAME in another tab. | ||
| 733 | Like \\[switch-to-buffer-other-frame] (which see), but creates a new tab." | ||
| 734 | (interactive | ||
| 735 | (list (read-buffer-to-switch "Switch to buffer in other tab: "))) | ||
| 736 | (tab-bar-new-tab) | ||
| 737 | (delete-other-windows) | ||
| 738 | (switch-to-buffer buffer-or-name norecord)) | ||
| 739 | |||
| 740 | (defun find-file-other-tab (filename &optional wildcards) | ||
| 741 | "Edit file FILENAME, in another tab. | ||
| 742 | Like \\[find-file-other-frame] (which see), but creates a new tab." | ||
| 743 | (interactive | ||
| 744 | (find-file-read-args "Find file in other tab: " | ||
| 745 | (confirm-nonexistent-file-or-buffer))) | ||
| 746 | (let ((value (find-file-noselect filename nil nil wildcards))) | ||
| 747 | (if (listp value) | ||
| 748 | (progn | ||
| 749 | (setq value (nreverse value)) | ||
| 750 | (switch-to-buffer-other-tab (car value)) | ||
| 751 | (mapc 'switch-to-buffer (cdr value)) | ||
| 752 | value) | ||
| 753 | (switch-to-buffer-other-tab value)))) | ||
| 754 | |||
| 755 | (define-key ctl-x-6-map "2" 'tab-bar-new-tab) | ||
| 756 | (define-key ctl-x-6-map "0" 'tab-bar-close-current-tab) | ||
| 757 | (define-key ctl-x-6-map "b" 'switch-to-buffer-other-tab) | ||
| 758 | (define-key ctl-x-6-map "f" 'find-file-other-tab) | ||
| 759 | (define-key ctl-x-6-map "\C-f" 'find-file-other-tab) | ||
| 760 | |||
| 761 | |||
| 762 | (provide 'tab-bar) | ||
| 763 | |||
| 764 | ;;; tab-bar.el ends here | ||
diff --git a/lisp/tab-line.el b/lisp/tab-line.el new file mode 100644 index 00000000000..62e06a797d5 --- /dev/null +++ b/lisp/tab-line.el | |||
| @@ -0,0 +1,362 @@ | |||
| 1 | ;;; tab-line.el --- window-local tabs with window buffers -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2019 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Juri Linkov <juri@linkov.net> | ||
| 6 | ;; Keywords: windows tabs | ||
| 7 | ;; Maintainer: emacs-devel@gnu.org | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; To enable this mode, run `M-x global-tab-line-mode'. | ||
| 27 | |||
| 28 | ;;; Code: | ||
| 29 | |||
| 30 | (require 'seq) ; tab-line.el is not pre-loaded so it's safe to use it here | ||
| 31 | |||
| 32 | |||
| 33 | (defgroup tab-line nil | ||
| 34 | "Window-local tabs." | ||
| 35 | :group 'convenience | ||
| 36 | :version "27.1") | ||
| 37 | |||
| 38 | (defgroup tab-line-faces nil | ||
| 39 | "Faces used in the tab line." | ||
| 40 | :group 'tab-line | ||
| 41 | :group 'faces | ||
| 42 | :version "27.1") | ||
| 43 | |||
| 44 | (defface tab-line | ||
| 45 | '((((type x w32 ns) (class color)) | ||
| 46 | :background "grey85" | ||
| 47 | :foreground "black") | ||
| 48 | (((type x) (class mono)) | ||
| 49 | :background "grey") | ||
| 50 | (t | ||
| 51 | :inverse-video t)) | ||
| 52 | "Tab line face." | ||
| 53 | :version "27.1" | ||
| 54 | :group 'tab-line-faces) | ||
| 55 | |||
| 56 | (defface tab-line-tab | ||
| 57 | '((((class color) (min-colors 88)) | ||
| 58 | :box (:line-width 1 :style released-button) | ||
| 59 | :background "grey85") | ||
| 60 | (t | ||
| 61 | :inverse-video nil)) | ||
| 62 | "Tab line face for selected tab." | ||
| 63 | :version "27.1" | ||
| 64 | :group 'tab-line-faces) | ||
| 65 | |||
| 66 | (defface tab-line-tab-inactive | ||
| 67 | '((default | ||
| 68 | :inherit tab-line-tab) | ||
| 69 | (((class color) (min-colors 88)) | ||
| 70 | :background "grey75") | ||
| 71 | (t | ||
| 72 | :inverse-video t)) | ||
| 73 | "Tab line face for non-selected tab." | ||
| 74 | :version "27.1" | ||
| 75 | :group 'tab-line-faces) | ||
| 76 | |||
| 77 | (defface tab-line-highlight | ||
| 78 | '((default :inherit tab-line-tab)) | ||
| 79 | "Tab line face for highlighting." | ||
| 80 | :version "27.1" | ||
| 81 | :group 'tab-line-faces) | ||
| 82 | |||
| 83 | (defface tab-line-close-highlight | ||
| 84 | '((t :foreground "red")) | ||
| 85 | "Tab line face for highlighting of the close button." | ||
| 86 | :version "27.1" | ||
| 87 | :group 'tab-line-faces) | ||
| 88 | |||
| 89 | |||
| 90 | (defvar tab-line-tab-map | ||
| 91 | (let ((map (make-sparse-keymap))) | ||
| 92 | (define-key map [tab-line mouse-1] 'tab-line-select-tab) | ||
| 93 | (define-key map [tab-line mouse-2] 'tab-line-close-tab) | ||
| 94 | (define-key map [tab-line mouse-4] 'tab-line-switch-to-prev-tab) | ||
| 95 | (define-key map [tab-line mouse-5] 'tab-line-switch-to-next-tab) | ||
| 96 | (define-key map "\C-m" 'tab-line-select-tab) | ||
| 97 | map) | ||
| 98 | "Local keymap for `tab-line-mode' window tabs.") | ||
| 99 | |||
| 100 | (defvar tab-line-add-map | ||
| 101 | (let ((map (make-sparse-keymap))) | ||
| 102 | (define-key map [tab-line mouse-1] 'tab-line-new-tab) | ||
| 103 | (define-key map [tab-line mouse-2] 'tab-line-new-tab) | ||
| 104 | (define-key map "\C-m" 'tab-line-new-tab) | ||
| 105 | map) | ||
| 106 | "Local keymap to add `tab-line-mode' window tabs.") | ||
| 107 | |||
| 108 | (defvar tab-line-tab-close-map | ||
| 109 | (let ((map (make-sparse-keymap))) | ||
| 110 | (define-key map [tab-line mouse-1] 'tab-line-close-tab) | ||
| 111 | (define-key map [tab-line mouse-2] 'tab-line-close-tab) | ||
| 112 | map) | ||
| 113 | "Local keymap to close `tab-line-mode' window tabs.") | ||
| 114 | |||
| 115 | |||
| 116 | (defcustom tab-line-new-tab-choice t | ||
| 117 | "Defines what to show in a new tab. | ||
| 118 | If t, display a selection menu with all available buffers. | ||
| 119 | If the value is a function, call it with no arguments. | ||
| 120 | If nil, don't show the new tab button." | ||
| 121 | :type '(choice (const :tag "Buffer menu" t) | ||
| 122 | (function :tag "Function") | ||
| 123 | (const :tag "No button" nil)) | ||
| 124 | :group 'tab-line | ||
| 125 | :version "27.1") | ||
| 126 | |||
| 127 | (defvar tab-line-new-button | ||
| 128 | (propertize " + " | ||
| 129 | 'display `(image :type xpm | ||
| 130 | :file ,(expand-file-name | ||
| 131 | "images/tabs/new.xpm" | ||
| 132 | data-directory) | ||
| 133 | :margin (2 . 0) | ||
| 134 | :ascent center) | ||
| 135 | 'keymap tab-line-add-map | ||
| 136 | 'mouse-face 'tab-line-highlight | ||
| 137 | 'help-echo "Click to add tab") | ||
| 138 | "Button for creating a new tab.") | ||
| 139 | |||
| 140 | (defcustom tab-line-close-button-show t | ||
| 141 | "Defines where to show the close tab button. | ||
| 142 | If t, show the close tab button on all tabs. | ||
| 143 | If `selected', show it only on the selected tab. | ||
| 144 | If `non-selected', show it only on non-selected tab. | ||
| 145 | If nil, don't show it at all." | ||
| 146 | :type '(choice (const :tag "On all tabs" t) | ||
| 147 | (const :tag "On selected tab" selected) | ||
| 148 | (const :tag "On non-selected tabs" non-selected) | ||
| 149 | (const :tag "None" nil)) | ||
| 150 | :set (lambda (sym val) | ||
| 151 | (set sym val) | ||
| 152 | (force-mode-line-update)) | ||
| 153 | :group 'tab-line | ||
| 154 | :version "27.1") | ||
| 155 | |||
| 156 | (defvar tab-line-close-button | ||
| 157 | (propertize " x" | ||
| 158 | 'display `(image :type xpm | ||
| 159 | :file ,(expand-file-name | ||
| 160 | "images/tabs/close.xpm" | ||
| 161 | data-directory) | ||
| 162 | :margin (2 . 0) | ||
| 163 | :ascent center) | ||
| 164 | 'keymap tab-line-tab-close-map | ||
| 165 | 'mouse-face 'tab-line-close-highlight | ||
| 166 | 'help-echo "Click to close tab") | ||
| 167 | "Button for closing the clicked tab.") | ||
| 168 | |||
| 169 | (defvar tab-line-separator nil) | ||
| 170 | |||
| 171 | (defvar tab-line-tab-name-ellipsis | ||
| 172 | (if (char-displayable-p ?…) "…" "...")) | ||
| 173 | |||
| 174 | |||
| 175 | (defvar tab-line-tab-name-function #'tab-line-tab-name | ||
| 176 | "Function to get a tab name. | ||
| 177 | Function gets two arguments: tab to get name for and a list of tabs | ||
| 178 | to display. By default, use function `tab-line-tab-name'.") | ||
| 179 | |||
| 180 | (defun tab-line-tab-name (buffer &optional buffers) | ||
| 181 | "Generate tab name from BUFFER. | ||
| 182 | Reduce tab width proportionally to space taken by other tabs. | ||
| 183 | This function can be overridden by changing the default value of the | ||
| 184 | variable `tab-line-tab-name-function'." | ||
| 185 | (let ((tab-name (buffer-name buffer)) | ||
| 186 | (limit (when buffers | ||
| 187 | (max 1 (- (/ (window-width) (length buffers)) 3))))) | ||
| 188 | (if (or (not limit) (< (length tab-name) limit)) | ||
| 189 | tab-name | ||
| 190 | (propertize (truncate-string-to-width tab-name limit nil nil | ||
| 191 | tab-line-tab-name-ellipsis) | ||
| 192 | 'help-echo tab-name)))) | ||
| 193 | |||
| 194 | (defvar tab-line-tabs-limit 15 | ||
| 195 | "Maximum number of buffer tabs displayed in the tab line.") | ||
| 196 | |||
| 197 | (defvar tab-line-tabs-function #'tab-line-tabs | ||
| 198 | "Function to get a list of tabs to display in the tab line. | ||
| 199 | This function should return either a list of buffers whose names will | ||
| 200 | be displayed, or just a list of strings to display in the tab line. | ||
| 201 | By default, use function `tab-line-tabs'.") | ||
| 202 | |||
| 203 | (defun tab-line-tabs () | ||
| 204 | "Return a list of tabs that should be displayed in the tab line. | ||
| 205 | By default returns a list of window buffers, i.e. buffers previously | ||
| 206 | shown in the same window where the tab line is displayed. | ||
| 207 | This list can be overridden by changing the default value of the | ||
| 208 | variable `tab-line-tabs-function'." | ||
| 209 | (let* ((window (selected-window)) | ||
| 210 | (buffer (window-buffer window)) | ||
| 211 | (next-buffers (seq-remove (lambda (b) (eq b buffer)) | ||
| 212 | (window-next-buffers window))) | ||
| 213 | (next-buffers (seq-filter #'buffer-live-p next-buffers)) | ||
| 214 | (prev-buffers (seq-remove (lambda (b) (eq b buffer)) | ||
| 215 | (mapcar #'car (window-prev-buffers window)))) | ||
| 216 | (prev-buffers (seq-filter #'buffer-live-p prev-buffers)) | ||
| 217 | ;; Remove next-buffers from prev-buffers | ||
| 218 | (prev-buffers (seq-difference prev-buffers next-buffers)) | ||
| 219 | (half-limit (/ tab-line-tabs-limit 2)) | ||
| 220 | (prev-buffers-limit | ||
| 221 | (if (> (length prev-buffers) half-limit) | ||
| 222 | (if (> (length next-buffers) half-limit) | ||
| 223 | half-limit | ||
| 224 | (+ half-limit (- half-limit (length next-buffers)))) | ||
| 225 | (length prev-buffers))) | ||
| 226 | (next-buffers-limit | ||
| 227 | (- tab-line-tabs-limit prev-buffers-limit)) | ||
| 228 | (buffer-tabs | ||
| 229 | (append (reverse (seq-take prev-buffers prev-buffers-limit)) | ||
| 230 | (list buffer) | ||
| 231 | (seq-take next-buffers next-buffers-limit)))) | ||
| 232 | buffer-tabs)) | ||
| 233 | |||
| 234 | (defun tab-line-format () | ||
| 235 | "Template for displaying tab line for selected window." | ||
| 236 | (let* ((window (selected-window)) | ||
| 237 | (selected-buffer (window-buffer window)) | ||
| 238 | (tabs (funcall tab-line-tabs-function)) | ||
| 239 | (separator (or tab-line-separator (if window-system " " "|")))) | ||
| 240 | (append | ||
| 241 | (mapcar | ||
| 242 | (lambda (tab) | ||
| 243 | (concat | ||
| 244 | separator | ||
| 245 | (apply 'propertize (concat (propertize | ||
| 246 | (funcall tab-line-tab-name-function tab tabs) | ||
| 247 | 'keymap tab-line-tab-map) | ||
| 248 | (or (and tab-line-close-button-show | ||
| 249 | (not (eq tab-line-close-button-show | ||
| 250 | (if (eq tab selected-buffer) | ||
| 251 | 'non-selected | ||
| 252 | 'selected))) | ||
| 253 | tab-line-close-button) "")) | ||
| 254 | `( | ||
| 255 | tab ,tab | ||
| 256 | face ,(if (eq tab selected-buffer) | ||
| 257 | 'tab-line-tab | ||
| 258 | 'tab-line-tab-inactive) | ||
| 259 | mouse-face tab-line-highlight)))) | ||
| 260 | tabs) | ||
| 261 | (list (concat separator (when tab-line-new-tab-choice | ||
| 262 | tab-line-new-button)))))) | ||
| 263 | |||
| 264 | |||
| 265 | (defun tab-line-new-tab (&optional e) | ||
| 266 | "Add a new tab to the tab line. | ||
| 267 | Usually is invoked by clicking on the plus-shaped button. | ||
| 268 | But any switching to other buffer also adds a new tab | ||
| 269 | corresponding to the switched buffer." | ||
| 270 | (interactive "e") | ||
| 271 | (if (functionp tab-line-new-tab-choice) | ||
| 272 | (funcall tab-line-new-tab-choice) | ||
| 273 | (if window-system ; (display-popup-menus-p) | ||
| 274 | (mouse-buffer-menu e) ; like (buffer-menu-open) | ||
| 275 | ;; tty menu doesn't support mouse clicks, so use tmm | ||
| 276 | (tmm-prompt (mouse-buffer-menu-keymap))))) | ||
| 277 | |||
| 278 | (defun tab-line-select-tab (&optional e) | ||
| 279 | "Switch to the selected tab. | ||
| 280 | This command maintains the original order of prev/next buffers. | ||
| 281 | So for example, switching to a previous tab is equivalent to | ||
| 282 | using the `previous-buffer' command." | ||
| 283 | (interactive "e") | ||
| 284 | (let* ((posnp (event-start e)) | ||
| 285 | (window (posn-window posnp)) | ||
| 286 | (buffer (get-pos-property 1 'tab (car (posn-string posnp)))) | ||
| 287 | (window-buffer (window-buffer window)) | ||
| 288 | (next-buffers (seq-remove (lambda (b) (eq b window-buffer)) | ||
| 289 | (window-next-buffers window))) | ||
| 290 | (prev-buffers (seq-remove (lambda (b) (eq b window-buffer)) | ||
| 291 | (mapcar #'car (window-prev-buffers window)))) | ||
| 292 | ;; Remove next-buffers from prev-buffers | ||
| 293 | (prev-buffers (seq-difference prev-buffers next-buffers))) | ||
| 294 | (cond | ||
| 295 | ((memq buffer next-buffers) | ||
| 296 | (dotimes (_ (1+ (seq-position next-buffers buffer))) | ||
| 297 | (switch-to-next-buffer window))) | ||
| 298 | ((memq buffer prev-buffers) | ||
| 299 | (dotimes (_ (1+ (seq-position prev-buffers buffer))) | ||
| 300 | (switch-to-prev-buffer window))) | ||
| 301 | (t | ||
| 302 | (with-selected-window window | ||
| 303 | (switch-to-buffer buffer)))))) | ||
| 304 | |||
| 305 | (defun tab-line-switch-to-prev-tab (&optional e) | ||
| 306 | "Switch to the previous tab. | ||
| 307 | Its effect is the same as using the `previous-buffer' command | ||
| 308 | (\\[previous-buffer])." | ||
| 309 | (interactive "e") | ||
| 310 | (switch-to-prev-buffer (posn-window (event-start e)))) | ||
| 311 | |||
| 312 | (defun tab-line-switch-to-next-tab (&optional e) | ||
| 313 | "Switch to the next tab. | ||
| 314 | Its effect is the same as using the `next-buffer' command | ||
| 315 | (\\[next-buffer])." | ||
| 316 | (interactive "e") | ||
| 317 | (switch-to-next-buffer (posn-window (event-start e)))) | ||
| 318 | |||
| 319 | (defcustom tab-line-close-tab-action 'bury-buffer | ||
| 320 | "Defines what to do on closing the tab. | ||
| 321 | If `bury-buffer', put the tab's buffer at the end of the list of all | ||
| 322 | buffers that effectively hides the buffer's tab from the tab line. | ||
| 323 | If `kill-buffer', kills the tab's buffer." | ||
| 324 | :type '(choice (const :tag "Bury buffer" bury-buffer) | ||
| 325 | (const :tag "Kill buffer" kill-buffer)) | ||
| 326 | :group 'tab-line | ||
| 327 | :version "27.1") | ||
| 328 | |||
| 329 | (defun tab-line-close-tab (&optional e) | ||
| 330 | "Close the selected tab. | ||
| 331 | Usually is invoked by clicking on the close button on the right side | ||
| 332 | of the tab. This command buries the buffer, so it goes out of sight | ||
| 333 | from the tab line." | ||
| 334 | (interactive "e") | ||
| 335 | (let* ((posnp (event-start e)) | ||
| 336 | (window (posn-window posnp)) | ||
| 337 | (buffer (get-pos-property 1 'tab (car (posn-string posnp))))) | ||
| 338 | (with-selected-window window | ||
| 339 | (cond | ||
| 340 | ((eq tab-line-close-tab-action 'kill-buffer) | ||
| 341 | (kill-buffer buffer)) | ||
| 342 | ((eq tab-line-close-tab-action 'bury-buffer) | ||
| 343 | (if (eq buffer (current-buffer)) | ||
| 344 | (bury-buffer) | ||
| 345 | (set-window-prev-buffers nil (assq-delete-all buffer (window-prev-buffers))) | ||
| 346 | (set-window-next-buffers nil (delq buffer (window-next-buffers)))))) | ||
| 347 | (force-mode-line-update)))) | ||
| 348 | |||
| 349 | |||
| 350 | ;;;###autoload | ||
| 351 | (define-minor-mode global-tab-line-mode | ||
| 352 | "Display window-local tab line." | ||
| 353 | :group 'tab-line | ||
| 354 | :type 'boolean | ||
| 355 | :global t | ||
| 356 | :init-value nil | ||
| 357 | (setq-default tab-line-format (when global-tab-line-mode | ||
| 358 | '(:eval (tab-line-format))))) | ||
| 359 | |||
| 360 | |||
| 361 | (provide 'tab-line) | ||
| 362 | ;;; tab-line.el ends here | ||
diff --git a/lisp/window.el b/lisp/window.el index 620eacdd290..d93ec0add67 100644 --- a/lisp/window.el +++ b/lisp/window.el | |||
| @@ -1419,7 +1419,10 @@ dumping to it." | |||
| 1419 | (format "frame text pixel: %s x %s cols/lines: %s x %s\n" | 1419 | (format "frame text pixel: %s x %s cols/lines: %s x %s\n" |
| 1420 | (frame-text-width frame) (frame-text-height frame) | 1420 | (frame-text-width frame) (frame-text-height frame) |
| 1421 | (frame-text-cols frame) (frame-text-lines frame)) | 1421 | (frame-text-cols frame) (frame-text-lines frame)) |
| 1422 | (format "tool: %s scroll: %s/%s fringe: %s border: %s right: %s bottom: %s\n\n" | 1422 | (format "tab: %s tool: %s scroll: %s/%s fringe: %s border: %s right: %s bottom: %s\n\n" |
| 1423 | (if (fboundp 'tab-bar-height) | ||
| 1424 | (tab-bar-height frame t) | ||
| 1425 | "0") | ||
| 1423 | (if (fboundp 'tool-bar-height) | 1426 | (if (fboundp 'tool-bar-height) |
| 1424 | (tool-bar-height frame t) | 1427 | (tool-bar-height frame t) |
| 1425 | "0") | 1428 | "0") |
diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el index 9e8a32a28ff..308f602b6d0 100644 --- a/lisp/xt-mouse.el +++ b/lisp/xt-mouse.el | |||
| @@ -253,7 +253,13 @@ which is the \"1006\" extension implemented in Xterm >= 277." | |||
| 253 | (top (nth 1 ltrb)) | 253 | (top (nth 1 ltrb)) |
| 254 | (posn (if w | 254 | (posn (if w |
| 255 | (posn-at-x-y (- x left) (- y top) w t) | 255 | (posn-at-x-y (- x left) (- y top) w t) |
| 256 | (append (list nil 'menu-bar) | 256 | (append (list nil (if (and tab-bar-mode |
| 257 | (or (not menu-bar-mode) | ||
| 258 | ;; The tab-bar is on the | ||
| 259 | ;; second row below menu-bar | ||
| 260 | (eq y 1))) | ||
| 261 | 'tab-bar | ||
| 262 | 'menu-bar)) | ||
| 257 | (nthcdr 2 (posn-at-x-y x y))))) | 263 | (nthcdr 2 (posn-at-x-y x y))))) |
| 258 | (event (list type posn))) | 264 | (event (list type posn))) |
| 259 | (setcar (nthcdr 3 posn) timestamp) | 265 | (setcar (nthcdr 3 posn) timestamp) |