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