refactor: modularize architecture

refactors the application structure to move away from a monolithic
`main.rs`.

- **Domain Models**: Extracted `State`, `Message`, `FileAction`, and
`ViewAction` into `state.rs` and `message.rs`. Enforced encapsulation on
`State` using private fields and public accessors.
- **Controller Layer**: Created `handler.rs` to handle business logic
and I/O operations, transforming the main `update` loop into a pure
event router.
- **Component System**: Split the UI into composable, stateless
functions in `components/` (tabs, editor, status_bar, action_bar),
removing the 100+ line view function.
- **File Logic**: Encapsulated file operations and safe path handling
within `file.rs`, removing fragile `expect` calls from the UI layer.
- **Utilities**: Centralized static configuration, keybindings, and I/O
helpers into `constants.rs`, `key_bindings.rs`, and `io.rs`.

This change decouples the view from the model and makes the core
application logic testable
This commit is contained in:
Stevan Freeborn
2026-01-24 20:51:26 -06:00
parent cb5f1cf717
commit f5d98197ac
13 changed files with 695 additions and 507 deletions
+32
View File
@@ -0,0 +1,32 @@
use iced::{
Element, Length, Theme,
widget::{container, markdown, row, text_editor},
};
use crate::{constants, file::File, message::Message, state::Mode};
pub fn view<'a>(file: &'a File, mode: Mode, font_size: u32) -> Element<'a, Message> {
match mode {
Mode::Edit => {
let editor = text_editor(file.content())
.size(font_size)
.height(Length::Fill)
.on_action(Message::Edit);
container(row![editor]).height(Length::Fill).into()
}
Mode::Preview => {
let mut style: markdown::Style = Theme::Dark.into();
style.font = constants::CUSTOM_FONT;
let settings = markdown::Settings::with_text_size(font_size, style);
let markdown_preview =
markdown::view(file.markdown(), settings).map(Message::LinkClicked);
container(row![markdown_preview])
.height(Length::Fill)
.into()
}
}
}