use crate::command::GitCommand;
use crate::view::filterable_list::FilterableList;
use crate::view::selection::Direction;
use acp_utils::notifications::WorkspaceMoveTarget;
use agent_client_protocol::schema::v1::SessionId;
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use std::path::PathBuf;
const COMPOSED_MODIFIERS: KeyModifiers = KeyModifiers::CONTROL
.union(KeyModifiers::ALT)
.union(KeyModifiers::SUPER)
.union(KeyModifiers::HYPER)
.union(KeyModifiers::META);
pub(crate) fn is_composed_char(key: KeyEvent) -> bool {
matches!(key.code, KeyCode::Char(_)) && key.modifiers.intersects(COMPOSED_MODIFIERS)
}
pub(crate) fn is_press(key: KeyEvent) -> bool {
matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat)
}
#[derive(Debug)]
pub enum SessionPickerOutput {
Close,
Load { session_id: SessionId, cwd: PathBuf },
Preview(String),
}
#[derive(Debug)]
pub enum WorkspacePickerOutput {
Close,
Move { target: WorkspaceMoveTarget },
}
#[derive(Debug)]
pub enum SettingsOutput {
Close,
SetConfigOption { config_id: String, value: String },
SetTheme(String),
AuthenticateServer(String),
AuthenticateProvider(String),
}
#[derive(Debug)]
pub enum ElicitationOutput {
Close,
}
#[derive(Debug)]
pub enum ReviewOutcome {
Cancelled,
Submitted(String),
}
#[derive(Debug)]
pub enum GitReviewOutput {
Outcome(ReviewOutcome),
Task(GitCommand),
}
#[derive(Debug)]
pub enum PlanReviewOutput {
Outcome(ReviewOutcome),
}
#[derive(Debug)]
pub enum RootOutput {
Session(SessionPickerOutput),
Workspace(WorkspacePickerOutput),
Settings(SettingsOutput),
Elicitation(ElicitationOutput),
GitReview(GitReviewOutput),
PlanReview(PlanReviewOutput),
}
pub enum UiEvent {
Key(KeyEvent),
Paste(String),
Mouse(MouseAction, (u16, u16)),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MouseAction {
ScrollUp,
ScrollDown,
Click,
}
impl MouseAction {
pub fn from_event(kind: crossterm::event::MouseEventKind) -> Option<Self> {
use crossterm::event::MouseEventKind;
match kind {
MouseEventKind::ScrollUp => Some(Self::ScrollUp),
MouseEventKind::ScrollDown => Some(Self::ScrollDown),
MouseEventKind::Down(_) => Some(Self::Click),
_ => None,
}
}
pub fn direction(self) -> Option<Direction> {
match self {
Self::ScrollUp => Some(Direction::Backward),
Self::ScrollDown => Some(Direction::Forward),
Self::Click => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum Nav {
Close,
Activate,
Clicked,
Moved,
Unhandled,
}
impl<T> FilterableList<T> {
pub(crate) fn on_nav_event(&mut self, event: &UiEvent) -> Nav {
match event {
UiEvent::Key(key) if is_press(*key) => self.on_nav_key(*key),
UiEvent::Key(_) | UiEvent::Paste(_) => Nav::Unhandled,
UiEvent::Mouse(action, (_, row)) => match action.direction() {
Some(direction) => {
self.step(direction);
Nav::Moved
}
None if self.select_at(*row) => Nav::Clicked,
None => Nav::Unhandled,
},
}
}
fn on_nav_key(&mut self, key: KeyEvent) -> Nav {
match key.code {
KeyCode::Esc => Nav::Close,
KeyCode::Enter => Nav::Activate,
KeyCode::Up => {
self.step(Direction::Backward);
Nav::Moved
}
KeyCode::Down => {
self.step(Direction::Forward);
Nav::Moved
}
KeyCode::Backspace => {
self.pop_query_char();
Nav::Moved
}
KeyCode::Char(c) if !c.is_control() && !is_composed_char(key) => {
self.push_query_char(c);
Nav::Moved
}
_ => Nav::Unhandled,
}
}
}
pub(crate) fn one<T>(action: Option<T>) -> Vec<T> {
action.into_iter().collect()
}