emacs/var/elfeed/db/data/21/21694acca40f79f4c32d2db9228edfffd8475ebc
2022-01-03 12:49:32 -06:00

173 lines
15 KiB
Plaintext

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="By Pierre Neidhardt" />
<title>Emacs Hacks</title>
<style type="text/css">
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
</style>
<style type="text/css">
a.sourceLine { display: inline-block; line-height: 1.25; }
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
a.sourceLine:empty { height: 1.2em; position: absolute; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
a.sourceLine { text-indent: -1em; padding-left: 1em; }
}
pre.numberSource a.sourceLine
{ position: relative; }
pre.numberSource a.sourceLine:empty
{ position: absolute; }
pre.numberSource a.sourceLine::before
{ content: attr(data-line-number);
position: absolute; left: -5em; text-align: right; vertical-align: baseline;
border: none; pointer-events: all;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
a.sourceLine::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<header>
<h1 class="title">Emacs Hacks</h1>
<p class="author">By Pierre Neidhardt</p>
</header>
<h1 id="nyxt-interfaces">Nyxt interfaces</h1>
<p>Nyxt can be controlled from an external program via SWANK. For instance, Emacs with <a href="https://github.com/slime/slime">SLIME</a> makes it possible to hack Nyxt while it's running.</p>
<p>But what about the other way around? Can we use Nyxt to manipulate other programs? Sure we can! Nyxt is a full-blown Common Lisp program that can leverage all of Common Lisp power to interact with foreign programs via Unix sockets, XML-RPC, you name it.</p>
<p>An interesting example is that of Emacs, since it can be hacked live with Elisp, a language very similar to Common Lisp. In the following article, Nyxt will generate Elisp code to hack Emacs live!</p>
<h1 id="youtube-dl-with-emacs">Youtube-dl with Emacs</h1>
<p><a href="https://yt-dl.org">Youtube-dl</a> is a great program for downloading audio and video files from a ridiculous number of websites.</p>
<p>That said, we would like to make it more convenient than constantly copy-pasting to a shell. Besides, naively calling <code>youtube-dl</code> from Nyxt is not so convenient since we lack feedback or interactivity with the running subprocess.</p>
<p>We could derive our own Youtube-dl extension for Nyxt so that we would have a buffer showing progress of multiple downloads and allowing for interactivity. However, until someone works on the extension, let's see what others have been doing so far.</p>
<p>Chris Wellon wrote <a href="https://github.com/skeeto/youtube-dl-emacs/">a great Emacs extension</a>, which sadly, only works for Youtube. This won't stop us! Here is our poor man's universal interface to Youtube-dl to supplement the aforementioned extension:</p>
<div class="sourceCode" id="cb1" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb1-1" data-line-number="1">(<span class="kw">defun</span><span class="fu"> youtube-dl-url </span>(&amp;optional url)</a>
<a class="sourceLine" id="cb1-2" data-line-number="2"> <span class="st">&quot;Run &#39;youtube-dl&#39; over the URL.</span></a>
<a class="sourceLine" id="cb1-3" data-line-number="3"><span class="st">If URL is nil, use URL at point.&quot;</span></a>
<a class="sourceLine" id="cb1-4" data-line-number="4"> (interactive)</a>
<a class="sourceLine" id="cb1-5" data-line-number="5"> (<span class="kw">setq</span> url (<span class="kw">or</span> url (thing-at-point-url-at-point)))</a>
<a class="sourceLine" id="cb1-6" data-line-number="6"> (<span class="kw">let</span> ((eshell-buffer-name <span class="st">&quot;*youtube-dl*&quot;</span>)</a>
<a class="sourceLine" id="cb1-7" data-line-number="7"> (<span class="kw">directory</span> (cl-loop for dir in &#39;(<span class="st">&quot;~/Videos&quot;</span> <span class="st">&quot;~/Downloads&quot;</span>)</a>
<a class="sourceLine" id="cb1-8" data-line-number="8"> <span class="kw">when</span> (file-directory-p dir)</a>
<a class="sourceLine" id="cb1-9" data-line-number="9"> <span class="kw">return</span> (expand-file-name dir)</a>
<a class="sourceLine" id="cb1-10" data-line-number="10"> finally <span class="kw">return</span> <span class="st">&quot;.&quot;</span>))</a>
<a class="sourceLine" id="cb1-11" data-line-number="11"> (eshell)</a>
<a class="sourceLine" id="cb1-12" data-line-number="12"> (<span class="kw">when</span> (eshell-interactive-process)</a>
<a class="sourceLine" id="cb1-13" data-line-number="13"> (eshell <span class="kw">t</span>))</a>
<a class="sourceLine" id="cb1-14" data-line-number="14"> (eshell-interrupt-process)</a>
<a class="sourceLine" id="cb1-15" data-line-number="15"> (insert (<span class="kw">format</span> <span class="st">&quot;cd &#39;%s&#39; &amp;&amp; youtube-dl &quot;</span> <span class="kw">directory</span>) url)</a>
<a class="sourceLine" id="cb1-16" data-line-number="16"> (eshell-send-input)))</a></code></pre></div>
<p>The above snippet opens a dedicated <code>*youtube-dl*</code> Eshell buffer so that we can track download progress from there, as well as stack multiple video downloads.</p>
<p>With that set up, now we can add the following snippet to our <code>nyxt/init.lisp</code>:</p>
<div class="sourceCode" id="cb2" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb2-1" data-line-number="1">(<span class="kw">defun</span><span class="fu"> eval-in-emacs </span>(&amp;<span class="kw">rest</span> s-exps)</a>
<a class="sourceLine" id="cb2-2" data-line-number="2"> <span class="st">&quot;Evaluate S-EXPS with emacsclient.&quot;</span></a>
<a class="sourceLine" id="cb2-3" data-line-number="3"> (<span class="kw">let</span> ((s-exps-string (cl-strings:replace-all</a>
<a class="sourceLine" id="cb2-4" data-line-number="4"> (<span class="kw">write-to-string</span></a>
<a class="sourceLine" id="cb2-5" data-line-number="5"> `(<span class="kw">progn</span> ,@s-exps) <span class="bu">:case</span> :downcase)</a>
<a class="sourceLine" id="cb2-6" data-line-number="6"> <span class="co">;; Discard the package prefix.</span></a>
<a class="sourceLine" id="cb2-7" data-line-number="7"> <span class="st">&quot;nyxt::&quot;</span> <span class="st">&quot;&quot;</span>)))</a>
<a class="sourceLine" id="cb2-8" data-line-number="8"> (<span class="kw">format</span> <span class="va">*error-output*</span> <span class="st">&quot;Sending to Emacs:~%~a~%&quot;</span> s-exps-string)</a>
<a class="sourceLine" id="cb2-9" data-line-number="9"> (uiop:run-program</a>
<a class="sourceLine" id="cb2-10" data-line-number="10"> (<span class="kw">list</span> <span class="st">&quot;emacsclient&quot;</span> <span class="st">&quot;--eval&quot;</span> s-exps-string))))</a>
<a class="sourceLine" id="cb2-11" data-line-number="11"></a>
<a class="sourceLine" id="cb2-12" data-line-number="12">(define-command youtube-dl-current-page ()</a>
<a class="sourceLine" id="cb2-13" data-line-number="13"> <span class="st">&quot;Download a video in the currently open buffer.&quot;</span></a>
<a class="sourceLine" id="cb2-14" data-line-number="14"> (with-result (url (buffer-get-url))</a>
<a class="sourceLine" id="cb2-15" data-line-number="15"> (eval-in-emacs</a>
<a class="sourceLine" id="cb2-16" data-line-number="16"> (<span class="kw">if</span> (<span class="kw">search</span> <span class="st">&quot;youtu&quot;</span> url)</a>
<a class="sourceLine" id="cb2-17" data-line-number="17"> `(<span class="kw">progn</span> (youtube-dl ,url) (youtube-dl-list))</a>
<a class="sourceLine" id="cb2-18" data-line-number="18"> `(youtube-dl-url ,url)))))</a>
<a class="sourceLine" id="cb2-19" data-line-number="19"></a>
<a class="sourceLine" id="cb2-20" data-line-number="20">(define-key <span class="st">&quot;C-c d&quot;</span> &#39;youtube-dl-current-page)</a></code></pre></div>
<p>We define a helper function <code>eval-in-emacs</code> which sends a bunch of formatted s-expressions to Emacs. This requires the Emacs daemon, either by starting Emacs with <code>emacs --daemon</code> or by running <code>M-x server-start</code> from an Emacs instance.</p>
<p>The <code>youtube-dl-current-page</code> command tests whether the current URL is Youtube, in which case we use Chris Wellons' <code>youtube-dl</code> extension, or else we rely on the Eshell version we wrote in our Emacs config.</p>
<h1 id="org-mode-and-org-capture">Org-mode and Org-capture</h1>
<p><a href="https://orgmode.org/">Org-mode</a> is a great extension for Emacs for note taking (and so much more). It has a feature called &quot;org-capture&quot; which, on a key press, will store some contextual information to your agenda, so that later Org will remind you to refer to it again.</p>
<p>This can be useful for a web browser: You'd like to mark this page and let Org remind you to read it later? Nothing easier! And off we go to add the following snippet to our Emacs init file:</p>
<div class="sourceCode" id="cb3" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb3-1" data-line-number="1">(add-to-list</a>
<a class="sourceLine" id="cb3-2" data-line-number="2"> &#39;org-capture-templates</a>
<a class="sourceLine" id="cb3-3" data-line-number="3"> `(<span class="st">&quot;w&quot;</span> <span class="st">&quot;Web link&quot;</span> entry (file+headline ,(<span class="kw">car</span> org-agenda-files) <span class="st">&quot;Links&quot;</span>)</a>
<a class="sourceLine" id="cb3-4" data-line-number="4"> <span class="st">&quot;* %?%a</span><span class="sc">\n</span><span class="st">:SCHEDULED: %(org-insert-time-stamp (org-read-date nil t </span><span class="sc">\&quot;</span><span class="st">+1d</span><span class="sc">\&quot;</span><span class="st">))</span><span class="sc">\n</span><span class="st">&quot;</span>))</a></code></pre></div>
<p>The above snippet does quite a few things, so let's analyze it carefully:</p>
<ul>
<li><p>The first <code>&quot;w&quot;</code> is the key binding to insert a web link when Org mode asks which capture to perform. You can add several capture templates with different key bindings to perform different actions.</p></li>
<li><p><code>(file+headline ,(car org-agenda-files) &quot;Links&quot;)</code> tells Org where to insert to result. Here we chose the &quot;Links&quot; headline in our first agenda file.</p></li>
<li><p><code>%a</code> is the Org URL of the page we are going to pass from Nyxt.</p></li>
<li><p>The rest is arbitrary. Here we schedule the reading to the day after (<code>+1d</code> in the time-stamp).</p></li>
</ul>
<p>See <code>org-capture-templates</code> documentation for more details.</p>
<p>Now to our <code>nyxt/init.lisp</code>:</p>
<div class="sourceCode" id="cb4" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb4-1" data-line-number="1">(define-command org-capture ()</a>
<a class="sourceLine" id="cb4-2" data-line-number="2"> <span class="st">&quot;Org-capture current page.&quot;</span></a>
<a class="sourceLine" id="cb4-3" data-line-number="3"> (with-result* ((url (buffer-get-url))</a>
<a class="sourceLine" id="cb4-4" data-line-number="4"> (title (buffer-get-title)))</a>
<a class="sourceLine" id="cb4-5" data-line-number="5"> (eval-in-emacs</a>
<a class="sourceLine" id="cb4-6" data-line-number="6"> `(org-link-set-parameters</a>
<a class="sourceLine" id="cb4-7" data-line-number="7"> <span class="st">&quot;nyxt&quot;</span></a>
<a class="sourceLine" id="cb4-8" data-line-number="8"> :store (<span class="kw">lambda</span> ()</a>
<a class="sourceLine" id="cb4-9" data-line-number="9"> (org-store-link-props</a>
<a class="sourceLine" id="cb4-10" data-line-number="10"> <span class="bu">:type</span> <span class="st">&quot;nyxt&quot;</span></a>
<a class="sourceLine" id="cb4-11" data-line-number="11"> :link ,url</a>
<a class="sourceLine" id="cb4-12" data-line-number="12"> :description ,title)))</a>
<a class="sourceLine" id="cb4-13" data-line-number="13"> `(org-capture))))</a>
<a class="sourceLine" id="cb4-14" data-line-number="14"></a>
<a class="sourceLine" id="cb4-15" data-line-number="15">(define-key <span class="st">&quot;C-c C-t&quot;</span> &#39;org-capture)</a></code></pre></div>
<p>This is similar to the example in the previous section. Note that we are passing multiple s-expressions to Emacs, they will all be wrapped into a single <code>(progn ...)</code> and Emacs will evaluate everything in one go.</p>
<p>Happy hacking!</p>
</body>
</html>