elvish-edit(7)

πŸ“¦
version 0.17.0 Β· 2 versions
, in ubuntu jammy

okay bestie, welcome to elvish-edit(7) πŸ’– β€” the manual page for the edit: module, aka your backstage pass to the Elvish editor. no cap.

The edit: module is literally the interface to the Elvish editor. it's giving backstage pass to the whole editor vibe. ✨

Function usages here are written in the same format as the reference for the builtin module, so if you already know that format you're set, periodt.

This document is incomplete. yes, bestie, it said what it said. πŸ’€

🌸 Overview 🌸

πŸ¦‹ Modes and Submodules πŸ¦‹

The Elvish editor has different modes, and exactly one mode is active at the same time. no multitasking, no main character overlap.

Each mode has its own UI and its own keybindings. like, the default insert mode lets you modify the current command. the completion mode (triggered by Tab by default) shows you all the candidates for completion, and you can use arrow keys to navigate those candidates. slay. πŸ’…

Every mode has its own submodule under edit:. for example, builtin functions and configuration variables for completion mode live in the edit:completion: module. iykyk.

The primary modes supported right now are insert, command, completion, navigation, history, histlist, location, and lastcmd. the last 4 are β€œlisting modes”, and their whole particularity is documented below. πŸ‘‘

πŸ’– Prompts πŸ’–

Elvish has two prompts: the (normal) left-hand prompt and the right-side prompt (rprompt). most of this section only documents the left-hand prompt, but the API for rprompt is the same except for the variable name: just replace prompt with rprompt. easy.

To customize the prompt, assign a function to edit:prompt. that function can write value outputs or byte outputs:

  • Value outputs can be either strings or styled values; they get joined with no spaces in between. not even one. πŸ’…
  • Byte outputs are output as-is, including any newlines. any SGR escape sequences (https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters) included in the byte outputs will be parsed, but any other escape sequences or control character will be removed. so SGR stays, everything else gets yeeted. 🧚

If you mix value and byte outputs, the order they show up in is non-deterministic. so don't count on a cute little lineup.

Prefer using styled to output styled text. the support for SGR escape sequences is mostly there for compatibility with external cross-shell prompts. periodt.

The default prompt and rprompt are equivalent to:

edit:prompt = { tilde-abbr $pwd; put '> ' }
edit:rprompt = (constantly (styled (whoami)@(hostname) inverse))

More prompt functions, bestie:

~> edit:prompt = { tilde-abbr $pwd; styled '> ' green }
~> # ">" is now green
~> edit:prompt = { echo '$' }
$
# Cursor will be on the next line as `echo` outputs a trailing newline

😭 Stale Prompt 😭

Elvish never waits for the prompt function to finish. instead, the prompt function is always executed on a separate thread, and Elvish updates the screen when the function finishes. it's giving parallel universe energy.

But that can be misleading when the function is slow: the prompt on screen may not have the latest info. to deal with that, if the prompt function doesn't finish within a certain threshold β€” by default 0.2 seconds β€” Elvish marks the prompt as stale. it still shows the old stale prompt content, but transforms it using a stale transformer. the default stale transformer applies reverse-video to the whole prompt. it's giving ghosted but make it fashion. πŸ‘»

The threshold is customizable with $edit:prompt-stale-threshold; it specifies the threshold in seconds. set it, bestie.

The transformer is customizable with $edit:prompt-stale-transform. it's a function; the function is called with one argument, a styled text, and the output is interpreted the same way as prompt functions. some examples are:

# The following effectively disables marking of stale prompt.
edit:prompt-stale-transform = [x]{ put $x }
# Show stale prompts in inverse; equivalent to the default.
edit:prompt-stale-transform = [x]{ styled $x inverse }
# Gray out stale prompts.
edit:prompt-stale-transform = [x]{ styled $x bright-black }

To see the transformer in action, try this example (assuming default $edit:prompt-stale-transform):

n = 0
edit:prompt = { sleep 2; put $n; n = (+ $n 1); put ': ' }
edit:-prompt-eagerness = 10 # update prompt on each keystroke
edit:prompt-stale-threshold = 0.5

And then start typing. type one character; the prompt becomes inverse after 0.5 second: that's when Elvish starts to consider the prompt stale. the prompt will return normal after 2 seconds, and the counter in the prompt is updated: that's when the prompt function finishes. ate. πŸ’…

Another thing you'll notice: if you type a few characters quickly (in less than 2 seconds, to be precise), the prompt is only updated twice. that's because Elvish never does two prompt updates in parallel: prompt updates are serialized. if a prompt update is required while the prompt function is still running, Elvish simply queues another update. if an update is already queued, Elvish does not queue another update. why exactly two updates happen in this case, and how this algorithm ensures freshness of the prompt, is left as an exercise to the reader. not me doing homework on main. 😭

⚑ Prompt Eagerness ⚑

The occasions when the prompt should get updated can be controlled with $edit:-prompt-eagerness:

  • The prompt is always updated when the editor becomes active – when Elvish starts, or a command finishes execution, or when the user presses Enter.
  • If $edit:-prompt-eagerness >= 5, it is updated when the working directory changes.
  • If $edit:-prompt-eagerness >= 10, it is updated on each keystroke. keyboard smash = prompt refresh, no cap.

