add shape ExternalResolved to show found externals via syntax highlighting in the repl (#11135)

# Description

This PR enables a new feature that shows which externals are found in
your path via the syntax highlighter as you type.

![external_resolved](https://github.com/nushell/nushell/assets/343840/e5fa91f0-6fac-485c-8afc-5711fc0ed9bc)

This idea could use some improvement where it caches the items in your
path and on some trigger, expires that cache and creates a new on. Right
now, all it does is call the `which` crate on every character you type.
This could be problematic if you have hundreds of paths in your PATH or
if some of your paths in your Path point to extraordinarily slow file
systems. WSL pointing to Windows comes to mind. Either way, I've thrown
it up here for people to try and provide feedback. I think the novelty
of showing what is valid and what isn't is pretty cool. I believe
fish-shell also does this, IIRC.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2023-11-25 09:42:05 -06:00 committed by GitHub
parent 85c6047b71
commit d77f1753c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 33 additions and 2 deletions

1
Cargo.lock generated
View File

@ -2723,6 +2723,7 @@ dependencies = [
"sysinfo", "sysinfo",
"unicode-segmentation", "unicode-segmentation",
"uuid", "uuid",
"which 5.0.0",
] ]
[[package]] [[package]]

View File

@ -40,6 +40,7 @@ pathdiff = "0.2"
sysinfo = "0.29" sysinfo = "0.29"
unicode-segmentation = "1.10" unicode-segmentation = "1.10"
uuid = { version = "1.6.0", features = ["v4"] } uuid = { version = "1.6.0", features = ["v4"] }
which = "5.0.0"
[features] [features]
plugin = [] plugin = []

View File

@ -17,10 +17,27 @@ impl Highlighter for NuHighlighter {
fn highlight(&self, line: &str, _cursor: usize) -> StyledText { fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
trace!("highlighting: {}", line); trace!("highlighting: {}", line);
let highlight_resolved_externals =
self.engine_state.get_config().highlight_resolved_externals;
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
let block = parse(&mut working_set, None, line.as_bytes(), false); let block = parse(&mut working_set, None, line.as_bytes(), false);
let (shapes, global_span_offset) = { let (shapes, global_span_offset) = {
let shapes = flatten_block(&working_set, &block); let mut shapes = flatten_block(&working_set, &block);
// Highlighting externals has a config point because of concerns that using which to resolve
// externals may slow down things too much.
if highlight_resolved_externals {
for (span, shape) in shapes.iter_mut() {
if *shape == FlatShape::External {
let str_contents =
working_set.get_span_contents(Span::new(span.start, span.end));
let str_word = String::from_utf8_lossy(str_contents).to_string();
if which::which(str_word).ok().is_some() {
*shape = FlatShape::ExternalResolved;
}
}
}
}
(shapes, self.engine_state.next_span_start()) (shapes, self.engine_state.next_span_start())
}; };
@ -91,6 +108,7 @@ impl Highlighter for NuHighlighter {
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token), FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
FlatShape::External => add_colored_token(&shape.1, next_token), FlatShape::External => add_colored_token(&shape.1, next_token),
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token), FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
FlatShape::Keyword => add_colored_token(&shape.1, next_token), FlatShape::Keyword => add_colored_token(&shape.1, next_token),
FlatShape::Literal => add_colored_token(&shape.1, next_token), FlatShape::Literal => add_colored_token(&shape.1, next_token),
FlatShape::Operator => add_colored_token(&shape.1, next_token), FlatShape::Operator => add_colored_token(&shape.1, next_token),

View File

@ -15,6 +15,7 @@ pub fn default_shape_color(shape: String) -> Style {
"shape_directory" => Style::new().fg(Color::Cyan), "shape_directory" => Style::new().fg(Color::Cyan),
"shape_external" => Style::new().fg(Color::Cyan), "shape_external" => Style::new().fg(Color::Cyan),
"shape_externalarg" => Style::new().fg(Color::Green).bold(), "shape_externalarg" => Style::new().fg(Color::Green).bold(),
"shape_external_resolved" => Style::new().fg(Color::LightYellow).bold(),
"shape_filepath" => Style::new().fg(Color::Cyan), "shape_filepath" => Style::new().fg(Color::Cyan),
"shape_flag" => Style::new().fg(Color::Blue).bold(), "shape_flag" => Style::new().fg(Color::Blue).bold(),
"shape_float" => Style::new().fg(Color::Purple).bold(), "shape_float" => Style::new().fg(Color::Purple).bold(),

View File

@ -18,6 +18,7 @@ pub enum FlatShape {
Directory, Directory,
External, External,
ExternalArg, ExternalArg,
ExternalResolved,
Filepath, Filepath,
Flag, Flag,
Float, Float,
@ -57,6 +58,7 @@ impl Display for FlatShape {
FlatShape::Directory => write!(f, "shape_directory"), FlatShape::Directory => write!(f, "shape_directory"),
FlatShape::External => write!(f, "shape_external"), FlatShape::External => write!(f, "shape_external"),
FlatShape::ExternalArg => write!(f, "shape_externalarg"), FlatShape::ExternalArg => write!(f, "shape_externalarg"),
FlatShape::ExternalResolved => write!(f, "shape_external_resolved"),
FlatShape::Filepath => write!(f, "shape_filepath"), FlatShape::Filepath => write!(f, "shape_filepath"),
FlatShape::Flag => write!(f, "shape_flag"), FlatShape::Flag => write!(f, "shape_flag"),
FlatShape::Float => write!(f, "shape_float"), FlatShape::Float => write!(f, "shape_float"),

View File

@ -72,6 +72,7 @@ pub struct Config {
pub datetime_table_format: Option<String>, pub datetime_table_format: Option<String>,
pub error_style: ErrorStyle, pub error_style: ErrorStyle,
pub use_kitty_protocol: bool, pub use_kitty_protocol: bool,
pub highlight_resolved_externals: bool,
} }
impl Default for Config { impl Default for Config {
@ -137,6 +138,7 @@ impl Default for Config {
error_style: ErrorStyle::Fancy, error_style: ErrorStyle::Fancy,
use_kitty_protocol: false, use_kitty_protocol: false,
highlight_resolved_externals: false,
} }
} }
} }
@ -622,6 +624,9 @@ impl Value {
"use_kitty_protocol" => { "use_kitty_protocol" => {
process_bool_config(value, &mut errors, &mut config.use_kitty_protocol); process_bool_config(value, &mut errors, &mut config.use_kitty_protocol);
} }
"highlight_resolved_externals" => {
process_bool_config(value, &mut errors, &mut config.highlight_resolved_externals);
}
// Menus // Menus
"menus" => match create_menus(value) { "menus" => match create_menus(value) {
Ok(map) => config.menus = map, Ok(map) => config.menus = map,

View File

@ -42,6 +42,7 @@ let dark_theme = {
shape_directory: cyan shape_directory: cyan
shape_external: cyan shape_external: cyan
shape_externalarg: green_bold shape_externalarg: green_bold
shape_external_resolved: light_yellow_bold
shape_filepath: cyan shape_filepath: cyan
shape_flag: blue_bold shape_flag: blue_bold
shape_float: purple_bold shape_float: purple_bold
@ -106,6 +107,7 @@ let light_theme = {
shape_directory: cyan shape_directory: cyan
shape_external: cyan shape_external: cyan
shape_externalarg: green_bold shape_externalarg: green_bold
shape_external_resolved: light_purple_bold
shape_filepath: cyan shape_filepath: cyan
shape_flag: blue_bold shape_flag: blue_bold
shape_float: purple_bold shape_float: purple_bold
@ -233,7 +235,8 @@ $env.config = {
edit_mode: emacs # emacs, vi edit_mode: emacs # emacs, vi
shell_integration: false # enables terminal shell integration. Off by default, as some terminals have issues with this. shell_integration: false # enables terminal shell integration. Off by default, as some terminals have issues with this.
render_right_prompt_on_last_line: false # true or false to enable or disable right prompt to be rendered on last line of the prompt. render_right_prompt_on_last_line: false # true or false to enable or disable right prompt to be rendered on last line of the prompt.
use_kitty_protocol: false # enables keyboard enhancement protocol implemented by kitty console, only if your terminal support this use_kitty_protocol: false # enables keyboard enhancement protocol implemented by kitty console, only if your terminal support this.
highlight_resolved_externals: false # true enables highlighting of external commands in the repl resolved by which.
hooks: { hooks: {
pre_prompt: [{ null }] # run before the prompt is shown pre_prompt: [{ null }] # run before the prompt is shown