Compare commits

..

1 Commits

Author SHA1 Message Date
Johannes Altmanninger
94c5a9d604 Don't emit OSC notifications
See #11442, #11427
2025-05-01 07:05:35 +02:00
55 changed files with 235 additions and 720 deletions

View File

@@ -69,12 +69,7 @@ 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
------------------

View File

@@ -10,7 +10,7 @@ Synopsis
fish_config [browse]
fish_config prompt (choose | list | save | show)
fish_config theme (choose | demo | dump | list | save | show | update)
fish_config theme (choose | demo | dump | list | save | show)
Description
-----------
@@ -40,9 +40,6 @@ 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.

View File

@@ -59,12 +59,6 @@ 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.

View File

@@ -33,8 +33,6 @@ Synopsis
status buildinfo
status get-file FILE
status list-files [PATH]
status xtgettcap TERMINFO-CAPABILITY
status xtversion
Description
-----------
@@ -119,13 +117,6 @@ 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
-----

View File

@@ -177,9 +177,7 @@ Optional Commands
- Su
- Reset underline color to the default (follow the foreground color).
- kitty
* - .. _indn:
``\e[ Ps S``
* - ``\e[ Ps S``
- indn
- Scroll forward Ps lines.
-
@@ -271,12 +269,6 @@ Optional Commands
- FinalTerm
* - ``\eP+q Pt \e\\``
-
- 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
- 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

0
reader
View File

View File

@@ -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 update
set -l theme_commands choose demo dump save show list
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,7 +16,6 @@ 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" \
@@ -29,5 +28,3 @@ 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"

View File

@@ -8,4 +8,3 @@ 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'

View File

@@ -27,9 +27,7 @@ set -l __fish_status_all_commands \
list-files \
print-stack-trace \
stack-trace \
test-feature \
xtgettcap \
xtversion
test-feature
# These are the recognized flags.
complete -c status -s h -l help -d "Display help and exit"
@@ -66,8 +64,6 @@ 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"

View File

@@ -30,13 +30,12 @@ if status is-interactive
# Commands to run in interactive sessions can go here
end" >$__fish_config_dir/config.fish
fish_config theme save "fish default" --yes --track
echo yes | fish_config theme save "fish default"
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.

View File

@@ -1,3 +0,0 @@
function __fish_in_gnu_screen
test -n "$STY" || contains -- $TERM screen screen-256color
end

View File

@@ -1,9 +0,0 @@
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

View File

@@ -77,11 +77,7 @@ 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 (
if not __fish_in_gnu_screen && and __fish_xtgettcap indn
echo scrollback-push
end
) clear-screen
bind --preset $argv ctrl-l scrollback-push clear-screen
bind --preset $argv ctrl-c clear-commandline
bind --preset $argv ctrl-u backward-kill-line
bind --preset $argv ctrl-k kill-line

View File

@@ -1,12 +0,0 @@
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

View File

@@ -1,11 +1,5 @@
# 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
function fish_config --description "Launch fish's web based configuration"
argparse h/help -- $argv
or return
if set -q _flag_help
@@ -16,15 +10,6 @@ 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
@@ -80,6 +65,9 @@ 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
@@ -156,8 +144,8 @@ function fish_config --description "Launch fish's web based configuration" \
functions --erase fish_right_prompt
end
case save
if set -q _flag_yes[1] ||
{ read -P"Overwrite prompt? [y/N]" -l yesno; string match -riq 'y(es)?' -- $yesno }
read -P"Overwrite prompt? [y/N]" -l yesno
if string match -riq 'y(es)?' -- $yesno
echo Overwriting
# Skip the cp if unnecessary,
# or we'd throw an error on a stock fish.
@@ -286,10 +274,8 @@ function fish_config --description "Launch fish's web based configuration" \
set -l have_colors
if contains -- $cmd save
if not set -q _flag_yes &&
{ read -P"Overwrite your current theme? [y/N] " -l yesno
not string match -riq 'y(es)?' -- $yesno
}
read -P"Overwrite your current theme? [y/N] " -l yesno
if not string match -riq 'y(es)?' -- $yesno
echo Not overwriting >&2
return 1
end
@@ -306,7 +292,34 @@ 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]
__fish_config_theme_get $argv[1] | while read -lat toks
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
# 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.
@@ -318,7 +331,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 $_flag_track=$argv[1]
set $scope $toks
set -a have_colors $toks[1]
end
@@ -330,7 +343,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 $_flag_track=$argv[1]
set $scope $c
end
else
# We're persisting whatever current colors are loaded (maybe in the global scope)
@@ -352,8 +365,6 @@ 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
@@ -363,100 +374,3 @@ 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

View File

@@ -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 --underline=curly
fish_color_error ab4642
fish_color_escape 86c1b9
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error ab4642
fish_color_escape 86c1b9
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error f2777a
fish_color_escape 66cccc
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error FF7400
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error ff5555
fish_color_escape ff79c6
fish_color_history_current --bold
fish_color_host bd93f9

View File

@@ -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 --underline=curly
fish_color_error EC3B86
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error FFA779
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -6,7 +6,7 @@ fish_color_command FF9400
fish_color_quote BF9C30
fish_color_redirection BF5B30
fish_color_end FF4C00
fish_color_error FFDD73 --underline=curly
fish_color_error FFDD73
fish_color_param FFC000
fish_color_comment A63100
fish_color_selection white --background=brblack --bold

View File

@@ -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 --underline=curly
fish_color_error b2b2b2
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -6,7 +6,7 @@ fish_color_command ffffff
fish_color_quote a8a8a8
fish_color_redirection 808080
fish_color_end 949494
fish_color_error 585858 --underline=curly
fish_color_error 585858
fish_color_param d7d7d7
fish_color_comment bcbcbc
fish_color_selection white --background=brblack --bold

View File

@@ -9,7 +9,7 @@ fish_color_comment
fish_color_cwd normal
fish_color_cwd_root normal
fish_color_end
fish_color_error --underline=curly
fish_color_error
fish_color_escape
fish_color_history_current
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error bf616a
fish_color_escape ebcb8b
fish_color_history_current e5e9f0 --bold
fish_color_host a3be8c

View File

@@ -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 --underline=curly
fish_color_error A40000
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error 60B9CE
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -6,7 +6,7 @@ fish_color_command 164CC9
fish_color_quote 4C3499
fish_color_redirection 248E8E
fish_color_end 02BDBD
fish_color_error 9177E5 --underline=curly
fish_color_error 9177E5
fish_color_param 4319CC
fish_color_comment 007B7B
fish_color_selection white --background=brblack --bold

View File

@@ -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 --underline=curly
fish_color_error dc322f
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -7,7 +7,7 @@ fish_color_command 586e75
fish_color_quote 839496
fish_color_redirection 6c71c4
fish_color_end 268bd2
fish_color_error dc322f --underline=curly
fish_color_error dc322f
fish_color_param 657b83
fish_color_comment 93a1a1
fish_color_selection white --background=brblack --bold

View File

@@ -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 --underline=curly
fish_color_error d54e53
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -7,7 +7,7 @@ fish_color_command b294bb
fish_color_quote b5bd68
fish_color_redirection 8abeb7
fish_color_end b294bb
fish_color_error cc6666 --underline=curly
fish_color_error cc6666
fish_color_param 81a2be
fish_color_comment f0c674
fish_color_selection white --background=brblack --bold

View File

@@ -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 --underline=curly
fish_color_error c82829
fish_color_escape 00a6b2
fish_color_history_current --bold
fish_color_host normal

View File

@@ -7,7 +7,7 @@ fish_color_command 39BAE6
fish_color_quote C2D94C
fish_color_redirection FFEE99
fish_color_end F29668
fish_color_error FF3333 --underline=curly
fish_color_error FF3333
fish_color_param B3B1AD
fish_color_comment 626A73
fish_color_selection --background=E6B450 --bold

View File

@@ -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 --underline=curly
fish_color_error F51818
fish_color_escape 4CBF99
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error FF3333
fish_color_escape 95E6CB
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error F22
fish_color_escape 0AA
fish_color_history_current 0AA
fish_color_host normal

View File

@@ -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 --underline=curly
fish_color_error brred
fish_color_escape brcyan
fish_color_history_current --bold
fish_color_host normal

View File

@@ -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);
initial_query(&blocking_query, streams.out, None);
if continuous_mode {
streams.err.append(L!("\n"));

View File

@@ -28,7 +28,6 @@
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;
@@ -220,14 +219,12 @@ 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();
@@ -247,7 +244,7 @@ fn read_interactive(
let mline = {
let _interactive = parser.push_scope(|s| s.is_interactive = true);
reader_readline(parser, NonZeroUsize::try_from(nchars).ok())
reader_readline(parser, nchars)
};
terminal_protocols_disable_ifn();
if let Some(line) = mline {

View File

@@ -86,13 +86,6 @@ 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
@@ -131,7 +124,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.unwrap_or_default(),
specified_face.style,
));
if specified_face.fg.is_some() && outp.contents().is_empty() {

View File

@@ -1,3 +1,5 @@
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};
@@ -9,9 +11,6 @@
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)),* $(,)?) => {
@@ -38,7 +37,7 @@ fn to_wstr(self) -> &'static wstr {
use StatusCmd::*;
#[derive(Clone, Copy)]
pub(in crate::builtins::status) enum StatusCmd {
enum StatusCmd {
STATUS_CURRENT_CMD = 1,
STATUS_BASENAME,
STATUS_DIRNAME,
@@ -63,8 +62,6 @@ pub(in crate::builtins::status) enum StatusCmd {
STATUS_BUILDINFO,
STATUS_GET_FILE,
STATUS_LIST_FILES,
STATUS_XTGETTCAP,
STATUS_XTVERSION,
}
str_enum!(
@@ -99,8 +96,6 @@ pub(in crate::builtins::status) 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.
@@ -532,12 +527,6 @@ 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!(
@@ -725,9 +714,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
| STATUS_FEATURES
| STATUS_TEST_FEATURE
| STATUS_GET_FILE
| STATUS_LIST_FILES
| STATUS_XTGETTCAP
| STATUS_XTVERSION => {
| STATUS_LIST_FILES => {
unreachable!("")
}
}

View File

@@ -1,166 +0,0 @@
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(())
}

View File

@@ -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, SpecifiedTextFace, TextFace, UnderlineStyle};
use crate::text_face::{parse_text_face, TextFace, UnderlineStyle};
use crate::threads::assert_is_background_thread;
use crate::tokenizer::{variable_assignment_equals_pos, PipeOrRedir};
use crate::wchar::{wstr, WString, L};
@@ -140,16 +140,12 @@ pub(crate) fn resolve_spec_uncached(
vars: &dyn Environment,
) -> TextFace {
let resolve_role = |role| {
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()
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)
};
let mut face = resolve_role(highlight.foreground);
@@ -166,8 +162,7 @@ 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).unwrap_or_default();
let valid_path_face = parse_text_face_for_highlight(&valid_path_var);
// 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() {
@@ -186,21 +181,19 @@ 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) -> Option<TextFace> {
pub(crate) fn parse_text_face_for_highlight(var: &EnvVar) -> TextFace {
let face = parse_text_face(var.as_list());
(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,
}
})
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,
}
}
fn command_is_valid(

View File

@@ -18,7 +18,10 @@
KittyKeyboardProgressiveEnhancementsDisable, KittyKeyboardProgressiveEnhancementsEnable,
ModifyOtherKeysDisable, ModifyOtherKeysEnable,
};
use crate::terminal::{Capability, Output, Outputter, KITTY_KEYBOARD_SUPPORTED, XTVERSION};
use crate::terminal::{
Capability, Output, Outputter, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED,
SCROLL_FORWARD_TERMINFO_CODE,
};
use crate::threads::{iothread_port, is_main_thread};
use crate::universal_notifier::default_notifier;
use crate::wchar::{encode_byte_to_char, prelude::*};
@@ -29,7 +32,6 @@
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;
@@ -325,8 +327,6 @@ 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,21 +758,15 @@ pub fn function_set_status(&mut self, status: bool) {
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub enum CursorPositionQuery {
MouseLeft(ViewportPosition),
ScrollbackPush,
}
#[derive(Debug)]
pub struct XtgettcapQuery {
pub terminfo_code: Vec<u8>,
pub result: Rc<RelaxedAtomicBool>,
}
#[derive(Debug)]
#[derive(Eq, PartialEq)]
pub enum TerminalQuery {
PrimaryDeviceAttribute(Option<XtgettcapQuery>),
PrimaryDeviceAttribute,
CursorPositionReport(CursorPositionQuery),
}
@@ -938,8 +932,6 @@ 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;
}
@@ -967,7 +959,10 @@ fn parse_escape_sequence(
assert!(buffer.len() <= 2);
let recursive_invocation = buffer.len() == 2;
let Some(next) = self.try_readb(buffer) else {
return Some(KeyEvent::from_raw(key::Escape));
if !self.paste_is_buffering() {
return Some(KeyEvent::from_raw(key::Escape));
}
return None;
};
let invalid = KeyEvent::from_raw(key::Invalid);
if recursive_invocation && next == b'\x1b' {
@@ -1228,7 +1223,6 @@ 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,
));
@@ -1375,12 +1369,13 @@ 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: {}", xtversion)
format!(
"Received XTVERSION response: {}",
str2wcstring(&buffer[4..buffer.len()])
)
);
XTVERSION.get_or_init(|| xtversion);
None
}
@@ -1437,17 +1432,9 @@ fn parse_dcs(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
format!("Received XTGETTCAP response: {}", str2wcstring(&key))
);
}
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"
);
}
}
if key == SCROLL_FORWARD_TERMINFO_CODE.as_bytes() {
SCROLL_FORWARD_SUPPORTED.store(true);
FLOG!(reader, "Scroll forward is supported");
}
return None;
}