The default value is 5.

πŸ’… RPrompt Persistency πŸ’…

By default, the rprompt is only shown while the editor is active: as soon as you press Enter, it is erased. if you want to keep it, simply set $edit:rprompt-persistent to $true:

edit:rprompt-persistent = $true

πŸŽ€ Keybindings πŸŽ€

Each mode has its own keybinding, accessible as the binding variable in its module. for instance, the binding table for insert mode is $edit:insert:binding. to see current bindings, just print the binding table: pprint $edit:insert:binding (replace insert with any other mode).

The global key binding table, $edit:global-binding, is consulted when a key is not handled by the active mode. it's the fallback bestie.

A binding table is literally just a map that maps keys to functions. for instance, to bind Alt-x in insert mode to exit Elvish, simply do:

edit:insert:binding[Alt-x] = { exit }

Outputs from a bound function always appear above the Elvish prompt. you can see this by doing the following:

edit:insert:binding[Alt-x] = { echo 'output from a bound function!' }

and press Alt-x in insert mode. it lets you put debugging outputs in bound functions without messing up the terminal. main character debugging energy.

Internally, this is implemented by connecting their output to a pipe. that does the correct thing in most cases, but if you're sure you want to do something to the terminal, redirect the output to /dev/tty. since this will break Elvish’s internal tracking of the terminal state, you should also do a full redraw with edit:redraw &full=$true. for instance, the following binds Ctrl-L to clearing the terminal:

edit:insert:binding[Ctrl-L] = { clear > /dev/tty; edit:redraw &full=$true }

(The same functionality is already available as a builtin, edit:clear.)

Bound functions have their inputs redirected to /dev/null. yes, they get no input, periodt.

⌨️ Format of Keys ⌨️

Key modifiers and names are case sensitive. this includes single character key names such as x and Y, as well as function key names such as Enter. no lowercase slander.

Key names have zero or more modifiers from these symbols:

A  Alt
C  Ctrl
M  Meta
S  Shift

Modifiers, if present, end with either a - or +; e.g., S-F1, Ctrl-X or Alt+Enter. you can stack modifiers; e.g., C+A-X. stack 'em like friendship bracelets. 🫢

The key name may be a simple character such as x, or a function key from these symbols:

F1  F2  F3  F4  F5  F6  F7  F8  F9  F10  F11  F12
Up  Down  Right  Left
Home  Insert  Delete  End  PageUp  PageDown
Tab  Enter  Backspace
Note: Tab is an alias for "\t" (aka Ctrl-I), Enter for "\n" (aka Ctrl-J), and Backspace for "\x7F" (aka Ctrl-?).
Note: The Shift modifier is only applicable to function keys such as F1. you cannot write Shift-m as a synonym for M. nope, not a thing. πŸ™ˆ
TODO: Document the behavior of the Shift modifier.

πŸ“‹ Listing Modes πŸ“‹

The modes histlist, loc and lastcmd are all listing modes: they all show a list, and you can filter items and accept items. it's giving picker energy.

Because they're very similar, you might want to change their bindings at the same time. that's made possible by the $edit:listing:binding binding table (listing is not a β€œreal” mode but an β€œabstract” mode). these modes still have their own binding tables like $edit:histlist:binding, and bindings there have higher precedence over those in the shared $edit:listing:binding table. so specific beats shared, periodt.

Moreover, there are a lot of builtin functions in the edit:listing module like edit:listing:down (for moving down selection). they always apply to whichever listing mode is active. iconic.

🚨 Caveat: Bindings to Start Modes 🚨

Note that keybindings to start modes live in the binding table of the insert mode, not the target mode. for instance, if you want to be able to use Alt-l to start location mode, you should modify $edit:insert:binding[Alt-l]:

edit:insert:binding[Alt-l] = { edit:location:start }

One tricky case is history mode. you can press β–²οΈŽ to start searching for history, and keep pressing it to search further. however, when the first press happens, the editor is in insert mode, while with subsequent presses, the editor is in history mode. so this binding actually relies on two entries, $edit:insert:binding[Up] and $edit:history:binding[Up]. two keys, one vibe. πŸ¦‹

So for instance, if you want to be able to use Ctrl-P for this, you need to modify both bindings:

edit:insert:binding[Ctrl-P] =  { edit:history:start }
edit:history:binding[Ctrl-P] = { edit:history:up }

πŸ” Filter DSL πŸ”

The completion, history listing, location and navigation modes all support filtering the items to show using a filter DSL. it uses a small subset of Elvish’s expression syntax, and can be any of the following:

  • A literal string (barewords and single-quoted or double-quoted strings all work) matches items containing the string. if the string is all lower case, the match is done case-insensitively; otherwise the match is case-sensitive. lowercase = chill, uppercase = strict. πŸ’…
  • A list [re $string] matches items matching the regular expression $string. the $string must be a literal string.
  • A list [and $expr...] matches items matching all of the $exprs.
  • A list [or $expr...] matches items matching any of the $exprs.

If the filter contains multiple expressions, they are ANDed, as if surrounded by an implicit [and ...]. so it's all for one and one for all, bestie. 🫢

✨ Completion API ✨

πŸŽ€ Argument Completer πŸŽ€

