summaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp')
-rw-r--r--lisp/cus-start.el5
-rw-r--r--lisp/frame.el42
-rw-r--r--lisp/loadup.el1
-rw-r--r--lisp/menu-bar.el10
-rw-r--r--lisp/mouse.el1
-rw-r--r--lisp/startup.el3
-rw-r--r--lisp/subr.el6
-rw-r--r--lisp/tab-bar.el764
-rw-r--r--lisp/tab-line.el362
-rw-r--r--lisp/window.el5
-rw-r--r--lisp/xt-mouse.el8
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
;; FIXME?
;; :initialize custom-initialize-default
:set custom-set-minor-mode)
+ (tab-bar-mode (frames mouse) boolean nil
+ ;; :initialize custom-initialize-default
+ :set custom-set-minor-mode)
(tool-bar-mode (frames mouse) boolean nil
;; :initialize custom-initialize-default
:set custom-set-minor-mode)
@@ -726,6 +729,8 @@ since it could result in memory overflow and make Emacs crash."
;; the condition for loadup.el to preload tool-bar.el.
((string-match "tool-bar-" (symbol-name symbol))
(fboundp 'x-create-frame))
+ ((string-match "tab-bar-" (symbol-name symbol))
+ (fboundp 'x-create-frame))
((equal "vertical-centering-font-regexp"
(symbol-name symbol))
;; 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)."
;; If the initial frame is still around, apply initial-frame-alist
;; and default-frame-alist to it.
(when (frame-live-p frame-initial-frame)
+ ;; When tab-bar has been switched off, correct the frame size
+ ;; by the lines added in x-create-frame for the tab-bar and
+ ;; switch `tab-bar-mode' off.
+ (when (display-graphic-p)
+ (let* ((init-lines
+ (assq 'tab-bar-lines initial-frame-alist))
+ (other-lines
+ (or (assq 'tab-bar-lines window-system-frame-alist)
+ (assq 'tab-bar-lines default-frame-alist)))
+ (lines (or init-lines other-lines))
+ (height (tab-bar-height frame-initial-frame t)))
+ ;; Adjust frame top if either zero (nil) tab bar lines have
+ ;; been requested in the most relevant of the frame's alists
+ ;; or tab bar mode has been explicitly turned off in the
+ ;; user's init file.
+ (when (and (> height 0)
+ (or (and lines
+ (or (null (cdr lines))
+ (eq 0 (cdr lines))))
+ (not tab-bar-mode)))
+ (let* ((initial-top
+ (cdr (assq 'top frame-initial-geometry-arguments)))
+ (top (frame-parameter frame-initial-frame 'top)))
+ (when (and (consp initial-top) (eq '- (car initial-top)))
+ (let ((adjusted-top
+ (cond
+ ((and (consp top) (eq '+ (car top)))
+ (list '+ (+ (cadr top) height)))
+ ((and (consp top) (eq '- (car top)))
+ (list '- (- (cadr top) height)))
+ (t (+ top height)))))
+ (modify-frame-parameters
+ frame-initial-frame `((top . ,adjusted-top))))))
+ ;; Reset `tab-bar-mode' when zero tab bar lines have been
+ ;; requested for the window-system or default frame alists.
+ (when (and tab-bar-mode
+ (and other-lines
+ (or (null (cdr other-lines))
+ (eq 0 (cdr other-lines)))))
+ (tab-bar-mode -1)))))
+
;; When tool-bar has been switched off, correct the frame size
;; by the lines added in x-create-frame for the tool-bar and
;; switch `tool-bar-mode' off.
@@ -1593,6 +1634,7 @@ and width values are in pixels.
'(tool-bar-external . nil)
'(tool-bar-position . nil)
'(tool-bar-size 0 . 0)
+ '(tab-bar-size 0 . 0)
(cons 'internal-border-width
(frame-parameter frame 'internal-border-width)))))))
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 @@
(load "rfn-eshadow")
(load "menu-bar")
+(load "tab-bar")
(load "emacs-lisp/lisp")
(load "textmodes/page")
(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."
;; side-effect that turning them off via X
;; resources acts like having customized them, but
;; that seems harmless.
- menu-bar-mode tool-bar-mode))
+ menu-bar-mode tab-bar-mode tool-bar-mode))
;; FIXME ? It's a little annoying that running this command
;; always loads cua-base, paren, time, and battery, even if they
;; have not been customized in any way. (Due to custom-load-symbol.)
@@ -1242,6 +1242,14 @@ mail status in mode line"))
(frame-parameter (menu-bar-frame-for-menubar)
'menu-bar-lines)))))
+ (bindings--define-key menu [showhide-tab-bar]
+ '(menu-item "Tab Bar" toggle-tab-bar-mode-from-frame
+ :help "Turn tab bar on/off"
+ :button
+ (:toggle . (menu-bar-positive-p
+ (frame-parameter (menu-bar-frame-for-menubar)
+ 'tab-bar-lines)))))
+
(if (and (boundp 'menu-bar-showhide-tool-bar-menu)
(keymapp menu-bar-showhide-tool-bar-menu))
(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."
;; versions.
(global-set-key [header-line down-mouse-1] 'mouse-drag-header-line)
(global-set-key [header-line mouse-1] 'mouse-select-window)
+(global-set-key [tab-line mouse-1] 'mouse-select-window)
;; (global-set-key [mode-line drag-mouse-1] 'mouse-select-window)
(global-set-key [mode-line down-mouse-1] 'mouse-drag-mode-line)
(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'."
("--background-color" . "-bg")
("--color" . "-color")))
+;; FIXME: this var unused?
(defconst tool-bar-images-pixel-height 24
"Height in pixels of images in the tool-bar.")
@@ -1300,6 +1301,7 @@ please check its value")
(unless (daemonp)
(if (or noninteractive emacs-basic-display)
(setq menu-bar-mode nil
+ tab-bar-mode nil
tool-bar-mode nil
no-blinking-cursor t))
(frame-initialize))
@@ -1515,6 +1517,7 @@ This can set the values of `menu-bar-mode', `tool-bar-mode', and
settings will be marked as \"CHANGED outside of Customize\"."
(let ((no-vals '("no" "off" "false" "0"))
(settings '(("menuBar" "MenuBar" menu-bar-mode nil)
+ ("tabBar" "TabBar" tab-bar-mode nil)
("toolBar" "ToolBar" tool-bar-mode nil)
("scrollBar" "ScrollBar" scroll-bar-mode nil)
("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'."
(progn
(use-global-map
(let ((map (make-sparse-keymap)))
- ;; Don't hide the menu-bar and tool-bar entries.
+ ;; Don't hide the menu-bar, tab-bar and tool-bar entries.
(define-key map [menu-bar] (lookup-key global-map [menu-bar]))
+ (define-key map [tab-bar]
+ ;; This hack avoids evaluating the :filter (Bug#9922).
+ (or (cdr (assq 'tab-bar global-map))
+ (lookup-key global-map [tab-bar])))
(define-key map [tool-bar]
;; This hack avoids evaluating the :filter (Bug#9922).
(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 @@
+;;; tab-bar.el --- frame-local tabs with named persistent window configurations -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; Author: Juri Linkov <juri@linkov.net>
+;; 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 <https://www.gnu.org/licenses/>.
+
+;;; 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. Modes
+;; may either bind items under the [tab-bar] prefix key of the local
+;; map to add to the global bar or may set `tab-bar-map'
+;; buffer-locally to override it.
+
+;;; Code:
+
+
+(defgroup tab-bar nil
+ "Frame-local tabs."
+ :group 'convenience
+ :version "27.1")
+
+(defgroup tab-bar-faces nil
+ "Faces used in the tab bar."
+ :group 'tab-bar
+ :group 'faces
+ :version "27.1")
+
+(defface tab-bar
+ '((((type x w32 ns) (class color))
+ :height 1.1
+ :background "grey85"
+ :foreground "black")
+ (((type x) (class mono))
+ :background "grey")
+ (t
+ :inverse-video t))
+ "Tab bar face."
+ :version "27.1"
+ :group 'tab-bar-faces)
+
+(defface tab-bar-tab
+ '((((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)
+
+
+(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
+ (let ((val (if tab-bar-mode 1 0)))
+ (dolist (frame (frame-list))
+ (set-frame-parameter frame 'tab-bar-lines val))
+ ;; If the user has given `default-frame-alist' a `tab-bar-lines'
+ ;; parameter, replace it.
+ (if (assq 'tab-bar-lines default-frame-alist)
+ (setq default-frame-alist
+ (cons (cons 'tab-bar-lines val)
+ (assq-delete-all 'tab-bar-lines
+ default-frame-alist)))))
+ (when tab-bar-mode
+ (global-set-key [(control shift iso-lefttab)] 'tab-bar-switch-to-prev-tab)
+ (global-set-key [(control shift tab)] 'tab-bar-switch-to-prev-tab)
+ (global-set-key [(control tab)] 'tab-bar-switch-to-next-tab)))
+
+(defun tab-bar-handle-mouse (event)
+ "Text-mode emulation of switching tabs on the tab bar.
+This command is used when you click the mouse in the tab bar
+on a console which has no window system but does have a mouse."
+ (interactive "e")
+ (let* ((x-position (car (posn-x-y (event-start event))))
+ (keymap (lookup-key (cons 'keymap (nreverse (current-active-maps))) [tab-bar]))
+ (column 0))
+ (when x-position
+ (unless (catch 'done
+ (map-keymap
+ (lambda (_key binding)
+ (when (eq (car-safe binding) 'menu-item)
+ (when (> (+ column (length (nth 1 binding))) x-position)
+ ;; TODO: handle close
+ (unless (get-text-property (- x-position column) 'close-tab (nth 1 binding))
+ (call-interactively (nth 2 binding)))
+ (throw 'done t))
+ (setq column (+ column (length (nth 1 binding))))))
+ keymap))
+ ;; Clicking anywhere outside existing tabs will add a new tab
+ (tab-bar-new-tab)))))
+
+;; Used in the Show/Hide menu, to have the toggle reflect the current frame.
+(defun toggle-tab-bar-mode-from-frame (&optional arg)
+ "Toggle tab bar on or off, based on the status of 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)))
+
+(defvar tab-bar-map (make-sparse-keymap)
+ "Keymap for the tab bar.
+Define this locally to override the global tab bar.")
+
+(global-set-key [tab-bar]
+ `(menu-item ,(purecopy "tab bar") ignore
+ :filter tab-bar-make-keymap))
+
+(defconst tab-bar-keymap-cache (make-hash-table :weakness t :test 'equal))
+
+(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."
+ (if (= 1 (length tab-bar-map))
+ (tab-bar-make-keymap-1)
+ (let ((key (cons (frame-terminal) tab-bar-map)))
+ (or (gethash key tab-bar-keymap-cache)
+ (puthash key tab-bar-map tab-bar-keymap-cache)))))
+
+
+(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, switch to a buffer if it 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)
+ (directory :tag "Directory" :value "~/")
+ (file :tag "File" :value "~/.emacs")
+ (string :tag "Buffer" "*scratch*")
+ (function :tag "Function")
+ (const :tag "Duplicate tab" nil))
+ :group 'tab-bar
+ :version "27.1")
+
+(defvar tab-bar-new-button
+ (propertize " + "
+ 'display `(image :type xpm
+ :file ,(expand-file-name
+ "images/tabs/new.xpm"
+ data-directory)
+ :margin (2 . 0)
+ :ascent center))
+ "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))
+ :set (lambda (sym val)
+ (set sym val)
+ (force-mode-line-update))
+ :group 'tab-bar
+ :version "27.1")
+
+(defvar tab-bar-close-button
+ (propertize " x"
+ 'display `(image :type xpm
+ :file ,(expand-file-name
+ "images/tabs/close.xpm"
+ data-directory)
+ :margin (2 . 0)
+ :ascent center)
+ 'close-tab t
+ :help "Click to close tab")
+ "Button for closing the clicked tab.")
+
+(defvar tab-bar-separator nil)
+
+
+(defvar tab-bar-tab-name-function #'tab-bar-tab-name
+ "Function to get a tab name.
+Function gets no arguments.
+By default, use function `tab-bar-tab-name'.")
+
+(defun tab-bar-tab-name ()
+ "Generate tab name in the context of the selected frame."
+ (mapconcat #'buffer-name
+ (delete-dups (mapcar #'window-buffer
+ (window-list-1 (frame-first-window)
+ 'nomini)))
+ ", "))
+
+(defvar tab-bar-tabs-function #'tab-bar-tabs
+ "Function to get a list of tabs to display in the tab bar.
+This function 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 ()
+ "Return a list of tabs belonging to the selected frame.
+Ensure the frame parameter `tabs' is pre-populated.
+Return its existing value or a new value."
+ (let ((tabs (frame-parameter nil 'tabs)))
+ (if tabs
+ ;; Update current tab name
+ (let ((name (assq 'name (assq 'current-tab tabs))))
+ (when name (setcdr name (funcall tab-bar-tab-name-function))))
+ ;; Create default tabs
+ (setq tabs `((current-tab (name . ,(funcall tab-bar-tab-name-function)))))
+ (set-frame-parameter nil 'tabs tabs))
+ tabs))
+
+(defun tab-bar-make-keymap-1 ()
+ "Generate an actual keymap from `tab-bar-map', without caching."
+ (let ((separator (or tab-bar-separator (if window-system " " "|")))
+ (i 0))
+ (append
+ '(keymap (mouse-1 . tab-bar-handle-mouse))
+ (mapcan
+ (lambda (tab)
+ (setq i (1+ i))
+ (append
+ `((,(intern (format "sep-%i" i)) menu-item ,separator ignore))
+ (cond
+ ((eq (car tab) 'current-tab)
+ `((current-tab
+ menu-item
+ ,(propertize (concat (cdr (assq 'name tab))
+ (or (and tab-bar-close-button-show
+ (not (eq tab-bar-close-button-show
+ 'non-selected))
+ tab-bar-close-button) ""))
+ 'face 'tab-bar-tab)
+ ignore
+ :help "Current tab")))
+ (t
+ `((,(intern (format "tab-%i" i))
+ menu-item
+ ,(propertize (concat (cdr (assq 'name tab))
+ (or (and tab-bar-close-button-show
+ (not (eq tab-bar-close-button-show
+ 'selected))
+ tab-bar-close-button) ""))
+ 'face 'tab-bar-tab-inactive)
+ ,(or
+ (cdr (assq 'binding tab))
+ (lambda ()
+ (interactive)
+ (tab-bar-select-tab tab)))
+ :help "Click to visit tab"))))
+ `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i)))
+ menu-item ""
+ ,(or
+ (cdr (assq 'close-binding tab))
+ (lambda ()
+ (interactive)
+ (tab-bar-close-tab tab)))))))
+ (funcall tab-bar-tabs-function))
+ (when tab-bar-new-button
+ `((sep-add-tab menu-item ,separator ignore)
+ (add-tab menu-item ,tab-bar-new-button tab-bar-new-tab
+ :help "New tab"))))))
+
+
+(defun tab-bar-read-tab-name (prompt)
+ (let* ((tabs (tab-bar-tabs))
+ (tab-name
+ (completing-read prompt
+ (or (delq nil (mapcar (lambda (tab)
+ (cdr (assq 'name tab)))
+ tabs))
+ '("")))))
+ (catch 'done
+ (dolist (tab tabs)
+ (when (equal (cdr (assq 'name tab)) tab-name)
+ (throw 'done tab))))))
+
+(defun tab-bar-tab-default ()
+ (let ((tab `(tab
+ (name . ,(funcall tab-bar-tab-name-function))
+ (time . ,(time-convert nil 'integer))
+ (wc . ,(current-window-configuration))
+ (ws . ,(window-state-get
+ (frame-root-window (selected-frame)) 'writable)))))
+ tab))
+
+(defun tab-bar-find-prev-tab (&optional tabs)
+ (unless tabs
+ (setq tabs (tab-bar-tabs)))
+ (unless (eq (car (car tabs)) 'current-tab)
+ (while (and tabs (not (eq (car (car (cdr tabs))) 'current-tab)))
+ (setq tabs (cdr tabs)))
+ tabs))
+
+
+(defun tab-bar-select-tab (tab)
+ "Switch to the specified TAB."
+ (interactive (list (tab-bar-read-tab-name "Select tab by name: ")))
+ (when (and tab (not (eq (car tab) 'current-tab)))
+ (let* ((tabs (tab-bar-tabs))
+ (new-tab (tab-bar-tab-default))
+ (wc (cdr (assq 'wc 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.
+ (if (window-configuration-p wc)
+ (set-window-configuration wc)
+ (window-state-put (cdr (assq 'ws tab))
+ (frame-root-window (selected-frame)) 'safe))
+ (while tabs
+ (cond
+ ((eq (car tabs) tab)
+ (setcar tabs `(current-tab (name . ,(funcall tab-bar-tab-name-function)))))
+ ((eq (car (car tabs)) 'current-tab)
+ (setcar tabs new-tab)))
+ (setq tabs (cdr tabs)))
+ (force-mode-line-update))))
+
+(defun tab-bar-switch-to-prev-tab (&optional _arg)
+ "Switch to ARGth previous tab."
+ (interactive "p")
+ (let ((prev-tab (tab-bar-find-prev-tab)))
+ (when prev-tab
+ (tab-bar-select-tab (car prev-tab)))))
+
+(defun tab-bar-switch-to-next-tab (&optional _arg)
+ "Switch to ARGth next tab."
+ (interactive "p")
+ (let* ((tabs (tab-bar-tabs))
+ (prev-tab (tab-bar-find-prev-tab tabs)))
+ (if prev-tab
+ (tab-bar-select-tab (car (cdr (cdr prev-tab))))
+ (tab-bar-select-tab (car (cdr tabs))))))
+
+
+(defcustom tab-bar-new-tab-to 'right
+ "Defines where to create a new tab.
+If `leftmost', create as the first tab.
+If `left', create to the left from the current tab.
+If `right', create to the right from the current tab.
+If `rightmost', create as the last tab."
+ :type '(choice (const :tag "First tab" leftmost)
+ (const :tag "To the left" left)
+ (const :tag "To the right" right)
+ (const :tag "Last tab" rightmost))
+ :group 'tab-bar
+ :version "27.1")
+
+(defun tab-bar-new-tab ()
+ "Clone the current tab to the position specified by `tab-bar-new-tab-to'."
+ (interactive)
+ (unless tab-bar-mode
+ (tab-bar-mode 1))
+ (let* ((tabs (tab-bar-tabs))
+ ;; (i-tab (- (length tabs) (length (memq tab tabs))))
+ (new-tab (tab-bar-tab-default)))
+ (cond
+ ((eq tab-bar-new-tab-to 'leftmost)
+ (setq tabs (cons new-tab tabs)))
+ ((eq tab-bar-new-tab-to 'rightmost)
+ (setq tabs (append tabs (list new-tab))))
+ (t
+ (let ((prev-tab (tab-bar-find-prev-tab tabs)))
+ (cond
+ ((eq tab-bar-new-tab-to 'left)
+ (if prev-tab
+ (setcdr prev-tab (cons new-tab (cdr prev-tab)))
+ (setq tabs (cons new-tab tabs))))
+ ((eq tab-bar-new-tab-to 'right)
+ (if prev-tab
+ (setq prev-tab (cdr prev-tab))
+ (setq prev-tab tabs))
+ (setcdr prev-tab (cons new-tab (cdr prev-tab))))))))
+ (set-frame-parameter nil 'tabs tabs)
+ (tab-bar-select-tab new-tab)
+ (when tab-bar-new-tab-choice
+ (delete-other-windows)
+ (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))))
+ (unless tab-bar-mode
+ (message "Added new tab with the current window configuration"))))
+
+
+(defcustom tab-bar-close-tab-select 'right
+ "Defines what tab to select after closing the specified tab.
+If `left', select the adjacent left tab.
+If `right', select the adjacent right tab."
+ :type '(choice (const :tag "Select left tab" left)
+ (const :tag "Select right tab" right))
+ :group 'tab-bar
+ :version "27.1")
+
+(defun tab-bar-close-current-tab (&optional tab select-tab)
+ "Close the current TAB.
+After closing the current tab switch to the tab
+specified by `tab-bar-close-tab-select', or to `select-tab'
+if its value is provided."
+ (interactive)
+ (let ((tabs (tab-bar-tabs)))
+ (unless tab
+ (let ((prev-tab (tab-bar-find-prev-tab tabs)))
+ (setq tab (if prev-tab
+ (car (cdr prev-tab))
+ (car tabs)))))
+ (if select-tab
+ (setq tabs (delq tab tabs))
+ (let* ((i-tab (- (length tabs) (length (memq tab tabs))))
+ (i-select
+ (cond
+ ((eq tab-bar-close-tab-select 'left)
+ (1- i-tab))
+ ((eq tab-bar-close-tab-select 'right)
+ ;; Do nothing: the next tab will take
+ ;; the index of the closed tab
+ i-tab)
+ (t 0))))
+ (setq tabs (delq tab tabs)
+ i-select (max 0 (min (1- (length tabs)) i-select))
+ select-tab (nth i-select tabs))))
+ (set-frame-parameter nil 'tabs tabs)
+ (tab-bar-select-tab select-tab)))
+
+(defun tab-bar-close-tab (tab)
+ "Close the specified TAB.
+After closing the current tab switch to the tab
+specified by `tab-bar-close-tab-select'."
+ (interactive (list (tab-bar-read-tab-name "Close tab by name: ")))
+ (when tab
+ (if (eq (car tab) 'current-tab)
+ (tab-bar-close-current-tab tab)
+ ;; Close non-current tab, no need to switch to another tab
+ (set-frame-parameter nil 'tabs (delq tab (tab-bar-tabs)))
+ (force-mode-line-update))))
+
+
+;;; Non-graphical access to frame-local tabs (named window configurations)
+
+(defun tab-new ()
+ "Create a new named window configuration without having to click a tab."
+ (interactive)
+ (tab-bar-new-tab)
+ (unless tab-bar-mode
+ (message "Added new tab with the current window configuration")))
+
+(defun tab-close ()
+ "Delete the current window configuration without clicking a close button."
+ (interactive)
+ (tab-bar-close-current-tab)
+ (unless tab-bar-mode
+ (message "Deleted the current tab")))
+
+;; Short aliases
+;; (defalias 'tab-switch 'tab-bar-switch-to-next-tab)
+(defalias 'tab-select 'tab-bar-select-tab)
+(defalias 'tab-previous 'tab-bar-switch-to-prev-tab)
+(defalias 'tab-next 'tab-bar-switch-to-next-tab)
+(defalias 'tab-list 'tab-bar-list)
+
+(defun tab-bar-list ()
+ "Display a list of named window configurations.
+The list is displayed in the buffer `*Tabs*'.
+
+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 for a window configuration you have
+marked for deletion."
+ (interactive)
+ (let ((dir default-directory)
+ (minibuf (minibuffer-selected-window)))
+ (let ((tab-bar-mode t)) ; don't enable tab-bar-mode if it's disabled
+ (tab-bar-new-tab))
+ ;; Handle the case when it's called in the active minibuffer.
+ (when minibuf (select-window (minibuffer-selected-window)))
+ (delete-other-windows)
+ ;; Create a new window to replace the existing one, to not break the
+ ;; window parameters (e.g. prev/next buffers) of the window just saved
+ ;; to the window configuration. So when a saved window is restored,
+ ;; its parameters left intact.
+ (split-window) (delete-window)
+ (let ((switch-to-buffer-preserve-window-point nil))
+ (switch-to-buffer (tab-bar-list-noselect)))
+ (setq default-directory dir))
+ (message "Commands: d, x; RET; q to quit; ? for help."))
+
+(defun tab-bar-list-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-bar-list'."
+ (let* ((tabs (delq nil (mapcar (lambda (tab) ; remove current tab
+ (unless (eq (car tab) 'current-tab)
+ tab))
+ (tab-bar-tabs))))
+ ;; Sort by recency
+ (tabs (sort tabs (lambda (a b) (< (cdr (assq 'time b))
+ (cdr (assq 'time a)))))))
+ (with-current-buffer (get-buffer-create
+ (format " *Tabs*<%s>" (or (frame-parameter nil 'window-id)
+ (frame-parameter nil 'name))))
+ (erase-buffer)
+ (tab-bar-list-mode)
+ (setq buffer-read-only nil)
+ ;; 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-bar-list-column (- (/ (frame-width) 2) 15))
+ (dolist (tab tabs)
+ (insert (propertize
+ (format "%s %s\n"
+ (make-string tab-bar-list-column ?\040)
+ (propertize
+ (cdr (assq '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-bar-list-next-line))
+ (move-to-column tab-bar-list-column)
+ (set-buffer-modified-p nil)
+ (current-buffer))))
+
+(defvar tab-bar-list-column 3)
+(make-variable-buffer-local 'tab-bar-list-column)
+
+(defvar tab-bar-list-mode-map
+ (let ((map (make-keymap)))
+ (suppress-keymap map t)
+ (define-key map "q" 'quit-window)
+ (define-key map "\C-m" 'tab-bar-list-select)
+ (define-key map "d" 'tab-bar-list-delete)
+ (define-key map "k" 'tab-bar-list-delete)
+ (define-key map "\C-d" 'tab-bar-list-delete-backwards)
+ (define-key map "\C-k" 'tab-bar-list-delete)
+ (define-key map "x" 'tab-bar-list-execute)
+ (define-key map " " 'tab-bar-list-next-line)
+ (define-key map "n" 'tab-bar-list-next-line)
+ (define-key map "p" 'tab-bar-list-prev-line)
+ (define-key map "\177" 'tab-bar-list-backup-unmark)
+ (define-key map "?" 'describe-mode)
+ (define-key map "u" 'tab-bar-list-unmark)
+ (define-key map [mouse-2] 'tab-bar-list-mouse-select)
+ (define-key map [follow-link] 'mouse-face)
+ map)
+ "Local keymap for `tab-bar-list-mode' buffers.")
+
+(define-derived-mode tab-bar-list-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-bar-list-mode-map>
+\\[tab-bar-list-mouse-select] -- select window configuration you click on.
+\\[tab-bar-list-select] -- select current line's window configuration.
+\\[tab-bar-list-delete] -- mark that window configuration to be deleted, and move down.
+\\[tab-bar-list-delete-backwards] -- mark that window configuration to be deleted, and move up.
+\\[tab-bar-list-execute] -- delete marked window configurations.
+\\[tab-bar-list-unmark] -- remove all kinds of marks from current line.
+ With prefix argument, also move up one line.
+\\[tab-bar-list-backup-unmark] -- back up a line and remove marks."
+ (setq truncate-lines t)
+ (setq buffer-read-only t))
+
+(defun tab-bar-list-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-bar-list-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-bar-list-next-line (&optional arg)
+ (interactive)
+ (forward-line arg)
+ (beginning-of-line)
+ (move-to-column tab-bar-list-column))
+
+(defun tab-bar-list-prev-line (&optional arg)
+ (interactive)
+ (forward-line (- arg))
+ (beginning-of-line)
+ (move-to-column tab-bar-list-column))
+
+(defun tab-bar-list-unmark (&optional backup)
+ "Cancel all requested operations on window configuration on this line and move down.
+Optional prefix arg means move up."
+ (interactive "P")
+ (beginning-of-line)
+ (move-to-column tab-bar-list-column)
+ (let* ((buffer-read-only nil))
+ (delete-char 1)
+ (insert " "))
+ (forward-line (if backup -1 1))
+ (move-to-column tab-bar-list-column))
+
+(defun tab-bar-list-backup-unmark ()
+ "Move up and cancel all requested operations on window configuration on line above."
+ (interactive)
+ (forward-line -1)
+ (tab-bar-list-unmark)
+ (forward-line -1)
+ (move-to-column tab-bar-list-column))
+
+(defun tab-bar-list-delete (&optional arg)
+ "Mark window configuration on this line to be deleted by \\<tab-bar-list-mode-map>\\[tab-bar-list-execute] command.
+Prefix arg is 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-bar-list-column)))
+
+(defun tab-bar-list-delete-backwards (&optional arg)
+ "Mark window configuration on this line to be deleted by \\<tab-bar-list-mode-map>\\[tab-bar-list-execute] command.
+Then move up one line. Prefix arg means move that many lines."
+ (interactive "p")
+ (tab-bar-list-delete (- (or arg 1))))
+
+(defun tab-bar-list-delete-from-list (tab)
+ "Delete the window configuration from both lists."
+ (set-frame-parameter nil 'tabs (delq tab (tab-bar-tabs))))
+
+(defun tab-bar-list-execute ()
+ "Delete window configurations marked with \\<tab-bar-list-mode-map>\\[tab-bar-list-delete] commands."
+ (interactive)
+ (save-excursion
+ (goto-char (point-min))
+ (let ((buffer-read-only nil))
+ (while (re-search-forward
+ (format "^%sD" (make-string tab-bar-list-column ?\040))
+ nil t)
+ (forward-char -1)
+ (let ((tab (tab-bar-list-current-tab nil)))
+ (when tab
+ (tab-bar-list-delete-from-list tab)
+ (beginning-of-line)
+ (delete-region (point) (progn (forward-line 1) (point))))))))
+ (beginning-of-line)
+ (move-to-column tab-bar-list-column)
+ (when tab-bar-mode
+ (force-mode-line-update)))
+
+(defun tab-bar-list-select ()
+ "Select this line's window configuration.
+This command deletes and replaces all the previously existing windows
+in the selected frame."
+ (interactive)
+ (let* ((select-tab (tab-bar-list-current-tab t)))
+ (kill-buffer (current-buffer))
+ ;; Delete the current window configuration
+ (tab-bar-close-current-tab nil select-tab)
+ ;; (tab-bar-select-tab select-tab)
+ ))
+
+(defun tab-bar-list-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-bar-list-select))
+
+
+(defvar ctl-x-6-map (make-sparse-keymap)
+ "Keymap for tab commands.")
+(defalias 'ctl-x-6-prefix ctl-x-6-map)
+(define-key ctl-x-map "6" 'ctl-x-6-prefix)
+
+(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."
+ (interactive
+ (list (read-buffer-to-switch "Switch to buffer in other tab: ")))
+ (tab-bar-new-tab)
+ (delete-other-windows)
+ (switch-to-buffer buffer-or-name norecord))
+
+(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."
+ (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))))
+
+(define-key ctl-x-6-map "2" 'tab-bar-new-tab)
+(define-key ctl-x-6-map "0" 'tab-bar-close-current-tab)
+(define-key ctl-x-6-map "b" 'switch-to-buffer-other-tab)
+(define-key ctl-x-6-map "f" 'find-file-other-tab)
+(define-key ctl-x-6-map "\C-f" 'find-file-other-tab)
+
+
+(provide 'tab-bar)
+
+;;; 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 @@
+;;; tab-line.el --- window-local tabs with window buffers -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; Author: Juri Linkov <juri@linkov.net>
+;; Keywords: windows 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; To enable this mode, run `M-x global-tab-line-mode'.
+
+;;; Code:
+
+(require 'seq) ; tab-line.el is not pre-loaded so it's safe to use it here
+
+
+(defgroup tab-line nil
+ "Window-local tabs."
+ :group 'convenience
+ :version "27.1")
+
+(defgroup tab-line-faces nil
+ "Faces used in the tab line."
+ :group 'tab-line
+ :group 'faces
+ :version "27.1")
+
+(defface tab-line
+ '((((type x w32 ns) (class color))
+ :background "grey85"
+ :foreground "black")
+ (((type x) (class mono))
+ :background "grey")
+ (t
+ :inverse-video t))
+ "Tab line face."
+ :version "27.1"
+ :group 'tab-line-faces)
+
+(defface tab-line-tab
+ '((((class color) (min-colors 88))
+ :box (:line-width 1 :style released-button)
+ :background "grey85")
+ (t
+ :inverse-video nil))
+ "Tab line face for selected tab."
+ :version "27.1"
+ :group 'tab-line-faces)
+
+(defface tab-line-tab-inactive
+ '((default
+ :inherit tab-line-tab)
+ (((class color) (min-colors 88))
+ :background "grey75")
+ (t
+ :inverse-video t))
+ "Tab line face for non-selected tab."
+ :version "27.1"
+ :group 'tab-line-faces)
+
+(defface tab-line-highlight
+ '((default :inherit tab-line-tab))
+ "Tab line face for highlighting."
+ :version "27.1"
+ :group 'tab-line-faces)
+
+(defface tab-line-close-highlight
+ '((t :foreground "red"))
+ "Tab line face for highlighting of the close button."
+ :version "27.1"
+ :group 'tab-line-faces)
+
+
+(defvar tab-line-tab-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [tab-line mouse-1] 'tab-line-select-tab)
+ (define-key map [tab-line mouse-2] 'tab-line-close-tab)
+ (define-key map [tab-line mouse-4] 'tab-line-switch-to-prev-tab)
+ (define-key map [tab-line mouse-5] 'tab-line-switch-to-next-tab)
+ (define-key map "\C-m" 'tab-line-select-tab)
+ map)
+ "Local keymap for `tab-line-mode' window tabs.")
+
+(defvar tab-line-add-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [tab-line mouse-1] 'tab-line-new-tab)
+ (define-key map [tab-line mouse-2] 'tab-line-new-tab)
+ (define-key map "\C-m" 'tab-line-new-tab)
+ map)
+ "Local keymap to add `tab-line-mode' window tabs.")
+
+(defvar tab-line-tab-close-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [tab-line mouse-1] 'tab-line-close-tab)
+ (define-key map [tab-line mouse-2] 'tab-line-close-tab)
+ map)
+ "Local keymap to close `tab-line-mode' window tabs.")
+
+
+(defcustom tab-line-new-tab-choice t
+ "Defines what to show in a new tab.
+If t, display a selection menu with all available buffers.
+If the value is a function, call it with no arguments.
+If nil, don't show the new tab button."
+ :type '(choice (const :tag "Buffer menu" t)
+ (function :tag "Function")
+ (const :tag "No button" nil))
+ :group 'tab-line
+ :version "27.1")
+
+(defvar tab-line-new-button
+ (propertize " + "
+ 'display `(image :type xpm
+ :file ,(expand-file-name
+ "images/tabs/new.xpm"
+ data-directory)
+ :margin (2 . 0)
+ :ascent center)
+ 'keymap tab-line-add-map
+ 'mouse-face 'tab-line-highlight
+ 'help-echo "Click to add tab")
+ "Button for creating a new tab.")
+
+(defcustom tab-line-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))
+ :set (lambda (sym val)
+ (set sym val)
+ (force-mode-line-update))
+ :group 'tab-line
+ :version "27.1")
+
+(defvar tab-line-close-button
+ (propertize " x"
+ 'display `(image :type xpm
+ :file ,(expand-file-name
+ "images/tabs/close.xpm"
+ data-directory)
+ :margin (2 . 0)
+ :ascent center)
+ 'keymap tab-line-tab-close-map
+ 'mouse-face 'tab-line-close-highlight
+ 'help-echo "Click to close tab")
+ "Button for closing the clicked tab.")
+
+(defvar tab-line-separator nil)
+
+(defvar tab-line-tab-name-ellipsis
+ (if (char-displayable-p ?…) "…" "..."))
+
+
+(defvar tab-line-tab-name-function #'tab-line-tab-name
+ "Function to get a tab name.
+Function gets two arguments: tab to get name for and a list of tabs
+to display. By default, use function `tab-line-tab-name'.")
+
+(defun tab-line-tab-name (buffer &optional buffers)
+ "Generate tab name from BUFFER.
+Reduce tab width proportionally to space taken by other tabs.
+This function can be overridden by changing the default value of the
+variable `tab-line-tab-name-function'."
+ (let ((tab-name (buffer-name buffer))
+ (limit (when buffers
+ (max 1 (- (/ (window-width) (length buffers)) 3)))))
+ (if (or (not limit) (< (length tab-name) limit))
+ tab-name
+ (propertize (truncate-string-to-width tab-name limit nil nil
+ tab-line-tab-name-ellipsis)
+ 'help-echo tab-name))))
+
+(defvar tab-line-tabs-limit 15
+ "Maximum number of buffer tabs displayed in the tab line.")
+
+(defvar tab-line-tabs-function #'tab-line-tabs
+ "Function to get a list of tabs to display in the tab line.
+This function should return either a list of buffers whose names will
+be displayed, or just a list of strings to display in the tab line.
+By default, use function `tab-line-tabs'.")
+
+(defun tab-line-tabs ()
+ "Return a list of tabs that should be displayed in the tab line.
+By default returns a list of window buffers, i.e. buffers previously
+shown in the same window where the tab line is displayed.
+This list can be overridden by changing the default value of the
+variable `tab-line-tabs-function'."
+ (let* ((window (selected-window))
+ (buffer (window-buffer window))
+ (next-buffers (seq-remove (lambda (b) (eq b buffer))
+ (window-next-buffers window)))
+ (next-buffers (seq-filter #'buffer-live-p next-buffers))
+ (prev-buffers (seq-remove (lambda (b) (eq b buffer))
+ (mapcar #'car (window-prev-buffers window))))
+ (prev-buffers (seq-filter #'buffer-live-p prev-buffers))
+ ;; Remove next-buffers from prev-buffers
+ (prev-buffers (seq-difference prev-buffers next-buffers))
+ (half-limit (/ tab-line-tabs-limit 2))
+ (prev-buffers-limit
+ (if (> (length prev-buffers) half-limit)
+ (if (> (length next-buffers) half-limit)
+ half-limit
+ (+ half-limit (- half-limit (length next-buffers))))
+ (length prev-buffers)))
+ (next-buffers-limit
+ (- tab-line-tabs-limit prev-buffers-limit))
+ (buffer-tabs
+ (append (reverse (seq-take prev-buffers prev-buffers-limit))
+ (list buffer)
+ (seq-take next-buffers next-buffers-limit))))
+ buffer-tabs))
+
+(defun tab-line-format ()
+ "Template for displaying tab line for selected window."
+ (let* ((window (selected-window))
+ (selected-buffer (window-buffer window))
+ (tabs (funcall tab-line-tabs-function))
+ (separator (or tab-line-separator (if window-system " " "|"))))
+ (append
+ (mapcar
+ (lambda (tab)
+ (concat
+ separator
+ (apply 'propertize (concat (propertize
+ (funcall tab-line-tab-name-function tab tabs)
+ 'keymap tab-line-tab-map)
+ (or (and tab-line-close-button-show
+ (not (eq tab-line-close-button-show
+ (if (eq tab selected-buffer)
+ 'non-selected
+ 'selected)))
+ tab-line-close-button) ""))
+ `(
+ tab ,tab
+ face ,(if (eq tab selected-buffer)
+ 'tab-line-tab
+ 'tab-line-tab-inactive)
+ mouse-face tab-line-highlight))))
+ tabs)
+ (list (concat separator (when tab-line-new-tab-choice
+ tab-line-new-button))))))
+
+
+(defun tab-line-new-tab (&optional e)
+ "Add a new tab to the tab line.
+Usually is invoked by clicking on the plus-shaped button.
+But any switching to other buffer also adds a new tab
+corresponding to the switched buffer."
+ (interactive "e")
+ (if (functionp tab-line-new-tab-choice)
+ (funcall tab-line-new-tab-choice)
+ (if window-system ; (display-popup-menus-p)
+ (mouse-buffer-menu e) ; like (buffer-menu-open)
+ ;; tty menu doesn't support mouse clicks, so use tmm
+ (tmm-prompt (mouse-buffer-menu-keymap)))))
+
+(defun tab-line-select-tab (&optional e)
+ "Switch to the selected tab.
+This command maintains the original order of prev/next buffers.
+So for example, switching to a previous tab is equivalent to
+using the `previous-buffer' command."
+ (interactive "e")
+ (let* ((posnp (event-start e))
+ (window (posn-window posnp))
+ (buffer (get-pos-property 1 'tab (car (posn-string posnp))))
+ (window-buffer (window-buffer window))
+ (next-buffers (seq-remove (lambda (b) (eq b window-buffer))
+ (window-next-buffers window)))
+ (prev-buffers (seq-remove (lambda (b) (eq b window-buffer))
+ (mapcar #'car (window-prev-buffers window))))
+ ;; Remove next-buffers from prev-buffers
+ (prev-buffers (seq-difference prev-buffers next-buffers)))
+ (cond
+ ((memq buffer next-buffers)
+ (dotimes (_ (1+ (seq-position next-buffers buffer)))
+ (switch-to-next-buffer window)))
+ ((memq buffer prev-buffers)
+ (dotimes (_ (1+ (seq-position prev-buffers buffer)))
+ (switch-to-prev-buffer window)))
+ (t
+ (with-selected-window window
+ (switch-to-buffer buffer))))))
+
+(defun tab-line-switch-to-prev-tab (&optional e)
+ "Switch to the previous tab.
+Its effect is the same as using the `previous-buffer' command
+(\\[previous-buffer])."
+ (interactive "e")
+ (switch-to-prev-buffer (posn-window (event-start e))))
+
+(defun tab-line-switch-to-next-tab (&optional e)
+ "Switch to the next tab.
+Its effect is the same as using the `next-buffer' command
+(\\[next-buffer])."
+ (interactive "e")
+ (switch-to-next-buffer (posn-window (event-start e))))
+
+(defcustom tab-line-close-tab-action 'bury-buffer
+ "Defines what to do on closing the tab.
+If `bury-buffer', put the tab's buffer at the end of the list of all
+buffers that effectively hides the buffer's tab from the tab line.
+If `kill-buffer', kills the tab's buffer."
+ :type '(choice (const :tag "Bury buffer" bury-buffer)
+ (const :tag "Kill buffer" kill-buffer))
+ :group 'tab-line
+ :version "27.1")
+
+(defun tab-line-close-tab (&optional e)
+ "Close the selected tab.
+Usually is invoked by clicking on the close button on the right side
+of the tab. This command buries the buffer, so it goes out of sight
+from the tab line."
+ (interactive "e")
+ (let* ((posnp (event-start e))
+ (window (posn-window posnp))
+ (buffer (get-pos-property 1 'tab (car (posn-string posnp)))))
+ (with-selected-window window
+ (cond
+ ((eq tab-line-close-tab-action 'kill-buffer)
+ (kill-buffer buffer))
+ ((eq tab-line-close-tab-action 'bury-buffer)
+ (if (eq buffer (current-buffer))
+ (bury-buffer)
+ (set-window-prev-buffers nil (assq-delete-all buffer (window-prev-buffers)))
+ (set-window-next-buffers nil (delq buffer (window-next-buffers))))))
+ (force-mode-line-update))))
+
+
+;;;###autoload
+(define-minor-mode global-tab-line-mode
+ "Display window-local tab line."
+ :group 'tab-line
+ :type 'boolean
+ :global t
+ :init-value nil
+ (setq-default tab-line-format (when global-tab-line-mode
+ '(:eval (tab-line-format)))))
+
+
+(provide 'tab-line)
+;;; 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."
(format "frame text pixel: %s x %s cols/lines: %s x %s\n"
(frame-text-width frame) (frame-text-height frame)
(frame-text-cols frame) (frame-text-lines frame))
- (format "tool: %s scroll: %s/%s fringe: %s border: %s right: %s bottom: %s\n\n"
+ (format "tab: %s tool: %s scroll: %s/%s fringe: %s border: %s right: %s bottom: %s\n\n"
+ (if (fboundp 'tab-bar-height)
+ (tab-bar-height frame t)
+ "0")
(if (fboundp 'tool-bar-height)
(tool-bar-height frame t)
"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."
(top (nth 1 ltrb))
(posn (if w
(posn-at-x-y (- x left) (- y top) w t)
- (append (list nil 'menu-bar)
+ (append (list nil (if (and tab-bar-mode
+ (or (not menu-bar-mode)
+ ;; The tab-bar is on the
+ ;; second row below menu-bar
+ (eq y 1)))
+ 'tab-bar
+ 'menu-bar))
(nthcdr 2 (posn-at-x-y x y)))))
(event (list type posn)))
(setcar (nthcdr 3 posn) timestamp)