diff options
| author | Juri Linkov | 2019-10-06 00:50:19 +0300 |
|---|---|---|
| committer | Juri Linkov | 2019-10-06 00:50:19 +0300 |
| commit | e3fcf1f38bb5900a595f441a13cf83b034701790 (patch) | |
| tree | 2a1da791a1f11226fb727cbe81e50326a16c1fcd | |
| parent | 080e8084e83f70f9add7b42d2f24b03db931fcf9 (diff) | |
| download | emacs-e3fcf1f38bb5900a595f441a13cf83b034701790.tar.gz emacs-e3fcf1f38bb5900a595f441a13cf83b034701790.zip | |
* lisp/tab-bar.el: In tab switching allow absolute and relative args.
* lisp/tab-bar.el (tab-bar-tab-hints): New defcustom.
(tab-bar-make-keymap-1): Use tab-bar-tab-hints.
(tab-bar--tab, tab-bar--current-tab, tab-bar--current-tab-index)
(tab-bar--tab-index, tab-bar--tab-index-by-name): New internal functions.
(tab-bar-select-tab): Use arg as absolute position of tab to select.
(tab-bar-switch-to-next-tab, tab-bar-switch-to-prev-tab): Use arg
as offset relative to the current tab.
(tab-bar-switch-to-tab): New command.
(tab-bar-new-tab): Simplify by using cl-pushnew.
(tab-bar-close-current-tab): Remove (the current tab is closed
by nil arg of tab-bar-close-tab).
(tab-bar-close-tab): Use arg as absolute position of tab to close.
(tab-bar-close-tab-by-name): New command.
| -rw-r--r-- | lisp/tab-bar.el | 404 |
1 files changed, 217 insertions, 187 deletions
diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index 6d2c915aa67..d8d9bdac26d 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el | |||
| @@ -34,6 +34,8 @@ | |||
| 34 | 34 | ||
| 35 | ;;; Code: | 35 | ;;; Code: |
| 36 | 36 | ||
| 37 | (eval-when-compile (require 'cl-lib)) | ||
| 38 | |||
| 37 | 39 | ||
| 38 | (defgroup tab-bar nil | 40 | (defgroup tab-bar nil |
| 39 | "Frame-local tabs." | 41 | "Frame-local tabs." |
| @@ -179,16 +181,16 @@ keyboard commands `tab-list', `tab-new', `tab-close', `tab-next', etc." | |||
| 179 | If t, start a new tab with the current buffer, i.e. the buffer | 181 | If t, start a new tab with the current buffer, i.e. the buffer |
| 180 | that was current before calling the command that adds a new tab | 182 | that was current before calling the command that adds a new tab |
| 181 | (this is the same what `make-frame' does by default). | 183 | (this is the same what `make-frame' does by default). |
| 182 | If the value is a string, switch to a buffer if it exists, or switch | 184 | If the value is a string, use it as a buffer name switch to a buffer |
| 183 | to a buffer visiting the file or directory that the string specifies. | 185 | if such buffer exists, or switch to a buffer visiting the file or |
| 184 | If the value is a function, call it with no arguments and switch to | 186 | directory that the string specifies. If the value is a function, |
| 185 | the buffer that it returns. | 187 | call it with no arguments and switch to the buffer that it returns. |
| 186 | If nil, duplicate the contents of the tab that was active | 188 | If nil, duplicate the contents of the tab that was active |
| 187 | before calling the command that adds a new tab." | 189 | before calling the command that adds a new tab." |
| 188 | :type '(choice (const :tag "Current buffer" t) | 190 | :type '(choice (const :tag "Current buffer" t) |
| 191 | (string :tag "Buffer" "*scratch*") | ||
| 189 | (directory :tag "Directory" :value "~/") | 192 | (directory :tag "Directory" :value "~/") |
| 190 | (file :tag "File" :value "~/.emacs") | 193 | (file :tag "File" :value "~/.emacs") |
| 191 | (string :tag "Buffer" "*scratch*") | ||
| 192 | (function :tag "Function") | 194 | (function :tag "Function") |
| 193 | (const :tag "Duplicate tab" nil)) | 195 | (const :tag "Duplicate tab" nil)) |
| 194 | :group 'tab-bar | 196 | :group 'tab-bar |
| @@ -233,6 +235,17 @@ If nil, don't show it at all." | |||
| 233 | :help "Click to close tab") | 235 | :help "Click to close tab") |
| 234 | "Button for closing the clicked tab.") | 236 | "Button for closing the clicked tab.") |
| 235 | 237 | ||
| 238 | (defcustom tab-bar-tab-hints nil | ||
| 239 | "Show absolute numbers on tabs in the tab bar before the tab name. | ||
| 240 | This helps to select the tab by its number using `tab-bar-select-tab'." | ||
| 241 | :type 'boolean | ||
| 242 | :initialize 'custom-initialize-default | ||
| 243 | :set (lambda (sym val) | ||
| 244 | (set-default sym val) | ||
| 245 | (force-mode-line-update)) | ||
| 246 | :group 'tab-bar | ||
| 247 | :version "27.1") | ||
| 248 | |||
| 236 | (defvar tab-bar-separator nil) | 249 | (defvar tab-bar-separator nil) |
| 237 | 250 | ||
| 238 | 251 | ||
| @@ -261,19 +274,20 @@ By default, use function `tab-bar-tabs'.") | |||
| 261 | Ensure the frame parameter `tabs' is pre-populated. | 274 | Ensure the frame parameter `tabs' is pre-populated. |
| 262 | Return its existing value or a new value." | 275 | Return its existing value or a new value." |
| 263 | (let ((tabs (frame-parameter nil 'tabs))) | 276 | (let ((tabs (frame-parameter nil 'tabs))) |
| 264 | (if tabs | 277 | (unless tabs |
| 265 | ;; Update current tab name | ||
| 266 | (let ((name (assq 'name (assq 'current-tab tabs)))) | ||
| 267 | (when name (setcdr name (funcall tab-bar-tab-name-function)))) | ||
| 268 | ;; Create default tabs | 278 | ;; Create default tabs |
| 269 | (setq tabs `((current-tab (name . ,(funcall tab-bar-tab-name-function))))) | 279 | (setq tabs (list (tab-bar--current-tab))) |
| 270 | (set-frame-parameter nil 'tabs tabs)) | 280 | (set-frame-parameter nil 'tabs tabs)) |
| 271 | tabs)) | 281 | tabs)) |
| 272 | 282 | ||
| 273 | (defun tab-bar-make-keymap-1 () | 283 | (defun tab-bar-make-keymap-1 () |
| 274 | "Generate an actual keymap from `tab-bar-map', without caching." | 284 | "Generate an actual keymap from `tab-bar-map', without caching." |
| 275 | (let ((separator (or tab-bar-separator (if window-system " " "|"))) | 285 | (let* ((separator (or tab-bar-separator (if window-system " " "|"))) |
| 276 | (i 0)) | 286 | (i 0) |
| 287 | (tabs (funcall tab-bar-tabs-function)) | ||
| 288 | (current-tab-name (assq 'name (assq 'current-tab tabs)))) | ||
| 289 | (when current-tab-name | ||
| 290 | (setf (cdr current-tab-name) (funcall tab-bar-tab-name-function))) | ||
| 277 | (append | 291 | (append |
| 278 | '(keymap (mouse-1 . tab-bar-handle-mouse)) | 292 | '(keymap (mouse-1 . tab-bar-handle-mouse)) |
| 279 | (mapcan | 293 | (mapcan |
| @@ -285,7 +299,8 @@ Return its existing value or a new value." | |||
| 285 | ((eq (car tab) 'current-tab) | 299 | ((eq (car tab) 'current-tab) |
| 286 | `((current-tab | 300 | `((current-tab |
| 287 | menu-item | 301 | menu-item |
| 288 | ,(propertize (concat (cdr (assq 'name tab)) | 302 | ,(propertize (concat (if tab-bar-tab-hints (format "%d " i) "") |
| 303 | (cdr (assq 'name tab)) | ||
| 289 | (or (and tab-bar-close-button-show | 304 | (or (and tab-bar-close-button-show |
| 290 | (not (eq tab-bar-close-button-show | 305 | (not (eq tab-bar-close-button-show |
| 291 | 'non-selected)) | 306 | 'non-selected)) |
| @@ -296,7 +311,8 @@ Return its existing value or a new value." | |||
| 296 | (t | 311 | (t |
| 297 | `((,(intern (format "tab-%i" i)) | 312 | `((,(intern (format "tab-%i" i)) |
| 298 | menu-item | 313 | menu-item |
| 299 | ,(propertize (concat (cdr (assq 'name tab)) | 314 | ,(propertize (concat (if tab-bar-tab-hints (format "%d " i) "") |
| 315 | (cdr (assq 'name tab)) | ||
| 300 | (or (and tab-bar-close-button-show | 316 | (or (and tab-bar-close-button-show |
| 301 | (not (eq tab-bar-close-button-show | 317 | (not (eq tab-bar-close-button-show |
| 302 | 'selected)) | 318 | 'selected)) |
| @@ -304,97 +320,132 @@ Return its existing value or a new value." | |||
| 304 | 'face 'tab-bar-tab-inactive) | 320 | 'face 'tab-bar-tab-inactive) |
| 305 | ,(or | 321 | ,(or |
| 306 | (cdr (assq 'binding tab)) | 322 | (cdr (assq 'binding tab)) |
| 307 | (lambda () | 323 | `(lambda () |
| 308 | (interactive) | 324 | (interactive) |
| 309 | (tab-bar-select-tab tab))) | 325 | (tab-bar-select-tab ,i))) |
| 310 | :help "Click to visit tab")))) | 326 | :help "Click to visit tab")))) |
| 311 | `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) | 327 | `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) |
| 312 | menu-item "" | 328 | menu-item "" |
| 313 | ,(or | 329 | ,(or |
| 314 | (cdr (assq 'close-binding tab)) | 330 | (cdr (assq 'close-binding tab)) |
| 315 | (lambda () | 331 | `(lambda () |
| 316 | (interactive) | 332 | (interactive) |
| 317 | (tab-bar-close-tab tab))))))) | 333 | (tab-bar-close-tab ,i))))))) |
| 318 | (funcall tab-bar-tabs-function)) | 334 | tabs) |
| 319 | (when tab-bar-new-button | 335 | (when tab-bar-new-button |
| 320 | `((sep-add-tab menu-item ,separator ignore) | 336 | `((sep-add-tab menu-item ,separator ignore) |
| 321 | (add-tab menu-item ,tab-bar-new-button tab-bar-new-tab | 337 | (add-tab menu-item ,tab-bar-new-button tab-bar-new-tab |
| 322 | :help "New tab")))))) | 338 | :help "New tab")))))) |
| 323 | 339 | ||
| 324 | 340 | ||
| 325 | (defun tab-bar-read-tab-name (prompt) | 341 | (defun tab-bar--tab () |
| 326 | (let* ((tabs (tab-bar-tabs)) | 342 | `(tab |
| 327 | (tab-name | 343 | (name . ,(funcall tab-bar-tab-name-function)) |
| 328 | (completing-read prompt | 344 | (time . ,(time-convert nil 'integer)) |
| 329 | (or (delq nil (mapcar (lambda (tab) | 345 | (wc . ,(current-window-configuration)) |
| 330 | (cdr (assq 'name tab))) | 346 | (ws . ,(window-state-get |
| 331 | tabs)) | 347 | (frame-root-window (selected-frame)) 'writable)))) |
| 332 | '(""))))) | 348 | |
| 349 | (defun tab-bar--current-tab () | ||
| 350 | `(current-tab | ||
| 351 | (name . ,(funcall tab-bar-tab-name-function)))) | ||
| 352 | |||
| 353 | (defun tab-bar--current-tab-index (&optional tabs) | ||
| 354 | ;; FIXME: could be replaced with 1-liner using seq-position | ||
| 355 | (let ((tabs (or tabs (tab-bar-tabs))) | ||
| 356 | (i 0)) | ||
| 333 | (catch 'done | 357 | (catch 'done |
| 334 | (dolist (tab tabs) | 358 | (while tabs |
| 335 | (when (equal (cdr (assq 'name tab)) tab-name) | 359 | (when (eq (car (car tabs)) 'current-tab) |
| 336 | (throw 'done tab)))))) | 360 | (throw 'done i)) |
| 337 | 361 | (setq i (1+ i) tabs (cdr tabs)))))) | |
| 338 | (defun tab-bar-tab-default () | ||
| 339 | (let ((tab `(tab | ||
| 340 | (name . ,(funcall tab-bar-tab-name-function)) | ||
| 341 | (time . ,(time-convert nil 'integer)) | ||
| 342 | (wc . ,(current-window-configuration)) | ||
| 343 | (ws . ,(window-state-get | ||
| 344 | (frame-root-window (selected-frame)) 'writable))))) | ||
| 345 | tab)) | ||
| 346 | |||
| 347 | (defun tab-bar-find-prev-tab (&optional tabs) | ||
| 348 | (unless tabs | ||
| 349 | (setq tabs (tab-bar-tabs))) | ||
| 350 | (unless (eq (car (car tabs)) 'current-tab) | ||
| 351 | (while (and tabs (not (eq (car (car (cdr tabs))) 'current-tab))) | ||
| 352 | (setq tabs (cdr tabs))) | ||
| 353 | tabs)) | ||
| 354 | 362 | ||
| 355 | 363 | (defun tab-bar--tab-index (tab &optional tabs) | |
| 356 | (defun tab-bar-select-tab (tab) | 364 | ;; FIXME: could be replaced with 1-liner using seq-position |
| 357 | "Switch to the specified TAB." | 365 | (let ((tabs (or tabs (tab-bar-tabs))) |
| 358 | (interactive (list (tab-bar-read-tab-name "Select tab by name: "))) | 366 | (i 0)) |
| 359 | (when (and tab (not (eq (car tab) 'current-tab))) | 367 | (catch 'done |
| 360 | (let* ((tabs (tab-bar-tabs)) | ||
| 361 | (new-tab (tab-bar-tab-default)) | ||
| 362 | (wc (cdr (assq 'wc tab)))) | ||
| 363 | ;; During the same session, use window-configuration to switch | ||
| 364 | ;; tabs, because window-configurations are more reliable | ||
| 365 | ;; (they keep references to live buffers) than window-states. | ||
| 366 | ;; But after restoring tabs from a previously saved session, | ||
| 367 | ;; its value of window-configuration is unreadable, | ||
| 368 | ;; so restore its saved window-state. | ||
| 369 | (if (window-configuration-p wc) | ||
| 370 | (set-window-configuration wc) | ||
| 371 | (window-state-put (cdr (assq 'ws tab)) | ||
| 372 | (frame-root-window (selected-frame)) 'safe)) | ||
| 373 | (while tabs | 368 | (while tabs |
| 374 | (cond | 369 | (when (eq (car tabs) tab) |
| 375 | ((eq (car tabs) tab) | 370 | (throw 'done i)) |
| 376 | (setcar tabs `(current-tab (name . ,(funcall tab-bar-tab-name-function))))) | 371 | (setq i (1+ i) tabs (cdr tabs)))) |
| 377 | ((eq (car (car tabs)) 'current-tab) | 372 | i)) |
| 378 | (setcar tabs new-tab))) | 373 | |
| 379 | (setq tabs (cdr tabs))) | 374 | (defun tab-bar--tab-index-by-name (name &optional tabs) |
| 375 | ;; FIXME: could be replaced with 1-liner using seq-position | ||
| 376 | (let ((tabs (or tabs (tab-bar-tabs))) | ||
| 377 | (i 0)) | ||
| 378 | (catch 'done | ||
| 379 | (while tabs | ||
| 380 | (when (equal (cdr (assq 'name (car tabs))) name) | ||
| 381 | (throw 'done i)) | ||
| 382 | (setq i (1+ i) tabs (cdr tabs)))) | ||
| 383 | i)) | ||
| 384 | |||
| 385 | |||
| 386 | (defun tab-bar-select-tab (&optional arg) | ||
| 387 | "Switch to the tab by its absolute position ARG in the tab bar. | ||
| 388 | When this command is bound to a numeric key (with a prefix or modifier), | ||
| 389 | calling it without an argument will translate its bound numeric key | ||
| 390 | to the numeric argument. ARG counts from 1." | ||
| 391 | (interactive "P") | ||
| 392 | (unless (integerp arg) | ||
| 393 | (let ((key (event-basic-type last-command-event))) | ||
| 394 | (setq arg (if (and (characterp key) (>= key ?1) (<= key ?9)) | ||
| 395 | (- key ?0) | ||
| 396 | 1)))) | ||
| 397 | |||
| 398 | (let* ((tabs (tab-bar-tabs)) | ||
| 399 | (from-index (tab-bar--current-tab-index tabs)) | ||
| 400 | (to-index (1- (max 1 (min arg (length tabs)))))) | ||
| 401 | (unless (eq from-index to-index) | ||
| 402 | (let* ((from-tab (tab-bar--tab)) | ||
| 403 | (to-tab (nth to-index tabs)) | ||
| 404 | (wc (cdr (assq 'wc to-tab))) | ||
| 405 | (ws (cdr (assq 'ws to-tab)))) | ||
| 406 | |||
| 407 | ;; During the same session, use window-configuration to switch | ||
| 408 | ;; tabs, because window-configurations are more reliable | ||
| 409 | ;; (they keep references to live buffers) than window-states. | ||
| 410 | ;; But after restoring tabs from a previously saved session, | ||
| 411 | ;; its value of window-configuration is unreadable, | ||
| 412 | ;; so restore its saved window-state. | ||
| 413 | (if (window-configuration-p wc) | ||
| 414 | (set-window-configuration wc) | ||
| 415 | (if ws (window-state-put ws (frame-root-window (selected-frame)) | ||
| 416 | 'safe))) | ||
| 417 | |||
| 418 | (when from-index | ||
| 419 | (setf (nth from-index tabs) from-tab)) | ||
| 420 | (setf (nth to-index tabs) (tab-bar--current-tab))) | ||
| 421 | |||
| 380 | (when tab-bar-mode | 422 | (when tab-bar-mode |
| 381 | (force-mode-line-update))))) | 423 | (force-mode-line-update))))) |
| 382 | 424 | ||
| 383 | (defun tab-bar-switch-to-prev-tab (&optional _arg) | 425 | (defun tab-bar-switch-to-next-tab (&optional arg) |
| 384 | "Switch to ARGth previous tab." | ||
| 385 | (interactive "p") | ||
| 386 | (let ((prev-tab (tab-bar-find-prev-tab))) | ||
| 387 | (when prev-tab | ||
| 388 | (tab-bar-select-tab (car prev-tab))))) | ||
| 389 | |||
| 390 | (defun tab-bar-switch-to-next-tab (&optional _arg) | ||
| 391 | "Switch to ARGth next tab." | 426 | "Switch to ARGth next tab." |
| 392 | (interactive "p") | 427 | (interactive "p") |
| 428 | (unless (integerp arg) | ||
| 429 | (setq arg 1)) | ||
| 393 | (let* ((tabs (tab-bar-tabs)) | 430 | (let* ((tabs (tab-bar-tabs)) |
| 394 | (prev-tab (tab-bar-find-prev-tab tabs))) | 431 | (from-index (or (tab-bar--current-tab-index tabs) 0)) |
| 395 | (if prev-tab | 432 | (to-index (mod (+ from-index arg) (length tabs)))) |
| 396 | (tab-bar-select-tab (car (cdr (cdr prev-tab)))) | 433 | (tab-bar-select-tab (1+ to-index)))) |
| 397 | (tab-bar-select-tab (car (cdr tabs)))))) | 434 | |
| 435 | (defun tab-bar-switch-to-prev-tab (&optional arg) | ||
| 436 | "Switch to ARGth previous tab." | ||
| 437 | (interactive "p") | ||
| 438 | (unless (integerp arg) | ||
| 439 | (setq arg 1)) | ||
| 440 | (tab-bar-switch-to-next-tab (- arg))) | ||
| 441 | |||
| 442 | (defun tab-bar-switch-to-tab (name) | ||
| 443 | "Switch to the tab by NAME." | ||
| 444 | (interactive (list (completing-read "Switch to tab by name: " | ||
| 445 | (mapcar (lambda (tab) | ||
| 446 | (cdr (assq 'name tab))) | ||
| 447 | (tab-bar-tabs))))) | ||
| 448 | (tab-bar-select-tab (1+ (tab-bar--tab-index-by-name name)))) | ||
| 398 | 449 | ||
| 399 | 450 | ||
| 400 | (defcustom tab-bar-new-tab-to 'right | 451 | (defcustom tab-bar-new-tab-to 'right |
| @@ -411,35 +462,12 @@ If `rightmost', create as the last tab." | |||
| 411 | :version "27.1") | 462 | :version "27.1") |
| 412 | 463 | ||
| 413 | (defun tab-bar-new-tab () | 464 | (defun tab-bar-new-tab () |
| 414 | "Clone the current tab to the position specified by `tab-bar-new-tab-to'." | 465 | "Add a new tab at the position specified by `tab-bar-new-tab-to'." |
| 415 | (interactive) | 466 | (interactive) |
| 416 | (let* ((tabs (tab-bar-tabs)) | 467 | (let* ((tabs (tab-bar-tabs)) |
| 417 | ;; (i-tab (- (length tabs) (length (memq tab tabs)))) | 468 | (from-index (tab-bar--current-tab-index tabs)) |
| 418 | (new-tab (tab-bar-tab-default))) | 469 | (from-tab (tab-bar--tab))) |
| 419 | (when (and (not tab-bar-mode) | 470 | |
| 420 | (or (eq tab-bar-show t) | ||
| 421 | (and (natnump tab-bar-show) | ||
| 422 | (>= (length tabs) tab-bar-show)))) | ||
| 423 | (tab-bar-mode 1)) | ||
| 424 | (cond | ||
| 425 | ((eq tab-bar-new-tab-to 'leftmost) | ||
| 426 | (setq tabs (cons new-tab tabs))) | ||
| 427 | ((eq tab-bar-new-tab-to 'rightmost) | ||
| 428 | (setq tabs (append tabs (list new-tab)))) | ||
| 429 | (t | ||
| 430 | (let ((prev-tab (tab-bar-find-prev-tab tabs))) | ||
| 431 | (cond | ||
| 432 | ((eq tab-bar-new-tab-to 'left) | ||
| 433 | (if prev-tab | ||
| 434 | (setcdr prev-tab (cons new-tab (cdr prev-tab))) | ||
| 435 | (setq tabs (cons new-tab tabs)))) | ||
| 436 | ((eq tab-bar-new-tab-to 'right) | ||
| 437 | (if prev-tab | ||
| 438 | (setq prev-tab (cdr prev-tab)) | ||
| 439 | (setq prev-tab tabs)) | ||
| 440 | (setcdr prev-tab (cons new-tab (cdr prev-tab)))))))) | ||
| 441 | (set-frame-parameter nil 'tabs tabs) | ||
| 442 | (tab-bar-select-tab new-tab) | ||
| 443 | (when tab-bar-new-tab-choice | 471 | (when tab-bar-new-tab-choice |
| 444 | (delete-other-windows) | 472 | (delete-other-windows) |
| 445 | ;; Create a new window to get rid of old window parameters | 473 | ;; Create a new window to get rid of old window parameters |
| @@ -453,8 +481,29 @@ If `rightmost', create as the last tab." | |||
| 453 | (find-file-noselect tab-bar-new-tab-choice)))))) | 481 | (find-file-noselect tab-bar-new-tab-choice)))))) |
| 454 | (when (buffer-live-p buffer) | 482 | (when (buffer-live-p buffer) |
| 455 | (switch-to-buffer buffer)))) | 483 | (switch-to-buffer buffer)))) |
| 456 | (unless tab-bar-mode | 484 | |
| 457 | (message "Added new tab with the current window configuration")))) | 485 | (when from-index |
| 486 | (setf (nth from-index tabs) from-tab)) | ||
| 487 | (let ((to-tab (tab-bar--current-tab)) | ||
| 488 | (to-index (pcase tab-bar-new-tab-to | ||
| 489 | ('leftmost 0) | ||
| 490 | ('rightmost (length tabs)) | ||
| 491 | ('left (1- (or from-index 1))) | ||
| 492 | ('right (1+ (or from-index 0)))))) | ||
| 493 | (setq to-index (max 0 (min (or to-index 0) (length tabs)))) | ||
| 494 | (cl-pushnew to-tab (nthcdr to-index tabs)) | ||
| 495 | (when (eq to-index 0) | ||
| 496 | ;; pushnew handles the head of tabs but not frame-parameter | ||
| 497 | (set-frame-parameter nil 'tabs tabs))) | ||
| 498 | |||
| 499 | (when (and (not tab-bar-mode) | ||
| 500 | (or (eq tab-bar-show t) | ||
| 501 | (and (natnump tab-bar-show) | ||
| 502 | (> (length tabs) tab-bar-show)))) | ||
| 503 | (tab-bar-mode 1)) | ||
| 504 | (if tab-bar-mode | ||
| 505 | (force-mode-line-update) | ||
| 506 | (message "Added new tab at %s" tab-bar-new-tab-to)))) | ||
| 458 | 507 | ||
| 459 | 508 | ||
| 460 | (defcustom tab-bar-close-tab-select 'right | 509 | (defcustom tab-bar-close-tab-select 'right |
| @@ -466,85 +515,69 @@ If `right', select the adjacent right tab." | |||
| 466 | :group 'tab-bar | 515 | :group 'tab-bar |
| 467 | :version "27.1") | 516 | :version "27.1") |
| 468 | 517 | ||
| 469 | (defun tab-bar-close-current-tab (&optional tab select-tab) | 518 | (defun tab-bar-close-tab (&optional arg to-index) |
| 470 | "Close the current TAB. | 519 | "Close the tab specified by its absolute position ARG. |
| 471 | After closing the current tab switch to the tab | 520 | If no ARG is specified, then close the current tab and switch |
| 472 | specified by `tab-bar-close-tab-select', or to `select-tab' | 521 | to the tab specified by `tab-bar-close-tab-select'. |
| 473 | if its value is provided." | 522 | ARG counts from 1. |
| 474 | (interactive) | 523 | Optional TO-INDEX could be specified to override the value of |
| 475 | (let ((tabs (tab-bar-tabs))) | 524 | `tab-bar-close-tab-select' programmatically with a position |
| 476 | (unless tab | 525 | of an existing tab to select after closing the current tab. |
| 477 | (let ((prev-tab (tab-bar-find-prev-tab tabs))) | 526 | TO-INDEX counts from 1." |
| 478 | (setq tab (if prev-tab | 527 | (interactive "P") |
| 479 | (car (cdr prev-tab)) | 528 | (let* ((tabs (tab-bar-tabs)) |
| 480 | (car tabs))))) | 529 | (current-index (tab-bar--current-tab-index tabs)) |
| 481 | (if select-tab | 530 | (close-index (if (integerp arg) (1- arg) current-index))) |
| 482 | (setq tabs (delq tab tabs)) | 531 | |
| 483 | (let* ((i-tab (- (length tabs) (length (memq tab tabs)))) | 532 | ;; Select another tab before deleting the current tab |
| 484 | (i-select | 533 | (when (eq current-index close-index) |
| 485 | (cond | 534 | (let ((to-index (or (if to-index (1- to-index)) |
| 486 | ((eq tab-bar-close-tab-select 'left) | 535 | (pcase tab-bar-close-tab-select |
| 487 | (1- i-tab)) | 536 | ('left (1- current-index)) |
| 488 | ((eq tab-bar-close-tab-select 'right) | 537 | ('right (if (> (length tabs) (1+ current-index)) |
| 489 | ;; Do nothing: the next tab will take | 538 | (1+ current-index) |
| 490 | ;; the index of the closed tab | 539 | (1- current-index))))))) |
| 491 | i-tab) | 540 | (setq to-index (max 0 (min (or to-index 0) (1- (length tabs))))) |
| 492 | (t 0)))) | 541 | (tab-bar-select-tab (1+ to-index)) |
| 493 | (setq tabs (delq tab tabs) | 542 | ;; Re-read tabs after selecting another tab |
| 494 | i-select (max 0 (min (1- (length tabs)) i-select)) | 543 | (setq tabs (tab-bar-tabs)))) |
| 495 | select-tab (nth i-select tabs)))) | 544 | |
| 545 | (set-frame-parameter nil 'tabs (delq (nth close-index tabs) tabs)) | ||
| 546 | |||
| 496 | (when (and tab-bar-mode | 547 | (when (and tab-bar-mode |
| 497 | (and (natnump tab-bar-show) | 548 | (and (natnump tab-bar-show) |
| 498 | (<= (length tabs) tab-bar-show))) | 549 | (<= (length tabs) tab-bar-show))) |
| 499 | (tab-bar-mode -1)) | 550 | (tab-bar-mode -1)) |
| 500 | (set-frame-parameter nil 'tabs tabs) | 551 | (if tab-bar-mode |
| 501 | (tab-bar-select-tab select-tab))) | 552 | (force-mode-line-update) |
| 502 | 553 | (message "Deleted tab and switched to %s" tab-bar-close-tab-select)))) | |
| 503 | (defun tab-bar-close-tab (tab) | 554 | |
| 504 | "Close the specified TAB. | 555 | (defun tab-bar-close-tab-by-name (name) |
| 505 | After closing the current tab switch to the tab | 556 | "Close the tab by NAME." |
| 506 | specified by `tab-bar-close-tab-select'." | 557 | (interactive (list (completing-read "Close tab by name: " |
| 507 | (interactive (list (tab-bar-read-tab-name "Close tab by name: "))) | 558 | (mapcar (lambda (tab) |
| 508 | (when tab | 559 | (cdr (assq 'name tab))) |
| 509 | (if (eq (car tab) 'current-tab) | 560 | (tab-bar-tabs))))) |
| 510 | (tab-bar-close-current-tab tab) | 561 | (tab-bar-close-tab (1+ (tab-bar--tab-index-by-name name)))) |
| 511 | (let ((tabs (tab-bar-tabs))) | ||
| 512 | ;; Close non-current tab, no need to switch to another tab | ||
| 513 | (when (and tab-bar-mode | ||
| 514 | (and (natnump tab-bar-show) | ||
| 515 | (<= (length tabs) tab-bar-show))) | ||
| 516 | (tab-bar-mode -1)) | ||
| 517 | (set-frame-parameter nil 'tabs (delq tab tabs)) | ||
| 518 | (when tab-bar-mode | ||
| 519 | (force-mode-line-update)))))) | ||
| 520 | 562 | ||
| 521 | 563 | ||
| 522 | ;;; Non-graphical access to frame-local tabs (named window configurations) | 564 | ;;; Short aliases |
| 523 | |||
| 524 | (defun tab-new () | ||
| 525 | "Create a new named window configuration without having to click a tab." | ||
| 526 | (interactive) | ||
| 527 | (tab-bar-new-tab) | ||
| 528 | (unless tab-bar-mode | ||
| 529 | (message "Added new tab with the current window configuration"))) | ||
| 530 | |||
| 531 | (defun tab-close () | ||
| 532 | "Delete the current window configuration without clicking a close button." | ||
| 533 | (interactive) | ||
| 534 | (tab-bar-close-current-tab) | ||
| 535 | (unless tab-bar-mode | ||
| 536 | (message "Deleted the current tab"))) | ||
| 537 | 565 | ||
| 538 | ;; Short aliases | 566 | (defalias 'tab-new 'tab-bar-new-tab) |
| 539 | ;; (defalias 'tab-switch 'tab-bar-switch-to-next-tab) | 567 | (defalias 'tab-close 'tab-bar-close-tab) |
| 540 | (defalias 'tab-select 'tab-bar-select-tab) | 568 | (defalias 'tab-select 'tab-bar-select-tab) |
| 569 | (defalias 'tab-next 'tab-bar-switch-to-next-tab) | ||
| 541 | (defalias 'tab-previous 'tab-bar-switch-to-prev-tab) | 570 | (defalias 'tab-previous 'tab-bar-switch-to-prev-tab) |
| 542 | (defalias 'tab-next 'tab-bar-switch-to-next-tab) | 571 | (defalias 'tab-list 'tab-bar-list) |
| 543 | (defalias 'tab-list 'tab-bar-list) | 572 | |
| 573 | |||
| 574 | ;;; Non-graphical access to frame-local tabs (named window configurations) | ||
| 544 | 575 | ||
| 545 | (defun tab-bar-list () | 576 | (defun tab-bar-list () |
| 546 | "Display a list of named window configurations. | 577 | "Display a list of named window configurations. |
| 547 | The list is displayed in the buffer `*Tabs*'. | 578 | The list is displayed in the buffer `*Tabs*'. |
| 579 | It's placed in the center of the frame to resemble a window list | ||
| 580 | displayed by a window switcher in some window managers on Alt+Tab. | ||
| 548 | 581 | ||
| 549 | In this list of window configurations you can delete or select them. | 582 | In this list of window configurations you can delete or select them. |
| 550 | Type ? after invocation to get help on commands available. | 583 | Type ? after invocation to get help on commands available. |
| @@ -555,7 +588,7 @@ marked for deletion." | |||
| 555 | (interactive) | 588 | (interactive) |
| 556 | (let ((dir default-directory) | 589 | (let ((dir default-directory) |
| 557 | (minibuf (minibuffer-selected-window))) | 590 | (minibuf (minibuffer-selected-window))) |
| 558 | (let ((tab-bar-mode t)) ; don't enable tab-bar-mode if it's disabled | 591 | (let ((tab-bar-show nil)) ; don't enable tab-bar-mode if it's disabled |
| 559 | (tab-bar-new-tab)) | 592 | (tab-bar-new-tab)) |
| 560 | ;; Handle the case when it's called in the active minibuffer. | 593 | ;; Handle the case when it's called in the active minibuffer. |
| 561 | (when minibuf (select-window (minibuffer-selected-window))) | 594 | (when minibuf (select-window (minibuffer-selected-window))) |
| @@ -660,7 +693,6 @@ Letters do not insert themselves; instead, they are commands. | |||
| 660 | (user-error "No window configuration on this line") | 693 | (user-error "No window configuration on this line") |
| 661 | nil)))) | 694 | nil)))) |
| 662 | 695 | ||
| 663 | |||
| 664 | (defun tab-bar-list-next-line (&optional arg) | 696 | (defun tab-bar-list-next-line (&optional arg) |
| 665 | (interactive) | 697 | (interactive) |
| 666 | (forward-line arg) | 698 | (forward-line arg) |
| @@ -748,12 +780,10 @@ Then move up one line. Prefix arg means move that many lines." | |||
| 748 | This command deletes and replaces all the previously existing windows | 780 | This command deletes and replaces all the previously existing windows |
| 749 | in the selected frame." | 781 | in the selected frame." |
| 750 | (interactive) | 782 | (interactive) |
| 751 | (let* ((select-tab (tab-bar-list-current-tab t))) | 783 | (let* ((to-tab (tab-bar-list-current-tab t))) |
| 752 | (kill-buffer (current-buffer)) | 784 | (kill-buffer (current-buffer)) |
| 753 | ;; Delete the current window configuration | 785 | ;; Delete the current window configuration |
| 754 | (tab-bar-close-current-tab nil select-tab) | 786 | (tab-bar-close-tab nil (1+ (tab-bar--tab-index to-tab))))) |
| 755 | ;; (tab-bar-select-tab select-tab) | ||
| 756 | )) | ||
| 757 | 787 | ||
| 758 | (defun tab-bar-list-mouse-select (event) | 788 | (defun tab-bar-list-mouse-select (event) |
| 759 | "Select the window configuration whose line you click on." | 789 | "Select the window configuration whose line you click on." |