okay so bestie, Elvish has two whole types of completions: one for internal data and one for command arguments. The internal data one? That's for variable names (like when you type echo $ then hit Tab) and indices (like echo $edit:insert:binding[ then Tab). These are the ones Elvish can do all by itself because they only depend on Elvish's internal state. πŸ’…

The other type? That's when you type something like cat then Tab. Elvish can't just know what to complete for arbitrary commands without full knowledge of the command. So it's programmable.

Command argument completions are programmable via the $edit:completion:arg-completer variable. When Elvish is completing an argument of command $x, it will call the value stored in $edit:completion:arg-completer[$x], with all the existing arguments, plus the command name in the front. 🫢

For example, if the user types man 1 then Tab, Elvish will call:

$edit:completion:arg-completer[man] man 1

If the user is starting a new argument when hitting Tab, Elvish will call the completer with a trailing empty string. For instance, if you do man 1 then Space then Tab, Elvish will call:

$edit:completion:arg-completer[man] man 1 ""

The output of this call becomes candidates. There are several ways of outputting candidates:

  • Writing byte output, e.g. echo cand1; echo cand2. Each line becomes a candidate. This has the drawback that you cannot put newlines in candidates. Only use this if you are sure that your candidates will not contain newlines – e.g. package names, usernames, but not file names, etc..
  • Write strings to value output, e.g. put cand1 cand2. Each string output becomes a candidate.
  • Use the edit:complex-candidate command, e.g.:
edit:complex-candidate &code-suffix='' &display=$stem' ('$description')'  $stem

See edit:complex-candidate for the full description of the arguments it accepts.

After receiving your candidates, Elvish will match your candidates against what the user has typed. Hence, normally you don't need to (and shouldn't) do any matching yourself. That means that in many cases you can (and should) simply ignore the last argument to your completer. However, they can be useful for deciding what kind of things to complete. For instance, if you are to write a completer for ls, you want to see whether the last argument starts with - or not: if it does, complete an option; and if not, complete a filename. πŸ‘‘

Here is a very basic example of configuring a completer for the apt command. It only supports completing the install and remove command and package names after that:

all-packages = [(apt-cache search '' | eawk [0 1 @rest]{ put $1 })]

edit:completion:arg-completer[apt] = [@args]{
    n = (count $args)
    if (== $n 2) {
        # apt x<Tab> -- complete a subcommand name
        put install uninstall
    } elif (== $n 3) {
        put $@all-packages
    }
}

Here is another slightly more complex example for the git command. It supports completing some common subcommands and then branch names after that:

fn all-git-branches {
    # Note: this assumes a recent version of git that supports the format
    # string used.
    git branch -a --format="%(refname:strip=2)" | eawk [0 1 @rest]{ put $1 }
}

common-git-commands = [
  add branch checkout clone commit diff init log merge
  pull push rebase reset revert show stash status
]

edit:arg-completer[git] = [@args]{
    n = (count $args)
    if (== $n 2) {
        put $@common-git-commands
    } elif (>= $n 3) {
        all-git-branches
    }
}

πŸ” Matcher πŸ”

As stated above, after the completer outputs candidates, Elvish matches them with what the user has typed. For clarity, the part of the user input that is relevant to tab completion is called the seed of the completion. For instance, in echo x then Tab, the seed is x. 🌱

Elvish first indexes the matcher table – $edit:completion:matcher – with the completion type to find a matcher. The completion type is currently one of variable, index, command, redir or argument. If the $edit:completion:matcher lacks the suitable key, $edit:completion:matcher[''] is used.

Elvish then calls the matcher with one argument – the seed, and feeds the text of all candidates to the input. The matcher must output an identical number of booleans, indicating whether the candidate should be kept. βœ…βŒ

As an example, the following code configures a prefix matcher for all completion types:

edit:completion:matcher[''] = [seed]{ each [cand]{ has-prefix $cand $seed } }

Elvish provides three builtin matchers, edit:match-prefix, edit:match-substr and edit:match-subseq. In addition to conforming to the matcher protocol, they accept two options &ignore-case and &smart-case. For example, if you want completion of arguments to use prefix matching and ignore case, use:

edit:completion:matcher[argument] = [seed]{ edit:match-prefix $seed &ignore-case=$true }

The default value of $edit:completion:matcher is [&''=$edit:match-prefix~], hence that candidates for all completion types are matched by prefix. πŸ’–


πŸͺ Hooks πŸͺ

Hooks are functions that are executed at certain points in time. In Elvish this functionality is provided by variables that are a list of functions.

NOTE: Hook variables may be initialized with a non-empty list, and you may have modules that add their own hooks. In general you should append to a hook variable rather than assign a list of functions to it. That is, rather than doing set edit:some-hook = [ []{ put 'I ran' } ] you should do set edit:some-hook = [ $@hook-var []{ put 'I ran' } ].

