diff options
| author | Juri Linkov | 2019-10-23 00:17:27 +0300 |
|---|---|---|
| committer | Juri Linkov | 2019-10-23 00:17:27 +0300 |
| commit | 095908aec4a6a414c13385c429e559a73d787ae0 (patch) | |
| tree | 17a647338ae239d643d288b483dabe56809957f2 | |
| parent | f342f9cd267d39b3ef7d0c2a58718f4ac62f5abd (diff) | |
| download | emacs-095908aec4a6a414c13385c429e559a73d787ae0.tar.gz emacs-095908aec4a6a414c13385c429e559a73d787ae0.zip | |
Tab-line horizontal scrolling with UI buttons and commands (bug#37667)
* etc/images/tabs/left-arrow.xpm:
* etc/images/tabs/right-arrow.xpm: New images.
* lisp/tab-line.el (tab-line-left-map, tab-line-right-map): New keymaps.
(tab-line-left-button, tab-line-right-button): New variables.
(tab-line-tab-name-function): Turn defvar into defcustom.
(tab-line-tab-name-buffer): New function.
(tab-line-tab-name-truncated-buffer): Rename from tab-line-tab-name.
(tab-line-tabs-limit): Default to nil.
(tab-line-tabs): Behavior depends on tab-line-tabs-limit.
(tab-line-format): Use window-parameter tab-line-hscroll.
Add left/right buttons.
(tab-line-hscroll): New function.
(tab-line-hscroll-right, tab-line-hscroll-left): New commands
bound to mouse-wheel. Rebind tab-switching commands to mouse-wheel
with Ctrl-modifier.
| -rw-r--r-- | etc/images/tabs/README | 2 | ||||
| -rw-r--r-- | etc/images/tabs/left-arrow.xpm | 16 | ||||
| -rw-r--r-- | etc/images/tabs/right-arrow.xpm | 16 | ||||
| -rw-r--r-- | lisp/tab-line.el | 185 |
4 files changed, 175 insertions, 44 deletions
diff --git a/etc/images/tabs/README b/etc/images/tabs/README index 1e9f4e5b595..ac549cf4bdf 100644 --- a/etc/images/tabs/README +++ b/etc/images/tabs/README | |||
| @@ -2,7 +2,7 @@ This directory contains icons for the Tabs user interface. | |||
| 2 | 2 | ||
| 3 | COPYRIGHT AND LICENSE INFORMATION FOR IMAGE FILES | 3 | COPYRIGHT AND LICENSE INFORMATION FOR IMAGE FILES |
| 4 | 4 | ||
| 5 | Files: close.xpm new.xpm | 5 | Files: close.xpm new.xpm left-arrow.xpm right-arrow.xpm |
| 6 | Author: Juri Linkov <juri@linkov.net> | 6 | Author: Juri Linkov <juri@linkov.net> |
| 7 | Copyright (C) 2019 Free Software Foundation, Inc. | 7 | Copyright (C) 2019 Free Software Foundation, Inc. |
| 8 | License: GNU General Public License version 3 or later (see COPYING) | 8 | License: GNU General Public License version 3 or later (see COPYING) |
diff --git a/etc/images/tabs/left-arrow.xpm b/etc/images/tabs/left-arrow.xpm new file mode 100644 index 00000000000..f133cd62173 --- /dev/null +++ b/etc/images/tabs/left-arrow.xpm | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | /* XPM */ | ||
| 2 | static char * left_arrow_xpm[] = { | ||
| 3 | "9 9 4 1", | ||
| 4 | " c None", | ||
| 5 | ". c #BFBFBF", | ||
| 6 | "+ c #000000", | ||
| 7 | "@ c #808080", | ||
| 8 | ".........", | ||
| 9 | ".....+@..", | ||
| 10 | "....+@...", | ||
| 11 | "...+@....", | ||
| 12 | "..+@.....", | ||
| 13 | "...+@....", | ||
| 14 | "....+@...", | ||
| 15 | ".....+@..", | ||
| 16 | "........."}; | ||
diff --git a/etc/images/tabs/right-arrow.xpm b/etc/images/tabs/right-arrow.xpm new file mode 100644 index 00000000000..ab1f1a099f1 --- /dev/null +++ b/etc/images/tabs/right-arrow.xpm | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | /* XPM */ | ||
| 2 | static char * right_arrow_xpm[] = { | ||
| 3 | "9 9 4 1", | ||
| 4 | " c None", | ||
| 5 | ". c #BFBFBF", | ||
| 6 | "+ c #808080", | ||
| 7 | "@ c #000000", | ||
| 8 | ".........", | ||
| 9 | "..+@.....", | ||
| 10 | "...+@....", | ||
| 11 | "....+@...", | ||
| 12 | ".....+@..", | ||
| 13 | "....+@...", | ||
| 14 | "...+@....", | ||
| 15 | "..+@.....", | ||
| 16 | "........."}; | ||
diff --git a/lisp/tab-line.el b/lisp/tab-line.el index 58f648c2827..7dc6e2b6d04 100644 --- a/lisp/tab-line.el +++ b/lisp/tab-line.el | |||
| @@ -113,6 +113,22 @@ | |||
| 113 | map) | 113 | map) |
| 114 | "Local keymap to close `tab-line-mode' window tabs.") | 114 | "Local keymap to close `tab-line-mode' window tabs.") |
| 115 | 115 | ||
| 116 | (defvar tab-line-left-map | ||
| 117 | (let ((map (make-sparse-keymap))) | ||
| 118 | (define-key map [tab-line mouse-1] 'tab-line-hscroll-left) | ||
| 119 | (define-key map [tab-line mouse-2] 'tab-line-hscroll-left) | ||
| 120 | (define-key map "\C-m" 'tab-line-new-tab) | ||
| 121 | map) | ||
| 122 | "Local keymap to scroll `tab-line-mode' window tabs to the left.") | ||
| 123 | |||
| 124 | (defvar tab-line-right-map | ||
| 125 | (let ((map (make-sparse-keymap))) | ||
| 126 | (define-key map [tab-line mouse-1] 'tab-line-hscroll-right) | ||
| 127 | (define-key map [tab-line mouse-2] 'tab-line-hscroll-right) | ||
| 128 | (define-key map "\C-m" 'tab-line-new-tab) | ||
| 129 | map) | ||
| 130 | "Local keymap to scroll `tab-line-mode' window tabs to the right.") | ||
| 131 | |||
| 116 | 132 | ||
| 117 | (defcustom tab-line-new-tab-choice t | 133 | (defcustom tab-line-new-tab-choice t |
| 118 | "Defines what to show in a new tab. | 134 | "Defines what to show in a new tab. |
| @@ -164,22 +180,60 @@ If nil, don't show it at all." | |||
| 164 | 'help-echo "Click to close tab") | 180 | 'help-echo "Click to close tab") |
| 165 | "Button for closing the clicked tab.") | 181 | "Button for closing the clicked tab.") |
| 166 | 182 | ||
| 183 | (defvar tab-line-left-button | ||
| 184 | (propertize " <" | ||
| 185 | 'display `(image :type xpm | ||
| 186 | :file "tabs/left-arrow.xpm" | ||
| 187 | :margin (2 . 0) | ||
| 188 | :ascent center) | ||
| 189 | 'keymap tab-line-left-map | ||
| 190 | 'mouse-face 'tab-line-highlight | ||
| 191 | 'help-echo "Click to scroll left") | ||
| 192 | "Button for scrolling horizontally to the left.") | ||
| 193 | |||
| 194 | (defvar tab-line-right-button | ||
| 195 | (propertize "> " | ||
| 196 | 'display `(image :type xpm | ||
| 197 | :file "tabs/right-arrow.xpm" | ||
| 198 | :margin (2 . 0) | ||
| 199 | :ascent center) | ||
| 200 | 'keymap tab-line-right-map | ||
| 201 | 'mouse-face 'tab-line-highlight | ||
| 202 | 'help-echo "Click to scroll right") | ||
| 203 | "Button for scrolling horizontally to the right.") | ||
| 204 | |||
| 167 | (defvar tab-line-separator nil) | 205 | (defvar tab-line-separator nil) |
| 168 | 206 | ||
| 169 | (defvar tab-line-tab-name-ellipsis | 207 | (defvar tab-line-tab-name-ellipsis |
| 170 | (if (char-displayable-p ?…) "…" "...")) | 208 | (if (char-displayable-p ?…) "…" "...")) |
| 171 | 209 | ||
| 172 | 210 | ||
| 173 | (defvar tab-line-tab-name-function #'tab-line-tab-name | 211 | (defcustom tab-line-tab-name-function #'tab-line-tab-name-buffer |
| 174 | "Function to get a tab name. | 212 | "Function to get a tab name. |
| 175 | Function gets two arguments: tab to get name for and a list of tabs | 213 | Function gets two arguments: tab to get name for and a list of tabs |
| 176 | to display. By default, use function `tab-line-tab-name'.") | 214 | to display. By default, use function `tab-line-tab-name'." |
| 215 | :type '(choice (const :tag "Buffer name" | ||
| 216 | tab-line-tab-name-buffer) | ||
| 217 | (const :tag "Truncated buffer name" | ||
| 218 | tab-line-tab-name-truncated-buffer) | ||
| 219 | (function :tag "Function")) | ||
| 220 | :initialize 'custom-initialize-default | ||
| 221 | :set (lambda (sym val) | ||
| 222 | (set-default sym val) | ||
| 223 | (force-mode-line-update)) | ||
| 224 | :group 'tab-line | ||
| 225 | :version "27.1") | ||
| 177 | 226 | ||
| 178 | (defun tab-line-tab-name (buffer &optional buffers) | 227 | (defun tab-line-tab-name-buffer (buffer &optional _buffers) |
| 179 | "Generate tab name from BUFFER. | 228 | "Generate tab name from BUFFER. |
| 180 | Reduce tab width proportionally to space taken by other tabs. | 229 | Reduce tab width proportionally to space taken by other tabs. |
| 181 | This function can be overridden by changing the default value of the | 230 | This function can be overridden by changing the default value of the |
| 182 | variable `tab-line-tab-name-function'." | 231 | variable `tab-line-tab-name-function'." |
| 232 | (buffer-name buffer)) | ||
| 233 | |||
| 234 | (defun tab-line-tab-name-truncated-buffer (buffer &optional buffers) | ||
| 235 | "Generate tab name from BUFFER. | ||
| 236 | Reduce tab width proportionally to space taken by other tabs." | ||
| 183 | (let ((tab-name (buffer-name buffer)) | 237 | (let ((tab-name (buffer-name buffer)) |
| 184 | (limit (when buffers | 238 | (limit (when buffers |
| 185 | (max 1 (- (/ (window-width) (length buffers)) 3))))) | 239 | (max 1 (- (/ (window-width) (length buffers)) 3))))) |
| @@ -189,8 +243,9 @@ variable `tab-line-tab-name-function'." | |||
| 189 | tab-line-tab-name-ellipsis) | 243 | tab-line-tab-name-ellipsis) |
| 190 | 'help-echo tab-name)))) | 244 | 'help-echo tab-name)))) |
| 191 | 245 | ||
| 192 | (defvar tab-line-tabs-limit 15 | 246 | (defvar tab-line-tabs-limit nil |
| 193 | "Maximum number of buffer tabs displayed in the tab line.") | 247 | "Maximum number of buffer tabs displayed in the tab line. |
| 248 | If nil, no limit.") | ||
| 194 | 249 | ||
| 195 | (defvar tab-line-tabs-function #'tab-line-tabs | 250 | (defvar tab-line-tabs-function #'tab-line-tabs |
| 196 | "Function to get a list of tabs to display in the tab line. | 251 | "Function to get a list of tabs to display in the tab line. |
| @@ -213,53 +268,91 @@ variable `tab-line-tabs-function'." | |||
| 213 | (mapcar #'car (window-prev-buffers window)))) | 268 | (mapcar #'car (window-prev-buffers window)))) |
| 214 | (prev-buffers (seq-filter #'buffer-live-p prev-buffers)) | 269 | (prev-buffers (seq-filter #'buffer-live-p prev-buffers)) |
| 215 | ;; Remove next-buffers from prev-buffers | 270 | ;; Remove next-buffers from prev-buffers |
| 216 | (prev-buffers (seq-difference prev-buffers next-buffers)) | 271 | (prev-buffers (seq-difference prev-buffers next-buffers))) |
| 217 | (half-limit (/ tab-line-tabs-limit 2)) | 272 | (if (natnump tab-line-tabs-limit) |
| 218 | (prev-buffers-limit | 273 | (let* ((half-limit (/ tab-line-tabs-limit 2)) |
| 219 | (if (> (length prev-buffers) half-limit) | 274 | (prev-buffers-limit |
| 220 | (if (> (length next-buffers) half-limit) | 275 | (if (> (length prev-buffers) half-limit) |
| 221 | half-limit | 276 | (if (> (length next-buffers) half-limit) |
| 222 | (+ half-limit (- half-limit (length next-buffers)))) | 277 | half-limit |
| 223 | (length prev-buffers))) | 278 | (+ half-limit (- half-limit (length next-buffers)))) |
| 224 | (next-buffers-limit | 279 | (length prev-buffers))) |
| 225 | (- tab-line-tabs-limit prev-buffers-limit)) | 280 | (next-buffers-limit |
| 226 | (buffer-tabs | 281 | (- tab-line-tabs-limit prev-buffers-limit))) |
| 227 | (append (reverse (seq-take prev-buffers prev-buffers-limit)) | 282 | (append (reverse (seq-take prev-buffers prev-buffers-limit)) |
| 228 | (list buffer) | 283 | (list buffer) |
| 229 | (seq-take next-buffers next-buffers-limit)))) | 284 | (seq-take next-buffers next-buffers-limit))) |
| 230 | buffer-tabs)) | 285 | (append (reverse prev-buffers) |
| 286 | (list buffer) | ||
| 287 | next-buffers)))) | ||
| 231 | 288 | ||
| 232 | (defun tab-line-format () | 289 | (defun tab-line-format () |
| 233 | "Template for displaying tab line for selected window." | 290 | "Template for displaying tab line for selected window." |
| 234 | (let* ((window (selected-window)) | 291 | (let* ((window (selected-window)) |
| 235 | (selected-buffer (window-buffer window)) | 292 | (selected-buffer (window-buffer window)) |
| 236 | (tabs (funcall tab-line-tabs-function)) | 293 | (tabs (funcall tab-line-tabs-function)) |
| 237 | (separator (or tab-line-separator (if window-system " " "|")))) | 294 | (separator (or tab-line-separator (if window-system " " "|"))) |
| 295 | (hscroll (window-parameter nil 'tab-line-hscroll)) | ||
| 296 | (strings | ||
| 297 | (mapcar | ||
| 298 | (lambda (tab) | ||
| 299 | (concat | ||
| 300 | separator | ||
| 301 | (apply 'propertize | ||
| 302 | (concat (propertize | ||
| 303 | (funcall tab-line-tab-name-function tab tabs) | ||
| 304 | 'keymap tab-line-tab-map) | ||
| 305 | (or (and tab-line-close-button-show | ||
| 306 | (not (eq tab-line-close-button-show | ||
| 307 | (if (eq tab selected-buffer) | ||
| 308 | 'non-selected | ||
| 309 | 'selected))) | ||
| 310 | tab-line-close-button) "")) | ||
| 311 | `( | ||
| 312 | tab ,tab | ||
| 313 | face ,(if (eq tab selected-buffer) | ||
| 314 | 'tab-line-tab | ||
| 315 | 'tab-line-tab-inactive) | ||
| 316 | mouse-face tab-line-highlight)))) | ||
| 317 | tabs))) | ||
| 238 | (append | 318 | (append |
| 239 | (mapcar | 319 | (list separator |
| 240 | (lambda (tab) | 320 | (when (and (natnump hscroll) (> hscroll 0)) |
| 241 | (concat | 321 | tab-line-left-button) |
| 242 | separator | 322 | (when (if (natnump hscroll) |
| 243 | (apply 'propertize (concat (propertize | 323 | (< hscroll (1- (length strings))) |
| 244 | (funcall tab-line-tab-name-function tab tabs) | 324 | (> (length strings) 1)) |
| 245 | 'keymap tab-line-tab-map) | 325 | tab-line-right-button)) |
| 246 | (or (and tab-line-close-button-show | 326 | (if hscroll (nthcdr hscroll strings) strings) |
| 247 | (not (eq tab-line-close-button-show | ||
| 248 | (if (eq tab selected-buffer) | ||
| 249 | 'non-selected | ||
| 250 | 'selected))) | ||
| 251 | tab-line-close-button) "")) | ||
| 252 | `( | ||
| 253 | tab ,tab | ||
| 254 | face ,(if (eq tab selected-buffer) | ||
| 255 | 'tab-line-tab | ||
| 256 | 'tab-line-tab-inactive) | ||
| 257 | mouse-face tab-line-highlight)))) | ||
| 258 | tabs) | ||
| 259 | (list (concat separator (when tab-line-new-tab-choice | 327 | (list (concat separator (when tab-line-new-tab-choice |
| 260 | tab-line-new-button)))))) | 328 | tab-line-new-button)))))) |
| 261 | 329 | ||
| 262 | 330 | ||
| 331 | (defun tab-line-hscroll (&optional arg window) | ||
| 332 | (let* ((hscroll (window-parameter window 'tab-line-hscroll)) | ||
| 333 | (tabs (if window | ||
| 334 | (with-selected-window window (funcall tab-line-tabs-function)) | ||
| 335 | (funcall tab-line-tabs-function)))) | ||
| 336 | (set-window-parameter | ||
| 337 | window 'tab-line-hscroll | ||
| 338 | (max 0 (min (+ (or hscroll 0) (or arg 1)) | ||
| 339 | (1- (length tabs))))) | ||
| 340 | (when window | ||
| 341 | (force-mode-line-update t)))) | ||
| 342 | |||
| 343 | (defun tab-line-hscroll-right (&optional arg mouse-event) | ||
| 344 | (interactive (list current-prefix-arg last-nonmenu-event)) | ||
| 345 | (let ((window (and (listp mouse-event) (posn-window (event-start mouse-event))))) | ||
| 346 | (tab-line-hscroll arg window) | ||
| 347 | (force-mode-line-update window))) | ||
| 348 | |||
| 349 | (defun tab-line-hscroll-left (&optional arg mouse-event) | ||
| 350 | (interactive (list current-prefix-arg last-nonmenu-event)) | ||
| 351 | (let ((window (and (listp mouse-event) (posn-window (event-start mouse-event))))) | ||
| 352 | (tab-line-hscroll (- (or arg 1)) window) | ||
| 353 | (force-mode-line-update window))) | ||
| 354 | |||
| 355 | |||
| 263 | (defun tab-line-new-tab (&optional mouse-event) | 356 | (defun tab-line-new-tab (&optional mouse-event) |
| 264 | "Add a new tab to the tab line. | 357 | "Add a new tab to the tab line. |
| 265 | Usually is invoked by clicking on the plus-shaped button. | 358 | Usually is invoked by clicking on the plus-shaped button. |
| @@ -316,6 +409,7 @@ Its effect is the same as using the `next-buffer' command | |||
| 316 | (switch-to-next-buffer | 409 | (switch-to-next-buffer |
| 317 | (and (listp mouse-event) (posn-window (event-start mouse-event))))) | 410 | (and (listp mouse-event) (posn-window (event-start mouse-event))))) |
| 318 | 411 | ||
| 412 | |||
| 319 | (defcustom tab-line-close-tab-action 'bury-buffer | 413 | (defcustom tab-line-close-tab-action 'bury-buffer |
| 320 | "Defines what to do on closing the tab. | 414 | "Defines what to do on closing the tab. |
| 321 | If `bury-buffer', put the tab's buffer at the end of the list of all | 415 | If `bury-buffer', put the tab's buffer at the end of the list of all |
| @@ -359,10 +453,15 @@ from the tab line." | |||
| 359 | '(:eval (tab-line-format))))) | 453 | '(:eval (tab-line-format))))) |
| 360 | 454 | ||
| 361 | 455 | ||
| 362 | (global-set-key [tab-line mouse-4] 'tab-line-switch-to-prev-tab) | 456 | (global-set-key [tab-line mouse-4] 'tab-line-hscroll-left) |
| 363 | (global-set-key [tab-line mouse-5] 'tab-line-switch-to-next-tab) | 457 | (global-set-key [tab-line mouse-5] 'tab-line-hscroll-right) |
| 364 | (global-set-key [tab-line wheel-up] 'tab-line-switch-to-prev-tab) | 458 | (global-set-key [tab-line wheel-up] 'tab-line-hscroll-left) |
| 365 | (global-set-key [tab-line wheel-down] 'tab-line-switch-to-next-tab) | 459 | (global-set-key [tab-line wheel-down] 'tab-line-hscroll-right) |
| 460 | |||
| 461 | (global-set-key [tab-line C-mouse-4] 'tab-line-switch-to-prev-tab) | ||
| 462 | (global-set-key [tab-line C-mouse-5] 'tab-line-switch-to-next-tab) | ||
| 463 | (global-set-key [tab-line C-wheel-up] 'tab-line-switch-to-prev-tab) | ||
| 464 | (global-set-key [tab-line C-wheel-down] 'tab-line-switch-to-next-tab) | ||
| 366 | 465 | ||
| 367 | 466 | ||
| 368 | (provide 'tab-line) | 467 | (provide 'tab-line) |