Skip to content

Commit

Permalink
Skip input handling for ctrl-c (closes #18)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasPickering committed Nov 4, 2023
1 parent 19c10a1 commit 92c1331
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Fixed

- Differentiate history between different collections [#10](https://github.com/LucasPickering/slumber/issues/10)
- Ensure ctrl-c can't get eaten by text boxes (it guarantees exit now) [#18](https://github.com/LucasPickering/slumber/issues/18)

## [0.4.0] - 2023-11-02

Expand Down
54 changes: 30 additions & 24 deletions src/tui/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,27 @@ use tracing::trace;
/// events to actions, but then the actions are actually processed by the view.
#[derive(Debug)]
pub struct InputEngine {
/// Intuitively this should be binding:action, but we can't look up a
/// binding from the map based on an input event, because event<=>binding
/// matching is more nuanced that simple equality (e.g. bonus modifiers
/// keys can be ignored). We have to iterate over map when checking inputs,
/// but keying by action at least allows us to look up action=>binding for
/// help text.
bindings: HashMap<Action, InputBinding>,
}

impl InputEngine {
pub fn new() -> Self {
Self {
bindings: [
InputBinding {
action: Action::Quit,
primary: KeyCombination {
key_code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
},
secondary: Some(KeyCombination {
InputBinding::new(KeyCode::Char('q'), Action::Quit),
InputBinding::new(
KeyCombination {
key_code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}),
},
},
Action::ForceQuit,
),
InputBinding::new(KeyCode::Char('r'), Action::ReloadCollection),
InputBinding::new(KeyCode::F(11), Action::Fullscreen),
InputBinding::new(KeyCode::BackTab, Action::PreviousPane),
Expand Down Expand Up @@ -91,6 +94,10 @@ impl InputEngine {
pub enum Action {
/// Exit the app
Quit,
/// A special keybinding that short-circuits the standard view input
/// process to force an exit. Standard shutdown will *still run*, but this
/// input can't be consumed by any components in the view tree.
ForceQuit,
/// Reload the request collection from the same file as the initial load
#[display(fmt = "Reload Collection")]
ReloadCollection,
Expand Down Expand Up @@ -120,36 +127,26 @@ pub enum Action {
#[derive(Copy, Clone, Debug)]
pub struct InputBinding {
action: Action,
primary: KeyCombination,
secondary: Option<KeyCombination>,
input: KeyCombination,
}

impl InputBinding {
/// Create a binding with only a primary
const fn new(key_code: KeyCode, action: Action) -> Self {
fn new(input: impl Into<KeyCombination>, action: Action) -> Self {
Self {
action,
primary: KeyCombination {
key_code,
modifiers: KeyModifiers::NONE,
},
secondary: None,
input: input.into(),
}
}

fn matches(&self, event: &KeyEvent) -> bool {
self.primary.matches(event)
|| self
.secondary
.map(|secondary| secondary.matches(event))
.unwrap_or_default()
self.input.matches(event)
}
}

impl Display for InputBinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Don't display secondary binding in help text
write!(f, "{} {}", self.primary, self.action)
write!(f, "{} {}", self.input, self.action)
}
}

Expand Down Expand Up @@ -185,3 +182,12 @@ impl Display for KeyCombination {
}
}
}

impl From<KeyCode> for KeyCombination {
fn from(key_code: KeyCode) -> Self {
Self {
key_code,
modifiers: KeyModifiers::NONE,
}
}
}
19 changes: 15 additions & 4 deletions src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
http::{HttpEngine, Repository, RequestBuilder},
template::TemplateContext,
tui::{
input::InputEngine,
input::{Action, InputEngine},
message::{Message, MessageSender},
view::{ModalPriority, RequestState, View},
},
Expand Down Expand Up @@ -123,7 +123,13 @@ impl Tui {
// editors and such
let event = crossterm::event::read()?;
let action = self.input_engine.action(&event);
self.view.handle_input(event, action);
if let Some(Action::ForceQuit) = action {
// Short-circuit the view/message cycle, to make sure this
// doesn't get ate
self.quit();
} else {
self.view.handle_input(event, action);
}
}
if last_tick.elapsed() >= Self::TICK_TIME {
last_tick = Instant::now();
Expand All @@ -143,13 +149,18 @@ impl Tui {

// ===== Signal Phase =====
if quit_signals.pending().next().is_some() {
self.should_run = false;
self.quit();
}
}

Ok(())
}

/// GOODBYE
fn quit(&mut self) {
self.should_run = false;
}

/// Handle an incoming message. Any error here will be displayed as a modal
fn handle_message(&mut self, message: Message) -> anyhow::Result<()> {
match message {
Expand Down Expand Up @@ -217,7 +228,7 @@ impl Tui {
Message::Error { error } => {
self.view.open_modal(error, ModalPriority::High)
}
Message::Quit => self.should_run = false,
Message::Quit => self.quit(),
}
Ok(())
}
Expand Down

0 comments on commit 92c1331

Please sign in to comment.