mirror of
https://github.com/nushell/nushell.git
synced 2025-02-08 14:40:53 +01:00
IDE style completion (#11593)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> Adds an IDE-Style completion menu ![grafik](https://github.com/nushell/nushell/assets/104733404/df7f1039-2bbc-42f7-9501-fe28507b5cfe) # 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:
parent
4458aae3d4
commit
ea1bd9f8f9
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4517,7 +4517,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reedline"
|
name = "reedline"
|
||||||
version = "0.28.0"
|
version = "0.28.0"
|
||||||
source = "git+https://github.com/nushell/reedline?branch=main#31eaeeb231664b889c92595ff38ead4ccb570766"
|
source = "git+https://github.com/nushell/reedline?branch=main#9ca229de321174ccd18bc2d8f96e865b788e65b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -12,7 +12,8 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
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,
|
||||||
ColumnarMenu, EditCommand, Keybindings, ListMenu, Reedline, ReedlineEvent, ReedlineMenu,
|
ColumnarMenu, DescriptionMode, EditCommand, IdeMenu, Keybindings, ListMenu, Reedline,
|
||||||
|
ReedlineEvent, ReedlineMenu,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -138,9 +139,10 @@ fn add_menu(
|
|||||||
match layout.as_str() {
|
match layout.as_str() {
|
||||||
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||||
|
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "columnar, list or description".to_string(),
|
expected: "columnar, list, ide or description".to_string(),
|
||||||
value: menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.into_abbreviated_string(config),
|
||||||
span: menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
}),
|
}),
|
||||||
@ -351,6 +353,208 @@ pub(crate) fn add_list_menu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds an IDE menu to the line editor
|
||||||
|
pub(crate) fn add_ide_menu(
|
||||||
|
line_editor: Reedline,
|
||||||
|
menu: &ParsedMenu,
|
||||||
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: &Stack,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Reedline, ShellError> {
|
||||||
|
let span = menu.menu_type.span();
|
||||||
|
let name = menu.name.into_string("", config);
|
||||||
|
let mut ide_menu = IdeMenu::default().with_name(&name);
|
||||||
|
|
||||||
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
|
ide_menu = match extract_value("min_completion_width", val, span) {
|
||||||
|
Ok(min_completion_width) => {
|
||||||
|
let min_completion_width = min_completion_width.as_int()?;
|
||||||
|
ide_menu.with_min_completion_width(min_completion_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_completion_width", val, span) {
|
||||||
|
Ok(max_completion_width) => {
|
||||||
|
let max_completion_width = max_completion_width.as_int()?;
|
||||||
|
ide_menu.with_max_completion_width(max_completion_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_completion_height", val, span) {
|
||||||
|
Ok(max_completion_height) => {
|
||||||
|
let max_completion_height = max_completion_height.as_int()?;
|
||||||
|
ide_menu.with_max_completion_height(max_completion_height as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("padding", val, span) {
|
||||||
|
Ok(padding) => {
|
||||||
|
let padding = padding.as_int()?;
|
||||||
|
ide_menu.with_padding(padding as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("border", val, span) {
|
||||||
|
Ok(border) => {
|
||||||
|
if let Ok(border) = border.as_bool() {
|
||||||
|
if border {
|
||||||
|
ide_menu.with_default_border()
|
||||||
|
} else {
|
||||||
|
ide_menu
|
||||||
|
}
|
||||||
|
} else if let Ok(border_chars) = border.as_record() {
|
||||||
|
let top_right = extract_value("top_right", border_chars, span)?.as_char()?;
|
||||||
|
let top_left = extract_value("top_left", border_chars, span)?.as_char()?;
|
||||||
|
let bottom_right =
|
||||||
|
extract_value("bottom_right", border_chars, span)?.as_char()?;
|
||||||
|
let bottom_left =
|
||||||
|
extract_value("bottom_left", border_chars, span)?.as_char()?;
|
||||||
|
let horizontal = extract_value("horizontal", border_chars, span)?.as_char()?;
|
||||||
|
let vertical = extract_value("vertical", border_chars, span)?.as_char()?;
|
||||||
|
|
||||||
|
ide_menu.with_border(
|
||||||
|
top_right,
|
||||||
|
top_left,
|
||||||
|
bottom_right,
|
||||||
|
bottom_left,
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
|
expected: "bool or record".to_string(),
|
||||||
|
value: border.into_abbreviated_string(config),
|
||||||
|
span: border.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("cursor_offset", val, span) {
|
||||||
|
Ok(cursor_offset) => {
|
||||||
|
let cursor_offset = cursor_offset.as_int()?;
|
||||||
|
ide_menu.with_cursor_offset(cursor_offset as i16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("description_mode", val, span) {
|
||||||
|
Ok(description_mode) => {
|
||||||
|
let description_mode_str = description_mode.as_string()?;
|
||||||
|
match description_mode_str.as_str() {
|
||||||
|
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
||||||
|
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
||||||
|
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
|
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
||||||
|
value: description_mode.into_abbreviated_string(config),
|
||||||
|
span: description_mode.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("min_description_width", val, span) {
|
||||||
|
Ok(min_description_width) => {
|
||||||
|
let min_description_width = min_description_width.as_int()?;
|
||||||
|
ide_menu.with_min_description_width(min_description_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_description_width", val, span) {
|
||||||
|
Ok(max_description_width) => {
|
||||||
|
let max_description_width = max_description_width.as_int()?;
|
||||||
|
ide_menu.with_max_description_width(max_description_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_description_height", val, span) {
|
||||||
|
Ok(max_description_height) => {
|
||||||
|
let max_description_height = max_description_height.as_int()?;
|
||||||
|
ide_menu.with_max_description_height(max_description_height as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("description_offset", val, span) {
|
||||||
|
Ok(description_padding) => {
|
||||||
|
let description_padding = description_padding.as_int()?;
|
||||||
|
ide_menu.with_description_offset(description_padding as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = menu.style.span();
|
||||||
|
if let Value::Record { val, .. } = &menu.style {
|
||||||
|
add_style!(
|
||||||
|
"text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"selected_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_selected_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"description_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_description_text_style
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker = menu.marker.into_string("", config);
|
||||||
|
ide_menu = ide_menu.with_marker(marker);
|
||||||
|
|
||||||
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
|
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
let span = menu.source.span();
|
||||||
|
match &menu.source {
|
||||||
|
Value::Nothing { .. } => {
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
|
||||||
|
}
|
||||||
|
Value::Closure { val, .. } => {
|
||||||
|
let menu_completer = NuMenuCompleter::new(
|
||||||
|
val.block_id,
|
||||||
|
span,
|
||||||
|
stack.captures_to_stack(val.captures.clone()),
|
||||||
|
engine_state,
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(ide_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
|
expected: "block or omitted value".to_string(),
|
||||||
|
value: menu.source.into_abbreviated_string(config),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a description menu to the line editor
|
// Adds a description menu to the line editor
|
||||||
pub(crate) fn add_description_menu(
|
pub(crate) fn add_description_menu(
|
||||||
line_editor: Reedline,
|
line_editor: Reedline,
|
||||||
|
@ -269,6 +269,30 @@ $env.config = {
|
|||||||
description_text: yellow
|
description_text: yellow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name: ide_completion_menu
|
||||||
|
only_buffer_difference: false
|
||||||
|
marker: "| "
|
||||||
|
type: {
|
||||||
|
layout: ide
|
||||||
|
min_completion_width: 0,
|
||||||
|
max_completion_width: 50,
|
||||||
|
# max_completion_height: 10, # will be limited by the available lines in the terminal
|
||||||
|
padding: 0,
|
||||||
|
border: false,
|
||||||
|
cursor_offset: 0,
|
||||||
|
description_mode: "prefer_right"
|
||||||
|
min_description_width: 0
|
||||||
|
max_description_width: 50
|
||||||
|
max_description_height: 10
|
||||||
|
description_offset: 1
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: green_reverse
|
||||||
|
description_text: yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name: history_menu
|
name: history_menu
|
||||||
only_buffer_difference: true
|
only_buffer_difference: true
|
||||||
@ -317,6 +341,19 @@ $env.config = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name: ide_completion_menu
|
||||||
|
modifier: control
|
||||||
|
keycode: char_n
|
||||||
|
mode: [emacs vi_normal vi_insert]
|
||||||
|
event: {
|
||||||
|
until: [
|
||||||
|
{ send: menu name: ide_completion_menu }
|
||||||
|
{ send: menunext }
|
||||||
|
{ edit: complete }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name: history_menu
|
name: history_menu
|
||||||
modifier: control
|
modifier: control
|
||||||
|
Loading…
Reference in New Issue
Block a user