diff --git a/Cargo.lock b/Cargo.lock index 2cc13277c4..caff42c25d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1230,6 +1230,16 @@ dependencies = [ "regex", ] +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -2213,11 +2223,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -2651,7 +2661,7 @@ dependencies = [ "atty", "chrono", "crossterm 0.24.0", - "fancy-regex", + "fancy-regex 0.10.0", "fuzzy-matcher", "is_executable", "log", @@ -2709,7 +2719,7 @@ dependencies = [ "dtparse", "eml-parser", "encoding_rs", - "fancy-regex", + "fancy-regex 0.10.0", "filesize", "filetime", "fs_extra", @@ -2892,7 +2902,7 @@ dependencies = [ "byte-unit", "chrono", "chrono-humanize", - "fancy-regex", + "fancy-regex 0.11.0", "indexmap", "lru", "miette", @@ -4143,8 +4153,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d91d2a38dbdaf0c748a96055c14aa4746be9add5f4aea4b1460e6e6492f770" +source = "git+https://github.com/nushell/reedline.git?branch=main#3d83306b2dadd17d065d9d25ea62a36c3a0a76b2" dependencies = [ "chrono", "crossterm 0.24.0", diff --git a/Cargo.toml b/Cargo.toml index 95304fe34c..db15fc1a9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,7 +140,7 @@ path = "src/main.rs" # To use a development version of a dependency please use a global override here # changing versions in each sub-crate of the workspace is tedious [patch.crates-io] -# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" } # Criterion benchmarking setup # Run all benchmarks with `cargo bench` diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 17b6c65322..842e0a5fbb 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -105,7 +105,7 @@ impl Prompt for NushellPrompt { if let Some(prompt_string) = &self.left_prompt_string { prompt_string.replace('\n', "\r\n").into() } else { - let default = DefaultPrompt::new(); + let default = DefaultPrompt::default(); default .render_prompt_left() .to_string() @@ -118,7 +118,7 @@ impl Prompt for NushellPrompt { if let Some(prompt_string) = &self.right_prompt_string { prompt_string.replace('\n', "\r\n").into() } else { - let default = DefaultPrompt::new(); + let default = DefaultPrompt::default(); default .render_prompt_right() .to_string() diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 9d5acda290..f5145cb0ee 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -5,6 +5,7 @@ use crate::{ util::{eval_source, get_guaranteed_cwd, report_error, report_error_new}, NuHighlighter, NuValidator, NushellPrompt, }; +use crossterm::cursor::CursorShape; use log::{info, trace, warn}; use miette::{IntoDiagnostic, Result}; use nu_color_config::StyleComputer; @@ -12,11 +13,12 @@ use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return}; use nu_parser::{lex, parse, trim_quotes_str}; use nu_protocol::{ ast::PathMember, + config::NuCursorShape, engine::{EngineState, ReplOperation, Stack, StateWorkingSet}, format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Spanned, Type, Value, VarId, }; -use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi}; +use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi}; use std::{ io::{self, Write}, sync::atomic::Ordering, @@ -174,6 +176,18 @@ pub fn evaluate_repl( info!("update reedline {}:{}:{}", file!(), line!(), column!()); let engine_reference = std::sync::Arc::new(engine_state.clone()); + + // Find the configured cursor shapes for each mode + let cursor_config = CursorConfig { + vi_insert: Some(map_nucursorshape_to_cursorshape( + config.cursor_shape_vi_insert, + )), + vi_normal: Some(map_nucursorshape_to_cursorshape( + config.cursor_shape_vi_normal, + )), + emacs: Some(map_nucursorshape_to_cursorshape(config.cursor_shape_emacs)), + }; + line_editor = line_editor .with_highlighter(Box::new(NuHighlighter { engine_state: engine_reference.clone(), @@ -188,7 +202,8 @@ pub fn evaluate_repl( ))) .with_quick_completions(config.quick_completions) .with_partial_completions(config.partial_completions) - .with_ansi_colors(config.use_ansi_coloring); + .with_ansi_colors(config.use_ansi_coloring) + .with_cursor_config(cursor_config); let style_computer = StyleComputer::from_config(engine_state, stack); @@ -535,6 +550,14 @@ pub fn evaluate_repl( Ok(()) } +fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> CursorShape { + match shape { + NuCursorShape::Block => CursorShape::Block, + NuCursorShape::UnderScore => CursorShape::UnderScore, + NuCursorShape::Line => CursorShape::Line, + } +} + fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String { let age = match eval_string_with_input( engine_state, diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index d4c8bd3b09..c259c07f33 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -10,18 +10,21 @@ version = "0.74.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-utils = { path = "../nu-utils", version = "0.74.1" } -nu-json = { path = "../nu-json", version = "0.74.1" } +nu-utils = { path = "../nu-utils", version = "0.74.1" } +nu-json = { path = "../nu-json", version = "0.74.1" } byte-unit = "4.0.9" -chrono = { version="0.4.23", features= ["serde", "std"], default-features = false } +chrono = { version = "0.4.23", features = [ + "serde", + "std", +], default-features = false } chrono-humanize = "0.2.1" -fancy-regex = "0.10.0" -indexmap = { version="1.7" } -lru = "0.8.1" +fancy-regex = "0.11.0" +indexmap = { version = "1.7" } +lru = "0.9.0" miette = { version = "5.1.0", features = ["fancy-no-backtrace"] } num-format = "0.4.3" -serde = {version = "1.0.143", default-features = false } +serde = { version = "1.0.143", default-features = false } serde_json = { version = "1.0", optional = true } strum = "0.24" strum_macros = "0.24" @@ -34,4 +37,4 @@ plugin = ["serde_json"] [dev-dependencies] serde_json = "1.0" -nu-test-support = { path = "../nu-test-support", version = "0.74.1" } +nu-test-support = { path = "../nu-test-support", version = "0.74.1" } diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index c3add09bc4..361389363d 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -52,6 +52,14 @@ impl Default for Hooks { } } +/// Definition of a Nushell CursorShape (to be mapped to crossterm::cursor::CursorShape) +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum NuCursorShape { + UnderScore, + Line, + Block, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Config { pub external_completer: Option, @@ -88,6 +96,9 @@ pub struct Config { pub show_clickable_links_in_ls: bool, pub render_right_prompt_on_last_line: bool, pub explore: HashMap, + pub cursor_shape_vi_insert: NuCursorShape, + pub cursor_shape_vi_normal: NuCursorShape, + pub cursor_shape_emacs: NuCursorShape, } impl Default for Config { @@ -127,6 +138,9 @@ impl Default for Config { show_clickable_links_in_ls: true, render_right_prompt_on_last_line: false, explore: HashMap::new(), + cursor_shape_vi_insert: NuCursorShape::Block, + cursor_shape_vi_normal: NuCursorShape::UnderScore, + cursor_shape_emacs: NuCursorShape::Line, } } } @@ -628,6 +642,156 @@ impl Value { ); } } + "cursor_shape" => { + macro_rules! reconstruct_cursor_shape { + ($name:expr, $span:expr) => { + Value::string( + match $name { + NuCursorShape::Line => "line", + NuCursorShape::Block => "block", + NuCursorShape::UnderScore => "underscore", + }, + *$span, + ) + }; + } + if let Value::Record { cols, vals, span } = &mut vals[index] { + for index in (0..cols.len()).rev() { + let value = &vals[index]; + let key2 = cols[index].as_str(); + match key2 { + "vi_insert" => { + if let Ok(v) = value.as_string() { + let val_str = v.to_lowercase(); + match val_str.as_ref() { + "line" => { + config.cursor_shape_vi_insert = + NuCursorShape::Line; + } + "block" => { + config.cursor_shape_vi_insert = + NuCursorShape::Block; + } + "underscore" => { + config.cursor_shape_vi_insert = + NuCursorShape::UnderScore; + } + _ => { + invalid!(Some(*span), + "unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'line', 'block', or 'underscore'" + ); + // Reconstruct + vals[index] = reconstruct_cursor_shape!( + config.cursor_shape_vi_insert, + span + ); + } + }; + } else { + invalid!(Some(*span), "should be a string"); + // Reconstruct + vals[index] = reconstruct_cursor_shape!( + config.cursor_shape_vi_insert, + span + ); + } + } + "vi_normal" => { + if let Ok(v) = value.as_string() { + let val_str = v.to_lowercase(); + match val_str.as_ref() { + "line" => { + config.cursor_shape_vi_normal = + NuCursorShape::Line; + } + "block" => { + config.cursor_shape_vi_normal = + NuCursorShape::Block; + } + "underscore" => { + config.cursor_shape_vi_normal = + NuCursorShape::UnderScore; + } + _ => { + invalid!(Some(*span), + "unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'line', 'block', or 'underscore'" + ); + // Reconstruct + vals[index] = reconstruct_cursor_shape!( + config.cursor_shape_vi_normal, + span + ); + } + }; + } else { + invalid!(Some(*span), "should be a string"); + // Reconstruct + vals[index] = reconstruct_cursor_shape!( + config.cursor_shape_vi_normal, + span + ); + } + } + "emacs" => { + if let Ok(v) = value.as_string() { + let val_str = v.to_lowercase(); + match val_str.as_ref() { + "line" => { + config.cursor_shape_emacs = NuCursorShape::Line; + } + "block" => { + config.cursor_shape_emacs = + NuCursorShape::Block; + } + "underscore" => { + config.cursor_shape_emacs = + NuCursorShape::UnderScore; + } + _ => { + invalid!(Some(*span), + "unrecognized $env.config.{key}.{key2} '{val_str}'; expected either 'line', 'block', or 'underscore'" + ); + // Reconstruct + vals[index] = reconstruct_cursor_shape!( + config.cursor_shape_emacs, + span + ); + } + }; + } else { + invalid!(Some(*span), "should be a string"); + // Reconstruct + vals[index] = reconstruct_cursor_shape!( + config.cursor_shape_emacs, + span + ); + } + } + x => { + invalid_key!( + cols, + vals, + index, + value.span().ok(), + "$env.config.{key}.{x} is an unknown config setting" + ); + } + } + } + } else { + invalid!(vals[index].span().ok(), "should be a record"); + // Reconstruct + vals[index] = Value::record( + vec!["vi_insert".into(), "vi_normal".into(), "emacs".into()], + vec![ + reconstruct_cursor_shape!(config.cursor_shape_vi_insert, span), + reconstruct_cursor_shape!(config.cursor_shape_vi_normal, span), + reconstruct_cursor_shape!(config.cursor_shape_emacs, span), + ], + *span, + ); + } + } "table" => { macro_rules! reconstruct_index_mode { ($span:expr) => { @@ -1148,6 +1312,67 @@ impl Value { invalid!(Some(*span), "should be a string"); } } + "cursor_shape_vi_insert" => { + legacy_options_used = true; + if let Ok(b) = value.as_string() { + let val_str = b.to_lowercase(); + config.cursor_shape_vi_insert = match val_str.as_ref() { + "block" => NuCursorShape::Block, + "underline" => NuCursorShape::UnderScore, + "line" => NuCursorShape::Line, + _ => { + invalid!( + Some(*span), + "unrecognized $env.config.{key} '{val_str}'" + ); + NuCursorShape::Line + } + }; + } else { + invalid!(Some(*span), "should be a string"); + } + } + "cursor_shape_vi_normal" => { + legacy_options_used = true; + if let Ok(b) = value.as_string() { + let val_str = b.to_lowercase(); + config.cursor_shape_vi_normal = match val_str.as_ref() { + "block" => NuCursorShape::Block, + "underline" => NuCursorShape::UnderScore, + "line" => NuCursorShape::Line, + _ => { + invalid!( + Some(*span), + "unrecognized $env.config.{key} '{val_str}'" + ); + NuCursorShape::Line + } + }; + } else { + invalid!(Some(*span), "should be a string"); + } + } + "cursor_shape_emacs" => { + legacy_options_used = true; + if let Ok(b) = value.as_string() { + let val_str = b.to_lowercase(); + config.cursor_shape_emacs = match val_str.as_ref() { + "block" => NuCursorShape::Block, + "underline" => NuCursorShape::UnderScore, + "line" => NuCursorShape::Line, + _ => { + invalid!( + Some(*span), + "unrecognized $env.config.{key} '{val_str}'" + ); + NuCursorShape::Line + } + }; + } else { + invalid!(Some(*span), "should be a string"); + } + } + // End legacy options x => { invalid_key!( diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index b4490575c2..fff11f5f40 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,6 +1,6 @@ pub mod ast; mod cli_error; -mod config; +pub mod config; pub mod engine; mod example; mod exportable; diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index c3e4f9f770..34e7d0c367 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -395,6 +395,11 @@ let-env config = { metric: true # true => KB, MB, GB (ISO standard), false => KiB, MiB, GiB (Windows standard) format: "auto" # b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, zb, zib, auto } + cursor_shape: { + emacs: line # block, underscore, line (line is the default) + vi_insert: block # block, underscore, line (block is the default) + vi_normal: underscore # block, underscore, line (underscore is the default) + } color_config: $dark_theme # if you want a light theme, replace `$dark_theme` to `$light_theme` use_grid_icons: true footer_mode: "25" # always, never, number_of_rows, auto