use crossterm::event::{KeyCode, KeyEvent};
use super::{App, CiRequest, Flow, MouseGesture, Screen, hit_index, logs, page_step};
use crate::keymap::Action;
impl App {
pub(super) fn logs_mouse(&mut self, gesture: MouseGesture) {
let Some(view) = self.logs.as_mut() else {
return;
};
let last = view.rows().len().saturating_sub(1);
match gesture {
MouseGesture::Scroll { down, .. } => {
let delta = if down { 3 } else { -3 };
view.cursor = view.cursor.saturating_add_signed(delta).min(last);
}
MouseGesture::Press { col, row } => {
if let Some(i) = hit_index(view.body, view.scroll, col, row).filter(|i| *i <= last)
{
view.cursor = i;
view.visual_anchor = None;
}
}
MouseGesture::DoublePress { col, row } => {
if let Some(i) = hit_index(view.body, view.scroll, col, row).filter(|i| *i <= last)
{
view.cursor = i;
view.toggle_fold_at_cursor();
}
}
MouseGesture::Drag { col, row } => {
if let Some(i) = hit_index(view.body, view.scroll, col, row).filter(|i| *i <= last)
{
if view.visual_anchor.is_none() {
view.visual_anchor = Some(view.cursor);
}
view.cursor = i;
}
}
MouseGesture::Cancel => view.visual_anchor = None,
}
}
pub(super) fn open_runs(&mut self) {
if self.ci_remotes.is_empty() {
self.info("no CI provider detected for this repo");
return;
}
self.runs_cursor = 0;
self.push_screen(Screen::Runs);
self.pending_ci = Some(CiRequest::Runs);
}
pub(super) fn open_selected_run(&mut self) {
let Some(run) = self.runs.get(self.runs_cursor) else {
return;
};
let id = run.id.clone();
self.open_run_remote = run.remote.clone();
self.open_run = Some(id.clone());
self.extras = None;
self.graph = Some(crate::graph::GraphView::new());
self.push_screen(Screen::Graph);
self.pending_ci = Some(CiRequest::Detail(id));
}
pub(super) fn on_ci_log(
&mut self,
text: &str,
steps: Vec<crate::ci::LogStepMeta>,
offset: u64,
done: bool,
) {
self.log_text.push_str(text);
self.log_offset = offset;
self.log_done = done;
if !steps.is_empty() {
self.log_steps = steps;
}
let rebuilt = logs::LogsView::parse(&self.log_text, &self.log_steps);
self.logs = Some(match self.logs.take() {
Some(prev) => prev.carry_into(rebuilt),
None => rebuilt,
});
}
pub(super) fn on_ci_runs(&mut self, runs: Vec<crate::ci::CiRun>) {
self.runs = runs;
self.runs_cursor = self.runs_cursor.min(self.runs.len().saturating_sub(1));
self.clamp_cursor();
if !self.pr_checked {
self.pending_ci = Some(CiRequest::Pr);
}
}
pub(super) fn on_run_detail(&mut self, detail: &crate::ci::RunDetail) {
let model = crate::ci::to_model(detail);
if let Some(graph) = self.graph.as_mut() {
graph.set_model(model);
}
if self.extras.is_none()
&& self.screen() == Screen::Graph
&& let Some(run) = self.open_run.clone()
{
self.pending_ci = Some(CiRequest::Extras(run));
}
}
pub(super) fn open_logs(&mut self, job: crate::ci::JobId) {
let Some(run) = self.open_run.clone() else {
return;
};
self.open_job = Some(job.clone());
self.log_text.clear();
self.log_offset = 0;
self.log_steps.clear();
self.logs = None;
self.log_done = false;
self.push_screen(Screen::Logs);
self.pending_ci = Some(CiRequest::Log {
run,
job,
offset: 0,
});
}
pub(super) fn queue_ci_poll(&mut self) {
self.pending_ci = match self.screen() {
Screen::Status | Screen::Runs => Some(CiRequest::Runs),
Screen::Graph => self.open_run.clone().map(CiRequest::Detail),
Screen::Logs if self.log_done => None,
Screen::Logs => match (self.open_run.clone(), self.open_job.clone()) {
(Some(run), Some(job)) => Some(CiRequest::Log {
run,
job,
offset: self.log_offset,
}),
_ => None,
},
_ => None,
};
}
pub(super) fn handle_runs_key(&mut self, key: &KeyEvent) -> Flow {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return self.pop_screen(),
KeyCode::Char('j') | KeyCode::Down => {
if !self.runs.is_empty() {
self.runs_cursor = (self.runs_cursor + 1).min(self.runs.len() - 1);
}
}
KeyCode::Char('k') | KeyCode::Up => {
self.runs_cursor = self.runs_cursor.saturating_sub(1);
}
KeyCode::Enter => self.open_selected_run(),
_ => {}
}
Flow::Continue
}
pub(super) fn dispatch_logs(&mut self, action: Action) {
let Some(view) = self.logs.as_mut() else {
return;
};
let last = view.rows().len().saturating_sub(1);
match action {
Action::MoveDown => view.cursor = (view.cursor + 1).min(last),
Action::MoveUp => view.cursor = view.cursor.saturating_sub(1),
Action::GoTop => view.cursor = 0,
Action::GoBottom => view.cursor = last,
Action::HalfPageDown => self.logs_page(false, false),
Action::HalfPageUp => self.logs_page(true, false),
Action::FullPageDown => self.logs_page(false, true),
Action::FullPageUp => self.logs_page(true, true),
Action::ToggleFold => view.toggle_fold_at_cursor(),
Action::VisualSelect => {
view.visual_anchor = match view.visual_anchor {
Some(_) => None,
None => Some(view.cursor),
};
}
Action::CopyFileFeedback | Action::CopyAllFeedback => {
self.pending_clipboard = Some(view.selection_text());
let view = self.logs.as_mut();
if let Some(view) = view {
view.visual_anchor = None;
}
self.info("yanked log selection");
}
_ => {}
}
}
pub(super) fn logs_page(&mut self, up: bool, full: bool) {
let Some(view) = self.logs.as_mut() else {
return;
};
let last = view.rows().len().saturating_sub(1);
let step = page_step(view.viewport, full);
view.cursor = if up {
view.cursor.saturating_sub(step)
} else {
(view.cursor + step).min(last)
};
}
pub(super) fn handle_graph_key(&mut self, key: &KeyEvent) -> Flow {
if matches!(key.code, KeyCode::Char('q') | KeyCode::Esc) {
return self.pop_screen();
}
if let Some(action) = self.graph.as_mut().and_then(|g| g.on_key(*key)) {
self.on_graph_action(&action);
}
Flow::Continue
}
pub(super) fn on_graph_action(&mut self, action: &crate::graph::GraphAction) {
match action {
crate::graph::GraphAction::Activated(id) => {
self.open_logs(crate::ci::JobId(id.0.clone()));
}
crate::graph::GraphAction::Folded { .. } => {}
}
}
}