Emacs, Nyxt and Engine-mode: how to browse URLs via Nyxt and Slime
Too long; didn't read
In my last blog, we saw how to leverage Guix to start using Nyxt. We also discovered how easy is to command Nyxt via Common Lisp. The problem is that I am too lazy to run this by hand every time. Also, I would rather focus on testing out how good Nyxt really is. (And pick up some Common Lisp in the process!)
My main issue is that all the setting up of Swank and Slime
connections distracts me from what I really want. For a start, I would like
Emacs to use Nyxt as any other browser. Emacs has a nice
interface for this: the
Still, the communication between Emacs and Nyxt is not the terminal,
but a CL REPL! Is the
browse-url-nyxt function doable?
It is a problem indeed
To have a
browse-url-nyxt is useful. It is the standard Emacs way to
search online. And there is a nice little mode that makes searches a
breeze: engine-mode. This allows you to define your own search engines
to use via Emacs. For example my DuckDuckGo search engine looks like
(defengine duckduckgo "https://duckduckgo.com/?q=%s" :keybinding "D" :browser 'browse-url-chromium)
Here I specify the query url for DuckDuckGo. Can you see that
the end of the URL? When I use this, engine-mode will ask me for a
search string and it will embed it there. The
the key I have to press to use this search. And the
defines what browser to use for it.
How cool would it be to simply add Nyxt in the browser section?! It would allow to do our searches in Common Lisp!
And there is a solution
Be ready for another hack of mine. The first challenge we face is to automate the start of the Nyxt browser. We want to call an Emacs command and have a Nyxt open and listen to us from a REPL. We need to run the browser, and to connect Swank and Slime. This is how it looks in Elisp.
(defun my/start-and-connect-to-nyxt (&optional no-maximize) "Start Nyxt with swank capabilities." (interactive) (async-shell-command (format "nyxt -e \"(nyxt-user::start-swank)\"")) (sleep-for my/slime-nyxt-delay) (my/slime-connect "localhost" "4006") (unless no-maximize (my/slime-repl-send-string "(toggle-fullscreen)")))
First, we run asynchronously Nyxt with a Lisp expression that starts
the Swank connection. Then we wait a little for the connection to be
ready. At this point we can connect Slime. Finally, we command Nyxt to
go full screen (unless we don't want to). Note by the way: we just
used a CL namespace! We need that
nyxt-user:: because Nyxt evaluates
a Common Lisp but does not import its package. How cool are we?
We need the delays because both Nyxt and Slime take some time to run.
my/slime-connect function is a bit ugly because my local version
of Slime is different from the Swank version of Guix.
(defun my/slime-connect (host port) (defun true (&rest args) 't) (advice-add 'slime-check-version :override #'true) (slime-connect host port) (sleep-for my/slime-nyxt-delay) (advice-remove 'slime-check-version #'true))
You can see how I have to "advice"
slime-check-version to stop
asking me: "is it alright if versions mismatch?".
my/slime-repl-send-string needs the same trick.
(defun my/slime-repl-send-string (sexp) (defun true (&rest args) 't) (advice-add 'slime-check-version :override #'true) (if (slime-connected-p) (slime-repl-send-string sexp) (error "Slime is not connected to Nyxt. Run `my/start-and-connect-to-nyxt' first.")) (sleep-for my/slime-nyxt-delay) (advice-remove 'slime-check-version #'true))
This function simply sends a string to the current Slime connection. For now I am using strings, but it would be much more Lispy to make this a macro. That way we could just input lists and make strings in the macro.
Next challenge is to browse the url we want. We just need to know the
right command for this. Then we can send it via
(defun my/browse-url-nyxt (url &optional buffer-title) (interactive "sURL: ") (my/slime-repl-send-string (format "(buffer-load \"%s\" %s)" url (if buffer-title (format ":buffer (make-buffer :title \"%s\")" buffer-title) ""))))
buffer-load is the Nyxt function we need. The optional argument lets
us open a new link in another "tab"/buffer of the browser.
At this point we have all we need to make
(defun browse-url-nyxt (url &optional new-window) (interactive "sURL: ") (unless (slime-connected-p) (my/start-and-connect-to-nyxt)) (my/browse-url-nyxt url url))
This looks line any other browse-url function. If we miss the Slime connection to Nyxt, we create one. The we just try to browse through Nyxt.
Cherry on the cake: our new engine-mode definition!
(defengine duckduckgo "https://duckduckgo.com/?q=%s" :keybinding "n" :browser 'browse-url-nyxt)
You can find the entire code here.
This was our first integration of Emacs and Nyxt! You saw also that we learned some Common Lisp in the process. This is just great!
Feel free to get in touch if you start Nyxting: we can learn together!