diff --git a/.oclint b/.oclint index 86f1f7402..8bd3df49a 100644 --- a/.oclint +++ b/.oclint @@ -51,3 +51,17 @@ disable-rules: # and is therefore just noise. Disable this rule. # - InvertedLogic + # + # The idea behind the "double negative" rule is sound since constructs + # like "!!(var & flag)" should be written as "static_cast(var & + # flag)". Unfortunately this rule has way too many false positives; + # especially in the context of assert statements. So disable this rule. + # + - DoubleNegative + # + # Avoiding bitwise operators in a conditional is a good idea with one + # exception: testing whether a bit flag is set. Which happens to be the + # only time you'll see something like `if (j->flags & JOB_CONSTRUCTED)` + # in fish source. + # + - BitwiseOperatorInConditional diff --git a/CHANGELOG.md b/CHANGELOG.md index a4911479a..4e56b963c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,16 +7,16 @@ - `history search` is now case-insensitive by default (which also affects `history delete`) (#3236). - `history delete` now correctly handles multiline commands (#31). - Vi-style bindings no longer include all of the default emacs-style bindings; instead, they share some definitions (#3068). -- If there is no locale set in the environment, various known system configuration files will be checked for a default, otherwise forcing en_US-UTF.8 (#277). +- If there is no locale set in the environment, various known system configuration files will be checked for a default. If no locale can be found, `en_US-UTF.8` will be used (#277). - A number followed by a caret (e.g. `5^`) is no longer treated as a redirection (#1873). - The `$version` special variable can be overwritten, so that it can be used for other purposes if required. ## Notable fixes and improvements - The `fish_realpath` builtin has been renamed to `realpath` and made compatible with GNU `realpath` when run without arguments (#3400). It is used only for systems without a `realpath` or `grealpath` utility (#3374). -- `fish_indent` can now read from named files, rather than just standard input (#3037). +- Improved color handling on terminals/consoles with 8-16 colors, particularly the use of bright named color (#3176, #3260). +- `fish_indent` can now read from files given as arguments, rather than just standard input (#3037). - Fuzzy tab completions behave in a less surprising manner (#3090, #3211). - `jobs` should only print its header line once (#3127). -- Improved color handling on terminals with limited colors (#3176, #3260). - Wildcards in redirections are highlighted appropriately (#2789). - Suggestions will be offered more often, like after removing characters (#3069). - `history --merge` now correctly interleaves items in chronological order (#2312). diff --git a/Makefile.in b/Makefile.in index ee373324d..747be9d9e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -386,7 +386,7 @@ toc.txt: $(HDR_FILES:index.hdr=index.hdr.in) | show-SED @echo " SED $(em)$@$(sgr0)" $v rm -f toc.tmp $@ # Ugly hack to set the toc initial title for the main page - $v echo '- fish shell documentation - $FISH_BUILD_VERSION' > toc.tmp + $v echo '- fish shell documentation - $(FISH_BUILD_VERSION)' > toc.tmp # The first sed command captures the page name, followed by the description # The second sed command captures the command name \1 and the description \2, but only up to a dash # This is to reduce the size of the TOC in the command listing on the main page @@ -454,7 +454,7 @@ lexicon_filter: lexicon.txt lexicon_filter.in | show-SED else \ WORDBL='\\<'; WORDBR='\\>'; \ fi; $(SED) >$@.tmp -n -e "s|^\([a-z][a-z][a-z][a-z]\) \([a-z_-]*\)$$|s,$$WORDBL\2$$WORDBR,@\1{\2},g|p" -e '$$G;s/.*\n/b tidy/p'; - mv $@.tmp $@; test -x $@ || chmod a+x $@; + $v mv $@.tmp $@; test -x $@ || chmod a+x $@; # @@ -649,6 +649,7 @@ install-force: all install-translations | show-datadir show-sysconfdir show-extr $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/completions $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/functions + $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/groff $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/man/man1 $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/tools $v $(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/tools/web_config @@ -673,6 +674,7 @@ install-force: all install-translations | show-datadir show-sysconfdir show-extr $(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/functions/; \ done; @echo "Installing $(bo)man pages$(sgr0)"; + $v $(INSTALL) -m 644 share/groff/* $(DESTDIR)$(datadir)/fish/groff/ $v for i in $(wildcard share/man/man1/*.1); do \ $(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/man/man1/; \ done; diff --git a/build_tools/lint.fish b/build_tools/lint.fish index 5d9e4443d..7fb72e790 100755 --- a/build_tools/lint.fish +++ b/build_tools/lint.fish @@ -92,7 +92,7 @@ if set -q c_files[1] # The stderr to stdout redirection is because cppcheck, incorrectly IMHO, writes its # diagnostic messages to stderr. Anyone running this who wants to capture its output will # expect those messages to be written to stdout. - cppcheck -q --verbose --std=posix --language=c++ --template "[{file}:{line}]: {severity} ({id}): {message}" --suppress=missingIncludeSystem --inline-suppr --enable=$cppchecks --rule-file=.cppcheck.rule $cppcheck_args $c_files 2>&1 + cppcheck -q --verbose --std=posix --language=c++ --template \[(set_color --bold)(set_color --underline)"{file}"(set_color normal)(set_color --bold)":{line}"(set_color normal)"] "(set_color brmagenta)"{severity}"(set_color magenta)" ({id}):"\n(set_color normal)" {message}" --suppress=missingIncludeSystem --inline-suppr --enable=$cppchecks --rule-file=.cppcheck.rule $cppcheck_args $c_files 2>&1 end if type -q oclint @@ -105,7 +105,7 @@ if set -q c_files[1] # output will expect those messages to be written to stdout. if test "$kernel_name" = "Darwin" if not test -f compile_commands.json - xcodebuild >xcodebuild.log + xcodebuild -alltargets >xcodebuild.log oclint-xcodebuild xcodebuild.log >/dev/null end if test $all = yes diff --git a/doc_src/alias.txt b/doc_src/alias.txt index cbb9508eb..fc36fbbac 100644 --- a/doc_src/alias.txt +++ b/doc_src/alias.txt @@ -2,39 +2,34 @@ \subsection alias-synopsis Synopsis \fish{synopsis} +alias alias NAME DEFINITION alias NAME=DEFINITION \endfish \subsection alias-description Description -`alias` is a simple wrapper for the `function` builtin. It exists for backwards compatibility with Posix shells. For other uses, it is recommended to define a function. +`alias` is a simple wrapper for the `function` builtin, which creates a function wrapping a command. It has similar syntax to POSIX shell `alias`. For other uses, it is recommended to define a function. -`fish` does not keep track of which functions have been defined using `alias`. They must be erased using `functions -e`. +`fish` marks functions that have been created by `alias` by including the command used to create them in the function description. You can list `alias`-created functions by running `alias` without arguments. They must be erased using `functions -e`. - `NAME` is the name of the alias - - `DEFINITION` is the actual command to execute. The string `$argv` will be appended. -You cannot create an alias to a function with the same name. - -Note that spaces need to be escaped in the call to alias just like in the commandline _even inside the quotes_. - +You cannot create an alias to a function with the same name. Note that spaces need to be escaped in the call to `alias` just like at the command line, _even inside quoted parts_. \subsection alias-example Example The following code will create `rmi`, which runs `rm` with additional arguments on every invocation. \fish -alias rmi "rm -i" +alias rmi="rm -i" # This is equivalent to entering the following function: - -function rmi +function rmi --wraps rm --description 'alias rmi=rm -i' rm -i $argv end # This needs to have the spaces escaped or "Chrome.app..." will be seen as an argument to "/Applications/Google": - alias chrome='/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome banana' \endfish diff --git a/doc_src/history.txt b/doc_src/history.txt index 388ded19a..cc8d311a3 100644 --- a/doc_src/history.txt +++ b/doc_src/history.txt @@ -14,8 +14,6 @@ history ( -h | --help ) `history` is used to search, delete, and otherwise manipulate the history of interactive commands. -Note that for backwards compatibility each subcommand can also be specified as a long option. For example, rather than `history search` you can type `history --search`. Those long options are deprecated and will be removed in a future release. - The following operations (sub-commands) are available: - `search` returns history items matching the search string. If no search string is provided it returns all history items. This is the default operation if no other operation is specified. You only have to explicitly say `history search` if you wish to search for one of the subcommands. The `--contains` search option will be used if you don't specify a different search option. Entries are ordered newest to oldest. If stdout is attached to a tty the output will be piped through your pager by the history function. The history builtin simply writes the results to stdout. @@ -60,9 +58,10 @@ history --search --contains "foo" history --delete --prefix "foo" # Interactively deletes commands which start with "foo" from the history. # You can select more than one entry by entering their IDs seperated by a space. +\endfish \subsection history-notes Notes If you specify both `--prefix` and `--contains` the last flag seen is used. -\endfish +Note that for backwards compatibility each subcommand can also be specified as a long option. For example, rather than `history search` you can type `history --search`. Those long options are deprecated and will be removed in a future release. diff --git a/doc_src/open.txt b/doc_src/open.txt index c81c3c9a1..7207fc172 100644 --- a/doc_src/open.txt +++ b/doc_src/open.txt @@ -9,6 +9,8 @@ open FILES... `open` opens a file in its default application, using the appropriate tool for the operating system. On GNU/Linux, this requires the common but optional `xdg-open` utility, from the `xdg-utils` package. +Note that this function will not be used if a command by this name exists (which is the case on macOS or Haiku). + \subsection open-example Example diff --git a/doc_src/status.txt b/doc_src/status.txt index b939f6fe9..b93502059 100644 --- a/doc_src/status.txt +++ b/doc_src/status.txt @@ -2,33 +2,50 @@ \subsection status-synopsis Synopsis \fish{synopsis} -status [OPTION] +status +status is-login +status is-interactive +status is-block +status is-command-substitution +status is-no-job-control +status is-full-job-control +status is-interactive-job-control +status current-filename +status current-line-number +status print-stack-trace +status job-control CONTROL-TYPE \endfish \subsection status-description Description With no arguments, `status` displays a summary of the current login and job control status of the shell. -The following options are available: +The following operations (sub-commands) are available: -- `-c` or `--is-command-substitution` returns 0 if fish is currently executing a command substitution. +- `is-command-sub` returns 0 if fish is currently executing a command substitution. Also `-c` or `--is-command-substitution`. -- `-b` or `--is-block` returns 0 if fish is currently executing a block of code. +- `is-block` returns 0 if fish is currently executing a block of code. Also `-b` or `--is-block`. -- `-i` or `--is-interactive` returns 0 if fish is interactive - that is, connected to a keyboard. +- `is-interactive` returns 0 if fish is interactive - that is, connected to a keyboard. Also `-i` or `--is-interactive`. -- `-l` or `--is-login` returns 0 if fish is a login shell - that is, if fish should perform login tasks such as setting up the PATH. +- `is-login` returns 0 if fish is a login shell - that is, if fish should perform login tasks such as setting up the PATH. Also `-l` or `--is-login`. -- `--is-full-job-control` returns 0 if full job control is enabled. +- `is-full-job-control` returns 0 if full job control is enabled. Also `--is-full-job-control` (no short flag). -- `--is-interactive-job-control` returns 0 if interactive job control is enabled. +- `is-interactive-job-control` returns 0 if interactive job control is enabled. Also, `--is-interactive-job-control` (no short flag). -- `--is-no-job-control` returns 0 if no job control is enabled. +- `is-no-job-control` returns 0 if no job control is enabled. Also `--is-no-job-control` (no short flag). -- `-f` or `--current-filename` prints the filename of the currently running script. +- `current-filename` prints the filename of the currently running script. Also `-f` or `--current-filename`. -- `-n` or `--current-line-number` prints the line number of the currently running script. +- `current-line-number` prints the line number of the currently running script. Also `-n` or `--current-line-number`. -- `-j CONTROLTYPE` or `--job-control=CONTROLTYPE` sets the job control type, which can be `none`, `full`, or `interactive`. +- `job-control CONTROL-TYPE` sets the job control type, which can be `none`, `full`, or `interactive`. Also `-j CONTROL-TYPE` or `--job-control=CONTROL-TYPE`. -- `-t` or `--print-stack-trace` prints a stack trace of all function calls on the call stack. +- `print-stack-trace` prints a stack trace of all function calls on the call stack. Also `-t` or `--print-stack-trace`. + +\subsection status-notes Notes + +For backwards compatibility each subcommand can also be specified as a long or short option. For example, rather than `status is-login` you can type `status --is-login`. The flag forms are deprecated and may be removed in a future release (but not before fish 3.0). + +You can only specify one subcommand per invocation even if you use the flag form of the subcommand. diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj index 5aa3fa599..f31cdf263 100644 --- a/fish.xcodeproj/project.pbxproj +++ b/fish.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ D01A2C9B16964C8200767098 /* Copy Files */, ); dependencies = ( + 9C7A55801DCD73930049C25D /* PBXTargetDependency */, D0F01A1315AA36280034B3B1 /* PBXTargetDependency */, D0F01A1715AA36300034B3B1 /* PBXTargetDependency */, D0A564EF168D09C000AF6161 /* PBXTargetDependency */, @@ -67,6 +68,72 @@ /* Begin PBXBuildFile section */ 63A2C0E91CC60F3B00973404 /* pcre2_find_bracket.c in Sources */ = {isa = PBXBuildFile; fileRef = 63A2C0E81CC5F9FB00973404 /* pcre2_find_bracket.c */; }; + 9C7A55271DCD651F0049C25D /* fallback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853E13B3ACEE0099B651 /* fallback.cpp */; }; + 9C7A55281DCD65540049C25D /* builtin_commandline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853013B3ACEE0099B651 /* builtin_commandline.cpp */; }; + 9C7A55291DCD65540049C25D /* builtin_complete.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853113B3ACEE0099B651 /* builtin_complete.cpp */; }; + 9C7A552A1DCD65540049C25D /* builtin_jobs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853213B3ACEE0099B651 /* builtin_jobs.cpp */; }; + 9C7A552B1DCD65540049C25D /* builtin_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853313B3ACEE0099B651 /* builtin_set.cpp */; }; + 9C7A552C1DCD65540049C25D /* builtin_set_color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C861EA16CC7054003B5A04 /* builtin_set_color.cpp */; }; + 9C7A552D1DCD65540049C25D /* builtin_ulimit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853413B3ACEE0099B651 /* builtin_ulimit.cpp */; }; + 9C7A552E1DCD65540049C25D /* builtin_printf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0CA63F316FC275F00093BD4 /* builtin_printf.cpp */; }; + 9C7A552F1DCD65820049C25D /* util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855E13B3ACEE0099B651 /* util.cpp */; }; + 9C7A55361DCD71330049C25D /* autoload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */; }; + 9C7A55371DCD71330049C25D /* builtin_commandline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853013B3ACEE0099B651 /* builtin_commandline.cpp */; }; + 9C7A55381DCD71330049C25D /* builtin_complete.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853113B3ACEE0099B651 /* builtin_complete.cpp */; }; + 9C7A55391DCD71330049C25D /* builtin_jobs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853213B3ACEE0099B651 /* builtin_jobs.cpp */; }; + 9C7A553A1DCD71330049C25D /* builtin_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853313B3ACEE0099B651 /* builtin_set.cpp */; }; + 9C7A553B1DCD71330049C25D /* builtin_set_color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C861EA16CC7054003B5A04 /* builtin_set_color.cpp */; }; + 9C7A553C1DCD71330049C25D /* builtin_ulimit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853413B3ACEE0099B651 /* builtin_ulimit.cpp */; }; + 9C7A553D1DCD71330049C25D /* builtin_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0F3373A1506DE3C00ECEFC0 /* builtin_test.cpp */; }; + 9C7A553E1DCD71330049C25D /* builtin_printf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0CA63F316FC275F00093BD4 /* builtin_printf.cpp */; }; + 9C7A553F1DCD71330049C25D /* builtin_string.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D04F7F7B1BA4BF4000B0F227 /* builtin_string.cpp */; }; + 9C7A55401DCD71330049C25D /* color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0B6B0FE14E88BA400AD6C10 /* color.cpp */; }; + 9C7A55411DCD71330049C25D /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853613B3ACEE0099B651 /* common.cpp */; }; + 9C7A55421DCD71330049C25D /* event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853B13B3ACEE0099B651 /* event.cpp */; }; + 9C7A55431DCD71330049C25D /* input_common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854913B3ACEE0099B651 /* input_common.cpp */; }; + 9C7A55441DCD71330049C25D /* io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854C13B3ACEE0099B651 /* io.cpp */; }; + 9C7A55451DCD71330049C25D /* iothread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854D13B3ACEE0099B651 /* iothread.cpp */; }; + 9C7A55461DCD71330049C25D /* parse_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855213B3ACEE0099B651 /* parse_util.cpp */; }; + 9C7A55471DCD71330049C25D /* path.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855513B3ACEE0099B651 /* path.cpp */; }; + 9C7A55481DCD71330049C25D /* parse_execution.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D052D8091868F7FC003ABCBD /* parse_execution.cpp */; }; + 9C7A55491DCD71330049C25D /* postfork.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D09B1C1914FC7B5B00F91077 /* postfork.cpp */; }; + 9C7A554A1DCD71330049C25D /* screen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855A13B3ACEE0099B651 /* screen.cpp */; }; + 9C7A554B1DCD71330049C25D /* signal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855C13B3ACEE0099B651 /* signal.cpp */; }; + 9C7A554C1DCD71330049C25D /* utf8.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C9733718DE5449002D7C81 /* utf8.cpp */; }; + 9C7A554D1DCD71330049C25D /* builtin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853513B3ACEE0099B651 /* builtin.cpp */; }; + 9C7A554E1DCD71330049C25D /* function.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854413B3ACEE0099B651 /* function.cpp */; }; + 9C7A554F1DCD71330049C25D /* complete.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853713B3ACEE0099B651 /* complete.cpp */; }; + 9C7A55501DCD71330049C25D /* env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853A13B3ACEE0099B651 /* env.cpp */; }; + 9C7A55511DCD71330049C25D /* exec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853C13B3ACEE0099B651 /* exec.cpp */; }; + 9C7A55521DCD71330049C25D /* wcstringutil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0F5B46319CFCDE80090665E /* wcstringutil.cpp */; }; + 9C7A55531DCD71330049C25D /* expand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853D13B3ACEE0099B651 /* expand.cpp */; }; + 9C7A55541DCD71330049C25D /* fallback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853E13B3ACEE0099B651 /* fallback.cpp */; }; + 9C7A55551DCD71330049C25D /* fish_version.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D00F63F019137E9D00FCCDEC /* fish_version.cpp */; settings = {COMPILER_FLAGS = "-I$(DERIVED_FILE_DIR)"; }; }; + 9C7A55561DCD71330049C25D /* highlight.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854713B3ACEE0099B651 /* highlight.cpp */; }; + 9C7A55571DCD71330049C25D /* history.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854813B3ACEE0099B651 /* history.cpp */; }; + 9C7A55581DCD71330049C25D /* kill.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854F13B3ACEE0099B651 /* kill.cpp */; }; + 9C7A55591DCD71330049C25D /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855413B3ACEE0099B651 /* parser.cpp */; }; + 9C7A555A1DCD71330049C25D /* parser_keywords.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855313B3ACEE0099B651 /* parser_keywords.cpp */; }; + 9C7A555B1DCD71330049C25D /* proc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855713B3ACEE0099B651 /* proc.cpp */; }; + 9C7A555C1DCD71330049C25D /* reader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855813B3ACEE0099B651 /* reader.cpp */; }; + 9C7A555D1DCD71330049C25D /* sanity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855913B3ACEE0099B651 /* sanity.cpp */; }; + 9C7A555E1DCD71330049C25D /* tokenizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855D13B3ACEE0099B651 /* tokenizer.cpp */; }; + 9C7A555F1DCD71330049C25D /* wildcard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0856013B3ACEE0099B651 /* wildcard.cpp */; }; + 9C7A55601DCD71330049C25D /* wgetopt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855F13B3ACEE0099B651 /* wgetopt.cpp */; }; + 9C7A55611DCD71330049C25D /* wutil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0856113B3ACEE0099B651 /* wutil.cpp */; }; + 9C7A55621DCD71330049C25D /* input.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854A13B3ACEE0099B651 /* input.cpp */; }; + 9C7A55631DCD71330049C25D /* output.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855113B3ACEE0099B651 /* output.cpp */; }; + 9C7A55641DCD71330049C25D /* intern.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854B13B3ACEE0099B651 /* intern.cpp */; }; + 9C7A55651DCD71330049C25D /* env_universal_common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853813B3ACEE0099B651 /* env_universal_common.cpp */; }; + 9C7A55661DCD71330049C25D /* pager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D03238891849D1980032CF2C /* pager.cpp */; }; + 9C7A55681DCD71330049C25D /* parse_tree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C52F351765284C00BFAB82 /* parse_tree.cpp */; }; + 9C7A55691DCD71330049C25D /* parse_productions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0FE8EE7179FB75F008C9F21 /* parse_productions.cpp */; }; + 9C7A556A1DCD71330049C25D /* util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855E13B3ACEE0099B651 /* util.cpp */; }; + 9C7A556C1DCD71330049C25D /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D02A8C15983CFA008E62BD /* libncurses.dylib */; }; + 9C7A556D1DCD71330049C25D /* libpcre2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D04F7FD01BA4E29300B0F227 /* libpcre2.a */; }; + 9C7A557D1DCD71890049C25D /* fish_key_reader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9C7A557C1DCD717C0049C25D /* fish_key_reader.cpp */; }; + 9C7A557E1DCD71CD0049C25D /* print_help.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855613B3ACEE0099B651 /* print_help.cpp */; }; + 9C7A55811DCD739C0049C25D /* fish_key_reader in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C7A55721DCD71330049C25D /* fish_key_reader */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D00769121990137800CA4627 /* autoload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */; }; D00769131990137800CA4627 /* builtin_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0F3373A1506DE3C00ECEFC0 /* builtin_test.cpp */; }; D00769141990137800CA4627 /* color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0B6B0FE14E88BA400AD6C10 /* color.cpp */; }; @@ -286,6 +353,27 @@ /* End PBXBuildRule section */ /* Begin PBXContainerItemProxy section */ + 9C7A55321DCD71330049C25D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D0A084F213B3AC130099B651 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D008D0C41BC58F8800841177; + remoteInfo = "generate-version-header"; + }; + 9C7A55341DCD71330049C25D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D0A084F213B3AC130099B651 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D04F7FCF1BA4E29300B0F227; + remoteInfo = libpcre2.a; + }; + 9C7A557F1DCD73930049C25D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D0A084F213B3AC130099B651 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9C7A55301DCD71330049C25D; + remoteInfo = fish_key_reader; + }; D008D0CA1BC58FDD00841177 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D0A084F213B3AC130099B651 /* Project object */; @@ -438,6 +526,7 @@ dstPath = base/bin; dstSubfolderSpec = 1; files = ( + 9C7A55811DCD739C0049C25D /* fish_key_reader in CopyFiles */, D0F019F115A977140034B3B1 /* fish in CopyFiles */, D0F019F315A977290034B3B1 /* fish_indent in CopyFiles */, ); @@ -468,6 +557,17 @@ /* Begin PBXFileReference section */ 4E142D731B56B5D7008783C8 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = ../osx/config.h; sourceTree = ""; }; 63A2C0E81CC5F9FB00973404 /* pcre2_find_bracket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcre2_find_bracket.c; sourceTree = ""; }; + 9C7A55721DCD71330049C25D /* fish_key_reader */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_key_reader; sourceTree = BUILT_PRODUCTS_DIR; }; + 9C7A55731DCD716F0049C25D /* builtin_commandline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_commandline.h; sourceTree = ""; }; + 9C7A55741DCD716F0049C25D /* builtin_complete.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_complete.h; sourceTree = ""; }; + 9C7A55751DCD716F0049C25D /* builtin_jobs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_jobs.h; sourceTree = ""; }; + 9C7A55761DCD716F0049C25D /* builtin_printf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_printf.h; sourceTree = ""; }; + 9C7A55771DCD716F0049C25D /* builtin_set_color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_set_color.h; sourceTree = ""; }; + 9C7A55781DCD716F0049C25D /* builtin_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_set.h; sourceTree = ""; }; + 9C7A55791DCD716F0049C25D /* builtin_string.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_string.h; sourceTree = ""; }; + 9C7A557A1DCD716F0049C25D /* builtin_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_test.h; sourceTree = ""; }; + 9C7A557B1DCD716F0049C25D /* builtin_ulimit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin_ulimit.h; sourceTree = ""; }; + 9C7A557C1DCD717C0049C25D /* fish_key_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fish_key_reader.cpp; sourceTree = ""; }; D00769421990137800CA4627 /* fish_tests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_tests; sourceTree = BUILT_PRODUCTS_DIR; }; D00F63F019137E9D00FCCDEC /* fish_version.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fish_version.cpp; sourceTree = ""; }; D01A2D23169B730A00767098 /* man1 */ = {isa = PBXFileReference; lastKnownFileType = text; name = man1; path = pages_for_manpath/man1; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -618,6 +718,15 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 9C7A556B1DCD71330049C25D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9C7A556C1DCD71330049C25D /* libncurses.dylib in Frameworks */, + 9C7A556D1DCD71330049C25D /* libpcre2.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D007693C1990137800CA4627 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -743,6 +852,16 @@ D0D02A91159845EF008E62BD /* Sources */ = { isa = PBXGroup; children = ( + 9C7A557C1DCD717C0049C25D /* fish_key_reader.cpp */, + 9C7A55731DCD716F0049C25D /* builtin_commandline.h */, + 9C7A55741DCD716F0049C25D /* builtin_complete.h */, + 9C7A55751DCD716F0049C25D /* builtin_jobs.h */, + 9C7A55761DCD716F0049C25D /* builtin_printf.h */, + 9C7A55771DCD716F0049C25D /* builtin_set_color.h */, + 9C7A55781DCD716F0049C25D /* builtin_set.h */, + 9C7A55791DCD716F0049C25D /* builtin_string.h */, + 9C7A557A1DCD716F0049C25D /* builtin_test.h */, + 9C7A557B1DCD716F0049C25D /* builtin_ulimit.h */, 4E142D731B56B5D7008783C8 /* config.h */, D0C6FCCB14CFA4B7004CE8AD /* autoload.h */, D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */, @@ -888,6 +1007,7 @@ D0D02AD01598642A008E62BD /* fish_indent */, D00769421990137800CA4627 /* fish_tests */, D04F7FD01BA4E29300B0F227 /* libpcre2.a */, + 9C7A55721DCD71330049C25D /* fish_key_reader */, ); name = Products; sourceTree = ""; @@ -905,6 +1025,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 9C7A55301DCD71330049C25D /* fish_key_reader */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9C7A556E1DCD71330049C25D /* Build configuration list for PBXNativeTarget "fish_key_reader" */; + buildPhases = ( + 9C7A55351DCD71330049C25D /* Sources */, + 9C7A556B1DCD71330049C25D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9C7A55311DCD71330049C25D /* PBXTargetDependency */, + 9C7A55331DCD71330049C25D /* PBXTargetDependency */, + ); + name = fish_key_reader; + productName = fish_Xcode; + productReference = 9C7A55721DCD71330049C25D /* fish_key_reader */; + productType = "com.apple.product-type.tool"; + }; D00769101990137800CA4627 /* fish_tests */ = { isa = PBXNativeTarget; buildConfigurationList = D007693E1990137800CA4627 /* Build configuration list for PBXNativeTarget "fish_tests" */; @@ -1031,6 +1169,7 @@ D00769101990137800CA4627 /* fish_tests */, D04F7FCF1BA4E29300B0F227 /* pcre2 */, D008D0C41BC58F8800841177 /* generate-version-header */, + 9C7A55301DCD71330049C25D /* fish_key_reader */, ); }; /* End PBXProject section */ @@ -1226,10 +1365,80 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 9C7A55351DCD71330049C25D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9C7A557E1DCD71CD0049C25D /* print_help.cpp in Sources */, + 9C7A557D1DCD71890049C25D /* fish_key_reader.cpp in Sources */, + 9C7A55361DCD71330049C25D /* autoload.cpp in Sources */, + 9C7A55371DCD71330049C25D /* builtin_commandline.cpp in Sources */, + 9C7A55381DCD71330049C25D /* builtin_complete.cpp in Sources */, + 9C7A55391DCD71330049C25D /* builtin_jobs.cpp in Sources */, + 9C7A553A1DCD71330049C25D /* builtin_set.cpp in Sources */, + 9C7A553B1DCD71330049C25D /* builtin_set_color.cpp in Sources */, + 9C7A553C1DCD71330049C25D /* builtin_ulimit.cpp in Sources */, + 9C7A553D1DCD71330049C25D /* builtin_test.cpp in Sources */, + 9C7A553E1DCD71330049C25D /* builtin_printf.cpp in Sources */, + 9C7A553F1DCD71330049C25D /* builtin_string.cpp in Sources */, + 9C7A55401DCD71330049C25D /* color.cpp in Sources */, + 9C7A55411DCD71330049C25D /* common.cpp in Sources */, + 9C7A55421DCD71330049C25D /* event.cpp in Sources */, + 9C7A55431DCD71330049C25D /* input_common.cpp in Sources */, + 9C7A55441DCD71330049C25D /* io.cpp in Sources */, + 9C7A55451DCD71330049C25D /* iothread.cpp in Sources */, + 9C7A55461DCD71330049C25D /* parse_util.cpp in Sources */, + 9C7A55471DCD71330049C25D /* path.cpp in Sources */, + 9C7A55481DCD71330049C25D /* parse_execution.cpp in Sources */, + 9C7A55491DCD71330049C25D /* postfork.cpp in Sources */, + 9C7A554A1DCD71330049C25D /* screen.cpp in Sources */, + 9C7A554B1DCD71330049C25D /* signal.cpp in Sources */, + 9C7A554C1DCD71330049C25D /* utf8.cpp in Sources */, + 9C7A554D1DCD71330049C25D /* builtin.cpp in Sources */, + 9C7A554E1DCD71330049C25D /* function.cpp in Sources */, + 9C7A554F1DCD71330049C25D /* complete.cpp in Sources */, + 9C7A55501DCD71330049C25D /* env.cpp in Sources */, + 9C7A55511DCD71330049C25D /* exec.cpp in Sources */, + 9C7A55521DCD71330049C25D /* wcstringutil.cpp in Sources */, + 9C7A55531DCD71330049C25D /* expand.cpp in Sources */, + 9C7A55541DCD71330049C25D /* fallback.cpp in Sources */, + 9C7A55551DCD71330049C25D /* fish_version.cpp in Sources */, + 9C7A55561DCD71330049C25D /* highlight.cpp in Sources */, + 9C7A55571DCD71330049C25D /* history.cpp in Sources */, + 9C7A55581DCD71330049C25D /* kill.cpp in Sources */, + 9C7A55591DCD71330049C25D /* parser.cpp in Sources */, + 9C7A555A1DCD71330049C25D /* parser_keywords.cpp in Sources */, + 9C7A555B1DCD71330049C25D /* proc.cpp in Sources */, + 9C7A555C1DCD71330049C25D /* reader.cpp in Sources */, + 9C7A555D1DCD71330049C25D /* sanity.cpp in Sources */, + 9C7A555E1DCD71330049C25D /* tokenizer.cpp in Sources */, + 9C7A555F1DCD71330049C25D /* wildcard.cpp in Sources */, + 9C7A55601DCD71330049C25D /* wgetopt.cpp in Sources */, + 9C7A55611DCD71330049C25D /* wutil.cpp in Sources */, + 9C7A55621DCD71330049C25D /* input.cpp in Sources */, + 9C7A55631DCD71330049C25D /* output.cpp in Sources */, + 9C7A55641DCD71330049C25D /* intern.cpp in Sources */, + 9C7A55651DCD71330049C25D /* env_universal_common.cpp in Sources */, + 9C7A55661DCD71330049C25D /* pager.cpp in Sources */, + 9C7A55681DCD71330049C25D /* parse_tree.cpp in Sources */, + 9C7A55691DCD71330049C25D /* parse_productions.cpp in Sources */, + 9C7A556A1DCD71330049C25D /* util.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D00769111990137800CA4627 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9C7A552F1DCD65820049C25D /* util.cpp in Sources */, + 9C7A55281DCD65540049C25D /* builtin_commandline.cpp in Sources */, + 9C7A55291DCD65540049C25D /* builtin_complete.cpp in Sources */, + 9C7A552A1DCD65540049C25D /* builtin_jobs.cpp in Sources */, + 9C7A552B1DCD65540049C25D /* builtin_set.cpp in Sources */, + 9C7A552C1DCD65540049C25D /* builtin_set_color.cpp in Sources */, + 9C7A552D1DCD65540049C25D /* builtin_ulimit.cpp in Sources */, + 9C7A552E1DCD65540049C25D /* builtin_printf.cpp in Sources */, + 9C7A55271DCD651F0049C25D /* fallback.cpp in Sources */, D00769121990137800CA4627 /* autoload.cpp in Sources */, D00769131990137800CA4627 /* builtin_test.cpp in Sources */, D00769141990137800CA4627 /* color.cpp in Sources */, @@ -1440,6 +1649,21 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 9C7A55311DCD71330049C25D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D008D0C41BC58F8800841177 /* generate-version-header */; + targetProxy = 9C7A55321DCD71330049C25D /* PBXContainerItemProxy */; + }; + 9C7A55331DCD71330049C25D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D04F7FCF1BA4E29300B0F227 /* pcre2 */; + targetProxy = 9C7A55341DCD71330049C25D /* PBXContainerItemProxy */; + }; + 9C7A55801DCD73930049C25D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9C7A55301DCD71330049C25D /* fish_key_reader */; + targetProxy = 9C7A557F1DCD73930049C25D /* PBXContainerItemProxy */; + }; D008D0CB1BC58FDD00841177 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D008D0C41BC58F8800841177 /* generate-version-header */; @@ -1503,6 +1727,42 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 9C7A556F1DCD71330049C25D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 9C7A55701DCD71330049C25D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 9C7A55711DCD71330049C25D /* Release_C++11 */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Release_C++11"; + }; D007693F1990137800CA4627 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1511,6 +1771,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = NO; PRODUCT_NAME = fish_tests; }; name = Debug; @@ -1523,6 +1784,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; PRODUCT_NAME = fish_tests; }; name = Release; @@ -1535,6 +1797,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; PRODUCT_NAME = fish_tests; }; name = "Release_C++11"; @@ -1619,6 +1882,7 @@ GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = osx/Info.plist; + LLVM_LTO = YES_THIN; PRODUCT_BUNDLE_IDENTIFIER = "com.ridiculousfish.fish-shell"; PRODUCT_NAME = fish; WRAPPER_EXTENSION = app; @@ -1632,6 +1896,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; PRODUCT_NAME = fish; }; name = "Release_C++11"; @@ -1644,6 +1909,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = "Release_C++11"; @@ -1694,6 +1960,7 @@ ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_UNUSED_VARIABLE = NO; + LLVM_LTO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/osx/pcre2 $(SRCROOT)/osx/shared_headers/"; @@ -1719,6 +1986,7 @@ ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_UNUSED_VARIABLE = NO; + LLVM_LTO = YES_THIN; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/osx/pcre2 $(SRCROOT)/osx/shared_headers/"; @@ -1744,6 +2012,7 @@ ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_UNUSED_VARIABLE = NO; + LLVM_LTO = YES_THIN; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/osx/pcre2 $(SRCROOT)/osx/shared_headers/"; @@ -1900,6 +2169,7 @@ ); GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = osx/Info.plist; + LLVM_LTO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.ridiculousfish.fish-shell"; PRODUCT_NAME = fish; WRAPPER_EXTENSION = app; @@ -1917,6 +2187,7 @@ GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = osx/Info.plist; + LLVM_LTO = YES_THIN; PRODUCT_BUNDLE_IDENTIFIER = "com.ridiculousfish.fish-shell"; PRODUCT_NAME = fish; WRAPPER_EXTENSION = app; @@ -1935,6 +2206,7 @@ "$(inherited)", ); GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -1947,6 +2219,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -1958,6 +2231,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = NO; PRODUCT_NAME = fish; }; name = Debug; @@ -1969,6 +2243,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + LLVM_LTO = YES_THIN; PRODUCT_NAME = fish; }; name = Release; @@ -1996,6 +2271,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 9C7A556E1DCD71330049C25D /* Build configuration list for PBXNativeTarget "fish_key_reader" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9C7A556F1DCD71330049C25D /* Debug */, + 9C7A55701DCD71330049C25D /* Release */, + 9C7A55711DCD71330049C25D /* Release_C++11 */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D007693E1990137800CA4627 /* Build configuration list for PBXNativeTarget "fish_tests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/osx/osx_fish_launcher.m b/osx/osx_fish_launcher.m index 20b6e61b0..875ae75f7 100644 --- a/osx/osx_fish_launcher.m +++ b/osx/osx_fish_launcher.m @@ -18,11 +18,11 @@ static void die(const char *format, ...) { vfprintf(stderr, format, ap); va_end(ap); fputc('\n', stderr); - + if (s_command_path[0] != '\0') { unlink(s_command_path); } - + exit(EXIT_FAILURE); } @@ -31,14 +31,14 @@ static void launch_fish_with_applescript(NSString *fish_binary_path) // load the script from a resource by fetching its URL from within our bundle NSString *path = [[NSBundle mainBundle] pathForResource:@"launch_fish" ofType:@"scpt"]; if (! path) die("Couldn't get path to launch_fish.scpt"); - + NSURL *url = [NSURL fileURLWithPath:path isDirectory:NO]; if (! url) die("Couldn't get URL to launch_fish.scpt"); - + NSDictionary *errors = nil; NSAppleScript *appleScript = [[NSAppleScript alloc] initWithContentsOfURL:url error:&errors]; if (! appleScript) die("Couldn't load AppleScript"); - + // create the first parameter NSAppleEventDescriptor *firstParameter = [NSAppleEventDescriptor descriptorWithString:fish_binary_path]; @@ -84,16 +84,17 @@ static void launch_fish_with_applescript(NSString *fish_binary_path) /* This approach asks Terminal to open a script that we control */ int main(void) { - + @autoreleasepool { /* Get the fish executable. Make sure it's absolute. */ - NSURL *fish_executable = [[NSBundle mainBundle] URLForResource:@"fish" withExtension:@"" subdirectory:@"base/bin"]; + NSURL *fish_executable = [[NSBundle mainBundle] URLForResource:@"fish" withExtension:@"" + subdirectory:@"base/bin"]; if (! fish_executable) die("Could not find fish executable in bundle"); - + launch_fish_with_applescript([fish_executable path]); } - + /* If we succeeded, it will clean itself up */ return 0; } diff --git a/share/completions/caffeinate.fish b/share/completions/caffeinate.fish new file mode 100644 index 000000000..bfcc708af --- /dev/null +++ b/share/completions/caffeinate.fish @@ -0,0 +1,11 @@ +# completion for caffeinate (macOS) + +complete -c caffeinate -s d -f -d 'Create an assertion to prevent the display from sleeping' +complete -c caffeinate -s i -f -d 'Create an assertion to prevent the system from idle sleeping' +complete -c caffeinate -s m -f -d 'Create an assertion to prevent the disk from idle sleeping' +complete -c caffeinate -s s -f -d 'Create an assertion to prevent the system from sleeping (AC power)' +complete -c caffeinate -s u -f -d 'Create an assertion to declare that user is active' +complete -c caffeinate -s t -x -a '10 60 300 600 1800 3600' -d 'Specifies the timeout value in seconds' +complete -c caffeinate -s w -x -a '(__fish_complete_pids)' -d 'Waits for the process with the specified PID to exit' + +complete -c caffeinate -x -a '(__fish_complete_subcommand)' diff --git a/share/completions/defaults.fish b/share/completions/defaults.fish new file mode 100644 index 000000000..408bfe688 --- /dev/null +++ b/share/completions/defaults.fish @@ -0,0 +1,33 @@ +# completion for defaults (macOS) + +function __fish_defaults_domains + defaults domains | string split ", " +end + +complete -f -c defaults -o 'currentHost' -d 'Restricts preferences operations to the current logged-in host' +complete -f -c defaults -o 'host' -d 'Restricts preferences operations to hostname' + +# read +complete -f -c defaults -n '__fish_use_subcommand' -a read -d 'Shows defaults entire given domain' +complete -f -c defaults -n '__fish_seen_subcommand_from read read-type write rename delete' -a '(__fish_defaults_domains)' +complete -f -c defaults -n '__fish_seen_subcommand_from read read-type write rename delete' -o 'app' + +# write +complete -f -c defaults -n '__fish_use_subcommand' -a write -d 'Writes domain or or a key in the domain' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'string' -d 'String as the value for the given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'data' -d 'Raw data bytes for given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'int' -d 'Integer as the value for the given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'float' -d 'Floating point number as the value for the given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'bool' -d 'Boolean as the value for the given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'date' -d 'Date as the value for the given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'array' -d 'Array as the value for the given key' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'array-add' -d 'Add new elements to the end of an array' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'dict' -d 'Add a dictionary to domain' +complete -f -c defaults -n '__fish_seen_subcommand_from write' -o 'dict-add' -d 'Add new key/value pairs to a dictionary' + +complete -f -c defaults -n '__fish_use_subcommand' -a read-type -d 'Shows the type for the given domain, key' +complete -f -c defaults -n '__fish_use_subcommand' -a rename -d 'Renames old_key to new_key' +complete -f -c defaults -n '__fish_use_subcommand' -a delete -d 'Deletes domain or a key in the domain' +complete -f -c defaults -n '__fish_use_subcommand' -a domains -d 'Prints the names of all domains in the users defaults system' +complete -f -c defaults -n '__fish_use_subcommand' -a find -d 'Searches for word in domain names, keys, and values' +complete -f -c defaults -n '__fish_use_subcommand' -a help -d 'Prints a list of possible command formats' diff --git a/share/completions/dig.fish b/share/completions/dig.fish new file mode 100644 index 000000000..7a8b11031 --- /dev/null +++ b/share/completions/dig.fish @@ -0,0 +1,64 @@ +# completion for dig + +function __fish_complete_dig + set -l token (commandline -ct) + switch $token + case '+tries=*' '+retry=*' '+time=*' '+bufsize=*' '+ndots=*' '+edns=*' + printf '%s\n' $token(seq 0 50) + end +end + +complete -f -c dig -s 4 -d 'Use IPv4 query transport only' +complete -f -c dig -s 6 -d 'Use IPv6 query transport only' +complete -f -c dig -s m -d 'Enable memory usage debugging' +complete -c dig -s b -x -d 'Bind to source address/port' +complete -c dig -s f -r -d 'Specify batch mode file' +complete -c dig -s c -x -a 'IN CH HS QCLASS' -d 'Specify query class' +complete -c dig -s k -r -d 'Specify TSIG key file' +complete -c dig -s y -x -d 'specify named base64 TSIG key' +complete -c dig -s p -x -d 'Specify port number' +complete -c dig -s q -x -d 'Specify query name' +complete -c dig -s t -x -a 'A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT URI' -d 'Specify query type' +complete -c dig -s x -x -d 'Reverse lookup' +complete -f -c dig -s h -d 'Print help and exit' +complete -f -c dig -s v -d 'Print version and exit' + +complete -f -c dig -a '+vc +novc' -d 'TCP mode' +complete -f -c dig -a '+tcp +notcp' -d 'TCP mode, alternate syntax' +complete -f -c dig -a '+search +nosearch' -d 'Set whether to use searchlist' +complete -f -c dig -a '+showsearch +noshowsearch' -d 'Search with intermediate results' +complete -f -c dig -a '+defname +nodefname' -d 'Deprecated, treated as a synonym for +[no]search' +complete -f -c dig -a '+recurse +norecurse' -d 'Recursive mode' +complete -f -c dig -a '+ignore +noignore' -d 'Dont revert to TCP for TC responses.' +complete -f -c dig -a '+fail +nofail' -d 'Dont try next server on SERVFAIL' +complete -f -c dig -a '+besteffort +nobesteffort' -d 'Try to parse even illegal messages' +complete -f -c dig -a '+aaonly +noaaonly' -d 'Set AA flag in query (+[no]aaflag)' +complete -f -c dig -a '+adflag +noadflag' -d 'Set AD flag in query' +complete -f -c dig -a '+cdflag +nocdflag' -d 'Set CD flag in query' +complete -f -c dig -a '+cl +nocl' -d 'Control display of class in records' +complete -f -c dig -a '+cmd +nocmd' -d 'Control display of command line' +complete -f -c dig -a '+comments +nocomments' -d 'Control display of comment lines' +complete -f -c dig -a '+question +noquestion' -d 'Control display of question' +complete -f -c dig -a '+answer +noanswer' -d 'Control display of answer' +complete -f -c dig -a '+authority +noauthority' -d 'Control display of authority' +complete -f -c dig -a '+additional +noadditional' -d 'Control display of additional' +complete -f -c dig -a '+stats +nostats' -d 'Control display of statistics' +complete -f -c dig -a '+short +noshort' -d 'Disable everything except short form of answer' +complete -f -c dig -a '+ttlid +nottlid' -d 'Control display of ttls in records' +complete -f -c dig -a '+all +noall' -d 'Set or clear all display flags' +complete -f -c dig -a '+qr +noqr' -d 'Print question before sending' +complete -f -c dig -a '+nssearch +nonssearch' -d 'Search all authoritative nameservers' +complete -f -c dig -a '+identify +noidentify' -d 'ID responders in short answers' +complete -f -c dig -a '+trace +notrace' -d 'Trace delegation down from root' +complete -f -c dig -a '+dnssec +nodnssec' -d 'Request DNSSEC records' +complete -f -c dig -a '+nsid +nonsid' -d 'Request Name Server ID' +complete -f -c dig -a '+multiline +nomultiline' -d 'Print records in an expanded format' +complete -f -c dig -a '+onesoa +noonesoa' -d 'AXFR prints only one soa record' + +complete -f -c dig -a '+tries=' -d 'Set number of UDP attempts' +complete -f -c dig -a '+retry=' -d 'Set number of UDP retries' +complete -f -c dig -a '+time=' -d 'Set query timeout' +complete -f -c dig -a '+bufsize=' -d 'Set EDNS0 Max UDP packet size' +complete -f -c dig -a '+ndots=' -d 'Set NDOTS value' +complete -f -c dig -a '+edns=' -d 'Set EDNS version' +complete -c dig -a '(__fish_complete_dig)' diff --git a/share/completions/diskutil.fish b/share/completions/diskutil.fish index 89e1c61e7..86315ade9 100644 --- a/share/completions/diskutil.fish +++ b/share/completions/diskutil.fish @@ -1,4 +1,4 @@ -#completion for diskutil +# completion for diskutil (macOS) function __fish_diskutil_devices set -l mountpoints /dev/disk*; printf '%s\n' $mountpoints @@ -22,6 +22,10 @@ complete -f -c diskutil -n '__fish_seen_subcommand_from info' -o 'all' -d 'Proce # activity complete -f -c diskutil -n '__fish_use_subcommand' -a activity -d 'Continuously display system-wide disk manipulation activity' +# listFilesystems +complete -f -c diskutil -n '__fish_use_subcommand' -a listFilesystems -d 'Show the file system personalities available' +complete -f -c diskutil -n '__fish_seen_subcommand_from listFilesystems' -o 'plist' -d 'Return a property list' + # umount complete -f -c diskutil -n '__fish_use_subcommand' -a umount -d 'Unmount a single volume' complete -f -c diskutil -n '__fish_seen_subcommand_from umount' -a '(__fish_diskutil_mounted_volumes)' diff --git a/share/completions/dpkg-reconfigure.fish b/share/completions/dpkg-reconfigure.fish new file mode 100644 index 000000000..144fe734f --- /dev/null +++ b/share/completions/dpkg-reconfigure.fish @@ -0,0 +1,14 @@ +# Completions for the `dpkg-reconfigure` command + +complete -f -c dpkg-reconfigure -a '(__fish_print_packages)' --description 'Package' + +# Support flags +complete -x -f -c dpkg-reconfigure -s h -l help --description 'Display help' + +# General options +complete -f -c dpkg-reconfigure -s f -l frontend -r -a "dialog readline noninteractive gnome kde editor web" --description 'Set configuration frontend' +complete -f -c dpkg-reconfigure -s p -l priority -r -a "low medium high critical" --description 'Set priority threshold' +complete -f -c dpkg-reconfigure -l default-priority --description 'Use default priority threshold' +complete -f -c dpkg-reconfigure -s u -l unseen-only --description 'Show only unseen question' +complete -f -c dpkg-reconfigure -l force --description 'Reconfigure also inconsistent packages' +complete -f -c dpkg-reconfigure -l no-reload --description 'Prevent reloading templates' diff --git a/share/completions/mddiagnose.fish b/share/completions/mddiagnose.fish new file mode 100644 index 000000000..3a93426a6 --- /dev/null +++ b/share/completions/mddiagnose.fish @@ -0,0 +1,12 @@ +# completion for mddiagnose (macOS) + +complete -c mddiagnose -s h -f -d 'Display help' +complete -c mddiagnose -s d -f -d 'Ignore unknown options' +complete -c mddiagnose -s n -f -d 'Do not reveal the resulting package in the Finder' +complete -c mddiagnose -s r -f -d 'Avoid restricted operations such as heap' +complete -c mddiagnose -s s -f -d 'Skip gathering system.log' +complete -c mddiagnose -s v -f -d 'Prints version of mddiagnose' +complete -c mddiagnose -s m -f -d 'Minimal report' +complete -c mddiagnose -s e -r -d 'Evalute indexing information for path' +complete -c mddiagnose -s p -r -d 'Evalute permissions information for path' +complete -c mddiagnose -s f -r -d 'Write the diagnostic to the specified path' diff --git a/share/completions/mdfind.fish b/share/completions/mdfind.fish new file mode 100644 index 000000000..b5598621f --- /dev/null +++ b/share/completions/mdfind.fish @@ -0,0 +1,12 @@ +# completion for mdfind (macOS) + +complete -c mdfind -o attr -x -d 'Fetches the value of the specified attribute' +complete -c mdfind -o count -f -d 'Query only reports matching items count' +complete -c mdfind -o onlyin -x -a '(__fish_complete_directories (commandline -ct))' -d 'Search only within given directory' +complete -c mdfind -o live -f -d 'Query should stay active' +complete -c mdfind -o name -x -d 'Search on file name only' +complete -c mdfind -o reprint -f -d 'Reprint results on live update' +complete -c mdfind -s s -x -d 'Show contents of smart folder' +complete -c mdfind -s 0 -f -d 'Use NUL (\0) as a path separator, for use with xargs -0' +complete -c mdfind -o literal -f -d 'Force the provided query string to be taken as a literal' +complete -c mdfind -o interpret -f -d 'Interprete query string as Spotlight query' diff --git a/share/completions/mdimport.fish b/share/completions/mdimport.fish new file mode 100644 index 000000000..816958b1a --- /dev/null +++ b/share/completions/mdimport.fish @@ -0,0 +1,13 @@ +# completion for mdimport (macOS) + +complete -c mdimport -s g -r -d 'Import files using the listed plugin' +complete -c mdimport -s V -f -d 'Print timing information for this run' +complete -c mdimport -s A -f -d 'Print out the list of all of the attributes and exit' +complete -c mdimport -s X -f -d 'Print out the schema file and exit' +complete -c mdimport -s r -f -d 'Ask the server to reimport files for UTIs claimed by the listed plugin' +complete -c mdimport -s p -f -d 'Print out performance information gathered during the run' +complete -c mdimport -s L -f -d 'Print the list of installed importers and exit' +complete -c mdimport -s d -x -a '1 2 3 4' -d 'Print debugging information' +complete -c mdimport -s n -f -d 'Dont send the imported attributes to the data store' +complete -c mdimport -s w -x -d 'Wait for the specified interval between scanning files' +complete -c mdimport -s o -r -d 'Write the imported attributes to a file' diff --git a/share/completions/mdls.fish b/share/completions/mdls.fish new file mode 100644 index 000000000..4aaf59c7e --- /dev/null +++ b/share/completions/mdls.fish @@ -0,0 +1,6 @@ +# completion for mdls (macOS) + +complete -c mdls -s n -o name -x -d 'Print only the matching metadata attribute value' +complete -c mdls -s r -o raw -f -d 'Print raw attribute data' +complete -c mdls -n '__fish_seen_subcommand_from -raw -r' -o nullMarker -x -d 'Sets a marker string to be used when a requested attribute is null' +complete -c mdls -s p -o plist -r -d 'Output attributes in XML format to file' diff --git a/share/completions/mdutil.fish b/share/completions/mdutil.fish new file mode 100644 index 000000000..ec3e626be --- /dev/null +++ b/share/completions/mdutil.fish @@ -0,0 +1,24 @@ +# completion for mdutil (macOS) + +function __fish_mdutil_volumes + command mdutil -a -s | while read -l line + if string match -q \t"*" -- $line + printf "%s\n" $line + else + # Use printf to not output a newline so indented lines are joined + # to non-indented ones + printf "%s" (string replace -r ':$' '' -- $line) + end + end +end + +complete -c mdutil -s p -f -d 'Publish metadata' +complete -c mdutil -s i -f -a 'on off' -d 'Turn indexing on or off' +complete -c mdutil -s d -f -d 'Disable Spotlight activity for volume' +complete -c mdutil -s E -f -d 'Erase and rebuild index' +complete -c mdutil -s s -f -d 'Print indexing status' +complete -c mdutil -s t -x -a '(__fish_mdutil_volumes)' -d 'Resolve files from file id with an optional volume path or device id' +complete -c mdutil -s a -f -d 'Apply command to all volumes' +complete -c mdutil -s V -x -a '(__fish_mdutil_volumes)' -d 'Apply command to all stores on the specified volume' +complete -c mdutil -s v -f -d 'Display verbose information' +complete -c mdutil -x -a '(__fish_mdutil_volumes)' diff --git a/share/completions/nvram.fish b/share/completions/nvram.fish new file mode 100644 index 000000000..1b5e501b5 --- /dev/null +++ b/share/completions/nvram.fish @@ -0,0 +1,12 @@ +# completion for nvram (macOS) + +function __fish_nvram_variables + command nvram -p +end + +complete -c nvram -s x -f -d 'Use XML format for reading and writing variables' +complete -c nvram -s p -f -d 'Print all of the firmware variables' +complete -c nvram -s f -r -d 'Set firmware variables from a text file' +complete -c nvram -s d -x -a '(__fish_nvram_variables)' -d 'Deletes the named firmware variable' +complete -c nvram -s c -f -d 'Delete all of the firmware variable' +complete -c nvram -x -a '(__fish_nvram_variables)' diff --git a/share/completions/status.fish b/share/completions/status.fish index 56dabc86f..0b85406b9 100644 --- a/share/completions/status.fish +++ b/share/completions/status.fish @@ -1,14 +1,25 @@ +# Note that when a completion file is sourced a new block scope is created so `set -l` works. +set -l __fish_status_all_commands is-login is-interactive is-block is-command-substitution is-no-job-control is-interactive-job-control is-full-job-control current-filename current-line-number print-stack-trace job-control +# These are the recognized flags. complete -c status -s h -l help --description "Display help and exit" -complete -c status -l is-command-substitution --description "Test if a command substitution is currently evaluated" -complete -c status -l is-block --description "Test if a code block is currently evaluated" -complete -c status -l is-interactive --description "Test if this is an interactive shell" -complete -c status -l is-login --description "Test if this is a login shell" -complete -c status -l is-full-job-control --description "Test if all new jobs are put under job control" -complete -c status -l is-interactive-job-control --description "Test if only interactive new jobs are put under job control" -complete -c status -l is-no-job-control --description "Test if new jobs are never put under job control" -complete -c status -s j -l job-control -xa "full interactive none" --description "Set which jobs are out under job control" -complete -c status -s t -l print-stack-trace --description "Print a list of all function calls leading up to running the current command" -complete -c status -s f -l current-filename --description "Print the filename of the currently running script" -complete -c status -s n -l current-line-number --description "Print the line number of the currently running script" -complete -c status -s t -l print-stack-trace --description "Prints a trace of all function calls on the stack" + +# The "is-something" subcommands. +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-login -d "Test if this is a login shell" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-interactive -d "Test if this is an interactive shell" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-command-substitution -d "Test if a command substitution is currently evaluated" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-block -d "Test if a code block is currently evaluated" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-no-job-control -d "Test if new jobs are never put under job control" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-interactive-job-control -d "Test if only interactive new jobs are put under job control" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a is-full-job-control -d "Test if all new jobs are put under job control" + +# The subcommands that are not "is-something" which don't change the fish state. +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a current-filename -d "Print the filename of the currently running script" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a current-line-number -d "Print the line number of the currently running script" +complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a print-stack-trace -d "Print a list of all function calls leading up to running the current command" + +# 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" +complete -f -c status -n "__fish_seen_subcommand_from job-control" -a full -d "Set all jobs under job control" +complete -f -c status -n "__fish_seen_subcommand_from job-control" -a interactive -d "Set only interactive jobs under job control" +complete -f -c status -n "__fish_seen_subcommand_from job-control" -a none -d "Set no jobs under job control" diff --git a/share/completions/svn.fish b/share/completions/svn.fish index fd24a13d5..56b9082a0 100644 --- a/share/completions/svn.fish +++ b/share/completions/svn.fish @@ -165,7 +165,7 @@ for cmd in $blame $diff $log $merge _svn_cmpl_ $cmd -l extensions -s x -d 'Ignore changes in amount of whitespace' -xa '-b --ignore-space-change' _svn_cmpl_ $cmd -l extensions -s x -d 'Ignore all whitespace' -xa '-w --ignore-all-space' _svn_cmpl_ $cmd -l extensions -s x -d 'Ignore eol style' -xa '-w --ignore-eol-style' - _svn_cmpl_ $cmd -l extensions -s x -d 'Show C function name' -xa '-p --shoe-c-function' + _svn_cmpl_ $cmd -l extensions -s x -d 'Show C function name' -xa '-p --show-c-function' # Next completion doesn't work, since fish doesn't respect -x key #_svn_cmpl_ $cmd -l extensions -n '__fish_seen_subcommand_from --diff-cmd' -xa '(__fish_complete_svn_diff)' diff --git a/share/completions/sysbench.fish b/share/completions/sysbench.fish new file mode 100644 index 000000000..36b1d1bc5 --- /dev/null +++ b/share/completions/sysbench.fish @@ -0,0 +1,111 @@ +### Auto-complete for sysbench (cross-platform and multi-threaded benchmark tool) ### + +### sub commands specification ### +complete -c sysbench -f -a "run\t'Run the test'" +complete -c sysbench -n "__fish_contains_opt test=fileio" -a " + prepare\t'Prepare and create test file' + cleanup\t'Cleanup test files' + " +complete -c sysbench -n "__fish_contains_opt test=oltp" -a " + prepare\t'Prepare test table' + cleanup\t'Cleanup test table' + " + +### generic long options specification ### +complete -c sysbench -x -l num-threads -d 'The total number of worker threads to create (default: 1)' +complete -c sysbench -x -l max-requests -d 'Limit for total number of requests. 0 means unlimited (default: 10000)' +complete -c sysbench -x -l max-time -d 'Limit for total execution time in seconds. 0 means unlimited (default: 0)' +complete -c sysbench -x -l thread-stack-size -d 'Size of stack for each thread (defaut: 32K)' +complete -c sysbench -f -l init-rng -d 'Specifies if random numbers generator should be initialized from timer (defaut: off)' -a 'on off' +complete -c sysbench -x -l test -d 'Name of the test mode to run(required)' -a " + cpu\t'Benchmark cpu by calculating prime numbers' + threads\t'Benchmark scheduler performance' + mutex\t'Benchmark mutex implementation' + fileio\t'Benchmark various file I/O workloads' + oltp\t'Benchmark a real database performance' + " +complete -c sysbench -f -l debug -d 'Print more debug info (default: off)' -a 'on off' +complete -c sysbench -f -l validate -d 'Perform validation of test results where possible (default: off)' -a 'on off' +complete -c sysbench -l help -d 'Print help on general syntax' +complete -c sysbench -l version -d 'Show version of program' +complete -c sysbench -x -l percentile -d 'A percentile rank of query execution times to count (default: 95)' +complete -c sysbench -f -l batch -d 'Dump current results periodically (default: off)' -a 'on off' +complete -c sysbench -x -l batch-delay -d 'Delay between batch dumps in secods (default: 300)' + +### options for test=`cpu` mode ### +complete -c sysbench -n "__fish_contains_opt test=cpu" -x -l cpu-max-prime -d 'Calculation of prime numbers up to the specified value' + +### options for test=`threads` mode ### +complete -c sysbench -n "__fish_contains_opt test=threads" -x -l thread-yields -d 'Number of lock/yield/unlock loops to execute per each request (default: 1000)' +complete -c sysbench -n "__fish_contains_opt test=threads" -x -l thread-locks -d 'Number of mutexes to create (default: 8)' + +### options for test=`mutex` mode ### +complete -c sysbench -n "__fish_contains_opt test=mutex" -x -l mutex-num -d 'Number of mutexes to create (default: 4096)' +complete -c sysbench -n "__fish_contains_opt test=mutex" -x -l memory-scope -d 'Specifies whether each thread uses a global or local allocation (default:global)' -a " + local\t'Allocate memory locally' + global\t'Allocate memory globally' + " +complete -c sysbench -n "__fish_contains_opt test=mutex" -x -l memory-total-size -d 'Total size of data to transfer (default: 100G)' +complete -c sysbench -n "__fish_contains_opt test=mutex" -x -l memory-oper -d 'Type of memory operations' -a 'read write' + +### options for test=`fileio` mode ### +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-num -d 'Number of files to create (default: 128)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-block-size -d 'Block size to use in all I/O operations (default: 16K)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-total-size -d 'Total size of files (default: 2G)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-test-mode -d 'Type of workload to produce' -a " + seqwr\t'Sequential write' + seqrewr\t'Sequential rewrite' + seqrd\t'Sequential read' + rndrd\t'Random read' + rndwr\t'Random write' + rndrw\t'Random read/write' + " +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-io-mode -d 'I/O mode (default: sync)' -a 'sync async fastmmap slowmmap' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-async-backlog -d 'Number of asynchronous operations to queue per thread (default: 128)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-extra-flags -d 'Additional flags to use with open(2)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-fsync-freq -d 'Do fsync() after this number of requests (default: 0)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -f -l file-fsync-all -d 'Do fsync() after each write operation (default: no)' -a 'yes no' +complete -c sysbench -n "__fish_contains_opt test=fileio" -f -l file-fsync-end -d 'Do fsync() at the end of the test (default: yes)' -a 'yes no' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-fsync-mode -d 'Method used for synchronization: fsync, fdatasync (default: fsync)' -a 'fsync fdatasync' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-merged-requests -d 'Upper limit of I/O requests merge (default: 0)' +complete -c sysbench -n "__fish_contains_opt test=fileio" -x -l file-rw-ratio -d 'reads/writes ratio for combined random read/write test (default: 1.5)' + +### options for test=`oltp` mode ### +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-test-mode -d 'Execution mode: simple, complex and nontrx(non-transactional)(default: complex)' -a " + simple\t'Simple' + complex\t'Advanced transactional' + nontrx\t'Non-transactional' + " +complete -c sysbench -n "__fish_contains_opt test=oltp" -f -l oltp-read-only -d 'Read-only mode. No UPDATE, DELETE or INSERT queries will be performed. (default: off)' -a 'on off' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-range-size -d 'Range size for range queries (default: 100)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-point-selects -d 'Number of point select queries in a single transaction (default: 10)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-simple-ranges -d 'Number of simple range queries in a single transaction (default: 1)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-sum-ranges -d 'Number of SUM range queries in a single transaction (default: 1)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-order-ranges -d 'Number of ORDER range queries in a single transaction (default: 1)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-distinct-ranges -d 'Number of DISTINCT range queries in a single transaction (default: 1)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-index-updates -d 'Number of index UPDATE queries in a single transaction (default: 1)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-non-index-updates -d 'Number of non-index UPDATE queries in a single transaction (default: 1)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-nontrx-mode -d 'Type of queries for non-transactional execution mode (default: select)' -a 'select update_key update_nokey insert delete' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-connect-delay -d 'Time to sleep(in microseconds) after each connection to database (default: 10000)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-user-delay-min -d 'Minimum time to sleep(in microseconds) after each request (default: 0)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-user-delay-max -d 'Maximum time to sleep(in microseconds) after each request (default: 0)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-table-name -d 'Name of the test table (default: sbtest)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-table-size -d 'Number of rows in the test table (default: 10000)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l oltp-dist-type -d 'Distribution type of random numbers (default: special)' -a " + uniform\t'Uniform distribution' + gauss\t'Gaussian distribution' + special\t'Specified percent of numbers is generated in a specified percent of cases' + " +complete -c sysbench -n "__fish_contains_opt oltp-dist-type=special" -x -l oltp-dist-pct -d 'Percentage of values to be treated as \'special\'(default: 1)' +complete -c sysbench -n "__fish_contains_opt oltp-dist-type=special" -x -l oltp-dist-res -d 'Percentage of cases when \'special\' values are generated (default: 75)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l db-ps-mode -d 'Use "Prepared Statements" API if supported, otherwise - use clientside statements: disable, auto (default: auto)' -a 'disable auto' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-host -d 'MySQL server host (default: localhost)' -a "(__fish_print_hostnames)" +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-port -d 'MySQL server port (default: 3306)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -l mysql-socket -d 'Unix socket file to communicate with the MySQL server' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-user -d 'MySQL user (default: sbtest)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-password -d 'MySQL password' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-db -d 'MySQL database name (default: sbtest)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-table-engine -d 'Type of the test table to use' -a 'myisam innodb heap ndbcluster bdb maria falcon pbxt' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l mysql-ssl -d 'Use SSL connections. (default: no)' -a 'yes no' +complete -c sysbench -n "__fish_contains_opt test=oltp" -x -l myisam-max-rows -d 'MAX_ROWS option for MyISAM tables (required for big tables) (default: 1000000)' +complete -c sysbench -n "__fish_contains_opt test=oltp" -l mysql-create-options -d 'Additional options passed to CREATE TABLE.' diff --git a/share/completions/tmutil.fish b/share/completions/tmutil.fish new file mode 100644 index 000000000..5553567e3 --- /dev/null +++ b/share/completions/tmutil.fish @@ -0,0 +1,56 @@ +# completion for tmutil (macOS) + +complete -f -c tmutil -n '__fish_use_subcommand' -a addexclusion -d 'Add an exclusion not to back up a file' +complete -f -c tmutil -n '__fish_seen_subcommand_from addexclusion' -s v -d 'Volume exclusion' +complete -f -c tmutil -n '__fish_seen_subcommand_from addexclusion' -s p -d 'Path exclusion' +complete -r -c tmutil -n '__fish_use_subcommand' -a associatedisk -d 'Bind a snapshot volume directory to the specified local disk' +complete -r -c tmutil -n '__fish_use_subcommand' -a calculatedrift -d 'Determine the amount of change between snapshots' +complete -r -c tmutil -n '__fish_use_subcommand' -a compare -d 'Perform a backup diff' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s a -d 'Compare all supported metadata' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s n -d 'No metadata comparison' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s @ -d 'Compare extended attributes' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s c -d 'Compare creation times' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s d -d 'Compare file data forks' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s e -d 'Compare ACLs' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s f -d 'Compare file flags' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s g -d 'Compare GIDs' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s m -d 'Compare file modes' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s s -d 'Compare sizes' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s t -d 'Compare modification times' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s u -d 'Compare UIDs' +complete -r -c tmutil -n '__fish_seen_subcommand_from compare' -s D -d 'Limit traversal depth to depth levels from the beginning of iteration' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s E -d 'Dont take exclusions into account' +complete -r -c tmutil -n '__fish_seen_subcommand_from compare' -s I -d 'Ignore path' +complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s U -d 'Ignore logical volume identity' +complete -r -c tmutil -n '__fish_use_subcommand' -a delete -d 'Delete one or more snapshots' +complete -f -c tmutil -n '__fish_use_subcommand' -a destinationinfo -d 'Print information about destinations' +complete -f -c tmutil -n '__fish_use_subcommand' -a disable -d 'Turn off automatic backups' +complete -f -c tmutil -n '__fish_use_subcommand' -a disablelocal -d 'Turn off local Time Machine snapshots' +complete -f -c tmutil -n '__fish_use_subcommand' -a enable -d 'Turn on automatic backups' +complete -f -c tmutil -n '__fish_use_subcommand' -a enablelocal -d 'Turn on local Time Machine snapshots' +complete -r -c tmutil -n '__fish_use_subcommand' -a inheritbackup -d 'Claim a machine directory or sparsebundle for use by the current machine' +complete -f -c tmutil -n '__fish_use_subcommand' -a isexcluded -d 'Determine if a file, directory, or volume are excluded from backups' +complete -f -c tmutil -n '__fish_use_subcommand' -a latestbackup -d 'Print the path to the latest snapshot' +complete -f -c tmutil -n '__fish_use_subcommand' -a listbackups -d 'Print paths for all snapshots' +complete -f -c tmutil -n '__fish_use_subcommand' -a machinedirectory -d 'Print the path to the current machine directory' +complete -f -c tmutil -n '__fish_use_subcommand' -a removedestination -d 'Removes a backup destination' +complete -f -c tmutil -n '__fish_use_subcommand' -a removeexclusion -d 'Remove an exclusion' +complete -f -c tmutil -n '__fish_seen_subcommand_from removeexclusion' -s v -d 'Volume exclusion' +complete -f -c tmutil -n '__fish_seen_subcommand_from removeexclusion' -s p -d 'Path exclusion' +complete -f -c tmutil -n '__fish_use_subcommand' -a restore -d 'Restore an item' +complete -r -c tmutil -n '__fish_seen_subcommand_from restore' -s v +complete -r -c tmutil -n '__fish_use_subcommand' -a setdestination -d 'Set a backup destination' +complete -f -c tmutil -n '__fish_seen_subcommand_from setdestination' -s a -d 'Add to the list of destinations' +complete -f -c tmutil -n '__fish_seen_subcommand_from setdestination' -s p -d 'Enter the password at a non-echoing interactive prompt' +complete -f -c tmutil -n '__fish_use_subcommand' -a snapshot -d 'Create new local Time Machine snapshot' +complete -f -c tmutil -n '__fish_use_subcommand' -a startbackup -d 'Begin a backup if one is not already running' +complete -f -c tmutil -n '__fish_seen_subcommand_from startbackup' -s a -l auto -d 'Automatic mode' +complete -f -c tmutil -n '__fish_seen_subcommand_from startbackup' -s b -l block -d 'Block until finished' +complete -f -c tmutil -n '__fish_seen_subcommand_from startbackup' -s r -l rotation -d 'Autmatic rotation' +complete -r -c tmutil -n '__fish_seen_subcommand_from startbackup' -s d -l destination -d 'Backup destination' +complete -f -c tmutil -n '__fish_use_subcommand' -a stopbackup -d 'Cancel a backup currently in progress' +complete -r -c tmutil -n '__fish_use_subcommand' -a uniquesize -d 'Analyze the specified path and determine its unique size' +complete -r -c tmutil -n '__fish_use_subcommand' -a verifychecksums -d 'Verify snapshot' +complete -f -c tmutil -n '__fish_use_subcommand' -a version -d 'Print version' + +complete -f -c tmutil -n '__fish_seen_subcommand_from destinationinfo isexcluded compare' -s X -d 'Print as XML' diff --git a/share/functions/__fish_config_interactive.fish b/share/functions/__fish_config_interactive.fish index 691593379..c7143dd65 100644 --- a/share/functions/__fish_config_interactive.fish +++ b/share/functions/__fish_config_interactive.fish @@ -220,13 +220,18 @@ function __fish_config_interactive -d "Initializations that should be performed # Notify terminals when $PWD changes (issue #906) # VTE and Terminal.app support this in practice. if test "0$VTE_VERSION" -ge 3405 -o "$TERM_PROGRAM" = "Apple_Terminal" - function fish_title; end function __update_cwd_osc --on-variable PWD --description 'Notify capable terminals when $PWD changes' status --is-command-substitution or test -n "$INSIDE_EMACS" and return printf \e\]7\;file://\%s\%s\a (hostname) (echo -n $PWD | __fish_urlencode) end + if test "$TERM_PROGRAM" = "Apple_Terminal" + # Suppress duplicative title display on Terminal.app + echo -n \e\]0\;\a # clear existing title + function fish_title + end + end __update_cwd_osc # Run once because we might have already inherited a PWD from an old tab end diff --git a/share/functions/__fish_print_help.fish b/share/functions/__fish_print_help.fish index 265f96159..31190e552 100644 --- a/share/functions/__fish_print_help.fish +++ b/share/functions/__fish_print_help.fish @@ -39,10 +39,11 @@ function __fish_print_help --description "Print help message for the specified f set cols (math $cols - 4) # leave a bit of space on the right set rLL -rLL=$cols[1]n end + set -lx GROFF_TMAC_PATH $__fish_datadir/groff if test -e "$__fish_datadir/man/man1/$item.1" - set help (nroff -man -c -t $rLL "$__fish_datadir/man/man1/$item.1" ^/dev/null) + set help (nroff -c -man -mfish -t $rLL "$__fish_datadir/man/man1/$item.1" ^/dev/null) else if test -e "$__fish_datadir/man/man1/$item.1.gz" - set help (gunzip -c "$__fish_datadir/man/man1/$item.1.gz" ^/dev/null | nroff -man -c -t $rLL ^/dev/null) + set help (gunzip -c "$__fish_datadir/man/man1/$item.1.gz" ^/dev/null | nroff -c -man -mfish -t $rLL ^/dev/null) end # The original implementation trimmed off the top 5 lines and bottom 3 lines diff --git a/share/functions/alias.fish b/share/functions/alias.fish index 6a4973823..41d1b7ae8 100644 --- a/share/functions/alias.fish +++ b/share/functions/alias.fish @@ -1,4 +1,4 @@ -function alias --description 'Legacy function for creating shellscript functions using an alias-like syntax' +function alias --description 'Creates a function wrapping a command' if count $argv > /dev/null switch $argv[1] case -h --h --he --hel --help @@ -14,8 +14,12 @@ function alias --description 'Legacy function for creating shellscript functions switch (count $argv) case 0 - echo "Fish implements aliases using functions. Use 'functions' builtin to see list of functions and 'functions function_name' to see function definition, type 'help alias' for more information." - return 1 + for func in (functions -n) + set -l output (functions $func | string match -r -- "function .* --description '(alias .*)'" | string split \n) + set -q output[2] + and echo $output[2] + end + return 0 case 1 set -l tmp (string replace -r "=" '\n' -- $argv) "" set name $tmp[1] @@ -60,5 +64,6 @@ function alias --description 'Legacy function for creating shellscript functions set prefix command end end - echo "function $name --wraps $first_word; $prefix $first_word $body \$argv; end" | source + set -l cmd_string (string escape "alias $argv") + echo "function $name --wraps $first_word --description $cmd_string; $prefix $first_word $body \$argv; end" | source end diff --git a/share/functions/fish_fallback_prompt.fish b/share/functions/fish_fallback_prompt.fish index ae4fa0210..1910eeaea 100644 --- a/share/functions/fish_fallback_prompt.fish +++ b/share/functions/fish_fallback_prompt.fish @@ -3,11 +3,6 @@ # Set the default prompt command. function fish_fallback_prompt --description "A simple fallback prompt without too much color or special characters for linux VTs" - # Just calculate this once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) - end - set -l color_cwd set -l suffix switch $USER @@ -23,5 +18,5 @@ function fish_fallback_prompt --description "A simple fallback prompt without to set suffix '>' end - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " + echo -n -s "$USER" @ (prompt_hostname) ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " end diff --git a/share/functions/fish_key_reader.fish b/share/functions/fish_key_reader.fish new file mode 100644 index 000000000..5a955e13f --- /dev/null +++ b/share/functions/fish_key_reader.fish @@ -0,0 +1,7 @@ +# check if command fish_key_reader works and is the same version that +# came with this fish. This will happen one time. +command -s fish_key_reader > /dev/null +and command fish_key_reader --version 2>&1 | string match -rq $FISH_VERSION +# if alias doesn't define the function here, this is an autoloaded "nothing". +# the command (if there is one) will be used by default. +or alias fish_key_reader=(string escape $__fish_bin_dir/fish_key_reader) diff --git a/share/functions/fish_prompt.fish b/share/functions/fish_prompt.fish index b4a350b8f..7f8581971 100644 --- a/share/functions/fish_prompt.fish +++ b/share/functions/fish_prompt.fish @@ -3,11 +3,6 @@ # Set the default prompt command. function fish_prompt --description "Write out the prompt" - # Just calculate this once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) - end - set -l color_cwd set -l suffix switch $USER @@ -23,5 +18,5 @@ function fish_prompt --description "Write out the prompt" set suffix '>' end - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " + echo -n -s "$USER" @ (prompt_hostname) ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " end diff --git a/share/functions/fish_vi_cursor.fish b/share/functions/fish_vi_cursor.fish index be04eb430..1ab8ef25f 100644 --- a/share/functions/fish_vi_cursor.fish +++ b/share/functions/fish_vi_cursor.fish @@ -1,11 +1,46 @@ function fish_vi_cursor -d 'Set cursor shape for different vi modes' - # Since we read exported variables (KONSOLE_PROFILE_NAME and ITERM_PROFILE) - # we need to check harder if we're actually in a supported terminal, - # because we might be in a term-in-a-term (emacs ansi-term). - if not contains -- $TERM xterm konsole xterm-256color konsole-256color - and not set -q TMUX + # Check hard if we are in a supporting terminal. + # + # Challenges here are term-in-a-terms (emacs ansi-term does not support this, tmux does), + # that we can only figure out if we are in konsole/iterm/vte via exported variables, + # and ancient xterm versions. + # + # tmux defaults to $TERM = screen, but can do this if it is in a supporting terminal. + # Unfortunately, we can only detect this via the exported variables, so we miss some cases. + # + # We will also miss some cases of terminal-stacking, + # e.g. tmux started in suckless' st (no support) started in konsole. + # But since tmux in konsole seems rather common and that case so uncommon, + # we will just fail there (though it seems that tmux or st swallow it anyway). + # + # We use the `tput` here just to see if terminfo thinks we can change the cursor. + # We cannot use that sequence directly as it's not the correct one for konsole and iTerm, + # and because we may want to change the cursor even though terminfo says we can't (tmux). + if not tput Ss > /dev/null ^/dev/null + # Whitelist tmux... + and not begin + set -q TMUX + # ...in a supporting term... + and begin set -q KONSOLE_PROFILE_NAME + or set -q ITERM_PROFILE + or test "$VTE_VERSION" -gt 1910 + end + # .. unless an unsupporting terminal has been started in tmux inside a supporting one + and begin string match -q "screen*" -- $TERM + or string match -q "tmux*" -- $TERM + end + end + and not string match -q "konsole*" -- $TERM + # Blacklist + or begin + # vte-based terms set $TERM = xterm*, but only gained support relatively recently. + set -q VTE_VERSION + and test "$VTE_VERSION" -le 1910 + end + or set -q INSIDE_EMACS return end + set -l terminal $argv[1] set -q terminal[1] or set terminal auto @@ -18,11 +53,9 @@ function fish_vi_cursor -d 'Set cursor shape for different vi modes' or set -q ITERM_PROFILE set function __fish_cursor_konsole set uses_echo 1 - else if string match -q "xterm*" -- $TERM; or test "$VTE_VERSION" -gt 1910 + else set function __fish_cursor_xterm set uses_echo 1 - else - return 1 end case konsole set function __fish_cursor_konsole diff --git a/share/functions/open.fish b/share/functions/open.fish index 533b3df19..9eaa7eda6 100644 --- a/share/functions/open.fish +++ b/share/functions/open.fish @@ -3,7 +3,7 @@ # application for the file. # -if not test (uname) = Darwin +if not command -s open >/dev/null function open --description "Open file in default application" if count $argv >/dev/null switch $argv[1] diff --git a/share/functions/prompt_hostname.fish b/share/functions/prompt_hostname.fish new file mode 100644 index 000000000..4348bce21 --- /dev/null +++ b/share/functions/prompt_hostname.fish @@ -0,0 +1,10 @@ +# Fetching the host name can be expensive if there is a problem with DNS or whatever subsystem the +# hostname command uses. So cache the answer so including it in the prompt doesn't make fish seem +# slow. +if not set -q __fish_prompt_hostname + set -g __fish_prompt_hostname (hostname | string split '.')[1] +end + +function prompt_hostname + echo $__fish_prompt_hostname +end diff --git a/share/functions/suspend.fish b/share/functions/suspend.fish index 8d609dec1..af2ce5012 100644 --- a/share/functions/suspend.fish +++ b/share/functions/suspend.fish @@ -1,4 +1,8 @@ function suspend -d "Suspend the current shell." + if contains -- $argv --help; or contains -- $argv -h + __fish_print_help suspend + and return 0 + end if begin contains -- $argv --force or not status --is-interactive and not status --is-login end diff --git a/share/groff/fish.tmac b/share/groff/fish.tmac new file mode 100644 index 000000000..cf8dbe8fa --- /dev/null +++ b/share/groff/fish.tmac @@ -0,0 +1,14 @@ +.\" This is needed on systems that ship with groff versions older than 1.20; +.\" such as macOS up thru Sierra (10.12). See fish issue #2673. +.\" +.\" For UTF-8, map some characters conservatively for the sake +.\" of easy cut and paste. +. +.if '\*[.T]'utf8' \{\ +. rchar \- - ' ` +. +. char \- \N'45' +. char - \N'45' +. char ' \N'39' +. char ` \N'96' +.\} diff --git a/share/tools/web_config/sample_prompts/acidhub.fish b/share/tools/web_config/sample_prompts/acidhub.fish index afb702463..08fd47fe7 100644 --- a/share/tools/web_config/sample_prompts/acidhub.fish +++ b/share/tools/web_config/sample_prompts/acidhub.fish @@ -12,8 +12,8 @@ function fish_prompt -d "Write out the prompt" if [ (_git_branch_name) ] set -l git_branch (set_color -o blue)(_git_branch_name) if [ (_is_git_dirty) ] - for i in (git branch -qv --no-color| string match -r \*|cut -d' ' -f4-|cut -d] -f1|tr , \n)\ - (git status --porcelain | cut -c 1-2 | uniq) + for i in (git branch -qv --no-color | string match -r \* | cut -d' ' -f4- | cut -d] -f1 | tr , \n)\ + (git status --porcelain | cut -c 1-2 | uniq) switch $i case "*[ahead *" set git_status "$git_status"(set_color red)⬆ @@ -39,29 +39,10 @@ function fish_prompt -d "Write out the prompt" set git_info "(git$git_status$git_branch"(set_color white)")" end set_color -b black - printf '%s%s%s%s%s%s%s%s%s%s%s%s%s'\ - (set_color -o white) \ - '❰' \ - (set_color green) \ - $USER \ - (set_color white) \ - '❙' \ - (set_color yellow) \ - (echo $PWD | sed -e "s|^$HOME|~|") \ - (set_color white) \ - $git_info \ - (set_color white) \ - '❱' \ - (set_color white) + printf '%s%s%s%s%s%s%s%s%s%s%s%s%s' (set_color -o white) '❰' (set_color green) $USER (set_color white) '❙' (set_color yellow) (echo $PWD | sed -e "s|^$HOME|~|") (set_color white) $git_info (set_color white) '❱' (set_color white) if test $laststatus -eq 0 - printf "%s✔%s≻%s " \ - (set_color -o green)\ - (set_color white) \ - (set_color normal) + printf "%s✔%s≻%s " (set_color -o green) (set_color white) (set_color normal) else - printf "%s✘%s≻%s " \ - (set_color -o red) \ - (set_color white) \ - (set_color normal) + printf "%s✘%s≻%s " (set_color -o red) (set_color white) (set_color normal) end end diff --git a/share/tools/web_config/sample_prompts/classic.fish b/share/tools/web_config/sample_prompts/classic.fish index b092803e4..2eaa4ddd0 100644 --- a/share/tools/web_config/sample_prompts/classic.fish +++ b/share/tools/web_config/sample_prompts/classic.fish @@ -1,24 +1,19 @@ # name: Classic function fish_prompt --description "Write out the prompt" - # Just calculate this once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) - end + set -l color_cwd + set -l suffix + switch $USER + case root toor + if set -q fish_color_cwd_root + set color_cwd $fish_color_cwd_root + else + set color_cwd $fish_color_cwd + end + set suffix '#' + case '*' + set color_cwd $fish_color_cwd + set suffix '>' + end - set -l color_cwd - set -l suffix - switch $USER - case root toor - if set -q fish_color_cwd_root - set color_cwd $fish_color_cwd_root - else - set color_cwd $fish_color_cwd - end - set suffix '#' - case '*' - set color_cwd $fish_color_cwd - set suffix '>' - end - - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " + echo -n -s "$USER" @ (prompt_hostname) ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " end diff --git a/share/tools/web_config/sample_prompts/classic_status.fish b/share/tools/web_config/sample_prompts/classic_status.fish index fe64f7934..dd0548949 100644 --- a/share/tools/web_config/sample_prompts/classic_status.fish +++ b/share/tools/web_config/sample_prompts/classic_status.fish @@ -10,25 +10,20 @@ function fish_prompt --description "Write out the prompt" printf "%s(%d)%s " (set_color red --bold) $last_status (set_color normal) end - # Just calculate this once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) - end - set -l color_cwd set -l suffix switch $USER - case root toor - if set -q fish_color_cwd_root - set color_cwd $fish_color_cwd_root - else + case root toor + if set -q fish_color_cwd_root + set color_cwd $fish_color_cwd_root + else + set color_cwd $fish_color_cwd + end + set suffix '#' + case '*' set color_cwd $fish_color_cwd - end - set suffix '#' - case '*' - set color_cwd $fish_color_cwd - set suffix '>' + set suffix '>' end - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " + echo -n -s "$USER" @ (prompt_hostname) ' ' (set_color $color_cwd) (prompt_pwd) (set_color normal) "$suffix " end diff --git a/share/tools/web_config/sample_prompts/classic_vcs.fish b/share/tools/web_config/sample_prompts/classic_vcs.fish index 755c95027..beac87a8b 100644 --- a/share/tools/web_config/sample_prompts/classic_vcs.fish +++ b/share/tools/web_config/sample_prompts/classic_vcs.fish @@ -3,71 +3,68 @@ # vim: set noet: function fish_prompt --description 'Write out the prompt' - set -l last_status $status + set -l last_status $status + set -l normal (set_color normal) - # Just calculate this once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) - end + # Hack; fish_config only copies the fish_prompt function (see #736) + if not set -q -g __fish_classic_git_functions_defined + set -g __fish_classic_git_functions_defined - set -l normal (set_color normal) + function __fish_repaint_user --on-variable fish_color_user --description "Event handler, repaint when fish_color_user changes" + if status --is-interactive + commandline -f repaint ^/dev/null + end + end - # Hack; fish_config only copies the fish_prompt function (see #736) - if not set -q -g __fish_classic_git_functions_defined - set -g __fish_classic_git_functions_defined + function __fish_repaint_host --on-variable fish_color_host --description "Event handler, repaint when fish_color_host changes" + if status --is-interactive + commandline -f repaint ^/dev/null + end + end - function __fish_repaint_user --on-variable fish_color_user --description "Event handler, repaint when fish_color_user changes" - if status --is-interactive - commandline -f repaint ^/dev/null - end - end - - function __fish_repaint_host --on-variable fish_color_host --description "Event handler, repaint when fish_color_host changes" - if status --is-interactive - commandline -f repaint ^/dev/null - end - end - - function __fish_repaint_status --on-variable fish_color_status --description "Event handler; repaint when fish_color_status changes" - if status --is-interactive - commandline -f repaint ^/dev/null - end - end + function __fish_repaint_status --on-variable fish_color_status --description "Event handler; repaint when fish_color_status changes" + if status --is-interactive + commandline -f repaint ^/dev/null + end + end - function __fish_repaint_bind_mode --on-variable fish_key_bindings --description "Event handler; repaint when fish_key_bindings changes" - if status --is-interactive - commandline -f repaint ^/dev/null - end - end + function __fish_repaint_bind_mode --on-variable fish_key_bindings --description "Event handler; repaint when fish_key_bindings changes" + if status --is-interactive + commandline -f repaint ^/dev/null + end + end - # initialize our new variables - if not set -q __fish_classic_git_prompt_initialized - set -qU fish_color_user; or set -U fish_color_user -o green - set -qU fish_color_host; or set -U fish_color_host -o cyan - set -qU fish_color_status; or set -U fish_color_status red - set -U __fish_classic_git_prompt_initialized - end - end + # initialize our new variables + if not set -q __fish_classic_git_prompt_initialized + set -qU fish_color_user + or set -U fish_color_user -o green + set -qU fish_color_host + or set -U fish_color_host -o cyan + set -qU fish_color_status + or set -U fish_color_status red + set -U __fish_classic_git_prompt_initialized + end + end - set -l color_cwd - set -l prefix - switch $USER - case root toor - if set -q fish_color_cwd_root - set color_cwd $fish_color_cwd_root - else - set color_cwd $fish_color_cwd - end - set suffix '#' - case '*' - set color_cwd $fish_color_cwd - set suffix '>' - end + set -l color_cwd + set -l prefix + switch $USER + case root toor + if set -q fish_color_cwd_root + set color_cwd $fish_color_cwd_root + else + set color_cwd $fish_color_cwd + end + set suffix '#' + case '*' + set color_cwd $fish_color_cwd + set suffix '>' + end - set -l prompt_status - if test $last_status -ne 0 - set prompt_status ' ' (set_color $fish_color_status) "[$last_status]" "$normal" - end + set -l prompt_status + if test $last_status -ne 0 + set prompt_status ' ' (set_color $fish_color_status) "[$last_status]" "$normal" + end - echo -n -s (set_color $fish_color_user) "$USER" $normal @ (set_color $fish_color_host) "$__fish_prompt_hostname" $normal ' ' (set_color $color_cwd) (prompt_pwd) $normal (__fish_vcs_prompt) $normal $prompt_status "> " + echo -n -s (set_color $fish_color_user) "$USER" $normal @ (set_color $fish_color_host) (prompt_hostname) $normal ' ' (set_color $color_cwd) (prompt_pwd) $normal (__fish_vcs_prompt) $normal $prompt_status "> " end diff --git a/share/tools/web_config/sample_prompts/debian_chroot.fish b/share/tools/web_config/sample_prompts/debian_chroot.fish index 80acfa9a5..249dc91ab 100644 --- a/share/tools/web_config/sample_prompts/debian_chroot.fish +++ b/share/tools/web_config/sample_prompts/debian_chroot.fish @@ -2,54 +2,55 @@ # author: Maurizio De Santis function fish_prompt --description 'Write out the prompt, prepending the Debian chroot environment if present' + if not set -q __fish_prompt_normal + set -g __fish_prompt_normal (set_color normal) + end - # Just calculate these once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) + if not set -q __fish_prompt_chroot_env + set -g __fish_prompt_chroot_env (set_color yellow) + end + + # Set variable identifying the chroot you work in (used in the prompt below) + if begin + not set -q debian_chroot + and test -r /etc/debian_chroot end - - if not set -q __fish_prompt_normal - set -g __fish_prompt_normal (set_color normal) + set debian_chroot (cat /etc/debian_chroot) + end + if begin + not set -q __fish_debian_chroot_prompt + and set -q debian_chroot + and test -n $debian_chroot end + set -g __fish_debian_chroot_prompt "($debian_chroot)" + end - if not set -q __fish_prompt_chroot_env - set -g __fish_prompt_chroot_env (set_color yellow) - end + # Prepend the chroot environment if present + if set -q __fish_debian_chroot_prompt + echo -n -s "$__fish_prompt_chroot_env" "$__fish_debian_chroot_prompt" "$__fish_prompt_normal" ' ' + end - # Set variable identifying the chroot you work in (used in the prompt below) - if begin; not set -q debian_chroot; and test -r /etc/debian_chroot; end - set debian_chroot (cat /etc/debian_chroot) - end - if begin; not set -q __fish_debian_chroot_prompt; and set -q debian_chroot; and test -n $debian_chroot; end - set -g __fish_debian_chroot_prompt "($debian_chroot)" - end + switch $USER - # Prepend the chroot environment if present - if set -q __fish_debian_chroot_prompt - echo -n -s "$__fish_prompt_chroot_env" "$__fish_debian_chroot_prompt" "$__fish_prompt_normal" ' ' - end + case root toor - switch $USER - - case root toor - - if not set -q __fish_prompt_cwd - if set -q fish_color_cwd_root - set -g __fish_prompt_cwd (set_color $fish_color_cwd_root) - else - set -g __fish_prompt_cwd (set_color $fish_color_cwd) - end + if not set -q __fish_prompt_cwd + if set -q fish_color_cwd_root + set -g __fish_prompt_cwd (set_color $fish_color_cwd_root) + else + set -g __fish_prompt_cwd (set_color $fish_color_cwd) end + end - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' "$__fish_prompt_cwd" (prompt_pwd) "$__fish_prompt_normal" '# ' + echo -n -s "$USER" @ (prompt_hostname) ' ' "$__fish_prompt_cwd" (prompt_pwd) "$__fish_prompt_normal" '# ' - case '*' + case '*' - if not set -q __fish_prompt_cwd - set -g __fish_prompt_cwd (set_color $fish_color_cwd) - end + if not set -q __fish_prompt_cwd + set -g __fish_prompt_cwd (set_color $fish_color_cwd) + end - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' "$__fish_prompt_cwd" (prompt_pwd) "$__fish_prompt_normal" '> ' + echo -n -s "$USER" @ (prompt_hostname) ' ' "$__fish_prompt_cwd" (prompt_pwd) "$__fish_prompt_normal" '> ' - end + end end diff --git a/share/tools/web_config/sample_prompts/informative.fish b/share/tools/web_config/sample_prompts/informative.fish index 749d81713..3f084bd30 100644 --- a/share/tools/web_config/sample_prompts/informative.fish +++ b/share/tools/web_config/sample_prompts/informative.fish @@ -6,46 +6,41 @@ function fish_prompt --description 'Write out the prompt' #Save the return status of the previous command set stat $status -# Just calculate these once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname|cut -d . -f 1) - end - -if not set -q __fish_prompt_normal + if not set -q __fish_prompt_normal set -g __fish_prompt_normal (set_color normal) end -if not set -q __fish_color_blue + if not set -q __fish_color_blue set -g __fish_color_blue (set_color -o blue) end -#Set the color for the status depending on the value + #Set the color for the status depending on the value set __fish_color_status (set_color -o green) if test $stat -gt 0 set __fish_color_status (set_color -o red) end -switch $USER + switch $USER -case root toor + case root toor -if not set -q __fish_prompt_cwd - if set -q fish_color_cwd_root - set -g __fish_prompt_cwd (set_color $fish_color_cwd_root) - else + if not set -q __fish_prompt_cwd + if set -q fish_color_cwd_root + set -g __fish_prompt_cwd (set_color $fish_color_cwd_root) + else + set -g __fish_prompt_cwd (set_color $fish_color_cwd) + end + end + + printf '%s@%s %s%s%s# ' $USER (prompt_hostname) "$__fish_prompt_cwd" (prompt_pwd) "$__fish_prompt_normal" + + case '*' + + if not set -q __fish_prompt_cwd set -g __fish_prompt_cwd (set_color $fish_color_cwd) end - end -printf '%s@%s %s%s%s# ' $USER $__fish_prompt_hostname "$__fish_prompt_cwd" (prompt_pwd) "$__fish_prompt_normal" - -case '*' - -if not set -q __fish_prompt_cwd - set -g __fish_prompt_cwd (set_color $fish_color_cwd) - end - -printf '[%s] %s%s@%s %s%s %s(%s)%s \f\r> ' (date "+%H:%M:%S") "$__fish_color_blue" $USER $__fish_prompt_hostname "$__fish_prompt_cwd" "$PWD" "$__fish_color_status" "$stat" "$__fish_prompt_normal" + printf '[%s] %s%s@%s %s%s %s(%s)%s \f\r> ' (date "+%H:%M:%S") "$__fish_color_blue" $USER (prompt_hostname) "$__fish_prompt_cwd" "$PWD" "$__fish_color_status" "$stat" "$__fish_prompt_normal" -end + end end diff --git a/share/tools/web_config/sample_prompts/informative_vcs.fish b/share/tools/web_config/sample_prompts/informative_vcs.fish index c9fe667f6..f8d01faf8 100644 --- a/share/tools/web_config/sample_prompts/informative_vcs.fish +++ b/share/tools/web_config/sample_prompts/informative_vcs.fish @@ -4,94 +4,94 @@ function fish_prompt --description 'Write out the prompt' - if not set -q __fish_git_prompt_show_informative_status - set -g __fish_git_prompt_show_informative_status 1 - end - if not set -q __fish_git_prompt_hide_untrackedfiles - set -g __fish_git_prompt_hide_untrackedfiles 1 - end + if not set -q __fish_git_prompt_show_informative_status + set -g __fish_git_prompt_show_informative_status 1 + end + if not set -q __fish_git_prompt_hide_untrackedfiles + set -g __fish_git_prompt_hide_untrackedfiles 1 + end - if not set -q __fish_git_prompt_color_branch - set -g __fish_git_prompt_color_branch magenta --bold - end - if not set -q __fish_git_prompt_showupstream - set -g __fish_git_prompt_showupstream "informative" - end - if not set -q __fish_git_prompt_char_upstream_ahead - set -g __fish_git_prompt_char_upstream_ahead "↑" - end - if not set -q __fish_git_prompt_char_upstream_behind - set -g __fish_git_prompt_char_upstream_behind "↓" - end - if not set -q __fish_git_prompt_char_upstream_prefix - set -g __fish_git_prompt_char_upstream_prefix "" - end + if not set -q __fish_git_prompt_color_branch + set -g __fish_git_prompt_color_branch magenta --bold + end + if not set -q __fish_git_prompt_showupstream + set -g __fish_git_prompt_showupstream "informative" + end + if not set -q __fish_git_prompt_char_upstream_ahead + set -g __fish_git_prompt_char_upstream_ahead "↑" + end + if not set -q __fish_git_prompt_char_upstream_behind + set -g __fish_git_prompt_char_upstream_behind "↓" + end + if not set -q __fish_git_prompt_char_upstream_prefix + set -g __fish_git_prompt_char_upstream_prefix "" + end - if not set -q __fish_git_prompt_char_stagedstate - set -g __fish_git_prompt_char_stagedstate "●" - end - if not set -q __fish_git_prompt_char_dirtystate - set -g __fish_git_prompt_char_dirtystate "✚" - end - if not set -q __fish_git_prompt_char_untrackedfiles - set -g __fish_git_prompt_char_untrackedfiles "…" - end - if not set -q __fish_git_prompt_char_conflictedstate - set -g __fish_git_prompt_char_conflictedstate "✖" - end - if not set -q __fish_git_prompt_char_cleanstate - set -g __fish_git_prompt_char_cleanstate "✔" - end + if not set -q __fish_git_prompt_char_stagedstate + set -g __fish_git_prompt_char_stagedstate "●" + end + if not set -q __fish_git_prompt_char_dirtystate + set -g __fish_git_prompt_char_dirtystate "✚" + end + if not set -q __fish_git_prompt_char_untrackedfiles + set -g __fish_git_prompt_char_untrackedfiles "…" + end + if not set -q __fish_git_prompt_char_conflictedstate + set -g __fish_git_prompt_char_conflictedstate "✖" + end + if not set -q __fish_git_prompt_char_cleanstate + set -g __fish_git_prompt_char_cleanstate "✔" + end - if not set -q __fish_git_prompt_color_dirtystate - set -g __fish_git_prompt_color_dirtystate blue - end - if not set -q __fish_git_prompt_color_stagedstate - set -g __fish_git_prompt_color_stagedstate yellow - end - if not set -q __fish_git_prompt_color_invalidstate - set -g __fish_git_prompt_color_invalidstate red - end - if not set -q __fish_git_prompt_color_untrackedfiles - set -g __fish_git_prompt_color_untrackedfiles $fish_color_normal - end - if not set -q __fish_git_prompt_color_cleanstate - set -g __fish_git_prompt_color_cleanstate green --bold - end + if not set -q __fish_git_prompt_color_dirtystate + set -g __fish_git_prompt_color_dirtystate blue + end + if not set -q __fish_git_prompt_color_stagedstate + set -g __fish_git_prompt_color_stagedstate yellow + end + if not set -q __fish_git_prompt_color_invalidstate + set -g __fish_git_prompt_color_invalidstate red + end + if not set -q __fish_git_prompt_color_untrackedfiles + set -g __fish_git_prompt_color_untrackedfiles $fish_color_normal + end + if not set -q __fish_git_prompt_color_cleanstate + set -g __fish_git_prompt_color_cleanstate green --bold + end - set -l last_status $status + set -l last_status $status - if not set -q __fish_prompt_normal - set -g __fish_prompt_normal (set_color normal) - end + if not set -q __fish_prompt_normal + set -g __fish_prompt_normal (set_color normal) + end - set -l color_cwd - set -l prefix - switch $USER - case root toor - if set -q fish_color_cwd_root - set color_cwd $fish_color_cwd_root - else - set color_cwd $fish_color_cwd - end - set suffix '#' - case '*' - set color_cwd $fish_color_cwd - set suffix '$' - end + set -l color_cwd + set -l prefix + switch $USER + case root toor + if set -q fish_color_cwd_root + set color_cwd $fish_color_cwd_root + else + set color_cwd $fish_color_cwd + end + set suffix '#' + case '*' + set color_cwd $fish_color_cwd + set suffix '$' + end - # PWD - set_color $color_cwd - echo -n (prompt_pwd) - set_color normal + # PWD + set_color $color_cwd + echo -n (prompt_pwd) + set_color normal - printf '%s ' (__fish_vcs_prompt) + printf '%s ' (__fish_vcs_prompt) - if not test $last_status -eq 0 - set_color $fish_color_error - end + if not test $last_status -eq 0 + set_color $fish_color_error + end - echo -n "$suffix " + echo -n "$suffix " - set_color normal + set_color normal end diff --git a/share/tools/web_config/sample_prompts/justadollar.fish b/share/tools/web_config/sample_prompts/justadollar.fish index d39a2f4a2..a9b438a00 100644 --- a/share/tools/web_config/sample_prompts/justadollar.fish +++ b/share/tools/web_config/sample_prompts/justadollar.fish @@ -1,4 +1,4 @@ # name: Just a Dollar function fish_prompt - echo -n '$ ' + echo -n '$ ' end diff --git a/share/tools/web_config/sample_prompts/lonetwin.fish b/share/tools/web_config/sample_prompts/lonetwin.fish index d22beff41..a8844017d 100644 --- a/share/tools/web_config/sample_prompts/lonetwin.fish +++ b/share/tools/web_config/sample_prompts/lonetwin.fish @@ -2,19 +2,13 @@ # author: Steve function fish_prompt --description 'Write out the prompt' - # Just calculate these once, to save a few cycles when displaying the prompt - if not set -q __fish_prompt_hostname - set -g __fish_prompt_hostname (hostname -s) - end + if not set -q __fish_prompt_normal + set -g __fish_prompt_normal (set_color normal) + end - if not set -q __fish_prompt_normal - set -g __fish_prompt_normal (set_color normal) - end - - if not set -q __fish_prompt_cwd - set -g __fish_prompt_cwd (set_color $fish_color_cwd) - end - - echo -n -s "$USER" @ "$__fish_prompt_hostname" ' ' "$__fish_prompt_cwd" (prompt_pwd) (__fish_vcs_prompt) "$__fish_prompt_normal" '> ' + if not set -q __fish_prompt_cwd + set -g __fish_prompt_cwd (set_color $fish_color_cwd) + end + echo -n -s "$USER" @ (prompt_hostname) ' ' "$__fish_prompt_cwd" (prompt_pwd) (__fish_vcs_prompt) "$__fish_prompt_normal" '> ' end diff --git a/share/tools/web_config/sample_prompts/minimalist.fish b/share/tools/web_config/sample_prompts/minimalist.fish index 14cc04458..70fb357f6 100644 --- a/share/tools/web_config/sample_prompts/minimalist.fish +++ b/share/tools/web_config/sample_prompts/minimalist.fish @@ -2,8 +2,8 @@ # author: ridiculous_fish function fish_prompt - set_color $fish_color_cwd - echo -n (basename $PWD) - set_color normal - echo -n ' ) ' + set_color $fish_color_cwd + echo -n (basename $PWD) + set_color normal + echo -n ' ) ' end diff --git a/share/tools/web_config/sample_prompts/nim.fish b/share/tools/web_config/sample_prompts/nim.fish index 5d2dac394..6c6616821 100644 --- a/share/tools/web_config/sample_prompts/nim.fish +++ b/share/tools/web_config/sample_prompts/nim.fish @@ -2,8 +2,11 @@ # author: Guilhem "Nim" Saurel − https://github.com/nim65s/dotfiles/ function fish_prompt - and set retc green; or set retc red - tty|string match -q -r tty; and set tty tty; or set tty pts + and set retc green + or set retc red + tty | string match -q -r tty + and set tty tty + or set tty pts set_color $retc if [ $tty = tty ] @@ -26,7 +29,7 @@ function fish_prompt else set_color -o cyan end - echo -n (hostname) + echo -n (prompt_hostname) set_color -o white #echo -n :(prompt_pwd) echo -n :(pwd|sed "s=$HOME=~=") @@ -46,16 +49,16 @@ function fish_prompt echo -n (date +%X) set_color -o green echo -n ] - + if type -q acpi - if [ (acpi -a 2> /dev/null | string match -r off) ] - echo -n '─[' - set_color -o red - echo -n (acpi -b|cut -d' ' -f 4-) - set_color -o green - echo -n ']' - end - end + if [ (acpi -a 2> /dev/null | string match -r off) ] + echo -n '─[' + set_color -o red + echo -n (acpi -b|cut -d' ' -f 4-) + set_color -o green + echo -n ']' + end + end echo set_color normal for job in (jobs) diff --git a/share/tools/web_config/sample_prompts/pythonista.fish b/share/tools/web_config/sample_prompts/pythonista.fish index ad772e2f7..9529035c4 100644 --- a/share/tools/web_config/sample_prompts/pythonista.fish +++ b/share/tools/web_config/sample_prompts/pythonista.fish @@ -3,28 +3,28 @@ function fish_prompt - if not set -q VIRTUAL_ENV_DISABLE_PROMPT - set -g VIRTUAL_ENV_DISABLE_PROMPT true - end - set_color yellow - printf '%s' (whoami) - set_color normal - printf ' at ' + if not set -q VIRTUAL_ENV_DISABLE_PROMPT + set -g VIRTUAL_ENV_DISABLE_PROMPT true + end + set_color yellow + printf '%s' (whoami) + set_color normal + printf ' at ' - set_color magenta - printf '%s' (hostname|cut -d . -f 1) - set_color normal - printf ' in ' + set_color magenta + echo -n (prompt_hostname) + set_color normal + printf ' in ' - set_color $fish_color_cwd - printf '%s' (prompt_pwd) - set_color normal + set_color $fish_color_cwd + printf '%s' (prompt_pwd) + set_color normal - # Line 2 - echo - if test $VIRTUAL_ENV - printf "(%s) " (set_color blue)(basename $VIRTUAL_ENV)(set_color normal) - end - printf '↪ ' - set_color normal + # Line 2 + echo + if test $VIRTUAL_ENV + printf "(%s) " (set_color blue)(basename $VIRTUAL_ENV)(set_color normal) + end + printf '↪ ' + set_color normal end diff --git a/share/tools/web_config/sample_prompts/robbyrussell.fish b/share/tools/web_config/sample_prompts/robbyrussell.fish index 0a895ba4b..1e94127f9 100644 --- a/share/tools/web_config/sample_prompts/robbyrussell.fish +++ b/share/tools/web_config/sample_prompts/robbyrussell.fish @@ -3,74 +3,76 @@ function fish_prompt - if not set -q -g __fish_robbyrussell_functions_defined - set -g __fish_robbyrussell_functions_defined - function _git_branch_name - echo (git symbolic-ref HEAD ^/dev/null | sed -e 's|^refs/heads/||') + if not set -q -g __fish_robbyrussell_functions_defined + set -g __fish_robbyrussell_functions_defined + function _git_branch_name + echo (git symbolic-ref HEAD ^/dev/null | sed -e 's|^refs/heads/||') + end + + function _is_git_dirty + echo (git status -s --ignore-submodules=dirty ^/dev/null) + end + + function _is_git_repo + type -q git + or return 1 + git status -s >/dev/null ^/dev/null + end + + function _hg_branch_name + echo (hg branch ^/dev/null) + end + + function _is_hg_dirty + echo (hg status -mard ^/dev/null) + end + + function _is_hg_repo + type -q hg + or return 1 + hg summary >/dev/null ^/dev/null + end + + function _repo_branch_name + eval "_$argv[1]_branch_name" + end + + function _is_repo_dirty + eval "_is_$argv[1]_dirty" + end + + function _repo_type + if _is_hg_repo + echo 'hg' + else if _is_git_repo + echo 'git' + end + end end - function _is_git_dirty - echo (git status -s --ignore-submodules=dirty ^/dev/null) + set -l cyan (set_color -o cyan) + set -l yellow (set_color -o yellow) + set -l red (set_color -o red) + set -l blue (set_color -o blue) + set -l normal (set_color normal) + + set -l arrow "$red➜ " + if [ $USER = 'root' ] + set arrow "$red# " end - function _is_git_repo - type -q git; or return 1 - git status -s >/dev/null ^/dev/null + set -l cwd $cyan(basename (prompt_pwd)) + + set -l repo_type (_repo_type) + if [ $repo_type ] + set -l repo_branch $red(_repo_branch_name $repo_type) + set repo_info "$blue $repo_type:($repo_branch$blue)" + + if [ (_is_repo_dirty $repo_type) ] + set -l dirty "$yellow ✗" + set repo_info "$repo_info$dirty" + end end - function _hg_branch_name - echo (hg branch ^/dev/null) - end - - function _is_hg_dirty - echo (hg status -mard ^/dev/null) - end - - function _is_hg_repo - type -q hg; or return 1 - hg summary >/dev/null ^/dev/null - end - - function _repo_branch_name - eval "_$argv[1]_branch_name" - end - - function _is_repo_dirty - eval "_is_$argv[1]_dirty" - end - - function _repo_type - if _is_hg_repo - echo 'hg' - else if _is_git_repo - echo 'git' - end - end - end - - set -l cyan (set_color -o cyan) - set -l yellow (set_color -o yellow) - set -l red (set_color -o red) - set -l blue (set_color -o blue) - set -l normal (set_color normal) - - set -l arrow "$red➜ " - if [ $USER = 'root' ] - set arrow "$red# " - end - - set -l cwd $cyan(basename (prompt_pwd)) - - set -l repo_type (_repo_type) - if [ $repo_type ] - set -l repo_branch $red(_repo_branch_name $repo_type) - set repo_info "$blue $repo_type:($repo_branch$blue)" - - if [ (_is_repo_dirty $repo_type) ] - set -l dirty "$yellow ✗" - set repo_info "$repo_info$dirty" - end - end - - echo -n -s $arrow ' '$cwd $repo_info $normal ' ' + echo -n -s $arrow ' '$cwd $repo_info $normal ' ' end diff --git a/share/tools/web_config/sample_prompts/screen_savvy.fish b/share/tools/web_config/sample_prompts/screen_savvy.fish index 83c7db641..411a55019 100644 --- a/share/tools/web_config/sample_prompts/screen_savvy.fish +++ b/share/tools/web_config/sample_prompts/screen_savvy.fish @@ -1,9 +1,9 @@ # name: Screen Savvy # author: Matthias function fish_prompt -d "Write out the prompt" - if test -z $WINDOW - printf '%s%s@%s%s%s%s%s> ' (set_color yellow) (whoami) (set_color purple) (hostname|cut -d . -f 1) (set_color $fish_color_cwd) (prompt_pwd) (set_color normal) - else - printf '%s%s@%s%s%s(%s)%s%s%s> ' (set_color yellow) (whoami) (set_color purple) (hostname|cut -d . -f 1) (set_color white) (echo $WINDOW) (set_color $fish_color_cwd) (prompt_pwd) (set_color normal) - end + if test -z $WINDOW + printf '%s%s@%s%s%s%s%s> ' (set_color yellow) (whoami) (set_color purple) (prompt_hostname) (set_color $fish_color_cwd) (prompt_pwd) (set_color normal) + else + printf '%s%s@%s%s%s(%s)%s%s%s> ' (set_color yellow) (whoami) (set_color purple) (prompt_hostname) (set_color white) (echo $WINDOW) (set_color $fish_color_cwd) (prompt_pwd) (set_color normal) + end end diff --git a/share/tools/web_config/sample_prompts/sorin.fish b/share/tools/web_config/sample_prompts/sorin.fish index cab4b8c58..60f40307c 100644 --- a/share/tools/web_config/sample_prompts/sorin.fish +++ b/share/tools/web_config/sample_prompts/sorin.fish @@ -2,52 +2,55 @@ # author: Ivan Tham function fish_prompt - test $SSH_TTY; and printf (set_color red)(whoami)(set_color white)'@'(set_color yellow)(hostname)' ' - - test $USER = 'root'; and echo (set_color red)"#" + test $SSH_TTY + and printf (set_color red)$USER(set_color brwhite)'@'(set_color yellow)(prompt_hostname)' ' + test $USER = 'root' + and echo (set_color red)"#" # Main - echo -n (set_color cyan)(prompt_pwd) (set_color red)'❯'(set_color yellow)'❯'(set_color green)'❯ ' + echo -n (set_color cyan)(prompt_pwd) (set_color red)'❯'(set_color yellow)'❯'(set_color green)'❯ ' end function fish_right_prompt # last status - test $status != 0; and printf (set_color red)"⏎ " + test $status != 0 + and printf (set_color red)"⏎ " - if git rev-parse ^ /dev/null - # Purple if branch detached else green - git branch -qv | grep "\*" | grep -q detached - and set_color purple --bold - or set_color green --bold + if git rev-parse ^/dev/null + # Magenta if branch detached else green + git branch -qv | grep "\*" | string match -rq detached + and set_color brmagenta + or set_color brgreen # Need optimization on this block (eliminate space) git name-rev --name-only HEAD # Merging state - git merge -q ^ /dev/null; or printf ':'(set_color red)'merge' + git merge -q ^/dev/null + or printf ':'(set_color red)'merge' printf ' ' # Symbols - for i in (git branch -qv --no-color|grep \*|cut -d' ' -f4-|cut -d] -f1|tr , \n)\ - (git status --porcelain | cut -c 1-2 | uniq) - switch $i + for i in (git branch -qv --no-color|grep \*|cut -d' ' -f4-|cut -d] -f1|tr , \n)\ + (git status --porcelain | cut -c 1-2 | uniq) + switch $i case "*[ahead *" - printf (set_color purple)⬆' ' + printf (set_color magenta)⬆' ' case "*behind *" - printf (set_color purple)⬇' ' - case "." - printf (set_color green)✚' ' - case " D" - printf (set_color red)✖' ' - case "*M*" - printf (set_color blue)✱' ' + printf (set_color magenta)⬇' ' + case "." + printf (set_color green)✚' ' + case " D" + printf (set_color red)✖' ' + case "*M*" + printf (set_color blue)✱' ' case "*R*" - printf (set_color purple)➜' ' + printf (set_color brmagenta)➜' ' case "*U*" - printf (set_color brown)═' ' - case "??" - printf (set_color white)◼' ' - end - end - end + printf (set_color bryellow)═' ' + case "??" + printf (set_color brwhite)◼' ' + end + end + end end diff --git a/share/tools/web_config/sample_prompts/terlar.fish b/share/tools/web_config/sample_prompts/terlar.fish index d75b17b58..d49ef340c 100644 --- a/share/tools/web_config/sample_prompts/terlar.fish +++ b/share/tools/web_config/sample_prompts/terlar.fish @@ -2,35 +2,35 @@ # author: terlar - https://github.com/terlar function fish_prompt --description 'Write out the prompt' - set -l last_status $status + set -l last_status $status - # User - set_color $fish_color_user - echo -n (whoami) - set_color normal + # User + set_color $fish_color_user + echo -n (whoami) + set_color normal - echo -n '@' + echo -n '@' - # Host - set_color $fish_color_host - echo -n (hostname -s) - set_color normal + # Host + set_color $fish_color_host + echo -n (prompt_hostname) + set_color normal - echo -n ':' + echo -n ':' - # PWD - set_color $fish_color_cwd - echo -n (prompt_pwd) - set_color normal + # PWD + set_color $fish_color_cwd + echo -n (prompt_pwd) + set_color normal - __terlar_git_prompt - __fish_hg_prompt - echo + __terlar_git_prompt + __fish_hg_prompt + echo - if not test $last_status -eq 0 - set_color $fish_color_error - end + if not test $last_status -eq 0 + set_color $fish_color_error + end - echo -n '➤ ' - set_color normal + echo -n '➤ ' + set_color normal end diff --git a/share/tools/web_config/sample_prompts/user_host_path.fish b/share/tools/web_config/sample_prompts/user_host_path.fish index de917ee71..4082610a6 100644 --- a/share/tools/web_config/sample_prompts/user_host_path.fish +++ b/share/tools/web_config/sample_prompts/user_host_path.fish @@ -2,12 +2,14 @@ # author: Jon Clayden function fish_prompt -d "Write out the prompt" - set -l home_escaped (echo -n $HOME | sed 's/\//\\\\\//g') - set -l pwd (echo -n $PWD | sed "s/^$home_escaped/~/" | sed 's/ /%20/g') - set -l prompt_symbol '' - switch $USER - case root toor; set prompt_symbol '#' - case '*'; set prompt_symbol '$' - end - printf "[%s@%s %s%s%s]%s " $USER (hostname -s) (set_color $fish_color_cwd) $pwd (set_color normal) $prompt_symbol + set -l home_escaped (echo -n $HOME | sed 's/\//\\\\\//g') + set -l pwd (echo -n $PWD | sed "s/^$home_escaped/~/" | sed 's/ /%20/g') + set -l prompt_symbol '' + switch $USER + case root toor + set prompt_symbol '#' + case '*' + set prompt_symbol '$' + end + printf "[%s@%s %s%s%s]%s " $USER (prompt_hostname) (set_color $fish_color_cwd) $pwd (set_color normal) $prompt_symbol end diff --git a/src/autoload.cpp b/src/autoload.cpp index 37ca2b8f1..a10de1364 100644 --- a/src/autoload.cpp +++ b/src/autoload.cpp @@ -157,6 +157,19 @@ autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcs return func; } +static bool use_cached(autoload_function_t *func, bool really_load, bool allow_stale_functions) { + if (!func) { + return false; // can't use a function that doesn't exist + } + if (really_load && !func->is_placeholder && !func->is_loaded) { + return false; // can't use an unloaded function + } + if (!allow_stale_functions && is_stale(func)) { + return false; // can't use a stale function + } + return true; // I guess we can use it +} + /// This internal helper function does all the real work. By using two functions, the internal /// function can return on various places in the code, and the caller can take care of various /// cleanup work. @@ -169,35 +182,21 @@ autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcs bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload, const wcstring_list_t &path_list) { // Note that we are NOT locked in this function! - bool reloaded = 0; + bool reloaded = false; // Try using a cached function. If we really want the function to be loaded, require that it be // really loaded. If we're not reloading, allow stale functions. { bool allow_stale_functions = !reload; - scoped_lock locker(lock); autoload_function_t *func = this->get_node(cmd); // get the function - // Determine if we can use this cached function. - bool use_cached; - if (!func) { - // Can't use a function that doesn't exist. - use_cached = false; - } else if (really_load && !func->is_placeholder && !func->is_loaded) { - use_cached = false; // can't use an unloaded function - } else if (!allow_stale_functions && is_stale(func)) { - use_cached = false; // can't use a stale function - } else { - use_cached = true; // I guess we can use it - } - // If we can use this function, return whether we were able to access it. - if (use_cached) { - assert(func != NULL); + if (use_cached(func, really_load, allow_stale_functions)) { return func->is_internalized || func->access.accessible; } } + // The source of the script will end up here. wcstring script_source; bool has_script_source = false; @@ -234,55 +233,52 @@ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_ if (!has_script_source) { // Iterate over path searching for suitable completion files. - for (size_t i = 0; i < path_list.size(); i++) { + for (size_t i = 0; i < path_list.size() && !found_file; i++) { wcstring next = path_list.at(i); wcstring path = next + L"/" + cmd + L".fish"; const file_access_attempt_t access = access_file(path, R_OK); - if (access.accessible) { - found_file = true; - - // Now we're actually going to take the lock. - scoped_lock locker(lock); - autoload_function_t *func = this->get_node(cmd); - - // Generate the source if we need to load it. - bool need_to_load_function = - really_load && - (func == NULL || func->access.mod_time != access.mod_time || !func->is_loaded); - if (need_to_load_function) { - // Generate the script source. - wcstring esc = escape_string(path, 1); - script_source = L"source " + esc; - has_script_source = true; - - // Remove any loaded command because we are going to reload it. Note that this - // will deadlock if command_removed calls back into us. - if (func && func->is_loaded) { - command_removed(cmd); - func->is_placeholder = false; - } - - // Mark that we're reloading it. - reloaded = true; - } - - // Create the function if we haven't yet. This does not load it. Do not trigger - // eviction unless we are actually loading, because we don't want to evict off of - // the main thread. - if (!func) { - func = get_autoloaded_function_with_creation(cmd, really_load); - } - - // It's a fiction to say the script is loaded at this point, but we're definitely - // going to load it down below. - if (need_to_load_function) func->is_loaded = true; - - // Unconditionally record our access time. - func->access = access; - - break; + if (!access.accessible) { + continue; } + + // Now we're actually going to take the lock. + scoped_lock locker(lock); + autoload_function_t *func = this->get_node(cmd); + + // Generate the source if we need to load it. + bool need_to_load_function = + really_load && + (func == NULL || func->access.mod_time != access.mod_time || !func->is_loaded); + if (need_to_load_function) { + // Generate the script source. + wcstring esc = escape_string(path, 1); + script_source = L"source " + esc; + has_script_source = true; + + // Remove any loaded command because we are going to reload it. Note that this + // will deadlock if command_removed calls back into us. + if (func && func->is_loaded) { + command_removed(cmd); + func->is_placeholder = false; + } + + // Mark that we're reloading it. + reloaded = true; + } + + // Create the function if we haven't yet. This does not load it. Do not trigger + // eviction unless we are actually loading, because we don't want to evict off of + // the main thread. + if (!func) func = get_autoloaded_function_with_creation(cmd, really_load); + + // It's a fiction to say the script is loaded at this point, but we're definitely + // going to load it down below. + if (need_to_load_function) func->is_loaded = true; + + // Unconditionally record our access time. + func->access = access; + found_file = true; } // If no file or builtin script was found we insert a placeholder function. Later we only @@ -313,7 +309,7 @@ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_ if (really_load) { return reloaded; - } else { - return found_file || has_script_source; } + + return found_file || has_script_source; } diff --git a/src/builtin.cpp b/src/builtin.cpp index 06944591b..6172222a1 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -188,25 +188,26 @@ void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t * if (pos && *pos) { // Then find the next empty line. for (; *pos; pos++) { - if (*pos == L'\n') { - wchar_t *pos2; - int is_empty = 1; + if (*pos != L'\n') { + continue; + } - for (pos2 = pos + 1; *pos2; pos2++) { - if (*pos2 == L'\n') break; + int is_empty = 1; + wchar_t *pos2; + for (pos2 = pos + 1; *pos2; pos2++) { + if (*pos2 == L'\n') break; - if (*pos2 != L'\t' && *pos2 != L' ') { - is_empty = 0; - break; - } - } - if (is_empty) { - // And cut it. - *(pos2 + 1) = L'\0'; - cut = 1; + if (*pos2 != L'\t' && *pos2 != L' ') { + is_empty = 0; break; } } + if (is_empty) { + // And cut it. + *(pos2 + 1) = L'\0'; + cut = 1; + break; + } } } @@ -345,23 +346,16 @@ static int get_terminfo_sequence(const wchar_t *seq, wcstring *out_seq, io_strea if (input_terminfo_get_sequence(seq, out_seq)) { return 1; } + wcstring eseq = escape_string(seq, 0); - switch (errno) { - case ENOENT: { - streams.err.append_format(_(L"%ls: No key with name '%ls' found\n"), L"bind", - eseq.c_str()); - break; - } - case EILSEQ: { - streams.err.append_format(_(L"%ls: Key with name '%ls' does not have any mapping\n"), - L"bind", eseq.c_str()); - break; - } - default: { - streams.err.append_format(_(L"%ls: Unknown error trying to bind to key named '%ls'\n"), - L"bind", eseq.c_str()); - break; - } + if (errno == ENOENT) { + streams.err.append_format(_(L"%ls: No key with name '%ls' found\n"), L"bind", eseq.c_str()); + } else if (errno == EILSEQ) { + streams.err.append_format(_(L"%ls: Key with name '%ls' does not have any mapping\n"), + L"bind", eseq.c_str()); + } else { + streams.err.append_format(_(L"%ls: Unknown error trying to bind to key named '%ls'\n"), + L"bind", eseq.c_str()); } return 0; } @@ -412,7 +406,7 @@ static int builtin_bind_erase(wchar_t **seq, int all, const wchar_t *mode, int u } int res = 0; - if (mode == NULL) mode = DEFAULT_BIND_MODE; + if (mode == NULL) mode = DEFAULT_BIND_MODE; //!OCLINT(parameter reassignment) while (*seq) { if (use_terminfo) { @@ -459,7 +453,6 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) while (1) { int opt_index = 0; int opt = w.wgetopt_long_only(argc, argv, L"aehkKfM:m:", long_options, &opt_index); - if (opt == -1) break; switch (opt) { @@ -508,6 +501,10 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -525,43 +522,37 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) break; } case BIND_INSERT: { - switch (argc - w.woptind) { - case 0: { - builtin_bind_list(bind_mode_given ? bind_mode : NULL, streams); - break; + int arg_count = argc - w.woptind; + if (arg_count == 0) { + builtin_bind_list(bind_mode_given ? bind_mode : NULL, streams); + } else if (arg_count == 1) { + wcstring seq; + if (use_terminfo) { + if (!get_terminfo_sequence(argv[w.woptind], &seq, streams)) { + res = STATUS_BUILTIN_ERROR; + // get_terminfo_sequence already printed the error. + break; + } + } else { + seq = argv[w.woptind]; } - case 1: { - wcstring seq; + if (!builtin_bind_list_one(seq, bind_mode, streams)) { + res = STATUS_BUILTIN_ERROR; + wcstring eseq = escape_string(argv[w.woptind], 0); if (use_terminfo) { - if (!get_terminfo_sequence(argv[w.woptind], &seq, streams)) { - res = STATUS_BUILTIN_ERROR; - // get_terminfo_sequence already printed the error. - break; - } + streams.err.append_format(_(L"%ls: No binding found for key '%ls'\n"), + argv[0], eseq.c_str()); } else { - seq = argv[w.woptind]; + streams.err.append_format(_(L"%ls: No binding found for sequence '%ls'\n"), + argv[0], eseq.c_str()); } - if (!builtin_bind_list_one(seq, bind_mode, streams)) { - res = STATUS_BUILTIN_ERROR; - wcstring eseq = escape_string(argv[w.woptind], 0); - if (use_terminfo) { - streams.err.append_format(_(L"%ls: No binding found for key '%ls'\n"), - argv[0], eseq.c_str()); - } else { - streams.err.append_format( - _(L"%ls: No binding found for sequence '%ls'\n"), argv[0], - eseq.c_str()); - } - } - break; } - default: { - if (builtin_bind_add(argv[w.woptind], argv + (w.woptind + 1), - argc - (w.woptind + 1), bind_mode, sets_bind_mode, - use_terminfo, streams)) { - res = STATUS_BUILTIN_ERROR; - } - break; + break; + } else { + if (builtin_bind_add(argv[w.woptind], argv + (w.woptind + 1), + argc - (w.woptind + 1), bind_mode, sets_bind_mode, + use_terminfo, streams)) { + res = STATUS_BUILTIN_ERROR; } } break; @@ -638,6 +629,10 @@ static int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -670,6 +665,7 @@ static int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv } case GLOBAL: { block = NULL; + break; } case UNSET: { while (block != NULL && block->type() != FUNCTION_CALL && @@ -677,6 +673,11 @@ static int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv // Set it in function scope block = parser.block_at_index(++block_idx); } + break; + } + default: { + DIE("unexpected scope"); + break; } } if (block) { @@ -726,6 +727,10 @@ static int builtin_builtin(parser_t &parser, io_streams_t &streams, wchar_t **ar builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -772,6 +777,10 @@ static int builtin_emit(parser_t &parser, io_streams_t &streams, wchar_t **argv) builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -825,6 +834,10 @@ static int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **ar builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -883,6 +896,10 @@ static int builtin_generic(parser_t &parser, io_streams_t &streams, wchar_t **ar builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -891,7 +908,7 @@ static int builtin_generic(parser_t &parser, io_streams_t &streams, wchar_t **ar /// Return a definition of the specified function. Used by the functions builtin. static wcstring functions_def(const wcstring &name) { - CHECK(!name.empty(), L""); + CHECK(!name.empty(), L""); //!OCLINT(multiple unary operator) wcstring out; wcstring desc, def; function_get_desc(name, &desc); @@ -947,6 +964,10 @@ static wcstring functions_def(const wcstring &name) { append_format(out, L" --on-event %ls", next->str_param1.c_str()); break; } + default: { + DIE("unexpected next->type"); + break; + } } } @@ -1061,11 +1082,16 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t ** builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } // Erase, desc, query, copy and list are mutually exclusive. - if ((erase + (!!desc) + list + query + copy) > 1) { + int describe = desc ? 1 : 0; + if (erase + describe + list + query + copy > 1) { streams.err.append_format(_(L"%ls: Invalid combination of options\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); @@ -1179,67 +1205,62 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t ** return res; } +// Convert a octal or hex character to its binary value. Surprisingly a version +// of this function using a lookup table is only ~1.5% faster than the `switch` +// statement version below. Since that requires initializing a table statically +// (which is problematic if we run on an EBCDIC system) we don't use that +// solution. Also, we relax the style rule that `case` blocks should always be +// enclosed in parentheses given the nature of this code. static unsigned int builtin_echo_digit(wchar_t wc, unsigned int base) { assert(base == 8 || base == 16); // base must be hex or octal switch (wc) { - case L'0': { + case L'0': return 0; - } - case L'1': { + case L'1': return 1; - } - case L'2': { + case L'2': return 2; - } - case L'3': { + case L'3': return 3; - } - case L'4': { + case L'4': return 4; - } - case L'5': { + case L'5': return 5; - } - case L'6': { + case L'6': return 6; - } - case L'7': { + case L'7': return 7; - } + default: { break; } + } + + if (base != 16) return UINT_MAX; + + switch (wc) { + case L'8': + return 8; + case L'9': + return 9; + case L'a': + case L'A': + return 10; + case L'b': + case L'B': + return 11; + case L'c': + case L'C': + return 12; + case L'd': + case L'D': + return 13; + case L'e': + case L'E': + return 14; + case L'f': + case L'F': + return 15; + default: { break; } } - if (base == 16) switch (wc) { - case L'8': { - return 8; - } - case L'9': { - return 9; - } - case L'a': - case L'A': { - return 10; - } - case L'b': - case L'B': { - return 11; - } - case L'c': - case L'C': { - return 12; - } - case L'd': - case L'D': { - return 13; - } - case L'e': - case L'E': { - return 14; - } - case L'f': - case L'F': { - return 15; - } - } return UINT_MAX; } @@ -1271,21 +1292,23 @@ static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *cons start = 1; } - if (base != 0) { - unsigned int idx; - unsigned char val = 0; // resulting character - for (idx = start; idx < start + max_digits; idx++) { - unsigned int digit = builtin_echo_digit(str[idx], base); - if (digit == UINT_MAX) break; - val = val * base + digit; - } + if (base == 0) { + return success; + } - // We succeeded if we consumed at least one digit. - if (idx > start) { - *consumed = idx; - *out_val = val; - success = true; - } + unsigned int idx; + unsigned char val = 0; // resulting character + for (idx = start; idx < start + max_digits; idx++) { + unsigned int digit = builtin_echo_digit(str[idx], base); + if (digit == UINT_MAX) break; + val = val * base + digit; + } + + // We succeeded if we consumed at least one digit. + if (idx > start) { + *consumed = idx; + *out_val = val; + success = true; } return success; } @@ -1343,7 +1366,7 @@ static int builtin_echo(parser_t &parser, io_streams_t &streams, wchar_t **argv) break; } default: { - assert(0 && "Unexpected character in builtin_echo argument"); + DIE("unexpected character in builtin_echo argument"); break; } } @@ -1522,7 +1545,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis {L"inherit-variable", required_argument, 0, 'V'}, {0, 0, 0, 0}}; - while (1 && !res) { + while (res == STATUS_BUILTIN_OK) { int opt_index = 0; // The leading - here specifies RETURN_IN_ORDER. @@ -1532,7 +1555,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis case 0: { if (long_options[opt_index].flag != 0) break; append_format(*out_err, BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); - res = 1; + res = STATUS_BUILTIN_ERROR; break; } case 'd': { @@ -1543,7 +1566,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis int sig = wcs2sig(w.woptarg); if (sig < 0) { append_format(*out_err, _(L"%ls: Unknown signal '%ls'"), argv[0], w.woptarg); - res = 1; + res = STATUS_BUILTIN_ERROR; break; } events.push_back(event_t::signal_event(sig)); @@ -1593,7 +1616,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis append_format(*out_err, _(L"%ls: Cannot find calling job for event handler"), argv[0]); - res = 1; + res = STATUS_BUILTIN_ERROR; } else { e.type = EVENT_JOB_ID; e.param1.job_id = job_id; @@ -1604,14 +1627,14 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis if (errno || !end || *end) { append_format(*out_err, _(L"%ls: Invalid process id %ls"), argv[0], w.woptarg); - res = 1; + res = STATUS_BUILTIN_ERROR; break; } e.type = EVENT_EXIT; e.param1.pid = (opt == 'j' ? -1 : 1) * abs(pid); } - if (!res) { + if (res == STATUS_BUILTIN_OK) { events.push_back(e); } break; @@ -1652,89 +1675,96 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - res = 1; + res = STATUS_BUILTIN_ERROR; + break; + } + default: { + DIE("unexpected opt"); break; } } } - if (!res) { - // Determine the function name, and remove it from the list of positionals. - wcstring function_name; - bool name_is_missing = positionals.empty(); - if (!name_is_missing) { - if (name_is_first_positional) { - function_name = positionals.front(); - positionals.erase(positionals.begin()); - } else { - function_name = positionals.back(); - positionals.erase(positionals.end() - 1); - } - } + if (res != STATUS_BUILTIN_OK) { + return STATUS_BUILTIN_ERROR; + } - if (name_is_missing) { - append_format(*out_err, _(L"%ls: Expected function name"), argv[0]); - res = 1; - } else if (wcsfuncname(function_name)) { - append_format(*out_err, _(L"%ls: Illegal function name '%ls'"), argv[0], - function_name.c_str()); - - res = 1; - } else if (parser_keywords_is_reserved(function_name)) { - append_format( - *out_err, - _(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name"), - argv[0], function_name.c_str()); - - res = 1; - } else if (function_name.empty()) { - append_format(*out_err, _(L"%ls: No function name given"), argv[0]); - res = 1; + // Determine the function name, and remove it from the list of positionals. + wcstring function_name; + bool name_is_missing = positionals.empty(); + if (!name_is_missing) { + if (name_is_first_positional) { + function_name = positionals.front(); + positionals.erase(positionals.begin()); } else { - if (has_named_arguments) { - // All remaining positionals are named arguments. - named_arguments.swap(positionals); - for (size_t i = 0; i < named_arguments.size(); i++) { - if (wcsvarname(named_arguments.at(i))) { - append_format(*out_err, _(L"%ls: Invalid variable name '%ls'"), argv[0], - named_arguments.at(i).c_str()); - res = STATUS_BUILTIN_ERROR; - break; - } + function_name = positionals.back(); + positionals.erase(positionals.end() - 1); + } + } + + if (name_is_missing) { + append_format(*out_err, _(L"%ls: Expected function name"), argv[0]); + res = STATUS_BUILTIN_ERROR; + } else if (wcsfuncname(function_name)) { + append_format(*out_err, _(L"%ls: Illegal function name '%ls'"), argv[0], + function_name.c_str()); + + res = STATUS_BUILTIN_ERROR; + } else if (parser_keywords_is_reserved(function_name)) { + append_format( + *out_err, + _(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name"), argv[0], + function_name.c_str()); + + res = STATUS_BUILTIN_ERROR; + } else if (function_name.empty()) { + append_format(*out_err, _(L"%ls: No function name given"), argv[0]); + res = STATUS_BUILTIN_ERROR; + } else { + if (has_named_arguments) { + // All remaining positionals are named arguments. + named_arguments.swap(positionals); + for (size_t i = 0; i < named_arguments.size(); i++) { + if (wcsvarname(named_arguments.at(i))) { + append_format(*out_err, _(L"%ls: Invalid variable name '%ls'"), argv[0], + named_arguments.at(i).c_str()); + res = STATUS_BUILTIN_ERROR; + break; } - } else if (!positionals.empty()) { - // +1 because we already got the function name. - append_format(*out_err, _(L"%ls: Expected one argument, got %lu"), argv[0], - (unsigned long)(positionals.size() + 1)); - res = 1; } + } else if (!positionals.empty()) { + // +1 because we already got the function name. + append_format(*out_err, _(L"%ls: Expected one argument, got %lu"), argv[0], + (unsigned long)(positionals.size() + 1)); + res = STATUS_BUILTIN_ERROR; } + } - if (!res) { - // Here we actually define the function! - function_data_t d; + if (res != STATUS_BUILTIN_OK) { + return res; + } - d.name = function_name; - if (desc) d.description = desc; - d.events.swap(events); - d.shadow_scope = shadow_scope; - d.named_arguments.swap(named_arguments); - d.inherit_vars.swap(inherit_vars); + // Here we actually define the function! + function_data_t d; - for (size_t i = 0; i < d.events.size(); i++) { - event_t &e = d.events.at(i); - e.function_name = d.name; - } + d.name = function_name; + if (desc) d.description = desc; + d.events.swap(events); + d.shadow_scope = shadow_scope; + d.named_arguments.swap(named_arguments); + d.inherit_vars.swap(inherit_vars); - d.definition = contents.c_str(); + for (size_t i = 0; i < d.events.size(); i++) { + event_t &e = d.events.at(i); + e.function_name = d.name; + } - function_add(d, parser, definition_line_offset); + d.definition = contents.c_str(); + function_add(d, parser, definition_line_offset); - // Handle wrap targets. - for (size_t w = 0; w < wrap_targets.size(); w++) { - complete_add_wrapper(function_name, wrap_targets.at(w)); - } - } + // Handle wrap targets. + for (size_t w = 0; w < wrap_targets.size(); w++) { + complete_add_wrapper(function_name, wrap_targets.at(w)); } return res; @@ -1773,42 +1803,40 @@ static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **arg builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } - switch (argc - w.woptind) { - case 0: { - long res; - if (!seeded) { - seeded = 1; - srand48_r(time(0), &seed_buffer); - } - lrand48_r(&seed_buffer, &res); - streams.out.append_format(L"%ld\n", res % 32768); - break; - } - case 1: { - long foo; - wchar_t *end = 0; - - errno = 0; - foo = wcstol(argv[w.woptind], &end, 10); - if (errno || *end) { - streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"), - argv[0], argv[w.woptind]); - - return STATUS_BUILTIN_ERROR; - } + int arg_count = argc - w.woptind; + if (arg_count == 0) { + long res; + if (!seeded) { seeded = 1; - srand48_r(foo, &seed_buffer); - break; + srand48_r(time(0), &seed_buffer); } - default: { - streams.err.append_format(_(L"%ls: Expected zero or one argument, got %d\n"), argv[0], - argc - w.woptind); - builtin_print_help(parser, streams, argv[0], streams.err); + lrand48_r(&seed_buffer, &res); + streams.out.append_format(L"%ld\n", res % 32768); + } else if (arg_count == 1) { + long foo; + wchar_t *end = 0; + + errno = 0; + foo = wcstol(argv[w.woptind], &end, 10); + if (errno || *end) { + streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"), argv[0], + argv[w.woptind]); return STATUS_BUILTIN_ERROR; } + seeded = 1; + srand48_r(foo, &seed_buffer); + } else { + streams.err.append_format(_(L"%ls: Expected zero or one argument, got %d\n"), argv[0], + argc - w.woptind); + builtin_print_help(parser, streams, argv[0], streams.err); + return STATUS_BUILTIN_ERROR; } return STATUS_BUILTIN_OK; } @@ -1900,20 +1928,17 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) errno = 0; nchars = fish_wcstoi(w.woptarg, &end, 10); if (errno || *end != 0) { - switch (errno) { - case ERANGE: { - streams.err.append_format(_(L"%ls: Argument '%ls' is out of range\n"), - argv[0], w.woptarg); - builtin_print_help(parser, streams, argv[0], streams.err); - return STATUS_BUILTIN_ERROR; - } - default: { - streams.err.append_format( - _(L"%ls: Argument '%ls' must be an integer\n"), argv[0], w.woptarg); - builtin_print_help(parser, streams, argv[0], streams.err); - return STATUS_BUILTIN_ERROR; - } + if (errno == ERANGE) { + streams.err.append_format(_(L"%ls: Argument '%ls' is out of range\n"), + argv[0], w.woptarg); + builtin_print_help(parser, streams, argv[0], streams.err); + return STATUS_BUILTIN_ERROR; } + + streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), + argv[0], w.woptarg); + builtin_print_help(parser, streams, argv[0], streams.err); + return STATUS_BUILTIN_ERROR; } break; } @@ -1937,6 +1962,10 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -2045,18 +2074,10 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) finished = 1; } else { size_t sz = mbrtowc(&res, &b, 1, &state); - switch (sz) { - case (size_t)-1: { - memset(&state, 0, sizeof(state)); - break; - } - case (size_t)-2: { - break; - } - default: { - finished = 1; - break; - } + if (sz == (size_t)-1) { + memset(&state, 0, sizeof(state)); + } else if (sz != (size_t)-2) { + finished = 1; } } } @@ -2079,162 +2100,276 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) } } - if (i != argc && !exit_res) { - env_var_t ifs = env_get_string(L"IFS"); - if (ifs.missing_or_empty()) { - // Every character is a separate token. - size_t bufflen = buff.size(); - if (array) { - if (bufflen > 0) { - wcstring chars(bufflen + (bufflen - 1), ARRAY_SEP); - wcstring::iterator out = chars.begin(); - for (wcstring::const_iterator it = buff.begin(), end = buff.end(); it != end; - ++it) { - *out = *it; - out += 2; - } - env_set(argv[i], chars.c_str(), place); - } else { - env_set(argv[i], NULL, place); - } - } else { // not array - size_t j = 0; - for (; i + 1 < argc; ++i) { - if (j < bufflen) { - wchar_t buffer[2] = {buff[j++], 0}; - env_set(argv[i], buffer, place); - } else { - env_set(argv[i], L"", place); - } - } - if (i < argc) env_set(argv[i], &buff[j], place); - } - } else if (array) { - wcstring tokens; - tokens.reserve(buff.size()); - bool empty = true; + if (i == argc || exit_res != STATUS_BUILTIN_OK) { + return exit_res; + } - for (wcstring_range loc = wcstring_tok(buff, ifs); loc.first != wcstring::npos; - loc = wcstring_tok(buff, ifs, loc)) { - if (!empty) tokens.push_back(ARRAY_SEP); - tokens.append(buff, loc.first, loc.second); - empty = false; + env_var_t ifs = env_get_string(L"IFS"); + if (ifs.missing_or_empty()) { + // Every character is a separate token. + size_t bufflen = buff.size(); + if (array) { + if (bufflen > 0) { + wcstring chars(bufflen + (bufflen - 1), ARRAY_SEP); + wcstring::iterator out = chars.begin(); + for (wcstring::const_iterator it = buff.begin(), end = buff.end(); it != end; + ++it) { + *out = *it; + out += 2; + } + env_set(argv[i], chars.c_str(), place); + } else { + env_set(argv[i], NULL, place); } - env_set(argv[i], empty ? NULL : tokens.c_str(), place); } else { // not array - wcstring_range loc = wcstring_range(0, 0); - - while (i < argc) { - loc = wcstring_tok(buff, (i + 1 < argc) ? ifs : wcstring(), loc); - env_set(argv[i], loc.first == wcstring::npos ? L"" : &buff.c_str()[loc.first], - place); - ++i; + size_t j = 0; + for (; i + 1 < argc; ++i) { + if (j < bufflen) { + wchar_t buffer[2] = {buff[j++], 0}; + env_set(argv[i], buffer, place); + } else { + env_set(argv[i], L"", place); + } } + if (i < argc) env_set(argv[i], &buff[j], place); + } + } else if (array) { + wcstring tokens; + tokens.reserve(buff.size()); + bool empty = true; + + for (wcstring_range loc = wcstring_tok(buff, ifs); loc.first != wcstring::npos; + loc = wcstring_tok(buff, ifs, loc)) { + if (!empty) tokens.push_back(ARRAY_SEP); + tokens.append(buff, loc.first, loc.second); + empty = false; + } + env_set(argv[i], empty ? NULL : tokens.c_str(), place); + } else { // not array + wcstring_range loc = wcstring_range(0, 0); + + while (i < argc) { + loc = wcstring_tok(buff, (i + 1 < argc) ? ifs : wcstring(), loc); + env_set(argv[i], loc.first == wcstring::npos ? L"" : &buff.c_str()[loc.first], place); + ++i; } } return exit_res; } +enum status_cmd_t { + STATUS_NOOP, + STATUS_IS_LOGIN, + STATUS_IS_INTERACTIVE, + STATUS_IS_BLOCK, + STATUS_IS_COMMAND_SUB, + STATUS_IS_FULL_JOB_CTRL, + STATUS_IS_INTERACTIVE_JOB_CTRL, + STATUS_IS_NO_JOB_CTRL, + STATUS_CURRENT_FILENAME, + STATUS_CURRENT_LINE_NUMBER, + STATUS_SET_JOB_CONTROL, + STATUS_PRINT_STACK_TRACE +}; + +static status_cmd_t status_string_to_cmd(const wchar_t *status_command) { + if (wcscmp(status_command, L"is-login") == 0) { + return STATUS_IS_LOGIN; + } else if (wcscmp(status_command, L"is-interactive") == 0) { + return STATUS_IS_INTERACTIVE; + } else if (wcscmp(status_command, L"is-block") == 0) { + return STATUS_IS_BLOCK; + } else if (wcscmp(status_command, L"is-command-sub") == 0) { + return STATUS_IS_COMMAND_SUB; + } else if (wcscmp(status_command, L"is-full-job-control") == 0) { + return STATUS_IS_FULL_JOB_CTRL; + } else if (wcscmp(status_command, L"is-interactive-job-control") == 0) { + return STATUS_IS_INTERACTIVE_JOB_CTRL; + } else if (wcscmp(status_command, L"is-no-job-control") == 0) { + return STATUS_IS_NO_JOB_CTRL; + } else if (wcscmp(status_command, L"current-filename") == 0) { + return STATUS_CURRENT_FILENAME; + } else if (wcscmp(status_command, L"current-line-number") == 0) { + return STATUS_CURRENT_LINE_NUMBER; + } else if (wcscmp(status_command, L"job-control") == 0) { + return STATUS_SET_JOB_CONTROL; + } else if (wcscmp(status_command, L"print-stack-trace") == 0) { + return STATUS_PRINT_STACK_TRACE; + } + return STATUS_NOOP; +} + +static const wcstring status_cmd_to_string(status_cmd_t status_cmd) { + switch (status_cmd) { + case STATUS_NOOP: + return L"no-op"; + case STATUS_IS_LOGIN: + return L"is-login"; + case STATUS_IS_INTERACTIVE: + return L"is-interactive"; + case STATUS_IS_BLOCK: + return L"is-block"; + case STATUS_IS_COMMAND_SUB: + return L"is-command-sub"; + case STATUS_IS_FULL_JOB_CTRL: + return L"is-full-job-control"; + case STATUS_IS_INTERACTIVE_JOB_CTRL: + return L"is-interactive-job-control"; + case STATUS_IS_NO_JOB_CTRL: + return L"is-no-job-control"; + case STATUS_CURRENT_FILENAME: + return L"current-filename"; + case STATUS_CURRENT_LINE_NUMBER: + return L"current-line-number"; + case STATUS_SET_JOB_CONTROL: + return L"job-control"; + case STATUS_PRINT_STACK_TRACE: + return L"print-stack-trace"; + } +} + +/// Remember the status subcommand and disallow selecting more than one status subcommand. +static bool set_status_cmd(wchar_t *const cmd, status_cmd_t *status_cmd, status_cmd_t sub_cmd, + io_streams_t &streams) { + if (*status_cmd != STATUS_NOOP) { + wchar_t err_text[1024]; + swprintf(err_text, sizeof(err_text) / sizeof(wchar_t), + _(L"you cannot do both '%ls' and '%ls' in the same invocation"), + status_cmd_to_string(*status_cmd).c_str(), status_cmd_to_string(sub_cmd).c_str()); + streams.err.append_format(BUILTIN_ERR_COMBO2, cmd, err_text); + return false; + } + + *status_cmd = sub_cmd; + return true; +} + +#define CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) \ + if (args.size() != 0) { \ + streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, \ + status_cmd_to_string(status_cmd).c_str(), 0, args.size()); \ + status = STATUS_BUILTIN_ERROR; \ + break; \ + } + +int job_control_str_to_mode(const wchar_t *mode, wchar_t *cmd, io_streams_t &streams) { + if (wcscmp(mode, L"full") == 0) { + return JOB_CONTROL_ALL; + } else if (wcscmp(mode, L"interactive") == 0) { + return JOB_CONTROL_INTERACTIVE; + } else if (wcscmp(mode, L"none") == 0) { + return JOB_CONTROL_NONE; + } + streams.err.append_format(L"%ls: Invalid job control mode '%ls'\n", cmd, mode); + return -1; +} + /// The status builtin. Gives various status information on fish. static int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv) { - wgetopter_t w; - enum { - NORMAL, - IS_SUBST, - IS_BLOCK, - IS_INTERACTIVE, - IS_LOGIN, - IS_FULL_JOB_CONTROL, - IS_INTERACTIVE_JOB_CONTROL, - IS_NO_JOB_CONTROL, - STACK_TRACE, - DONE, - CURRENT_FILENAME, - CURRENT_LINE_NUMBER - }; - - int mode = NORMAL; - + wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); - int res = STATUS_BUILTIN_OK; + status_cmd_t status_cmd = STATUS_NOOP; + int status = STATUS_BUILTIN_OK; + int new_job_control_mode = -1; - const struct woption long_options[] = { - {L"help", no_argument, 0, 'h'}, - {L"is-command-substitution", no_argument, 0, 'c'}, - {L"is-block", no_argument, 0, 'b'}, - {L"is-interactive", no_argument, 0, 'i'}, - {L"is-login", no_argument, 0, 'l'}, - {L"is-full-job-control", no_argument, &mode, IS_FULL_JOB_CONTROL}, - {L"is-interactive-job-control", no_argument, &mode, IS_INTERACTIVE_JOB_CONTROL}, - {L"is-no-job-control", no_argument, &mode, IS_NO_JOB_CONTROL}, - {L"current-filename", no_argument, 0, 'f'}, - {L"current-line-number", no_argument, 0, 'n'}, - {L"job-control", required_argument, 0, 'j'}, - {L"print-stack-trace", no_argument, 0, 't'}, - {0, 0, 0, 0}}; - - while (1) { - int opt_index = 0; - - int opt = w.wgetopt_long(argc, argv, L":cbilfnhj:t", long_options, &opt_index); - if (opt == -1) break; + /// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to + /// the non-flag subcommand form. While these flags are deprecated they must be supported at + /// least until fish 3.0 and possibly longer to avoid breaking everyones config.fish and other + /// scripts. + const wchar_t *short_options = L":cbilfnhj:t"; + const struct woption long_options[] = {{L"help", no_argument, 0, 'h'}, + {L"is-command-substitution", no_argument, 0, 'c'}, + {L"is-block", no_argument, 0, 'b'}, + {L"is-interactive", no_argument, 0, 'i'}, + {L"is-login", no_argument, 0, 'l'}, + {L"is-full-job-control", no_argument, 0, 1}, + {L"is-interactive-job-control", no_argument, 0, 2}, + {L"is-no-job-control", no_argument, 0, 3}, + {L"current-filename", no_argument, 0, 'f'}, + {L"current-line-number", no_argument, 0, 'n'}, + {L"job-control", required_argument, 0, 'j'}, + {L"print-stack-trace", no_argument, 0, 't'}, + {0, 0, 0, 0}}; + int opt; + wgetopter_t w; + while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { - case 0: { - if (long_options[opt_index].flag != 0) break; - streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], - long_options[opt_index].name); - builtin_print_help(parser, streams, argv[0], streams.err); - return STATUS_BUILTIN_ERROR; + case 1: { + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_FULL_JOB_CTRL, streams)) { + return STATUS_BUILTIN_ERROR; + } + break; + } + case 2: { + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_INTERACTIVE_JOB_CTRL, streams)) { + return STATUS_BUILTIN_ERROR; + } + break; + } + case 3: { + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_NO_JOB_CTRL, streams)) { + return STATUS_BUILTIN_ERROR; + } + break; } case 'c': { - mode = IS_SUBST; + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_COMMAND_SUB, streams)) { + return STATUS_BUILTIN_ERROR; + } break; } case 'b': { - mode = IS_BLOCK; + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_BLOCK, streams)) { + return STATUS_BUILTIN_ERROR; + } break; } case 'i': { - mode = IS_INTERACTIVE; + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_INTERACTIVE, streams)) { + return STATUS_BUILTIN_ERROR; + } break; } case 'l': { - mode = IS_LOGIN; + if (!set_status_cmd(cmd, &status_cmd, STATUS_IS_LOGIN, streams)) { + return STATUS_BUILTIN_ERROR; + } break; } case 'f': { - mode = CURRENT_FILENAME; + if (!set_status_cmd(cmd, &status_cmd, STATUS_CURRENT_FILENAME, streams)) { + return STATUS_BUILTIN_ERROR; + } break; } case 'n': { - mode = CURRENT_LINE_NUMBER; + if (!set_status_cmd(cmd, &status_cmd, STATUS_CURRENT_LINE_NUMBER, streams)) { + return STATUS_BUILTIN_ERROR; + } + break; + } + case 'j': { + if (!set_status_cmd(cmd, &status_cmd, STATUS_SET_JOB_CONTROL, streams)) { + return STATUS_BUILTIN_ERROR; + } + new_job_control_mode = job_control_str_to_mode(w.woptarg, cmd, streams); + if (new_job_control_mode == -1) { + return STATUS_BUILTIN_ERROR; + } + break; + } + case 't': { + if (!set_status_cmd(cmd, &status_cmd, STATUS_PRINT_STACK_TRACE, streams)) { + return STATUS_BUILTIN_ERROR; + } break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } - case 'j': { - if (wcscmp(w.woptarg, L"full") == 0) - job_control_mode = JOB_CONTROL_ALL; - else if (wcscmp(w.woptarg, L"interactive") == 0) - job_control_mode = JOB_CONTROL_INTERACTIVE; - else if (wcscmp(w.woptarg, L"none") == 0) - job_control_mode = JOB_CONTROL_NONE; - else { - streams.err.append_format(L"%ls: Invalid job control mode '%ls'\n", L"status", - w.woptarg); - res = 1; - } - mode = DONE; - break; - } - case 't': { - mode = STACK_TRACE; - break; - } case ':': { builtin_missing_argument(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; @@ -2243,94 +2378,143 @@ static int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **arg builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } - } - } - - if (!res) { - switch (mode) { - case CURRENT_FILENAME: { - const wchar_t *fn = parser.current_filename(); - - if (!fn) fn = _(L"Standard input"); - - streams.out.append_format(L"%ls\n", fn); - - break; - } - case CURRENT_LINE_NUMBER: { - streams.out.append_format(L"%d\n", parser.get_lineno()); - break; - } - case IS_INTERACTIVE: { - return !is_interactive_session; - } - case IS_SUBST: { - return !is_subshell; - } - case IS_BLOCK: { - return !is_block; - } - case IS_LOGIN: { - return !is_login; - } - case IS_FULL_JOB_CONTROL: { - return job_control_mode != JOB_CONTROL_ALL; - } - case IS_INTERACTIVE_JOB_CONTROL: { - return job_control_mode != JOB_CONTROL_INTERACTIVE; - } - case IS_NO_JOB_CONTROL: { - return job_control_mode != JOB_CONTROL_NONE; - } - case STACK_TRACE: { - streams.out.append(parser.stack_trace()); - break; - } - case NORMAL: { - if (is_login) - streams.out.append_format(_(L"This is a login shell\n")); - else - streams.out.append_format(_(L"This is not a login shell\n")); - - streams.out.append_format( - _(L"Job control: %ls\n"), - job_control_mode == JOB_CONTROL_INTERACTIVE - ? _(L"Only on interactive jobs") - : (job_control_mode == JOB_CONTROL_NONE ? _(L"Never") : _(L"Always"))); - streams.out.append(parser.stack_trace()); + default: { + DIE("unexpected retval from wgetopt_long"); break; } } } - return res; + // If a status command hasn't already been specified via a flag check the first word. + // Note that this can be simplified after we eliminate allowing subcommands as flags. + if (w.woptind < argc) { + status_cmd_t subcmd = status_string_to_cmd(argv[w.woptind]); + if (subcmd != STATUS_NOOP) { + if (!set_status_cmd(cmd, &status_cmd, subcmd, streams)) { + return STATUS_BUILTIN_ERROR; + } + w.woptind++; + } + } + + // Every argument that we haven't consumed already is an argument for a subcommand. + const wcstring_list_t args(argv + w.woptind, argv + argc); + + switch (status_cmd) { + case STATUS_NOOP: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + if (is_login) { + streams.out.append_format(_(L"This is a login shell\n")); + } else { + streams.out.append_format(_(L"This is not a login shell\n")); + } + + streams.out.append_format( + _(L"Job control: %ls\n"), + job_control_mode == JOB_CONTROL_INTERACTIVE + ? _(L"Only on interactive jobs") + : (job_control_mode == JOB_CONTROL_NONE ? _(L"Never") : _(L"Always"))); + streams.out.append(parser.stack_trace()); + break; + } + case STATUS_SET_JOB_CONTROL: { + if (new_job_control_mode != -1) { + // Flag form was used. + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + } else { + if (args.size() != 1) { + streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, + status_cmd_to_string(status_cmd).c_str(), 1, + args.size()); + status = STATUS_BUILTIN_ERROR; + break; + } + new_job_control_mode = job_control_str_to_mode(args[0].c_str(), cmd, streams); + if (new_job_control_mode == -1) { + return STATUS_BUILTIN_ERROR; + } + } + job_control_mode = new_job_control_mode; + break; + } + case STATUS_CURRENT_FILENAME: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + const wchar_t *fn = parser.current_filename(); + + if (!fn) fn = _(L"Standard input"); + streams.out.append_format(L"%ls\n", fn); + break; + } + case STATUS_CURRENT_LINE_NUMBER: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + streams.out.append_format(L"%d\n", parser.get_lineno()); + break; + } + case STATUS_IS_INTERACTIVE: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = !is_interactive_session; + break; + } + case STATUS_IS_COMMAND_SUB: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = !is_subshell; + break; + } + case STATUS_IS_BLOCK: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = !is_block; + break; + } + case STATUS_IS_LOGIN: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = !is_login; + break; + } + case STATUS_IS_FULL_JOB_CTRL: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = job_control_mode != JOB_CONTROL_ALL; + break; + } + case STATUS_IS_INTERACTIVE_JOB_CTRL: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = job_control_mode != JOB_CONTROL_INTERACTIVE; + break; + } + case STATUS_IS_NO_JOB_CTRL: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + status = job_control_mode != JOB_CONTROL_NONE; + break; + } + case STATUS_PRINT_STACK_TRACE: { + CHECK_FOR_UNEXPECTED_STATUS_ARGS(status_cmd) + streams.out.append(parser.stack_trace()); + break; + } + } + + return status; } /// The exit builtin. Calls reader_exit to exit and returns the value specified. static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); - long ec = 0; - switch (argc) { - case 1: { - ec = proc_get_last_status(); - break; - } - case 2: { - wchar_t *end; - errno = 0; - ec = wcstol(argv[1], &end, 10); - if (errno || *end != 0) { - streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], - argv[1]); - builtin_print_help(parser, streams, argv[0], streams.err); - return STATUS_BUILTIN_ERROR; - } - break; - } - default: { - streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); + if (argc > 2) { + streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); + builtin_print_help(parser, streams, argv[0], streams.err); + return STATUS_BUILTIN_ERROR; + } + long ec; + if (argc == 1) { + ec = proc_get_last_status(); + } else { + wchar_t *end; + errno = 0; + ec = wcstol(argv[1], &end, 10); + if (errno || *end != 0) { + streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], + argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } @@ -2461,6 +2645,10 @@ static int builtin_contains(parser_t &parser, io_streams_t &streams, wchar_t **a should_output_index = true; break; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -2612,25 +2800,27 @@ static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } } - if (j) { - if (streams.err_is_redirected) { - streams.err.append_format(FG_MSG, j->job_id, j->command_wcstr()); - } else { - // If we aren't redirecting, send output to real stderr, since stuff in sb_err won't get - // printed until the command finishes. - fwprintf(stderr, FG_MSG, j->job_id, j->command_wcstr()); - } - - const wcstring ft = tok_first(j->command()); - if (!ft.empty()) env_set(L"_", ft.c_str(), ENV_EXPORT); - reader_write_title(j->command()); - - make_first(j); - job_set_flag(j, JOB_FOREGROUND, 1); - - job_continue(j, job_is_stopped(j)); + if (!j) { + return STATUS_BUILTIN_ERROR; } - return j ? STATUS_BUILTIN_OK : STATUS_BUILTIN_ERROR; + + if (streams.err_is_redirected) { + streams.err.append_format(FG_MSG, j->job_id, j->command_wcstr()); + } else { + // If we aren't redirecting, send output to real stderr, since stuff in sb_err won't get + // printed until the command finishes. + fwprintf(stderr, FG_MSG, j->job_id, j->command_wcstr()); + } + + const wcstring ft = tok_first(j->command()); + if (!ft.empty()) env_set(L"_", ft.c_str(), ENV_EXPORT); + reader_write_title(j->command()); + + make_first(j); + job_set_flag(j, JOB_FOREGROUND, 1); + + job_continue(j, job_is_stopped(j)); + return STATUS_BUILTIN_OK; } /// Helper function for builtin_bg(). @@ -2752,29 +2942,26 @@ static int builtin_breakpoint(parser_t &parser, io_streams_t &streams, wchar_t * /// Function for handling the \c return builtin. static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); - int status = proc_get_last_status(); - switch (argc) { - case 1: { - break; - } - case 2: { - wchar_t *end; - errno = 0; - status = fish_wcstoi(argv[1], &end, 10); - if (errno || *end != 0) { - streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], - argv[1]); - builtin_print_help(parser, streams, argv[0], streams.err); - return STATUS_BUILTIN_ERROR; - } - break; - } - default: { - streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]); + if (argc > 2) { + streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]); + builtin_print_help(parser, streams, argv[0], streams.err); + return STATUS_BUILTIN_ERROR; + } + + int status; + if (argc == 2) { + wchar_t *end; + errno = 0; + status = fish_wcstoi(argv[1], &end, 10); + if (errno || *end != 0) { + streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], + argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } + } else { + status = proc_get_last_status(); } // Find the function block. @@ -2830,10 +3017,9 @@ static const wcstring hist_cmd_to_string(hist_cmd_t hist_cmd) { return L"merge"; case HIST_SAVE: return L"save"; - default: - assert(0 && "Unhandled hist_cmd_t constant!"); - abort(); } + + DIE("should not reach this statement"); // silence some compiler errors about not returning } /// Remember the history subcommand and disallow selecting more than one history subcommand. @@ -2878,8 +3064,10 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar bool case_sensitive = false; bool null_terminate = false; - // TODO: Remove the long options that correspond to subcommands (e.g., '--delete') on or after - // 2017-10 (which will be a full year after these flags have been deprecated). + /// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to + /// the non-flag subcommand form. While many of these flags are deprecated they must be + /// supported at least until fish 3.0 and possibly longer to avoid breaking everyones + /// config.fish and other scripts. const wchar_t *short_options = L":Cmn:epchtz"; const struct woption long_options[] = {{L"prefix", no_argument, NULL, 'p'}, {L"contains", no_argument, NULL, 'c'}, @@ -2992,7 +3180,10 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar w.nextchar = NULL; break; } - default: { DIE("unexpected retval from wgetopt_long"); } + default: { + DIE("unexpected retval from wgetopt_long"); + break; + } } } @@ -3005,9 +3196,12 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar // If a history command hasn't already been specified via a flag check the first word. // Note that this can be simplified after we eliminate allowing subcommands as flags. // See the TODO above regarding the `long_options` array. - if (hist_cmd == HIST_NOOP && w.woptind < argc) { - hist_cmd = hist_string_to_cmd(argv[w.woptind]); - if (hist_cmd != HIST_NOOP) { + if (w.woptind < argc) { + hist_cmd_t subcmd = hist_string_to_cmd(argv[w.woptind]); + if (subcmd != HIST_NOOP) { + if (!set_hist_cmd(cmd, &hist_cmd, subcmd, streams)) { + return STATUS_BUILTIN_ERROR; + } w.woptind++; } } @@ -3104,7 +3298,8 @@ int builtin_parse(parser_t &parser, io_streams_t &streams, wchar_t **argv) const wcstring src = str2wcstring(&txt.at(0), txt.size()); parse_node_tree_t parse_tree; parse_error_list_t errors; - bool success = parse_tree_from_string(src, parse_flag_include_comments, &parse_tree, &errors); + bool success = parse_tree_from_string(src, parse_flag_include_comments, &parse_tree, + &errors); if (! success) { streams.out.append(L"Parsing failed:\n"); @@ -3117,7 +3312,8 @@ int builtin_parse(parser_t &parser, io_streams_t &streams, wchar_t **argv) streams.out.append(L"(Reparsed with continue after error)\n"); parse_tree.clear(); errors.clear(); - parse_tree_from_string(src, parse_flag_continue_after_error | parse_flag_include_comments, &parse_tree, &errors); + parse_tree_from_string(src, parse_flag_continue_after_error | + parse_flag_include_comments, &parse_tree, &errors); } const wcstring dump = parse_dump_tree(parse_tree, src); streams.out.append(dump); @@ -3266,7 +3462,7 @@ void builtin_init() { void builtin_destroy() {} /// Is there a builtin command with the given name? -bool builtin_exists(const wcstring &cmd) { return !!builtin_lookup(cmd); } +bool builtin_exists(const wcstring &cmd) { return static_cast(builtin_lookup(cmd)); } /// If builtin takes care of printing help itself static bool builtin_handles_help(const wchar_t *cmd) { @@ -3277,22 +3473,25 @@ static bool builtin_handles_help(const wchar_t *cmd) { /// Execute a builtin command int builtin_run(parser_t &parser, const wchar_t *const *argv, io_streams_t &streams) { - int (*cmd)(parser_t & parser, io_streams_t & streams, const wchar_t *const *argv) = NULL; + UNUSED(parser); + UNUSED(streams); if (argv == NULL || argv[0] == NULL) return STATUS_BUILTIN_ERROR; const builtin_data_t *data = builtin_lookup(argv[0]); - cmd = (int (*)(parser_t & parser, io_streams_t & streams, const wchar_t *const *))( - data ? data->func : NULL); - - if (argv[1] != NULL && !builtin_handles_help(argv[0])) { - if (argv[2] == NULL && (parse_util_argument_is_help(argv[1], 0))) { - builtin_print_help(parser, streams, argv[0], streams.out); - return STATUS_BUILTIN_OK; - } + if (argv[1] != NULL && !builtin_handles_help(argv[0]) && argv[2] == NULL && + parse_util_argument_is_help(argv[1], 0)) { + builtin_print_help(parser, streams, argv[0], streams.out); + return STATUS_BUILTIN_OK; } if (data != NULL) { - return cmd(parser, streams, argv); + // Warning: layering violation and naughty cast. The code originally had a much more + // complicated solution to achieve exactly the same result: lie about the constness of argv. + // Some of the builtins we call do mutate the array via their calls to wgetopt() which could + // result in the pointers being reordered. This is harmless because we only get called once + // with a given argv array and nothing else will look at the contents of the array after we + // return. + return data->func(parser, streams, (wchar_t **)argv); } debug(0, UNKNOWN_BUILTIN_ERR_MSG, argv[0]); diff --git a/src/builtin_commandline.cpp b/src/builtin_commandline.cpp index fd44975dd..9941a62cc 100644 --- a/src/builtin_commandline.cpp +++ b/src/builtin_commandline.cpp @@ -127,6 +127,10 @@ static void replace_part(const wchar_t *begin, const wchar_t *end, const wchar_t out_pos += wcslen(insert); break; } + default: { + DIE("unexpected append_mode"); + break; + } } out.append(end); reader_set_buffer(out, out_pos); @@ -152,29 +156,23 @@ static void write_part(const wchar_t *begin, const wchar_t *end, int cut_at_curs while (tok.next(&token)) { if ((cut_at_cursor) && (token.offset + token.text.size() >= pos)) break; - switch (token.type) { - case TOK_STRING: { - wcstring tmp = token.text; - unescape_string_in_place(&tmp, UNESCAPE_INCOMPLETE); - out.append(tmp); - out.push_back(L'\n'); - break; - } - default: { break; } + if (token.type == TOK_STRING) { + wcstring tmp = token.text; + unescape_string_in_place(&tmp, UNESCAPE_INCOMPLETE); + out.append(tmp); + out.push_back(L'\n'); } } streams.out.append(out); - free(buff); } else { if (cut_at_cursor) { - end = begin + pos; + streams.out.append(begin, pos); + } else { + streams.out.append(begin, end - begin); } - - // debug( 0, L"woot2 %ls -> %ls", buff, esc ); - streams.out.append(begin, end - begin); - streams.out.append(L"\n"); + streams.out.push_back(L'\n'); } } @@ -332,6 +330,10 @@ int builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_t **argv) builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return 1; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -476,26 +478,24 @@ int builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_t **argv) parse_util_token_extent(get_buffer(), get_cursor_pos(), &begin, &end, 0, 0); break; } + default: { + DIE("unexpected buffer_part"); + break; + } } - switch (argc - w.woptind) { - case 0: { - write_part(begin, end, cut_at_cursor, tokenize, streams); - break; - } - case 1: { - replace_part(begin, end, argv[w.woptind], append_mode); - break; - } - default: { - wcstring sb = argv[w.woptind]; - for (int i = w.woptind + 1; i < argc; i++) { - sb.push_back(L'\n'); - sb.append(argv[i]); - } - replace_part(begin, end, sb.c_str(), append_mode); - break; + int arg_count = argc - w.woptind; + if (arg_count == 0) { + write_part(begin, end, cut_at_cursor, tokenize, streams); + } else if (arg_count == 1) { + replace_part(begin, end, argv[w.woptind], append_mode); + } else { + wcstring sb = argv[w.woptind]; + for (int i = w.woptind + 1; i < argc; i++) { + sb.push_back(L'\n'); + sb.append(argv[i]); } + replace_part(begin, end, sb.c_str(), append_mode); } return 0; diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index 9dd180106..25c60ffbc 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -283,125 +283,127 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { res = true; break; } - } - } - - if (!res) { - if (condition && wcslen(condition)) { - const wcstring condition_string = condition; - parse_error_list_t errors; - if (parse_util_detect_errors(condition_string, &errors, - false /* do not accept incomplete */)) { - streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", argv[0], - condition); - for (size_t i = 0; i < errors.size(); i++) { - streams.err.append_format(L"\n%s: ", argv[0]); - streams.err.append(errors.at(i).describe(condition_string)); - } - res = true; + default: { + DIE("unexpected opt"); + break; } } } - if (!res) { - if (comp && wcslen(comp)) { - wcstring prefix; - if (argv[0]) { - prefix.append(argv[0]); - prefix.append(L": "); + if (!res && condition && wcslen(condition)) { + const wcstring condition_string = condition; + parse_error_list_t errors; + if (parse_util_detect_errors(condition_string, &errors, + false /* do not accept incomplete */)) { + streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", argv[0], + condition); + for (size_t i = 0; i < errors.size(); i++) { + streams.err.append_format(L"\n%s: ", argv[0]); + streams.err.append(errors.at(i).describe(condition_string)); } - - wcstring err_text; - if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) { - streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", - argv[0], comp); - streams.err.append(err_text); - streams.err.push_back(L'\n'); - res = true; - } - } - } - - if (!res) { - if (do_complete) { - const wchar_t *token; - - parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, - 0, 0); - - // Create a scoped transient command line, so that bulitin_commandline will see our - // argument, not the reader buffer. - builtin_commandline_scoped_transient_t temp_buffer(do_complete_param); - - if (recursion_level < 1) { - recursion_level++; - - std::vector comp; - complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT, - env_vars_snapshot_t::current()); - - for (size_t i = 0; i < comp.size(); i++) { - const completion_t &next = comp.at(i); - - // Make a fake commandline, and then apply the completion to it. - const wcstring faux_cmdline = token; - size_t tmp_cursor = faux_cmdline.size(); - wcstring faux_cmdline_with_completion = completion_apply_to_command_line( - next.completion, next.flags, faux_cmdline, &tmp_cursor, false); - - // completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE - // is set. We don't want to set COMPLETE_NO_SPACE because that won't close - // quotes. What we want is to close the quote, but not append the space. So we - // just look for the space and clear it. - if (!(next.flags & COMPLETE_NO_SPACE) && - string_suffixes_string(L" ", faux_cmdline_with_completion)) { - faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - - 1); - } - - // The input data is meant to be something like you would have on the command - // line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So - // we need to unescape the command line. See #1127. - unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT); - streams.out.append(faux_cmdline_with_completion); - - // Append any description. - if (!next.description.empty()) { - streams.out.push_back(L'\t'); - streams.out.append(next.description); - } - streams.out.push_back(L'\n'); - } - - recursion_level--; - } - } else if (w.woptind != argc) { - streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]); - builtin_print_help(parser, streams, argv[0], streams.err); - res = true; - } else if (cmd.empty() && path.empty()) { - // No arguments specified, meaning we print the definitions of all specified completions - // to stdout. - streams.out.append(complete_print()); - } else { - int flags = COMPLETE_AUTO_SPACE; + } + } - if (remove) { - builtin_complete_remove(cmd, path, short_opt.c_str(), gnu_opt, old_opt); + if (!res && comp && wcslen(comp)) { + wcstring prefix; + if (argv[0]) { + prefix.append(argv[0]); + prefix.append(L": "); + } - } else { - builtin_complete_add(cmd, path, short_opt.c_str(), gnu_opt, old_opt, result_mode, - authoritative, condition, comp, desc, flags); + wcstring err_text; + if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) { + streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", argv[0], + comp); + streams.err.append(err_text); + streams.err.push_back(L'\n'); + res = true; + } + } + + if (res) { + return 1; + } + + if (do_complete) { + const wchar_t *token; + + parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, + 0, 0); + + // Create a scoped transient command line, so that bulitin_commandline will see our + // argument, not the reader buffer. + builtin_commandline_scoped_transient_t temp_buffer(do_complete_param); + + if (recursion_level < 1) { + recursion_level++; + + std::vector comp; + complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT, + env_vars_snapshot_t::current()); + + for (size_t i = 0; i < comp.size(); i++) { + const completion_t &next = comp.at(i); + + // Make a fake commandline, and then apply the completion to it. + const wcstring faux_cmdline = token; + size_t tmp_cursor = faux_cmdline.size(); + wcstring faux_cmdline_with_completion = completion_apply_to_command_line( + next.completion, next.flags, faux_cmdline, &tmp_cursor, false); + + // completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE + // is set. We don't want to set COMPLETE_NO_SPACE because that won't close + // quotes. What we want is to close the quote, but not append the space. So we + // just look for the space and clear it. + if (!(next.flags & COMPLETE_NO_SPACE) && + string_suffixes_string(L" ", faux_cmdline_with_completion)) { + faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - + 1); + } + + // The input data is meant to be something like you would have on the command + // line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So + // we need to unescape the command line. See #1127. + unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT); + streams.out.append(faux_cmdline_with_completion); + + // Append any description. + if (!next.description.empty()) { + streams.out.push_back(L'\t'); + streams.out.append(next.description); + } + streams.out.push_back(L'\n'); } - // Handle wrap targets (probably empty). We only wrap commands, not paths. - for (size_t w = 0; w < wrap_targets.size(); w++) { - const wcstring &wrap_target = wrap_targets.at(w); - for (size_t i = 0; i < cmd.size(); i++) { - (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i), - wrap_target); - } + recursion_level--; + } + } else if (w.woptind != argc) { + streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]); + builtin_print_help(parser, streams, argv[0], streams.err); + + res = true; + } else if (cmd.empty() && path.empty()) { + // No arguments specified, meaning we print the definitions of all specified completions + // to stdout. + streams.out.append(complete_print()); + } else { + int flags = COMPLETE_AUTO_SPACE; + + if (remove) { + builtin_complete_remove(cmd, path, short_opt.c_str(), gnu_opt, old_opt); + + } else { + builtin_complete_add(cmd, path, short_opt.c_str(), gnu_opt, old_opt, result_mode, + authoritative, condition, comp, desc, flags); + } + + // Handle wrap targets (probably empty). We only wrap commands, not paths. + for (size_t w = 0; w < wrap_targets.size(); w++) { + const wcstring &wrap_target = wrap_targets.at(w); + for (size_t i = 0; i < cmd.size(); i++) { + (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i), + wrap_target); } } } diff --git a/src/builtin_jobs.cpp b/src/builtin_jobs.cpp index 72765cb82..7a92e6b9e 100644 --- a/src/builtin_jobs.cpp +++ b/src/builtin_jobs.cpp @@ -101,6 +101,10 @@ static void builtin_jobs_print(const job_t *j, int mode, int header, io_streams_ } break; } + default: { + DIE("unexpected mode"); + break; + } } } @@ -159,6 +163,10 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return 1; } + default: { + DIE("unexpected opt"); + break; + } } } diff --git a/src/builtin_printf.cpp b/src/builtin_printf.cpp index 7442673fe..645dd982b 100644 --- a/src/builtin_printf.cpp +++ b/src/builtin_printf.cpp @@ -508,39 +508,48 @@ void builtin_printf_state_t::print_direc(const wchar_t *start, size_t length, wc case L'G': { long double arg = string_to_scalar_type(argument, this); if (!have_field_width) { - if (!have_precision) + if (!have_precision) { this->append_format_output(fmt.c_str(), arg); - else + } else { this->append_format_output(fmt.c_str(), precision, arg); + } } else { - if (!have_precision) + if (!have_precision) { this->append_format_output(fmt.c_str(), field_width, arg); - else + } else { this->append_format_output(fmt.c_str(), field_width, precision, arg); + } } break; } case L'c': { - if (!have_field_width) + if (!have_field_width) { this->append_format_output(fmt.c_str(), *argument); - else + } else { this->append_format_output(fmt.c_str(), field_width, *argument); + } break; } case L's': { if (!have_field_width) { if (!have_precision) { this->append_format_output(fmt.c_str(), argument); - } else + } else { this->append_format_output(fmt.c_str(), precision, argument); + } } else { - if (!have_precision) + if (!have_precision) { this->append_format_output(fmt.c_str(), field_width, argument); - else + } else { this->append_format_output(fmt.c_str(), field_width, precision, argument); + } } break; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -588,8 +597,7 @@ int builtin_printf_state_t::print_formatted(const wchar_t *format, int argc, wch } modify_allowed_format_specifiers(ok, "aAcdeEfFgGiosuxX", true); - - for (;; f++, direc_length++) { + for (bool continue_looking_for_flags = true; continue_looking_for_flags;) { switch (*f) { case L'I': case L'\'': { @@ -609,10 +617,16 @@ int builtin_printf_state_t::print_formatted(const wchar_t *format, int argc, wch modify_allowed_format_specifiers(ok, "cs", false); break; } - default: { goto no_more_flag_characters; } + default: { + continue_looking_for_flags = false; + break; + } + } + if (continue_looking_for_flags) { + f++; + direc_length++; } } - no_more_flag_characters:; if (*f == L'*') { ++f; @@ -687,7 +701,10 @@ int builtin_printf_state_t::print_formatted(const wchar_t *format, int argc, wch f += print_esc(f, false); break; } - default: { this->append_output(*f); } + default: { + this->append_output(*f); + break; + } } } return save_argc - argc; diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index 9eeea3172..a210cbb75 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -77,12 +77,10 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, struct stat buff; if (wstat(dir, &buff) == -1) { error = true; - } - else if (!S_ISDIR(buff.st_mode)) { + } else if (!S_ISDIR(buff.st_mode)) { error = true; errno = ENOTDIR; - } - else if (waccess(dir, X_OK) == -1) { + } else if (waccess(dir, X_OK) == -1) { error = true; } @@ -90,7 +88,7 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, any_success = true; } else { streams.err.append_format(_(BUILTIN_SET_PATH_ERROR), L"set", key, dir.c_str(), - strerror(errno)); + strerror(errno)); const wchar_t *colon = wcschr(dir.c_str(), L':'); if (colon && *(colon + 1)) { @@ -123,6 +121,9 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, } switch (env_set(key, val_str, scope | ENV_USER)) { + case ENV_OK: { + break; + } case ENV_PERM: { streams.err.append_format(_(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key); @@ -143,6 +144,10 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, retcode = 1; break; } + default: { + DIE("unexpected env_set() ret val"); + break; + } } return retcode; @@ -159,7 +164,6 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, static int parse_index(std::vector &indexes, const wchar_t *src, const wchar_t *name, size_t var_count, io_streams_t &streams) { size_t len; - int count = 0; const wchar_t *src_orig = src; @@ -167,9 +171,7 @@ static int parse_index(std::vector &indexes, const wchar_t *src, const wch return 0; } - while (*src != L'\0' && (iswalnum(*src) || *src == L'_')) { - src++; - } + while (*src != L'\0' && (iswalnum(*src) || *src == L'_')) src++; if (*src != L'[') { streams.err.append_format(_(BUILTIN_SET_ARG_COUNT), L"set"); @@ -177,7 +179,6 @@ static int parse_index(std::vector &indexes, const wchar_t *src, const wch } len = src - src_orig; - if ((wcsncmp(src_orig, name, len) != 0) || (wcslen(name) != (len))) { streams.err.append_format( _(L"%ls: Multiple variable names specified in single call (%ls and %.*ls)\n"), L"set", @@ -186,37 +187,29 @@ static int parse_index(std::vector &indexes, const wchar_t *src, const wch } src++; - - while (iswspace(*src)) { - src++; - } + while (iswspace(*src)) src++; while (*src != L']') { wchar_t *end; - long l_ind; errno = 0; - l_ind = wcstol(src, &end, 10); - if (end == src || errno) { streams.err.append_format(_(L"%ls: Invalid index starting at '%ls'\n"), L"set", src); return 0; } - if (l_ind < 0) { - l_ind = var_count + l_ind + 1; - } + if (l_ind < 0) l_ind = var_count + l_ind + 1; - src = end; + src = end; //!OCLINT(parameter reassignment) if (*src == L'.' && *(src + 1) == L'.') { src += 2; long l_ind2 = wcstol(src, &end, 10); if (end == src || errno) { return 1; } - src = end; + src = end; //!OCLINT(parameter reassignment) if (l_ind2 < 0) { l_ind2 = var_count + l_ind2 + 1; @@ -231,6 +224,7 @@ static int parse_index(std::vector &indexes, const wchar_t *src, const wch indexes.push_back(l_ind); count++; } + while (iswspace(*src)) src++; } @@ -342,7 +336,7 @@ int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int erase = 0, list = 0, unexport = 0; int universal = 0, query = 0; bool shorten_ok = true; - bool preserve_incoming_failure_exit_status = true; + bool preserve_failure_exit_status = true; const int incoming_exit_status = proc_get_last_status(); // Variables used for performing the actual work. @@ -368,12 +362,12 @@ int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } case 'e': { erase = 1; - preserve_incoming_failure_exit_status = false; + preserve_failure_exit_status = false; break; } case 'n': { list = 1; - preserve_incoming_failure_exit_status = false; + preserve_failure_exit_status = false; break; } case 'x': { @@ -402,7 +396,7 @@ int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } case 'q': { query = 1; - preserve_incoming_failure_exit_status = false; + preserve_failure_exit_status = false; break; } case 'h': { @@ -633,7 +627,7 @@ int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { free(dest); - if (retcode == STATUS_BUILTIN_OK && preserve_incoming_failure_exit_status) + if (retcode == STATUS_BUILTIN_OK && preserve_failure_exit_status) retcode = incoming_exit_status; return retcode; } diff --git a/src/builtin_set_color.cpp b/src/builtin_set_color.cpp index a99882c2c..b05bf016d 100644 --- a/src/builtin_set_color.cpp +++ b/src/builtin_set_color.cpp @@ -77,13 +77,13 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // Parse options to obtain the requested operation and the modifiers. w.woptind = 0; while (1) { - int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); + int opt = w.wgetopt_long(argc, argv, short_options, long_options, 0); - if (c == -1) { + if (opt == -1) { break; } - switch (c) { + switch (opt) { case 0: { break; } @@ -110,6 +110,10 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { case '?': { return STATUS_BUILTIN_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -159,19 +163,17 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { builtin_set_color_output.clear(); output_set_writer(set_color_builtin_outputter); - if (bold) { - if (enter_bold_mode) writembs(tparm(enter_bold_mode)); + if (bold && enter_bold_mode) { + writembs(tparm(enter_bold_mode)); } - if (underline) { - if (enter_underline_mode) writembs(enter_underline_mode); + if (underline && enter_underline_mode) { + writembs(enter_underline_mode); } - if (bgcolor != NULL) { - if (bg.is_normal()) { - write_color(rgb_color_t::black(), false /* not is_fg */); - writembs(tparm(exit_attribute_mode)); - } + if (bgcolor != NULL && bg.is_normal()) { + write_color(rgb_color_t::black(), false /* not is_fg */); + writembs(tparm(exit_attribute_mode)); } if (!fg.is_none()) { @@ -188,10 +190,8 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } } - if (bgcolor != NULL) { - if (!bg.is_normal() && !bg.is_reset()) { - write_color(bg, false /* not is_fg */); - } + if (bgcolor != NULL && !bg.is_normal() && !bg.is_reset()) { + write_color(bg, false /* not is_fg */); } // Restore saved writer function. diff --git a/src/builtin_string.cpp b/src/builtin_string.cpp index 0f01096df..b19ad38e8 100644 --- a/src/builtin_string.cpp +++ b/src/builtin_string.cpp @@ -100,12 +100,13 @@ static int string_escape(parser_t &parser, io_streams_t &streams, int argc, wcha escape_flags_t flags = ESCAPE_ALL; wgetopter_t w; for (;;) { - int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); + int opt = w.wgetopt_long(argc, argv, short_options, long_options, 0); - if (c == -1) { + if (opt == -1) { break; } - switch (c) { + + switch (opt) { case 0: { break; } @@ -117,6 +118,10 @@ static int string_escape(parser_t &parser, io_streams_t &streams, int argc, wcha string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -145,11 +150,13 @@ static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_ bool quiet = false; wgetopter_t w; for (;;) { - int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); - if (c == -1) { + int opt = w.wgetopt_long(argc, argv, short_options, long_options, 0); + + if (opt == -1) { break; } - switch (c) { + + switch (opt) { case 0: { break; } @@ -161,6 +168,10 @@ static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_ string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -202,12 +213,12 @@ static int string_length(parser_t &parser, io_streams_t &streams, int argc, wcha bool quiet = false; wgetopter_t w; for (;;) { - int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); + int opt = w.wgetopt_long(argc, argv, short_options, long_options, 0); - if (c == -1) { + if (opt == -1) { break; } - switch (c) { + switch (opt) { case 0: { break; } @@ -219,6 +230,10 @@ static int string_length(parser_t &parser, io_streams_t &streams, int argc, wcha string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -499,12 +514,12 @@ static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar bool regex = false; wgetopter_t w; for (;;) { - int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); + int opt = w.wgetopt_long(argc, argv, short_options, long_options, 0); - if (c == -1) { + if (opt == -1) { break; } - switch (c) { + switch (opt) { case 0: { break; } @@ -536,6 +551,10 @@ static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -666,59 +685,63 @@ class regex_replacer_t : public string_replacer_t { regex(argv0, pattern, opts.ignore_case, streams), replacement(interpret_escapes(replacement_)) {} - bool replace_matches(const wchar_t *arg) { - // A return value of true means all is well (even if no replacements were performed), false - // indicates an unrecoverable error. - if (regex.code == 0) { - // pcre2_compile() failed - return false; - } - - uint32_t options = PCRE2_SUBSTITUTE_OVERFLOW_LENGTH | PCRE2_SUBSTITUTE_EXTENDED | - (opts.all ? PCRE2_SUBSTITUTE_GLOBAL : 0); - size_t arglen = wcslen(arg); - PCRE2_SIZE bufsize = (arglen == 0) ? 16 : 2 * arglen; - wchar_t *output = (wchar_t *)malloc(sizeof(wchar_t) * bufsize); - int pcre2_rc = 0; - for (;;) { - if (output == NULL) { - DIE_MEM(); - } - PCRE2_SIZE outlen = bufsize; - pcre2_rc = pcre2_substitute(regex.code, PCRE2_SPTR(arg), arglen, - 0, // start offset - options, regex.match, - 0, // match context - PCRE2_SPTR(replacement.c_str()), PCRE2_ZERO_TERMINATED, - (PCRE2_UCHAR *)output, &outlen); - - if (pcre2_rc == PCRE2_ERROR_NOMEMORY && bufsize < outlen) { - bufsize = outlen; - // cppcheck-suppress memleakOnRealloc - output = (wchar_t *)realloc(output, sizeof(wchar_t) * bufsize); - continue; - } - break; - } - - bool rc = true; - if (pcre2_rc < 0) { - string_error(streams, _(L"%ls: Regular expression substitute error: %ls\n"), argv0, - pcre2_strerror(pcre2_rc).c_str()); - rc = false; - } else { - if (!opts.quiet) { - streams.out.append(output); - streams.out.append(L'\n'); - } - total_replaced += pcre2_rc; - } - - free(output); - return rc; - } + bool replace_matches(const wchar_t *arg); }; +/// A return value of true means all is well (even if no replacements were performed), false +/// indicates an unrecoverable error. +bool regex_replacer_t::replace_matches(const wchar_t *arg) { + if (regex.code == 0) { + // pcre2_compile() failed + return false; + } + + uint32_t options = PCRE2_SUBSTITUTE_OVERFLOW_LENGTH | PCRE2_SUBSTITUTE_EXTENDED | + (opts.all ? PCRE2_SUBSTITUTE_GLOBAL : 0); + size_t arglen = wcslen(arg); + PCRE2_SIZE bufsize = (arglen == 0) ? 16 : 2 * arglen; + wchar_t *output = (wchar_t *)malloc(sizeof(wchar_t) * bufsize); + int pcre2_rc; + + bool done = false; + while (!done) { + if (output == NULL) { + DIE_MEM(); + } + PCRE2_SIZE outlen = bufsize; + pcre2_rc = pcre2_substitute(regex.code, PCRE2_SPTR(arg), arglen, + 0, // start offset + options, regex.match, + 0, // match context + PCRE2_SPTR(replacement.c_str()), PCRE2_ZERO_TERMINATED, + (PCRE2_UCHAR *)output, &outlen); + + if (pcre2_rc != PCRE2_ERROR_NOMEMORY || bufsize >= outlen) { + done = true; + } else { + bufsize = outlen; + // cppcheck-suppress memleakOnRealloc + output = (wchar_t *)realloc(output, sizeof(wchar_t) * bufsize); + } + } + + bool rc = true; + if (pcre2_rc < 0) { + string_error(streams, _(L"%ls: Regular expression substitute error: %ls\n"), argv0, + pcre2_strerror(pcre2_rc).c_str()); + rc = false; + } else { + if (!opts.quiet) { + streams.out.append(output); + streams.out.append(L'\n'); + } + total_replaced += pcre2_rc; + } + + free(output); + return rc; +} + static int string_replace(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { const wchar_t *short_options = L"aiqr"; const struct woption long_options[] = {{L"all", no_argument, 0, 'a'}, @@ -731,12 +754,12 @@ static int string_replace(parser_t &parser, io_streams_t &streams, int argc, wch bool regex = false; wgetopter_t w; for (;;) { - int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); + int opt = w.wgetopt_long(argc, argv, short_options, long_options, 0); - if (c == -1) { + if (opt == -1) { break; } - switch (c) { + switch (opt) { case 0: { break; } @@ -760,6 +783,10 @@ static int string_replace(parser_t &parser, io_streams_t &streams, int argc, wch string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -876,6 +903,10 @@ static int string_split(parser_t &parser, io_streams_t &streams, int argc, wchar string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -989,6 +1020,10 @@ static int string_sub(parser_t &parser, io_streams_t &streams, int argc, wchar_t string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -1078,6 +1113,10 @@ static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_ string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -1124,7 +1163,8 @@ static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_ static const struct string_subcommand { const wchar_t *name; - int (*handler)(parser_t &, io_streams_t &, int argc, wchar_t **argv); + int (*handler)(parser_t &, io_streams_t &, int argc, //!OCLINT(unused param) + wchar_t **argv); //!OCLINT(unused param) } string_subcommands[] = { diff --git a/src/builtin_test.cpp b/src/builtin_test.cpp index 334cbfb50..cc2fcea23 100644 --- a/src/builtin_test.cpp +++ b/src/builtin_test.cpp @@ -518,8 +518,8 @@ expression *test_parser::parse_expression(unsigned int start, unsigned int end) unsigned int argc = end - start; switch (argc) { case 0: { - assert(0); // should have been caught by the above test - return NULL; + DIE("argc should not be zero"); // should have been caught by the above test + break; } case 1: { return error(L"Missing argument at index %u", start + 1); @@ -579,62 +579,55 @@ bool binary_primary::evaluate(wcstring_list_t &errors) { } bool unary_operator::evaluate(wcstring_list_t &errors) { - switch (token) { - case test_bang: { - assert(subject.get()); - return !subject->evaluate(errors); - } - default: { - errors.push_back(format_string(L"Unknown token type in %s", __func__)); - return false; - } + if (token == test_bang) { + assert(subject.get()); + return !subject->evaluate(errors); } + + errors.push_back(format_string(L"Unknown token type in %s", __func__)); + return false; } bool combining_expression::evaluate(wcstring_list_t &errors) { - switch (token) { - case test_combine_and: - case test_combine_or: { - // One-element case. - if (subjects.size() == 1) return subjects.at(0)->evaluate(errors); + if (token == test_combine_and || token == test_combine_or) { + assert(!subjects.empty()); //!OCLINT(multiple unary operator) + assert(combiners.size() + 1 == subjects.size()); - // Evaluate our lists, remembering that AND has higher precedence than OR. We can - // visualize this as a sequence of OR expressions of AND expressions. - assert(combiners.size() + 1 == subjects.size()); - assert(!subjects.empty()); + // One-element case. + if (subjects.size() == 1) return subjects.at(0)->evaluate(errors); - size_t idx = 0, max = subjects.size(); - bool or_result = false; - while (idx < max) { - if (or_result) { // short circuit + // Evaluate our lists, remembering that AND has higher precedence than OR. We can + // visualize this as a sequence of OR expressions of AND expressions. + size_t idx = 0, max = subjects.size(); + bool or_result = false; + while (idx < max) { + if (or_result) { // short circuit + break; + } + + // Evaluate a stream of AND starting at given subject index. It may only have one + // element. + bool and_result = true; + for (; idx < max; idx++) { + // Evaluate it, short-circuiting. + and_result = and_result && subjects.at(idx)->evaluate(errors); + + // If the combiner at this index (which corresponding to how we combine with the + // next subject) is not AND, then exit the loop. + if (idx + 1 < max && combiners.at(idx) != test_combine_and) { + idx++; break; } - - // Evaluate a stream of AND starting at given subject index. It may only have one - // element. - bool and_result = true; - for (; idx < max; idx++) { - // Evaluate it, short-circuiting. - and_result = and_result && subjects.at(idx)->evaluate(errors); - - // If the combiner at this index (which corresponding to how we combine with the - // next subject) is not AND, then exit the loop. - if (idx + 1 < max && combiners.at(idx) != test_combine_and) { - idx++; - break; - } - } - - // OR it in. - or_result = or_result || and_result; } - return or_result; - } - default: { - errors.push_back(format_string(L"Unknown token type in %s", __func__)); - return BUILTIN_TEST_FAIL; + + // OR it in. + or_result = or_result || and_result; } + return or_result; } + + errors.push_back(format_string(L"Unknown token type in %s", __func__)); + return BUILTIN_TEST_FAIL; } bool parenthetical_expression::evaluate(wcstring_list_t &errors) { @@ -798,42 +791,36 @@ int builtin_test(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // Collect the arguments into a list. const wcstring_list_t args(argv + 1, argv + 1 + argc); - switch (argc) { - case 0: { - // Per 1003.1, exit false. - return BUILTIN_TEST_FAIL; - } - case 1: { - // Per 1003.1, exit true if the arg is non-empty. - return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS; - } - default: { - // Try parsing. If expr is not nil, we are responsible for deleting it. - wcstring err; - expression *expr = test_parser::parse_args(args, err); - if (!expr) { -#if 0 - printf("Oops! test was given args:\n"); - for (size_t i=0; i < argc; i++) { - printf("\t%ls\n", args.at(i).c_str()); - } - printf("and returned parse error: %ls\n", err.c_str()); -#endif - streams.err.append(err); - return BUILTIN_TEST_FAIL; - } + if (argc == 0) { + return BUILTIN_TEST_FAIL; // Per 1003.1, exit false. + } else if (argc == 1) { + // Per 1003.1, exit true if the arg is non-empty. + return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS; + } - wcstring_list_t eval_errors; - bool result = expr->evaluate(eval_errors); - if (!eval_errors.empty()) { - printf("test returned eval errors:\n"); - for (size_t i = 0; i < eval_errors.size(); i++) { - printf("\t%ls\n", eval_errors.at(i).c_str()); - } - } - delete expr; - return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL; + // Try parsing. If expr is not nil, we are responsible for deleting it. + wcstring err; + expression *expr = test_parser::parse_args(args, err); + if (!expr) { +#if 0 + printf("Oops! test was given args:\n"); + for (size_t i=0; i < argc; i++) { + printf("\t%ls\n", args.at(i).c_str()); + } + printf("and returned parse error: %ls\n", err.c_str()); +#endif + streams.err.append(err); + return BUILTIN_TEST_FAIL; + } + + wcstring_list_t eval_errors; + bool result = expr->evaluate(eval_errors); + if (!eval_errors.empty()) { + printf("test returned eval errors:\n"); + for (size_t i = 0; i < eval_errors.size(); i++) { + printf("\t%ls\n", eval_errors.at(i).c_str()); } } - return 1; + delete expr; + return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL; } diff --git a/src/builtin_ulimit.cpp b/src/builtin_ulimit.cpp index 6399cbdfe..5cc752982 100644 --- a/src/builtin_ulimit.cpp +++ b/src/builtin_ulimit.cpp @@ -262,6 +262,10 @@ int builtin_ulimit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return 1; } + default: { + DIE("unexpected opt"); + break; + } } } @@ -278,46 +282,42 @@ int builtin_ulimit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { return 0; } - switch (argc - w.woptind) { - case 0: { // show current limit value - print(what, hard, streams); - break; - } - case 1: { // change current limit value - rlim_t new_limit; - wchar_t *end; + int arg_count = argc - w.woptind; + if (arg_count == 0) { + // Show current limit value. + print(what, hard, streams); + } else if (arg_count == 1) { + // Change current limit value. + rlim_t new_limit; + wchar_t *end; - // Set both hard and soft limits if nothing else was specified. - if (!(hard + soft)) { - hard = soft = 1; + // Set both hard and soft limits if nothing else was specified. + if (!(hard + soft)) { + hard = soft = 1; + } + + if (wcscasecmp(argv[w.woptind], L"unlimited") == 0) { + new_limit = RLIM_INFINITY; + } else if (wcscasecmp(argv[w.woptind], L"hard") == 0) { + new_limit = get(what, 1); + } else if (wcscasecmp(argv[w.woptind], L"soft") == 0) { + new_limit = get(what, soft); + } else { + errno = 0; + new_limit = wcstol(argv[w.woptind], &end, 10); + if (errno || *end) { + streams.err.append_format(L"%ls: Invalid limit '%ls'\n", argv[0], argv[w.woptind]); + builtin_print_help(parser, streams, argv[0], streams.err); + return 1; } - - if (wcscasecmp(argv[w.woptind], L"unlimited") == 0) { - new_limit = RLIM_INFINITY; - } else if (wcscasecmp(argv[w.woptind], L"hard") == 0) { - new_limit = get(what, 1); - } else if (wcscasecmp(argv[w.woptind], L"soft") == 0) { - new_limit = get(what, soft); - } else { - errno = 0; - new_limit = wcstol(argv[w.woptind], &end, 10); - if (errno || *end) { - streams.err.append_format(L"%ls: Invalid limit '%ls'\n", argv[0], - argv[w.woptind]); - builtin_print_help(parser, streams, argv[0], streams.err); - return 1; - } - new_limit *= get_multiplier(what); - } - - return set(what, hard, soft, new_limit, streams); - } - default: { - streams.err.append(argv[0]); - streams.err.append(L": Too many arguments\n"); - builtin_print_help(parser, streams, argv[0], streams.err); - return 1; + new_limit *= get_multiplier(what); } + + return set(what, hard, soft, new_limit, streams); } - return 0; + + streams.err.append(argv[0]); + streams.err.append(L": Too many arguments\n"); + builtin_print_help(parser, streams, argv[0], streams.err); + return 1; } diff --git a/src/color.h b/src/color.h index fd756d0a2..05f1fa930 100644 --- a/src/color.h +++ b/src/color.h @@ -96,7 +96,7 @@ class rgb_color_t { color24_t to_color24() const; /// Returns whether the color is bold. - bool is_bold() const { return !!(flags & flag_bold); } + bool is_bold() const { return static_cast(flags & flag_bold); } /// Set whether the color is bold. void set_bold(bool x) { @@ -107,7 +107,7 @@ class rgb_color_t { } /// Returns whether the color is underlined. - bool is_underline() const { return !!(flags & flag_underline); } + bool is_underline() const { return static_cast(flags & flag_underline); } /// Set whether the color is underlined. void set_underline(bool x) { diff --git a/src/common.cpp b/src/common.cpp index 9f7d9c504..76d2b752a 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -43,7 +43,7 @@ struct termios shell_modes; // Note we foolishly assume that pthread_t is just a primitive. But it might be a struct. static pthread_t main_thread_id = 0; -static bool thread_assertions_configured_for_testing = false; +static bool thread_asserts_cfg_for_testing = false; wchar_t ellipsis_char; wchar_t omitted_newline_char; @@ -56,7 +56,7 @@ int debug_stack_frames = 0; // default number of stack frames to show on debug( static pid_t initial_pid = 0; /// Be able to restore the term's foreground process group. -static pid_t initial_foreground_process_group = -1; +static pid_t initial_fg_process_group = -1; /// This struct maintains the current state of the terminal size. It is updated on demand after /// receiving a SIGWINCH. Do not touch this struct directly, it's managed with a rwlock. Use @@ -107,6 +107,7 @@ demangled_backtrace(int max_frames, int skip_levels) { void __attribute__((noinline)) show_stackframe(const wchar_t msg_level, int frame_count, int skip_levels) { ASSERT_IS_NOT_FORKED_CHILD(); + if (frame_count < 1) return; // TODO: Decide if this is still needed. I'm commenting it out because it caused me some grief // while trying to debug a test failure. And the tests run just fine without spurious failures @@ -115,7 +116,6 @@ show_stackframe(const wchar_t msg_level, int frame_count, int skip_levels) { // Hack to avoid showing backtraces in the tester. // if (program_name && !wcscmp(program_name, L"(ignore)")) return; - if (frame_count < 1) frame_count = 999; debug_shared(msg_level, L"Backtrace:"); std::vector bt = demangled_backtrace(frame_count, skip_levels + 2); for (int i = 0; (size_t)i < bt.size(); i++) { @@ -196,15 +196,14 @@ static wcstring str2wcs_internal(const char *in, const size_t in_len) { // Protect against broken mbrtowc() implementations which attempt to encode UTF-8 // sequences longer than four bytes (e.g., OS X Snow Leopard). use_encode_direct = true; - } else if (sizeof(wchar_t) == 2 && (in[in_pos] & 0xF8) == 0xF0) { + } else if (sizeof(wchar_t) == 2 && //!OCLINT(constant if expression) + (in[in_pos] & 0xF8) == 0xF0) { // Assume we are in a UTF-16 environment (e.g., Cygwin) using a UTF-8 encoding. // The bits set check will be true for a four byte UTF-8 sequence that requires // two UTF-16 chars. Something that doesn't work with our simple use of mbrtowc(). use_encode_direct = true; } else { ret = mbrtowc(&wc, &in[in_pos], in_len - in_pos, &state); - // fprintf(stderr, "WTF in_pos %d ret %d\n", in_pos, ret); - // Determine whether to encode this character with our crazy scheme. if (wc >= ENCODE_DIRECT_BASE && wc < ENCODE_DIRECT_BASE + 256) { use_encode_direct = true; @@ -219,7 +218,8 @@ static wcstring str2wcs_internal(const char *in, const size_t in_len) { } else if (ret > in_len - in_pos) { // Other error codes? Terrifying, should never happen. use_encode_direct = true; - } else if (sizeof(wchar_t) == 2 && wc >= 0xD800 && wc <= 0xDFFF) { + } else if (sizeof(wchar_t) == 2 && wc >= 0xD800 && //!OCLINT(constant if expression) + wc <= 0xDFFF) { // If we get a surrogate pair char on a UTF-16 system (e.g., Cygwin) then // it's guaranteed the UTF-8 decoding is wrong so use direct encoding. use_encode_direct = true; @@ -227,24 +227,20 @@ static wcstring str2wcs_internal(const char *in, const size_t in_len) { } if (use_encode_direct) { - // fprintf(stderr, "WTF use_encode_direct\n"); wc = ENCODE_DIRECT_BASE + (unsigned char)in[in_pos]; result.push_back(wc); in_pos++; memset(&state, 0, sizeof state); - } else if (ret == 0) { - // fprintf(stderr, "WTF null byte\n"); - // Embedded null byte! + } else if (ret == 0) { // embedded null byte! result.push_back(L'\0'); in_pos++; memset(&state, 0, sizeof state); - } else { - // fprintf(stderr, "WTF null byte\n"); - // Normal case. + } else { // normal case result.push_back(wc); in_pos += ret; } } + return result; } @@ -286,16 +282,15 @@ std::string wcs2string(const wcstring &input) { result.reserve(input.size()); mbstate_t state = {}; - char converted[MB_LEN_MAX + 1]; + char converted[MB_LEN_MAX]; for (size_t i = 0; i < input.size(); i++) { wchar_t wc = input[i]; if (wc == INTERNAL_SEPARATOR) { - // Do nothing. + ; // do nothing } else if (wc >= ENCODE_DIRECT_BASE && wc < ENCODE_DIRECT_BASE + 256) { result.push_back(wc - ENCODE_DIRECT_BASE); - } else if (MB_CUR_MAX == 1) // single-byte locale (C/POSIX/ISO-8859) - { + } else if (MB_CUR_MAX == 1) { // single-byte locale (C/POSIX/ISO-8859) // If `wc` contains a wide character we emit a question-mark. if (wc & ~0xFF) { wc = '?'; @@ -305,8 +300,8 @@ std::string wcs2string(const wcstring &input) { } else { memset(converted, 0, sizeof converted); size_t len = wcrtomb(converted, wc, &state); - if (len == (size_t)(-1)) { - debug(1, L"Wide character %d has no narrow representation", wc); + if (len == (size_t)-1) { + debug(1, L"Wide character U+%4X has no narrow representation", wc); memset(&state, 0, sizeof(state)); } else { result.append(converted, len); @@ -332,7 +327,7 @@ static char *wcs2str_internal(const wchar_t *in, char *out) { while (in[in_pos]) { if (in[in_pos] == INTERNAL_SEPARATOR) { - // Do nothing. + ; // do nothing } else if (in[in_pos] >= ENCODE_DIRECT_BASE && in[in_pos] < ENCODE_DIRECT_BASE + 256) { out[out_pos++] = in[in_pos] - ENCODE_DIRECT_BASE; } else if (MB_CUR_MAX == 1) // single-byte locale (C/POSIX/ISO-8859) @@ -346,7 +341,7 @@ static char *wcs2str_internal(const wchar_t *in, char *out) { } else { size_t len = wcrtomb(&out[out_pos], in[in_pos], &state); if (len == (size_t)-1) { - debug(1, L"Wide character %d has no narrow representation", in[in_pos]); + debug(1, L"Wide character U+%4X has no narrow representation", in[in_pos]); memset(&state, 0, sizeof(state)); } else { out_pos += len; @@ -359,6 +354,14 @@ static char *wcs2str_internal(const wchar_t *in, char *out) { return out; } +/// Test if the character can be encoded using the current locale. +static bool can_be_encoded(wchar_t wc) { + char converted[MB_LEN_MAX]; + mbstate_t state = {}; + + return wcrtomb(converted, wc, &state) != (size_t)-1; +} + wcstring format_string(const wchar_t *format, ...) { va_list va; va_start(va, format); @@ -449,11 +452,11 @@ wchar_t *quote_end(const wchar_t *pos) { } void fish_setlocale() { - // Use ellipsis if on known unicode system, otherwise use $. - ellipsis_char = (fish_wcwidth(L'\x2026') > 0) ? L'\x2026' : L'$'; + // Use the Unicode "ellipsis" symbol if it can be encoded using the current locale. + ellipsis_char = can_be_encoded(L'\x2026') ? L'\x2026' : L'$'; - // U+23CE is the "return" character - omitted_newline_char = (fish_wcwidth(L'\x23CE') > 0) ? L'\x23CE' : L'~'; + // Use the Unicode "return" symbol if it can be encoded using the current locale. + omitted_newline_char = can_be_encoded(L'\x23CE') ? L'\x23CE' : L'~'; } bool contains_internal(const wchar_t *a, int vararg_handle, ...) { @@ -768,9 +771,9 @@ static void escape_string_internal(const wchar_t *orig_in, size_t in_len, wcstri assert(orig_in != NULL); const wchar_t *in = orig_in; - bool escape_all = !!(flags & ESCAPE_ALL); - bool no_quoted = !!(flags & ESCAPE_NO_QUOTED); - bool no_tilde = !!(flags & ESCAPE_NO_TILDE); + bool escape_all = static_cast(flags & ESCAPE_ALL); + bool no_quoted = static_cast(flags & ESCAPE_NO_QUOTED); + bool no_tilde = static_cast(flags & ESCAPE_NO_TILDE); int need_escape = 0; int need_complex_escape = 0; @@ -1117,8 +1120,8 @@ static bool unescape_string_internal(const wchar_t *const input, const size_t in wcstring result; result.reserve(input_len); - const bool unescape_special = !!(flags & UNESCAPE_SPECIAL); - const bool allow_incomplete = !!(flags & UNESCAPE_INCOMPLETE); + const bool unescape_special = static_cast(flags & UNESCAPE_SPECIAL); + const bool allow_incomplete = static_cast(flags & UNESCAPE_INCOMPLETE); int bracket_count = 0; @@ -1220,6 +1223,7 @@ static bool unescape_string_internal(const wchar_t *const input, const size_t in to_append_or_none = unescape_special ? INTERNAL_SEPARATOR : NOT_A_WCHAR; break; } + default: { break; } } } else if (mode == mode_single_quotes) { if (c == L'\\') { @@ -1297,6 +1301,7 @@ static bool unescape_string_internal(const wchar_t *const input, const size_t in } break; } + default: { break; } } } @@ -1505,7 +1510,7 @@ bool list_contains_string(const wcstring_list_t &list, const wcstring &str) { } int create_directory(const wcstring &d) { - int ok = 0; + bool ok = false; struct stat buf; int stat_res = 0; @@ -1514,18 +1519,10 @@ int create_directory(const wcstring &d) { } if (stat_res == 0) { - if (S_ISDIR(buf.st_mode)) { - ok = 1; - } - } else { - if (errno == ENOENT) { - wcstring dir = wdirname(d); - if (!create_directory(dir)) { - if (!wmkdir(d, 0700)) { - ok = 1; - } - } - } + if (S_ISDIR(buf.st_mode)) ok = true; + } else if (errno == ENOENT) { + wcstring dir = wdirname(d); + if (!create_directory(dir) && !wmkdir(d, 0700)) ok = true; } return ok ? 0 : -1; @@ -1682,9 +1679,7 @@ __attribute__((noinline)) void debug_thread_error(void) { void set_main_thread() { main_thread_id = pthread_self(); } -void configure_thread_assertions_for_testing(void) { - thread_assertions_configured_for_testing = true; -} +void configure_thread_assertions_for_testing(void) { thread_asserts_cfg_for_testing = true; } bool is_forked_child(void) { // Just bail if nobody's called setup_fork_guards, e.g. some of our tools. @@ -1704,14 +1699,14 @@ void setup_fork_guards(void) { } void save_term_foreground_process_group(void) { - initial_foreground_process_group = tcgetpgrp(STDIN_FILENO); + initial_fg_process_group = tcgetpgrp(STDIN_FILENO); } void restore_term_foreground_process_group(void) { - if (initial_foreground_process_group != -1) { + if (initial_fg_process_group != -1) { // This is called during shutdown and from a signal handler. We don't bother to complain on // failure. - tcsetpgrp(STDIN_FILENO, initial_foreground_process_group); + tcsetpgrp(STDIN_FILENO, initial_fg_process_group); } } @@ -1721,7 +1716,7 @@ bool is_main_thread() { } void assert_is_main_thread(const char *who) { - if (!is_main_thread() && !thread_assertions_configured_for_testing) { + if (!is_main_thread() && !thread_asserts_cfg_for_testing) { fprintf(stderr, "Warning: %s called off of main thread. Break on debug_thread_error to debug.\n", who); @@ -1739,7 +1734,7 @@ void assert_is_not_forked_child(const char *who) { } void assert_is_background_thread(const char *who) { - if (is_main_thread() && !thread_assertions_configured_for_testing) { + if (is_main_thread() && !thread_asserts_cfg_for_testing) { fprintf(stderr, "Warning: %s called on the main thread (may block!). Break on debug_thread_error " "to debug.\n", @@ -1761,7 +1756,7 @@ void assert_is_locked(void *vmutex, const char *who, const char *caller) { } void scoped_lock::lock(void) { - assert(!locked); + assert(!locked); //!OCLINT(multiple unary operator) ASSERT_IS_NOT_FORKED_CHILD(); VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_lock(lock_obj)); locked = true; @@ -1785,7 +1780,7 @@ scoped_lock::~scoped_lock() { } void scoped_rwlock::lock(void) { - assert(!(locked || locked_shared)); + assert(!(locked || locked_shared)); //!OCLINT(multiple unary operator) ASSERT_IS_NOT_FORKED_CHILD(); VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_rdlock(rwlock_obj)); locked = true; @@ -1799,7 +1794,7 @@ void scoped_rwlock::unlock(void) { } void scoped_rwlock::lock_shared(void) { - assert(!(locked || locked_shared)); + assert(!(locked || locked_shared)); //!OCLINT(multiple unary operator) ASSERT_IS_NOT_FORKED_CHILD(); VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_wrlock(rwlock_obj)); locked_shared = true; @@ -1935,3 +1930,17 @@ long convert_digit(wchar_t d, int base) { return res; } + +// Test if the specified character is in a range that fish uses interally to store special tokens. +// +// NOTE: This is used when tokenizing the input. It is also used when reading input, before +// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted +// string. We don't want external input to be able to feed reserved characters into our lexer/parser +// or code evaluator. +// +// TODO: Actually implement the replacement as documented above. +bool fish_reserved_codepoint(wchar_t c) { + return (c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END) || + (c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END) || + (c >= INPUT_COMMON_BASE && c < INPUT_COMMON_END); +} diff --git a/src/common.h b/src/common.h index 0cf6a3bc0..33781f680 100644 --- a/src/common.h +++ b/src/common.h @@ -211,6 +211,9 @@ extern bool has_working_tty_timestamps; } /// Pause for input, then exit the program. If supported, print a backtrace first. +// The `return` will never be run but silences oclint warnings. Especially when this is called +// from within a `switch` block. As of the time I'm writing this oclint doesn't recognize the +// `__attribute__((noreturn))` on the exit_without_destructors() function. #define FATAL_EXIT() \ { \ char exit_read_buff; \ @@ -258,7 +261,7 @@ extern bool has_working_tty_timestamps; #define contains(str, ...) contains_internal(str, 0, __VA_ARGS__, NULL) /// Print a stack trace to stderr. -void show_stackframe(const wchar_t msg_level, int frame_count = -1, int skip_levels = 0); +void show_stackframe(const wchar_t msg_level, int frame_count = 100, int skip_levels = 0); /// Read a line from the stream f into the string. Returns the number of bytes read or -1 on /// failure. @@ -776,3 +779,6 @@ long convert_digit(wchar_t d, int base); } while (0) #endif + +// Return true if the character is in a range reserved for fish's private use. +bool fish_reserved_codepoint(wchar_t c); diff --git a/src/complete.cpp b/src/complete.cpp index 9491c58b8..1ed733258 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -119,7 +119,7 @@ typedef struct complete_entry_opt { case option_type_double_long: return 2; } - assert(0 && "Unreachable"); + DIE("unreachable"); } } complete_entry_opt_t; @@ -289,9 +289,11 @@ class completer_t { return flags & COMPLETION_REQUEST_AUTOSUGGESTION ? COMPLETE_AUTOSUGGEST : COMPLETE_DEFAULT; } - bool wants_descriptions() const { return !!(flags & COMPLETION_REQUEST_DESCRIPTIONS); } + bool wants_descriptions() const { + return static_cast(flags & COMPLETION_REQUEST_DESCRIPTIONS); + } - bool fuzzy() const { return !!(flags & COMPLETION_REQUEST_FUZZY_MATCH); } + bool fuzzy() const { return static_cast(flags & COMPLETION_REQUEST_FUZZY_MATCH); } fuzzy_match_type_t max_fuzzy_match_type() const { // If we are doing fuzzy matching, request all types; if not request only prefix matching. @@ -668,22 +670,22 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool std::vector possible_comp; if (use_command) { - if (expand_string(str_cmd, &this->completions, - EXPAND_SPECIAL_FOR_COMMAND | EXPAND_FOR_COMPLETIONS | EXECUTABLES_ONLY | - this->expand_flags(), - NULL) != EXPAND_ERROR) { - if (this->wants_descriptions()) { - this->complete_cmd_desc(str_cmd); + expand_error_t result = expand_string(str_cmd, &this->completions, + EXPAND_SPECIAL_FOR_COMMAND | EXPAND_FOR_COMPLETIONS | + EXECUTABLES_ONLY | this->expand_flags(), + NULL); + if (result != EXPAND_ERROR && this->wants_descriptions()) { + this->complete_cmd_desc(str_cmd); } - } } + if (use_implicit_cd) { - if (!expand_string(str_cmd, &this->completions, - EXPAND_FOR_COMPLETIONS | DIRECTORIES_ONLY | this->expand_flags(), - NULL)) { - // Not valid as implicit cd. - } + // We don't really care if this succeeds or fails. If it succeeds this->completions will be + // updated with choices for the user. + (void)expand_string(str_cmd, &this->completions, + EXPAND_FOR_COMPLETIONS | DIRECTORIES_ONLY | this->expand_flags(), NULL); } + if (str_cmd.find(L'/') == wcstring::npos && str_cmd.at(0) != L'~') { if (use_function) { wcstring_list_t names = function_get_names(str_cmd.at(0) == L'_'); @@ -875,11 +877,10 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop if (this->type() == COMPLETE_DEFAULT) { ASSERT_IS_MAIN_THREAD(); complete_load(cmd, true); - } else if (this->type() == COMPLETE_AUTOSUGGEST) { - // Maybe load this command (on the main thread). - if (!completion_autoloader.has_tried_loading(cmd)) { - iothread_perform_on_main(complete_load_no_reload, &cmd); - } + } else if (this->type() == COMPLETE_AUTOSUGGEST && + !completion_autoloader.has_tried_loading(cmd)) { + // Load this command (on the main thread). + iothread_perform_on_main(complete_load_no_reload, &cmd); } // Make a list of lists of all options that we care about. @@ -925,13 +926,12 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); ++oiter) { const complete_entry_opt_t *o = &*oiter; - if (o->type == option_type_single_long) { - if (param_match(o, popt) && this->condition_test(o->condition)) { - old_style_match = true; - if (o->result_mode & NO_COMMON) use_common = false; - if (o->result_mode & NO_FILES) use_files = false; - complete_from_args(str, o->comp, o->localized_desc(), o->flags); - } + if (o->type == option_type_single_long && param_match(o, popt) && + this->condition_test(o->condition)) { + old_style_match = true; + if (o->result_mode & NO_COMMON) use_common = false; + if (o->result_mode & NO_FILES) use_files = false; + complete_from_args(str, o->comp, o->localized_desc(), o->flags); } } @@ -956,71 +956,73 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop } } - if (use_common) { - for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); - ++oiter) { - const complete_entry_opt_t *o = &*oiter; - // If this entry is for the base command, check if any of the arguments match. - if (!this->condition_test(o->condition)) continue; - if (o->option.empty()) { - use_files = use_files && ((o->result_mode & NO_FILES) == 0); - complete_from_args(str, o->comp, o->localized_desc(), o->flags); - } + if (!use_common) { + continue; + } - if (wcslen(str) > 0 && use_switches) { - // Check if the short style option matches. - if (short_ok(str, o, options)) { - // It's a match. - const wcstring desc = o->localized_desc(); - append_completion(&this->completions, o->option, desc, 0); - } - - // Check if the long style option matches. - if (o->type == option_type_single_long || o->type == option_type_double_long) { - int match = 0, match_no_case = 0; - - wcstring whole_opt(o->expected_dash_count(), L'-'); - whole_opt.append(o->option); - - match = string_prefixes_string(str, whole_opt); - - if (!match) { - match_no_case = wcsncasecmp(str, whole_opt.c_str(), wcslen(str)) == 0; - } - - if (match || match_no_case) { - int has_arg = 0; // does this switch have any known arguments - int req_arg = 0; // does this switch _require_ an argument - - size_t offset = 0; - complete_flags_t flags = 0; - - if (match) { - offset = wcslen(str); - } else { - flags = COMPLETE_REPLACES_TOKEN; - } - - has_arg = !o->comp.empty(); - req_arg = (o->result_mode & NO_COMMON); - - if (o->type == option_type_double_long && (has_arg && !req_arg)) { - // Optional arguments to a switch can only be handled using the '=', - // so we add it as a completion. By default we avoid using '=' and - // instead rely on '--switch switch-arg', since it is more commonly - // supported by homebrew getopt-like functions. - wcstring completion = - format_string(L"%ls=", whole_opt.c_str() + offset); - append_completion(&this->completions, completion, C_(o->desc), - flags); - } - - append_completion(&this->completions, whole_opt.c_str() + offset, - C_(o->desc), flags); - } - } - } + for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); + ++oiter) { + const complete_entry_opt_t *o = &*oiter; + // If this entry is for the base command, check if any of the arguments match. + if (!this->condition_test(o->condition)) continue; + if (o->option.empty()) { + use_files = use_files && ((o->result_mode & NO_FILES) == 0); + complete_from_args(str, o->comp, o->localized_desc(), o->flags); } + + if (wcslen(str) == 0 || !use_switches) { + continue; + } + + // Check if the short style option matches. + if (short_ok(str, o, options)) { + // It's a match. + const wcstring desc = o->localized_desc(); + append_completion(&this->completions, o->option, desc, 0); + } + + // Check if the long style option matches. + if (o->type != option_type_single_long && o->type != option_type_double_long) { + continue; + } + int match = 0, match_no_case = 0; + + wcstring whole_opt(o->expected_dash_count(), L'-'); + whole_opt.append(o->option); + + match = string_prefixes_string(str, whole_opt); + if (!match) { + match_no_case = wcsncasecmp(str, whole_opt.c_str(), wcslen(str)) == 0; + } + + if (!match && !match_no_case) { + continue; + } + + int has_arg = 0; // does this switch have any known arguments + int req_arg = 0; // does this switch _require_ an argument + size_t offset = 0; + complete_flags_t flags = 0; + + if (match) { + offset = wcslen(str); + } else { + flags = COMPLETE_REPLACES_TOKEN; + } + + has_arg = !o->comp.empty(); + req_arg = (o->result_mode & NO_COMMON); + + if (o->type == option_type_double_long && (has_arg && !req_arg)) { + // Optional arguments to a switch can only be handled using the '=', so we add it as + // a completion. By default we avoid using '=' and instead rely on '--switch + // switch-arg', since it is more commonly supported by homebrew getopt-like + // functions. + wcstring completion = format_string(L"%ls=", whole_opt.c_str() + offset); + append_completion(&this->completions, completion, C_(o->desc), flags); + } + + append_completion(&this->completions, whole_opt.c_str() + offset, C_(o->desc), flags); } } @@ -1144,31 +1146,35 @@ bool completer_t::try_complete_variable(const wcstring &str) { } switch (c) { - case L'\\': + case L'\\': { in_pos++; break; - - case L'$': + } + case L'$': { if (mode == e_unquoted || mode == e_double_quoted) { variable_start = in_pos; } break; - - case L'\'': + } + case L'\'': { if (mode == e_single_quoted) { mode = e_unquoted; } else if (mode == e_unquoted) { mode = e_single_quoted; } break; - - case L'"': + } + case L'"': { if (mode == e_double_quoted) { mode = e_unquoted; } else if (mode == e_unquoted) { mode = e_double_quoted; } break; + } + default: { + break; // all other chars ignored here + } } } @@ -1196,50 +1202,52 @@ bool completer_t::try_complete_user(const wcstring &str) { #else const wchar_t *cmd = str.c_str(); const wchar_t *first_char = cmd; - int res = 0; - double start_time = timef(); - if (*first_char == L'~' && !wcschr(first_char, L'/')) { - const wchar_t *user_name = first_char + 1; - const wchar_t *name_end = wcschr(user_name, L'~'); - if (name_end == 0) { - struct passwd *pw; - size_t name_len = wcslen(user_name); - - setpwent(); - - while ((pw = getpwent()) != 0) { - double current_time = timef(); - - if (current_time - start_time > 0.2) { - return 1; - } - - if (pw->pw_name) { - const wcstring pw_name_str = str2wcstring(pw->pw_name); - const wchar_t *pw_name = pw_name_str.c_str(); - if (wcsncmp(user_name, pw_name, name_len) == 0) { - wcstring desc = format_string(COMPLETE_USER_DESC, pw_name); - append_completion(&this->completions, &pw_name[name_len], desc, - COMPLETE_NO_SPACE); - - res = 1; - } else if (wcsncasecmp(user_name, pw_name, name_len) == 0) { - wcstring name = format_string(L"~%ls", pw_name); - wcstring desc = format_string(COMPLETE_USER_DESC, pw_name); - - append_completion(&this->completions, name, desc, COMPLETE_REPLACES_TOKEN | - COMPLETE_DONT_ESCAPE | - COMPLETE_NO_SPACE); - res = 1; - } - } - } - endpwent(); - } + if (*first_char != L'~' || wcschr(first_char, L'/')) { + return false; } - return res; + const wchar_t *user_name = first_char + 1; + const wchar_t *name_end = wcschr(user_name, L'~'); + if (name_end) { + return false; + } + + double start_time = timef(); + bool result = false; + struct passwd *pw; + size_t name_len = wcslen(user_name); + + setpwent(); + while ((pw = getpwent()) != 0) { + double current_time = timef(); + if (current_time - start_time > 0.2) { + return 1; + } + + if (!pw->pw_name) { + continue; + } + + const wcstring pw_name_str = str2wcstring(pw->pw_name); + const wchar_t *pw_name = pw_name_str.c_str(); + if (wcsncmp(user_name, pw_name, name_len) == 0) { + wcstring desc = format_string(COMPLETE_USER_DESC, pw_name); + append_completion(&this->completions, &pw_name[name_len], desc, COMPLETE_NO_SPACE); + + result = true; + } else if (wcsncasecmp(user_name, pw_name, name_len) == 0) { + wcstring name = format_string(L"~%ls", pw_name); + wcstring desc = format_string(COMPLETE_USER_DESC, pw_name); + + append_completion(&this->completions, name, desc, COMPLETE_REPLACES_TOKEN | + COMPLETE_DONT_ESCAPE | + COMPLETE_NO_SPACE); + result = true; + } + } + endpwent(); + return result; #endif } @@ -1534,7 +1542,7 @@ wcstring complete_print() { break; } case option_type_short: { - assert(!o->option.empty()); + assert(!o->option.empty()); //!OCLINT(multiple unary operator) append_format(out, L" --short-option '%lc'", o->option.at(0)); break; } diff --git a/src/env.cpp b/src/env.cpp index 79a1e0f0c..0fcee01c5 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -283,10 +283,6 @@ static void universal_callback(fish_message_type_t type, const wchar_t *name) { str = L"ERASE"; break; } - default: { - assert(0 && "Unhandled fish_message_type_t constant!"); - abort(); - } } if (str) { @@ -476,6 +472,25 @@ static env_node_t *env_get_node(const wcstring &key) { return env; } +/// Set the value of the environment variable whose name matches key to val. +/// +/// Memory policy: All keys and values are copied, the parameters can and should be freed by the +/// caller afterwards +/// +/// \param key The key +/// \param val The value +/// \param var_mode The type of the variable. Can be any combination of ENV_GLOBAL, ENV_LOCAL, +/// ENV_EXPORT and ENV_USER. If mode is zero, the current variable space is searched and the current +/// mode is used. If no current variable with the same name is found, ENV_LOCAL is assumed. +/// +/// Returns: +/// +/// * ENV_OK on success. +/// * ENV_PERM, can only be returned when setting as a user, e.g. ENV_USER is set. This means that +/// the user tried to change a read-only variable. +/// * ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric +/// variables set from the local or universal scopes, or set as exported. +/// * ENV_INVALID, the variable value was invalid. This applies only to special variables. int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode) { ASSERT_IS_MAIN_THREAD(); bool has_changed_old = has_changed_exported; @@ -512,7 +527,7 @@ int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode) umask(mask); // Do not actually create a umask variable, on env_get, it will be calculated // dynamically. - return 0; + return ENV_OK; } } @@ -521,7 +536,7 @@ int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode) // Zero element arrays are internaly not coded as null but as this placeholder string. if (!val) { - val = ENV_NULL; + val = ENV_NULL; //!OCLINT(parameter reassignment) } if (var_mode & ENV_UNIVERSAL) { @@ -566,10 +581,10 @@ int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode) node = top; } else if (preexisting_node != NULL) { node = preexisting_node; - if ((var_mode & (ENV_EXPORT | ENV_UNEXPORT)) == 0) { // use existing entry's exportv - var_mode = preexisting_entry_exportv ? ENV_EXPORT : 0; + var_mode = //!OCLINT(parameter reassignment) + preexisting_entry_exportv ? ENV_EXPORT : 0; } } else { if (!get_proc_had_barrier()) { @@ -635,7 +650,7 @@ int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode) // debug( 1, L"env_set: return from event firing" ); react_to_variable_change(key); - return 0; + return ENV_OK; } /// Attempt to remove/free the specified key/value pair from the specified map. @@ -713,7 +728,7 @@ int env_remove(const wcstring &key, int var_mode) { } const wchar_t *env_var_t::c_str(void) const { - assert(!is_missing); + assert(!is_missing); //!OCLINT(multiple unary operator) return wcstring::c_str(); } @@ -864,9 +879,7 @@ void env_push(bool new_scope) { node->next = top; node->new_scope = new_scope; - if (new_scope) { - if (local_scope_exports(top)) mark_changed_exported(); - } + if (new_scope && local_scope_exports(top)) mark_changed_exported(); top = node; } @@ -884,7 +897,7 @@ void env_pop() { } } - if (killme->new_scope) { + if (killme->new_scope) { //!OCLINT(collapsible if statements) if (killme->exportv || local_scope_exports(killme->next)) mark_changed_exported(); } diff --git a/src/env.h b/src/env.h index 6f783312c..56a84ffde 100644 --- a/src/env.h +++ b/src/env.h @@ -36,8 +36,8 @@ enum { }; typedef uint32_t env_mode_flags_t; -/// Error code for trying to alter read-only variable. -enum { ENV_PERM = 1, ENV_SCOPE, ENV_INVALID }; +/// Return values for `env_set()`. +enum { ENV_OK, ENV_PERM, ENV_SCOPE, ENV_INVALID }; /// A struct of configuration directories, determined in main() that fish will optionally pass to /// env_init. @@ -51,26 +51,6 @@ struct config_paths_t { /// Initialize environment variable data. void env_init(const struct config_paths_t *paths = NULL); -/// Set the value of the environment variable whose name matches key to val. -/// -/// Memory policy: All keys and values are copied, the parameters can and should be freed by the -/// caller afterwards -/// -/// \param key The key -/// \param val The value -/// \param mode The type of the variable. Can be any combination of ENV_GLOBAL, ENV_LOCAL, -/// ENV_EXPORT and ENV_USER. If mode is zero, the current variable space is searched and the current -/// mode is used. If no current variable with the same name is found, ENV_LOCAL is assumed. -/// -/// \returns 0 on success or an error code on failiure. -/// -/// The current error codes are: -/// -/// * ENV_PERM, can only be returned when setting as a user, e.g. ENV_USER is set. This means that -/// the user tried to change a read-only variable. -/// * ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric -/// variables set from the local or universal scopes, or set as exported. -/// * ENV_INVALID, the variable value was invalid. This applies only to special variables. int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t mode); class env_var_t : public wcstring { diff --git a/src/env_universal_common.cpp b/src/env_universal_common.cpp index a77eb6a6e..d0fcdab3c 100644 --- a/src/env_universal_common.cpp +++ b/src/env_universal_common.cpp @@ -930,23 +930,24 @@ static bool get_mac_address(unsigned char macaddr[MAC_ADDRESS_MAX_LEN], struct ifaddrs *ifap; bool ok = false; - if (getifaddrs(&ifap) == 0) { - for (const ifaddrs *p = ifap; p; p = p->ifa_next) { - if (p->ifa_addr && p->ifa_addr->sa_family == AF_LINK) { - if (p->ifa_name && p->ifa_name[0] && - !strcmp((const char *)p->ifa_name, interface)) { - const sockaddr_dl &sdl = *reinterpret_cast(p->ifa_addr); - - size_t alen = sdl.sdl_alen; - if (alen > MAC_ADDRESS_MAX_LEN) alen = MAC_ADDRESS_MAX_LEN; - memcpy(macaddr, sdl.sdl_data + sdl.sdl_nlen, alen); - ok = true; - break; - } - } - } - freeifaddrs(ifap); + if (getifaddrs(&ifap) != 0) { + return ok; } + + for (const ifaddrs *p = ifap; p; p = p->ifa_next) { + bool is_af_link = p->ifa_addr && p->ifa_addr->sa_family == AF_LINK; + if (is_af_link && p->ifa_name && p->ifa_name[0] && + !strcmp((const char *)p->ifa_name, interface)) { + const sockaddr_dl &sdl = *reinterpret_cast(p->ifa_addr); + + size_t alen = sdl.sdl_alen; + if (alen > MAC_ADDRESS_MAX_LEN) alen = MAC_ADDRESS_MAX_LEN; + memcpy(macaddr, sdl.sdl_data + sdl.sdl_nlen, alen); + ok = true; + break; + } + } + freeifaddrs(ifap); return ok; } @@ -978,11 +979,8 @@ wcstring get_machine_identifier() { for (size_t i = 0; i < MAC_ADDRESS_MAX_LEN; i++) { append_format(result, L"%02x", mac_addr[i]); } - } else if (get_hostname_identifier(&result)) { - // Hooray - } else { - // Fallback - result.assign(L"nohost"); + } else if (!get_hostname_identifier(&result)) { + result.assign(L"nohost"); // fallback to a dummy value } return result; } @@ -1033,12 +1031,11 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { } // Set the size, if it's too small. - if (!errored && size < (off_t)sizeof(universal_notifier_shmem_t)) { - if (ftruncate(fd, sizeof(universal_notifier_shmem_t)) < 0) { - int err = errno; - report_error(err, L"Unable to truncate shared memory object with path '%s'", path); - errored = true; - } + bool set_size = !errored && size < (off_t)sizeof(universal_notifier_shmem_t); + if (set_size && ftruncate(fd, sizeof(universal_notifier_shmem_t)) < 0) { + int err = errno; + report_error(err, L"Unable to truncate shared memory object with path '%s'", path); + errored = true; } // Memory map the region. @@ -1074,7 +1071,7 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { void post_notification() { if (region != NULL) { /* Read off the seed */ - uint32_t seed = ntohl(region->universal_variable_seed); + uint32_t seed = ntohl(region->universal_variable_seed); //!OCLINT(constant cond op) // Increment it. Don't let it wrap to zero. do { @@ -1083,9 +1080,9 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { last_seed = seed; // Write out our data. - region->magic = htonl(SHMEM_MAGIC_NUMBER); - region->version = htonl(SHMEM_VERSION_CURRENT); - region->universal_variable_seed = htonl(seed); + region->magic = htonl(SHMEM_MAGIC_NUMBER); //!OCLINT(constant cond op) + region->version = htonl(SHMEM_VERSION_CURRENT); //!OCLINT(constant cond op) + region->universal_variable_seed = htonl(seed); //!OCLINT(constant cond op) } } @@ -1106,7 +1103,7 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { bool poll() { bool result = false; if (region != NULL) { - uint32_t seed = ntohl(region->universal_variable_seed); + uint32_t seed = ntohl(region->universal_variable_seed); //!OCLINT(constant cond op) if (seed != last_seed) { result = true; last_seed = seed; @@ -1237,10 +1234,12 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { int fd = wopen_cloexec(vars_path, O_RDWR | O_NONBLOCK, 0600); if (fd < 0 && errno == ENOENT) { // File doesn't exist, try creating it. - if (mkfifo(narrow_path.c_str(), 0600) >= 0) { + int mkfifo_status = mkfifo(narrow_path.c_str(), 0600); + if (mkfifo_status != -1) { fd = wopen_cloexec(vars_path, O_RDWR | O_NONBLOCK, 0600); } } + if (fd < 0) { // Maybe open failed, maybe mkfifo failed. int err = errno; @@ -1314,13 +1313,11 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { if (pipe_fd >= 0) { // We need to write some data (any data) to the pipe, then wait for a while, then read // it back. Nobody is expected to read it except us. - int pid_nbo = htonl(getpid()); + int pid_nbo = htonl(getpid()); //!OCLINT(constant cond op) ssize_t amt_written = write(this->pipe_fd, &pid_nbo, sizeof pid_nbo); - if (amt_written < 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { - // Very unsual: the pipe is full! - drain_excessive_data(); - } + if (amt_written < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + // Very unsual: the pipe is full! + drain_excessive_data(); } // Now schedule a read for some time in the future. @@ -1358,8 +1355,6 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { } bool poll() { - bool result = false; - // Check if we are past the readback time. if (this->readback_time_usec > 0 && get_time() >= this->readback_time_usec) { // Read back what we wrote. We do nothing with the value. @@ -1374,30 +1369,29 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { } // Check to see if we are doing readability polling. - if (polling_due_to_readable_fd && pipe_fd >= 0) { - // We are polling, so we are definitely going to sync. - result = true; - - // See if this is still readable. - fd_set fds; - FD_ZERO(&fds); - FD_SET(this->pipe_fd, &fds); - struct timeval timeout = {}; - select(this->pipe_fd + 1, &fds, NULL, NULL, &timeout); - if (!FD_ISSET(this->pipe_fd, &fds)) { - // No longer readable, no longer polling. - polling_due_to_readable_fd = false; - drain_if_still_readable_time_usec = 0; - } else { - // Still readable. If it's been readable for a long time, there is probably - // lingering data on the pipe. - if (get_time() >= drain_if_still_readable_time_usec) { - drain_excessive_data(); - } - } + if (!polling_due_to_readable_fd || pipe_fd < 0) { + return false; } - return result; + // We are polling, so we are definitely going to sync. + // See if this is still readable. + fd_set fds; + FD_ZERO(&fds); + FD_SET(this->pipe_fd, &fds); + struct timeval timeout = {}; + select(this->pipe_fd + 1, &fds, NULL, NULL, &timeout); + if (!FD_ISSET(this->pipe_fd, &fds)) { + // No longer readable, no longer polling. + polling_due_to_readable_fd = false; + drain_if_still_readable_time_usec = 0; + } else { + // Still readable. If it's been readable for a long time, there is probably + // lingering data on the pipe. + if (get_time() >= drain_if_still_readable_time_usec) { + drain_excessive_data(); + } + } + return true; } }; @@ -1416,23 +1410,25 @@ static universal_notifier_t::notifier_strategy_t fetch_default_strategy_from_env const size_t opt_count = sizeof options / sizeof *options; const char *var = getenv(UNIVERSAL_NOTIFIER_ENV_NAME); - if (var != NULL && var[0] != '\0') { - size_t i; - for (i = 0; i < opt_count; i++) { - if (!strcmp(var, options[i].name)) { - result = options[i].strat; - break; - } + if (var == NULL || var[0] == '\0') { + return result; + } + + size_t i; + for (i = 0; i < opt_count; i++) { + if (!strcmp(var, options[i].name)) { + result = options[i].strat; + break; } - if (i >= opt_count) { - fprintf(stderr, "Warning: unrecognized value for %s: '%s'\n", - UNIVERSAL_NOTIFIER_ENV_NAME, var); - fprintf(stderr, "Warning: valid values are "); - for (size_t j = 0; j < opt_count; j++) { - fprintf(stderr, "%s%s", j > 0 ? ", " : "", options[j].name); - } - fputc('\n', stderr); + } + if (i >= opt_count) { + fprintf(stderr, "Warning: unrecognized value for %s: '%s'\n", + UNIVERSAL_NOTIFIER_ENV_NAME, var); + fprintf(stderr, "Warning: valid values are "); + for (size_t j = 0; j < opt_count; j++) { + fprintf(stderr, "%s%s", j > 0 ? ", " : "", options[j].name); } + fputc('\n', stderr); } return result; } @@ -1460,7 +1456,7 @@ universal_notifier_t &universal_notifier_t::default_notifier() { universal_notifier_t *universal_notifier_t::new_notifier_for_strategy( universal_notifier_t::notifier_strategy_t strat, const wchar_t *test_path) { if (strat == strategy_default) { - strat = resolve_default_strategy(); + strat = resolve_default_strategy(); //!OCLINT(parameter reassignment) } switch (strat) { case strategy_shmem_polling: { diff --git a/src/event.cpp b/src/event.cpp index 143992c7e..31651054c 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -88,6 +88,10 @@ static int event_match(const event_t &classv, const event_t &instance) { case EVENT_GENERIC: { return instance.str_param1 == classv.str_param1; } + default: { + DIE("unexpected classv.type"); + break; + } } // This should never be reached. @@ -226,7 +230,10 @@ static wcstring event_desc_compact(const event_t &event) { res = format_string(L"EVENT_GENERIC(%ls)", event.str_param1.c_str()); break; } - default: { res = format_string(L"unknown/illegal event(%x)", event.type); } + default: { + res = format_string(L"unknown/illegal event(%x)", event.type); + break; + } } if (event.function_name.size()) { return format_string(L"%ls: \"%ls\"", res.c_str(), event.function_name.c_str()); diff --git a/src/exec.cpp b/src/exec.cpp index 777f8e354..bd6ae4560 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -282,11 +282,6 @@ static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t *out_chain, out.reset(new io_fd_t(in->fd, fd, false)); break; } - default: { - // Unknown type, should never happen. - assert(0 && "Unhandled io_mode constant"); - abort(); - } } if (out.get() != NULL) result_chain.push_back(out); @@ -353,7 +348,7 @@ static void internal_exec_helper(parser_t &parser, const wcstring &def, node_off // foreground process group, we don't use posix_spawn if we're going to foreground the process. (If // we use fork(), we can call tcsetpgrp after the fork, before the exec, and avoid the race). static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *process) { - if (job_get_flag(job, JOB_CONTROL)) { + if (job_get_flag(job, JOB_CONTROL)) { //!OCLINT(collapsible if statements) // We are going to use job control; therefore when we launch this job it will get its own // process group ID. But will it be foregrounded? if (job_get_flag(job, JOB_TERMINAL) && job_get_flag(job, JOB_FOREGROUND)) { @@ -406,7 +401,7 @@ void exec_job(parser_t &parser, job_t *j) { if ((io->io_mode == IO_BUFFER)) { io_buffer_t *io_buffer = static_cast(io.get()); - assert(!io_buffer->is_input); + assert(!io_buffer->is_input); //!OCLINT(multiple unary operator) } } @@ -442,7 +437,7 @@ void exec_job(parser_t &parser, job_t *j) { j->first_process->completed = 1; return; } - assert(0 && "This should be unreachable"); + DIE("this should be unreachable"); } // We may have block IOs that conflict with fd redirections. For example, we may have a command @@ -827,9 +822,7 @@ void exec_job(parser_t &parser, job_t *j) { case INTERNAL_EXEC: // We should have handled exec up above. - assert( - 0 && - "INTERNAL_EXEC process found in pipeline, where it should never be. Aborting."); + DIE("INTERNAL_EXEC process found in pipeline, where it should never be. Aborting."); break; } @@ -913,45 +906,42 @@ void exec_job(parser_t &parser, job_t *j) { // output, so that we can truncate the file. Does not apply to /dev/null. bool must_fork = redirection_is_to_real_file(stdout_io.get()) || redirection_is_to_real_file(stderr_io.get()); - if (!must_fork) { - if (p->next == NULL) { - const bool stdout_is_to_buffer = - stdout_io && stdout_io->io_mode == IO_BUFFER; - const bool no_stdout_output = stdout_buffer.empty(); - const bool no_stderr_output = stderr_buffer.empty(); + if (!must_fork && p->next == NULL) { + const bool stdout_is_to_buffer = stdout_io && stdout_io->io_mode == IO_BUFFER; + const bool no_stdout_output = stdout_buffer.empty(); + const bool no_stderr_output = stderr_buffer.empty(); - if (no_stdout_output && no_stderr_output) { - // The builtin produced no output and is not inside of a pipeline. No - // need to fork or even output anything. - debug(3, L"Skipping fork: no output for internal builtin '%ls'", - p->argv0()); - fork_was_skipped = true; - } else if (no_stderr_output && stdout_is_to_buffer) { - // The builtin produced no stderr, and its stdout is going to an - // internal buffer. There is no need to fork. This helps out the - // performance quite a bit in complex completion code. - debug(3, L"Skipping fork: buffered output for internal builtin '%ls'", - p->argv0()); + if (no_stdout_output && no_stderr_output) { + // The builtin produced no output and is not inside of a pipeline. No + // need to fork or even output anything. + debug(3, L"Skipping fork: no output for internal builtin '%ls'", + p->argv0()); + fork_was_skipped = true; + } else if (no_stderr_output && stdout_is_to_buffer) { + // The builtin produced no stderr, and its stdout is going to an + // internal buffer. There is no need to fork. This helps out the + // performance quite a bit in complex completion code. + debug(3, L"Skipping fork: buffered output for internal builtin '%ls'", + p->argv0()); - io_buffer_t *io_buffer = static_cast(stdout_io.get()); - const std::string res = wcs2string(builtin_io_streams->out.buffer()); + io_buffer_t *io_buffer = static_cast(stdout_io.get()); + const std::string res = wcs2string(builtin_io_streams->out.buffer()); - io_buffer->out_buffer_append(res.data(), res.size()); - fork_was_skipped = true; - } else if (stdout_io.get() == NULL && stderr_io.get() == NULL) { - // We are writing to normal stdout and stderr. Just do it - no need to - // fork. - debug(3, L"Skipping fork: ordinary output for internal builtin '%ls'", - p->argv0()); - const std::string outbuff = wcs2string(stdout_buffer); - const std::string errbuff = wcs2string(stderr_buffer); - bool builtin_io_done = do_builtin_io(outbuff.data(), outbuff.size(), - errbuff.data(), errbuff.size()); - if (!builtin_io_done && errno != EPIPE) { - show_stackframe(L'E'); - } - fork_was_skipped = true; + io_buffer->out_buffer_append(res.data(), res.size()); + fork_was_skipped = true; + } else if (stdout_io.get() == NULL && stderr_io.get() == NULL) { + // We are writing to normal stdout and stderr. Just do it - no need to + // fork. + debug(3, L"Skipping fork: ordinary output for internal builtin '%ls'", + p->argv0()); + const std::string outbuff = wcs2string(stdout_buffer); + const std::string errbuff = wcs2string(stderr_buffer); + bool builtin_io_done = do_builtin_io(outbuff.data(), outbuff.size(), + errbuff.data(), errbuff.size()); + if (!builtin_io_done && errno != EPIPE) { + show_stackframe(L'E'); } + fork_was_skipped = true; } } @@ -1074,7 +1064,7 @@ void exec_job(parser_t &parser, job_t *j) { setup_child_process(j, p, process_net_io_chain); safe_launch_process(p, actual_cmd, argv, envv); // safe_launch_process _never_ returns... - assert(0 && "safe_launch_process should not have returned"); + DIE("safe_launch_process should not have returned"); } else { debug(2, L"Fork #%d, pid %d: external command '%s' from '%ls'\n", g_fork_count, pid, p->argv0(), file ? file : L""); @@ -1088,17 +1078,13 @@ void exec_job(parser_t &parser, job_t *j) { // This is the parent process. Store away information on the child, and possibly // fice it control over the terminal. p->pid = pid; - set_child_group(j, p, 0); - break; } case INTERNAL_EXEC: { // We should have handled exec up above. - assert( - 0 && - "INTERNAL_EXEC process found in pipeline, where it should never be. Aborting."); + DIE("INTERNAL_EXEC process found in pipeline, where it should never be. Aborting."); break; } } @@ -1177,37 +1163,38 @@ static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, // If the caller asked us to preserve the exit status, restore the old status. Otherwise set the // status of the subcommand. proc_set_last_status(apply_exit_status ? subcommand_status : prev_status); - is_subshell = prev_subshell; - if (lst != NULL && io_buffer.get() != NULL) { - const char *begin = io_buffer->out_buffer_ptr(); - const char *end = begin + io_buffer->out_buffer_size(); - if (split_output) { - const char *cursor = begin; - while (cursor < end) { - // Look for the next separator. - const char *stop = (const char *)memchr(cursor, '\n', end - cursor); - const bool hit_separator = (stop != NULL); - if (!hit_separator) { - // If it's not found, just use the end. - stop = end; - } - // Stop now points at the first character we do not want to copy. - const wcstring wc = str2wcstring(cursor, stop - cursor); - lst->push_back(wc); + if (lst == NULL || io_buffer.get() == NULL) { + return subcommand_status; + } - // If we hit a separator, skip over it; otherwise we're at the end. - cursor = stop + (hit_separator ? 1 : 0); + const char *begin = io_buffer->out_buffer_ptr(); + const char *end = begin + io_buffer->out_buffer_size(); + if (split_output) { + const char *cursor = begin; + while (cursor < end) { + // Look for the next separator. + const char *stop = (const char *)memchr(cursor, '\n', end - cursor); + const bool hit_separator = (stop != NULL); + if (!hit_separator) { + // If it's not found, just use the end. + stop = end; } - } else { - // we're not splitting output, but we still want to trim off a trailing newline. - if (end != begin && end[-1] == '\n') { - --end; - } - const wcstring wc = str2wcstring(begin, end - begin); + // Stop now points at the first character we do not want to copy. + const wcstring wc = str2wcstring(cursor, stop - cursor); lst->push_back(wc); + + // If we hit a separator, skip over it; otherwise we're at the end. + cursor = stop + (hit_separator ? 1 : 0); } + } else { + // We're not splitting output, but we still want to trim off a trailing newline. + if (end != begin && end[-1] == '\n') { + --end; + } + const wcstring wc = str2wcstring(begin, end - begin); + lst->push_back(wc); } return subcommand_status; diff --git a/src/expand.cpp b/src/expand.cpp index 1c2a92eb1..4bb30bdd4 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -166,36 +166,31 @@ wcstring expand_escape_variable(const wcstring &in) { tokenize_variable_array(in, lst); - switch (lst.size()) { - case 0: { - buff.append(L"''"); - break; - } - case 1: { - const wcstring &el = lst.at(0); + int size = lst.size(); + if (size == 0) { + buff.append(L"''"); + } else if (size == 1) { + const wcstring &el = lst.at(0); - if (el.find(L' ') != wcstring::npos && is_quotable(el)) { + if (el.find(L' ') != wcstring::npos && is_quotable(el)) { + buff.append(L"'"); + buff.append(el); + buff.append(L"'"); + } else { + buff.append(escape_string(el, 1)); + } + } else { + for (size_t j = 0; j < lst.size(); j++) { + const wcstring &el = lst.at(j); + if (j) buff.append(L" "); + + if (is_quotable(el)) { buff.append(L"'"); buff.append(el); buff.append(L"'"); } else { buff.append(escape_string(el, 1)); } - break; - } - default: { - for (size_t j = 0; j < lst.size(); j++) { - const wcstring &el = lst.at(j); - if (j) buff.append(L" "); - - if (is_quotable(el)) { - buff.append(L"'"); - buff.append(el); - buff.append(L"'"); - } else { - buff.append(escape_string(el, 1)); - } - } } } return buff; @@ -225,10 +220,8 @@ static bool match_pid(const wcstring &cmd, const wchar_t *proc, size_t *offset) const wcstring base_cmd = wbasename(cmd); bool result = string_prefixes_string(proc, base_cmd); - if (result) { - // It's a match. Return the offset within the full command. - if (offset) *offset = cmd.size() - base_cmd.size(); - } + // It's a match. Return the offset within the full command. + if (result && offset) *offset = cmd.size() - base_cmd.size(); return result; } @@ -442,7 +435,7 @@ struct find_job_data_t { /// The following function is invoked on the main thread, because the job list is not thread safe. /// It should search the job list for something matching the given proc, and then return 1 to stop -/// the search, 0 to continue it . +/// the search, 0 to continue it. static int find_job(const struct find_job_data_t *info) { ASSERT_IS_MAIN_THREAD(); @@ -501,42 +494,45 @@ static int find_job(const struct find_job_data_t *info) { found = 1; } - if (!found) { - job_iterator_t jobs; - while ((j = jobs.next())) { - if (j->command_is_empty()) continue; + if (found) { + return found; + } - size_t offset; - if (match_pid(j->command(), proc, &offset)) { - if (flags & EXPAND_FOR_COMPLETIONS) { - append_completion(&completions, j->command_wcstr() + offset + wcslen(proc), - COMPLETE_JOB_DESC, 0); - } else { - append_completion(&completions, to_string(j->pgid)); - found = 1; - } + job_iterator_t jobs; + while ((j = jobs.next())) { + if (j->command_is_empty()) continue; + + size_t offset; + if (match_pid(j->command(), proc, &offset)) { + if (flags & EXPAND_FOR_COMPLETIONS) { + append_completion(&completions, j->command_wcstr() + offset + wcslen(proc), + COMPLETE_JOB_DESC, 0); + } else { + append_completion(&completions, to_string(j->pgid)); + found = 1; } } + } - if (!found) { - jobs.reset(); - while ((j = jobs.next())) { - process_t *p; - if (j->command_is_empty()) continue; - for (p = j->first_process; p; p = p->next) { - if (p->actual_cmd.empty()) continue; + if (found) { + return found; + } - size_t offset; - if (match_pid(p->actual_cmd, proc, &offset)) { - if (flags & EXPAND_FOR_COMPLETIONS) { - append_completion(&completions, - wcstring(p->actual_cmd, offset + wcslen(proc)), - COMPLETE_CHILD_PROCESS_DESC, 0); - } else { - append_completion(&completions, to_string(p->pid), L"", 0); - found = 1; - } - } + jobs.reset(); + while ((j = jobs.next())) { + process_t *p; + if (j->command_is_empty()) continue; + for (p = j->first_process; p; p = p->next) { + if (p->actual_cmd.empty()) continue; + + size_t offset; + if (match_pid(p->actual_cmd, proc, &offset)) { + if (flags & EXPAND_FOR_COMPLETIONS) { + append_completion(&completions, wcstring(p->actual_cmd, offset + wcslen(proc)), + COMPLETE_CHILD_PROCESS_DESC, 0); + } else { + append_completion(&completions, to_string(p->pid), L"", 0); + found = 1; } } } @@ -632,13 +628,11 @@ static bool expand_pid(const wcstring &instr_with_sep, expand_flags_t flags, const size_t prev_count = out->size(); find_process(in + 1, flags, out); - if (prev_count == out->size()) { - if (!(flags & EXPAND_FOR_COMPLETIONS)) { - // We failed to find anything. - append_syntax_error(errors, 1, FAILED_EXPANSION_PROCESS_ERR_MSG, - escape(in + 1, ESCAPE_NO_QUOTED).c_str()); - return false; - } + if (prev_count == out->size() && !(flags & EXPAND_FOR_COMPLETIONS)) { + // We failed to find anything. + append_syntax_error(errors, 1, FAILED_EXPANSION_PROCESS_ERR_MSG, + escape(in + 1, ESCAPE_NO_QUOTED).c_str()); + return false; } return true; @@ -755,193 +749,196 @@ static int expand_variables(const wcstring &instr, std::vector *ou for (long i = last_idx - 1; (i >= 0) && is_ok && !empty; i--) { const wchar_t c = instr.at(i); - if ((c == VARIABLE_EXPAND) || (c == VARIABLE_EXPAND_SINGLE)) { - size_t start_pos = i + 1; - size_t stop_pos; - long var_len; - int is_single = (c == VARIABLE_EXPAND_SINGLE); + if (c != VARIABLE_EXPAND && c != VARIABLE_EXPAND_SINGLE) { + continue; + } - stop_pos = start_pos; - while (stop_pos < insize) { - const wchar_t nc = instr.at(stop_pos); - if (nc == VARIABLE_EXPAND_EMPTY) { - stop_pos++; - break; - } - if (!wcsvarchr(nc)) break; + long var_len; + int is_single = (c == VARIABLE_EXPAND_SINGLE); + size_t start_pos = i + 1; + size_t stop_pos = start_pos; + while (stop_pos < insize) { + const wchar_t nc = instr.at(stop_pos); + if (nc == VARIABLE_EXPAND_EMPTY) { stop_pos++; - } - - // printf( "Stop for '%c'\n", in[stop_pos]); - var_len = stop_pos - start_pos; - - if (var_len == 0) { - if (errors) { - parse_util_expand_variable_error(instr, 0 /* global_token_pos */, i, errors); - } - - is_ok = false; break; } + if (!wcsvarchr(nc)) break; - var_tmp.append(instr, start_pos, var_len); - env_var_t var_val; - if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY) { - var_val = env_var_t::missing_var(); - } else { - var_val = expand_var(var_tmp.c_str()); + stop_pos++; + } + + // printf( "Stop for '%c'\n", in[stop_pos]); + var_len = stop_pos - start_pos; + + if (var_len == 0) { + if (errors) { + parse_util_expand_variable_error(instr, 0 /* global_token_pos */, i, errors); } - if (!var_val.missing()) { - int all_vars = 1; - wcstring_list_t var_item_list; + is_ok = false; + break; + } - if (is_ok) { - tokenize_variable_array(var_val, var_item_list); + var_tmp.append(instr, start_pos, var_len); + env_var_t var_val; + if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY) { + var_val = env_var_t::missing_var(); + } else { + var_val = expand_var(var_tmp.c_str()); + } - const size_t slice_start = stop_pos; - if (slice_start < insize && instr.at(slice_start) == L'[') { - wchar_t *slice_end; - size_t bad_pos; - all_vars = 0; - const wchar_t *in = instr.c_str(); - bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, - var_pos_list, var_item_list.size()); - if (bad_pos != 0) { - append_syntax_error(errors, stop_pos + bad_pos, L"Invalid index value"); + if (!var_val.missing()) { + int all_vars = 1; + wcstring_list_t var_item_list; + + if (is_ok) { + tokenize_variable_array(var_val, var_item_list); + + const size_t slice_start = stop_pos; + if (slice_start < insize && instr.at(slice_start) == L'[') { + wchar_t *slice_end; + size_t bad_pos; + all_vars = 0; + const wchar_t *in = instr.c_str(); + bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, + var_item_list.size()); + if (bad_pos != 0) { + append_syntax_error(errors, stop_pos + bad_pos, L"Invalid index value"); + is_ok = false; + break; + } + stop_pos = (slice_end - in); + } + + if (!all_vars) { + wcstring_list_t string_values(var_idx_list.size()); + for (size_t j = 0; j < var_idx_list.size(); j++) { + long tmp = var_idx_list.at(j); + // Check that we are within array bounds. If not, truncate the list to + // exit. + if (tmp < 1 || (size_t)tmp > var_item_list.size()) { + size_t var_src_pos = var_pos_list.at(j); + // The slice was parsed starting at stop_pos, so we have to add that + // to the error position. + append_syntax_error(errors, slice_start + var_src_pos, + ARRAY_BOUNDS_ERR); is_ok = false; + var_idx_list.resize(j); break; + } else { + // Replace each index in var_idx_list inplace with the string value + // at the specified index. + // al_set( var_idx_list, j, wcsdup((const wchar_t *)al_get( + // &var_item_list, tmp-1 ) ) ); + string_values.at(j) = var_item_list.at(tmp - 1); } - stop_pos = (slice_end - in); } - if (!all_vars) { - wcstring_list_t string_values(var_idx_list.size()); - for (size_t j = 0; j < var_idx_list.size(); j++) { - long tmp = var_idx_list.at(j); - // Check that we are within array bounds. If not, truncate the list to - // exit. - if (tmp < 1 || (size_t)tmp > var_item_list.size()) { - size_t var_src_pos = var_pos_list.at(j); - // The slice was parsed starting at stop_pos, so we have to add that - // to the error position. - append_syntax_error(errors, slice_start + var_src_pos, - ARRAY_BOUNDS_ERR); - is_ok = false; - var_idx_list.resize(j); - break; - } else { - // Replace each index in var_idx_list inplace with the string value - // at the specified index. - // al_set( var_idx_list, j, wcsdup((const wchar_t *)al_get( - // &var_item_list, tmp-1 ) ) ); - string_values.at(j) = var_item_list.at(tmp - 1); - } - } - - // string_values is the new var_item_list. - var_item_list.swap(string_values); - } - } - - if (is_ok) { - if (is_single) { - wcstring res(instr, 0, i); - if (i > 0) { - if (instr.at(i - 1) != VARIABLE_EXPAND_SINGLE) { - res.push_back(INTERNAL_SEPARATOR); - } else if (var_item_list.empty() || var_item_list.front().empty()) { - // First expansion is empty, but we need to recursively expand. - res.push_back(VARIABLE_EXPAND_EMPTY); - } - } - - for (size_t j = 0; j < var_item_list.size(); j++) { - const wcstring &next = var_item_list.at(j); - if (is_ok) { - if (j != 0) res.append(L" "); - res.append(next); - } - } - assert(stop_pos <= insize); - res.append(instr, stop_pos, insize - stop_pos); - is_ok &= expand_variables(res, out, i, errors); - } else { - for (size_t j = 0; j < var_item_list.size(); j++) { - const wcstring &next = var_item_list.at(j); - if (is_ok && (i == 0) && stop_pos == insize) { - append_completion(out, next); - } else { - if (is_ok) { - wcstring new_in; - new_in.append(instr, 0, i); - - if (i > 0) { - if (instr.at(i - 1) != VARIABLE_EXPAND) { - new_in.push_back(INTERNAL_SEPARATOR); - } else if (next.empty()) { - new_in.push_back(VARIABLE_EXPAND_EMPTY); - } - } - assert(stop_pos <= insize); - new_in.append(next); - new_in.append(instr, stop_pos, insize - stop_pos); - is_ok &= expand_variables(new_in, out, i, errors); - } - } - } - } + // string_values is the new var_item_list. + var_item_list.swap(string_values); } + } + if (!is_ok) { return is_ok; } - // Even with no value, we still need to parse out slice syntax. Behave as though we - // had 1 value, so $foo[1] always works. - const size_t slice_start = stop_pos; - if (slice_start < insize && instr.at(slice_start) == L'[') { - const wchar_t *in = instr.c_str(); - wchar_t *slice_end; - size_t bad_pos; - - bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, 1); - if (bad_pos != 0) { - append_syntax_error(errors, stop_pos + bad_pos, L"Invalid index value"); - is_ok = 0; - return is_ok; - } - stop_pos = (slice_end - in); - - // Validate that the parsed indexes are valid. - for (size_t j = 0; j < var_idx_list.size(); j++) { - long tmp = var_idx_list.at(j); - if (tmp != 1) { - size_t var_src_pos = var_pos_list.at(j); - append_syntax_error(errors, slice_start + var_src_pos, ARRAY_BOUNDS_ERR); - is_ok = 0; - return is_ok; + if (is_single) { + wcstring res(instr, 0, i); + if (i > 0) { + if (instr.at(i - 1) != VARIABLE_EXPAND_SINGLE) { + res.push_back(INTERNAL_SEPARATOR); + } else if (var_item_list.empty() || var_item_list.front().empty()) { + // First expansion is empty, but we need to recursively expand. + res.push_back(VARIABLE_EXPAND_EMPTY); } } - } - // Expand a non-existing variable. - if (c == VARIABLE_EXPAND) { - // Regular expansion, i.e. expand this argument to nothing. - empty = true; - } else { - // Expansion to single argument. - wcstring res; - res.append(instr, 0, i); - if (i > 0 && instr.at(i - 1) == VARIABLE_EXPAND_SINGLE) { - res.push_back(VARIABLE_EXPAND_EMPTY); + for (size_t j = 0; j < var_item_list.size(); j++) { + const wcstring &next = var_item_list.at(j); + if (is_ok) { + if (j != 0) res.append(L" "); + res.append(next); + } } assert(stop_pos <= insize); res.append(instr, stop_pos, insize - stop_pos); - is_ok &= expand_variables(res, out, i, errors); + } else { + for (size_t j = 0; j < var_item_list.size(); j++) { + const wcstring &next = var_item_list.at(j); + if (is_ok && i == 0 && stop_pos == insize) { + append_completion(out, next); + } else { + if (is_ok) { + wcstring new_in; + new_in.append(instr, 0, i); + + if (i > 0) { + if (instr.at(i - 1) != VARIABLE_EXPAND) { + new_in.push_back(INTERNAL_SEPARATOR); + } else if (next.empty()) { + new_in.push_back(VARIABLE_EXPAND_EMPTY); + } + } + assert(stop_pos <= insize); + new_in.append(next); + new_in.append(instr, stop_pos, insize - stop_pos); + is_ok &= expand_variables(new_in, out, i, errors); + } + } + } + } + + return is_ok; + } + + // Even with no value, we still need to parse out slice syntax. Behave as though we + // had 1 value, so $foo[1] always works. + const size_t slice_start = stop_pos; + if (slice_start < insize && instr.at(slice_start) == L'[') { + const wchar_t *in = instr.c_str(); + wchar_t *slice_end; + size_t bad_pos; + + bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, 1); + if (bad_pos != 0) { + append_syntax_error(errors, stop_pos + bad_pos, L"Invalid index value"); + is_ok = 0; return is_ok; } + stop_pos = (slice_end - in); + + // Validate that the parsed indexes are valid. + for (size_t j = 0; j < var_idx_list.size(); j++) { + long tmp = var_idx_list.at(j); + if (tmp != 1) { + size_t var_src_pos = var_pos_list.at(j); + append_syntax_error(errors, slice_start + var_src_pos, ARRAY_BOUNDS_ERR); + is_ok = 0; + return is_ok; + } + } + } + + // Expand a non-existing variable. + if (c == VARIABLE_EXPAND) { + // Regular expansion, i.e. expand this argument to nothing. + empty = true; + } else { + // Expansion to single argument. + wcstring res; + res.append(instr, 0, i); + if (i > 0 && instr.at(i - 1) == VARIABLE_EXPAND_SINGLE) { + res.push_back(VARIABLE_EXPAND_EMPTY); + } + assert(stop_pos <= insize); + res.append(instr, stop_pos, insize - stop_pos); + + is_ok &= expand_variables(res, out, i, errors); + return is_ok; } } @@ -985,6 +982,10 @@ static expand_error_t expand_brackets(const wcstring &instr, expand_flags_t flag } case BRACKET_SEP: { if (bracket_count == 1) last_sep = pos; + break; + } + default: { + break; // we ignore all other characters here } } } @@ -1025,21 +1026,19 @@ static expand_error_t expand_brackets(const wcstring &instr, expand_flags_t flag tot_len = length_preceding_brackets + length_following_brackets; item_begin = bracket_begin + 1; for (const wchar_t *pos = (bracket_begin + 1); true; pos++) { - if (bracket_count == 0) { - if ((*pos == BRACKET_SEP) || (pos == bracket_end)) { - assert(pos >= item_begin); - size_t item_len = pos - item_begin; + if (bracket_count == 0 && ((*pos == BRACKET_SEP) || (pos == bracket_end))) { + assert(pos >= item_begin); + size_t item_len = pos - item_begin; - wcstring whole_item; - whole_item.reserve(tot_len + item_len + 2); - whole_item.append(in, length_preceding_brackets); - whole_item.append(item_begin, item_len); - whole_item.append(bracket_end + 1); - expand_brackets(whole_item, flags, out, errors); + wcstring whole_item; + whole_item.reserve(tot_len + item_len + 2); + whole_item.append(in, length_preceding_brackets); + whole_item.append(item_begin, item_len); + whole_item.append(bracket_end + 1); + expand_brackets(whole_item, flags, out, errors); - item_begin = pos + 1; - if (pos == bracket_end) break; - } + item_begin = pos + 1; + if (pos == bracket_end) break; } if (*pos == BRACKET_BEGIN) { @@ -1076,6 +1075,10 @@ static int expand_cmdsubst(const wcstring &input, std::vector *out case 1: { break; } + default: { + DIE("unhandled parse_ret value"); + break; + } } const wcstring subcmd(paran_begin + 1, paran_end - paran_begin - 1); @@ -1299,6 +1302,9 @@ static void remove_internal_separator(wcstring *str, bool conv) { str->at(idx) = L'*'; break; } + default: { + break; // we ignore all other characters + } } } } @@ -1307,8 +1313,10 @@ static void remove_internal_separator(wcstring *str, bool conv) { /// A stage in string expansion is represented as a function that takes an input and returns a list /// of output (by reference). We get flags and errors. It may return an error; if so expansion /// halts. -typedef expand_error_t (*expand_stage_t)(const wcstring &input, std::vector *out, - expand_flags_t flags, parse_error_list_t *errors); +typedef expand_error_t (*expand_stage_t)(const wcstring &input, //!OCLINT(unused param) + std::vector *out, //!OCLINT(unused param) + expand_flags_t flags, //!OCLINT(unused param) + parse_error_list_t *errors); //!OCLINT(unused param) static expand_error_t expand_stage_cmdsubst(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) { @@ -1389,7 +1397,7 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector< const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_CHAR */); if (has_wildcard && (flags & EXECUTABLES_ONLY)) { - // Don't do wildcard expansion for executables. See #785. Make them expand to nothing here. + ; // don't do wildcard expansion for executables, see issue #785 } else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) || has_wildcard) { // We either have a wildcard, or we don't have a wildcard but we're doing completion @@ -1401,8 +1409,8 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector< // which may be CDPATH if the special flag is set. const wcstring working_dir = env_get_pwd_slash(); wcstring_list_t effective_working_dirs; - bool for_cd = !!(flags & EXPAND_SPECIAL_FOR_CD); - bool for_command = !!(flags & EXPAND_SPECIAL_FOR_COMMAND); + bool for_cd = static_cast(flags & EXPAND_SPECIAL_FOR_CD); + bool for_command = static_cast(flags & EXPAND_SPECIAL_FOR_COMMAND); if (!for_cd && !for_command) { // Common case. effective_working_dirs.push_back(working_dir); @@ -1517,19 +1525,17 @@ expand_error_t expand_string(const wcstring &input, std::vector *o bool expand_one(wcstring &string, expand_flags_t flags, parse_error_list_t *errors) { std::vector completions; - bool result = false; - if ((!(flags & EXPAND_FOR_COMPLETIONS)) && expand_is_clean(string)) { + if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(string)) { return true; } - if (expand_string(string, &completions, flags | EXPAND_NO_DESCRIPTIONS, errors)) { - if (completions.size() == 1) { - string = completions.at(0).completion; - result = true; - } + if (expand_string(string, &completions, flags | EXPAND_NO_DESCRIPTIONS, errors) && + completions.size() == 1) { + string = completions.at(0).completion; + return true; } - return result; + return false; } // https://github.com/fish-shell/fish-shell/issues/367 @@ -1556,24 +1562,26 @@ static std::string escape_single_quoted_hack_hack_hack_hack(const char *str) { bool fish_xdm_login_hack_hack_hack_hack(std::vector *cmds, int argc, const char *const *argv) { - bool result = false; - if (cmds && cmds->size() == 1) { - const std::string &cmd = cmds->at(0); - if (cmd == "exec \"${@}\"" || cmd == "exec \"$@\"") { - // We're going to construct a new command that starts with exec, and then has the - // remaining arguments escaped. - std::string new_cmd = "exec"; - for (int i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (arg) { - new_cmd.push_back(' '); - new_cmd.append(escape_single_quoted_hack_hack_hack_hack(arg)); - } - } + if (!cmds || cmds->size() != 1) { + return false; + } - cmds->at(0) = new_cmd; - result = true; + bool result = false; + const std::string &cmd = cmds->at(0); + if (cmd == "exec \"${@}\"" || cmd == "exec \"$@\"") { + // We're going to construct a new command that starts with exec, and then has the + // remaining arguments escaped. + std::string new_cmd = "exec"; + for (int i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg) { + new_cmd.push_back(' '); + new_cmd.append(escape_single_quoted_hack_hack_hack_hack(arg)); + } } + + cmds->at(0) = new_cmd; + result = true; } return result; } diff --git a/src/fallback.cpp b/src/fallback.cpp index 0c386fa1e..255d8d95f 100644 --- a/src/fallback.cpp +++ b/src/fallback.cpp @@ -203,13 +203,10 @@ size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t siz) { // Not enough room in dst, add NUL and traverse rest of src. if (n == 0) { - if (siz != 0) *d = '\0'; - // NUL-terminate dst. - while (*s++) - ; + if (siz != 0) *d = '\0'; // NUL-terminate dst + while (*s++) ; // ignore rest of src } - return s - src - 1; - // Count does not include NUL. + return s - src - 1; // count does not include NUL } #endif @@ -353,7 +350,7 @@ static int bisearch(wchar_t ucs, const struct interval *table, int max) { if (ucs > table[mid].last) min = mid + 1; else if (ucs < table[mid].first) - max = mid - 1; + max = mid - 1; //!OCLINT(parameter reassignment) else return 1; } diff --git a/src/fish.cpp b/src/fish.cpp index 644e62a6f..cc84686c0 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -314,6 +314,7 @@ static int fish_parse_opt(int argc, char **argv, std::vector *cmds) case 0: { fwprintf(stderr, _(L"getopt_long() unexpectedly returned zero\n")); exit(127); + break; } case 'c': { cmds->push_back(optarg); @@ -358,6 +359,7 @@ static int fish_parse_opt(int argc, char **argv, std::vector *cmds) case 'v': { fwprintf(stdout, _(L"%s, version %s\n"), PACKAGE_NAME, get_fish_version()); exit(0); + break; } case 'D': { char *end; @@ -377,6 +379,7 @@ static int fish_parse_opt(int argc, char **argv, std::vector *cmds) default: { // We assume getopt_long() has already emitted a diagnostic msg. exit(1); + break; } } } @@ -424,11 +427,6 @@ int main(int argc, char **argv) { int res = 1; int my_optind = 0; - // We can't do this at compile time due to the use of enum symbols. - assert(EXPAND_SENTINAL >= EXPAND_RESERVED_BASE && EXPAND_SENTINAL <= EXPAND_RESERVED_END); - assert(ANY_SENTINAL >= WILDCARD_RESERVED_BASE && ANY_SENTINAL <= WILDCARD_RESERVED_END); - assert(R_SENTINAL >= INPUT_COMMON_BASE && R_SENTINAL <= INPUT_COMMON_END); - program_name = L"fish"; set_main_thread(); setup_fork_guards(); diff --git a/src/fish_indent.cpp b/src/fish_indent.cpp index 7f8df7cab..8605e4ead 100644 --- a/src/fish_indent.cpp +++ b/src/fish_indent.cpp @@ -102,7 +102,7 @@ static void dump_node(indent_t node_indent, const parse_node_t &node, const wcst nextc_str[1] = L'c'; nextc_str[2] = nextc + '@'; } - fwprintf(stderr, L"{off %4d, len %4d, indent %2u, kw %ls, %ls} [%ls|%ls|%ls]\n", + fwprintf(stderr, L"{off %4u, len %4u, indent %2u, kw %ls, %ls} [%ls|%ls|%ls]\n", node.source_start, node.source_length, node_indent, keyword_description(node.keyword), token_type_description(node.type), prevc_str, source_txt.c_str(), nextc_str); } diff --git a/src/fish_key_reader.cpp b/src/fish_key_reader.cpp index c2deb5076..281396638 100644 --- a/src/fish_key_reader.cpp +++ b/src/fish_key_reader.cpp @@ -206,9 +206,8 @@ static void process_input(bool continuous_mode) { output_bind_command(bind_chars); if (first_char_seen && !continuous_mode) { return; - } else { - continue; } + continue; } prev_tstamp = output_elapsed_time(prev_tstamp, first_char_seen); @@ -307,11 +306,13 @@ int main(int argc, char **argv) { {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; int opt; - while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) { + bool error = false; + while (!error && (opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) { switch (opt) { case 0: { fprintf(stderr, "getopt_long() unexpectedly returned zero\n"); - exit(1); + error = true; + break; } case 'c': { continuous_mode = true; @@ -320,6 +321,7 @@ int main(int argc, char **argv) { case 'h': { print_help("fish_key_reader", 0); exit(0); + break; } case 'd': { char *end; @@ -332,7 +334,7 @@ int main(int argc, char **argv) { debug_level = (int)tmp; } else { fwprintf(stderr, _(L"Invalid value '%s' for debug-level flag"), optarg); - exit(1); + error = true; } break; } @@ -347,16 +349,19 @@ int main(int argc, char **argv) { debug_stack_frames = (int)tmp; } else { fwprintf(stderr, _(L"Invalid value '%s' for debug-stack-frames flag"), optarg); - exit(1); + error = true; + break; } break; } default: { // We assume getopt_long() has already emitted a diagnostic msg. - exit(1); + error = true; + break; } } } + if (error) return 1; argc -= optind; if (argc != 0) { diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 9b82ded00..f08eeecbf 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -95,8 +95,6 @@ static bool should_test_function(const char *func_name) { #define ESCAPE_TEST_LENGTH 100 /// The higest character number of character to try and escape. #define ESCAPE_TEST_CHAR 4000 -/// Number of laps to run performance testing loop. -#define LAPS 50 /// Number of encountered errors. static int err_count = 0; @@ -156,19 +154,22 @@ static int chdir_set_pwd(const char *path) { return ret; } +// The odd formulation of these macros is to avoid "multiple unary operator" warnings from oclint +// were we to use the more natural "if (!(e)) err(..." form. We have to do this because the rules +// for the C preprocessor make it practically impossible to embed a comment in the body of a macro. #define do_test(e) \ do { \ - if (!(e)) err(L"Test failed on line %lu: %s", __LINE__, #e); \ + if (e) { ; } else { err(L"Test failed on line %lu: %s", __LINE__, #e); } \ } while (0) -#define do_test_from(e, from_line) \ +#define do_test_from(e, from) \ do { \ - if (!(e)) err(L"Test failed on line %lu (from %lu): %s", __LINE__, from_line, #e); \ + if (e) { ; } else { err(L"Test failed on line %lu (from %lu): %s", __LINE__, from, #e); } \ } while (0) #define do_test1(e, msg) \ do { \ - if (!(e)) err(L"Test failed on line %lu: %ls", __LINE__, (msg)); \ + if (e) { ; } else { err(L"Test failed on line %lu: %ls", __LINE__, (msg)); } \ } while (0) /// Test sane escapes. @@ -201,7 +202,7 @@ static void test_unescape_sane() { err(L"Should not have been able to unescape \\U110000\n"); } if (is_wchar_ucs2()) { - // TODO: Make this work on MS Windows. + ; // TODO: Make this work on MS Windows. } else { if (!unescape_string(L"echo \\U10FFFF", &output, UNESCAPE_DEFAULT)) { err(L"Should have been able to unescape \\U10FFFF\n"); @@ -486,7 +487,7 @@ static parser_test_error_bits_t detect_argument_errors(const wcstring &src) { return PARSER_TEST_ERROR; } - assert(!tree.empty()); + assert(!tree.empty()); //!OCLINT(multiple unary operator) const parse_node_t *first_arg = tree.next_node_in_node_list(tree.at(0), symbol_argument, NULL); assert(first_arg != NULL); return parse_util_detect_errors_in_argument(*first_arg, first_arg->get_source(src)); @@ -495,7 +496,6 @@ static parser_test_error_bits_t detect_argument_errors(const wcstring &src) { /// Test the parser. static void test_parser() { say(L"Testing parser"); - parser_t parser; say(L"Testing block nesting"); if (!parse_util_detect_errors(L"if; end")) { @@ -629,7 +629,8 @@ static void test_parser() { #if 0 // This is disabled since it produces a long backtrace. We should find a way to either visually // compress the backtrace, or disable error spewing. - parser_t::principal_parser().eval(L"function recursive1 ; recursive2 ; end ; function recursive2 ; recursive1 ; end ; recursive1; ", io_chain_t(), TOP); + parser_t::principal_parser().eval(L"function recursive1 ; recursive2 ; end ; " + L"function recursive2 ; recursive1 ; end ; recursive1; ", io_chain_t(), TOP); #endif say(L"Testing empty function name"); @@ -842,8 +843,8 @@ static void test_utf82wchar(const char *src, size_t slen, const wchar_t *dst, si const unsigned char astral_mask = 0xF0; for (size_t i = 0; i < slen; i++) { if ((src[i] & astral_mask) == astral_mask) { - // Astral char. We expect this conversion to just fail. - res = 0; + // Astral char. We want this conversion to fail. + res = 0; //!OCLINT(parameter reassignment) break; } } @@ -890,8 +891,8 @@ static void test_wchar2utf8(const wchar_t *src, size_t slen, const char *dst, si const uint32_t astral_mask = 0xFFFF0000U; for (size_t i = 0; i < slen; i++) { if ((src[i] & astral_mask) != 0) { - /* astral char */ - res = 0; + // Astral char. We want this conversion to fail. + res = 0; //!OCLINT(parameter reassignment) break; } } @@ -1139,7 +1140,8 @@ static bool expand_test(const wchar_t *in, expand_flags_t flags, ...) { } if (!res) { - if ((arg = va_arg(va, wchar_t *)) != 0) { + arg = va_arg(va, wchar_t *); + if (arg) { wcstring msg = L"Expected ["; bool first = true; for (wcstring_list_t::const_iterator it = expected.begin(), end = expected.end(); @@ -2209,7 +2211,6 @@ static void test_history_matches(history_search_t &search, size_t matches, unsig size_t i; for (i = 0; i < matches; i++) { do_test(search.go_backwards()); - wcstring item = search.current_string(); } // do_test_from(!search.go_backwards(), from_line); bool result = search.go_backwards(); @@ -2405,7 +2406,7 @@ static void trigger_or_wait_for_notification(universal_notifier_t *notifier, universal_notifier_t::notifier_strategy_t strategy) { switch (strategy) { case universal_notifier_t::strategy_default: { - assert(0 && "strategy_default should be passed"); + DIE("strategy_default should be passed"); break; } case universal_notifier_t::strategy_shmem_polling: { @@ -3063,35 +3064,36 @@ static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *o out_joined_args->clear(); *out_deco = parse_statement_decoration_none; - bool result = false; parse_node_tree_t tree; - if (parse_tree_from_string(src, parse_flag_none, &tree, NULL)) { - // Get the statement. Should only have one. - const parse_node_tree_t::parse_node_list_t stmt_nodes = - tree.find_nodes(tree.at(0), symbol_plain_statement); - if (stmt_nodes.size() != 1) { - say(L"Unexpected number of statements (%lu) found in '%ls'", stmt_nodes.size(), - src.c_str()); - return false; - } - const parse_node_t &stmt = *stmt_nodes.at(0); - - // Return its decoration. - *out_deco = tree.decoration_for_plain_statement(stmt); - - // Return its command. - tree.command_for_plain_statement(stmt, src, out_cmd); - - // Return arguments separated by spaces. - const parse_node_tree_t::parse_node_list_t arg_nodes = - tree.find_nodes(stmt, symbol_argument); - for (size_t i = 0; i < arg_nodes.size(); i++) { - if (i > 0) out_joined_args->push_back(L' '); - out_joined_args->append(arg_nodes.at(i)->get_source(src)); - } - result = true; + if (!parse_tree_from_string(src, parse_flag_none, &tree, NULL)) { + return false; } - return result; + + // Get the statement. Should only have one. + const parse_node_tree_t::parse_node_list_t stmt_nodes = + tree.find_nodes(tree.at(0), symbol_plain_statement); + if (stmt_nodes.size() != 1) { + say(L"Unexpected number of statements (%lu) found in '%ls'", stmt_nodes.size(), + src.c_str()); + return false; + } + const parse_node_t &stmt = *stmt_nodes.at(0); + + // Return its decoration. + *out_deco = tree.decoration_for_plain_statement(stmt); + + // Return its command. + tree.command_for_plain_statement(stmt, src, out_cmd); + + // Return arguments separated by spaces. + const parse_node_tree_t::parse_node_list_t arg_nodes = + tree.find_nodes(stmt, symbol_argument); + for (size_t i = 0; i < arg_nodes.size(); i++) { + if (i > 0) out_joined_args->push_back(L' '); + out_joined_args->append(arg_nodes.at(i)->get_source(src)); + } + + return true; } // Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and @@ -3255,29 +3257,31 @@ static wcstring_list_t separate_by_format_specifiers(const wchar_t *format) { // Walk over the format specifier (if any). cursor = next_specifier; - if (*cursor == '%') { - cursor++; - // Flag - if (wcschr(L"#0- +'", *cursor)) cursor++; - // Minimum field width - while (iswdigit(*cursor)) cursor++; - // Precision - if (*cursor == L'.') { - cursor++; - while (iswdigit(*cursor)) cursor++; - } - // Length modifier - if (!wcsncmp(cursor, L"ll", 2) || !wcsncmp(cursor, L"hh", 2)) { - cursor += 2; - } else if (wcschr(L"hljtzqL", *cursor)) { - cursor++; - } - // The format specifier itself. We allow any character except NUL. - if (*cursor != L'\0') { - cursor += 1; - } - assert(cursor <= end); + if (*cursor != '%') { + continue; } + + cursor++; + // Flag + if (wcschr(L"#0- +'", *cursor)) cursor++; + // Minimum field width + while (iswdigit(*cursor)) cursor++; + // Precision + if (*cursor == L'.') { + cursor++; + while (iswdigit(*cursor)) cursor++; + } + // Length modifier + if (!wcsncmp(cursor, L"ll", 2) || !wcsncmp(cursor, L"hh", 2)) { + cursor += 2; + } else if (wcschr(L"hljtzqL", *cursor)) { + cursor++; + } + // The format specifier itself. We allow any character except NUL. + if (*cursor != L'\0') { + cursor += 1; + } + assert(cursor <= end); } return result; } diff --git a/src/function.cpp b/src/function.cpp index 34dbd85cd..3f303a468 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -161,7 +161,7 @@ void function_add(const function_data_t &data, const parser_t &parser, int defin UNUSED(parser); ASSERT_IS_MAIN_THREAD(); - CHECK(!data.name.empty(), ); + CHECK(!data.name.empty(), ); //!OCLINT(multiple unary operator) CHECK(data.definition, ); scoped_lock locker(functions_lock); @@ -272,9 +272,9 @@ bool function_get_desc(const wcstring &name, wcstring *out_desc) { if (out_desc && func && !func->description.empty()) { out_desc->assign(_(func->description.c_str())); return true; - } else { - return false; } + + return false; } void function_set_desc(const wcstring &name, const wcstring &desc) { @@ -311,8 +311,8 @@ wcstring_list_t function_get_names(int get_hidden) { const wcstring &name = iter->first; // Maybe skip hidden. - if (!get_hidden) { - if (name.empty() || name.at(0) == L'_') continue; + if (!get_hidden && (name.empty() || name.at(0) == L'_')) { + continue; } names.insert(name); } diff --git a/src/highlight.cpp b/src/highlight.cpp index 21b75bce3..85844dc1c 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -52,11 +52,15 @@ static const wchar_t *const highlight_var[] = { }; -/// Determine if the filesystem containing the given fd is case insensitive. +/// Determine if the filesystem containing the given fd is case insensitive for lookups regardless +/// of whether it preserves the case when saving a pathname. +/// +/// Returns: +/// false: the filesystem is not case insensitive +/// true: the file system is case insensitive typedef std::map case_sensitivity_cache_t; bool fs_is_case_insensitive(const wcstring &path, int fd, case_sensitivity_cache_t &case_sensitivity_cache) { - // If _PC_CASE_SENSITIVE is not defined, assume case sensitive. bool result = false; #ifdef _PC_CASE_SENSITIVE // Try the cache first. @@ -71,6 +75,11 @@ bool fs_is_case_insensitive(const wcstring &path, int fd, result = (ret == 0); case_sensitivity_cache[path] = result; } +#else + // Silence lint tools about the unused parameters. + UNUSED(path); + UNUSED(fd); + UNUSED(case_sensitivity_cache); #endif return result; } @@ -87,7 +96,7 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l path_flags_t flags) { ASSERT_IS_BACKGROUND_THREAD(); - const bool require_dir = !!(flags & PATH_REQUIRE_DIR); + const bool require_dir = static_cast(flags & PATH_REQUIRE_DIR); wcstring clean_potential_path_fragment; int has_magic = 0; bool result = false; @@ -122,73 +131,73 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l } } - if (!has_magic && !clean_potential_path_fragment.empty()) { - // Don't test the same path multiple times, which can happen if the path is absolute and the - // CDPATH contains multiple entries. - std::set checked_paths; + if (has_magic || clean_potential_path_fragment.empty()) { + return result; + } - // Keep a cache of which paths / filesystems are case sensitive. - case_sensitivity_cache_t case_sensitivity_cache; + // Don't test the same path multiple times, which can happen if the path is absolute and the + // CDPATH contains multiple entries. + std::set checked_paths; - for (size_t wd_idx = 0; wd_idx < directories.size() && !result; wd_idx++) { - const wcstring &wd = directories.at(wd_idx); + // Keep a cache of which paths / filesystems are case sensitive. + case_sensitivity_cache_t case_sensitivity_cache; - const wcstring abs_path = - path_apply_working_directory(clean_potential_path_fragment, wd); + for (size_t wd_idx = 0; wd_idx < directories.size() && !result; wd_idx++) { + const wcstring &wd = directories.at(wd_idx); - // Skip this if it's empty or we've already checked it. - if (abs_path.empty() || checked_paths.count(abs_path)) continue; - checked_paths.insert(abs_path); + const wcstring abs_path = path_apply_working_directory(clean_potential_path_fragment, wd); - // If we end with a slash, then it must be a directory. - bool must_be_full_dir = abs_path.at(abs_path.size() - 1) == L'/'; - if (must_be_full_dir) { - struct stat buf; - if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) { - result = true; - } - } else { - // We do not end with a slash; it does not have to be a directory. - DIR *dir = NULL; - const wcstring dir_name = wdirname(abs_path); - const wcstring filename_fragment = wbasename(abs_path); - if (dir_name == L"/" && filename_fragment == L"/") { - // cd ///.... No autosuggestion. - result = true; - } else if ((dir = wopendir(dir_name))) { - // Check if we're case insensitive. - const bool do_case_insensitive = - fs_is_case_insensitive(dir_name, dirfd(dir), case_sensitivity_cache); + // Skip this if it's empty or we've already checked it. + if (abs_path.empty() || checked_paths.count(abs_path)) continue; + checked_paths.insert(abs_path); - wcstring matched_file; + // If we end with a slash, then it must be a directory. + bool must_be_full_dir = abs_path.at(abs_path.size() - 1) == L'/'; + if (must_be_full_dir) { + struct stat buf; + if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) { + result = true; + } + } else { + // We do not end with a slash; it does not have to be a directory. + DIR *dir = NULL; + const wcstring dir_name = wdirname(abs_path); + const wcstring filename_fragment = wbasename(abs_path); + if (dir_name == L"/" && filename_fragment == L"/") { + // cd ///.... No autosuggestion. + result = true; + } else if ((dir = wopendir(dir_name))) { + // Check if we're case insensitive. + const bool do_case_insensitive = + fs_is_case_insensitive(dir_name, dirfd(dir), case_sensitivity_cache); - // We opened the dir_name; look for a string where the base name prefixes it - // Don't ask for the is_dir value unless we care, because it can cause extra - // filesystem access. - wcstring ent; - bool is_dir = false; - while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL)) { - // Maybe skip directories. - if (require_dir && !is_dir) { - continue; - } + wcstring matched_file; - if (string_prefixes_string(filename_fragment, ent) || - (do_case_insensitive && - string_prefixes_string_case_insensitive(filename_fragment, ent))) { - // We matched. - matched_file = ent; - break; - } + // We opened the dir_name; look for a string where the base name prefixes it Don't + // ask for the is_dir value unless we care, because it can cause extra filesystem + // access. + wcstring ent; + bool is_dir = false; + while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL)) { + // Maybe skip directories. + if (require_dir && !is_dir) { + continue; } - closedir(dir); - // We succeeded if we found a match. - result = !matched_file.empty(); + if (string_prefixes_string(filename_fragment, ent) || + (do_case_insensitive && + string_prefixes_string_case_insensitive(filename_fragment, ent))) { + matched_file = ent; // we matched + break; + } } + closedir(dir); + + result = !matched_file.empty(); // we succeeded if we found a match } } } + return result; } @@ -224,19 +233,16 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d bool plain_statement_get_expanded_command(const wcstring &src, const parse_node_tree_t &tree, const parse_node_t &plain_statement, wcstring *out_cmd) { assert(plain_statement.type == symbol_plain_statement); - bool result = false; - // Get the command. + // Get the command. Try expanding it. If we cannot, it's an error. wcstring cmd; - if (tree.command_for_plain_statement(plain_statement, src, &cmd)) { - // Try expanding it. If we cannot, it's an error. - if (expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS)) { - // Success, return the expanded string by reference. - out_cmd->swap(cmd); - result = true; - } + if (tree.command_for_plain_statement(plain_statement, src, &cmd) && + expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS)) { + // Success, return the expanded string by reference. + out_cmd->swap(cmd); + return true; } - return result; + return false; } rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background) { @@ -297,8 +303,6 @@ static bool has_expand_reserved(const wcstring &str) { // (as a copied node), if any. This is used by autosuggestions. static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expanded_command, parse_node_t *out_last_arg) { - bool result = false; - // Parse the buffer. parse_node_tree_t parse_tree; parse_tree_from_string(buff, @@ -308,21 +312,17 @@ static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expand // Find the last statement. const parse_node_t *last_statement = parse_tree.find_last_node_of_type(symbol_plain_statement, NULL); - if (last_statement != NULL) { - if (plain_statement_get_expanded_command(buff, parse_tree, *last_statement, - out_expanded_command)) { - // We got it. - result = true; - - // Find the last argument. If we don't get one, return an invalid node. - const parse_node_t *last_arg = - parse_tree.find_last_node_of_type(symbol_argument, last_statement); - if (last_arg != NULL) { - *out_last_arg = *last_arg; - } + if (last_statement != NULL && plain_statement_get_expanded_command( + buff, parse_tree, *last_statement, out_expanded_command)) { + // Find the last argument. If we don't get one, return an invalid node. + const parse_node_t *last_arg = + parse_tree.find_last_node_of_type(symbol_argument, last_statement); + if (last_arg != NULL) { + *out_last_arg = *last_arg; } + return true; } - return result; + return false; } bool autosuggest_validate_from_history(const history_item_t &item, @@ -346,41 +346,35 @@ bool autosuggest_validate_from_history(const history_item_t &item, handled = true; bool is_help = string_prefixes_string(dir, L"--help") || string_prefixes_string(dir, L"-h"); - if (is_help) { - suggestionOK = false; - } else { + if (!is_help) { wcstring path; bool can_cd = path_get_cdpath(dir, &path, working_directory.c_str(), vars); - if (!can_cd) { - suggestionOK = false; - } else if (paths_are_same_file(working_directory, path)) { - // Don't suggest the working directory as the path! - suggestionOK = false; - } else { + if (can_cd && !paths_are_same_file(working_directory, path)) { suggestionOK = true; } } } } - // If not handled specially, handle it here. - if (!handled) { - bool cmd_ok = false; + if (handled) { + return suggestionOK; + } - if (path_get_path(parsed_command, NULL)) { - cmd_ok = true; - } else if (builtin_exists(parsed_command) || - function_exists_no_autoload(parsed_command, vars)) { - cmd_ok = true; - } + // Not handled specially so handle it here. + bool cmd_ok = false; + if (path_get_path(parsed_command, NULL)) { + cmd_ok = true; + } else if (builtin_exists(parsed_command) || + function_exists_no_autoload(parsed_command, vars)) { + cmd_ok = true; + } - if (cmd_ok) { - const path_list_t &paths = item.get_required_paths(); - if (paths.empty()) { - suggestionOK = true; - } else { - suggestionOK = detector.paths_are_valid(paths); - } + if (cmd_ok) { + const path_list_t &paths = item.get_required_paths(); + if (paths.empty()) { + suggestionOK = true; + } else { + suggestionOK = detector.paths_are_valid(paths); } } @@ -587,6 +581,9 @@ static void color_argument_internal(const wcstring &buffstr, mode = e_double_quoted; break; } + default: { + break; // we ignore all other characters + } } } break; @@ -640,6 +637,9 @@ static void color_argument_internal(const wcstring &buffstr, in_pos -= 1; break; } + default: { + break; // we ignore all other characters + } } break; } @@ -1102,26 +1102,27 @@ const highlighter_t::color_array_t &highlighter_t::highlight() { // Color the command. const parse_node_t *cmd_node = parse_tree.get_child(node, 0, parse_token_type_string); - if (cmd_node != NULL && cmd_node->has_source()) { - bool is_valid_cmd = false; - if (!this->io_ok) { - // We cannot check if the command is invalid, so just assume it's valid. - is_valid_cmd = true; - } else { - // Check to see if the command is valid. - wcstring cmd(buff, cmd_node->source_start, cmd_node->source_length); - - // Try expanding it. If we cannot, it's an error. - bool expanded = expand_one( - cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS); - if (expanded && !has_expand_reserved(cmd)) { - is_valid_cmd = - command_is_valid(cmd, decoration, working_directory, vars); - } - } - this->color_node(*cmd_node, - is_valid_cmd ? highlight_spec_command : highlight_spec_error); + if (cmd_node == NULL || !cmd_node->has_source()) { + break; // not much as we can do without a node that has source text } + + bool is_valid_cmd = false; + if (!this->io_ok) { + // We cannot check if the command is invalid, so just assume it's valid. + is_valid_cmd = true; + } else { + // Check to see if the command is valid. + wcstring cmd(buff, cmd_node->source_start, cmd_node->source_length); + + // Try expanding it. If we cannot, it's an error. + bool expanded = expand_one( + cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS); + if (expanded && !has_expand_reserved(cmd)) { + is_valid_cmd = command_is_valid(cmd, decoration, working_directory, vars); + } + } + this->color_node(*cmd_node, + is_valid_cmd ? highlight_spec_command : highlight_spec_error); break; } case symbol_arguments_or_redirections_list: @@ -1150,32 +1151,29 @@ const highlighter_t::color_array_t &highlighter_t::highlight() { } } - if (this->io_ok && this->cursor_pos <= this->buff.size()) { - // If the cursor is over an argument, and that argument is a valid path, underline it. - for (parse_node_tree_t::const_iterator iter = parse_tree.begin(); iter != parse_tree.end(); - ++iter) { - const parse_node_t &node = *iter; + if (!this->io_ok || this->cursor_pos > this->buff.size()) { + return color_array; + } - // Must be an argument with source. - if (node.type != symbol_argument || !node.has_source()) continue; + // If the cursor is over an argument, and that argument is a valid path, underline it. + for (parse_node_tree_t::const_iterator iter = parse_tree.begin(); iter != parse_tree.end(); + ++iter) { + const parse_node_t &node = *iter; - // See if this node contains the cursor. We check <= source_length so that, when - // backspacing (and the cursor is just beyond the last token), we may still underline - // it. - if (this->cursor_pos >= node.source_start && - this->cursor_pos - node.source_start <= node.source_length) { - // See if this is a valid path. - if (node_is_potential_path(buff, node, working_directory)) { - // It is, underline it. - for (size_t i = node.source_start; i < node.source_start + node.source_length; - i++) { - // Don't color highlight_spec_error because it looks dorky. For example, - // trying to cd into a non-directory would show an underline and also red. - if (highlight_get_primary(this->color_array.at(i)) != - highlight_spec_error) { - this->color_array.at(i) |= highlight_modifier_valid_path; - } - } + // Must be an argument with source. + if (node.type != symbol_argument || !node.has_source()) continue; + + // See if this node contains the cursor. We check <= source_length so that, when backspacing + // (and the cursor is just beyond the last token), we may still underline it. + if (this->cursor_pos >= node.source_start && + this->cursor_pos - node.source_start <= node.source_length && + node_is_potential_path(buff, node, working_directory)) { + // It is, underline it. + for (size_t i = node.source_start; i < node.source_start + node.source_length; i++) { + // Don't color highlight_spec_error because it looks dorky. For example, + // trying to cd into a non-directory would show an underline and also red. + if (highlight_get_primary(this->color_array.at(i)) != highlight_spec_error) { + this->color_array.at(i) |= highlight_modifier_valid_path; } } } @@ -1257,6 +1255,9 @@ static void highlight_universal_internal(const wcstring &buffstr, } break; } + default: { + break; // we ignore all other characters + } } if ((*str == L'\0')) break; str++; diff --git a/src/history.cpp b/src/history.cpp index 7c33166ef..0f07f566f 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -769,7 +769,7 @@ void history_t::save_internal_unless_disabled() { } // This might be a good candidate for moving to a background thread. - time_profiler_t profiler(vacuum ? "save_internal vacuum" + time_profiler_t profiler(vacuum ? "save_internal vacuum" //!OCLINT(unused var) : "save_internal no vacuum"); //!OCLINT(side-effect) this->save_internal(vacuum); @@ -794,11 +794,10 @@ void history_t::add(const wcstring &str, history_identifier_t ident, bool pendin bool icompare_pred(wchar_t a, wchar_t b) { return std::tolower(a) == std::tolower(b); } bool icompare(wcstring const &a, wcstring const &b) { - if (a.length() == b.length()) { - return std::equal(b.begin(), b.end(), a.begin(), icompare_pred); - } else { + if (a.length() != b.length()) { return false; } + return std::equal(b.begin(), b.end(), a.begin(), icompare_pred); } // Remove matching history entries from our list of new items. This only supports literal, @@ -939,43 +938,45 @@ void history_t::populate_from_mmap(void) { /// if successful. Returns the mapped memory region by reference. bool history_t::map_file(const wcstring &name, const char **out_map_start, size_t *out_map_len, file_id_t *file_id) { - bool result = false; wcstring filename = history_filename(name, L""); - if (!filename.empty()) { - int fd = wopen_cloexec(filename, O_RDONLY); - if (fd >= 0) { - // Get the file ID if requested. - if (file_id != NULL) *file_id = file_id_for_fd(fd); + if (filename.empty()) { + return false; + } - // Take a read lock to guard against someone else appending. This is released when the - // file is closed (below). We will read the file after releasing the lock, but that's - // not a problem, because we never modify already written data. In short, the purpose of - // this lock is to ensure we don't see the file size change mid-update. - // - // We may fail to lock (e.g. on lockless NFS - see - // https://github.com/fish-shell/fish-shell/issues/685 ). In that case, we proceed as - // if it did not fail. The risk is that we may get an incomplete history item; this - // is unlikely because we only treat an item as valid if it has a terminating - // newline. - // - // Simulate a failing lock in chaos_mode. - if (!chaos_mode) history_file_lock(fd, F_RDLCK); - off_t len = lseek(fd, 0, SEEK_END); - if (len != (off_t)-1) { - size_t mmap_length = (size_t)len; - if (lseek(fd, 0, SEEK_SET) == 0) { - char *mmap_start; - if ((mmap_start = (char *)mmap(0, mmap_length, PROT_READ, MAP_PRIVATE, fd, - 0)) != MAP_FAILED) { - result = true; - *out_map_start = mmap_start; - *out_map_len = mmap_length; - } - } + int fd = wopen_cloexec(filename, O_RDONLY); + if (fd == -1) { + return false; + } + + bool result = false; + // Get the file ID if requested. + if (file_id != NULL) *file_id = file_id_for_fd(fd); + + // Take a read lock to guard against someone else appending. This is released when the file + // is closed (below). We will read the file after releasing the lock, but that's not a + // problem, because we never modify already written data. In short, the purpose of this lock + // is to ensure we don't see the file size change mid-update. + // + // We may fail to lock (e.g. on lockless NFS - see issue #685. In that case, we proceed as + // if it did not fail. The risk is that we may get an incomplete history item; this is + // unlikely because we only treat an item as valid if it has a terminating newline. + // + // Simulate a failing lock in chaos_mode. + if (!chaos_mode) history_file_lock(fd, F_RDLCK); + off_t len = lseek(fd, 0, SEEK_END); + if (len != (off_t)-1) { + size_t mmap_length = (size_t)len; + if (lseek(fd, 0, SEEK_SET) == 0) { + char *mmap_start; + if ((mmap_start = (char *)mmap(0, mmap_length, PROT_READ, MAP_PRIVATE, fd, + 0)) != MAP_FAILED) { + result = true; + *out_map_start = mmap_start; + *out_map_len = mmap_length; } - close(fd); } } + close(fd); return result; } diff --git a/src/input.cpp b/src/input.cpp index 1e6d9b726..971a6a9c8 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -56,8 +56,8 @@ struct input_mapping_t { input_mapping_t(const wcstring &s, const std::vector &c, const wcstring &m = DEFAULT_BIND_MODE, const wcstring &sm = DEFAULT_BIND_MODE) : seq(s), commands(c), mode(m), sets_mode(sm) { - static unsigned int s_last_input_mapping_specification_order = 0; - specification_order = ++s_last_input_mapping_specification_order; + static unsigned int s_last_input_map_spec_order = 0; + specification_order = ++s_last_input_map_spec_order; } }; @@ -223,14 +223,8 @@ void input_set_bind_mode(const wcstring &bm) { } /// Returns the arity of a given input function. -int input_function_arity(int function) { - switch (function) { - case R_FORWARD_JUMP: - case R_BACKWARD_JUMP: { - return 1; - } - default: { return 0; } - } +static int input_function_arity(int function) { + return (function == R_FORWARD_JUMP || function == R_BACKWARD_JUMP) ? 1 : 0; } /// Sets the return status of the most recently executed input function. @@ -378,7 +372,6 @@ int input_init() { } else { debug(0, _(L"Using fallback terminal type '%ls'"), DEFAULT_TERM); } - } else { } input_terminfo_init(); @@ -614,8 +607,9 @@ wint_t input_readch(bool allow_commands) { if (input_function_status) { return input_readch(); } - while ((c = input_common_readch(0)) && c >= R_MIN && c <= R_MAX) { - // do nothing + c = input_common_readch(0); + while (c >= R_MIN && c <= R_MAX) { + c = input_common_readch(0); } input_common_next_ch(c); return input_readch(); diff --git a/src/input_common.cpp b/src/input_common.cpp index b5525d4aa..a82e801ce 100644 --- a/src/input_common.cpp +++ b/src/input_common.cpp @@ -110,26 +110,17 @@ static wint_t readb() { res = select(fd_max + 1, &fdset, 0, 0, usecs_delay > 0 ? &tv : NULL); if (res == -1) { - switch (errno) { - case EINTR: - case EAGAIN: { - if (interrupt_handler) { - int res = interrupt_handler(); - if (res) { - return res; - } - if (has_lookahead()) { - return lookahead_pop(); - } - } + if (errno == EINTR || errno == EAGAIN) { + if (interrupt_handler) { + int res = interrupt_handler(); + if (res) return res; + if (has_lookahead()) return lookahead_pop(); + } - do_loop = true; - break; - } - default: { - // The terminal has been closed. Save and exit. - return R_EOF; - } + do_loop = true; + } else { + // The terminal has been closed. Save and exit. + return R_EOF; } } else { // Assume we loop unless we see a character in stdin. @@ -167,9 +158,6 @@ static wint_t readb() { return arr[0]; } -// Directly set the input timeout. -void set_wait_on_escape_ms(int ms) { wait_on_escape_ms = ms; } - // Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being // set. void update_wait_on_escape_ms() { diff --git a/src/input_common.h b/src/input_common.h index 5b39d41ff..88e75c0fd 100644 --- a/src/input_common.h +++ b/src/input_common.h @@ -86,9 +86,6 @@ void input_common_destroy(); /// Adjust the escape timeout. void update_wait_on_escape_ms(); -/// Set the escape timeout directly. -void set_wait_on_escape_ms(int ms); - /// Function used by input_readch to read bytes from stdin until enough bytes have been read to /// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously been /// read and then 'unread' using \c input_common_unreadch, that character is returned. If timed is diff --git a/src/io.cpp b/src/io.cpp index b274897fd..be33bcb12 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -165,35 +165,35 @@ void io_print(const io_chain_t &chain) /// created is marked close-on-exec. Returns -1 on failure (in which case the given fd is still /// closed). static int move_fd_to_unused(int fd, const io_chain_t &io_chain) { - int new_fd = fd; - if (fd >= 0 && io_chain.get_io_for_fd(fd).get() != NULL) { - // We have fd >= 0, and it's a conflict. dup it and recurse. Note that we recurse before - // anything is closed; this forces the kernel to give us a new one (or report fd - // exhaustion). - int tmp_fd; - do { - tmp_fd = dup(fd); - } while (tmp_fd < 0 && errno == EINTR); - - assert(tmp_fd != fd); - if (tmp_fd < 0) { - // Likely fd exhaustion. - new_fd = -1; - } else { - // Ok, we have a new candidate fd. Recurse. If we get a valid fd, either it's the same - // as what we gave it, or it's a new fd and what we gave it has been closed. If we get a - // negative value, the fd also has been closed. - set_cloexec(tmp_fd); - new_fd = move_fd_to_unused(tmp_fd, io_chain); - } - - // We're either returning a new fd or an error. In both cases, we promise to close the old - // one. - assert(new_fd != fd); - int saved_errno = errno; - exec_close(fd); - errno = saved_errno; + if (fd < 0 || io_chain.get_io_for_fd(fd).get() == NULL) { + return fd; } + + // We have fd >= 0, and it's a conflict. dup it and recurse. Note that we recurse before + // anything is closed; this forces the kernel to give us a new one (or report fd exhaustion). + int new_fd = fd; + int tmp_fd; + do { + tmp_fd = dup(fd); + } while (tmp_fd < 0 && errno == EINTR); + + assert(tmp_fd != fd); + if (tmp_fd < 0) { + // Likely fd exhaustion. + new_fd = -1; + } else { + // Ok, we have a new candidate fd. Recurse. If we get a valid fd, either it's the same as + // what we gave it, or it's a new fd and what we gave it has been closed. If we get a + // negative value, the fd also has been closed. + set_cloexec(tmp_fd); + new_fd = move_fd_to_unused(tmp_fd, io_chain); + } + + // We're either returning a new fd or an error. In both cases, we promise to close the old one. + assert(new_fd != fd); + int saved_errno = errno; + exec_close(fd); + errno = saved_errno; return new_fd; } diff --git a/src/iothread.cpp b/src/iothread.cpp index a83631ef8..e9279dae9 100644 --- a/src/iothread.cpp +++ b/src/iothread.cpp @@ -30,8 +30,6 @@ #define IO_SERVICE_MAIN_THREAD_REQUEST_QUEUE 99 #define IO_SERVICE_RESULT_QUEUE 100 -#define IOTHREAD_LOG if (0) - static void iothread_service_main_thread_requests(void); static void iothread_service_result_queue(); @@ -59,9 +57,9 @@ static pthread_mutex_t s_result_queue_lock; static std::queue s_result_queue; // "Do on main thread" support. -static pthread_mutex_t s_main_thread_performer_lock; // protects the main thread requests -static pthread_cond_t s_main_thread_performer_condition; // protects the main thread requests -static pthread_mutex_t s_main_thread_request_queue_lock; // protects the queue +static pthread_mutex_t s_main_thread_performer_lock; // protects the main thread requests +static pthread_cond_t s_main_thread_performer_cond; // protects the main thread requests +static pthread_mutex_t s_main_thread_request_q_lock; // protects the queue static std::queue s_main_thread_request_queue; // Notifying pipes. @@ -75,9 +73,9 @@ static void iothread_init(void) { // Initialize some locks. VOMIT_ON_FAILURE(pthread_mutex_init(&s_spawn_queue_lock, NULL)); VOMIT_ON_FAILURE(pthread_mutex_init(&s_result_queue_lock, NULL)); - VOMIT_ON_FAILURE(pthread_mutex_init(&s_main_thread_request_queue_lock, NULL)); + VOMIT_ON_FAILURE(pthread_mutex_init(&s_main_thread_request_q_lock, NULL)); VOMIT_ON_FAILURE(pthread_mutex_init(&s_main_thread_performer_lock, NULL)); - VOMIT_ON_FAILURE(pthread_cond_init(&s_main_thread_performer_condition, NULL)); + VOMIT_ON_FAILURE(pthread_cond_init(&s_main_thread_performer_cond, NULL)); // Initialize the completion pipes. int pipes[2] = {0, 0}; @@ -120,7 +118,7 @@ static void *iothread_worker(void *unused) { scoped_lock locker(s_spawn_queue_lock); struct SpawnRequest_t *req; while ((req = dequeue_spawn_request()) != NULL) { - IOTHREAD_LOG fprintf(stderr, "pthread %p dequeued %p\n", this_thread(), req); + debug(5, "pthread %p dequeued %p\n", this_thread(), req); // Unlock the queue while we execute the request. locker.unlock(); @@ -153,7 +151,7 @@ static void *iothread_worker(void *unused) { assert(s_active_thread_count > 0); s_active_thread_count -= 1; - IOTHREAD_LOG fprintf(stderr, "pthread %p exiting\n", this_thread()); + debug(5, "pthread %p exiting\n", this_thread()); // We're done. return NULL; } @@ -175,7 +173,7 @@ static void iothread_spawn() { // We will never join this thread. VOMIT_ON_FAILURE(pthread_detach(thread)); - IOTHREAD_LOG fprintf(stderr, "pthread %p spawned\n", (void *)(intptr_t)thread); + debug(5, "pthread %p spawned\n", (void *)(intptr_t)thread); // Restore our sigmask. VOMIT_ON_FAILURE(pthread_sigmask(SIG_SETMASK, &saved_set, NULL)); } @@ -222,21 +220,15 @@ int iothread_port(void) { void iothread_service_completion(void) { ASSERT_IS_MAIN_THREAD(); - char wakeup_byte = 0; + char wakeup_byte; + VOMIT_ON_FAILURE(1 != read_loop(iothread_port(), &wakeup_byte, sizeof wakeup_byte)); - switch (wakeup_byte) { - case IO_SERVICE_MAIN_THREAD_REQUEST_QUEUE: { - iothread_service_main_thread_requests(); - break; - } - case IO_SERVICE_RESULT_QUEUE: { - iothread_service_result_queue(); - break; - } - default: { - fprintf(stderr, "Unknown wakeup byte %02x in %s\n", wakeup_byte, __FUNCTION__); - break; - } + if (wakeup_byte == IO_SERVICE_MAIN_THREAD_REQUEST_QUEUE) { + iothread_service_main_thread_requests(); + } else if (wakeup_byte == IO_SERVICE_RESULT_QUEUE) { + iothread_service_result_queue(); + } else { + fprintf(stderr, "Unknown wakeup byte %02x in %s\n", wakeup_byte, __FUNCTION__); } } @@ -295,7 +287,7 @@ static void iothread_service_main_thread_requests(void) { // Move the queue to a local variable. std::queue request_queue; { - scoped_lock queue_lock(s_main_thread_request_queue_lock); + scoped_lock queue_lock(s_main_thread_request_q_lock); std::swap(request_queue, s_main_thread_request_queue); } @@ -319,7 +311,7 @@ static void iothread_service_main_thread_requests(void) { // Because the waiting thread performs step 1 under the lock, if we take the lock, we avoid // posting before the waiting thread is waiting. scoped_lock broadcast_lock(s_main_thread_performer_lock); - VOMIT_ON_FAILURE(pthread_cond_broadcast(&s_main_thread_performer_condition)); + VOMIT_ON_FAILURE(pthread_cond_broadcast(&s_main_thread_performer_cond)); } } @@ -359,7 +351,7 @@ int iothread_perform_on_main_base(int (*handler)(void *), void *context) { // Append it. Do not delete the nested scope as it is crucial to the proper functioning of this // code by virtue of the lock management. { - scoped_lock queue_lock(s_main_thread_request_queue_lock); + scoped_lock queue_lock(s_main_thread_request_q_lock); s_main_thread_request_queue.push(&req); } @@ -373,7 +365,7 @@ int iothread_perform_on_main_base(int (*handler)(void *), void *context) { // It would be nice to support checking for cancellation here, but the clients need a // deterministic way to clean up to avoid leaks VOMIT_ON_FAILURE( - pthread_cond_wait(&s_main_thread_performer_condition, &s_main_thread_performer_lock)); + pthread_cond_wait(&s_main_thread_performer_cond, &s_main_thread_performer_lock)); } // Ok, the request must now be done. diff --git a/src/output.cpp b/src/output.cpp index 2533a0dd1..707a922aa 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -32,7 +32,7 @@ static int writeb_internal(char c); /// The function used for output. -static int (*out)(char c) = writeb_internal; +static int (*out)(char c) = writeb_internal; //!OCLINT(unused param) /// Whether term256 and term24bit are supported. static color_support_t color_support = 0; @@ -66,33 +66,32 @@ static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) { if (term_supports_color_natively(idx)) { // Use tparm to emit color escape. writembs(tparm(todo, idx)); - return true; - } else { - // We are attempting to bypass the term here. Generate the ANSI escape sequence ourself. - char buff[16] = ""; - if (idx < 16) { - // this allows the non-bright color to happen instead of no color working at all when - // a bright is attempted when only colors 0-7 are supported. - // TODO: enter bold mode in builtin_set_color in the same circumstance- doing that - // combined - // with what we do here, will make the brights actually work for virtual - // consoles/ancient emulators. - if (max_colors == 8 && idx > 8) idx -= 8; - - snprintf(buff, sizeof buff, "\x1b[%dm", ((idx > 7) ? 82 : 30) + idx + !is_fg * 10); - } else { - snprintf(buff, sizeof buff, "\x1b[%d;5;%dm", is_fg ? 38 : 48, idx); - } - - int (*writer)(char) = output_get_writer(); - if (writer) { - for (size_t i = 0; buff[i]; i++) { - writer(buff[i]); - } - } - return true; } + + // We are attempting to bypass the term here. Generate the ANSI escape sequence ourself. + char buff[16] = ""; + if (idx < 16) { + // this allows the non-bright color to happen instead of no color working at all when a + // bright is attempted when only colors 0-7 are supported. + // + // TODO: enter bold mode in builtin_set_color in the same circumstance- doing that combined + // with what we do here, will make the brights actually work for virtual consoles/ancient + // emulators. + if (max_colors == 8 && idx > 8) idx -= 8; + snprintf(buff, sizeof buff, "\x1b[%dm", ((idx > 7) ? 82 : 30) + idx + !is_fg * 10); + } else { + snprintf(buff, sizeof buff, "\x1b[%d;5;%dm", is_fg ? 38 : 48, idx); + } + + int (*writer)(char) = output_get_writer(); + if (writer) { + for (size_t i = 0; buff[i]; i++) { + writer(buff[i]); + } + } + + return true; } static bool write_foreground_color(unsigned char idx) { @@ -115,26 +114,28 @@ static bool write_background_color(unsigned char idx) { // Exported for builtin_set_color's usage only. bool write_color(rgb_color_t color, bool is_fg) { - bool supports_term24bit = !!(output_get_color_support() & color_support_term24bit); + bool supports_term24bit = + static_cast(output_get_color_support() & color_support_term24bit); if (!supports_term24bit || !color.is_rgb()) { // Indexed or non-24 bit color. unsigned char idx = index_for_color(color); return (is_fg ? write_foreground_color : write_background_color)(idx); - } else { - // 24 bit! No tparm here, just ANSI escape sequences. - // Foreground: ^[38;2;;;m - // Background: ^[48;2;;;m - color24_t rgb = color.to_color24(); - char buff[128]; - snprintf(buff, sizeof buff, "\x1b[%d;2;%u;%u;%um", is_fg ? 38 : 48, rgb.rgb[0], rgb.rgb[1], - rgb.rgb[2]); - int (*writer)(char) = output_get_writer(); - if (writer) { - for (size_t i = 0; buff[i]; i++) { - writer(buff[i]); - } + } + + // 24 bit! No tparm here, just ANSI escape sequences. + // Foreground: ^[38;2;;;m + // Background: ^[48;2;;;m + color24_t rgb = color.to_color24(); + char buff[128]; + snprintf(buff, sizeof buff, "\x1b[%d;2;%u;%u;%um", is_fg ? 38 : 48, rgb.rgb[0], rgb.rgb[1], + rgb.rgb[2]); + int (*writer)(char) = output_get_writer(); + if (writer) { + for (size_t i = 0; buff[i]; i++) { + writer(buff[i]); } } + return true; } @@ -271,12 +272,8 @@ void set_color(rgb_color_t c, rgb_color_t c2) { } // Lastly, we set bold mode and underline mode correctly. - if ((enter_bold_mode != 0) && (strlen(enter_bold_mode) > 0) && !bg_set) { - if (is_bold && !was_bold) { - if (enter_bold_mode) { - writembs(tparm(enter_bold_mode)); - } - } + if (is_bold && !was_bold && enter_bold_mode && strlen(enter_bold_mode) > 0 && !bg_set) { + writembs(tparm(enter_bold_mode)); was_bold = is_bold; } @@ -312,13 +309,10 @@ int writech(wint_t ch) { if (ch >= ENCODE_DIRECT_BASE && ch < ENCODE_DIRECT_BASE + 256) { buff[0] = ch - ENCODE_DIRECT_BASE; len = 1; - } else if (MB_CUR_MAX == 1) // single-byte locale (C/POSIX/ISO-8859) - { + } else if (MB_CUR_MAX == 1) { + // single-byte locale (C/POSIX/ISO-8859) // If `wc` contains a wide character we emit a question-mark. - if (ch & ~0xFF) { - ch = '?'; - } - buff[0] = ch; + buff[0] = ch & ~0xFF ? '?' : ch; len = 1; } else { mbstate_t state = {}; @@ -389,7 +383,7 @@ rgb_color_t best_color(const std::vector &candidates, color_support } // If we have both RGB and named colors, then prefer rgb if term256 is supported. rgb_color_t result = rgb_color_t::none(); - bool has_term256 = !!(support & color_support_term256); + bool has_term256 = static_cast(support & color_support_term256); if ((!first_rgb.is_none() && has_term256) || first_named.is_none()) { result = first_rgb; } else { @@ -446,7 +440,8 @@ rgb_color_t parse_color(const wcstring &val, bool is_background) { #if 0 wcstring desc = result.description(); - printf("Parsed %ls from %ls (%s)\n", desc.c_str(), val.c_str(), is_background ? "background" : "foreground"); + printf("Parsed %ls from %ls (%s)\n", desc.c_str(), val.c_str(), + is_background ? "background" : "foreground"); #endif return result; diff --git a/src/pager.cpp b/src/pager.cpp index b6663a24b..4a952c205 100644 --- a/src/pager.cpp +++ b/src/pager.cpp @@ -84,16 +84,15 @@ static int print_max(const wcstring &str, highlight_spec_t color, int max, bool /// Print the specified item using at the specified amount of space. line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, - size_t column, size_t width, bool secondary, bool selected, + size_t column, int width, bool secondary, bool selected, page_rendering_t *rendering) const { UNUSED(column); UNUSED(row); UNUSED(rendering); - size_t comp_width = 0, desc_width = 0; - size_t written = 0; + int comp_width, desc_width; line_t line_data; - if (c->pref_width <= (size_t)width) { + if (c->pref_width <= width) { // The entry fits, we give it as much space as it wants. comp_width = c->comp_width; desc_width = c->desc_width; @@ -102,8 +101,8 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s // the space to the completion, and whatever is left to the description. int desc_all = c->desc_width ? c->desc_width + 4 : 0; - comp_width = maxi(mini(c->comp_width, 2 * (width - 4) / 3), width - desc_all); - if (c->desc_width) desc_width = width - comp_width - 4; + comp_width = maxi(mini((int)c->comp_width, 2 * (width - 4) / 3), width - desc_all); + desc_width = c->desc_width ? width - 4 - comp_width : 0; } int bg_color = secondary ? highlight_spec_pager_secondary : highlight_spec_normal; @@ -111,6 +110,7 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s bg_color = highlight_spec_search_match; } + int written = 0; for (size_t i = 0; i < c->comp.size(); i++) { const wcstring &comp = c->comp.at(i); @@ -214,8 +214,7 @@ static void mangle_1_completion_description(wcstring *str) { str->at(trailing++) = wc; } else if (!was_space) { // initial space in a run str->at(trailing++) = L' '; - } else { // non-initial space in a run, do nothing - } + } // else non-initial space in a run, do nothing was_space = is_space; } @@ -365,7 +364,7 @@ void pager_t::set_term_size(int w, int h) { } /// Try to print the list of completions l with the prefix prefix using cols as the number of -/// columns. Return true if the completion list was printed, false if the terminal is to narrow for +/// columns. Return true if the completion list was printed, false if the terminal is too narrow for /// the specified number of columns. Always succeeds if cols is 1. bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const { @@ -446,75 +445,78 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co print = true; } - if (print) { - // Determine the starting and stop row. - size_t start_row = 0, stop_row = 0; - if (row_count <= term_height) { - // Easy, we can show everything. - start_row = 0; - stop_row = row_count; - } else { - // We can only show part of the full list. Determine which part based on the - // suggested_start_row. - assert(row_count > term_height); - size_t last_starting_row = row_count - term_height; - start_row = mini(suggested_start_row, last_starting_row); - stop_row = start_row + term_height; - assert(start_row >= 0 && start_row <= last_starting_row); - } - - assert(stop_row >= start_row); - assert(stop_row <= row_count); - assert(stop_row - start_row <= term_height); - completion_print(cols, width, start_row, stop_row, prefix, lst, rendering); - - // Ellipsis helper string. Either empty or containing the ellipsis char. - const wchar_t ellipsis_string[] = {ellipsis_char == L'\x2026' ? L'\x2026' : L'\0', L'\0'}; - - // Add the progress line. It's a "more to disclose" line if necessary, or a row listing if - // it's scrollable; otherwise ignore it. - wcstring progress_text; - if (rendering->remaining_to_disclose == 1) { - // I don't expect this case to ever happen. - progress_text = format_string(_(L"%lsand 1 more row"), ellipsis_string); - } else if (rendering->remaining_to_disclose > 1) { - progress_text = format_string(_(L"%lsand %lu more rows"), ellipsis_string, - (unsigned long)rendering->remaining_to_disclose); - } else if (start_row > 0 || stop_row < row_count) { - // We have a scrollable interface. The +1 here is because we are zero indexed, but want - // to present things as 1-indexed. We do not add 1 to stop_row or row_count because - // these are the "past the last value". - progress_text = - format_string(_(L"rows %lu to %lu of %lu"), start_row + 1, stop_row, row_count); - } else if (completion_infos.empty() && !unfiltered_completion_infos.empty()) { - // Everything is filtered. - progress_text = _(L"(no matches)"); - } - - if (!progress_text.empty()) { - line_t &line = rendering->screen_data.add_line(); - print_max(progress_text, highlight_spec_pager_progress | - highlight_make_background(highlight_spec_pager_progress), - term_width, true /* has_more */, &line); - } - - if (search_field_shown) { - // Add the search field. - wcstring search_field_text = search_field_line.text; - // Append spaces to make it at least the required width. - if (search_field_text.size() < PAGER_SEARCH_FIELD_WIDTH) { - search_field_text.append(PAGER_SEARCH_FIELD_WIDTH - search_field_text.size(), L' '); - } - line_t *search_field = &rendering->screen_data.insert_line_at_index(0); - - // We limit the width to term_width - 1. - int search_field_written = print_max(SEARCH_FIELD_PROMPT, highlight_spec_normal, - term_width - 1, false, search_field); - print_max(search_field_text, highlight_modifier_force_underline, - term_width - search_field_written - 1, false, search_field); - } + if (!print) { + return false; // no need to continue } - return print; + + // Determine the starting and stop row. + size_t start_row = 0, stop_row = 0; + if (row_count <= term_height) { + // Easy, we can show everything. + start_row = 0; + stop_row = row_count; + } else { + // We can only show part of the full list. Determine which part based on the + // suggested_start_row. + assert(row_count > term_height); + size_t last_starting_row = row_count - term_height; + start_row = mini(suggested_start_row, last_starting_row); + stop_row = start_row + term_height; + assert(start_row >= 0 && start_row <= last_starting_row); + } + + assert(stop_row >= start_row); + assert(stop_row <= row_count); + assert(stop_row - start_row <= term_height); + completion_print(cols, width, start_row, stop_row, prefix, lst, rendering); + + // Ellipsis helper string. Either empty or containing the ellipsis char. + const wchar_t ellipsis_string[] = {ellipsis_char == L'\x2026' ? L'\x2026' : L'\0', L'\0'}; + + // Add the progress line. It's a "more to disclose" line if necessary, or a row listing if + // it's scrollable; otherwise ignore it. + wcstring progress_text; + if (rendering->remaining_to_disclose == 1) { + // I don't expect this case to ever happen. + progress_text = format_string(_(L"%lsand 1 more row"), ellipsis_string); + } else if (rendering->remaining_to_disclose > 1) { + progress_text = format_string(_(L"%lsand %lu more rows"), ellipsis_string, + (unsigned long)rendering->remaining_to_disclose); + } else if (start_row > 0 || stop_row < row_count) { + // We have a scrollable interface. The +1 here is because we are zero indexed, but want + // to present things as 1-indexed. We do not add 1 to stop_row or row_count because + // these are the "past the last value". + progress_text = + format_string(_(L"rows %lu to %lu of %lu"), start_row + 1, stop_row, row_count); + } else if (completion_infos.empty() && !unfiltered_completion_infos.empty()) { + // Everything is filtered. + progress_text = _(L"(no matches)"); + } + + if (!progress_text.empty()) { + line_t &line = rendering->screen_data.add_line(); + print_max(progress_text, highlight_spec_pager_progress | + highlight_make_background(highlight_spec_pager_progress), + term_width, true /* has_more */, &line); + } + + if (search_field_shown) { + // Add the search field. + wcstring search_field_text = search_field_line.text; + // Append spaces to make it at least the required width. + if (search_field_text.size() < PAGER_SEARCH_FIELD_WIDTH) { + search_field_text.append(PAGER_SEARCH_FIELD_WIDTH - search_field_text.size(), L' '); + } + line_t *search_field = &rendering->screen_data.insert_line_at_index(0); + + // We limit the width to term_width - 1. + int search_field_written = print_max(SEARCH_FIELD_PROMPT, highlight_spec_normal, + term_width - 1, false, search_field); + print_max(search_field_text, highlight_modifier_force_underline, + term_width - search_field_written - 1, false, search_field); + } + + return true; } page_rendering_t pager_t::render() const { @@ -611,10 +613,6 @@ bool pager_t::select_next_completion_in_direction(selection_direction_t directio // These do nothing. return false; } - default: { - assert(0 && "Unhandled selection_direction_t constant"); - abort(); - } } } @@ -636,7 +634,7 @@ bool pager_t::select_next_completion_in_direction(selection_direction_t directio new_selected_completion_idx = selected_completion_idx - 1; } } else { - assert(0 && "Unknown non-cardinal direction"); + DIE("unknown non-cardinal direction"); } } else { // Cardinal directions. We have a completion index; we wish to compute its row and column. @@ -646,10 +644,11 @@ bool pager_t::select_next_completion_in_direction(selection_direction_t directio switch (direction) { case direction_page_north: { - if (current_row > page_height) + if (current_row > page_height) { current_row = current_row - page_height; - else + } else { current_row = 0; + } break; } case direction_north: { @@ -708,7 +707,7 @@ bool pager_t::select_next_completion_in_direction(selection_direction_t directio break; } default: { - assert(0 && "Unknown cardinal direction"); + DIE("unknown cardinal direction"); break; } } @@ -717,42 +716,40 @@ bool pager_t::select_next_completion_in_direction(selection_direction_t directio new_selected_completion_idx = current_col * rendering.rows + current_row; } - if (new_selected_completion_idx != selected_completion_idx) { - selected_completion_idx = new_selected_completion_idx; - - // Update suggested_row_start to ensure the selection is visible. suggested_row_start * - // rendering.cols is the first suggested visible completion; add the visible completion - // count to that to get the last one. - size_t visible_row_count = rendering.row_end - rendering.row_start; - - if (visible_row_count > 0 && selected_completion_idx != PAGER_SELECTION_NONE) // paranoia - { - size_t row_containing_selection = this->get_selected_row(rendering); - - // Ensure our suggested row start is not past the selected row. - if (suggested_row_start > row_containing_selection) { - suggested_row_start = row_containing_selection; - } - - // Ensure our suggested row start is not too early before it. - if (suggested_row_start + visible_row_count <= row_containing_selection) { - // The user moved south past the bottom completion. - if (!fully_disclosed && rendering.remaining_to_disclose > 0) { - fully_disclosed = true; // perform disclosure - } else { - // Scroll - suggested_row_start = row_containing_selection - visible_row_count + 1; - // Ensure fully_disclosed is set. I think we can hit this case if the user - // resizes the window - we don't want to drop back to the disclosed style. - fully_disclosed = true; - } - } - } - - return true; - } else { + if (selected_completion_idx == new_selected_completion_idx) { return false; } + selected_completion_idx = new_selected_completion_idx; + + // Update suggested_row_start to ensure the selection is visible. suggested_row_start * + // rendering.cols is the first suggested visible completion; add the visible completion + // count to that to get the last one. + size_t visible_row_count = rendering.row_end - rendering.row_start; + if (visible_row_count == 0 || selected_completion_idx == PAGER_SELECTION_NONE) { + return true; // this should never happen but be paranoid + } + + // Ensure our suggested row start is not past the selected row. + size_t row_containing_selection = this->get_selected_row(rendering); + if (suggested_row_start > row_containing_selection) { + suggested_row_start = row_containing_selection; + } + + // Ensure our suggested row start is not too early before it. + if (suggested_row_start + visible_row_count <= row_containing_selection) { + // The user moved south past the bottom completion. + if (!fully_disclosed && rendering.remaining_to_disclose > 0) { + fully_disclosed = true; // perform disclosure + } else { + // Scroll + suggested_row_start = row_containing_selection - visible_row_count + 1; + // Ensure fully_disclosed is set. I think we can hit this case if the user + // resizes the window - we don't want to drop back to the disclosed style. + fully_disclosed = true; + } + } + + return true; } size_t pager_t::visual_selected_completion_index(size_t rows, size_t cols) const { diff --git a/src/pager.h b/src/pager.h index 3f7bfce0d..1376e51e7 100644 --- a/src/pager.h +++ b/src/pager.h @@ -114,7 +114,7 @@ class pager_t { const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, - size_t width, bool secondary, bool selected, + int width, bool secondary, bool selected, page_rendering_t *rendering) const; public: diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index a4a87c99e..b18514ef2 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -372,38 +372,41 @@ parse_execution_result_t parse_execution_context_t::run_function_statement( wcstring_list_t argument_list; parse_execution_result_t result = this->determine_arguments(header, &argument_list, failglob); - if (result == parse_execution_success) { - // The function definition extends from the end of the header to the function end. It's not - // just the range of the contents because that loses comments - see issue #1710. - assert(block_end_command.has_source()); - size_t contents_start = header.source_start + header.source_length; - size_t contents_end = - block_end_command.source_start; // 1 past the last character in the function definition - assert(contents_end >= contents_start); - - // Swallow whitespace at both ends. - while (contents_start < contents_end && iswspace(this->src.at(contents_start))) { - contents_start++; - } - while (contents_start < contents_end && iswspace(this->src.at(contents_end - 1))) { - contents_end--; - } - - assert(contents_end >= contents_start); - const wcstring contents_str = - wcstring(this->src, contents_start, contents_end - contents_start); - int definition_line_offset = this->line_offset_of_character_at_offset(contents_start); - wcstring error_str; - io_streams_t streams; - int err = builtin_function(*parser, streams, argument_list, contents_str, - definition_line_offset, &error_str); - proc_set_last_status(err); - - if (!error_str.empty()) { - this->report_error(header, L"%ls", error_str.c_str()); - result = parse_execution_errored; - } + if (result != parse_execution_success) { + return result; } + + // The function definition extends from the end of the header to the function end. It's not + // just the range of the contents because that loses comments - see issue #1710. + assert(block_end_command.has_source()); + size_t contents_start = header.source_start + header.source_length; + size_t contents_end = + block_end_command.source_start; // 1 past the last character in the function definition + assert(contents_end >= contents_start); + + // Swallow whitespace at both ends. + while (contents_start < contents_end && iswspace(this->src.at(contents_start))) { + contents_start++; + } + while (contents_start < contents_end && iswspace(this->src.at(contents_end - 1))) { + contents_end--; + } + + assert(contents_end >= contents_start); + const wcstring contents_str = + wcstring(this->src, contents_start, contents_end - contents_start); + int definition_line_offset = this->line_offset_of_character_at_offset(contents_start); + wcstring error_str; + io_streams_t streams; + int err = builtin_function(*parser, streams, argument_list, contents_str, + definition_line_offset, &error_str); + proc_set_last_status(err); + + if (!error_str.empty()) { + this->report_error(header, L"%ls", error_str.c_str()); + result = parse_execution_errored; + } + return result; } @@ -534,6 +537,10 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( case EXPAND_OK: { break; } + default: { + DIE("unexpected expand_string() return value"); + break; + } } if (result == parse_execution_success && switch_values_expanded.size() != 1) { @@ -542,66 +549,67 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( switch_values_expanded.size()); } - if (result == parse_execution_success) { - const wcstring &switch_value_expanded = switch_values_expanded.at(0).completion; + if (result != parse_execution_success) { + return result; + } - switch_block_t *sb = new switch_block_t(); - parser->push_block(sb); + const wcstring &switch_value_expanded = switch_values_expanded.at(0).completion; - // Expand case statements. - const parse_node_t *case_item_list = get_child(statement, 3, symbol_case_item_list); + switch_block_t *sb = new switch_block_t(); + parser->push_block(sb); - // Loop while we don't have a match but do have more of the list. - const parse_node_t *matching_case_item = NULL; - while (matching_case_item == NULL && case_item_list != NULL) { - if (should_cancel_execution(sb)) { - result = parse_execution_cancelled; - break; - } + // Expand case statements. + const parse_node_t *case_item_list = get_child(statement, 3, symbol_case_item_list); - // Get the next item and the remainder of the list. - const parse_node_t *case_item = - tree.next_node_in_node_list(*case_item_list, symbol_case_item, &case_item_list); - if (case_item == NULL) { - // No more items. - break; - } + // Loop while we don't have a match but do have more of the list. + const parse_node_t *matching_case_item = NULL; + while (matching_case_item == NULL && case_item_list != NULL) { + if (should_cancel_execution(sb)) { + result = parse_execution_cancelled; + break; + } - // Pull out the argument list. - const parse_node_t &arg_list = *get_child(*case_item, 1, symbol_argument_list); + // Get the next item and the remainder of the list. + const parse_node_t *case_item = + tree.next_node_in_node_list(*case_item_list, symbol_case_item, &case_item_list); + if (case_item == NULL) { + // No more items. + break; + } - // Expand arguments. A case item list may have a wildcard that fails to expand to - // anything. We also report case errors, but don't stop execution; i.e. a case item that - // contains an unexpandable process will report and then fail to match. - wcstring_list_t case_args; - parse_execution_result_t case_result = - this->determine_arguments(arg_list, &case_args, failglob); - if (case_result == parse_execution_success) { - for (size_t i = 0; i < case_args.size(); i++) { - const wcstring &arg = case_args.at(i); + // Pull out the argument list. + const parse_node_t &arg_list = *get_child(*case_item, 1, symbol_argument_list); - // Unescape wildcards so they can be expanded again. - wcstring unescaped_arg = parse_util_unescape_wildcards(arg); - bool match = wildcard_match(switch_value_expanded, unescaped_arg); + // Expand arguments. A case item list may have a wildcard that fails to expand to + // anything. We also report case errors, but don't stop execution; i.e. a case item that + // contains an unexpandable process will report and then fail to match. + wcstring_list_t case_args; + parse_execution_result_t case_result = + this->determine_arguments(arg_list, &case_args, failglob); + if (case_result == parse_execution_success) { + for (size_t i = 0; i < case_args.size(); i++) { + const wcstring &arg = case_args.at(i); - // If this matched, we're done. - if (match) { - matching_case_item = case_item; - break; - } + // Unescape wildcards so they can be expanded again. + wcstring unescaped_arg = parse_util_unescape_wildcards(arg); + bool match = wildcard_match(switch_value_expanded, unescaped_arg); + + // If this matched, we're done. + if (match) { + matching_case_item = case_item; + break; } } } - - if (result == parse_execution_success && matching_case_item != NULL) { - // Success, evaluate the job list. - const parse_node_t *job_list = get_child(*matching_case_item, 3, symbol_job_list); - result = this->run_job_list(*job_list, sb); - } - - parser->pop_block(sb); } + if (result == parse_execution_success && matching_case_item != NULL) { + // Success, evaluate the job list. + const parse_node_t *job_list = get_child(*matching_case_item, 3, symbol_job_list); + result = this->run_job_list(*job_list, sb); + } + + parser->pop_block(sb); return result; } @@ -945,6 +953,10 @@ parse_execution_result_t parse_execution_context_t::determine_arguments( case EXPAND_OK: { break; } + default: { + DIE("unexpected expand_string() return value"); + break; + } } // Now copy over any expanded arguments. Do it using swap() to avoid extra allocations; this @@ -1184,7 +1196,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node( // Return what happened. if (result == parse_execution_success) { // Link up the processes. - assert(!processes.empty()); + assert(!processes.empty()); //!OCLINT(multiple unary operator) j->first_process = processes.at(0); for (size_t i = 1; i < processes.size(); i++) { processes.at(i - 1)->next = processes.at(i); @@ -1208,12 +1220,10 @@ parse_execution_result_t parse_execution_context_t::run_1_job(const parse_node_t // Get terminal modes. struct termios tmodes = {}; - if (shell_is_interactive()) { - if (tcgetattr(STDIN_FILENO, &tmodes)) { - // Need real error handling here. - wperror(L"tcgetattr"); - return parse_execution_errored; - } + if (shell_is_interactive() && tcgetattr(STDIN_FILENO, &tmodes)) { + // Need real error handling here. + wperror(L"tcgetattr"); + return parse_execution_errored; } // Increment the eval_level for the duration of this command. @@ -1370,7 +1380,7 @@ parse_execution_result_t parse_execution_context_t::run_job_list(const parse_nod parse_execution_result_t parse_execution_context_t::eval_node_at_offset( node_offset_t offset, const block_t *associated_block, const io_chain_t &io) { // Don't ever expect to have an empty tree if this is called. - assert(!tree.empty()); + assert(!tree.empty()); //!OCLINT(multiple unary operator) assert(offset < tree.size()); // Apply this block IO for the duration of this function. diff --git a/src/parse_productions.cpp b/src/parse_productions.cpp index 3c1fcbcb2..b3d976905 100644 --- a/src/parse_productions.cpp +++ b/src/parse_productions.cpp @@ -428,19 +428,18 @@ RESOLVE_ONLY(end_command) = {KEYWORD(parse_keyword_end)}; case (symbol_##sym): \ resolver = resolve_##sym; \ break; + const production_t *parse_productions::production_for_token(parse_token_type_t node_type, const parse_token_t &input1, const parse_token_t &input2, parse_node_tag_t *out_tag) { - const bool log_it = false; - if (log_it) { - fprintf(stderr, "Resolving production for %ls with input token <%ls>\n", - token_type_description(node_type), input1.describe().c_str()); - } + debug(5, "Resolving production for %ls with input token <%ls>\n", + token_type_description(node_type), input1.describe().c_str()); // Fetch the function to resolve the list of productions. - const production_t *(*resolver)(const parse_token_t &input1, const parse_token_t &input2, - parse_node_tag_t *out_tag) = NULL; + const production_t *(*resolver)(const parse_token_t &input1, //!OCLINT(unused param) + const parse_token_t &input2, //!OCLINT(unused param) + parse_node_tag_t *out_tag) = NULL; //!OCLINT(unused param) switch (node_type) { TEST(job_list) TEST(job) @@ -501,10 +500,8 @@ const production_t *parse_productions::production_for_token(parse_token_type_t n const production_t *result = resolver(input1, input2, out_tag); if (result == NULL) { - if (log_it) { - fprintf(stderr, "Node type '%ls' has no production for input '%ls' (in %s)\n", - token_type_description(node_type), input1.describe().c_str(), __FUNCTION__); - } + debug(5, "Node type '%ls' has no production for input '%ls' (in %s)\n", + token_type_description(node_type), input1.describe().c_str(), __FUNCTION__); } return result; diff --git a/src/parse_tree.cpp b/src/parse_tree.cpp index b524f59fe..3a0ceb168 100644 --- a/src/parse_tree.cpp +++ b/src/parse_tree.cpp @@ -73,70 +73,74 @@ static bool production_is_empty(const production_t *production) { wcstring parse_error_t::describe_with_prefix(const wcstring &src, const wcstring &prefix, bool is_interactive, bool skip_caret) const { wcstring result = text; - if (!skip_caret && source_start < src.size() && source_start + source_length <= src.size()) { - // Locate the beginning of this line of source. - size_t line_start = 0; - // Look for a newline prior to source_start. If we don't find one, start at the beginning of - // the string; otherwise start one past the newline. Note that source_start may itself point - // at a newline; we want to find the newline before it. - if (source_start > 0) { - size_t newline = src.find_last_of(L'\n', source_start - 1); - if (newline != wcstring::npos) { - line_start = newline + 1; - } - } + if (skip_caret || source_start >= src.size() || source_start + source_length > src.size()) { + return result; + } - // Look for the newline after the source range. If the source range itself includes a - // newline, that's the one we want, so start just before the end of the range. - size_t last_char_in_range = - (source_length == 0 ? source_start : source_start + source_length - 1); - size_t line_end = src.find(L'\n', last_char_in_range); - if (line_end == wcstring::npos) { - line_end = src.size(); - } + // Locate the beginning of this line of source. + size_t line_start = 0; - assert(line_end >= line_start); - assert(source_start >= line_start); - - // Don't include the caret and line if we're interactive this is the first line, because - // then it's obvious. - bool skip_caret = (is_interactive && source_start == 0); - - if (!skip_caret) { - // Append the line of text. - if (!result.empty()) { - result.push_back(L'\n'); - } - result.append(prefix); - result.append(src, line_start, line_end - line_start); - - // Append the caret line. The input source may include tabs; for that reason we - // construct a "caret line" that has tabs in corresponding positions. - const wcstring line_to_measure = - prefix + wcstring(src, line_start, source_start - line_start); - wcstring caret_space_line; - caret_space_line.reserve(source_start - line_start); - for (size_t i = 0; i < line_to_measure.size(); i++) { - wchar_t wc = line_to_measure.at(i); - if (wc == L'\t') { - caret_space_line.push_back(L'\t'); - } else if (wc == L'\n') { - // It's possible that the source_start points at a newline itself. In that case, - // pretend it's a space. We only expect this to be at the end of the string. - caret_space_line.push_back(L' '); - } else { - int width = fish_wcwidth(wc); - if (width > 0) { - caret_space_line.append(static_cast(width), L' '); - } - } - } - result.push_back(L'\n'); - result.append(caret_space_line); - result.push_back(L'^'); + // Look for a newline prior to source_start. If we don't find one, start at the beginning of + // the string; otherwise start one past the newline. Note that source_start may itself point + // at a newline; we want to find the newline before it. + if (source_start > 0) { + size_t newline = src.find_last_of(L'\n', source_start - 1); + if (newline != wcstring::npos) { + line_start = newline + 1; } } + + // Look for the newline after the source range. If the source range itself includes a + // newline, that's the one we want, so start just before the end of the range. + size_t last_char_in_range = + (source_length == 0 ? source_start : source_start + source_length - 1); + size_t line_end = src.find(L'\n', last_char_in_range); + if (line_end == wcstring::npos) { + line_end = src.size(); + } + + assert(line_end >= line_start); + assert(source_start >= line_start); + + // Don't include the caret and line if we're interactive this is the first line, because + // then it's obvious. + bool interactive_skip_caret = is_interactive && source_start == 0; + + if (interactive_skip_caret) { + return result; + } + + // Append the line of text. + if (!result.empty()) { + result.push_back(L'\n'); + } + result.append(prefix); + result.append(src, line_start, line_end - line_start); + + // Append the caret line. The input source may include tabs; for that reason we + // construct a "caret line" that has tabs in corresponding positions. + const wcstring line_to_measure = prefix + wcstring(src, line_start, source_start - line_start); + wcstring caret_space_line; + caret_space_line.reserve(source_start - line_start); + for (size_t i = 0; i < line_to_measure.size(); i++) { + wchar_t wc = line_to_measure.at(i); + if (wc == L'\t') { + caret_space_line.push_back(L'\t'); + } else if (wc == L'\n') { + // It's possible that the source_start points at a newline itself. In that case, + // pretend it's a space. We only expect this to be at the end of the string. + caret_space_line.push_back(L' '); + } else { + int width = fish_wcwidth(wc); + if (width > 0) { + caret_space_line.append(static_cast(width), L' '); + } + } + } + result.push_back(L'\n'); + result.append(caret_space_line); + result.push_back(L'^'); return result; } @@ -328,7 +332,7 @@ static inline parse_token_type_t parse_token_type_from_tokenizer_token( default: { fprintf(stderr, "Bad token type %d passed to %s\n", (int)tokenizer_token_type, __FUNCTION__); - assert(0); + DIE("bad token type"); break; } } @@ -475,7 +479,7 @@ class parse_ll_t { /// Get the node corresponding to the top element of the stack. parse_node_t &node_for_top_symbol() { - PARSE_ASSERT(!symbol_stack.empty()); + PARSE_ASSERT(!symbol_stack.empty()); //!OCLINT(multiple unary operator) const parse_stack_element_t &top_symbol = symbol_stack.back(); PARSE_ASSERT(top_symbol.node_idx != NODE_OFFSET_INVALID); PARSE_ASSERT(top_symbol.node_idx < nodes.size()); @@ -888,116 +892,112 @@ bool parse_ll_t::report_error_for_unclosed_block() { block_node = this->nodes.get_child(*block_node, 0, symbol_block_header); block_node = this->nodes.get_child(*block_node, 0); // specific statement } - if (block_node != NULL) { - // block_node is now an if_statement, switch_statement, for_header, while_header, - // function_header, or begin_header. - // - // Hackish: descend down the first node until we reach the bottom. This will be a keyword - // node like SWITCH, which will have the source range. Ordinarily the source range would be - // known by the parent node too, but we haven't completed parsing yet, so we haven't yet - // propagated source ranges. - const parse_node_t *cursor = block_node; - while (cursor->child_count > 0) { - cursor = this->nodes.get_child(*cursor, 0); - assert(cursor != NULL); - } - if (cursor->source_start != NODE_OFFSET_INVALID) { - const wcstring node_desc = block_type_user_presentable_description(block_node->type); - this->parse_error_at_location(cursor->source_start, parse_error_generic, - L"Missing end to balance this %ls", node_desc.c_str()); - reported_error = true; - } + if (block_node == NULL) { + return reported_error; + } + + // block_node is now an if_statement, switch_statement, for_header, while_header, + // function_header, or begin_header. + // + // Hackish: descend down the first node until we reach the bottom. This will be a keyword + // node like SWITCH, which will have the source range. Ordinarily the source range would be + // known by the parent node too, but we haven't completed parsing yet, so we haven't yet + // propagated source ranges. + const parse_node_t *cursor = block_node; + while (cursor->child_count > 0) { + cursor = this->nodes.get_child(*cursor, 0); + assert(cursor != NULL); + } + if (cursor->source_start != NODE_OFFSET_INVALID) { + const wcstring node_desc = block_type_user_presentable_description(block_node->type); + this->parse_error_at_location(cursor->source_start, parse_error_generic, + L"Missing end to balance this %ls", node_desc.c_str()); + reported_error = true; } return reported_error; } bool parse_ll_t::top_node_handle_terminal_types(parse_token_t token) { - PARSE_ASSERT(!symbol_stack.empty()); + PARSE_ASSERT(!symbol_stack.empty()); //!OCLINT(multiple unary operator) PARSE_ASSERT(token.type >= FIRST_PARSE_TOKEN_TYPE); - bool handled = false; parse_stack_element_t &stack_top = symbol_stack.back(); - if (type_is_terminal_type(stack_top.type)) { - // The top of the stack is terminal. We are going to handle this (because we can't produce - // from a terminal type). - handled = true; - // Now see if we actually matched - bool matched = false; - if (stack_top.type == token.type) { - switch (stack_top.type) { - case parse_token_type_string: { - // We matched if the keywords match, or no keyword was required. - matched = (stack_top.keyword == parse_keyword_none || - stack_top.keyword == token.keyword); + if (!type_is_terminal_type(stack_top.type)) { + return false; // was not handled + } + + // The top of the stack is terminal. We are going to handle this (because we can't produce + // from a terminal type). + + // Now see if we actually matched + bool matched = false; + if (stack_top.type == token.type) { + if (stack_top.type == parse_token_type_string) { + // We matched if the keywords match, or no keyword was required. + matched = + (stack_top.keyword == parse_keyword_none || stack_top.keyword == token.keyword); + } else { + // For other types, we only require that the types match. + matched = true; + } + } + + if (matched) { + // Success. Tell the node that it matched this token, and what its source range is in + // the parse phase, we only set source ranges for terminal types. We propagate ranges to + // parent nodes afterwards. + parse_node_t &node = node_for_top_symbol(); + node.keyword = token.keyword; + node.source_start = token.source_start; + node.source_length = token.source_length; + } else { + // Failure + if (stack_top.type == parse_token_type_string && token.type == parse_token_type_string) { + // Keyword failure. We should unify this with the 'matched' computation above. + assert(stack_top.keyword != parse_keyword_none && stack_top.keyword != token.keyword); + + // Check to see which keyword we got which was considered wrong. + switch (token.keyword) { + // Some keywords are only valid in certain contexts. If this cascaded all the + // way down through the outermost job_list, it was not in a valid context. + case parse_keyword_case: + case parse_keyword_end: + case parse_keyword_else: { + this->parse_error_unbalancing_token(token); + break; + } + case parse_keyword_none: { + // This is a random other string (not a keyword). + const wcstring expected = keyword_description(stack_top.keyword); + this->parse_error(token, parse_error_generic, L"Expected keyword '%ls'", + expected.c_str()); break; } default: { - // For other types, we only require that the types match. - matched = true; + // Got a real keyword we can report. + const wcstring actual = + (token.keyword == parse_keyword_none ? token.describe() + : keyword_description(token.keyword)); + const wcstring expected = keyword_description(stack_top.keyword); + this->parse_error(token, parse_error_generic, + L"Expected keyword '%ls', instead got keyword '%ls'", + expected.c_str(), actual.c_str()); break; } } - } - - if (matched) { - // Success. Tell the node that it matched this token, and what its source range is in - // the parse phase, we only set source ranges for terminal types. We propagate ranges to - // parent nodes afterwards. - parse_node_t &node = node_for_top_symbol(); - node.keyword = token.keyword; - node.source_start = token.source_start; - node.source_length = token.source_length; + } else if (stack_top.keyword == parse_keyword_end && + token.type == parse_token_type_terminate && + this->report_error_for_unclosed_block()) { + ; // handled by report_error_for_unclosed_block } else { - // Failure - if (stack_top.type == parse_token_type_string && - token.type == parse_token_type_string) { - // Keyword failure. We should unify this with the 'matched' computation above. - assert(stack_top.keyword != parse_keyword_none && - stack_top.keyword != token.keyword); - - // Check to see which keyword we got which was considered wrong. - switch (token.keyword) { - // Some keywords are only valid in certain contexts. If this cascaded all the - // way down through the outermost job_list, it was not in a valid context. - case parse_keyword_case: - case parse_keyword_end: - case parse_keyword_else: { - this->parse_error_unbalancing_token(token); - break; - } - case parse_keyword_none: { - // This is a random other string (not a keyword). - const wcstring expected = keyword_description(stack_top.keyword); - this->parse_error(token, parse_error_generic, L"Expected keyword '%ls'", - expected.c_str()); - break; - } - default: { - // Got a real keyword we can report. - const wcstring actual = (token.keyword == parse_keyword_none - ? token.describe() - : keyword_description(token.keyword)); - const wcstring expected = keyword_description(stack_top.keyword); - this->parse_error(token, parse_error_generic, - L"Expected keyword '%ls', instead got keyword '%ls'", - expected.c_str(), actual.c_str()); - break; - } - } - } else if (stack_top.keyword == parse_keyword_end && - token.type == parse_token_type_terminate && - this->report_error_for_unclosed_block()) { - // Handled by report_error_for_unclosed_block. - } else { - const wcstring expected = stack_top.user_presentable_description(); - this->parse_error_unexpected_token(expected.c_str(), token); - } + const wcstring expected = stack_top.user_presentable_description(); + this->parse_error_unexpected_token(expected.c_str(), token); } - - // We handled the token, so pop the symbol stack. - symbol_stack.pop_back(); } - return handled; + + // We handled the token, so pop the symbol stack. + symbol_stack.pop_back(); + return true; } void parse_ll_t::accept_tokens(parse_token_t token1, parse_token_t token2) { @@ -1036,7 +1036,7 @@ void parse_ll_t::accept_tokens(parse_token_t token1, parse_token_t token2) { } while (!consumed && !this->fatal_errored) { - PARSE_ASSERT(!symbol_stack.empty()); + PARSE_ASSERT(!symbol_stack.empty()); //!OCLINT(multiple unary operator) if (top_node_handle_terminal_types(token1)) { if (logit) { @@ -1238,32 +1238,33 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t parse_flags, parser.report_tokenizer_error(tokenizer_token); } - // Handle errors. - if (parser.has_fatal_error()) { - if (parse_flags & parse_flag_continue_after_error) { - // Hack. Typically the parse error is due to the first token. However, if it's a - // tokenizer error, then has_fatal_error was set due to the check above; in that - // case the second token is what matters. - size_t error_token_idx = 0; - if (queue[1].type == parse_special_type_tokenizer_error) { - error_token_idx = (queue[1].type == parse_special_type_tokenizer_error ? 1 : 0); - token_count = -1; // so that it will be 0 after incrementing, and our tokenizer - // error will be ignored - } - - // Mark a special error token, and then keep going. - const parse_token_t token = {parse_special_type_parse_error, - parse_keyword_none, - false, - false, - queue[error_token_idx].source_start, - queue[error_token_idx].source_length}; - parser.accept_tokens(token, kInvalidToken); - parser.reset_symbols(goal); - } else { - break; // bail out - } + if (!parser.has_fatal_error()) { + continue; } + + // Handle errors. + if (!(parse_flags & parse_flag_continue_after_error)) { + break; // bail out + } + // Hack. Typically the parse error is due to the first token. However, if it's a + // tokenizer error, then has_fatal_error was set due to the check above; in that + // case the second token is what matters. + size_t error_token_idx = 0; + if (queue[1].type == parse_special_type_tokenizer_error) { + error_token_idx = (queue[1].type == parse_special_type_tokenizer_error ? 1 : 0); + token_count = -1; // so that it will be 0 after incrementing, and our tokenizer + // error will be ignored + } + + // Mark a special error token, and then keep going. + const parse_token_t token = {parse_special_type_parse_error, + parse_keyword_none, + false, + false, + queue[error_token_idx].source_start, + queue[error_token_idx].source_length}; + parser.accept_tokens(token, kInvalidToken); + parser.reset_symbols(goal); } // Teach each node where its source range is. @@ -1275,7 +1276,8 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t parse_flags, #if 0 //wcstring result = dump_tree(this->parser->nodes, str); //fprintf(stderr, "Tree (%ld nodes):\n%ls", this->parser->nodes.size(), result.c_str()); - fprintf(stderr, "%lu nodes, node size %lu, %lu bytes\n", output->size(), sizeof(parse_node_t), output->size() * sizeof(parse_node_t)); + fprintf(stderr, "%lu nodes, node size %lu, %lu bytes\n", output->size(), sizeof(parse_node_t), + output->size() * sizeof(parse_node_t)); #endif // Indicate if we had a fatal error. @@ -1310,8 +1312,7 @@ const parse_node_t &parse_node_tree_t::find_child(const parse_node_t &parent, return *child; } } - PARSE_ASSERT(0); - return *(parse_node_t *)(NULL); // unreachable + DIE("failed to find child node"); } const parse_node_t *parse_node_tree_t::get_parent(const parse_node_t &node, @@ -1370,13 +1371,11 @@ const parse_node_t *parse_node_tree_t::find_last_node_of_type(parse_token_type_t size_t idx = this->size(); while (idx--) { const parse_node_t &node = this->at(idx); - if (node.type == type) { - // Types match. Check if it has the right parent. - if (parent == NULL || node_has_ancestor(*this, node, *parent)) { - // Success - result = &node; - break; - } + bool expected_type = (node.type == type); + if (expected_type && (parent == NULL || node_has_ancestor(*this, node, *parent))) { + // The types match and it has the right parent. + result = &node; + break; } } return result; @@ -1387,7 +1386,7 @@ const parse_node_t *parse_node_tree_t::find_node_matching_source_location( const parse_node_t *result = NULL; // Find nodes of the given type in the tree, working backwards. const size_t len = this->size(); - for (size_t idx = 0; idx < len; idx++) { + for (size_t idx = 0; idx < len && result == NULL; idx++) { const parse_node_t &node = this->at(idx); // Types must match. @@ -1401,8 +1400,8 @@ const parse_node_t *parse_node_tree_t::find_node_matching_source_location( // Found it. result = &node; - break; } + return result; } @@ -1552,8 +1551,7 @@ enum parse_bool_statement_type_t parse_node_tree_t::statement_boolean_type( bool parse_node_tree_t::job_should_be_backgrounded(const parse_node_t &job) const { assert(job.type == symbol_job); const parse_node_t *opt_background = get_child(job, 2, symbol_optional_background); - bool result = opt_background != NULL && opt_background->tag == parse_background; - return result; + return opt_background != NULL && opt_background->tag == parse_background; } const parse_node_t *parse_node_tree_t::next_node_in_node_list( diff --git a/src/parse_tree.h b/src/parse_tree.h index a5245aca6..0582fc947 100644 --- a/src/parse_tree.h +++ b/src/parse_tree.h @@ -118,7 +118,9 @@ class parse_node_t { } /// Indicate if the node has comment nodes. - bool has_comments() const { return !!(this->flags & parse_node_flag_has_comments); } + bool has_comments() const { + return static_cast(this->flags & parse_node_flag_has_comments); + } /// Gets source for the node, or the empty string if it has no source. wcstring get_source(const wcstring &str) const { diff --git a/src/parse_util.cpp b/src/parse_util.cpp index 509c6a612..5e84f2fcf 100644 --- a/src/parse_util.cpp +++ b/src/parse_util.cpp @@ -88,13 +88,11 @@ size_t parse_util_get_offset(const wcstring &str, int line, long line_offset) { size_t off2 = parse_util_get_offset_from_line(buff, line + 1); if (off == (size_t)-1) return (size_t)-1; - if (off2 == (size_t)-1) off2 = wcslen(buff) + 1; - - if (line_offset < 0) line_offset = 0; + if (line_offset < 0) line_offset = 0; //!OCLINT(parameter reassignment) if ((size_t)line_offset >= off2 - off - 1) { - line_offset = off2 - off - 1; + line_offset = off2 - off - 1; //!OCLINT(parameter reassignment) } return off + line_offset; @@ -199,26 +197,28 @@ static int parse_util_locate_brackets_range(const wcstring &str, size_t *inout_c int ret = parse_util_locate_brackets_of_type(valid_range_start, &bracket_range_begin, &bracket_range_end, accept_incomplete, open_type, close_type); - if (ret > 0) { - // The command substitutions must not be NULL and must be in the valid pointer range, and - // the end must be bigger than the beginning. - assert(bracket_range_begin != NULL && bracket_range_begin >= valid_range_start && - bracket_range_begin <= valid_range_end); - assert(bracket_range_end != NULL && bracket_range_end > bracket_range_begin && - bracket_range_end >= valid_range_start && bracket_range_end <= valid_range_end); - - // Assign the substring to the out_contents. - const wchar_t *interior_begin = bracket_range_begin + 1; - out_contents->assign(interior_begin, bracket_range_end - interior_begin); - - // Return the start and end. - *out_start = bracket_range_begin - buff; - *out_end = bracket_range_end - buff; - - // Update the inout_cursor_offset. Note this may cause it to exceed str.size(), though - // overflow is not likely. - *inout_cursor_offset = 1 + *out_end; + if (ret <= 0) { + return ret; } + + // The command substitutions must not be NULL and must be in the valid pointer range, and + // the end must be bigger than the beginning. + assert(bracket_range_begin != NULL && bracket_range_begin >= valid_range_start && + bracket_range_begin <= valid_range_end); + assert(bracket_range_end != NULL && bracket_range_end > bracket_range_begin && + bracket_range_end >= valid_range_start && bracket_range_end <= valid_range_end); + + // Assign the substring to the out_contents. + const wchar_t *interior_begin = bracket_range_begin + 1; + out_contents->assign(interior_begin, bracket_range_end - interior_begin); + + // Return the start and end. + *out_start = bracket_range_begin - buff; + *out_end = bracket_range_end - buff; + + // Update the inout_cursor_offset. Note this may cause it to exceed str.size(), though + // overflow is not likely. + *inout_cursor_offset = 1 + *out_end; return ret; } @@ -472,8 +472,7 @@ static wchar_t get_quote(const wcstring &cmd_str, size_t len) { void parse_util_get_parameter_info(const wcstring &cmd, const size_t pos, wchar_t *quote, size_t *offset, enum token_type *out_type) { size_t prev_pos = 0; - wchar_t last_quote = '\0'; - int unfinished; + wchar_t last_quote = L'\0'; tokenizer_t tok(cmd.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS); tok_t token; @@ -490,30 +489,25 @@ void parse_util_get_parameter_info(const wcstring &cmd, const size_t pos, wchar_ wchar_t *cmd_tmp = wcsdup(cmd.c_str()); cmd_tmp[pos] = 0; size_t cmdlen = wcslen(cmd_tmp); - unfinished = (cmdlen == 0); - if (!unfinished) { - unfinished = (quote != 0); - - if (!unfinished) { - if (wcschr(L" \t\n\r", cmd_tmp[cmdlen - 1]) != 0) { - if ((cmdlen == 1) || (cmd_tmp[cmdlen - 2] != L'\\')) { - unfinished = 1; - } - } + bool finished = cmdlen != 0; + if (finished) { + finished = (quote == NULL); + if (finished && wcschr(L" \t\n\r", cmd_tmp[cmdlen - 1]) != L'\0') { + finished = cmdlen > 1 && cmd_tmp[cmdlen - 2] == L'\\'; } } if (quote) *quote = last_quote; if (offset != 0) { - if (!unfinished) { + if (finished) { while ((cmd_tmp[prev_pos] != 0) && (wcschr(L";|", cmd_tmp[prev_pos]) != 0)) prev_pos++; - *offset = prev_pos; } else { *offset = pos; } } + free(cmd_tmp); } @@ -763,10 +757,9 @@ static int parser_is_pipe_forbidden(const wcstring &word) { bool parse_util_argument_is_help(const wchar_t *s, int min_match) { CHECK(s, 0); - size_t len = wcslen(s); - min_match = maxi(min_match, 3); + min_match = maxi(min_match, 3); //!OCLINT(parameter reassignment) return wcscmp(L"-h", s) == 0 || (len >= (size_t)min_match && (wcsncmp(L"--help", s, len) == 0)); } @@ -930,25 +923,28 @@ static parser_test_error_bits_t detect_dollar_cmdsub_errors(size_t arg_src_offse parse_error_list_t *out_errors) { parser_test_error_bits_t result_bits = 0; wcstring unescaped_arg_src; - if (unescape_string(arg_src, &unescaped_arg_src, UNESCAPE_SPECIAL)) { - if (!unescaped_arg_src.empty()) { - wchar_t last = unescaped_arg_src.at(unescaped_arg_src.size() - 1); - if (last == VARIABLE_EXPAND) { - result_bits |= PARSER_TEST_ERROR; - if (out_errors != NULL) { - wcstring subcommand_first_token = tok_first(cmdsubst_src); - if (subcommand_first_token.empty()) { - // e.g. $(). Report somthing. - subcommand_first_token = L"..."; - } - append_syntax_error( - out_errors, - arg_src_offset + arg_src.size() - 1, // global position of the dollar - ERROR_BAD_VAR_SUBCOMMAND1, truncate_string(subcommand_first_token).c_str()); - } + + if (!unescape_string(arg_src, &unescaped_arg_src, UNESCAPE_SPECIAL) || + unescaped_arg_src.empty()) { + return result_bits; + } + + wchar_t last = unescaped_arg_src.at(unescaped_arg_src.size() - 1); + if (last == VARIABLE_EXPAND) { + result_bits |= PARSER_TEST_ERROR; + if (out_errors != NULL) { + wcstring subcommand_first_token = tok_first(cmdsubst_src); + if (subcommand_first_token.empty()) { + // e.g. $(). Report somthing. + subcommand_first_token = L"..."; } + append_syntax_error( + out_errors, + arg_src_offset + arg_src.size() - 1, // global position of the dollar + ERROR_BAD_VAR_SUBCOMMAND1, truncate_string(subcommand_first_token).c_str()); } } + return result_bits; } @@ -1014,6 +1010,10 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const parse_node_t } break; } + default: { + DIE("unexpected parse_util_locate_cmdsubst() return value"); + break; + } } } @@ -1029,28 +1029,24 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const parse_node_t // Check for invalid variable expansions. const size_t unesc_size = unesc.size(); for (size_t idx = 0; idx < unesc_size; idx++) { - switch (unesc.at(idx)) { - case VARIABLE_EXPAND: - case VARIABLE_EXPAND_SINGLE: { - wchar_t next_char = idx + 1 < unesc_size ? unesc.at(idx + 1) : L'\0'; + if (unesc.at(idx) != VARIABLE_EXPAND && unesc.at(idx) != VARIABLE_EXPAND_SINGLE) { + continue; + } - if (next_char != VARIABLE_EXPAND && next_char != VARIABLE_EXPAND_SINGLE && - !wcsvarchr(next_char)) { - err = 1; - if (out_errors) { - // We have something like $$$^.... Back up until we reach the first $. - size_t first_dollar = idx; - while (first_dollar > 0 && - (unesc.at(first_dollar - 1) == VARIABLE_EXPAND || - unesc.at(first_dollar - 1) == VARIABLE_EXPAND_SINGLE)) { - first_dollar--; - } - parse_util_expand_variable_error(unesc, node.source_start, first_dollar, - out_errors); - } + wchar_t next_char = idx + 1 < unesc_size ? unesc.at(idx + 1) : L'\0'; + if (next_char != VARIABLE_EXPAND && next_char != VARIABLE_EXPAND_SINGLE && + !wcsvarchr(next_char)) { + err = 1; + if (out_errors) { + // We have something like $$$^.... Back up until we reach the first $. + size_t first_dollar = idx; + while (first_dollar > 0 && + (unesc.at(first_dollar - 1) == VARIABLE_EXPAND || + unesc.at(first_dollar - 1) == VARIABLE_EXPAND_SINGLE)) { + first_dollar--; } - - break; + parse_util_expand_variable_error(unesc, node.source_start, first_dollar, + out_errors); } } } @@ -1158,33 +1154,33 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, assert(next_job_list != NULL); const parse_node_t *next_job = node_tree.next_node_in_node_list(*next_job_list, symbol_job, NULL); - if (next_job != NULL) { - const parse_node_t *next_statement = - node_tree.get_child(*next_job, 0, symbol_statement); - if (next_statement != NULL) { - const parse_node_t *spec_statement = - node_tree.get_child(*next_statement, 0); - if (spec_statement && - spec_statement->type == symbol_boolean_statement) { - switch (parse_node_tree_t::statement_boolean_type( - *spec_statement)) { - // These are not allowed. - case parse_bool_and: - errored = append_syntax_error( - &parse_errors, spec_statement->source_start, - BOOL_AFTER_BACKGROUND_ERROR_MSG, L"and"); - break; - case parse_bool_or: - errored = append_syntax_error( - &parse_errors, spec_statement->source_start, - BOOL_AFTER_BACKGROUND_ERROR_MSG, L"or"); - break; - case parse_bool_not: - // This one is OK. - break; - } - } - } + if (next_job == NULL) { + break; + } + + const parse_node_t *next_statement = + node_tree.get_child(*next_job, 0, symbol_statement); + if (next_statement == NULL) { + break; + } + + const parse_node_t *spec_statement = + node_tree.get_child(*next_statement, 0); + if (!spec_statement || + spec_statement->type != symbol_boolean_statement) { + break; + } + + parse_bool_statement_type_t bool_type = + parse_node_tree_t::statement_boolean_type(*spec_statement); + if (bool_type == parse_bool_and) { // this is not allowed + errored = append_syntax_error( + &parse_errors, spec_statement->source_start, + BOOL_AFTER_BACKGROUND_ERROR_MSG, L"and"); + } else if (bool_type == parse_bool_or) { // this is not allowed + errored = append_syntax_error( + &parse_errors, spec_statement->source_start, + BOOL_AFTER_BACKGROUND_ERROR_MSG, L"or"); } break; } diff --git a/src/parser.cpp b/src/parser.cpp index 17ada81c0..914fcf036 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -270,45 +270,46 @@ static void print_profile(const std::vector &items, FILE *out) int my_time; me = items.at(pos); - if (!me->skipped) { - my_time = me->parse + me->exec; + if (me->skipped) { + continue; + } - for (i = pos + 1; i < items.size(); i++) { - prev = items.at(i); - if (prev->skipped) { - continue; - } - - if (prev->level <= me->level) { - break; - } - - if (prev->level > me->level + 1) { - continue; - } - - my_time -= prev->parse; - my_time -= prev->exec; + my_time = me->parse + me->exec; + for (i = pos + 1; i < items.size(); i++) { + prev = items.at(i); + if (prev->skipped) { + continue; + } + if (prev->level <= me->level) { + break; + } + if (prev->level > me->level + 1) { + continue; } - if (me->cmd.size() > 0) { - if (fwprintf(out, L"%d\t%d\t", my_time, me->parse + me->exec) < 0) { - wperror(L"fwprintf"); - return; - } + my_time -= prev->parse + prev->exec; + } - for (i = 0; i < me->level; i++) { - if (fwprintf(out, L"-") < 0) { - wperror(L"fwprintf"); - return; - } - } - if (fwprintf(out, L"> %ls\n", me->cmd.c_str()) < 0) { - wperror(L"fwprintf"); - return; - } + if (me->cmd.size() == 0) { + continue; + } + + if (fwprintf(out, L"%d\t%d\t", my_time, me->parse + me->exec) < 0) { + wperror(L"fwprintf"); + return; + } + + for (i = 0; i < me->level; i++) { + if (fwprintf(out, L"-") < 0) { + wperror(L"fwprintf"); + return; } } + + if (fwprintf(out, L"> %ls\n", me->cmd.c_str()) < 0) { + wperror(L"fwprintf"); + return; + } } } @@ -344,7 +345,7 @@ void parser_t::expand_argument_list(const wcstring &arg_list_src, expand_flags_t } // Get the root argument list. - assert(!tree.empty()); + assert(!tree.empty()); //!OCLINT(multiple unary operator) const parse_node_t *arg_list = &tree.at(0); assert(arg_list->type == symbol_freestanding_argument_list); @@ -563,11 +564,11 @@ bool parser_t::job_remove(job_t *j) { if (iter != my_job_list.end()) { my_job_list.erase(iter); return true; - } else { - debug(1, _(L"Job inconsistency")); - sanity_lose(); - return false; } + + debug(1, _(L"Job inconsistency")); + sanity_lose(); + return false; } void parser_t::job_promote(job_t *job) { @@ -713,9 +714,7 @@ bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcst parse_error_list_t errors; // Use empty string for the prefix if it's NULL. - if (prefix == NULL) { - prefix = L""; - } + if (!prefix) prefix = L""; //!OCLINT(parameter reassignment) // Parse the string as an argument list. parse_node_tree_t tree; @@ -727,7 +726,7 @@ bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcst if (!errored) { // Get the root argument list. - assert(!tree.empty()); + assert(!tree.empty()); //!OCLINT(multiple unary operator) const parse_node_t *arg_list = &tree.at(0); assert(arg_list->type == symbol_freestanding_argument_list); @@ -869,10 +868,6 @@ wcstring block_t::description() const { result.append(L"breakpoint"); break; } - default: { - assert(0 && "Unhandled block_type_t constant"); - abort(); - } } if (this->src_lineno >= 0) { diff --git a/src/path.cpp b/src/path.cpp index c84789ce5..e4775cec5 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -84,6 +84,7 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, default: { debug(1, MISSING_COMMAND_ERR_MSG, nxt_path.c_str()); wperror(L"access"); + break; } } } @@ -189,19 +190,7 @@ wcstring path_apply_working_directory(const wcstring &path, const wcstring &work // We're going to make sure that if we want to prepend the wd, that the string has no leading // "/". - bool prepend_wd; - switch (path.at(0)) { - case L'/': - case HOME_DIRECTORY: { - prepend_wd = false; - break; - } - default: { - prepend_wd = true; - break; - } - } - + bool prepend_wd = path.at(0) != L'/' && path.at(0) != HOME_DIRECTORY; if (!prepend_wd) { // No need to prepend the wd, so just return the path we were given. return path; @@ -298,16 +287,6 @@ bool path_get_data(wcstring &path) { return !result.empty(); } -__attribute__((unused)) static void replace_all(wcstring &str, const wchar_t *needle, - const wchar_t *replacement) { - size_t needle_len = wcslen(needle); - size_t offset = 0; - while ((offset = str.find(needle, offset)) != wcstring::npos) { - str.replace(offset, needle_len, replacement); - offset += needle_len; - } -} - void path_make_canonical(wcstring &path) { // Ignore trailing slashes, unless it's the first character. size_t len = path.size(); @@ -386,7 +365,7 @@ bool paths_are_same_file(const wcstring &path1, const wcstring &path2) { struct stat s1, s2; if (wstat(path1, &s1) == 0 && wstat(path2, &s2) == 0) { return s1.st_ino == s2.st_ino && s1.st_dev == s2.st_dev; - } else { - return false; } + + return false; } diff --git a/src/postfork.cpp b/src/postfork.cpp index 5f15f9954..792ee2917 100644 --- a/src/postfork.cpp +++ b/src/postfork.cpp @@ -66,7 +66,9 @@ int set_child_group(job_t *j, process_t *p, int print_errors) { j->pgid = p->pid; } - if (setpgid(p->pid, j->pgid)) { + if (setpgid(p->pid, j->pgid)) { //!OCLINT(collapsible if statements) + // TODO: Figure out why we're testing whether the pgid is correct after attempting to + // set it failed. This was added in commit 4e912ef8 from 2012-02-27. if (getpgid(p->pid) != j->pgid && print_errors) { char pid_buff[128]; char job_id_buff[128]; @@ -95,7 +97,8 @@ int set_child_group(job_t *j, process_t *p, int print_errors) { } if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND)) { - if (tcsetpgrp(0, j->pgid) && print_errors) { + int result = tcsetpgrp(0, j->pgid); + if (result == -1 && print_errors) { char job_id_buff[64]; char command_buff[64]; format_long_safe(job_id_buff, j->job_id); @@ -485,20 +488,14 @@ void safe_report_exec_error(int err, const char *actual_cmd, const char *const * /// allocate memory, etc. bool do_builtin_io(const char *out, size_t outlen, const char *err, size_t errlen) { bool success = true; - if (out && outlen) { - if (write_loop(STDOUT_FILENO, out, outlen) < 0) { - int e = errno; - debug_safe(0, "Error while writing to stdout"); - safe_perror("write_loop"); - success = false; - errno = e; - } + if (out && outlen && write_loop(STDOUT_FILENO, out, outlen) < 0) { + int e = errno; + debug_safe(0, "Error while writing to stdout"); + safe_perror("write_loop"); + success = false; + errno = e; } - if (err && errlen) { - if (write_loop(STDERR_FILENO, err, errlen) < 0) { - success = false; - } - } + if (err && errlen && write_loop(STDERR_FILENO, err, errlen) < 0) success = false; return success; } diff --git a/src/print_help.cpp b/src/print_help.cpp index 03ae38593..c19a51dfc 100644 --- a/src/print_help.cpp +++ b/src/print_help.cpp @@ -16,9 +16,7 @@ void print_help(const char *c, int fd) { char cmd[CMD_LEN]; int printed = snprintf(cmd, CMD_LEN, "fish -c '__fish_print_help %s >&%d'", c, fd); - if (printed < CMD_LEN) { - if ((system(cmd) == -1)) { - write_loop(2, HELP_ERR, strlen(HELP_ERR)); - } + if (printed < CMD_LEN && system(cmd) == -1) { + write_loop(2, HELP_ERR, strlen(HELP_ERR)); } } diff --git a/src/proc.cpp b/src/proc.cpp index a5199c71b..42850f6b7 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -247,7 +247,7 @@ void job_set_flag(job_t *j, unsigned int flag, int set) { } } -int job_get_flag(const job_t *j, unsigned int flag) { return !!(j->flags & flag); } +int job_get_flag(const job_t *j, unsigned int flag) { return static_cast(j->flags & flag); } int job_signal(job_t *j, int signal) { pid_t my_pid = getpid(); @@ -257,13 +257,9 @@ int job_signal(job_t *j, int signal) { res = killpg(j->pgid, signal); } else { for (process_t *p = j->first_process; p; p = p->next) { - if (!p->completed) { - if (p->pid) { - if (kill(p->pid, signal)) { - res = -1; - break; - } - } + if (!p->completed && p->pid && kill(p->pid, signal)) { + res = -1; + break; } } } @@ -312,10 +308,8 @@ static void handle_child_status(pid_t pid, int status) { for (p = j->first_process; p; p = p->next) { if (pid == p->pid) { mark_process_status(p, status); - if (p->completed && prev != 0) { - if (!prev->completed && prev->pid) { - kill(prev->pid, SIGPIPE); - } + if (p->completed && prev && !prev->completed && prev->pid) { + kill(prev->pid, SIGPIPE); } found_proc = true; break; @@ -324,28 +318,34 @@ static void handle_child_status(pid_t pid, int status) { } } - if (WIFSIGNALED(status) && (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGQUIT)) { - if (!is_interactive_session) { - struct sigaction act; - sigemptyset(&act.sa_mask); - act.sa_flags = 0; - act.sa_handler = SIG_DFL; - sigaction(SIGINT, &act, 0); - sigaction(SIGQUIT, &act, 0); - kill(getpid(), WTERMSIG(status)); - } else { - // In an interactive session, tell the principal parser to skip all blocks we're - // executing so control-C returns control to the user. - if (p && found_proc) { - parser_t::skip_all_blocks(); - } - } + // If the child process was not killed by a signal or other than SIGINT or SIGQUIT we're done. + if (!WIFSIGNALED(status) || (WTERMSIG(status) != SIGINT && WTERMSIG(status) != SIGQUIT)) { + return; } + if (is_interactive_session) { + // In an interactive session, tell the principal parser to skip all blocks we're executing + // so control-C returns control to the user. + if (p && found_proc) parser_t::skip_all_blocks(); + } else { + // Deliver the SIGINT or SIGQUIT signal to ourself since we're not interactive. + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = SIG_DFL; + sigaction(SIGINT, &act, 0); + sigaction(SIGQUIT, &act, 0); + kill(getpid(), WTERMSIG(status)); + } + +#if 0 + // TODO: Decide whether to eliminate this block or have it emit a warning message. + // WARNING: See the special short-circuit logic above vis-a-vis signals. if (!found_proc) { // A child we lost track of? There have been bugs in both subshell handling and in builtin // handling that have caused this previously... } +#endif return; } @@ -391,7 +391,7 @@ typedef unsigned int process_generation_count_t; /// A static value tracking how many SIGCHLDs we have seen. This is only ever modified from within /// the SIGCHLD signal handler, and therefore does not need atomics or locks. -static volatile process_generation_count_t s_sigchld_generation_count = 0; +static volatile process_generation_count_t s_sigchld_generation_cnt = 0; /// If we have received a SIGCHLD signal, process any children. If await is false, this returns /// immediately if no SIGCHLD has been received. If await is true, this waits for one. Returns true @@ -400,10 +400,10 @@ static int process_mark_finished_children(bool wants_await) { ASSERT_IS_MAIN_THREAD(); // A static value tracking the SIGCHLD gen count at the time we last processed it. When this is - // different from s_sigchld_generation_count, it indicates there may be unreaped processes. + // different from s_sigchld_generation_cnt, it indicates there may be unreaped processes. // There may not be if we reaped them via the other waitpid path. This is only ever modified // from the main thread, and not from a signal handler. - static process_generation_count_t s_last_processed_sigchld_generation_count = 0; + static process_generation_count_t s_last_sigchld_generation_cnt = 0; int processed_count = 0; bool got_error = false; @@ -412,12 +412,12 @@ static int process_mark_finished_children(bool wants_await) { // needs to be an atomic read (we'd use sig_atomic_t, if we knew that were unsigned - // fortunately aligned unsigned int is atomic on pretty much any modern chip.) It also needs to // occur before we start reaping, since the signal handler can be invoked at any point. - const process_generation_count_t local_count = s_sigchld_generation_count; + const process_generation_count_t local_count = s_sigchld_generation_cnt; // Determine whether we have children to process. Note that we can't reliably use the difference // because a single SIGCHLD may be delivered for multiple children - see #1768. Also if we are // awaiting, we always process. - bool wants_waitpid = wants_await || local_count != s_last_processed_sigchld_generation_count; + bool wants_waitpid = wants_await || local_count != s_last_sigchld_generation_cnt; if (wants_waitpid) { for (;;) { @@ -451,7 +451,7 @@ static int process_mark_finished_children(bool wants_await) { if (got_error) { return -1; } - s_last_processed_sigchld_generation_count = local_count; + s_last_sigchld_generation_cnt = local_count; return processed_count; } @@ -461,7 +461,7 @@ void job_handle_signal(int signal, siginfo_t *info, void *context) { UNUSED(info); UNUSED(context); // This is the only place that this generation count is modified. It's OK if it overflows. - s_sigchld_generation_count += 1; + s_sigchld_generation_cnt += 1; } /// Given a command like "cat file", truncate it to a reasonable length. @@ -568,62 +568,57 @@ int job_reap(bool allow_interactive) { proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, p->pid, (WIFSIGNALED(s) ? -1 : WEXITSTATUS(s))); - if (WIFSIGNALED(s)) { - // Ignore signal SIGPIPE.We issue it ourselves to the pipe writer when the pipe - // reader dies. - if (WTERMSIG(s) != SIGPIPE) { - int proc_is_job = ((p == j->first_process) && (p->next == 0)); - if (proc_is_job) job_set_flag(j, JOB_NOTIFIED, 1); - if (!job_get_flag(j, JOB_SKIP_NOTIFICATION)) { - // Print nothing if we get SIGINT in the foreground process group, to avoid - // spamming obvious stuff on the console (#1119). If we get SIGINT for the - // foreground process, assume the user typed ^C and can see it working. It's - // possible they didn't, and the signal was delivered via pkill, etc., but - // the SIGINT/SIGTERM distinction is precisely to allow INT to be from a UI - // and TERM to be programmatic, so this assumption is keeping with the - // design of signals. If echoctl is on, then the terminal will have written - // ^C to the console. If off, it won't have. We don't echo ^C either way, so - // as to respect the user's preference. - if (WTERMSIG(p->status) != SIGINT || !job_get_flag(j, JOB_FOREGROUND)) { - if (proc_is_job) { - // We want to report the job number, unless it's the only job, in - // which case we don't need to. - const wcstring job_number_desc = - (job_count == 1) ? wcstring() - : format_string(L"Job %d, ", j->job_id); - fwprintf(stdout, - _(L"%ls: %ls\'%ls\' terminated by signal %ls (%ls)"), - program_name, job_number_desc.c_str(), - truncate_command(j->command()).c_str(), - sig2wcs(WTERMSIG(p->status)), - signal_get_desc(WTERMSIG(p->status))); - } else { - const wcstring job_number_desc = - (job_count == 1) ? wcstring() - : format_string(L"from job %d, ", j->job_id); - fwprintf(stdout, _(L"%ls: Process %d, \'%ls\' %ls\'%ls\' " - L"terminated by signal %ls (%ls)"), - program_name, p->pid, p->argv0(), job_number_desc.c_str(), - truncate_command(j->command()).c_str(), - sig2wcs(WTERMSIG(p->status)), - signal_get_desc(WTERMSIG(p->status))); - } - - if (cur_term != NULL) - tputs(clr_eol, 1, &writeb); - else - fwprintf(stdout, - L"\x1b[K"); // no term set up - do clr_eol manually - - fwprintf(stdout, L"\n"); - } - found = 1; - } - - // Clear status so it is not reported more than once. - p->status = 0; - } + // Ignore signal SIGPIPE.We issue it ourselves to the pipe writer when the pipe reader + // dies. + if (!WIFSIGNALED(s) || WTERMSIG(s) == SIGPIPE) { + continue; } + + // Handle signals other than SIGPIPE. + int proc_is_job = ((p == j->first_process) && (p->next == 0)); + if (proc_is_job) job_set_flag(j, JOB_NOTIFIED, 1); + if (job_get_flag(j, JOB_SKIP_NOTIFICATION)) { + continue; + } + + // Print nothing if we get SIGINT in the foreground process group, to avoid spamming + // obvious stuff on the console (#1119). If we get SIGINT for the foreground + // process, assume the user typed ^C and can see it working. It's possible they + // didn't, and the signal was delivered via pkill, etc., but the SIGINT/SIGTERM + // distinction is precisely to allow INT to be from a UI + // and TERM to be programmatic, so this assumption is keeping with the design of + // signals. If echoctl is on, then the terminal will have written ^C to the console. + // If off, it won't have. We don't echo ^C either way, so as to respect the user's + // preference. + if (WTERMSIG(p->status) != SIGINT || !job_get_flag(j, JOB_FOREGROUND)) { + if (proc_is_job) { + // We want to report the job number, unless it's the only job, in which case + // we don't need to. + const wcstring job_number_desc = + (job_count == 1) ? wcstring() : format_string(L"Job %d, ", j->job_id); + fwprintf(stdout, _(L"%ls: %ls\'%ls\' terminated by signal %ls (%ls)"), + program_name, job_number_desc.c_str(), + truncate_command(j->command()).c_str(), sig2wcs(WTERMSIG(p->status)), + signal_get_desc(WTERMSIG(p->status))); + } else { + const wcstring job_number_desc = + (job_count == 1) ? wcstring() : format_string(L"from job %d, ", j->job_id); + fwprintf(stdout, _(L"%ls: Process %d, \'%ls\' %ls\'%ls\' " + L"terminated by signal %ls (%ls)"), + program_name, p->pid, p->argv0(), job_number_desc.c_str(), + truncate_command(j->command()).c_str(), sig2wcs(WTERMSIG(p->status)), + signal_get_desc(WTERMSIG(p->status))); + } + + if (cur_term != NULL) { + tputs(clr_eol, 1, &writeb); + } else { + fwprintf(stdout, L"\x1b[K"); // no term set up - do clr_eol manually + } + fwprintf(stdout, L"\n"); + } + found = 1; + p->status = 0; // clear status so it is not reported more than once } // If all processes have completed, tell the user the job has completed and delete it from @@ -803,13 +798,10 @@ static bool terminal_give_to_job(job_t *j, int cont) { return false; } - if (cont) { - if (tcsetattr(0, TCSADRAIN, &j->tmodes)) { - debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, - j->command_wcstr()); - wperror(L"tcsetattr"); - return false; - } + if (cont && tcsetattr(0, TCSADRAIN, &j->tmodes)) { + debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr()); + wperror(L"tcsetattr"); + return false; } return true; } @@ -908,13 +900,11 @@ void job_continue(job_t *j, bool cont) { process_mark_finished_children(false); break; } - case 0: { // No FDs are ready. Look for finished processes. process_mark_finished_children(false); break; } - case -1: { // If there is no funky IO magic, we can use waitpid instead of handling // child deaths through signals. This gives a rather large speed boost (A @@ -925,6 +915,10 @@ void job_continue(job_t *j, bool cont) { process_mark_finished_children(true); break; } + default: { + DIE("unexpected return value from select_try()"); + break; + } } } } @@ -944,15 +938,13 @@ void job_continue(job_t *j, bool cont) { process_t *p = j->first_process; while (p->next) p = p->next; - if (WIFEXITED(p->status) || WIFSIGNALED(p->status)) { - // Mark process status only if we are in the foreground and the last process in a - // pipe, and it is not a short circuited builtin. - if (p->pid) { - int status = proc_format_status(p->status); - // wprintf(L"setting status %d for %ls\n", job_get_flag( j, JOB_NEGATE - // )?!status:status, j->command); - proc_set_last_status(job_get_flag(j, JOB_NEGATE) ? !status : status); - } + // Mark process status only if we are in the foreground and the last process in a pipe, + // and it is not a short circuited builtin. + if ((WIFEXITED(p->status) || WIFSIGNALED(p->status)) && p->pid) { + int status = proc_format_status(p->status); + // wprintf(L"setting status %d for %ls\n", job_get_flag( j, JOB_NEGATE + // )?!status:status, j->command); + proc_set_last_status(job_get_flag(j, JOB_NEGATE) ? !status : status); } } diff --git a/src/reader.cpp b/src/reader.cpp index d044a1753..f3886668a 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -135,8 +135,8 @@ static void set_command_line_and_position(editable_line_t *el, const wcstring &n void editable_line_t::insert_string(const wcstring &str, size_t start, size_t len) { // Clamp the range to something valid. size_t string_length = str.size(); - start = mini(start, string_length); - len = mini(len, string_length - start); + start = mini(start, string_length); //!OCLINT(parameter reassignment) + len = mini(len, string_length - start); //!OCLINT(parameter reassignment) this->text.insert(this->position, str, start, len); this->position += len; } @@ -300,7 +300,7 @@ static wchar_t unescaped_quote(const wcstring &str, size_t pos); static struct termios terminal_mode_on_startup; /// Mode we use to execute programs. -static struct termios terminal_mode_for_executing_programs; +static struct termios tty_modes_for_external_cmds; static void reader_super_highlight_me_plenty(int highlight_pos_adjust = 0, bool no_io = false); @@ -312,7 +312,7 @@ static void term_donate() { set_color(rgb_color_t::normal(), rgb_color_t::normal()); while (1) { - if (tcsetattr(0, TCSANOW, &terminal_mode_for_executing_programs)) { + if (tcsetattr(0, TCSANOW, &tty_modes_for_external_cmds)) { if (errno != EINTR) { debug(1, _(L"Could not set terminal mode for new job")); wperror(L"tcsetattr"); @@ -668,20 +668,16 @@ void reader_write_title(const wcstring &cmd, bool reset_cursor_position) { if (term_str.missing()) return; const wchar_t *term = term_str.c_str(); - bool recognized = false; - recognized = recognized || contains(term, L"xterm", L"screen", L"nxterm", L"rxvt"); + bool recognized = contains(term, L"xterm", L"screen", L"tmux", L"nxterm", L"rxvt"); recognized = recognized || !wcsncmp(term, L"xterm-", wcslen(L"xterm-")); recognized = recognized || !wcsncmp(term, L"screen-", wcslen(L"screen-")); + recognized = recognized || !wcsncmp(term, L"tmux-", wcslen(L"tmux-")); if (!recognized) { char *n = ttyname(STDIN_FILENO); - if (contains(term, L"linux")) { - return; - } - + if (contains(term, L"linux")) return; if (contains(term, L"dumb")) return; - if (strstr(n, "tty") || strstr(n, "/vc/")) return; } @@ -697,14 +693,13 @@ void reader_write_title(const wcstring &cmd, bool reset_cursor_position) { wcstring_list_t lst; proc_push_interactive(0); - if (exec_subshell(fish_title_command, lst, false /* do not apply exit status */) != -1) { - if (!lst.empty()) { - writestr(L"\x1b]0;"); - for (size_t i = 0; i < lst.size(); i++) { - writestr(lst.at(i).c_str()); - } - writestr(L"\7"); + if (exec_subshell(fish_title_command, lst, false /* ignore exit status */) != -1 && + !lst.empty()) { + writestr(L"\x1b]0;"); + for (size_t i = 0; i < lst.size(); i++) { + writestr(lst.at(i).c_str()); } + writestr(L"\7"); } proc_pop_interactive(); set_color(rgb_color_t::reset(), rgb_color_t::reset()); @@ -774,10 +769,10 @@ void reader_init() { tcgetattr(STDIN_FILENO, &terminal_mode_on_startup); // Set the mode used for program execution, initialized to the current mode. - memcpy(&terminal_mode_for_executing_programs, &terminal_mode_on_startup, - sizeof terminal_mode_for_executing_programs); - terminal_mode_for_executing_programs.c_iflag &= ~IXON; // disable flow control - terminal_mode_for_executing_programs.c_iflag &= ~IXOFF; // disable flow control + memcpy(&tty_modes_for_external_cmds, &terminal_mode_on_startup, + sizeof tty_modes_for_external_cmds); + tty_modes_for_external_cmds.c_iflag &= ~IXON; // disable flow control + tty_modes_for_external_cmds.c_iflag &= ~IXOFF; // disable flow control // Set the mode used for the terminal, initialized to the current mode. memcpy(&shell_modes, &terminal_mode_on_startup, sizeof shell_modes); @@ -883,9 +878,8 @@ static bool command_ends_paging(wchar_t c, bool focused_on_search_field) { case R_REPAINT: case R_SUPPRESS_AUTOSUGGESTION: case R_BEGINNING_OF_HISTORY: - case R_END_OF_HISTORY: - default: { - // These commands never do. + case R_END_OF_HISTORY: { + // These commands never end paging. return false; } case R_EXECUTE: { @@ -924,6 +918,7 @@ static bool command_ends_paging(wchar_t c, bool focused_on_search_field) { // These commands operate on the search field if that's where the focus is. return !focused_on_search_field; } + default: { return false; } } } @@ -1027,9 +1022,9 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag const wcstring &command_line, size_t *inout_cursor_pos, bool append_only) { const wchar_t *val = val_str.c_str(); - bool add_space = !(flags & COMPLETE_NO_SPACE); - bool do_replace = !!(flags & COMPLETE_REPLACES_TOKEN); - bool do_escape = !(flags & COMPLETE_DONT_ESCAPE); + bool add_space = !static_cast(flags & COMPLETE_NO_SPACE); + bool do_replace = static_cast(flags & COMPLETE_REPLACES_TOKEN); + bool do_escape = !static_cast(flags & COMPLETE_DONT_ESCAPE); const size_t cursor_pos = *inout_cursor_pos; bool back_into_trailing_quote = false; @@ -1046,7 +1041,7 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag if (do_escape) { // Respect COMPLETE_DONT_ESCAPE_TILDES. - bool no_tilde = !!(flags & COMPLETE_DONT_ESCAPE_TILDES); + bool no_tilde = static_cast(flags & COMPLETE_DONT_ESCAPE_TILDES); wcstring escaped = escape(val, ESCAPE_ALL | ESCAPE_NO_QUOTED | (no_tilde ? ESCAPE_NO_TILDE : 0)); sb.append(escaped); @@ -1363,12 +1358,12 @@ static fuzzy_match_type_t get_best_match_type(const std::vector &c /// through the completions. /// /// \param comp the list of completion strings -/// \param continue_after_prefix_insertion If we have a shared prefix, whether to print the list of +/// \param cont_after_prefix_insertion If we have a shared prefix, whether to print the list of /// completions after inserting it. /// /// Return true if we inserted text into the command line, false if we did not. static bool handle_completions(const std::vector &comp, - bool continue_after_prefix_insertion) { + bool cont_after_prefix_insertion) { bool done = false; bool success = false; const editable_line_t *el = &data->command_line; @@ -1380,148 +1375,146 @@ static bool handle_completions(const std::vector &comp, const wcstring tok(begin, end - begin); // Check trivial cases. - switch (comp.size()) { - case 0: { - // No suitable completions found, flash screen and return. - reader_flash(); - done = true; - success = false; - break; - } - case 1: { - // Exactly one suitable completion found - insert it. - const completion_t &c = comp.at(0); + int size = comp.size(); + if (size == 0) { + // No suitable completions found, flash screen and return. + reader_flash(); + done = true; + success = false; + } else if (size == 1) { + // Exactly one suitable completion found - insert it. + const completion_t &c = comp.at(0); - // If this is a replacement completion, check that we know how to replace it, e.g. that - // the token doesn't contain evil operators like {}. - if (!(c.flags & COMPLETE_REPLACES_TOKEN) || reader_can_replace(tok, c.flags)) { - completion_insert(c.completion.c_str(), c.flags); - } - done = true; - success = true; + // If this is a replacement completion, check that we know how to replace it, e.g. that + // the token doesn't contain evil operators like {}. + if (!(c.flags & COMPLETE_REPLACES_TOKEN) || reader_can_replace(tok, c.flags)) { + completion_insert(c.completion.c_str(), c.flags); + } + done = true; + success = true; + } + + if (done) { + return success; + } + + fuzzy_match_type_t best_match_type = get_best_match_type(comp); + + // Determine whether we are going to replace the token or not. If any commands of the best + // type do not require replacement, then ignore all those that want to use replacement. + bool will_replace_token = true; + for (size_t i = 0; i < comp.size(); i++) { + const completion_t &el = comp.at(i); + if (el.match.type <= best_match_type && !(el.flags & COMPLETE_REPLACES_TOKEN)) { + will_replace_token = false; break; } } - if (!done) { - fuzzy_match_type_t best_match_type = get_best_match_type(comp); + // Decide which completions survived. There may be a lot of them; it would be nice if we could + // figure out how to avoid copying them here. + std::vector surviving_completions; + for (size_t i = 0; i < comp.size(); i++) { + const completion_t &el = comp.at(i); + // Ignore completions with a less suitable match type than the best. + if (el.match.type > best_match_type) continue; - // Determine whether we are going to replace the token or not. If any commands of the best - // type do not require replacement, then ignore all those that want to use replacement. - bool will_replace_token = true; - for (size_t i = 0; i < comp.size(); i++) { - const completion_t &el = comp.at(i); - if (el.match.type <= best_match_type && !(el.flags & COMPLETE_REPLACES_TOKEN)) { - will_replace_token = false; - break; - } - } + // Only use completions that match replace_token. + bool completion_replace_token = static_cast(el.flags & COMPLETE_REPLACES_TOKEN); + if (completion_replace_token != will_replace_token) continue; - // Decide which completions survived. There may be a lot of them; it would be nice if we - // could figure out how to avoid copying them here. - std::vector surviving_completions; - for (size_t i = 0; i < comp.size(); i++) { - const completion_t &el = comp.at(i); - // Ignore completions with a less suitable match type than the best. - if (el.match.type > best_match_type) continue; + // Don't use completions that want to replace, if we cannot replace them. + if (completion_replace_token && !reader_can_replace(tok, el.flags)) continue; - // Only use completions that match replace_token. - bool completion_replace_token = !!(el.flags & COMPLETE_REPLACES_TOKEN); - if (completion_replace_token != will_replace_token) continue; + // This completion survived. + surviving_completions.push_back(el); + } - // Don't use completions that want to replace, if we cannot replace them. - if (completion_replace_token && !reader_can_replace(tok, el.flags)) continue; - - // This completion survived. - surviving_completions.push_back(el); - } - - bool use_prefix = false; - if (match_type_shares_prefix(best_match_type)) { - // Try to find a common prefix to insert among the surviving completions. - wcstring common_prefix; - complete_flags_t flags = 0; - bool prefix_is_partial_completion = false; - for (size_t i = 0; i < surviving_completions.size(); i++) { - const completion_t &el = surviving_completions.at(i); - if (i == 0) { - // First entry, use the whole string. - common_prefix = el.completion; - flags = el.flags; - } else { - // Determine the shared prefix length. - size_t idx, max = mini(common_prefix.size(), el.completion.size()); - for (idx = 0; idx < max; idx++) { - wchar_t ac = common_prefix.at(idx), bc = el.completion.at(idx); - bool matches = (ac == bc); - // If we are replacing the token, allow case to vary. - if (will_replace_token && !matches) { - // Hackish way to compare two strings in a case insensitive way, - // hopefully better than towlower(). - matches = (wcsncasecmp(&ac, &bc, 1) == 0); - } - if (!matches) break; - } - - // idx is now the length of the new common prefix. - common_prefix.resize(idx); - prefix_is_partial_completion = true; - - // Early out if we decide there's no common prefix. - if (idx == 0) break; - } - } - - // Determine if we use the prefix. We use it if it's non-empty and it will actually make - // the command line longer. It may make the command line longer by virtue of not using - // REPLACE_TOKEN (so it always appends to the command line), or by virtue of replacing - // the token but being longer than it. - use_prefix = common_prefix.size() > (will_replace_token ? tok.size() : 0); - assert(!use_prefix || !common_prefix.empty()); - - if (use_prefix) { - // We got something. If more than one completion contributed, then it means we have - // a prefix; don't insert a space after it. - if (prefix_is_partial_completion) flags |= COMPLETE_NO_SPACE; - completion_insert(common_prefix.c_str(), flags); - success = true; - } - } - - if (continue_after_prefix_insertion || !use_prefix) { - // We didn't get a common prefix, or we want to print the list anyways. - size_t len, prefix_start = 0; - wcstring prefix; - parse_util_get_parameter_info(el->text, el->position, NULL, &prefix_start, NULL); - - assert(el->position >= prefix_start); - len = el->position - prefix_start; - - if (will_replace_token || match_type_requires_full_replacement(best_match_type)) { - // No prefix. - prefix.clear(); - } else if (len <= PREFIX_MAX_LEN) { - prefix.append(el->text, prefix_start, len); + bool use_prefix = false; + if (match_type_shares_prefix(best_match_type)) { + // Try to find a common prefix to insert among the surviving completions. + wcstring common_prefix; + complete_flags_t flags = 0; + bool prefix_is_partial_completion = false; + for (size_t i = 0; i < surviving_completions.size(); i++) { + const completion_t &el = surviving_completions.at(i); + if (i == 0) { + // First entry, use the whole string. + common_prefix = el.completion; + flags = el.flags; } else { - // Append just the end of the string. - prefix = wcstring(&ellipsis_char, 1); - prefix.append(el->text, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); - } + // Determine the shared prefix length. + size_t idx, max = mini(common_prefix.size(), el.completion.size()); + for (idx = 0; idx < max; idx++) { + wchar_t ac = common_prefix.at(idx), bc = el.completion.at(idx); + bool matches = (ac == bc); + // If we are replacing the token, allow case to vary. + if (will_replace_token && !matches) { + // Hackish way to compare two strings in a case insensitive way, + // hopefully better than towlower(). + matches = (wcsncasecmp(&ac, &bc, 1) == 0); + } + if (!matches) break; + } - wchar_t quote; - parse_util_get_parameter_info(el->text, el->position, "e, NULL, NULL); - // Update the pager data. - data->pager.set_prefix(prefix); - data->pager.set_completions(surviving_completions); - // Invalidate our rendering. - data->current_page_rendering = page_rendering_t(); - // Modify the command line to reflect the new pager. - data->pager_selection_changed(); - reader_repaint_needed(); - success = false; + // idx is now the length of the new common prefix. + common_prefix.resize(idx); + prefix_is_partial_completion = true; + + // Early out if we decide there's no common prefix. + if (idx == 0) break; + } + } + + // Determine if we use the prefix. We use it if it's non-empty and it will actually make + // the command line longer. It may make the command line longer by virtue of not using + // REPLACE_TOKEN (so it always appends to the command line), or by virtue of replacing + // the token but being longer than it. + use_prefix = common_prefix.size() > (will_replace_token ? tok.size() : 0); + assert(!use_prefix || !common_prefix.empty()); + + if (use_prefix) { + // We got something. If more than one completion contributed, then it means we have + // a prefix; don't insert a space after it. + if (prefix_is_partial_completion) flags |= COMPLETE_NO_SPACE; + completion_insert(common_prefix.c_str(), flags); + success = true; } } - return success; + + if (!cont_after_prefix_insertion && use_prefix) { + return success; + } + + // We didn't get a common prefix, or we want to print the list anyways. + size_t len, prefix_start = 0; + wcstring prefix; + parse_util_get_parameter_info(el->text, el->position, NULL, &prefix_start, NULL); + + assert(el->position >= prefix_start); + len = el->position - prefix_start; + + if (will_replace_token || match_type_requires_full_replacement(best_match_type)) { + prefix.clear(); // no prefix + } else if (len <= PREFIX_MAX_LEN) { + prefix.append(el->text, prefix_start, len); + } else { + // Append just the end of the string. + prefix = wcstring(&ellipsis_char, 1); + prefix.append(el->text, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); + } + + wchar_t quote; + parse_util_get_parameter_info(el->text, el->position, "e, NULL, NULL); + // Update the pager data. + data->pager.set_prefix(prefix); + data->pager.set_completions(surviving_completions); + // Invalidate our rendering. + data->current_page_rendering = page_rendering_t(); + // Modify the command line to reflect the new pager. + data->pager_selection_changed(); + reader_repaint_needed(); + return false; } /// Return true if we believe ourselves to be orphaned. loop_count is how many times we've tried to @@ -1531,10 +1524,8 @@ static bool check_for_orphaned_process(unsigned long loop_count, pid_t shell_pgi // Try kill-0'ing the process whose pid corresponds to our process group ID. It's possible this // will fail because we don't have permission to signal it. But more likely it will fail because // it no longer exists, and we are orphaned. - if (loop_count % 64 == 0) { - if (kill(shell_pgid, 0) < 0 && errno == ESRCH) { - we_think_we_are_orphaned = true; - } + if (loop_count % 64 == 0 && kill(shell_pgid, 0) < 0 && errno == ESRCH) { + we_think_we_are_orphaned = true; } if (!we_think_we_are_orphaned && loop_count % 128 == 0) { @@ -1646,12 +1637,10 @@ static void reader_interactive_init() { // Put ourselves in our own process group. shell_pgid = getpid(); - if (getpgrp() != shell_pgid) { - if (setpgid(shell_pgid, shell_pgid) < 0) { - debug(1, _(L"Couldn't put the shell in its own process group")); - wperror(L"setpgid"); - exit_without_destructors(1); - } + if (getpgrp() != shell_pgid && setpgid(shell_pgid, shell_pgid) < 0) { + debug(1, _(L"Couldn't put the shell in its own process group")); + wperror(L"setpgid"); + exit_without_destructors(1); } // Grab control of the terminal. @@ -1795,24 +1784,22 @@ static void handle_token_history(int forward, int reset) { tokenizer_t tok(data->token_history_buff.c_str(), TOK_ACCEPT_UNFINISHED); tok_t token; while (tok.next(&token)) { - switch (token.type) { - case TOK_STRING: { - if (token.text.find(data->search_buff) != wcstring::npos) { - // debug( 3, L"Found token at pos %d\n", tok_get_pos( &tok ) ); - if (token.offset >= current_pos) { - break; - } - // debug( 3, L"ok pos" ); - - if (find(data->search_prev.begin(), data->search_prev.end(), - token.text) == data->search_prev.end()) { - data->token_history_pos = token.offset; - str = token.text; - } + if (token.type == TOK_STRING) { + if (token.text.find(data->search_buff) != wcstring::npos) { + // debug( 3, L"Found token at pos %d\n", tok_get_pos( &tok ) ); + if (token.offset >= current_pos) { + break; + } + // debug( 3, L"ok pos" ); + + if (find(data->search_prev.begin(), data->search_prev.end(), token.text) == + data->search_prev.end()) { + data->token_history_pos = token.offset; + str = token.text; } - break; } - default: { break; } + } else { + break; } } } @@ -1898,7 +1885,7 @@ static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos) { data->command_line_changed(&data->command_line); // Don't set a position past the command line length. - if (pos > command_line_len) pos = command_line_len; + if (pos > command_line_len) pos = command_line_len; //!OCLINT(parameter reassignment) update_buff_pos(&data->command_line, pos); @@ -2214,17 +2201,17 @@ static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust, boo const wcstring &cmd = el->text, &suggest = data->autosuggestion; if (can_autosuggest() && !suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest)) { - // The autosuggestion is still reasonable, so do nothing. + ; // the autosuggestion is still reasonable, so do nothing } else { update_autosuggestion(); } } bool shell_is_exiting() { - if (shell_is_interactive()) + if (shell_is_interactive()) { return job_list_is_empty() && data != NULL && data->end_loop; - else - return end_loop; + } + return end_loop; } /// This function is called when the main loop notices that end_loop has been set while in @@ -2351,20 +2338,6 @@ static int can_read(int fd) { return select(fd + 1, &fds, 0, 0, &can_read_timeout) == 1; } -// Test if the specified character is in a range that fish uses interally to store special tokens. -// -// NOTE: This is used when tokenizing the input. It is also used when reading input, before -// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted -// string. We don't want external input to be able to feed reserved characters into our lexer/parser -// or code evaluator. -// -// TODO: Actually implement the replacement as documented above. -static int wchar_private(wchar_t c) { - return (c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END) || - (c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END) || - (c >= INPUT_COMMON_BASE && c < INPUT_COMMON_END); -} - /// Test if the specified character in the specified string is backslashed. pos may be at the end of /// the string, which indicates if there is a trailing backslash. static bool is_backslashed(const wcstring &str, size_t pos) { @@ -2396,7 +2369,7 @@ static bool text_ends_in_comment(const wcstring &text) { tokenizer_t tok(text.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SHOW_COMMENTS | TOK_SQUASH_ERRORS); tok_t token; while (tok.next(&token)) { - // pass + ; // pass } return token.type == TOK_COMMENT; } @@ -2452,42 +2425,40 @@ const wchar_t *reader_readline(int nchars) { is_interactive_read = was_interactive_read; // fprintf(stderr, "C: %lx\n", (long)c); - if (((!wchar_private(c))) && (c > 31) && (c != 127)) { - if (can_read(0)) { - wchar_t arr[READAHEAD_MAX + 1]; - size_t i; - size_t limit = 0 < nchars ? std::min((size_t)nchars - data->command_line.size(), - (size_t)READAHEAD_MAX) - : READAHEAD_MAX; + if (((!fish_reserved_codepoint(c))) && (c > 31) && (c != 127) && can_read(0)) { + wchar_t arr[READAHEAD_MAX + 1]; + size_t i; + size_t limit = 0 < nchars ? std::min((size_t)nchars - data->command_line.size(), + (size_t)READAHEAD_MAX) + : READAHEAD_MAX; - memset(arr, 0, sizeof(arr)); - arr[0] = c; + memset(arr, 0, sizeof(arr)); + arr[0] = c; - for (i = 1; i < limit; ++i) { - if (!can_read(0)) { - c = 0; - break; - } - // Only allow commands on the first key; otherwise, we might have data we - // need to insert on the commandline that the commmand might need to be able - // to see. - c = input_readch(false); - if ((!wchar_private(c)) && (c > 31) && (c != 127)) { - arr[i] = c; - c = 0; - } else - break; + for (i = 1; i < limit; ++i) { + if (!can_read(0)) { + c = 0; + break; } - - editable_line_t *el = data->active_edit_line(); - insert_string(el, arr, true); - - // End paging upon inserting into the normal command line. - if (el == &data->command_line) { - clear_pager(); - } - last_char = c; + // Only allow commands on the first key; otherwise, we might have data we + // need to insert on the commandline that the commmand might need to be able + // to see. + c = input_readch(false); + if (!fish_reserved_codepoint(c) && c > 31 && c != 127) { + arr[i] = c; + c = 0; + } else + break; } + + editable_line_t *el = data->active_edit_line(); + insert_string(el, arr, true); + + // End paging upon inserting into the normal command line. + if (el == &data->command_line) { + clear_pager(); + } + last_char = c; } if (c != 0) break; @@ -2649,8 +2620,8 @@ const wchar_t *reader_readline(int nchars) { data->cycle_command_line = el->text; data->cycle_cursor_pos = el->position; - bool continue_after_prefix_insertion = (c == R_COMPLETE_AND_SEARCH); - comp_empty = handle_completions(comp, continue_after_prefix_insertion); + bool cont_after_prefix_insertion = (c == R_COMPLETE_AND_SEARCH); + comp_empty = handle_completions(comp, cont_after_prefix_insertion); // Show the search field if requested and if we printed a list of completions. if (c == R_COMPLETE_AND_SEARCH && !comp_empty && !data->pager.empty()) { @@ -2679,26 +2650,24 @@ const wchar_t *reader_readline(int nchars) { } case R_BACKWARD_KILL_LINE: { editable_line_t *el = data->active_edit_line(); - if (el->position > 0) { - const wchar_t *buff = el->text.c_str(); - const wchar_t *end = &buff[el->position]; - const wchar_t *begin = end; - - begin--; // make sure we delete at least one character (see issue #580) - - // Delete until we hit a newline, or the beginning of the string. - while (begin > buff && *begin != L'\n') begin--; - - // If we landed on a newline, don't delete it. - if (*begin == L'\n') begin++; - - assert(end >= begin); - size_t len = maxi(end - begin, 1); - begin = end - len; - - reader_kill(el, begin - buff, len, KILL_PREPEND, - last_char != R_BACKWARD_KILL_LINE); + if (el->position <= 0) { + break; } + const wchar_t *buff = el->text.c_str(); + const wchar_t *end = &buff[el->position]; + const wchar_t *begin = end; + + begin--; // make sure we delete at least one character (see issue #580) + + // Delete until we hit a newline, or the beginning of the string. + while (begin > buff && *begin != L'\n') begin--; + + // If we landed on a newline, don't delete it. + if (*begin == L'\n') begin++; + assert(end >= begin); + size_t len = maxi(end - begin, 1); + begin = end - len; + reader_kill(el, begin - buff, len, KILL_PREPEND, last_char != R_BACKWARD_KILL_LINE); break; } case R_KILL_WHOLE_LINE: { @@ -2835,31 +2804,24 @@ const wchar_t *reader_readline(int nchars) { } } - switch (command_test_result) { - case 0: { - // Finished command, execute it. Don't add items that start with a leading - // space. - const editable_line_t *el = &data->command_line; - if (data->history != NULL && !el->empty() && el->text.at(0) != L' ') { - data->history->add_pending_with_file_detection(el->text); - } - finished = 1; - update_buff_pos(&data->command_line, data->command_line.size()); - reader_repaint(); - break; - } - case PARSER_TEST_INCOMPLETE: { - // We are incomplete, continue editing. - insert_char(el, '\n'); - break; - } - default: { - // Result must be some combination including an error. The error message - // will already be printed, all we need to do is repaint. - s_reset(&data->screen, screen_reset_abandon_line); - reader_repaint_needed(); - break; + if (command_test_result == 0) { + // Finished command, execute it. Don't add items that start with a leading + // space. + const editable_line_t *el = &data->command_line; + if (data->history != NULL && !el->empty() && el->text.at(0) != L' ') { + data->history->add_pending_with_file_detection(el->text); } + finished = 1; + update_buff_pos(&data->command_line, data->command_line.size()); + reader_repaint(); + } else if (command_test_result == PARSER_TEST_INCOMPLETE) { + // We are incomplete, continue editing. + insert_char(el, '\n'); + } else { + // Result must be some combination including an error. The error message will + // already be printed, all we need to do is repaint. + s_reset(&data->screen, screen_reset_abandon_line); + reader_repaint_needed(); } break; @@ -2894,36 +2856,30 @@ const wchar_t *reader_readline(int nchars) { data->history_search.skip_matches(skip_list); } - switch (data->search_mode) { - case LINE_SEARCH: { - if ((c == R_HISTORY_SEARCH_BACKWARD) || - (c == R_HISTORY_TOKEN_SEARCH_BACKWARD)) { - data->history_search.go_backwards(); - } else { - if (!data->history_search.go_forwards()) { - // If you try to go forwards past the end, we just go to the end. - data->history_search.go_to_end(); - } + if (data->search_mode == LINE_SEARCH) { + if ((c == R_HISTORY_SEARCH_BACKWARD) || + (c == R_HISTORY_TOKEN_SEARCH_BACKWARD)) { + data->history_search.go_backwards(); + } else { + if (!data->history_search.go_forwards()) { + // If you try to go forwards past the end, we just go to the end. + data->history_search.go_to_end(); } - - wcstring new_text; - if (data->history_search.is_at_end()) { - new_text = data->search_buff; - } else { - new_text = data->history_search.current_string(); - } - set_command_line_and_position(&data->command_line, new_text, - new_text.size()); - break; } - case TOKEN_SEARCH: { - if ((c == R_HISTORY_SEARCH_BACKWARD) || - (c == R_HISTORY_TOKEN_SEARCH_BACKWARD)) { - handle_token_history(SEARCH_BACKWARD, reset); - } else { - handle_token_history(SEARCH_FORWARD, reset); - } - break; + + wcstring new_text; + if (data->history_search.is_at_end()) { + new_text = data->search_buff; + } else { + new_text = data->history_search.current_string(); + } + set_command_line_and_position(&data->command_line, new_text, new_text.size()); + } else if (data->search_mode == TOKEN_SEARCH) { + if ((c == R_HISTORY_SEARCH_BACKWARD) || + (c == R_HISTORY_TOKEN_SEARCH_BACKWARD)) { + handle_token_history(SEARCH_BACKWARD, reset); + } else { + handle_token_history(SEARCH_FORWARD, reset); } } break; @@ -3260,7 +3216,8 @@ const wchar_t *reader_readline(int nchars) { } default: { // Other, if a normal character, we add it to the command. - if (!wchar_private(c) && (c >= L' ' || c == L'\n' || c == L'\r') && c != 0x7F) { + if (!fish_reserved_codepoint(c) && (c >= L' ' || c == L'\n' || c == L'\r') && + c != 0x7F) { bool allow_expand_abbreviations = false; if (data->is_navigating_pager_contents()) { data->pager.set_search_field_shown(true); @@ -3323,7 +3280,7 @@ int reader_search_mode() { if (!data) { return -1; } - return !!data->search_mode; + return data->search_mode == NO_SEARCH ? 0 : 1; } int reader_has_pager_contents() { diff --git a/src/screen.cpp b/src/screen.cpp index a199672fc..54a1203f3 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -105,24 +105,30 @@ static bool allow_soft_wrap(void) { /// Does this look like the escape sequence for setting a screen name. static bool is_screen_name_escape_seq(const wchar_t *code, size_t *resulting_length) { - bool found = false; - if (code[1] == L'k') { - const env_var_t term_name = env_get_string(L"TERM"); - if (!term_name.missing() && string_prefixes_string(L"screen", term_name)) { - const wchar_t *const screen_name_end_sentinel = L"\x1b\\"; - const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel); - if (screen_name_end == NULL) { - // Consider just k to be the code. - *resulting_length = 2; - } else { - const wchar_t *escape_sequence_end = - screen_name_end + wcslen(screen_name_end_sentinel); - *resulting_length = escape_sequence_end - code; - } - found = true; - } + if (code[1] != L'k') { + return false; } - return found; + +#if 0 + // TODO: Decide if this should be removed or modified to also test for TERM values that begin + // with "tmux". See issue #3512. + const env_var_t term_name = env_get_string(L"TERM"); + if (term_name.missing() || !string_prefixes_string(L"screen", term_name)) { + return false; + } +#endif + + const wchar_t *const screen_name_end_sentinel = L"\x1b\\"; + const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel); + if (screen_name_end == NULL) { + // Consider just k to be the code. + *resulting_length = 2; + } else { + const wchar_t *escape_sequence_end = + screen_name_end + wcslen(screen_name_end_sentinel); + *resulting_length = escape_sequence_end - code; + } + return true; } /// iTerm2 escape codes: CSI followed by ], terminated by either BEL or escape + backslash. @@ -169,28 +175,28 @@ static bool is_two_byte_escape_seq(const wchar_t *code, size_t *resulting_length /// Generic VT100 CSI-style sequence. , followed by zero or more ASCII characters NOT in /// the range [@,_], followed by one character in that range. static bool is_csi_style_escape_seq(const wchar_t *code, size_t *resulting_length) { - bool found = false; - if (code[1] == L'[') { - // Start at 2 to skip over [ - size_t cursor = 2; - for (; code[cursor] != L'\0'; cursor++) { - // Consume a sequence of ASCII characters not in the range [@, ~]. - wchar_t widechar = code[cursor]; - - // If we're not in ASCII, just stop. - if (widechar > 127) break; - - // If we're the end character, then consume it and then stop. - if (widechar >= L'@' && widechar <= L'~') { - cursor++; - break; - } - } - // curs now indexes just beyond the end of the sequence (or at the terminating zero). - found = true; - *resulting_length = cursor; + if (code[1] != L'[') { + return false; } - return found; + + // Start at 2 to skip over [ + size_t cursor = 2; + for (; code[cursor] != L'\0'; cursor++) { + // Consume a sequence of ASCII characters not in the range [@, ~]. + wchar_t widechar = code[cursor]; + + // If we're not in ASCII, just stop. + if (widechar > 127) break; + + // If we're the end character, then consume it and then stop. + if (widechar >= L'@' && widechar <= L'~') { + cursor++; + break; + } + } + // cursor now indexes just beyond the end of the sequence (or at the terminating zero). + *resulting_length = cursor; + return true; } /// Returns the number of characters in the escape code starting at 'code' (which should initially @@ -817,7 +823,7 @@ static void s_update(screen_t *scr, const wchar_t *left_prompt, const wchar_t *r } // Output any rprompt if this is the first line. - if (i == 0 && right_prompt_width > 0) { + if (i == 0 && right_prompt_width > 0) { //!OCLINT(Use early exit/continue) s_move(scr, &output, (int)(screen_width - right_prompt_width), (int)i); s_set_color(scr, &output, 0xffffffff); s_write_str(&output, right_prompt); diff --git a/src/signal.cpp b/src/signal.cpp index a20f51cc4..a718d250e 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -403,4 +403,4 @@ void signal_unblock() { // debug( 0, L"signal block level decreased to %d", block_count ); } -bool signal_is_blocked() { return !!block_count; } +bool signal_is_blocked() { return static_cast(block_count); } diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index cc19615c3..29d11639c 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -64,10 +64,10 @@ tokenizer_t::tokenizer_t(const wchar_t *b, tok_flags_t flags) continue_line_after_comment(false) { assert(b != NULL); - this->accept_unfinished = !!(flags & TOK_ACCEPT_UNFINISHED); - this->show_comments = !!(flags & TOK_SHOW_COMMENTS); - this->squash_errors = !!(flags & TOK_SQUASH_ERRORS); - this->show_blank_lines = !!(flags & TOK_SHOW_BLANK_LINES); + this->accept_unfinished = static_cast(flags & TOK_ACCEPT_UNFINISHED); + this->show_comments = static_cast(flags & TOK_SHOW_COMMENTS); + this->squash_errors = static_cast(flags & TOK_SQUASH_ERRORS); + this->show_blank_lines = static_cast(flags & TOK_SHOW_BLANK_LINES); this->has_next = (*b != L'\0'); this->tok_next(); @@ -219,6 +219,7 @@ void tokenizer_t::read_string() { if (!tok_is_string_character(*(this->buff), is_first)) { do_loop = 0; } + break; } } break; @@ -265,6 +266,9 @@ void tokenizer_t::read_string() { do_loop = 0; break; } + default: { + break; // ignore other chars + } } break; } @@ -285,6 +289,9 @@ void tokenizer_t::read_string() { do_loop = 0; break; } + default: { + break; // ignore other chars + } } break; } @@ -318,7 +325,7 @@ void tokenizer_t::read_string() { break; } default: { - assert(0 && "Unexpected mode in read_string"); + DIE("unexpected mode in read_string"); break; } } @@ -706,10 +713,7 @@ bool move_word_state_machine_t::consume_char_path_components(wchar_t c) { break; } case s_end: - default: { - // We won't get here, but keep the compiler happy. - break; - } + default: { break; } } } return consumed; @@ -760,8 +764,9 @@ bool move_word_state_machine_t::consume_char(wchar_t c) { case move_word_style_whitespace: { return consume_char_whitespace(c); } - default: { abort(); } } + + DIE("should not reach this statement"); // silence some compiler errors about not returning } move_word_state_machine_t::move_word_state_machine_t(move_word_style_t syl) diff --git a/src/utf8.cpp b/src/utf8.cpp index 0e87e50ab..55d404fe8 100644 --- a/src/utf8.cpp +++ b/src/utf8.cpp @@ -20,6 +20,7 @@ #include #include +#include "common.h" #include "utf8.h" #define _NXT 0x80 @@ -81,7 +82,7 @@ size_t utf8_to_wchar(const char *in, size_t insize, std::wstring *out, int flags } size_t result; - if (sizeof(wchar_t) == sizeof(utf8_wchar_t)) { + if (sizeof(wchar_t) == sizeof(utf8_wchar_t)) { //!OCLINT(constant if expression) result = utf8_to_wchar_internal(in, insize, reinterpret_cast(out), flags); } else if (out == NULL) { result = utf8_to_wchar_internal(in, insize, NULL, flags); @@ -101,7 +102,7 @@ size_t wchar_to_utf8(const wchar_t *in, size_t insize, char *out, size_t outsize } size_t result; - if (sizeof(wchar_t) == sizeof(utf8_wchar_t)) { + if (sizeof(wchar_t) == sizeof(utf8_wchar_t)) { //!OCLINT(constant if expression) result = wchar_to_utf8_internal(reinterpret_cast(in), insize, out, outsize, flags); } else { @@ -137,9 +138,8 @@ static int __utf8_forbitten(unsigned char octet) { case 0xff: { return -1; } + default: { return 0; } } - - return 0; } /// This function translates UTF-8 string into UCS-2 or UCS-4 string (all symbols will be in local @@ -288,18 +288,17 @@ static size_t wchar_to_utf8_internal(const utf8_wchar_t *in, size_t insize, char if ((flags & UTF8_IGNORE_ERROR) == 0) return 0; continue; } - if (w_wide <= 0x0000007f) + if (w_wide <= 0x0000007f) { n = 1; - else if (w_wide <= 0x000007ff) + } else if (w_wide <= 0x000007ff) { n = 2; - else if (w_wide <= 0x0000ffff) + } else if (w_wide <= 0x0000ffff) { n = 3; - else if (w_wide <= 0x001fffff) + } else if (w_wide <= 0x001fffff) { n = 4; - else if (w_wide <= 0x03ffffff) - n = 5; - else - n = 6; /// if (w_wide <= 0x7fffffff) + } else { + DIE("invalid wide char"); + } total += n; @@ -340,10 +339,14 @@ static size_t wchar_to_utf8_internal(const utf8_wchar_t *in, size_t insize, char p[0] = _SEQ4 | ((oc[1] & 0x1f) >> 2); break; } + default: { + DIE("unexpected utff8 len"); + break; + } } - // NOTE: do not check here for forbitten UTF-8 characters. They cannot appear here because - // we do proper convertion. + // NOTE: do not check here for forbidden UTF-8 characters. They cannot appear here because + // we do proper conversion. p += n; } diff --git a/src/util.cpp b/src/util.cpp index f13334807..7caba32bc 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -47,8 +47,8 @@ int wcsfilecmp(const wchar_t *a, const wchar_t *b) { secondary_diff = (aend - a) - (bend - b); - a = aend - 1; - b = bend - 1; + a = aend - 1; //!OCLINT(parameter reassignment) + b = bend - 1; //!OCLINT(parameter reassignment) } else { int diff = towlower(*a) - towlower(*b); if (diff != 0) return diff > 0 ? 2 : -2; @@ -58,12 +58,10 @@ int wcsfilecmp(const wchar_t *a, const wchar_t *b) { int res = wcsfilecmp(a + 1, b + 1); - if (abs(res) < 2) { - // No primary difference in rest of string. Use secondary difference on this element if - // found. - if (secondary_diff) { - return secondary_diff > 0 ? 1 : -1; - } + // If no primary difference in rest of string use secondary difference on this element if + // found. + if (abs(res) < 2 && secondary_diff) { + return secondary_diff > 0 ? 1 : -1; } return res; diff --git a/src/wgetopt.cpp b/src/wgetopt.cpp index f31b623b0..dbb0d4393 100644 --- a/src/wgetopt.cpp +++ b/src/wgetopt.cpp @@ -161,8 +161,9 @@ const wchar_t *wgetopter_t::_wgetopt_initialize(const wchar_t *optstring) { } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; - } else + } else { ordering = PERMUTE; + } return optstring; } @@ -211,7 +212,7 @@ int wgetopter_t::_wgetopt_internal(int argc, wchar_t **argv, const wchar_t *opts const struct woption *longopts, int *longind, int long_only) { woptarg = NULL; - if (woptind == 0) optstring = _wgetopt_initialize(optstring); + if (woptind == 0) optstring = _wgetopt_initialize(optstring); //!OCLINT(parameter reassignment) if (nextchar == NULL || *nextchar == '\0') { // Advance to the next ARGV-element. @@ -380,59 +381,62 @@ int wgetopter_t::_wgetopt_internal(int argc, wchar_t **argv, const wchar_t *opts } // Look at and handle the next short option-character. - { - wchar_t c = *nextchar++; - wchar_t *temp = const_cast(my_index(optstring, c)); + wchar_t c = *nextchar++; + wchar_t *temp = const_cast(my_index(optstring, c)); - // Increment `woptind' when we start to process its last character. - if (*nextchar == '\0') ++woptind; + // Increment `woptind' when we start to process its last character. + if (*nextchar == '\0') ++woptind; - if (temp == NULL || c == ':') { - if (wopterr) { - fwprintf(stderr, _(L"%ls: Invalid option -- %lc\n"), argv[0], (wint_t)c); - } - woptopt = c; - - if (*nextchar != '\0') woptind++; - - return '?'; - } - if (temp[1] == ':') { - if (temp[2] == ':') { - // This is an option that accepts an argument optionally. - if (*nextchar != '\0') { - woptarg = nextchar; - woptind++; - } else - woptarg = NULL; - nextchar = NULL; - } else { - // This is an option that requires an argument. - if (*nextchar != '\0') { - woptarg = nextchar; - // If we end this ARGV-element by taking the rest as an arg, we must advance to - // the next element now. - woptind++; - } else if (woptind == argc) { - if (wopterr) { - // 1003.2 specifies the format of this message. - fwprintf(stderr, _(L"%ls: Option requires an argument -- %lc\n"), argv[0], - (wint_t)c); - } - woptopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - } else - // We already incremented `woptind' once; increment it again when taking next - // ARGV-elt as argument. - woptarg = argv[woptind++]; - nextchar = NULL; - } + if (temp == NULL || c == ':') { + if (wopterr) { + fwprintf(stderr, _(L"%ls: Invalid option -- %lc\n"), argv[0], (wint_t)c); } + woptopt = c; + + if (*nextchar != '\0') woptind++; + return '?'; + } + + if (temp[1] != ':') { return c; } + + if (temp[2] == ':') { + // This is an option that accepts an argument optionally. + if (*nextchar != '\0') { + woptarg = nextchar; + woptind++; + } else { + woptarg = NULL; + } + nextchar = NULL; + } else { + // This is an option that requires an argument. + if (*nextchar != '\0') { + woptarg = nextchar; + // If we end this ARGV-element by taking the rest as an arg, we must advance to + // the next element now. + woptind++; + } else if (woptind == argc) { + if (wopterr) { + // 1003.2 specifies the format of this message. + fwprintf(stderr, _(L"%ls: Option requires an argument -- %lc\n"), argv[0], + (wint_t)c); + } + woptopt = c; + if (optstring[0] == ':') { + c = ':'; + } else { + c = '?'; + } + } else { + // We already incremented `woptind' once; increment it again when taking next + // ARGV-elt as argument. + woptarg = argv[woptind++]; + } + nextchar = NULL; + } + return c; } int wgetopter_t::wgetopt_long(int argc, wchar_t **argv, const wchar_t *options, diff --git a/src/wildcard.cpp b/src/wildcard.cpp index 5d57ead80..cd8cb226d 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -216,22 +216,24 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc, match_acceptable = match_type_shares_prefix(match.type); } - if (match_acceptable && out != NULL) { - // Wildcard complete. - bool full_replacement = match_type_requires_full_replacement(match.type) || - (flags & COMPLETE_REPLACES_TOKEN); - - // If we are not replacing the token, be careful to only store the part of the string - // after the wildcard. - assert(!full_replacement || wcslen(wc) <= wcslen(str)); - wcstring out_completion = full_replacement ? params.orig : str + wcslen(wc); - wcstring out_desc = resolve_description(&out_completion, params.desc, params.desc_func); - - // Note: out_completion may be empty if the completion really is empty, e.g. - // tab-completing 'foo' when a file 'foo' exists. - complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0); - append_completion(out, out_completion, out_desc, local_flags, match); + if (!match_acceptable || out == NULL) { + return match_acceptable; } + + // Wildcard complete. + bool full_replacement = + match_type_requires_full_replacement(match.type) || (flags & COMPLETE_REPLACES_TOKEN); + + // If we are not replacing the token, be careful to only store the part of the string after + // the wildcard. + assert(!full_replacement || wcslen(wc) <= wcslen(str)); + wcstring out_completion = full_replacement ? params.orig : str + wcslen(wc); + wcstring out_desc = resolve_description(&out_completion, params.desc, params.desc_func); + + // Note: out_completion may be empty if the completion really is empty, e.g. tab-completing + // 'foo' when a file 'foo' exists. + complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0); + append_completion(out, out_completion, out_desc, local_flags, match); return match_acceptable; } else if (next_wc_char_pos > 0) { // Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before @@ -289,10 +291,13 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc, // We don't even try with this one. return false; } - default: { assert(0 && "Unreachable code reached"); } + default: { + DIE("Unreachable code reached"); + break; + } } - assert(0 && "Unreachable code reached"); + DIE("unreachable code reached"); } bool wildcard_complete(const wcstring &str, const wchar_t *wc, const wchar_t *desc, @@ -322,58 +327,44 @@ bool wildcard_match(const wcstring &str, const wcstring &wc, bool leading_dots_f /// \param err The errno value after a failed stat call on the file. static wcstring file_get_desc(const wcstring &filename, int lstat_res, const struct stat &lbuf, int stat_res, const struct stat &buf, int err) { - if (!lstat_res) { - if (S_ISLNK(lbuf.st_mode)) { - if (!stat_res) { - if (S_ISDIR(buf.st_mode)) { - return COMPLETE_DIRECTORY_SYMLINK_DESC; - } - if (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { - if (waccess(filename, X_OK) == 0) { - // Weird group permissions and other such issues make it non-trivial to - // find out if we can actually execute a file using the result from - // stat. It is much safer to use the access function, since it tells us - // exactly what we want to know. - return COMPLETE_EXEC_LINK_DESC; - } - } + if (lstat_res) { + return COMPLETE_FILE_DESC; + } - return COMPLETE_SYMLINK_DESC; + if (S_ISLNK(lbuf.st_mode)) { + if (!stat_res) { + if (S_ISDIR(buf.st_mode)) { + return COMPLETE_DIRECTORY_SYMLINK_DESC; + } + if (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH) && waccess(filename, X_OK) == 0) { + // Weird group permissions and other such issues make it non-trivial to find out if + // we can actually execute a file using the result from stat. It is much safer to + // use the access function, since it tells us exactly what we want to know. + return COMPLETE_EXEC_LINK_DESC; } - switch (err) { - case ENOENT: { - return COMPLETE_ROTTEN_SYMLINK_DESC; - } - case ELOOP: { - return COMPLETE_LOOP_SYMLINK_DESC; - } - default: { - // On unknown errors we do nothing. The file will be given the default 'File' - // description or one based on the suffix. - } - } - } else if (S_ISCHR(buf.st_mode)) { - return COMPLETE_CHAR_DESC; - } else if (S_ISBLK(buf.st_mode)) { - return COMPLETE_BLOCK_DESC; - } else if (S_ISFIFO(buf.st_mode)) { - return COMPLETE_FIFO_DESC; - } else if (S_ISSOCK(buf.st_mode)) { - return COMPLETE_SOCKET_DESC; - } else if (S_ISDIR(buf.st_mode)) { - return COMPLETE_DIRECTORY_DESC; - } else { - if (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXGRP)) { - if (waccess(filename, X_OK) == 0) { - // Weird group permissions and other such issues make it non-trivial to find out - // if we can actually execute a file using the result from stat. It is much - // safer to use the access function, since it tells us exactly what we want to - // know. - return COMPLETE_EXEC_DESC; - } - } + return COMPLETE_SYMLINK_DESC; } + + if (err == ENOENT) return COMPLETE_ROTTEN_SYMLINK_DESC; + if (err == ELOOP) return COMPLETE_LOOP_SYMLINK_DESC; + // On unknown errors we do nothing. The file will be given the default 'File' + // description or one based on the suffix. + } else if (S_ISCHR(buf.st_mode)) { + return COMPLETE_CHAR_DESC; + } else if (S_ISBLK(buf.st_mode)) { + return COMPLETE_BLOCK_DESC; + } else if (S_ISFIFO(buf.st_mode)) { + return COMPLETE_FIFO_DESC; + } else if (S_ISSOCK(buf.st_mode)) { + return COMPLETE_SOCKET_DESC; + } else if (S_ISDIR(buf.st_mode)) { + return COMPLETE_DIRECTORY_DESC; + } else if (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXGRP) && waccess(filename, X_OK) == 0) { + // Weird group permissions and other such issues make it non-trivial to find out if we can + // actually execute a file using the result from stat. It is much safer to use the access + // function, since it tells us exactly what we want to know. + return COMPLETE_EXEC_DESC; } return COMPLETE_FILE_DESC; @@ -394,9 +385,7 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc int stat_res = -1; int stat_errno = 0; int lstat_res = lwstat(filepath, &lstat_buf); - if (lstat_res < 0) { - // lstat failed. - } else { + if (lstat_res >= 0) { if (S_ISLNK(lstat_buf.st_mode)) { stat_res = wstat(filepath, &stat_buf); @@ -415,15 +404,14 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc const bool is_directory = stat_res == 0 && S_ISDIR(stat_buf.st_mode); const bool is_executable = stat_res == 0 && S_ISREG(stat_buf.st_mode); - if (expand_flags & DIRECTORIES_ONLY) { - if (!is_directory) { - return false; - } + const bool need_directory = expand_flags & DIRECTORIES_ONLY; + if (need_directory && !is_directory) { + return false; } - if (expand_flags & EXECUTABLES_ONLY) { - if (!is_executable || waccess(filepath, X_OK) != 0) { - return false; - } + + const bool executables_only = expand_flags & EXECUTABLES_ONLY; + if (executables_only && (!is_executable || waccess(filepath, X_OK) != 0)) { + return false; } // Compute the description. @@ -502,7 +490,7 @@ class wildcard_expander_t { void add_expansion_result(const wcstring &result) { // This function is only for the non-completions case. - assert(!(this->flags & EXPAND_FOR_COMPLETIONS)); + assert(!static_cast(this->flags & EXPAND_FOR_COMPLETIONS)); //!OCLINT(multiple unary operator) if (this->completion_set.insert(result).second) { append_completion(this->resolved_completions, result); this->did_add = true; @@ -812,7 +800,7 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc, if (wc_segment.empty()) { // Handle empty segment. - assert(!segment_has_wildcards); + assert(!segment_has_wildcards); //!OCLINT(multiple unary operator) if (is_last_segment) { this->expand_trailing_slash(base_dir, effective_prefix); } else { @@ -856,7 +844,6 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc, } else { // Not the last segment, nonempty wildcard. assert(next_slash != NULL); - wcstring child_effective_prefix = effective_prefix + wc_segment; this->expand_intermediate_segment(base_dir, dir, wc_segment, wc_remainder, effective_prefix + wc_segment + L'/'); } diff --git a/src/wutil.cpp b/src/wutil.cpp index 0461b9a38..25556c92f 100644 --- a/src/wutil.cpp +++ b/src/wutil.cpp @@ -50,40 +50,39 @@ bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &ou if (!d) return false; out_name = str2wcstring(d->d_name); - if (out_is_dir) { - // The caller cares if this is a directory, so check. - bool is_dir = false; + if (!out_is_dir) return true; - // We may be able to skip stat, if the readdir can tell us the file type directly. - bool check_with_stat = true; + // The caller cares if this is a directory, so check. + bool is_dir = false; + // We may be able to skip stat, if the readdir can tell us the file type directly. + bool check_with_stat = true; #ifdef HAVE_STRUCT_DIRENT_D_TYPE - if (d->d_type == DT_DIR) { - // Known directory. - is_dir = true; - check_with_stat = false; - } else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) { - // We want to treat symlinks to directories as directories. Use stat to resolve it. - check_with_stat = true; - } else { - // Regular file. - is_dir = false; - check_with_stat = false; - } -#endif // HAVE_STRUCT_DIRENT_D_TYPE - if (check_with_stat) { - // We couldn't determine the file type from the dirent; check by stat'ing it. - cstring fullpath = wcs2string(dir_path); - fullpath.push_back('/'); - fullpath.append(d->d_name); - struct stat buf; - if (stat(fullpath.c_str(), &buf) != 0) { - is_dir = false; - } else { - is_dir = !!(S_ISDIR(buf.st_mode)); - } - } - *out_is_dir = is_dir; + if (d->d_type == DT_DIR) { + // Known directory. + is_dir = true; + check_with_stat = false; + } else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) { + // We want to treat symlinks to directories as directories. Use stat to resolve it. + check_with_stat = true; + } else { + // Regular file. + is_dir = false; + check_with_stat = false; } +#endif // HAVE_STRUCT_DIRENT_D_TYPE + if (check_with_stat) { + // We couldn't determine the file type from the dirent; check by stat'ing it. + cstring fullpath = wcs2string(dir_path); + fullpath.push_back('/'); + fullpath.append(d->d_name); + struct stat buf; + if (stat(fullpath.c_str(), &buf) != 0) { + is_dir = false; + } else { + is_dir = static_cast(S_ISDIR(buf.st_mode)); + } + } + *out_is_dir = is_dir; return true; } @@ -186,11 +185,11 @@ bool set_cloexec(int fd) { int flags = fcntl(fd, F_GETFD, 0); if (flags < 0) { return false; - } else if (flags & FD_CLOEXEC) { - return true; - } else { - return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) >= 0; } + if (flags & FD_CLOEXEC) { + return true; + } + return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) >= 0; } static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec) { @@ -448,14 +447,6 @@ int wrename(const wcstring &old, const wcstring &newv) { return rename(old_narrow.c_str(), new_narrow.c_str()); } -/// Return one if the code point is in the range we reserve for internal use. -int fish_is_reserved_codepoint(wint_t wc) { - if (RESERVED_CHAR_BASE <= wc && wc < RESERVED_CHAR_END) return 1; - if (EXPAND_RESERVED_BASE <= wc && wc < EXPAND_RESERVED_END) return 1; - if (WILDCARD_RESERVED_BASE <= wc && wc < WILDCARD_RESERVED_END) return 1; - return 0; -} - /// Return one if the code point is in a Unicode private use area. int fish_is_pua(wint_t wc) { if (PUA1_START <= wc && wc < PUA1_END) return 1; @@ -467,7 +458,7 @@ int fish_is_pua(wint_t wc) { /// We need this because there are too many implementations that don't return the proper answer for /// some code points. See issue #3050. int fish_iswalnum(wint_t wc) { - if (fish_is_reserved_codepoint(wc)) return 0; + if (fish_reserved_codepoint(wc)) return 0; if (fish_is_pua(wc)) return 0; return iswalnum(wc); } @@ -475,7 +466,7 @@ int fish_iswalnum(wint_t wc) { /// We need this because there are too many implementations that don't return the proper answer for /// some code points. See issue #3050. int fish_iswalpha(wint_t wc) { - if (fish_is_reserved_codepoint(wc)) return 0; + if (fish_reserved_codepoint(wc)) return 0; if (fish_is_pua(wc)) return 0; return iswalpha(wc); } @@ -483,7 +474,7 @@ int fish_iswalpha(wint_t wc) { /// We need this because there are too many implementations that don't return the proper answer for /// some code points. See issue #3050. int fish_iswgraph(wint_t wc) { - if (fish_is_reserved_codepoint(wc)) return 0; + if (fish_reserved_codepoint(wc)) return 0; if (fish_is_pua(wc)) return 1; return iswgraph(wc); } diff --git a/tests/status.err b/tests/status.err index b04178e47..d2b89daf6 100644 --- a/tests/status.err +++ b/tests/status.err @@ -1,2 +1,8 @@ fish: An error occurred while redirecting file '/' open: Is a directory +status: Invalid combination of options, +you cannot do both 'is-interactive' and 'is-login' in the same invocation +status: Invalid combination of options, +you cannot do both 'is-block' and 'is-interactive' in the same invocation +status: Invalid job control mode 'full1' +status: Invalid job control mode '1none' diff --git a/tests/status.in b/tests/status.in index 875f62f38..cdda0d76a 100644 --- a/tests/status.in +++ b/tests/status.in @@ -1,17 +1,41 @@ # vim: set filetype=fish: status -b -or echo 'top level' +and echo '"status -b" unexpectedly returned true at top level' begin status -b + or echo '"status -b" unexpectedly returned false inside a begin block' end -and echo 'block' # Issue #1728 # Bad file redirection on a block causes `status --is-block` to return 0 forever. begin; end >/ # / is a directory, it can't be opened for writing status -b -and echo 'unexpected block' +and echo '"status -b" unexpectedly returned true after bad redirect on a begin block' -true +status -l +and echo '"status -l" unexpectedly returned true for a non-login shell' + +status -i +and echo '"status -i" unexpectedly returned true for a non-interactive shell' + +status is-login +and echo '"status is-login" unexpectedly returned true for a non-login shell' + +status is-interactive +and echo '"status is-interactive" unexpectedly returned true for a non-interactive shell' + +# We should get an error message about an invalid combination of flags. +status --is-interactive --is-login + +# We should get an error message about an unexpected arg for `status +# is-block`. +status -b is-interactive + +# Try to set the job control to an invalid mode. +status job-control full1 +status --job-control=1none + +# Now set it to a valid mode. +status job-control none diff --git a/tests/status.out b/tests/status.out index a6546ffd0..e69de29bb 100644 --- a/tests/status.out +++ b/tests/status.out @@ -1,2 +0,0 @@ -top level -block