From bcc60c5203de8288f2480161845ff4d31525bc18 Mon Sep 17 00:00:00 2001 From: fromost Date: Mon, 13 Oct 2025 00:08:56 +0800 Subject: [PATCH] Add textarea support --- Cargo.lock | 23 +++++++++ Cargo.toml | 18 +++++-- src/app.rs | 110 ++++++++++++++++++++++++++-------------- src/cli.rs | 39 +++++++++----- src/event.rs | 36 ++++++------- src/main.rs | 3 +- src/widgets/mod.rs | 1 + src/widgets/textarea.rs | 49 ++++++++++++++++++ 8 files changed, 201 insertions(+), 78 deletions(-) create mode 100644 src/widgets/mod.rs create mode 100644 src/widgets/textarea.rs diff --git a/Cargo.lock b/Cargo.lock index d4ee59b..36b20ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1442,6 +1442,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.37.3" @@ -1734,6 +1743,7 @@ dependencies = [ "lru", "paste", "strum", + "time", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -2232,6 +2242,7 @@ dependencies = [ "tokio", "tokio-util", "tokio-utils", + "tui-input", "uuid", ] @@ -2348,7 +2359,9 @@ checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -2568,6 +2581,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tui-input" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911e93158bf80bbc94bad533b2b16e3d711e1132d69a6a6980c3920a63422c19" +dependencies = [ + "crossterm 0.29.0", + "unicode-width 0.2.0", +] + [[package]] name = "unicode-ident" version = "1.0.19" diff --git a/Cargo.toml b/Cargo.toml index 07e1d13..ce81788 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ edition = "2024" [dependencies] color-eyre = "0.6.3" -ratatui = "0.29.0" futures = "0.3.28" tokio-util = "0.7.9" tokio-utils = "0.1.2" @@ -18,6 +17,19 @@ rust-ini = "0.21.3" robotstxt = "0.3.0" scraper = "0.24.0" +[dependencies.tui-input] +version = "0.14.0" +features = ["crossterm"] +default-features = false + +[dependencies.crossterm] +version = "0.29.0" +features = ["event-stream"] + +[dependencies.ratatui] +version = "0.29.0" +features = ["all-widgets"] + [dependencies.clap] version = "4.5.48" features = ["derive", "cargo"] @@ -26,10 +38,6 @@ features = ["derive", "cargo"] version = "0.12.23" features = ["blocking"] -[dependencies.crossterm] -version = "0.29.0" -features = ["event-stream"] - [dependencies.tokio] version = "1.47.1" features = ["full"] diff --git a/src/app.rs b/src/app.rs index 214979c..ef73eec 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,19 +1,22 @@ -use crate::event::{Event, EventHandler}; +use std::any::Any; +use std::collections::HashMap; +use crate::event::{AppEvent, EventHandler}; use ratatui::widgets::{Block, Borders, Paragraph}; -use ratatui::{DefaultTerminal}; +use ratatui::{DefaultTerminal, Frame}; use std::time::Duration; use color_eyre::Result; -use crossterm::event; -use crossterm::event::KeyCode; +use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use crossterm::event::KeyCode::Char; +use crossterm::event::Event as CrosstermEvent; use diesel::{Connection, SqliteConnection}; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; -use ratatui::prelude::{Widget}; +use ratatui::prelude::{StatefulWidget, Widget}; use ratatui::style::{Color, Style}; use ratatui::text::{Line, Span, Text}; use crate::config::types::ApplicationConfig; use crate::constants::{APP_CONFIG_DIR, APP_CONIFG_FILE_PATH, APP_DATA_DIR}; +use crate::widgets::textarea::TextArea; enum AppState { Running, @@ -32,15 +35,16 @@ pub(crate) struct App { events: EventHandler, db_connection: SqliteConnection, app_config: ApplicationConfig, - current_event: Option + stateful_widgets: HashMap>, + } impl App { - pub fn new() -> Self { + pub async fn create() -> Self { let app_conf = if APP_CONIFG_FILE_PATH.exists() { ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH).unwrap() } else { ApplicationConfig::new() }; - Self::initialize(); + Self::initialize_folders(); let db_conn = Self::establish_db_connection(app_conf.clone()); Self { state: AppState::Running, @@ -48,11 +52,11 @@ impl App { events: EventHandler::new(Duration::from_millis(app_conf.basic_config.tick_rate)), db_connection: db_conn, app_config: app_conf, - current_event: None + stateful_widgets: HashMap::new() } } - fn initialize() { + fn initialize_folders() { if !APP_CONFIG_DIR.exists() { std::fs::create_dir_all(APP_CONFIG_DIR.as_path()).unwrap(); } @@ -67,9 +71,9 @@ impl App { .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) } - pub async fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + pub async fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> { loop { - terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; + terminal.draw(|frame| self.draw(frame))?; let event = self.events.next().await?; self.update(event)?; if matches!(self.state, AppState::Exiting) { @@ -78,20 +82,27 @@ impl App { } } - fn update(&mut self, event: Event) -> Result<()> { - self.current_event = Some(event.clone()); - if let Event::Key(key) = event && matches!(self.state, AppState::Input) { - match key.code { - KeyCode::Esc => { - self.state = AppState::Running; - }, - _ => {} - } + fn update(&mut self, event: AppEvent) -> Result<()> { + if matches!(self.state, AppState::Input) && let AppEvent::Raw(raw) = &event { + self.stateful_widgets.iter_mut() + .map(|(_, widget)| widget.downcast_mut::