emacs/var/elfeed/db/data/3e/3e3ad231a30205e018d5bdfbbe3efb8c7bb9c06d
2022-01-03 12:49:32 -06:00

111 lines
13 KiB
Plaintext

<p>When I&rsquo;m working in Emacs, I like to have some visual separation between
different workspaces (which roughly equate to different projects). Previously, I
was using Doom Emacs&rsquo; <a href="https://github.com/hlissner/doom-emacs/tree/develop/modules/ui/workspaces">workspaces</a> feature, which uses <a href="https://github.com/Bad-ptr/persp-mode.el">persp-mode</a>. My initial
reason for disabling workspaces was because I found that
<code>projectile-switch-project</code> didn&rsquo;t work properly with it enabled, but I also
thought it would be interesting to see what I could set up using built-in Emacs
functions. What I ended up using was Emacs&rsquo; built-in tab-bar.</p>
<p>Emacs (at least in version 27.1) has two kinds of tabs: the tab-line feature is
similar to the way tabs are handled in most other editors. Each window (which in
other editors would be considered a pane inside a window) has a row of tabs at
the top to open buffers which have been associated with that window. Tab-bar, on
the other hand, is associated with frames (which would be considered windows in other
editors), and switches between different window configurations (or layouts of
buffers in split windows) in one frame. I hadn&rsquo;t thought about using tab-bar
before, because I assumed (wrongly) that you had to have the graphical tabs
visible at the top of the frame, and I didn&rsquo;t like the visual noise that
involved. However &mdash; as with all things in Emacs &mdash; it turned out that this is
completely configurable. If you don&rsquo;t like the visible tabs, you don&rsquo;t have to
have them: you use <code>(tab-bar-show nil)</code> to turn them off, and the commands and
key-bindings work just the same without them.</p>
<p>I wanted to use tabs to visually separate different projects which I might need
open at the same time, each of which is usually a separate <code>projectile</code> project.
You can name tabs and then switch between projects by name in the minibuffer,
using whatever completion mechanism you usually use. I thought it would be handy
to name tabs automatically with the projectile project name, and save myself
from having to do it manually. I dug around in the documentation for the
functions related to tab-bars and found that you could set a variable to the
name of a function to do the naming of tabs. I could then write my own function
to get the project name and set that as the tab name, or revert to a number if
we&rsquo;re not in a project: <code>(projectile-project-name)</code> returns &ldquo;-&rdquo; in the latter
case. This really only required looking at the built-in tab naming function,
copying it and modifying it in a minor way to do what I wanted (which is 90% of
my emacs-lisp hacking, to be honest). While I was at it, I also re-used the <kbd>SPC
TAB</kbd> leader key formerly used by the workspaces function to hold some useful
tab-bar commands. This is how I have set it up:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-emacs-lisp" data-lang="emacs-lisp">(<span style="color:#038">defun</span> <span style="color:#369">my/name-tab-by-project-or-default</span> ()
<span style="color:#d20;background-color:#fff0f0">&#34;Return project name if in a project, or default tab-bar name if not.
</span><span style="color:#d20;background-color:#fff0f0">The default tab-bar name uses the buffer name.&#34;</span>
(<span style="color:#038">let</span> ((<span style="color:#369">project-name</span> (<span style="color:#369">projectile-project-name</span>)))
(<span style="color:#038">if</span> (<span style="color:#369">string=</span> <span style="color:#d20;background-color:#fff0f0">&#34;-&#34;</span> <span style="color:#369">project-name</span>)
(<span style="color:#369">tab-bar-tab-name-current</span>)
(<span style="color:#369">projectile-project-name</span>))))
(<span style="color:#038">setq</span> <span style="color:#369">tab-bar-mode</span> <span style="color:#036;font-weight:bold">t</span>)
(<span style="color:#038">setq</span> <span style="color:#369">tab-bar-show</span> <span style="color:#036;font-weight:bold">nil</span>)
(<span style="color:#038">setq</span> <span style="color:#369">tab-bar-new-tab-choice</span> <span style="color:#d20;background-color:#fff0f0">&#34;*doom*&#34;</span>)
(<span style="color:#038">setq</span> <span style="color:#369">tab-bar-tab-name-function</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">my/name-tab-by-project-or-default</span>)
(<span style="color:#369">map!</span> <span style="color:#038">:leader</span>
(<span style="color:#038">:prefix-map</span> (<span style="color:#d20;background-color:#fff0f0">&#34;TAB&#34;</span> . <span style="color:#d20;background-color:#fff0f0">&#34;Tabs&#34;</span>)
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;Switch tab&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;TAB&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-select-tab-by-name</span>
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;New tab&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;n&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-new-tab</span>
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;Rename tab&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;r&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-rename-tab</span>
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;Rename tab by name&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;R&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-rename-tab-by-name</span>
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;Close tab&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;d&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-close-tab</span>
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;Close tab by name&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;D&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-close-tab-by-name</span>
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">&#34;Close other tabs&#34;</span> <span style="color:#d20;background-color:#fff0f0">&#34;1&#34;</span> <span style="color:#06b;font-weight:bold">#&#39;</span><span style="color:#369">tab-bar-close-other-tabs</span>))
</code></pre></div><p>My workflow is that when I want to work on a different project, I hit
<kbd>SPC TAB n</kbd> to create a new tab, which brings me to the Doom dashboard, because
I set that as the default <code>tab-bar-new-tab-choice</code> above. Then I use <kbd>SPC pp</kbd> to
switch project and choose my project. The wrinkle I hit here was that while my
function had named the tab appropriately (which I could see if I used the
command to select a tab by name), it didn&rsquo;t appear in the modeline. If I set the
name manually, it would appear in the modeline.</p>
<p>After more digging in the naming functions, I realised that names set manually
(as opposed to programmatically) are designated as the tab&rsquo;s &lsquo;explicit name&rsquo;:
Doom modeline checks for the explicit name, otherwise it just shows the tab
number. There were a number of different ways I could see to fix this, but in
the end I decided on the least disruptive way I could think of, which was to
re-define the relevant segment of Doom modeline so that it always shows the name
of the tab, whether explicitly set or not. The code below does that in a
slightly clunky way by setting both <code>tab-name</code> and <code>explicit-name</code> to <code>current-tab</code>,
but it works!</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-emacs-lisp" data-lang="emacs-lisp">(<span style="color:#369">after!</span> <span style="color:#369">doom-modeline</span>
(<span style="color:#369">doom-modeline-def-segment</span> <span style="color:#369">workspace-name</span>
<span style="color:#d20;background-color:#fff0f0">&#34;The current workspace name or number.
</span><span style="color:#d20;background-color:#fff0f0">Requires </span><span style="color:#a60;background-color:#fff0f0">`eyebrowse-mode&#39;</span><span style="color:#d20;background-color:#fff0f0"> or </span><span style="color:#a60;background-color:#fff0f0">`tab-bar-mode&#39;</span><span style="color:#d20;background-color:#fff0f0"> to be enabled.&#34;</span>
(<span style="color:#038">when</span> <span style="color:#369">doom-modeline-workspace-name</span>
(<span style="color:#369">when-let</span>
((<span style="color:#369">name</span> (<span style="color:#038">cond</span>
((<span style="color:#038">and</span> (<span style="color:#369">bound-and-true-p</span> <span style="color:#369">eyebrowse-mode</span>)
(<span style="color:#06b;font-weight:bold">&lt;</span> <span style="color:#00d;font-weight:bold">1</span> (<span style="color:#06b;font-weight:bold">length</span> (<span style="color:#369">eyebrowse--get</span> <span style="color:#a60;background-color:#fff0f0">&#39;window-configs</span>))))
(<span style="color:#369">assq-delete-all</span> <span style="color:#a60;background-color:#fff0f0">&#39;eyebrowse-mode</span> <span style="color:#369">mode-line-misc-info</span>)
(<span style="color:#369">when-let*</span>
((<span style="color:#369">num</span> (<span style="color:#369">eyebrowse--get</span> <span style="color:#a60;background-color:#fff0f0">&#39;current-slot</span>))
(<span style="color:#369">tag</span> (<span style="color:#06b;font-weight:bold">nth</span> <span style="color:#00d;font-weight:bold">2</span> (<span style="color:#06b;font-weight:bold">assoc</span> <span style="color:#369">num</span> (<span style="color:#369">eyebrowse--get</span> <span style="color:#a60;background-color:#fff0f0">&#39;window-configs</span>)))))
(<span style="color:#038">if</span> (<span style="color:#06b;font-weight:bold">&lt;</span> <span style="color:#00d;font-weight:bold">0</span> (<span style="color:#06b;font-weight:bold">length</span> <span style="color:#369">tag</span>)) <span style="color:#369">tag</span> (<span style="color:#369">int-to-string</span> <span style="color:#369">num</span>))))
(<span style="color:#036;font-weight:bold">t</span>
(<span style="color:#038">let*</span> ((<span style="color:#369">current-tab</span> (<span style="color:#369">tab-bar--current-tab</span>))
(<span style="color:#369">tab-index</span> (<span style="color:#369">tab-bar--current-tab-index</span>))
(<span style="color:#369">explicit-name</span> (<span style="color:#369">alist-get</span> <span style="color:#a60;background-color:#fff0f0">&#39;name</span> <span style="color:#369">current-tab</span>))
(<span style="color:#369">tab-name</span> (<span style="color:#369">alist-get</span> <span style="color:#a60;background-color:#fff0f0">&#39;name</span> <span style="color:#369">current-tab</span>)))
(<span style="color:#038">if</span> <span style="color:#369">explicit-name</span> <span style="color:#369">tab-name</span> (<span style="color:#06b;font-weight:bold">+</span> <span style="color:#00d;font-weight:bold">1</span> <span style="color:#369">tab-index</span>)))))))
(<span style="color:#06b;font-weight:bold">propertize</span> (<span style="color:#06b;font-weight:bold">format</span> <span style="color:#d20;background-color:#fff0f0">&#34; %s &#34;</span> <span style="color:#369">name</span>) <span style="color:#a60;background-color:#fff0f0">&#39;face</span>
(<span style="color:#038">if</span> (<span style="color:#369">doom-modeline--active</span>)
<span style="color:#a60;background-color:#fff0f0">&#39;doom-modeline-buffer-major-mode</span>
<span style="color:#a60;background-color:#fff0f0">&#39;mode-line-inactive</span>))))))
</code></pre></div><p>The final bit of customisation I did was to set a keybinding for the two keys on
my ErgoDox which are on the inner edges of each half on the top row. I have set
these to switch to the previous buffer (left half) or next buffer (right half),
so it seemed natural to go to the previous/next tab by holding <kbd>CTRL</kbd> and hitting
the key.</p>
<p>I&rsquo;m really pleased with this new set up. It works well for my use, and the only
thing I am missing from my former workspaces set up is isolation of each project
within each workspace, so that only buffers in the project are available.
However, now that I am used to the Emacs way of doing things (have hundreds of
open buffers, and use the completion framework to quickly narrow to what you
want), this doesn&rsquo;t bother me. If I specifically want to search for files or
buffers in a project, there are projectile commands to do that.</p>
<p>Emacs may be a dangerous rabbit hole at times, but I can&rsquo;t see myself ever going
back to an editor which is not so incredibly open to inspection of the code and
customisation. It&rsquo;s amazing that you can so easily look up the code that defines
built-in functions and then adapt it yourself (while the editor is running!).</p>