use crate::tui::app_states::components::ComponentsState;
use crate::tui::traits::{EventResult, Shortcut, TabTarget, ViewContext, ViewState};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent};
pub struct ComponentsView {
inner: ComponentsState,
}
impl ComponentsView {
pub(crate) fn new() -> Self {
Self {
inner: ComponentsState::new(0),
}
}
pub(crate) fn inner(&self) -> &ComponentsState {
&self.inner
}
pub(crate) fn inner_mut(&mut self) -> &mut ComponentsState {
&mut self.inner
}
}
impl Default for ComponentsView {
fn default() -> Self {
Self::new()
}
}
impl ViewState for ComponentsView {
fn handle_key(&mut self, key: KeyEvent, _ctx: &mut ViewContext) -> EventResult {
match key.code {
KeyCode::Char('f') => {
self.inner.toggle_filter();
EventResult::Consumed
}
KeyCode::Char('s') => {
self.inner.toggle_sort();
EventResult::Consumed
}
KeyCode::Char('v') => {
self.inner.toggle_multi_select_mode();
EventResult::Consumed
}
KeyCode::Char('p') | KeyCode::Tab => {
self.inner.toggle_focus();
EventResult::Consumed
}
KeyCode::Char(' ') if self.inner.multi_select_mode => {
self.inner.toggle_current_selection();
EventResult::Consumed
}
KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
self.inner.select_all();
EventResult::Consumed
}
KeyCode::Char('A') => {
self.inner.select_all();
EventResult::Consumed
}
KeyCode::Esc if self.inner.multi_select_mode => {
self.inner.toggle_multi_select_mode();
EventResult::Consumed
}
KeyCode::Char(c @ '1'..='8') => {
let idx = (c as u8 - b'1') as usize;
self.inner.security_filter.toggle_by_index(idx);
EventResult::status(self.inner.security_filter.summary())
}
KeyCode::Char('0') => {
self.inner.security_filter.clear_all();
EventResult::status("All filters cleared")
}
KeyCode::Char('d') => {
EventResult::NavigateTo(TabTarget::Dependencies)
}
KeyCode::Char('y' | 'F' | 'o' | 'n') => EventResult::Ignored,
_ => EventResult::Ignored,
}
}
fn handle_mouse(&mut self, _mouse: MouseEvent, _ctx: &mut ViewContext) -> EventResult {
EventResult::Ignored
}
fn title(&self) -> &'static str {
"Components"
}
fn shortcuts(&self) -> Vec<Shortcut> {
vec![
Shortcut::primary("j/k", "Navigate"),
Shortcut::new("d", "Dependencies"),
Shortcut::new("f", "Filter"),
Shortcut::new("s", "Sort"),
Shortcut::new("v", "Multi-select"),
Shortcut::new("1-8", "Quick filters"),
Shortcut::new("F", "Flag"),
Shortcut::new("y", "Copy"),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tui::traits::ViewMode;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
fn make_key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::NONE)
}
fn make_ctx(mode: ViewMode) -> ViewContext<'static> {
let status: &'static mut Option<String> = Box::leak(Box::new(None));
ViewContext {
mode,
focused: true,
width: 80,
height: 24,
tick: 0,
status_message: status,
}
}
#[test]
fn test_filter_toggle() {
let mut view = ComponentsView::new();
let mut ctx = make_ctx(ViewMode::Diff);
let initial = view.inner().filter;
view.handle_key(make_key(KeyCode::Char('f')), &mut ctx);
assert_ne!(view.inner().filter, initial);
}
#[test]
fn test_multi_select() {
let mut view = ComponentsView::new();
let mut ctx = make_ctx(ViewMode::Diff);
assert!(!view.inner().multi_select_mode);
view.handle_key(make_key(KeyCode::Char('v')), &mut ctx);
assert!(view.inner().multi_select_mode);
view.handle_key(make_key(KeyCode::Esc), &mut ctx);
assert!(!view.inner().multi_select_mode);
}
#[test]
fn test_data_dependent_keys_ignored() {
let mut view = ComponentsView::new();
let mut ctx = make_ctx(ViewMode::Diff);
assert_eq!(
view.handle_key(make_key(KeyCode::Char('y')), &mut ctx),
EventResult::Ignored
);
assert_eq!(
view.handle_key(make_key(KeyCode::Char('F')), &mut ctx),
EventResult::Ignored
);
}
#[test]
fn test_d_navigates_to_dependencies() {
let mut view = ComponentsView::new();
let mut ctx = make_ctx(ViewMode::Diff);
let result = view.handle_key(make_key(KeyCode::Char('d')), &mut ctx);
assert_eq!(result, EventResult::NavigateTo(TabTarget::Dependencies));
}
}