aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuri Linkov2019-09-01 22:16:18 +0300
committerJuri Linkov2019-09-01 22:16:18 +0300
commit7edb95454999d28e4f8d1b1cc042e3c98bb0961b (patch)
tree29683198df7237f5b88d0e60f89f2502e4e9f0e2
parent3e0ad29a607c8c085de3b74c7505e417ad7f9062 (diff)
downloademacs-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/NEWS8
-rw-r--r--lisp/tab-bar.el295
2 files changed, 278 insertions, 25 deletions
diff --git a/etc/NEWS b/etc/NEWS
index fe49f8f348b..28a844c5478 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1887,11 +1887,19 @@ good replacement, even in very large source files.
1887to switch named persistent window configurations in it using tabs. 1887to switch named persistent window configurations in it using tabs.
1888New tab-based keybindings (similar to frame-based): 1888New 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
1896Also it's possible to switch named persistent window configurations
1897without having graphical access to the tab-bar, even on a tty
1898or 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
1896switch buffers in it to previous/next buffers. Selecting a previous 1904switch buffers in it to previous/next buffers. Selecting a previous
1897window-local tab is the same as running 'C-x <left>' (previous-buffer), 1905window-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.
319After closing the current tab switch to the tab 322After closing the current tab switch to the tab
320specified by `tab-bar-close-tab-select'." 323specified by `tab-bar-close-tab-select', or to `select-tab'
321 (interactive 324if 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.
383The list is displayed in the buffer `*Tabs*'.
384
385In this list of window configurations you can delete or select them.
386Type ? after invocation to get help on commands available.
387Type q to remove the list of window configurations from the display.
388
389The first column shows `D' for for a window configuration you have
390marked 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.
411The list is displayed in a buffer named `*Tabs*'.
412
413For 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.
474Each line describes one window configuration in Emacs.
475Letters 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.
514Optional 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.
534Prefix arg is how many window configurations to delete.
535Negative 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.
554Then 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.
584This command deletes and replaces all the previously existing windows
585in 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)