mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Extended & human-friendly keys
See the changelog additions for user-visible changes. Since we enable/disable terminal protocols whenever we pass terminal ownership, tests can no longer run in parallel on the same terminal. For the same reason, readline shortcuts in the gdb REPL will not work anymore. As a remedy, use gdbserver, or lobby for CSI u support in libreadline. Add sleep to some tests, otherwise they fall (both in CI and locally). There are two weird failures on FreeBSD remaining, disable them for now https://github.com/fish-shell/fish-shell/pull/10359/checks?check_run_id=23330096362 Design and implementation borrows heavily from Kakoune. In future, we should try to implement more of the kitty progressive enhancements. Closes #10359
This commit is contained in:
parent
8ada027f05
commit
8bf8b10f68
180 changed files with 2203 additions and 1304 deletions
|
@ -12,7 +12,7 @@ fish 3.8.0 (released ???)
|
|||
10198 10200 10201 10204 10210 10214 10219 10223 10227 10232 10235 10237 10243 10244 10245
|
||||
10246 10251 10260 10267 10281 10347 10366 10368 10370 10371 10263 10270 10272 10276 10277
|
||||
10278 10279 10291 10293 10305 10306 10309 10316 10317 10327 10328 10329 10330 10336 10340
|
||||
10345 10346 10353 10354 10356 10372 10373 3299 10360
|
||||
10345 10346 10353 10354 10356 10372 10373 3299 10360 10359
|
||||
|
||||
The entirety of fish's C++ code has been ported to Rust (:issue:`9512`).
|
||||
This means a large change in dependencies and how to build fish.
|
||||
|
@ -21,6 +21,24 @@ Packagers should see the :ref:`For Distributors <rust-packaging>` section at the
|
|||
Notable backwards-incompatible changes
|
||||
--------------------------------------
|
||||
|
||||
- Fish now decodes keyboard input into human-readable key names.
|
||||
To make this for for a wide range of terminals, fish asks terminals to speak several keyboard protocols,
|
||||
including CSI u, XTerm's ``modifyOtherKeys`` and some progressive enhancements from the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/).
|
||||
Depending on terminal support, this allows to bind a lot more key combinations,
|
||||
including arbitrary combinations of modifiers ``ctrl``, ``alt`` and ``shift``.
|
||||
|
||||
This comes with a new syntax for specifying keys to builtin ``bind``.
|
||||
The new syntax introduces modifier names and names for some keys that don't have an obvious and printable Unicode code point.
|
||||
The old syntax remains mostly supported but the new one is preferred.
|
||||
- Existing bindings that use the new names have a different meaning now.
|
||||
For example
|
||||
- ``bind up 'do something'`` binds the up arrow key instead of a two-key sequence.
|
||||
- ``bind ctrl-x,alt-c 'do something'`` binds a sequence of two keys.
|
||||
Since ``,`` and ``-`` act as separators, there are some cases where they need to be written as ``comma`` and ``minus`` respectively.
|
||||
- To minimize gratuitous breakage, the key argument to ``bind`` is parsed using the old syntax in two cases:
|
||||
- If key starts with a raw escape character (``\e``) or a raw ASCII control character (``\c``).
|
||||
- If key consists of exactly two characters, contains none of ``,`` or ``-`` and is not a named key.
|
||||
|
||||
- ``random`` now uses a different random number generator and so the values you get even with the same seed have changed.
|
||||
Notably, it will now work much more sensibly with very small seeds.
|
||||
The seed was never guaranteed to give the same result across systems,
|
||||
|
@ -39,7 +57,7 @@ Notable backwards-incompatible changes
|
|||
Notable improvements and fixes
|
||||
------------------------------
|
||||
- New function ``fish_should_add_to_history`` can be overridden to decide whether a command should be added to the history (:issue:`10302`).
|
||||
- :kbd:`Control-C` during command input no longer prints ``^C`` and a new prompt but merely clears the command line. This restores the behavior from version 2.2. To revert to the old behavior use ``bind \cc __fish_cancel_commandline`` (:issue:`10213`).
|
||||
- :kbd:`Control-C` during command input no longer prints ``^C`` and a new prompt but merely clears the command line. This restores the behavior from version 2.2. To revert to the old behavior use ``bind ctrl-c __fish_cancel_commandline`` (:issue:`10213`).
|
||||
- The :kbd:`Control-R` history search now uses glob syntax (:issue:`10131`).
|
||||
- The :kbd:`Control-R` history search now operates only on the line at cursor, making it easier to quickly compose a multi-line commandline by recalling previous commands.
|
||||
|
||||
|
@ -48,6 +66,12 @@ Deprecations and removed features
|
|||
|
||||
- ``commandline --tokenize`` (short option ``-o``) has been deprecated in favor of ``commandline --tokens-expanded`` (short option ``-x``) which expands variables and other shell expressions, removing the need to use "eval" in custom completions (:issue:`10212`).
|
||||
- A new feature flag, ``remove-percent-self`` (see ``status features``) disables PID expansion of ``%self`` which has been supplanted by ``$fish_pid`` (:issue:`10262`).
|
||||
- Specifying key names as terminfo name (``bind -k``) is deprecated and may be removed in a future version.
|
||||
- Flow control -- which if enabled by ``stty ixon ixoff`` allows to pause terminal input with ``ctrl-s`` and resume it with ``ctrl-q`` -- now works only while fish is executing an external command.
|
||||
- When a terminal pastes text into fish using bracketed paste, fish used to switch to a special ``paste`` bind mode.
|
||||
This bind mode has been removed. The behavior on paste is currently not meant to be configurable.
|
||||
- When fish is stopped or terminated by a signal that cannot be caught (SIGSTOP or SIGKILL), it may leave the terminal in a state where keypresses with modifiers are sent as CSI u sequences instead of traditional control characters or escape sequecnes (that are recognized by bash/readline). If this happens, you can use the ``reset`` command from ``ncurses`` to restore the terminal state.
|
||||
- ``fish_key_reader --verbose`` is now ignored, so it no longer shows raw byte values or timing information. Since fish now decodes keys, this should no longer be necessary.
|
||||
|
||||
Scripting improvements
|
||||
----------------------
|
||||
|
@ -78,13 +102,14 @@ Interactive improvements
|
|||
|
||||
New or improved bindings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Bindings can now mix special input functions and shell commands, so ``bind \cg expand-abbr "commandline -i \n"`` works as expected (:issue:`8186`).
|
||||
- Bindings can now mix special input functions and shell commands, so ``bind ctrl-g expand-abbr "commandline -i \n"`` works as expected (:issue:`8186`).
|
||||
- When the cursor is on a command that resolves to an executable script, :kbd:`Alt-O` will now open that script in your editor (:issue:`10266`).
|
||||
- Two improvements to the :kbd:`Alt-E` binding which edits the commandline in an external editor:
|
||||
- The editor's cursor position is copied back to fish. This is currently supported for Vim and Kakoune.
|
||||
- Cursor position synchronization is only supported for a set of known editors. This has been extended by also resolving aliases. For example use ``complete --wraps my-vim vim`` to synchronize cursors when `EDITOR=my-vim`.
|
||||
- ``backward-kill-path-component`` and friends now treat ``#`` as part of a path component (:issue:`10271`).
|
||||
- The ``E`` binding in vi mode now correctly handles the last character of the word, by jumping to the next word (:issue:`9700`).
|
||||
- If the terminal supports shifted key codes from the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), ``shift-enter`` now inserts a newline instead of executing the command line.
|
||||
- Vi mode has seen some improvements but continues to suffer from the lack of people working on it.
|
||||
- Insert-mode :kbd:`Control-N` accepts autosuggestions (:issue:`10339`).
|
||||
- Outside insert mode, the cursor will no longer be placed beyond the last character on the commandline.
|
||||
|
@ -103,6 +128,8 @@ Completions
|
|||
Improved terminal support
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Fish now sets the terminal window title unconditionally (:issue:`10037`).
|
||||
- Focus reporting is enabled unconditionally, not just inside tmux.
|
||||
To use it, define functions that handle events ``fish_focus_in`` and ``fish_focus_out``.
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
|
|
|
@ -34,7 +34,12 @@ def get_prompt_re(counter):
|
|||
"""Return a regular expression for matching a with a given prompt counter."""
|
||||
return re.compile(
|
||||
r"""(?:\r\n?|^) # beginning of line
|
||||
(?:\x1b[\d\[KB(m]*)* # optional colors
|
||||
(?:\x1b[\d[KB(m]*)* # optional colors
|
||||
(?:\x1b[\?2004h) # Bracketed paste
|
||||
(?:\x1b[>4;1m) # XTerm's modifyOtherKeys
|
||||
(?:\x1b[>5u) # CSI u with kitty progressive enhancement
|
||||
(?:\x1b=) # set application keypad mode, so the keypad keys send unique codes
|
||||
(?:\x1b[\?1004h)? # enable focus notify
|
||||
(?:\[.\]\ )? # optional vi mode prompt
|
||||
"""
|
||||
+ (r"prompt\ %d>" % counter) # prompt with counter
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
# This adds ctest support to the project
|
||||
enable_testing()
|
||||
|
||||
# By default, ctest runs tests serially
|
||||
if(NOT CTEST_PARALLEL_LEVEL)
|
||||
include(ProcessorCount)
|
||||
ProcessorCount(CORES)
|
||||
set(CTEST_PARALLEL_LEVEL ${CORES})
|
||||
endif()
|
||||
|
||||
# Put in a tests folder to reduce the top level targets in IDEs.
|
||||
set(CMAKE_FOLDER tests)
|
||||
|
||||
|
@ -24,8 +17,6 @@ set(SKIP_RETURN_CODE 125)
|
|||
# running `make test` does not require any of the binaries to be built before testing.
|
||||
# * The only way to have a test depend on a binary is to add a fake test with a name like
|
||||
# "build_fish" that executes CMake recursively to build the `fish` target.
|
||||
# * It is not possible to set top-level CTest options/settings such as CTEST_PARALLEL_LEVEL from
|
||||
# within the CMake configuration file.
|
||||
# * Circling back to the point about individual tests not being actual Makefile targets, CMake does
|
||||
# not offer any way to execute a named test via the `make`/`ninja`/whatever interface; the only
|
||||
# way to manually invoke test `foo` is to to manually run `ctest` and specify a regex matching
|
||||
|
@ -33,7 +24,7 @@ set(SKIP_RETURN_CODE 125)
|
|||
|
||||
# The top-level test target is "fish_run_tests".
|
||||
add_custom_target(fish_run_tests
|
||||
COMMAND env CTEST_PARALLEL_LEVEL=${CTEST_PARALLEL_LEVEL} FISH_FORCE_COLOR=1
|
||||
COMMAND env FISH_FORCE_COLOR=1
|
||||
FISH_SOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_CTEST_COMMAND} --force-new-ctest-process # --verbose
|
||||
--output-on-failure --progress
|
||||
|
|
|
@ -7,49 +7,67 @@ Synopsis
|
|||
|
||||
.. synopsis::
|
||||
|
||||
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] [-k | --key] SEQUENCE COMMAND ...
|
||||
bind [(-M | --mode) MODE] [-k | --key] [--preset] [--user] SEQUENCE
|
||||
bind (-K | --key-names) [-a | --all] [--preset] [--user]
|
||||
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] KEYS COMMAND ...
|
||||
bind [(-M | --mode) MODE] [--preset] [--user] [KEYS]
|
||||
bind [-a | --all] [--preset] [--user]
|
||||
bind (-f | --function-names)
|
||||
bind (-L | --list-modes)
|
||||
bind (-e | --erase) [(-M | --mode) MODE] [--preset] [--user] [-a | --all] | [-k | --key] SEQUENCE ...
|
||||
bind (-e | --erase) [(-M | --mode) MODE] [--preset] [--user] [-a | --all] | KEYS ...
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
``bind`` manages bindings.
|
||||
``bind`` manages key bindings.
|
||||
|
||||
It can add bindings if given a SEQUENCE of characters to bind to. These should be written as :ref:`fish escape sequences <escapes>`. The most important of these are ``\c`` for the control key, and ``\e`` for escape, and because of historical reasons also the Alt key (sometimes also called "Meta").
|
||||
If both ``KEYS`` and ``COMMAND`` are given, ``bind`` adds (or replaces) a binding in ``MODE``.
|
||||
If only ``KEYS`` is given, any existing binding in the given ``MODE`` will be printed.
|
||||
|
||||
For example, :kbd:`Alt`\ +\ :kbd:`W` can be written as ``\ew``, and :kbd:`Control`\ +\ :kbd:`X` (^X) can be written as ``\cx``. Note that Alt-based key bindings are case sensitive and Control-based key bindings are not. This is a constraint of text-based terminals, not ``fish``.
|
||||
``KEYS`` is a comma-separated list of key names.
|
||||
Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-`` and ``shift-``.
|
||||
For example, :kbd:`Alt`\ +\ :kbd:`w` is written as ``alt-w``.
|
||||
Key names are case-sensitive; for example ``alt-W`` is the same as ``alt-shift-w``.
|
||||
|
||||
The generic key binding that matches if no other binding does can be set by specifying a ``SEQUENCE`` of the empty string (``''``). For most key bindings, it makes sense to bind this to the ``self-insert`` function (i.e. ``bind '' self-insert``). This will insert any keystrokes not specifically bound to into the editor. Non-printable characters are ignored by the editor, so this will not result in control sequences being inserted.
|
||||
Some keys have names, usually because they don't have an obvious printable character representation.
|
||||
They are:
|
||||
|
||||
If the ``-k`` switch is used, the name of a key (such as 'down', 'up' or 'backspace') is used instead of a sequence. The names used are the same as the corresponding curses variables, but without the 'key\_' prefix. (See ``terminfo(5)`` for more information, or use ``bind --key-names`` for a list of all available named keys). Normally this will print an error if the current ``$TERM`` entry doesn't have a given key, unless the ``-s`` switch is given.
|
||||
``plus`` (``+``),
|
||||
``minus`` (``-``),
|
||||
``comma`` (``,``),
|
||||
``backspace``,
|
||||
``delete``,
|
||||
``escape``,
|
||||
``enter``,
|
||||
the arrow keys ``up``, ``down``, ``left`` and ``right``,
|
||||
``pageup``,
|
||||
``pagedown``,
|
||||
``home``,
|
||||
``end``,
|
||||
``insert``,
|
||||
``tab``,
|
||||
``space`` and
|
||||
``F1`` through ``F12``.
|
||||
|
||||
To find out what sequence a key combination sends, you can use :doc:`fish_key_reader <fish_key_reader>`.
|
||||
An empty value (``''``) for ``KEYS`` designates the generic binding. For most bind modes, it makes sense to bind this to the ``self-insert`` function (i.e. ``bind '' self-insert``). This will insert any keystrokes not specifically bound to into the editor. Non-printable characters are ignored by the editor, so this will not result in control sequences being inserted.
|
||||
|
||||
To find the name of a key combination you can use :doc:`fish_key_reader <fish_key_reader>`.
|
||||
|
||||
``COMMAND`` can be any fish command, but it can also be one of a set of special input functions. These include functions for moving the cursor, operating on the kill-ring, performing tab completion, etc. Use ``bind --function-names`` or :ref:`see below <special-input-functions>` for a list of these input functions.
|
||||
|
||||
.. note::
|
||||
If a script changes the commandline, it should finish by calling the ``repaint`` special input function.
|
||||
|
||||
If no ``SEQUENCE`` is provided, all bindings (or just the bindings in the given ``MODE``) are printed. If ``SEQUENCE`` is provided but no ``COMMAND``, just the binding matching that sequence is printed.
|
||||
If no ``KEYS`` argument is provided, all bindings (in the given ``MODE``) are printed. If ``KEYS`` is provided but no ``COMMAND``, just the binding matching that sequence is printed.
|
||||
|
||||
Key bindings may use "modes", which mimics vi's modal input behavior. The default mode is "default". Every key binding applies to a single mode; you can specify which one with ``-M MODE``. If the key binding should change the mode, you can specify the new mode with ``-m NEW_MODE``. The mode can be viewed and changed via the ``$fish_bind_mode`` variable. If you want to change the mode from inside a fish function, use ``set fish_bind_mode MODE``.
|
||||
|
||||
To bind a sequence of keys, separate them with ``,``.
|
||||
|
||||
To save custom key bindings, put the ``bind`` statements into :ref:`config.fish <configuration>`. Alternatively, fish also automatically executes a function called ``fish_user_key_bindings`` if it exists.
|
||||
|
||||
Options
|
||||
-------
|
||||
The following options are available:
|
||||
|
||||
**-k** or **--key**
|
||||
Specify a key name, such as 'left' or 'backspace' instead of a character sequence
|
||||
|
||||
**-K** or **--key-names**
|
||||
Display a list of available key names. Specifying **-a** or **--all** includes keys that don't have a known mapping
|
||||
|
||||
**-f** or **--function-names**
|
||||
Display a list of available input functions
|
||||
|
||||
|
@ -69,7 +87,7 @@ The following options are available:
|
|||
Specifying **-a** or **--all** without **-M** or **--mode** erases all binds in all modes regardless of sequence.
|
||||
|
||||
**-a** or **--all**
|
||||
See **--erase** and **--key-names**
|
||||
See **--erase**
|
||||
|
||||
**--preset** and **--user**
|
||||
Specify if bind should operate on user or preset bindings.
|
||||
|
@ -349,7 +367,7 @@ Examples
|
|||
|
||||
Exit the shell when :kbd:`Control`\ +\ :kbd:`D` is pressed::
|
||||
|
||||
bind \cd 'exit'
|
||||
bind ctrl-d 'exit'
|
||||
|
||||
Perform a history search when :kbd:`Page Up` is pressed::
|
||||
|
||||
|
|
|
@ -15,16 +15,11 @@ Description
|
|||
|
||||
:program:`fish_key_reader` is used to explain how you would bind a certain key sequence. By default, it prints the :doc:`bind <bind>` command for one key sequence read interactively over standard input.
|
||||
|
||||
If the character sequence matches a special key name (see ``bind --key-names``), both ``bind CHARS ...`` and ``bind -k KEYNAME ...`` usage will be shown. In verbose mode (enabled by passing ``--verbose``), additional details about the characters received, such as the delay between chars, are written to standard error.
|
||||
|
||||
The following options are available:
|
||||
|
||||
**-c** or **--continuous**
|
||||
Begins a session where multiple key sequences can be inspected. By default the program exits after capturing a single key sequence.
|
||||
|
||||
**-V** or **--verbose**
|
||||
Tells fish_key_reader to output timing information and explain the sequence in more detail.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
|
@ -34,8 +29,6 @@ The following options are available:
|
|||
Usage Notes
|
||||
-----------
|
||||
|
||||
In verbose mode, the delay in milliseconds since the previous character was received is included in the diagnostic information written to standard error. This information may be useful to determine the optimal ``fish_escape_delay_ms`` setting or learn the amount of lag introduced by tools like ``ssh``, ``mosh`` or ``tmux``.
|
||||
|
||||
``fish_key_reader`` intentionally disables handling of many signals. To terminate ``fish_key_reader`` in ``--continuous`` mode do:
|
||||
|
||||
- press :kbd:`Control`\ +\ :kbd:`C` twice, or
|
||||
|
@ -51,12 +44,4 @@ Example
|
|||
> fish_key_reader
|
||||
Press a key:
|
||||
# press up-arrow
|
||||
bind \e\[A 'do something'
|
||||
|
||||
> fish_key_reader --verbose
|
||||
Press a key:
|
||||
# press alt+enter
|
||||
hex: 1B char: \e
|
||||
( 0.027 ms) hex: D char: \cM (or \r)
|
||||
bind \e\r 'do something'
|
||||
|
||||
bind up 'do something'
|
||||
|
|
|
@ -534,38 +534,29 @@ Custom bindings
|
|||
In addition to the standard bindings listed here, you can also define your own with :doc:`bind <cmds/bind>`::
|
||||
|
||||
# Just clear the commandline on control-c
|
||||
bind \cc 'commandline -r ""'
|
||||
bind ctrl-c 'commandline -r ""'
|
||||
|
||||
Put ``bind`` statements into :ref:`config.fish <configuration>` or a function called ``fish_user_key_bindings``.
|
||||
|
||||
If you change your mind on a binding and want to go back to fish's default, you can simply erase it again::
|
||||
|
||||
bind --erase \cc
|
||||
bind --erase ctrl-c
|
||||
|
||||
Fish remembers its preset bindings and so it will take effect again. This saves you from having to remember what it was before and add it again yourself.
|
||||
|
||||
If you use :ref:`vi bindings <vi-mode>`, note that ``bind`` will by default bind keys in :ref:`command mode <vi-mode-command>`. To bind something in :ref:`insert mode <vi-mode-insert>`::
|
||||
|
||||
bind --mode insert \cc 'commandline -r ""'
|
||||
bind --mode insert ctrl-c 'commandline -r ""'
|
||||
|
||||
.. _interactive-key-sequences:
|
||||
|
||||
Key sequences
|
||||
"""""""""""""
|
||||
|
||||
The terminal tells fish which keys you pressed by sending some sequences of bytes to describe that key. For some keys, this is easy - pressing :kbd:`a` simply means the terminal sends "a". In others it's more complicated and terminals disagree on which they send.
|
||||
To find out the name of a key, you can use :doc:`fish_key_reader <cmds/fish_key_reader>`.
|
||||
|
||||
In these cases, :doc:`fish_key_reader <cmds/fish_key_reader>` can tell you how to write the key sequence for your terminal. Just start it and press the keys you are interested in::
|
||||
|
||||
> fish_key_reader # pressing control-c
|
||||
> fish_key_reader # Press Alt + right-arrow
|
||||
Press a key:
|
||||
Press [ctrl-C] again to exit
|
||||
bind \cC 'do something'
|
||||
|
||||
> fish_key_reader # pressing the right-arrow
|
||||
Press a key:
|
||||
bind \e\[C 'do something'
|
||||
|
||||
Note that some key combinations are indistinguishable or unbindable. For instance control-i *is the same* as the tab key. This is a terminal limitation that fish can't do anything about. When ``fish_key_reader`` prints the same sequence for two different keys, then that is because your terminal sends the same sequence for them.
|
||||
|
||||
Also, :kbd:`Escape` is the same thing as :kbd:`Alt` in a terminal. To distinguish between pressing :kbd:`Escape` and then another key, and pressing :kbd:`Alt` and that key (or an escape sequence the key sends), fish waits for a certain time after seeing an escape character. This is configurable via the :envvar:`fish_escape_delay_ms` variable.
|
||||
|
@ -576,10 +567,10 @@ If you want to be able to press :kbd:`Escape` and then a character and have it c
|
|||
|
||||
Similarly, to disambiguate *other* keypresses where you've bound a subsequence and a longer sequence, fish has :envvar:`fish_sequence_key_delay_ms`::
|
||||
|
||||
# This binds "jk" to switch to normal mode in vi mode.
|
||||
# This binds the sequence j,k to switch to normal mode in vi mode.
|
||||
# If you kept it like that, every time you press "j",
|
||||
# fish would wait for a "k" or other key to disambiguate
|
||||
bind -M insert -m default jk cancel repaint-mode
|
||||
bind -M insert -m default j,k cancel repaint-mode
|
||||
|
||||
# After setting this, fish only waits 200ms for the "k",
|
||||
# or decides to treat the "j" as a separate sequence, inserting it.
|
||||
|
|
|
@ -57,5 +57,21 @@ complete -c bind -s s -l silent -d 'Operate silently'
|
|||
complete -c bind -l preset -d 'Operate on preset bindings'
|
||||
complete -c bind -l user -d 'Operate on user bindings'
|
||||
|
||||
complete -c bind -n __fish_bind_test1 -a '(bind --key-names)' -d 'Key name' -x
|
||||
complete -c bind -n __fish_bind_test2 -a '(bind --function-names)' -d 'Function name' -x
|
||||
|
||||
function __fish_bind_complete
|
||||
argparse M/mode= m/sets-mode= preset user s/silent \
|
||||
a/all function-names list-modes e/erase -- (commandline -xpc)[2..] 2>/dev/null
|
||||
or return 1
|
||||
set -l token (commandline -ct)
|
||||
if test (count $argv) = 0 && set -l prefix (string match -r -- '(.*,)?(ctrl-|alt-|shift-)*' $token)
|
||||
printf '%sctrl-\tCtrl modifier…\n' $prefix
|
||||
printf '%salt-\tAlt modifier…\n' $prefix
|
||||
printf '%sshift-\tShift modifier…\n' $prefix
|
||||
set -l key_names plus minus comma backspace delete escape \
|
||||
enter up down left right pageup pagedown home end insert tab \
|
||||
space F(seq 12)
|
||||
printf '%s\tNamed key\n' $prefix$key_names
|
||||
end
|
||||
end
|
||||
complete -c bind -k -a '(__fish_bind_complete)' -f
|
||||
|
|
|
@ -191,52 +191,6 @@ end" >$__fish_config_dir/config.fish
|
|||
# Load key bindings
|
||||
__fish_reload_key_bindings
|
||||
|
||||
# Enable bracketed paste exception when running unit tests so we don't have to add
|
||||
# the sequences to bind.expect
|
||||
if not set -q FISH_UNIT_TESTS_RUNNING
|
||||
# Enable bracketed paste before every prompt (see __fish_shared_bindings for the bindings).
|
||||
# We used to do this for read, but that would break non-interactive use and
|
||||
# compound commandlines like `read; cat`, because
|
||||
# it won't disable it after the read.
|
||||
function __fish_enable_bracketed_paste --on-event fish_prompt
|
||||
printf "\e[?2004h"
|
||||
end
|
||||
|
||||
# Disable BP before every command because that might not support it.
|
||||
function __fish_disable_bracketed_paste --on-event fish_preexec --on-event fish_exit
|
||||
printf "\e[?2004l"
|
||||
end
|
||||
|
||||
# Tell the terminal we support BP. Since we are in __f_c_i, the first fish_prompt
|
||||
# has already fired.
|
||||
# But only if we're interactive, in case we are in `read`
|
||||
status is-interactive
|
||||
and __fish_enable_bracketed_paste
|
||||
end
|
||||
|
||||
# Similarly, enable TMUX's focus reporting when in tmux.
|
||||
# This will be handled by
|
||||
# - The keybindings (reading the sequence and triggering an event)
|
||||
# - Any listeners (like the vi-cursor)
|
||||
if set -q TMUX
|
||||
and not set -q FISH_UNIT_TESTS_RUNNING
|
||||
# Allow overriding these - we're called very late,
|
||||
# and so it's otherwise awkward to disable focus reporting again.
|
||||
not functions -q __fish_enable_focus
|
||||
and function __fish_enable_focus --on-event fish_postexec
|
||||
echo -n \e\[\?1004h
|
||||
end
|
||||
not functions -q __fish_disable_focus
|
||||
and function __fish_disable_focus --on-event fish_preexec
|
||||
echo -n \e\[\?1004l
|
||||
end
|
||||
# Note: Don't call this initially because, even though we're in a fish_prompt event,
|
||||
# tmux reacts sooo quickly that we'll still get a sequence before we're prepared for it.
|
||||
# So this means that we won't get focus events until you've run at least one command, but that's preferable
|
||||
# to always seeing `^[[I` when starting fish.
|
||||
# __fish_enable_focus
|
||||
end
|
||||
|
||||
# Detect whether the terminal reflows on its own
|
||||
# If it does we shouldn't do it.
|
||||
# Allow $fish_handle_reflow to override it.
|
||||
|
|
|
@ -18,8 +18,6 @@ function __fish_edit_command_if_at_cursor --description 'If cursor is at the com
|
|||
|
||||
set -l editor (__fish_anyeditor)
|
||||
or return 0 # We already printed a warning, so tell the caller to finish.
|
||||
__fish_disable_bracketed_paste
|
||||
$editor $command_path
|
||||
__fish_enable_bracketed_paste
|
||||
return 0
|
||||
end
|
||||
|
|
47
share/functions/__fish_paste.fish
Normal file
47
share/functions/__fish_paste.fish
Normal file
|
@ -0,0 +1,47 @@
|
|||
function __fish_paste
|
||||
# Also split on \r, otherwise it looks confusing
|
||||
set -l data (string split \r -- $argv[1] | string split \n)
|
||||
|
||||
if commandline --search-field >/dev/null
|
||||
commandline --search-field -i -- $data
|
||||
return
|
||||
end
|
||||
|
||||
# If the current token has an unmatched single-quote,
|
||||
# escape all single-quotes (and backslashes) in the paste,
|
||||
# in order to turn it into a single literal token.
|
||||
#
|
||||
# This eases pasting non-code (e.g. markdown or git commitishes).
|
||||
set -l quote_state (__fish_tokenizer_state -- (commandline -ct | string collect))
|
||||
if contains -- $quote_state single single-escaped
|
||||
if status test-feature regex-easyesc
|
||||
set data (string replace -ra "(['\\\])" '\\\\$1' -- $data)
|
||||
else
|
||||
set data (string replace -ra "(['\\\])" '\\\\\\\$1' -- $data)
|
||||
end
|
||||
else if not contains -- $quote_state double double-escaped
|
||||
and set -q data[2]
|
||||
# Leading whitespace in subsequent lines is unneded, since fish
|
||||
# already indents. Also gets rid of tabs (issue #5274).
|
||||
set -l tmp
|
||||
for line in $data
|
||||
switch $quote_state
|
||||
case normal
|
||||
set -a tmp (string trim -l -- $line)
|
||||
case single single-escaped double double-escaped escaped
|
||||
set -a tmp $line
|
||||
end
|
||||
set quote_state (__fish_tokenizer_state -i $quote_state -- $line)
|
||||
end
|
||||
set data $data[1] $tmp[2..]
|
||||
end
|
||||
if not string length -q -- (commandline -c)
|
||||
# If we're at the beginning of the first line, trim whitespace from the start,
|
||||
# so we don't trigger ignoring history.
|
||||
set data[1] (string trim -l -- $data[1])
|
||||
end
|
||||
|
||||
if test -n "$data"
|
||||
commandline -i -- $data
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mode"
|
||||
set -l legacy_bind bind
|
||||
# These are some bindings that are supposed to be shared between vi mode and default mode.
|
||||
# They are supposed to be unrelated to text-editing (or movement).
|
||||
# This takes $argv so the vi-bindings can pass the mode they are valid in.
|
||||
|
@ -9,164 +10,128 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
|
|||
return 1
|
||||
end
|
||||
|
||||
bind --preset $argv \cy yank
|
||||
bind --preset $argv ctrl-y yank
|
||||
or return # protect against invalid $argv
|
||||
bind --preset $argv \ey yank-pop
|
||||
bind --preset $argv alt-y yank-pop
|
||||
|
||||
# Left/Right arrow
|
||||
bind --preset $argv -k right forward-char
|
||||
bind --preset $argv -k left backward-char
|
||||
bind --preset $argv \e\[C forward-char
|
||||
bind --preset $argv \e\[D backward-char
|
||||
bind --preset $argv right forward-char
|
||||
bind --preset $argv left backward-char
|
||||
$legacy_bind --preset $argv -k right forward-char
|
||||
$legacy_bind --preset $argv -k left backward-char
|
||||
$legacy_bind --preset $argv \e\[C forward-char
|
||||
$legacy_bind --preset $argv \e\[D backward-char
|
||||
# Some terminals output these when they're in in keypad mode.
|
||||
bind --preset $argv \eOC forward-char
|
||||
bind --preset $argv \eOD backward-char
|
||||
$legacy_bind --preset $argv \eOC forward-char
|
||||
$legacy_bind --preset $argv \eOD backward-char
|
||||
|
||||
# Ctrl-left/right - these also work in vim.
|
||||
bind --preset $argv \e\[1\;5C forward-word
|
||||
bind --preset $argv \e\[1\;5D backward-word
|
||||
bind --preset $argv ctrl-right forward-word
|
||||
bind --preset $argv ctrl-left backward-word
|
||||
$legacy_bind --preset $argv \e\[1\;5C forward-word
|
||||
$legacy_bind --preset $argv \e\[1\;5D backward-word
|
||||
|
||||
bind --preset $argv -k ppage beginning-of-history
|
||||
bind --preset $argv -k npage end-of-history
|
||||
bind --preset $argv pageup beginning-of-history
|
||||
bind --preset $argv pagedown end-of-history
|
||||
$legacy_bind --preset $argv -k ppage beginning-of-history
|
||||
$legacy_bind --preset $argv -k npage end-of-history
|
||||
|
||||
# Interaction with the system clipboard.
|
||||
bind --preset $argv \cx fish_clipboard_copy
|
||||
bind --preset $argv \cv fish_clipboard_paste
|
||||
bind --preset $argv ctrl-x fish_clipboard_copy
|
||||
bind --preset $argv ctrl-v fish_clipboard_paste
|
||||
|
||||
bind --preset $argv \e cancel
|
||||
bind --preset $argv \t complete
|
||||
bind --preset $argv \cs pager-toggle-search
|
||||
bind --preset $argv escape cancel
|
||||
bind --preset $argv ctrl-\[ cancel
|
||||
bind --preset $argv tab complete
|
||||
bind --preset $argv ctrl-i complete
|
||||
bind --preset $argv ctrl-s pager-toggle-search
|
||||
# shift-tab does a tab complete followed by a search.
|
||||
bind --preset $argv --key btab complete-and-search
|
||||
bind --preset $argv -k sdc history-pager-delete or backward-delete-char # shifted delete
|
||||
bind --preset $argv shift-tab complete-and-search
|
||||
$legacy_bind --preset $argv -k btab complete-and-search
|
||||
bind --preset $argv shift-delete history-pager-delete or backward-delete-char
|
||||
$legacy_bind --preset $argv -k sdc history-pager-delete or backward-delete-char
|
||||
|
||||
bind --preset $argv -k down down-or-search
|
||||
bind --preset $argv -k up up-or-search
|
||||
bind --preset $argv \e\[A up-or-search
|
||||
bind --preset $argv \e\[B down-or-search
|
||||
bind --preset $argv \eOA up-or-search
|
||||
bind --preset $argv \eOB down-or-search
|
||||
bind --preset $argv down down-or-search
|
||||
$legacy_bind --preset $argv -k down down-or-search
|
||||
bind --preset $argv up up-or-search
|
||||
$legacy_bind --preset $argv -k up up-or-search
|
||||
$legacy_bind --preset $argv \e\[A up-or-search
|
||||
$legacy_bind --preset $argv \e\[B down-or-search
|
||||
$legacy_bind --preset $argv \eOA up-or-search
|
||||
$legacy_bind --preset $argv \eOB down-or-search
|
||||
|
||||
bind --preset $argv -k sright forward-bigword
|
||||
bind --preset $argv -k sleft backward-bigword
|
||||
bind --preset $argv shift-right forward-bigword
|
||||
bind --preset $argv shift-left backward-bigword
|
||||
$legacy_bind --preset $argv -k sright forward-bigword
|
||||
$legacy_bind --preset $argv -k sleft backward-bigword
|
||||
|
||||
# Alt-left/Alt-right
|
||||
bind --preset $argv \e\eOC nextd-or-forward-word
|
||||
bind --preset $argv \e\eOD prevd-or-backward-word
|
||||
bind --preset $argv \e\e\[C nextd-or-forward-word
|
||||
bind --preset $argv \e\e\[D prevd-or-backward-word
|
||||
bind --preset $argv \eO3C nextd-or-forward-word
|
||||
bind --preset $argv \eO3D prevd-or-backward-word
|
||||
bind --preset $argv \e\[3C nextd-or-forward-word
|
||||
bind --preset $argv \e\[3D prevd-or-backward-word
|
||||
bind --preset $argv \e\[1\;3C nextd-or-forward-word
|
||||
bind --preset $argv \e\[1\;3D prevd-or-backward-word
|
||||
bind --preset $argv \e\[1\;9C nextd-or-forward-word #iTerm2
|
||||
bind --preset $argv \e\[1\;9D prevd-or-backward-word #iTerm2
|
||||
bind --preset $argv alt-right nextd-or-forward-word
|
||||
bind --preset $argv alt-left prevd-or-backward-word
|
||||
$legacy_bind --preset $argv \e\eOC nextd-or-forward-word
|
||||
$legacy_bind --preset $argv \e\eOD prevd-or-backward-word
|
||||
$legacy_bind --preset $argv \e\e\[C nextd-or-forward-word
|
||||
$legacy_bind --preset $argv \e\e\[D prevd-or-backward-word
|
||||
$legacy_bind --preset $argv \eO3C nextd-or-forward-word
|
||||
$legacy_bind --preset $argv \eO3D prevd-or-backward-word
|
||||
$legacy_bind --preset $argv \e\[3C nextd-or-forward-word
|
||||
$legacy_bind --preset $argv \e\[3D prevd-or-backward-word
|
||||
$legacy_bind --preset $argv \e\[1\;3C nextd-or-forward-word
|
||||
$legacy_bind --preset $argv \e\[1\;3D prevd-or-backward-word
|
||||
$legacy_bind --preset $argv \e\[1\;9C nextd-or-forward-word #iTerm2
|
||||
$legacy_bind --preset $argv \e\[1\;9D prevd-or-backward-word #iTerm2
|
||||
|
||||
# Alt-up/Alt-down
|
||||
bind --preset $argv \e\eOA history-token-search-backward
|
||||
bind --preset $argv \e\eOB history-token-search-forward
|
||||
bind --preset $argv \e\e\[A history-token-search-backward
|
||||
bind --preset $argv \e\e\[B history-token-search-forward
|
||||
bind --preset $argv \eO3A history-token-search-backward
|
||||
bind --preset $argv \eO3B history-token-search-forward
|
||||
bind --preset $argv \e\[3A history-token-search-backward
|
||||
bind --preset $argv \e\[3B history-token-search-forward
|
||||
bind --preset $argv \e\[1\;3A history-token-search-backward
|
||||
bind --preset $argv \e\[1\;3B history-token-search-forward
|
||||
bind --preset $argv \e\[1\;9A history-token-search-backward # iTerm2
|
||||
bind --preset $argv \e\[1\;9B history-token-search-forward # iTerm2
|
||||
bind --preset $argv alt-up history-token-search-backward
|
||||
bind --preset $argv alt-down history-token-search-forward
|
||||
$legacy_bind --preset $argv \e\eOA history-token-search-backward
|
||||
$legacy_bind --preset $argv \e\eOB history-token-search-forward
|
||||
$legacy_bind --preset $argv \e\e\[A history-token-search-backward
|
||||
$legacy_bind --preset $argv \e\e\[B history-token-search-forward
|
||||
$legacy_bind --preset $argv \eO3A history-token-search-backward
|
||||
$legacy_bind --preset $argv \eO3B history-token-search-forward
|
||||
$legacy_bind --preset $argv \e\[3A history-token-search-backward
|
||||
$legacy_bind --preset $argv \e\[3B history-token-search-forward
|
||||
$legacy_bind --preset $argv \e\[1\;3A history-token-search-backward
|
||||
$legacy_bind --preset $argv \e\[1\;3B history-token-search-forward
|
||||
$legacy_bind --preset $argv \e\[1\;9A history-token-search-backward # iTerm2
|
||||
$legacy_bind --preset $argv \e\[1\;9B history-token-search-forward # iTerm2
|
||||
# Bash compatibility
|
||||
# https://github.com/fish-shell/fish-shell/issues/89
|
||||
bind --preset $argv \e. history-token-search-backward
|
||||
bind --preset $argv alt-. history-token-search-backward
|
||||
|
||||
bind --preset $argv \el __fish_list_current_token
|
||||
bind --preset $argv \eo __fish_preview_current_file
|
||||
bind --preset $argv \ew __fish_whatis_current_token
|
||||
bind --preset $argv \cl clear-screen
|
||||
bind --preset $argv \cc cancel-commandline
|
||||
bind --preset $argv \cu backward-kill-line
|
||||
bind --preset $argv \cw backward-kill-path-component
|
||||
bind --preset $argv \e\[F end-of-line
|
||||
bind --preset $argv \e\[H beginning-of-line
|
||||
bind --preset $argv alt-l __fish_list_current_token
|
||||
bind --preset $argv alt-o __fish_preview_current_file
|
||||
bind --preset $argv alt-w __fish_whatis_current_token
|
||||
bind --preset $argv ctrl-l clear-screen
|
||||
bind --preset $argv ctrl-c cancel-commandline
|
||||
bind --preset $argv ctrl-u backward-kill-line
|
||||
bind --preset $argv ctrl-w backward-kill-path-component
|
||||
bind --preset $argv end end-of-line
|
||||
$legacy_bind --preset $argv \e\[F end-of-line
|
||||
bind --preset $argv home beginning-of-line
|
||||
$legacy_bind --preset $argv \e\[H beginning-of-line
|
||||
|
||||
bind --preset $argv \ed 'set -l cmd (commandline); if test -z "$cmd"; echo; dirh; commandline -f repaint; else; commandline -f kill-word; end'
|
||||
bind --preset $argv \cd delete-or-exit
|
||||
bind --preset $argv alt-d 'set -l cmd (commandline); if test -z "$cmd"; echo; dirh; commandline -f repaint; else; commandline -f kill-word; end'
|
||||
bind --preset $argv ctrl-d delete-or-exit
|
||||
|
||||
bind --preset $argv \es 'for cmd in sudo doas please; if command -q $cmd; fish_commandline_prepend $cmd; break; end; end'
|
||||
bind --preset $argv alt-s 'for cmd in sudo doas please; if command -q $cmd; fish_commandline_prepend $cmd; break; end; end'
|
||||
|
||||
# Allow reading manpages by pressing F1 (many GUI applications) or Alt+h (like in zsh).
|
||||
bind --preset $argv -k f1 __fish_man_page
|
||||
bind --preset $argv \eh __fish_man_page
|
||||
bind --preset $argv F1 __fish_man_page
|
||||
$legacy_bind --preset $argv -k f1 __fish_man_page
|
||||
bind --preset $argv alt-h __fish_man_page
|
||||
|
||||
# This will make sure the output of the current command is paged using the default pager when
|
||||
# you press Meta-p.
|
||||
# If none is set, less will be used.
|
||||
bind --preset $argv \ep __fish_paginate
|
||||
bind --preset $argv alt-p __fish_paginate
|
||||
|
||||
# Make it easy to turn an unexecuted command into a comment in the shell history. Also,
|
||||
# remove the commenting chars so the command can be further edited then executed.
|
||||
bind --preset $argv \e\# __fish_toggle_comment_commandline
|
||||
bind --preset $argv alt-# __fish_toggle_comment_commandline
|
||||
|
||||
# The [meta-e] and [meta-v] keystrokes invoke an external editor on the command buffer.
|
||||
bind --preset $argv \ee edit_command_buffer
|
||||
bind --preset $argv \ev edit_command_buffer
|
||||
|
||||
# Tmux' focus events.
|
||||
# Exclude paste mode because that should get _everything_ literally.
|
||||
for mode in (bind --list-modes | string match -v paste)
|
||||
# We only need the in-focus event currently (to redraw the vi-cursor).
|
||||
bind --preset -M $mode \e\[I 'emit fish_focus_in'
|
||||
bind --preset -M $mode \e\[O false
|
||||
bind --preset -M $mode \e\[\?1004h false
|
||||
end
|
||||
|
||||
# Support for "bracketed paste"
|
||||
# The way it works is that we acknowledge our support by printing
|
||||
# \e\[?2004h
|
||||
# then the terminal will "bracket" every paste in
|
||||
# \e\[200~ and \e\[201~
|
||||
# Every character in between those two will be part of the paste and should not cause a binding to execute (like \n executing commands).
|
||||
#
|
||||
# We enable it after every command and disable it before (in __fish_config_interactive.fish)
|
||||
#
|
||||
# Support for this seems to be ubiquitous - emacs enables it unconditionally (!) since 25.1
|
||||
# (though it only supports it since then, it seems to be the last term to gain support).
|
||||
#
|
||||
# NOTE: This is more of a "security" measure than a proper feature.
|
||||
# The better way to paste remains the `fish_clipboard_paste` function (bound to \cv by default).
|
||||
# We don't disable highlighting here, so it will be redone after every character (which can be slow),
|
||||
# and it doesn't handle "paste-stop" sequences in the paste (which the terminal needs to strip).
|
||||
#
|
||||
# See http://thejh.net/misc/website-terminal-copy-paste.
|
||||
|
||||
# Bind the starting sequence in every bind mode, even user-defined ones.
|
||||
# Exclude paste mode or there'll be an additional binding after switching between emacs and vi
|
||||
for mode in (bind --list-modes | string match -v paste)
|
||||
bind --preset -M $mode -m paste \e\[200~ "__fish_start_bracketed_paste $mode"
|
||||
end
|
||||
# This sequence ends paste-mode and returns to the previous mode we have saved before.
|
||||
bind --preset -M paste \e\[201~ __fish_stop_bracketed_paste
|
||||
# In paste-mode, everything self-inserts except for the sequence to get out of it
|
||||
bind --preset -M paste "" self-insert
|
||||
# Pass through formatting control characters else they may be dropped
|
||||
# on some terminals.
|
||||
bind --preset -M paste \b 'commandline -i \b'
|
||||
bind --preset -M paste \t 'commandline -i \t'
|
||||
bind --preset -M paste \v 'commandline -i \v'
|
||||
# Without this, a \r will overwrite the other text, rendering it invisible - which makes the exercise kinda pointless.
|
||||
bind --preset -M paste \r "commandline -i \n"
|
||||
|
||||
# We usually just pass the text through as-is to facilitate pasting code,
|
||||
# but when the current token contains an unbalanced single-quote (`'`),
|
||||
# we escape all single-quotes and backslashes, effectively turning the paste
|
||||
# into one literal token, to facilitate pasting non-code (e.g. markdown or git commitishes)
|
||||
bind --preset -M paste "'" "__fish_commandline_insert_escaped \' \$__fish_paste_quoted"
|
||||
bind --preset -M paste \\ "__fish_commandline_insert_escaped \\\ \$__fish_paste_quoted"
|
||||
# Only insert spaces if we're either quoted or not at the beginning of the commandline
|
||||
# - this strips leading spaces if they would trigger histignore.
|
||||
bind --preset -M paste " " self-insert-notfirst
|
||||
# These keystrokes invoke an external editor on the command buffer.
|
||||
bind --preset $argv alt-e edit_command_buffer
|
||||
bind --preset $argv alt-v edit_command_buffer
|
||||
|
||||
# Bindings that are shared in text-insertion modes.
|
||||
if not set -l index (contains --index -- -M $argv)
|
||||
|
@ -183,48 +148,27 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
|
|||
bind --preset $argv "&" self-insert expand-abbr
|
||||
bind --preset $argv ">" self-insert expand-abbr
|
||||
bind --preset $argv "<" self-insert expand-abbr
|
||||
bind --preset $argv shift-enter expand-abbr "commandline -i \n"
|
||||
# Shift+Return as sent with XTerm.vt100.formatOtherKeys: 0
|
||||
bind --preset $argv \e\[27\;2\;13~ expand-abbr "commandline -i \n"
|
||||
$legacy_bind --preset $argv \e\[27\;2\;13~ expand-abbr "commandline -i \n"
|
||||
# Shift+Return CSI u sequence, sent with XTerm.vt100.formatOtherKeys: 1
|
||||
bind --preset $argv \e\[13\;2u expand-abbr "commandline -i \n"
|
||||
bind --preset $argv \e\n expand-abbr "commandline -i \n"
|
||||
bind --preset $argv \e\r expand-abbr "commandline -i \n"
|
||||
$legacy_bind --preset $argv \e\[13\;2u expand-abbr "commandline -i \n"
|
||||
bind --preset $argv alt-enter expand-abbr "commandline -i \n"
|
||||
# Closing a command substitution expands abbreviations
|
||||
bind --preset $argv ")" self-insert expand-abbr
|
||||
# Ctrl-space inserts space without expanding abbrs
|
||||
bind --preset $argv ctrl-space 'test -n "$(commandline)" && commandline -i " "'
|
||||
bind --preset $argv -k nul 'test -n "$(commandline)" && commandline -i " "'
|
||||
# Shift-space (CSI u escape sequence) behaves like space because it's easy to mistype.
|
||||
bind --preset $argv \e\[32\;2u 'commandline -i " "; commandline -f expand-abbr'
|
||||
# Shift-space behaves like space because it's easy to mistype.
|
||||
bind --preset $argv shift-space 'commandline -i " "; commandline -f expand-abbr'
|
||||
$legacy_bind --preset $argv \e\[32\;2u 'commandline -i " "; commandline -f expand-abbr' # CSI u escape sequence
|
||||
|
||||
bind --preset $argv \n execute
|
||||
bind --preset $argv \r execute
|
||||
bind --preset $argv enter execute
|
||||
bind --preset $argv ctrl-j execute
|
||||
bind --preset $argv ctrl-m execute
|
||||
# Make Control+Return behave like Return because it's easy to mistype after accepting an autosuggestion.
|
||||
bind --preset $argv \e\[27\;5\;13~ execute # Sent with XTerm.vt100.formatOtherKeys: 0
|
||||
bind --preset $argv \e\[13\;5u execute # CSI u sequence, sent with XTerm.vt100.formatOtherKeys: 1
|
||||
bind --preset $argv ctrl-enter execute
|
||||
$legacy_bind --preset $argv \e\[27\;5\;13~ execute # Sent with XTerm.vt100.formatOtherKeys: 0
|
||||
$legacy_bind --preset $argv \e\[13\;5u execute # CSI u sequence, sent with XTerm.vt100.formatOtherKeys: 1
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_commandline_insert_escaped --description 'Insert the first arg escaped if a second arg is given'
|
||||
if set -q argv[2]
|
||||
commandline -i \\$argv[1]
|
||||
else
|
||||
commandline -i $argv[1]
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_start_bracketed_paste
|
||||
# Save the last bind mode so we can restore it.
|
||||
set -g __fish_last_bind_mode $argv[1]
|
||||
# If the token is currently single-quoted,
|
||||
# we escape single-quotes (and backslashes).
|
||||
string match -q 'single*' (__fish_tokenizer_state -- (commandline -ct | string collect))
|
||||
and set -g __fish_paste_quoted 1
|
||||
commandline -f begin-undo-group
|
||||
end
|
||||
|
||||
function __fish_stop_bracketed_paste
|
||||
# Restore the last bind mode.
|
||||
set fish_bind_mode $__fish_last_bind_mode
|
||||
set -e __fish_paste_quoted
|
||||
commandline -f end-undo-group
|
||||
end
|
||||
|
|
|
@ -86,13 +86,10 @@ function edit_command_buffer --description 'Edit the command buffer in an extern
|
|||
set -a editor $f
|
||||
end
|
||||
|
||||
__fish_disable_bracketed_paste
|
||||
$editor
|
||||
set -l editor_status $status
|
||||
__fish_enable_bracketed_paste
|
||||
|
||||
# Here we're checking the exit status of the editor.
|
||||
if test $editor_status -eq 0 -a -s $f
|
||||
if test $status -eq 0 -a -s $f
|
||||
# Set the command to the output of the edited command and move the cursor to the
|
||||
# end of the edited command.
|
||||
commandline -r -- (command cat $f)
|
||||
|
|
|
@ -23,49 +23,5 @@ function fish_clipboard_paste
|
|||
return
|
||||
end
|
||||
|
||||
# Also split on \r, otherwise it looks confusing
|
||||
set data (string split \r -- $data | string split \n)
|
||||
|
||||
if commandline --search-field >/dev/null
|
||||
commandline --search-field -i -- $data
|
||||
return
|
||||
end
|
||||
|
||||
# If the current token has an unmatched single-quote,
|
||||
# escape all single-quotes (and backslashes) in the paste,
|
||||
# in order to turn it into a single literal token.
|
||||
#
|
||||
# This eases pasting non-code (e.g. markdown or git commitishes).
|
||||
set -l quote_state (__fish_tokenizer_state -- (commandline -ct | string collect))
|
||||
if contains -- $quote_state single single-escaped
|
||||
if status test-feature regex-easyesc
|
||||
set data (string replace -ra "(['\\\])" '\\\\$1' -- $data)
|
||||
else
|
||||
set data (string replace -ra "(['\\\])" '\\\\\\\$1' -- $data)
|
||||
end
|
||||
else if not contains -- $quote_state double double-escaped
|
||||
and set -q data[2]
|
||||
# Leading whitespace in subsequent lines is unneded, since fish
|
||||
# already indents. Also gets rid of tabs (issue #5274).
|
||||
set -l tmp
|
||||
for line in $data
|
||||
switch $quote_state
|
||||
case normal
|
||||
set -a tmp (string trim -l -- $line)
|
||||
case single single-escaped double double-escaped escaped
|
||||
set -a tmp $line
|
||||
end
|
||||
set quote_state (__fish_tokenizer_state -i $quote_state -- $line)
|
||||
end
|
||||
set data $data[1] $tmp[2..]
|
||||
end
|
||||
if not string length -q -- (commandline -c)
|
||||
# If we're at the beginning of the first line, trim whitespace from the start,
|
||||
# so we don't trigger ignoring history.
|
||||
set data[1] (string trim -l -- $data[1])
|
||||
end
|
||||
|
||||
if test -n "$data"
|
||||
commandline -i -- $data
|
||||
end
|
||||
__fish_paste $data
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
function fish_default_key_bindings -d "emacs-like key binds"
|
||||
set -l legacy_bind bind
|
||||
if contains -- -h $argv
|
||||
or contains -- --help $argv
|
||||
echo "Sorry but this function doesn't support -h or --help"
|
||||
|
@ -30,65 +31,65 @@ function fish_default_key_bindings -d "emacs-like key binds"
|
|||
__fish_shared_key_bindings $argv
|
||||
or return # protect against invalid $argv
|
||||
|
||||
bind --preset $argv \ck kill-line
|
||||
bind --preset $argv ctrl-k kill-line
|
||||
|
||||
bind --preset $argv \eOC forward-char
|
||||
bind --preset $argv \eOD backward-char
|
||||
bind --preset $argv \e\[C forward-char
|
||||
bind --preset $argv \e\[D backward-char
|
||||
bind --preset $argv -k right forward-char
|
||||
bind --preset $argv -k left backward-char
|
||||
bind --preset $argv right forward-char
|
||||
bind --preset $argv left backward-char
|
||||
$legacy_bind --preset $argv \eOC forward-char
|
||||
$legacy_bind --preset $argv \eOD backward-char
|
||||
$legacy_bind --preset $argv \e\[C forward-char
|
||||
$legacy_bind --preset $argv \e\[D backward-char
|
||||
$legacy_bind --preset $argv -k right forward-char
|
||||
$legacy_bind --preset $argv -k left backward-char
|
||||
|
||||
bind --preset $argv -k dc delete-char
|
||||
bind --preset $argv -k backspace backward-delete-char
|
||||
bind --preset $argv \x7f backward-delete-char
|
||||
bind --preset $argv delete delete-char
|
||||
bind --preset $argv backspace backward-delete-char
|
||||
bind --preset $argv shift-backspace backward-delete-char
|
||||
|
||||
# for PuTTY
|
||||
# https://github.com/fish-shell/fish-shell/issues/180
|
||||
bind --preset $argv \e\[1~ beginning-of-line
|
||||
bind --preset $argv \e\[3~ delete-char
|
||||
bind --preset $argv \e\[4~ end-of-line
|
||||
$legacy_bind --preset $argv \e\[1~ beginning-of-line
|
||||
$legacy_bind --preset $argv \e\[3~ delete-char
|
||||
$legacy_bind --preset $argv \e\[4~ end-of-line
|
||||
|
||||
bind --preset $argv -k home beginning-of-line
|
||||
bind --preset $argv -k end end-of-line
|
||||
bind --preset $argv home beginning-of-line
|
||||
$legacy_bind --preset $argv -k home beginning-of-line
|
||||
bind --preset $argv end end-of-line
|
||||
$legacy_bind --preset $argv -k end end-of-line
|
||||
|
||||
bind --preset $argv \ca beginning-of-line
|
||||
bind --preset $argv \ce end-of-line
|
||||
bind --preset $argv \ch backward-delete-char
|
||||
bind --preset $argv \cp up-or-search
|
||||
bind --preset $argv \cn down-or-search
|
||||
bind --preset $argv \cf forward-char
|
||||
bind --preset $argv \cb backward-char
|
||||
bind --preset $argv \ct transpose-chars
|
||||
bind --preset $argv \cg cancel
|
||||
bind --preset $argv \c_ undo
|
||||
bind --preset $argv \cz undo
|
||||
bind --preset $argv \e/ redo
|
||||
bind --preset $argv \et transpose-words
|
||||
bind --preset $argv \eu upcase-word
|
||||
bind --preset $argv ctrl-a beginning-of-line
|
||||
bind --preset $argv ctrl-e end-of-line
|
||||
bind --preset $argv ctrl-h backward-delete-char
|
||||
bind --preset $argv ctrl-p up-or-search
|
||||
bind --preset $argv ctrl-n down-or-search
|
||||
bind --preset $argv ctrl-f forward-char
|
||||
bind --preset $argv ctrl-b backward-char
|
||||
bind --preset $argv ctrl-t transpose-chars
|
||||
bind --preset $argv ctrl-g cancel
|
||||
bind --preset $argv ctrl-/ undo
|
||||
bind --preset $argv ctrl-_ undo # XTerm idiosyncracy, can get rid of this once we go full CSI u
|
||||
bind --preset $argv ctrl-z undo
|
||||
bind --preset $argv alt-/ redo
|
||||
bind --preset $argv alt-t transpose-words
|
||||
bind --preset $argv alt-u upcase-word
|
||||
|
||||
# This clashes with __fish_list_current_token
|
||||
# bind --preset $argv \el downcase-word
|
||||
bind --preset $argv \ec capitalize-word
|
||||
# One of these is alt+backspace.
|
||||
bind --preset $argv \e\x7f backward-kill-word
|
||||
bind --preset $argv \e\b backward-kill-word
|
||||
if not test "$TERM_PROGRAM" = Apple_Terminal
|
||||
bind --preset $argv \eb backward-word
|
||||
bind --preset $argv \ef forward-word
|
||||
else
|
||||
bind --preset $argv alt-c capitalize-word
|
||||
bind --preset $argv alt-backspace backward-kill-word
|
||||
bind --preset $argv alt-b backward-word
|
||||
bind --preset $argv alt-f forward-word
|
||||
if test "$TERM_PROGRAM" = Apple_Terminal
|
||||
# Terminal.app sends \eb for alt+left, \ef for alt+right.
|
||||
# Yeah.
|
||||
bind --preset $argv \eb prevd-or-backward-word
|
||||
bind --preset $argv \ef nextd-or-forward-word
|
||||
$legacy_bind --preset $argv alt-b prevd-or-backward-word
|
||||
$legacy_bind --preset $argv alt-f nextd-or-forward-word
|
||||
end
|
||||
|
||||
bind --preset $argv \e\< beginning-of-buffer
|
||||
bind --preset $argv \e\> end-of-buffer
|
||||
bind --preset $argv alt-\< beginning-of-buffer
|
||||
bind --preset $argv alt-\> end-of-buffer
|
||||
|
||||
bind --preset $argv \ed kill-word
|
||||
bind --preset $argv alt-d kill-word
|
||||
|
||||
bind --preset $argv \cr history-pager
|
||||
bind --preset $argv ctrl-r history-pager
|
||||
|
||||
# term-specific special bindings
|
||||
switch "$TERM"
|
||||
|
@ -96,16 +97,16 @@ function fish_default_key_bindings -d "emacs-like key binds"
|
|||
# suckless and bash/zsh/fish have a different approach to how the terminal should be configured;
|
||||
# the major effect is that several keys do not work as intended.
|
||||
# This is a workaround, there will be additions in he future.
|
||||
bind --preset $argv \e\[P delete-char
|
||||
bind --preset $argv \e\[Z up-line
|
||||
$legacy_bind --preset $argv \e\[P delete-char
|
||||
$legacy_bind --preset $argv \e\[Z up-line
|
||||
case 'rxvt*'
|
||||
bind --preset $argv \e\[8~ end-of-line
|
||||
bind --preset $argv \eOc forward-word
|
||||
bind --preset $argv \eOd backward-word
|
||||
$legacy_bind --preset $argv \e\[8~ end-of-line
|
||||
$legacy_bind --preset $argv \eOc forward-word
|
||||
$legacy_bind --preset $argv \eOd backward-word
|
||||
case xterm-256color
|
||||
# Microsoft's conemu uses xterm-256color plus
|
||||
# the following to tell a console to paste:
|
||||
bind --preset $argv \e\x20ep fish_clipboard_paste
|
||||
$legacy_bind --preset $argv \e\x20ep fish_clipboard_paste
|
||||
end
|
||||
|
||||
set -e -g fish_cursor_selection_mode
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
set -l legacy_bind bind
|
||||
if contains -- -h $argv
|
||||
or contains -- --help $argv
|
||||
echo "Sorry but this function doesn't support -h or --help" >&2
|
||||
|
@ -37,7 +38,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
set -l init_mode insert
|
||||
# These are only the special vi-style keys
|
||||
# not end/home, we share those.
|
||||
set -l eol_keys \$ g\$
|
||||
set -l eol_keys \$ g,\$
|
||||
set -l bol_keys \^ 0 g\^
|
||||
|
||||
if contains -- $argv[1] insert default visual
|
||||
|
@ -56,7 +57,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
# Add a way to switch from insert to normal (command) mode.
|
||||
# Note if we are paging, we want to stay in insert mode
|
||||
# See #2871
|
||||
bind -s --preset -M insert \e '
|
||||
set -l on_escape '
|
||||
if commandline -P
|
||||
commandline -f cancel
|
||||
else
|
||||
|
@ -67,23 +68,26 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
commandline -f repaint-mode
|
||||
end
|
||||
'
|
||||
bind -s --preset -M insert escape $on_escape
|
||||
bind -s --preset -M insert ctrl-\[ $on_escape
|
||||
|
||||
# Default (command) mode
|
||||
bind -s --preset :q exit
|
||||
bind -s --preset -m insert \cc cancel-commandline repaint-mode
|
||||
bind -s --preset :,q exit
|
||||
bind -s --preset -m insert ctrl-c cancel-commandline repaint-mode
|
||||
bind -s --preset -M default h backward-char
|
||||
bind -s --preset -M default l forward-char
|
||||
bind -s --preset -m insert \n execute
|
||||
bind -s --preset -m insert \r execute
|
||||
bind -s --preset -m insert enter execute
|
||||
bind -s --preset -m insert ctrl-j execute
|
||||
bind -s --preset -m insert ctrl-m execute
|
||||
bind -s --preset -m insert o 'set fish_cursor_end_mode exclusive' insert-line-under repaint-mode
|
||||
bind -s --preset -m insert O 'set fish_cursor_end_modefish_cursor_end_modeexclusive' insert-line-over repaint-mode
|
||||
bind -s --preset -m insert O 'set fish_cursor_end_mode exclusive' insert-line-over repaint-mode
|
||||
bind -s --preset -m insert i repaint-mode
|
||||
bind -s --preset -m insert I beginning-of-line repaint-mode
|
||||
bind -s --preset -m insert a 'set fish_cursor_end_mode exclusive' forward-single-char repaint-mode
|
||||
bind -s --preset -m insert A 'set fish_cursor_end_mode exclusive' end-of-line repaint-mode
|
||||
bind -s --preset -m visual v begin-selection repaint-mode
|
||||
|
||||
bind -s --preset gg beginning-of-buffer
|
||||
bind -s --preset g,g beginning-of-buffer
|
||||
bind -s --preset G end-of-buffer
|
||||
|
||||
for key in $eol_keys
|
||||
|
@ -94,7 +98,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
end
|
||||
|
||||
bind -s --preset u undo
|
||||
bind -s --preset \cr redo
|
||||
bind -s --preset ctrl-r redo
|
||||
|
||||
bind -s --preset [ history-token-search-backward
|
||||
bind -s --preset ] history-token-search-forward
|
||||
|
@ -104,14 +108,14 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
bind -s --preset j down-or-search
|
||||
bind -s --preset b backward-word
|
||||
bind -s --preset B backward-bigword
|
||||
bind -s --preset ge backward-word
|
||||
bind -s --preset gE backward-bigword
|
||||
bind -s --preset g,e backward-word
|
||||
bind -s --preset g,E backward-bigword
|
||||
bind -s --preset w forward-word forward-single-char
|
||||
bind -s --preset W forward-bigword forward-single-char
|
||||
bind -s --preset e 'set fish_cursor_end_mode exclusive' forward-single-char forward-word backward-char 'set fish_cursor_end_mode inclusive'
|
||||
bind -s --preset E 'set fish_cursor_end_mode exclusive' forward-single-char forward-bigword backward-char 'set fish_cursor_end_mode inclusive'
|
||||
|
||||
bind -s --preset -M insert \cn accept-autosuggestion
|
||||
bind -s --preset -M insert ctrl-n accept-autosuggestion
|
||||
|
||||
# Vi/Vim doesn't support these keys in insert mode but that seems silly so we do so anyway.
|
||||
bind -s --preset -M insert -k home beginning-of-line
|
||||
|
@ -128,104 +132,105 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
bind -s --preset -M default -k dc delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
|
||||
|
||||
# Backspace deletes a char in insert mode, but not in normal/default mode.
|
||||
bind -s --preset -M insert -k backspace backward-delete-char
|
||||
bind -s --preset -M default -k backspace backward-char
|
||||
bind -s --preset -M insert \ch backward-delete-char
|
||||
bind -s --preset -M default \ch backward-char
|
||||
bind -s --preset -M insert \x7f backward-delete-char
|
||||
bind -s --preset -M default \x7f backward-char
|
||||
bind -s --preset -M insert backspace backward-delete-char
|
||||
bind -s --preset -M insert shift-backspace backward-delete-char
|
||||
$legacy_bind -s --preset -M insert -k backspace backward-delete-char
|
||||
bind -s --preset -M default backspace backward-char
|
||||
$legacy_bind -s --preset -M default -k backspace backward-char
|
||||
bind -s --preset -M insert ctrl-h backward-delete-char
|
||||
bind -s --preset -M default ctrl-h backward-char
|
||||
|
||||
bind -s --preset dd kill-whole-line
|
||||
bind -s --preset d,d kill-whole-line
|
||||
bind -s --preset D kill-line
|
||||
bind -s --preset d\$ kill-line
|
||||
bind -s --preset d\^ backward-kill-line
|
||||
bind -s --preset d0 backward-kill-line
|
||||
bind -s --preset dw kill-word
|
||||
bind -s --preset dW kill-bigword
|
||||
bind -s --preset diw forward-single-char forward-single-char backward-word kill-word
|
||||
bind -s --preset diW forward-single-char forward-single-char backward-bigword kill-bigword
|
||||
bind -s --preset daw forward-single-char forward-single-char backward-word kill-word
|
||||
bind -s --preset daW forward-single-char forward-single-char backward-bigword kill-bigword
|
||||
bind -s --preset de kill-word
|
||||
bind -s --preset dE kill-bigword
|
||||
bind -s --preset db backward-kill-word
|
||||
bind -s --preset dB backward-kill-bigword
|
||||
bind -s --preset dge backward-kill-word
|
||||
bind -s --preset dgE backward-kill-bigword
|
||||
bind -s --preset df begin-selection forward-jump kill-selection end-selection
|
||||
bind -s --preset dt begin-selection forward-jump backward-char kill-selection end-selection
|
||||
bind -s --preset dF begin-selection backward-jump kill-selection end-selection
|
||||
bind -s --preset dT begin-selection backward-jump forward-single-char kill-selection end-selection
|
||||
bind -s --preset dh backward-char delete-char
|
||||
bind -s --preset dl delete-char
|
||||
bind -s --preset di backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection
|
||||
bind -s --preset da backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection
|
||||
bind -s --preset 'd;' begin-selection repeat-jump kill-selection end-selection
|
||||
bind -s --preset 'd,' begin-selection repeat-jump-reverse kill-selection end-selection
|
||||
bind -s --preset d,\$ kill-line
|
||||
bind -s --preset d,\^ backward-kill-line
|
||||
bind -s --preset d,0 backward-kill-line
|
||||
bind -s --preset d,w kill-word
|
||||
bind -s --preset d,W kill-bigword
|
||||
bind -s --preset d,i,w forward-single-char forward-single-char backward-word kill-word
|
||||
bind -s --preset d,i,W forward-single-char forward-single-char backward-bigword kill-bigword
|
||||
bind -s --preset d,a,w forward-single-char forward-single-char backward-word kill-word
|
||||
bind -s --preset d,a,W forward-single-char forward-single-char backward-bigword kill-bigword
|
||||
bind -s --preset d,e kill-word
|
||||
bind -s --preset d,E kill-bigword
|
||||
bind -s --preset d,b backward-kill-word
|
||||
bind -s --preset d,B backward-kill-bigword
|
||||
bind -s --preset d,g,e backward-kill-word
|
||||
bind -s --preset d,g,E backward-kill-bigword
|
||||
bind -s --preset d,f begin-selection forward-jump kill-selection end-selection
|
||||
bind -s --preset d,t begin-selection forward-jump backward-char kill-selection end-selection
|
||||
bind -s --preset d,F begin-selection backward-jump kill-selection end-selection
|
||||
bind -s --preset d,T begin-selection backward-jump forward-single-char kill-selection end-selection
|
||||
bind -s --preset d,h backward-char delete-char
|
||||
bind -s --preset d,l delete-char
|
||||
bind -s --preset d,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection
|
||||
bind -s --preset d,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection
|
||||
bind -s --preset 'd,;' begin-selection repeat-jump kill-selection end-selection
|
||||
bind -s --preset 'd,comma' begin-selection repeat-jump-reverse kill-selection end-selection
|
||||
|
||||
bind -s --preset -m insert s delete-char repaint-mode
|
||||
bind -s --preset -m insert S kill-inner-line repaint-mode
|
||||
bind -s --preset -m insert cc kill-inner-line repaint-mode
|
||||
bind -s --preset -m insert c,c kill-inner-line repaint-mode
|
||||
bind -s --preset -m insert C kill-line repaint-mode
|
||||
bind -s --preset -m insert c\$ kill-line repaint-mode
|
||||
bind -s --preset -m insert c\^ backward-kill-line repaint-mode
|
||||
bind -s --preset -m insert c0 backward-kill-line repaint-mode
|
||||
bind -s --preset -m insert cw kill-word repaint-mode
|
||||
bind -s --preset -m insert cW kill-bigword repaint-mode
|
||||
bind -s --preset -m insert ciw forward-single-char forward-single-char backward-word kill-word repaint-mode
|
||||
bind -s --preset -m insert ciW forward-single-char forward-single-char backward-bigword kill-bigword repaint-mode
|
||||
bind -s --preset -m insert caw forward-single-char forward-single-char backward-word kill-word repaint-mode
|
||||
bind -s --preset -m insert caW forward-single-char forward-single-char backward-bigword kill-bigword repaint-mode
|
||||
bind -s --preset -m insert ce kill-word repaint-mode
|
||||
bind -s --preset -m insert cE kill-bigword repaint-mode
|
||||
bind -s --preset -m insert cb backward-kill-word repaint-mode
|
||||
bind -s --preset -m insert cB backward-kill-bigword repaint-mode
|
||||
bind -s --preset -m insert cge backward-kill-word repaint-mode
|
||||
bind -s --preset -m insert cgE backward-kill-bigword repaint-mode
|
||||
bind -s --preset -m insert cf begin-selection forward-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert ct begin-selection forward-jump backward-char kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert cF begin-selection backward-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert cT begin-selection backward-jump forward-single-char kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert ch backward-char begin-selection kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert cl begin-selection kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert ci backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert ca backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,\$ kill-line repaint-mode
|
||||
bind -s --preset -m insert c,\^ backward-kill-line repaint-mode
|
||||
bind -s --preset -m insert c,0 backward-kill-line repaint-mode
|
||||
bind -s --preset -m insert c,w kill-word repaint-mode
|
||||
bind -s --preset -m insert c,W kill-bigword repaint-mode
|
||||
bind -s --preset -m insert c,i,w forward-single-char forward-single-char backward-word kill-word repaint-mode
|
||||
bind -s --preset -m insert c,i,W forward-single-char forward-single-char backward-bigword kill-bigword repaint-mode
|
||||
bind -s --preset -m insert c,a,w forward-single-char forward-single-char backward-word kill-word repaint-mode
|
||||
bind -s --preset -m insert c,a,W forward-single-char forward-single-char backward-bigword kill-bigword repaint-mode
|
||||
bind -s --preset -m insert c,e kill-word repaint-mode
|
||||
bind -s --preset -m insert c,E kill-bigword repaint-mode
|
||||
bind -s --preset -m insert c,b backward-kill-word repaint-mode
|
||||
bind -s --preset -m insert c,B backward-kill-bigword repaint-mode
|
||||
bind -s --preset -m insert c,g,e backward-kill-word repaint-mode
|
||||
bind -s --preset -m insert c,g,E backward-kill-bigword repaint-mode
|
||||
bind -s --preset -m insert c,f begin-selection forward-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,t begin-selection forward-jump backward-char kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,F begin-selection backward-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,T begin-selection backward-jump forward-single-char kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,h backward-char begin-selection kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,l begin-selection kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode
|
||||
bind -s --preset -m insert c,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode
|
||||
|
||||
bind -s --preset '~' togglecase-char forward-single-char
|
||||
bind -s --preset gu downcase-word
|
||||
bind -s --preset gU upcase-word
|
||||
bind -s --preset g,u downcase-word
|
||||
bind -s --preset g,U upcase-word
|
||||
|
||||
bind -s --preset J end-of-line delete-char
|
||||
bind -s --preset K 'man (commandline -t) 2>/dev/null; or echo -n \a'
|
||||
|
||||
bind -s --preset yy kill-whole-line yank
|
||||
for seq in '"*yy' '"*Y' '"+yy' '"+Y'
|
||||
for seq in '",*,y,y' '",*,Y' '"+,y,y' '",+,Y'
|
||||
bind -s --preset $seq fish_clipboard_copy
|
||||
end
|
||||
bind -s --preset Y kill-whole-line yank
|
||||
bind -s --preset y\$ kill-line yank
|
||||
bind -s --preset y\^ backward-kill-line yank
|
||||
bind -s --preset y0 backward-kill-line yank
|
||||
bind -s --preset yw kill-word yank
|
||||
bind -s --preset yW kill-bigword yank
|
||||
bind -s --preset yiw forward-single-char forward-single-char backward-word kill-word yank
|
||||
bind -s --preset yiW forward-single-char forward-single-char backward-bigword kill-bigword yank
|
||||
bind -s --preset yaw forward-single-char forward-single-char backward-word kill-word yank
|
||||
bind -s --preset yaW forward-single-char forward-single-char backward-bigword kill-bigword yank
|
||||
bind -s --preset ye kill-word yank
|
||||
bind -s --preset yE kill-bigword yank
|
||||
bind -s --preset yb backward-kill-word yank
|
||||
bind -s --preset yB backward-kill-bigword yank
|
||||
bind -s --preset yge backward-kill-word yank
|
||||
bind -s --preset ygE backward-kill-bigword yank
|
||||
bind -s --preset yf begin-selection forward-jump kill-selection yank end-selection
|
||||
bind -s --preset yt begin-selection forward-jump-till kill-selection yank end-selection
|
||||
bind -s --preset yF begin-selection backward-jump kill-selection yank end-selection
|
||||
bind -s --preset yT begin-selection backward-jump-till kill-selection yank end-selection
|
||||
bind -s --preset yh backward-char begin-selection kill-selection yank end-selection
|
||||
bind -s --preset yl begin-selection kill-selection yank end-selection
|
||||
bind -s --preset yi backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection yank end-selection
|
||||
bind -s --preset ya backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection yank end-selection
|
||||
bind -s --preset y,\$ kill-line yank
|
||||
bind -s --preset y,\^ backward-kill-line yank
|
||||
bind -s --preset y,0 backward-kill-line yank
|
||||
bind -s --preset y,w kill-word yank
|
||||
bind -s --preset y,W kill-bigword yank
|
||||
bind -s --preset y,i,w forward-single-char forward-single-char backward-word kill-word yank
|
||||
bind -s --preset y,i,W forward-single-char forward-single-char backward-bigword kill-bigword yank
|
||||
bind -s --preset y,a,w forward-single-char forward-single-char backward-word kill-word yank
|
||||
bind -s --preset y,a,W forward-single-char forward-single-char backward-bigword kill-bigword yank
|
||||
bind -s --preset y,e kill-word yank
|
||||
bind -s --preset y,E kill-bigword yank
|
||||
bind -s --preset y,b backward-kill-word yank
|
||||
bind -s --preset y,B backward-kill-bigword yank
|
||||
bind -s --preset y,g,e backward-kill-word yank
|
||||
bind -s --preset y,g,E backward-kill-bigword yank
|
||||
bind -s --preset y,f begin-selection forward-jump kill-selection yank end-selection
|
||||
bind -s --preset y,t begin-selection forward-jump-till kill-selection yank end-selection
|
||||
bind -s --preset y,F begin-selection backward-jump kill-selection yank end-selection
|
||||
bind -s --preset y,T begin-selection backward-jump-till kill-selection yank end-selection
|
||||
bind -s --preset y,h backward-char begin-selection kill-selection yank end-selection
|
||||
bind -s --preset y,l begin-selection kill-selection yank end-selection
|
||||
bind -s --preset y,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection yank end-selection
|
||||
bind -s --preset y,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection yank end-selection
|
||||
|
||||
bind -s --preset f forward-jump
|
||||
bind -s --preset F backward-jump
|
||||
|
@ -240,32 +245,40 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
# \ so there's no need to go back a char, just paste it without moving
|
||||
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_modefish_cursor_end_modeinclusive' yank
|
||||
bind -s --preset P yank
|
||||
bind -s --preset gp yank-pop
|
||||
bind -s --preset g,p yank-pop
|
||||
|
||||
# same vim 'pasting' note as upper
|
||||
bind -s --preset '"*p' 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' fish_clipboard_paste
|
||||
bind -s --preset '"*P' fish_clipboard_paste
|
||||
bind -s --preset '"+p' 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' fish_clipboard_paste
|
||||
bind -s --preset '"+P' fish_clipboard_paste
|
||||
bind -s --preset '",*,p' 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' fish_clipboard_paste
|
||||
bind -s --preset '",*,P' fish_clipboard_paste
|
||||
bind -s --preset '",+,p' 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' fish_clipboard_paste
|
||||
bind -s --preset '",+,P' fish_clipboard_paste
|
||||
|
||||
#
|
||||
# Lowercase r, enters replace_one mode
|
||||
#
|
||||
bind -s --preset -m replace_one r repaint-mode
|
||||
bind -s --preset -M replace_one -m default '' delete-char self-insert backward-char repaint-mode
|
||||
bind -s --preset -M replace_one -m default \r 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default \e cancel repaint-mode
|
||||
bind -s --preset -M replace_one -m default enter 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default ctrl-j 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default ctrl-m 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default escape cancel repaint-mode
|
||||
bind -s --preset -M replace_one -m default ctrl-\[ cancel repaint-mode
|
||||
|
||||
#
|
||||
# Uppercase R, enters replace mode
|
||||
#
|
||||
bind -s --preset -m replace R repaint-mode
|
||||
bind -s --preset -M replace '' delete-char self-insert
|
||||
bind -s --preset -M replace -m insert \r execute repaint-mode
|
||||
bind -s --preset -M replace -m default \e cancel repaint-mode
|
||||
bind -s --preset -M replace -m insert enter execute repaint-mode
|
||||
bind -s --preset -M replace -m insert ctrl-j execute repaint-mode
|
||||
bind -s --preset -M replace -m insert ctrl-m execute repaint-mode
|
||||
bind -s --preset -M replace -m default escape cancel repaint-mode
|
||||
bind -s --preset -M replace -m default ctrl-\[ cancel repaint-mode
|
||||
# in vim (and maybe in vi), <BS> deletes the changes
|
||||
# but this binding just move cursor backward, not delete the changes
|
||||
bind -s --preset -M replace -k backspace backward-char
|
||||
bind -s --preset -M replace backspace backward-char
|
||||
bind -s --preset -M replace shift-backspace backward-char
|
||||
$legacy_bind -s --preset -M replace -k backspace backward-char
|
||||
|
||||
#
|
||||
# visual mode
|
||||
|
@ -278,8 +291,8 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
|
||||
bind -s --preset -M visual b backward-word
|
||||
bind -s --preset -M visual B backward-bigword
|
||||
bind -s --preset -M visual ge backward-word
|
||||
bind -s --preset -M visual gE backward-bigword
|
||||
bind -s --preset -M visual g,e backward-word
|
||||
bind -s --preset -M visual g,E backward-bigword
|
||||
bind -s --preset -M visual w forward-word
|
||||
bind -s --preset -M visual W forward-bigword
|
||||
bind -s --preset -M visual e 'set fish_cursor_end_mode exclusive' forward-single-char forward-word backward-char 'set fish_cursor_end_mode inclusive'
|
||||
|
@ -304,12 +317,13 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
|||
bind -s --preset -M visual -m default x kill-selection end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default X kill-whole-line end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default y kill-selection yank end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default '"*y' "fish_clipboard_copy; commandline -f end-selection repaint-mode"
|
||||
bind -s --preset -M visual -m default '"+y' "fish_clipboard_copy; commandline -f end-selection repaint-mode"
|
||||
bind -s --preset -M visual -m default '",*,y' "fish_clipboard_copy; commandline -f end-selection repaint-mode"
|
||||
bind -s --preset -M visual -m default '",+,y' "fish_clipboard_copy; commandline -f end-selection repaint-mode"
|
||||
bind -s --preset -M visual -m default '~' togglecase-selection end-selection repaint-mode
|
||||
|
||||
bind -s --preset -M visual -m default \cc end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default \e end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default ctrl-c end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default escape end-selection repaint-mode
|
||||
bind -s --preset -M visual -m default ctrl-\[ end-selection repaint-mode
|
||||
|
||||
# Make it easy to turn an unexecuted command into a comment in the shell history. Also, remove
|
||||
# the commenting chars so the command can be further edited then executed.
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
//!
|
||||
//! Type "exit" or "quit" to terminate the program.
|
||||
|
||||
use core::panic;
|
||||
use std::{
|
||||
ops::ControlFlow,
|
||||
os::unix::prelude::OsStrExt,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{ops::ControlFlow, os::unix::prelude::OsStrExt};
|
||||
|
||||
use libc::{STDIN_FILENO, TCSANOW, VEOF, VINTR};
|
||||
|
||||
|
@ -22,16 +17,15 @@ use fish::{
|
|||
builtins::shared::BUILTIN_ERR_UNKNOWN,
|
||||
common::{shell_modes, str2wcstring, PROGRAM_NAME},
|
||||
env::env_init,
|
||||
eprintf,
|
||||
fallback::fish_wcwidth,
|
||||
fprintf,
|
||||
eprintf, fprintf,
|
||||
input::input_terminfo_get_name,
|
||||
input_common::{CharEvent, InputEventQueue, InputEventQueuer},
|
||||
key::Key,
|
||||
panic::panic_handler,
|
||||
print_help::print_help,
|
||||
printf,
|
||||
proc::set_interactive_session,
|
||||
reader::{check_exit_loop_maybe_warning, reader_init, reader_test_and_clear_interrupted},
|
||||
reader::{check_exit_loop_maybe_warning, reader_init},
|
||||
signal::signal_set_handlers,
|
||||
threads,
|
||||
topic_monitor::topic_monitor_init,
|
||||
|
@ -40,15 +34,15 @@ use fish::{
|
|||
};
|
||||
|
||||
/// Return true if the recent sequence of characters indicates the user wants to exit the program.
|
||||
fn should_exit(recent_chars: &mut Vec<u8>, c: char) -> bool {
|
||||
let c = if c < '\u{80}' { c as u8 } else { 0 };
|
||||
|
||||
recent_chars.push(c);
|
||||
fn should_exit(recent_keys: &mut Vec<Key>, key: Key) -> bool {
|
||||
recent_keys.push(key);
|
||||
|
||||
for evt in [VINTR, VEOF] {
|
||||
let modes = shell_modes();
|
||||
if c == modes.c_cc[evt] {
|
||||
if recent_chars.iter().rev().nth(1) == Some(&modes.c_cc[evt]) {
|
||||
let cc = Key::from_single_byte(modes.c_cc[evt]);
|
||||
|
||||
if key == cc {
|
||||
if recent_keys.iter().rev().nth(1) == Some(&cc) {
|
||||
return true;
|
||||
}
|
||||
eprintf!(
|
||||
|
@ -59,7 +53,15 @@ fn should_exit(recent_chars: &mut Vec<u8>, c: char) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
recent_chars.ends_with(b"exit") || recent_chars.ends_with(b"quit")
|
||||
let Some(tail) = recent_keys
|
||||
.len()
|
||||
.checked_sub(4)
|
||||
.and_then(|start| recent_keys.get(start..))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let tail = tail.iter().map(|c| c.codepoint);
|
||||
tail.clone().eq("exit".chars()) || tail.eq("quit".chars())
|
||||
}
|
||||
|
||||
/// Return the name if the recent sequence of characters matches a known terminfo sequence.
|
||||
|
@ -80,116 +82,17 @@ fn sequence_name(recent_chars: &mut Vec<u8>, c: char) -> Option<WString> {
|
|||
input_terminfo_get_name(&str2wcstring(recent_chars))
|
||||
}
|
||||
|
||||
/// Return true if the character must be escaped when used in the sequence of chars to be bound in
|
||||
/// a `bind` command.
|
||||
fn must_escape(c: char) -> bool {
|
||||
"[]()<>{}*\\?$#;&|'\"".contains(c)
|
||||
}
|
||||
|
||||
fn ctrl_to_symbol(buf: &mut WString, c: char, bind_friendly: bool) {
|
||||
let ctrl_symbolic_names: [&wstr; 29] = {
|
||||
std::array::from_fn(|i| match i {
|
||||
8 => L!("\\b"),
|
||||
9 => L!("\\t"),
|
||||
10 => L!("\\n"),
|
||||
13 => L!("\\r"),
|
||||
27 => L!("\\e"),
|
||||
28 => L!("\\x1c"),
|
||||
_ => L!(""),
|
||||
})
|
||||
};
|
||||
|
||||
let c = u8::try_from(c).unwrap();
|
||||
let cu = usize::from(c);
|
||||
|
||||
if !ctrl_symbolic_names[cu].is_empty() {
|
||||
if bind_friendly {
|
||||
sprintf!(=> buf, "%s", ctrl_symbolic_names[cu]);
|
||||
} else {
|
||||
sprintf!(=> buf, "\\c%c (or %ls)", char::from(c + 0x40), ctrl_symbolic_names[cu]);
|
||||
}
|
||||
} else {
|
||||
sprintf!(=> buf, "\\c%c", char::from(c + 0x40));
|
||||
}
|
||||
}
|
||||
|
||||
fn space_to_symbol(buf: &mut WString, c: char, bind_friendly: bool) {
|
||||
if bind_friendly {
|
||||
sprintf!(=> buf, "\\x%X", u32::from(c));
|
||||
} else {
|
||||
sprintf!(=> buf, "\\x%X (aka \"space\")", u32::from(c));
|
||||
}
|
||||
}
|
||||
|
||||
fn del_to_symbol(buf: &mut WString, c: char, bind_friendly: bool) {
|
||||
if bind_friendly {
|
||||
sprintf!(=> buf, "\\x%X", u32::from(c));
|
||||
} else {
|
||||
sprintf!(=> buf, "\\x%X (aka \"del\")", u32::from(c));
|
||||
}
|
||||
}
|
||||
|
||||
fn ascii_printable_to_symbol(buf: &mut WString, c: char, bind_friendly: bool) {
|
||||
if bind_friendly && must_escape(c) {
|
||||
sprintf!(=> buf, "\\%c", c);
|
||||
} else {
|
||||
sprintf!(=> buf, "%c", c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a wide-char to a symbol that can be used in our output.
|
||||
fn char_to_symbol(c: char, bind_friendly: bool) -> WString {
|
||||
let mut buff = WString::new();
|
||||
let buf = &mut buff;
|
||||
if c == '\x1b' {
|
||||
// Escape - this is *technically* also \c[
|
||||
buf.push_str("\\e");
|
||||
} else if c < ' ' {
|
||||
// ASCII control character
|
||||
ctrl_to_symbol(buf, c, bind_friendly);
|
||||
} else if c == ' ' {
|
||||
// the "space" character
|
||||
space_to_symbol(buf, c, bind_friendly);
|
||||
} else if c == '\x7F' {
|
||||
// the "del" character
|
||||
del_to_symbol(buf, c, bind_friendly);
|
||||
} else if c < '\u{80}' {
|
||||
// ASCII characters that are not control characters
|
||||
ascii_printable_to_symbol(buf, c, bind_friendly);
|
||||
} else if fish_wcwidth(c) > 0 {
|
||||
sprintf!(=> buf, "%lc", c);
|
||||
} else if c <= '\u{FFFF}' {
|
||||
// BMP Unicode chararacter
|
||||
sprintf!(=> buf, "\\u%04X", u32::from(c));
|
||||
} else {
|
||||
sprintf!(=> buf, "\\U%06X", u32::from(c));
|
||||
}
|
||||
buff
|
||||
}
|
||||
|
||||
fn add_char_to_bind_command(c: char, bind_chars: &mut Vec<char>) {
|
||||
bind_chars.push(c);
|
||||
}
|
||||
|
||||
fn output_bind_command(bind_chars: &mut Vec<char>) {
|
||||
fn output_bind_command(bind_chars: &mut Vec<(Key, WString)>) {
|
||||
if !bind_chars.is_empty() {
|
||||
printf!("bind ");
|
||||
for &bind_char in &*bind_chars {
|
||||
printf!("%s", char_to_symbol(bind_char, true));
|
||||
for (key, _seq) in &*bind_chars {
|
||||
printf!("%s", key);
|
||||
}
|
||||
printf!(" 'do something'\n");
|
||||
bind_chars.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn output_info_about_char(c: char) {
|
||||
eprintf!(
|
||||
"hex: %4X char: %ls\n",
|
||||
u32::from(c),
|
||||
char_to_symbol(c, false)
|
||||
);
|
||||
}
|
||||
|
||||
fn output_matching_key_name(recent_chars: &mut Vec<u8>, c: char) -> bool {
|
||||
if let Some(name) = sequence_name(recent_chars, c) {
|
||||
printf!("bind -k %ls 'do something'\n", name);
|
||||
|
@ -198,72 +101,29 @@ fn output_matching_key_name(recent_chars: &mut Vec<u8>, c: char) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
fn output_elapsed_time(prev_timestamp: Instant, first_char_seen: bool, verbose: bool) -> Instant {
|
||||
// How much time has passed since the previous char was received in microseconds.
|
||||
let now = Instant::now();
|
||||
let delta = now - prev_timestamp;
|
||||
|
||||
if verbose {
|
||||
if delta >= Duration::from_millis(200) && first_char_seen {
|
||||
eprintf!("\n");
|
||||
}
|
||||
if delta >= Duration::from_millis(1000) {
|
||||
eprintf!(" ");
|
||||
} else {
|
||||
eprintf!(
|
||||
"(%3lld.%03lld ms) ",
|
||||
u64::try_from(delta.as_millis()).unwrap(),
|
||||
u64::try_from(delta.as_micros() % 1000).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
now
|
||||
}
|
||||
|
||||
/// Process the characters we receive as the user presses keys.
|
||||
fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
|
||||
fn process_input(continuous_mode: bool) -> i32 {
|
||||
let mut first_char_seen = false;
|
||||
let mut prev_timestamp = Instant::now()
|
||||
.checked_sub(Duration::from_millis(1000))
|
||||
.unwrap_or(Instant::now());
|
||||
let mut queue = InputEventQueue::new(STDIN_FILENO);
|
||||
let mut bind_chars = vec![];
|
||||
let mut recent_chars1 = vec![];
|
||||
let mut recent_chars2 = vec![];
|
||||
eprintf!("Press a key:\n");
|
||||
|
||||
while !check_exit_loop_maybe_warning(None) {
|
||||
let evt = if reader_test_and_clear_interrupted() != 0 {
|
||||
Some(CharEvent::from_char(char::from(shell_modes().c_cc[VINTR])))
|
||||
} else {
|
||||
queue.readch_timed_esc()
|
||||
};
|
||||
while (!first_char_seen || continuous_mode) && !check_exit_loop_maybe_warning(None) {
|
||||
let evt = queue.readch();
|
||||
|
||||
if evt.as_ref().is_none_or(|evt| !evt.is_char()) {
|
||||
output_bind_command(&mut bind_chars);
|
||||
if first_char_seen && !continuous_mode {
|
||||
return 0;
|
||||
}
|
||||
let CharEvent::Key(kevt) = evt else {
|
||||
continue;
|
||||
}
|
||||
let evt = evt.unwrap();
|
||||
|
||||
let c = evt.get_char().unwrap();
|
||||
prev_timestamp = output_elapsed_time(prev_timestamp, first_char_seen, verbose);
|
||||
// Hack for #3189. Do not suggest \c@ as the binding for nul, because a string containing
|
||||
// nul cannot be passed to builtin_bind since it uses C strings. We'll output the name of
|
||||
// this key (nul) elsewhere.
|
||||
if c != '\0' {
|
||||
add_char_to_bind_command(c, &mut bind_chars);
|
||||
}
|
||||
if verbose {
|
||||
output_info_about_char(c);
|
||||
}
|
||||
};
|
||||
let c = kevt.key.codepoint;
|
||||
bind_chars.push((kevt.key, kevt.seq));
|
||||
output_bind_command(&mut bind_chars);
|
||||
if output_matching_key_name(&mut recent_chars1, c) {
|
||||
output_bind_command(&mut bind_chars);
|
||||
}
|
||||
|
||||
if continuous_mode && should_exit(&mut recent_chars2, c) {
|
||||
if continuous_mode && should_exit(&mut recent_chars2, kevt.key) {
|
||||
eprintf!("\nExiting at your request.\n");
|
||||
break;
|
||||
}
|
||||
|
@ -274,7 +134,7 @@ fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
|
|||
}
|
||||
|
||||
/// Setup our environment (e.g., tty modes), process key strokes, then reset the environment.
|
||||
fn setup_and_process_keys(continuous_mode: bool, verbose: bool) -> i32 {
|
||||
fn setup_and_process_keys(continuous_mode: bool) -> i32 {
|
||||
set_interactive_session(true);
|
||||
topic_monitor_init();
|
||||
threads::init();
|
||||
|
@ -298,16 +158,16 @@ fn setup_and_process_keys(continuous_mode: bool, verbose: bool) -> i32 {
|
|||
eprintf!("\n");
|
||||
}
|
||||
|
||||
process_input(continuous_mode, verbose)
|
||||
process_input(continuous_mode)
|
||||
}
|
||||
|
||||
fn parse_flags(continuous_mode: &mut bool, verbose: &mut bool) -> ControlFlow<i32> {
|
||||
fn parse_flags(continuous_mode: &mut bool) -> ControlFlow<i32> {
|
||||
let short_opts: &wstr = L!("+chvV");
|
||||
let long_opts: &[woption] = &[
|
||||
wopt(L!("continuous"), woption_argument_t::no_argument, 'c'),
|
||||
wopt(L!("help"), woption_argument_t::no_argument, 'h'),
|
||||
wopt(L!("version"), woption_argument_t::no_argument, 'v'),
|
||||
wopt(L!("verbose"), woption_argument_t::no_argument, 'V'),
|
||||
wopt(L!("verbose"), woption_argument_t::no_argument, 'V'), // Removed
|
||||
];
|
||||
|
||||
let args: Vec<WString> = std::env::args_os()
|
||||
|
@ -335,9 +195,7 @@ fn parse_flags(continuous_mode: &mut bool, verbose: &mut bool) -> ControlFlow<i3
|
|||
);
|
||||
return ControlFlow::Break(0);
|
||||
}
|
||||
'V' => {
|
||||
*verbose = true;
|
||||
}
|
||||
'V' => {}
|
||||
'?' => {
|
||||
printf!(
|
||||
"%s",
|
||||
|
@ -369,9 +227,8 @@ fn main() {
|
|||
|
||||
fn throwing_main() -> i32 {
|
||||
let mut continuous_mode = false;
|
||||
let mut verbose = false;
|
||||
|
||||
if let ControlFlow::Break(i) = parse_flags(&mut continuous_mode, &mut verbose) {
|
||||
if let ControlFlow::Break(i) = parse_flags(&mut continuous_mode) {
|
||||
return i;
|
||||
}
|
||||
|
||||
|
@ -380,5 +237,5 @@ fn throwing_main() -> i32 {
|
|||
return 1;
|
||||
}
|
||||
|
||||
setup_and_process_keys(continuous_mode, verbose)
|
||||
setup_and_process_keys(continuous_mode)
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ use crate::common::{
|
|||
};
|
||||
use crate::highlight::{colorize, highlight_shell};
|
||||
use crate::input::{
|
||||
input_function_get_names, input_mappings, input_terminfo_get_name, input_terminfo_get_names,
|
||||
input_function_get_names, input_mappings, input_terminfo_get_names,
|
||||
input_terminfo_get_sequence, GetSequenceError, InputMappingSet,
|
||||
};
|
||||
use crate::key::{self, canonicalize_raw_escapes, parse_keys, Key};
|
||||
use crate::nix::isatty;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
|
@ -75,7 +76,7 @@ impl BuiltinBind {
|
|||
/// Returns false if no binding with that sequence and mode exists.
|
||||
fn list_one(
|
||||
&self,
|
||||
seq: &wstr,
|
||||
seq: &[Key],
|
||||
bind_mode: &wstr,
|
||||
user: bool,
|
||||
parser: &Parser,
|
||||
|
@ -83,11 +84,16 @@ impl BuiltinBind {
|
|||
) -> bool {
|
||||
let mut ecmds: &[_] = &[];
|
||||
let mut sets_mode = None;
|
||||
let mut terminfo_name = None;
|
||||
let mut out = WString::new();
|
||||
if !self
|
||||
.input_mappings
|
||||
.get(seq, bind_mode, &mut ecmds, user, &mut sets_mode)
|
||||
{
|
||||
if !self.input_mappings.get(
|
||||
seq,
|
||||
bind_mode,
|
||||
&mut ecmds,
|
||||
user,
|
||||
&mut sets_mode,
|
||||
&mut terminfo_name,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -109,16 +115,22 @@ impl BuiltinBind {
|
|||
}
|
||||
}
|
||||
|
||||
// Append the name.
|
||||
if let Some(tname) = input_terminfo_get_name(seq) {
|
||||
if let Some(tname) = terminfo_name {
|
||||
// Note that we show -k here because we have an input key name.
|
||||
out.push_str(" -k ");
|
||||
out.push_utfstr(&tname);
|
||||
} else {
|
||||
// No key name, so no -k; we show the escape sequence directly.
|
||||
let eseq = escape(seq);
|
||||
out.push(' ');
|
||||
out.push_utfstr(&eseq);
|
||||
// Append the name.
|
||||
for (i, key) in seq.iter().enumerate() {
|
||||
if i != 0 {
|
||||
out.push(key::KEY_SEPARATOR);
|
||||
}
|
||||
out.push_utfstr(&WString::from(*key));
|
||||
}
|
||||
if seq.is_empty() {
|
||||
out.push_str("''");
|
||||
}
|
||||
}
|
||||
|
||||
// Now show the list of commands.
|
||||
|
@ -144,7 +156,7 @@ impl BuiltinBind {
|
|||
// Returns false only if neither exists.
|
||||
fn list_one_user_andor_preset(
|
||||
&self,
|
||||
seq: &wstr,
|
||||
seq: &[Key],
|
||||
bind_mode: &wstr,
|
||||
user: bool,
|
||||
preset: bool,
|
||||
|
@ -224,41 +236,53 @@ impl BuiltinBind {
|
|||
cmds: &[&wstr],
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
terminfo: bool,
|
||||
is_terminfo_key: bool,
|
||||
user: bool,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let cmds = cmds.iter().map(|&s| s.to_owned()).collect();
|
||||
if terminfo {
|
||||
if let Some(seq2) = self.get_terminfo_sequence(seq, streams) {
|
||||
self.input_mappings.add(seq2, cmds, mode, sets_mode, user);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
self.input_mappings
|
||||
.add(seq.to_owned(), cmds, mode, sets_mode, user)
|
||||
}
|
||||
let Some(key_seq) = self.compute_seq(streams, seq) else {
|
||||
return true;
|
||||
};
|
||||
self.input_mappings.add(
|
||||
key_seq,
|
||||
is_terminfo_key.then(|| seq.to_owned()),
|
||||
cmds,
|
||||
mode,
|
||||
sets_mode,
|
||||
user,
|
||||
);
|
||||
false
|
||||
}
|
||||
|
||||
fn compute_seq(&self, streams: &mut IoStreams, seq: &wstr) -> Option<Vec<Key>> {
|
||||
if self.opts.use_terminfo {
|
||||
let Some(tinfo_seq) = self.get_terminfo_sequence(seq, streams) else {
|
||||
// get_terminfo_sequence already printed the error.
|
||||
return None;
|
||||
};
|
||||
Some(canonicalize_raw_escapes(
|
||||
tinfo_seq.chars().map(Key::from_single_char).collect(),
|
||||
))
|
||||
} else {
|
||||
match parse_keys(seq) {
|
||||
Ok(keys) => Some(keys),
|
||||
Err(err) => {
|
||||
streams.err.append(sprintf!("bind: %s\n", err));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Erase specified key bindings
|
||||
///
|
||||
/// @param seq
|
||||
/// an array of all key bindings to erase
|
||||
/// @param all
|
||||
/// if specified, _all_ key bindings will be erased
|
||||
/// @param use_terminfo
|
||||
/// Whether to look use terminfo -k name
|
||||
///
|
||||
fn erase(
|
||||
&mut self,
|
||||
seq: &[&wstr],
|
||||
all: bool,
|
||||
use_terminfo: bool,
|
||||
user: bool,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
fn erase(&mut self, seq: &[&wstr], all: bool, user: bool, streams: &mut IoStreams) -> bool {
|
||||
let mode = if self.opts.bind_mode_given {
|
||||
Some(self.opts.bind_mode.as_utfstr())
|
||||
} else {
|
||||
|
@ -270,21 +294,15 @@ impl BuiltinBind {
|
|||
return false;
|
||||
}
|
||||
|
||||
let mut res = false;
|
||||
let mode = mode.unwrap_or(DEFAULT_BIND_MODE);
|
||||
|
||||
for s in seq {
|
||||
if use_terminfo {
|
||||
if let Some(seq2) = self.get_terminfo_sequence(s, streams) {
|
||||
self.input_mappings.erase(&seq2, mode, user);
|
||||
} else {
|
||||
res = true;
|
||||
}
|
||||
} else {
|
||||
self.input_mappings.erase(s, mode, user);
|
||||
}
|
||||
let Some(s) = self.compute_seq(streams, s) else {
|
||||
return true;
|
||||
};
|
||||
self.input_mappings.erase(&s, mode, user);
|
||||
}
|
||||
res
|
||||
false
|
||||
}
|
||||
|
||||
fn insert(
|
||||
|
@ -331,14 +349,8 @@ impl BuiltinBind {
|
|||
self.list(bind_mode, true, parser, streams);
|
||||
}
|
||||
} else if arg_count == 1 {
|
||||
let seq = if self.opts.use_terminfo {
|
||||
let Some(seq2) = self.get_terminfo_sequence(argv[optind], streams) else {
|
||||
// get_terminfo_sequence already printed the error.
|
||||
return true;
|
||||
};
|
||||
seq2
|
||||
} else {
|
||||
argv[optind].to_owned()
|
||||
let Some(seq) = self.compute_seq(streams, argv[optind]) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
if !self.list_one_user_andor_preset(
|
||||
|
@ -360,9 +372,15 @@ impl BuiltinBind {
|
|||
cmd,
|
||||
eseq
|
||||
));
|
||||
} else if seq.len() == 1 {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: No binding found for key '%ls'\n",
|
||||
cmd,
|
||||
eseq
|
||||
));
|
||||
} else {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: No binding found for sequence '%ls'\n",
|
||||
"%ls: No binding found for key sequence '%ls'\n",
|
||||
cmd,
|
||||
eseq
|
||||
));
|
||||
|
@ -372,8 +390,9 @@ impl BuiltinBind {
|
|||
}
|
||||
} else {
|
||||
// Actually insert!
|
||||
let seq = argv[optind];
|
||||
if self.add(
|
||||
argv[optind],
|
||||
seq,
|
||||
&argv[optind + 1..],
|
||||
self.opts.bind_mode.to_owned(),
|
||||
self.opts.sets_bind_mode.to_owned(),
|
||||
|
@ -527,7 +546,6 @@ impl BuiltinBind {
|
|||
if self.erase(
|
||||
&argv[optind..],
|
||||
self.opts.all,
|
||||
self.opts.use_terminfo,
|
||||
true, /* user */
|
||||
streams,
|
||||
) {
|
||||
|
@ -538,7 +556,6 @@ impl BuiltinBind {
|
|||
if self.erase(
|
||||
&argv[optind..],
|
||||
self.opts.all,
|
||||
self.opts.use_terminfo,
|
||||
false, /* user */
|
||||
streams,
|
||||
) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Implementation of the fg builtin.
|
||||
|
||||
use crate::fds::make_fd_blocking;
|
||||
use crate::input_common::terminal_protocols_disable_scoped;
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::reader_write_title;
|
||||
use crate::tokenizer::tok_command;
|
||||
use crate::wutil::perror;
|
||||
|
@ -155,6 +157,8 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Optio
|
|||
}
|
||||
}
|
||||
}
|
||||
let _terminal_protocols = (is_interactive_session() && job.group().wants_terminal())
|
||||
.then(terminal_protocols_disable_scoped);
|
||||
let mut transfer = TtyTransfer::new();
|
||||
transfer.to_job_group(job.group.as_ref().unwrap());
|
||||
let resumed = job.resume();
|
||||
|
|
|
@ -7,15 +7,16 @@ use crate::common::scoped_push_replacer;
|
|||
use crate::common::str2wcstring;
|
||||
use crate::common::unescape_string;
|
||||
use crate::common::valid_var_name;
|
||||
use crate::common::ScopeGuard;
|
||||
use crate::common::UnescapeStringStyle;
|
||||
use crate::env::EnvMode;
|
||||
use crate::env::Environment;
|
||||
use crate::env::READ_BYTE_LIMIT;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::input_common::terminal_protocols_enable_scoped;
|
||||
use crate::libc::MB_CUR_MAX;
|
||||
use crate::nix::isatty;
|
||||
use crate::reader::commandline_set_buffer;
|
||||
use crate::reader::reader_current_data;
|
||||
use crate::reader::ReaderConfig;
|
||||
use crate::reader::{reader_pop, reader_push, reader_readline};
|
||||
use crate::tokenizer::Tokenizer;
|
||||
|
@ -26,9 +27,7 @@ use crate::wutil;
|
|||
use crate::wutil::encoding::mbrtowc;
|
||||
use crate::wutil::encoding::zero_mbstate;
|
||||
use crate::wutil::perror;
|
||||
use crate::wutil::write_to_fd;
|
||||
use libc::SEEK_CUR;
|
||||
use libc::STDOUT_FILENO;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
|
@ -592,12 +591,9 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Opt
|
|||
|
||||
let stream_stdin_is_a_tty = isatty(streams.stdin_fd);
|
||||
|
||||
let _maybe_disable_bracketed_paste = stream_stdin_is_a_tty.then(|| {
|
||||
let _ = write_to_fd(b"\x1b[?2004h", STDOUT_FILENO);
|
||||
ScopeGuard::new((), |()| {
|
||||
let _ = write_to_fd(b"\x1b[?2004l", STDOUT_FILENO);
|
||||
})
|
||||
});
|
||||
// Enable terminal protocols if noninteractive.
|
||||
let _terminal_protocols = (stream_stdin_is_a_tty && reader_current_data().is_none())
|
||||
.then(terminal_protocols_enable_scoped);
|
||||
|
||||
// Normally, we either consume a line of input or all available input. But if we are reading a
|
||||
// line at a time, we need a middle ground where we only consume as many lines as we need to
|
||||
|
|
17
src/exec.rs
17
src/exec.rs
|
@ -24,6 +24,9 @@ use crate::fork_exec::postfork::{
|
|||
#[cfg(FISH_USE_POSIX_SPAWN)]
|
||||
use crate::fork_exec::spawn::PosixSpawner;
|
||||
use crate::function::{self, FunctionProperties};
|
||||
use crate::input_common::{
|
||||
terminal_protocols_disable, terminal_protocols_disable_scoped, TERMINAL_PROTOCOLS,
|
||||
};
|
||||
use crate::io::{
|
||||
BufferedOutputStream, FdOutputStream, IoBufferfill, IoChain, IoClose, IoMode, IoPipe,
|
||||
IoStreams, OutputStream, SeparatedBuffer, StringOutputStream,
|
||||
|
@ -39,7 +42,7 @@ use crate::proc::{
|
|||
print_exit_warning_for_jobs, InternalProc, Job, JobGroupRef, ProcStatus, Process, ProcessType,
|
||||
TtyTransfer, INVALID_PID,
|
||||
};
|
||||
use crate::reader::{reader_run_count, restore_term_mode};
|
||||
use crate::reader::{reader_current_data, reader_run_count, restore_term_mode};
|
||||
use crate::redirection::{dup2_list_resolve_chain, Dup2List};
|
||||
use crate::threads::{iothread_perform_cant_wait, is_forked_child};
|
||||
use crate::timer::push_timer;
|
||||
|
@ -72,6 +75,15 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
|||
return true;
|
||||
}
|
||||
|
||||
let _terminal_protocols_disabled = (
|
||||
// If interactive or inside noninteractive builtin read.
|
||||
reader_current_data().is_some() &&
|
||||
// If we try to start an external process.
|
||||
job.group().wants_terminal()
|
||||
&& TERMINAL_PROTOCOLS.get().borrow().is_some()
|
||||
)
|
||||
.then(terminal_protocols_disable_scoped);
|
||||
|
||||
// Handle an exec call.
|
||||
if job.processes()[0].typ == ProcessType::exec {
|
||||
// If we are interactive, perhaps disallow exec if there are background jobs.
|
||||
|
@ -439,6 +451,9 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
|||
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
restore_term_mode();
|
||||
if reader_current_data().is_some() && TERMINAL_PROTOCOLS.get().borrow().is_some() {
|
||||
terminal_protocols_disable();
|
||||
}
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &*envp);
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ pub mod categories {
|
|||
(fd_monitor, "fd-monitor", "FD monitor events");
|
||||
|
||||
(term_support, "term-support", "Terminal feature detection");
|
||||
(term_protocols, "term-protocols", "Terminal protocol negotiation");
|
||||
|
||||
(reader, "reader", "The interactive reader/input system");
|
||||
(reader_render, "reader-render", "Rendering the command line");
|
||||
|
|
341
src/input.rs
341
src/input.rs
|
@ -6,6 +6,7 @@ use crate::flog::FLOG;
|
|||
use crate::input_common::{
|
||||
CharEvent, CharInputStyle, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
};
|
||||
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key};
|
||||
use crate::parser::Parser;
|
||||
use crate::proc::job_reap;
|
||||
use crate::reader::{
|
||||
|
@ -33,7 +34,7 @@ pub const NUL_MAPPING_NAME: &wstr = L!("nul");
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputMappingName {
|
||||
pub seq: WString,
|
||||
pub seq: Vec<Key>,
|
||||
pub mode: WString,
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,7 @@ pub struct InputMappingName {
|
|||
#[derive(Debug, Clone)]
|
||||
struct InputMapping {
|
||||
/// Character sequence which generates this event.
|
||||
seq: WString,
|
||||
seq: Vec<Key>,
|
||||
/// Commands that should be evaluated by this mapping.
|
||||
commands: Vec<WString>,
|
||||
/// We wish to preserve the user-specified order. This is just an incrementing value.
|
||||
|
@ -50,15 +51,18 @@ struct InputMapping {
|
|||
mode: WString,
|
||||
/// New mode that should be switched to after command evaluation, or None to leave the mode unchanged.
|
||||
sets_mode: Option<WString>,
|
||||
/// Whether this sequence was specified via its terminfo name.
|
||||
terminfo_name: Option<WString>,
|
||||
}
|
||||
|
||||
impl InputMapping {
|
||||
/// Create a new mapping.
|
||||
fn new(
|
||||
seq: WString,
|
||||
seq: Vec<Key>,
|
||||
commands: Vec<WString>,
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
terminfo_name: Option<WString>,
|
||||
) -> InputMapping {
|
||||
static LAST_INPUT_MAP_SPEC_ORDER: AtomicU32 = AtomicU32::new(0);
|
||||
let specification_order = 1 + LAST_INPUT_MAP_SPEC_ORDER.fetch_add(1, Ordering::Relaxed);
|
||||
|
@ -72,6 +76,7 @@ impl InputMapping {
|
|||
specification_order,
|
||||
mode,
|
||||
sets_mode,
|
||||
terminfo_name,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +120,8 @@ const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadat
|
|||
const INPUT_FUNCTION_METADATA: &[InputFunctionMetadata] = &[
|
||||
// NULL makes it unusable - this is specially inserted when we detect mouse input
|
||||
make_md(L!(""), ReadlineCmd::DisableMouseTracking),
|
||||
make_md(L!(""), ReadlineCmd::FocusIn),
|
||||
make_md(L!(""), ReadlineCmd::FocusOut),
|
||||
make_md(L!("accept-autosuggestion"), ReadlineCmd::AcceptAutosuggestion),
|
||||
make_md(L!("and"), ReadlineCmd::FuncAnd),
|
||||
make_md(L!("backward-bigword"), ReadlineCmd::BackwardBigword),
|
||||
|
@ -274,7 +281,8 @@ impl InputMappingSet {
|
|||
/// Adds an input mapping.
|
||||
pub fn add(
|
||||
&mut self,
|
||||
sequence: WString,
|
||||
sequence: Vec<Key>,
|
||||
terminfo_name: Option<WString>,
|
||||
commands: Vec<WString>,
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
|
@ -299,20 +307,28 @@ impl InputMappingSet {
|
|||
}
|
||||
|
||||
// Add a new mapping, using the next order.
|
||||
let new_mapping = InputMapping::new(sequence, commands, mode, sets_mode);
|
||||
let new_mapping = InputMapping::new(sequence, commands, mode, sets_mode, terminfo_name);
|
||||
input_mapping_insert_sorted(ml, new_mapping);
|
||||
}
|
||||
|
||||
// Like add(), but takes a single command.
|
||||
pub fn add1(
|
||||
&mut self,
|
||||
sequence: WString,
|
||||
sequence: Vec<Key>,
|
||||
terminfo_name: Option<WString>,
|
||||
command: WString,
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
user: bool,
|
||||
) {
|
||||
self.add(sequence, vec![command], mode, sets_mode, user);
|
||||
self.add(
|
||||
sequence,
|
||||
terminfo_name,
|
||||
vec![command],
|
||||
mode,
|
||||
sets_mode,
|
||||
user,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,35 +346,44 @@ pub fn init_input() {
|
|||
// If we have no keybindings, add a few simple defaults.
|
||||
if input_mapping.preset_mapping_list.is_empty() {
|
||||
// Helper for adding.
|
||||
let mut add = |seq: &str, cmd: &str| {
|
||||
let mut add = |key: Vec<Key>, cmd: &str| {
|
||||
let mode = DEFAULT_BIND_MODE.to_owned();
|
||||
let sets_mode = Some(DEFAULT_BIND_MODE.to_owned());
|
||||
input_mapping.add1(seq.into(), cmd.into(), mode, sets_mode, false);
|
||||
input_mapping.add1(key, None, cmd.into(), mode, sets_mode, false);
|
||||
};
|
||||
|
||||
add("", "self-insert");
|
||||
add("\n", "execute");
|
||||
add("\r", "execute");
|
||||
add("\t", "complete");
|
||||
add("\x03", "cancel-commandline");
|
||||
add("\x04", "exit");
|
||||
add("\x05", "bind");
|
||||
// ctrl-s
|
||||
add("\x13", "pager-toggle-search");
|
||||
// ctrl-u
|
||||
add("\x15", "backward-kill-line");
|
||||
// del/backspace
|
||||
add("\x7f", "backward-delete-char");
|
||||
add(vec![], "self-insert");
|
||||
add(vec![Key::from_raw(key::Enter)], "execute");
|
||||
add(vec![Key::from_raw(key::Tab)], "complete");
|
||||
add(vec![ctrl('c')], "cancel-commandline");
|
||||
add(vec![ctrl('d')], "exit");
|
||||
add(vec![ctrl('e')], "bind");
|
||||
add(vec![ctrl('s')], "pager-toggle-search");
|
||||
add(vec![ctrl('u')], "backward-kill-line");
|
||||
add(vec![Key::from_raw(key::Backspace)], "backward-delete-char");
|
||||
// Arrows - can't have functions, so *-or-search isn't available.
|
||||
add("\x1B[A", "up-line");
|
||||
add("\x1B[B", "down-line");
|
||||
add("\x1B[C", "forward-char");
|
||||
add("\x1B[D", "backward-char");
|
||||
// emacs-style ctrl-p/n/b/f
|
||||
add("\x10", "up-line");
|
||||
add("\x0e", "down-line");
|
||||
add("\x02", "backward-char");
|
||||
add("\x06", "forward-char");
|
||||
add(vec![Key::from_raw(key::Up)], "up-line");
|
||||
add(vec![Key::from_raw(key::Down)], "down-line");
|
||||
add(vec![Key::from_raw(key::Right)], "forward-char");
|
||||
add(vec![Key::from_raw(key::Left)], "backward-char");
|
||||
// Emacs style
|
||||
add(vec![ctrl('p')], "up-line");
|
||||
add(vec![ctrl('n')], "down-line");
|
||||
add(vec![ctrl('b')], "backward-char");
|
||||
add(vec![ctrl('f')], "forward-char");
|
||||
|
||||
let mut add_legacy = |escape_sequence: &str, cmd: &str| {
|
||||
add(
|
||||
canonicalize_raw_escapes(
|
||||
escape_sequence.chars().map(Key::from_single_char).collect(),
|
||||
),
|
||||
cmd,
|
||||
);
|
||||
};
|
||||
add_legacy("\x1B[A", "up-line");
|
||||
add_legacy("\x1B[B", "down-line");
|
||||
add_legacy("\x1B[C", "forward-char");
|
||||
add_legacy("\x1B[D", "backward-char");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,6 +395,7 @@ pub type CommandHandler<'a> = dyn FnMut(&[WString]) + 'a;
|
|||
pub struct Inputter {
|
||||
in_fd: RawFd,
|
||||
queue: VecDeque<CharEvent>,
|
||||
paste_buffer: Option<Vec<u8>>,
|
||||
// We need a parser to evaluate bindings.
|
||||
parser: Rc<Parser>,
|
||||
input_function_args: Vec<char>,
|
||||
|
@ -417,7 +443,7 @@ impl InputEventQueuer for Inputter {
|
|||
if reader_reading_interrupted() != 0 {
|
||||
let vintr = shell_modes().c_cc[libc::VINTR];
|
||||
if vintr != 0 {
|
||||
self.push_front(CharEvent::from_char(vintr.into()));
|
||||
self.push_front(CharEvent::from_key(Key::from_single_byte(vintr)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -427,6 +453,23 @@ impl InputEventQueuer for Inputter {
|
|||
fn uvar_change_notified(&mut self) {
|
||||
self.parser.sync_uvars_and_fire(true /* always */);
|
||||
}
|
||||
|
||||
fn paste_start_buffering(&mut self) {
|
||||
self.paste_buffer = Some(vec![]);
|
||||
}
|
||||
fn paste_is_buffering(&self) -> bool {
|
||||
self.paste_buffer.is_some()
|
||||
}
|
||||
fn paste_commit(&mut self) {
|
||||
let buffer = self.paste_buffer.take().unwrap();
|
||||
self.push_front(CharEvent::Command(sprintf!(
|
||||
"__fish_paste %s",
|
||||
escape(&str2wcstring(&buffer))
|
||||
)));
|
||||
}
|
||||
fn paste_push_char(&mut self, b: u8) {
|
||||
self.paste_buffer.as_mut().unwrap().push(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inputter {
|
||||
|
@ -435,6 +478,7 @@ impl Inputter {
|
|||
Inputter {
|
||||
in_fd,
|
||||
queue: VecDeque::new(),
|
||||
paste_buffer: None,
|
||||
parser,
|
||||
input_function_args: Vec::new(),
|
||||
function_status: false,
|
||||
|
@ -464,9 +508,12 @@ impl Inputter {
|
|||
let arg: char;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if let Some(c) = evt.get_char() {
|
||||
arg = c;
|
||||
break;
|
||||
if let Some(kevt) = evt.get_key() {
|
||||
if let Some(c) = kevt.key.codepoint_text() {
|
||||
// TODO forward the whole key
|
||||
arg = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
skipped.push(evt);
|
||||
}
|
||||
|
@ -492,7 +539,16 @@ impl Inputter {
|
|||
let evt = match input_function_get_code(cmd) {
|
||||
Some(code) => {
|
||||
self.function_push_args(code);
|
||||
CharEvent::from_readline_seq(code, m.seq.clone())
|
||||
// At this point, the sequence is only used for reinserting the keys into
|
||||
// the event queue for self-insert. Modifiers make no sense here so drop them.
|
||||
CharEvent::from_readline_seq(
|
||||
code,
|
||||
m.seq
|
||||
.iter()
|
||||
.filter(|key| key.modifiers.is_none())
|
||||
.map(|key| key.codepoint)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
None => CharEvent::Command(cmd.clone()),
|
||||
};
|
||||
|
@ -539,6 +595,8 @@ struct EventQueuePeeker<'q> {
|
|||
|
||||
/// The current index. This never exceeds peeked.len().
|
||||
idx: usize,
|
||||
/// The current index within a the raw characters within a single key event.
|
||||
subidx: usize,
|
||||
|
||||
/// The queue from which to read more events.
|
||||
event_queue: &'q mut Inputter,
|
||||
|
@ -550,6 +608,7 @@ impl EventQueuePeeker<'_> {
|
|||
peeked: Vec::new(),
|
||||
had_timeout: false,
|
||||
idx: 0,
|
||||
subidx: 0,
|
||||
event_queue,
|
||||
}
|
||||
}
|
||||
|
@ -566,12 +625,13 @@ impl EventQueuePeeker<'_> {
|
|||
}
|
||||
let res = self.peeked[self.idx].clone();
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
res
|
||||
}
|
||||
|
||||
/// Check if the next event is the given character. This advances the index on success only.
|
||||
/// If \p escaped is set, then return false if this (or any other) character had a timeout.
|
||||
fn next_is_char(&mut self, c: char, escaped: bool) -> bool {
|
||||
fn next_is_char(&mut self, key: Key, escaped: bool) -> bool {
|
||||
assert!(
|
||||
self.idx <= self.peeked.len(),
|
||||
"Index must not be larger than dequeued event count"
|
||||
|
@ -583,36 +643,74 @@ impl EventQueuePeeker<'_> {
|
|||
// Grab a new event if we have exhausted what we have already peeked.
|
||||
// Use either readch or readch_timed, per our param.
|
||||
if self.idx == self.peeked.len() {
|
||||
let newevt: CharEvent;
|
||||
if !escaped {
|
||||
if let Some(mevt) = self.event_queue.readch_timed_sequence_key() {
|
||||
newevt = mevt;
|
||||
} else {
|
||||
self.had_timeout = true;
|
||||
return false;
|
||||
}
|
||||
} else if let Some(mevt) = self.event_queue.readch_timed_esc() {
|
||||
newevt = mevt;
|
||||
let Some(newevt) = (if escaped {
|
||||
self.event_queue.readch_timed_esc()
|
||||
} else {
|
||||
self.event_queue.readch_timed_sequence_key()
|
||||
}) else {
|
||||
self.had_timeout = true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
self.peeked.push(newevt);
|
||||
}
|
||||
// Now we have peeked far enough; check the event.
|
||||
// If it matches the char, then increment the index.
|
||||
if self.peeked[self.idx].get_char() == Some(c) {
|
||||
let evt = &self.peeked[self.idx];
|
||||
let Some(kevt) = evt.get_key() else {
|
||||
return false;
|
||||
};
|
||||
if kevt.key == key {
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
return true;
|
||||
}
|
||||
let actual_seq = kevt.seq.as_char_slice();
|
||||
if !actual_seq.is_empty() {
|
||||
let seq_char = actual_seq[self.subidx];
|
||||
FLOG!(
|
||||
reader,
|
||||
"match mapping's",
|
||||
key,
|
||||
format!("against actual char {}", u32::from(seq_char)),
|
||||
);
|
||||
if Key::from_single_char(seq_char) == key {
|
||||
self.subidx += 1;
|
||||
if self.subidx == actual_seq.len() {
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
}
|
||||
FLOG!(reader, "matched legacy sequence");
|
||||
return true;
|
||||
}
|
||||
if key.modifiers.alt
|
||||
&& !key.modifiers.ctrl
|
||||
&& !key.modifiers.shift
|
||||
&& seq_char == '\x1b'
|
||||
{
|
||||
if self.subidx + 1 == actual_seq.len() {
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
FLOG!(reader, "matched escape prefix of legacy alt sequence");
|
||||
return self.next_is_char(Key::from_raw(key.codepoint), true);
|
||||
} else if actual_seq
|
||||
.get(self.subidx + 1)
|
||||
.cloned()
|
||||
.map(|c| Key::from_single_char(c).codepoint)
|
||||
== Some(key.codepoint)
|
||||
{
|
||||
self.subidx += 2;
|
||||
if self.subidx == actual_seq.len() {
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
}
|
||||
FLOG!(reader, "matched legacy alt sequence");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// \return the current index.
|
||||
fn len(&self) -> usize {
|
||||
self.idx
|
||||
}
|
||||
|
||||
/// Consume all events up to the current index.
|
||||
/// Remaining events are returned to the queue.
|
||||
fn consume(mut self) {
|
||||
|
@ -620,6 +718,7 @@ impl EventQueuePeeker<'_> {
|
|||
self.event_queue.insert_front(self.peeked.drain(self.idx..));
|
||||
self.peeked.clear();
|
||||
self.idx = 0;
|
||||
self.subidx = 0;
|
||||
}
|
||||
|
||||
/// Test if any of our peeked events are readline or check_exit.
|
||||
|
@ -632,82 +731,42 @@ impl EventQueuePeeker<'_> {
|
|||
/// Reset our index back to 0.
|
||||
fn restart(&mut self) {
|
||||
self.idx = 0;
|
||||
self.subidx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventQueuePeeker<'_> {
|
||||
fn drop(&mut self) {
|
||||
assert!(
|
||||
self.idx == 0,
|
||||
self.idx == 0 && self.subidx == 0,
|
||||
"Events left on the queue - missing restart or consume?",
|
||||
);
|
||||
self.event_queue.insert_front(self.peeked.drain(self.idx..));
|
||||
}
|
||||
}
|
||||
|
||||
/// Try reading a mouse-tracking CSI sequence, using the given \p peeker.
|
||||
/// Events are left on the peeker and the caller must restart or consume it.
|
||||
/// \return true if matched, false if not.
|
||||
fn have_mouse_tracking_csi(peeker: &mut EventQueuePeeker) -> bool {
|
||||
// Maximum length of any CSI is NPAR (which is nominally 16), although this does not account for
|
||||
// user input intermixed with pseudo input generated by the tty emulator.
|
||||
// Check for the CSI first.
|
||||
if !peeker.next_is_char('\x1b', false) || !peeker.next_is_char('[', true /* escaped */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut next = peeker.next().get_char();
|
||||
let length;
|
||||
if next == Some('M') {
|
||||
// Generic X10 or modified VT200 sequence. It doesn't matter which, they're both 6 chars
|
||||
// (although in mode 1005, the characters may be unicode and not necessarily just one byte
|
||||
// long) reporting the button that was clicked and its location.
|
||||
length = 6;
|
||||
} else if next == Some('<') {
|
||||
// Extended (SGR/1006) mouse reporting mode, with semicolon-separated parameters for button
|
||||
// code, Px, and Py, ending with 'M' for button press or 'm' for button release.
|
||||
loop {
|
||||
next = peeker.next().get_char();
|
||||
if next == Some('M') || next == Some('m') {
|
||||
// However much we've read, we've consumed the CSI in its entirety.
|
||||
length = peeker.len();
|
||||
break;
|
||||
}
|
||||
if peeker.len() >= 16 {
|
||||
// This is likely a malformed mouse-reporting CSI but we can't do anything about it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if next == Some('t') {
|
||||
// VT200 button released in mouse highlighting mode at valid text location. 5 chars.
|
||||
length = 5;
|
||||
} else if next == Some('T') {
|
||||
// VT200 button released in mouse highlighting mode past end-of-line. 9 characters.
|
||||
length = 9;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Consume however many characters it takes to prevent the mouse tracking sequence from reaching
|
||||
// the prompt, dependent on the class of mouse reporting as detected above.
|
||||
while peeker.len() < length {
|
||||
let _ = peeker.next();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// \return true if a given \p peeker matches a given sequence of char events given by \p str.
|
||||
fn try_peek_sequence(peeker: &mut EventQueuePeeker, str: &wstr) -> bool {
|
||||
assert!(!str.is_empty(), "Empty string passed to try_peek_sequence");
|
||||
let mut prev = '\0';
|
||||
for c in str.chars() {
|
||||
fn try_peek_sequence(peeker: &mut EventQueuePeeker, seq: &[Key]) -> bool {
|
||||
assert!(
|
||||
!seq.is_empty(),
|
||||
"Empty sequence passed to try_peek_sequence"
|
||||
);
|
||||
let mut prev = Key::from_raw(key::Invalid);
|
||||
for key in seq {
|
||||
// If we just read an escape, we need to add a timeout for the next char,
|
||||
// to distinguish between the actual escape key and an "alt"-modifier.
|
||||
let escaped = prev == '\x1B';
|
||||
if !peeker.next_is_char(c, escaped) {
|
||||
let escaped = prev == Key::from_raw(key::Escape);
|
||||
if !peeker.next_is_char(*key, escaped) {
|
||||
return false;
|
||||
}
|
||||
prev = c;
|
||||
prev = *key;
|
||||
}
|
||||
if peeker.subidx != 0 {
|
||||
FLOG!(
|
||||
reader,
|
||||
"legacy binding matched prefix of key encoding but did not consume all of it"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -739,7 +798,7 @@ impl Inputter {
|
|||
if try_peek_sequence(peeker, &m.seq) {
|
||||
// A binding for just escape should also be deferred
|
||||
// so escape sequences take precedence.
|
||||
if m.seq == "\x1B" {
|
||||
if m.seq == vec![Key::from_raw(key::Escape)] {
|
||||
if escape.is_none() {
|
||||
escape = Some(m);
|
||||
}
|
||||
|
@ -771,27 +830,12 @@ impl Inputter {
|
|||
fn mapping_execute_matching_or_generic(&mut self) {
|
||||
let vars = self.parser.vars_ref();
|
||||
let mut peeker = EventQueuePeeker::new(self);
|
||||
// Check for mouse-tracking CSI before mappings to prevent the generic mapping handler from
|
||||
// taking over.
|
||||
if have_mouse_tracking_csi(&mut peeker) {
|
||||
// fish recognizes but does not actually support mouse reporting. We never turn it on, and
|
||||
// it's only ever enabled if a program we spawned enabled it and crashed or forgot to turn
|
||||
// it off before exiting. We turn it off here to avoid wasting resources.
|
||||
//
|
||||
// Since this is only called when we detect an incoming mouse reporting payload, we know the
|
||||
// terminal emulator supports mouse reporting, so no terminfo checks.
|
||||
FLOG!(reader, "Disabling mouse tracking");
|
||||
|
||||
// We shouldn't directly manipulate stdout from here, so we ask the reader to do it.
|
||||
// writembs(outputter_t::stdoutput(), "\x1B[?1000l");
|
||||
peeker.consume();
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::DisableMouseTracking));
|
||||
return;
|
||||
}
|
||||
peeker.restart();
|
||||
|
||||
// Check for ordinary mappings.
|
||||
if let Some(mapping) = Self::find_mapping(&*vars, &mut peeker) {
|
||||
FLOG!(
|
||||
reader,
|
||||
format!("Found mapping {:?} from {:?}", &mapping, &peeker.peeked)
|
||||
);
|
||||
peeker.consume();
|
||||
self.mapping_execute(&mapping);
|
||||
return;
|
||||
|
@ -839,14 +883,7 @@ impl Inputter {
|
|||
evt_to_return
|
||||
}
|
||||
|
||||
/// Read a character from stdin. Try to convert some escape sequences into character constants,
|
||||
/// but do not permanently block the escape character.
|
||||
///
|
||||
/// This is performed in the same way vim does it, i.e. if an escape character is read, wait for
|
||||
/// more input for a short time (a few milliseconds). If more input is available, it is assumed
|
||||
/// to be an escape sequence for a special character (such as an arrow key), and readch attempts
|
||||
/// to parse it. If no more input follows after the escape key, it is assumed to be an actual
|
||||
/// escape key press, and is returned as such.
|
||||
/// Read a key from stdin.
|
||||
pub fn read_char(&mut self) -> CharEvent {
|
||||
// Clear the interrupted flag.
|
||||
reader_reset_interrupted();
|
||||
|
@ -867,8 +904,8 @@ impl Inputter {
|
|||
|
||||
// Hackish: mark the input style.
|
||||
if readline_event.cmd == ReadlineCmd::SelfInsertNotFirst {
|
||||
if let CharEvent::Char(cevt) = &mut res {
|
||||
cevt.input_style = CharInputStyle::NotFirst;
|
||||
if let CharEvent::Key(kevt) = &mut res {
|
||||
kevt.input_style = CharInputStyle::NotFirst;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
@ -897,7 +934,17 @@ impl Inputter {
|
|||
// Allow the reader to check for exit conditions.
|
||||
return evt;
|
||||
}
|
||||
CharEvent::Char(ref _cevt) => {
|
||||
CharEvent::Key(ref kevt) => {
|
||||
FLOG!(
|
||||
reader,
|
||||
"Read char",
|
||||
kevt.key,
|
||||
format!(
|
||||
"-- {:?} -- {:?}",
|
||||
kevt.key,
|
||||
kevt.seq.chars().map(u32::from).collect::<Vec<_>>()
|
||||
)
|
||||
);
|
||||
self.push_front(evt);
|
||||
self.mapping_execute_matching_or_generic();
|
||||
}
|
||||
|
@ -941,7 +988,7 @@ impl InputMappingSet {
|
|||
}
|
||||
|
||||
/// Erase binding for specified key sequence.
|
||||
pub fn erase(&mut self, sequence: &wstr, mode: &wstr, user: bool) -> bool {
|
||||
pub fn erase(&mut self, sequence: &[Key], mode: &wstr, user: bool) -> bool {
|
||||
// Clear cached mappings.
|
||||
self.all_mappings_cache = RefCell::new(None);
|
||||
|
||||
|
@ -965,11 +1012,12 @@ impl InputMappingSet {
|
|||
/// it exists, false if not.
|
||||
pub fn get<'a>(
|
||||
&'a self,
|
||||
sequence: &wstr,
|
||||
sequence: &[Key],
|
||||
mode: &wstr,
|
||||
out_cmds: &mut &'a [WString],
|
||||
user: bool,
|
||||
out_sets_mode: &mut Option<&'a wstr>,
|
||||
out_terminfo_name: &mut Option<WString>,
|
||||
) -> bool {
|
||||
let ml = if user {
|
||||
&self.mapping_list
|
||||
|
@ -980,6 +1028,7 @@ impl InputMappingSet {
|
|||
if m.seq == sequence && m.mode == mode {
|
||||
*out_cmds = &m.commands;
|
||||
*out_sets_mode = m.sets_mode.as_deref();
|
||||
*out_terminfo_name = m.terminfo_name.clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
use crate::common::{fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked};
|
||||
use libc::STDOUT_FILENO;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::common::{
|
||||
fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked, shell_modes, ScopeGuard,
|
||||
ScopeGuarding,
|
||||
};
|
||||
use crate::env::{EnvStack, Environment};
|
||||
use crate::fd_readable_set::FdReadableSet;
|
||||
use crate::flog::FLOG;
|
||||
use crate::reader::reader_current_data;
|
||||
use crate::threads::{iothread_port, iothread_service_main};
|
||||
use crate::key::{
|
||||
self, alt, canonicalize_control_char, canonicalize_keyed_control_char, function_key, shift,
|
||||
Key, Modifiers,
|
||||
};
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{reader_current_data, reader_test_and_clear_interrupted};
|
||||
use crate::threads::{iothread_port, iothread_service_main, MainThread};
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wchar::{encode_byte_to_char, prelude::*};
|
||||
use crate::wutil::encoding::{mbrtowc, zero_mbstate};
|
||||
use crate::wutil::fish_wcstol;
|
||||
use crate::wutil::encoding::{mbrtowc, mbstate_t, zero_mbstate};
|
||||
use crate::wutil::{fish_wcstol, write_to_fd};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::os::fd::RawFd;
|
||||
use std::ptr;
|
||||
|
@ -111,6 +123,8 @@ pub enum ReadlineCmd {
|
|||
EndUndoGroup,
|
||||
RepeatJump,
|
||||
DisableMouseTracking,
|
||||
FocusIn,
|
||||
FocusOut,
|
||||
// ncurses uses the obvious name
|
||||
ClearScreenAndRepaint,
|
||||
// NOTE: This one has to be last.
|
||||
|
@ -121,7 +135,7 @@ pub enum ReadlineCmd {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum CharEventType {
|
||||
/// A character was entered.
|
||||
Char(char),
|
||||
Char(Key),
|
||||
|
||||
/// A readline event.
|
||||
Readline(ReadlineCmd),
|
||||
|
@ -147,9 +161,9 @@ pub struct ReadlineCmdEvent {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlainCharEvent {
|
||||
pub struct KeyEvent {
|
||||
// The key.
|
||||
pub char: char,
|
||||
pub key: Key,
|
||||
// The style to use when inserting characters into the command line.
|
||||
pub input_style: CharInputStyle,
|
||||
/// The sequence of characters in the input mapping which generated this event.
|
||||
|
@ -161,7 +175,7 @@ pub struct PlainCharEvent {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum CharEvent {
|
||||
/// A character was entered.
|
||||
Char(PlainCharEvent),
|
||||
Key(KeyEvent),
|
||||
|
||||
/// A readline event.
|
||||
Readline(ReadlineCmdEvent),
|
||||
|
@ -179,7 +193,7 @@ pub enum CharEvent {
|
|||
|
||||
impl CharEvent {
|
||||
pub fn is_char(&self) -> bool {
|
||||
matches!(self, CharEvent::Char(_))
|
||||
matches!(self, CharEvent::Key(_))
|
||||
}
|
||||
|
||||
pub fn is_eof(&self) -> bool {
|
||||
|
@ -198,6 +212,20 @@ impl CharEvent {
|
|||
matches!(self, CharEvent::Readline(_) | CharEvent::Command(_))
|
||||
}
|
||||
|
||||
pub fn get_char(&self) -> char {
|
||||
let CharEvent::Key(kevt) = self else {
|
||||
panic!("Not a char type");
|
||||
};
|
||||
kevt.key.codepoint
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> Option<&KeyEvent> {
|
||||
match self {
|
||||
CharEvent::Key(kevt) => Some(kevt),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_readline(&self) -> ReadlineCmd {
|
||||
let CharEvent::Readline(c) = self else {
|
||||
panic!("Not a readline type");
|
||||
|
@ -213,19 +241,24 @@ impl CharEvent {
|
|||
}
|
||||
|
||||
pub fn from_char(c: char) -> CharEvent {
|
||||
Self::from_char_seq(c, WString::new())
|
||||
Self::from_key(Key::from_raw(c))
|
||||
}
|
||||
|
||||
pub fn get_char(&self) -> Option<char> {
|
||||
match self {
|
||||
CharEvent::Char(cevt) => Some(cevt.char),
|
||||
_ => None,
|
||||
}
|
||||
pub fn from_key(key: Key) -> CharEvent {
|
||||
Self::from_key_seq(key, WString::new())
|
||||
}
|
||||
|
||||
pub fn from_key_seq(key: Key, seq: WString) -> CharEvent {
|
||||
CharEvent::Key(KeyEvent {
|
||||
key,
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_char_seq(c: char, seq: WString) -> CharEvent {
|
||||
CharEvent::Char(PlainCharEvent {
|
||||
char: c,
|
||||
CharEvent::Key(KeyEvent {
|
||||
key: Key::from_raw(c),
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq,
|
||||
})
|
||||
|
@ -272,9 +305,11 @@ enum ReadbResult {
|
|||
|
||||
// Our ioport reported a change, so service main thread requests.
|
||||
IOPortNotified,
|
||||
|
||||
NothingToRead,
|
||||
}
|
||||
|
||||
fn readb(in_fd: RawFd) -> ReadbResult {
|
||||
fn readb(in_fd: RawFd, blocking: bool) -> ReadbResult {
|
||||
assert!(in_fd >= 0, "Invalid in fd");
|
||||
let mut fdset = FdReadableSet::new();
|
||||
loop {
|
||||
|
@ -293,7 +328,11 @@ fn readb(in_fd: RawFd) -> ReadbResult {
|
|||
}
|
||||
|
||||
// Here's where we call select().
|
||||
let select_res = fdset.check_readable(FdReadableSet::kNoTimeout);
|
||||
let select_res = fdset.check_readable(if blocking {
|
||||
FdReadableSet::kNoTimeout
|
||||
} else {
|
||||
0
|
||||
});
|
||||
if select_res < 0 {
|
||||
let err = errno::errno().0;
|
||||
if err == libc::EINTR || err == libc::EAGAIN {
|
||||
|
@ -305,12 +344,15 @@ fn readb(in_fd: RawFd) -> ReadbResult {
|
|||
}
|
||||
}
|
||||
|
||||
// select() did not return an error, so we may have a readable fd.
|
||||
// The priority order is: uvars, stdin, ioport.
|
||||
// Check to see if we want a universal variable barrier.
|
||||
if let Some(notifier_fd) = notifier_fd {
|
||||
if fdset.test(notifier_fd) && notifier.notification_fd_became_readable(notifier_fd) {
|
||||
return ReadbResult::UvarNotified;
|
||||
if blocking {
|
||||
// select() did not return an error, so we may have a readable fd.
|
||||
// The priority order is: uvars, stdin, ioport.
|
||||
// Check to see if we want a universal variable barrier.
|
||||
if let Some(notifier_fd) = notifier_fd {
|
||||
if fdset.test(notifier_fd) && notifier.notification_fd_became_readable(notifier_fd)
|
||||
{
|
||||
return ReadbResult::UvarNotified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,6 +366,9 @@ fn readb(in_fd: RawFd) -> ReadbResult {
|
|||
// The common path is to return a u8.
|
||||
return ReadbResult::Byte(arr[0]);
|
||||
}
|
||||
if !blocking {
|
||||
return ReadbResult::NothingToRead;
|
||||
}
|
||||
|
||||
// Check for iothread completions only if there is no data to be read from the stdin.
|
||||
// This gives priority to the foreground.
|
||||
|
@ -383,6 +428,111 @@ pub fn update_wait_on_sequence_key_ms(vars: &EnvStack) {
|
|||
}
|
||||
}
|
||||
|
||||
pub static TERMINAL_PROTOCOLS: MainThread<RefCell<Option<TerminalProtocols>>> =
|
||||
MainThread::new(RefCell::new(None));
|
||||
|
||||
pub fn terminal_protocols_enable() {
|
||||
assert!(TERMINAL_PROTOCOLS.get().borrow().is_none());
|
||||
TERMINAL_PROTOCOLS
|
||||
.get()
|
||||
.replace(Some(TerminalProtocols::new()));
|
||||
}
|
||||
|
||||
pub fn terminal_protocols_disable() {
|
||||
assert!(TERMINAL_PROTOCOLS.get().borrow().is_some());
|
||||
TERMINAL_PROTOCOLS.get().replace(None);
|
||||
}
|
||||
|
||||
pub fn terminal_protocols_enable_scoped() -> impl ScopeGuarding<Target = ()> {
|
||||
terminal_protocols_enable();
|
||||
ScopeGuard::new((), |()| terminal_protocols_disable())
|
||||
}
|
||||
|
||||
pub fn terminal_protocols_disable_scoped() -> impl ScopeGuarding<Target = ()> {
|
||||
terminal_protocols_disable();
|
||||
ScopeGuard::new((), |()| {
|
||||
// If a child is stopped, this will already be enabled.
|
||||
if TERMINAL_PROTOCOLS.get().borrow().is_none() {
|
||||
terminal_protocols_enable()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct TerminalProtocols {}
|
||||
|
||||
impl TerminalProtocols {
|
||||
fn new() -> Self {
|
||||
terminal_protocols_enable_impl();
|
||||
TerminalProtocols {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TerminalProtocols {
|
||||
fn drop(&mut self) {
|
||||
terminal_protocols_disable_impl();
|
||||
}
|
||||
}
|
||||
|
||||
fn terminal_protocols_enable_impl() {
|
||||
// Interactive or inside builtin read.
|
||||
assert!(is_interactive_session() || reader_current_data().is_some());
|
||||
|
||||
let mut sequences = concat!(
|
||||
"\x1b[?2004h", // Bracketed paste
|
||||
"\x1b[>4;1m", // XTerm's modifyOtherKeys
|
||||
"\x1b[>5u", // CSI u with kitty progressive enhancement
|
||||
"\x1b=", // set application keypad mode, so the keypad keys send unique codes
|
||||
"\x1b[?1004h", // enable focus notify
|
||||
);
|
||||
|
||||
// Note: Don't call this initially because, even though we're in a fish_prompt event,
|
||||
// tmux reacts sooo quickly that we'll still get a sequence before we're prepared for it.
|
||||
// So this means that we won't get focus events until you've run at least one command,
|
||||
// but that's preferable to always seeing "^[[I" when starting fish.
|
||||
static FIRST_CALL_HACK: OnceCell<()> = OnceCell::new();
|
||||
if FIRST_CALL_HACK.get().is_none() {
|
||||
sequences = sequences.strip_suffix("\x1b[?1004h").unwrap();
|
||||
}
|
||||
FIRST_CALL_HACK.get_or_init(|| ());
|
||||
|
||||
FLOG!(
|
||||
term_protocols,
|
||||
format!(
|
||||
"Enabling extended keys, bracketed paste and focus reporting: {:?}",
|
||||
sequences
|
||||
)
|
||||
);
|
||||
let _ = write_to_fd(sequences.as_bytes(), STDOUT_FILENO);
|
||||
}
|
||||
|
||||
fn terminal_protocols_disable_impl() {
|
||||
// Interactive or inside builtin read.
|
||||
assert!(is_interactive_session() || reader_current_data().is_some());
|
||||
let sequences = concat!(
|
||||
"\x1b[?2004l",
|
||||
"\x1b[>4;0m",
|
||||
"\x1b[<1u", // Konsole breaks unless we pass an explicit number of entries to pop.
|
||||
"\x1b>",
|
||||
"\x1b[?1004l",
|
||||
);
|
||||
FLOG!(
|
||||
term_protocols,
|
||||
format!(
|
||||
"Disabling extended keys, bracketed paste and focus reporting: {:?}",
|
||||
sequences
|
||||
)
|
||||
);
|
||||
let _ = write_to_fd(sequences.as_bytes(), STDOUT_FILENO);
|
||||
}
|
||||
|
||||
fn parse_mask(mask: u32) -> Modifiers {
|
||||
Modifiers {
|
||||
ctrl: (mask & 4) != 0,
|
||||
alt: (mask & 2) != 0,
|
||||
shift: (mask & 1) != 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait which knows how to produce a stream of input events.
|
||||
/// Note this is conceptually a "base class" with override points.
|
||||
pub trait InputEventQueuer {
|
||||
|
@ -395,10 +545,7 @@ pub trait InputEventQueuer {
|
|||
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously
|
||||
/// been read and then 'unread' using \c input_common_unreadch, that character is returned.
|
||||
fn readch(&mut self) -> CharEvent {
|
||||
let mut res: char = '\0';
|
||||
let mut state = zero_mbstate();
|
||||
let mut bytes = [0; 64 * 16];
|
||||
let mut num_bytes = 0;
|
||||
loop {
|
||||
// Do we have something enqueued already?
|
||||
// Note this may be initially true, or it may become true through calls to
|
||||
|
@ -413,14 +560,13 @@ pub trait InputEventQueuer {
|
|||
return mevt;
|
||||
}
|
||||
|
||||
let rr = readb(self.get_in_fd());
|
||||
let rr = readb(self.get_in_fd(), /*blocking=*/ true);
|
||||
match rr {
|
||||
ReadbResult::Eof => {
|
||||
return CharEvent::Eof;
|
||||
}
|
||||
|
||||
ReadbResult::Interrupted => {
|
||||
// FIXME: here signals may break multibyte sequences.
|
||||
self.select_interrupted();
|
||||
}
|
||||
|
||||
|
@ -433,58 +579,405 @@ pub trait InputEventQueuer {
|
|||
}
|
||||
|
||||
ReadbResult::Byte(read_byte) => {
|
||||
if crate::libc::MB_CUR_MAX() == 1 {
|
||||
// single-byte locale, all values are legal
|
||||
// FIXME: this looks wrong, this falsely assumes that
|
||||
// the single-byte locale is compatible with Unicode upper-ASCII.
|
||||
res = read_byte.into();
|
||||
return CharEvent::from_char(res);
|
||||
let mut have_escape_prefix = false;
|
||||
let mut buffer = vec![read_byte];
|
||||
let key_with_escape = if read_byte == 0x1b {
|
||||
self.parse_escape_sequence(&mut buffer, &mut have_escape_prefix)
|
||||
} else {
|
||||
canonicalize_control_char(read_byte)
|
||||
};
|
||||
if self.paste_is_buffering() {
|
||||
if read_byte != 0x1b {
|
||||
self.paste_push_char(read_byte);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let mut codepoint = u32::from(res);
|
||||
let sz = unsafe {
|
||||
mbrtowc(
|
||||
std::ptr::addr_of_mut!(codepoint).cast(),
|
||||
std::ptr::addr_of!(read_byte).cast(),
|
||||
1,
|
||||
let mut seq = WString::new();
|
||||
let mut key = key_with_escape;
|
||||
let mut consumed = 0;
|
||||
for i in 0..buffer.len() {
|
||||
self.parse_codepoint(
|
||||
&mut state,
|
||||
)
|
||||
} as isize;
|
||||
match sz {
|
||||
-1 => {
|
||||
FLOG!(reader, "Illegal input");
|
||||
return CharEvent::from_check_exit();
|
||||
}
|
||||
-2 => {
|
||||
// Sequence not yet complete.
|
||||
bytes[num_bytes] = read_byte;
|
||||
num_bytes += 1;
|
||||
&mut key,
|
||||
&mut seq,
|
||||
&buffer,
|
||||
i,
|
||||
&mut consumed,
|
||||
&mut have_escape_prefix,
|
||||
);
|
||||
}
|
||||
return if let Some(key) = key {
|
||||
CharEvent::from_key_seq(key, seq)
|
||||
} else {
|
||||
self.insert_front(seq.chars().skip(1).map(CharEvent::from_char));
|
||||
let Some(c) = seq.chars().next() else {
|
||||
continue;
|
||||
}
|
||||
0 => {
|
||||
// Actual nul char.
|
||||
return CharEvent::from_char('\0');
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if let Some(res) = char::from_u32(codepoint) {
|
||||
// Sequence complete.
|
||||
if !fish_reserved_codepoint(res) {
|
||||
return CharEvent::from_char(res);
|
||||
}
|
||||
}
|
||||
bytes[num_bytes] = read_byte;
|
||||
num_bytes += 1;
|
||||
for &b in &bytes[1..num_bytes] {
|
||||
let c = CharEvent::from_char(encode_byte_to_char(b));
|
||||
self.push_back(c);
|
||||
}
|
||||
let res = CharEvent::from_char(encode_byte_to_char(bytes[0]));
|
||||
return res;
|
||||
};
|
||||
CharEvent::from_key_seq(Key::from_raw(c), seq)
|
||||
};
|
||||
}
|
||||
ReadbResult::NothingToRead => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_readb(&mut self, buffer: &mut Vec<u8>) -> Option<u8> {
|
||||
let ReadbResult::Byte(next) = readb(self.get_in_fd(), /*blocking=*/ false) else {
|
||||
return None;
|
||||
};
|
||||
buffer.push(next);
|
||||
Some(next)
|
||||
}
|
||||
|
||||
fn parse_escape_sequence(
|
||||
&mut self,
|
||||
buffer: &mut Vec<u8>,
|
||||
have_escape_prefix: &mut bool,
|
||||
) -> Option<Key> {
|
||||
let Some(next) = self.try_readb(buffer) else {
|
||||
if !self.paste_is_buffering() {
|
||||
return Some(Key {
|
||||
modifiers: Modifiers::default(),
|
||||
codepoint: key::Escape,
|
||||
});
|
||||
}
|
||||
return None;
|
||||
};
|
||||
if next == b'[' {
|
||||
// potential CSI
|
||||
return Some(self.parse_csi(buffer).unwrap_or(alt('[')));
|
||||
}
|
||||
if next == b'O' {
|
||||
// potential SS3
|
||||
return Some(self.parse_ss3(buffer).unwrap_or(alt('O')));
|
||||
}
|
||||
match canonicalize_control_char(next) {
|
||||
Some(mut key) => {
|
||||
key.modifiers.alt = true;
|
||||
Some(key)
|
||||
}
|
||||
None => {
|
||||
*have_escape_prefix = true;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_codepoint(
|
||||
&mut self,
|
||||
state: &mut mbstate_t,
|
||||
out_key: &mut Option<Key>,
|
||||
out_seq: &mut WString,
|
||||
buffer: &[u8],
|
||||
i: usize,
|
||||
consumed: &mut usize,
|
||||
have_escape_prefix: &mut bool,
|
||||
) {
|
||||
let mut res: char = '\0';
|
||||
let read_byte = buffer[i];
|
||||
if crate::libc::MB_CUR_MAX() == 1 {
|
||||
// single-byte locale, all values are legal
|
||||
// FIXME: this looks wrong, this falsely assumes that
|
||||
// the single-byte locale is compatible with Unicode upper-ASCII.
|
||||
res = read_byte.into();
|
||||
out_seq.push(res);
|
||||
return;
|
||||
}
|
||||
let mut codepoint = u32::from(res);
|
||||
let sz = unsafe {
|
||||
mbrtowc(
|
||||
std::ptr::addr_of_mut!(codepoint).cast(),
|
||||
std::ptr::addr_of!(read_byte).cast(),
|
||||
1,
|
||||
state,
|
||||
)
|
||||
} as isize;
|
||||
match sz {
|
||||
-1 => {
|
||||
FLOG!(reader, "Illegal input");
|
||||
*consumed += 1;
|
||||
self.push_front(CharEvent::from_check_exit());
|
||||
return;
|
||||
}
|
||||
-2 => {
|
||||
// Sequence not yet complete.
|
||||
return;
|
||||
}
|
||||
0 => {
|
||||
// Actual nul char.
|
||||
*consumed += 1;
|
||||
out_seq.push('\0');
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if let Some(res) = char::from_u32(codepoint) {
|
||||
// Sequence complete.
|
||||
if !fish_reserved_codepoint(res) {
|
||||
if *have_escape_prefix && i != 0 {
|
||||
*have_escape_prefix = false;
|
||||
*out_key = Some(alt(res));
|
||||
}
|
||||
*consumed += 1;
|
||||
out_seq.push(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for &b in &buffer[*consumed..i] {
|
||||
out_seq.push(encode_byte_to_char(b));
|
||||
*consumed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<Key> {
|
||||
let mut next_char = |zelf: &mut Self| zelf.try_readb(buffer).unwrap_or(0xff);
|
||||
let mut params = [[0_u32; 16]; 4];
|
||||
let mut c = next_char(self);
|
||||
let private_mode;
|
||||
if matches!(c, b'?' | b'<' | b'=' | b'>') {
|
||||
// private mode
|
||||
private_mode = Some(c);
|
||||
c = next_char(self);
|
||||
} else {
|
||||
private_mode = None;
|
||||
}
|
||||
let mut count = 0;
|
||||
let mut subcount = 0;
|
||||
while count < 16 && c >= 0x30 && c <= 0x3f {
|
||||
if c.is_ascii_digit() {
|
||||
params[count][subcount] = params[count][subcount] * 10 + u32::from(c - b'0');
|
||||
} else if c == b':' && subcount < 3 {
|
||||
subcount += 1;
|
||||
} else if c == b';' {
|
||||
count += 1;
|
||||
subcount = 0;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
c = next_char(self);
|
||||
}
|
||||
if c != b'$' && !(0x40..=0x7e).contains(&c) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let masked_key = |mut codepoint, shifted_codepoint| {
|
||||
let mask = params[1][0].saturating_sub(1);
|
||||
let mut modifiers = parse_mask(mask);
|
||||
if let Some(shifted_codepoint) = shifted_codepoint {
|
||||
if shifted_codepoint != '\0' && modifiers.shift {
|
||||
modifiers.shift = false;
|
||||
codepoint = shifted_codepoint;
|
||||
}
|
||||
}
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
};
|
||||
|
||||
let key = match c {
|
||||
b'$' => {
|
||||
if private_mode == Some(b'?') && next_char(self) == b'y' {
|
||||
// DECRPM
|
||||
return None;
|
||||
}
|
||||
match params[0][0] {
|
||||
23 | 24 => shift(
|
||||
char::from_u32(u32::from(function_key(11)) + params[0][0] - 23).unwrap(), // rxvt style
|
||||
),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
b'A' => masked_key(key::Up, None),
|
||||
b'B' => masked_key(key::Down, None),
|
||||
b'C' => masked_key(key::Right, None),
|
||||
b'D' => masked_key(key::Left, None),
|
||||
b'E' => masked_key('5', None), // Numeric keypad
|
||||
b'F' => masked_key(key::End, None), // PC/xterm style
|
||||
b'H' => masked_key(key::Home, None), // PC/xterm style
|
||||
b'M' | b'm' => {
|
||||
self.disable_mouse_tracking();
|
||||
let sgr = private_mode == Some(b'<');
|
||||
if !sgr && c == b'm' {
|
||||
return None;
|
||||
}
|
||||
// Extended (SGR/1006) mouse reporting mode, with semicolon-separated parameters
|
||||
// for button code, Px, and Py, ending with 'M' for button press or 'm' for
|
||||
// button release.
|
||||
if sgr {
|
||||
return None;
|
||||
}
|
||||
// Generic X10 or modified VT200 sequence. It doesn't matter which, they're both 6
|
||||
// chars (although in mode 1005, the characters may be unicode and not necessarily
|
||||
// just one byte long) reporting the button that was clicked and its location.
|
||||
let _ = next_char(self);
|
||||
let _ = next_char(self);
|
||||
let _ = next_char(self);
|
||||
return None;
|
||||
}
|
||||
b't' => {
|
||||
self.disable_mouse_tracking();
|
||||
// VT200 button released in mouse highlighting mode at valid text location. 5 chars.
|
||||
let _ = next_char(self);
|
||||
let _ = next_char(self);
|
||||
return None;
|
||||
}
|
||||
b'T' => {
|
||||
self.disable_mouse_tracking();
|
||||
// VT200 button released in mouse highlighting mode past end-of-line. 9 characters.
|
||||
for _ in 0..7 {
|
||||
let _ = next_char(self);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
b'P' => masked_key(function_key(1), None),
|
||||
b'Q' => masked_key(function_key(2), None),
|
||||
b'R' => masked_key(function_key(3), None),
|
||||
b'S' => masked_key(function_key(4), None),
|
||||
b'~' => match params[0][0] {
|
||||
1 => masked_key(key::Home, None), // VT220/tmux style
|
||||
2 => masked_key(key::Insert, None),
|
||||
3 => masked_key(key::Delete, None),
|
||||
4 => masked_key(key::End, None), // VT220/tmux style
|
||||
5 => masked_key(key::PageUp, None),
|
||||
6 => masked_key(key::PageDown, None),
|
||||
7 => masked_key(key::Home, None), // rxvt style
|
||||
8 => masked_key(key::End, None), // rxvt style
|
||||
11..=15 => masked_key(
|
||||
char::from_u32(u32::from(function_key(1)) + params[0][0] - 11).unwrap(),
|
||||
None,
|
||||
),
|
||||
17..=21 => masked_key(
|
||||
char::from_u32(u32::from(function_key(6)) + params[0][0] - 17).unwrap(),
|
||||
None,
|
||||
),
|
||||
23 | 24 => masked_key(
|
||||
char::from_u32(u32::from(function_key(11)) + params[0][0] - 23).unwrap(),
|
||||
None,
|
||||
),
|
||||
25 | 26 => {
|
||||
shift(char::from_u32(u32::from(function_key(3)) + params[0][0] - 25).unwrap())
|
||||
} // rxvt style
|
||||
28 | 29 => {
|
||||
shift(char::from_u32(u32::from(function_key(5)) + params[0][0] - 28).unwrap())
|
||||
} // rxvt style
|
||||
31 | 32 => {
|
||||
shift(char::from_u32(u32::from(function_key(7)) + params[0][0] - 31).unwrap())
|
||||
} // rxvt style
|
||||
33 | 34 => {
|
||||
shift(char::from_u32(u32::from(function_key(9)) + params[0][0] - 33).unwrap())
|
||||
} // rxvt style
|
||||
200 => {
|
||||
self.paste_start_buffering();
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::BeginUndoGroup));
|
||||
return Some(Key::from_raw(key::Invalid));
|
||||
}
|
||||
201 => {
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::EndUndoGroup));
|
||||
self.paste_commit();
|
||||
return Some(Key::from_raw(key::Invalid));
|
||||
}
|
||||
_ => return None,
|
||||
},
|
||||
b'u' => {
|
||||
// Treat numpad keys the same as their non-numpad counterparts. Could add a numpad modifier here.
|
||||
let key = match params[0][0] {
|
||||
57414 => key::Enter,
|
||||
57417 => key::Left,
|
||||
57418 => key::Right,
|
||||
57419 => key::Up,
|
||||
57420 => key::Down,
|
||||
57421 => key::PageUp,
|
||||
57422 => key::PageDown,
|
||||
57423 => key::Home,
|
||||
57424 => key::End,
|
||||
57425 => key::Insert,
|
||||
57426 => key::Delete,
|
||||
cp => canonicalize_keyed_control_char(char::from_u32(cp).unwrap()),
|
||||
};
|
||||
masked_key(
|
||||
key,
|
||||
Some(canonicalize_keyed_control_char(
|
||||
char::from_u32(params[0][1]).unwrap(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
b'Z' => shift(key::Tab),
|
||||
b'I' => {
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::FocusIn));
|
||||
return Some(Key::from_raw(key::Invalid));
|
||||
}
|
||||
b'O' => {
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::FocusOut));
|
||||
return Some(Key::from_raw(key::Invalid));
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(key)
|
||||
}
|
||||
|
||||
fn disable_mouse_tracking(&mut self) {
|
||||
// fish recognizes but does not actually support mouse reporting. We never turn it on, and
|
||||
// it's only ever enabled if a program we spawned enabled it and crashed or forgot to turn
|
||||
// it off before exiting. We turn it off here to avoid wasting resources.
|
||||
//
|
||||
// Since this is only called when we detect an incoming mouse reporting payload, we know the
|
||||
// terminal emulator supports mouse reporting, so no terminfo checks.
|
||||
FLOG!(reader, "Disabling mouse tracking");
|
||||
|
||||
// We shouldn't directly manipulate stdout from here, so we ask the reader to do it.
|
||||
// writembs(outputter_t::stdoutput(), "\x1B[?1000l");
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::DisableMouseTracking));
|
||||
}
|
||||
|
||||
fn parse_ss3(&mut self, buffer: &mut Vec<u8>) -> Option<Key> {
|
||||
let mut raw_mask = 0;
|
||||
let mut code = b'0';
|
||||
loop {
|
||||
raw_mask = raw_mask * 10 + u32::from(code - b'0');
|
||||
code = self.try_readb(buffer).unwrap_or(0xff);
|
||||
if !(b'0'..=b'9').contains(&code) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let modifiers = parse_mask(raw_mask.saturating_sub(1));
|
||||
#[rustfmt::skip]
|
||||
let key = match code {
|
||||
b' ' => Key{modifiers, codepoint: key::Space},
|
||||
b'A' => Key{modifiers, codepoint: key::Up},
|
||||
b'B' => Key{modifiers, codepoint: key::Down},
|
||||
b'C' => Key{modifiers, codepoint: key::Right},
|
||||
b'D' => Key{modifiers, codepoint: key::Left},
|
||||
b'F' => Key{modifiers, codepoint: key::End},
|
||||
b'H' => Key{modifiers, codepoint: key::Home},
|
||||
b'I' => Key{modifiers, codepoint: key::Tab},
|
||||
b'M' => Key{modifiers, codepoint: key::Enter},
|
||||
b'P' => Key{modifiers, codepoint: function_key(1)},
|
||||
b'Q' => Key{modifiers, codepoint: function_key(2)},
|
||||
b'R' => Key{modifiers, codepoint: function_key(3)},
|
||||
b'S' => Key{modifiers, codepoint: function_key(4)},
|
||||
b'X' => Key{modifiers, codepoint: '='},
|
||||
b'j' => Key{modifiers, codepoint: '*'},
|
||||
b'k' => Key{modifiers, codepoint: '+'},
|
||||
b'l' => Key{modifiers, codepoint: ','},
|
||||
b'm' => Key{modifiers, codepoint: '-'},
|
||||
b'n' => Key{modifiers, codepoint: '.'},
|
||||
b'o' => Key{modifiers, codepoint: '/'},
|
||||
b'p' => Key{modifiers, codepoint: '0'},
|
||||
b'q' => Key{modifiers, codepoint: '1'},
|
||||
b'r' => Key{modifiers, codepoint: '2'},
|
||||
b's' => Key{modifiers, codepoint: '3'},
|
||||
b't' => Key{modifiers, codepoint: '4'},
|
||||
b'u' => Key{modifiers, codepoint: '5'},
|
||||
b'v' => Key{modifiers, codepoint: '6'},
|
||||
b'w' => Key{modifiers, codepoint: '7'},
|
||||
b'x' => Key{modifiers, codepoint: '8'},
|
||||
b'y' => Key{modifiers, codepoint: '9'},
|
||||
_ => return None,
|
||||
};
|
||||
Some(key)
|
||||
}
|
||||
|
||||
fn readch_timed_esc(&mut self) -> Option<CharEvent> {
|
||||
self.readch_timed(WAIT_ON_ESCAPE_MS.load(Ordering::Relaxed))
|
||||
}
|
||||
|
@ -556,6 +1049,24 @@ pub trait InputEventQueuer {
|
|||
/// Return the fd corresponding to stdin.
|
||||
fn get_in_fd(&self) -> RawFd;
|
||||
|
||||
// Support for "bracketed paste"
|
||||
// The way it works is that we acknowledge our support by printing
|
||||
// \e\[?2004h
|
||||
// then the terminal will "bracket" every paste in
|
||||
// \e\[200~ and \e\[201~
|
||||
// Every character in between those two will be part of the paste and should not cause a binding to execute (like \n executing commands).
|
||||
//
|
||||
// We enable it after every command and disable it before, see the terminal protocols logic.
|
||||
//
|
||||
// Support for this seems to be ubiquitous - emacs enables it unconditionally (!) since 25.1
|
||||
// (though it only supports it since then, it seems to be the last term to gain support).
|
||||
//
|
||||
// See http://thejh.net/misc/website-terminal-copy-paste.
|
||||
fn paste_start_buffering(&mut self);
|
||||
fn paste_is_buffering(&self) -> bool;
|
||||
fn paste_push_char(&mut self, _b: u8) {}
|
||||
fn paste_commit(&mut self);
|
||||
|
||||
/// Enqueue a character or a readline function to the queue of unread characters that
|
||||
/// readch will return before actually reading from fd 0.
|
||||
fn push_back(&mut self, ch: CharEvent) {
|
||||
|
@ -622,8 +1133,8 @@ pub trait InputEventQueuer {
|
|||
/// nothing.
|
||||
fn prepare_to_select(&mut self) {}
|
||||
|
||||
/// Override point for when when select() is interrupted by a signal. The default does nothing.
|
||||
fn select_interrupted(&mut self) {}
|
||||
/// Called when select() is interrupted by a signal.
|
||||
fn select_interrupted(&mut self);
|
||||
|
||||
/// Override point for when when select() is interrupted by the universal variable notifier.
|
||||
/// The default does nothing.
|
||||
|
@ -639,6 +1150,7 @@ pub trait InputEventQueuer {
|
|||
pub struct InputEventQueue {
|
||||
queue: VecDeque<CharEvent>,
|
||||
in_fd: RawFd,
|
||||
is_in_bracketed_paste: bool,
|
||||
}
|
||||
|
||||
impl InputEventQueue {
|
||||
|
@ -646,6 +1158,7 @@ impl InputEventQueue {
|
|||
InputEventQueue {
|
||||
queue: VecDeque::new(),
|
||||
in_fd,
|
||||
is_in_bracketed_paste: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -662,4 +1175,23 @@ impl InputEventQueuer for InputEventQueue {
|
|||
fn get_in_fd(&self) -> RawFd {
|
||||
self.in_fd
|
||||
}
|
||||
|
||||
fn select_interrupted(&mut self) {
|
||||
if reader_test_and_clear_interrupted() != 0 {
|
||||
let vintr = shell_modes().c_cc[libc::VINTR];
|
||||
if vintr != 0 {
|
||||
self.push_front(CharEvent::from_key(Key::from_single_byte(vintr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paste_start_buffering(&mut self) {
|
||||
self.is_in_bracketed_paste = true;
|
||||
}
|
||||
fn paste_is_buffering(&self) -> bool {
|
||||
self.is_in_bracketed_paste
|
||||
}
|
||||
fn paste_commit(&mut self) {
|
||||
self.is_in_bracketed_paste = false;
|
||||
}
|
||||
}
|
||||
|
|
430
src/key.rs
Normal file
430
src/key.rs
Normal file
|
@ -0,0 +1,430 @@
|
|||
use std::ops;
|
||||
use std::rc::Rc;
|
||||
|
||||
use libc::VERASE;
|
||||
|
||||
use crate::{
|
||||
fallback::fish_wcwidth, reader::TERMINAL_MODE_ON_STARTUP, wchar::prelude::*, wutil::fish_wcstoi,
|
||||
};
|
||||
|
||||
pub(crate) const Backspace: char = '\u{F500}'; // below ENCODE_DIRECT_BASE
|
||||
pub(crate) const Delete: char = '\u{F501}';
|
||||
pub(crate) const Escape: char = '\u{F502}';
|
||||
pub(crate) const Enter: char = '\u{F503}';
|
||||
pub(crate) const Up: char = '\u{F504}';
|
||||
pub(crate) const Down: char = '\u{F505}';
|
||||
pub(crate) const Left: char = '\u{F506}';
|
||||
pub(crate) const Right: char = '\u{F507}';
|
||||
pub(crate) const PageUp: char = '\u{F508}';
|
||||
pub(crate) const PageDown: char = '\u{F509}';
|
||||
pub(crate) const Home: char = '\u{F50a}';
|
||||
pub(crate) const End: char = '\u{F50b}';
|
||||
pub(crate) const Insert: char = '\u{F50c}';
|
||||
pub(crate) const Tab: char = '\u{F50d}';
|
||||
pub(crate) const Space: char = '\u{F50e}';
|
||||
pub(crate) const Invalid: char = '\u{F50f}';
|
||||
pub(crate) fn function_key(n: u32) -> char {
|
||||
assert!((1..=12).contains(&n));
|
||||
char::from_u32(u32::from(Invalid) + n).unwrap()
|
||||
}
|
||||
|
||||
const NAMED_KEYS_RANGE: ops::Range<u32> = 0xF500..(Invalid as u32 + 12);
|
||||
|
||||
const KEY_NAMES: &[(char, &wstr)] = &[
|
||||
('+', L!("plus")),
|
||||
('-', L!("minus")),
|
||||
(',', L!("comma")),
|
||||
(Backspace, L!("backspace")),
|
||||
(Delete, L!("delete")),
|
||||
(Escape, L!("escape")),
|
||||
(Enter, L!("enter")),
|
||||
(Up, L!("up")),
|
||||
(Down, L!("down")),
|
||||
(Left, L!("left")),
|
||||
(Right, L!("right")),
|
||||
(PageUp, L!("pageup")),
|
||||
(PageDown, L!("pagedown")),
|
||||
(Home, L!("home")),
|
||||
(End, L!("end")),
|
||||
(Insert, L!("insert")),
|
||||
(Tab, L!("tab")),
|
||||
(Space, L!("space")),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Modifiers {
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
pub shift: bool,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
const fn new() -> Self {
|
||||
Modifiers {
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
}
|
||||
}
|
||||
pub(crate) fn is_some(&self) -> bool {
|
||||
self.ctrl || self.alt || self.shift
|
||||
}
|
||||
pub(crate) fn is_none(&self) -> bool {
|
||||
!self.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Key {
|
||||
pub modifiers: Modifiers,
|
||||
pub codepoint: char,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub(crate) fn from_raw(codepoint: char) -> Self {
|
||||
Self {
|
||||
modifiers: Modifiers::default(),
|
||||
codepoint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn ctrl(codepoint: char) -> Key {
|
||||
let mut modifiers = Modifiers::new();
|
||||
modifiers.ctrl = true;
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn alt(codepoint: char) -> Key {
|
||||
let mut modifiers = Modifiers::new();
|
||||
modifiers.alt = true;
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn shift(codepoint: char) -> Key {
|
||||
let mut modifiers = Modifiers::new();
|
||||
modifiers.shift = true;
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn from_single_char(c: char) -> Self {
|
||||
u8::try_from(c)
|
||||
.map(Key::from_single_byte)
|
||||
.unwrap_or(Key::from_raw(c))
|
||||
}
|
||||
pub fn from_single_byte(c: u8) -> Self {
|
||||
canonicalize_control_char(c).unwrap_or(Key::from_raw(char::from(c)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonicalize_control_char(c: u8) -> Option<Key> {
|
||||
let codepoint = canonicalize_keyed_control_char(char::from(c));
|
||||
if u32::from(codepoint) > 255 {
|
||||
return Some(Key {
|
||||
modifiers: Modifiers::default(),
|
||||
codepoint,
|
||||
});
|
||||
}
|
||||
|
||||
if c < 32 {
|
||||
return Some(ctrl(canonicalize_unkeyed_control_char(c)));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn ascii_control(c: char) -> char {
|
||||
char::from_u32(u32::from(c) & 0o37).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn canonicalize_keyed_control_char(c: char) -> char {
|
||||
if c == ascii_control('m') || c == ascii_control('j') {
|
||||
return Enter;
|
||||
}
|
||||
if c == ascii_control('i') {
|
||||
return Tab;
|
||||
}
|
||||
if c == ' ' {
|
||||
return Space;
|
||||
}
|
||||
if c == char::from(TERMINAL_MODE_ON_STARTUP.lock().unwrap().c_cc[VERASE]) {
|
||||
return Backspace;
|
||||
}
|
||||
if c == char::from(127) {
|
||||
// when it's not backspace
|
||||
return Delete;
|
||||
}
|
||||
if c == '\x1b' {
|
||||
return Escape;
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
pub(crate) fn canonicalize_unkeyed_control_char(c: u8) -> char {
|
||||
if c == 0 {
|
||||
// For legacy terminals we have to make a decision here; they send NUL on Ctrl-2,
|
||||
// Ctrl-Shift-2 or Ctrl-Backtick, but the most straightforward way is Ctrl-Space.
|
||||
return Space;
|
||||
}
|
||||
// Represent Ctrl-letter combinations in lower-case, to be clear
|
||||
// that Shift is not involved.
|
||||
if c < 27 {
|
||||
return char::from(c - 1 + b'a');
|
||||
}
|
||||
// Represent Ctrl-symbol combinations in "upper-case", as they are
|
||||
// traditionally-rendered.
|
||||
assert!(c < 32);
|
||||
return char::from(c - 1 + b'A');
|
||||
}
|
||||
|
||||
pub(crate) fn canonicalize_key(mut key: Key) -> Result<Key, WString> {
|
||||
// Leave raw escapes to disambiguate from named escape.
|
||||
if key.codepoint != '\x1b' {
|
||||
key.codepoint = canonicalize_keyed_control_char(key.codepoint);
|
||||
if key.codepoint < ' ' {
|
||||
key.codepoint = canonicalize_unkeyed_control_char(u8::try_from(key.codepoint).unwrap());
|
||||
if key.modifiers.ctrl {
|
||||
return Err(wgettext_fmt!(
|
||||
"Cannot add control modifier to control character '%s'",
|
||||
key
|
||||
));
|
||||
}
|
||||
key.modifiers.ctrl = true;
|
||||
}
|
||||
}
|
||||
if key.modifiers.shift {
|
||||
if key.codepoint.is_ascii_alphabetic() {
|
||||
// Shift + ASCII letters is just the uppercase letter.
|
||||
key.modifiers.shift = false;
|
||||
key.codepoint = key.codepoint.to_ascii_uppercase();
|
||||
} else if !NAMED_KEYS_RANGE.contains(&u32::from(key.codepoint)) {
|
||||
// Shift + any other printable character is not allowed.
|
||||
return Err(wgettext_fmt!(
|
||||
"Shift modifier is only supported on special keys and lowercase ASCII, not '%s'",
|
||||
key,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
pub const KEY_SEPARATOR: char = ',';
|
||||
|
||||
pub(crate) fn parse_keys(value: &wstr) -> Result<Vec<Key>, WString> {
|
||||
let mut res = vec![];
|
||||
if value.is_empty() {
|
||||
return Ok(res);
|
||||
}
|
||||
let first = value.as_char_slice()[0];
|
||||
if value.len() == 1 {
|
||||
// Hack: allow singular comma.
|
||||
res.push(canonicalize_key(Key::from_raw(first)).unwrap());
|
||||
} else if (value.len() == 2
|
||||
&& !value.contains('-')
|
||||
&& !value.contains(KEY_SEPARATOR)
|
||||
&& !KEY_NAMES.iter().any(|(_codepoint, name)| name == value))
|
||||
|| (first == '\x1b' || first == ascii_control(first))
|
||||
{
|
||||
// Hack: treat as legacy syntax (meaning: not comma separated) if
|
||||
// 1. it doesn't contain '-' or ',' and is short enough to probably not be a key name.
|
||||
// 2. it starts with raw escape (\e) or a raw ASCII control character (\c).
|
||||
for c in value.chars() {
|
||||
res.push(canonicalize_key(Key::from_raw(c)).unwrap());
|
||||
}
|
||||
} else {
|
||||
for full_key_name in value.split(KEY_SEPARATOR) {
|
||||
if full_key_name == "-" {
|
||||
// Hack: allow singular minus.
|
||||
res.push(canonicalize_key(Key::from_raw('-')).unwrap());
|
||||
continue;
|
||||
}
|
||||
let mut modifiers = Modifiers::default();
|
||||
let num_keys = full_key_name.split('-').count();
|
||||
let mut components = full_key_name.split('-');
|
||||
for _i in 0..num_keys.checked_sub(1).unwrap() {
|
||||
let modifier = components.next().unwrap();
|
||||
match modifier {
|
||||
_ if modifier == "ctrl" => modifiers.ctrl = true,
|
||||
_ if modifier == "alt" => modifiers.alt = true,
|
||||
_ if modifier == "shift" => modifiers.shift = true,
|
||||
_ => {
|
||||
return Err(wgettext_fmt!(
|
||||
"unknown modififer '%s' in '%s'",
|
||||
modifier,
|
||||
full_key_name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
let key_name = components.next().unwrap();
|
||||
let codepoint = KEY_NAMES
|
||||
.iter()
|
||||
.find_map(|(codepoint, name)| (name == key_name).then_some(*codepoint))
|
||||
.or_else(|| (key_name.len() == 1).then(|| key_name.as_char_slice()[0]));
|
||||
let key = if let Some(codepoint) = codepoint {
|
||||
canonicalize_key(Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
})?
|
||||
} else if codepoint.is_none() && key_name.starts_with('F') && key_name.len() <= 3 {
|
||||
let num = key_name.strip_prefix('F');
|
||||
let codepoint = match fish_wcstoi(num) {
|
||||
Ok(n) if (1..=12).contains(&n) => function_key(u32::try_from(n).unwrap()),
|
||||
_ => {
|
||||
return Err(wgettext_fmt!(
|
||||
"only F1 through F12 are supported, not '%s'",
|
||||
num,
|
||||
full_key_name
|
||||
));
|
||||
}
|
||||
};
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
} else {
|
||||
return Err(wgettext_fmt!("cannot parse key '%s'", full_key_name));
|
||||
};
|
||||
res.push(key);
|
||||
}
|
||||
}
|
||||
Ok(canonicalize_raw_escapes(res))
|
||||
}
|
||||
|
||||
pub(crate) fn canonicalize_raw_escapes(keys: Vec<Key>) -> Vec<Key> {
|
||||
// Historical bindings use \ek to mean alt-k. Canonicalize them.
|
||||
if !keys.iter().any(|key| key.codepoint == '\x1b') {
|
||||
return keys;
|
||||
}
|
||||
let mut canonical = vec![];
|
||||
let mut had_literal_escape = false;
|
||||
for mut key in keys {
|
||||
if had_literal_escape {
|
||||
had_literal_escape = false;
|
||||
if key.modifiers.alt {
|
||||
canonical.push(Key::from_raw(Escape));
|
||||
} else {
|
||||
key.modifiers.alt = true;
|
||||
if key.codepoint == '\x1b' {
|
||||
key.codepoint = Escape;
|
||||
}
|
||||
}
|
||||
} else if key.codepoint == '\x1b' {
|
||||
had_literal_escape = true;
|
||||
continue;
|
||||
}
|
||||
canonical.push(key);
|
||||
}
|
||||
if had_literal_escape {
|
||||
canonical.push(Key::from_raw(Escape));
|
||||
}
|
||||
canonical
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub(crate) fn codepoint_text(&self) -> Option<char> {
|
||||
if self.modifiers.is_some() {
|
||||
return None;
|
||||
}
|
||||
let c = self.codepoint;
|
||||
if c == Space {
|
||||
return Some(' ');
|
||||
}
|
||||
if c == Enter {
|
||||
return Some('\n');
|
||||
}
|
||||
if c == Tab {
|
||||
return Some('\t');
|
||||
}
|
||||
if NAMED_KEYS_RANGE.contains(&u32::from(c)) || u32::from(c) <= 27 {
|
||||
return None;
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
WString::from(*self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl printf_compat::args::ToArg<'static> for Key {
|
||||
fn to_arg(self) -> printf_compat::args::Arg<'static> {
|
||||
printf_compat::args::Arg::BoxedStr(Rc::new(WString::from(self).into_boxed_utfstr()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Key> for WString {
|
||||
fn from(key: Key) -> Self {
|
||||
let name = KEY_NAMES
|
||||
.iter()
|
||||
.find_map(|&(codepoint, name)| (codepoint == key.codepoint).then(|| name.to_owned()))
|
||||
.or_else(|| {
|
||||
(function_key(1)..=function_key(12))
|
||||
.contains(&key.codepoint)
|
||||
.then(|| {
|
||||
sprintf!(
|
||||
"F%d",
|
||||
u32::from(key.codepoint) - u32::from(function_key(1)) + 1
|
||||
)
|
||||
})
|
||||
});
|
||||
let mut res = name.unwrap_or_else(|| char_to_symbol(key.codepoint));
|
||||
|
||||
if key.modifiers.shift {
|
||||
res.insert_utfstr(0, L!("shift-"));
|
||||
}
|
||||
if key.modifiers.alt {
|
||||
res.insert_utfstr(0, L!("alt-"));
|
||||
}
|
||||
if key.modifiers.ctrl {
|
||||
res.insert_utfstr(0, L!("ctrl-"));
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if the character must be escaped when used in the sequence of chars to be bound in
|
||||
/// a `bind` command.
|
||||
fn must_escape(c: char) -> bool {
|
||||
"[]()<>{}*\\?$#;&|'\"".contains(c)
|
||||
}
|
||||
|
||||
fn ascii_printable_to_symbol(buf: &mut WString, c: char) {
|
||||
if must_escape(c) {
|
||||
sprintf!(=> buf, "\\%c", c);
|
||||
} else {
|
||||
sprintf!(=> buf, "%c", c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a wide-char to a symbol that can be used in our output.
|
||||
fn char_to_symbol(c: char) -> WString {
|
||||
let mut buff = WString::new();
|
||||
let buf = &mut buff;
|
||||
assert!(c >= ' ');
|
||||
if c < '\u{80}' {
|
||||
// ASCII characters that are not control characters
|
||||
ascii_printable_to_symbol(buf, c);
|
||||
} else if fish_wcwidth(c) > 0 {
|
||||
sprintf!(=> buf, "%lc", c);
|
||||
} else if c <= '\u{FFFF}' {
|
||||
// BMP Unicode chararacter
|
||||
sprintf!(=> buf, "\\u%04X", u32::from(c));
|
||||
} else {
|
||||
sprintf!(=> buf, "\\U%06X", u32::from(c));
|
||||
}
|
||||
buff
|
||||
}
|
|
@ -59,6 +59,7 @@ pub mod input;
|
|||
pub mod input_common;
|
||||
pub mod io;
|
||||
pub mod job_group;
|
||||
pub mod key;
|
||||
pub mod kill;
|
||||
#[allow(non_snake_case)]
|
||||
pub mod libc;
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::env::Statuses;
|
|||
use crate::event::{self, Event};
|
||||
use crate::flog::{FLOG, FLOGF};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::terminal_protocols_enable;
|
||||
use crate::io::IoChain;
|
||||
use crate::job_group::{JobGroup, MaybeJobId};
|
||||
use crate::parse_tree::ParsedSourceRef;
|
||||
|
@ -1412,6 +1413,9 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool) {
|
|||
let status = ProcStatus::from_waitpid(statusv);
|
||||
handle_child_status(j, proc, &status);
|
||||
if status.stopped() {
|
||||
if is_interactive_session() && j.group().wants_terminal() {
|
||||
terminal_protocols_enable();
|
||||
}
|
||||
j.group().set_is_foreground(false);
|
||||
}
|
||||
if status.continued() {
|
||||
|
|
|
@ -69,7 +69,9 @@ use crate::history::{
|
|||
};
|
||||
use crate::input::init_input;
|
||||
use crate::input::Inputter;
|
||||
use crate::input_common::{CharEvent, CharInputStyle, ReadlineCmd};
|
||||
use crate::input_common::{
|
||||
terminal_protocols_enable_scoped, CharEvent, CharInputStyle, ReadlineCmd,
|
||||
};
|
||||
use crate::io::IoChain;
|
||||
use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate};
|
||||
use crate::libc::MB_CUR_MAX;
|
||||
|
@ -133,7 +135,7 @@ pub static SHELL_MODES: Lazy<Mutex<libc::termios>> =
|
|||
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
||||
|
||||
/// Mode on startup, which we restore on exit.
|
||||
static TERMINAL_MODE_ON_STARTUP: Lazy<Mutex<libc::termios>> =
|
||||
pub static TERMINAL_MODE_ON_STARTUP: Lazy<Mutex<libc::termios>> =
|
||||
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
||||
|
||||
/// Mode we use to execute programs.
|
||||
|
@ -784,10 +786,15 @@ pub fn reader_init() -> impl ScopeGuarding<Target = ()> {
|
|||
|
||||
// Set up our fixed terminal modes once,
|
||||
// so we don't get flow control just because we inherited it.
|
||||
if is_interactive_session() && unsafe { libc::getpgrp() == libc::tcgetpgrp(STDIN_FILENO) } {
|
||||
term_donate(/*quiet=*/ true);
|
||||
let mut terminal_protocols = None;
|
||||
if is_interactive_session() {
|
||||
terminal_protocols = Some(terminal_protocols_enable_scoped());
|
||||
if unsafe { libc::getpgrp() == libc::tcgetpgrp(STDIN_FILENO) } {
|
||||
term_donate(/*quiet=*/ true);
|
||||
}
|
||||
}
|
||||
ScopeGuard::new((), |()| {
|
||||
ScopeGuard::new((), move |()| {
|
||||
let _terminal_protocols = terminal_protocols;
|
||||
restore_term_mode();
|
||||
})
|
||||
}
|
||||
|
@ -1912,26 +1919,23 @@ impl ReaderData {
|
|||
CharEvent::Command(command) => {
|
||||
zelf.run_input_command_scripts(&command);
|
||||
}
|
||||
CharEvent::Char(cevt) => {
|
||||
CharEvent::Key(kevt) => {
|
||||
// Ordinary char.
|
||||
let c = cevt.char;
|
||||
if cevt.input_style == CharInputStyle::NotFirst
|
||||
if kevt.input_style == CharInputStyle::NotFirst
|
||||
&& zelf.active_edit_line().1.position() == 0
|
||||
{
|
||||
// This character is skipped.
|
||||
} else if c.is_control() {
|
||||
// This can happen if the user presses a control char we don't recognize. No
|
||||
// reason to report this to the user unless they've enabled debugging output.
|
||||
FLOG!(reader, wgettext_fmt!("Unknown key binding 0x%X", c));
|
||||
} else {
|
||||
// Regular character.
|
||||
let (elt, _el) = zelf.active_edit_line();
|
||||
zelf.insert_char(elt, c);
|
||||
if let Some(c) = kevt.key.codepoint_text() {
|
||||
zelf.insert_char(elt, c);
|
||||
|
||||
if elt == EditableLineTag::Commandline {
|
||||
zelf.clear_pager();
|
||||
// We end history search. We could instead update the search string.
|
||||
zelf.history_search.reset();
|
||||
if elt == EditableLineTag::Commandline {
|
||||
zelf.clear_pager();
|
||||
// We end history search. We could instead update the search string.
|
||||
zelf.history_search.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
rls.last_cmd = None;
|
||||
|
@ -2036,7 +2040,7 @@ impl ReaderData {
|
|||
|
||||
while accumulated_chars.len() < limit {
|
||||
let evt = self.inputter.read_char();
|
||||
let CharEvent::Char(cevt) = &evt else {
|
||||
let CharEvent::Key(kevt) = &evt else {
|
||||
event_needing_handling = Some(evt);
|
||||
break;
|
||||
};
|
||||
|
@ -2044,16 +2048,20 @@ impl ReaderData {
|
|||
event_needing_handling = Some(evt);
|
||||
break;
|
||||
}
|
||||
if cevt.input_style == CharInputStyle::NotFirst
|
||||
if kevt.input_style == CharInputStyle::NotFirst
|
||||
&& accumulated_chars.is_empty()
|
||||
&& self.active_edit_line().1.position() == 0
|
||||
{
|
||||
// The cursor is at the beginning and nothing is accumulated, so skip this character.
|
||||
continue;
|
||||
} else {
|
||||
accumulated_chars.push(cevt.char);
|
||||
}
|
||||
|
||||
if let Some(c) = kevt.key.codepoint_text() {
|
||||
accumulated_chars.push(c);
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if last_exec_count != self.exec_count() {
|
||||
last_exec_count = self.exec_count();
|
||||
self.screen.save_status();
|
||||
|
@ -3047,6 +3055,12 @@ impl ReaderData {
|
|||
.borrow_mut()
|
||||
.write_wstr(L!("\x1B[?1000l"));
|
||||
}
|
||||
rl::FocusIn => {
|
||||
event::fire_generic(self.parser(), L!("fish_focus_in").to_owned(), vec![]);
|
||||
}
|
||||
rl::FocusOut => {
|
||||
event::fire_generic(self.parser(), L!("fish_focus_out").to_owned(), vec![]);
|
||||
}
|
||||
rl::ClearScreenAndRepaint => {
|
||||
self.parser().libdata_mut().pods.is_repaint = true;
|
||||
let clear = screen_clear();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::input::{input_mappings, Inputter, DEFAULT_BIND_MODE};
|
||||
use crate::input_common::{CharEvent, ReadlineCmd};
|
||||
use crate::key::Key;
|
||||
use crate::parser::Parser;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wchar::prelude::*;
|
||||
|
@ -15,8 +16,9 @@ fn test_input() {
|
|||
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix
|
||||
// of the second, and then emit the second key list. The second binding should be invoked, not
|
||||
// the first!
|
||||
let prefix_binding = WString::from_str("qqqqqqqa");
|
||||
let desired_binding = prefix_binding.clone() + "a";
|
||||
let prefix_binding: Vec<Key> = "qqqqqqqa".chars().map(Key::from_raw).collect();
|
||||
let mut desired_binding = prefix_binding.clone();
|
||||
desired_binding.push(Key::from_raw('a'));
|
||||
|
||||
let default_mode = || DEFAULT_BIND_MODE.to_owned();
|
||||
|
||||
|
@ -24,6 +26,7 @@ fn test_input() {
|
|||
let mut input_mapping = input_mappings();
|
||||
input_mapping.add1(
|
||||
prefix_binding,
|
||||
None,
|
||||
WString::from_str("up-line"),
|
||||
default_mode(),
|
||||
None,
|
||||
|
@ -31,6 +34,7 @@ fn test_input() {
|
|||
);
|
||||
input_mapping.add1(
|
||||
desired_binding.clone(),
|
||||
None,
|
||||
WString::from_str("down-line"),
|
||||
default_mode(),
|
||||
None,
|
||||
|
@ -39,8 +43,8 @@ fn test_input() {
|
|||
}
|
||||
|
||||
// Push the desired binding to the queue.
|
||||
for c in desired_binding.chars() {
|
||||
input.queue_char(CharEvent::from_char(c));
|
||||
for c in desired_binding {
|
||||
input.queue_char(CharEvent::from_key(c));
|
||||
}
|
||||
|
||||
// Now test.
|
||||
|
|
|
@ -7,10 +7,10 @@ fn test_push_front_back() {
|
|||
queue.push_front(CharEvent::from_char('b'));
|
||||
queue.push_back(CharEvent::from_char('c'));
|
||||
queue.push_back(CharEvent::from_char('d'));
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'd');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'd');
|
||||
assert!(queue.try_pop().is_none());
|
||||
}
|
||||
|
||||
|
@ -27,15 +27,15 @@ fn test_promote_interruptions_to_front() {
|
|||
|
||||
assert_eq!(queue.try_pop().unwrap().get_readline(), ReadlineCmd::Undo);
|
||||
assert_eq!(queue.try_pop().unwrap().get_readline(), ReadlineCmd::Redo);
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'd');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'd');
|
||||
assert!(!queue.has_lookahead());
|
||||
|
||||
queue.push_back(CharEvent::from_char('e'));
|
||||
queue.promote_interruptions_to_front();
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'e');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'e');
|
||||
assert!(!queue.has_lookahead());
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,9 @@ fn test_insert_front() {
|
|||
CharEvent::from_char('C'),
|
||||
];
|
||||
queue.insert_front(events);
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'A');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'B');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'C');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'A');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'B');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'C');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
}
|
||||
|
|
13
src/tests/key.rs
Normal file
13
src/tests/key.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use crate::key::{self, ctrl, parse_keys, Key};
|
||||
use crate::wchar::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_key() {
|
||||
assert_eq!(
|
||||
parse_keys(L!("escape")),
|
||||
Ok(vec![Key::from_raw(key::Escape)])
|
||||
);
|
||||
assert_eq!(parse_keys(L!("\x1b")), Ok(vec![Key::from_raw(key::Escape)]));
|
||||
assert_eq!(parse_keys(L!("ctrl-a")), Ok(vec![ctrl('a')]));
|
||||
assert_eq!(parse_keys(L!("\x01")), Ok(vec![ctrl('a')]));
|
||||
}
|
|
@ -12,6 +12,7 @@ mod highlight;
|
|||
mod history;
|
||||
mod input;
|
||||
mod input_common;
|
||||
mod key;
|
||||
mod pager;
|
||||
mod parse_util;
|
||||
mod parser;
|
||||
|
|
|
@ -277,6 +277,16 @@ pub trait WExt {
|
|||
iter_prefixes_iter(prefix.chars(), self.as_char_slice().iter().copied())
|
||||
}
|
||||
|
||||
fn strip_prefix<Prefix: IntoCharIter>(&self, prefix: Prefix) -> &wstr {
|
||||
let iter = prefix.chars();
|
||||
let prefix_len = iter.clone().count();
|
||||
if iter_prefixes_iter(iter, self.as_char_slice().iter().copied()) {
|
||||
self.slice_from(prefix_len)
|
||||
} else {
|
||||
self.slice_from(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// \return whether we end with a given Suffix.
|
||||
/// The Suffix can be a char, a &str, a &wstr, or a &WString.
|
||||
fn ends_with<Suffix: IntoCharIter>(&self, suffix: Suffix) -> bool {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
# Universal abbreviations are imported.
|
||||
set -U _fish_abbr_cuckoo somevalue
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# Avoid regressions of issue \#3860 wherein the first word of the alias ends with a semicolon
|
||||
function foo
|
||||
echo ran foo
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# "Basic && and || support"
|
||||
|
||||
echo first && echo second
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
set -xl LANG C # uniform quotes
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
##########
|
||||
|
||||
# NOTE: This uses argparse, which touches the local variables.
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish -Z
|
||||
#RUN: %fish -Z | %filter-ctrlseqs
|
||||
# CHECKERR: {{.*fish}}: {{unrecognized option: Z|invalid option -- '?Z'?|unknown option -- Z|illegal option -- Z|-Z: unknown option}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish -C 'set -g fish %fish' %s
|
||||
# RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
#
|
||||
# Test function, loops, conditionals and some basic elements
|
||||
#
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# Test various `bind` command invocations. This is meant to verify that
|
||||
# invalid flags, mode names, etc. are caught as well as to verify that valid
|
||||
# ones are allowed.
|
||||
|
@ -14,102 +14,103 @@ bind -M bind-mode \cX true
|
|||
# This should succeed and result in a success, zero, status.
|
||||
bind -M bind_mode \cX true
|
||||
|
||||
### HACK: All full bind listings need to have the \x7f -> backward-delete-char
|
||||
# binding explicitly removed, because on some systems that's backspace, on others not.
|
||||
# Listing bindings
|
||||
bind | string match -v '*backward-delete-char'
|
||||
bind --user --preset | string match -v '*backward-delete-char'
|
||||
bind | string match -v '*escape,\\[*' # Hide legacy bindings.
|
||||
bind --user --preset | string match -v '*escape,\\[*'
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset \n execute
|
||||
# CHECK: bind --preset \r execute
|
||||
# CHECK: bind --preset \t complete
|
||||
# CHECK: bind --preset \cc cancel-commandline
|
||||
# CHECK: bind --preset \cd exit
|
||||
# CHECK: bind --preset \ce bind
|
||||
# CHECK: bind --preset \cs pager-toggle-search
|
||||
# CHECK: bind --preset \cu backward-kill-line
|
||||
# CHECK: bind --preset \e\[A up-line
|
||||
# CHECK: bind --preset \e\[B down-line
|
||||
# CHECK: bind --preset \e\[C forward-char
|
||||
# CHECK: bind --preset \e\[D backward-char
|
||||
# CHECK: bind --preset \cp up-line
|
||||
# CHECK: bind --preset \cn down-line
|
||||
# CHECK: bind --preset \cb backward-char
|
||||
# CHECK: bind --preset \cf forward-char
|
||||
# CHECK: bind -M bind_mode \cx true
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
# CHECK: bind --preset ctrl-c cancel-commandline
|
||||
# CHECK: bind --preset ctrl-d exit
|
||||
# CHECK: bind --preset ctrl-e bind
|
||||
# CHECK: bind --preset ctrl-s pager-toggle-search
|
||||
# CHECK: bind --preset ctrl-u backward-kill-line
|
||||
# CHECK: bind --preset backspace backward-delete-char
|
||||
# CHECK: bind --preset up up-line
|
||||
# CHECK: bind --preset down down-line
|
||||
# CHECK: bind --preset right forward-char
|
||||
# CHECK: bind --preset left backward-char
|
||||
# CHECK: bind --preset ctrl-p up-line
|
||||
# CHECK: bind --preset ctrl-n down-line
|
||||
# CHECK: bind --preset ctrl-b backward-char
|
||||
# CHECK: bind --preset ctrl-f forward-char
|
||||
# CHECK: bind -M bind_mode ctrl-x true
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset \n execute
|
||||
# CHECK: bind --preset \r execute
|
||||
# CHECK: bind --preset \t complete
|
||||
# CHECK: bind --preset \cc cancel-commandline
|
||||
# CHECK: bind --preset \cd exit
|
||||
# CHECK: bind --preset \ce bind
|
||||
# CHECK: bind --preset \cs pager-toggle-search
|
||||
# CHECK: bind --preset \cu backward-kill-line
|
||||
# CHECK: bind --preset \e\[A up-line
|
||||
# CHECK: bind --preset \e\[B down-line
|
||||
# CHECK: bind --preset \e\[C forward-char
|
||||
# CHECK: bind --preset \e\[D backward-char
|
||||
# CHECK: bind --preset \cp up-line
|
||||
# CHECK: bind --preset \cn down-line
|
||||
# CHECK: bind --preset \cb backward-char
|
||||
# CHECK: bind --preset \cf forward-char
|
||||
# CHECK: bind -M bind_mode \cx true
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
# CHECK: bind --preset ctrl-c cancel-commandline
|
||||
# CHECK: bind --preset ctrl-d exit
|
||||
# CHECK: bind --preset ctrl-e bind
|
||||
# CHECK: bind --preset ctrl-s pager-toggle-search
|
||||
# CHECK: bind --preset ctrl-u backward-kill-line
|
||||
# CHECK: bind --preset backspace backward-delete-char
|
||||
# CHECK: bind --preset up up-line
|
||||
# CHECK: bind --preset down down-line
|
||||
# CHECK: bind --preset right forward-char
|
||||
# CHECK: bind --preset left backward-char
|
||||
# CHECK: bind --preset ctrl-p up-line
|
||||
# CHECK: bind --preset ctrl-n down-line
|
||||
# CHECK: bind --preset ctrl-b backward-char
|
||||
# CHECK: bind --preset ctrl-f forward-char
|
||||
# CHECK: bind -M bind_mode ctrl-x true
|
||||
|
||||
# Preset only
|
||||
bind --preset | string match -v '*backward-delete-char'
|
||||
bind --preset | string match -v '*escape,\\[*'
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset \n execute
|
||||
# CHECK: bind --preset \r execute
|
||||
# CHECK: bind --preset \t complete
|
||||
# CHECK: bind --preset \cc cancel-commandline
|
||||
# CHECK: bind --preset \cd exit
|
||||
# CHECK: bind --preset \ce bind
|
||||
# CHECK: bind --preset \cs pager-toggle-search
|
||||
# CHECK: bind --preset \cu backward-kill-line
|
||||
# CHECK: bind --preset \e\[A up-line
|
||||
# CHECK: bind --preset \e\[B down-line
|
||||
# CHECK: bind --preset \e\[C forward-char
|
||||
# CHECK: bind --preset \e\[D backward-char
|
||||
# CHECK: bind --preset \cp up-line
|
||||
# CHECK: bind --preset \cn down-line
|
||||
# CHECK: bind --preset \cb backward-char
|
||||
# CHECK: bind --preset \cf forward-char
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
# CHECK: bind --preset ctrl-c cancel-commandline
|
||||
# CHECK: bind --preset ctrl-d exit
|
||||
# CHECK: bind --preset ctrl-e bind
|
||||
# CHECK: bind --preset ctrl-s pager-toggle-search
|
||||
# CHECK: bind --preset ctrl-u backward-kill-line
|
||||
# CHECK: bind --preset backspace backward-delete-char
|
||||
# CHECK: bind --preset up up-line
|
||||
# CHECK: bind --preset down down-line
|
||||
# CHECK: bind --preset right forward-char
|
||||
# CHECK: bind --preset left backward-char
|
||||
# CHECK: bind --preset ctrl-p up-line
|
||||
# CHECK: bind --preset ctrl-n down-line
|
||||
# CHECK: bind --preset ctrl-b backward-char
|
||||
# CHECK: bind --preset ctrl-f forward-char
|
||||
|
||||
# User only
|
||||
bind --user | string match -v '*backward-delete-char'
|
||||
# CHECK: bind -M bind_mode \cx true
|
||||
bind --user | string match -v '*escape,\\[*'
|
||||
# CHECK: bind -M bind_mode ctrl-x true
|
||||
|
||||
# Adding bindings
|
||||
bind \t 'echo banana'
|
||||
bind | string match -v '*backward-delete-char'
|
||||
bind tab 'echo banana'
|
||||
bind | string match -v '*escape,\\[*'
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset \n execute
|
||||
# CHECK: bind --preset \r execute
|
||||
# CHECK: bind --preset \t complete
|
||||
# CHECK: bind --preset \cc cancel-commandline
|
||||
# CHECK: bind --preset \cd exit
|
||||
# CHECK: bind --preset \ce bind
|
||||
# CHECK: bind --preset \cs pager-toggle-search
|
||||
# CHECK: bind --preset \cu backward-kill-line
|
||||
# CHECK: bind --preset \e\[A up-line
|
||||
# CHECK: bind --preset \e\[B down-line
|
||||
# CHECK: bind --preset \e\[C forward-char
|
||||
# CHECK: bind --preset \e\[D backward-char
|
||||
# CHECK: bind --preset \cp up-line
|
||||
# CHECK: bind --preset \cn down-line
|
||||
# CHECK: bind --preset \cb backward-char
|
||||
# CHECK: bind --preset \cf forward-char
|
||||
# CHECK: bind -M bind_mode \cx true
|
||||
# CHECK: bind \t 'echo banana'
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
# CHECK: bind --preset ctrl-c cancel-commandline
|
||||
# CHECK: bind --preset ctrl-d exit
|
||||
# CHECK: bind --preset ctrl-e bind
|
||||
# CHECK: bind --preset ctrl-s pager-toggle-search
|
||||
# CHECK: bind --preset ctrl-u backward-kill-line
|
||||
# CHECK: bind --preset backspace backward-delete-char
|
||||
# CHECK: bind --preset up up-line
|
||||
# CHECK: bind --preset down down-line
|
||||
# CHECK: bind --preset right forward-char
|
||||
# CHECK: bind --preset left backward-char
|
||||
# CHECK: bind --preset ctrl-p up-line
|
||||
# CHECK: bind --preset ctrl-n down-line
|
||||
# CHECK: bind --preset ctrl-b backward-char
|
||||
# CHECK: bind --preset ctrl-f forward-char
|
||||
# CHECK: bind -M bind_mode ctrl-x true
|
||||
# CHECK: bind tab 'echo banana'
|
||||
|
||||
# Erasing bindings
|
||||
bind --erase \t
|
||||
bind \t
|
||||
bind \t 'echo wurst'
|
||||
# CHECK: bind --preset \t complete
|
||||
bind --erase --user --preset \t
|
||||
bind \t
|
||||
# CHECKERR: bind: No binding found for sequence '\t'
|
||||
bind --erase tab
|
||||
bind tab
|
||||
bind tab 'echo wurst'
|
||||
# CHECK: bind --preset tab complete
|
||||
bind --erase --user --preset tab
|
||||
bind tab
|
||||
# CHECKERR: bind: No binding found for key 'tab'
|
||||
|
||||
bind ctrl-\b
|
||||
# CHECKERR: bind: Cannot add control modifier to control character 'ctrl-h'
|
||||
|
||||
exit 0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
echo x-{1}
|
||||
#CHECK: x-{1}
|
||||
|
@ -22,7 +22,7 @@ echo foo-{""} # still expands to foo-{}
|
|||
#CHECK: foo-{}
|
||||
|
||||
echo foo-{$undefinedvar} # still expands to nothing
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
|
||||
echo foo-{,,,} # four empty items in the braces.
|
||||
#CHECK: foo- foo- foo- foo-
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -g fish %fish' %s
|
||||
#RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
begin
|
||||
set -l dir $PWD/(dirname (status -f))
|
||||
set -gx XDG_CONFIG_HOME $dir/broken-config/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# Tests for the "builtin" builtin/keyword.
|
||||
builtin -q string; and echo String exists
|
||||
#CHECK: String exists
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
echo (function foo1 --on-job-exit caller; end; functions --handlers-type caller-exit | grep foo)
|
||||
# CHECK: caller-exit foo1
|
||||
echo (function foo2 --on-job-exit caller; end; functions --handlers-type process-exit | grep foo)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
# Verify the '--on-job-exit caller' misfeature.
|
||||
function make_call_observer -a type
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish -C 'set -g fish %fish' %s
|
||||
# RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
set -g fish (realpath $fish)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
# Test ALL THE FISH FILES
|
||||
# in share/, that is - the tests are exempt because they contain syntax errors, on purpose
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
# Test all completions where the command exists
|
||||
|
||||
# No output is good output
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
#REQUIRES: msgfmt --help
|
||||
|
||||
set -l fail_count 0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
# This tests various corner cases involving command substitution.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
echo $(echo 1\n2)
|
||||
# CHECK: 1 2
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish -c "echo 1.2.3.4."
|
||||
#RUN: %fish -c "echo 1.2.3.4." | %filter-ctrlseqs
|
||||
# CHECK: 1.2.3.4.
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#RUN: %fish -c "echo 1.2.3.4." -c "echo 5.6.7.8."
|
||||
#RUN: %fish -c "echo 1.2.3.4." -c "echo 5.6.7.8." | %filter-ctrlseqs
|
||||
# CHECK: 1.2.3.4.
|
||||
# CHECK: 5.6.7.8.
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
# RUN: %fish -C 'set -g fish %fish' %s
|
||||
# RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
set -g PATH
|
||||
$fish -c "nonexistent-command-1234 banana rama"
|
||||
#CHECKERR: fish: Unknown command: nonexistent-command-1234
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: nonexistent-command-1234 banana rama
|
||||
#CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~^
|
||||
$fish -C 'function fish_command_not_found; echo cmd-not-found; end' -ic "nonexistent-command-1234 1 2 3 4"
|
||||
#CHECKERR: cmd-not-found
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: nonexistent-command-1234 1 2 3 4
|
||||
#CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~^
|
||||
$fish -C 'function fish_command_not_found; echo command-not-found $argv; end' -c "nonexistent-command-abcd foo bar baz"
|
||||
#CHECKERR: command-not-found nonexistent-command-abcd foo bar baz
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: nonexistent-command-abcd foo bar baz
|
||||
#CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~^
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish -c 'set foo bar' -c 'echo $foo'
|
||||
#RUN: %fish -c 'set foo bar' -c 'echo $foo' | %filter-ctrlseqs
|
||||
# CHECK: bar
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
commandline --input "echo foo | bar" --is-valid
|
||||
and echo Valid
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
function fooc; true; end;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
function complete_test_alpha1
|
||||
echo $argv
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish --interactive %s
|
||||
#RUN: %fish --interactive %s | %filter-ctrlseqs
|
||||
# ^ interactive so we can do `complete`
|
||||
mkdir -p __fish_complete_directories/
|
||||
cd __fish_complete_directories
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
function commandline
|
||||
if test $argv[1] = -ct
|
||||
echo --long4\n-4
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# Validate the behavior of the `count` command.
|
||||
|
||||
# no args
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
# Ensure we don't hang on deep command substitutions - see #6503.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -g fish %fish' %s
|
||||
#RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
if command -q getconf
|
||||
# (no env -u, some systems don't support that)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
begin
|
||||
end >.
|
||||
status -b; and echo "status -b returned true after bad redirect on a begin block"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: env fish_test_helper=%fish_test_helper %fish %s
|
||||
# RUN: env fish_test_helper=%fish_test_helper %fish %s | %filter-ctrlseqs
|
||||
|
||||
# Ensure that a job which attempts to disown itself does not explode.
|
||||
# Here fish_test_helper is the process group leader; we attempt to disown
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
# See issue 5692
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
function getenvs
|
||||
env | string match FISH_ENV_TEST_\*
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
# Regression test for issue #4443
|
||||
eval set -l previously_undefined foo
|
||||
echo $previously_undefined
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
exec cat <nosuchfile
|
||||
#CHECKERR: warning: An error occurred while redirecting file 'nosuchfile'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
argparse r-require= -- --require 2>/dev/null
|
||||
echo $status
|
||||
# CHECK: 2
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish -C 'set -g fish %fish' %s
|
||||
# RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
# caret position (#5812)
|
||||
printf '<%s>\n' ($fish -c ' $f[a]' 2>&1)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish -C "set helper %fish_test_helper" %s
|
||||
# RUN: %fish -C "set helper %fish_test_helper" %s | %filter-ctrlseqs
|
||||
|
||||
# Check that we don't leave stray FDs.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish --features=ampersand-nobg-in-token -C 'set -g fish_indent %fish_indent' %s
|
||||
#RUN: %fish --features=ampersand-nobg-in-token -C 'set -g fish_indent %fish_indent' %s | %filter-ctrlseqs
|
||||
|
||||
echo no&background
|
||||
# CHECK: no&background
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'no-stderr-nocaret' -c 'status test-feature stderr-nocaret; echo nocaret: $status'
|
||||
#RUN: %fish --features 'no-stderr-nocaret' -c 'status test-feature stderr-nocaret; echo nocaret: $status' | %filter-ctrlseqs
|
||||
# CHECK: nocaret: 0
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'stderr-nocaret' -c 'status test-feature stderr-nocaret; echo nocaret: $status'
|
||||
#RUN: %fish --features 'stderr-nocaret' -c 'status test-feature stderr-nocaret; echo nocaret: $status' | %filter-ctrlseqs
|
||||
# CHECK: nocaret: 0
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'no-stderr-nocaret' -c 'echo -n careton:; echo ^/dev/null'
|
||||
#RUN: %fish --features 'no-stderr-nocaret' -c 'echo -n careton:; echo ^/dev/null' | %filter-ctrlseqs
|
||||
# CHECK: careton:^/dev/null
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features ' stderr-nocaret' -c 'echo -n "caretoff: "; echo ^/dev/null'
|
||||
#RUN: %fish --features ' stderr-nocaret' -c 'echo -n "caretoff: "; echo ^/dev/null' | %filter-ctrlseqs
|
||||
# CHECK: caretoff: ^/dev/null
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish --features=remove-percent-self %s
|
||||
#RUN: %fish --features=remove-percent-self %s | %filter-ctrlseqs
|
||||
|
||||
echo %self
|
||||
# CHECK: %self
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'remove-percent-self' -c 'status test-feature remove-percent-self; echo remove-percent-self: $status'
|
||||
#RUN: %fish --features 'remove-percent-self' -c 'status test-feature remove-percent-self; echo remove-percent-self: $status' | %filter-ctrlseqs
|
||||
# CHECK: remove-percent-self: 0
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Explicitly overriding HOME/XDG_CONFIG_HOME is only required if not invoking via `make test`
|
||||
# RUN: %fish --features '' -c 'string match --quiet "??" ab ; echo "qmarkon: $status"'
|
||||
# RUN: %fish --features '' -c 'string match --quiet "??" ab ; echo "qmarkon: $status"' | %filter-ctrlseqs
|
||||
#CHECK: qmarkon: 1
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'qmark-noglob' -C 'string match --quiet "??" ab ; echo "qmarkoff: $status"'
|
||||
#RUN: %fish --features 'qmark-noglob' -C 'string match --quiet "??" ab ; echo "qmarkoff: $status"' | %filter-ctrlseqs
|
||||
# CHECK: qmarkoff: 1
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'no-regex-easyesc' -C 'string replace -ra "\\\\" "\\\\\\\\" -- "a\b\c"'
|
||||
#RUN: %fish --features 'no-regex-easyesc' -C 'string replace -ra "\\\\" "\\\\\\\\" -- "a\b\c"' | %filter-ctrlseqs
|
||||
# CHECK: a\b\c
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish --features 'regex-easyesc' -C 'string replace -ra "\\\\" "\\\\\\\\" -- "a\b\c"'
|
||||
#RUN: %fish --features 'regex-easyesc' -C 'string replace -ra "\\\\" "\\\\\\\\" -- "a\b\c"' | %filter-ctrlseqs
|
||||
# CHECK: a\\b\\c
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
#
|
||||
# This deals with $PATH manipulation. We need to be careful not to step on anything.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -g fish %fish' %s
|
||||
#RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
# fish_exit fires successfully.
|
||||
echo 'function do_exit --on-event fish_exit; echo "fish_exiting $fish_pid"; end' > /tmp/test_exit.fish
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
#
|
||||
# This deals with $PATH manipulation. We need to be careful not to step on anything.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
# A for-loop-variable is a local variable in the enclosing scope.
|
||||
set -g i global
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
function stuff --argument a b c
|
||||
# This is a comment
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
function t --argument-names a b c
|
||||
echo t
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# Test the `functions` builtin
|
||||
|
||||
function f1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -i %s
|
||||
#RUN: %fish -i %s | %filter-ctrlseqs
|
||||
# Note: ^ this is interactive so we test interactive behavior,
|
||||
# e.g. the fish_git_prompt variable handlers test `status is-interactive`.
|
||||
#REQUIRES: command -v git
|
||||
|
@ -198,4 +198,3 @@ end
|
|||
|
||||
$fish -c 'complete -C "git -C ./.gi"'
|
||||
# CHECK: ./.git/ Directory
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish %s
|
||||
# RUN: %fish %s | %filter-ctrlseqs
|
||||
|
||||
set -l oldpwd $PWD
|
||||
cd (mktemp -d)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish %s
|
||||
#RUN: %fish %s | %filter-ctrlseqs
|
||||
# Verify that specifying unexpected options or arguments results in an error.
|
||||
|
||||
# First using the legacy, now deprecated, long options to specify a
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# RUN: %fish -C 'set -g fish_indent %fish_indent' %s
|
||||
# RUN: %fish -C 'set -g fish_indent %fish_indent' %s | %filter-ctrlseqs
|
||||
# Test file for fish_indent
|
||||
# Note that littlecheck ignores leading whitespace, so we have to use {{ }} to explicitly match it.
|
||||
|
||||
|
@ -73,7 +73,7 @@ end | cat | cat | begin ; echo hi ; end | begin ; begin ; echo hi ; end ; end ar
|
|||
|
||||
#CHECK: begin
|
||||
#CHECK: {{ }}echo hi
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: end | cat | cat | begin
|
||||
#CHECK: {{ }}echo hi
|
||||
#CHECK: end | begin
|
||||
|
@ -99,7 +99,7 @@ end
|
|||
#CHECK: {{ }}{{ }}echo sup
|
||||
#CHECK: {{ }}case beta gamma
|
||||
#CHECK: {{ }}{{ }}echo hi
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: end
|
||||
|
||||
echo -n '
|
||||
|
@ -117,15 +117,15 @@ function hello_world
|
|||
' | $fish_indent
|
||||
|
||||
#CHECK: function hello_world
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: {{ }}begin
|
||||
#CHECK: {{ }}{{ }}echo hi
|
||||
#CHECK: {{ }}end | cat
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: {{ }}echo sup
|
||||
#CHECK: {{ }}echo sup
|
||||
#CHECK: {{ }}echo hello
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: {{ }}echo hello
|
||||
#CHECK: end
|
||||
|
||||
|
@ -149,13 +149,13 @@ qqq
|
|||
end' | $fish_indent
|
||||
#CHECK: echo alpha #comment1
|
||||
#CHECK: #comment2
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: #comment3
|
||||
#CHECK: for i in abc #comment1
|
||||
#CHECK: {{ }}#comment2
|
||||
#CHECK: {{ }}echo hi
|
||||
#CHECK: end
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: switch foo #abc
|
||||
#CHECK: {{ }}# bar
|
||||
#CHECK: {{ }}case bar
|
||||
|
@ -299,26 +299,26 @@ echo bye
|
|||
#CHECK: {{ }}echo yes
|
||||
#CHECK: en\
|
||||
#CHECK: d
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: while true
|
||||
#CHECK: {{ }}builtin yes
|
||||
#CHECK: end
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: alpha | beta
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: gamma | \
|
||||
#CHECK: # comment3
|
||||
#CHECK: delta
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: if true
|
||||
#CHECK: {{ }}echo abc
|
||||
#CHECK: end
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: if false # comment4
|
||||
#CHECK: {{ }}and true && false
|
||||
#CHECK: {{ }}echo abc
|
||||
#CHECK: end
|
||||
#CHECK:
|
||||
#CHECK:
|
||||
#CHECK: echo hi |
|
||||
#CHECK: {{ }}echo bye
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#RUN: %fish -C 'echo init-command' -C 'echo 2nd init-command'
|
||||
#RUN: %fish -C 'echo init-command' -C 'echo 2nd init-command' | %filter-ctrlseqs
|
||||
# CHECK: init-command
|
||||
# CHECK: 2nd init-command
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#RUN: %fish -c 'echo command' -C 'echo init-command'
|
||||
#RUN: %fish -c 'echo command' -C 'echo init-command' | %filter-ctrlseqs
|
||||
# CHECK: init-command
|
||||
# CHECK: command
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#RUN: %fish -C 'echo init-command' -c 'echo command'
|
||||
#RUN: %fish -C 'echo init-command' -c 'echo command' | %filter-ctrlseqs
|
||||
# CHECK: init-command
|
||||
# CHECK: command
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#RUN: %fish -C 'echo init-command'
|
||||
#RUN: %fish -C 'echo init-command' | %filter-ctrlseqs
|
||||
# CHECK: init-command
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -g fish %fish' %s
|
||||
#RUN: %fish -C 'set -g fish %fish' %s | %filter-ctrlseqs
|
||||
# Test that fish doesn't crash if cwd is unreadable at the start (#6597)
|
||||
|
||||
set -l oldpwd $PWD
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#RUN: %fish -C 'set -l fish %fish' %s
|
||||
#RUN: %fish -C 'set -l fish %fish' %s | %filter-ctrlseqs
|
||||
|
||||
$fish -c "echo 1.2.3.4."
|
||||
# CHECK: 1.2.3.4.
|
||||
|
@ -107,4 +107,3 @@ $fish --no-config -c 'echo notprinted | and true'
|
|||
|
||||
# Regression test for a hang.
|
||||
echo "set -L" | $fish > /dev/null
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue