emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master 6d67ebe: * frame-tabs: New package


From: Stefan Monnier
Subject: [elpa] master 6d67ebe: * frame-tabs: New package
Date: Fri, 29 Jun 2018 20:59:09 -0400 (EDT)

branch: master
commit 6d67ebe7f958426d34019cd3a901ff4654a357d7
Author: Stefan Monnier <address@hidden>
Commit: Stefan Monnier <address@hidden>

    * frame-tabs: New package
---
 packages/frame-tabs/frame-tabs.el | 512 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 512 insertions(+)

diff --git a/packages/frame-tabs/frame-tabs.el 
b/packages/frame-tabs/frame-tabs.el
new file mode 100644
index 0000000..5b3471f
--- /dev/null
+++ b/packages/frame-tabs/frame-tabs.el
@@ -0,0 +1,512 @@
+;;; frame-tabs.el --- show buffer tabs in side window  -*- lexical-binding:t 
-*-
+
+;; Copyright (C) 2013-2018  Free Software Foundation, Inc.
+
+;; Author: Martin Rudalics <address@hidden>
+;; Keywords: frames, tabs
+;; Version: 1.1
+
+;; frame-tabs.el is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; frame-tabs.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Minor mode to display buffer tabs in a side window on each frame.
+
+;; This mode shows, in a side window on each frame, tabs listing the
+;; names of all live buffers that have been displayed on that frame.
+;; Clicking on a tab with the left mouse button switches to the
+;; corresponding buffer in a window.  Clicking on a tab with the right
+;; mouse button dismisses the buffer.  See 'frame-tabs-map'.
+;; Alternatively, tabs can display an additional 'x' button which
+;; permits to dismiss buffers with the left mouse button.  See
+;; 'frame-tabs-x' and 'frame-tabs-x-map'.
+
+;; Caveats: Many desirable features are either underdeveloped or
+;; simply don't work.  Navigating tabs windows with the keyboard has
+;; not been implemented; neither has been displaying alternative items
+;; like tabs representing window configurations.  You're welcome to
+;; expand existing and/or add new features at your like.
+
+;;; Code:
+(defgroup frame-tabs nil
+  "Frame tabs."
+  :version "26.1"
+  :group 'convenience
+  :group 'frames)
+
+;; Customizable faces
+(defface frame-tabs-buffer-tab
+  '((t :inherit variable-pitch
+       :box (:line-width 2 :color "grey72")
+       :foreground "black"
+       :background "grey84"))
+  "Basic frame tabs buffer tab face.
+This face is used for buffer tabs and is inherited by all other
+frame tabs faces."
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defface frame-tabs-selected-tab
+  '((t :inherit frame-tabs-buffer-tab
+       :background "pink"))
+  "Frame tabs face for selected window's buffer tab.
+This is the face used for the tab corresponding to the buffer
+currently shown in the selected window."
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defface frame-tabs-higlight-tab
+  '((t :inherit frame-tabs-buffer-tab
+       :foreground "white"
+       :background "green3"))
+  "Frame tabs face for highlighting buffer tabs.
+This is the face used when the mouse cursor hovers over a buffer
+tab."
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defface frame-tabs-x-tab
+  '((t :inherit frame-tabs-buffer-tab
+       :bold t))
+  "Frame tabs face for 'x' buttons."
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defface frame-tabs-x-higlight-tab
+  '((t :inherit frame-tabs-item-tab
+       :foreground "white"
+       :background "red3"))
+  "Frame tabs face for highlighting 'x' buttons.
+This face is used when the mouse cursor hovers over an 'x'
+button."
+  :version "26.1"
+  :group 'frame-tabs)
+
+;; Options
+(defun frame-tabs--set-value (symbol value)
+  "Helper function for customizing frame tabs."
+  (set-default symbol value)
+  (when frame-tabs-mode
+    (frame-tabs-mode -1))
+  (frame-tabs-mode 1))
+
+(defcustom frame-tabs-side 'top
+  "Side of frame where tabs windows are located.
+Choices are 'top' (the default), 'bottom', 'left' and 'right'."
+  :type '(choice (const top)
+                 (const bottom)
+                 (const left)
+                 (const right))
+  :initialize 'custom-initialize-default
+  :set 'frame-tabs--set-value
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defcustom frame-tabs-x nil
+  "Non-nil means frame tabs show an 'x' button for each buffer tab.
+The 'x' button serves to dismiss the corresponding buffer in
+various ways."
+  :type 'boolean
+  :initialize 'custom-initialize-default
+  :set 'frame-tabs--set-value
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defcustom frame-tabs-min-size 1
+  "Mimimum size of frame tabs windows.
+For tabs windows at the top or bottom of a frame this is their
+minimum number of lines.  For tabs windows at the left or right
+of a frame this is their minimum number of columns.
+
+This value may be overridden when the major side window showing
+the frame tabs window contains other windows."
+  :type 'integer
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defcustom frame-tabs-max-size 6
+  "Maximum size of frame tabs windows.
+For tabs windows at the top or bottom of a frame this is their
+maximum number of lines.  For tabs windows at the left or right
+of a frame this is their maximum number of columns.
+
+This value may be overridden when the major side window showing
+the frame tabs window contains other windows."
+  :type 'integer
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defcustom frame-tabs-delay 0.0
+  "Frame tabs update interval, in seconds.
+This is the time Emacs waits before updating frame tabs windows."
+  :type 'float
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defun frame-tabs-default-filter (buffer _frame)
+  "Default filter function for frame tabs."
+  (let ((name (buffer-name buffer)))
+    (unless (eq (aref name 0) ?\s)
+      name)))
+
+(defcustom frame-tabs-filter-function 'frame-tabs-default-filter
+  "Filter function for frame tabs.
+This is a function that takes two arguments - a buffer and a
+frame.  If this function returns nil, no tab will be shown for
+the buffer in the frame's tab window.  Otherwise, this function
+must return a string and the frame's tabs window will display
+that string as the buffer's tab.
+
+The default excludes buffers whose name starts with a space."
+  :type 'function
+  :version "26.1"
+  :group 'frame-tabs)
+
+(defun frame-tabs-default-buffer-list (frame)
+  "Default frame tabs function for getting a buffer list for FRAME."
+  (buffer-list frame))
+
+(defcustom frame-tabs-buffer-list 'frame-tabs-default-buffer-list
+  "Function for returning a buffer list for frame tabs.
+This is a function that takes one argument - a frame - and
+returns a buffer list for that frame.  The default is to call
+`buffer-list' for that frame which means to return the frame's
+local buffer list.  Customizing this option allows, for example,
+to return the fundamental buffer list or a list of buffer in
+alphabetical order of their names instead."
+  :type 'function
+  :version "26.1"
+  :group 'frame-tabs)
+
+;; Internal variables and functions
+(defvar frame-tabs-timer nil
+  "Frame tabs idle timer.")
+
+(defvar frame-tabs-run nil
+  "Non-nil while frame tabs runs its idle timer function.")
+
+(defvar frame-tabs-buffers nil
+  "List of frame tabs buffers.")
+
+(defvar frame-tabs-map
+  (let ((map (make-sparse-keymap)))
+    ;; Buffer switching commands.
+    (define-key map [down-mouse-1] 'frame-tabs-switch-to-buffer)
+    (define-key map [mouse-1] 'ignore)
+    (define-key map [C-down-mouse-1] 'frame-tabs-switch-to-buffer-other-window)
+    (define-key map [C-mouse-1] 'ignore)
+    (define-key map [M-down-mouse-1] 'frame-tabs-switch-to-buffer-other-frame)
+    (define-key map [M-mouse-1] 'ignore)
+    ;; Buffer dismissal commands.
+    (define-key map [down-mouse-3] 'frame-tabs-bury-buffer)
+    (define-key map [mouse-3] 'ignore)
+    (define-key map [C-down-mouse-3] 'frame-tabs-replace-buffer)
+    (define-key map [C-mouse-3] 'ignore)
+    (define-key map [M-down-mouse-3] 'frame-tabs-kill-buffer)
+    (define-key map [M-mouse-3] 'ignore)
+    map)
+  "Frame tabs keymap.")
+
+(defvar frame-tabs-x-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [down-mouse-1] 'frame-tabs-bury-buffer)
+    (define-key map [mouse-1] 'ignore)
+    (define-key map [C-down-mouse-1] 'frame-tabs-replace-buffer)
+    (define-key map [C-mouse-1] 'ignore)
+    (define-key map [M-down-mouse-1] 'frame-tabs-kill-buffer)
+    (define-key map [M-mouse-1] 'ignore)
+    map)
+  "Frame tabs 'x' keymap.
+This keymap is used when frame tabs show an 'x' button via
+`frame-tabs-x'.")
+
+(defun frame-tabs--window (&optional frame)
+  "Return or create a tabs window for FRAME.
+FRAME must be a live frame and defaults to the selected one."
+  (setq frame (window-normalize-frame frame))
+  (let ((window-resize-pixelwise t)
+       (tabs-vertical (memq frame-tabs-side '(top bottom)))
+       tabs-buffer tabs-window tabs-point tabs-selected)
+    ;; `tabs-buffer' is the buffer showing tabs on FRAME and
+    ;; `tabs-window' is its window.
+    (unless (catch 'found
+             (walk-window-tree
+              (lambda (window)
+                (let ((buffer (window-buffer window)))
+                  (when (memq buffer frame-tabs-buffers)
+                    (setq tabs-buffer buffer)
+                    (setq tabs-window window)
+                    (if (eq tabs-window (selected-window))
+                        (with-current-buffer tabs-buffer
+                          (setq tabs-selected
+                                (get-text-property (point) 'buffer)))
+                      (setq tabs-selected (window-buffer)))
+                    (throw 'found t))))))
+      (setq tabs-buffer (generate-new-buffer " *tabs*"))
+      (setq frame-tabs-buffers (cons tabs-buffer frame-tabs-buffers))
+      ;; Set `tabs-selected' to the truly selected window.
+      (setq tabs-selected (window-buffer))
+      (with-current-buffer tabs-buffer
+       ;; Make a tabs window.
+       (setq tabs-window
+             (display-buffer-in-side-window
+              tabs-buffer `((side . ,frame-tabs-side)
+                            (,(if tabs-vertical 'window-width 'window-height)
+                             . ,frame-tabs-min-size))))))
+    ;; Display tabs in the window.
+    (when tabs-window
+      (with-current-buffer tabs-buffer
+       (setq mode-line-format nil)
+       (setq header-line-format nil)
+       (setq buffer-read-only nil)
+       (erase-buffer)
+       (dolist (buffer (funcall frame-tabs-buffer-list frame))
+         (let ((name (funcall frame-tabs-filter-function buffer frame)))
+           (when name
+             (insert
+              (propertize
+               name
+               'buffer buffer
+               'keymap frame-tabs-map
+               'face (if (eq buffer tabs-selected)
+                         (progn
+                           (setq tabs-point (point))
+                           'frame-tabs-selected-tab)
+                       'frame-tabs-buffer-tab)
+               'mouse-face 'frame-tabs-higlight-tab))
+             (when frame-tabs-x
+               (insert
+                (propertize
+                 "×" 'buffer buffer ; U-00D7
+                 ;;          "⌧" 'buffer buffer ; U-2327
+                 'keymap frame-tabs-x-map
+                 'face 'frame-tabs-x-tab
+                 'mouse-face 'frame-tabs-x-higlight-tab)))
+             ;; We'd like to use "​" (U-200B) instead of " " in the
+             ;; following form but the display-engine word-wraps only at
+             ;; spaces or tabs so use a display specification instead.
+             ;; Note that we can't use :width either since it would make
+             ;; the spaces of the last item on each line extend to the
+             ;; end of that line.
+             (insert
+              (if tabs-vertical
+                  ;; "​"
+                  (propertize " " 'display '(space . (:relative-width 0.1)))
+                "\n")))))
+       ;; Delete very last space or newline inserted.
+       (when (memq (char-before) '(?\s ?\n)) (delete-char -1))
+       (when tabs-vertical
+         ;; Make sure word wrapping takes care of buffer tabs.
+         (set (make-local-variable 'truncate-lines) nil)
+         (set (make-local-variable 'truncate-partial-width-windows) nil)
+         (set (make-local-variable 'word-wrap) t))
+       ;; Handle window.
+       (set-window-margins tabs-window 0 0)
+       (set-window-fringes tabs-window 0 0)
+       (set-window-scroll-bars tabs-window 0)
+       (setq window-size-fixed nil)
+       ;; We have bound 'window-resize-pixelwise' to t to make sure
+       ;; the following call handles the boxed face for our tabs as
+       ;; expected.
+       (fit-window-to-buffer
+        tabs-window frame-tabs-max-size frame-tabs-min-size)
+       (if tabs-vertical
+           (setq window-size-fixed 'height)
+         (setq window-size-fixed 'width))
+       (set-window-start tabs-window (point-min))
+       (setq cursor-type nil)
+       (set-window-parameter tabs-window 'no-other-window t)
+       (set (make-local-variable 'transient-mark-mode) nil)
+       (set-window-point
+        tabs-window
+        (or tabs-point (point-min))))
+      (set-window-dedicated-p tabs-window t))
+    tabs-window))
+
+(defun frame-tabs--update ()
+  "Update frame tabs after timer fires."
+  (let ((selected-window (selected-window))
+       buffer-list-update-hook)
+    (unwind-protect
+       (progn
+         (setq frame-tabs-run t)
+         ;; Sanitize frame tabs buffers.
+         (dolist (buffer frame-tabs-buffers)
+           (cond
+            ((not (buffer-live-p buffer))
+             (setq frame-tabs-buffers
+                   (delq buffer frame-tabs-buffers)))
+            ((not (get-buffer-window buffer t))
+             (setq frame-tabs-buffers
+                   (delq buffer frame-tabs-buffers))
+             (kill-buffer buffer))))
+         ;; Provide tabs window on each frame.  Exclude
+         ;; minibuffer-only and unsplittable frames.
+         (dolist (frame (frame-list))
+           (unless (or (eq (frame-parameter frame 'minibuffer) 'only)
+                       (frame-parameter frame 'unsplittable))
+             (frame-tabs--window frame))))
+      (select-window selected-window)
+      (setq frame-tabs-run nil)
+      (cancel-timer frame-tabs-timer))))
+
+(defun frame-tabs--enable-timer ()
+  "Schedule updating frame tabs."
+  (unless frame-tabs-run
+    (when (timerp frame-tabs-timer)
+      (cancel-timer frame-tabs-timer))
+    (setq frame-tabs-timer
+         (run-with-idle-timer frame-tabs-delay t 'frame-tabs--update))))
+
+(defun frame-tabs--remove ()
+  "Remove frame tabs window from each frame."
+  (condition-case nil
+      (progn
+       (setq frame-tabs-run t)
+       (cancel-timer frame-tabs-timer)
+       (dolist (buffer frame-tabs-buffers)
+         (when (buffer-live-p buffer)
+           (delete-windows-on buffer t))
+         (kill-buffer buffer))
+       (setq frame-tabs-buffers nil)
+       (setq frame-tabs-run nil)
+       (cancel-timer frame-tabs-timer))
+    (error nil)))
+
+(defun frame-tabs--window-configuration-change ()
+  "Run timer when a window configuration changes."
+  (frame-tabs--enable-timer))
+
+(defun frame-tabs--buffer-list-update ()
+  "Run timer when the buffer list has been updated."
+  (frame-tabs--enable-timer))
+
+(defun frame-tabs--window-size-change (frame)
+  "Run timer when the root window of FRAME changes size."
+  (let ((root (frame-root-window frame)))
+    (when (or (and (memq frame-tabs-side '(top bottom))
+                  (not (= (window-pixel-width-before-size-change root)
+                          (window-pixel-width root))))
+             (and (memq frame-tabs-side '(left right))
+                  (not (= (window-pixel-height-before-size-change root)
+                          (window-pixel-height root)))))
+      (frame-tabs--enable-timer))))
+
+;; Commands
+(defun frame-tabs--switch-to-buffer (event &optional where)
+  "Switch to buffer of tab clicked on.
+EVENT is the original event associated with the click.  WHERE is
+the location where the switch shall take place."
+  (let* ((tabs-window (posn-window (event-end event)))
+        (tabs-buffer (window-buffer tabs-window))
+        buffer)
+    (with-current-buffer tabs-buffer
+      (save-excursion
+       (goto-char (posn-point (event-end event)))
+       (setq buffer (get-text-property (point) 'buffer))
+       (when (eq tabs-window (selected-window))
+         (select-window (get-mru-window 0 nil t)))
+       (cond
+        ((not (buffer-live-p buffer))
+         (message "Not a live buffer %s" buffer))
+        ((eq where 'other-window)
+         (switch-to-buffer-other-window buffer))
+        ((eq where 'other-frame)
+         (switch-to-buffer-other-frame buffer))
+        (t
+         (switch-to-buffer buffer)))))))
+
+(defun frame-tabs-switch-to-buffer (event)
+  "In selected window switch to buffer of tab clicked on."
+  (interactive "e")
+  (frame-tabs--switch-to-buffer event))
+
+(defun frame-tabs-switch-to-buffer-other-window (event)
+  "In other window switch to buffer of tab clicked on."
+  (interactive "e")
+  (frame-tabs--switch-to-buffer event 'other-window))
+
+(defun frame-tabs-switch-to-buffer-other-frame (event)
+  "In other frame switch to buffer of tab clicked on."
+  (interactive "e")
+  (frame-tabs--switch-to-buffer event 'other-frame))
+
+(defun frame-tabs--dismiss-buffer (event &optional how)
+  "Dismiss buffer of tab clicked on.
+EVENT is the original event associated with the click.  HOW is
+the type of dismissal chosen."
+  (let* ((tabs-window (posn-window (event-end event)))
+        (tabs-buffer (window-buffer tabs-window))
+        buffer)
+    (with-current-buffer tabs-buffer
+      (save-excursion
+       (goto-char (posn-point (event-end event)))
+       (setq buffer (get-text-property (point) 'buffer))))
+    (when (eq tabs-window (selected-window))
+      (select-window (get-mru-window 0 nil t)))
+    (cond
+     ((eq how 'replace)
+      (replace-buffer-in-windows buffer))
+     ((eq how 'kill)
+      (kill-buffer buffer))
+     ((eq (window-buffer) buffer)
+      ;; Must not call this with BUFFER as argument!
+      (bury-buffer))
+     (t
+      (bury-buffer buffer)))))
+
+(defun frame-tabs-bury-buffer (event)
+  "Bury buffer of tab clicked on."
+  (interactive "e")
+  (frame-tabs--dismiss-buffer event))
+
+(defun frame-tabs-replace-buffer (event)
+  "Replace buffer of tab clicked on in all windows showing it."
+  (interactive "e")
+  (frame-tabs--dismiss-buffer event 'replace))
+
+(defun frame-tabs-kill-buffer (event)
+  "Kill buffer of tab clicked on."
+  (interactive "e")
+  (frame-tabs--dismiss-buffer event 'kill))
+
+;;;###autoload
+(define-minor-mode frame-tabs-mode
+  "Toggle display of a buffer tabs side window on each frame.
+With a prefix argument ARG, enable this mode if ARG is positive,
+and disable it otherwise.  If called from Lisp, enable the mode
+if ARG is omitted or nil.
+
+When this mode is enabled, every normal frame is equipped with a
+side window showing tabs for all buffers that appeared on that
+frame."
+  :global t
+  :group 'frame-tabs
+  :init-value nil
+  :link '(emacs-commentary-link "frame-tabs.el")
+  (if frame-tabs-mode
+      (progn
+       (add-hook 'buffer-list-update-hook 'frame-tabs--buffer-list-update 
'append)
+       (add-hook 'window-configuration-change-hook 
'frame-tabs--window-configuration-change 'append)
+       (add-hook 'window-size-change-functions 'frame-tabs--window-size-change 
'append)
+       (frame-tabs--enable-timer)
+       (frame-tabs--update))
+    (remove-hook 'buffer-list-update-hook 'frame-tabs--buffer-list-update)
+    (remove-hook 'window-configuration-change-hook 
'frame-tabs--window-configuration-change)
+    (remove-hook 'window-size-change-functions 'frame-tabs--window-size-change)
+    (frame-tabs--remove)))
+
+(provide 'frame-tabs)
+
+;;; frame-tabs.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]