Skip to content

Commit

Permalink
Add ability to toggle cursor mode
Browse files Browse the repository at this point in the history
Also refactor a shit load of view code :)
  • Loading branch information
LucasPickering committed Nov 23, 2023
1 parent ac54e29 commit 2d27833
Show file tree
Hide file tree
Showing 29 changed files with 1,082 additions and 891 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased] - ReleaseDate

### Added

- Option to toggle cursor capture

## [0.8.0] - 2023-11-21

### Added
Expand Down
30 changes: 19 additions & 11 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async-trait = "^0.1.73"
chrono = {version = "^0.4.31", default-features = false, features = ["clock", "serde", "std"]}
clap = {version = "^4.4.2", features = ["derive"]}
crossterm = "^0.27.0"
derive_more = "^0.99.17"
derive_more = {version = "1.0.0-beta.6", features = ["debug", "deref", "display", "from"]}
dialoguer = {version = "^0.11.0", default-features = false, features = ["password"]}
dirs = "^5.0.1"
futures = "^0.3.28"
Expand Down
6 changes: 3 additions & 3 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub struct TemplateContext {
/// during creation to identify template keys, hence the immutability.
#[derive(Clone, Debug, Deref, Display, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
#[display(fmt = "{template}")]
#[display("{template}")]
#[serde(try_from = "String", into = "String")]
pub struct Template {
#[deref(forward)]
Expand Down Expand Up @@ -127,10 +127,10 @@ enum TemplateKey<T> {
/// A plain field, which can come from the profile or an override
Field(T),
/// A value from a predefined chain of another recipe
#[display(fmt = "{}{}", "CHAIN_PREFIX", "_0")]
#[display("{CHAIN_PREFIX}{_0}")]
Chain(T),
/// A value pulled from the process environment
#[display(fmt = "{}{}", "ENV_PREFIX", "_0")]
#[display("{ENV_PREFIX}{_0}")]
Environment(T),
}

Expand Down
45 changes: 31 additions & 14 deletions src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ use crate::{
};
use anyhow::{anyhow, Context};
use crossterm::{
execute,
terminal::{enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
event::{DisableMouseCapture, EnableMouseCapture},
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use futures::Future;
use indexmap::IndexMap;
Expand Down Expand Up @@ -65,15 +66,8 @@ impl Tui {
/// because they prevent TUI execution.
pub async fn start(collection_file: PathBuf) {
initialize_panic_handler();

// Set up terminal
enable_raw_mode().expect("Error initializing terminal");
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)
.expect("Error initializing terminal");
let backend = CrosstermBackend::new(stdout);
let terminal =
Terminal::new(backend).expect("Error initializing terminal");
initialize_terminal().expect("Error initializing terminal");

// Create a message queue for handling async tasks
let (messages_tx, messages_rx) = mpsc::unbounded_channel();
Expand Down Expand Up @@ -189,6 +183,10 @@ impl Tui {
self.reload_collection(collection);
}

Message::Error { error } => {
self.view.open_modal(error, ModalPriority::High)
}

// Manage HTTP life cycle
Message::HttpBeginRequest {
recipe_id,
Expand Down Expand Up @@ -237,6 +235,8 @@ impl Tui {
self.view.open_modal(prompt, ModalPriority::Low);
}

Message::Quit => self.quit(),

Message::TemplatePreview {
template,
profile_id,
Expand All @@ -249,10 +249,14 @@ impl Tui {
)?;
}

Message::Error { error } => {
self.view.open_modal(error, ModalPriority::High)
Message::ToggleMouseCapture { capture } => {
let mut stdout = io::stdout();
if capture {
stdout.execute(EnableMouseCapture)?;
} else {
stdout.execute(DisableMouseCapture)?;
}
}
Message::Quit => self.quit(),
}
Ok(())
}
Expand Down Expand Up @@ -449,10 +453,23 @@ fn initialize_panic_handler() {
}));
}

/// Set up terminal for TUI
fn initialize_terminal() -> anyhow::Result<Terminal<CrosstermBackend<Stdout>>> {
crossterm::terminal::enable_raw_mode()?;
let mut stdout = io::stdout();
crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
Ok(Terminal::new(backend)?)
}

/// Return terminal to initial state
fn restore_terminal() -> anyhow::Result<()> {
debug!("Restoring terminal");
crossterm::terminal::disable_raw_mode()?;
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen)?;
crossterm::execute!(
io::stderr(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
Ok(())
}
13 changes: 5 additions & 8 deletions src/tui/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use derive_more::Display;
use std::{
collections::HashMap,
fmt::{Debug, Display},
};
use std::{collections::HashMap, fmt::Debug};
use tracing::trace;

/// Top-level input manager. This handles things like bindings and mapping
Expand Down Expand Up @@ -101,10 +98,10 @@ pub enum Action {
ForceQuit,

/// Focus the previous pane
#[display(fmt = "Prev Pane")]
#[display("Prev Pane")]
PreviousPane,
/// Focus the next pane
#[display(fmt = "Next Pane")]
#[display("Next Pane")]
NextPane,

Up,
Expand All @@ -115,12 +112,12 @@ pub enum Action {
/// Do a thing. E.g. select an item in a list
Submit,
/// Send the active request from *any* context
#[display(fmt = "Send Request")]
#[display("Send Request")]
SendRequest,
/// Embiggen a pane
Fullscreen,
/// Open the settings modal
#[display(fmt = "Settings")]
#[display("Settings")]
OpenSettings,
/// Close the current modal/dialog/etc.
Cancel,
Expand Down
20 changes: 12 additions & 8 deletions src/tui/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub enum Message {
/// Store a reloaded collection value in state
CollectionEndReload(RequestCollection),

/// An error occurred in some async process and should be shown to the user
Error { error: anyhow::Error },

/// Launch an HTTP request from the given recipe/profile.
HttpBeginRequest {
recipe_id: RequestRecipeId,
Expand All @@ -74,15 +77,18 @@ pub enum Message {
/// these two cases saves a bit of boilerplate.
HttpComplete(Result<RequestRecord, RequestError>),

/// Show a prompt to the user, asking for some input. Use the included
/// channel to return the value.
PromptStart(Prompt),

/// Exit the program
Quit,

/// Load the most recent response for a recipe from the repository
RepositoryStartLoad { recipe_id: RequestRecipeId },
/// Finished loading a response from the repository
RepositoryEndLoad { record: RequestRecord },

/// Show a prompt to the user, asking for some input. Use the included
/// channel to return the value.
PromptStart(Prompt),

/// Render a template string, to be previewed in the UI. Ideally this could
/// be launched directly by the component that needs it, but only the
/// controller has the data needed to build the template context. The
Expand All @@ -97,8 +103,6 @@ pub enum Message {
destination: Arc<OnceLock<Vec<TemplateChunk>>>,
},

/// An error occurred in some async process and should be shown to the user
Error { error: anyhow::Error },
/// Exit the program
Quit,
/// Enable/disable mouse capture in the terminal
ToggleMouseCapture { capture: bool },
}
28 changes: 17 additions & 11 deletions src/tui/view.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
mod common;
mod component;
mod draw;
mod event;
mod state;
mod theme;
mod util;

pub use component::ModalPriority;
pub use common::modal::{IntoModal, ModalPriority};
pub use state::RequestState;
pub use util::PreviewPrompter;

Expand All @@ -13,12 +16,10 @@ use crate::{
input::{Action, InputEngine},
message::MessageSender,
view::{
component::{
Component, Draw, DrawContext, Event, IntoModal, Root, Update,
UpdateContext,
},
component::root::Root,
draw::{Draw, DrawContext},
event::{Event, EventHandler, Update, UpdateContext},
state::Notification,
theme::Theme,
},
},
};
Expand All @@ -35,7 +36,6 @@ use tracing::{error, trace, trace_span};
pub struct View {
messages_tx: MessageSender,
config: ViewConfig,
theme: Theme,
root: Root,
}

Expand All @@ -47,7 +47,6 @@ impl View {
let mut view = Self {
messages_tx,
config: ViewConfig::default(),
theme: Theme::default(),
root: Root::new(collection),
};
// Tell the components to wake up
Expand All @@ -68,7 +67,6 @@ impl View {
&mut DrawContext {
input_engine,
config: &self.config,
theme: &self.theme,
messages_tx,
frame,
},
Expand Down Expand Up @@ -150,7 +148,7 @@ impl View {
/// lowest descendant. Recursively walk up the tree until a component
/// consumes the event.
fn update_all(
component: &mut dyn Component,
component: &mut dyn EventHandler,
context: &mut UpdateContext,
mut event: Event,
) -> Update {
Expand All @@ -172,7 +170,8 @@ impl View {

// None of our children handled it, we'll take it ourselves.
// Message is already traced in the parent span, so don't dupe it.
let span = trace_span!("Component handling", %component);
// TODO figure out a way to print just the component type name
let span = trace_span!("Component handling", ?component);
span.in_scope(|| {
let outcome = component.update(context, event);
trace!(?outcome);
Expand All @@ -184,13 +183,20 @@ impl View {
/// Settings that control the behavior of the view
#[derive(Debug)]
struct ViewConfig {
/// Should templates be rendered inline in the UI, or should we show the
/// raw text?
preview_templates: bool,

/// Are we capture cursor events, or are they being handled by the terminal
/// emulator? This can be toggled by the user
capture_mouse: bool,
}

impl Default for ViewConfig {
fn default() -> Self {
Self {
preview_templates: true,
capture_mouse: true,
}
}
}
Loading

0 comments on commit 2d27833

Please sign in to comment.