Add --color option for some builtins

Fixes #9716

Closes #12252
This commit is contained in:
phanium
2026-01-01 23:39:37 +08:00
committed by Johannes Altmanninger
parent 5d8f7801f7
commit c23a4cbd9f
32 changed files with 301 additions and 75 deletions

View File

@@ -57,6 +57,7 @@ Scripting improvements
- New :ref:`status language <status-language>` command allows showing and modifying language settings for fish messages without having to modify environment variables.
- When using a noninteractive fish instance to compute completions, ``commandline --cursor`` works as expected instead of throwing an error (:issue:`11993`).
- :envvar:`fish_trace` can now be set to ``all`` to also trace execution of key bindings, event handlers as well as prompt and title functions.
- The ``abbr``, ``bind``, ``complete``, ``functions``, ``history`` and ``type`` commands now support a ``--color`` option to control syntax highlighting in their output. Valid values are ``auto`` (default), ``always``, or ``never``.
Interactive improvements
------------------------

View File

@@ -10,7 +10,7 @@ Synopsis
[--set-cursor[=MARKER]] ([-f | --function FUNCTION] | EXPANSION)
abbr --erase [ [-c | --command COMMAND]... ] NAME ...
abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD
abbr --show
abbr [--show] [--color WHEN]
abbr --list
abbr --query NAME ...
@@ -75,7 +75,6 @@ With **--set-cursor=MARKER**, the cursor is moved to the first occurrence of **M
With **-f FUNCTION** or **--function FUNCTION**, **FUNCTION** is treated as the name of a fish function instead of a literal replacement. When the abbreviation matches, the function will be called with the matching token as an argument. If the function's exit status is 0 (success), the token will be replaced by the function's output; otherwise the token will be left unchanged. No **EXPANSION** may be given separately.
Examples
########

View File

@@ -6,8 +6,8 @@ Synopsis
.. synopsis::
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] KEYS COMMAND ...
bind [(-M | --mode) MODE] [--preset] [--user] [KEYS]
bind [-a | --all] [--preset] [--user]
bind [(-M | --mode) MODE] [--preset] [--user] [--color WHEN] [KEYS]
bind [-a | --all] [--preset] [--user] [--color WHEN]
bind (-f | --function-names)
bind (-K | --key-names)
bind (-L | --list-modes)
@@ -104,6 +104,10 @@ The following options are available:
**-s** or **--silent**
Silences some of the error messages, including for unknown key names and unbound sequences.
**--color** *WHEN*
Controls when to use syntax highlighting colors when listing bindings.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -6,7 +6,7 @@ Synopsis
.. synopsis::
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS]
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS] [--color WHEN]
complete (-C | --do-complete) [--escape] STRING
Description
@@ -74,6 +74,10 @@ The following options are available:
**--escape**
When used with ``-C``, escape special characters in completions.
**--color** *WHEN*
Controls when to use syntax highlighting colors when printing completions.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -6,8 +6,8 @@ Synopsis
.. synopsis::
functions [-a | --all] [-n | --names]
functions [-D | --details] [-v] FUNCTION
functions [-a | --all] [-n | --names] [--color WHEN]
functions [-D | --details] [-v] [--color WHEN] FUNCTION
functions -c OLDNAME NEWNAME
functions -d DESCRIPTION FUNCTION
functions [-e | -q] FUNCTION ...
@@ -60,6 +60,10 @@ The following options are available:
**-t** or **--handlers-type** *TYPE*
Show all event handlers matching the given *TYPE*.
**--color** *WHEN*
Controls when to use syntax highlighting colors when printing function definitions.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -75,6 +75,10 @@ These flags can appear before or immediately after one of the sub-commands liste
**-R** or **--reverse**
Causes the history search results to be ordered oldest to newest. Which is the order used by most shells. The default is newest to oldest.
**--color** *WHEN*
Controls when to use syntax highlighting colors for the history entries.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help for this command.

View File

