Switch from persp to built-in tab-bar
Might as well turn on desktop-save as well, as that seems nicer than reapplying a state and hitting the recentf file list.
This commit is contained in:
		
							parent
							
								
									bd69943337
								
							
						
					
					
						commit
						9a67d92054
					
				
					 10 changed files with 188 additions and 220 deletions
				
			
		| 
						 | 
				
			
			@ -220,7 +220,8 @@ The following /defines/ the rest of my org-mode literate files, that I load late
 | 
			
		|||
                                  "ha-org-publishing.org"
 | 
			
		||||
                                  "ha-email.org"
 | 
			
		||||
                                  "ha-aux-apps.org"))
 | 
			
		||||
                             "ha-dashboard.org"))
 | 
			
		||||
                             ;; "ha-dashboard.org"
 | 
			
		||||
                             ))
 | 
			
		||||
    "List of org files that complete the hamacs project.")
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,7 +41,7 @@ I would like a dedicate perspective to Mastodon, and I would like a leader key s
 | 
			
		|||
#+begin_src emacs-lisp
 | 
			
		||||
  (use-package mastodon
 | 
			
		||||
    :config
 | 
			
		||||
    (ha-leader "a m" `("mastodon" . ,(ha-app-perspective "mastodon" #'mastodon)))
 | 
			
		||||
    (ha-leader "a m" `("mastodon" . ,(ha-tab-bar-new "mastodon" #'mastodon)))
 | 
			
		||||
 | 
			
		||||
    (defun ha-mastodon-scroll-or-more ()
 | 
			
		||||
      "Scroll a window, and at the end, get more entries in timeline."
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +175,7 @@ I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be
 | 
			
		|||
    (when (fboundp 'evil-insert-state)
 | 
			
		||||
      (add-hook 'telega-chat-mode-hook 'evil-insert-state))
 | 
			
		||||
 | 
			
		||||
    (ha-leader "a t" `("telega" . ,(ha-app-perspective "telega" #'telega))))
 | 
			
		||||
    (ha-leader "a t" `("telega" . ,(ha-tab-bar-new "telega" #'telega))))
 | 
			
		||||
#+end_src
 | 
			
		||||
For some reason, you need [[https://github.com/Fanael/rainbow-identifiers][rainbow-identifiers]] to work, oh, I guess the docs state this.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										344
									
								
								ha-config.org
									
									
									
									
									
								
							
							
						
						
									
										344
									
								
								ha-config.org
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -926,14 +926,13 @@ Since I wasn’t using all the features that [[https://github.com/bbatsov/projec
 | 
			
		|||
    (ha-leader
 | 
			
		||||
      "p"  '(:ignore t :which-key "projects")
 | 
			
		||||
      "p W" '("initialize workspace" . ha-workspace-initialize)
 | 
			
		||||
      "p n" '("new project space" . ha-project-persp)
 | 
			
		||||
      "p p" '("switch project" . ha-tab-bar-new-project)
 | 
			
		||||
 | 
			
		||||
      "p !" '("run cmd in project root" . project-shell-command)
 | 
			
		||||
      "p &" '("run cmd async" . project-async-shell-command)
 | 
			
		||||
      "p a" '("add new project" . project-remember-projects-under)
 | 
			
		||||
      "p d" '("dired" . project-dired)
 | 
			
		||||
      "p k" '("kill project buffers" . project-kill-buffers)
 | 
			
		||||
      "p p" '("switch project" . project-switch-project)
 | 
			
		||||
      "p x" '("remove known project" . project-forget-project)
 | 
			
		||||
 | 
			
		||||
      "p f" '("find file" . project-find-file)
 | 
			
		||||
| 
						 | 
				
			
			@ -947,205 +946,174 @@ Since I wasn’t using all the features that [[https://github.com/bbatsov/projec
 | 
			
		|||
      "p s" '("project shell" . project-shell)))
 | 
			
		||||
#+end_src
 | 
			
		||||
** Workspaces
 | 
			
		||||
A /workspace/ (at least to me) requires a quick jump to a collection of buffer windows organized around a project or task. For this, I'm basing my work on the [[https://github.com/nex3/perspective-el][perspective.el]] project.
 | 
			
		||||
A /workspace/ (at least to me) requires a quick jump to a collection of buffer windows organized around a project or task. Later versions of Emacs use [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][Tab Bars]] which group windows and buffers in a perspective. The code that follows is a Poor Person’s Workspace package. Also let’s dive into the end section of [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Mickey Petersen's essay]] on the subject.
 | 
			
		||||
 | 
			
		||||
I build a Hydra to dynamically list the current projects as well as select the project.
 | 
			
		||||
To do this, we need a way to generate a string of the perspectives in alphabetical order:
 | 
			
		||||
Couple notes:
 | 
			
		||||
  - Function, =tab-bar-switch-to-tab=, switches or /creates/ a tab. We will always use this.
 | 
			
		||||
  - We can switch to a tab by number with =tab-bar-select-tab=
 | 
			
		||||
 | 
			
		||||
#+BEGIN_SRC emacs-lisp
 | 
			
		||||
  (setq tab-bar-show 1                       ; hide bar if <= 1 tabs open
 | 
			
		||||
        tab-bar-close-button-show nil        ; hide tab close / X button
 | 
			
		||||
        tab-bar-new-tab-choice "*dashboard*" ; buffer to show in new tabs
 | 
			
		||||
        tab-bar-tab-hints t                  ; show tab numbers
 | 
			
		||||
 | 
			
		||||
        ;; Jump to a tab by numbers (see the keybindings set later):
 | 
			
		||||
        tab-bar-select-tab-modifiers '(super control))
 | 
			
		||||
#+END_SRC
 | 
			
		||||
 | 
			
		||||
I’ve struggled to /programmatically/ create sane workspaces, so let’s just save them off:
 | 
			
		||||
 | 
			
		||||
#+BEGIN_SRC emacs-lisp
 | 
			
		||||
  (desktop-save-mode 1)
 | 
			
		||||
#+END_SRC
 | 
			
		||||
 | 
			
		||||
New workspace is a tab with a specific name that opens up a specific buffer or application. My motive for such a complicated function allows me to pre-create tabs with already running applications or files.
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha--persp-label (num names)
 | 
			
		||||
    "Return string of numbered elements.
 | 
			
		||||
  NUM is the starting number and NAMES is a list of strings."
 | 
			
		||||
    (when names
 | 
			
		||||
      (concat
 | 
			
		||||
       (format "  %d: %s%s"    ; Shame that the following doesn't work:
 | 
			
		||||
               num             ; (propertize (number-to-string num) :foreground "#00a0")
 | 
			
		||||
               (car names)     ; Nor does surrounding the number with underbars.
 | 
			
		||||
 | 
			
		||||
               (if (equal (car names) (persp-name (persp-curr))) "*" ""))
 | 
			
		||||
       (ha--persp-label (1+ num) (cdr names)))))
 | 
			
		||||
 | 
			
		||||
  (defun ha-persp-labels ()
 | 
			
		||||
    "Return a string of numbered elements from a list of names."
 | 
			
		||||
    (ha--persp-label 1 (sort (hash-table-keys (perspectives-hash)) 's-less?)))
 | 
			
		||||
  (defun ha-tab-bar-new (name &optional bff)
 | 
			
		||||
    "Create a new tab with a NAME.
 | 
			
		||||
  With a non-nil IFF, call IFF as a function or switch
 | 
			
		||||
  to the IFF buffer or  the files listed."
 | 
			
		||||
    (interactive "sWorkspace Name: ")
 | 
			
		||||
    (tab-bar-switch-to-tab name)
 | 
			
		||||
    (when bff
 | 
			
		||||
      (cond
 | 
			
		||||
       ((listp bff) (find-file (car bff))
 | 
			
		||||
        (dolist (f (cdr bff))
 | 
			
		||||
          (split-window-right)
 | 
			
		||||
          (find-file f)))
 | 
			
		||||
       ((fboundp bff) (call-interactively bff))
 | 
			
		||||
       ((bufferp bff) (switch-to-buffer bff)))))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
Build the hydra as well as configure the =perspective= project.
 | 
			
		||||
With a new tab group for a directory or probably a project, let’s see if we can load the most useful files.
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (use-package perspective
 | 
			
		||||
    :custom
 | 
			
		||||
    (persp-modestring-short t)
 | 
			
		||||
    (persp-show-modestring t)
 | 
			
		||||
 | 
			
		||||
    :config
 | 
			
		||||
    (setq persp-suppress-no-prefix-key-warning t)
 | 
			
		||||
 | 
			
		||||
    (persp-mode)
 | 
			
		||||
 | 
			
		||||
    (defhydra hydra-workspace-leader (:color blue :hint nil) "
 | 
			
		||||
    Workspaces- %s(ha-persp-labels)
 | 
			
		||||
    _n_: new project  _r_: rename    _a_: add buffer     _l_: load worksp
 | 
			
		||||
    _]_: next worksp  _d_: delete    _b_: goto buffer    _s_: save worksp
 | 
			
		||||
    _[_: previous     _W_: init all  _k_: remove buffer  _`_: to last worksp "
 | 
			
		||||
      ("TAB" persp-switch-quick)
 | 
			
		||||
      ("RET" persp-switch)
 | 
			
		||||
      ("`" persp-switch-last)
 | 
			
		||||
      ("1" (persp-switch-by-number 1))
 | 
			
		||||
      ("2" (persp-switch-by-number 2))
 | 
			
		||||
      ("3" (persp-switch-by-number 3))
 | 
			
		||||
      ("4" (persp-switch-by-number 4))
 | 
			
		||||
      ("5" (persp-switch-by-number 5))
 | 
			
		||||
      ("6" (persp-switch-by-number 6))
 | 
			
		||||
      ("7" (persp-switch-by-number 7))
 | 
			
		||||
      ("8" (persp-switch-by-number 8))
 | 
			
		||||
      ("9" (persp-switch-by-number 9))
 | 
			
		||||
      ("0" (persp-switch-by-number 0))
 | 
			
		||||
      ("n" ha-project-persp)
 | 
			
		||||
      ("N" persp-switch)
 | 
			
		||||
      ("]" persp-next :color pink)
 | 
			
		||||
      ("[" persp-prev :color pink)
 | 
			
		||||
      ("d" persp-kill)
 | 
			
		||||
      ("W" ha-workspace-initialize)
 | 
			
		||||
      ("a" persp-add-buffer)
 | 
			
		||||
      ("b" persp-switch-to-buffer)
 | 
			
		||||
      ("k" persp-remove-buffer)
 | 
			
		||||
      ("K" persp-kill-buffer)
 | 
			
		||||
      ("m" persp-merge)
 | 
			
		||||
      ("u" persp-unmerge)
 | 
			
		||||
      ("i" persp-import)
 | 
			
		||||
      ("r" persp-rename)
 | 
			
		||||
      ("s" persp-state-save)
 | 
			
		||||
      ("l" persp-state-load)
 | 
			
		||||
      ("w" ha-switch-to-special)  ; The most special perspective
 | 
			
		||||
      ("q" nil)
 | 
			
		||||
      ("C-g" nil)))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
Let’s give it a binding:
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (ha-leader "TAB" '("workspaces" . hydra-workspace-leader/body))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
When called, it /can/ look like:
 | 
			
		||||
 | 
			
		||||
[[file:screenshots/projects-hydra.png]]
 | 
			
		||||
 | 
			
		||||
The /special/ perspective is a nice shortcut to the one I use the most:
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha-switch-to-special ()
 | 
			
		||||
    "Change to the projects perspective."
 | 
			
		||||
    (interactive)
 | 
			
		||||
    (persp-switch "projects"))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
I often want a workspace dedicated to an /application/, so this function:
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha-app-perspective (name func)
 | 
			
		||||
    "Generate new perspective NAME, automatically running FUNC."
 | 
			
		||||
    (lambda ()
 | 
			
		||||
      (interactive)
 | 
			
		||||
      (let ((already-started? (seq-contains-p (persp-names) name 'equal)))
 | 
			
		||||
        (persp-switch name)
 | 
			
		||||
        (unless already-started?
 | 
			
		||||
          (call-interactively func)))))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
And I can then use it like:
 | 
			
		||||
#+begin_src emacs-lisp :tangle no
 | 
			
		||||
  (ha-leader "a x" `("to foobar" . ,(ha-app-perspective "foobar" #'foobar)))
 | 
			
		||||
#+end_src
 | 
			
		||||
*** Predefined Workspaces
 | 
			
		||||
Let's describe a list of startup project workspaces. This way, I don't need the clutter of the recent state, but also get back to a state of mental normality.
 | 
			
		||||
Granted, this list is essentially a list of projects that I'm currently developing, so I expect this to change often.
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defvar ha-workspace-projects-personal nil "List of default projects with a name.")
 | 
			
		||||
 | 
			
		||||
  (add-to-list 'ha-workspace-projects-personal
 | 
			
		||||
               '("projects" "~/projects" ("breathe.org" "tasks.org")))
 | 
			
		||||
  (add-to-list 'ha-workspace-projects-personal
 | 
			
		||||
               '("personal" "~/personal" ("general.org")))
 | 
			
		||||
  (add-to-list 'ha-workspace-projects-personal
 | 
			
		||||
               '("technical" "~/technical" ("ansible.org")))
 | 
			
		||||
  (add-to-list 'ha-workspace-projects-personal
 | 
			
		||||
               '("hamacs" "~/src/hamacs" ("README.org" "ha-config.org")))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
Given a list of information about project-workspaces, can we create them all?
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha-persp-exists? (name)
 | 
			
		||||
    "Return non-nill if a perspective of NAME exists."
 | 
			
		||||
    (when (fboundp 'perspectives-hash)
 | 
			
		||||
      (seq-contains (hash-table-keys (perspectives-hash)) name)))
 | 
			
		||||
 | 
			
		||||
  (defun ha-workspace-initialize (&optional projects)
 | 
			
		||||
    "Precreate workspace projects from a PROJECTS list.
 | 
			
		||||
  Each entry in the list is a list containing:
 | 
			
		||||
      - name (as a string)
 | 
			
		||||
      - project root directory
 | 
			
		||||
      - a optional list of files to display"
 | 
			
		||||
    (interactive)
 | 
			
		||||
    (unless projects
 | 
			
		||||
      (setq projects ha-workspace-projects-personal))
 | 
			
		||||
 | 
			
		||||
    (dolist (project projects)
 | 
			
		||||
      (seq-let (name root files) project
 | 
			
		||||
        (unless (ha-persp-exists? name)
 | 
			
		||||
          (message "Creating workspace: %s (from %s)" name root)
 | 
			
		||||
          (ha-project-persp root name files))))
 | 
			
		||||
    (persp-switch "main"))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
Often, but not always, I want a perspective based on an actual Git repository, e.g. a project. Emacs calls these transients.
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha-project-persp (project &optional name files)
 | 
			
		||||
    "Create a new perspective, and then switch to the PROJECT.
 | 
			
		||||
  If NAME is not given, then figure it out based on the name of the
 | 
			
		||||
  PROJECT. If FILES aren't specified, then see if there is a
 | 
			
		||||
  README. Otherwise, pull up Dired."
 | 
			
		||||
    (interactive (list (completing-read "Project: "
 | 
			
		||||
                                        (project-known-project-roots))))
 | 
			
		||||
    (when (f-directory-p project)
 | 
			
		||||
      (unless name
 | 
			
		||||
        (setq name (f-filename project)))
 | 
			
		||||
      (persp-switch name)
 | 
			
		||||
 | 
			
		||||
      (let ((recent-files (thread-last recentf-list
 | 
			
		||||
                                       (--filter (s-starts-with? project it))
 | 
			
		||||
                                       (-take 3)))
 | 
			
		||||
            (readme-org (f-join project "README.org"))
 | 
			
		||||
            (readme-md  (f-join project "README.md"))
 | 
			
		||||
            (readme-rst (f-join project "README.rst")))
 | 
			
		||||
  (defun ha-tab-bar-new-default ()
 | 
			
		||||
    "Given a new perspective, display some buffer windows.
 | 
			
		||||
  The choice of files to display depends on a combination of READMEs and
 | 
			
		||||
  most recently viewed files in the project. This function assumes the
 | 
			
		||||
  variable `default-directory' contains the root of the project."
 | 
			
		||||
    (cl-flet ((one-win (file) (find-file file))
 | 
			
		||||
              (two-win (left right)
 | 
			
		||||
                (find-file right)
 | 
			
		||||
                (split-window-right)
 | 
			
		||||
                (find-file left))
 | 
			
		||||
              (in-project (file)
 | 
			
		||||
                (string-match (rx bos (literal default-directory))
 | 
			
		||||
                              (expand-file-name file))))
 | 
			
		||||
      (let* ((recent-files (seq-filter #'in-project recentf-list))
 | 
			
		||||
             (recent (car recent-files))
 | 
			
		||||
             (readme-org (expand-file-name "README.org"))
 | 
			
		||||
             (readme-md  (expand-file-name "README.md")))
 | 
			
		||||
        (cond
 | 
			
		||||
         (files                  (ha--project-show-files project files))
 | 
			
		||||
         (recent-files           (ha--project-show-files project recent-files))
 | 
			
		||||
         ((f-exists? readme-org) (find-file readme-org))
 | 
			
		||||
         ((f-exists? readme-md)  (find-file readme-md))
 | 
			
		||||
         ((f-exists? readme-rst) (find-file readme-rst))
 | 
			
		||||
         (t                      (dired project))))))
 | 
			
		||||
         ;; ORG + recent
 | 
			
		||||
         ((and (file-exists-p recent) (file-exists-p readme-org))
 | 
			
		||||
          (two-win readme-org recent))
 | 
			
		||||
         ;; MD + recent
 | 
			
		||||
         ((and (file-exists-p recent) (file-exists-p readme-md))
 | 
			
		||||
          (two-win readme-md recent))
 | 
			
		||||
         ;; recent-only
 | 
			
		||||
         ((file-exists-p recent)
 | 
			
		||||
          (one-win recent))
 | 
			
		||||
         ;; ORG only
 | 
			
		||||
         ((file-exists-p readme-org)
 | 
			
		||||
          (one-win readme-org))
 | 
			
		||||
         ;; MD only
 | 
			
		||||
         ((file-exists-p readme-md)
 | 
			
		||||
          (one-win readme-md))))))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
When starting a new perspective, and I specify more than one file, this function splits the window horizontally for each file.
 | 
			
		||||
Create a new tab associated with a project:
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha--project-show-files (root files)
 | 
			
		||||
    "Display a list of FILES in a project ROOT directory.
 | 
			
		||||
  Each file gets its own window (so don't make the list of files
 | 
			
		||||
  long)."
 | 
			
		||||
    (when files
 | 
			
		||||
      (let ((default-directory root)
 | 
			
		||||
            (file (car files))
 | 
			
		||||
            (more (cdr files)))
 | 
			
		||||
        (message "Loading files from %s ... %s and %s" root file more)
 | 
			
		||||
        (when (f-exists? file)
 | 
			
		||||
          (find-file file))
 | 
			
		||||
        (when more
 | 
			
		||||
          (split-window-horizontally)
 | 
			
		||||
          (ha--project-show-files root more)))))
 | 
			
		||||
  (defun ha-tab-bar-new-project (project-dir)
 | 
			
		||||
    "Create a new tab/workspace based on a project.
 | 
			
		||||
  The project is defined by the PROJECT-DIR directory."
 | 
			
		||||
    (interactive (list (completing-read "Project: " (project-known-project-roots))))
 | 
			
		||||
    (let ((name (project-name (project-current nil project-dir)))
 | 
			
		||||
          (default-directory project-dir))
 | 
			
		||||
      (ha-tab-bar-new name)
 | 
			
		||||
      (project-switch-project project-dir)
 | 
			
		||||
      (ha-tab-bar-new-default)))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
If we close a tab that is a project, we want to close all the buffers associated with it. I wouldn’t do this if it wasn’t so easy to re-create them:
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (defun ha-tab-bar-delete (tab-name)
 | 
			
		||||
    "Delete a tab, TAB-NAME, and all buffers associated with it."
 | 
			
		||||
    (interactive
 | 
			
		||||
     (list (completing-read "Close tab by name: "
 | 
			
		||||
                            (mapcar (lambda (tab)
 | 
			
		||||
                                      (alist-get 'name tab))
 | 
			
		||||
                                    (funcall tab-bar-tabs-function)))))
 | 
			
		||||
    (dolist (buf (ha-tab-bar-buffers tab-name))
 | 
			
		||||
      (kill-buffer buf))
 | 
			
		||||
    (tab-bar-close-tab-by-name tab-name))
 | 
			
		||||
 | 
			
		||||
  (defun ha-tab-bar-buffers (tab-name)
 | 
			
		||||
    "Return list of buffers associated with TAB-NAME."
 | 
			
		||||
    (seq-filter (lambda (b)
 | 
			
		||||
                  (thread-last b
 | 
			
		||||
                               (tab-bar-get-buffer-tab)
 | 
			
		||||
                               (alist-get 'name)
 | 
			
		||||
                               (string-equal tab-name)))
 | 
			
		||||
                (buffer-list)))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
And some shortcut keys from the =general= project:
 | 
			
		||||
 | 
			
		||||
#+BEGIN_SRC emacs-lisp
 | 
			
		||||
  (general-nmap :prefix "SPC"
 | 
			
		||||
    "<tab>"  '(:ignore t :which-key "workspaces")
 | 
			
		||||
    "<tab> <tab>" '("switch" . tab-switch)
 | 
			
		||||
    "<tab> p" '("new project" . ha-tab-bar-new-project)
 | 
			
		||||
    "<tab> n" '("new space" . ha-tab-bar-new)
 | 
			
		||||
    "<tab> d" '("delete space" . ha-tab-bar-delete))
 | 
			
		||||
 | 
			
		||||
  (global-set-key (kbd "s-C-t") 'ha-tab-bar-new)
 | 
			
		||||
  (global-set-key (kbd "s-C-[") 'tab-bar-switch-to-prev-tab)
 | 
			
		||||
  (global-set-key (kbd "s-C-]") 'tab-bar-switch-to-next-tab)
 | 
			
		||||
 | 
			
		||||
  (tab-bar-mode 1)
 | 
			
		||||
#+END_SRC
 | 
			
		||||
 | 
			
		||||
I want to quickly jump, by the number shown on the tab, to that grouping. The following two functions create leader sequences with the name of the tab group:
 | 
			
		||||
 | 
			
		||||
#+BEGIN_SRC emacs-lisp
 | 
			
		||||
  (defun ha-tab-update-names ()
 | 
			
		||||
    "Create normal-mode keybindings for the tab groupings.
 | 
			
		||||
  This creates `SPC TAB 1' to jump to the first tab, etc."
 | 
			
		||||
    ;; Remove all previously created keybindings:
 | 
			
		||||
    (ignore-errors
 | 
			
		||||
      (dolist (indx (number-sequence 1 9))
 | 
			
		||||
        (general-nmap :prefix "SPC" (format "<tab> %d" indx) nil)))
 | 
			
		||||
 | 
			
		||||
    ;; Loop through the existing tabs, create keys for each:
 | 
			
		||||
    (seq-do-indexed 'ha-tab-update-tab-keybinding (tab-bar-tabs)))
 | 
			
		||||
 | 
			
		||||
  (defun ha-tab-update-tab-keybinding (tab-deets indx)
 | 
			
		||||
    "Create a keybinding to jump to tab described by TAB-DEETS.
 | 
			
		||||
  The key sequence, `SPC' `TAB' then INDX."
 | 
			
		||||
    (let ((name (alist-get 'name tab-deets)))
 | 
			
		||||
      (general-nmap :prefix "SPC"
 | 
			
		||||
        (format "<tab> %d" (1+ indx))
 | 
			
		||||
        `(,name .
 | 
			
		||||
              (lambda () (interactive) (tab-bar-select-tab ,(1+ indx)))))))
 | 
			
		||||
#+END_SRC
 | 
			
		||||
 | 
			
		||||
Any time I create or delete a new tab, we can call =ha-tab-update-names=:
 | 
			
		||||
 | 
			
		||||
#+BEGIN_SRC emacs-lisp
 | 
			
		||||
  (advice-add #'tab-bar-new-tab :after #'ha-tab-update-names)
 | 
			
		||||
  (advice-add #'tab-bar-close-tab :after #'ha-tab-update-names)
 | 
			
		||||
  (advice-add #'tab-bar-close-other-tabs :after #'ha-tab-update-names)
 | 
			
		||||
 | 
			
		||||
  (add-hook desktop-after-read-hook #'ha-tab-update-names)
 | 
			
		||||
#+END_SRC
 | 
			
		||||
 | 
			
		||||
* Pretty Good Encryption
 | 
			
		||||
For details on using GnuPG in Emacs, see Mickey Petersen’s [[https://www.masteringemacs.org/article/keeping-secrets-in-emacs-gnupg-auth-sources][GnuPG Essay]].
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -169,12 +169,11 @@ The [[https://github.com/emacs-dashboard/emacs-dashboard][emacs-dashboard]] proj
 | 
			
		|||
          dashboard-set-heading-icons t
 | 
			
		||||
          dashboard-footer-messages (list (ha--dad-joke)))
 | 
			
		||||
 | 
			
		||||
    :config
 | 
			
		||||
    (dashboard-setup-startup-hook)
 | 
			
		||||
 | 
			
		||||
    ;; Real shame that :config is incompatible with :hook, otherwise:
 | 
			
		||||
    ;; :hook (dashboard-after-initialize . ha-dashboard)
 | 
			
		||||
    (add-hook 'dashboard-after-initialize-hook 'ha-dashboard))
 | 
			
		||||
 | 
			
		||||
    :config
 | 
			
		||||
    (dashboard-setup-startup-hook))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
 This dashboard project requires [[https://github.com/purcell/page-break-lines][page-break-lines]] (which is a nice project):
 | 
			
		||||
| 
						 | 
				
			
			@ -264,6 +263,7 @@ The =dashboard= project hooks to [[help:emacs-startup-hook][emacs-startup-hook]]
 | 
			
		|||
  (defun ha-dashboard ()
 | 
			
		||||
    "Shows the extra stuff with the dashboard."
 | 
			
		||||
    (interactive)
 | 
			
		||||
    (tab-bar-switch-to-tab "main")
 | 
			
		||||
    (switch-to-buffer "*dashboard*")
 | 
			
		||||
    (setq-local mode-line-format nil)
 | 
			
		||||
    (delete-other-windows)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,19 +63,14 @@ To make the active window /more noticeable/, we /dim/ the in-active windows with
 | 
			
		|||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (use-package dimmer
 | 
			
		||||
    :custom (dimmer-adjustment-mode :foreground))
 | 
			
		||||
#+end_src
 | 
			
		||||
    :custom (dimmer-adjustment-mode :foreground)
 | 
			
		||||
    :config
 | 
			
		||||
    ;; I get issues with Magit and Dimmer, so let’s turn off this feature in certain windows:
 | 
			
		||||
    (dimmer-configure-which-key)      ; Do not dim these special windows
 | 
			
		||||
    (dimmer-configure-hydra)
 | 
			
		||||
    (dimmer-configure-magit)
 | 
			
		||||
 | 
			
		||||
I get issues with Magic and Dimmer, so let’s turn off this feature in certain windows:
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
   (use-package dimmer
 | 
			
		||||
     :config
 | 
			
		||||
     (dimmer-configure-which-key)    ; Do not dim these special windows
 | 
			
		||||
     (dimmer-configure-hydra)
 | 
			
		||||
     (dimmer-configure-magit)
 | 
			
		||||
 | 
			
		||||
     (dimmer-mode t))
 | 
			
		||||
    (dimmer-mode t))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
As an interesting alternative, check out the [[https://www.emacs.dyerdwelling.family/emacs/20240208164549-emacs-selected-window-accent-mode-now-on-melpa/][selected-window-accent]] project.
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +84,8 @@ either be "there or not" which resulted large jumps and large distractions.
 | 
			
		|||
    :straight (:type git :host github :repo "jdtsmith/ultra-scroll")
 | 
			
		||||
    :config
 | 
			
		||||
    (setq scroll-conservatively 101 ; important!
 | 
			
		||||
        scroll-margin 0)
 | 
			
		||||
          pixel-scroll-precision-interpolate-page t
 | 
			
		||||
          scroll-margin 0)
 | 
			
		||||
    (ultra-scroll-mode 1))
 | 
			
		||||
#+END_SRC
 | 
			
		||||
** Find the Bloody Cursor
 | 
			
		||||
| 
						 | 
				
			
			@ -661,7 +657,9 @@ Suggests to bind some keys to =hl-todo-next= in order to jump from tag to tag, b
 | 
			
		|||
        (?f . "FIXME")
 | 
			
		||||
        (?n . "NOTE"))
 | 
			
		||||
      "Mapping of narrow and keywords.")
 | 
			
		||||
    :general (:states 'normal "g t" '("jump todos" . consult-todo)))
 | 
			
		||||
    ;; :config
 | 
			
		||||
    ;; (evil-define-key '(normal) 'global "g t" '("jump todos" . consult-todo))
 | 
			
		||||
    )
 | 
			
		||||
#+end_src
 | 
			
		||||
* Full Size Frame
 | 
			
		||||
Taken from [[https://emacsredux.com/blog/2020/12/04/maximize-the-emacs-frame-on-startup/][this essay]], I figured I would start the initial frame automatically in fullscreen, but not any subsequent frames (as this could be part of the capturing system).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,7 +133,7 @@ Also, let's do some basic configuration of Emacs' mail system:
 | 
			
		|||
 | 
			
		||||
Create a special mail perspective:
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (ha-leader "a M" `("mail" . ,(ha-app-perspective "mail" #'notmuch)))
 | 
			
		||||
  (ha-leader "a M" `("mail" . ,(ha-tab-bar-new "mail" #'notmuch)))
 | 
			
		||||
#+end_src
 | 
			
		||||
* Configuration
 | 
			
		||||
Do I want to sign messages by default? Nope.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,7 +87,7 @@ According to Ben Maughan and [[http://pragmaticemacs.com/emacs/to-eww-or-not-to-
 | 
			
		|||
 | 
			
		||||
And some global keys to display them in the =apps= menu:
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (ha-leader "a f" `("feed reader" . ,(ha-app-perspective "elfeed" #'elfeed)))
 | 
			
		||||
  (ha-leader "a f" `("feed reader" . ,(ha-tab-bar-new "elfeed" #'elfeed)))
 | 
			
		||||
#+end_src
 | 
			
		||||
* The Feeds :elfeed:
 | 
			
		||||
The [[https://github.com/remyhonig/elfeed-org][elfeed-org]] project configures =elfeed= to read the RSS feeds from an Org file … like this one!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -521,6 +521,7 @@ The goal here is toggle switches and other miscellaneous settings.
 | 
			
		|||
    "t T" '("tramp mode"     . tramp-mode)
 | 
			
		||||
    "t v" '("visual"         . visual-line-mode)
 | 
			
		||||
    "t w" '("whitespace"     . whitespace-mode)
 | 
			
		||||
    "t <tab>" '("tab-bar"    . tab-bar-mode)
 | 
			
		||||
 | 
			
		||||
    "t <escape>" '(keyboard-escape-quit :which-key t)
 | 
			
		||||
    "t C-g" '(keyboard-escape-quit :which-key t))
 | 
			
		||||
| 
						 | 
				
			
			@ -546,13 +547,10 @@ And put it on the toggle menu:
 | 
			
		|||
  (ha-leader "t n" '("narrow" . ha-narrow-dwim))
 | 
			
		||||
#+end_src
 | 
			
		||||
* Window Operations
 | 
			
		||||
While it comes with Emacs, I use [[https://www.emacswiki.org/emacs/WinnerMode][winner-mode]] to undo window-related changes:
 | 
			
		||||
While it comes with Emacs, the =tab-bar= feature keeps track of all window configurations within a tab, allowing me to revert situations where I accidentally delete all the windows.
 | 
			
		||||
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (use-package winner
 | 
			
		||||
    :custom
 | 
			
		||||
    (winner-dont-bind-my-keys t)
 | 
			
		||||
    :config
 | 
			
		||||
    (winner-mode +1))
 | 
			
		||||
  (tab-bar-history-mode)
 | 
			
		||||
#+end_src
 | 
			
		||||
** Ace Window
 | 
			
		||||
Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ Quick way to start and jump to my IRC world.
 | 
			
		|||
 | 
			
		||||
And some global keys to display them:
 | 
			
		||||
#+begin_src emacs-lisp
 | 
			
		||||
  (ha-leader "a i" `("irc" . ,(ha-app-perspective "irc" #'ha-erc)))
 | 
			
		||||
  (ha-leader "a i" `("irc" . ,(ha-tab-bar-new "irc" #'ha-erc)))
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
And a quick shortcuts to call it:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -323,8 +323,6 @@ Let’s make a /theme/:
 | 
			
		|||
     'hamacs
 | 
			
		||||
     `(default ((t (:foreground ,default-fg :background ,default-bg))))
 | 
			
		||||
     `(fringe ((t :background ,default-bg)))
 | 
			
		||||
     `(tab-bar ((t :foreground ,default-fg :background ,default-bg)))
 | 
			
		||||
     `(tab-line ((t :foreground ,default-fg :background ,default-bg)))
 | 
			
		||||
     `(window-divider ((t :foreground "black")))
 | 
			
		||||
     `(cursor ((t (:foreground ,gray-10 :background ,cursor))))
 | 
			
		||||
     `(region ((t (:background ,region))))
 | 
			
		||||
| 
						 | 
				
			
			@ -333,6 +331,11 @@ Let’s make a /theme/:
 | 
			
		|||
     `(mode-line-active ((t (:background ,active))))
 | 
			
		||||
     `(mode-line-inactive ((t (:background ,inactive))))
 | 
			
		||||
 | 
			
		||||
     `(tab-bar ((t :foreground ,default-fg :background ,default-bg)))
 | 
			
		||||
     `(tab-line ((t :foreground ,default-fg :background ,default-bg)))
 | 
			
		||||
     `(tab-bar-tab ((t (:inherit variable-pitch :background ,active))))
 | 
			
		||||
     `(tab-bar-tab-inactive ((t (:inherit variable-pitch :background ,inactive))))
 | 
			
		||||
 | 
			
		||||
     `(doom-modeline-buffer-path ((t (:foreground ,almond))))
 | 
			
		||||
     `(doom-modeline-buffer-file ((t (:foreground "white" :weight bold))))
 | 
			
		||||
     `(doom-modeline-buffer-major-mode ((t (:foreground ,almond))))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue