Refactoring completion menus

Methods that have some amount of code duplication:

  • update_values
    • This doesn't seem like something the menu should concern itself with tbh
  • replace_in_buffer
    • This also doesn't feel like one of the menu's responsibilities
    • The description menu's implementation is slightly different from the others, because it also looks at examples, but it's not a big deal, we can account for that
  • can_partially_complete - Columnar menu and IDE menu both have the same code

Things we could do to reduce code duplication

Moving the description_menu into Reedline or a separate menu crate

stormasm suggested this in #706. It would make sharing logic between the description menu and other menus easier, as well as updating menu logic. NuHelpCompleter would have to remain in the Nushell repo, though.

Not sure the best location of this code but options would include

  • Moving the description menu over to reedline from nushell
  • Having a separate menu crate that lives in the reedline repo ?
  • Having a separate menu crate that lives inside the nushell organization
    so that both reedline and nushell have access to them along with other
    projects that want a terminal based menu system.

Helper functions

Reedline already has a menu_functions module with some helper functions. We can put more functionality there (other Reedline users will also be able to use them, not just Nushell). This won't require changing the API in any way.

This option may suit can_partially_complete in particular well. Originally, only the columnar menu had an actual implementation for it, but now that the IDE menu uses the same logic, it can be extracted to a helper function.

Moving functionality out of Menu

If it weren't for breaking the API, this might not be a bad idea for replace_in_buffer. The description menu does get an example if applicable, but if we make get_value a method on Menu and replace_in_buffer uses that, the description menu could return a modified Suggestion that has the example text in the value field.

maxomatic458 also suggested extracting name, active, and other fields into a separate struct:

so i guess we could do something like this then

struct MenuCommon {
    name: String,
    active: bool,
    // a bunch of fields that the menus share
}

// and then include this in the menus
struct Menu {
    common: MenuCommon,
    some_specific_thing_for_this_menu: ...
}

this would remove a good bit of code duplication, but then we would need two builders (one for the MenuCommon, and one for the menu itself) to build a menu

As for update_values, the list of suggestions could be maintained completely separately from the menu itself. However, this would be a bit difficult to implement since only the menu knows which value should be selected when you hit down or tab or whatever. A similar approach that wouldn't necessarily require breaking the API would be with a helper struct.

Helper struct to hold suggestions

Like the helper functions option, but this would give less power to the menus. The helper struct could have a method to update suggestions, as well as a can_partially_complete-like method.

The menu could hold the struct as one of its members (instead of having a values: Vec<Suggestion> like it currently does), and we could either

  • change the get_values() method to return this struct (breaking change D:), or
  • we could keep the API exactly the same. Reedline users could choose to use this helper struct to make life easier if they're fine with losing some power.

We could also hold the struct separately and pass it to any methods on Menu that need it. Breaking API change, though.

???

Select a repo