diff options
| author | Juri Linkov | 2019-09-01 22:16:18 +0300 |
|---|---|---|
| committer | Juri Linkov | 2019-09-01 22:16:18 +0300 |
| commit | 7edb95454999d28e4f8d1b1cc042e3c98bb0961b (patch) | |
| tree | 29683198df7237f5b88d0e60f89f2502e4e9f0e2 | |
| parent | 3e0ad29a607c8c085de3b74c7505e417ad7f9062 (diff) | |
| download | emacs-7edb95454999d28e4f8d1b1cc042e3c98bb0961b.tar.gz emacs-7edb95454999d28e4f8d1b1cc042e3c98bb0961b.zip | |
Non-graphical access to frame-local tabs (named window configurations)
* lisp/tab-bar.el (make-tab, delete-tab, tab-bar-list)
(tab-bar-list-next-line, tab-bar-list-prev-line)
(tab-bar-list-unmark, tab-bar-list-backup-unmark)
(tab-bar-list-delete, tab-bar-list-delete-backwards)
(tab-bar-list-execute, tab-bar-list-select)
(tab-bar-list-mouse-select): New commands.
(tab-bar-list-noselect, tab-bar-list-current-tab)
(tab-bar-list-delete-from-list): New functions.
(tab-bar-list-column): New defvar.
| -rw-r--r-- | etc/NEWS | 8 | ||||
| -rw-r--r-- | lisp/tab-bar.el | 295 |
2 files changed, 278 insertions, 25 deletions
| @@ -1887,11 +1887,19 @@ good replacement, even in very large source files. | |||
| 1887 | to switch named persistent window configurations in it using tabs. | 1887 | to switch named persistent window configurations in it using tabs. |
| 1888 | New tab-based keybindings (similar to frame-based): | 1888 | New tab-based keybindings (similar to frame-based): |
| 1889 | 'C-x 6 2' creates a new tab; | 1889 | 'C-x 6 2' creates a new tab; |
| 1890 | 'C-x 6 0' deletes the current tab; | ||
| 1890 | 'C-x 6 b' switches to buffer in another tab; | 1891 | 'C-x 6 b' switches to buffer in another tab; |
| 1891 | 'C-x 6 f' and 'C-x 6 C-f' edit file in another tab; | 1892 | 'C-x 6 f' and 'C-x 6 C-f' edit file in another tab; |
| 1892 | 'C-TAB' switches to the next tab; | 1893 | 'C-TAB' switches to the next tab; |
| 1893 | 'S-C-TAB' switches to the previous tab. | 1894 | 'S-C-TAB' switches to the previous tab. |
| 1894 | 1895 | ||
| 1896 | Also it's possible to switch named persistent window configurations | ||
| 1897 | without having graphical access to the tab-bar, even on a tty | ||
| 1898 | or when 'tab-bar-mode' is disabled, with these commands: | ||
| 1899 | 'make-tab' creates a new window configuration; | ||
| 1900 | 'delete-tab' deletes the current window configuration; | ||
| 1901 | 'list-tabs' displays a list of named window configurations. | ||
| 1902 | |||
| 1895 | ** 'global-tab-line-mode' enables the tab-line above each window to | 1903 | ** 'global-tab-line-mode' enables the tab-line above each window to |
| 1896 | switch buffers in it to previous/next buffers. Selecting a previous | 1904 | switch buffers in it to previous/next buffers. Selecting a previous |
| 1897 | window-local tab is the same as running 'C-x <left>' (previous-buffer), | 1905 | window-local tab is the same as running 'C-x <left>' (previous-buffer), |
diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index a5224180b2a..0532ac67f08 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | ;;; tab-bar.el --- frame-local tab bar with window configurations -*- lexical-binding: t; -*- | 1 | ;;; tab-bar.el --- frame-local tab bar with named persistent window configurations -*- lexical-binding: t; -*- |
| 2 | 2 | ||
| 3 | ;; Copyright (C) 2019 Free Software Foundation, Inc. | 3 | ;; Copyright (C) 2019 Free Software Foundation, Inc. |
| 4 | 4 | ||
| @@ -208,6 +208,7 @@ Return its existing value or a new value." | |||
| 208 | (defun tab-bar-new-tab () | 208 | (defun tab-bar-new-tab () |
| 209 | (let ((tab `(tab | 209 | (let ((tab `(tab |
| 210 | (name . ,(tab-bar-tab-name)) | 210 | (name . ,(tab-bar-tab-name)) |
| 211 | (time . ,(time-convert nil 'integer)) | ||
| 211 | (wc . ,(current-window-configuration)) | 212 | (wc . ,(current-window-configuration)) |
| 212 | (ws . ,(window-state-get | 213 | (ws . ,(window-state-get |
| 213 | (frame-root-window (selected-frame)) 'writable))))) | 214 | (frame-root-window (selected-frame)) 'writable))))) |
| @@ -303,7 +304,9 @@ If `rightmost', create as the last tab." | |||
| 303 | (setq prev-tab tabs)) | 304 | (setq prev-tab tabs)) |
| 304 | (setcdr prev-tab (cons new-tab (cdr prev-tab)))))))) | 305 | (setcdr prev-tab (cons new-tab (cdr prev-tab)))))))) |
| 305 | (set-frame-parameter nil 'tabs tabs) | 306 | (set-frame-parameter nil 'tabs tabs) |
| 306 | (tab-bar-select-tab new-tab))) | 307 | (tab-bar-select-tab new-tab) |
| 308 | (unless (and (display-graphic-p) tab-bar-mode) | ||
| 309 | (message "Added new tab with the current window configuration")))) | ||
| 307 | 310 | ||
| 308 | 311 | ||
| 309 | (defcustom tab-bar-close-tab-select 'right | 312 | (defcustom tab-bar-close-tab-select 'right |
| @@ -314,31 +317,33 @@ If `right', select the adjacent right tab." | |||
| 314 | (const :tag "Select right tab" right)) | 317 | (const :tag "Select right tab" right)) |
| 315 | :version "27.1") | 318 | :version "27.1") |
| 316 | 319 | ||
| 317 | (defun tab-bar-close-current-tab (tab) | 320 | (defun tab-bar-close-current-tab (&optional tab select-tab) |
| 318 | "Close the current TAB. | 321 | "Close the current TAB. |
| 319 | After closing the current tab switch to the tab | 322 | After closing the current tab switch to the tab |
| 320 | specified by `tab-bar-close-tab-select'." | 323 | specified by `tab-bar-close-tab-select', or to `select-tab' |
| 321 | (interactive | 324 | if its value is provided." |
| 322 | (list | 325 | (interactive) |
| 323 | (let* ((tabs (tab-bar-tabs)) | 326 | (let ((tabs (tab-bar-tabs))) |
| 324 | (prev-tab (tab-bar-find-prev-tab tabs))) | 327 | (unless tab |
| 325 | (if prev-tab | 328 | (let ((prev-tab (tab-bar-find-prev-tab tabs))) |
| 326 | (tab-bar-select-tab (car prev-tab)) | 329 | (setq tab (if prev-tab |
| 327 | (car tabs))))) | 330 | (car (cdr prev-tab)) |
| 328 | (let* ((tabs (tab-bar-tabs)) | 331 | (car tabs))))) |
| 329 | (i-tab (- (length tabs) (length (memq tab tabs)))) | 332 | (if select-tab |
| 330 | (i-select | 333 | (setq tabs (delq tab tabs)) |
| 331 | (cond | 334 | (let* ((i-tab (- (length tabs) (length (memq tab tabs)))) |
| 332 | ((eq tab-bar-close-tab-select 'left) | 335 | (i-select |
| 333 | (1- i-tab)) | 336 | (cond |
| 334 | ((eq tab-bar-close-tab-select 'right) | 337 | ((eq tab-bar-close-tab-select 'left) |
| 335 | ;; Do nothing: the next tab will take | 338 | (1- i-tab)) |
| 336 | ;; the index of the closed tab | 339 | ((eq tab-bar-close-tab-select 'right) |
| 337 | i-tab) | 340 | ;; Do nothing: the next tab will take |
| 338 | (t 0))) | 341 | ;; the index of the closed tab |
| 339 | (tabs (delq tab tabs)) | 342 | i-tab) |
| 340 | (i-select (max 0 (min (1- (length tabs)) i-select))) | 343 | (t 0)))) |
| 341 | (select-tab (nth i-select tabs))) | 344 | (setq tabs (delq tab tabs) |
| 345 | i-select (max 0 (min (1- (length tabs)) i-select)) | ||
| 346 | select-tab (nth i-select tabs)))) | ||
| 342 | (set-frame-parameter nil 'tabs tabs) | 347 | (set-frame-parameter nil 'tabs tabs) |
| 343 | (tab-bar-select-tab select-tab))) | 348 | (tab-bar-select-tab select-tab))) |
| 344 | 349 | ||
| @@ -355,6 +360,245 @@ specified by `tab-bar-close-tab-select'." | |||
| 355 | (force-window-update)))) | 360 | (force-window-update)))) |
| 356 | 361 | ||
| 357 | 362 | ||
| 363 | ;;; Non-graphical access to frame-local tabs (named window configurations) | ||
| 364 | |||
| 365 | (defun make-tab () | ||
| 366 | "Create a new named window configuration without having to click a tab." | ||
| 367 | (interactive) | ||
| 368 | (tab-bar-add-tab) | ||
| 369 | (unless (and (display-graphic-p) tab-bar-mode) | ||
| 370 | (message "Added new tab with the current window configuration"))) | ||
| 371 | |||
| 372 | (defun delete-tab () | ||
| 373 | "Delete the current window configuration without clicking a close button." | ||
| 374 | (interactive) | ||
| 375 | (tab-bar-close-current-tab) | ||
| 376 | (unless (and (display-graphic-p) tab-bar-mode) | ||
| 377 | (message "Deleted the current tab"))) | ||
| 378 | |||
| 379 | (defalias 'list-tabs 'tab-bar-list) | ||
| 380 | |||
| 381 | (defun tab-bar-list () | ||
| 382 | "Display a list of named window configurations. | ||
| 383 | The list is displayed in the buffer `*Tabs*'. | ||
| 384 | |||
| 385 | In this list of window configurations you can delete or select them. | ||
| 386 | Type ? after invocation to get help on commands available. | ||
| 387 | Type q to remove the list of window configurations from the display. | ||
| 388 | |||
| 389 | The first column shows `D' for for a window configuration you have | ||
| 390 | marked for deletion." | ||
| 391 | (interactive) | ||
| 392 | (let ((dir default-directory) | ||
| 393 | (minibuf (minibuffer-selected-window))) | ||
| 394 | (let ((tab-bar-mode t)) ; don't enable tab-bar-mode if it's disabled | ||
| 395 | (tab-bar-add-tab)) | ||
| 396 | ;; Handle the case when it's called in the active minibuffer. | ||
| 397 | (when minibuf (select-window (minibuffer-selected-window))) | ||
| 398 | (delete-other-windows) | ||
| 399 | ;; Create a new window to replace the existing one, to not break the | ||
| 400 | ;; window parameters (e.g. prev/next buffers) of the window just saved | ||
| 401 | ;; to the window configuration. So when a saved window is restored, | ||
| 402 | ;; its parameters left intact. | ||
| 403 | (split-window) (delete-window) | ||
| 404 | (let ((switch-to-buffer-preserve-window-point nil)) | ||
| 405 | (switch-to-buffer (tab-bar-list-noselect))) | ||
| 406 | (setq default-directory dir)) | ||
| 407 | (message "Commands: d, x; RET; q to quit; ? for help.")) | ||
| 408 | |||
| 409 | (defun tab-bar-list-noselect () | ||
| 410 | "Create and return a buffer with a list of window configurations. | ||
| 411 | The list is displayed in a buffer named `*Tabs*'. | ||
| 412 | |||
| 413 | For more information, see the function `tab-bar-list'." | ||
| 414 | (let* ((tabs (delq nil (mapcar (lambda (tab) ; remove current tab | ||
| 415 | (unless (eq (car tab) 'current-tab) | ||
| 416 | tab)) | ||
| 417 | (tab-bar-tabs)))) | ||
| 418 | ;; Sort by recency | ||
| 419 | (tabs (sort tabs (lambda (a b) (< (cdr (assq 'time b)) | ||
| 420 | (cdr (assq 'time a))))))) | ||
| 421 | (with-current-buffer (get-buffer-create | ||
| 422 | (format " *Tabs*<%s>" (or (frame-parameter nil 'window-id) | ||
| 423 | (frame-parameter nil 'name)))) | ||
| 424 | (erase-buffer) | ||
| 425 | (tab-bar-list-mode) | ||
| 426 | (setq buffer-read-only nil) | ||
| 427 | ;; Vertical alignment to the center of the frame | ||
| 428 | (insert-char ?\n (/ (- (frame-height) (length tabs) 1) 2)) | ||
| 429 | ;; Horizontal alignment to the center of the frame | ||
| 430 | (setq tab-bar-list-column (- (/ (frame-width) 2) 15)) | ||
| 431 | (dolist (tab tabs) | ||
| 432 | (insert (propertize | ||
| 433 | (format "%s %s\n" | ||
| 434 | (make-string tab-bar-list-column ?\040) | ||
| 435 | (propertize | ||
| 436 | (cdr (assq 'name tab)) | ||
| 437 | 'mouse-face 'highlight | ||
| 438 | 'help-echo "mouse-2: select this window configuration")) | ||
| 439 | 'tab tab))) | ||
| 440 | (goto-char (point-min)) | ||
| 441 | (goto-char (or (next-single-property-change (point) 'tab) (point-min))) | ||
| 442 | (when (> (length tabs) 1) | ||
| 443 | (tab-bar-list-next-line)) | ||
| 444 | (move-to-column tab-bar-list-column) | ||
| 445 | (set-buffer-modified-p nil) | ||
| 446 | (current-buffer)))) | ||
| 447 | |||
| 448 | (defvar tab-bar-list-column 3) | ||
| 449 | (make-variable-buffer-local 'tab-bar-list-column) | ||
| 450 | |||
| 451 | (defvar tab-bar-list-mode-map | ||
| 452 | (let ((map (make-keymap))) | ||
| 453 | (suppress-keymap map t) | ||
| 454 | (define-key map "q" 'quit-window) | ||
| 455 | (define-key map "\C-m" 'tab-bar-list-select) | ||
| 456 | (define-key map "d" 'tab-bar-list-delete) | ||
| 457 | (define-key map "k" 'tab-bar-list-delete) | ||
| 458 | (define-key map "\C-d" 'tab-bar-list-delete-backwards) | ||
| 459 | (define-key map "\C-k" 'tab-bar-list-delete) | ||
| 460 | (define-key map "x" 'tab-bar-list-execute) | ||
| 461 | (define-key map " " 'tab-bar-list-next-line) | ||
| 462 | (define-key map "n" 'tab-bar-list-next-line) | ||
| 463 | (define-key map "p" 'tab-bar-list-prev-line) | ||
| 464 | (define-key map "\177" 'tab-bar-list-backup-unmark) | ||
| 465 | (define-key map "?" 'describe-mode) | ||
| 466 | (define-key map "u" 'tab-bar-list-unmark) | ||
| 467 | (define-key map [mouse-2] 'tab-bar-list-mouse-select) | ||
| 468 | (define-key map [follow-link] 'mouse-face) | ||
| 469 | map) | ||
| 470 | "Local keymap for `tab-bar-list-mode' buffers.") | ||
| 471 | |||
| 472 | (define-derived-mode tab-bar-list-mode nil "Window Configurations" | ||
| 473 | "Major mode for selecting a window configuration. | ||
| 474 | Each line describes one window configuration in Emacs. | ||
| 475 | Letters do not insert themselves; instead, they are commands. | ||
| 476 | \\<tab-bar-list-mode-map> | ||
| 477 | \\[tab-bar-list-mouse-select] -- select window configuration you click on. | ||
| 478 | \\[tab-bar-list-select] -- select current line's window configuration. | ||
| 479 | \\[tab-bar-list-delete] -- mark that window configuration to be deleted, and move down. | ||
| 480 | \\[tab-bar-list-delete-backwards] -- mark that window configuration to be deleted, and move up. | ||
| 481 | \\[tab-bar-list-execute] -- delete marked window configurations. | ||
| 482 | \\[tab-bar-list-unmark] -- remove all kinds of marks from current line. | ||
| 483 | With prefix argument, also move up one line. | ||
| 484 | \\[tab-bar-list-backup-unmark] -- back up a line and remove marks." | ||
| 485 | (setq truncate-lines t) | ||
| 486 | (setq buffer-read-only t)) | ||
| 487 | |||
| 488 | (defun tab-bar-list-current-tab (error-if-non-existent-p) | ||
| 489 | "Return window configuration described by this line of the list." | ||
| 490 | (let* ((where (save-excursion | ||
| 491 | (beginning-of-line) | ||
| 492 | (+ 2 (point) tab-bar-list-column))) | ||
| 493 | (tab (and (not (eobp)) (get-text-property where 'tab)))) | ||
| 494 | (or tab | ||
| 495 | (if error-if-non-existent-p | ||
| 496 | (user-error "No window configuration on this line") | ||
| 497 | nil)))) | ||
| 498 | |||
| 499 | |||
| 500 | (defun tab-bar-list-next-line (&optional arg) | ||
| 501 | (interactive) | ||
| 502 | (forward-line arg) | ||
| 503 | (beginning-of-line) | ||
| 504 | (move-to-column tab-bar-list-column)) | ||
| 505 | |||
| 506 | (defun tab-bar-list-prev-line (&optional arg) | ||
| 507 | (interactive) | ||
| 508 | (forward-line (- arg)) | ||
| 509 | (beginning-of-line) | ||
| 510 | (move-to-column tab-bar-list-column)) | ||
| 511 | |||
| 512 | (defun tab-bar-list-unmark (&optional backup) | ||
| 513 | "Cancel all requested operations on window configuration on this line and move down. | ||
| 514 | Optional prefix arg means move up." | ||
| 515 | (interactive "P") | ||
| 516 | (beginning-of-line) | ||
| 517 | (move-to-column tab-bar-list-column) | ||
| 518 | (let* ((buffer-read-only nil)) | ||
| 519 | (delete-char 1) | ||
| 520 | (insert " ")) | ||
| 521 | (forward-line (if backup -1 1)) | ||
| 522 | (move-to-column tab-bar-list-column)) | ||
| 523 | |||
| 524 | (defun tab-bar-list-backup-unmark () | ||
| 525 | "Move up and cancel all requested operations on window configuration on line above." | ||
| 526 | (interactive) | ||
| 527 | (forward-line -1) | ||
| 528 | (tab-bar-list-unmark) | ||
| 529 | (forward-line -1) | ||
| 530 | (move-to-column tab-bar-list-column)) | ||
| 531 | |||
| 532 | (defun tab-bar-list-delete (&optional arg) | ||
| 533 | "Mark window configuration on this line to be deleted by \\<tab-bar-list-mode-map>\\[tab-bar-list-execute] command. | ||
| 534 | Prefix arg is how many window configurations to delete. | ||
| 535 | Negative arg means delete backwards." | ||
| 536 | (interactive "p") | ||
| 537 | (let ((buffer-read-only nil)) | ||
| 538 | (if (or (null arg) (= arg 0)) | ||
| 539 | (setq arg 1)) | ||
| 540 | (while (> arg 0) | ||
| 541 | (delete-char 1) | ||
| 542 | (insert ?D) | ||
| 543 | (forward-line 1) | ||
| 544 | (setq arg (1- arg))) | ||
| 545 | (while (< arg 0) | ||
| 546 | (delete-char 1) | ||
| 547 | (insert ?D) | ||
| 548 | (forward-line -1) | ||
| 549 | (setq arg (1+ arg))) | ||
| 550 | (move-to-column tab-bar-list-column))) | ||
| 551 | |||
| 552 | (defun tab-bar-list-delete-backwards (&optional arg) | ||
| 553 | "Mark window configuration on this line to be deleted by \\<tab-bar-list-mode-map>\\[tab-bar-list-execute] command. | ||
| 554 | Then move up one line. Prefix arg means move that many lines." | ||
| 555 | (interactive "p") | ||
| 556 | (tab-bar-list-delete (- (or arg 1)))) | ||
| 557 | |||
| 558 | (defun tab-bar-list-delete-from-list (tab) | ||
| 559 | "Delete the window configuration from both lists." | ||
| 560 | (set-frame-parameter nil 'tabs (delq tab (tab-bar-tabs)))) | ||
| 561 | |||
| 562 | (defun tab-bar-list-execute () | ||
| 563 | "Delete window configurations marked with \\<tab-bar-list-mode-map>\\[tab-bar-list-delete] commands." | ||
| 564 | (interactive) | ||
| 565 | (save-excursion | ||
| 566 | (goto-char (point-min)) | ||
| 567 | (let ((buffer-read-only nil)) | ||
| 568 | (while (re-search-forward | ||
| 569 | (format "^%sD" (make-string tab-bar-list-column ?\040)) | ||
| 570 | nil t) | ||
| 571 | (forward-char -1) | ||
| 572 | (let ((tab (tab-bar-list-current-tab nil))) | ||
| 573 | (when tab | ||
| 574 | (tab-bar-list-delete-from-list tab) | ||
| 575 | (beginning-of-line) | ||
| 576 | (delete-region (point) (progn (forward-line 1) (point)))))))) | ||
| 577 | (beginning-of-line) | ||
| 578 | (move-to-column tab-bar-list-column) | ||
| 579 | (when tab-bar-mode | ||
| 580 | (force-window-update))) | ||
| 581 | |||
| 582 | (defun tab-bar-list-select () | ||
| 583 | "Select this line's window configuration. | ||
| 584 | This command deletes and replaces all the previously existing windows | ||
| 585 | in the selected frame." | ||
| 586 | (interactive) | ||
| 587 | (let* ((select-tab (tab-bar-list-current-tab t))) | ||
| 588 | (kill-buffer (current-buffer)) | ||
| 589 | ;; Delete the current window configuration | ||
| 590 | (tab-bar-close-current-tab nil select-tab) | ||
| 591 | ;; (tab-bar-select-tab select-tab) | ||
| 592 | )) | ||
| 593 | |||
| 594 | (defun tab-bar-list-mouse-select (event) | ||
| 595 | "Select the window configuration whose line you click on." | ||
| 596 | (interactive "e") | ||
| 597 | (set-buffer (window-buffer (posn-window (event-end event)))) | ||
| 598 | (goto-char (posn-point (event-end event))) | ||
| 599 | (tab-bar-list-select)) | ||
| 600 | |||
| 601 | |||
| 358 | (defvar ctl-x-6-map (make-sparse-keymap) | 602 | (defvar ctl-x-6-map (make-sparse-keymap) |
| 359 | "Keymap for tab commands.") | 603 | "Keymap for tab commands.") |
| 360 | (defalias 'ctl-x-6-prefix ctl-x-6-map) | 604 | (defalias 'ctl-x-6-prefix ctl-x-6-map) |
| @@ -385,6 +629,7 @@ Like \\[find-file-other-frame] (which see), but creates a new tab." | |||
| 385 | (switch-to-buffer-other-tab value)))) | 629 | (switch-to-buffer-other-tab value)))) |
| 386 | 630 | ||
| 387 | (define-key ctl-x-6-map "2" 'tab-bar-add-tab) | 631 | (define-key ctl-x-6-map "2" 'tab-bar-add-tab) |
| 632 | (define-key ctl-x-6-map "0" 'tab-bar-close-current-tab) | ||
| 388 | (define-key ctl-x-6-map "b" 'switch-to-buffer-other-tab) | 633 | (define-key ctl-x-6-map "b" 'switch-to-buffer-other-tab) |
| 389 | (define-key ctl-x-6-map "f" 'find-file-other-tab) | 634 | (define-key ctl-x-6-map "f" 'find-file-other-tab) |
| 390 | (define-key ctl-x-6-map "\C-f" 'find-file-other-tab) | 635 | (define-key ctl-x-6-map "\C-f" 'find-file-other-tab) |