These are the editor/REPL hooks:

  • $edit:before-readline (https://elv.sh/ref/edit.html#editbefore-readline): The functions are called before the editor runs. Each function is called with no arguments.
  • $edit:after-readline (https://elv.sh/ref/edit.html#editafter-readline): The functions are called after the editor accepts a command for execution. Each function is called with a sole argument: the line just read.
  • $edit:after-command (https://elv.sh/ref/edit.html#editafter-command): The functions are called after the shell executes the command you entered (typically by pressing the Enter key). Each function is called with a sole argument: a map that provides information about the executed command. This hook is also called after your interactive RC file is executed and before the first prompt is output.

Example usage:

edit:before-readline = [{ echo 'going to read' }]
edit:after-readline = [[line]{ echo 'just read '$line }]
edit:after-command = [[m]{ echo 'command took '$m[duration]' seconds' }]

Given the above hooks...

  1. Every time you accept a chunk of code (normally by pressing Enter) just read is printed.
  2. At the very beginning of an Elvish session, or after a chunk of code is handled, going to read is printed.
  3. After each non empty chunk of code is accepted and executed the string command took ... seconds is output.

πŸ“ Word types πŸ“

The editor supports operating on entire β€œwords”. As intuitive as the concept of β€œword” is, there is actually no single definition for the concept. The editor supports the following three definitions of words:

  • A big word, or simply word, is a sequence of non-whitespace characters. This definition corresponds to the concept of β€œWORD” in vi.
  • A small word is a sequence of alphanumerical characters (β€œalnum small word”), or a sequence of non-alphanumerical, non-whitespace characters (β€œpunctuation small word”). This definition corresponds to the concept of β€œword” in vi and zsh.
  • An alphanumerical word is a sequence of alphanumerical characters. This definition corresponds to the concept of β€œword” in bash.

Whitespace characters are those with the Unicode Whitespace (https://en.wikipedia.org/wiki/Whitespace_character#Unicode) property. Alphanumerical characters are those in the Unicode Letter or Number category.

A word boundary is an imaginary zero-length boundary around a word.

To see the difference between these definitions, consider the following string: abc++ /* xyz:

  • It contains three (big) words: abc++, /* and xyz.
  • It contains four small words, abc, ++, /* and xyz. Among them, abc and xyz are alnum small words, while ++ and /* are punctuation small words.
  • It contains two alnum words, abc and xyz.

✨ VARIABLES ✨

  • $edit:abbr {#edit:abbr} β€” bestie this is a map from (simple) abbreviations to their expansions, and it's lowkey one of the most useful things ever πŸ’…

So here's the vibe: an abbreviation gets replaced by its expansion the moment you type it in full and consecutively, without getting interrupted by other editing stuff (like, if you move the cursor mid-way, that breaks the consecutiveness, periodt).

If more than one abbreviation would match, the longest one wins. iykyk.

Examples:

edit:abbr['||'] = '| less'
edit:abbr['>dn'] = '2>/dev/null'

With the definitions above, typing || anywhere expands to | less, and typing >dn anywhere expands to 2>/dev/null. BUT β€” typing a |, moving the cursor left, and typing another | does not expand to | less, because the abbreviation || was not typed consecutively. πŸ™ˆ

See also edit:small-word-abbr.


  • $edit:add-cmd-filters {#edit:add-cmd-filters} β€” a list of filters to run before adding a command to history 🌸

A filter is a function that takes a command as argument and outputs a boolean value. If any of the filters outputs $false, the command is not saved to history, and the rest of the filters are not run. The default value of this list contains a filter which ignores command starts with space.

See also edit:command-duration.

  • $edit:after-readline {#edit:after-readline} β€” a list of functions to call after each readline cycle πŸ¦‹ Each function is called with a single string argument containing the code that has been read.
  • $edit:before-readline {#edit:before-readline} β€” a list of functions to call before each readline cycle πŸŽ€ Each function is called without any arguments.
  • $edit:command-duration {#edit:command-duration} β€” duration, in seconds, of the most recent interactive command ⏱️ This can be useful in your prompt to provide feedback on how long a command took to run. The initial value of this variable is the time to evaluate your ~/.elvish/rc.elv script before printing the first prompt.

See also edit:after-command.

  • $edit:command:binding {#edit:command:binding} β€” key bindings for command mode πŸ‘‘ This is currently a very small subset of Vi command mode bindings.

See also edit:command:start.

  • $edit:completion:arg-completer {#edit:completion:arg-completer} β€” a map containing argument completers.
  • $edit:completion:binding {#edit:completion:binding} β€” keybinding for the completion mode.
  • $edit:completion:matcher {#edit:completion:matcher} β€” a map mapping from context names to matcher functions. See the Matcher section.
  • $edit:current-command {#edit:current-command} β€” contains the content of the current input πŸ’– Setting the variable will cause the cursor to move to the very end, as if edit-dot = (count $edit:current-command) has been invoked.
This API is subject to change. πŸ™ˆ
  • $edit:-dot {#edit:-dot} β€” contains the current position of the cursor, as a byte position within $edit:current-command.
  • $edit:exceptions {#edit:exceptions} β€” a list of exceptions thrown from callbacks such as prompts. Useful for examining tracebacks and other metadata.
  • $edit:global-binding {#edit:global-binding} β€” global keybindings, consulted for keys not handled by mode-specific bindings.

See Keybindings.

  • $edit:history:binding {#edit:history:binding} β€” binding table for the history mode.
  • $edit:-instant:binding {#edit:-instant:binding} β€” binding for the instant mode.
  • $edit:location:hidden {#edit:location:hidden} β€” a list of directories to hide in the location addon.
  • $edit:location:pinned {#edit:location:pinned} β€” a list of directories to always show at the top of the list of the location addon πŸ“Œ
  • $edit:location:workspaces {#edit:location:workspaces} β€” a map mapping types of workspaces to their patterns.
  • $edit:max-height {#edit:max-height} β€” maximum height the editor is allowed to use, defaults to +Inf πŸ’€

By default, the height of the editor is only restricted by the terminal height. Some modes like location mode can use a lot of lines; as a result, it can often occupy the entire terminal, and push up your scrollback buffer. Change this variable to a finite number to restrict the height of the editor.

  • $edit:navigation:binding {#edit:navigation:binding} β€” keybinding for the navigation mode.
  • $edit:navigation:width-ratio {#edit:navigation:width-ratio} β€” a list of 3 integers, used for specifying the width ratio of the 3 columns in navigation mode.
  • $edit:prompt {#edit:prompt} β€” See Prompts.
  • $edit:-prompt-eagerness {#edit:-prompt-eagerness} β€” See Prompt Eagerness.
  • $edit:prompt-stale-threshold {#edit:prompt-stale-threshold} β€” See Stale Prompt.
  • $edit:prompt-stale-transformer. {#edit:prompt-stale-transformer.} β€” See Stale Prompt.
  • $edit:rprompt {#edit:rprompt} β€” See Prompts.
  • $edit:-rprompt-eagerness {#edit:-rprompt-eagerness} β€” See Prompt Eagerness.
  • $edit:rprompt-persistent {#edit:rprompt-persistent} β€” See RPrompt Persistency.
  • $edit:rprompt-stale-threshold {#edit:rprompt-stale-threshold} β€” See Stale Prompt.
  • $edit:rprompt-stale-transformer. {#edit:rprompt-stale-transformer.} β€” See Stale Prompt.
  • $edit:selected-file {#edit:selected-file} β€” name of the currently selected file in navigation mode. $nil if not in navigation mode. πŸ’…
  • $edit:small-word-abbr {#edit:small-word-abbr} β€” a map from small-word abbreviations and their expansions 🌈

A small-word abbreviation is replaced by its expansion after it is typed in full and consecutively, and followed by another character (the trigger character). Furthermore, the expansion requires the following conditions to be satisfied:

  • The end of the abbreviation must be adjacent to a small-word boundary, i.e. the last character of the abbreviation and the trigger character must be from two different small-word categories.
  • The start of the abbreviation must also be adjacent to a small-word boundary, unless it appears at the beginning of the code buffer.
  • The cursor must be at the end of the buffer.

If more than one abbreviations would match, the longest one is used.

As an example, with the following configuration:

edit:small-word-abbr['gcm'] = 'git checkout master'

In the following scenarios, the gcm abbreviation is expanded:

  • With an empty buffer, typing gcm and a space or semicolon;
  • When the buffer ends with a space, typing gcm and a space or semicolon.

The space or semicolon after gcm is preserved in both cases. πŸ’–

In the following scenarios, the gcm abbreviation is not expanded:

  • With an empty buffer, typing Xgcm and a space or semicolon (start of abbreviation is not adjacent to a small-word boundary);
  • When the buffer ends with X, typing gcm and a space or semicolon (end of abbreviation is not adjacent to a small-word boundary);
  • When the buffer is non-empty, move the cursor to the beginning, and typing gcm and a space (cursor not at the end of the buffer).

This example shows the case where the abbreviation consists of a single small word of alphanumerical characters, but that doesn't have to be the case. For example, with the following configuration:

edit:small-word-abbr['>dn'] = ' 2>/dev/null'

The abbreviation >dn starts with a punctuation character, and ends with an alphanumerical character. This means that it is expanded when it borders a whitespace or alphanumerical character to the left, and a whitespace or punctuation to the right; for example, typing ls>dn; will expand it. πŸŽ€

Some extra examples of small-word abbreviations:

edit:small-word-abbr['gcp'] = 'git cherry-pick -x'
edit:small-word-abbr['ll'] = 'ls -ltr'

If both a simple abbreviation and a small-word abbreviation can be expanded, the simple abbreviation has priority. πŸ‘‘

See also edit:abbr.


πŸ’œ FUNCTIONS πŸ’œ

edit:add-var {#edit:add-var}

edit:add-var $name $value

Declares a new variable in the REPL. The new variable becomes available during the next REPL cycle ✨

Equivalent to running var $name = $value at the REPL, but $name can be dynamic.

Example:

~> edit:add-var foo bar
~> put $foo
β–Ά bar

edit:add-vars {#edit:add-vars}

edit:add-vars $map

Takes a map from strings to arbitrary values. Equivalent to calling edit:add-var for each key-value pair in the map. πŸ¦‹

edit:binding-table {#edit:binding-table}

Converts a normal map into a binding map.

edit:clear {#edit:clear}

edit:clear

Clears the screen 🌸

This command should be used in place of the external clear command to clear the screen.

edit:close-mode {#edit:close-mode}

Closes the current active mode.

edit:command-history {#edit:command-history}

edit:command-history &cmd-only=$false &dedup=$false &newest-first

Outputs the command history πŸ“œ

By default, each entry is represented as a map, with an id key key for the sequence number of the command, and a cmd key for the text of the command. If &cmd-only is $true, only the text of each command is output.

All entries are output by default. If &dedup is $true, only the most recent instance of each command (when comparing just the cmd key) is output.

Commands are are output in oldest to newest order by default. If &newest-first is $true the output is in newest to oldest order instead.

As an example, either of the following extracts the text of the most recent command:

edit:command-history | put [(all)][-1][cmd]
edit:command-history &cmd-only &newest-first | take 1

See also builtin:dir-history.

edit:command:start {#edit:command:start}

Enter command mode πŸ‘‘ This mode is intended to emulate Vi's command mode, but it is very incomplete right now.

See also edit:command:binding.

edit:complete-filename {#edit:complete-filename}

edit:complete-filename $args...

Produces a list of filenames found in the directory of the last argument. All other arguments are ignored. If the last argument does not contain a path (either absolute or relative to the current directory), then the current directory is used. Relevant files are output as edit:complex-candidate objects. πŸ’…

This function is the default handler for any commands without explicit handlers in $edit:completion:arg-completer. See Argument Completer.

Example:

…and bestie it does NOT stop there, the output just keeps going:

~> edit:complete-filename ''
β–Ά (edit:complex-candidate Applications &code-suffix=/ &style='01;34')
β–Ά (edit:complex-candidate Books &code-suffix=/ &style='01;34')
β–Ά (edit:complex-candidate Desktop &code-suffix=/ &style='01;34')
β–Ά (edit:complex-candidate Docsafe &code-suffix=/ &style='01;34')
β–Ά (edit:complex-candidate Documents &code-suffix=/ &style='01;34')
...
~> edit:complete-filename .elvish/
β–Ά (edit:complex-candidate .elvish/aliases &code-suffix=/ &style='01;34')
β–Ά (edit:complex-candidate .elvish/db &code-suffix=' ' &style='')
β–Ά (edit:complex-candidate .elvish/epm-installed &code-suffix=' ' &style='')
β–Ά (edit:complex-candidate .elvish/lib &code-suffix=/ &style='01;34')
β–Ά (edit:complex-candidate .elvish/rc.elv &code-suffix=' ' &style='')

edit:complete-getopt {#edit:complete-getopt}

edit:complete-getopt $args $opt-specs $arg-handlers

Okay so this one is the big brain completion machine 🌈✨ It spits out completions based on a spec of accepted command-line options (both short AND long options, she does both πŸ’…), positional handler functions for each command position, and the current arguments on the command line. Here's the tea on the arguments:

  • $args β€” an array holding the current arguments on the command line (the command itself is NOT in there, she's not invited). These are the arguments as passed to the Argument Completer function. 🫢
  • $opt-specs β€” an array of maps, each one defining one possible command-line option. Matching options get offered as completions when the last element of $args starts with a dash β€” and only then, no dash no party. Each map can have these keys (at least one of short or long needs to be there, bestie, it's required):
    • short β€” holds the one-letter short option, if any, without the dash.
    • long β€” holds the long option name, if any, without the initial two dashes.
    • arg-optional β€” if set to $true, says this option takes an optional argument.
    • arg-required β€” if set to $true, says this option takes a mandatory argument. ⚠️ Only one of arg-optional or arg-required can be $true, don't be greedy.
    • desc β€” a human-readable description of the option, which gets displayed in the completion menu. Cute and functional πŸŽ€
    • completer β€” a function that generates possible completions for the option argument. It receives as argument the element at that position and returns zero or more candidates.
  • $arg-handlers β€” an array of functions, each one returning the possible completions for that position in the arguments. Each function receives as argument the last element of $args, and should return zero or more possible values for the completions at that point. Those returned values can be plain strings or the output of edit:complex-candidate. ✨ And here's the spicy bit: if the last element of the list is the string ..., then the last handler gets reused for all following arguments. Iconic.

Example, because you know you want it:

~> fn complete {|@args|
     opt-specs = [ [&short=a &long=all &desc="Show all"]
                   [&short=n &desc="Set name" &arg-required=$true
                    &completer= {|_| put name1 name2 }] ]
     arg-handlers = [ {|_| put first1 first2 }
                      {|_| put second1 second2 } ... ]
     edit:complete-getopt $args $opt-specs $arg-handlers
   }
~> complete ''
β–Ά first1
β–Ά first2
~> complete '-'
β–Ά (edit:complex-candidate -a &display='-a (Show all)')
β–Ά (edit:complex-candidate --all &display='--all (Show all)')
β–Ά (edit:complex-candidate -n &display='-n (Set name)')
~> complete -n ''
β–Ά name1
β–Ά name2
~> complete -a ''
β–Ά first1
β–Ά first2
~> complete arg1 ''
β–Ά second1
β–Ά second2
~> complete arg1 arg2 ''
β–Ά second1
β–Ά second2

edit:completion:close {#edit:completion:close}

Closes the completion mode UI. Bye bye completion menu πŸ‘‹

edit:completion:smart-start {#edit:completion:smart-start}

Starts the completion mode. BUT β€” plot twist β€” if all the candidates share a non-empty prefix and that prefix starts with the seed, she inserts the prefix instead. So smart. So efficient. 🧠✨

edit:completion:start {#edit:completion:start}

Start the completion mode. That's it, that's the function πŸ’…

edit:complex-candidate {#edit:complex-candidate}

edit:complex-candidate $stem &display='' &code-suffix=''

Builds a complex candidate. Mainly useful in argument completers β€” that's where she shines 🌟

  • The &display option controls how the candidate is shown in the UI. It can be a string or a styled text. If it's empty, $stem gets used instead. πŸ’–
  • The &code-suffix option affects how the candidate is inserted into the code when it's accepted. By default, a quoted version of $stem is inserted. If &code-suffix is non-empty, it's added to that text, and the suffix is not quoted. πŸŽ€

edit:-dump-buf {#edit:-dump-buf}

Dumps the current UI buffer as HTML. This command is what generates the β€œttyshots” on the website (https://elv.sh). πŸ“Έ

Example:

ttyshot = ~/a.html
edit:insert:binding[Ctrl-X] = { edit:-dump-buf > $tty }

edit:end-of-history {#edit:end-of-history}

Adds a notification saying β€œEnd of history”. That's the whole thing, no cap. πŸ›‘

edit:history:down {#edit:history:down}

Walks to the next entry in history mode. πŸšΆβ€β™€οΈ

edit:history:down-or-quit {#edit:history:down-or-quit}

Walks to the next entry in history mode, or quits the history mode if you're already sitting at the newest entry. She knows when to leave the party 😌

edit:history:fast-forward {#edit:history:fast-forward}

Imports command history entries that happened after the current session started. Catching up on the group chat vibes β©πŸ’¬

edit:history:start {#edit:history:start}

Starts the history mode. πŸ•°οΈ

edit:history:up {#edit:history:up}

Walks to the previous entry in history mode. πŸšΆβ€β™€οΈβ¬†οΈ

edit:insert-at-dot {#edit:insert-at-dot}

edit:insert-at-dot $text

Inserts the given text at the dot, then moves the dot after the newly inserted text. Cursor follows the text, main character behaviour πŸ’…

edit:insert-last-word {#edit:insert-last-word}

Inserts the last word of the last command. So handy, so slay πŸ“

edit:insert-raw {#edit:insert-raw}

Requests the next terminal input to be inserted uninterpreted. No editing, no processing, raw as it comes. 🫢

edit:-instant:start {#edit:-instant:start}

Starts the instant mode. In instant mode, any text you enter at the command line gets evaluated immediately, with the output displayed. ⚑

WARNING: βš οΈπŸ’€ Beware of unintended consequences when using destructive commands. For example, if you type sudo rm -rf /tmp/* in the instant mode, Elvish will attempt to evaluate sudo rm -rf / when you typed that far. Like. Bestie. Please be careful. This is not a drill, this is your whole filesystem. πŸ™ˆ

edit:key {#edit:key}

edit:key $string

Parses a string into a key. 🎹

edit:kill-alnum-word-left {#edit:kill-alnum-word-left}

Deletes the the last alnum word to the left of the dot. (Yes, the double "the the" is in the original, we're keeping it exactly as written, it's part of her charm πŸ’€)

edit:kill-alnum-word-right {#edit:kill-alnum-word-right}

Deletes the the first alnum word to the right of the dot.

edit:kill-line-left {#edit:kill-line-left}

Deletes the text between the dot and the start of the current line. βœ‚οΈ

edit:kill-line-right {#edit:kill-line-right}

Deletes the text between the dot and the end of the current line. βœ‚οΈ

edit:kill-rune-left {#edit:kill-rune-left}

Kills one rune right of the dot. Does nothing if the dot is at the end of the buffer. πŸͺ¦

edit:kill-small-word-left {#edit:kill-small-word-left}

Deletes the the last small word to the left of the dot.

edit:kill-small-word-right {#edit:kill-small-word-right}

Deletes the the first small word to the right of the dot.

edit:kill-word-left {#edit:kill-word-left}

Deletes the the last word to the left of the dot.

edit:kill-word-right {#edit:kill-word-right}

Deletes the the first word to the right of the dot.

edit:listing:accept {#edit:listing:accept}

Accepts the current selected listing item. πŸ’–βœ¨

edit:listing:down {#edit:listing:down}

Moves the cursor down in listing mode. ⬇️

edit:listing:down-cycle {#edit:listing:down-cycle}

Moves the cursor down in listing mode, or wraps to the first item if the last item is currently selected. πŸ”

edit:listing:left {#edit:listing:left}

Moves the cursor left in listing mode. ⬅️

edit:listing:page-down {#edit:listing:page-down}

Moves the cursor down one page. πŸ“„β¬‡οΈ

edit:listing:page-up {#edit:listing:page-up}

Moves the cursor up one page. πŸ“„β¬†οΈ

edit:listing:right {#edit:listing:right}

Moves the cursor right in listing mode. ➑️

edit:listing:start-custom {#edit:listing:start-custom}

Starts a custom listing addon. Go build your own 🌈

edit:listing:up {#edit:listing:up}

Moves the cursor up in listing mode. ⬆️

edit:listing:up-cycle {#edit:listing:up-cycle}

Moves the cursor up in listing mode, or wraps to the last item if the first item is currently selected. πŸ”

edit:match-prefix {#edit:match-prefix}

edit:match-prefix $seed $inputs?

For each input, outputs whether the input has $seed as a prefix. Uses the result of to-string for non-string inputs. ✨

Roughly equivalent to the following Elvish function, but more efficient:

use str
fn match-prefix {|seed @input|
  each {|x| str:has-prefix (to-string $x) $seed } $@input
}

edit:match-subseq {#edit:match-subseq}

edit:match-subseq $seed $inputs?

For each input, outputs whether the input has $seed as a subsequence (https://en.wikipedia.org/wiki/Subsequence). Uses the result of to-string for non-string inputs. πŸ¦‹

edit:match-substr {#edit:match-substr}

edit:match-substr $seed $inputs?

For each input, outputs whether the input has $seed as a substring. Uses the result of to-string for non-string inputs. 🌸

Roughly equivalent to the following Elvish function, but more efficient:

use str
fn match-substr {|seed @input|
  each {|x| str:has-contains (to-string $x) $seed } $@input
}

edit:move-dot-down {#edit:move-dot-down}

Moves the dot down one line, trying to preserve the visual horizontal position. Does nothing if the dot is already on the last line of the buffer. ⬇️

edit:move-dot-eol {#edit:move-dot-eol}

Moves the dot to the end of the current line. 🏁

edit:move-dot-left {#edit:move-dot-left}

Moves the dot left one rune. Does nothing if the dot is at the beginning of the buffer. ⬅️

edit:move-dot-left-alnum-word {#edit:move-dot-left-alnum-word}

Moves the dot to the beginning of the last alnum word to the left of the dot.

edit:move-dot-left-small-word {#edit:move-dot-left-small-word}

Moves the dot to the beginning of the last small word to the left of the dot.

edit:move-dot-left-word {#edit:move-dot-left-word}

Moves the dot to the beginning of the last word to the left of the dot.

edit:move-dot-right {#edit:move-dot-right}

Moves the dot right one rune. Does nothing if the dot is at the end of the buffer. ➑️

edit:move-dot-right-alnum-word

Moves the dot to the beginning of the first alnum word to the right of the dot. ✨

edit:move-dot-right-small-word

Moves the dot to the beginning of the first small word to the right of the dot.

edit:move-dot-right-word

Moves the dot to the beginning of the first word to the right of the dot. πŸ’…

edit:move-dot-sol

Moves the dot to the start of the current line. Periodt.

edit:move-dot-up

Moves the dot up one line, trying to preserve the visual horizontal position. Does nothing if dot is already on the first line of the buffer. It's giving "I'm already at the top, bestie, there's nowhere to go" πŸ¦‹

edit:navigation:insert-selected

Inserts the selected filename. πŸŽ€

edit:navigation:insert-selected-and-quit

Inserts the selected filename and closes the navigation addon. Two things at once, main character energy fr.

edit:navigation:start

Start the navigation mode. Let's gooo ✨

edit:navigation:trigger-filter

Toggles the filtering status of the navigation addon.

edit:navigation:trigger-shown-hidden

Toggles whether the navigation addon should be showing hidden files. πŸ™ˆ


edit:notify

edit:notify $message

Prints a notification message. πŸ“£

If called while the editor is active, this will print the message above the editor, and redraw the editor.

If called while the editor is inactive, the message will be queued, and shown once the editor becomes active. Lowkey so patient of it.

edit:redraw

edit:redraw &full=$false

Triggers a redraw. πŸ”₯

The &full option controls whether to do a full redraw. By default, all redraws performed by the line editor are incremental redraws, updating only the part of the screen that has changed from the last redraw. A full redraw updates the entire command line.

edit:replace-input

edit:replace-input $text

Equivalent to assigning $text to $edit:current-command. Same vibe, shorter way to say it. πŸ’–

edit:return-eof

Causes the Elvish REPL to terminate. If called from a key binding, takes effect after the key binding returns. πŸ‘‘

edit:return-line

Causes the Elvish REPL to end the current read iteration and evaluate the code it just read. If called from a key binding, takes effect after the key binding returns.

edit:smart-enter

Inserts a literal newline if the current code is not syntactically complete Elvish code. Accepts the current line otherwise. It literally reads your mind bestie 🧠✨

edit:transpose-alnum-word

Swaps the alnum words to the left and right of the dot. If the dot is at the beginning of the buffer, it swaps the first two alnum words, and if the dot is at the end, it swaps the last two.

edit:transpose-rune

Swaps the runes to the left and right of the dot. If the dot is at the beginning of the buffer, swaps the first two runes, and if the dot is at the end, it swaps the last two.

edit:transpose-small-word

Swaps the small words to the left and right of the dot. If the dot is at the beginning of the buffer, it swaps the first two small words, and if the dot is at the end, it swaps the last two.

edit:transpose-word

Swaps the words to the left and right of the dot. If the dot is at the beginning of the buffer, swaps the first two words, and the dot is at the end, it swaps the last two.

edit:wordify

edit:wordify $code

Breaks Elvish code into words. πŸ“


And that's a wrap on elvish-edit(7), bestie!! πŸ’œ This whole thing was brought to you by Elvish 0.17.0, dated Nov 18, 2024 β€” go forth and edit slayfully, ily, bye 🫢🌈⭐

πŸ“¦ where this page comes from