diff --git a/Cargo.lock b/Cargo.lock index 9a4d74c4e..70163ac7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,7 +535,7 @@ dependencies = [ "os_str_bytes", "strsim", "termcolor", - "textwrap 0.14.2", + "textwrap", "vec_map", ] @@ -1004,7 +1004,7 @@ dependencies = [ "termcolor", "termimad", "test-case", - "textwrap 0.13.4", + "textwrap", "thiserror", "tokio", "toml", @@ -3206,22 +3206,15 @@ dependencies = [ "tempdir", ] -[[package]] -name = "textwrap" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" -dependencies = [ - "smawk", - "unicode-width", -] - [[package]] name = "textwrap" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" dependencies = [ + "smawk", + "terminal_size", + "unicode-linebreak", "unicode-width", ] @@ -3532,6 +3525,15 @@ dependencies = [ "matches", ] +[[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + [[package]] name = "unicode-normalization" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index 1073bdf68..6b562abae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ termcolor = "1.1.0" async-listen = "0.2.0" sha1 = "0.6.0" hex = "0.4.3" -textwrap = "0.13.4" +textwrap = {version="0.14.2", features=["terminal_size"]} log = "0.4.8" env_logger = "0.8.2" os-release = "0.1.0" diff --git a/src/options.rs b/src/options.rs index c031b9b88..3d7f33a1d 100644 --- a/src/options.rs +++ b/src/options.rs @@ -26,6 +26,9 @@ use crate::server; pub mod describe; +const MAX_TERM_WIDTH: usize = 90; +const MIN_TERM_WIDTH: usize = 50; + static CONNECTION_ARG_HINT: &str = "\ Run `edgedb project init` or use any of `-H`, `-P`, `-I` arguments \ to specify connection parameters. See `--help` for details"; @@ -34,9 +37,9 @@ const CONN_OPTIONS_GROUP: &str = "CONNECTION OPTIONS (`edgedb --help-connect` to see the full list)"; const EDGEDB_ABOUT: &str = "\ - Use the `edgedb` command-line tool to spin up local instances,\n\ - manage EdgeDB projects, create and apply migrations, and more.\n\ - \n\ + Use the `edgedb` command-line tool to spin up local instances, \ + manage EdgeDB projects, create and apply migrations, and more. \ + \n\n\ Running `edgedb` without a subcommand opens an interactive shell."; pub trait PropagateArgs { @@ -281,6 +284,36 @@ fn say_option_is_deprecated(option_name: &str, suggestion: &str) { fn make_subcommand_help() -> String { use std::fmt::Write; + let width = term_width(); + + // When the terminal is wider than 82 characters clap aligns + // the flags description text to the right of the flag name, + // when it is narrower than 82, the description goes below + // the option name. We want to align the subcommand description + // with the option description, hence there's some hand-tuning + // of the padding here. + let padding: usize = if width > 82 { 28 } else { 24 }; + + let extra_padding: usize = 4 + 1; + let details_width: usize = width - padding - extra_padding; + + let wrap = |text: &str| { + if text.len() <= details_width { + return text.to_string(); + } + + let text = textwrap::fill(text, details_width); + let mut lines = text.lines(); + let mut new_lines = vec![lines.nth(0).unwrap().to_string()]; + for line in lines { + new_lines.push( + format!(" {:padding$} {}", " ", line, padding=padding) + ); + } + + new_lines.join("\n") + }; + let mut buf = String::with_capacity(4096); write!(&mut buf, "SUBCOMMANDS:\n").unwrap(); @@ -301,16 +334,19 @@ fn make_subcommand_help() -> String { if subcmd.hidden { continue; } - writeln!(&mut buf, " {:28} {}", + writeln!(&mut buf, " {:padding$} {}", format!("{} {}", cmd.name, subcmd.name), - sdescr.help_title, + wrap(sdescr.help_title), + padding=padding ).unwrap(); } buf.push('\n'); empty_line = true; } else { - writeln!(&mut buf, " {:28} {}", - cmd.name, cdescr.help_title).unwrap(); + writeln!(&mut buf, " {:padding$} {}", + cmd.name, wrap(cdescr.help_title), + padding=padding + ).unwrap(); empty_line = false; } } @@ -338,7 +374,8 @@ fn print_full_connection_options() { let app = ::into_app(); let mut new_app = clap::App::new("edgedb-connect") - .setting(clap::AppSettings::DeriveDisplayOrder); + .setting(clap::AppSettings::DeriveDisplayOrder) + .term_width(term_width()); for arg in app.get_arguments() { let arg_name = arg.get_name(); @@ -446,11 +483,27 @@ fn get_deprecated_matches(mismatch_cmd: &str) -> Option { Some(app.get_matches_from(new_args)) } +fn term_width() -> usize { + use std::cmp; + + // clap::App::max_term_width() works poorly in conjunction + // with clap::App::term_width(); it appears that one call + // disables the effect of the other. Therefore we want to + // calculate the acceptable term width ourselves and use + // that to configure clap and to render subcommands help. + + cmp::max( + cmp::min(textwrap::termwidth(), MAX_TERM_WIDTH), + MIN_TERM_WIDTH + ) +} + impl Options { pub fn from_args_and_env() -> anyhow::Result { let app = ::into_app() .name("edgedb") - .about(EDGEDB_ABOUT); + .about(EDGEDB_ABOUT) + .term_width(term_width()); let app = update_main_help(app); let matches = get_matches(app); let tmp: RawOptions =