mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-04-19 14:51:13 -03:00
Compare commits
3 Commits
Integratio
...
curly-unde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba2ad33a81 | ||
|
|
3453565a41 | ||
|
|
cb2b1c6621 |
@@ -69,7 +69,12 @@ Improved terminal support
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Support for curly underlines in `fish_color_*` variables and :doc:`set_color <cmds/set_color>` (:issue:`10957`).
|
||||
- Underlines can now be colored independent of text (:issue:`7619`).
|
||||
- Errors are now highlighted with curly underlines in the default themes.
|
||||
For compatibility with terminals that interpret the curly-underline escape sequence in an unexpected way,
|
||||
the default themes enable this only if the terminal advertises support for the ``Su`` capability via XTGETTCAP.
|
||||
- New documentation page `Terminal Compatibility <terminal-compatibility.html>`_ (also accessible via ``man fish-terminal-compatibility``) lists required and optional terminal control sequences used by fish.
|
||||
- :doc:`status <cmds/status>` learned the ``xtgettcap`` subcommand, to query terminfo capabilities via XTGETTCAP commands.
|
||||
- :doc:`status <cmds/status>` learned the ``xtversion`` subcommand, to show the terminal's name and version (as reported by via the XTVERSION command).
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
|
||||
@@ -10,7 +10,7 @@ Synopsis
|
||||
|
||||
fish_config [browse]
|
||||
fish_config prompt (choose | list | save | show)
|
||||
fish_config theme (choose | demo | dump | list | save | show)
|
||||
fish_config theme (choose | demo | dump | list | save | show | update)
|
||||
|
||||
Description
|
||||
-----------
|
||||
@@ -40,6 +40,9 @@ Available subcommands for the ``theme`` command:
|
||||
- ``list`` lists the names of the available sample themes.
|
||||
- ``save`` saves the given theme to :ref:`universal variables <variables-universal>`.
|
||||
- ``show`` shows what the given sample theme (or all) would look like.
|
||||
- ``update`` updates any ``fish_color_*`` and ``fish_pager_color_*`` variables whose value contains
|
||||
"--track=THEME". They are set to the latest version of that theme, and the tracking
|
||||
option is preserved. Note that ``fish_config theme update`` is run at fish startup.
|
||||
|
||||
The **-h** or **--help** option displays help about using this command.
|
||||
|
||||
|
||||
@@ -59,6 +59,12 @@ The following options are available:
|
||||
**-u** or **--underline**, or **-uSTYLE** or **--underline=STYLE**
|
||||
Set the underline mode; supported styles are **single** (default) and **curly**.
|
||||
|
||||
**--track=THEME**
|
||||
Ignored. Included by default in universally-scoped color variables to tell fish to update
|
||||
them as the associated theme changes.
|
||||
This flag can be set for all variables when loading a theme with the `--track`` option, that is
|
||||
:doc:`fish_config theme save THEME --track <fish_config>`.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ Synopsis
|
||||
status buildinfo
|
||||
status get-file FILE
|
||||
status list-files [PATH]
|
||||
status xtgettcap TERMINFO-CAPABILITY
|
||||
status xtversion
|
||||
|
||||
Description
|
||||
-----------
|
||||
@@ -117,6 +119,13 @@ The following operations (subcommands) are available:
|
||||
This lists the files embedded in the fish binary at compile time. Only files where the path starts with the optional *FILE* argument are shown.
|
||||
Returns 0 if something was printed, 1 otherwise.
|
||||
|
||||
**xtgettcap** *TERMINFO-CAPABILITY*
|
||||
Query the terminal for a terminfo capability via XTGETTCAP.
|
||||
Returns 0 if the capability is present and 1 otherwise.
|
||||
|
||||
**xtversion**
|
||||
Show the terminal name and version (XTVERSION).
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
|
||||
@@ -177,7 +177,9 @@ Optional Commands
|
||||
- Su
|
||||
- Reset underline color to the default (follow the foreground color).
|
||||
- kitty
|
||||
* - ``\e[ Ps S``
|
||||
* - .. _indn:
|
||||
|
||||
``\e[ Ps S``
|
||||
- indn
|
||||
- Scroll forward Ps lines.
|
||||
-
|
||||
@@ -269,6 +271,12 @@ Optional Commands
|
||||
- FinalTerm
|
||||
* - ``\eP+q Pt \e\\``
|
||||
-
|
||||
- Request terminfo capability (XTGETTCAP). The parameter is the capability's hex-encoded terminfo code.
|
||||
Specifically, fish asks for the ``indn`` string capability. At the time of writing string capabilities are supported by kitty and foot.
|
||||
- XTerm, kitty, foot
|
||||
- Request terminfo capability (XTGETTCAP).
|
||||
The parameter is the capability's hex-encoded terminfo code.
|
||||
To advertise a capability, the response must of the form
|
||||
``\eP1+q Pt \e\\`` or ``\eP1+q Pt = Pt \e\\``.
|
||||
In either variant the first parameter must be the hex-encoded terminfo code.
|
||||
In the second variant, fish ignores the part after the equals sign.
|
||||
|
||||
At startup, fish checks for the presence of the `indn <#indn>`_ string capability.
|
||||
- XTerm, kitty, foot, wezterm, contour
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
complete fish_config -f
|
||||
set -l prompt_commands choose save show list
|
||||
set -l theme_commands choose demo dump save show list
|
||||
set -l theme_commands choose demo dump save show list update
|
||||
complete fish_config -n __fish_use_subcommand -a prompt -d 'View and pick from the sample prompts'
|
||||
complete fish_config -n "__fish_seen_subcommand_from prompt; and not __fish_seen_subcommand_from $prompt_commands" \
|
||||
-a choose -d 'View and pick from the sample prompts'
|
||||
@@ -16,6 +16,7 @@ complete fish_config -n __fish_use_subcommand -a browse -d 'Open the web-based U
|
||||
|
||||
complete fish_config -n __fish_use_subcommand -a theme -d 'View and pick from the sample themes'
|
||||
complete fish_config -n '__fish_seen_subcommand_from theme; and __fish_seen_subcommand_from choose save show' -a '(fish_config theme list)'
|
||||
complete fish_config -n '__fish_seen_subcommand_from theme; and __fish_seen_subcommand_from save' -l track -d 'Add --track to color variables to apply future theme updates'
|
||||
complete fish_config -n "__fish_seen_subcommand_from theme; and not __fish_seen_subcommand_from $theme_commands" \
|
||||
-a choose -d 'View and pick from the sample themes'
|
||||
complete fish_config -n "__fish_seen_subcommand_from theme; and not __fish_seen_subcommand_from $theme_commands" \
|
||||
@@ -28,3 +29,5 @@ complete fish_config -n "__fish_seen_subcommand_from theme; and not __fish_seen_
|
||||
-a demo -d 'Show example in the current theme'
|
||||
complete fish_config -n "__fish_seen_subcommand_from theme; and not __fish_seen_subcommand_from $theme_commands" \
|
||||
-a dump -d 'Print the current theme in .theme format'
|
||||
complete fish_config -n "__fish_seen_subcommand_from theme; and not __fish_seen_subcommand_from $theme_commands" \
|
||||
-a update -d "Update universal colors that have the tracking flag set"
|
||||
|
||||
@@ -8,3 +8,4 @@ complete -c set_color -s r -l reverse -d 'Reverse color text'
|
||||
complete -c set_color -s u -l underline -d 'Underline style' -a 'single curly'
|
||||
complete -c set_color -s h -l help -d 'Display help and exit'
|
||||
complete -c set_color -s c -l print-colors -d 'Print a list of all accepted color names'
|
||||
complete -c set_color -l track -xa '(fish_config theme list)' -d 'Ignored. Used in color variables to follow theme changes'
|
||||
|
||||
@@ -27,7 +27,9 @@ set -l __fish_status_all_commands \
|
||||
list-files \
|
||||
print-stack-trace \
|
||||
stack-trace \
|
||||
test-feature
|
||||
test-feature \
|
||||
xtgettcap \
|
||||
xtversion
|
||||
|
||||
# These are the recognized flags.
|
||||
complete -c status -s h -l help -d "Display help and exit"
|
||||
@@ -64,6 +66,8 @@ complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_com
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a get-file -d "Print an embedded file from the fish binary"
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a list-files -d "List embedded files contained in the fish binary"
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a fish-path -d "Print the path to the current instance of fish"
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a xtgettcap -d "Query the terminal for a terminfo capability"
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a xtversion -d "Show terminal name and version"
|
||||
|
||||
# The job-control command changes fish state.
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a job-control -d "Set which jobs are under job control"
|
||||
|
||||
@@ -30,12 +30,13 @@ if status is-interactive
|
||||
# Commands to run in interactive sessions can go here
|
||||
end" >$__fish_config_dir/config.fish
|
||||
|
||||
echo yes | fish_config theme save "fish default"
|
||||
fish_config theme save "fish default" --yes --track
|
||||
set -Ue fish_color_keyword fish_color_option
|
||||
end
|
||||
if test $__fish_initialized -lt 3800 && test "$fish_color_search_match[1]" = bryellow
|
||||
set --universal fish_color_search_match[1] white
|
||||
end
|
||||
fish_config theme update
|
||||
|
||||
#
|
||||
# Generate man page completions if not present.
|
||||
|
||||
3
share/functions/__fish_in_gnu_screen.fish
Normal file
3
share/functions/__fish_in_gnu_screen.fish
Normal file
@@ -0,0 +1,3 @@
|
||||
function __fish_in_gnu_screen
|
||||
test -n "$STY" || contains -- $TERM screen screen-256color
|
||||
end
|
||||
9
share/functions/__fish_in_terminal_multiplexer.fish
Normal file
9
share/functions/__fish_in_terminal_multiplexer.fish
Normal file
@@ -0,0 +1,9 @@
|
||||
function __fish_in_terminal_multiplexer
|
||||
set -l terminal_name "(status xtversion | string match -r '^\S*')"
|
||||
string match -q -- tmux $terminal_name ||
|
||||
__fish_in_gnu_screen ||
|
||||
# Emacs does probably not support multiplexing between multiple parent
|
||||
# terminals, but it is affected by the same issues. Same for Vim's
|
||||
# :terminal TODO detect that before they implement XTGETTCAP.
|
||||
contains -- $TERM dvtm-256color eterm eterm-color
|
||||
end
|
||||
@@ -77,7 +77,11 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
|
||||
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 scrollback-push clear-screen
|
||||
bind --preset $argv ctrl-l (
|
||||
if not __fish_in_gnu_screen && and __fish_xtgettcap indn
|
||||
echo scrollback-push
|
||||
end
|
||||
) clear-screen
|
||||
bind --preset $argv ctrl-c clear-commandline
|
||||
bind --preset $argv ctrl-u backward-kill-line
|
||||
bind --preset $argv ctrl-k kill-line
|
||||
|
||||
12
share/functions/__fish_xtgettcap.fish
Normal file
12
share/functions/__fish_xtgettcap.fish
Normal file
@@ -0,0 +1,12 @@
|
||||
function __fish_xtgettcap --argument-names terminfo_code
|
||||
if test $FISH_UNIT_TESTS_RUNNING = 1
|
||||
return 1
|
||||
end
|
||||
set -l varname __fish_tcap_$terminfo_code
|
||||
if not set -q $varname\[0\]
|
||||
set --global $varname (
|
||||
status xtgettcap $terminfo_code && echo true || echo false
|
||||
)
|
||||
end
|
||||
test "$$varname" = true
|
||||
end
|
||||
@@ -1,5 +1,11 @@
|
||||
function fish_config --description "Launch fish's web based configuration"
|
||||
argparse h/help -- $argv
|
||||
# Variables a theme is allowed to set
|
||||
set -l theme_var_filter '^fish_(?:pager_)?color.*$'
|
||||
|
||||
function fish_config --description "Launch fish's web based configuration" \
|
||||
--inherit-variable theme_var_filter
|
||||
|
||||
set -l _flag_track
|
||||
argparse h/help track yes -- $argv
|
||||
or return
|
||||
|
||||
if set -q _flag_help
|
||||
@@ -10,6 +16,15 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
set -l cmd $argv[1]
|
||||
set -e argv[1]
|
||||
|
||||
if set -q _flag_track[1] && not { test "$cmd" = theme && test "$argv[1]" = save }
|
||||
echo >&2 fish_config: --track: unknown option
|
||||
return 1
|
||||
end
|
||||
if set -q _flag_yes[1] && not { contains "$cmd" prompt theme && test "$argv[1]" = save }
|
||||
echo >&2 fish_config: --yes: unknown option
|
||||
return 1
|
||||
end
|
||||
|
||||
set -q cmd[1]
|
||||
or set cmd browse
|
||||
|
||||
@@ -65,9 +80,6 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
return 1
|
||||
end
|
||||
|
||||
# Variables a theme is allowed to set
|
||||
set -l theme_var_filter '^fish_(?:pager_)?color.*$'
|
||||
|
||||
switch $cmd
|
||||
case prompt
|
||||
# prompt - for prompt switching
|
||||
@@ -144,8 +156,8 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
functions --erase fish_right_prompt
|
||||
end
|
||||
case save
|
||||
read -P"Overwrite prompt? [y/N]" -l yesno
|
||||
if string match -riq 'y(es)?' -- $yesno
|
||||
if set -q _flag_yes[1] ||
|
||||
{ read -P"Overwrite prompt? [y/N]" -l yesno; string match -riq 'y(es)?' -- $yesno }
|
||||
echo Overwriting
|
||||
# Skip the cp if unnecessary,
|
||||
# or we'd throw an error on a stock fish.
|
||||
@@ -274,8 +286,10 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
set -l have_colors
|
||||
|
||||
if contains -- $cmd save
|
||||
read -P"Overwrite your current theme? [y/N] " -l yesno
|
||||
if not string match -riq 'y(es)?' -- $yesno
|
||||
if not set -q _flag_yes &&
|
||||
{ read -P"Overwrite your current theme? [y/N] " -l yesno
|
||||
not string match -riq 'y(es)?' -- $yesno
|
||||
}
|
||||
echo Not overwriting >&2
|
||||
return 1
|
||||
end
|
||||
@@ -292,34 +306,7 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
# If we are choosing a theme or saving from a named theme, load the theme now.
|
||||
# Otherwise, we'll persist the currently loaded/themed variables (in case of `theme save`).
|
||||
if set -q argv[1]
|
||||
set -l files $dirs/$argv[1].theme
|
||||
set -l file
|
||||
|
||||
for f in $files
|
||||
if test -e "$f"
|
||||
set file $f
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not set -q file[1]
|
||||
if status list-files tools/web_config/themes/$argv[1].theme &>/dev/null
|
||||
set file tools/web_config/themes/$argv[1].theme
|
||||
else
|
||||
echo "No such theme: $argv[1]" >&2
|
||||
echo "Searched directories: $dirs" >&2
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
set -l content
|
||||
if string match -qr '^tools/' -- $file
|
||||
set content (status get-file $file)
|
||||
else
|
||||
read -z content < $file
|
||||
end
|
||||
|
||||
printf %s\n $content | while read -lat toks
|
||||
__fish_config_theme_get $argv[1] | while read -lat toks
|
||||
# The whitelist allows only color variables.
|
||||
# Not the specific list, but something named *like* a color variable.
|
||||
# This also takes care of empty lines and comment lines.
|
||||
@@ -331,7 +318,7 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
if test x"$scope" = x-U; and set -qg $toks[1]
|
||||
set -eg $toks[1]
|
||||
end
|
||||
set $scope $toks
|
||||
set $scope $toks $_flag_track=$argv[1]
|
||||
set -a have_colors $toks[1]
|
||||
end
|
||||
|
||||
@@ -343,7 +330,7 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
# Erase conflicting global variables so we don't get a warning and
|
||||
# so changes are observed immediately.
|
||||
set -eg $c
|
||||
set $scope $c
|
||||
set $scope $c $_flag_track=$argv[1]
|
||||
end
|
||||
else
|
||||
# We're persisting whatever current colors are loaded (maybe in the global scope)
|
||||
@@ -365,6 +352,8 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
# If we've made it this far, we've either found a theme file or persisted the current
|
||||
# state (if any). In all cases we haven't failed, so return 0.
|
||||
return 0
|
||||
case update
|
||||
__fish_config_theme_update $argv
|
||||
case dump
|
||||
# Write the current theme in .theme format, to stdout.
|
||||
set -L | string match -r $theme_var_filter
|
||||
@@ -374,3 +363,100 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_config_theme_get
|
||||
set -l dirs $__fish_config_dir/themes $__fish_data_dir/tools/web_config/themes
|
||||
set -l files $dirs/$argv[1].theme
|
||||
set -l file
|
||||
set -l is_default_theme false
|
||||
|
||||
for f in $files
|
||||
if test -e "$f"
|
||||
set file $f
|
||||
if test $f = $__fish_data_dir/tools/web_config/themes/$argv[1].theme
|
||||
set is_default_theme true
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not set -q file[1]
|
||||
if status list-files tools/web_config/themes/$argv[1].theme &>/dev/null
|
||||
set file tools/web_config/themes/$argv[1].theme
|
||||
set is_default_theme true
|
||||
else
|
||||
echo "No such theme: $argv[1]" >&2
|
||||
echo "Searched directories: $dirs" >&2
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
set -l content (
|
||||
if string match -qr '^tools/' -- $file
|
||||
status get-file $file
|
||||
else
|
||||
string join \n <$file
|
||||
end
|
||||
)
|
||||
if $is_default_theme && not __fish_in_gnu_screen && not __fish_xtgettcap Su && not __fish_in_terminal_multiplexer
|
||||
set content (string replace -- --underline=curly "" $content)
|
||||
end
|
||||
|
||||
string join \n $content
|
||||
end
|
||||
|
||||
function __fish_config_show_tracked_color_vars
|
||||
set -l color_var $argv[1]
|
||||
set -l _flag_track
|
||||
argparse --ignore-unknown track= -- _set_color $argv[2..]
|
||||
or return $status
|
||||
if not set -q _flag_track[1]
|
||||
return
|
||||
end
|
||||
if set -q _flag_track[2]
|
||||
echo >&2 "fish_config: $color_var: --track option can only be specified once"
|
||||
exit 1
|
||||
end
|
||||
if test (printf %s $_flag_track | count) -ne 0
|
||||
echo >&2 "fish_config: $color_var: error: tracking theme name must not contain newlines"
|
||||
exit 1
|
||||
end
|
||||
printf %s\n $color_var $_flag_track
|
||||
end
|
||||
|
||||
function __fish_config_theme_update --inherit-variable theme_var_filter
|
||||
if set -q argv[1]
|
||||
echo "fish_config: too many arguments" >&2
|
||||
return 1
|
||||
end
|
||||
set -l themes
|
||||
|
||||
set -l tracking_variables (
|
||||
set --universal --long |
|
||||
string match -r '^fish_(?:pager_)?color.*$' |
|
||||
string replace -r '.*' '__fish_config_show_tracked_color_vars $0' |
|
||||
source
|
||||
)
|
||||
or return $status
|
||||
string join \n $tracking_variables |
|
||||
while read --line _colorvar theme
|
||||
if not contains -- $theme $themes
|
||||
set -a themes $theme
|
||||
end
|
||||
end
|
||||
|
||||
for theme in $themes
|
||||
set -l colorvars
|
||||
string join \n $tracking_variables |
|
||||
while read --line color_var t
|
||||
if test $t = $theme
|
||||
set -a colorvars $color_var
|
||||
end
|
||||
end
|
||||
set -l theme_escaped (string escape -- $theme)
|
||||
__fish_config_theme_get $theme |
|
||||
string match -r -- "^(?:$(string join '|' $colorvars))\b .*" |
|
||||
string replace -r '.*' "set -U \$0 --track=$theme_escaped" |
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment f7ca88
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end ba8baf
|
||||
fish_color_error ab4642
|
||||
fish_color_error ab4642 --underline=curly
|
||||
fish_color_escape 86c1b9
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment f7ca88
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end ba8baf
|
||||
fish_color_error ab4642
|
||||
fish_color_error ab4642 --underline=curly
|
||||
fish_color_escape 86c1b9
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment ffcc66
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end cc99cc
|
||||
fish_color_error f2777a
|
||||
fish_color_error f2777a --underline=curly
|
||||
fish_color_escape 66cccc
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment FF9640
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end FFB273
|
||||
fish_color_error FF7400
|
||||
fish_color_error FF7400 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -21,7 +21,7 @@ fish_color_comment 6272a4
|
||||
fish_color_cwd 50fa7b
|
||||
fish_color_cwd_root red
|
||||
fish_color_end ffb86c
|
||||
fish_color_error ff5555
|
||||
fish_color_error ff5555 --underline=curly
|
||||
fish_color_escape ff79c6
|
||||
fish_color_history_current --bold
|
||||
fish_color_host bd93f9
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment FFE100
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end 8D003B
|
||||
fish_color_error EC3B86
|
||||
fish_color_error EC3B86 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment B0B0B0
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end 969696
|
||||
fish_color_error FFA779
|
||||
fish_color_error FFA779 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -6,7 +6,7 @@ fish_color_command FF9400
|
||||
fish_color_quote BF9C30
|
||||
fish_color_redirection BF5B30
|
||||
fish_color_end FF4C00
|
||||
fish_color_error FFDD73
|
||||
fish_color_error FFDD73 --underline=curly
|
||||
fish_color_param FFC000
|
||||
fish_color_comment A63100
|
||||
fish_color_selection white --background=brblack --bold
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment 4e4e4e
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end 767676
|
||||
fish_color_error b2b2b2
|
||||
fish_color_error b2b2b2 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -6,7 +6,7 @@ fish_color_command ffffff
|
||||
fish_color_quote a8a8a8
|
||||
fish_color_redirection 808080
|
||||
fish_color_end 949494
|
||||
fish_color_error 585858
|
||||
fish_color_error 585858 --underline=curly
|
||||
fish_color_param d7d7d7
|
||||
fish_color_comment bcbcbc
|
||||
fish_color_selection white --background=brblack --bold
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment
|
||||
fish_color_cwd normal
|
||||
fish_color_cwd_root normal
|
||||
fish_color_end
|
||||
fish_color_error
|
||||
fish_color_error --underline=curly
|
||||
fish_color_escape
|
||||
fish_color_history_current
|
||||
fish_color_host normal
|
||||
|
||||
@@ -11,7 +11,7 @@ fish_color_comment 4c566a --italics
|
||||
fish_color_cwd 5e81ac
|
||||
fish_color_cwd_root bf616a
|
||||
fish_color_end 81a1c1
|
||||
fish_color_error bf616a
|
||||
fish_color_error bf616a --underline=curly
|
||||
fish_color_escape ebcb8b
|
||||
fish_color_history_current e5e9f0 --bold
|
||||
fish_color_host a3be8c
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment 30BE30
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end FF7B7B
|
||||
fish_color_error A40000
|
||||
fish_color_error A40000 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment 5C9900
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end 8EEB00
|
||||
fish_color_error 60B9CE
|
||||
fish_color_error 60B9CE --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -6,7 +6,7 @@ fish_color_command 164CC9
|
||||
fish_color_quote 4C3499
|
||||
fish_color_redirection 248E8E
|
||||
fish_color_end 02BDBD
|
||||
fish_color_error 9177E5
|
||||
fish_color_error 9177E5 --underline=curly
|
||||
fish_color_param 4319CC
|
||||
fish_color_comment 007B7B
|
||||
fish_color_selection white --background=brblack --bold
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment 586e75
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end 268bd2
|
||||
fish_color_error dc322f
|
||||
fish_color_error dc322f --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -7,7 +7,7 @@ fish_color_command 586e75
|
||||
fish_color_quote 839496
|
||||
fish_color_redirection 6c71c4
|
||||
fish_color_end 268bd2
|
||||
fish_color_error dc322f
|
||||
fish_color_error dc322f --underline=curly
|
||||
fish_color_param 657b83
|
||||
fish_color_comment 93a1a1
|
||||
fish_color_selection white --background=brblack --bold
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment e7c547
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end c397d8
|
||||
fish_color_error d54e53
|
||||
fish_color_error d54e53 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -7,7 +7,7 @@ fish_color_command b294bb
|
||||
fish_color_quote b5bd68
|
||||
fish_color_redirection 8abeb7
|
||||
fish_color_end b294bb
|
||||
fish_color_error cc6666
|
||||
fish_color_error cc6666 --underline=curly
|
||||
fish_color_param 81a2be
|
||||
fish_color_comment f0c674
|
||||
fish_color_selection white --background=brblack --bold
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment eab700
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end 8959a8
|
||||
fish_color_error c82829
|
||||
fish_color_error c82829 --underline=curly
|
||||
fish_color_escape 00a6b2
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -7,7 +7,7 @@ fish_color_command 39BAE6
|
||||
fish_color_quote C2D94C
|
||||
fish_color_redirection FFEE99
|
||||
fish_color_end F29668
|
||||
fish_color_error FF3333
|
||||
fish_color_error FF3333 --underline=curly
|
||||
fish_color_param B3B1AD
|
||||
fish_color_comment 626A73
|
||||
fish_color_selection --background=E6B450 --bold
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment ABB0B6
|
||||
fish_color_cwd 399EE6
|
||||
fish_color_cwd_root red
|
||||
fish_color_end ED9366
|
||||
fish_color_error F51818
|
||||
fish_color_error F51818 --underline=curly
|
||||
fish_color_escape 4CBF99
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -10,7 +10,7 @@ fish_color_comment 5C6773
|
||||
fish_color_cwd 73D0FF
|
||||
fish_color_cwd_root red
|
||||
fish_color_end F29E74
|
||||
fish_color_error FF3333
|
||||
fish_color_error FF3333 --underline=curly
|
||||
fish_color_escape 95E6CB
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -9,7 +9,7 @@ fish_color_comment '888' '--italics'
|
||||
fish_color_cwd 0A0
|
||||
fish_color_cwd_root A00
|
||||
fish_color_end 009900
|
||||
fish_color_error F22
|
||||
fish_color_error F22 --underline=curly
|
||||
fish_color_escape 0AA
|
||||
fish_color_history_current 0AA
|
||||
fish_color_host normal
|
||||
|
||||
@@ -11,7 +11,7 @@ fish_color_comment red
|
||||
fish_color_cwd green
|
||||
fish_color_cwd_root red
|
||||
fish_color_end green
|
||||
fish_color_error brred
|
||||
fish_color_error brred --underline=curly
|
||||
fish_color_escape brcyan
|
||||
fish_color_history_current --bold
|
||||
fish_color_host normal
|
||||
|
||||
@@ -153,7 +153,7 @@ fn setup_and_process_keys(
|
||||
unsafe { libc::tcsetattr(0, TCSANOW, &*shell_modes()) };
|
||||
terminal_protocol_hacks();
|
||||
let blocking_query: OnceCell<RefCell<Option<TerminalQuery>>> = OnceCell::new();
|
||||
initial_query(&blocking_query, streams.out, None);
|
||||
initial_query(&blocking_query, streams.out);
|
||||
|
||||
if continuous_mode {
|
||||
streams.err.append(L!("\n"));
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
use crate::wutil::encoding::zero_mbstate;
|
||||
use crate::wutil::perror;
|
||||
use libc::SEEK_CUR;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -219,12 +220,14 @@ fn read_interactive(
|
||||
conf.complete_ok = shell;
|
||||
conf.highlight_ok = shell;
|
||||
conf.syntax_check_ok = shell;
|
||||
conf.prompt_ok = true;
|
||||
|
||||
// No autosuggestions or abbreviations in builtin_read.
|
||||
conf.autosuggest_ok = false;
|
||||
conf.expand_abbrev_ok = false;
|
||||
|
||||
conf.exit_on_interrupt = true;
|
||||
conf.in_builtin_read = true;
|
||||
conf.in_silent_mode = silent;
|
||||
|
||||
conf.left_prompt_cmd = prompt.to_owned();
|
||||
@@ -244,7 +247,7 @@ fn read_interactive(
|
||||
|
||||
let mline = {
|
||||
let _interactive = parser.push_scope(|s| s.is_interactive = true);
|
||||
reader_readline(parser, nchars)
|
||||
reader_readline(parser, NonZeroUsize::try_from(nchars).ok())
|
||||
};
|
||||
terminal_protocols_disable_ifn();
|
||||
if let Some(line) = mline {
|
||||
|
||||
@@ -86,6 +86,13 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
// In future we change both to actually print an error.
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(MultipleTracking) => {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: --track option can only be specified once\n",
|
||||
argv[0]
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(UnknownColor(arg)) => {
|
||||
streams
|
||||
.err
|
||||
@@ -124,7 +131,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
specified_face.fg.unwrap_or(Color::None),
|
||||
specified_face.bg.unwrap_or(Color::None),
|
||||
specified_face.underline_color.unwrap_or(Color::None),
|
||||
specified_face.style,
|
||||
specified_face.style.unwrap_or_default(),
|
||||
));
|
||||
|
||||
if specified_face.fg.is_some() && outp.contents().is_empty() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::{get_executable_path, str2wcstring, PROGRAM_NAME};
|
||||
use crate::future_feature_flags::{self as features, feature_test};
|
||||
@@ -11,6 +9,9 @@
|
||||
use libc::F_OK;
|
||||
use nix::errno::Errno;
|
||||
use nix::NixPath;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
mod query;
|
||||
|
||||
macro_rules! str_enum {
|
||||
($name:ident, $(($val:ident, $str:expr)),* $(,)?) => {
|
||||
@@ -37,7 +38,7 @@ fn to_wstr(self) -> &'static wstr {
|
||||
|
||||
use StatusCmd::*;
|
||||
#[derive(Clone, Copy)]
|
||||
enum StatusCmd {
|
||||
pub(in crate::builtins::status) enum StatusCmd {
|
||||
STATUS_CURRENT_CMD = 1,
|
||||
STATUS_BASENAME,
|
||||
STATUS_DIRNAME,
|
||||
@@ -62,6 +63,8 @@ enum StatusCmd {
|
||||
STATUS_BUILDINFO,
|
||||
STATUS_GET_FILE,
|
||||
STATUS_LIST_FILES,
|
||||
STATUS_XTGETTCAP,
|
||||
STATUS_XTVERSION,
|
||||
}
|
||||
|
||||
str_enum!(
|
||||
@@ -96,6 +99,8 @@ enum StatusCmd {
|
||||
(STATUS_STACK_TRACE, "print-stack-trace"),
|
||||
(STATUS_STACK_TRACE, "stack-trace"),
|
||||
(STATUS_TEST_FEATURE, "test-feature"),
|
||||
(STATUS_XTGETTCAP, "xtgettcap"),
|
||||
(STATUS_XTVERSION, "xtversion"),
|
||||
);
|
||||
|
||||
/// Values that may be returned from the test-feature option to status.
|
||||
@@ -527,6 +532,12 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
STATUS_XTGETTCAP => {
|
||||
return query::status_xtgettcap(parser, streams, cmd, args);
|
||||
}
|
||||
STATUS_XTVERSION => {
|
||||
return query::status_xtversion(parser, streams, cmd, args);
|
||||
}
|
||||
ref s => {
|
||||
if !args.is_empty() {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
@@ -714,7 +725,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
| STATUS_FEATURES
|
||||
| STATUS_TEST_FEATURE
|
||||
| STATUS_GET_FILE
|
||||
| STATUS_LIST_FILES => {
|
||||
| STATUS_LIST_FILES
|
||||
| STATUS_XTGETTCAP
|
||||
| STATUS_XTVERSION => {
|
||||
unreachable!("")
|
||||
}
|
||||
}
|
||||
|
||||
166
src/builtins/status/query.rs
Normal file
166
src/builtins/status/query.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use std::ops::ControlFlow;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::builtins::prelude::*;
|
||||
|
||||
use crate::common::wcs2string;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::{
|
||||
terminal_protocols_disable_ifn, InputEventQueuer, TerminalQuery, XtgettcapQuery,
|
||||
};
|
||||
use crate::nix::isatty;
|
||||
use crate::reader::{
|
||||
query_xtgettcap, querying_allowed, reader_pop, reader_push, reader_readline, ReaderConfig,
|
||||
UserQuery,
|
||||
};
|
||||
use crate::terminal::TerminalCommand::QueryPrimaryDeviceAttribute;
|
||||
use crate::terminal::{Output, Outputter, XTVERSION};
|
||||
use libc::STDOUT_FILENO;
|
||||
|
||||
use super::StatusCmd;
|
||||
|
||||
pub(crate) fn status_xtversion(
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
cmd: &wstr,
|
||||
args: &[&wstr],
|
||||
) -> BuiltinResult {
|
||||
use super::StatusCmd::STATUS_XTVERSION;
|
||||
if !args.is_empty() {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
STATUS_XTVERSION.to_wstr(),
|
||||
0,
|
||||
args.len()
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
let run_query = { move |_query: &mut Option<TerminalQuery>| ControlFlow::Break(()) };
|
||||
synchronous_query(parser, streams, cmd, &STATUS_XTVERSION, Box::new(run_query))?;
|
||||
|
||||
let Some(xtversion) = XTVERSION.get() else {
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
|
||||
streams.out.appendln(xtversion);
|
||||
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
pub(crate) fn status_xtgettcap(
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
cmd: &wstr,
|
||||
args: &[&wstr],
|
||||
) -> BuiltinResult {
|
||||
use super::StatusCmd::STATUS_XTGETTCAP;
|
||||
if !querying_allowed() {
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
if args.len() != 1 {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
STATUS_XTGETTCAP.to_wstr(),
|
||||
1,
|
||||
args.len()
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let result = Rc::new(RelaxedAtomicBool::new(false));
|
||||
let run_query = {
|
||||
let terminfo_code = wcs2string(args[0]);
|
||||
let result = Rc::clone(&result);
|
||||
move |query: &mut Option<TerminalQuery>| {
|
||||
assert!(matches!(
|
||||
*query,
|
||||
None | Some(TerminalQuery::PrimaryDeviceAttribute(None))
|
||||
));
|
||||
let mut output = Outputter::stdoutput().borrow_mut();
|
||||
output.begin_buffering();
|
||||
query_xtgettcap(output.by_ref(), &terminfo_code);
|
||||
output.write_command(QueryPrimaryDeviceAttribute);
|
||||
output.end_buffering();
|
||||
*query = Some(TerminalQuery::PrimaryDeviceAttribute(Some(
|
||||
XtgettcapQuery {
|
||||
terminfo_code,
|
||||
result,
|
||||
},
|
||||
)));
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
};
|
||||
synchronous_query(parser, streams, cmd, &STATUS_XTGETTCAP, Box::new(run_query))?;
|
||||
if !result.load() {
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
fn synchronous_query(
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
cmd: &wstr,
|
||||
subcmd: &StatusCmd,
|
||||
run_query: UserQuery,
|
||||
) -> Result<(), ErrorCode> {
|
||||
if !isatty(streams.stdin_fd) {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%s %s: stdin is not a TTY",
|
||||
cmd,
|
||||
subcmd.to_wstr(),
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
let out_fd = STDOUT_FILENO;
|
||||
if !isatty(out_fd) {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%s %s: stdout is not a TTY",
|
||||
cmd,
|
||||
subcmd.to_wstr(),
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
|
||||
if let Some(query_state) = parser.blocking_query.get() {
|
||||
if (run_query)(&mut query_state.borrow_mut()).is_break() {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
// We are the first reader.
|
||||
let empty_spot = parser.pending_user_query.replace(Some(run_query));
|
||||
assert!(empty_spot.is_none());
|
||||
}
|
||||
|
||||
let mut conf = ReaderConfig::default();
|
||||
conf.inputfd = streams.stdin_fd;
|
||||
conf.prompt_ok = false;
|
||||
conf.exit_on_interrupt = true;
|
||||
|
||||
let pending_keys = {
|
||||
let mut reader = reader_push(parser, L!(""), conf);
|
||||
{
|
||||
let _interactive = parser.push_scope(|s| s.is_interactive = true);
|
||||
let no_line = reader_readline(parser, None);
|
||||
assert!(no_line.is_none());
|
||||
}
|
||||
terminal_protocols_disable_ifn();
|
||||
let input_data = reader.get_input_data_mut();
|
||||
let pending_keys = std::mem::take(&mut input_data.queue);
|
||||
// We blocked code and mapping execution so input function args must be empty.
|
||||
assert!(input_data.input_function_args.is_empty());
|
||||
if input_data.paste_buffer.is_some() {
|
||||
FLOG!(
|
||||
reader,
|
||||
"Bracketed paste was interrupted; dropping uncommitted paste buffer"
|
||||
)
|
||||
}
|
||||
reader_pop();
|
||||
pending_keys
|
||||
};
|
||||
FLOGF!(reader, "Adding %lu pending keys", pending_keys.len());
|
||||
parser.pending_keys.borrow_mut().extend(pending_keys);
|
||||
Ok(())
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
};
|
||||
use crate::path::{path_as_implicit_cd, path_get_cdpath, path_get_path, paths_are_same_file};
|
||||
use crate::terminal::Outputter;
|
||||
use crate::text_face::{parse_text_face, TextFace, UnderlineStyle};
|
||||
use crate::text_face::{parse_text_face, SpecifiedTextFace, TextFace, UnderlineStyle};
|
||||
use crate::threads::assert_is_background_thread;
|
||||
use crate::tokenizer::{variable_assignment_equals_pos, PipeOrRedir};
|
||||
use crate::wchar::{wstr, WString, L};
|
||||
@@ -140,12 +140,16 @@ pub(crate) fn resolve_spec_uncached(
|
||||
vars: &dyn Environment,
|
||||
) -> TextFace {
|
||||
let resolve_role = |role| {
|
||||
vars.get_unless_empty(get_highlight_var_name(role))
|
||||
.or_else(|| vars.get_unless_empty(get_highlight_var_name(get_fallback(role))))
|
||||
.or_else(|| vars.get_unless_empty(get_highlight_var_name(HighlightRole::normal)))
|
||||
.as_ref()
|
||||
.map(parse_text_face_for_highlight)
|
||||
.unwrap_or_else(TextFace::default)
|
||||
for role in [role, get_fallback(role), HighlightRole::normal] {
|
||||
if let Some(face) = vars
|
||||
.get_unless_empty(get_highlight_var_name(role))
|
||||
.as_ref()
|
||||
.and_then(parse_text_face_for_highlight)
|
||||
{
|
||||
return face;
|
||||
}
|
||||
}
|
||||
TextFace::default()
|
||||
};
|
||||
let mut face = resolve_role(highlight.foreground);
|
||||
|
||||
@@ -162,7 +166,8 @@ pub(crate) fn resolve_spec_uncached(
|
||||
if highlight.valid_path {
|
||||
if let Some(valid_path_var) = vars.get(L!("fish_color_valid_path")) {
|
||||
// Historical behavior is to not apply background.
|
||||
let valid_path_face = parse_text_face_for_highlight(&valid_path_var);
|
||||
let valid_path_face =
|
||||
parse_text_face_for_highlight(&valid_path_var).unwrap_or_default();
|
||||
// Apply the foreground, except if it's normal. The intention here is likely
|
||||
// to only override foreground if the valid path color has an explicit foreground.
|
||||
if !valid_path_face.fg.is_normal() {
|
||||
@@ -181,19 +186,21 @@ pub(crate) fn resolve_spec_uncached(
|
||||
}
|
||||
|
||||
/// Return the internal color code representing the specified color.
|
||||
pub(crate) fn parse_text_face_for_highlight(var: &EnvVar) -> TextFace {
|
||||
pub(crate) fn parse_text_face_for_highlight(var: &EnvVar) -> Option<TextFace> {
|
||||
let face = parse_text_face(var.as_list());
|
||||
let default = TextFace::default();
|
||||
let fg = face.fg.unwrap_or(default.fg);
|
||||
let bg = face.bg.unwrap_or(default.bg);
|
||||
let underline_color = face.underline_color.unwrap_or(default.underline_color);
|
||||
let style = face.style;
|
||||
TextFace {
|
||||
fg,
|
||||
bg,
|
||||
underline_color,
|
||||
style,
|
||||
}
|
||||
(face != SpecifiedTextFace::default()).then(|| {
|
||||
let default = TextFace::default();
|
||||
let fg = face.fg.unwrap_or(default.fg);
|
||||
let bg = face.bg.unwrap_or(default.bg);
|
||||
let underline_color = face.underline_color.unwrap_or(default.underline_color);
|
||||
let style = face.style.unwrap_or_default();
|
||||
TextFace {
|
||||
fg,
|
||||
bg,
|
||||
underline_color,
|
||||
style,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn command_is_valid(
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
KittyKeyboardProgressiveEnhancementsDisable, KittyKeyboardProgressiveEnhancementsEnable,
|
||||
ModifyOtherKeysDisable, ModifyOtherKeysEnable,
|
||||
};
|
||||
use crate::terminal::{
|
||||
Capability, Output, Outputter, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED,
|
||||
SCROLL_FORWARD_TERMINFO_CODE,
|
||||
};
|
||||
use crate::terminal::{Capability, Output, Outputter, KITTY_KEYBOARD_SUPPORTED, XTVERSION};
|
||||
use crate::threads::{iothread_port, is_main_thread};
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wchar::{encode_byte_to_char, prelude::*};
|
||||
@@ -32,6 +29,7 @@
|
||||
use std::os::fd::RawFd;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -327,6 +325,8 @@ pub struct KeyInputEvent {
|
||||
pub enum ImplicitEvent {
|
||||
/// end-of-file was reached.
|
||||
Eof,
|
||||
/// Done
|
||||
Break,
|
||||
/// An event was handled internally, or an interrupt was received. Check to see if the reader
|
||||
/// loop should exit.
|
||||
CheckExit,
|
||||
@@ -758,15 +758,21 @@ pub fn function_set_status(&mut self, status: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum CursorPositionQuery {
|
||||
MouseLeft(ViewportPosition),
|
||||
ScrollbackPush,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct XtgettcapQuery {
|
||||
pub terminfo_code: Vec<u8>,
|
||||
pub result: Rc<RelaxedAtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TerminalQuery {
|
||||
PrimaryDeviceAttribute,
|
||||
PrimaryDeviceAttribute(Option<XtgettcapQuery>),
|
||||
CursorPositionReport(CursorPositionQuery),
|
||||
}
|
||||
|
||||
@@ -932,6 +938,8 @@ fn try_readch(&mut self, blocking: bool) -> Option<CharEvent> {
|
||||
);
|
||||
let ok = stop_query(self.blocking_query());
|
||||
assert!(ok);
|
||||
// TODO only if cancellation
|
||||
self.push_front(CharEvent::Implicit(ImplicitEvent::Break));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -959,10 +967,7 @@ fn parse_escape_sequence(
|
||||
assert!(buffer.len() <= 2);
|
||||
let recursive_invocation = buffer.len() == 2;
|
||||
let Some(next) = self.try_readb(buffer) else {
|
||||
if !self.paste_is_buffering() {
|
||||
return Some(KeyEvent::from_raw(key::Escape));
|
||||
}
|
||||
return None;
|
||||
return Some(KeyEvent::from_raw(key::Escape));
|
||||
};
|
||||
let invalid = KeyEvent::from_raw(key::Invalid);
|
||||
if recursive_invocation && next == b'\x1b' {
|
||||
@@ -1223,6 +1228,7 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
_ => return None,
|
||||
},
|
||||
b'c' if private_mode == Some(b'?') => {
|
||||
FLOG!(reader, "Received primary device attribute response");
|
||||
self.push_front(CharEvent::QueryResponse(
|
||||
QueryResponseEvent::PrimaryDeviceAttribute,
|
||||
));
|
||||
@@ -1369,13 +1375,12 @@ fn parse_xtversion(&mut self, buffer: &mut Vec<u8>) -> Option<()> {
|
||||
if buffer.get(3)? != &b'|' {
|
||||
return None;
|
||||
}
|
||||
let xtversion = str2wcstring(&buffer[4..buffer.len()]);
|
||||
FLOG!(
|
||||
reader,
|
||||
format!(
|
||||
"Received XTVERSION response: {}",
|
||||
str2wcstring(&buffer[4..buffer.len()])
|
||||
)
|
||||
format!("Received XTVERSION response: {}", xtversion)
|
||||
);
|
||||
XTVERSION.get_or_init(|| xtversion);
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1432,9 +1437,17 @@ fn parse_dcs(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
format!("Received XTGETTCAP response: {}", str2wcstring(&key))
|
||||
);
|
||||
}
|
||||
if key == SCROLL_FORWARD_TERMINFO_CODE.as_bytes() {
|
||||
SCROLL_FORWARD_SUPPORTED.store(true);
|
||||
FLOG!(reader, "Scroll forward is supported");
|
||||
if let Some(TerminalQuery::PrimaryDeviceAttribute(Some(tcap_query))) =
|
||||
&*self.blocking_query()
|
||||
{
|
||||
if tcap_query.terminfo_code == key {
|
||||
if tcap_query.result.swap(true) {
|
||||
FLOG!(
|
||||
reader,
|
||||
"Error: received multiple XTGETTCAP responses for user query"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -772,6 +772,10 @@ pub fn new(fd: RawFd) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd
|
||||
}
|
||||
|
||||
fn append(&mut self, s: &wstr) -> bool {
|
||||
if self.errored {
|
||||
return false;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
};
|
||||
use crate::fds::{open_dir, BEST_O_SEARCH};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::{terminal_protocols_disable_ifn, TerminalQuery};
|
||||
use crate::input_common::{terminal_protocols_disable_ifn, CharEvent, TerminalQuery};
|
||||
use crate::io::IoChain;
|
||||
use crate::job_group::MaybeJobId;
|
||||
use crate::operation_context::{OperationContext, EXPANSION_LIMIT_DEFAULT};
|
||||
@@ -25,6 +25,7 @@
|
||||
use crate::parse_execution::{EndExecutionReason, ExecutionContext};
|
||||
use crate::parse_tree::{parse_source, LineCounter, ParsedSourceRef};
|
||||
use crate::proc::{job_reap, JobGroupRef, JobList, JobRef, Pid, ProcStatus};
|
||||
use crate::reader::UserQuery;
|
||||
use crate::signal::{signal_check_cancel, signal_clear_cancel, Signal};
|
||||
use crate::threads::assert_is_main_thread;
|
||||
use crate::util::get_time;
|
||||
@@ -37,6 +38,7 @@
|
||||
#[cfg(not(target_has_atomic = "64"))]
|
||||
use portable_atomic::AtomicU64;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::{CStr, OsStr};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
@@ -442,6 +444,10 @@ pub struct Parser {
|
||||
pub global_event_blocks: AtomicU64,
|
||||
|
||||
pub blocking_query: OnceCell<RefCell<Option<TerminalQuery>>>,
|
||||
|
||||
pub pending_user_query: RefCell<Option<UserQuery>>,
|
||||
|
||||
pub pending_keys: RefCell<VecDeque<CharEvent>>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
@@ -460,6 +466,8 @@ pub fn new(variables: Rc<EnvStack>, cancel_behavior: CancelBehavior) -> Parser {
|
||||
profile_items: RefCell::default(),
|
||||
global_event_blocks: AtomicU64::new(0),
|
||||
blocking_query: OnceCell::new(),
|
||||
pending_user_query: RefCell::new(None),
|
||||
pending_keys: RefCell::new(VecDeque::new()),
|
||||
};
|
||||
|
||||
match open_dir(CStr::from_bytes_with_nul(b".\0").unwrap(), BEST_O_SEARCH) {
|
||||
|
||||
209
src/reader.rs
209
src/reader.rs
@@ -85,6 +85,7 @@
|
||||
use crate::input_common::terminal_protocols_disable_ifn;
|
||||
use crate::input_common::CursorPositionQuery;
|
||||
use crate::input_common::ImplicitEvent;
|
||||
use crate::input_common::InputEventQueuer;
|
||||
use crate::input_common::QueryResponseEvent;
|
||||
use crate::input_common::TerminalQuery;
|
||||
use crate::input_common::IN_DVTM;
|
||||
@@ -134,9 +135,7 @@
|
||||
QueryCursorPosition, QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute,
|
||||
QueryXtgettcap, QueryXtversion,
|
||||
};
|
||||
use crate::terminal::{
|
||||
Capability, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE,
|
||||
};
|
||||
use crate::terminal::{Capability, KITTY_KEYBOARD_SUPPORTED};
|
||||
use crate::termsize::{termsize_invalidate_tty, termsize_last, termsize_update};
|
||||
use crate::text_face::parse_text_face;
|
||||
use crate::text_face::TextFace;
|
||||
@@ -238,25 +237,25 @@ fn redirect_tty_after_sighup() {
|
||||
pub(crate) fn initial_query(
|
||||
blocking_query: &OnceCell<RefCell<Option<TerminalQuery>>>,
|
||||
out: &mut impl Output,
|
||||
vars: Option<&dyn Environment>,
|
||||
) {
|
||||
blocking_query.get_or_init(|| {
|
||||
let query = if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() {
|
||||
None
|
||||
} else {
|
||||
let query = querying_allowed().then(|| {
|
||||
// Query for kitty keyboard protocol support.
|
||||
out.write_command(QueryKittyKeyboardProgressiveEnhancements);
|
||||
out.write_command(QueryXtversion);
|
||||
if let Some(vars) = vars {
|
||||
query_capabilities_via_dcs(out.by_ref(), vars);
|
||||
}
|
||||
out.write_command(QueryPrimaryDeviceAttribute);
|
||||
Some(TerminalQuery::PrimaryDeviceAttribute)
|
||||
};
|
||||
TerminalQuery::PrimaryDeviceAttribute(None)
|
||||
});
|
||||
RefCell::new(query)
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) type UserQuery = Box<dyn FnOnce(&mut Option<TerminalQuery>) -> ControlFlow<()>>;
|
||||
|
||||
pub(crate) fn querying_allowed() -> bool {
|
||||
!(is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load())
|
||||
}
|
||||
|
||||
/// The stack of current interactive reading contexts.
|
||||
fn reader_data_stack() -> &'static mut Vec<Pin<Box<ReaderData>>> {
|
||||
struct ReaderDataStack(UnsafeCell<Vec<Pin<Box<ReaderData>>>>);
|
||||
@@ -273,7 +272,7 @@ pub fn reader_in_interactive_read() -> bool {
|
||||
reader_data_stack()
|
||||
.iter()
|
||||
.rev()
|
||||
.any(|reader| reader.conf.exit_on_interrupt)
|
||||
.any(|reader| reader.conf.in_builtin_read)
|
||||
}
|
||||
|
||||
/// Access the top level reader data.
|
||||
@@ -338,6 +337,9 @@ pub struct ReaderConfig {
|
||||
/// Whether to allow autosuggestions.
|
||||
pub autosuggest_ok: bool,
|
||||
|
||||
/// Whether to show a prompt.
|
||||
pub prompt_ok: bool,
|
||||
|
||||
/// Whether to reexecute prompt function before final rendering.
|
||||
pub transient_prompt: bool,
|
||||
|
||||
@@ -347,6 +349,9 @@ pub struct ReaderConfig {
|
||||
/// Whether to exit on interrupt (^C).
|
||||
pub exit_on_interrupt: bool,
|
||||
|
||||
/// Whether we are in builtin read.
|
||||
pub in_builtin_read: bool,
|
||||
|
||||
/// If set, do not show what is typed.
|
||||
pub in_silent_mode: bool,
|
||||
|
||||
@@ -691,6 +696,7 @@ fn read_i(parser: &Parser) {
|
||||
conf.syntax_check_ok = true;
|
||||
conf.expand_abbrev_ok = true;
|
||||
conf.autosuggest_ok = check_bool_var(parser.vars(), L!("fish_autosuggestion_enabled"), true);
|
||||
conf.prompt_ok = true;
|
||||
conf.transient_prompt = check_bool_var(parser.vars(), L!("fish_transient_prompt"), false);
|
||||
|
||||
if parser.is_breakpoint() && function::exists(DEBUG_PROMPT_FUNCTION_NAME, parser) {
|
||||
@@ -882,6 +888,8 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
term_donate(/*quiet=*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
terminal_protocol_hacks();
|
||||
}
|
||||
|
||||
pub fn reader_deinit(in_signal_handler: bool, restore_foreground_pgroup: bool) {
|
||||
@@ -1061,8 +1069,7 @@ pub fn reader_reading_interrupted(data: &mut ReaderData) -> i32 {
|
||||
/// characters even if a full line has not yet been read. Note: the returned value may be longer
|
||||
/// than nchars if a single keypress resulted in multiple characters being inserted into the
|
||||
/// commandline.
|
||||
pub fn reader_readline(parser: &Parser, nchars: usize) -> Option<WString> {
|
||||
let nchars = NonZeroUsize::try_from(nchars).ok();
|
||||
pub fn reader_readline(parser: &Parser, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
let data = current_data().unwrap();
|
||||
let mut reader = Reader { parser, data };
|
||||
reader.readline(nchars)
|
||||
@@ -2202,73 +2209,77 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
initial_query(
|
||||
&self.parser.blocking_query,
|
||||
&mut BufferedOutputter::new(Outputter::stdoutput()),
|
||||
Some(self.parser.vars()),
|
||||
);
|
||||
|
||||
// HACK: Don't abandon line for the first prompt, because
|
||||
// if we're started with the terminal it might not have settled,
|
||||
// so the width is quite likely to be in flight.
|
||||
//
|
||||
// This means that `printf %s foo; fish` will overwrite the `foo`,
|
||||
// but that's a smaller problem than having the omitted newline char
|
||||
// appear constantly.
|
||||
//
|
||||
// I can't see a good way around this.
|
||||
if !self.first_prompt {
|
||||
self.screen
|
||||
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
|
||||
if self.conf.prompt_ok {
|
||||
// HACK: Don't abandon line for the first prompt, because
|
||||
// if we're started with the terminal it might not have settled,
|
||||
// so the width is quite likely to be in flight.
|
||||
//
|
||||
// This means that `printf %s foo; fish` will overwrite the `foo`,
|
||||
// but that's a smaller problem than having the omitted newline char
|
||||
// appear constantly.
|
||||
//
|
||||
// I can't see a good way around this.
|
||||
if !self.first_prompt {
|
||||
self.screen
|
||||
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
|
||||
}
|
||||
self.first_prompt = false;
|
||||
|
||||
if !self.conf.event.is_empty() {
|
||||
event::fire_generic(self.parser, self.conf.event.to_owned(), vec![]);
|
||||
}
|
||||
self.exec_prompt(true, false);
|
||||
|
||||
// Start out as initially dirty.
|
||||
self.force_exec_prompt_and_repaint = true;
|
||||
}
|
||||
self.first_prompt = false;
|
||||
|
||||
if !self.conf.event.is_empty() {
|
||||
event::fire_generic(self.parser, self.conf.event.to_owned(), vec![]);
|
||||
}
|
||||
self.exec_prompt(true, false);
|
||||
|
||||
// Start out as initially dirty.
|
||||
self.force_exec_prompt_and_repaint = true;
|
||||
|
||||
self.insert_front(self.parser.pending_keys.take());
|
||||
while !self.rls().finished && !check_exit_loop_maybe_warning(Some(self)) {
|
||||
if self.handle_char_event(None).is_break() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if self.conf.transient_prompt {
|
||||
self.exec_prompt(true, true);
|
||||
}
|
||||
if self.conf.prompt_ok {
|
||||
if self.conf.transient_prompt {
|
||||
self.exec_prompt(true, true);
|
||||
}
|
||||
|
||||
// Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the
|
||||
// user presses enter.
|
||||
if self.is_repaint_needed(None)
|
||||
|| self.screen.scrolled()
|
||||
|| self.conf.inputfd != STDIN_FILENO
|
||||
{
|
||||
self.layout_and_repaint_before_execution();
|
||||
}
|
||||
// Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the
|
||||
// user presses enter.
|
||||
if self.is_repaint_needed(None)
|
||||
|| self.screen.scrolled()
|
||||
|| self.conf.inputfd != STDIN_FILENO
|
||||
{
|
||||
self.layout_and_repaint_before_execution();
|
||||
}
|
||||
|
||||
// Finish syntax highlighting (but do not wait forever).
|
||||
if self.rls().finished {
|
||||
self.finish_highlighting_before_exec();
|
||||
}
|
||||
// Finish syntax highlighting (but do not wait forever).
|
||||
if self.rls().finished {
|
||||
self.finish_highlighting_before_exec();
|
||||
}
|
||||
|
||||
// Emit a newline so that the output is on the line after the command.
|
||||
// But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826.
|
||||
if !self.screen.cursor_is_wrapped_to_own_line() {
|
||||
let _ = write_to_fd(b"\n", STDOUT_FILENO);
|
||||
}
|
||||
// Emit a newline so that the output is on the line after the command.
|
||||
// But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826.
|
||||
if !self.screen.cursor_is_wrapped_to_own_line() {
|
||||
let _ = write_to_fd(b"\n", STDOUT_FILENO);
|
||||
}
|
||||
|
||||
// HACK: If stdin isn't the same terminal as stdout, we just moved the cursor.
|
||||
// For now, just reset it to the beginning of the line.
|
||||
if self.conf.inputfd != STDIN_FILENO {
|
||||
let _ = write_loop(&STDOUT_FILENO, b"\r");
|
||||
}
|
||||
// HACK: If stdin isn't the same terminal as stdout, we just moved the cursor.
|
||||
// For now, just reset it to the beginning of the line.
|
||||
if self.conf.inputfd != STDIN_FILENO {
|
||||
let _ = write_loop(&STDOUT_FILENO, b"\r");
|
||||
}
|
||||
|
||||
// Ensure we have no pager contents when we exit.
|
||||
if !self.pager.is_empty() {
|
||||
// Clear to end of screen to erase the pager contents.
|
||||
screen_force_clear_to_end();
|
||||
self.clear_pager();
|
||||
// Ensure we have no pager contents when we exit.
|
||||
if !self.pager.is_empty() {
|
||||
// Clear to end of screen to erase the pager contents.
|
||||
screen_force_clear_to_end();
|
||||
self.clear_pager();
|
||||
}
|
||||
}
|
||||
|
||||
if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as _ {
|
||||
@@ -2430,10 +2441,12 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
});
|
||||
|
||||
// If we ran `exit` anywhere, exit.
|
||||
self.exit_loop_requested =
|
||||
self.exit_loop_requested || self.parser.libdata().exit_current_script;
|
||||
self.exit_loop_requested |= self.parser.libdata().exit_current_script;
|
||||
self.parser.libdata_mut().exit_current_script = false;
|
||||
if self.exit_loop_requested {
|
||||
if !self.conf.prompt_ok {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
@@ -2507,6 +2520,7 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
ImplicitEvent::Eof => {
|
||||
reader_sighup();
|
||||
}
|
||||
ImplicitEvent::Break => return ControlFlow::Break(()),
|
||||
ImplicitEvent::CheckExit => (),
|
||||
ImplicitEvent::FocusIn => {
|
||||
event::fire_generic(self.parser, L!("fish_focus_in").to_owned(), vec![]);
|
||||
@@ -2531,16 +2545,31 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
CharEvent::QueryResponse(query_response) => {
|
||||
match query_response {
|
||||
QueryResponseEvent::PrimaryDeviceAttribute => {
|
||||
if *self.blocking_query() != Some(TerminalQuery::PrimaryDeviceAttribute) {
|
||||
let mut query = self.blocking_query();
|
||||
let Some(TerminalQuery::PrimaryDeviceAttribute(tcap_query)) = &*query
|
||||
else {
|
||||
// Rogue reply.
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
};
|
||||
if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed)
|
||||
== Capability::Unknown as _
|
||||
{
|
||||
KITTY_KEYBOARD_SUPPORTED
|
||||
.store(Capability::NotSupported as _, Ordering::Release);
|
||||
}
|
||||
assert!(
|
||||
tcap_query.is_none()
|
||||
|| self.parser.pending_user_query.borrow().is_none()
|
||||
);
|
||||
if tcap_query.is_some() {
|
||||
stop_query(query);
|
||||
// TODO hack
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
if let Some(queued_query) = self.parser.pending_user_query.take() {
|
||||
// TODO hack
|
||||
return (queued_query)(&mut query);
|
||||
}
|
||||
}
|
||||
QueryResponseEvent::CursorPositionReport(cursor_pos) => {
|
||||
let cursor_pos_query = match &*self.blocking_query() {
|
||||
@@ -2567,29 +2596,22 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
}
|
||||
}
|
||||
|
||||
fn send_xtgettcap_query(out: &mut impl Output, cap: &'static str) {
|
||||
pub(crate) fn query_xtgettcap(out: &mut impl Output, cap: &[u8]) {
|
||||
let command = QueryXtgettcap(cap);
|
||||
if should_flog!(reader) {
|
||||
let mut tmp = Vec::<u8>::new();
|
||||
tmp.write_command(QueryXtgettcap(cap));
|
||||
tmp.write_command(command.clone());
|
||||
FLOG!(
|
||||
reader,
|
||||
format!("Sending XTGETTCAP request for {}: {:?}", cap, tmp)
|
||||
sprintf!(
|
||||
"Sending XTGETTCAP request for %s: %s",
|
||||
str2wcstring(cap),
|
||||
escape(&str2wcstring(&tmp))
|
||||
)
|
||||
);
|
||||
}
|
||||
out.write_command(QueryXtgettcap(cap));
|
||||
}
|
||||
|
||||
fn query_capabilities_via_dcs(out: &mut impl Output, vars: &dyn Environment) {
|
||||
if vars.get_unless_empty(L!("STY")).is_some()
|
||||
|| vars.get_unless_empty(L!("TERM")).is_some_and(|term| {
|
||||
let term = &term.as_list()[0];
|
||||
term == "screen" || term == "screen-256color"
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
out.write_command(DecsetAlternateScreenBuffer); // enable alternative screen buffer
|
||||
send_xtgettcap_query(out, SCROLL_FORWARD_TERMINFO_CODE);
|
||||
out.write_command(command);
|
||||
out.write_command(DecrstAlternateScreenBuffer); // disable alternative screen buffer
|
||||
}
|
||||
|
||||
@@ -2688,7 +2710,9 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
|
||||
let mut outp = Outputter::stdoutput().borrow_mut();
|
||||
if let Some(fish_color_cancel) = self.vars().get(L!("fish_color_cancel")) {
|
||||
outp.set_text_face(parse_text_face_for_highlight(&fish_color_cancel));
|
||||
outp.set_text_face(
|
||||
parse_text_face_for_highlight(&fish_color_cancel).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
outp.write_wstr(L!("^C"));
|
||||
outp.reset_text_face();
|
||||
@@ -3815,9 +3839,6 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
self.clear_screen_and_repaint();
|
||||
}
|
||||
rl::ScrollbackPush => {
|
||||
if !SCROLL_FORWARD_SUPPORTED.load() {
|
||||
return;
|
||||
}
|
||||
let query = self.blocking_query();
|
||||
let Some(query) = &*query else {
|
||||
drop(query);
|
||||
@@ -3828,7 +3849,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
return;
|
||||
};
|
||||
match query {
|
||||
TerminalQuery::PrimaryDeviceAttribute => panic!(),
|
||||
TerminalQuery::PrimaryDeviceAttribute(_) => panic!(),
|
||||
TerminalQuery::CursorPositionReport(_) => {
|
||||
// TODO: re-queue it I guess.
|
||||
FLOG!(
|
||||
@@ -4449,8 +4470,6 @@ fn reader_interactive_init(parser: &Parser) {
|
||||
parser
|
||||
.vars()
|
||||
.set_one(L!("_"), EnvMode::GLOBAL, L!("fish").to_owned());
|
||||
|
||||
terminal_protocol_hacks();
|
||||
}
|
||||
|
||||
/// Destroy data for interactive use.
|
||||
@@ -4528,6 +4547,7 @@ pub fn reader_write_title(
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
fn exec_prompt_cmd(&self, prompt_cmd: &wstr, final_prompt: bool) -> Vec<WString> {
|
||||
assert!(self.conf.prompt_ok);
|
||||
let mut output = vec![];
|
||||
let prompt_cmd = if final_prompt && function::exists(prompt_cmd, self.parser) {
|
||||
Cow::Owned(prompt_cmd.to_owned() + L!(" --final-rendering"))
|
||||
@@ -4540,6 +4560,7 @@ fn exec_prompt_cmd(&self, prompt_cmd: &wstr, final_prompt: bool) -> Vec<WString>
|
||||
|
||||
/// Execute prompt commands based on the provided arguments. The output is inserted into prompt_buff.
|
||||
fn exec_prompt(&mut self, full_prompt: bool, final_prompt: bool) {
|
||||
assert!(self.conf.prompt_ok);
|
||||
// Suppress fish_trace while in the prompt.
|
||||
let _suppress_trace = self.parser.push_scope(|s| s.suppress_fish_trace = true);
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
use crate::common::ToCString;
|
||||
use crate::common::{self, escape_string, wcs2string, wcs2string_appending, EscapeStringStyle};
|
||||
use crate::future_feature_flags::{self, FeatureFlag};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::screen::{is_dumb, only_grayscale};
|
||||
use crate::text_face::{TextFace, TextStyling, UnderlineStyle};
|
||||
use crate::threads::MainThread;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::FLOGF;
|
||||
use bitflags::bitflags;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::env;
|
||||
use std::ffi::{CStr, CString};
|
||||
@@ -83,7 +83,7 @@ pub(crate) enum TerminalCommand<'a> {
|
||||
// Commands related to querying (used for backwards-incompatible features).
|
||||
QueryPrimaryDeviceAttribute,
|
||||
QueryXtversion,
|
||||
QueryXtgettcap(&'static str),
|
||||
QueryXtgettcap(&'a [u8]),
|
||||
|
||||
DecsetAlternateScreenBuffer,
|
||||
DecrstAlternateScreenBuffer,
|
||||
@@ -227,8 +227,7 @@ pub(crate) enum Capability {
|
||||
|
||||
pub(crate) static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _);
|
||||
|
||||
pub(crate) static SCROLL_FORWARD_SUPPORTED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
pub(crate) static SCROLL_FORWARD_TERMINFO_CODE: &str = "indn";
|
||||
pub(crate) static XTVERSION: OnceCell<WString> = OnceCell::new();
|
||||
|
||||
pub(crate) fn use_terminfo() -> bool {
|
||||
!future_feature_flags::test(FeatureFlag::ignore_terminfo) && TERM.lock().unwrap().is_some()
|
||||
@@ -349,16 +348,16 @@ fn cursor_move(out: &mut impl Output, direction: CardinalDirection, steps: usize
|
||||
true
|
||||
}
|
||||
|
||||
fn query_xtgettcap(out: &mut impl Output, cap: &str) -> bool {
|
||||
fn query_xtgettcap(out: &mut impl Output, cap: &[u8]) -> bool {
|
||||
write_to_output!(out, "\x1bP+q{}\x1b\\", DisplayAsHex(cap));
|
||||
true
|
||||
}
|
||||
|
||||
struct DisplayAsHex<'a>(&'a str);
|
||||
struct DisplayAsHex<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> std::fmt::Display for DisplayAsHex<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for byte in self.0.bytes() {
|
||||
for byte in self.0 {
|
||||
write!(f, "{:x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -399,7 +398,6 @@ fn osc_133_command_finished(out: &mut impl Output, exit_status: libc::c_int) ->
|
||||
}
|
||||
|
||||
fn scroll_forward(out: &mut impl Output, lines: usize) -> bool {
|
||||
assert!(SCROLL_FORWARD_SUPPORTED.load());
|
||||
write_to_output!(out, "\x1b[{}S", lines);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -111,6 +111,12 @@ pub(crate) struct TextFace {
|
||||
pub(crate) style: TextStyling,
|
||||
}
|
||||
|
||||
impl Default for TextFace {
|
||||
fn default() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextFace {
|
||||
pub const fn default() -> Self {
|
||||
Self {
|
||||
@@ -131,12 +137,12 @@ pub fn new(fg: Color, bg: Color, underline_color: Color, style: TextStyling) ->
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
pub(crate) struct SpecifiedTextFace {
|
||||
pub(crate) fg: Option<Color>,
|
||||
pub(crate) bg: Option<Color>,
|
||||
pub(crate) underline_color: Option<Color>,
|
||||
pub(crate) style: TextStyling,
|
||||
pub(crate) style: Option<TextStyling>,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_text_face(arguments: &[WString]) -> SpecifiedTextFace {
|
||||
@@ -167,6 +173,7 @@ pub(crate) enum ParsedArgs<'argarray, 'args> {
|
||||
|
||||
pub(crate) enum ParseError<'args> {
|
||||
MissingOptArg,
|
||||
MultipleTracking,
|
||||
UnknownColor(&'args wstr),
|
||||
UnknownUnderlineStyle(&'args wstr),
|
||||
UnknownOption(usize),
|
||||
@@ -189,6 +196,7 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
|
||||
wopt(L!("reverse"), ArgType::NoArgument, 'r'),
|
||||
wopt(L!("help"), ArgType::NoArgument, 'h'),
|
||||
wopt(L!("print-colors"), ArgType::NoArgument, 'c'),
|
||||
wopt(L!("track"), ArgType::RequiredArgument, '\x01'),
|
||||
];
|
||||
let long_options = &long_options[..long_options.len() - builtin_extra_args];
|
||||
|
||||
@@ -210,6 +218,7 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
|
||||
let mut underline_colors = vec![];
|
||||
let mut style = TextStyling::default();
|
||||
let mut print_color_mode = false;
|
||||
let mut tracking = false;
|
||||
|
||||
let mut w = WGetopter::new(short_options, long_options, argv);
|
||||
while let Some(c) = w.next_opt() {
|
||||
@@ -219,6 +228,12 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
|
||||
bg_colors.push(bg);
|
||||
}
|
||||
}
|
||||
'\x01' => {
|
||||
if is_builtin && tracking {
|
||||
return Err(MultipleTracking);
|
||||
}
|
||||
tracking = true;
|
||||
}
|
||||
'\x02' => {
|
||||
if let Some(underline_color) = parse_color(w.woptarg.unwrap())? {
|
||||
underline_colors.push(underline_color);
|
||||
@@ -297,6 +312,6 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
|
||||
fg,
|
||||
bg,
|
||||
underline_color,
|
||||
style,
|
||||
style: (style != TextStyling::default()).then_some(style),
|
||||
}))
|
||||
}
|
||||
|
||||
51
tests/checks/fish_config.fish
Normal file
51
tests/checks/fish_config.fish
Normal file
@@ -0,0 +1,51 @@
|
||||
#RUN: %fish %s
|
||||
|
||||
mkdir $__fish_config_dir/themes
|
||||
echo >$__fish_config_dir/themes/foo.theme '
|
||||
fish_color_normal cyan
|
||||
fish_color_error brred --underline=curly
|
||||
'
|
||||
|
||||
set -g fish_pager_color_secondary_background custom-value
|
||||
fish_config theme choose foo
|
||||
|
||||
set -S fish_color_normal
|
||||
# CHECK: $fish_color_normal: set in global scope, unexported, with 1 elements
|
||||
# CHECK: $fish_color_normal[1]: |cyan|
|
||||
set -S fish_pager_color_secondary_background
|
||||
# CHECK: $fish_pager_color_secondary_background: set in global scope, unexported, with 0 elements
|
||||
|
||||
# Not a default theme, so we allow --underline=curly even if we don't run inside a terminal that
|
||||
# advertises support via XTGETTCAP.
|
||||
set -S fish_color_error
|
||||
# CHECK: $fish_color_error: set in global scope, unexported, with 2 elements
|
||||
# CHECK: $fish_color_error[1]: |brred|
|
||||
# CHECK: $fish_color_error[2]: |--underline=curly|
|
||||
|
||||
|
||||
function change-theme
|
||||
echo >$__fish_config_dir/themes/fake-default.theme 'fish_color_command' $argv
|
||||
end
|
||||
|
||||
change-theme 'green'
|
||||
echo yes | fish_config theme save --track fake-default
|
||||
echo $fish_color_command
|
||||
# CHECK: green --track=fake-default
|
||||
|
||||
change-theme 'green --bold'
|
||||
fish_config theme update
|
||||
|
||||
echo $fish_color_command
|
||||
# CHECK: green --bold --track=fake-default
|
||||
|
||||
# Test that we silently update when there is a shadowing global.
|
||||
change-theme 'green --italics'
|
||||
set -g fish_color_command normal
|
||||
fish_config theme update
|
||||
set -S fish_color_command
|
||||
# CHECK: $fish_color_command: set in global scope, unexported, with 1 elements
|
||||
# CHECK: $fish_color_command[1]: |normal|
|
||||
# CHECK: $fish_color_command: set in universal scope, unexported, with 3 elements
|
||||
# CHECK: $fish_color_command[1]: |green|
|
||||
# CHECK: $fish_color_command[2]: |--italics|
|
||||
# CHECK: $fish_color_command[3]: |--track=fake-default|
|
||||
@@ -180,7 +180,10 @@ class SpawnedProc(object):
|
||||
self.spawn.delaybeforesend = None
|
||||
self.prompt_counter = 0
|
||||
if env.get("TERM") != "dumb":
|
||||
self.spawn.send("\x1b[?123c") # Primary Device Attribute
|
||||
self.send_primary_device_attribute()
|
||||
|
||||
def send_primary_device_attribute(self):
|
||||
self.spawn.send("\x1b[?123c")
|
||||
|
||||
def time_since_first_message(self):
|
||||
"""Return a delta in seconds since the first message, or 0 if this is the first."""
|
||||
|
||||
32
tests/pexpects/query.py
Normal file
32
tests/pexpects/query.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
from pexpect_helper import SpawnedProc
|
||||
|
||||
import os
|
||||
|
||||
env = os.environ.copy()
|
||||
env["TERM"] = "foot"
|
||||
|
||||
sp = SpawnedProc(env=env)
|
||||
send, sendline, sleep, expect_prompt, expect_re, expect_str = (
|
||||
sp.send,
|
||||
sp.sendline,
|
||||
sp.sleep,
|
||||
sp.expect_prompt,
|
||||
sp.expect_re,
|
||||
sp.expect_str,
|
||||
)
|
||||
expect_prompt()
|
||||
|
||||
sendline("function check; and echo true; or echo false; end")
|
||||
|
||||
sendline("status xtgettcap am; check")
|
||||
expect_str("\x1bP+q616d\x1b\\") # 616d is "am" in hex
|
||||
send("\x1bP1+r616d\x1b\\") # success
|
||||
sp.send_primary_device_attribute()
|
||||
expect_str("true")
|
||||
|
||||
sendline("status xtgettcap an; check")
|
||||
expect_str("\x1bP+q616e\x1b\\") # 616e is "an" in hex
|
||||
send("\x1bP0+r616e\x1b\\") # failure
|
||||
sp.send_primary_device_attribute()
|
||||
expect_str("false")
|
||||
Reference in New Issue
Block a user