@@ -41,6 +41,10 @@ The following options are available:
**-q** or **--query**
Suppresses all output; this is useful when testing the exit status. For compatibility with old fish versions this is also **--quiet**.
**--color** *WHEN*
Controls when to use syntax highlighting colors when printing function definitions.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -523,6 +523,10 @@ msgstr ""
msgid "%s: Invalid type '%s'\n"
msgstr ""
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s: Ungültiger Wert für '--color'-Option: '%s'. Erwartet wird 'always', 'never' oder 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr ""
@@ -3616,6 +3620,9 @@ msgstr ""
msgid "View and pick from the sample themes"
msgstr ""
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr ""

View File

@@ -521,6 +521,10 @@ msgstr ""
msgid "%s: Invalid type '%s'\n"
msgstr ""
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr ""
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr ""
@@ -3614,6 +3618,9 @@ msgstr ""
msgid "View and pick from the sample themes"
msgstr ""
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr ""

View File

@@ -652,6 +652,10 @@ msgstr "%s : Lexème invalide « %s »\n"
msgid "%s: Invalid type '%s'\n"
msgstr ""
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s : Valeur invalide pour l'option '--color' : '%s'. Attendu 'always', 'never' ou 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr ""
@@ -3745,6 +3749,9 @@ msgstr ""
msgid "View and pick from the sample themes"
msgstr ""
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr ""

View File

@@ -517,6 +517,10 @@ msgstr ""
msgid "%s: Invalid type '%s'\n"
msgstr ""
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s: Nieprawidłowa wartość dla opcji '--color': '%s'. Oczekiwano 'always', 'never' lub 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr ""
@@ -3610,6 +3614,9 @@ msgstr ""
msgid "View and pick from the sample themes"
msgstr ""
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr ""

View File

@@ -522,6 +522,10 @@ msgstr ""
msgid "%s: Invalid type '%s'\n"
msgstr ""
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s: Valor inválido para a opção '--color': '%s'. Esperado 'always', 'never' ou 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr ""
@@ -3615,6 +3619,9 @@ msgstr ""
msgid "View and pick from the sample themes"
msgstr ""
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr ""

View File

@@ -518,6 +518,10 @@ msgstr ""
msgid "%s: Invalid type '%s'\n"
msgstr ""
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s: Ogiltigt värde för '--color'-alternativet: '%s'. Förväntade 'always', 'never' eller 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr ""
@@ -3611,6 +3615,9 @@ msgstr ""
msgid "View and pick from the sample themes"
msgstr ""
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr ""

View File

@@ -544,6 +544,10 @@ msgstr "%s: 无效记号 '%s'\n"
msgid "%s: Invalid type '%s'\n"
msgstr "%s: 无效类型 '%s'\n"
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s: 无效的 '--color' 选项值 '%s'。应为 'always'、'never' 或 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr "%s: 无效的宽度值 '%s'\n"
@@ -3643,6 +3647,9 @@ msgstr "查看并从示例提示中选择"
msgid "View and pick from the sample themes"
msgstr "查看并从示例主题中选择"
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr "将调试输出导向何处"

View File

@@ -517,6 +517,10 @@ msgstr "%s無效的詞元「%s」\n"
msgid "%s: Invalid type '%s'\n"
msgstr "%s無效的類型「%s」\n"
#, c-format
msgid "%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n"
msgstr "%s無效的 '--color' 選項值「%s」。應為 'always'、'never' 或 'auto'\n"
#, c-format
msgid "%s: Invalid width value '%s'\n"
msgstr "%s無效的寬度「%s」\n"
@@ -3618,6 +3622,9 @@ msgstr "檢視提示範本並從中挑選"
msgid "View and pick from the sample themes"
msgstr "檢視主題範本並從中挑選"
msgid "When to colorize output"
msgstr ""
msgid "Where to direct debug output to"
msgstr "除錯輸出要放到的地方"

View File

