Add backtick code formatting to help (#15892)

# Description
Adds formatting for code in backticks in `help` output. If it's possible
to highlight syntax (`nu-highlight` is available and there's no invalid
syntax) then it's highlighted. If the syntax is invalid or not an
internal command, then it's dimmed and italicized. like some of the
output from `std/help`. If `use_ansi_coloring` is `false`, then we leave
the backticks alone. Here's a couple examples:


![image](https://github.com/user-attachments/assets/57eed1dd-b38c-48ef-92c6-3f805392487c)


![image](https://github.com/user-attachments/assets/a0efa0d7-fc11-4702-973b-a0b448c383e0)

(note on this one: usually we can highlight partial commands, like `get`
in the `select` help page which is invalid according to `nu-check` but
is still properly highlighted, however `where` is special cased and just
typing `where` with no row condition is highlighted with the garbage
style so `where` alone isn't highlighted here)

![image](https://github.com/user-attachments/assets/28c110c9-16c4-4890-bc74-6de0f2e6d1b8)

here's the `where` page with `$env.config.use_ansi_coloring = false`:

![image](https://github.com/user-attachments/assets/57871cc8-d509-4719-9dd4-e6f24f9d891c)


Technically, some syntax is valid but isn't really "Nushell code". For
example, the `select` help page has a line that says "Select just the
\`name\` column". If you just type `name` in the REPL, Nushell treats it
as an external command, but for the purposes of highlighted we actually
want this to fall back to the generic dimmed/italic style. This is
accomplished by temporarily setting the `shape_external` and
`shape_externalarg` color config to the generic/fallback style, and then
restoring the color config after highlighting. This is a bit hack-ish
but it seems to work pretty well.


# User-Facing Changes

- `help` command now supports code backtick formatting. Code will be
highlighted using `nu-highlight` if possible, otherwise it will fall
back to a generic format.
- Adds `--reject-garbage` flag to `nu-highlight` which will return an
error on invalid syntax (which would otherwise be highlighted with
`$env.config.color_config.shape_garbage`)

# Tests + Formatting

Added tests for the regex. I don't think tests for the actual
highlighting are very necessary since the failure mode is graceful and
it would be difficult to meaningfully test.

# After Submitting

N/A

---------

Co-authored-by: Piepmatz <git+github@cptpiepmatz.de>
This commit is contained in:
132ikl
2025-06-25 15:26:52 -04:00
committed by GitHub
parent cb7ac9199d
commit 6fba4b409e
8 changed files with 547 additions and 243 deletions

View File

@ -1,5 +1,5 @@
use crate::filters::find_internal;
use nu_engine::{command_prelude::*, scope::ScopeData};
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
#[derive(Clone)]
pub struct HelpAliases;
@ -101,42 +101,17 @@ pub fn help_aliases(
});
};
let Some(alias) = engine_state.get_decl(alias).as_alias() else {
let alias = engine_state.get_decl(alias);
if alias.as_alias().is_none() {
return Err(ShellError::AliasNotFound {
span: Span::merge_many(rest.iter().map(|s| s.span)),
});
};
let alias_expansion =
String::from_utf8_lossy(engine_state.get_span_contents(alias.wrapped_call.span));
let description = alias.description();
let extra_desc = alias.extra_description();
let help = get_full_help(alias, engine_state, stack);
// TODO: merge this into documentation.rs at some point
const G: &str = "\x1b[32m"; // green
const C: &str = "\x1b[36m"; // cyan
const RESET: &str = "\x1b[0m"; // reset
let mut long_desc = String::new();
long_desc.push_str(description);
long_desc.push_str("\n\n");
if !extra_desc.is_empty() {
long_desc.push_str(extra_desc);
long_desc.push_str("\n\n");
}
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
long_desc.push_str("\n\n");
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
let config = stack.get_config(engine_state);
if !config.use_ansi_coloring.get(engine_state) {
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
}
Ok(Value::string(long_desc, call.head).into_pipeline_data())
Ok(Value::string(help, call.head).into_pipeline_data())
}
}