feat: add todo management feature; implement todo content tracking and display in the UI
This commit is contained in:
Generated
+34
@@ -812,6 +812,15 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluent-uri"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -1638,6 +1647,19 @@ dependencies = [
|
|||||||
"hashbrown 0.17.1",
|
"hashbrown 0.17.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lsp-types"
|
||||||
|
version = "0.97.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"fluent-uri",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac_address"
|
name = "mac_address"
|
||||||
version = "1.1.8"
|
version = "1.1.8"
|
||||||
@@ -2805,6 +2827,17 @@ dependencies = [
|
|||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.118",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -4288,6 +4321,7 @@ dependencies = [
|
|||||||
"include_dir",
|
"include_dir",
|
||||||
"infer",
|
"infer",
|
||||||
"libc",
|
"libc",
|
||||||
|
"lsp-types",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ tracing = "0.1"
|
|||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
webbrowser = "1"
|
webbrowser = "1"
|
||||||
|
lsp-types = "0.97"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "zesdex"
|
name = "zesdex"
|
||||||
|
|||||||
@@ -303,6 +303,20 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
|||||||
state.misc.tick_count = state.misc.tick_count.wrapping_add(1);
|
state.misc.tick_count = state.misc.tick_count.wrapping_add(1);
|
||||||
let now_ms = chrono::Utc::now().timestamp_millis();
|
let now_ms = chrono::Utc::now().timestamp_millis();
|
||||||
state.misc.drain_expired_toasts(now_ms);
|
state.misc.drain_expired_toasts(now_ms);
|
||||||
|
|
||||||
|
if state.misc.tick_count.is_multiple_of(10) {
|
||||||
|
let todo_path = state.session_dir.join("todo.md");
|
||||||
|
if let Ok(content) = std::fs::read_to_string(&todo_path) {
|
||||||
|
if content != state.misc.todo_content {
|
||||||
|
state.misc.todo_content = content;
|
||||||
|
state.dirty = true;
|
||||||
|
}
|
||||||
|
} else if !state.misc.todo_content.is_empty() {
|
||||||
|
state.misc.todo_content.clear();
|
||||||
|
state.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Background API connectivity check — runs on a background thread
|
// Background API connectivity check — runs on a background thread
|
||||||
// every ~1s while disconnected, every ~30s while connected, so the
|
// every ~1s while disconnected, every ~30s while connected, so the
|
||||||
// status bar reflects real API availability without user input.
|
// status bar reflects real API availability without user input.
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ pub struct MiscState {
|
|||||||
pub editor: Option<super::super::mode::editor::EditorState>,
|
pub editor: Option<super::super::mode::editor::EditorState>,
|
||||||
pub api_connected: bool,
|
pub api_connected: bool,
|
||||||
pub tick_count: u64,
|
pub tick_count: u64,
|
||||||
|
pub todo_content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MiscState {
|
impl MiscState {
|
||||||
@@ -294,6 +295,7 @@ impl MiscState {
|
|||||||
editor: None,
|
editor: None,
|
||||||
api_connected: false,
|
api_connected: false,
|
||||||
tick_count: 0,
|
tick_count: 0,
|
||||||
|
todo_content: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -116,7 +116,7 @@ fn run_single_process() -> Result<()> {
|
|||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, crossterm::event::EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
terminal.clear()?;
|
terminal.clear()?;
|
||||||
@@ -124,7 +124,7 @@ fn run_single_process() -> Result<()> {
|
|||||||
let run_result = run_loop(&mut state, &mut terminal);
|
let run_result = run_loop(&mut state, &mut terminal);
|
||||||
|
|
||||||
let mut restore_stdout = io::stdout();
|
let mut restore_stdout = io::stdout();
|
||||||
let _ = execute!(restore_stdout, LeaveAlternateScreen, crossterm::event::DisableMouseCapture);
|
let _ = execute!(restore_stdout, LeaveAlternateScreen);
|
||||||
let _ = disable_raw_mode();
|
let _ = disable_raw_mode();
|
||||||
|
|
||||||
if let Err(e) = run_result {
|
if let Err(e) = run_result {
|
||||||
@@ -460,7 +460,7 @@ fn run_attach(session_id: &str) -> Result<()> {
|
|||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, crossterm::event::EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
terminal.clear()?;
|
terminal.clear()?;
|
||||||
@@ -544,7 +544,7 @@ fn run_attach(session_id: &str) -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = execute!(io::stdout(), LeaveAlternateScreen, crossterm::event::DisableMouseCapture);
|
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||||
let _ = disable_raw_mode();
|
let _ = disable_raw_mode();
|
||||||
|
|
||||||
let _ = client_state.settings.save();
|
let _ = client_state.settings.save();
|
||||||
@@ -570,7 +570,7 @@ fn run_loop(
|
|||||||
let _ = terminal.clear();
|
let _ = terminal.clear();
|
||||||
|
|
||||||
let _ = disable_raw_mode();
|
let _ = disable_raw_mode();
|
||||||
let _ = execute!(io::stdout(), LeaveAlternateScreen, crossterm::event::DisableMouseCapture);
|
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-5
@@ -29,6 +29,20 @@ use theme::Theme;
|
|||||||
pub fn draw(frame: &mut Frame, state: &crate::app::state::rest::AppStateRest) {
|
pub fn draw(frame: &mut Frame, state: &crate::app::state::rest::AppStateRest) {
|
||||||
let area = frame.area();
|
let area = frame.area();
|
||||||
|
|
||||||
|
let show_todo = !state.misc.todo_content.is_empty() || state.misc.overlay == crate::app::state::types::Overlay::Todo;
|
||||||
|
let (main_area, todo_area) = if show_todo && area.width > 60 {
|
||||||
|
let h_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Min(40),
|
||||||
|
Constraint::Length(30),
|
||||||
|
])
|
||||||
|
.split(area);
|
||||||
|
(h_chunks[0], Some(h_chunks[1]))
|
||||||
|
} else {
|
||||||
|
(area, None)
|
||||||
|
};
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
@@ -36,26 +50,50 @@ pub fn draw(frame: &mut Frame, state: &crate::app::state::rest::AppStateRest) {
|
|||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(main_area);
|
||||||
|
|
||||||
let main_area = chunks[0];
|
let chat_area = chunks[0];
|
||||||
let input_area = chunks[1];
|
let input_area = chunks[1];
|
||||||
let status_area = chunks[2];
|
let status_area = chunks[2];
|
||||||
|
|
||||||
if state.misc.overlay.is_active() {
|
if state.misc.overlay.is_active() && state.misc.overlay != crate::app::state::types::Overlay::Todo {
|
||||||
let overlay = state.misc.overlay;
|
let overlay = state.misc.overlay;
|
||||||
render_overlay(frame, main_area, overlay, state);
|
render_overlay(frame, chat_area, overlay, state);
|
||||||
} else {
|
} else {
|
||||||
render_main_panel(frame, main_area, state);
|
render_main_panel(frame, chat_area, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_input_bar(frame, input_area, state);
|
render_input_bar(frame, input_area, state);
|
||||||
status::draw_status_bar(frame, status_area, state);
|
status::draw_status_bar(frame, status_area, state);
|
||||||
|
|
||||||
|
if let Some(todo_rect) = todo_area {
|
||||||
|
render_todo_panel(frame, todo_rect, state);
|
||||||
|
}
|
||||||
|
|
||||||
// Toast notifications at top-right (like Hyprland)
|
// Toast notifications at top-right (like Hyprland)
|
||||||
render_toasts(frame, state);
|
render_toasts(frame, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_todo_panel(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
||||||
|
let block = Block::default()
|
||||||
|
.title(" Todo ")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::default().fg(Theme::PRIMARY))
|
||||||
|
.style(Style::default().bg(Theme::BG));
|
||||||
|
|
||||||
|
let content = if state.misc.todo_content.is_empty() {
|
||||||
|
"No tasks yet."
|
||||||
|
} else {
|
||||||
|
&state.misc.todo_content
|
||||||
|
};
|
||||||
|
|
||||||
|
let paragraph = Paragraph::new(content)
|
||||||
|
.block(block)
|
||||||
|
.wrap(Wrap { trim: false });
|
||||||
|
|
||||||
|
frame.render_widget(paragraph, area);
|
||||||
|
}
|
||||||
|
|
||||||
fn render_main_panel(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
fn render_main_panel(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
||||||
chat::draw_chat(frame, area, state);
|
chat::draw_chat(frame, area, state);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user