@@ -9,9 +9,12 @@ complete -c abbr -f -n $__fish_abbr_not_add_cond -l rename -d 'Rename an abbrevi
complete -c abbr -f -n $__fish_abbr_not_add_cond -s e -l erase -d 'Erase abbreviation' -xa '(abbr --list)'
complete -c abbr -f -n $__fish_abbr_not_add_cond -s s -l show -d 'Print all abbreviations'
complete -c abbr -f -n $__fish_abbr_not_add_cond -s l -l list -d 'Print all abbreviation names'
complete -c abbr -f -n $__fish_abbr_not_add_cond -l color -d 'When to colorize output' -xa 'always never auto'
complete -c abbr -f -n $__fish_abbr_not_add_cond -s h -l help -d Help
complete -c abbr -f -n $__fish_abbr_add_cond -s p -l position -a 'command anywhere' -d 'Expand only as a command, or anywhere' -x
complete -c abbr -f -n $__fish_abbr_add_cond -s f -l function -d 'Treat expansion argument as a fish function' -xa '(functions)'
complete -c abbr -f -n $__fish_abbr_add_cond -s r -l regex -d 'Match a regular expression' -x
complete -c abbr -f -n $__fish_abbr_add_cond -l set-cursor -d 'Position the cursor at % post-expansion'
complete -c abbr -f -n '__fish_seen_subcommand_from -s --show' -l color -d 'When to colorize output' -xa 'always never auto'

View File

@@ -25,6 +25,7 @@ complete -c bind -s L -l list-modes -d 'Display a list of defined bind modes'
complete -c bind -s s -l silent -d 'Operate silently'
complete -c bind -l preset -d 'Operate on preset bindings'
complete -c bind -l user -d 'Operate on user bindings'
complete -c bind -l color -d 'When to colorize output' -xa 'always never auto'
complete -c bind -n '__fish_bind_has_keys (commandline -pcx)' -a '(bind --function-names)' -d 'Function name' -x

View File

@@ -19,6 +19,7 @@ complete -c complete -l escape -d "Make -C escape special characters"
complete -c complete -s n -l condition -d "Completion only used if command has zero exit status" -x
complete -c complete -s w -l wraps -d "Inherit completions from specified command" -xa '(__fish_complete_command)'
complete -c complete -s k -l keep-order -d "Keep order of arguments instead of sorting alphabetically"
complete -c complete -l color -d "When to colorize output" -xa "always never auto"
# Deprecated options

View File

@@ -19,3 +19,4 @@ complete -c functions -s D -l details -d "Display information about the function
complete -c functions -s v -l verbose -d "Print more output"
complete -c functions -s H -l handlers -d "Show event handlers"
complete -c functions -s t -l handlers-type -d "Show event handlers matching the given type" -x -a "signal variable exit job-id generic"
complete -c functions -l color -d 'When to colorize output' -x -a 'always never auto'

View File

@@ -22,6 +22,8 @@ complete -c history -n '__fish_seen_subcommand_from search; or not __fish_seen_s
-s z -l null -d "Terminate entries with NUL character"
complete -c history -n '__fish_seen_subcommand_from search; or not __fish_seen_subcommand_from $__fish_history_all_commands' \
-s R -l reverse -d "Output the oldest results first" -x
complete -c history -n '__fish_seen_subcommand_from search; or not __fish_seen_subcommand_from $__fish_history_all_commands' \
-l color -d "When to colorize output" -xa "always never auto"
# We don't include a completion for the "save" subcommand because it should not be used
# interactively.

View File

@@ -6,6 +6,7 @@ complete -c type -s p -l path -d "Print path to command, or nothing if name is n
complete -c type -s P -l force-path -d "Print path to command"
complete -c type -s q -l query -l quiet -d "Check if something exists without output"
complete -c type -s s -l short -d "Don't print function definition"
complete -c type -l color -d "When to colorize output" -xa "always never auto"
complete -c type -a "(builtin -n)" -d Builtin
complete -c type -a "(functions -n)" -d Function

View File

