Reedline, Nu's line editor

Nushell's line editor Reedlineopen in new window is a cross-platform line reader designed to be modular and flexible. The engine is in charge of controlling the command history, validations, completions, hints and screen paint.

Configuration

Editing mode

Reedline allows you to edit text using two modes: vi and emacs. If not specified, the default edit mode is emacs mode. In order to select your favorite you need to modify your config file and write down your preferred mode.

For example:

  let $config = {
    ...
    edit_mode: emacs
    ...
  }

Default keybindings

Each edit mode comes with the usual keybinding for vi and emacs text editing.

Emacs and Vi Insert keybindings

KeyEvent
EscEsc
BackspaceBackspace
EndMove to end of line
EndComplete history hint
HomeMove to line start
Ctr + cCancel current line
Ctr + lClear screen
Ctr + rSearch history
Ctr + RightComplete history word
Ctr + RightMove word right
Ctr + LeftMove word left
UpMove menu up
UpMove up
DownMove menu down
DownMove down
LeftMove menu left
LeftMove left
RightHistory hint complete
RightMove menu right
RightMove right
Ctr + bMove menu left
Ctr + bMove left
Ctr + fHistory hint complete
Ctr + fMove menu right
Ctr + fMove right
Ctr + pMove menu up
Ctr + pMove up
Ctr + nMove menu down
Ctr + nMove down

Vi Normal keybindings

KeyEvent
Ctr + cCancel current line
Ctr + lClear screen
UpMove menu up
UpMove up
DownMove menu down
DownMove down
LeftMove menu left
LeftMove left
RightMove menu right
RightMove right

Besides the previous keybindings, while in Vi normal mode you can use the classic vi mode of executing actions by selecting a motion or an action. The available options for the combinations are:

Vi Normal motions

Keymotion
wWord
dLine end
0Line start
$Line end
fRight until char
tRight before char
FLeft until char
TLeft before char

Vi Normal actions

Keyaction
dDelete
pPaste after
PPaste before
hMove left
lMove right
jMove down
kMove up
wMove word right
bMove word left
iEnter Vi insert at current char
aEnter Vi insert after char
0Move to start of line
^Move to start of line
$Move to end of line
uUndo
cChange
xDelete char
sHistory search
DDelete to end
AAppend to end

Command history

As mentioned before, Reedline manages and stores all the commands that are edited and sent to Nushell. To configure the max number of records that Reedline should store you will need to adjust this value in your config file:

  let $config = {
    ...
    max_history_size: 1000
    ...
  }

Customizing your prompt

Reedline prompt is also highly customizable. In order to construct your perfect prompt, you could define the next environment variables in your config file:

# Use nushell functions to define your right and left prompt
def create_left_prompt [] {
    let path_segment = ($env.PWD)

    $path_segment
}

def create_right_prompt [] {
    let time_segment = ([
        (date now | date format '%m/%d/%Y %r')
    ] | str collect)

    $time_segment
}

let-env PROMPT_COMMAND = { create_left_prompt }
let-env PROMPT_COMMAND_RIGHT = { create_right_prompt }

TIP

You don't have to define the environment variables using Nushell functions. You can use simple strings to define them.

You can also customize the prompt indicator for the line editor by modifying the next env variables.

let-env PROMPT_INDICATOR = "〉"
let-env PROMPT_INDICATOR_VI_INSERT = ": "
let-env PROMPT_INDICATOR_VI_NORMAL = "〉"
let-env PROMPT_MULTILINE_INDICATOR = "::: "

TIP

The prompt indicators are environment variables that represent the state of the prompt

Keybindings

Reedline keybindings are powerful constructs that let you build chains of events that can be triggered with a specific combination of keys.

For example, let's say that you would like to map the completion menu to the Ctrl + t keybinding (default is tab). You can add the next entry to your config file.

  let $config = {
    ...

    keybindings: [
      {
        name: completion_menu
        modifier: control
        keycode: char_t
        mode: emacs
        event: { send: menu name: completion_menu }
      }
    ]

    ...
  }

After loading this new config.nu, your new keybinding (Ctrl + t) will open the completion command.

Each keybinding requires the next elements:

  • name: Unique name for your keybinding for easy reference in $config.keybindings
  • modifier: A key modifier for the keybinding. The options are:
    • none
    • control
    • alt
    • shift
    • control | alt
    • control | alt | shift
  • keycode: This represent the key to be pressed
  • mode: emacs, vi_insert, vi_normal (a single string or a list. e.g. [vi_insert vi_normal])
  • event: The type of event that is going to be sent by the keybinding. The options are:
    • send
    • edit
    • until

