If you are new to hacking on Nyxt, you may be looking for a straightforward way to set up the development environment. While there are several ways to go about it, we describe the approach the Nyxt team uses.
Firstly, install:
With these working on your system, clone the Nyxt repository at
~/common-lisp
. Then add the snippet below to your Emacs configuration file and
reload it.
(load "~/common-lisp/nyxt/build-scripts/nyxt-guix.el" :noerror)
(setq sly-lisp-implementations
'((nyxt-sbcl
(lambda () (nyxt-make-guix-cl-for-nyxt
"~/common-lisp/nyxt"
;; You may set it to t, if you experience odd behavior.
:force nil
:cl-implementation "sbcl"
:cl-system "nyxt/gi-gtk"
:no-grafts t
:ad-hoc '("emacs" "xdg-utils" "git"))))))
Then, to start the SLY REPL, M-- M-x sly RET nyxt-sbcl RET
. Wait for it to
finish and the REPL will open. At this point you are almost ready to start
hacking. In the SLY REPL, evaluate as follows:
(asdf:load-system :nyxt/gi-gtk)
(nyxt:start)
With Nyxt started in this way, you can hack on Nyxt without recompiling the whole codebase or even restarting the browser. You can make your changes, recompile only the relevant file/expression with SLY and it’s done.
You can also run the full test suite locally with:
(asdf:test-system :nyxt/gi-gtk)
It is recommended to restart the SLY session before and after running the tests.
There are several ways to install Nyxt on your system. For example, to install with Guix, follow the instructions in the ../build-scripts/nyxt.scm file of the Nyxt repository. In this section, we will provide a more tool-agnostic way.
Nyxt is written in Common Lisp. It should build with any standard Common Lisp implementation but currently, only SBCL support is tested. It is designed to be cross-platform, cross-engine compatible. Nyxt is available in both WebKit and WebEngine (experimental) flavors.
You’ll need SBCL ≥ 2.0.0 to compile Nyxt.
You can obtain SBCL from your package manager or by downloading it directly from the SBCL repository.
To install SBCL from source, download SBCL: http://www.sbcl.org/platform-table.html. Full installation instructions can be found here: http://www.sbcl.org/getting.html.
All Lisp dependencies are included as Git submodules of this repository, so there’s no extra setup.
In case you’d like to manage them manually, set the environment variable
NYXT_SUBMODULES=false
and place them at a location that ASDF honors. Note the
Nyxt requires some Lisp libraries and, since some are pinned at specific
versions, relying on Quicklisp is discouraged.
- WebKitGTK+ also known as webkit2gtk (make sure to use the most recent version for security reasons)
- gobject-introspection (for WebKitGTK+ bindings)
- glib-networking (for WebKitGTK+)
- gsettings-desktop-schemas (for WebKitGTK+)
- libfixposix
- xclip (if on X) or wl-clipboard (if on Wayland) (for clipboard support)
- enchant (for spellchecking)
- Debian-based distributions:
sudo apt install sbcl libwebkit2gtk-4.0-dev gobject-introspection glib-networking gsettings-desktop-schemas libfixposix-dev pkg-config xclip enchant-2 libssl-dev
- Arch Linux:
sudo pacman -S git sbcl cl-asdf webkit2gtk gobject-introspection glib-networking gsettings-desktop-schemas enchant libfixposix
- Fedora:
sudo dnf install sbcl webkit2gtk4.0-devel glib-networking gsettings-desktop-schemas libfixposix-devel xclip wl-clipboard enchant pkgconf
- FreeBSD and derivatives
pkg install sbcl webkit2-gtk3 glib-networking libfixposix xclip enchant rubygem-pkg-config
If your distribution does not install libraries in an FHS-expected location, you
have to let your Lisp compiler know where to find them. To do so, add the
library directories to cffi:*foreign-library-directories*
list. For instance,
if you are running Guix you may want to expose ~/.guix-profile/lib
to the
compiler by adding the following snippet to ~/.sbclrc
:
(require "asdf")
(let ((guix-profile (format nil "~a/.guix-profile/lib/" (uiop:getenv "HOME"))))
(when (and (probe-file guix-profile)
(ignore-errors (asdf:load-system "cffi")))
(push guix-profile
(symbol-value (find-symbol (string '*foreign-library-directories*)
(find-package 'cffi))))))
A note of caution about installing WebKit via your package manager: Your distribution supplied version of WebKit may not provide up-to-date versions of WebKit including the latest security patches. WebKitGTK+ tries to do the best job possible with maintaining security patches upstream, but it is also up to the distribution provider to update their packages to take advantage of these fixes.
Clone the Nyxt repository into ~/common-lisp
(or another directory where ASDF
will find it):
mkdir -p ~/common-lisp
git clone --recurse-submodules https://github.com/atlas-engineer/nyxt ~/common-lisp/nyxt
The following command will build the Lisp core.
- GNU/Linux:
make all
- FreeBSD
gmake all
Inside the Makefile you’ll find many options you can specify. Run make
to display some documentation or see the Makefile for more details.
After compiling Nyxt or installing it in some other way, you can use SLIME or
SLY to interact with it in a REPL. This is accomplished by starting a swank
or slynk
server (for SLIME and SLY respectively) from Nyxt and connecting
to it through Emacs.
- Run the command
start-swank
in Nyxt. Note the port number in the message buffer. The default is 4006. - Connect to the
swank
server in Emacs withM-x slime-connect RET 127.0.0.1 RET 4006
.
- Run the command
start-slynk
in Nyxt. Note the port number in the message buffer. The default is 4006. - Connect to the
slynk
server in Emacs withM-x sly-connect RET 127.0.0.1 RET 4006
.
Nyxt is a joint effort and we need you to make it succeed! You can find ideas on our issue tracker to suit your interests and skills. When ready to start working please fork the repository, add your changes and open a pull request on GitHub to pass the review process. Refer to the branch management section for more detailed information.
You can contribute to Nyxt without commit access. However, if you’re a frequent contributor, you may request it. Remember that with great power comes great responsibility.
Feel free to contact us at any point if you need guidance. There are several ways to ask for help from the community.
The first and easiest one is to simply open up an issue with whatever problem or suggestion you wish to discuss.
See https://nyxt-browser.com/learn-lisp for a few recommendations.
You can find Nyxt on Libera IRC: #nyxt.
We follow the general Git guidelines, namely we try to commit atomic changes that are “clean”, that is, on which Nyxt builds and starts.
Make sure to make seperate commits in these cases to avoid distracting noise in commits with actual changes:
- Indentation and whitespace trimming;
- Code movements (within a file or to a different file). In this case, it’s crucial that the commit contains nothing else, otherwise “diffs” may fail to highlight the changes.
For commit messages, we follow (somewhat flexibly) the convention of prefixing
the title with the basename of the file that was modified. For instance, for
changes in source/mode/blocker.lisp
the commit message would look like this:
mode/blocker: What and why this change. Rest of the message here.
Your commit should clarify what it does and why (in case it’s not already obvious).
Nyxt uses the following branches:
master
for development;<feature-branches>
for working on particular features;<2,3,...>-series
to backport commits corresponding to specific major versions.
It’s recommended to branch off from the target branch and to rebase onto it right before merging. This keeps the history as clear as possible and reduces the complexity of the diff.
Unless the changes are trivial and each commit is atomic (that is, leaving Nyxt
fully functional), they should be followed by a merge commit. That is
guaranteed by using the merge option no-ff
(no fast-forward). If required,
the merge commit can be reworded.
The names of the branches really matter since the merge commit references them, so please take that into account!
After the changes are merged, please do not forget to delete obsolete or dangling branches. If you merge the remote branch instead of the local one, then GitHub deletes the remote branch automatically.
Note to core contributors: since you have commit access, you can push trivial changes directly to the target branch (skipping the review process). The merge commit is required when at least one commit isn’t atomic.
- Add and shallow clone upstream source as a Git submodule in ../_build/ directory.
- Add dependency name to ../nyxt.asd and documents/SOURCES.org.
- Add dependency to ../build-scripts/nyxt.scm, checking to make sure Guix already has it packaged.
We try to follow the usual Common Lisp conventions as recommended by Norvig & Pitman’s Tutorial on Good Lisp Programming Style and Google Common Lisp Style Guide.
For symbol naming conventions, see https://www.cliki.net/Naming+conventions.
We’ve also developed some of our own:
- Prefer
first
andrest
overcar
andcdr
respectively. - Use
define-class
instead ofdefclass
. - Use
nyxt:define-package
for Nyxt-related pacakges. Notice that it features default imports (e.g.export-always
) and package nicknames (e.g.alex
,sera
, etc.). Preferuiop:define-package
for general purpose packages. - Export using
export-always
(from Serapeum) next to the symbol definition. This helps prevent exports to go out-of-sync, or catch typos. Unlikeexport
,export-always
saves you from surprises upon recompilation. - When sensible, declaim the function types using
->
(from Serapeum). Note that there is then no need to mention the type of the arguments and the return value in the docstring. - Use the
maybe
andmaybe*
types instead of(or null ...)
and(or null (array * (0)) ...)
respectively. - Use the
list-of
type for typed lists. - We make heavy use of Alexandria and Serapeum, remember to use them instead of
writing the same boilerplate over and over. In particular, note these
systematic uses of Serapeum:
sera:eval-always
;export-always
;sera:and-let*
;sera:lret
;sera:single
->
(declaimed types).
- Use
funcall*
to not error when function does not exist. - Prefer classes over structs. Rationale:
- Class slots have documentation.
- Class allow for full-fledged CLOS use (metaclasses, etc.).
- Structs have read-only slots but it’s easy enough to implement them for classes.
- Structs have better performance, but this is usually micro-optimization, and even then class implementations can be made more efficient via MOP.
- Classes should be usable with just a
make-instance
. - Slots classes should be formatted in the following way:
(slot-name
slot-value
...
:documentation "Foo.")
When slot-value
is the only parameter specified then:
(slot-name slot-value)
- Prefer
defmethod
overdefun
if one of the arguments is a user-class. This allows the user to write specializations of subclasses. customize-instance
is reserved for end users. Useinitialize-instance :after
orslot-unbound
to initialize the slots. Set up the rest of the class incustomize-instance :after
. Bear in mind that anything in this last method won’t be customizable for the end user.- Almost all files should be handled via the
nfiles
library. - Specialize
print-object
for recurring class instances. (setf SLOT-WRITER) :after
is reserved for “watchers”, i.e. handlers that are run whenever the slot is set. The:around
method is not used by watchers, and thus the watcher may be overridden.- A function as a slot value is often a sign that it should be a method instead.
Methods give more flexibility to the end user.
Example: Avoid adding a
constructor
slot, make it a method instead. - Define generic functions (in particular if they are heavily used) using an
explicit call to
defgeneric
, not with just calls todefmethod
. This enables proper source location of the generic function (otherwise it cannot be found), plus it lets you write different documentation for the generic and the specialized methods. - We use the
%foo%
naming convention for special local variables. But special variables are rare and ideally they should be avoided. - We suffix predicates with
-p
. Unlike the usual convention, we always use a hyphen even for single word predicates. - Prefer the term
url
overuri
. - URLs should be of type
quri:uri
. If you need to manipulate a URL string, call iturl-string
. In case the value contains a URL, but is notquri:url
, useurl-designator
and itsurl
method to normalize intoquri:uri
. - Paths should be of type
cl:pathname
. Useuiop:native-namestring
to “send” to OS-facing functions,uiop:ensure-pathname
to “receive” from OS-facing functions or to “trunamize”. - Prefer
handler-bind
overhandler-case
: when running from the REPL, this triggers the debugger with a full stacktrace; when running the Nyxt binary, all conditions are caught anyway. - Do not handle the
T
condition, this may break everything. Handleerror
,serious-condition
, or exceptionallycondition
(for instance if you do not control the called code, and some libraries subclasscondition
instead oferror
). - Dummy variables are called
_
. - Prefer American spelling.
- Construct
define-command
requires a short one-line docstring without newlines.