@@ -6,7 +6,7 @@ function history --description "display or manipulate interactive command histor
set -l cmd history
set -l options --exclusive 'c,e,p' --exclusive 'S,D,M,V,X'
set -a options h/help c/contains e/exact p/prefix
set -a options C/case-sensitive R/reverse z/null 't/show-time=?' 'n#max'
set -a options C/case-sensitive R/reverse z/null 't/show-time=?' 'n#max' 'color='
# The following options are deprecated and will be removed in the next major release.
# Note that they do not have usable short flags.
set -a options S-search D-delete M-merge V-save X-clear
@@ -22,9 +22,12 @@ function history --description "display or manipulate interactive command histor
set -l show_time
set -l max_count
set -l search_mode
set -l color_opt
set -q _flag_max
set max_count -n$_flag_max
set color_opt --color=$_flag_color
set -q _flag_with_time
and set -l _flag_show_time $_flag_with_time
if set -q _flag_show_time[1]
@@ -78,7 +81,7 @@ function history --description "display or manipulate interactive command histor
# If the user hasn't preconfigured less with the $LESS environment variable,
# we do so to have it behave like cat if output fits on one screen.
if not set -qx LESS
set -fx LESS --quit-if-one-screen
set -fx LESS --quit-if-one-screen --RAW-CONTROL-CHARS
# Also set --no-init for less < v530, see #8157.
if type -q less; and test (less --version | string match -r 'less (\d+)')[2] -lt 530 2>/dev/null
set LESS $LESS --no-init
@@ -87,9 +90,15 @@ function history --description "display or manipulate interactive command histor
not set -qx LV # ask the pager lv not to strip colors
and set -fx LV -c
builtin history search $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv | $pager
if contains -- "$color_opt" '' '--color=auto'
and test "$pager" = less
and string match -rq -- '^(-\w*R|--RAW-CONTROL-CHARS$)' $LESS
set color_opt --color=always
end
builtin history search $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv | $pager
else
builtin history search $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
builtin history search $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
end
case delete # interactively delete history
@@ -100,15 +109,15 @@ function history --description "display or manipulate interactive command histor
end
if test "$search_mode" = --exact
builtin history delete $search_mode $_flag_case_sensitive -- $searchterm
builtin history delete $color_opt $search_mode $_flag_case_sensitive -- $searchterm
builtin history save
return
end
# TODO: Fix this so that requesting history entries with a timestamp works:
# set -l found_items (builtin history search $search_mode $show_time -- $argv)
# set -l found_items (builtin history search $color_opt $search_mode $show_time -- $argv)
set -l found_items
set found_items (builtin history search $search_mode $_flag_case_sensitive --null -- $searchterm | string split0)
set found_items (builtin history search $color_opt $search_mode $_flag_case_sensitive --null -- $searchterm | string split0)
if set -q found_items[1]
set -l found_items_count (count $found_items)
for i in (seq $found_items_count)
@@ -132,7 +141,7 @@ function history --description "display or manipulate interactive command histor
if test "$choice" = all
printf "Deleting all matching entries!\n"
for item in $found_items
builtin history delete --exact --case-sensitive -- $item
builtin history delete $color_opt --exact --case-sensitive -- $item
end
builtin history save
return
@@ -173,15 +182,15 @@ function history --description "display or manipulate interactive command histor
echo Deleting choices: $choices
for x in $choices
printf "Deleting history entry %s: \"%s\"\n" $x $found_items[$x]
builtin history delete --exact --case-sensitive -- "$found_items[$x]"
builtin history delete $color_opt --exact --case-sensitive -- "$found_items[$x]"
end
builtin history save
end
case save # save our interactive command history to the persistent history
builtin history save $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
builtin history save $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
case merge # merge the persistent interactive command history with our history
builtin history merge $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
builtin history merge $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
case clear # clear the interactive command history
if test -n "$search_mode"
or set -q show_time[1]
@@ -197,13 +206,13 @@ function history --description "display or manipulate interactive command histor
read --local --prompt "echo 'Are you sure you want to clear history? (yes/no) '" choice
or return $status
if test "$choice" = yes
builtin history clear $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
builtin history clear $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
and printf (_ "Command history cleared!\n")
else
printf (_ "You did not say 'yes' so I will not clear your command history\n")
end
case clear-session # clears only session
builtin history clear-session $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
builtin history clear-session $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
and printf (_ "Command history for session cleared!\n")
case append
set -l newitem $argv
@@ -212,7 +221,7 @@ function history --description "display or manipulate interactive command histor
or return $status
end
builtin history append $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
builtin history append $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
case '*'
printf "%s: unexpected subcommand '%s'\n" $cmd $hist_cmd
return 2

View File

