mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 21:03:12 +00:00
Call "fish_command_not_found" if a command wasn't found
Previously, when a command wasn't found, fish would emit the "fish_command_not_found" *event*. This was annoying as it was hard to override (the code ended up checking for a function called `__fish_command_not_found_handler` anyway!), the setup was ugly, and it's useless - there is no use case for multiple command-not-found handlers. Instead, let's just call a function `fish_command_not_found` if it exists, or print the default message otherwise. The event is completely removed, but because a missing event is not an error (MEISNAE in C++-speak) this isn't an issue. Note that, for backwards-compatibility, we still keep the default handler function around even tho the new one is hard-coded in C++. Also, if we detect a previous handler, the new handler just calls it. This way, the backwards-compatible way to install a custom handler is: ```fish function __fish_command_not_found_handler --on-event fish_command_not_found # do a little dance, make a little love, get down tonight end ``` and the new hotness is ```fish function fish_command_not_found # do the thing end ``` Fixes #7293.
This commit is contained in:
parent
d1dab22691
commit
340de73172
7 changed files with 199 additions and 65 deletions
76
doc_src/cmds/fish_command_not_found.rst
Normal file
76
doc_src/cmds/fish_command_not_found.rst
Normal file
|
@ -0,0 +1,76 @@
|
|||
.. _cmd-fish_cmd_not_found:
|
||||
|
||||
fish_command_not_found - what to do when a command wasn't found
|
||||
===============================================================
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
|
||||
::
|
||||
|
||||
function fish_command_not_found
|
||||
...
|
||||
end
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
When fish tries to execute a command and can't find it, it invokes this function.
|
||||
|
||||
It can print a message to tell you about it, and it often also checks for a missing package that would include the command.
|
||||
|
||||
Fish ships multiple handlers for various operating systems and chooses from them when this function is loaded,
|
||||
or you can define your own.
|
||||
|
||||
It receives the full commandline as one argument per token, so $argv[1] contains the missing command.
|
||||
|
||||
When you leave ``fish_command_not_found`` undefined (e.g. by adding an empty function file) or explicitly call ``__fish_default_command_not_found_handler``, fish will just print a simple error.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
A simple handler:
|
||||
|
||||
::
|
||||
|
||||
function fish_command_not_found
|
||||
echo Did not find command $argv[1]
|
||||
end
|
||||
|
||||
> flounder
|
||||
Did not find command flounder
|
||||
|
||||
Or the handler for OpenSUSE's command-not-found::
|
||||
|
||||
function fish_command_not_found
|
||||
/usr/bin/command-not-found $argv[1]
|
||||
end
|
||||
|
||||
Or the simple default handler::
|
||||
|
||||
function fish_command_not_found
|
||||
__fish_default_command_not_found_handler $argv
|
||||
end
|
||||
|
||||
Backwards compatibility
|
||||
-----------------------
|
||||
|
||||
This command was introduced in fish 3.2.0. Previous versions of fish used the "fish_command_not_found" :ref:`event <event>` instead.
|
||||
|
||||
To define a handler that works in older versions of fish as well, define it the old way::
|
||||
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
echo COMMAND WAS NOT FOUND MY FRIEND $argv[1]
|
||||
end
|
||||
|
||||
in which case fish will define a ``fish_command_not_found`` that calls it,
|
||||
or define a wrapper::
|
||||
|
||||
function fish_command_not_found
|
||||
echo "G'day mate, could not find your command: $argv"
|
||||
end
|
||||
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
fish_command_not_found $argv
|
||||
end
|
|
@ -50,8 +50,6 @@ By using one of the event handler switches, a function can be made to run automa
|
|||
|
||||
- ``fish_prompt``, which is emitted whenever a new fish prompt is about to be displayed.
|
||||
|
||||
- ``fish_command_not_found``, which is emitted whenever a command lookup failed.
|
||||
|
||||
- ``fish_preexec``, which is emitted right before executing an interactive command. The commandline is passed as the first parameter. Not emitted if command is empty.
|
||||
|
||||
- ``fish_posterror``, which is emitted right after executing a command with syntax errors. The commandline is passed as the first parameter.
|
||||
|
|
|
@ -26,9 +26,9 @@ function __fish_default_command_not_found_handler
|
|||
end
|
||||
|
||||
if not status --is-interactive
|
||||
# Hook up the default as the principal command_not_found handler
|
||||
# in case we are not interactive
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
# Hook up the default as the command_not_found handler
|
||||
# if we are not interactive to avoid custom handlers.
|
||||
function fish_command_not_found --on-event fish_command_not_found
|
||||
__fish_default_command_not_found_handler $argv
|
||||
end
|
||||
end
|
||||
|
|
|
@ -258,63 +258,9 @@ function __fish_config_interactive -d "Initializations that should be performed
|
|||
__update_cwd_osc # Run once because we might have already inherited a PWD from an old tab
|
||||
end
|
||||
|
||||
### Command-not-found handlers
|
||||
# This can be overridden by defining a new __fish_command_not_found_handler function
|
||||
if not type -q __fish_command_not_found_handler
|
||||
# Read the OS/Distro from /etc/os-release.
|
||||
# This has a "ID=" line that defines the exact distribution,
|
||||
# and an "ID_LIKE=" line that defines what it is derived from or otherwise like.
|
||||
# For our purposes, we use both.
|
||||
set -l os
|
||||
if test -r /etc/os-release
|
||||
set os (string match -r '^ID(?:_LIKE)?\s*=.*' < /etc/os-release | \
|
||||
string replace -r '^ID(?:_LIKE)?\s*=(.*)' '$1' | string trim -c '\'"' | string split " ")
|
||||
end
|
||||
|
||||
# First check if we are on OpenSUSE since SUSE's handler has no options
|
||||
# but the same name and path as Ubuntu's.
|
||||
if contains -- suse $os || contains -- sles $os && type -q command-not-found
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
/usr/bin/command-not-found $argv[1]
|
||||
end
|
||||
# Check for Fedora's handler
|
||||
else if test -f /usr/libexec/pk-command-not-found
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
/usr/libexec/pk-command-not-found $argv[1]
|
||||
end
|
||||
# Check in /usr/lib, this is where modern Ubuntus place this command
|
||||
else if test -f /usr/lib/command-not-found
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
/usr/lib/command-not-found -- $argv[1]
|
||||
end
|
||||
# Check for NixOS handler
|
||||
else if test -f /run/current-system/sw/bin/command-not-found
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
/run/current-system/sw/bin/command-not-found $argv
|
||||
end
|
||||
# Ubuntu Feisty places this command in the regular path instead
|
||||
else if type -q command-not-found
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
command-not-found -- $argv[1]
|
||||
end
|
||||
# pkgfile is an optional, but official, package on Arch Linux
|
||||
# it ships with example handlers for bash and zsh, so we'll follow that format
|
||||
else if type -p -q pkgfile
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
set -l __packages (pkgfile --binaries --verbose -- $argv[1] 2>/dev/null)
|
||||
if test $status -eq 0
|
||||
printf "%s may be found in the following packages:\n" "$argv[1]"
|
||||
printf " %s\n" $__packages
|
||||
else
|
||||
__fish_default_command_not_found_handler $argv[1]
|
||||
end
|
||||
end
|
||||
# Use standard fish command not found handler otherwise
|
||||
else
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
__fish_default_command_not_found_handler $argv[1]
|
||||
end
|
||||
end
|
||||
# For backwards-compatibility - the event doesn't exist anymore so it's harmless.
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
fish_command_not_found $argv
|
||||
end
|
||||
|
||||
# Bump this whenever some code below needs to run once when upgrading to a new version.
|
||||
|
|
61
share/functions/fish_command_not_found.fish
Normal file
61
share/functions/fish_command_not_found.fish
Normal file
|
@ -0,0 +1,61 @@
|
|||
### Command-not-found handlers
|
||||
# This can be overridden by defining a new fish_command_not_found function
|
||||
|
||||
# Read the OS/Distro from /etc/os-release.
|
||||
# This has a "ID=" line that defines the exact distribution,
|
||||
# and an "ID_LIKE=" line that defines what it is derived from or otherwise like.
|
||||
# For our purposes, we use both.
|
||||
set -l os
|
||||
if test -r /etc/os-release
|
||||
set os (string match -r '^ID(?:_LIKE)?\s*=.*' < /etc/os-release | \
|
||||
string replace -r '^ID(?:_LIKE)?\s*=(.*)' '$1' | string trim -c '\'"' | string split " ")
|
||||
end
|
||||
|
||||
# If an old handler already exists, defer to that.
|
||||
if functions -q __fish_command_not_found_handler
|
||||
function fish_command_not_found
|
||||
# The fish_command_not_found event was removed in fish 3.2.0,
|
||||
# and future versions of fish will just call a function called "fish_command_not_found".
|
||||
# You have defined a custom handler, we suggest renaming it to "fish_command_not_found".
|
||||
__fish_command_not_found_handler $argv
|
||||
end
|
||||
# First check if we are on OpenSUSE since SUSE's handler has no options
|
||||
# but the same name and path as Ubuntu's.
|
||||
else if contains -- suse $os || contains -- sles $os && type -q command-not-found
|
||||
function fish_command_not_found
|
||||
/usr/bin/command-not-found $argv[1]
|
||||
end
|
||||
# Check for Fedora's handler
|
||||
else if test -f /usr/libexec/pk-command-not-found
|
||||
function fish_command_not_found
|
||||
/usr/libexec/pk-command-not-found $argv[1]
|
||||
end
|
||||
# Check in /usr/lib, where Ubuntu places this command
|
||||
else if test -f /usr/lib/command-not-found
|
||||
function fish_command_not_found
|
||||
/usr/lib/command-not-found -- $argv[1]
|
||||
end
|
||||
# Check for NixOS handler
|
||||
else if test -f /run/current-system/sw/bin/command-not-found
|
||||
function fish_command_not_found
|
||||
/run/current-system/sw/bin/command-not-found $argv
|
||||
end
|
||||
# Ubuntu Feisty places this command in the regular path instead
|
||||
else if type -q command-not-found
|
||||
function fish_command_not_found
|
||||
command-not-found -- $argv[1]
|
||||
end
|
||||
# pkgfile is an optional, but official, package on Arch Linux
|
||||
# it ships with example handlers for bash and zsh, so we'll follow that format
|
||||
else if type -p -q pkgfile
|
||||
function fish_command_not_found
|
||||
set -l __packages (pkgfile --binaries --verbose -- $argv[1] 2>/dev/null)
|
||||
if test $status -eq 0
|
||||
printf "%s may be found in the following packages:\n" "$argv[1]"
|
||||
printf " %s\n" $__packages
|
||||
else
|
||||
__fish_default_command_not_found_handler $argv[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
# Use standard fish command not found handler otherwise
|
|
@ -749,10 +749,39 @@ end_execution_reason_t parse_execution_context_t::handle_command_not_found(
|
|||
event_args.insert(event_args.begin(), cmd_str);
|
||||
}
|
||||
|
||||
event_fire_generic(*parser, L"fish_command_not_found", &event_args);
|
||||
wcstring buffer;
|
||||
wcstring error;
|
||||
|
||||
// Here we want to report an error (so it shows a backtrace), but with no text.
|
||||
return this->report_error(STATUS_CMD_UNKNOWN, statement, L"");
|
||||
// Redirect to stderr
|
||||
auto io = io_chain_t{};
|
||||
io.append_from_specs({redirection_spec_t{STDOUT_FILENO, redirection_mode_t::fd, L"2"}}, L"");
|
||||
|
||||
if (function_exists(L"fish_command_not_found", *parser)) {
|
||||
buffer = L"fish_command_not_found";
|
||||
for (const wcstring &arg : event_args) {
|
||||
buffer.push_back(L' ');
|
||||
buffer.append(escape_string(arg, ESCAPE_ALL));
|
||||
}
|
||||
auto prev_statuses = parser->get_last_statuses();
|
||||
|
||||
event_t event(event_type_t::generic);
|
||||
event.desc.str_param1 = L"fish_command_not_found";
|
||||
block_t *b = parser->push_block(block_t::event_block(event));
|
||||
parser->eval(buffer, io);
|
||||
parser->pop_block(b);
|
||||
parser->set_last_statuses(std::move(prev_statuses));
|
||||
} else {
|
||||
// If we have no handler, just print it as a normal error.
|
||||
error = _(L"Unknown command:");
|
||||
if (!event_args.empty()) {
|
||||
error.push_back(L' ');
|
||||
error.append(escape_string(event_args[0], ESCAPE_ALL));
|
||||
}
|
||||
}
|
||||
|
||||
// Here we want to report an error (so it shows a backtrace).
|
||||
// If the handler printed text, that's already shown, so error will be empty.
|
||||
return this->report_error(STATUS_CMD_UNKNOWN, statement, error.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
tests/checks/command-not-found.fish
Normal file
24
tests/checks/command-not-found.fish
Normal file
|
@ -0,0 +1,24 @@
|
|||
# RUN: %fish -C 'set -g fish %fish' %s
|
||||
set -g PATH
|
||||
$fish -c "nonexistent-command-1234 banana rama"
|
||||
#CHECKERR: fish: Unknown command: nonexistent-command-1234
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: nonexistent-command-1234 banana rama
|
||||
#CHECKERR: ^
|
||||
$fish -C 'function fish_command_not_found; echo cmd-not-found; end' -ic "nonexistent-command-1234 1 2 3 4"
|
||||
#CHECKERR: cmd-not-found
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: nonexistent-command-1234 1 2 3 4
|
||||
#CHECKERR: ^
|
||||
$fish -C 'function fish_command_not_found; echo command-not-found $argv; end' -c "nonexistent-command-abcd foo bar baz"
|
||||
#CHECKERR: command-not-found nonexistent-command-abcd foo bar baz
|
||||
#CHECKERR: fish:
|
||||
#CHECKERR: nonexistent-command-abcd foo bar baz
|
||||
#CHECKERR: ^
|
||||
|
||||
$fish -C 'functions --erase fish_command_not_found' -c 'nonexistent-command apple friday'
|
||||
#CHECKERR: fish: Unknown command: nonexistent-command
|
||||
#CHECKERR: nonexistent-command apple friday
|
||||
#CHECKERR: ^
|
||||
|
||||
exit 0
|
Loading…
Reference in a new issue