Skip to content

Commit fb22098

Browse files
maxomatic458dmatos2012
authored andcommitted
IDE style completion (nushell#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. -->
1 parent 27a508d commit fb22098

File tree

3 files changed

+244
-3
lines changed

3 files changed

+244
-3
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/nu-cli/src/reedline_config.rs

+206-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use nu_protocol::{
1212
};
1313
use reedline::{
1414
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
15-
ColumnarMenu, EditCommand, Keybindings, ListMenu, Reedline, ReedlineEvent, ReedlineMenu,
15+
ColumnarMenu, DescriptionMode, EditCommand, IdeMenu, Keybindings, ListMenu, Reedline,
16+
ReedlineEvent, ReedlineMenu,
1617
};
1718
use std::sync::Arc;
1819

@@ -138,9 +139,10 @@ fn add_menu(
138139
match layout.as_str() {
139140
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
140141
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
142+
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
141143
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
142144
_ => Err(ShellError::UnsupportedConfigValue {
143-
expected: "columnar, list or description".to_string(),
145+
expected: "columnar, list, ide or description".to_string(),
144146
value: menu.menu_type.into_abbreviated_string(config),
145147
span: menu.menu_type.span(),
146148
}),
@@ -351,6 +353,208 @@ pub(crate) fn add_list_menu(
351353
}
352354
}
353355

356+
// Adds an IDE menu to the line editor
357+
pub(crate) fn add_ide_menu(
358+
line_editor: Reedline,
359+
menu: &ParsedMenu,
360+
engine_state: Arc<EngineState>,
361+
stack: &Stack,
362+
config: &Config,
363+
) -> Result<Reedline, ShellError> {
364+
let span = menu.menu_type.span();
365+
let name = menu.name.into_string("", config);
366+
let mut ide_menu = IdeMenu::default().with_name(&name);
367+
368+
if let Value::Record { val, .. } = &menu.menu_type {
369+
ide_menu = match extract_value("min_completion_width", val, span) {
370+
Ok(min_completion_width) => {
371+
let min_completion_width = min_completion_width.as_int()?;
372+
ide_menu.with_min_completion_width(min_completion_width as u16)
373+
}
374+
Err(_) => ide_menu,
375+
};
376+
377+
ide_menu = match extract_value("max_completion_width", val, span) {
378+
Ok(max_completion_width) => {
379+
let max_completion_width = max_completion_width.as_int()?;
380+
ide_menu.with_max_completion_width(max_completion_width as u16)
381+
}
382+
Err(_) => ide_menu,
383+
};
384+
385+
ide_menu = match extract_value("max_completion_height", val, span) {
386+
Ok(max_completion_height) => {
387+
let max_completion_height = max_completion_height.as_int()?;
388+
ide_menu.with_max_completion_height(max_completion_height as u16)
389+
}
390+
Err(_) => ide_menu,
391+
};
392+
393+
ide_menu = match extract_value("padding", val, span) {
394+
Ok(padding) => {
395+
let padding = padding.as_int()?;
396+
ide_menu.with_padding(padding as u16)
397+
}
398+
Err(_) => ide_menu,
399+
};
400+
401+
ide_menu = match extract_value("border", val, span) {
402+
Ok(border) => {
403+
if let Ok(border) = border.as_bool() {
404+
if border {
405+
ide_menu.with_default_border()
406+
} else {
407+
ide_menu
408+
}
409+
} else if let Ok(border_chars) = border.as_record() {
410+
let top_right = extract_value("top_right", border_chars, span)?.as_char()?;
411+
let top_left = extract_value("top_left", border_chars, span)?.as_char()?;
412+
let bottom_right =
413+
extract_value("bottom_right", border_chars, span)?.as_char()?;
414+
let bottom_left =
415+
extract_value("bottom_left", border_chars, span)?.as_char()?;
416+
let horizontal = extract_value("horizontal", border_chars, span)?.as_char()?;
417+
let vertical = extract_value("vertical", border_chars, span)?.as_char()?;
418+
419+
ide_menu.with_border(
420+
top_right,
421+
top_left,
422+
bottom_right,
423+
bottom_left,
424+
horizontal,
425+
vertical,
426+
)
427+
} else {
428+
return Err(ShellError::UnsupportedConfigValue {
429+
expected: "bool or record".to_string(),
430+
value: border.into_abbreviated_string(config),
431+
span: border.span(),
432+
});
433+
}
434+
}
435+
Err(_) => ide_menu,
436+
};
437+
438+
ide_menu = match extract_value("cursor_offset", val, span) {
439+
Ok(cursor_offset) => {
440+
let cursor_offset = cursor_offset.as_int()?;
441+
ide_menu.with_cursor_offset(cursor_offset as i16)
442+
}
443+
Err(_) => ide_menu,
444+
};
445+
446+
ide_menu = match extract_value("description_mode", val, span) {
447+
Ok(description_mode) => {
448+
let description_mode_str = description_mode.as_string()?;
449+
match description_mode_str.as_str() {
450+
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
451+
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
452+
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
453+
_ => {
454+
return Err(ShellError::UnsupportedConfigValue {
455+
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
456+
value: description_mode.into_abbreviated_string(config),
457+
span: description_mode.span(),
458+
});
459+
}
460+
}
461+
}
462+
Err(_) => ide_menu,
463+
};
464+
465+
ide_menu = match extract_value("min_description_width", val, span) {
466+
Ok(min_description_width) => {
467+
let min_description_width = min_description_width.as_int()?;
468+
ide_menu.with_min_description_width(min_description_width as u16)
469+
}
470+
Err(_) => ide_menu,
471+
};
472+
473+
ide_menu = match extract_value("max_description_width", val, span) {
474+
Ok(max_description_width) => {
475+
let max_description_width = max_description_width.as_int()?;
476+
ide_menu.with_max_description_width(max_description_width as u16)
477+
}
478+
Err(_) => ide_menu,
479+
};
480+
481+
ide_menu = match extract_value("max_description_height", val, span) {
482+
Ok(max_description_height) => {
483+
let max_description_height = max_description_height.as_int()?;
484+
ide_menu.with_max_description_height(max_description_height as u16)
485+
}
486+
Err(_) => ide_menu,
487+
};
488+
489+
ide_menu = match extract_value("description_offset", val, span) {
490+
Ok(description_padding) => {
491+
let description_padding = description_padding.as_int()?;
492+
ide_menu.with_description_offset(description_padding as u16)
493+
}
494+
Err(_) => ide_menu,
495+
};
496+
}
497+
498+
let span = menu.style.span();
499+
if let Value::Record { val, .. } = &menu.style {
500+
add_style!(
501+
"text",
502+
val,
503+
span,
504+
config,
505+
ide_menu,
506+
IdeMenu::with_text_style
507+
);
508+
add_style!(
509+
"selected_text",
510+
val,
511+
span,
512+
config,
513+
ide_menu,
514+
IdeMenu::with_selected_text_style
515+
);
516+
add_style!(
517+
"description_text",
518+
val,
519+
span,
520+
config,
521+
ide_menu,
522+
IdeMenu::with_description_text_style
523+
);
524+
}
525+
526+
let marker = menu.marker.into_string("", config);
527+
ide_menu = ide_menu.with_marker(marker);
528+
529+
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
530+
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
531+
532+
let span = menu.source.span();
533+
match &menu.source {
534+
Value::Nothing { .. } => {
535+
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
536+
}
537+
Value::Closure { val, .. } => {
538+
let menu_completer = NuMenuCompleter::new(
539+
val.block_id,
540+
span,
541+
stack.captures_to_stack(val.captures.clone()),
542+
engine_state,
543+
only_buffer_difference,
544+
);
545+
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
546+
menu: Box::new(ide_menu),
547+
completer: Box::new(menu_completer),
548+
}))
549+
}
550+
_ => Err(ShellError::UnsupportedConfigValue {
551+
expected: "block or omitted value".to_string(),
552+
value: menu.source.into_abbreviated_string(config),
553+
span,
554+
}),
555+
}
556+
}
557+
354558
// Adds a description menu to the line editor
355559
pub(crate) fn add_description_menu(
356560
line_editor: Reedline,

crates/nu-utils/src/sample_config/default_config.nu

+37
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,30 @@ $env.config = {
269269
description_text: yellow
270270
}
271271
}
272+
{
273+
name: ide_completion_menu
274+
only_buffer_difference: false
275+
marker: "| "
276+
type: {
277+
layout: ide
278+
min_completion_width: 0,
279+
max_completion_width: 50,
280+
# max_completion_height: 10, # will be limited by the available lines in the terminal
281+
padding: 0,
282+
border: false,
283+
cursor_offset: 0,
284+
description_mode: "prefer_right"
285+
min_description_width: 0
286+
max_description_width: 50
287+
max_description_height: 10
288+
description_offset: 1
289+
}
290+
style: {
291+
text: green
292+
selected_text: green_reverse
293+
description_text: yellow
294+
}
295+
}
272296
{
273297
name: history_menu
274298
only_buffer_difference: true
@@ -317,6 +341,19 @@ $env.config = {
317341
]
318342
}
319343
}
344+
{
345+
name: ide_completion_menu
346+
modifier: control
347+
keycode: char_n
348+
mode: [emacs vi_normal vi_insert]
349+
event: {
350+
until: [
351+
{ send: menu name: ide_completion_menu }
352+
{ send: menunext }
353+
{ edit: complete }
354+
]
355+
}
356+
}
320357
{
321358
name: history_menu
322359
modifier: control

0 commit comments

Comments
 (0)