TIP

All of the available modifiers, keycodes and events can be found with the command keybindings list

TIP

The keybindings added to vi_insert mode will be available when the line editor is in insert mode (when you can write text), and the keybindings marked with vi_normal mode will be available when in normal (when the cursor moves using h, j, k or l)

The event section of the keybinding entry is where the actions to be performed are defined. In this field you can use either a record or a list of records. Something like this

  ...
  event: { send: Enter }
  ...

or

  ...
  event: [
    { edit: Clear }
    { send: Enter }
  ]
  ...

The first keybinding example shown in this page follows the first case; a single event is sent to the engine.

The next keybinding is an example of a series of events sent to the engine. It first clears the prompt, inserts a string and then enters that value

  let $config = {
    ...

    keybindings: [
    {
      name: change_dir_with_fzf
      modifier: CONTROL
      keycode: Char_t
      mode: emacs
      event:[
          { edit: Clear }
          { edit: InsertString,
            value: "cd (ls | where type == dir | each { |it| $it.name} | str collect (char nl) | fzf | decode utf-8 | str trim)"

          }
          { send: Enter }
        ]
    }

    ...
  }

One disadvantage of the previous keybinding is the fact that the inserted text will be processed by the validator and saved in the history, making the keybinding a bit slow and populating the command history with the same command. For that reason there is the executehostcommand type of event. The next example does the same as the previous one in a simpler way, sending a single event to the engine

  let $config = {
    ...

    keybindings: [
    {
      name: change_dir_with_fzf
      modifier: CONTROL
      keycode: Char_y
      mode: emacs
      event: {
        send: executehostcommand,
        cmd: "cd (ls | where type == dir | each { |it| $it.name} | str collect (char nl) | fzf | decode utf-8 | str trim)"
      }
    }
  ]

    ...
  }

Before we continue you must have noticed that the syntax changes for edits and sends, and for that reason it is important to explain them a bit more. A send is all the Reedline events that can be processed by the engine and an edit are all the EditCommands that can be processed by the engine.

Send type

To find all the available options for send you can use

keybindings list | where type == events

And the syntax for send events is the next one

    ...
      event: { send: <NAME OF EVENT FROM LIST> }
    ...

TIP

You can write the name of the events with capital letters. The keybinding parser is case insensitive

There are two exceptions to this rule: the Menu and ExecuteHostCommand. Those two events require an extra field to be complete. The Menu needs the name of the menu to be activated (completion_menu or history_menu)

    ...
      event: {
        send: menu
        name: completion_menu
      }
    ...

and the ExecuteHostCommand requires a valid command that will be sent to the engine

    ...
      event: {
        send: executehostcommand
        cmd: "cd ~"
      }
    ...

It is worth mentioning that in the events list you will also see Edit([]), Multiple([]) and UntilFound([]). These options are not available for the parser since they are constructed based on the keybinding definition. For example, a Multiple([]) event is built for you when defining a list of records in the keybinding's event. An Edit([]) event is the same as the edit type that was mentioned. And the UntilFound([]) event is the same as the until type mentioned before.

Edit type

The edit type is the simplification of the Edit([]) event. The event type simplifies defining complex editing events for the keybindings. To list the available options you can use the next command

keybindings list | where type == edits

The usual syntax for an edit is the next one

    ...
      event: { edit: <NAME OF EDIT FROM LIST> }
    ...

The syntax for the edits in the list that have a () changes a little bit. Since those edits require an extra value to be fully defined. For example, if we would like to insert a string where the prompt is located, then you will have to use

    ...
      event: {
        edit: insertstring
        value: "MY NEW STRING"
      }
    ...

or say you want to move right until the first S

    ...
      event: {
        edit: moverightuntil
        value: "S"
      }
    ...

As you can see, these two types will allow you to construct any type of keybinding that you require

Until type

To complete this keybinding tour we need to discuss the until type for event. As you have seen so far, you can send a single event or a list of events. And as we have seen, when a list of events is sent, each and every one of them is processed.

However, there may be cases when you want to assign different events to the same keybinding. This is especially useful with Nushell menus. For example, say you still want to activate your completion menu with Ctrl + t but you also want to move to the next element in the menu once it is activated using the same keybinding.

For these cases, we have the until keyword. The events listed inside the until event will be processed one by one with the difference that as soon as one is successful, the event processing is stopped.

The next keybinding represents this case.

  let $config = {
    ...

    keybindings: [
      {
        name: completion_menu
        modifier: control
        keycode: char_t
        mode: emacs
        event: {
          until: [
            { send: menu name: completion_menu }
            { send: menunext }
          ]
        }
      }
    ]

    ...
  }

The previous keybinding will first try to open a completion menu. If the menu is not active, it will activate it and send a success signal. If the keybinding is pressed again, since there is an active menu, then the next event it will send is MenuNext, which means that it will move the selector to the next element in the menu.

As you can see the until keyword allows us to define two events for the same keybinding. At the moment of this writing, only the Menu events allow this type of layering. The other non menu event types will always return a success value, meaning that the until event will stop as soon as it reaches the command.

For example, the next keybinding will always send a down because that event is always successful

  let $config = {
    ...

    keybindings: [
      {
        name: completion_menu
        modifier: control
        keycode: char_t
        mode: emacs
        event: {
          until: [
            { send: down }
            { send: menu name: completion_menu }
            { send: menunext }
          ]
        }
      }
    ]

    ...
  }

Removing a default keybinding

If you want to remove a certain default keybinding without replacing it with a different action, you can set event: null.

e.g. to disable screen clearing with Ctrl + l for all edit modes

  let $config = {
    ...

    keybindings: [
      {
        modifier: control
        keycode: char_l
        mode: [emacs, vi_normal, vi_insert]
        event: null
      }
    ]

    ...
  }

Troubleshooting keybinding problems

Your terminal environment may not always propagate your key combinations on to nushell the way you expect it to. You can use the command keybindings listen to figure out if certain keypresses are actually received by nushell, and how.

Thanks to Reedline, Nushell has menus that can help you with your day to day shell scripting. Next we present the default menus that are always available when using Nushell

Help menu

The help menu is there to ease your transition into Nushell. Say you are putting together an amazing pipeline and then you forgot the internal command that would reverse a string for you. Instead of deleting your pipe, you can activate the help menu with ctr+q. Once active just type keywords for the command you are looking for and the menu will show you commands that match your input. The matching is done on the name of the commands or the commands description.

To navigate the menu you can select the next element by using tab, you can scroll the description by pressing left or right and you can even paste into the line the available command examples.

The help menu can be configured by modifying the next parameters

  let $config = {
    ...

    menus = [
      ...
      {
        name: help_menu
        only_buffer_difference: true # Search is done on the text written after activating the menu
        marker: "? "                 # Indicator that appears with the menu is active
        type: {
            layout: description      # Type of menu
            columns: 4               # Number of columns where the options are displayed
            col_width: 20            # Optional value. If missing all the screen width is used to calculate column width
            col_padding: 2           # Padding between columns
            selection_rows: 4        # Number of rows allowed to display found options
            description_rows: 10     # Number of rows allowed to display command description
        }
        style: {
            text: green                   # Text style
            selected_text: green_reverse  # Text style for selected option
            description_text: yellow      # Text style for description
        }
      }
      ...
    ]
    ...

Completion menu

The completion menu is a context sensitive menu that will present suggestions based on the status of the prompt. These suggestions can range from path suggestions to command alternatives. While writing a command, you can activate the menu to see available flags for an internal command. Also, if you have defined your custom completions for external commands, these will appear in the menu as well.

The completion menu by default is accessed by pressing tab and it can be configured by modifying these values from the config object:

  let $config = {
    ...

    menus = [
      ...
      {
        name: completion_menu
        only_buffer_difference: false # Search is done on the text written after activating the menu
        marker: "| "                  # Indicator that appears with the menu is active
        type: {
            layout: columnar          # Type of menu
            columns: 4                # Number of columns where the options are displayed
            col_width: 20             # Optional value. If missing all the screen width is used to calculate column width
            col_padding: 2            # Padding between columns
        }
        style: {
            text: green                   # Text style
            selected_text: green_reverse  # Text style for selected option
            description_text: yellow      # Text style for description
        }
      }
      ...
    ]
    ...

By modifying these parameters you can customize the layout of your menu to your liking.

History menu

The history menu is a handy way to access the editor history. When activating the menu (default Ctrl+x) the command history is presented in reverse chronological order, making it extremely easy to select a previous command.

The history menu can be configured by modifying these values from the config object:

  let $config = {
    ...

    menus = [
      ...
      {
        name: help_menu
        only_buffer_difference: true # Search is done on the text written after activating the menu
        marker: "? "                 # Indicator that appears with the menu is active
        type: {
            layout: list             # Type of menu
            page_size: 10            # Number of entries that will presented when activating the menu
        }
        style: {
            text: green                   # Text style
            selected_text: green_reverse  # Text style for selected option
            description_text: yellow      # Text style for description
        }
      }
      ...
    ]
    ...

When the history menu is activated, it pulls page_size records from the history and presents them in the menu. If there is space in the terminal, when you press Ctrl+x again the menu will pull the same number of records and append them to the current page. If it isn't possible to present all the pulled records, the menu will create a new page. The pages can be navigated by pressing Ctrl+z to go to previous page or Ctrl+x to go to next page.

Searching the history

To search in your history you can start typing key words for the command you are looking for. Once the menu is activated, anything that you type will be replaced by the selected command from your history. for example, say that you have already typed this

let a = ()

you can place the cursor inside the () and activate the menu. You can filter the history by typing key words and as soon as you select an entry, the typed words will be replaced

let a = (ls | where size > 10MiB)

Another nice feature of the menu is the ability to quick select something from it. Say you have activated your menu and it looks like this

>
0: ls | where size > 10MiB
1: ls | where size > 20MiB
2: ls | where size > 30MiB
3: ls | where size > 40MiB

Instead of pressing down to select the fourth entry, you can type !3 and press enter. This will insert the selected text in the prompt position, saving you time scrolling down the menu.

History search and quick selection can be used together. You can activate the menu, do a quick search, and then quick select using the quick selection character.

User defined menus

In case you find that the default menus are not enough for you and you have the need to create your own menu, Nushell can help you with that.

In order to add a new menu that fulfills your needs, you can use one of the default layouts as a template. The templates available in nushell are columnar, list or description.

The columnar menu will show you data in a columnar fashion adjusting the column number based on the size of the text displayed in your columns.

The list type of menu will always display suggestions as a list, giving you the option to select values using ! plus number combination.

The description type will give you more space to display a description for some values, together with extra information that could be inserted into the buffer.

Let's say we want to create a menu that displays all the variables created during your session, we are going to call it vars_menu. This menu will use a list layout (layout: list). To search for values, we want to use only the things that are written after the menu has been activated (only_buffer_difference: true).

With that in mind, the desired menu would look like this

  let $config = {
    ...

    menus = [
      ...
      {
        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} }
        }
      }
      ...
    ]
    ...

As you can see, the new menu is identical to the history_menu previously described. The only huge difference is the new field called source. The source field is a nushell definition of the values you want to display in the menu. For this menu we are extracting the data from $nu.scope.vars and we are using it to create records that will be used to populate the menu.

The required structure for the record is the next one

{
  value:       # The value that will be inserted in the buffer
  description: # Optional. Description that will be display with the selected value
  span: {      # Optional. Span indicating what section of the string will be replaced by the value
    start:
    end:
  }
  extra: [string] # Optional. A list of strings that will be displayed with the selected value. Only works with a description menu
}

For the menu to display something, at least the value field has to be present in the resulting record.

In order to make the menu interactive, these two variables are available in the block: $buffer and $position. The $buffer contains the value captured by the menu, when the option only_buffer_difference is true, $buffer is the text written after the menu was activated. If only_buffer_difference is false, $buffer is all the string in line. The $position variable can be used to create replacement spans based on the idea you had for your menu. The value of $position changes based on whether only_buffer_difference is true or false. When true, $position is the starting position in the string where text was inserted after the menu was activated. When the value is false, $position indicates the actual cursor position.

Using this information, you can design your menu to present the information you require and to replace that value in the location you need it. The only thing extra that you need to play with your menu is to define a keybinding that will activate your brand new menu.

In case you want to change the default way both menus are activated, you can change that by defining new keybindings. For example, the next two keybindings assign the completion and history menu to Ctrl+t and Ctrl+y respectively

  let $config = {
    ...

    keybindings: [
      {
        name: completion_menu
        modifier: control
        keycode: char_t
        mode: [vi_insert vi_normal]
        event: {
          until: [
            { send: menu name: completion_menu }
            { send: menupagenext }
          ]
        }
      }
      {
        name: history_menu
        modifier: control
        keycode: char_y
        mode: [vi_insert vi_normal]
        event: {
          until: [
            { send: menu name: history_menu }
            { send: menupagenext }
          ]
        }
      }
    ]

    ...
  }