mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-18 10:07:28 +00:00
cli: make pager configurable per command
Allow `ui.pager` to be set as a table like so: [ui] pager.status = ":none" pager.log = { command = ["less", "-FRX"], env = { LESSCHARSET = "utf-8" } } pager.diff = ["delta", "..."] pager.default = "..." `ui.pager.log` is used for `jj log`, `ui.pager.diff` for `jj diff`, etc. The full name of the highest level subcommand is used, i.e. `jj operation diff` uses `ui.pager.operation`. The value ":none" disables paging for the selected command. If a command is not specifically mentioned, the value of `ui.pager.default` is used. The old behaviour is unchanged, i.e. `ui.pager = ["less", "-FRX"]` will set the pager for all subcommands.
This commit is contained in:
parent
7df0f16fe0
commit
79d45f6c96
19 changed files with 85 additions and 40 deletions
|
@ -884,7 +884,7 @@ fn handle_clap_error(ui: &mut Ui, err: &clap::Error, hints: &[ErrorHint]) -> io:
|
|||
|
||||
match err.kind() {
|
||||
clap::error::ErrorKind::DisplayHelp
|
||||
| clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => ui.request_pager(),
|
||||
| clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => ui.request_pager(""),
|
||||
_ => {}
|
||||
};
|
||||
// Definitions for exit codes and streams come from
|
||||
|
|
|
@ -148,7 +148,7 @@ pub fn cmd_bookmark_list(
|
|||
.labeled("bookmark_list")
|
||||
};
|
||||
|
||||
ui.request_pager();
|
||||
ui.request_pager("bookmark");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
|
||||
let mut found_deleted_local_bookmark = false;
|
||||
|
|
|
@ -89,7 +89,7 @@ pub fn cmd_config_list(
|
|||
}
|
||||
|
||||
if !annotated_values.is_empty() {
|
||||
ui.request_pager();
|
||||
ui.request_pager("config");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
for annotated in &annotated_values {
|
||||
template.format(annotated, formatter.as_mut())?;
|
||||
|
|
|
@ -123,7 +123,7 @@ pub(crate) fn cmd_diff(
|
|||
}
|
||||
|
||||
let diff_renderer = workspace_command.diff_renderer_for(&args.format)?;
|
||||
ui.request_pager();
|
||||
ui.request_pager("diff");
|
||||
diff_renderer.show_diff(
|
||||
ui,
|
||||
ui.stdout_formatter().as_mut(),
|
||||
|
|
|
@ -119,7 +119,7 @@ pub(crate) fn cmd_evolog(
|
|||
.labeled("node");
|
||||
}
|
||||
|
||||
ui.request_pager();
|
||||
ui.request_pager("evolog");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ fn render_file_annotation(
|
|||
template_render: &TemplateRenderer<Commit>,
|
||||
annotation: &FileAnnotation,
|
||||
) -> Result<(), CommandError> {
|
||||
ui.request_pager();
|
||||
ui.request_pager("file");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
for (line_no, (commit_id, line)) in annotation.lines().enumerate() {
|
||||
let commit_id = commit_id.expect("should reached to the empty ancestor");
|
||||
|
|
|
@ -51,7 +51,7 @@ pub(crate) fn cmd_file_list(
|
|||
let matcher = workspace_command
|
||||
.parse_file_patterns(ui, &args.paths)?
|
||||
.to_matcher();
|
||||
ui.request_pager();
|
||||
ui.request_pager("file");
|
||||
for (name, _value) in tree.entries_matching(matcher.as_ref()) {
|
||||
writeln!(
|
||||
ui.stdout(),
|
||||
|
|
|
@ -83,14 +83,14 @@ pub(crate) fn cmd_file_show(
|
|||
return Err(user_error(format!("No such path: {ui_path}")));
|
||||
}
|
||||
if !value.is_tree() {
|
||||
ui.request_pager();
|
||||
ui.request_pager("file");
|
||||
write_tree_entries(ui, &workspace_command, [(path, Ok(value))])?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let matcher = fileset_expression.to_matcher();
|
||||
ui.request_pager();
|
||||
ui.request_pager("file");
|
||||
write_tree_entries(
|
||||
ui,
|
||||
&workspace_command,
|
||||
|
|
|
@ -52,7 +52,7 @@ pub(crate) fn cmd_help(
|
|||
) -> Result<(), CommandError> {
|
||||
if let Some(name) = &args.keyword {
|
||||
let keyword = find_keyword(name).expect("clap should check this with `value_parser`");
|
||||
ui.request_pager();
|
||||
ui.request_pager("help");
|
||||
write!(ui.stdout(), "{}", keyword.content)?;
|
||||
|
||||
return Ok(());
|
||||
|
|
|
@ -78,7 +78,7 @@ pub(crate) fn cmd_interdiff(
|
|||
.parse_file_patterns(ui, &args.paths)?
|
||||
.to_matcher();
|
||||
let diff_renderer = workspace_command.diff_renderer_for(&args.format)?;
|
||||
ui.request_pager();
|
||||
ui.request_pager("interdiff");
|
||||
diff_renderer.show_inter_diff(
|
||||
ui,
|
||||
ui.stdout_formatter().as_mut(),
|
||||
|
|
|
@ -173,7 +173,7 @@ pub(crate) fn cmd_log(
|
|||
}
|
||||
|
||||
{
|
||||
ui.request_pager();
|
||||
ui.request_pager("log");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ pub fn cmd_op_diff(
|
|||
};
|
||||
|
||||
let op_summary_template = workspace_command.operation_summary_template();
|
||||
ui.request_pager();
|
||||
ui.request_pager("operation");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
write!(formatter, "From operation: ")?;
|
||||
op_summary_template.format(&from_op, &mut *formatter)?;
|
||||
|
|
|
@ -186,7 +186,7 @@ fn do_op_log(
|
|||
None
|
||||
};
|
||||
|
||||
ui.request_pager();
|
||||
ui.request_pager("operation");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
let limit = args.limit.unwrap_or(usize::MAX);
|
||||
|
|
|
@ -93,7 +93,7 @@ pub fn cmd_op_show(
|
|||
.labeled("operation")
|
||||
};
|
||||
|
||||
ui.request_pager();
|
||||
ui.request_pager("operation");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
template.format(&op, formatter.as_mut())?;
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ pub(crate) fn cmd_show(
|
|||
};
|
||||
let template = workspace_command.parse_commit_template(ui, &template_string)?;
|
||||
let diff_renderer = workspace_command.diff_renderer_for(&args.format)?;
|
||||
ui.request_pager();
|
||||
ui.request_pager("show");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
template.format(&commit, formatter)?;
|
||||
|
|
|
@ -58,7 +58,7 @@ pub(crate) fn cmd_status(
|
|||
let matcher = workspace_command
|
||||
.parse_file_patterns(ui, &args.paths)?
|
||||
.to_matcher();
|
||||
ui.request_pager();
|
||||
ui.request_pager("status");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
let formatter = formatter.as_mut();
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ fn cmd_tag_list(
|
|||
.labeled("tag_list")
|
||||
};
|
||||
|
||||
ui.request_pager();
|
||||
ui.request_pager("tag");
|
||||
let mut formatter = ui.stdout_formatter();
|
||||
|
||||
for (name, target) in view.tags() {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
|
@ -49,6 +50,7 @@ use crate::formatter::LabeledWriter;
|
|||
use crate::formatter::PlainTextFormatter;
|
||||
|
||||
const BUILTIN_PAGER_NAME: &str = ":builtin";
|
||||
const BUILTIN_PAGER_NONE_NAME: &str = ":none";
|
||||
|
||||
enum UiOutput {
|
||||
Terminal {
|
||||
|
@ -257,13 +259,28 @@ impl Write for UiStderr<'_> {
|
|||
|
||||
pub struct Ui {
|
||||
quiet: bool,
|
||||
pager_cmd: CommandNameAndArgs,
|
||||
pager_cmd: PagerCmd,
|
||||
paginate: PaginationChoice,
|
||||
progress_indicator: bool,
|
||||
formatter_factory: FormatterFactory,
|
||||
output: UiOutput,
|
||||
}
|
||||
|
||||
enum PagerCmd {
|
||||
Default(CommandNameAndArgs),
|
||||
PerSubcommand(HashMap<String, CommandNameAndArgs>),
|
||||
}
|
||||
|
||||
impl PagerCmd {
|
||||
fn from_config(config: &StackedConfig) -> Result<Self, ConfigGetError> {
|
||||
const KEY: &str = "ui.pager";
|
||||
config
|
||||
.get(KEY)
|
||||
.map(Self::Default)
|
||||
.or_else(|_| config.get(KEY).map(Self::PerSubcommand))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ColorChoice {
|
||||
|
@ -333,7 +350,7 @@ impl Ui {
|
|||
Ok(Ui {
|
||||
quiet: config.get("ui.quiet")?,
|
||||
formatter_factory,
|
||||
pager_cmd: config.get("ui.pager")?,
|
||||
pager_cmd: PagerCmd::from_config(config)?,
|
||||
paginate: config.get("ui.paginate")?,
|
||||
progress_indicator: config.get("ui.progress-indicator")?,
|
||||
output: UiOutput::new_terminal(),
|
||||
|
@ -343,7 +360,7 @@ impl Ui {
|
|||
pub fn reset(&mut self, config: &StackedConfig) -> Result<(), CommandError> {
|
||||
self.quiet = config.get("ui.quiet")?;
|
||||
self.paginate = config.get("ui.paginate")?;
|
||||
self.pager_cmd = config.get("ui.pager")?;
|
||||
self.pager_cmd = PagerCmd::from_config(config)?;
|
||||
self.progress_indicator = config.get("ui.progress-indicator")?;
|
||||
self.formatter_factory = prepare_formatter_factory(config, &io::stdout())?;
|
||||
Ok(())
|
||||
|
@ -351,7 +368,7 @@ impl Ui {
|
|||
|
||||
/// Switches the output to use the pager, if allowed.
|
||||
#[instrument(skip_all)]
|
||||
pub fn request_pager(&mut self) {
|
||||
pub fn request_pager(&mut self, subcommand: &str) {
|
||||
match self.paginate {
|
||||
PaginationChoice::Never => return,
|
||||
PaginationChoice::Auto => {}
|
||||
|
@ -360,25 +377,37 @@ impl Ui {
|
|||
return;
|
||||
}
|
||||
|
||||
let use_builtin_pager = matches!(
|
||||
&self.pager_cmd, CommandNameAndArgs::String(name) if name == BUILTIN_PAGER_NAME);
|
||||
let new_output = if use_builtin_pager {
|
||||
Some(UiOutput::new_builtin())
|
||||
} else {
|
||||
UiOutput::new_paged(&self.pager_cmd)
|
||||
.inspect_err(|err| {
|
||||
// The pager executable couldn't be found or couldn't be run
|
||||
writeln!(
|
||||
self.warning_default(),
|
||||
"Failed to spawn pager '{name}': {err}",
|
||||
name = self.pager_cmd.split_name(),
|
||||
err = format_error_with_sources(err),
|
||||
)
|
||||
.ok();
|
||||
writeln!(self.hint_default(), "Consider using the `:builtin` pager.").ok();
|
||||
})
|
||||
.ok()
|
||||
let show_err = |cmd: &CommandNameAndArgs, err: &_| {
|
||||
writeln!(
|
||||
self.warning_default(),
|
||||
"Failed to spawn pager '{name}': {err}",
|
||||
name = cmd.split_name(),
|
||||
err = format_error_with_sources(err)
|
||||
)
|
||||
.ok();
|
||||
writeln!(self.hint_default(), "Consider using the `:builtin` pager.").ok();
|
||||
};
|
||||
|
||||
let new_output = match &self.pager_cmd {
|
||||
PagerCmd::Default(CommandNameAndArgs::String(name)) if name == BUILTIN_PAGER_NAME => {
|
||||
Some(UiOutput::new_builtin())
|
||||
}
|
||||
PagerCmd::Default(cmd) => UiOutput::new_paged(cmd)
|
||||
.inspect_err(|err| show_err(cmd, err))
|
||||
.ok(),
|
||||
PagerCmd::PerSubcommand(map) => {
|
||||
match map.get(subcommand).or_else(|| map.get("default")) {
|
||||
Some(CommandNameAndArgs::String(name)) if name == BUILTIN_PAGER_NONE_NAME => {
|
||||
None
|
||||
}
|
||||
Some(cmd) => UiOutput::new_paged(cmd)
|
||||
.inspect_err(|err| show_err(cmd, err))
|
||||
.ok(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(output) = new_output {
|
||||
self.output = output;
|
||||
}
|
||||
|
|
|
@ -578,6 +578,22 @@ pager = "delta"
|
|||
format = "git"
|
||||
```
|
||||
|
||||
### Setting the pager per subcommand
|
||||
|
||||
A different pager can be configured for each subcommand by using a table. For
|
||||
example:
|
||||
|
||||
```toml
|
||||
[ui]
|
||||
# set to ":none" to disable paging for a command
|
||||
pager.status = ":none"
|
||||
pager.log = { command = ["less", "-FRX"], env = { LESSCHARSET = "utf-8" } }
|
||||
pager.diff = ["delta", "--dark"]
|
||||
|
||||
# use pager.default for all subcommands not otherwise mentioned
|
||||
pager.default = ":builtin"
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
||||
You can define aliases for commands, including their arguments. For example:
|
||||
|
|
Loading…
Reference in a new issue