View File

@@ -772,10 +772,6 @@ 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;

View File

@@ -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, CharEvent, TerminalQuery};
use crate::input_common::{terminal_protocols_disable_ifn, TerminalQuery};
use crate::io::IoChain;
use crate::job_group::MaybeJobId;
use crate::operation_context::{OperationContext, EXPANSION_LIMIT_DEFAULT};
@@ -25,7 +25,6 @@
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;
@@ -38,7 +37,6 @@
#[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;
@@ -444,10 +442,6 @@ 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 {
@@ -466,8 +460,6 @@ 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) {

View File

@@ -85,7 +85,6 @@
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;
@@ -135,7 +134,9 @@
QueryCursorPosition, QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute,
QueryXtgettcap, QueryXtversion,
};
use crate::terminal::{Capability, KITTY_KEYBOARD_SUPPORTED};
use crate::terminal::{
Capability, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE,
};
use crate::termsize::{termsize_invalidate_tty, termsize_last, termsize_update};
use crate::text_face::parse_text_face;
use crate::text_face::TextFace;
@@ -237,25 +238,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 = querying_allowed().then(|| {
let query = if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() {
None
} else {
// 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);
TerminalQuery::PrimaryDeviceAttribute(None)
});
Some(TerminalQuery::PrimaryDeviceAttribute)
};
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>>>>);
@@ -272,7 +273,7 @@ pub fn reader_in_interactive_read() -> bool {
reader_data_stack()
.iter()
.rev()
.any(|reader| reader.conf.in_builtin_read)
.any(|reader| reader.conf.exit_on_interrupt)
}
/// Access the top level reader data.
@@ -337,9 +338,6 @@ 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,
@@ -349,9 +347,6 @@ 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,
@@ -696,7 +691,6 @@ 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) {
@@ -888,8 +882,6 @@ 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) {
@@ -1069,7 +1061,8 @@ 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: Option<NonZeroUsize>) -> Option<WString> {
pub fn reader_readline(parser: &Parser, nchars: usize) -> Option<WString> {
let nchars = NonZeroUsize::try_from(nchars).ok();
let data = current_data().unwrap();
let mut reader = Reader { parser, data };
reader.readline(nchars)
@@ -2209,77 +2202,73 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
initial_query(
&self.parser.blocking_query,
&mut BufferedOutputter::new(Outputter::stdoutput()),
Some(self.parser.vars()),
);
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;
// 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.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.prompt_ok {
if self.conf.transient_prompt {
self.exec_prompt(true, true);
}
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 _ {
@@ -2441,12 +2430,10 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
});
// If we ran `exit` anywhere, exit.
self.exit_loop_requested |= self.parser.libdata().exit_current_script;
self.exit_loop_requested =
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(());
}
@@ -2520,7 +2507,6 @@ 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![]);
@@ -2545,31 +2531,16 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
CharEvent::QueryResponse(query_response) => {
match query_response {
QueryResponseEvent::PrimaryDeviceAttribute => {
let mut query = self.blocking_query();
let Some(TerminalQuery::PrimaryDeviceAttribute(tcap_query)) = &*query
else {
if *self.blocking_query() != Some(TerminalQuery::PrimaryDeviceAttribute) {
// 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() {
@@ -2596,22 +2567,29 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
}
}
pub(crate) fn query_xtgettcap(out: &mut impl Output, cap: &[u8]) {
let command = QueryXtgettcap(cap);
fn send_xtgettcap_query(out: &mut impl Output, cap: &'static str) {
if should_flog!(reader) {
let mut tmp = Vec::<u8>::new();
tmp.write_command(command.clone());
tmp.write_command(QueryXtgettcap(cap));
FLOG!(
reader,
sprintf!(
"Sending XTGETTCAP request for %s: %s",
str2wcstring(cap),
escape(&str2wcstring(&tmp))
)
format!("Sending XTGETTCAP request for {}: {:?}", cap, 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
out.write_command(command);
send_xtgettcap_query(out, SCROLL_FORWARD_TERMINFO_CODE);
out.write_command(DecrstAlternateScreenBuffer); // disable alternative screen buffer
}
@@ -2710,9 +2688,7 @@ 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).unwrap_or_default(),
);
outp.set_text_face(parse_text_face_for_highlight(&fish_color_cancel));
}
outp.write_wstr(L!("^C"));
outp.reset_text_face();
@@ -3839,6 +3815,9 @@ 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);
@@ -3849,7 +3828,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!(
@@ -4470,6 +4449,8 @@ 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.
@@ -4547,7 +4528,6 @@ 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"))
@@ -4560,7 +4540,6 @@ 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);

View File

@@ -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(&'a [u8]),
QueryXtgettcap(&'static str),
DecsetAlternateScreenBuffer,
DecrstAlternateScreenBuffer,
@@ -139,6 +139,16 @@ fn write_command(&mut self, cmd: TerminalCommand<'_>) -> bool
assert!(!matches!(cmd, CursorDown));
return false;
}
if matches!(
cmd,
Osc0WindowTitle(_)
| Osc133CommandStart(_)
| Osc133PromptStart
| Osc133CommandFinished(_)
| QueryKittyKeyboardProgressiveEnhancements
) {
return false;
}
let ti = maybe_terminfo;
fn write(out: &mut impl Output, sequence: &'static [u8]) -> bool {
out.write_bytes(sequence);
@@ -227,7 +237,8 @@ pub(crate) enum Capability {
pub(crate) static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _);
pub(crate) static XTVERSION: OnceCell<WString> = OnceCell::new();
pub(crate) static SCROLL_FORWARD_SUPPORTED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
pub(crate) static SCROLL_FORWARD_TERMINFO_CODE: &str = "indn";
pub(crate) fn use_terminfo() -> bool {
!future_feature_flags::test(FeatureFlag::ignore_terminfo) && TERM.lock().unwrap().is_some()
@@ -348,16 +359,16 @@ fn cursor_move(out: &mut impl Output, direction: CardinalDirection, steps: usize
true
}
fn query_xtgettcap(out: &mut impl Output, cap: &[u8]) -> bool {
fn query_xtgettcap(out: &mut impl Output, cap: &str) -> bool {
write_to_output!(out, "\x1bP+q{}\x1b\\", DisplayAsHex(cap));
true
}
struct DisplayAsHex<'a>(&'a [u8]);
struct DisplayAsHex<'a>(&'a str);
impl<'a> std::fmt::Display for DisplayAsHex<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for byte in self.0 {
for byte in self.0.bytes() {
write!(f, "{:x}", byte)?;
}
Ok(())
@@ -398,6 +409,7 @@ 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
}

View File

@@ -111,12 +111,6 @@ 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 {
@@ -137,12 +131,12 @@ pub fn new(fg: Color, bg: Color, underline_color: Color, style: TextStyling) ->
}
}
#[derive(Default, Eq, PartialEq)]
#[derive(Default)]
pub(crate) struct SpecifiedTextFace {
pub(crate) fg: Option<Color>,
pub(crate) bg: Option<Color>,
pub(crate) underline_color: Option<Color>,
pub(crate) style: Option<TextStyling>,
pub(crate) style: TextStyling,
}
pub(crate) fn parse_text_face(arguments: &[WString]) -> SpecifiedTextFace {
@@ -173,7 +167,6 @@ pub(crate) enum ParsedArgs<'argarray, 'args> {
pub(crate) enum ParseError<'args> {
MissingOptArg,
MultipleTracking,
UnknownColor(&'args wstr),
UnknownUnderlineStyle(&'args wstr),
UnknownOption(usize),
@@ -196,7 +189,6 @@ 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];
@@ -218,7 +210,6 @@ 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() {
@@ -228,12 +219,6 @@ 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);
@@ -312,6 +297,6 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
fg,
bg,
underline_color,
style: (style != TextStyling::default()).then_some(style),
style,
}))
}

View File

@@ -1,51 +0,0 @@
#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|

View File

@@ -180,10 +180,7 @@ class SpawnedProc(object):
self.spawn.delaybeforesend = None
self.prompt_counter = 0
if env.get("TERM") != "dumb":
self.send_primary_device_attribute()
def send_primary_device_attribute(self):
self.spawn.send("\x1b[?123c")
self.spawn.send("\x1b[?123c") # Primary Device Attribute
def time_since_first_message(self):
"""Return a delta in seconds since the first message, or 0 if this is the first."""

View File

@@ -1,32 +0,0 @@
#!/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")