Untitled

Next interfaces

Next can be controlled from an external program via SWANK. For instance, Emacs with SLIME makes it possible to hack Next while it's running.

But what about the other way around? Can we use Next to manipulate other programs? Sure we can! Next 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, Next 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 Next is not so convenient since we lack feedback or interactivity with the running subprocess.

We could derive our own Youtube-dl extension for Next 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 (expand-file-name (or (file-directory-p "~/Videos")
                                         (file-directory-p "~/Downloads")
                                         "."))))
    (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 next/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.
                        "next::" "")))
    (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 *global-map* (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:

See org-capture-templates documentation for more details.

Now to our next/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
       "next"
       :store (lambda ()
                (org-store-link-props
                 :type "next"
                 :link ,url
                 :description ,title)))
     `(org-capture))))

(define-key *global-map* (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!