mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 08:23:24 +01:00
Generic menus (#5085)
* updated to reedline generic menus * help menu with examples * generic menus in the engine * description menu template * list of menus in config * default value for menu * menu from block * generic menus examples * change to reedline git path * cargo fmt * menu name typo * remove commas from default file * added error message
This commit is contained in:
parent
a86e6ce89b
commit
608b6f3634
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3428,7 +3428,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reedline"
|
name = "reedline"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
source = "git+https://github.com/nushell/reedline?branch=main#accce4af7f50ea143ed818dd5fe58484e107e922"
|
source = "git+https://github.com/nushell/reedline?branch=main#698190c534e8632f76561cbe8b45a5de74a6e96f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
@ -95,6 +95,7 @@ impl NuCompleter {
|
|||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -109,6 +110,7 @@ impl NuCompleter {
|
|||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -123,6 +125,7 @@ impl NuCompleter {
|
|||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -152,6 +155,7 @@ impl NuCompleter {
|
|||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||||
description: x.1,
|
description: x.1,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -165,6 +169,7 @@ impl NuCompleter {
|
|||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: String::from_utf8_lossy(&x).to_string(),
|
value: String::from_utf8_lossy(&x).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -182,6 +187,7 @@ impl NuCompleter {
|
|||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x,
|
value: x,
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -193,6 +199,7 @@ impl NuCompleter {
|
|||||||
results.push(Suggestion {
|
results.push(Suggestion {
|
||||||
value: format!("^{}", external.value),
|
value: format!("^{}", external.value),
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: external.span,
|
span: external.span,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -266,6 +273,7 @@ impl NuCompleter {
|
|||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: new_span.start - offset,
|
start: new_span.start - offset,
|
||||||
end: new_span.end - offset,
|
end: new_span.end - offset,
|
||||||
@ -285,6 +293,7 @@ impl NuCompleter {
|
|||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: new_span.start - offset,
|
start: new_span.start - offset,
|
||||||
end: new_span.end - offset,
|
end: new_span.end - offset,
|
||||||
@ -341,6 +350,7 @@ impl NuCompleter {
|
|||||||
Ok(s) => Some(Suggestion {
|
Ok(s) => Some(Suggestion {
|
||||||
value: s,
|
value: s,
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: new_span.start - offset,
|
start: new_span.start - offset,
|
||||||
end: new_span.end - offset,
|
end: new_span.end - offset,
|
||||||
@ -453,6 +463,7 @@ impl NuCompleter {
|
|||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
end: x.0.end - offset,
|
end: x.0.end - offset,
|
||||||
@ -569,6 +580,7 @@ impl NuCompleter {
|
|||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
end: x.0.end - offset,
|
end: x.0.end - offset,
|
||||||
|
@ -3,8 +3,7 @@ mod completions;
|
|||||||
mod config_files;
|
mod config_files;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod eval_file;
|
mod eval_file;
|
||||||
mod help_completions;
|
mod menus;
|
||||||
mod help_menu;
|
|
||||||
mod nu_highlight;
|
mod nu_highlight;
|
||||||
mod print;
|
mod print;
|
||||||
mod prompt;
|
mod prompt;
|
||||||
@ -20,8 +19,7 @@ pub use completions::NuCompleter;
|
|||||||
pub use config_files::eval_config_contents;
|
pub use config_files::eval_config_contents;
|
||||||
pub use errors::CliError;
|
pub use errors::CliError;
|
||||||
pub use eval_file::evaluate_file;
|
pub use eval_file::evaluate_file;
|
||||||
pub use help_completions::NuHelpCompleter;
|
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
||||||
pub use help_menu::NuHelpMenu;
|
|
||||||
pub use nu_highlight::NuHighlight;
|
pub use nu_highlight::NuHighlight;
|
||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use {
|
use {
|
||||||
crate::help_completions::{EXAMPLE_MARKER, EXAMPLE_NEW_LINE},
|
|
||||||
nu_ansi_term::{ansi::RESET, Style},
|
nu_ansi_term::{ansi::RESET, Style},
|
||||||
reedline::{
|
reedline::{
|
||||||
menu_functions::string_difference, Completer, History, LineBuffer, Menu, MenuEvent,
|
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
||||||
MenuTextStyle, Painter, Suggestion,
|
Painter, Suggestion,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,7 +47,10 @@ struct WorkingDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Completion menu definition
|
/// Completion menu definition
|
||||||
pub struct NuHelpMenu {
|
pub struct DescriptionMenu {
|
||||||
|
/// Menu name
|
||||||
|
name: String,
|
||||||
|
/// Menu status
|
||||||
active: bool,
|
active: bool,
|
||||||
/// Menu coloring
|
/// Menu coloring
|
||||||
color: MenuTextStyle,
|
color: MenuTextStyle,
|
||||||
@ -80,11 +82,15 @@ pub struct NuHelpMenu {
|
|||||||
show_examples: bool,
|
show_examples: bool,
|
||||||
/// Skipped description rows
|
/// Skipped description rows
|
||||||
skipped_rows: usize,
|
skipped_rows: usize,
|
||||||
|
/// Calls the completer using only the line buffer difference difference
|
||||||
|
/// after the menu was activated
|
||||||
|
only_buffer_difference: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NuHelpMenu {
|
impl Default for DescriptionMenu {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
name: "description_menu".to_string(),
|
||||||
active: false,
|
active: false,
|
||||||
color: MenuTextStyle::default(),
|
color: MenuTextStyle::default(),
|
||||||
default_details: DefaultMenuDetails::default(),
|
default_details: DefaultMenuDetails::default(),
|
||||||
@ -100,11 +106,19 @@ impl Default for NuHelpMenu {
|
|||||||
example_index: None,
|
example_index: None,
|
||||||
show_examples: true,
|
show_examples: true,
|
||||||
skipped_rows: 0,
|
skipped_rows: 0,
|
||||||
|
only_buffer_difference: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NuHelpMenu {
|
// Menu configuration
|
||||||
|
impl DescriptionMenu {
|
||||||
|
/// Menu builder with new name
|
||||||
|
pub fn with_name(mut self, name: &str) -> Self {
|
||||||
|
self.name = name.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Menu builder with new value for text style
|
/// Menu builder with new value for text style
|
||||||
pub fn with_text_style(mut self, text_style: Style) -> Self {
|
pub fn with_text_style(mut self, text_style: Style) -> Self {
|
||||||
self.color.text_style = text_style;
|
self.color.text_style = text_style;
|
||||||
@ -159,6 +173,15 @@ impl NuHelpMenu {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Menu builder with new only buffer difference
|
||||||
|
pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
|
||||||
|
self.only_buffer_difference = only_buffer_difference;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu functionality
|
||||||
|
impl DescriptionMenu {
|
||||||
/// Move menu cursor to the next element
|
/// Move menu cursor to the next element
|
||||||
fn move_next(&mut self) {
|
fn move_next(&mut self) {
|
||||||
let mut new_col = self.col_pos + 1;
|
let mut new_col = self.col_pos + 1;
|
||||||
@ -279,19 +302,11 @@ impl NuHelpMenu {
|
|||||||
|
|
||||||
/// Update list of examples from the actual value
|
/// Update list of examples from the actual value
|
||||||
fn update_examples(&mut self) {
|
fn update_examples(&mut self) {
|
||||||
let examples = self
|
self.examples = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.extra)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default();
|
||||||
.lines()
|
|
||||||
.filter(|line| line.starts_with(EXAMPLE_MARKER))
|
|
||||||
.map(|line| {
|
|
||||||
line.replace(EXAMPLE_MARKER, "")
|
|
||||||
.replace(EXAMPLE_NEW_LINE, "\r\n")
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
self.examples = examples;
|
|
||||||
self.example_index = None;
|
self.example_index = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +374,6 @@ impl NuHelpMenu {
|
|||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_else(|| "".to_string())
|
||||||
.lines()
|
.lines()
|
||||||
.filter(|line| !line.starts_with(EXAMPLE_MARKER))
|
|
||||||
.skip(self.skipped_rows)
|
.skip(self.skipped_rows)
|
||||||
.take(self.working_details.description_rows)
|
.take(self.working_details.description_rows)
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
@ -420,10 +434,10 @@ impl NuHelpMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Menu for NuHelpMenu {
|
impl Menu for DescriptionMenu {
|
||||||
/// Menu name
|
/// Menu name
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"help_menu"
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Menu indicator
|
/// Menu indicator
|
||||||
@ -436,17 +450,16 @@ impl Menu for NuHelpMenu {
|
|||||||
self.active
|
self.active
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The help menu stays active even with one record
|
/// The menu stays active even with one record
|
||||||
fn can_quick_complete(&self) -> bool {
|
fn can_quick_complete(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The help menu does not need to partially complete
|
/// The menu does not need to partially complete
|
||||||
fn can_partially_complete(
|
fn can_partially_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
_values_updated: bool,
|
_values_updated: bool,
|
||||||
_line_buffer: &mut LineBuffer,
|
_line_buffer: &mut LineBuffer,
|
||||||
_history: &dyn History,
|
|
||||||
_completer: &dyn Completer,
|
_completer: &dyn Completer,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
@ -468,29 +481,20 @@ impl Menu for NuHelpMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates menu values
|
/// Updates menu values
|
||||||
fn update_values(
|
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &dyn Completer) {
|
||||||
&mut self,
|
if self.only_buffer_difference {
|
||||||
line_buffer: &mut LineBuffer,
|
if let Some(old_string) = &self.input {
|
||||||
_history: &dyn History,
|
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
||||||
completer: &dyn Completer,
|
if !input.is_empty() {
|
||||||
) {
|
self.reset_position();
|
||||||
if let Some(old_string) = &self.input {
|
self.values = completer.complete(input, start);
|
||||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
}
|
||||||
if !input.is_empty() {
|
|
||||||
self.reset_position();
|
|
||||||
self.values = completer
|
|
||||||
.complete(input, line_buffer.insertion_point())
|
|
||||||
.into_iter()
|
|
||||||
.map(|suggestion| Suggestion {
|
|
||||||
value: suggestion.value,
|
|
||||||
description: suggestion.description,
|
|
||||||
span: reedline::Span {
|
|
||||||
start,
|
|
||||||
end: start + input.len(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
||||||
|
self.values =
|
||||||
|
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
||||||
|
self.reset_position();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,7 +503,6 @@ impl Menu for NuHelpMenu {
|
|||||||
fn update_working_details(
|
fn update_working_details(
|
||||||
&mut self,
|
&mut self,
|
||||||
line_buffer: &mut LineBuffer,
|
line_buffer: &mut LineBuffer,
|
||||||
history: &dyn History,
|
|
||||||
completer: &dyn Completer,
|
completer: &dyn Completer,
|
||||||
painter: &Painter,
|
painter: &Painter,
|
||||||
) {
|
) {
|
||||||
@ -558,12 +561,12 @@ impl Menu for NuHelpMenu {
|
|||||||
MenuEvent::Activate(_) => {
|
MenuEvent::Activate(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.input = Some(line_buffer.get_buffer().to_string());
|
self.input = Some(line_buffer.get_buffer().to_string());
|
||||||
self.update_values(line_buffer, history, completer);
|
self.update_values(line_buffer, completer);
|
||||||
}
|
}
|
||||||
MenuEvent::Deactivate => self.active = false,
|
MenuEvent::Deactivate => self.active = false,
|
||||||
MenuEvent::Edit(_) => {
|
MenuEvent::Edit(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.update_values(line_buffer, history, completer);
|
self.update_values(line_buffer, completer);
|
||||||
self.update_examples()
|
self.update_examples()
|
||||||
}
|
}
|
||||||
MenuEvent::NextElement => {
|
MenuEvent::NextElement => {
|
||||||
@ -607,7 +610,6 @@ impl Menu for NuHelpMenu {
|
|||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_else(|| "".to_string())
|
||||||
.lines()
|
.lines()
|
||||||
.filter(|line| !line.starts_with(EXAMPLE_MARKER))
|
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let allowed_skips =
|
let allowed_skips =
|
||||||
@ -627,20 +629,24 @@ impl Menu for NuHelpMenu {
|
|||||||
/// The buffer gets replaced in the Span location
|
/// The buffer gets replaced in the Span location
|
||||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
||||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||||
|
let start = span.start.min(line_buffer.len());
|
||||||
|
let end = span.end.min(line_buffer.len());
|
||||||
|
|
||||||
let string_len = if let Some(example_index) = self.example_index {
|
let string_len = if let Some(example_index) = self.example_index {
|
||||||
let example = self
|
let example = self
|
||||||
.examples
|
.examples
|
||||||
.get(example_index)
|
.get(example_index)
|
||||||
.expect("the example index is always checked");
|
.expect("the example index is always checked");
|
||||||
line_buffer.replace(span.start..span.end, example);
|
|
||||||
|
line_buffer.replace(start..end, example);
|
||||||
example.len()
|
example.len()
|
||||||
} else {
|
} else {
|
||||||
line_buffer.replace(span.start..span.end, &value);
|
line_buffer.replace(start..end, &value);
|
||||||
value.len()
|
value.len()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut offset = line_buffer.insertion_point();
|
let mut offset = line_buffer.insertion_point();
|
||||||
offset += string_len.saturating_sub(span.end - span.start);
|
offset += string_len.saturating_sub(end.saturating_sub(start));
|
||||||
line_buffer.set_insertion_point(offset);
|
line_buffer.set_insertion_point(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,20 +2,15 @@ use nu_engine::documentation::get_flags_section;
|
|||||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
|
|
||||||
pub const EXAMPLE_MARKER: &str = ">>>>>>";
|
pub struct NuHelpCompleter(EngineState);
|
||||||
pub const EXAMPLE_NEW_LINE: &str = "%%%%%%";
|
|
||||||
|
|
||||||
pub struct NuHelpCompleter {
|
|
||||||
engine_state: EngineState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NuHelpCompleter {
|
impl NuHelpCompleter {
|
||||||
pub fn new(engine_state: EngineState) -> Self {
|
pub fn new(engine_state: EngineState) -> Self {
|
||||||
Self { engine_state }
|
Self(engine_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_helper(&self, line: &str, _pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let full_commands = self.engine_state.get_signatures_with_examples(false);
|
let full_commands = self.0.get_signatures_with_examples(false);
|
||||||
|
|
||||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||||
let mut commands = full_commands
|
let mut commands = full_commands
|
||||||
@ -83,20 +78,18 @@ impl NuHelpCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for example in examples {
|
let extra: Vec<String> = examples
|
||||||
long_desc.push_str(&format!(
|
.iter()
|
||||||
"{}{}\r\n",
|
.map(|example| example.example.to_string())
|
||||||
EXAMPLE_MARKER,
|
.collect();
|
||||||
example.example.replace('\n', EXAMPLE_NEW_LINE)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Suggestion {
|
Suggestion {
|
||||||
value: sig.name.clone(),
|
value: sig.name.clone(),
|
||||||
description: Some(long_desc),
|
description: Some(long_desc),
|
||||||
|
extra: Some(extra),
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: 0,
|
start: pos,
|
||||||
end: sig.name.len(),
|
end: pos + line.len(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
167
crates/nu-cli/src/menus/menu_completions.rs
Normal file
167
crates/nu-cli/src/menus/menu_completions.rs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
use nu_engine::eval_block;
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{EngineState, Stack},
|
||||||
|
IntoPipelineData, Span, Value,
|
||||||
|
};
|
||||||
|
use reedline::{menu_functions::parse_selection_char, Completer, Suggestion};
|
||||||
|
|
||||||
|
const SELECTION_CHAR: char = '!';
|
||||||
|
|
||||||
|
pub struct NuMenuCompleter {
|
||||||
|
block_id: usize,
|
||||||
|
span: Span,
|
||||||
|
stack: Stack,
|
||||||
|
engine_state: EngineState,
|
||||||
|
only_buffer_difference: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NuMenuCompleter {
|
||||||
|
pub fn new(
|
||||||
|
block_id: usize,
|
||||||
|
span: Span,
|
||||||
|
stack: Stack,
|
||||||
|
engine_state: EngineState,
|
||||||
|
only_buffer_difference: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
block_id,
|
||||||
|
span,
|
||||||
|
stack,
|
||||||
|
engine_state,
|
||||||
|
only_buffer_difference,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for NuMenuCompleter {
|
||||||
|
fn complete(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
|
let parsed = parse_selection_char(line, SELECTION_CHAR);
|
||||||
|
|
||||||
|
let block = self.engine_state.get_block(self.block_id);
|
||||||
|
let mut stack = self.stack.clone();
|
||||||
|
|
||||||
|
if let Some(buffer) = block.signature.get_positional(0) {
|
||||||
|
if let Some(buffer_id) = &buffer.var_id {
|
||||||
|
let line_buffer = Value::String {
|
||||||
|
val: parsed.remainder.to_string(),
|
||||||
|
span: self.span,
|
||||||
|
};
|
||||||
|
stack.add_var(*buffer_id, line_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(position) = block.signature.get_positional(1) {
|
||||||
|
if let Some(position_id) = &position.var_id {
|
||||||
|
let line_buffer = Value::Int {
|
||||||
|
val: pos as i64,
|
||||||
|
span: self.span,
|
||||||
|
};
|
||||||
|
stack.add_var(*position_id, line_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = Value::nothing(self.span).into_pipeline_data();
|
||||||
|
let res = eval_block(&self.engine_state, &mut stack, block, input, false, false);
|
||||||
|
|
||||||
|
if let Ok(values) = res {
|
||||||
|
let values = values.into_value(self.span);
|
||||||
|
convert_to_suggestions(values, line, pos, self.only_buffer_difference)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_suggestions(
|
||||||
|
value: Value,
|
||||||
|
line: &str,
|
||||||
|
pos: usize,
|
||||||
|
only_buffer_difference: bool,
|
||||||
|
) -> Vec<Suggestion> {
|
||||||
|
match value {
|
||||||
|
Value::Record { .. } => {
|
||||||
|
let text = match value
|
||||||
|
.get_data_by_key("value")
|
||||||
|
.and_then(|val| val.as_string().ok())
|
||||||
|
{
|
||||||
|
Some(val) => val,
|
||||||
|
None => "No value key".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = value
|
||||||
|
.get_data_by_key("description")
|
||||||
|
.and_then(|val| val.as_string().ok());
|
||||||
|
|
||||||
|
let span = match value.get_data_by_key("span") {
|
||||||
|
Some(span @ Value::Record { .. }) => {
|
||||||
|
let start = span
|
||||||
|
.get_data_by_key("start")
|
||||||
|
.and_then(|val| val.as_integer().ok());
|
||||||
|
let end = span
|
||||||
|
.get_data_by_key("end")
|
||||||
|
.and_then(|val| val.as_integer().ok());
|
||||||
|
match (start, end) {
|
||||||
|
(Some(start), Some(end)) => {
|
||||||
|
let start = start.min(end);
|
||||||
|
reedline::Span {
|
||||||
|
start: start as usize,
|
||||||
|
end: end as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => reedline::Span {
|
||||||
|
start: if only_buffer_difference { pos } else { 0 },
|
||||||
|
end: if only_buffer_difference {
|
||||||
|
pos + line.len()
|
||||||
|
} else {
|
||||||
|
line.len()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => reedline::Span {
|
||||||
|
start: if only_buffer_difference { pos } else { 0 },
|
||||||
|
end: if only_buffer_difference {
|
||||||
|
pos + line.len()
|
||||||
|
} else {
|
||||||
|
line.len()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let extra = match value.get_data_by_key("extra") {
|
||||||
|
Some(Value::List { vals, .. }) => {
|
||||||
|
let extra: Vec<String> = vals
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|extra| match extra {
|
||||||
|
Value::String { val, .. } => Some(val),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(extra)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![Suggestion {
|
||||||
|
value: text,
|
||||||
|
description,
|
||||||
|
extra,
|
||||||
|
span,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
Value::List { vals, .. } => vals
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
||||||
|
.collect(),
|
||||||
|
_ => vec![Suggestion {
|
||||||
|
value: format!("Not a record: {:?}", value),
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: 0,
|
||||||
|
end: line.len(),
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
7
crates/nu-cli/src/menus/mod.rs
Normal file
7
crates/nu-cli/src/menus/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
mod description_menu;
|
||||||
|
mod help_completions;
|
||||||
|
mod menu_completions;
|
||||||
|
|
||||||
|
pub use description_menu::DescriptionMenu;
|
||||||
|
pub use help_completions::NuHelpCompleter;
|
||||||
|
pub use menu_completions::NuMenuCompleter;
|
@ -1,219 +1,453 @@
|
|||||||
use super::NuHelpMenu;
|
use super::DescriptionMenu;
|
||||||
|
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use nu_color_config::lookup_ansi_color_style;
|
use nu_color_config::lookup_ansi_color_style;
|
||||||
use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value};
|
use nu_engine::eval_block;
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::{
|
||||||
|
create_menus,
|
||||||
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData,
|
||||||
|
ShellError, Span, Value,
|
||||||
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||||
Completer, CompletionMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent,
|
ColumnarMenu, EditCommand, Keybindings, ListMenu, Reedline, ReedlineEvent, ReedlineMenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Creates an input object for the completion menu based on the dictionary
|
const DEFAULT_COMPLETION_MENU: &str = r#"
|
||||||
// stored in the config variable
|
{
|
||||||
pub(crate) fn add_completion_menu(line_editor: Reedline, config: &Config) -> Reedline {
|
name: completion_menu
|
||||||
let mut completion_menu = CompletionMenu::default();
|
only_buffer_difference: false
|
||||||
|
marker: "| "
|
||||||
|
type: {
|
||||||
|
layout: columnar
|
||||||
|
columns: 4
|
||||||
|
col_width: 20
|
||||||
|
col_padding: 2
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green,
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
completion_menu = match config
|
const DEFAULT_HISTORY_MENU: &str = r#"
|
||||||
.menu_config
|
{
|
||||||
.get("columns")
|
name: history_menu
|
||||||
.and_then(|value| value.as_integer().ok())
|
only_buffer_difference: true
|
||||||
{
|
marker: "? "
|
||||||
Some(value) => completion_menu.with_columns(value as u16),
|
type: {
|
||||||
None => completion_menu,
|
layout: list
|
||||||
};
|
page_size: 10
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green,
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
completion_menu = completion_menu.with_column_width(
|
const DEFAULT_HELP_MENU: &str = r#"
|
||||||
config
|
{
|
||||||
.menu_config
|
name: help_menu
|
||||||
.get("col_width")
|
only_buffer_difference: true
|
||||||
.and_then(|value| value.as_integer().ok())
|
marker: "? "
|
||||||
.map(|value| value as usize),
|
type: {
|
||||||
);
|
layout: description
|
||||||
|
columns: 4
|
||||||
|
col_width: 20
|
||||||
|
col_padding: 2
|
||||||
|
selection_rows: 4
|
||||||
|
description_rows: 10
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green,
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
completion_menu = match config
|
// Adds all menus to line editor
|
||||||
.menu_config
|
pub(crate) fn add_menus(
|
||||||
.get("col_padding")
|
mut line_editor: Reedline,
|
||||||
.and_then(|value| value.as_integer().ok())
|
engine_state: &EngineState,
|
||||||
{
|
stack: &Stack,
|
||||||
Some(value) => completion_menu.with_column_padding(value as usize),
|
|
||||||
None => completion_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
completion_menu = match config
|
|
||||||
.menu_config
|
|
||||||
.get("text_style")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => completion_menu.with_text_style(lookup_ansi_color_style(&value)),
|
|
||||||
None => completion_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
completion_menu = match config
|
|
||||||
.menu_config
|
|
||||||
.get("selected_text_style")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => completion_menu.with_selected_text_style(lookup_ansi_color_style(&value)),
|
|
||||||
None => completion_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
completion_menu = match config
|
|
||||||
.menu_config
|
|
||||||
.get("marker")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => completion_menu.with_marker(value),
|
|
||||||
None => completion_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
line_editor.with_menu(Box::new(completion_menu), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an input object for the history menu based on the dictionary
|
|
||||||
// stored in the config variable
|
|
||||||
pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedline {
|
|
||||||
let mut history_menu = HistoryMenu::default();
|
|
||||||
|
|
||||||
history_menu = match config
|
|
||||||
.history_config
|
|
||||||
.get("page_size")
|
|
||||||
.and_then(|value| value.as_integer().ok())
|
|
||||||
{
|
|
||||||
Some(value) => history_menu.with_page_size(value as usize),
|
|
||||||
None => history_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
history_menu = match config
|
|
||||||
.history_config
|
|
||||||
.get("selector")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => {
|
|
||||||
let char = value.chars().next().unwrap_or('!');
|
|
||||||
history_menu.with_selection_char(char)
|
|
||||||
}
|
|
||||||
None => history_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
history_menu = match config
|
|
||||||
.history_config
|
|
||||||
.get("text_style")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => history_menu.with_text_style(lookup_ansi_color_style(&value)),
|
|
||||||
None => history_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
history_menu = match config
|
|
||||||
.history_config
|
|
||||||
.get("selected_text_style")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => history_menu.with_selected_text_style(lookup_ansi_color_style(&value)),
|
|
||||||
None => history_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
history_menu = match config
|
|
||||||
.history_config
|
|
||||||
.get("marker")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => history_menu.with_marker(value),
|
|
||||||
None => history_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
line_editor.with_menu(Box::new(history_menu), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an input object for the help menu based on the dictionary
|
|
||||||
// stored in the config variable
|
|
||||||
pub(crate) fn add_help_menu(
|
|
||||||
line_editor: Reedline,
|
|
||||||
help_completer: Box<dyn Completer>,
|
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Reedline {
|
) -> Result<Reedline, ShellError> {
|
||||||
let mut help_menu = NuHelpMenu::default();
|
line_editor = line_editor.clear_menus();
|
||||||
|
|
||||||
help_menu = match config
|
for menu in &config.menus {
|
||||||
.help_config
|
line_editor = add_menu(line_editor, menu, engine_state, stack, config)?
|
||||||
.get("columns")
|
}
|
||||||
.and_then(|value| value.as_integer().ok())
|
|
||||||
{
|
|
||||||
Some(value) => help_menu.with_columns(value as u16),
|
|
||||||
None => help_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
help_menu = help_menu.with_column_width(
|
// Checking if the default menus have been added from the config file
|
||||||
config
|
let default_menus = vec![
|
||||||
.help_config
|
("completion_menu", DEFAULT_COMPLETION_MENU),
|
||||||
.get("col_width")
|
("history_menu", DEFAULT_HISTORY_MENU),
|
||||||
.and_then(|value| value.as_integer().ok())
|
("help_menu", DEFAULT_HELP_MENU),
|
||||||
.map(|value| value as usize),
|
];
|
||||||
);
|
|
||||||
|
|
||||||
help_menu = match config
|
for (name, definition) in default_menus {
|
||||||
.help_config
|
if !config
|
||||||
.get("col_padding")
|
.menus
|
||||||
.and_then(|value| value.as_integer().ok())
|
.iter()
|
||||||
{
|
.any(|menu| menu.name.into_string("", config) == name)
|
||||||
Some(value) => help_menu.with_column_padding(value as usize),
|
{
|
||||||
None => help_menu,
|
let (block, _) = {
|
||||||
};
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let (output, _) = parse(
|
||||||
|
&mut working_set,
|
||||||
|
Some(name), // format!("entry #{}", entry_num)
|
||||||
|
definition.as_bytes(),
|
||||||
|
true,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
help_menu = match config
|
(output, working_set.render())
|
||||||
.help_config
|
};
|
||||||
.get("selection_rows")
|
|
||||||
.and_then(|value| value.as_integer().ok())
|
|
||||||
{
|
|
||||||
Some(value) => help_menu.with_selection_rows(value as u16),
|
|
||||||
None => help_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
help_menu = match config
|
let mut temp_stack = Stack::new();
|
||||||
.help_config
|
let input = Value::nothing(Span::test_data()).into_pipeline_data();
|
||||||
.get("description_rows")
|
let res = eval_block(engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||||
.and_then(|value| value.as_integer().ok())
|
|
||||||
{
|
|
||||||
Some(value) => help_menu.with_description_rows(value as usize),
|
|
||||||
None => help_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
help_menu = match config
|
if let PipelineData::Value(value, None) = res {
|
||||||
.help_config
|
for menu in create_menus(&value, config)? {
|
||||||
.get("text_style")
|
line_editor = add_menu(line_editor, &menu, engine_state, stack, config)?;
|
||||||
.and_then(|value| value.as_string().ok())
|
}
|
||||||
{
|
}
|
||||||
Some(value) => help_menu.with_text_style(lookup_ansi_color_style(&value)),
|
}
|
||||||
None => help_menu,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
help_menu = match config
|
Ok(line_editor)
|
||||||
.help_config
|
}
|
||||||
.get("selected_text_style")
|
|
||||||
.and_then(|value| value.as_string().ok())
|
|
||||||
{
|
|
||||||
Some(value) => help_menu.with_selected_text_style(lookup_ansi_color_style(&value)),
|
|
||||||
None => help_menu,
|
|
||||||
};
|
|
||||||
|
|
||||||
help_menu = match config
|
fn add_menu(
|
||||||
.help_config
|
line_editor: Reedline,
|
||||||
.get("description_text_style")
|
menu: &ParsedMenu,
|
||||||
.and_then(|value| value.as_string().ok())
|
engine_state: &EngineState,
|
||||||
{
|
stack: &Stack,
|
||||||
Some(value) => help_menu.with_description_text_style(lookup_ansi_color_style(&value)),
|
config: &Config,
|
||||||
None => help_menu,
|
) -> Result<Reedline, ShellError> {
|
||||||
};
|
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||||
|
let layout = extract_value("layout", cols, vals, span)?.into_string("", config);
|
||||||
|
|
||||||
help_menu = match config
|
match layout.as_str() {
|
||||||
.help_config
|
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||||
.get("marker")
|
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||||
.and_then(|value| value.as_string().ok())
|
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||||
{
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
Some(value) => help_menu.with_marker(value),
|
"columnar, list or description".to_string(),
|
||||||
None => help_menu,
|
menu.menu_type.into_abbreviated_string(config),
|
||||||
};
|
menu.menu_type.span()?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"only record type".to_string(),
|
||||||
|
menu.menu_type.into_abbreviated_string(config),
|
||||||
|
menu.menu_type.span()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
line_editor.with_menu(Box::new(help_menu), Some(help_completer))
|
// Adds a columnar menu to the editor engine
|
||||||
|
pub(crate) fn add_columnar_menu(
|
||||||
|
line_editor: Reedline,
|
||||||
|
menu: &ParsedMenu,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &Stack,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Reedline, ShellError> {
|
||||||
|
let name = menu.name.into_string("", config);
|
||||||
|
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||||
|
|
||||||
|
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||||
|
columnar_menu = match extract_value("columns", cols, vals, span) {
|
||||||
|
Ok(columns) => {
|
||||||
|
let columns = columns.as_integer()?;
|
||||||
|
columnar_menu.with_columns(columns as u16)
|
||||||
|
}
|
||||||
|
Err(_) => columnar_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
columnar_menu = match extract_value("col_width", cols, vals, span) {
|
||||||
|
Ok(col_width) => {
|
||||||
|
let col_width = col_width.as_integer()?;
|
||||||
|
columnar_menu.with_column_width(Some(col_width as usize))
|
||||||
|
}
|
||||||
|
Err(_) => columnar_menu.with_column_width(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
columnar_menu = match extract_value("col_padding", cols, vals, span) {
|
||||||
|
Ok(col_padding) => {
|
||||||
|
let col_padding = col_padding.as_integer()?;
|
||||||
|
columnar_menu.with_column_padding(col_padding as usize)
|
||||||
|
}
|
||||||
|
Err(_) => columnar_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Value::Record { cols, vals, span } = &menu.style {
|
||||||
|
columnar_menu = match extract_value("text", cols, vals, span) {
|
||||||
|
Ok(text) => {
|
||||||
|
let text = text.into_string("", config);
|
||||||
|
columnar_menu.with_text_style(lookup_ansi_color_style(&text))
|
||||||
|
}
|
||||||
|
Err(_) => columnar_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
columnar_menu = match extract_value("selected_text", cols, vals, span) {
|
||||||
|
Ok(selected) => {
|
||||||
|
let selected = selected.into_string("", config);
|
||||||
|
columnar_menu.with_selected_text_style(lookup_ansi_color_style(&selected))
|
||||||
|
}
|
||||||
|
Err(_) => columnar_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
columnar_menu = match extract_value("description_text", cols, vals, span) {
|
||||||
|
Ok(description) => {
|
||||||
|
let description = description.into_string("", config);
|
||||||
|
columnar_menu.with_description_text_style(lookup_ansi_color_style(&description))
|
||||||
|
}
|
||||||
|
Err(_) => columnar_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker = menu.marker.into_string("", config);
|
||||||
|
columnar_menu = columnar_menu.with_marker(marker);
|
||||||
|
|
||||||
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
|
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
match &menu.source {
|
||||||
|
Value::Nothing { .. } => {
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||||
|
}
|
||||||
|
Value::Block {
|
||||||
|
val,
|
||||||
|
captures,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let menu_completer = NuMenuCompleter::new(
|
||||||
|
*val,
|
||||||
|
*span,
|
||||||
|
stack.captures_to_stack(captures),
|
||||||
|
engine_state.clone(),
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(columnar_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"block or omitted value".to_string(),
|
||||||
|
menu.source.into_abbreviated_string(config),
|
||||||
|
menu.source.span()?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a search menu to the line editor
|
||||||
|
pub(crate) fn add_list_menu(
|
||||||
|
line_editor: Reedline,
|
||||||
|
menu: &ParsedMenu,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &Stack,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Reedline, ShellError> {
|
||||||
|
let name = menu.name.into_string("", config);
|
||||||
|
let mut list_menu = ListMenu::default().with_name(&name);
|
||||||
|
|
||||||
|
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||||
|
list_menu = match extract_value("page_size", cols, vals, span) {
|
||||||
|
Ok(page_size) => {
|
||||||
|
let page_size = page_size.as_integer()?;
|
||||||
|
list_menu.with_page_size(page_size as usize)
|
||||||
|
}
|
||||||
|
Err(_) => list_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Value::Record { cols, vals, span } = &menu.style {
|
||||||
|
list_menu = match extract_value("text", cols, vals, span) {
|
||||||
|
Ok(text) => {
|
||||||
|
let text = text.into_string("", config);
|
||||||
|
list_menu.with_text_style(lookup_ansi_color_style(&text))
|
||||||
|
}
|
||||||
|
Err(_) => list_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
list_menu = match extract_value("selected_text", cols, vals, span) {
|
||||||
|
Ok(selected) => {
|
||||||
|
let selected = selected.into_string("", config);
|
||||||
|
list_menu.with_selected_text_style(lookup_ansi_color_style(&selected))
|
||||||
|
}
|
||||||
|
Err(_) => list_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
list_menu = match extract_value("description_text", cols, vals, span) {
|
||||||
|
Ok(description) => {
|
||||||
|
let description = description.into_string("", config);
|
||||||
|
list_menu.with_description_text_style(lookup_ansi_color_style(&description))
|
||||||
|
}
|
||||||
|
Err(_) => list_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker = menu.marker.into_string("", config);
|
||||||
|
list_menu = list_menu.with_marker(marker);
|
||||||
|
|
||||||
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
|
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
match &menu.source {
|
||||||
|
Value::Nothing { .. } => {
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||||
|
}
|
||||||
|
Value::Block {
|
||||||
|
val,
|
||||||
|
captures,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let menu_completer = NuMenuCompleter::new(
|
||||||
|
*val,
|
||||||
|
*span,
|
||||||
|
stack.captures_to_stack(captures),
|
||||||
|
engine_state.clone(),
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(list_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"block or omitted value".to_string(),
|
||||||
|
menu.source.into_abbreviated_string(config),
|
||||||
|
menu.source.span()?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a description menu to the line editor
|
||||||
|
pub(crate) fn add_description_menu(
|
||||||
|
line_editor: Reedline,
|
||||||
|
menu: &ParsedMenu,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &Stack,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Reedline, ShellError> {
|
||||||
|
let name = menu.name.into_string("", config);
|
||||||
|
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||||
|
|
||||||
|
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||||
|
description_menu = match extract_value("columns", cols, vals, span) {
|
||||||
|
Ok(columns) => {
|
||||||
|
let columns = columns.as_integer()?;
|
||||||
|
description_menu.with_columns(columns as u16)
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
description_menu = match extract_value("col_width", cols, vals, span) {
|
||||||
|
Ok(col_width) => {
|
||||||
|
let col_width = col_width.as_integer()?;
|
||||||
|
description_menu.with_column_width(Some(col_width as usize))
|
||||||
|
}
|
||||||
|
Err(_) => description_menu.with_column_width(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
description_menu = match extract_value("col_padding", cols, vals, span) {
|
||||||
|
Ok(col_padding) => {
|
||||||
|
let col_padding = col_padding.as_integer()?;
|
||||||
|
description_menu.with_column_padding(col_padding as usize)
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
description_menu = match extract_value("selection_rows", cols, vals, span) {
|
||||||
|
Ok(selection_rows) => {
|
||||||
|
let selection_rows = selection_rows.as_integer()?;
|
||||||
|
description_menu.with_selection_rows(selection_rows as u16)
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
description_menu = match extract_value("description_rows", cols, vals, span) {
|
||||||
|
Ok(description_rows) => {
|
||||||
|
let description_rows = description_rows.as_integer()?;
|
||||||
|
description_menu.with_description_rows(description_rows as usize)
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Value::Record { cols, vals, span } = &menu.style {
|
||||||
|
description_menu = match extract_value("text", cols, vals, span) {
|
||||||
|
Ok(text) => {
|
||||||
|
let text = text.into_string("", config);
|
||||||
|
description_menu.with_text_style(lookup_ansi_color_style(&text))
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
description_menu = match extract_value("selected_text", cols, vals, span) {
|
||||||
|
Ok(selected) => {
|
||||||
|
let selected = selected.into_string("", config);
|
||||||
|
description_menu.with_selected_text_style(lookup_ansi_color_style(&selected))
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
description_menu = match extract_value("description_text", cols, vals, span) {
|
||||||
|
Ok(description) => {
|
||||||
|
let description = description.into_string("", config);
|
||||||
|
description_menu.with_description_text_style(lookup_ansi_color_style(&description))
|
||||||
|
}
|
||||||
|
Err(_) => description_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker = menu.marker.into_string("", config);
|
||||||
|
description_menu = description_menu.with_marker(marker);
|
||||||
|
|
||||||
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
|
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
match &menu.source {
|
||||||
|
Value::Nothing { .. } => {
|
||||||
|
let completer = Box::new(NuHelpCompleter::new(engine_state.clone()));
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(description_menu),
|
||||||
|
completer,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Value::Block {
|
||||||
|
val,
|
||||||
|
captures,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let menu_completer = NuMenuCompleter::new(
|
||||||
|
*val,
|
||||||
|
*span,
|
||||||
|
stack.captures_to_stack(captures),
|
||||||
|
engine_state.clone(),
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(description_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"block or omitted value".to_string(),
|
||||||
|
menu.source.into_abbreviated_string(config),
|
||||||
|
menu.source.span()?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::reedline_config::{add_completion_menu, add_help_menu, add_history_menu};
|
use crate::reedline_config::add_menus;
|
||||||
use crate::{prompt_update, reedline_config, NuHelpCompleter};
|
use crate::{prompt_update, reedline_config};
|
||||||
use crate::{
|
use crate::{
|
||||||
reedline_config::KeybindingsMode,
|
reedline_config::KeybindingsMode,
|
||||||
util::{eval_source, report_error},
|
util::{eval_source, report_error},
|
||||||
@ -194,11 +194,14 @@ pub fn evaluate_repl(
|
|||||||
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
line_editor = add_completion_menu(line_editor, &config);
|
line_editor = match add_menus(line_editor, engine_state, stack, &config) {
|
||||||
line_editor = add_history_menu(line_editor, &config);
|
Ok(line_editor) => line_editor,
|
||||||
|
Err(e) => {
|
||||||
let help_completer = Box::new(NuHelpCompleter::new(engine_state.clone()));
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
line_editor = add_help_menu(line_editor, help_completer, &config);
|
report_error(&working_set, &e);
|
||||||
|
Reedline::create()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if is_perf_true {
|
if is_perf_true {
|
||||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||||
|
@ -13,6 +13,17 @@ pub struct ParsedKeybinding {
|
|||||||
pub mode: Value,
|
pub mode: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Definition of a parsed menu from the config object
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct ParsedMenu {
|
||||||
|
pub name: Value,
|
||||||
|
pub marker: Value,
|
||||||
|
pub only_buffer_difference: Value,
|
||||||
|
pub style: Value,
|
||||||
|
pub menu_type: Value,
|
||||||
|
pub source: Value,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub filesize_metric: bool,
|
pub filesize_metric: bool,
|
||||||
@ -31,10 +42,8 @@ pub struct Config {
|
|||||||
pub max_history_size: i64,
|
pub max_history_size: i64,
|
||||||
pub sync_history_on_enter: bool,
|
pub sync_history_on_enter: bool,
|
||||||
pub log_level: String,
|
pub log_level: String,
|
||||||
pub menu_config: HashMap<String, Value>,
|
|
||||||
pub keybindings: Vec<ParsedKeybinding>,
|
pub keybindings: Vec<ParsedKeybinding>,
|
||||||
pub history_config: HashMap<String, Value>,
|
pub menus: Vec<ParsedMenu>,
|
||||||
pub help_config: HashMap<String, Value>,
|
|
||||||
pub rm_always_trash: bool,
|
pub rm_always_trash: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,10 +66,8 @@ impl Default for Config {
|
|||||||
max_history_size: 1000,
|
max_history_size: 1000,
|
||||||
sync_history_on_enter: true,
|
sync_history_on_enter: true,
|
||||||
log_level: String::new(),
|
log_level: String::new(),
|
||||||
menu_config: HashMap::new(),
|
|
||||||
history_config: HashMap::new(),
|
|
||||||
help_config: HashMap::new(),
|
|
||||||
keybindings: Vec::new(),
|
keybindings: Vec::new(),
|
||||||
|
menus: Vec::new(),
|
||||||
rm_always_trash: false,
|
rm_always_trash: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,34 +222,20 @@ impl Value {
|
|||||||
eprintln!("$config.log_level is not a string")
|
eprintln!("$config.log_level is not a string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"menu_config" => {
|
"menus" => match create_menus(value, &config) {
|
||||||
if let Ok(map) = create_map(value, &config) {
|
Ok(map) => config.menus = map,
|
||||||
config.menu_config = map;
|
Err(e) => {
|
||||||
} else {
|
eprintln!("$config.menus is not a valid list of menus");
|
||||||
eprintln!("$config.menu_config is not a record")
|
eprintln!("{:?}", e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
"history_config" => {
|
"keybindings" => match create_keybindings(value, &config) {
|
||||||
if let Ok(map) = create_map(value, &config) {
|
Ok(keybindings) => config.keybindings = keybindings,
|
||||||
config.history_config = map;
|
Err(e) => {
|
||||||
} else {
|
eprintln!("$config.keybindings is not a valid keybindings list");
|
||||||
eprintln!("$config.history_config is not a record")
|
eprintln!("{:?}", e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
"help_config" => {
|
|
||||||
if let Ok(map) = create_map(value, &config) {
|
|
||||||
config.help_config = map;
|
|
||||||
} else {
|
|
||||||
eprintln!("$config.help_config is not a record")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"keybindings" => {
|
|
||||||
if let Ok(keybindings) = create_keybindings(value, &config) {
|
|
||||||
config.keybindings = keybindings;
|
|
||||||
} else {
|
|
||||||
eprintln!("$config.keybindings is not a valid keybindings list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x => {
|
x => {
|
||||||
eprintln!("$config.{} is an unknown config setting", x)
|
eprintln!("$config.{} is an unknown config setting", x)
|
||||||
}
|
}
|
||||||
@ -310,18 +303,19 @@ fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybin
|
|||||||
match value {
|
match value {
|
||||||
Value::Record { cols, vals, span } => {
|
Value::Record { cols, vals, span } => {
|
||||||
// Finding the modifier value in the record
|
// Finding the modifier value in the record
|
||||||
let modifier = extract_value("modifier", cols, vals, span)?;
|
let modifier = extract_value("modifier", cols, vals, span)?.clone();
|
||||||
let keycode = extract_value("keycode", cols, vals, span)?;
|
let keycode = extract_value("keycode", cols, vals, span)?.clone();
|
||||||
let mode = extract_value("mode", cols, vals, span)?;
|
let mode = extract_value("mode", cols, vals, span)?.clone();
|
||||||
let event = extract_value("event", cols, vals, span)?;
|
let event = extract_value("event", cols, vals, span)?.clone();
|
||||||
|
|
||||||
let keybinding = ParsedKeybinding {
|
let keybinding = ParsedKeybinding {
|
||||||
modifier: modifier.clone(),
|
modifier,
|
||||||
keycode: keycode.clone(),
|
keycode,
|
||||||
mode: mode.clone(),
|
mode,
|
||||||
event: event.clone(),
|
event,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We return a menu to be able to do recursion on the same function
|
||||||
Ok(vec![keybinding])
|
Ok(vec![keybinding])
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -341,6 +335,49 @@ fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parses the config object to extract the strings that will compose a keybinding for reedline
|
||||||
|
pub fn create_menus(value: &Value, config: &Config) -> Result<Vec<ParsedMenu>, ShellError> {
|
||||||
|
match value {
|
||||||
|
Value::Record { cols, vals, span } => {
|
||||||
|
// Finding the modifier value in the record
|
||||||
|
let name = extract_value("name", cols, vals, span)?.clone();
|
||||||
|
let marker = extract_value("marker", cols, vals, span)?.clone();
|
||||||
|
let only_buffer_difference =
|
||||||
|
extract_value("only_buffer_difference", cols, vals, span)?.clone();
|
||||||
|
let style = extract_value("style", cols, vals, span)?.clone();
|
||||||
|
let menu_type = extract_value("type", cols, vals, span)?.clone();
|
||||||
|
|
||||||
|
// Source is an optional value
|
||||||
|
let source = match extract_value("source", cols, vals, span) {
|
||||||
|
Ok(source) => source.clone(),
|
||||||
|
Err(_) => Value::Nothing { span: *span },
|
||||||
|
};
|
||||||
|
|
||||||
|
let menu = ParsedMenu {
|
||||||
|
name,
|
||||||
|
only_buffer_difference,
|
||||||
|
marker,
|
||||||
|
style,
|
||||||
|
menu_type,
|
||||||
|
source,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(vec![menu])
|
||||||
|
}
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
let res = vals
|
||||||
|
.iter()
|
||||||
|
.map(|inner_value| create_menus(inner_value, config))
|
||||||
|
.collect::<Result<Vec<Vec<ParsedMenu>>, ShellError>>();
|
||||||
|
|
||||||
|
let res = res?.into_iter().flatten().collect::<Vec<ParsedMenu>>();
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
_ => Ok(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extract_value<'record>(
|
pub fn extract_value<'record>(
|
||||||
name: &str,
|
name: &str,
|
||||||
cols: &'record [String],
|
cols: &'record [String],
|
||||||
|
@ -198,32 +198,125 @@ let $config = {
|
|||||||
edit_mode: emacs # emacs, vi
|
edit_mode: emacs # emacs, vi
|
||||||
max_history_size: 10000 # Session has to be reloaded for this to take effect
|
max_history_size: 10000 # Session has to be reloaded for this to take effect
|
||||||
sync_history_on_enter: true # Enable to share the history between multiple sessions, else you have to close the session to persist history to file
|
sync_history_on_enter: true # Enable to share the history between multiple sessions, else you have to close the session to persist history to file
|
||||||
menu_config: {
|
menus: [
|
||||||
columns: 4
|
# Configuration for default nushell menus
|
||||||
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
|
# Note the lack of souce parameter
|
||||||
col_padding: 2
|
{
|
||||||
text_style: green
|
name: completion_menu
|
||||||
selected_text_style: green_reverse
|
only_buffer_difference: false
|
||||||
marker: "| "
|
marker: "| "
|
||||||
}
|
type: {
|
||||||
history_config: {
|
layout: columnar
|
||||||
page_size: 10
|
columns: 4
|
||||||
selector: "!"
|
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
|
||||||
text_style: green
|
col_padding: 2
|
||||||
selected_text_style: green_reverse
|
}
|
||||||
marker: "? "
|
style: {
|
||||||
}
|
text: green
|
||||||
help_config: {
|
selected_text: green_reverse
|
||||||
columns: 4
|
description_text: yellow
|
||||||
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
|
}
|
||||||
col_padding: 2
|
}
|
||||||
selection_rows: 4
|
{
|
||||||
description_rows: 10
|
name: history_menu
|
||||||
text_style: green
|
only_buffer_difference: true
|
||||||
selected_text_style: green_reverse
|
marker: "? "
|
||||||
description_text_style: yellow
|
type: {
|
||||||
marker: "? "
|
layout: list
|
||||||
}
|
page_size: 10
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: help_menu
|
||||||
|
only_buffer_difference: true
|
||||||
|
marker: "? "
|
||||||
|
type: {
|
||||||
|
layout: description
|
||||||
|
columns: 4
|
||||||
|
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
|
||||||
|
col_padding: 2
|
||||||
|
selection_rows: 4
|
||||||
|
description_rows: 10
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Example of extra menus created using a nushell source
|
||||||
|
# Use the source field to create a list of records that populates
|
||||||
|
# the menu
|
||||||
|
{
|
||||||
|
name: commands_menu
|
||||||
|
only_buffer_difference: false
|
||||||
|
marker: "# "
|
||||||
|
type: {
|
||||||
|
layout: columnar
|
||||||
|
columns: 4
|
||||||
|
col_width: 20
|
||||||
|
col_padding: 2
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
source: { |buffer, position|
|
||||||
|
$nu.scope.commands
|
||||||
|
| where command =~ $buffer
|
||||||
|
| each { |it| {value: $it.command description: $it.usage} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: vars_menu
|
||||||
|
only_buffer_difference: true
|
||||||
|
marker: "# "
|
||||||
|
type: {
|
||||||
|
layout: list
|
||||||
|
page_size: 10
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
source: { |buffer, position|
|
||||||
|
$nu.scope.vars
|
||||||
|
| where name =~ $buffer
|
||||||
|
| sort-by name
|
||||||
|
| each { |it| {value: $it.name description: $it.type} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: commands_with_description
|
||||||
|
only_buffer_difference: true
|
||||||
|
marker: "# "
|
||||||
|
type: {
|
||||||
|
layout: description
|
||||||
|
columns: 4
|
||||||
|
col_width: 20
|
||||||
|
col_padding: 2
|
||||||
|
selection_rows: 4
|
||||||
|
description_rows: 10
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
source: { |buffer, position|
|
||||||
|
$nu.scope.commands
|
||||||
|
| where command =~ $buffer
|
||||||
|
| each { |it| {value: $it.command description: $it.usage} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
keybindings: [
|
keybindings: [
|
||||||
{
|
{
|
||||||
name: completion_menu
|
name: completion_menu
|
||||||
@ -268,5 +361,27 @@ let $config = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# Keybindings used to trigger the user defined menus
|
||||||
|
{
|
||||||
|
name: commands_menu
|
||||||
|
modifier: control
|
||||||
|
keycode: char_t
|
||||||
|
mode: [emacs, vi_normal, vi_insert]
|
||||||
|
event: { send: menu name: commands_menu }
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: commands_menu
|
||||||
|
modifier: control
|
||||||
|
keycode: char_y
|
||||||
|
mode: [emacs, vi_normal, vi_insert]
|
||||||
|
event: { send: menu name: vars_menu }
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: commands_with_description
|
||||||
|
modifier: control
|
||||||
|
keycode: char_u
|
||||||
|
mode: [emacs, vi_normal, vi_insert]
|
||||||
|
event: { send: menu name: commands_with_description }
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user