Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 137 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ chrono = "0.4.39"
clap = { version = "4.5.31", features = ["derive", "env"] }
insta = { version = "1.42.1", features = ["filters"] }
itertools = "0.14.0"
notify = "8.0.0"
ratatui = { version = "0.29.0", features = [
"serde",
"unstable-rendered-line-info",
Expand Down
62 changes: 52 additions & 10 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
use std::path::PathBuf;

use anyhow::{anyhow, Result};
use core::fmt;
use ratatui::crossterm::{
self,
event::{Event as TermEvent, KeyCode, KeyModifiers},
};
use tracing::{info, info_span, trace};

use crate::{
commander::Commander,
env::Env,
event::{AppEvent, EventSource},
ui::{
bookmarks_tab::BookmarksTab, command_log_tab::CommandLogTab, command_popup::CommandPopup,
files_tab::FilesTab, log_tab::LogTab, Component, ComponentAction,
},
ComponentInputResult,
};
use anyhow::{anyhow, Result};
use core::fmt;
use ratatui::crossterm::event::{self, Event, KeyCode, KeyModifiers};
use tracing::{info, info_span};

#[derive(PartialEq, Copy, Clone)]
pub enum Tab {
Expand All @@ -36,25 +43,34 @@ impl Tab {
}

pub struct App<'a> {
// application environment
pub env: Env,

// user interace
pub current_tab: Tab,
pub log: Option<LogTab<'a>>,
pub files: Option<FilesTab>,
pub bookmarks: Option<BookmarksTab<'a>>,
pub command_log: Option<CommandLogTab>,
pub popup: Option<Box<dyn Component>>,

// event handling
event_source: EventSource,
}

impl<'a> App<'a> {
pub fn new(env: Env) -> Result<App<'a>> {
Ok(App {
env,

current_tab: Tab::Log,
log: None,
files: None,
bookmarks: None,
command_log: None,
popup: None,

event_source: EventSource::new(),
})
}

Expand Down Expand Up @@ -222,16 +238,42 @@ impl<'a> App<'a> {
Ok(())
}

pub fn input(&mut self, event: Event, commander: &mut Commander) -> Result<bool> {
/// Set up threads that capture input and send AppEvents
pub fn launch_input_channel(&mut self) {
let jj_folder = PathBuf::from(&self.env.root.clone())
.join(".jj")
.join("working_copy");
self.event_source.launch_watcher(jj_folder);
self.event_source.launch_user_input();
}

/// Recieve an AppEvent if one is waiting.
pub fn try_recv_app_event(&mut self) -> Option<AppEvent> {
self.event_source.try_recv()
}

/// Process an AppEvent
pub fn input(&mut self, event: AppEvent, commander: &mut Commander) -> Result<bool> {
let AppEvent::UserInput(event) = event else {
if event != AppEvent::DirtyJj {
panic!("Unknown AppEvent {:?}", event);
}
// Trigger update of jj data - simulate focus gained
trace!("no-event trigger re-read of jj data");
commander.jj_ignore_working_copy = true;
self.get_or_init_current_tab(commander)?.focus(commander)?;
commander.jj_ignore_working_copy = false;
return Ok(false); // do not terminate the app
};
if let Some(popup) = self.popup.as_mut() {
match popup.input(commander, event.clone())? {
ComponentInputResult::HandledAction(component_action) => {
self.handle_action(component_action, commander)?
}
ComponentInputResult::Handled => {}
ComponentInputResult::NotHandled => {
if let Event::Key(key) = event {
if key.kind == event::KeyEventKind::Press {
if let TermEvent::Key(key) = event {
if key.kind == crossterm::event::KeyEventKind::Press {
// Close
if matches!(
key.code,
Expand All @@ -248,7 +290,7 @@ impl<'a> App<'a> {
}
}
};
} else if event == event::Event::FocusGained {
} else if event == TermEvent::FocusGained {
self.get_or_init_current_tab(commander)?.focus(commander)?;
} else {
match self
Expand All @@ -260,8 +302,8 @@ impl<'a> App<'a> {
}
ComponentInputResult::Handled => {}
ComponentInputResult::NotHandled => {
if let Event::Key(key) = event {
if key.kind == event::KeyEventKind::Press {
if let TermEvent::Key(key) = event {
if key.kind == crossterm::event::KeyEventKind::Press {
// Close
if key.code == KeyCode::Char('q')
|| (key.modifiers.contains(KeyModifiers::CONTROL)
Expand Down
Loading