Refactor: extract history search, completion, and keymap handling into separate methods; support prefix matching for help topics
This commit is contained in:
197
doc/autocmd.txt
Normal file
197
doc/autocmd.txt
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
*autocmd* *autocmds* *hooks*
|
||||||
|
|
||||||
|
#AUTOCMDS#
|
||||||
|
|
||||||
|
Autocmds (automatic commands) execute shell commands in response to
|
||||||
|
specific events. They provide a hook system for customizing shell
|
||||||
|
behavior without modifying the shell itself.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
1. Registration *autocmd-register*
|
||||||
|
|
||||||
|
`autocmd [OPTIONS] {kind} {command}`
|
||||||
|
|
||||||
|
Register {command} to run whenever the event {kind} fires.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
`-p {pattern}` *autocmd-pattern*
|
||||||
|
|
||||||
|
Only run this autocmd when {pattern} (a regex) matches the
|
||||||
|
event's context string. The context varies by event type:
|
||||||
|
for |autocmd-pre-cmd| it is the command being executed, for
|
||||||
|
|autocmd-post-change-dir| it is the target directory, etc.
|
||||||
|
|
||||||
|
`-c` *autocmd-clear*
|
||||||
|
|
||||||
|
Clear all autocmds of the specified {kind}. No command is
|
||||||
|
needed.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
`autocmd post-cmd 'echo "exit: $?"'`
|
||||||
|
`autocmd -p '^git' pre-cmd 'echo running git...'`
|
||||||
|
`autocmd -c pre-cmd`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
2. Event Kinds *autocmd-kinds*
|
||||||
|
|
||||||
|
2.1 Command Execution *autocmd-cmd-events*
|
||||||
|
|
||||||
|
`pre-cmd` *autocmd-pre-cmd*
|
||||||
|
|
||||||
|
Fires before a command is executed. The command string is
|
||||||
|
available for pattern matching.
|
||||||
|
|
||||||
|
`post-cmd` *autocmd-post-cmd*
|
||||||
|
|
||||||
|
Fires after a command finishes. The command string is available
|
||||||
|
for pattern matching.
|
||||||
|
|
||||||
|
2.2 Directory Changes *autocmd-dir-events*
|
||||||
|
|
||||||
|
`pre-change-dir` *autocmd-pre-change-dir*
|
||||||
|
|
||||||
|
Fires before `cd` changes the working directory. The target
|
||||||
|
directory is available for pattern matching.
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
`$_NEW_DIR` the directory being changed to
|
||||||
|
`$_OLD_DIR` the current directory (before the change)
|
||||||
|
|
||||||
|
`post-change-dir` *autocmd-post-change-dir*
|
||||||
|
|
||||||
|
Fires after a successful directory change.
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
`$_NEW_DIR` the new working directory
|
||||||
|
`$_OLD_DIR` the previous directory
|
||||||
|
|
||||||
|
2.3 Job Events *autocmd-job-events*
|
||||||
|
|
||||||
|
`on-job-finish` *autocmd-on-job-finish*
|
||||||
|
|
||||||
|
Fires when a background job completes. The job's command string
|
||||||
|
is available for pattern matching.
|
||||||
|
|
||||||
|
2.4 Prompt Events *autocmd-prompt-events*
|
||||||
|
|
||||||
|
`pre-prompt` *autocmd-pre-prompt*
|
||||||
|
|
||||||
|
Fires before the prompt is rendered. Useful for updating prompt
|
||||||
|
state.
|
||||||
|
|
||||||
|
`post-prompt` *autocmd-post-prompt*
|
||||||
|
|
||||||
|
Fires after the prompt is rendered.
|
||||||
|
|
||||||
|
2.5 Mode Change Events *autocmd-mode-events*
|
||||||
|
|
||||||
|
`pre-mode-change` *autocmd-pre-mode-change*
|
||||||
|
|
||||||
|
Fires before the vi editing mode changes. The `$SHED_VI_MODE`
|
||||||
|
variable still holds the old mode.
|
||||||
|
|
||||||
|
`post-mode-change` *autocmd-post-mode-change*
|
||||||
|
|
||||||
|
Fires after the vi editing mode changes. `$SHED_VI_MODE` reflects
|
||||||
|
the new mode.
|
||||||
|
|
||||||
|
2.6 History Events *autocmd-hist-events*
|
||||||
|
|
||||||
|
`on-history-open` *autocmd-on-history-open*
|
||||||
|
|
||||||
|
Fires when the fuzzy history search window opens.
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
`$_ENTRIES` array of all history entries
|
||||||
|
`$_NUM_ENTRIES` count of all entries
|
||||||
|
`$_MATCHES` array of currently matching entries
|
||||||
|
`$_NUM_MATCHES` count of matching entries
|
||||||
|
`$_SEARCH_STR` the current search string
|
||||||
|
|
||||||
|
`on-history-close` *autocmd-on-history-close*
|
||||||
|
|
||||||
|
Fires when the history search is dismissed without selecting.
|
||||||
|
|
||||||
|
`on-history-select` *autocmd-on-history-select*
|
||||||
|
|
||||||
|
Fires when a history entry is selected. The entry text is
|
||||||
|
available for pattern matching.
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
`$_HIST_ENTRY` the selected history entry
|
||||||
|
|
||||||
|
2.7 Completion Events *autocmd-comp-events*
|
||||||
|
|
||||||
|
`on-completion-start` *autocmd-on-completion-start*
|
||||||
|
|
||||||
|
Fires when the completion menu becomes visible.
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
`$_MATCHES` array of completion candidates
|
||||||
|
`$_NUM_MATCHES` count of candidates
|
||||||
|
`$_SEARCH_STR` the token being completed
|
||||||
|
|
||||||
|
`on-completion-cancel` *autocmd-on-completion-cancel*
|
||||||
|
|
||||||
|
Fires when the completion menu is dismissed without selecting.
|
||||||
|
|
||||||
|
`on-completion-select` *autocmd-on-completion-select*
|
||||||
|
|
||||||
|
Fires when a completion candidate is accepted. The candidate
|
||||||
|
is available for pattern matching.
|
||||||
|
|
||||||
|
Special variables:
|
||||||
|
`$_COMP_CANDIDATE` the selected completion candidate
|
||||||
|
|
||||||
|
2.8 Exit Event *autocmd-exit-event*
|
||||||
|
|
||||||
|
`on-exit` *autocmd-on-exit*
|
||||||
|
|
||||||
|
Fires when the shell is about to exit.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
3. Behavior *autocmd-behavior*
|
||||||
|
|
||||||
|
- Multiple autocmds can be registered for the same event kind. They
|
||||||
|
execute in registration order.
|
||||||
|
|
||||||
|
- If an autocmd command fails, the error is printed but subsequent
|
||||||
|
autocmds for the same event still run.
|
||||||
|
|
||||||
|
- Autocmds do not affect the shell's exit status (`$?`). The exit
|
||||||
|
status is saved before autocmd execution and restored afterward.
|
||||||
|
|
||||||
|
- Pattern matching uses Rust regex syntax. If an autocmd has no
|
||||||
|
pattern, it always fires for its event kind.
|
||||||
|
|
||||||
|
- Special variables (e.g. `$_NEW_DIR`) are only available within the
|
||||||
|
scope of the autocmd execution. They are not set globally.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
4. Examples *autocmd-examples*
|
||||||
|
|
||||||
|
Notify on directory change:
|
||||||
|
|
||||||
|
`autocmd post-change-dir 'echo "moved to $_NEW_DIR"'`
|
||||||
|
|
||||||
|
Run a linter only on git commands:
|
||||||
|
|
||||||
|
`autocmd -p '^git commit' post-cmd 'lint-check'`
|
||||||
|
|
||||||
|
Refresh prompt on mode change (for mode indicator):
|
||||||
|
|
||||||
|
`autocmd post-mode-change 'kill -USR1 $$'`
|
||||||
|
(SIGUSR1 can be used to remotely refresh the prompt. See |prompt|)
|
||||||
|
|
||||||
|
Log completed jobs:
|
||||||
|
|
||||||
|
`autocmd on-job-finish 'echo "job done" >> /tmp/jobs.log'`
|
||||||
|
|
||||||
|
Clean up on exit:
|
||||||
|
|
||||||
|
`autocmd on-exit 'rm -f /tmp/my-shell-*.tmp'`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
See also: |keybinds| |prompt| |ex|
|
||||||
109
doc/ex.txt
Normal file
109
doc/ex.txt
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
*ex* *ex-mode* *ex-commands* *colon-commands*
|
||||||
|
|
||||||
|
#EX MODE#
|
||||||
|
|
||||||
|
Ex mode provides colon commands for operations that go beyond single-key
|
||||||
|
normal mode actions. Enter ex mode by pressing `:` in normal mode.
|
||||||
|
|
||||||
|
The command line supports full editing via insert mode, and has its own
|
||||||
|
command history navigable with `Up` and `Down`.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
1. Shell Commands *ex-shell*
|
||||||
|
|
||||||
|
`:!{cmd}` *ex-bang*
|
||||||
|
|
||||||
|
Execute {cmd} in the shell. The following special variables are
|
||||||
|
set during execution and can be read or modified:
|
||||||
|
|
||||||
|
`$_BUFFER` the current editor buffer contents
|
||||||
|
`$_CURSOR` the cursor position (flat byte index)
|
||||||
|
`$_ANCHOR` the visual selection anchor position
|
||||||
|
|
||||||
|
If the command modifies these variables, the editor state is
|
||||||
|
updated accordingly. This allows ex commands to programmatically
|
||||||
|
edit the buffer.
|
||||||
|
|
||||||
|
If the command sets `$_KEYS`, the value is fed back into the
|
||||||
|
editor as a key sequence.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`:!echo "$_BUFFER" | tr a-z A-Z > /tmp/out`
|
||||||
|
`:!_BUFFER=$(echo "$_BUFFER" | sort)`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
2. File Operations *ex-file*
|
||||||
|
|
||||||
|
`:r {file}` *ex-read*
|
||||||
|
|
||||||
|
Read the contents of {file} and insert them into the buffer at
|
||||||
|
the cursor position.
|
||||||
|
|
||||||
|
`:r !{cmd}` *ex-read-cmd*
|
||||||
|
|
||||||
|
Execute {cmd} and insert its output into the buffer.
|
||||||
|
|
||||||
|
`:w {file}` *ex-write*
|
||||||
|
|
||||||
|
Write the buffer contents to {file}. Creates the file if it does
|
||||||
|
not exist, or truncates it if it does.
|
||||||
|
|
||||||
|
`:w >> {file}` *ex-write-append*
|
||||||
|
|
||||||
|
Append the buffer contents to {file}.
|
||||||
|
|
||||||
|
`:w !{cmd}` *ex-write-cmd*
|
||||||
|
|
||||||
|
Pipe the buffer contents to {cmd} as stdin.
|
||||||
|
|
||||||
|
`:e {file}` *ex-edit*
|
||||||
|
|
||||||
|
Open {file} in the editor defined by `$EDITOR`. Requires the
|
||||||
|
`EDITOR` environment variable to be set.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`:e ~/.config/shed/shedrc`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
3. Buffer Operations *ex-buffer*
|
||||||
|
|
||||||
|
`:d` *ex-delete*
|
||||||
|
|
||||||
|
Delete the entire buffer.
|
||||||
|
|
||||||
|
`:y` *ex-yank*
|
||||||
|
|
||||||
|
Yank the entire buffer into the default register.
|
||||||
|
|
||||||
|
`:pu` *ex-put*
|
||||||
|
|
||||||
|
Put (paste) from the default register after the cursor.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
4. Other Commands *ex-other*
|
||||||
|
|
||||||
|
`:q` *ex-quit*
|
||||||
|
|
||||||
|
Quit the editor / exit the shell.
|
||||||
|
|
||||||
|
`:help {topic}` *ex-help*
|
||||||
|
|
||||||
|
Display help for {topic}. Runs the `help` builtin.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
5. Path Expansion *ex-paths*
|
||||||
|
|
||||||
|
File paths in ex commands are subject to variable expansion. You can
|
||||||
|
use environment variables in paths:
|
||||||
|
|
||||||
|
`:e $HOME/.config/shed/shedrc`
|
||||||
|
`:w ${TMPDIR}/output.txt`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
6. Ex Command History *ex-history*
|
||||||
|
|
||||||
|
Ex mode maintains its own command history, separate from the main
|
||||||
|
shell history. Navigate with `Up` and `Down` while in ex mode.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
See also: |keybinds| |autocmd| |prompt|
|
||||||
461
doc/keybinds.txt
Normal file
461
doc/keybinds.txt
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
*keybinds* *keys* *vi-mode* *keybindings*
|
||||||
|
|
||||||
|
#VI MODE KEYBINDINGS#
|
||||||
|
|
||||||
|
The line editor uses a vi-style modal editing system with six modes:
|
||||||
|
Normal, Insert, Visual, Replace, Ex (command), and Verbatim. The default
|
||||||
|
mode on startup is Insert.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
1. Normal Mode *normal-mode*
|
||||||
|
|
||||||
|
Normal mode is for navigating and manipulating text. Press `Esc` from
|
||||||
|
any other mode to return here.
|
||||||
|
|
||||||
|
1.1 Movement *normal-movement*
|
||||||
|
|
||||||
|
`h` `l` *key-h* *key-l*
|
||||||
|
|
||||||
|
Move left / right by one character.
|
||||||
|
|
||||||
|
`j` `k` *key-j* *key-k*
|
||||||
|
|
||||||
|
Move down / up by one line.
|
||||||
|
|
||||||
|
`w` `W` *key-w* *key-W*
|
||||||
|
|
||||||
|
Move to the start of the next word. `w` stops at punctuation
|
||||||
|
boundaries, `W` only stops at whitespace.
|
||||||
|
|
||||||
|
`b` `B` *key-b* *key-B*
|
||||||
|
|
||||||
|
Move to the start of the previous word.
|
||||||
|
|
||||||
|
`e` `E` *key-e* *key-E*
|
||||||
|
|
||||||
|
Move to the end of the next word.
|
||||||
|
|
||||||
|
`ge` `gE` *key-ge* *key-gE*
|
||||||
|
|
||||||
|
Move to the end of the previous word.
|
||||||
|
|
||||||
|
`0` *key-0*
|
||||||
|
|
||||||
|
Move to the start of the line.
|
||||||
|
|
||||||
|
`^` *key-caret*
|
||||||
|
|
||||||
|
Move to the first non-whitespace character on the line.
|
||||||
|
|
||||||
|
`$` *key-dollar*
|
||||||
|
|
||||||
|
Move to the end of the line.
|
||||||
|
|
||||||
|
`g_` *key-g_*
|
||||||
|
|
||||||
|
Move to the last non-whitespace character on the line.
|
||||||
|
|
||||||
|
`gg` *key-gg*
|
||||||
|
|
||||||
|
Move to the first line of the buffer.
|
||||||
|
|
||||||
|
`G` *key-G*
|
||||||
|
|
||||||
|
Move to the last line of the buffer.
|
||||||
|
|
||||||
|
`|` *key-bar*
|
||||||
|
|
||||||
|
Move to a specific column. `10|` moves to column 10.
|
||||||
|
|
||||||
|
`%` *key-percent*
|
||||||
|
|
||||||
|
Jump to the matching bracket: `()`, `[]`, `{}`.
|
||||||
|
|
||||||
|
`](` `[(` *key-paren-nav*
|
||||||
|
|
||||||
|
Jump to the next / previous unmatched parenthesis.
|
||||||
|
|
||||||
|
`]}` `[{` *key-brace-nav*
|
||||||
|
|
||||||
|
Jump to the next / previous unmatched brace.
|
||||||
|
|
||||||
|
1.2 Character Search *char-search*
|
||||||
|
|
||||||
|
`f{char}` *key-f*
|
||||||
|
|
||||||
|
Move forward to the next occurrence of {char} on the current line.
|
||||||
|
|
||||||
|
`F{char}` *key-F*
|
||||||
|
|
||||||
|
Move backward to the previous occurrence of {char}.
|
||||||
|
|
||||||
|
`t{char}` *key-t*
|
||||||
|
|
||||||
|
Move forward to just before {char}.
|
||||||
|
|
||||||
|
`T{char}` *key-T*
|
||||||
|
|
||||||
|
Move backward to just after {char}.
|
||||||
|
|
||||||
|
`;` *key-semicolon*
|
||||||
|
|
||||||
|
Repeat the last `f`, `F`, `t`, or `T` in the same direction.
|
||||||
|
|
||||||
|
`,` *key-comma*
|
||||||
|
|
||||||
|
Repeat the last `f`, `F`, `t`, or `T` in the reverse direction.
|
||||||
|
|
||||||
|
1.3 Scrolling *scrolling*
|
||||||
|
|
||||||
|
`Ctrl+D` *key-ctrl-d*
|
||||||
|
|
||||||
|
In normal mode, scroll down half a screen. See |viewport|.
|
||||||
|
In insert mode, `Ctrl+D` clears the buffer if there is any content, and exits the shell if there is not.
|
||||||
|
|
||||||
|
`Ctrl+U` *key-ctrl-u*
|
||||||
|
|
||||||
|
Scroll up half a screen.
|
||||||
|
|
||||||
|
`Ctrl+G` *key-ctrl-g*
|
||||||
|
|
||||||
|
Print current cursor position (line, column, total lines).
|
||||||
|
|
||||||
|
1.4 Operators *operators*
|
||||||
|
|
||||||
|
Operators take a {motion} or |text-object| to define the range they
|
||||||
|
act on. Double an operator to act on the whole line (e.g. `dd`, `>>`,
|
||||||
|
`==`).
|
||||||
|
|
||||||
|
`d{motion}` *key-d*
|
||||||
|
|
||||||
|
Delete the text covered by {motion}.
|
||||||
|
|
||||||
|
`dd` delete the whole line
|
||||||
|
`D` delete to end of line (same as `d$`)
|
||||||
|
`x` delete character under cursor (same as `dl`)
|
||||||
|
`X` delete character before cursor (same as `dh`)
|
||||||
|
|
||||||
|
`c{motion}` *key-c*
|
||||||
|
|
||||||
|
Delete the text covered by {motion} and enter insert mode.
|
||||||
|
|
||||||
|
`cc` change the whole line
|
||||||
|
`C` change to end of line (same as `c$`)
|
||||||
|
`s` change the character under cursor (same as `cl`)
|
||||||
|
`S` change the whole line (same as `cc`)
|
||||||
|
|
||||||
|
`y{motion}` *key-y*
|
||||||
|
|
||||||
|
Yank (copy) the text covered by {motion} into a register.
|
||||||
|
|
||||||
|
`yy` yank the whole line
|
||||||
|
`Y` yank the whole line
|
||||||
|
|
||||||
|
`>{motion}` *key-indent*
|
||||||
|
|
||||||
|
Indent the lines covered by {motion}.
|
||||||
|
|
||||||
|
`>>` indent the current line
|
||||||
|
|
||||||
|
`<{motion}` *key-dedent*
|
||||||
|
|
||||||
|
Dedent the lines covered by {motion}.
|
||||||
|
|
||||||
|
`<<` dedent the current line
|
||||||
|
|
||||||
|
`={motion}` *key-equalize*
|
||||||
|
|
||||||
|
Auto-indent the lines covered by {motion}. Uses the minimum
|
||||||
|
nesting depth of each line's start and end to determine the
|
||||||
|
correct indentation level.
|
||||||
|
|
||||||
|
`==` equalize the current line
|
||||||
|
|
||||||
|
`g~{motion}` *key-toggle-case*
|
||||||
|
|
||||||
|
Toggle the case of the text covered by {motion}.
|
||||||
|
|
||||||
|
`gu{motion}` *key-gu*
|
||||||
|
|
||||||
|
Convert the text covered by {motion} to lowercase.
|
||||||
|
|
||||||
|
`gU{motion}` *key-gU*
|
||||||
|
|
||||||
|
Convert the text covered by {motion} to uppercase.
|
||||||
|
|
||||||
|
1.5 Single-Key Actions *normal-actions*
|
||||||
|
|
||||||
|
`p` *key-p*
|
||||||
|
|
||||||
|
Paste from the register after the cursor.
|
||||||
|
|
||||||
|
`P` *key-P*
|
||||||
|
|
||||||
|
Paste from the register before the cursor.
|
||||||
|
|
||||||
|
`r{char}` *key-r*
|
||||||
|
|
||||||
|
Replace the character under the cursor with {char}. With a count,
|
||||||
|
replaces that many characters.
|
||||||
|
|
||||||
|
`~` *key-tilde*
|
||||||
|
|
||||||
|
Toggle the case of the character under the cursor and advance.
|
||||||
|
Accepts a count.
|
||||||
|
|
||||||
|
`J` *key-J*
|
||||||
|
|
||||||
|
Join the current line with the next line.
|
||||||
|
|
||||||
|
`u` *key-u*
|
||||||
|
|
||||||
|
Undo the last change.
|
||||||
|
|
||||||
|
`Ctrl+R` *key-ctrl-r*
|
||||||
|
|
||||||
|
Redo the last undone change.
|
||||||
|
|
||||||
|
`.` *key-dot*
|
||||||
|
|
||||||
|
Repeat the last editing command.
|
||||||
|
|
||||||
|
`Ctrl+A` *key-ctrl-a*
|
||||||
|
|
||||||
|
Increment the number under the cursor. Recognizes decimal,
|
||||||
|
hexadecimal (`0x`), binary (`0b`), and octal (`0o`) formats.
|
||||||
|
Preserves leading zeros and prefix.
|
||||||
|
|
||||||
|
`Ctrl+X` *key-ctrl-x*
|
||||||
|
|
||||||
|
Decrement the number under the cursor.
|
||||||
|
|
||||||
|
1.6 Entering Other Modes *mode-entry*
|
||||||
|
|
||||||
|
`i` insert before cursor *key-i*
|
||||||
|
`a` insert after cursor *key-a*
|
||||||
|
`I` insert at first non-whitespace *key-I*
|
||||||
|
`A` insert at end of line *key-A*
|
||||||
|
`o` open a new line below *key-o*
|
||||||
|
`O` open a new line above *key-O*
|
||||||
|
`R` enter replace mode *key-R*
|
||||||
|
`v` enter visual mode (character-wise) *key-v*
|
||||||
|
`V` enter visual mode (line-wise) *key-V-visual*
|
||||||
|
`gv` reselect last visual region *key-gv*
|
||||||
|
`:` enter ex mode *key-colon*
|
||||||
|
|
||||||
|
1.7 Registers *registers*
|
||||||
|
|
||||||
|
`"{reg}` *key-register*
|
||||||
|
|
||||||
|
Use register {reg} for the next delete, yank, or put. Registers
|
||||||
|
`a`-`z` store text; uppercase `A`-`Z` appends to the corresponding
|
||||||
|
lowercase register.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
2. Insert Mode *insert-mode*
|
||||||
|
|
||||||
|
Insert mode is for typing text. Characters are inserted at the cursor.
|
||||||
|
|
||||||
|
`Esc` return to normal mode
|
||||||
|
`Backspace` delete character before cursor
|
||||||
|
`Ctrl+H` same as Backspace
|
||||||
|
`Ctrl+W` delete word before cursor
|
||||||
|
`Delete` delete character under cursor
|
||||||
|
`Tab` trigger completion (see |completion|)
|
||||||
|
`Shift+Tab` trigger completion (reverse direction)
|
||||||
|
`Ctrl+R` open history search (see |history-search|)
|
||||||
|
`Ctrl+V` enter verbatim mode (insert literal key sequence)
|
||||||
|
`Enter` submit line or insert newline if input is incomplete
|
||||||
|
|
||||||
|
Arrow keys, Home, and End work as expected for navigation.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
3. Visual Mode *visual-mode*
|
||||||
|
|
||||||
|
Visual mode selects a region of text. Enter with `v` (character-wise)
|
||||||
|
or `V` (line-wise) from normal mode.
|
||||||
|
|
||||||
|
All normal-mode motions work to extend the selection. Operators act
|
||||||
|
on the selected region without needing a motion:
|
||||||
|
|
||||||
|
`d` `x` delete selection
|
||||||
|
`c` `s` `S` change selection (delete and enter insert mode)
|
||||||
|
`y` yank selection
|
||||||
|
`p` `P` paste, replacing selection
|
||||||
|
`>` `<` indent / dedent selection
|
||||||
|
`=` equalize selection
|
||||||
|
`~` toggle case of selection
|
||||||
|
`u` lowercase selection
|
||||||
|
`U` uppercase selection
|
||||||
|
`r{char}` replace every character in selection with {char}
|
||||||
|
`J` join selected lines
|
||||||
|
`o` `O` swap cursor and selection anchor
|
||||||
|
|
||||||
|
Press `Esc` to return to normal mode without acting.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
4. Replace Mode *replace-mode*
|
||||||
|
|
||||||
|
Replace mode overwrites existing characters as you type. Enter with
|
||||||
|
`R` from normal mode.
|
||||||
|
|
||||||
|
`Esc` return to normal mode
|
||||||
|
`Backspace` undo the last replacement
|
||||||
|
|
||||||
|
All other keys replace the character under the cursor and advance.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
5. Ex Mode *ex-mode-keys*
|
||||||
|
|
||||||
|
Ex mode accepts colon commands. Enter with `:` from normal mode.
|
||||||
|
See |ex| for available commands.
|
||||||
|
|
||||||
|
`Enter` execute the command
|
||||||
|
`Esc` cancel and return to normal mode
|
||||||
|
`Ctrl+C` clear the command line
|
||||||
|
`Up` `Down` navigate ex command history
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
6. Text Objects *text-objects*
|
||||||
|
|
||||||
|
Text objects define a range of text based on structure. They are used
|
||||||
|
with operators: `d`, `c`, `y`, etc. Each has an "inner" (`i`) and
|
||||||
|
"around" (`a`) variant.
|
||||||
|
|
||||||
|
`iw` `aw` *obj-word*
|
||||||
|
|
||||||
|
Word (punctuation-delimited). `aw` includes trailing whitespace.
|
||||||
|
|
||||||
|
`iW` `aW` *obj-WORD*
|
||||||
|
|
||||||
|
WORD (whitespace-delimited). `aW` includes trailing whitespace.
|
||||||
|
|
||||||
|
`i"` `a"` *obj-dquote*
|
||||||
|
|
||||||
|
Double-quoted string.
|
||||||
|
|
||||||
|
`i'` `a'` *obj-squote*
|
||||||
|
|
||||||
|
Single-quoted string.
|
||||||
|
|
||||||
|
`` i` `` `` a` `` *obj-backtick*
|
||||||
|
|
||||||
|
Backtick-quoted string.
|
||||||
|
|
||||||
|
`i)` `a)` `ib` `ab` *obj-paren*
|
||||||
|
|
||||||
|
Parenthesized block.
|
||||||
|
|
||||||
|
`i]` `a]` *obj-bracket*
|
||||||
|
|
||||||
|
Square-bracketed block.
|
||||||
|
|
||||||
|
`i}` `a}` `iB` `aB` *obj-brace*
|
||||||
|
|
||||||
|
Brace-delimited block.
|
||||||
|
|
||||||
|
`i<` `a<` *obj-angle*
|
||||||
|
|
||||||
|
Angle-bracketed block.
|
||||||
|
|
||||||
|
`it` `at` *obj-tag*
|
||||||
|
|
||||||
|
XML/HTML tag block.
|
||||||
|
|
||||||
|
`is` `as` *obj-sentence*
|
||||||
|
|
||||||
|
Sentence.
|
||||||
|
|
||||||
|
`ip` `ap` *obj-paragraph*
|
||||||
|
|
||||||
|
Paragraph (separated by blank lines).
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
7. Counts *counts*
|
||||||
|
|
||||||
|
Most motions, operators, and actions accept a numeric count prefix:
|
||||||
|
|
||||||
|
`3j` move down 3 lines
|
||||||
|
`2dw` delete 2 words
|
||||||
|
`5>>` indent 5 lines
|
||||||
|
`10l` move 10 characters right
|
||||||
|
|
||||||
|
When both the operator and the motion have counts, they are
|
||||||
|
multiplied: `2d3w` deletes 6 words.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
8. Viewport *viewport*
|
||||||
|
|
||||||
|
When the buffer is taller than the terminal, the editor displays a
|
||||||
|
scrolling viewport. The viewport follows the cursor and respects the
|
||||||
|
`scrolloff` option (minimum lines visible above/below the cursor).
|
||||||
|
|
||||||
|
`Ctrl+D` scroll down half a screen
|
||||||
|
`Ctrl+U` scroll up half a screen
|
||||||
|
`Ctrl+G` display current position in the buffer
|
||||||
|
|
||||||
|
Line numbers in the left margin reflect actual buffer positions, not
|
||||||
|
viewport-relative indices.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
9. User-Defined Keymaps *keymaps* *keymap*
|
||||||
|
|
||||||
|
Custom key bindings are created with the `keymap` command:
|
||||||
|
|
||||||
|
`keymap [flags] {keys} {action}`
|
||||||
|
|
||||||
|
Flags select the mode(s) the binding applies to:
|
||||||
|
|
||||||
|
`-n` normal mode
|
||||||
|
`-i` insert mode
|
||||||
|
`-v` visual mode
|
||||||
|
`-x` ex mode
|
||||||
|
`-o` operator-pending mode
|
||||||
|
`-r` replace mode
|
||||||
|
|
||||||
|
Keys and actions use angle-bracket notation for special keys:
|
||||||
|
|
||||||
|
`<CR>` Enter `<Esc>` Escape
|
||||||
|
`<Tab>` Tab `<BS>` Backspace
|
||||||
|
`<Del>` Delete `<Space>` Space
|
||||||
|
`<Up>` `<Down>` `<Left>` `<Right>` Arrow keys
|
||||||
|
`<Home>` `<End>` Home / End
|
||||||
|
`<F1>` - `<F12>` Function keys
|
||||||
|
`<CMD>` Enter ex mode
|
||||||
|
`<Leader>` Leader key (set via `shopt prompt.leader`)
|
||||||
|
|
||||||
|
Modifier prefixes:
|
||||||
|
|
||||||
|
`C-` Control `A-` `M-` Alt / Meta
|
||||||
|
`S-` Shift
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
`keymap -n <Leader>w ':w<CR>'` Leader+w writes to file
|
||||||
|
`keymap -i jk '<Esc>'` jk exits insert mode
|
||||||
|
`keymap -n <C-n> ':!mycmd<CR>'` Ctrl+n runs a shell command
|
||||||
|
|
||||||
|
To remove a binding:
|
||||||
|
|
||||||
|
`keymap --remove {keys}`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
10. Completion *completion*
|
||||||
|
|
||||||
|
`Tab` start or cycle through completion candidates (forward)
|
||||||
|
`Shift+Tab` cycle backward
|
||||||
|
|
||||||
|
When the completion menu is visible, `Tab` and arrow keys navigate
|
||||||
|
candidates. `Enter` accepts the selected candidate. `Esc` dismisses
|
||||||
|
the menu.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
11. History Search *history-search*
|
||||||
|
|
||||||
|
`Ctrl+R` open fuzzy history search (from insert or ex mode)
|
||||||
|
|
||||||
|
Type to filter history entries. `Enter` accepts the selected entry.
|
||||||
|
`Esc` dismisses the search.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
See also: |ex| |autocmd| |prompt|
|
||||||
229
doc/prompt.txt
Normal file
229
doc/prompt.txt
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
*prompt* *ps1* *psr* *prompt-expansion*
|
||||||
|
|
||||||
|
#PROMPT#
|
||||||
|
|
||||||
|
The shell displays a configurable prompt before each command. Two prompt
|
||||||
|
strings are available: PS1 (the main prompt) and PSR (an optional
|
||||||
|
right-aligned prompt).
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
1. Setting the Prompt *prompt-set*
|
||||||
|
|
||||||
|
The prompt is controlled by the `PS1` and `PSR` environment variables.
|
||||||
|
Set them in your shell configuration:
|
||||||
|
|
||||||
|
`PS1='\u@\h:\W\$ '`
|
||||||
|
`PSR='$SHED_VI_MODE'`
|
||||||
|
|
||||||
|
Prompts are re-expanded before each display, so command substitutions
|
||||||
|
and functions are evaluated every time.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
2. Escape Sequences *prompt-escapes*
|
||||||
|
|
||||||
|
The following backslash escapes are recognized in PS1 and PSR:
|
||||||
|
|
||||||
|
`\u` *prompt-user*
|
||||||
|
|
||||||
|
The current username (from $USER).
|
||||||
|
|
||||||
|
`\h` *prompt-host*
|
||||||
|
|
||||||
|
The hostname (from $HOST).
|
||||||
|
|
||||||
|
`\w` *prompt-pwd*
|
||||||
|
|
||||||
|
The current working directory, with $HOME replaced by `~`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`/home/user/projects` -> `~/projects`
|
||||||
|
|
||||||
|
`\W` *prompt-pwd-short*
|
||||||
|
|
||||||
|
Truncated working directory. Shows only the last N path segments,
|
||||||
|
where N is controlled by `shopt prompt.trunc_prompt_path`
|
||||||
|
(default: 4).
|
||||||
|
|
||||||
|
Example (with trunc_prompt_path=2):
|
||||||
|
`/home/user/projects/myapp` -> `projects/myapp`
|
||||||
|
|
||||||
|
`\s` *prompt-shell*
|
||||||
|
|
||||||
|
The shell name: `shed`.
|
||||||
|
|
||||||
|
`\$` *prompt-symbol*
|
||||||
|
|
||||||
|
`#` if the effective UID is 0 (root), `$` otherwise.
|
||||||
|
|
||||||
|
`\j` *prompt-jobs*
|
||||||
|
|
||||||
|
The number of background jobs currently managed by the shell.
|
||||||
|
|
||||||
|
`\t` *prompt-runtime-ms*
|
||||||
|
|
||||||
|
The runtime of the last command in milliseconds.
|
||||||
|
|
||||||
|
`\T` *prompt-runtime-fmt*
|
||||||
|
|
||||||
|
The runtime of the last command in human-readable format
|
||||||
|
(e.g. `1m 23s 456ms`).
|
||||||
|
|
||||||
|
`\n` *prompt-newline*
|
||||||
|
|
||||||
|
A literal newline. Use this to create multi-line prompts.
|
||||||
|
|
||||||
|
`\r` *prompt-return*
|
||||||
|
|
||||||
|
A literal carriage return.
|
||||||
|
|
||||||
|
`\\` *prompt-backslash*
|
||||||
|
|
||||||
|
A literal backslash.
|
||||||
|
|
||||||
|
`\e[...` *prompt-ansi*
|
||||||
|
|
||||||
|
An ANSI escape sequence. The sequence starts with `\e[` and ends
|
||||||
|
at the first letter character. Used for colors and formatting.
|
||||||
|
|
||||||
|
Common codes:
|
||||||
|
`\e[0m` reset all attributes
|
||||||
|
`\e[1m` bold
|
||||||
|
`\e[3m` italic
|
||||||
|
`\e[4m` underline
|
||||||
|
`\e[31m` red foreground
|
||||||
|
`\e[32m` green foreground
|
||||||
|
`\e[33m` yellow foreground
|
||||||
|
`\e[34m` blue foreground
|
||||||
|
`\e[35m` magenta foreground
|
||||||
|
`\e[36m` cyan foreground
|
||||||
|
`\e[1;32m` bold green
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`PS1='\e[1;32m\u\e[0m@\e[34m\h\e[0m:\w\$ '`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
3. Functions in Prompts *prompt-functions*
|
||||||
|
|
||||||
|
`\@{funcname}` *prompt-func*
|
||||||
|
`\@funcname`
|
||||||
|
|
||||||
|
Call a shell function and insert its output. The function must
|
||||||
|
be defined before the prompt is expanded. If the function does
|
||||||
|
not exist, the literal sequence is displayed.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`git_branch() { git branch --show-current 2>/dev/null; }`
|
||||||
|
`PS1='\u@\h (\@git_branch)\$ '`
|
||||||
|
|
||||||
|
This allows dynamic prompt content that updates on every command.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
4. Right Prompt (PSR) *prompt-right* *psr*
|
||||||
|
|
||||||
|
The PSR variable defines an optional right-aligned prompt displayed
|
||||||
|
on the last line of PS1. It supports the same escape sequences as PS1.
|
||||||
|
|
||||||
|
PSR is only displayed when it fits: if the combined width of the
|
||||||
|
input line and PSR exceeds the terminal width, PSR is hidden.
|
||||||
|
|
||||||
|
PSR is restricted to a single line. If it contains newlines, only the
|
||||||
|
first line is used.
|
||||||
|
|
||||||
|
`PSR='$SHED_VI_MODE'`
|
||||||
|
`PSR='\T'` # show last command runtime on the right
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
5. Multi-Line Prompts *prompt-multiline*
|
||||||
|
|
||||||
|
PS1 may span multiple lines using `\n`. The editor tracks line
|
||||||
|
positions for proper cursor movement and redrawing.
|
||||||
|
|
||||||
|
`PS1='\e[1m\u@\h\e[0m\n\W\$ '`
|
||||||
|
|
||||||
|
This displays the username and hostname on the first line, and the
|
||||||
|
working directory and prompt symbol on the second.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
6. echo -p *echo-prompt*
|
||||||
|
|
||||||
|
The `echo` builtin accepts a `-p` flag that enables prompt-style
|
||||||
|
expansion on its arguments. All prompt escape sequences listed above
|
||||||
|
are recognized.
|
||||||
|
|
||||||
|
`echo -p '\u'` # prints the current username
|
||||||
|
`echo -p '\W'` # prints the truncated working directory
|
||||||
|
`echo -p '\e[31mred\e[0m'` # prints "red" in red
|
||||||
|
|
||||||
|
The `-p` flag can be combined with `-n` (no trailing newline) and
|
||||||
|
`-e` (interpret escape sequences like `\n` and `\t`). When both `-e`
|
||||||
|
and `-p` are used, prompt expansion runs first, then escape sequence
|
||||||
|
interpretation.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
7. Prompt Options (shopt) *prompt-options*
|
||||||
|
|
||||||
|
The following options under `shopt prompt.*` affect prompt behavior:
|
||||||
|
|
||||||
|
`prompt.trunc_prompt_path` *opt-trunc-path*
|
||||||
|
|
||||||
|
Maximum number of path segments shown by `\W`. Default: 4.
|
||||||
|
|
||||||
|
`prompt.highlight` *opt-highlight*
|
||||||
|
|
||||||
|
Enable syntax highlighting in the input line. Default: true.
|
||||||
|
|
||||||
|
`prompt.auto_indent` *opt-auto-indent*
|
||||||
|
|
||||||
|
Automatically indent new lines to match the current nesting
|
||||||
|
depth. Default: true.
|
||||||
|
|
||||||
|
`prompt.linebreak_on_incomplete` *opt-linebreak*
|
||||||
|
|
||||||
|
Insert a newline when Enter is pressed on an incomplete command
|
||||||
|
(e.g. unclosed quotes or pipes). Default: true.
|
||||||
|
|
||||||
|
`prompt.line_numbers` *opt-line-numbers*
|
||||||
|
|
||||||
|
Display line numbers in the left margin for multi-line buffers.
|
||||||
|
Default: true.
|
||||||
|
|
||||||
|
`prompt.leader` *opt-leader*
|
||||||
|
|
||||||
|
The leader key sequence for |keymaps|. Default: `" "` (space).
|
||||||
|
|
||||||
|
`prompt.comp_limit` *opt-comp-limit*
|
||||||
|
|
||||||
|
Maximum number of completion candidates to display. Default: 100.
|
||||||
|
|
||||||
|
`prompt.completion_ignore_case` *opt-comp-case*
|
||||||
|
|
||||||
|
Case-insensitive tab completion. Default: false.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
8. Special Variables *prompt-variables*
|
||||||
|
|
||||||
|
`SHED_VI_MODE` *var-vi-mode*
|
||||||
|
|
||||||
|
Set automatically before each prompt to the current vi mode name:
|
||||||
|
`NORMAL`, `INSERT`, `VISUAL`, `COMMAND`, `REPLACE`, `SEARCH`, or
|
||||||
|
`COMPLETE`. Useful in PSR or prompt functions.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`PSR='$SHED_VI_MODE'`
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
9. Remote Refresh (SIGUSR1) *prompt-sigusr1*
|
||||||
|
|
||||||
|
Sending `SIGUSR1` to the shell process causes it to re-expand and
|
||||||
|
redraw the prompt. This is useful for updating the prompt from
|
||||||
|
external processes (e.g. a background script that detects a state
|
||||||
|
change).
|
||||||
|
|
||||||
|
`kill -USR1 $$`
|
||||||
|
|
||||||
|
Combined with prompt functions (see |prompt-func|), this allows the
|
||||||
|
prompt to reflect changes that happen outside the shell's normal
|
||||||
|
command cycle.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
See also: |keybinds| |autocmd| |ex|
|
||||||
@@ -61,23 +61,32 @@ pub fn help(node: Node) -> ShResult<()> {
|
|||||||
|
|
||||||
let hpath = env::var("SHED_HPATH").unwrap_or_default();
|
let hpath = env::var("SHED_HPATH").unwrap_or_default();
|
||||||
|
|
||||||
|
// search for prefixes of help doc filenames
|
||||||
for path in hpath.split(':') {
|
for path in hpath.split(':') {
|
||||||
let path = Path::new(&path).join(&topic);
|
let dir = Path::new(path);
|
||||||
if path.is_file() {
|
let Ok(entries) = dir.read_dir() else { continue };
|
||||||
let Ok(contents) = std::fs::read_to_string(&path) else {
|
for entry in entries {
|
||||||
|
let Ok(entry) = entry else { continue };
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_file() {
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
let filename = path.file_stem().unwrap().to_string_lossy().to_string();
|
let stem = path.file_stem().unwrap().to_string_lossy();
|
||||||
|
if stem.starts_with(&topic) {
|
||||||
|
let Ok(contents) = std::fs::read_to_string(&path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let unescaped = unescape_help(&contents);
|
let unescaped = unescape_help(&contents);
|
||||||
let expanded = expand_help(&unescaped);
|
let expanded = expand_help(&unescaped);
|
||||||
open_help(&expanded, None, Some(filename))?;
|
open_help(&expanded, None, Some(stem.into_owned()))?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// didn't find an exact filename match, its probably a tag search
|
// didn't find a filename match, its probably a tag search
|
||||||
for path in hpath.split(':') {
|
for path in hpath.split(':') {
|
||||||
let path = Path::new(path);
|
let path = Path::new(path);
|
||||||
if let Ok(entries) = path.read_dir() {
|
if let Ok(entries) = path.read_dir() {
|
||||||
|
|||||||
@@ -444,6 +444,173 @@ impl ShedVi {
|
|||||||
Ok(is_complete && is_top_level)
|
Ok(is_complete && is_top_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_hist_search_key(&mut self, key: KeyEvent) -> ShResult<()> {
|
||||||
|
self.print_line(false)?;
|
||||||
|
match self.focused_history().fuzzy_finder.handle_key(key)? {
|
||||||
|
SelectorResponse::Accept(cmd) => {
|
||||||
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||||
|
|
||||||
|
{
|
||||||
|
let editor = self.focused_editor();
|
||||||
|
editor.set_buffer(cmd.to_string());
|
||||||
|
editor.move_cursor_to_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
.history
|
||||||
|
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||||
|
self.editor.set_hint(None);
|
||||||
|
{
|
||||||
|
let mut writer = std::mem::take(&mut self.writer);
|
||||||
|
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||||
|
self.writer = writer;
|
||||||
|
}
|
||||||
|
self.focused_history().fuzzy_finder.reset();
|
||||||
|
|
||||||
|
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
||||||
|
post_cmds.exec_with(&cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
write_vars(|v| {
|
||||||
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
self.needs_redraw = true;
|
||||||
|
}
|
||||||
|
SelectorResponse::Dismiss => {
|
||||||
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose));
|
||||||
|
post_cmds.exec();
|
||||||
|
|
||||||
|
self.editor.set_hint(None);
|
||||||
|
{
|
||||||
|
let mut writer = std::mem::take(&mut self.writer);
|
||||||
|
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
||||||
|
self.writer = writer;
|
||||||
|
}
|
||||||
|
write_vars(|v| {
|
||||||
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
self.needs_redraw = true;
|
||||||
|
}
|
||||||
|
SelectorResponse::Consumed => {
|
||||||
|
self.needs_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_completion_key(&mut self, key: &KeyEvent) -> ShResult<bool> {
|
||||||
|
self.print_line(false)?;
|
||||||
|
match self.completer.handle_key(key.clone())? {
|
||||||
|
CompResponse::Accept(candidate) => {
|
||||||
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
|
||||||
|
|
||||||
|
let span_start = self.completer.token_span().0;
|
||||||
|
let new_cursor = span_start + candidate.len();
|
||||||
|
let line = self.completer.get_completed_line(&candidate);
|
||||||
|
self.focused_editor().set_buffer(line);
|
||||||
|
self.focused_editor().set_cursor_from_flat(new_cursor);
|
||||||
|
// Don't reset yet — clear() needs old_layout to erase the selector.
|
||||||
|
|
||||||
|
if !self.history.at_pending() {
|
||||||
|
self.history.reset_to_pending();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
.history
|
||||||
|
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
||||||
|
let hint = self.history.get_hint();
|
||||||
|
self.editor.set_hint(hint);
|
||||||
|
self.completer.clear(&mut self.writer)?;
|
||||||
|
self.needs_redraw = true;
|
||||||
|
self.completer.reset();
|
||||||
|
|
||||||
|
write_vars(|v| {
|
||||||
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
|
||||||
|
with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || {
|
||||||
|
post_cmds.exec_with(&candidate);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
CompResponse::Dismiss => {
|
||||||
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel));
|
||||||
|
post_cmds.exec();
|
||||||
|
|
||||||
|
let hint = self.history.get_hint();
|
||||||
|
self.editor.set_hint(hint);
|
||||||
|
self.completer.clear(&mut self.writer)?;
|
||||||
|
write_vars(|v| {
|
||||||
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
self.completer.reset();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
CompResponse::Consumed => {
|
||||||
|
/* just redraw */
|
||||||
|
self.needs_redraw = true;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
CompResponse::Passthrough => Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_keymap(&mut self, key: KeyEvent) -> ShResult<Option<ReadlineEvent>> {
|
||||||
|
let keymap_flags = self.curr_keymap_flags();
|
||||||
|
self.pending_keymap.push(key.clone());
|
||||||
|
|
||||||
|
let matches = read_logic(|l| l.keymaps_filtered(keymap_flags, &self.pending_keymap));
|
||||||
|
if matches.is_empty() {
|
||||||
|
// No matches. Drain the buffered keys and execute them.
|
||||||
|
for key in std::mem::take(&mut self.pending_keymap) {
|
||||||
|
if let Some(event) = self.handle_key(key)? {
|
||||||
|
return Ok(Some(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.needs_redraw = true;
|
||||||
|
} else if matches.len() == 1
|
||||||
|
&& matches[0].compare(&self.pending_keymap) == KeyMapMatch::IsExact
|
||||||
|
{
|
||||||
|
// We have a single exact match. Execute it.
|
||||||
|
let keymap = matches[0].clone();
|
||||||
|
self.pending_keymap.clear();
|
||||||
|
let action = keymap.action_expanded();
|
||||||
|
for key in action {
|
||||||
|
if let Some(event) = self.handle_key(key)? {
|
||||||
|
return Ok(Some(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is ambiguity. Allow the timeout in the main loop to handle this.
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Process any available input and return readline event
|
/// Process any available input and return readline event
|
||||||
/// This is non-blocking - returns Pending if no complete line yet
|
/// This is non-blocking - returns Pending if no complete line yet
|
||||||
pub fn process_input(&mut self) -> ShResult<ReadlineEvent> {
|
pub fn process_input(&mut self) -> ShResult<ReadlineEvent> {
|
||||||
@@ -457,138 +624,11 @@ impl ShedVi {
|
|||||||
while let Some(key) = self.reader.read_key()? {
|
while let Some(key) = self.reader.read_key()? {
|
||||||
// If completer or history search are active, delegate input to it
|
// If completer or history search are active, delegate input to it
|
||||||
if self.focused_history().fuzzy_finder.is_active() {
|
if self.focused_history().fuzzy_finder.is_active() {
|
||||||
self.print_line(false)?;
|
self.handle_hist_search_key(key)?;
|
||||||
match self.focused_history().fuzzy_finder.handle_key(key)? {
|
continue;
|
||||||
SelectorResponse::Accept(cmd) => {
|
} else if self.completer.is_active() && self.handle_completion_key(&key)? {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
// self.handle_completion_key() returns true if we need to continue the loop
|
||||||
|
continue;
|
||||||
{
|
|
||||||
let editor = self.focused_editor();
|
|
||||||
editor.set_buffer(cmd.to_string());
|
|
||||||
editor.move_cursor_to_end();
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
.history
|
|
||||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
|
||||||
self.editor.set_hint(None);
|
|
||||||
{
|
|
||||||
let mut writer = std::mem::take(&mut self.writer);
|
|
||||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
|
||||||
self.writer = writer;
|
|
||||||
}
|
|
||||||
self.focused_history().fuzzy_finder.reset();
|
|
||||||
|
|
||||||
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
|
||||||
post_cmds.exec_with(&cmd);
|
|
||||||
});
|
|
||||||
|
|
||||||
write_vars(|v| {
|
|
||||||
v.set_var(
|
|
||||||
"SHED_VI_MODE",
|
|
||||||
VarKind::Str(self.mode.report_mode().to_string()),
|
|
||||||
VarFlags::NONE,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
self.prompt.refresh();
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
SelectorResponse::Dismiss => {
|
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose));
|
|
||||||
post_cmds.exec();
|
|
||||||
|
|
||||||
self.editor.set_hint(None);
|
|
||||||
{
|
|
||||||
let mut writer = std::mem::take(&mut self.writer);
|
|
||||||
self.focused_history().fuzzy_finder.clear(&mut writer)?;
|
|
||||||
self.writer = writer;
|
|
||||||
}
|
|
||||||
write_vars(|v| {
|
|
||||||
v.set_var(
|
|
||||||
"SHED_VI_MODE",
|
|
||||||
VarKind::Str(self.mode.report_mode().to_string()),
|
|
||||||
VarFlags::NONE,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
self.prompt.refresh();
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
SelectorResponse::Consumed => {
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if self.completer.is_active() {
|
|
||||||
self.print_line(false)?;
|
|
||||||
match self.completer.handle_key(key.clone())? {
|
|
||||||
CompResponse::Accept(candidate) => {
|
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
|
|
||||||
|
|
||||||
let span_start = self.completer.token_span().0;
|
|
||||||
let new_cursor = span_start + candidate.len();
|
|
||||||
let line = self.completer.get_completed_line(&candidate);
|
|
||||||
self.focused_editor().set_buffer(line);
|
|
||||||
self.focused_editor().set_cursor_from_flat(new_cursor);
|
|
||||||
// Don't reset yet — clear() needs old_layout to erase the selector.
|
|
||||||
|
|
||||||
if !self.history.at_pending() {
|
|
||||||
self.history.reset_to_pending();
|
|
||||||
}
|
|
||||||
self
|
|
||||||
.history
|
|
||||||
.update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat()));
|
|
||||||
let hint = self.history.get_hint();
|
|
||||||
self.editor.set_hint(hint);
|
|
||||||
self.completer.clear(&mut self.writer)?;
|
|
||||||
self.needs_redraw = true;
|
|
||||||
self.completer.reset();
|
|
||||||
|
|
||||||
write_vars(|v| {
|
|
||||||
v.set_var(
|
|
||||||
"SHED_VI_MODE",
|
|
||||||
VarKind::Str(self.mode.report_mode().to_string()),
|
|
||||||
VarFlags::NONE,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
self.prompt.refresh();
|
|
||||||
|
|
||||||
with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || {
|
|
||||||
post_cmds.exec_with(&candidate);
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CompResponse::Dismiss => {
|
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel));
|
|
||||||
post_cmds.exec();
|
|
||||||
|
|
||||||
let hint = self.history.get_hint();
|
|
||||||
self.editor.set_hint(hint);
|
|
||||||
self.completer.clear(&mut self.writer)?;
|
|
||||||
write_vars(|v| {
|
|
||||||
v.set_var(
|
|
||||||
"SHED_VI_MODE",
|
|
||||||
VarKind::Str(self.mode.report_mode().to_string()),
|
|
||||||
VarFlags::NONE,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
self.prompt.refresh();
|
|
||||||
self.completer.reset();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CompResponse::Consumed => {
|
|
||||||
/* just redraw */
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CompResponse::Passthrough => { /* fall through to normal handling below */ }
|
|
||||||
}
|
|
||||||
} else if self.mode.pending_seq().is_some_and(|seq| !seq.is_empty()) {
|
} else if self.mode.pending_seq().is_some_and(|seq| !seq.is_empty()) {
|
||||||
// Vi mode is waiting for more input (e.g. after 'f', 'd', etc.)
|
// Vi mode is waiting for more input (e.g. after 'f', 'd', etc.)
|
||||||
// Bypass keymap matching and send directly to the mode handler
|
// Bypass keymap matching and send directly to the mode handler
|
||||||
@@ -597,42 +637,8 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else if let Some(event) = self.handle_keymap(key)? {
|
||||||
let keymap_flags = self.curr_keymap_flags();
|
return Ok(event);
|
||||||
self.pending_keymap.push(key.clone());
|
|
||||||
|
|
||||||
let matches = read_logic(|l| l.keymaps_filtered(keymap_flags, &self.pending_keymap));
|
|
||||||
if matches.is_empty() {
|
|
||||||
// No matches. Drain the buffered keys and execute them.
|
|
||||||
for key in std::mem::take(&mut self.pending_keymap) {
|
|
||||||
if let Some(event) = self.handle_key(key)? {
|
|
||||||
return Ok(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
} else if matches.len() == 1
|
|
||||||
&& matches[0].compare(&self.pending_keymap) == KeyMapMatch::IsExact
|
|
||||||
{
|
|
||||||
// We have a single exact match. Execute it.
|
|
||||||
let keymap = matches[0].clone();
|
|
||||||
self.pending_keymap.clear();
|
|
||||||
let action = keymap.action_expanded();
|
|
||||||
for key in action {
|
|
||||||
if let Some(event) = self.handle_key(key)? {
|
|
||||||
return Ok(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// There is ambiguity. Allow the timeout in the main loop to handle this.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(event) = self.handle_key(key)? {
|
|
||||||
return Ok(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() {
|
if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() {
|
||||||
|
|||||||
Reference in New Issue
Block a user