Skip to content

Commit

Permalink
feat: allow customizing whether UI should update while blocking (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
fritzrehde authored Jan 21, 2024
1 parent 51db6d1 commit 5a6880f
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 28 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ tabled = { version = "0.14.0", features = ["color"] }
terminal_size = "0.3.0"
owo-colors = "3.5.0"
dirs = "5.0.1"
const_format = "0.2.32"

# Config for 'cargo dist'
[workspace.metadata.dist]
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ Operation | Description
`help-[show\|hide\|toggle]` | \[Show\|Hide\|Toggle\] the help menu that shows all activated keybindings.

All `CMD` and `TUI-CMD` shell commands will be executed in a subshell (i.e. `sh -c "CMD"`) that has some environment variables set.
The environment variable `line` is set to the line the cursor is on.
The environment variable `lines` set to all selected lines, or if none are selected, the line the cursor is currently on.
The environment variable `$line` is set to the line the cursor is on.
The environment variable `$lines` set to all selected lines, or if none are selected, the line the cursor is currently on.
All set environment variables `ENV` will be made available in all future spawned commands/processes, including the watched command, any executed subcommands, as well as commands executed in `set-env` operations.
If multiple lines are selected, they will be separated by newlines in `lines`.
If multiple lines are selected, they will be separated by newlines in `$lines`.

### Styling

Expand Down Expand Up @@ -251,7 +251,7 @@ Choose only specific fields to display.
You can specify a comma-separated list of the indexes (starting at index 1) for individual fields (`X`), ranges (`X-Y`), or the capture of all fields from X onwards (`X-`).
For instance, the field selection `1,3-4,6-` will display the first, third and fourth fields, as well as all fields from the sixth onwards.

**Important**: The `lines` passed to the `exec --` operations will remain unformatted, i.e. will not have the separators replaced with elastic tabstops and will not have non-selected fields ommitted.
**Important**: The `$lines` passed to the `exec --` operations will remain unformatted, i.e. will not have the separators replaced with elastic tabstops and will not have non-selected fields ommitted.

### State management

Expand Down Expand Up @@ -314,3 +314,9 @@ But note that
watchbind --bind "enter:notify-send $lines" ls
```
will not work as expected, because `$lines` will be replaced in the shell you are running the `watchbind` command from.

### UI updates while blocking

Watchbind can enter blocking states when a blocking subcommand is executed.
The default behaviour in a blocking state is to *not* display any new output lines received from the watched command.
However, this behaviour can easily be customized with the `--update-ui-while-blocking <BOOL>` option.
67 changes: 48 additions & 19 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub struct Config {
pub header_lines: usize,
pub fields: Fields,
pub initial_env_ops: OperationsParsed,
pub update_ui_while_blocking: bool,
}

const GLOBAL_CONFIG_FILE: &str = "config.toml";
Expand Down Expand Up @@ -104,40 +105,52 @@ fn global_config_file_path() -> Result<PathBuf> {
Ok(global_config_dir)
}

/// Some `PartialConfig` fields **must** be set once everything has been
/// merged. Panic with this error message if that is not the case.
macro_rules! expect {
($obj:expr, $field:ident) => {
$obj.$field.expect(const_format::formatcp!(
"Expected field '{}' to be set in the default TOML config",
stringify!($field)
))
};
}

impl TryFrom<PartialConfig> for Config {
type Error = anyhow::Error;
fn try_from(toml: PartialConfig) -> Result<Self, Self::Error> {
fn try_from(config: PartialConfig) -> Result<Self, Self::Error> {
let non_cursor_non_header_style = Style::new(
toml.non_cursor_non_header_fg,
toml.non_cursor_non_header_bg,
toml.non_cursor_non_header_boldness,
config.non_cursor_non_header_fg,
config.non_cursor_non_header_bg,
config.non_cursor_non_header_boldness,
);
let cursor_style = Style::new(config.cursor_fg, config.cursor_bg, config.cursor_boldness);
let header_style = Style::new(config.header_fg, config.header_bg, config.header_boldness);
let selected_style = Style::new(
Color::Unspecified,
config.selected_bg,
Boldness::Unspecified,
);
let cursor_style = Style::new(toml.cursor_fg, toml.cursor_bg, toml.cursor_boldness);
let header_style = Style::new(toml.header_fg, toml.header_bg, toml.header_boldness);
let selected_style =
Style::new(Color::Unspecified, toml.selected_bg, Boldness::Unspecified);
let styles = Styles::new(
non_cursor_non_header_style,
cursor_style,
header_style,
selected_style,
);

// Some fields **must** contain a value in `TomlConfig::default()`.
// Panic with this error message if that is not the case.
let error_msg = "Should have a value in the default TOML config";
Ok(Self {
log_file: toml.log_file,
initial_env_ops: toml.initial_env_vars.unwrap_or_default().try_into()?,
watched_command: match toml.watched_command {
log_file: config.log_file,
initial_env_ops: config.initial_env_vars.unwrap_or_default().try_into()?,
watched_command: match config.watched_command {
Some(command) => command,
None => bail!("A command must be provided via command line or config file"),
},
watch_rate: Duration::from_secs_f64(toml.interval.expect(error_msg)),
watch_rate: Duration::from_secs_f64(expect!(config, interval)),
styles,
keybindings_parsed: toml.keybindings.expect(error_msg),
header_lines: toml.header_lines.expect(error_msg),
fields: Fields::try_new(toml.field_separator, toml.field_selections)?,
keybindings_parsed: expect!(config, keybindings),
header_lines: expect!(config, header_lines),
fields: Fields::try_new(config.field_separator, config.field_selections)?,
update_ui_while_blocking: expect!(config, update_ui_while_blocking),
})
}
}
Expand All @@ -164,6 +177,7 @@ pub struct PartialConfig {
selected_bg: Color,
field_selections: Option<FieldSelections>,
field_separator: Option<FieldSeparator>,
update_ui_while_blocking: Option<bool>,
keybindings: Option<KeybindingsParsed>,
}

Expand Down Expand Up @@ -213,6 +227,9 @@ impl PartialConfig {
header_lines: self.header_lines.or(other.header_lines),
field_separator: self.field_separator.or(other.field_separator),
field_selections: self.field_selections.or(other.field_selections),
update_ui_while_blocking: self
.update_ui_while_blocking
.or(other.update_ui_while_blocking),
keybindings: KeybindingsParsed::merge(self.keybindings, other.keybindings),
}
}
Expand All @@ -221,7 +238,7 @@ impl PartialConfig {
/// file type to be parsed from can be `global` or `local`.
fn parse_from_optional_toml_file(
opt_file: Option<&PathBuf>,
config_file_type: &'static str,
config_file_type: &str,
) -> Result<Option<PartialConfig>> {
match opt_file {
Some(file) => {
Expand Down Expand Up @@ -282,6 +299,8 @@ pub struct TomlFileConfig {
field_selections: Option<FieldSelections>,
field_separator: Option<FieldSeparator>,

update_ui_while_blocking: Option<bool>,

keybindings: Option<StringKeybindings>,
}

Expand Down Expand Up @@ -331,6 +350,7 @@ impl TryFrom<TomlFileConfig> for PartialConfig {
header_lines: toml.header_lines,
field_separator: toml.field_separator,
field_selections: toml.field_selections,
update_ui_while_blocking: toml.update_ui_while_blocking,
keybindings: toml
.keybindings
.map(KeybindingsParsed::try_from)
Expand Down Expand Up @@ -360,6 +380,7 @@ impl TryFrom<CliArgs> for PartialConfig {
header_lines: cli.header_lines,
field_separator: cli.field_separator,
field_selections: cli.field_selections,
update_ui_while_blocking: cli.update_ui_while_blocking,
keybindings: cli
.keybindings
.map(StringKeybindings::from)
Expand All @@ -369,6 +390,7 @@ impl TryFrom<CliArgs> for PartialConfig {
}
}

// TODO: add test that checks that the default config sets all values.
impl Default for PartialConfig {
fn default() -> Self {
let default_toml = indoc! {r#"
Expand All @@ -389,6 +411,8 @@ impl Default for PartialConfig {
"selected-bg" = "magenta"
"update-ui-while-blocking" = false
[keybindings]
"ctrl+c" = [ "exit" ]
"q" = [ "exit" ]
Expand Down Expand Up @@ -548,6 +572,11 @@ pub struct CliArgs {
#[arg(short = 'f', long = "fields", value_name = "LIST")]
field_selections: Option<FieldSelections>,

/// Whether to update the UI with new output from the watched command
/// while in a blocking state.
#[arg(long, value_name = "BOOL")]
update_ui_while_blocking: Option<bool>,

// TODO: replace with ClapKeybindings (currently panics, known clap bug)
// TODO: replace with StringKeybindings once clap supports parsing into HashMap
/// Keybindings as comma-separated `KEY:OP[+OP]*` pairs, e.g. `q:select+exit,r:reload`.
Expand Down
12 changes: 7 additions & 5 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct UI {
keybindings: Arc<Keybindings>,
remaining_operations: Option<RemainingOperations>,
channels: Channels,
update_ui_while_blocking: bool,
}

/// After having blocked, there might be some remaining operations, that
Expand Down Expand Up @@ -207,6 +208,7 @@ impl UI {
reload_tx,
polling_tx,
},
update_ui_while_blocking: config.update_ui_while_blocking,
};

Ok((ui, polling_state))
Expand Down Expand Up @@ -317,11 +319,11 @@ impl UI {
},
BlockingState::BlockedExecutingSubcommand => match event {
Event::CommandOutput(lines) => {
// TODO: it's up for discussion if we really want this behaviour, need to find use-cases against first

// We handle new output lines, but don't exit the
// blocking state.
self.state.update_lines(lines?)?;
if self.update_ui_while_blocking {
// We update the UI with the new output lines,
// but don't exit the blocking state.
self.state.update_lines(lines?)?;
}
}
Event::SubcommandCompleted(potential_error) => {
potential_error?;
Expand Down

0 comments on commit 5a6880f

Please sign in to comment.