diff options
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 ;; 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) |