Crate duat_core

Source
Expand description

The core of Duat, this crate is meant to be used only for the creation of plugins for Duat.

§Quick Start

The capabilities of duat-core are largely the same as the those of Duat, however, the main difference is the multi Ui APIs of this crate. In it, the public functions and types are defined in terms of U: Ui, which means that they can work on various different interfaces:

#[derive(Default, Clone)]
struct FindSeq(Option<char>);

impl<U: Ui> Mode<U> for FindSeq {
    type Widget = File;

    fn send_key(&mut self, key: KeyEvent, file: &mut File, area: &U::Area) {
        use KeyCode::*;
        let mut helper = EditHelper::new(file, area);

        // Make sure that the typed key is a character.
        let key!(Char(c)) = key else {
            mode::reset();
            return;
        };
        // Checking if a character was already sent.
        let Some(first) = self.0 else {
            self.0 = Some(c);
            return;
        };

        helper.move_many(.., |mut m| {
            let pat: String = [first, c].iter().collect();
            let matched = m.search_fwd(pat, None).next();
            if let Some([p0, p1]) = matched {
                m.move_to(p0);
                m.set_anchor();
                m.move_to(p1);
                m.move_hor(-1)
            }
        });

        mode::reset();
    }
}

In this example, I have created a Mode for Files. This mode is (I think) popular within Vim circles. It’s like the f key in Vim, but it lets you look for a sequence of 2 characters, instead of just one.

What’s great about it is that it will work no matter what editing model the user is using. It could be Vim inspired, Kakoune inspired, Emacs inspired, doesn’t matter. All the user has to do to use this mode is this:

map::<Normal>("<C-s>", &FindSeq::default());

And now, whenever the usert types Control S in Normal mode, the mode will switch to FindSeq. You could replace Normal with any other mode, from any other editing model, and this would still work.

Of course, this is most useful for plugins, for your own configuration, you should probably just rely on map to accomplish the same thing.

Okay, but that was a relatively simple example, here’s a more advanced example, which makes use of more of Duat’s features.

This is a copy of EasyMotion, a plugin for Vim/Neovim/Kakoune/Emacs that lets you skip around the screen with at most 2 keypresses.

In order to emulate it, we use ghost text and concealment:

#[derive(Clone)]
pub struct EasyMotion {
    is_line: bool,
    key: Key,
    points: Vec<[Point; 2]>,
    seq: String,
}

(NOTE: something)

impl EasyMotion {
    pub fn word() -> Self {
        Self {
            is_line: false,
            key: Key::new(),
            points: Vec::new(),
            seq: String::new(),
        }
    }

    pub fn line() -> Self {
        Self {
            is_line: true,
            key: Key::new(),
            points: Vec::new(),
            seq: String::new(),
        }
    }
}

impl<U: Ui> Mode<U> for EasyMotion {
    type Widget = File;

    fn on_switch(&mut self, file: &mut File, area: &<U as Ui>::Area) {
        let cfg = file.print_cfg();
        let text = file.text_mut();

        let regex = match self.is_line {
            true => "[^\n\\s][^\n]+",
            false => "[^\n\\s]+",
        };
        let (start, _) = area.first_points(text, cfg);
        let (end, _) = area.last_points(text, cfg);
        self.points = text.search_fwd(regex, (start, end)).unwrap().collect();

        let seqs = key_seqs(self.points.len());

        for (seq, [p0, _]) in seqs.iter().zip(&self.points) {
            let ghost = text!([EasyMotionWord] seq);

            text.insert_tag(p0.byte(), Tag::GhostText(ghost), self.key);
            text.insert_tag(p0.byte(), Tag::StartConceal, self.key);
            let seq_end = p0.byte() + seq.chars().count() ;
            text.insert_tag(seq_end, Tag::EndConceal, self.key);
        }
    }

    fn send_key(&mut self, key: KeyEvent, file: &mut File, area: &U::Area) {
        let char = match key {
            key!(KeyCode::Char(c)) => c,
            // Return a char that will never match.
            _ => '❌'
        };
        self.seq.push(char);

        let mut helper = EditHelper::new(file, area);
        helper.cursors_mut().remove_extras();

        let seqs = key_seqs(self.points.len());
        for (seq, &[p0, p1]) in seqs.iter().zip(&self.points) {
            if *seq == self.seq {
                helper.move_main(|mut m| {
                    m.move_to(p0);
                    m.set_anchor();
                    m.move_to(p1);
                });
                mode::reset();
            } else if seq.starts_with(&self.seq) {
                continue;
            }

            helper.text_mut().remove_tags(p1.byte(), self.key);
            helper.text_mut().remove_tags(p1.byte() + seq.len(), self.key);
        }

        if self.seq.chars().count() == 2 || !LETTERS.contains(char) {
            mode::reset();
        }
    }
}

fn key_seqs(len: usize) -> Vec<String> {
    let double = len / LETTERS.len();

    let mut seqs = Vec::new();
    seqs.extend(LETTERS.chars().skip(double).map(char::into));

    let chars = LETTERS.chars().take(double);
    seqs.extend(chars.flat_map(|c1| LETTERS.chars().map(move |c2| format!("{c1}{c2}"))));

    seqs
}

static LETTERS: &str = "abcdefghijklmnopqrstuvwxyz";

All that this plugin is doing is:

Now, in order to use this mode, it’s the exact same thing as FindSeq:

#[derive(Clone)]
map::<Normal>("<CA-w>", &EasyMotion::word());
map::<Normal>("<CA-l>", &EasyMotion::line());

Modules§

cache
Caching utilities for Duat
cfg
clipboard
Clipboard interaction for Duat
cmd
Creation and execution of commands.
context
Access to widgets and other other parts of the state of Duat
data
Duat’s way of sharing and updating state
form
Utilities for stylizing the text of Duat
hooks
Utilities for hooks in Duat
mode
prelude
The prelude of Duat
session
status
Common items in a StatusLine
text
The primary data structure in Duat
thread
Multithreading for Duat
ui
widgets
APIs for the construction of widgets, and a few common ones.

Traits§

Lender
A trait for dealing with lending iterators.
Lending
A trait for dealing with the ‘items’ of lending iterators.
Plugin
A plugin for Duat

Functions§

crate_dir
The path for the config crate of Duat
duat_name
Takes a type and generates an appropriate name for it
periodic_checker
A checker that returns true every duration
src_crate
Returns the source crate of a given type

Type Aliases§

Mutex
A mutual exclusion primitive useful for protecting shared data
MutexGuard
An RAII implementation of a “scoped lock” of a mutex. When this structure is dropped (falls out of scope), the lock will be unlocked.
RwLock
A reader-writer lock
RwLockReadGuard
RAII structure used to release the shared read access of a lock when dropped.
RwLockWriteGuard
RAII structure used to release the exclusive write access of a lock when dropped.