2018-12-16, updated: 2024-03-12
Tested with Nyxt 1.3.
Emacs Hacks
Nyxt interfaces
Nyxt can be controlled from an external program via SWANK. For instance, Emacs with SLIME makes it possible to hack Nyxt while it's running.
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.
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!
Youtube-dl with Emacs
Youtube-dl is a great program for downloading audio and video files from a ridiculous number of websites.
That said, we would like to make it more convenient than constantly copy-pasting to a shell. Besides, naively calling youtube-dl
from Nyxt is not so convenient since we lack feedback or interactivity with the running subprocess.
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.
Chris Wellon wrote a great Emacs extension, 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:
(defun youtube-dl-url (&optional url)
"Run 'youtube-dl' over the URL.
If URL is nil, use URL at point."
(interactive)
(setq url (or url (thing-at-point-url-at-point)))
(let ((eshell-buffer-name "*youtube-dl*")
(directory (cl-loop for dir in '("~/Videos" "~/Downloads")
when (file-directory-p dir)
return (expand-file-name dir)
finally return "."))
(eshell)
(when (eshell-interactive-process)
(eshell t))
(eshell-interrupt-process)
(insert (format "cd '%s' && youtube-dl " directory) url)
(eshell-send-input)))
The above snippet opens a dedicated *youtube-dl*
Eshell buffer so that we can track download progress from there, as well as stack multiple video downloads.
With that set up, now we can add the following snippet to our nyxt/init.lisp
:
(defun eval-in-emacs (&rest s-exps)
"Evaluate S-EXPS with emacsclient."
(let ((s-exps-string (cl-strings:replace-all
(write-to-string
`(progn ,@s-exps) :case :downcase)
;; Discard the package prefix.
"nyxt::" "")))
(format *error-output* "Sending to Emacs:~%~a~%" s-exps-string)
(uiop:run-program
(list "emacsclient" "--eval" s-exps-string))))
(define-command youtube-dl-current-page ()
"Download a video in the currently open buffer."
(with-result (url (buffer-get-url))
(eval-in-emacs
(if (search "youtu" url)
`(progn (youtube-dl ,url) (youtube-dl-list))
`(youtube-dl-url ,url)))))
(define-key "C-c d" 'youtube-dl-current-page)
We define a helper function eval-in-emacs
which sends a bunch of formatted s-expressions to Emacs. This requires the Emacs daemon, either by starting Emacs with emacs --daemon
or by running M-x server-start
from an Emacs instance.
The youtube-dl-current-page
command tests whether the current URL is Youtube, in which case we use Chris Wellons' youtube-dl
extension, or else we rely on the Eshell version we wrote in our Emacs config.
Org-mode and Org-capture
Org-mode is a great extension for Emacs for note taking (and so much more). It has a feature called "org-capture" 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.
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:
(add-to-list
'org-capture-templates
`("w" "Web link" entry (file+headline ,(car org-agenda-files) "Links")
"* %?%a\n:SCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"+1d\"))\n"))
The above snippet does quite a few things, so let's analyze it carefully:
The first
"w"
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.(file+headline ,(car org-agenda-files) "Links")
tells Org where to insert to result. Here we chose the "Links" headline in our first agenda file.%a
is the Org URL of the page we are going to pass from Nyxt.The rest is arbitrary. Here we schedule the reading to the day after (
+1d
in the time-stamp).
See org-capture-templates
documentation for more details.
Now to our nyxt/init.lisp
:
(define-command org-capture ()
"Org-capture current page."
(with-result* ((url (buffer-get-url))
(title (buffer-get-title)))
(eval-in-emacs
`(org-link-set-parameters
"nyxt"
:store (lambda ()
(org-store-link-props
:type "nyxt"
:link ,url
:description ,title)))
`(org-capture))))
(define-key "C-c C-t" 'org-capture)
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 (progn ...)
and Emacs will evaluate everything in one go.
Happy hacking!
Did you enjoy this article? Register for our newsletter to receive the latest hacker news from the world of Lisp and browsers!
- Maximum one email per month
- Unsubscribe at any time