268 lines
34 KiB
Plaintext
268 lines
34 KiB
Plaintext
<p><a href="https://github.com/oantolin/embark/tree/98121bacef39abaf6f6849f87a439ba2184c03e2">Embark</a> is a fantastic and thoughtfully designed package for Emacs that flips Emacs’ action → object ordering without adding a learning curve. It’s completely changed how I use Emacs, and I’m going to show you why.</p>
|
||
<p>By default, Emacs’ action model is to specify the action (<code>find-file</code>), then the object (the file):
|
||
<a href="https://karthinks.com/img/emacs-pattern.png"><img alt="" src="https://karthinks.com/img/emacs-pattern.png" /></a></p>
|
||
<p>This mirrors how one does things in a shell:
|
||
<a href="https://karthinks.com/img/shell-pattern.png"><img alt="" src="https://karthinks.com/img/shell-pattern.png" /></a>
|
||
The difference is that before you submit your shell command, you’re free to edit both the action and the object, since it’s just a line of text. In Emacs you can change the object freely, but you’d have to hit <code>C-g</code> and call a different command.</p>
|
||
<p>Things work the other way in a GUI program like a file manager. You select some representation of the object (usually an icon), then choose the action you would like to perform on it:
|
||
<a href="https://karthinks.com/img/gui-pattern.png"><img alt="" src="https://karthinks.com/img/gui-pattern.png" /></a></p>
|
||
<p>Either paradigm works fine, but this is Emacs, there’s no reason to choose just one! <strong>Embark lets you go back and forth between the two patterns.</strong> So you can call <code>find-file</code> (say) and pick a file, only to realize that you want to do something else with it, like open it in another window, or copy the file to a different directory:</p>
|
||
<figure><a href="https://karthinks.com/img/embark-pattern.png">
|
||
<img src="https://karthinks.com/img/embark-pattern.png" /> </a>
|
||
</figure>
|
||
|
||
<p>With Embark, this is a breeze.</p>
|
||
<h2 id="embark-act-actually-dot-dot-dot-dot-and-but-first-dot-dot-dot"><code>embark-act</code>: <em>Actually….</em> & <em>But first…</em></h2>
|
||
<p><code>embark-act</code> is your “<strong>Actually…</strong>” command. As in, I called <code>package-install</code> and picked a package but <em>actually</em> I’d like to read the package description instead!</p>
|
||
<p><code>embark-act</code> is your “<strong>Yes, but first…</strong>” command as well. As in, I called <code>find-file</code> but <em>first I’d like to</em> copy it elsewhere to be safe, then continue to open this file!</p>
|
||
<p>Or perhaps you want to think of it as a keyboard driven analog of a “right-click menu” in a GUI environment. That works too, but the former maps better to the idea of “late-binding” and laziness that I think of Embark as enabling.</p>
|
||
<p>Emacs makes you specify and fix the action/verb first (<code>find-file</code>, say), then choose the thing it acts on (the file). If you call embark-act, this is reversed. Now the object (file) is fixed, and you’re free to choose the action.</p>
|
||
<p>I know: It sounds like I’m describing Helm actions. The difference is that Embark works <em>everywhere</em>, across all types of “objects”, and with <em>every</em> initial and <em>wait-I-changed-my-mind</em> command. There is no predetermined set of alternative actions configured to work with another predetermined set of initial actions. No one (including yourself) needs to have anticipated in advance what actions go together.<sup id="fnref:1"><a class="footnote-ref" href="https://karthinks.com/tags/emacs/index.xml#fn:1">1</a></sup> This uniform, consistent integration into Emacs makes the difference between them one of kind and not of quantity, although it takes a bit of time to see this.</p>
|
||
<p>This means you can start a command and select a candidate in the minibuffer, then call <code>embark-act</code> and <code>M-x some-other-command</code> to run that command on the candidate instead. If you are about to kill a buffer with <code>C-x k</code> but want to switch to it instead, you can call <code>embark-act</code> followed by <code>C-x b</code>. You can even do this without losing the <code>kill-buffer</code> prompt if you just want a quick peek at the buffer!</p>
|
||
<p>The categories of objects Embark understands covers most common cases: filenames, buffers, bookmarks, URLs, text regions, variables, commands, symbols and more.</p>
|
||
<p>When you call <code>embark-act</code>, Embark also activates a keymap with direct access to common actions you might want to run for each category of object. This makes it unnecessary to use <code>M-x</code> to run your <em>I-changed-my-mind</em> action all the time, although you always have that option. You can, of course, add your own commands to this keymap as I do below.</p>
|
||
<p>I use <code>embark-act</code> literally hundreds of times every day. Here are a few of my common uses. A few of these are built in, others need some elisp to work, all are surprisingly useful. To be clear, this list barely scratches the surface of the sphere of possibilities with Embark.</p>
|
||
<h3 id="open-any-buffer-by-splitting-any-window">Open any buffer by splitting any window</h3>
|
||
<p>This needs a little background. The ace-window package allows you to switch to a window based on keyboard hints. A less well known feature is that it also provides a “dispatch menu” that lets you act on windows in ways beyond simply switching to them:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/ace-dispatch-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>So you can kill windows, move them around, split them and more by using the dispatch keys. (Hit <code>?</code> to bring up the dispatch menu.)</p>
|
||
<p>Now: You can call ace-window via Embark to display a candidate anywhere, including in splits that you create using the above dispatch menu. This means any buffer/file/bookmark I open is always placed exactly where I want it to be on the screen.</p>
|
||
<p>In the below demo, I open a bookmark (with <code>consult-bookmark</code>), a file (with <code>find-file</code>) and a buffer (with <code>consult-buffer</code>) in sequence. Each time, I run <code>embark-act</code> and select the ace-window action, which activates <code>ace-window</code>. You can then display the buffer in any existing window by making a selection with <code>ace-window</code>. I actually go one step further in the demo: I split one of the existing windows using <code>ace-window</code>'s dispatch feature from above and display the relevant buffer in that split!</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-ace-open-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>To get this to work, you’ll need to add a few ace-window functions to the Embark file actions map:</p>
|
||
<div class="highlight"><pre><code class="language-emacs-lisp">(<span style="color: #a2f;">eval-when-compile</span>
|
||
(<span style="color: #a2f;">defmacro</span> <span style="color: #b8860b;">my/embark-ace-action</span> (<span style="color: #b8860b;">fn</span>)
|
||
<span style="color: #666;">`</span>(<span style="color: #a2f;">defun</span> <span style="color: #666;">,</span>(<span style="color: #00a000;">intern</span> (<span style="color: #00a000;">concat</span> <span style="color: #b44;">"my/embark-ace-"</span> (<span style="color: #00a000;">symbol-name</span> <span style="color: #b8860b;">fn</span>))) ()
|
||
(<span style="color: #a2f;">interactive</span>)
|
||
(<span style="color: #a2f;">with-demoted-errors</span> <span style="color: #b44;">"%s"</span>
|
||
(<span style="color: #a2f;">require</span> <span style="color: #b8860b;">'ace-window</span>)
|
||
(<span style="color: #b8860b;">aw-switch-to-window</span> (<span style="color: #b8860b;">aw-select</span> <span style="color: #800;">nil</span>))
|
||
(<span style="color: #00a000;">call-interactively</span> (<span style="color: #00a000;">symbol-function</span> <span style="color: #b8860b;">',fn</span>)))))
|
||
|
||
(<span style="color: #a2f;">defmacro</span> <span style="color: #b8860b;">my/embark-split-action</span> (<span style="color: #b8860b;">fn</span> <span style="color: #b8860b;">split-type</span>)
|
||
<span style="color: #666;">`</span>(<span style="color: #a2f;">defun</span> <span style="color: #666;">,</span>(<span style="color: #00a000;">intern</span> (<span style="color: #00a000;">concat</span> <span style="color: #b44;">"my/embark-"</span>
|
||
(<span style="color: #00a000;">symbol-name</span> <span style="color: #b8860b;">fn</span>)
|
||
<span style="color: #b44;">"-"</span>
|
||
(<span style="color: #00a000;">car</span> (<span style="color: #b8860b;">last</span> (<span style="color: #b8860b;">split-string</span>
|
||
(<span style="color: #00a000;">symbol-name</span> <span style="color: #b8860b;">split-type</span>) <span style="color: #b44;">"-"</span>))))) ()
|
||
(<span style="color: #a2f;">interactive</span>)
|
||
(<span style="color: #00a000;">funcall</span> <span style="color: #00a000;">#'</span><span style="color: #666;">,</span><span style="color: #b8860b;">split-type</span>)
|
||
(<span style="color: #00a000;">call-interactively</span> <span style="color: #00a000;">#'</span><span style="color: #666;">,</span><span style="color: #b8860b;">fn</span>))))
|
||
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-file-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"o"</span>) (<span style="color: #b8860b;">my/embark-ace-action</span> <span style="color: #b8860b;">find-file</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-buffer-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"o"</span>) (<span style="color: #b8860b;">my/embark-ace-action</span> <span style="color: #b8860b;">switch-to-buffer</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-bookmark-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"o"</span>) (<span style="color: #b8860b;">my/embark-ace-action</span> <span style="color: #b8860b;">bookmark-jump</span>))
|
||
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-file-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"2"</span>) (<span style="color: #b8860b;">my/embark-split-action</span> <span style="color: #b8860b;">find-file</span> <span style="color: #b8860b;">split-window-below</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-buffer-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"2"</span>) (<span style="color: #b8860b;">my/embark-split-action</span> <span style="color: #b8860b;">switch-to-buffer</span> <span style="color: #b8860b;">split-window-below</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-bookmark-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"2"</span>) (<span style="color: #b8860b;">my/embark-split-action</span> <span style="color: #b8860b;">bookmark-jump</span> <span style="color: #b8860b;">split-window-below</span>))
|
||
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-file-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"3"</span>) (<span style="color: #b8860b;">my/embark-split-action</span> <span style="color: #b8860b;">find-file</span> <span style="color: #b8860b;">split-window-right</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-buffer-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"3"</span>) (<span style="color: #b8860b;">my/embark-split-action</span> <span style="color: #b8860b;">switch-to-buffer</span> <span style="color: #b8860b;">split-window-right</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-bookmark-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"3"</span>) (<span style="color: #b8860b;">my/embark-split-action</span> <span style="color: #b8860b;">bookmark-jump</span> <span style="color: #b8860b;">split-window-right</span>))
|
||
</code></pre></div><p>The <code>ace-window</code> action needs only the first macro, but I threw in some extras.</p>
|
||
<hr />
|
||
<h3 id="copy-a-file-to-a-remote-location-when-finding-a-file">Copy a file to a remote location when finding a file</h3>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-copy-remote-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>Here’s what happened. In any file prompt, you can call <code>embark-act</code> and select the copy action to copy the file instead. (You could just as well call <code>M-x copy-file</code>.) In this case I then use <a href="https://github.com/karthink/consult-dir">consult-dir</a> to insert a bookmark that points to my server into the destination prompt, and the file is copied using Tramp.</p>
|
||
<p>You can even do this without losing the <code>find-file</code> prompt! Calling <code>embark-act</code> with a prefix argument keeps the prompt alive:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-copy-remote-persist-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>At the end I quit the <code>find-file</code> prompt manually and check the remote directory to ensure that the file has been copied.</p>
|
||
<hr />
|
||
<h3 id="insert-a-minibuffer-candidate-into-the-buffer">Insert a minibuffer candidate into the buffer</h3>
|
||
<p>Simple but very convenient:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-insert-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<hr />
|
||
<h3 id="run-a-shell-command-on-a-minibuffer-candidate-file-without-losing-your-session">Run a shell command on a minibuffer candidate file without losing your session</h3>
|
||
<p>A perfect example of <em>But First I need to…</em>:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-shell-cmd-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>I called the “file” shell command for more info on the file without ending the <code>find-file</code> prompt.</p>
|
||
<hr />
|
||
<h3 id="open-a-file-as-root-without-losing-your-session">Open a file as root without losing your session</h3>
|
||
<p>Emacs’ version of forgetting to add <code>sudo</code> before the command. In the shell you can go back to the start of the prompt and type it in, or engage in the <code>sudo !!</code> ritual. In Emacs I use an Embark action:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-sudo-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>Like before, this works from any file prompt but the command I started with was <code>consult-locate</code>. For the sudo program there is the <code>sudo-edit</code> package, although I used a snippet from my init file that I can’t ascertain the provenance of anymore:</p>
|
||
<div class="highlight"><pre><code class="language-emacs-lisp">(<span style="color: #a2f;">defun</span> <span style="color: #b8860b;">sudo-find-file</span> (<span style="color: #b8860b;">file</span>)
|
||
<span style="color: #b44;">"Open FILE as root."</span>
|
||
(<span style="color: #a2f;">interactive</span> <span style="color: #b44;">"FOpen file as root: "</span>)
|
||
(<span style="color: #a2f;">when</span> (<span style="color: #00a000;">file-writable-p</span> <span style="color: #b8860b;">file</span>)
|
||
(<span style="color: #d2413a; font-weight: bold;">user-error</span> <span style="color: #b44;">"File is user writeable, aborting sudo"</span>))
|
||
(<span style="color: #b8860b;">find-file</span> (<span style="color: #a2f;">if</span> (<span style="color: #b8860b;">file-remote-p</span> <span style="color: #b8860b;">file</span>)
|
||
(<span style="color: #00a000;">concat</span> <span style="color: #b44;">"/"</span> (<span style="color: #b8860b;">file-remote-p</span> <span style="color: #b8860b;">file</span> <span style="color: #b8860b;">'method</span>) <span style="color: #b44;">":"</span>
|
||
(<span style="color: #b8860b;">file-remote-p</span> <span style="color: #b8860b;">file</span> <span style="color: #b8860b;">'user</span>) <span style="color: #b44;">"@"</span> (<span style="color: #b8860b;">file-remote-p</span> <span style="color: #b8860b;">file</span> <span style="color: #b8860b;">'host</span>)
|
||
<span style="color: #b44;">"|sudo:root@"</span>
|
||
(<span style="color: #b8860b;">file-remote-p</span> <span style="color: #b8860b;">file</span> <span style="color: #b8860b;">'host</span>) <span style="color: #b44;">":"</span> (<span style="color: #b8860b;">file-remote-p</span> <span style="color: #b8860b;">file</span> <span style="color: #b8860b;">'localname</span>))
|
||
(<span style="color: #00a000;">concat</span> <span style="color: #b44;">"/sudo:root@localhost:"</span> <span style="color: #b8860b;">file</span>))))
|
||
</code></pre></div><p>To use <code>sudo-find-file</code> as an Embark action, you can run it (with <code>M-x</code> or a global keybinding) after calling <code>embark-act</code>, or shorten the process further by adding an entry to Embark’s file actions map:</p>
|
||
<div class="highlight"><pre><code class="language-emacs-lisp">(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-file-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"S"</span>) <span style="color: #b8860b;">'sudo-find-file</span>)
|
||
</code></pre></div><hr />
|
||
<h3 id="upload-a-region-of-text-to-0x0">Upload a region of text to 0x0</h3>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-0x0-region-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>I’m using the <a href="https://melpa.org/#/0x0">0x0</a> package for the <code>0x0-dwim</code> function. When called as an Embark action on a URL, this shortens it. When called on a file, it uploads the file. The echo area message at the end (from <code>0x0-dwim</code>) tells me the upload URL has been copied to the kill ring. As with the other examples, you can call <code>0x0-dwim</code> after running <code>embark-act</code> or define a short key for it in one of Embark’s keymaps:</p>
|
||
<div class="highlight"><pre><code class="language-emacs-lisp">(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">embark-region-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"U"</span>) <span style="color: #b8860b;">'0x0-dwim</span>)
|
||
</code></pre></div><hr />
|
||
<h3 id="visit-a-package-s-url-from-the-minibuffer">Visit a package’s URL from the minibuffer</h3>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-package-url-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>In this case I ran the <code>describe-package</code> command before going “<strong>Actually… URL please</strong>", but in this example as all the others, there’s nothing special about <code>describe-package</code>. Any command that gives you a list of packages at the minibuffer will proffer the same set of Embark actions.</p>
|
||
<hr />
|
||
<h3 id="set-a-variable-from-anywhere-it-appears-in-a-buffer">Set a variable from anywhere it appears in a buffer</h3>
|
||
<p>Super handy for quickly setting variables, especially when testing code.</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-set-var-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>In this case Embark has an entry for <code>set-variable</code> in its variables keymap (bound to <code>=</code>), but you can just call <code>M-x set-variable</code>.</p>
|
||
<hr />
|
||
<h3 id="add-a-keybinding-for-a-command-name-from-anywhere-it-appears">Add a keybinding for a command name from anywhere it appears</h3>
|
||
<p>Set all the keys.</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-set-key-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>Embark provides an action in its keymap to run <code>global-set-key</code>, but you could just call <code>M-x global-set-key</code> after running <code>embark-act</code> with the point on a command name.</p>
|
||
<hr />
|
||
<h2 id="embark-export-i-want-a-gist-so-give-me-a-list"><code>embark-export</code>: <em>I want a gist, so give me a list</em></h2>
|
||
<p>If that was everything Embark did I’d be a happy camper. But <code>embark-act</code> isn’t even its best feature. That would be the gem of composability that is <code>embark-export</code> (and its lesser kin <code>embark-collect</code>). These commands create persistent collections from minibuffer candidate lists: It’s one part ivy-occur and one part glue that ties together Emacs libraries better than Emacs does. The examples illustrate why.</p>
|
||
<hr />
|
||
<h3 id="export-emacs-package-candidates-to-a-package-menu">Export Emacs package candidates to a package menu</h3>
|
||
<p>Want a package-menu-mode buffer with all packages involving shells in Emacs? <code>embark-export</code> has you covered:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-package-export-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>The clever idea behind <code>embark-export</code> is to reuse Emacs’ built-in functionality whenever possible: the package-menu library already handles displaying packages. If you’re generating a list of packages with user-specified conditions, why reinvent the wheel?</p>
|
||
<hr />
|
||
<h3 id="collect-imenu-candidates-in-an-imenu-list">Collect imenu candidates in an “imenu-list”</h3>
|
||
<p><code>embark-collect</code> creates persistent collections of minibuffer completion candidates (filtered by user input) in a way that basically obsoletes every “listing” package for me. In this example I create a filtered list of <code>imenu</code> items that sticks around and that I can use to navigate around the file:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-imenu-list-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>That’s <code>consult-imenu</code> + some user input + <code>embark-collect</code>. I didn’t show this in the demo, but all <code>embark-act</code> actions are available in the Collections buffer, and you can even call them directly (i.e. without calling <code>embark-act</code> first) by turning on <code>embark-collect-direct-action-minor-mode</code>.</p>
|
||
<hr />
|
||
<h3 id="export-file-candidates-to-a-dired-buffer">Export file candidates to a dired-buffer</h3>
|
||
<p>Have a list of files you arrived at in a tortuous manner that you want to keep around? <code>dired</code> was created to list files, and <code>embark-export</code> respects this:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-file-export-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>This obsoletes <code>find-name-dired</code>, another “listing” based feature.</p>
|
||
<hr />
|
||
<h3 id="export-buffer-candidates-to-ibuffer">Export buffer candidates to ibuffer</h3>
|
||
<p>You saw this coming: Any list of buffers gets exported to an <code>ibuffer</code>.</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-buffer-export-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>Before exporting I filtered away<sup id="fnref:2"><a class="footnote-ref" href="https://karthinks.com/tags/emacs/index.xml#fn:2">2</a></sup> the “special” buffers that start with <code>*</code>.</p>
|
||
<hr />
|
||
<h3 id="export-variable-candidates-to-a-customize-buffer">Export variable candidates to a customize buffer</h3>
|
||
<p>A list of variables is exported by <code>embark-export</code> into a customize buffer:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-custom-var-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>This is a great way to transition from looking up a variable to a full-fledged apropos on relevant items when you need to.</p>
|
||
<hr />
|
||
<h3 id="export-grep-or-line-candidates-to-a-grep-buffer">Export grep or line candidates to a grep buffer</h3>
|
||
<p>Any <code>occur</code>-like results (from consult-line, grep, xref etc) get exported into a <code>grep</code> buffer.</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-grep-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>Note that this is a regular grep buffer, so you can use all your tricks, like wgrep to edit the grep buffer and save changes in all the files.</p>
|
||
<hr />
|
||
<h2 id="bonus-use-embark-actions-like-helm">BONUS: Use Embark Actions like Helm</h2>
|
||
<p>In the above examples, the available embark actions were displayed in some window in the frame. Embark has multiple “prompters” listing the preset actions, and with a little elbow grease you can set up something similar to Helm<sup id="fnref:3"><a class="footnote-ref" href="https://karthinks.com/tags/emacs/index.xml#fn:3">3</a></sup>:</p>
|
||
<video controls="controls" loop="loop" width="700">
|
||
<source src="https://karthinks.com/img/embark-helm-demo.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
<p>Here I switch back and forth between the list of actions and the list of candidates (like in Helm) with <code>C-<tab></code>. In the actions list you can either type the action (matched with completing-read), or call the action directly by prepending its keybinding with <code>@</code>.</p>
|
||
<p>Elbow grease:</p>
|
||
<div class="highlight"><pre><code class="language-emacs-lisp">(<span style="color: #a2f;">defun</span> <span style="color: #b8860b;">with-minibuffer-keymap</span> (<span style="color: #b8860b;">keymap</span>)
|
||
(<span style="color: #a2f;">lambda</span> (<span style="color: #b8860b;">fn</span> <span style="color: #a2f;">&rest</span> <span style="color: #b8860b;">args</span>)
|
||
(<span style="color: #b8860b;">minibuffer-with-setup-hook</span>
|
||
(<span style="color: #a2f;">lambda</span> ()
|
||
(<span style="color: #00a000;">use-local-map</span>
|
||
(<span style="color: #b8860b;">make-composed-keymap</span> <span style="color: #b8860b;">keymap</span> (<span style="color: #00a000;">current-local-map</span>))))
|
||
(<span style="color: #00a000;">apply</span> <span style="color: #b8860b;">fn</span> <span style="color: #b8860b;">args</span>))))
|
||
|
||
(<span style="color: #a2f;">defvar</span> <span style="color: #b8860b;">embark-completing-read-prompter-map</span>
|
||
(<span style="color: #a2f;">let</span> ((<span style="color: #b8860b;">map</span> (<span style="color: #00a000;">make-sparse-keymap</span>)))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"<tab>"</span>) <span style="color: #b8860b;">'abort-recursive-edit</span>)
|
||
<span style="color: #b8860b;">map</span>))
|
||
|
||
(<span style="color: #b8860b;">advice-add</span> <span style="color: #b8860b;">'embark-completing-read-prompter</span> <span style="color: #a2f;">:around</span>
|
||
(<span style="color: #b8860b;">with-minibuffer-keymap</span> <span style="color: #b8860b;">embark-completing-read-prompter-map</span>))
|
||
(<span style="color: #00a000;">define-key</span> <span style="color: #b8860b;">vertico-map</span> (<span style="color: #b8860b;">kbd</span> <span style="color: #b44;">"<tab>"</span>) <span style="color: #b8860b;">'embark-act-with-completing-read</span>)
|
||
|
||
(<span style="color: #a2f;">defun</span> <span style="color: #b8860b;">embark-act-with-completing-read</span> (<span style="color: #a2f;">&optional</span> <span style="color: #b8860b;">arg</span>)
|
||
(<span style="color: #a2f;">interactive</span> <span style="color: #b44;">"P"</span>)
|
||
(<span style="color: #a2f;">let*</span> ((<span style="color: #b8860b;">embark-prompter</span> <span style="color: #b8860b;">'embark-completing-read-prompter</span>)
|
||
(<span style="color: #b8860b;">act</span> (<span style="color: #00a000;">propertize</span> <span style="color: #b44;">"Act"</span> <span style="color: #b8860b;">'face</span> <span style="color: #b8860b;">'highlight</span>))
|
||
(<span style="color: #b8860b;">embark-indicator</span> (<span style="color: #a2f;">lambda</span> (<span style="color: #b8860b;">_keymap</span> <span style="color: #b8860b;">targets</span>) <span style="color: #800;">nil</span>)))
|
||
(<span style="color: #b8860b;">embark-act</span> <span style="color: #b8860b;">arg</span>)))
|
||
</code></pre></div><p>Replace <code>vertico-map</code> above with your completion system of choice’s active minibuffer keymap. The default is <code>minibuffer-local-completion-map</code>.</p>
|
||
<p>Remember that unlike with Helm, <em>you’re not restricted to these actions</em> when you use Embark! You can call literally any command that it makes sense to with its keybinding or with <code>M-x</code> after running <code>embark-act</code>.</p>
|
||
<hr />
|
||
<h2 id="33">33%</h2>
|
||
<p>That’s fifteen useful Embark thingamajigs, and I didn’t get to mention <code>embark-become</code>. Or <code>embark-prefix-help-map</code>, <code>embark-which-key-prompter</code>, or Embark’s targets and target cycling, or half a dozen more thoughtful features and niceties about Embark. Maybe next time.</p>
|
||
<p>I’ll conclude instead by mentioning the main packages I used in the above demos:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/oantolin/embark"><code>embark</code></a> by Omar Antolin Camarena, who’s been a pleasure to interact with and pester with my requests for features. To add custom actions to the embark keymaps or otherwise customize Embark, I suggest perusing the README. It’s as readable and informative as they come.</li>
|
||
<li><a href="https://github.com/minad/consult"><code>consult</code></a> for its various enhancements to Emacs’ builtins. <code>consult-locate</code> and <code>consult-find</code> (actually <code>consult-fd</code>) to find files, <code>consult-imenu</code> for a colorful imenu with grouping and <code>consult-ripgrep</code> to grep across a directory.</li>
|
||
<li><a href="https://github.com/minad/marginalia"><code>marginalia</code></a> for the annotations in the minibuffer. Co-maintained by Omar Antolin and Daniel Mendler.</li>
|
||
<li><a href="https://github.com/minad/vertico"><code>vertico</code></a> as the minibuffer completion interface. Consult, Vertico and Marginalia are all authored by Daniel Mendler, who I’m convinced never sleeps. I didn’t even mention Corfu.</li>
|
||
<li>The <a href="https://github.com/oantolin/orderless"><code>orderless</code></a> completion style, also by Omar Antolin, to match pieces of text against minibuffer candidates independently. Together these five packages form the MOVEC pentagram, a composable enhancement suite that integrates Emacs’ loosely bound libraries into a modern and cohesive whole.</li>
|
||
<li><a href="https://github.com/karthink/consult-dir"><code>consult-dir</code></a> to switch directories quickly. I used this multiple times above to navigate to distant directories when in the minibuffer prompt.</li>
|
||
<li><a href="https://github.com/karthink/popper"><code>popper</code></a> to make <code>embark-collect</code>, help and other ephemeral buffers behave when they appear on screen.</li>
|
||
<li><a href="https://github.com/abo-abo/ace-window"><code>ace-window</code></a> by abo-abo, whose dispatch-keys idea in Ace-Window and Avy I promptly ripped off for Popper. If I understand correctly his Ivy-Occur was an early influence on what became Embark-Collect as well.</li>
|
||
<li><a href="https://gitlab.com/willvaughn/emacs-0x0/tree/63cd5eccc85e527f28e1acc89502a53245000428"><code>0x0</code></a> by William Vaughn. I use this far more often than I thought I would.</li>
|
||
</ul>
|
||
<p>Finally a quick note for Doom Emacs users: Doom ships with Embark out of the box (as of Sep 2021), you don’t need to do anything besides looking up the keys for <code>embark-act</code> and <code>embark-collect</code>.</p>
|
||
<p>Despite what these examples suggest, I estimate that I use less than a third of what Embark provides. Even so, in allowing me to change or chain actions at any time, it lets me pilot Emacs by the seat of my pants! A second, unforeseen benefit is that it makes commands and listings that I would never use available in a frictionless way: commands like <code>transpose-regions</code> and <code>apply-macro-to-region-lines</code>, or custom dired, ibuffer and package-menu listings that are interactively inaccessible otherwise<sup id="fnref:4"><a class="footnote-ref" href="https://karthinks.com/tags/emacs/index.xml#fn:4">4</a></sup>. The ability to quickly whip up such buffers makes knowhing how to use dired or ibuffer pay off several fold. In composing such features seamlessly with minibuffer interaction or with text-regions, Embark acts as a lever to amplify the power of Emacs’ myriad built in commands and libraries.</p>
|
||
<section class="footnotes">
|
||
<hr />
|
||
<ol>
|
||
<li id="fn:1">
|
||
<p>Although of course, Helm and Embark both do a good job with their presets. <a class="footnote-backref" href="https://karthinks.com/tags/emacs/index.xml#fnref:1">↩︎</a></p>
|
||
</li>
|
||
<li id="fn:2">
|
||
<p>To match the inverse of an input string with <code>!</code>, I used a <a href="https://github.com/oantolin/orderless#style-dispatchers">feature of the orderless package</a> for Emacs. <a class="footnote-backref" href="https://karthinks.com/tags/emacs/index.xml#fnref:2">↩︎</a></p>
|
||
</li>
|
||
<li id="fn:3">
|
||
<p>Yes, it’s not fully Helm-style since it still uses the minibuffer instead of a buffer to show the candidates/actions. You could use <a href="https://github.com/minad/vertico#extensions">vertico-buffer</a> if that’s a sticking point. <a class="footnote-backref" href="https://karthinks.com/tags/emacs/index.xml#fnref:3">↩︎</a></p>
|
||
</li>
|
||
<li id="fn:4">
|
||
<p>Technically custom package-menu listings are accessible. From the full package listing (<code>M-x list-packages</code>), you can filter package names by regexp with <code>/ n</code>. <a class="footnote-backref" href="https://karthinks.com/tags/emacs/index.xml#fnref:4">↩︎</a></p>
|
||
</li>
|
||
</ol>
|
||
</section> |