@@ -1,7 +1,8 @@
use super::prelude::*;
use crate::abbrs::{self, Abbreviation, Position};
use crate::common::{EscapeStringStyle, escape, escape_string, valid_func_name};
use crate::common::{EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_func_name};
use crate::env::{EnvMode, EnvStackSetResult};
use crate::highlight::highlight_and_colorize;
use crate::parser::ParserEnvSetMode;
use crate::re::{regex_make_anchored, to_boxed_chars};
use pcre2::utf32::{Regex, RegexBuilder};
@@ -22,6 +23,7 @@ struct Options {
position: Option<Position>,
set_cursor_marker: Option<WString>,
args: Vec<WString>,
color: ColorEnabled,
}
impl Options {
@@ -124,7 +126,7 @@ fn join(list: &[&wstr], sep: &wstr) -> WString {
}
// Print abbreviations in a fish-script friendly way.
fn abbr_show(streams: &mut IoStreams) -> BuiltinResult {
fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &Parser) -> BuiltinResult {
let style = EscapeStringStyle::Script(Default::default());
abbrs::with_abbrs(|abbrs| {
@@ -173,7 +175,15 @@ fn abbr_show(streams: &mut IoStreams) -> BuiltinResult {
));
}
result.push('\n');
streams.out.append(&result);
if opts.color.enabled(streams) {
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
&result,
&parser.context(),
parser.vars(),
)));
} else {
streams.out.append(&result);
}
}
});
@@ -508,6 +518,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
wopt(L!("global"), ArgType::NoArgument, 'g'),
wopt(L!("universal"), ArgType::NoArgument, 'U'),
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
];
let mut opts = Options::default();
@@ -614,6 +625,9 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
COLOR_OPTION_CHAR => {
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
_ => {
panic!("unexpected retval from wgeopter.next()");
}
@@ -632,7 +646,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
return abbr_add(&opts, streams);
};
if opts.show {
return abbr_show(streams);
return abbr_show(&opts, streams, parser);
};
if opts.list {
return abbr_list(&opts, streams);

View File

@@ -4,12 +4,11 @@
use crate::common::{
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
};
use crate::highlight::{colorize, highlight_shell};
use crate::highlight::highlight_and_colorize;
use crate::input::{InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings};
use crate::key::{
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
};
use crate::nix::isatty;
use std::sync::MutexGuard;
const DEFAULT_BIND_MODE: &wstr = L!("default");
@@ -32,6 +31,7 @@ struct Options {
mode: c_int,
bind_mode: WString,
sets_bind_mode: Option<WString>,
color: ColorEnabled,
}
impl Options {
@@ -49,6 +49,7 @@ fn new() -> Options {
mode: BIND_INSERT,
bind_mode: DEFAULT_BIND_MODE.to_owned(),
sets_bind_mode: None,
color: ColorEnabled::default(),
}
}
}
@@ -153,11 +154,12 @@ fn list_one(
}
out.push('\n');
if !streams.out_is_redirected && isatty(libc::STDOUT_FILENO) {
let mut colors = Vec::new();
highlight_shell(&out, &mut colors, &parser.context(), false, None);
let colored = colorize(&out, &colors, parser.vars());
streams.out.append(&bytes2wcstring(&colored));
if self.opts.color.enabled(streams) {
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
&out,
&parser.context(),
parser.vars(),
)));
} else {
streams.out.append(&out);
}
@@ -408,7 +410,7 @@ fn parse_cmd_opts(
) -> BuiltinResult {
let cmd = argv[0];
let short_options = L!("aehkKfM:Lm:s");
const long_options: &[WOption] = &[
let long_options: &[WOption] = &[
wopt(L!("all"), NoArgument, 'a'),
wopt(L!("erase"), NoArgument, 'e'),
wopt(L!("function-names"), NoArgument, 'f'),
@@ -421,9 +423,10 @@ fn parse_cmd_opts(
wopt(L!("sets-mode"), RequiredArgument, 'm'),
wopt(L!("silent"), NoArgument, 's'),
wopt(L!("user"), NoArgument, 'u'),
wopt(L!("color"), RequiredArgument, COLOR_OPTION_CHAR),
];
let mut check_mode_name = |mode_name: &wstr| -> Result<(), ErrorCode> {
let check_mode_name = |streams: &mut IoStreams, mode_name: &wstr| -> Result<(), ErrorCode> {
if !valid_var_name(mode_name) {
streams.err.append(&wgettext_fmt!(
BUILTIN_ERR_BIND_MODE,
@@ -457,13 +460,13 @@ fn parse_cmd_opts(
}
'M' => {
let applicable_mode = w.woptarg.unwrap();
check_mode_name(applicable_mode)?;
check_mode_name(streams, applicable_mode)?;
opts.bind_mode = applicable_mode.to_owned();
opts.bind_mode_given = true;
}
'm' => {
let new_mode = w.woptarg.unwrap();
check_mode_name(new_mode)?;
check_mode_name(streams, new_mode)?;
opts.sets_bind_mode = Some(new_mode.to_owned());
}
'p' => {
@@ -487,6 +490,9 @@ fn parse_cmd_opts(
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
COLOR_OPTION_CHAR => {
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
_ => {
panic!("unexpected retval from WGetopter")
}

View File

@@ -1,9 +1,7 @@
use super::prelude::*;
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
use crate::highlight::colorize;
use crate::highlight::highlight_shell;
use crate::nix::isatty;
use crate::highlight::highlight_and_colorize;
use crate::operation_context::OperationContext;
use crate::parse_constants::ParseErrorList;
use crate::parse_util::parse_util_detect_errors_in_argument_list;
@@ -18,7 +16,6 @@
complete_remove, complete_remove_all,
},
};
use libc::STDOUT_FILENO;
// builtin_complete_* are a set of rather silly looping functions that make sure that all the proper
// combinations of complete_add or complete_remove get called. This is needed since complete allows
@@ -221,16 +218,20 @@ fn builtin_complete_remove(
}
}
fn builtin_complete_print(cmd: &wstr, streams: &mut IoStreams, parser: &Parser) {
fn builtin_complete_print(
cmd: &wstr,
streams: &mut IoStreams,
parser: &Parser,
color: ColorEnabled,
) {
let repr = complete_print(cmd);
// colorize if interactive
if !streams.out_is_redirected && isatty(STDOUT_FILENO) {
let mut colors = vec![];
highlight_shell(&repr, &mut colors, &parser.context(), false, None);
streams
.out
.append(&bytes2wcstring(&colorize(&repr, &colors, parser.vars())));
if color.enabled(streams) {
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
&repr,
&parser.context(),
parser.vars(),
)));
} else {
streams.out.append(&repr);
}
@@ -259,9 +260,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
let mut wrap_targets = vec![];
let mut preserve_order = false;
let mut unescape_output = true;
let mut color = ColorEnabled::default();
const short_options: &wstr = L!("a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
const long_options: &[WOption] = &[
let short_options: &wstr = L!("a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
let long_options: &[WOption] = &[
wopt(L!("exclusive"), ArgType::NoArgument, 'x'),
wopt(L!("no-files"), ArgType::NoArgument, 'f'),
wopt(L!("force-files"), ArgType::NoArgument, 'F'),
@@ -282,6 +284,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("keep-order"), ArgType::NoArgument, 'k'),
wopt(L!("escape"), ArgType::NoArgument, OPT_ESCAPE),
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
];
let mut have_x = false;
@@ -401,6 +404,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
COLOR_OPTION_CHAR => {
color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
_ => panic!("unexpected retval from WGetopter"),
}
}
@@ -584,10 +590,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
// No arguments that would add or remove anything specified, so we print the definitions of
// all matching completions.
if cmd_to_complete.is_empty() {
builtin_complete_print(L!(""), streams, parser);
builtin_complete_print(L!(""), streams, parser, color);
} else {
for cmd in cmd_to_complete {
builtin_complete_print(&cmd, streams, parser);
builtin_complete_print(&cmd, streams, parser, color);
}
}
} else {

View File

@@ -6,8 +6,7 @@
use crate::common::{EscapeFlags, EscapeStringStyle};
use crate::event::{self};
use crate::function;
use crate::highlight::colorize;
use crate::highlight::highlight_shell;
use crate::highlight::highlight_and_colorize;
use crate::parse_util::apply_indents;
use crate::parse_util::parse_util_compute_indents;
use crate::parser_keywords::parser_keywords_is_reserved;
@@ -25,6 +24,7 @@ struct FunctionsCmdOpts<'args> {
no_metadata: bool,
verbose: bool,
handlers: bool,
color: ColorEnabled,
handlers_type: Option<&'args wstr>,
description: Option<&'args wstr>,
}
@@ -46,6 +46,7 @@ struct FunctionsCmdOpts<'args> {
wopt(L!("verbose"), ArgType::NoArgument, 'v'),
wopt(L!("handlers"), ArgType::NoArgument, 'H'),
wopt(L!("handlers-type"), ArgType::RequiredArgument, 't'),
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
];
/// Parses options to builtin function, populating opts.
@@ -79,6 +80,9 @@ fn parse_cmd_opts<'args>(
opts.handlers = true;
opts.handlers_type = Some(w.woptarg.unwrap());
}
COLOR_OPTION_CHAR => {
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
@@ -278,7 +282,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if opts.list || args.is_empty() {
let mut names = function::get_names(opts.show_hidden, parser.vars());
names.sort();
if streams.out_is_terminal() {
if opts.color.enabled(streams) {
let mut buff = WString::new();
let mut first: bool = true;
for name in names {
@@ -414,12 +418,12 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
def = apply_indents(&def, &parse_util_compute_indents(&def));
}
if streams.out_is_terminal() {
let mut colors = vec![];
highlight_shell(&def, &mut colors, &parser.context(), false, None);
streams
.out
.append(&bytes2wcstring(&colorize(&def, &colors, parser.vars())));
if opts.color.enabled(streams) {
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
&def,
&parser.context(),
parser.vars(),
)));
} else {
streams.out.append(&def);
}

View File

@@ -60,6 +60,7 @@ struct HistoryCmdOpts {
case_sensitive: bool,
null_terminate: bool,
reverse: bool,
color: ColorEnabled,
}
/// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to
@@ -82,6 +83,7 @@ struct HistoryCmdOpts {
wopt(L!("clear"), ArgType::NoArgument, '\x04'),
wopt(L!("merge"), ArgType::NoArgument, '\x05'),
wopt(L!("reverse"), ArgType::NoArgument, 'R'),
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
];
/// Remember the history subcommand and disallow selecting more than one history subcommand.
@@ -226,6 +228,9 @@ fn parse_cmd_opts(
}
w.remaining_text = L!("");
}
COLOR_OPTION_CHAR => {
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
_ => {
panic!("unexpected retval from WGetopter");
}
@@ -278,6 +283,8 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
match opts.hist_cmd {
HistCmd::None | HistCmd::Search => {
if !history.search(
parser,
streams,
opts.search_type
.unwrap_or(history::SearchType::ContainsGlob),
args,
@@ -287,7 +294,7 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
opts.null_terminate,
opts.reverse,
&parser.context().cancel_checker,
streams,
opts.color.enabled(streams),
) {
status = Err(STATUS_CMD_ERROR);
}

View File

@@ -1053,3 +1053,51 @@ pub fn builtin_break_continue(
};
Ok(SUCCESS)
}
/// Option character for --color flag
pub const COLOR_OPTION_CHAR: char = '\x10';
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColorEnabled {
#[default]
Auto,
Always,
Never,
}
impl TryFrom<&wstr> for ColorEnabled {
type Error = ();
fn try_from(s: &wstr) -> Result<Self, Self::Error> {
match s {
s if s == "auto" => Ok(ColorEnabled::Auto),
s if s == "always" => Ok(ColorEnabled::Always),
s if s == "never" => Ok(ColorEnabled::Never),
_ => Err(()),
}
}
}
impl ColorEnabled {
pub fn enabled(&self, streams: &crate::io::IoStreams) -> bool {
match self {
ColorEnabled::Always => true,
ColorEnabled::Never => false,
ColorEnabled::Auto => streams.out_is_terminal(),
}
}
pub fn parse_from_opt(
streams: &mut IoStreams,
cmd: &wstr,
arg: &wstr,
) -> Result<Self, ErrorCode> {
Self::try_from(arg).map_err(|()| {
streams.err.append(&wgettext_fmt!(
"%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n",
cmd,
arg
));
STATUS_INVALID_ARGS
})
}
}

View File

@@ -1,8 +1,7 @@
use super::prelude::*;
use crate::common::bytes2wcstring;
use crate::function;
use crate::highlight::{colorize, highlight_shell};
use crate::highlight::highlight_and_colorize;
use crate::parse_util::{apply_indents, parse_util_compute_indents};
use crate::path::{path_get_path, path_get_paths};
@@ -15,6 +14,7 @@ struct type_cmd_opts_t {
path: bool,
force_path: bool,
query: bool,
color: ColorEnabled,
}
pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
@@ -34,6 +34,7 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
wopt(L!("force-path"), ArgType::NoArgument, 'P'),
wopt(L!("query"), ArgType::NoArgument, 'q'),
wopt(L!("quiet"), ArgType::NoArgument, 'q'),
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
];
let mut w = WGetopter::new(shortopts, longopts, argv);
@@ -68,6 +69,9 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
COLOR_OPTION_CHAR => {
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
_ => {
panic!("unexpected retval from wgeopter.next()");
}
@@ -143,17 +147,12 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
def = apply_indents(&def, &parse_util_compute_indents(&def));
}
if streams.out_is_terminal() {
let mut color = vec![];
highlight_shell(
if opts.color.enabled(streams) {
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
&def,
&mut color,
&parser.context(),
/*io_ok=*/ false,
/*cursor=*/ None,
);
let col = bytes2wcstring(&colorize(&def, &color, parser.vars()));
streams.out.append(&col);
parser.vars(),
)));
} else {
streams.out.append(&def);
}

View File

@@ -106,6 +106,22 @@ pub fn highlight_shell(
*color = highlighter.highlight();
}
pub fn highlight_and_colorize(
text: &wstr,
ctx: &OperationContext<'_>,
vars: &dyn Environment,
) -> Vec<u8> {
let mut colors = Vec::new();
highlight_shell(
text,
&mut colors,
ctx,
/*io_ok=*/ false,
/*cursor=*/ None,
);
colorize(text, &colors, vars)
}
/// highlight_color_resolver_t resolves highlight specs (like "a command") to actual RGB colors.
/// It maintains a cache with no invalidation mechanism. The lifetime of these should typically be
/// one screen redraw.

View File

@@ -50,12 +50,14 @@
fds::wopen_cloexec,
flog::{flog, flogf},
fs::fsync,
highlight::highlight_and_colorize,
history::file::{HistoryFile, RawHistoryFile},
io::IoStreams,
localization::wgettext_fmt,
operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext},
parse_constants::{ParseTreeFlags, StatementDecoration},
parse_util::{parse_util_detect_errors, parse_util_unescape_wildcards},
parser::Parser,
path::{path_get_config, path_get_data, path_is_valid},
prelude::*,
threads::assert_is_background_thread,
@@ -1342,6 +1344,8 @@ pub fn save(&self) {
#[allow(clippy::too_many_arguments)]
pub fn search(
self: &Arc<Self>,
parser: &Parser,
streams: &mut IoStreams,
search_type: SearchType,
search_args: &[&wstr],
show_time_format: Option<&str>,
@@ -1350,7 +1354,7 @@ pub fn search(
null_terminate: bool,
reverse: bool,
cancel_check: &CancelChecker,
streams: &mut IoStreams,
color_enabled: bool,
) -> bool {
let mut remaining = max_items;
let mut collected = Vec::new();
@@ -1362,7 +1366,17 @@ pub fn search(
return ControlFlow::Break(());
}
remaining -= 1;
let formatted_record = format_history_record(item, show_time_format, null_terminate);
let mut formatted_record =
format_history_record(item, show_time_format, null_terminate);
if color_enabled {
formatted_record = bytes2wcstring(&highlight_and_colorize(
&formatted_record,
&parser.context(),
parser.vars(),
));
}
if reverse {
// We need to collect this for later.
collected.push(formatted_record);

View File

@@ -234,3 +234,21 @@ functions --all=arg
# CHECKERR: functions: --all=arg: option does not take an argument
echo $status
# CHECK: 2
# Test --color option
function test_color_option
echo hello
end
functions --color=invalid
# CHECKERR: functions: Invalid value for '--color' option: 'invalid'. Expected 'always', 'never', or 'auto'
functions --no-details --color=never test_color_option
# CHECK: function test_color_option
# CHECK: echo hello
# CHECK: end
string escape (functions --no-details --color=always test_color_option)
# CHECK: function\ \e\[36mtest_color_option\e\[32m
# CHECK: \e\[m\ \ \ \ echo\ \e\[36mhello\e\[32m
# CHECK: \e\[mend\e\[32m\e\[m