2021-05-14, updated: 2022-08-10
Tested with Nyxt 2 Pre-release 7.
Customizing the prompt buffer
For an introduction to the prompt buffer features and background story, see our other article.
In this article, we will review the API of the prompt buffer and see how the user can leverage this to write a custom prompt buffer.
API and customization
A prompt buffer is invoked with the
prompt command. For instance, in
set-url we can find:
(prompt :prompt "Open URL" :input (if prefill-current-url-p (render-url (url (current-buffer))) "") :history history ;; `actions' are defined elsewhere, more details below. :sources (list (make-instance 'new-url-or-search-source :actions actions) (make-instance 'global-history-source :actions actions) (make-instance 'bookmark-source :actions actions) (make-instance 'search-engine-url-source :actions actions)))
The only required parameters are
:prompt and the
Much of the prompt configuration actually happens in the sources themselves.
A source is just a class that inherits from
prompter:source. For instance the aforementioned
global-history-source could be defined with
(define-class global-history-source (prompter:source) ((prompter:name "Global history") (prompter:constructor (lambda (source) (declare (ignorable source)) (history-initial-suggestions))) (prompter:filter-preprocessor nil) ; Don't remove non-exact results. (prompter:actions '(buffer-load))) (:export-class-name-p t))
The required slots are
prompter:constructor. The latter provides a list of initial suggestions. If a list, the suggestions are immediately available. If a function, the initial suggestions are computed asynchronously, which is useful to avoid delaying the prompt display in case the initial suggestions take time to compute.
Actions are provided as a list of commands via the
prompter:actions slot. The above source has one action by default. It's possible to locally extend sources when invoking a prompt. For instance, in
set-url we have
(let (;; ... (actions (list (make-unmapped-command buffer-load) (make-command new-buffer-load (suggestion-values) "Load a URL in a new buffer." (make-buffer-focus :url (url (first suggestion-values))))))) ;; ... )
actions is passed to the various source instantiations as in:
An action is either a well-named command, as with buffer-load, or a locally-defined command like in the previous snippet.
Some more details are needed here:
define-commandand returns a
commandobject without defining it globally.
new-buffer-loadabove won't be listed in
make-unmapped-commandtransform a function into another one that takes a list as argument and runs the original function over the first element only.
Since prompts always return a list of suggestions (even when there is only one selected suggestion), this macro comes in handy to run existing functions over lists of 1 element.
Suggestion values are arbitrary objects: strings, numbers, URLs, structures… For compound structures, it's interesting to display the various parts of the objects (for instance, display the slots of a class).
This is where the
prompter:object-attributes method comes into play. The
global-history-source above lists suggestions of the
history-entry type. To display something more meaninful than a bunch of
in the prompt buffer, we can specialize the aforementioned method:
(defmethod prompter:object-attributes ((entry history-entry)) `(("URL" ,(render-url (url entry))) ("Title" ,(title entry))))
Now the suggestions are printed as
https://example.org Example Domain https://nyxt.atlas.engineer Nyxt ...
and all columns are automatically aligned for you!
Filters (suggestion processing)
Whenever the user inputs a character in the prompt buffer, the suggestions of all sources are processed.
prompter:filter-preprocessoris called on the whole list of suggestions. In particular, this means that the preprocessor can remove duplicates.
The source won't display anything until the filter-preprocessor is done.
prompter:filteris run over the suggestions returned by the preprocessor, one after the other.
This per-suggestion stepping allows the source view to update its display at regular intervals until all suggestions are filtered.
This filter function is particularly useful to score suggestions by relevance.
prompter:filter-postprocessoris run of the result of the filter, this time again over the entire list at once.
Once again, the source view is only updated when the postprocessor is done with the whole list.
The prompter is said to be ready when all its sources are ready, that is, when the preprocessor, the filter and the postprocessor have terminated for a given user input.
Internally, when the prompter filters and sorts suggestions, it wraps the list of initial values (arbitrary objects) into
It could be defined as follows:
(define-class suggestion () ((value nil :type t) (attributes '() :documentation "A non-dotted alist of attributes to structure the filtering. Both the key and the value are strings.") (match-data nil :type t :documentation "Arbitrary data that can be used by the `filter' function and its preprocessors. It's the responsibility of the filter to ensure the match-data is ready for its own use.") (score 0.0 :documentation "A score the can be set by the `filter' function and used by the `sort-predicate'.")) (:documentation "Suggestions are processed and listed in `source'. It wraps arbitrary object stored in the `value' slot. The other slots are optional. Suggestions are made with the `suggestion-maker' slot from `source'."))
This is useful to store information through the processing pipeline described in the previous section. For instance, the filter can calculate and store the score of each suggestion, in the
score slot, which in turn is readily available to the
filter-postprocessor for sorting and whatnot.
We hope this inspires you to write awesome configurations and, why not, extensions for Nyxt!
Thanks for reading :-)
Did you enjoy this article? Register for our newsletter to receive the latest hacker news from the world of Lisp and browsers!
- Maximum one email per month
- Unsubscribe at any time