2020-12-14, updated: 2021-07-22
Tested with Nyxt 2 Pre-release 5.
The package manager interface profits from the Nyxt minibuffer to allow for quick fuzzy-search and multi-selection actions.
Why in Nyxt?
One might wonder: why include a package manager interface in a web browser?
In short, a package manager allows us to integrate more easily with external programs and services. For instance, if we want to add an interface to IPFS, we can automatically install the required dependencies (after prompting the user of course).
With a functional package manager like Nix or Guix, it's even better, we can leverage the
guix environment commands to run commands without installing them (i.e. without polluting the user environment).
Guix interface implementation details
Guix was the first interface we developed. This choice was made for two primary reasons:
Being functional (in the mathematical sense), it is possibly the package manager with the broadest feature set. If our interface is complete enough to support Guix, then it's probably complete enough to support any other package manager.
Being written in Guile Scheme (which is a Lisp), we can naturally "talk" to Guix with no necessity for cumbersome language bindings or brittle shell output parsing.
This last point is key:
Guix has a
guix repl command, which is an interpreter (just like the one you'd get when running
python). Our approach is to start the interpreter, send it some Guile code and read the result.
To start the process, we use
Note that the
guix process will automatically terminate when its input stream gets closed (which happens when Nyxt terminates).
To simplify, we can send instructions to the Guix REPL by writing Scheme code to the input stream:
Then we read the result with:
This should get us the current Guix version. It is really that easy!
Now this is where it gets mind blowing: we can write Guile Scheme code directly in our Common Lisp code base! While the syntax is similar, it's not completely interchangeable and the Common Lisp compiler will choke on some Guile Scheme symbols like
#f (true and false, respectively). The solution is to write reader macros. This is made easy thanks to the named-readtables library which lets us effortlessly switch between Scheme and Common Lisp syntax whenever, and wherever we want.
This is what our function to list installed packages looks like:
(defun list-installed (&optional (profile '%current-profile)) "Return the installed package outputs in PROFILE as a list of (NAME (:VERSION :OUTPUT)). PROFILE is a full path to a profile." (guix-eval '(use-modules (guix profiles)) `(define make-output ,%make-output) `(map make-output (manifest-entries (profile-manifest ,(namestring profile))))))
Notice that it allows us to intertwine Common Lisp and Guile Scheme. We define a Common Lisp function which evaluates a bunch of Scheme expressions which themselves expand other Common Lisp expressions!
In addition to more package manager support (Nix, Dpkg, etc.), we would like to extend our Common Lisp "system" management to bring it under the same interface, thereby providing a uniform experience for the user (whether installing Lisp systems or system packages).
Addendum: our experience with the Guix REPL has paved the way for more REPL interactions. This means that we are very close to implementing a universal REPL interface (à la Emacs'
comint-mode). This means supporting interpreters from programming languages such as Guile, or Python, and giving the user the possibility to fiddle with foreign code from within the browser itself!
Thanks for reading!