From 584cff0bd213dbea0a489a9d5013cd3a06d034cb Mon Sep 17 00:00:00 2001 From: Madeleine Sydney Date: Mon, 17 Feb 2025 02:51:21 -0700 Subject: [PATCH] feat(emacs): Tab control - Removes default "implicit naming" behaviour. - Adds keybinds. --- users/crumb/programs/emacs/init.el | 1 + users/crumb/programs/emacs/lib/syd-prelude.el | 1 + .../crumb/programs/emacs/modules/syd-tabs.el | 66 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 users/crumb/programs/emacs/modules/syd-tabs.el diff --git a/users/crumb/programs/emacs/init.el b/users/crumb/programs/emacs/init.el index 00ab391..be6ec65 100755 --- a/users/crumb/programs/emacs/init.el +++ b/users/crumb/programs/emacs/init.el @@ -61,6 +61,7 @@ (require 'syd-projects) (require 'syd-scratch) (require 'syd-smartparens) +(require 'syd-tabs) (require 'syd-tooling) (require 'syd-tramp) (require 'syd-ui) diff --git a/users/crumb/programs/emacs/lib/syd-prelude.el b/users/crumb/programs/emacs/lib/syd-prelude.el index 00ba1a5..d44df25 100644 --- a/users/crumb/programs/emacs/lib/syd-prelude.el +++ b/users/crumb/programs/emacs/lib/syd-prelude.el @@ -87,6 +87,7 @@ KEYS is an alist of the parsed keywords." nil) (defmacro with-transient-after (hook-or-function &rest forms) + (declare (indent defun)) (let ((hook-name (gensym "transient-hook")) (hook-or-function* (gensym "hook-or-function"))) `(let* ((,hook-or-function* ,hook-or-function)) diff --git a/users/crumb/programs/emacs/modules/syd-tabs.el b/users/crumb/programs/emacs/modules/syd-tabs.el new file mode 100644 index 0000000..1696b42 --- /dev/null +++ b/users/crumb/programs/emacs/modules/syd-tabs.el @@ -0,0 +1,66 @@ +;;; syd-tabs.el -*- lexical-binding: t; -*- + +;; Many themes neglect tab-bar customisation, leaving it completely unstyled. +;; `vim-tab-bar' will style it to look like Vim's tab-bar, but more importantly, +;; it will colour it to match the current theme. +(use-package vim-tab-bar + :commands vim-tab-bar-mode) + +(with-eval-after-load 'tab-bar + (setq tab-bar-new-tab-to 'rightmost) + + (defun syd-tab-bar-make-free-name () + (cl-loop for i from 1 + with candidate = nil + do (setq candidate (format "#%d" i)) + while (tab-bar--tab-index-by-name candidate) + finally return candidate)) + + (defun syd-tab-bar-rename-tab (tab new-name) + ;; HACK: Using `tab-bar-rename-tab' doesn't work, for some reason. This + ;; relies on the internal representation of tabs. + (setf (alist-get 'name tab) new-name + (alist-get 'explicit-name tab) t)) + + (defvar syd-tab-bar-name-function #'syd-tab-bar-make-free-name + "Nullary function expected to return the name for a new tab.") + + (syd-add-hook 'tab-bar-tab-post-open-functions + (defun syd-tab-bar--name-new-tab-h (tab) + "Hooks to `tab-bar-tab-post-open-functions' such that new tabs will be +given \"explicit names\" that are static." + (syd-tab-bar-rename-tab tab (funcall syd-tab-bar-name-function))) + (defun syd-tab-bar--show-tab-bar-h (_tab) + "Show the tab-bar if it is not already visible. See +`syd-tab-bar-hide-tab-bar-h'." + (unless vim-tab-bar-mode + (vim-tab-bar-mode 1)))) + + (syd-add-hook 'tab-bar-tab-pre-close-functions + (defun syd-tab-bar-hide-tab-bar-h (_tab _final-tab-p) + "Hide the tab-bar when there is only a single tab to show. See +`syd-tab-bar--show-tab-bar-h'." + (when (<= (length (tab-bar-tabs)) 2) ; The tab hasn't yet been removed. + (tab-bar-mode -1)))) + + (defun syd-tab-bar-new-named-tab (name) + (interactive (list (read-string "Tab name: "))) + ;; Relies on `syd-tab-bar--name-new-tab-h'. + (let ((syd-tab-bar-name-function (lambda () name))) + (tab-bar-new-tab))) + + (general-def + :prefix-map 'syd-leader-tab-map + "[" #'tab-previous + "]" #'tab-next + "r" #'tab-rename + "R" #'tab-rename + "n" #'tab-new + "N" #'syd-tab-bar-new-named-tab + "d" #'tab-close) + (general-def + :keymaps 'syd-leader-map + "TAB" `("Tabs" . ,syd-leader-tab-map) + "" `("Tabs" . ,syd-leader-tab-map))) + +(provide 'syd-tabs)