Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Writing a New Module

This guide walks through adding a new module to ashell, step by step.

Step 1: Create the Module File

Create src/modules/my_module.rs:

#![allow(unused)]
fn main() {
use crate::theme::AshellTheme;
use iced::{Element, Subscription, widget::text};

#[derive(Debug, Clone)]
pub enum Message {
    // Define your messages here
    Tick,
}

pub struct MyModule {
    // Your state here
    value: String,
}

impl MyModule {
    pub fn new() -> Self {
        Self {
            value: "Hello".to_string(),
        }
    }

    pub fn update(&mut self, message: Message) {
        match message {
            Message::Tick => {
                // Handle the message
            }
        }
    }

    pub fn view(&self, _theme: &AshellTheme) -> Element<Message> {
        text(&self.value).into()
    }

    pub fn subscription(&self) -> Subscription<Message> {
        Subscription::none()
    }
}
}

Step 2: Register the Module Name

In src/config.rs, add your module to the ModuleName enum:

#![allow(unused)]
fn main() {
pub enum ModuleName {
    // ... existing variants
    MyModule,
}
}

Make sure the serde deserialization handles the string representation (the enum variant name is used as the TOML string).

Step 3: Add to Module Declarations

In src/modules/mod.rs, add the module declaration:

#![allow(unused)]
fn main() {
pub mod my_module;
}

Step 4: Add to App Struct

In src/app.rs, add the field:

#![allow(unused)]
fn main() {
pub struct App {
    // ... existing fields
    pub my_module: MyModule,
}
}

Step 5: Initialize in App::new

In App::new():

#![allow(unused)]
fn main() {
(App {
    // ... existing fields
    my_module: MyModule::new(),
}, task)
}

Step 6: Add Message Variant

In src/app.rs, add to the Message enum:

#![allow(unused)]
fn main() {
pub enum Message {
    // ... existing variants
    MyModule(modules::my_module::Message),
}
}

Step 7: Wire Up in App::update

In App::update(), add the match arm:

#![allow(unused)]
fn main() {
Message::MyModule(msg) => {
    self.my_module.update(msg);
    Task::none()
}
}

Step 8: Wire Up in Module Registry

In src/modules/mod.rs:

get_module_view

#![allow(unused)]
fn main() {
ModuleName::MyModule => Some((
    self.my_module.view(&self.theme).map(Message::MyModule),
    None,  // Or Some(OnModulePress::ToggleMenu(MenuType::MyModule)) if you have a menu
)),
}

get_module_subscription

#![allow(unused)]
fn main() {
ModuleName::MyModule => Some(
    self.my_module.subscription().map(Message::MyModule),
),
}

Step 9: Add Config (Optional)

If your module needs configuration, add a config struct in src/config.rs:

#![allow(unused)]
fn main() {
#[derive(Deserialize, Clone, Debug)]
#[serde(default)]
pub struct MyModuleConfig {
    pub some_setting: String,
}

impl Default for MyModuleConfig {
    fn default() -> Self {
        Self {
            some_setting: "default_value".to_string(),
        }
    }
}
}

Add the field to the Config struct:

#![allow(unused)]
fn main() {
pub struct Config {
    // ...
    pub my_module: MyModuleConfig,
}
}

Then accept it in your module’s constructor:

#![allow(unused)]
fn main() {
pub fn new(config: MyModuleConfig) -> Self { /* ... */ }
}

Step 10: Add a Menu (Optional)

If your module needs a popup menu:

  1. Add a variant to MenuType in src/menu.rs:
#![allow(unused)]
fn main() {
pub enum MenuType {
    // ...
    MyModule,
}
}
  1. Add a menu_view() method to your module.

  2. Change get_module_view to return Some(OnModulePress::ToggleMenu(MenuType::MyModule)).

  3. Handle the menu rendering in App::view() / App::menu_wrapper().

Step 11: Handle Config Reload (Optional)

If your module needs to respond to config changes, add a ConfigReloaded variant to your Message:

#![allow(unused)]
fn main() {
pub enum Message {
    ConfigReloaded(MyModuleConfig),
    // ...
}
}

And call it from App::refesh_config().

Testing Your Module

  1. Add your module to the config file:
[modules]
right = ["MyModule"]
  1. Build and run:
make start
  1. Edit the config to test hot-reload:
# Changes should appear without restarting ashell