111 lines
13 KiB
Plaintext
111 lines
13 KiB
Plaintext
<p>When I’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’ <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’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’ 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’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’t like the visual noise that
|
|
involved. However — as with all things in Emacs — it turned out that this is
|
|
completely configurable. If you don’t like the visible tabs, you don’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’re not in a project: <code>(projectile-project-name)</code> returns “-” 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">"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."</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">"-"</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">"*doom*"</span>)
|
|
(<span style="color:#038">setq</span> <span style="color:#369">tab-bar-tab-name-function</span> <span style="color:#06b;font-weight:bold">#'</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">"TAB"</span> . <span style="color:#d20;background-color:#fff0f0">"Tabs"</span>)
|
|
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">"Switch tab"</span> <span style="color:#d20;background-color:#fff0f0">"TAB"</span> <span style="color:#06b;font-weight:bold">#'</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">"New tab"</span> <span style="color:#d20;background-color:#fff0f0">"n"</span> <span style="color:#06b;font-weight:bold">#'</span><span style="color:#369">tab-bar-new-tab</span>
|
|
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">"Rename tab"</span> <span style="color:#d20;background-color:#fff0f0">"r"</span> <span style="color:#06b;font-weight:bold">#'</span><span style="color:#369">tab-bar-rename-tab</span>
|
|
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">"Rename tab by name"</span> <span style="color:#d20;background-color:#fff0f0">"R"</span> <span style="color:#06b;font-weight:bold">#'</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">"Close tab"</span> <span style="color:#d20;background-color:#fff0f0">"d"</span> <span style="color:#06b;font-weight:bold">#'</span><span style="color:#369">tab-bar-close-tab</span>
|
|
<span style="color:#038">:desc</span> <span style="color:#d20;background-color:#fff0f0">"Close tab by name"</span> <span style="color:#d20;background-color:#fff0f0">"D"</span> <span style="color:#06b;font-weight:bold">#'</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">"Close other tabs"</span> <span style="color:#d20;background-color:#fff0f0">"1"</span> <span style="color:#06b;font-weight:bold">#'</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’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’s ‘explicit name’:
|
|
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">"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'</span><span style="color:#d20;background-color:#fff0f0"> or </span><span style="color:#a60;background-color:#fff0f0">`tab-bar-mode'</span><span style="color:#d20;background-color:#fff0f0"> to be enabled."</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"><</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">'window-configs</span>))))
|
|
(<span style="color:#369">assq-delete-all</span> <span style="color:#a60;background-color:#fff0f0">'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">'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">'window-configs</span>)))))
|
|
(<span style="color:#038">if</span> (<span style="color:#06b;font-weight:bold"><</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">'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">'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">" %s "</span> <span style="color:#369">name</span>) <span style="color:#a60;background-color:#fff0f0">'face</span>
|
|
(<span style="color:#038">if</span> (<span style="color:#369">doom-modeline--active</span>)
|
|
<span style="color:#a60;background-color:#fff0f0">'doom-modeline-buffer-major-mode</span>
|
|
<span style="color:#a60;background-color:#fff0f0">'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’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’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’t see myself ever going
|
|
back to an editor which is not so incredibly open to inspection of the code and
|
|
customisation. It’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> |