The Duat Text Editor
Duat is a text editor that is built and configured in the Rust programming language. It is meant to be very modular in its design, while still having great defaults for anyone used to TUI and modal text editors.
Installation
In order to begin using Duat, you will need cargo
installed in your system.
See cargo's installation instructions for more info. After installing
cargo
, you will want to make sure you're using rust's nighlty features:
rustup install nightly
Now, you can go ahead and install duat using cargo
. Duat will be installed
for the current user, in the ~/.cargo/bin/
directory:
cargo install duat
Or, if you want the master
version of duat, from the latest commit, you can
do this:
cargo install --git https://github.com/AhoyISki/duat --features git-deps
When calling either of these commands, if it didn't already exist, a default
version of Duat's configuration will be installed in $XDG_CONFIG_HOME/duat
(~/.config/duat
for Linux). If you want to get the latest version of the
default configuration, you can just remove that, alongside other directories
used by Duat:
rm -rf ~/.config/duat/ ~/.cache/duat/ ~/.local/share/duat/
cargo install --git https://github.com/AhoyISki/duat --features gid-deps
If you don't have ~/.cargo/bin/
in your $PATH
variable, you should add that
in order to be able to call duat
. Alternatively, you may add just
~/.cargo/bin/duat
, if you don't want to bring the other things in
~/.cargo/bin/
to global executable scope.
At this point, you should be able to use Duat by calling duat
or duat some_file
. The first time you run duat
, the compilation of the config
crate
will take a while, but after running the reload
command from duat, it should
recompile in ~0.5 to ~2.5 seconds, depending on what you changed in the config.
To see how you can configure duat, look at the next chapter in this book.
Quick settings
First of all, before you start anything, you should try to become at least somewhat accustomed to how rust works as a programming language. If you don't know anything at all, the rust book is a great place to start learning.
One of the main aspects that Duat tries to accomplish is an emergent complexity for its features. That is, the more you learn about Duat, the easier it becomes to learn new things about it. Every feature should help you understand other features.
That being said, here are some quick settings, which should hopefully compose later on to help you better understand duat.
The setup
function and setup_duat!
In order for Duat to be able to run your config crate in ~/.config/duat
, it needs to have at the very least this in it:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() {} }
That's because Duat will call the setup
function (after some initial setup,
and before some final setup) in order to do the initial setup for duat. For all
intents and purposes, treat the setup
function as if it were the init.lus
on a neovim configuration, as it will do all the setup for Duat.
Of course, not everything needs to be placed inside of this function. For example, you could separate the plugins into a separate function, if you think that will look cleaner:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { add_plugins(); } fn add_plugins() { plug!( treesitter::TreeSitter, kak::Kak::new(), catppuccin::Catppuccin::new() ); } }
The plug!
macro
The plug!
macro serves the purpose of adding plugins to Duat. Every plugin
will have its own configuration options, that you will need to look at their
documentation to understand. But they should all be configured in the same way:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { plug!( kak::Kak::new() .f_and_t_set_search() .with_no_indent_on_capital_i(), ); } }
In this case, the Kak
plugin (from duat-kak
) is being modified via the
builder pattern in order to have the following settings:
- The
f
andt
keys will set the search pattern, son
will look for it. - When pressing
I
, don't reindent.
Then, it is being added to Duat. The f_and_t_set_search
and
with_no_indent_on_capital_i
modify the Kak
plugin and then return it back
to be plugged. This is the builder pattern, and is how all plugins are added.
The prelude
module
At the top of your crate, you should be able to find this:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { // The stuff inside your setup... } }
This will import everything in the prelude
module of duat
. This should have
everything you will need in order to configure Duat, not including things from
other crates that you may want to import (such as plugins).
When calling use duat::prelude::*
, most imported things will be in the form
of modules, like this:
#![allow(unused)] fn main() { use duat::print; }
This is importing the print
module, as opposed to importing its items
directly, like this:
#![allow(unused)] fn main() { use duat::print::*; }
This means that, for most options, their path is made up of a
{module}::{function}
combo. This means that the usual config
crate should
look something like this:
#![allow(unused)] fn main() { mod kak { use duat::{prelude::{*, mode::KeyEvent}}; #[derive(Clone)] pub struct Insert; impl Mode<Ui> for Insert { type Widget = File; fn send_key(&mut self, _: &mut Pass, _: KeyEvent, _: Handle<File>) { todo!(); } } pub struct Kak; impl Kak { pub fn new() -> Self { Self } } impl duat_core::Plugin<Ui> for Kak { fn plug(self) {} } } setup_duat!(setup); use duat::print::*; fn setup() { plug!(Kak::new()); print::wrap_on_cap(150); print::trailing_new_line(''); form::set("caret.main", Form::yellow()); cmd::add!("set-rel-lines", |pa, ln: Handles<LineNumbers<Ui>>| { ln.on_flags(pa, |pa, handle| { handle.write(pa, |ln, _| ln.options = ln.options.rel_abs()); }); }); map::<Insert>("jk", "<Esc>:w<Enter>"); } }
The exceptions to this are the map
and alias
functions, as well as the
plug!
and setup_duat!
macros. These items are imported directly.
The following chapters should give a quick overview of these items imported from the prelude module.
print
: How duat prints files
The print module has a bunch of simple functions to change how duat should print File
widgets:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { print::no_wrapping(); print::indent_wraps(true); print::tabstop(4); print::scrolloff(3, 3); print::word_chars!("A-Za-z0-9_-_"); print::new_line(' '); } }
These are the default options (for File
s). One thing to note about this
module, is that it is for File
s and File
s only. That is, nothing in
here will affect other widgets, like LineNumbers
or StatusLine
. If you want
to modify those, you can head over to the hook
chapter.
The functions in this module will affect all files unconditionally, if you want
to do that on a File
by File
basis, again, see the hook
chapter.
This is the current list of options in this module:
print::no_wrapping
: The default, don't wrap text at all;print::wrap_on_edge
: Wraps the text on the edge of its area;print::wrap_on_cap
: Wraps at a distance from the left edge, can go over the right edge;print::wrap_on_words
: Wraps on word termination, instead of any character;print::indent_wraps
: Copies the indentation on wrapped lines, can be used with the other options;print::tabstop
: The width of tabs, is also used to determine how many spaces a\t
should add. Is 4 by default;print::new_line
: What character to print when printing'\n'
s. Is' '
by default;print::trailing_new_line
: What character to print when printing'\n'
s that are preceeded by spaces. Is not set by default;
print::word_chars!
print::word_chars!
is a macro that determines which characters should be part
of a word. This is used by wrap_on_words
and tends to also be used to modify
text (in the <Ctrl-Backspace>
key in most editors, for example).
This macro is evaluated at compile time, so you don't have to worry about it being correct or not, and the syntax is the same as in regex, like this:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { print::word_chars!("A-Za-z0-9---_-_"); } }
In this case, every sequence of lowercase and capital letters, numbers, dashes and underscores would be considered part of a word.
form
: How text is colored
In duat, the way text is styled is through Form
s. The Form
struct,
alongside the form
module, are imported by the prelude:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { // Setting by Form form::set("punctuation.bracket", Form::red()); form::set("default", Form::with("#575279").on("#faf4ed")); form::set("matched_pair", Form::blue().underlined()); // Setting by reference form::set("accent.debug", "default.debug"); } }
The main function that you will use from this module is form::set
. This
function sets the form with the given name on the left to the argument on the
right. This argument can be of two types:
- A
Form
argument will just modify the named form to be shown like that; - A
&str
argument will "reference" the form on the right. If the form on the right is altered, so will the one on the left. This reduces the need for setting a ton of forms in things like colorschemes;
How forms should be named
Every form in duat should be named like this: [a-z0-9]+(\.[a-z0-9]+)*
. That
way, inheritance of forms becomes very predictable, and it's much easier for
plugin writers to depend on that feature.
There is one exception to this rule however, that being the defaut
form. The
default
form, unlike other forms, can have Widget
specific implementations,
like default.StatusLine
, which will change the default
form only on
StatusLine
s, and is set by default.
Colorschemes
The other main function that you will use from this module is the
form::set_colorscheme
function. This function will change the colorscheme to
a previously named one:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; use catppuccin::Catppuccin; fn setup() { // Adds four colorschemes, "catppuccin-latte" among them. plug!(Catppuccin::new()); form::set_colorscheme("catppuccin-latte"); } }
Form
inheritance
Another aspect of duat's forms that can save a lot of typing is the concept of Form
inheritance. In Duat, forms follow the following structure:
- If
form.subform
is unset, it will referenceform
; - If
form.subform
is set toForm::green()
, it won't be changed whenform
changes, staying atForm::green()
; - If
form.subform
is set to referenceother_form
, changingother_form
will also changeform.subform
, but changingform
won't;
As a consequence of this, for example, if you were to set the markup
form to
something, every form with a name like markup.*
that isn't already set,
would follow the change to the markup
form.
Additionally, if the form f0.f1.f2
is set to something, the forms f0
and
f1.f2
would also be set, although they will reference the default
form in
that situation, not whatever f0.f1.f2
was set to.
Masks
A mask is essentially the opposite of the inheritance concept. Instead of the longer form inheriting from the shorter forms, the shorter forms will be mapped for longer ones.
It works like this: Say I have a File
widget, and in it, there are instances
of the function
form, used to highlight function identifiers. If there is a
function.error
form, and I tell the File
to use the error
mask, instead
of using the function
form, Duat will use function.error
.
In duat, by default there are four masks: error
, warning
, info
, and inactive
. The first three are used primarily to show color coded notifications. The last one is unused, but you can use it to change how unfocused files should be displayed.
You can also add more masks through form::enable_mask
. If you want to learn
more about masks and how to use them, you should check out the masks
chapter
map and alias: modifying keys
In Duat, mapping works somewhat like Vim/neovim, but not quite. This is how it works:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; use kak::Kak; fn setup() { // Adds kakoune-like editing modes, like Insert, Normal and OneKey plug!(Kak::new()); map::<User>("f", "<Esc>|fold -s<Enter>"); alias::<Insert>("jk", "<Esc>"); alias::<Prompt>("jk", "<Esc>"); } }
In mapping, there are two main functions: map
and alias
. map
will take
the keys as is, and if the sequence matches, outputs the remapping, otherwise,
outputs the keys that were sent. alias
does the same thing, but it also
""prints"" the sequence that was sent, making it look like you are typing
real text.
In the functions, you can see a type argument. This type argument is the Mode
where this mapping will take place. So here, in duat-kak
's Insert
mode, if
you type jk
, the j
will show up as ""text"", but when you press k
, you
will immediately exit to Normal
Mode
.
User
is a standard Mode
in Duat. It is meant to be a "hub" for Plugin
writers to put default mappings on. Sort of like the leader key in Vim/Neovim.
On duat-kak
, by default, this mode is entered by pressing the space bar.
While you can change that like this:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; use kak::Kak; fn setup() { plug!(Kak::new()); map::<Normal>(" ", ""); // In rust, you have to escap a backslash map::<Normal>("\\", " "); } }
You can also do this:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; use kak::Kak; fn setup() { plug!(Kak::new()); map::<Normal>(" ", ""); map::<Normal>("\\", User); } }
This is allowed in order to support custom Mode
s. That way, you can just
place the Mode
as the second argument, and the mapping will switch modes,
instead of sending keys. This also works with alias
.
[!NOTE] In this case, since
User
is a struct with no fields, I could just putUser
as the second argument, which acts as a constructor. But in most otherMode
s, you're gonna have to write something likeInsert::new()
as the argument instead.
List of keys and modifiers
Syntax wise, the keys are very similar to vim style. Regular characters are
placed normally, special keys are enclosed in <
,>
pairs, and modified keys
are enclosed in these pairs, with a <{mod}-{key}>
syntax. Examples:
abc<C-Up><F12>
.<A-Enter><AS-Left>
.づあっと
.
This is the list of recognized special keys:
<Enter>
,<Tab>
,<Bspc>
,<Del>
,<Esc>
,<Up>
,<Down>
,<Left>
,<Right>
,<PageU>
,<PageD>
,<Home>
,<End>
,<Ins>
,<F{1-12}>
,
And these are the allowed modifiers, which, as you can see above, can be composed together:
C => Control
,A => Alt
,S => Shift
,M => Meta
,super => Super
,hyper => Hyper
,
cursor
: How to print cursors
The cursor
module is like the print
module, in that it provides some basic
options on how cursors should be printed. These options primarily concern if
cursors should be printed as "real cursors" (The blinking kind, that can turn
into a bar and stuff), or as just Form
s.
cursor::set_main
will set the "shape" of the main cursor. This takes aCursorShape
argument, and lets you set its shape to a vertical bar, a horizontal bar, and make it blink.cursor::set_extra
is the same but for extra cursors. Do note that this may not work on someUi
s, mainly terminals, which only allow for one cursor at a time.cursor::unset_main
andcursor::unset_extra
: Disables cursor shapes for every type of cursor, replacing them with aForm
, which will becaret.main
andcaret.extra
, respectivelycursor::unset
: The same as callingunset_main
andunset_extra
.
List of forms
Currently, in duat
, these are the forms in use:
default
: Is applied to every text. Can beWidget
dependent, likedefault.LineNumbers
.accent
: This form is used when formatting error, warning, information, or debug messages. Is used mostly with theerror
,warn
andinfo
masks.caret.main
: The form to use when printing the main "caret" (each cursor has a selection, an anchor, and a caret).caret.extra
: Same ascaret.main
, but for cursors other than the main one.selection.main
: Color to be used on the main selection.selection.extra
: Color to be used on extra selections.cloak
: This form is supposed to be a common "get rid of all forms temporarily" form. You should use this when you want to, for example, remove all visible color from the screen, in order to highlight something. Some plugins make use of this form, likeduat-hop
andduat-sneak
, which are recreations of some neovim plugins.alias
: Is used on aliases, see the map and alias chapter for more information.matched_pair
: Isn't technically part of duat, but it's part of a default plugin.
Some other forms are used by specific Widgets
-
LineNumbers
:linenum.main
: The form to be used on the main line's number.linenum.wrapped
: The form to be used on wrapped lines.linenum.wrapped.main
: Same, but for the main line, inherits fromlinenum.wrapped
by default.
Do note that you can set the form of the remaining lines by setting
default.LineNumbers
. And due to form inheritance, settinglinenum
will set all three forms. -
StatusLine
:file
,file.new
,file.unsaved
,file.new.scratch
: Are all used byfile_fmt
, which shows theFile
's name and some other info.mode
: Is used bymode_fmt
.coord
andseparator
: Are used bymain_fmt
.selections
: Is used byselections_fmt
.key
andkey.special
: Are used bycur_map_fmt
.
-
Notifications
:notifs.target
: The form for the "target" of the notification.notifs.colon
: The form used by the':'
that follows the target.
Since the
Notifications
widget makes heavy use of masks, you can also setnotifs.target.error
, if you want a different target color only when error messages are sent, for example. -
PromptLine
:prompt
: For the prompt on the prompt line.prompt.colon
: For the':'
that follows it.caller.info
andcaller.error
: For the caller, if it exists or not, respectively.parameter.info
andparameter.error
: For parameters, if they fit or not, respectively.regex.literal
,regex.operator.(flags|dot|repetition|alternation)
,regex.class.(unicode|perl|bracketed)
,regex.bracket.(class|group)
: A bunch of forms used for highlighting regex searches.
-
LogBook
:log_book.(error|warn|info|debug)
: For the types of messages.log_book.colon
: For the':'
that follows them.log_book.target
: For the "target" of the message.log_book.bracket
: For the(
s surrounding the target.
-
VertRule
:rule.upper
andrule.lower
: The forms to use above and below the main line.
And finally, there are also all the forms used by duat-treesitter
. Since the queries were taken from nvim-treesitter, the form names follow the same patters as those from neovim:
Form name | Purpose |
---|---|
variable | various variable names |
variable.builtin | built-in variable names (e.g. this, self) |
variable.parameter | parameters of a function |
variable.parameter.builtin | special parameters (e.g. _, it) |
variable.member | object and struct fields |
constant | constant identifiers |
constant.builtin | built-in constant values |
constant.macro | constants defined by the preprocessor |
module | modules or namespaces |
module.builtin | built-in modules or namespaces |
label | GOTO and other labels (e.g. label: in C), including heredoc labels |
string | string literals |
string.documentation | string documenting code (e.g. Python docstrings) |
string.regexp | regular expressions |
string.escape | escape sequences |
string.special | other special strings (e.g. dates) |
string.special.symbol | symbols or atoms |
string.special.path | filenames |
string.special.url | URIs (e.g. hyperlinks) |
character | character literals |
character.special | special characters (e.g. wildcards) |
boolean | boolean literals |
number | numeric literals |
number.float | floating-point number literals |
type | type or class definitions and annotations |
type.builtin | built-in types |
type.definition | identifiers in type definitions (e.g. typedef |
attribute | attribute annotations (e.g. Python decorators, Rust lifetimes) |
attribute.builtin | builtin annotations (e.g. @property in Python) |
property | the key in key/value pairs |
function | function definitions |
function.builtin | built-in functions |
function.call | function calls |
function.macro | preprocessor macros |
function.method | method definitions |
function.method.call | method calls |
constructor | constructor calls and definitions |
operator | symbolic operators (e.g. +, *) |
keyword | keywords not fitting into specific categories |
keyword.coroutine | keywords related to coroutines (e.g. go in Go, async/await in Python) |
keyword.function | keywords that define a function (e.g. func in Go, def in Python) |
keyword.operator | operators that are English words (e.g. and, or) |
keyword.import | keywords for including or exporting modules (e.g. import, from in Python) |
keyword.type | keywords describing namespaces and composite types (e.g. struct, enum) |
keyword.modifier | keywords modifying other constructs (e.g. const, static, public) |
keyword.repeat | keywords related to loops (e.g. for, while) |
keyword.return | keywords like return and yield |
keyword.debug | keywords related to debugging |
keyword.exception | keywords related to exceptions (e.g. throw, catch) |
keyword.conditional | keywords related to conditionals (e.g. if, else) |
keyword.conditional.ternary ternary | operator (e.g. ?, :) |
keyword.directive | various preprocessor directives and shebangs |
keyword.directive.define | preprocessor definition directives |
punctuation.delimiter | delimiters (e.g. ;, ., ,) |
punctuation.bracket | brackets (e.g. (), {}, []) |
punctuation.special | special symbols (e.g. {} in string interpolation) |
comment | line and block comments |
comment.documentation | comments documenting code |
comment.error | error-type comments (e.g. ERROR, FIXME, DEPRECATED) |
comment.warning | warning-type comments (e.g. WARNING, FIX, HACK) |
comment.todo | todo-type comments (e.g. TODO, WIP) |
comment.note | note-type comments (e.g. NOTE, INFO, XXX) |
markup.strong | bold text |
markup.italic | italic text |
markup.strikethrough | struck-through text |
markup.underline | underlined text (only for literal underline markup!) |
markup.heading | headings, titles (including markers) |
markup.heading.1 | top-level heading |
markup.heading.2 | section heading |
markup.heading.3 | subsection heading |
markup.heading.4 | and so on |
markup.heading.5 | and so forth |
markup.heading.6 | six levels ought to be enough for anybody |
markup.quote | block quotes |
markup.math | math environments (e.g. $ ... $ in LaTeX) |
markup.link | text references, footnotes, citations, etc. |
markup.link.label | link, reference descriptions |
markup.link.url | URL-style links |
markup.raw | literal or verbatim text (e.g. inline code) |
markup.raw.block | literal or verbatim text as a stand-alone block |
markup.list | list markers |
markup.list.checked | checked todo-style list markers |
markup.list.unchecked | unchecked todo-style list markers |
diff.plus | added text (for diff files) |
diff.minus | deleted text (for diff files) |
diff.delta | changed text (for diff files) |
tag | XML-style tag names (e.g. in XML, HTML, etc.) |
tag.builtin | builtin tag names (e.g. HTML5 tags) |
tag.attribute | XML-style tag attributes |
tag.delimiter | XML-style tag delimiters |
Frequently used snippets
If you just want some no nonsense snippets to copy paste into your config, this is the chapter for you.
For every snippet, assume that the snippet is placed inside of the setup
function, given the following setup:
#![allow(unused)] fn main() { setup_duat!(setup); use duat::prelude::*; fn setup() { // Here be snippets. } }
mapping jk to esc
This one is pretty simple. Assuming you are using some sort of Insert
Mode
:
#![allow(unused)] fn main() { use duat::prelude::*; use kak::Insert; // Or vim::Insert, or helix::Insert, when those come out. map::<Insert>("jk", "<Esc>"); }
This will not print out the 'j'
to the screen unless the following key is not
'k'
. If you wish to print 'j'
to the screen, use this:
#![allow(unused)] fn main() { use duat::prelude::*; use kak::Insert; alias::<Insert>("jk", "<Esc>"); }
Additionally, if you want to write to the file on jk
as well, you can do this:
#![allow(unused)] fn main() { use duat::prelude::*; use kak::Insert; alias::<Insert>("jk", "<Esc>:w<Enter>"); }
If you want to, you can also make this happen on the PromptLine
, i.e., while writing commands and searches:
#![allow(unused)] fn main() { use duat::prelude::*; alias::<Prompt>("jk", "<Esc>"); }
StatusLine on each File
Prompt and Status on same line
Common StatusLine parts
The most relevant parts for pretty much every StatusLine
are the following.
Formatted status parts:
file_fmt
: Prints theFile
's name and some info about it's newness.- Uses the forms
file
,file.new
,file.new.scratch
andfile.unsaved
- Uses the forms
mode_fmt
: The lowercased name of theMode
, e.g. "insert", "normal.- Uses the form
mode
- Uses the form
main_fmt
: Prints the main selection's column and line, and the number of lines. 1 indexed.- Uses the forms
coord
andseparator
- Uses the forms
sels_fmt
: Prints the number of selections.- Uses the form
selections
;
- Uses the form
cur_map_fmt
: Prints the keys being mapped.- Uses the forms
key
andkey.special
- Uses the forms
Unformatted status parts:
main_byte
,main_char
,main_line
,main_col
: Parts of the main cursor.mode_name
: The raw type name of the mode. Could look something likePrompt<IncSearcher<SearchFwd>, Ui>, Ui>
selections
: The number of selections, no formatting.last_key
: The last key that was typed. Useful for asciinema demonstrations.
Other:
Spacer
: This isn't actually aStatusPart
, it's aTag
that can go in anyText
, which includes theStatusLine
's.AlignLeft
,AlignCenter
,AlignRight
: TheseTag
s (like all others) can also be used in theStatusLine
. However, do note that they are applied line wise. Using any of them will shift the whole line's alignment. For that reason, aSpacer
should generally be preferred.- Forms like
[file]
. Any form can be placed within those braces, and they are all evaluated at compile time.
Some examples
This is the default:
#![allow(unused)] fn main() { use duat::prelude::*; status!("{file_fmt}{Spacer}{mode_fmt} {sels_fmt} {main_fmt}"); }
If you want a one sided StatusLine
:
#![allow(unused)] fn main() { use duat::prelude::*; status!("{Spacer}{file_fmt} {mode_fmt} {sels_fmt} {main_fmt}"); }
Customized main_fmt
:
#![allow(unused)] fn main() { use duat::prelude::*; status!( "{file_fmt}{Spacer} {mode_fmt} {sels_fmt}[coord]c{main_col} l{main_line}[separator]|[coord]{}", |file: &File| file.text().len().line() ); }
Customized file_fmt
:
#![allow(unused)] fn main() { use duat::prelude::*; fn file_fmt(file: &File) -> Text { let mut b = Text::builder(); if let Some(name) = file.name_set() { b.push(txt!("[file]{name}")); if !file.exists() { b.push(txt!("[file.new][[new file]]")); } else if file.text().has_unsaved_changes() { b.push(txt!("[file.unsaved][[has changes]]")); } if let Some("rust") = file.filetype() { b.push(txt!("[[🦀]]")); } } else { b.push(txt!("[file.new.scratch]?!?!?!"))); } b.build() } status!("{file_fmt}{Spacer}{mode_fmt} {sels_fmt} {main_fmt}"); }
Relative and aligned LineNumbers
File wise tabstops
If you want to change the tabstop size per file, you can just modify the following snippet:
#![allow(unused)] fn main() { use duat::prelude::*; hook::add::<OnFileOpen>(|pa, builder| { builder.write(pa, |file, _| match file.filetype() { Some("markdown" | "bash" | "lua" | "javascript" | "lisp") => { file.cfg.set_tabstop(2); } _ => { file.cfg.set_tabstop(4); } }) }); }
If you want, you can also set other options with this, like which characters
should be a part of words. In this case, I'm adding '-'
to the list:
#![allow(unused)] fn main() { use duat::prelude::*; hook::add::<OnFileOpen>(|pa, builder| { builder.write(pa, |file, _| match file.filetype() { Some("lisp" | "scheme" | "markdown" | "css" | "html") => { let wc = word_chars!("A-Za-z0-9_-_---"); file.cfg.set_tabstops(2).set_word_chars(wc); } Some("bash" | "lua" | "javascript" | "typescript") => { file.cfg.set_tabstops(2); } _ => { file.cfg.set_tabstops(4); } } }); }
Nerdfonts StatusLine
[!IMPORTANT]
This chapter assumes that you are using some kind of nerd font in your
Ui
. This also goes for this page in the book. If you are not using some kind of nerd font in your browser, you will not be able to see the characters being displayed. For that, I will provide pictures.
If you want to nerd-fontify your StatusLine
, you should probably redefine
some of the status line parts:
#![allow(unused)] fn main() { use duat::prelude::* fn file_fmt(file: &File) -> Text { let mut b = Text::builder(); if let Some(name) = file.name_set() { b.push(txt!("[file]{name}")); if !file.exists() { b.push(txt!("[file.new] ")); } else if file.text().has_unsaved_changes() { b.push(txt!("[file.unsaved] ")); } } else { b.push(txt!("[file.new.scratch]{} ", file.name()))); } b.build() } }