aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/builtin.cpp
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2015-07-24 00:50:58 -0700
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2015-07-24 00:59:27 -0700
commitb4f53143b0e05fd3061cdf2e65e17a6a2904090b (patch)
tree4785bf31f7b89fc2420aa740d9a6967dc6c6f9b1 /src/builtin.cpp
parent9c2fdc6da57032c4448b59de5872086eea626b74 (diff)
Migrate source files into src/ directory
This change moves source files into a src/ directory, and puts object files into an obj/ directory. The Makefile and xcode project are updated accordingly. Fixes #1866
Diffstat (limited to 'src/builtin.cpp')
-rw-r--r--src/builtin.cpp4287
1 files changed, 4287 insertions, 0 deletions
diff --git a/src/builtin.cpp b/src/builtin.cpp
new file mode 100644
index 00000000..59778862
--- /dev/null
+++ b/src/builtin.cpp
@@ -0,0 +1,4287 @@
+/** \file builtin.c
+ Functions for executing builtin functions.
+
+ How to add a new builtin function:
+
+ 1). Create a function in builtin.c with the following signature:
+
+ <tt>static int builtin_NAME( parser_t &parser, wchar_t ** args )</tt>
+
+ where NAME is the name of the builtin, and args is a zero-terminated list of arguments.
+
+ 2). Add a line like { L"NAME", &builtin_NAME, N_(L"Bla bla bla") }, to the builtin_data_t variable. The description is used by the completion system. Note that this array is sorted!
+
+ 3). Create a file doc_src/NAME.txt, containing the manual for the builtin in Doxygen-format. Check the other builtin manuals for proper syntax.
+
+ 4). Use 'git add doc_src/NAME.txt' to start tracking changes to the documentation file.
+
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <termios.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <signal.h>
+#include <wctype.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stack>
+
+#include "fallback.h"
+#include "util.h"
+
+#include "wutil.h"
+#include "builtin.h"
+#include "function.h"
+#include "complete.h"
+#include "proc.h"
+#include "parser.h"
+#include "reader.h"
+#include "env.h"
+#include "common.h"
+#include "wgetopt.h"
+#include "sanity.h"
+#include "tokenizer.h"
+#include "wildcard.h"
+#include "input_common.h"
+#include "input.h"
+#include "intern.h"
+#include "event.h"
+#include "signal.h"
+#include "exec.h"
+#include "highlight.h"
+#include "parse_util.h"
+#include "parser_keywords.h"
+#include "expand.h"
+#include "path.h"
+#include "history.h"
+#include "parse_tree.h"
+#include "wcstringutil.h"
+
+/**
+ The default prompt for the read command
+*/
+#define DEFAULT_READ_PROMPT L"set_color green; echo -n read; set_color normal; echo -n \"> \""
+
+/**
+ The mode name to pass to history and input
+*/
+
+#define READ_MODE_NAME L"fish_read"
+
+/**
+ The send stuff to foreground message
+*/
+#define FG_MSG _( L"Send job %d, '%ls' to foreground\n" )
+
+/**
+ Datastructure to describe a builtin.
+*/
+struct builtin_data_t
+{
+ /**
+ Name of the builtin
+ */
+ const wchar_t *name;
+ /**
+ Function pointer tothe builtin implementation
+ */
+ int (*func)(parser_t &parser, wchar_t **argv);
+ /**
+ Description of what the builtin does
+ */
+ const wchar_t *desc;
+
+ bool operator<(const wcstring &) const;
+ bool operator<(const builtin_data_t *) const;
+};
+
+bool builtin_data_t::operator<(const wcstring &other) const
+{
+ return wcscmp(this->name, other.c_str()) < 0;
+}
+
+bool builtin_data_t::operator<(const builtin_data_t *other) const
+{
+ return wcscmp(this->name, other->name) < 0;
+}
+
+int builtin_out_redirect;
+int builtin_err_redirect;
+
+/* Buffers for storing the output of builtin functions */
+wcstring stdout_buffer, stderr_buffer;
+
+const wcstring &get_stdout_buffer()
+{
+ ASSERT_IS_MAIN_THREAD();
+ return stdout_buffer;
+}
+
+const wcstring &get_stderr_buffer()
+{
+ ASSERT_IS_MAIN_THREAD();
+ return stderr_buffer;
+}
+
+void builtin_show_error(const wcstring &err)
+{
+ ASSERT_IS_MAIN_THREAD();
+ stderr_buffer.append(err);
+}
+
+/**
+ Stack containing builtin I/O for recursive builtin calls.
+*/
+struct io_stack_elem_t
+{
+ int in;
+ wcstring out;
+ wcstring err;
+};
+static std::stack<io_stack_elem_t, std::vector<io_stack_elem_t> > io_stack;
+
+/**
+ The file from which builtin functions should attempt to read, use
+ instead of stdin.
+*/
+static int builtin_stdin;
+
+/**
+ The underlying IO redirections behind the current builtin. This
+ should normally not be used - sb_out and friends are already
+ configured to handle everything.
+*/
+static const io_chain_t *real_io;
+
+/**
+ Counts the number of non null pointers in the specified array
+*/
+static int builtin_count_args(const wchar_t * const * argv)
+{
+ int argc = 1;
+ while (argv[argc] != NULL)
+ {
+ argc++;
+ }
+ return argc;
+}
+
+/**
+ This function works like wperror, but it prints its result into
+ the sb_err string instead of to stderr. Used by the builtin
+ commands.
+*/
+
+static void builtin_wperror(const wchar_t *s)
+{
+ if (s != 0)
+ {
+ stderr_buffer.append(s);
+ stderr_buffer.append(L": ");
+ }
+ char *err = strerror(errno);
+ if (err)
+ {
+ const wcstring werr = str2wcstring(err);
+ stderr_buffer.append(werr);
+ stderr_buffer.push_back(L'\n');
+ }
+}
+
+/**
+ Count the number of times the specified character occurs in the specified string
+*/
+static int count_char(const wchar_t *str, wchar_t c)
+{
+ int res = 0;
+ for (; *str; str++)
+ {
+ res += (*str==c);
+ }
+ return res;
+}
+
+wcstring builtin_help_get(parser_t &parser, const wchar_t *name)
+{
+ /* This won't ever work if no_exec is set */
+ if (no_exec)
+ return wcstring();
+
+ wcstring_list_t lst;
+ wcstring out;
+ const wcstring name_esc = escape_string(name, 1);
+ wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str());
+ if (!builtin_out_redirect && isatty(1))
+ {
+ // since we're using a subshell, __fish_print_help can't tell we're in
+ // a terminal. Tell it ourselves.
+ int cols = common_get_width();
+ cmd = format_string(L"__fish_print_help --tty-width %d %ls", cols, name_esc.c_str());
+ }
+ if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0)
+ {
+ for (size_t i=0; i<lst.size(); i++)
+ {
+ out.append(lst.at(i));
+ out.push_back(L'\n');
+ }
+ }
+ return out;
+}
+
+/**
+ Print help for the specified builtin. If \c b is sb_err, also print
+ the line information
+
+ If \c b is the buffer representing standard error, and the help
+ message is about to be printed to an interactive screen, it may be
+ shortened to fit the screen.
+
+
+*/
+
+static void builtin_print_help(parser_t &parser, const wchar_t *cmd, wcstring &b)
+{
+ if (&b == &stderr_buffer)
+ {
+ stderr_buffer.append(parser.current_line());
+ }
+
+ const wcstring h = builtin_help_get(parser, cmd);
+
+ if (!h.size())
+ return;
+
+ wchar_t *str = wcsdup(h.c_str());
+ if (str)
+ {
+ bool is_short = false;
+ if (&b == &stderr_buffer)
+ {
+
+ /*
+ Interactive mode help to screen - only print synopsis if
+ the rest won't fit
+ */
+
+ int screen_height, lines;
+
+ screen_height = common_get_height();
+ lines = count_char(str, L'\n');
+ if (!get_is_interactive() || (lines > 2*screen_height/3))
+ {
+ wchar_t *pos;
+ int cut=0;
+ int i;
+
+ is_short = true;
+
+ /*
+ First move down 4 lines
+ */
+
+ pos = str;
+ for (i=0; (i<4) && pos && *pos; i++)
+ {
+ pos = wcschr(pos+1, L'\n');
+ }
+
+ if (pos && *pos)
+ {
+
+ /*
+ Then find the next empty line
+ */
+ for (; *pos; pos++)
+ {
+ if (*pos == L'\n')
+ {
+ wchar_t *pos2;
+ int is_empty = 1;
+
+ 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;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ We did not find a good place to cut message to
+ shorten it - so we make sure we don't print
+ anything.
+ */
+ if (!cut)
+ {
+ *str = 0;
+ }
+
+ }
+ }
+
+ b.append(str);
+ if (is_short)
+ {
+ append_format(b, _(L"%ls: Type 'help %ls' for related documentation\n\n"), cmd, cmd);
+ }
+
+ free(str);
+ }
+}
+
+/**
+ Perform error reporting for encounter with unknown option
+*/
+static void builtin_unknown_option(parser_t &parser, const wchar_t *cmd, const wchar_t *opt)
+{
+ append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, cmd, opt);
+ builtin_print_help(parser, cmd, stderr_buffer);
+}
+
+/**
+ Perform error reporting for encounter with missing argument
+*/
+static void builtin_missing_argument(parser_t &parser, const wchar_t *cmd, const wchar_t *opt)
+{
+ append_format(stderr_buffer, BUILTIN_ERR_MISSING, cmd, opt);
+ builtin_print_help(parser, cmd, stderr_buffer);
+}
+
+/*
+ Here follows the definition of all builtin commands. The function
+ names are all on the form builtin_NAME where NAME is the name of the
+ builtin. so the function name for the builtin 'fg' is
+ 'builtin_fg'.
+
+ A few builtins, including 'while', 'command' and 'builtin' are not
+ defined here as they are handled directly by the parser. (They are
+ not parsed as commands, instead they only alter the parser state)
+
+ The builtins 'break' and 'continue' are so closely related that they
+ share the same implementation, namely 'builtin_break_continue.
+
+ Several other builtins, including jobs, ulimit and set are so big
+ that they have been given their own file. These files are all named
+ 'builtin_NAME.c', where NAME is the name of the builtin. These files
+ are included directly below.
+
+*/
+
+
+#include "builtin_set.cpp"
+#include "builtin_commandline.cpp"
+#include "builtin_complete.cpp"
+#include "builtin_ulimit.cpp"
+#include "builtin_jobs.cpp"
+#include "builtin_set_color.cpp"
+#include "builtin_printf.cpp"
+
+/* builtin_test lives in builtin_test.cpp */
+int builtin_test(parser_t &parser, wchar_t **argv);
+
+/**
+ List a single key binding.
+ Returns false if no binding with that sequence and mode exists.
+ */
+static bool builtin_bind_list_one(const wcstring& seq, const wcstring& bind_mode)
+{
+ std::vector<wcstring> ecmds;
+ wcstring sets_mode;
+
+ if (!input_mapping_get(seq, bind_mode, &ecmds, &sets_mode))
+ {
+ return false;
+ }
+
+ stdout_buffer.append(L"bind");
+
+ // Append the mode flags if applicable
+ if (bind_mode != DEFAULT_BIND_MODE)
+ {
+ const wcstring emode = escape_string(bind_mode, ESCAPE_ALL);
+ stdout_buffer.append(L" -M ");
+ stdout_buffer.append(emode);
+ }
+ if (sets_mode != bind_mode)
+ {
+ const wcstring esets_mode = escape_string(sets_mode, ESCAPE_ALL);
+ stdout_buffer.append(L" -m ");
+ stdout_buffer.append(esets_mode);
+ }
+
+ // Append the name
+ wcstring tname;
+ if (input_terminfo_get_name(seq, &tname))
+ {
+ // Note that we show -k here because we have an input key name
+ append_format(stdout_buffer, L" -k %ls", tname.c_str());
+ }
+ else
+ {
+ // No key name, so no -k; we show the escape sequence directly
+ const wcstring eseq = escape_string(seq, ESCAPE_ALL);
+ append_format(stdout_buffer, L" %ls", eseq.c_str());
+ }
+
+ // Now show the list of commands
+ for (size_t i = 0; i < ecmds.size(); i++)
+ {
+ const wcstring &ecmd = ecmds.at(i);
+ const wcstring escaped_ecmd = escape_string(ecmd, ESCAPE_ALL);
+ stdout_buffer.push_back(' ');
+ stdout_buffer.append(escaped_ecmd);
+ }
+ stdout_buffer.push_back(L'\n');
+
+ return true;
+}
+
+/**
+ List all current key bindings
+ */
+static void builtin_bind_list(const wchar_t *bind_mode)
+{
+ const std::vector<input_mapping_name_t> lst = input_mapping_get_names();
+
+ for (std::vector<input_mapping_name_t>::const_iterator it = lst.begin(), end = lst.end();
+ it != end;
+ ++it)
+ {
+ if (bind_mode != NULL && bind_mode != it->mode)
+ {
+ continue;
+ }
+
+ builtin_bind_list_one(it->seq, it->mode);
+ }
+}
+
+/**
+ Print terminfo key binding names to string buffer used for standard output.
+
+ \param all if set, all terminfo key binding names will be
+ printed. If not set, only ones that are defined for this terminal
+ are printed.
+ */
+static void builtin_bind_key_names(int all)
+{
+ const wcstring_list_t names = input_terminfo_get_names(!all);
+ for (size_t i=0; i<names.size(); i++)
+ {
+ const wcstring &name = names.at(i);
+
+ append_format(stdout_buffer, L"%ls\n", name.c_str());
+ }
+}
+
+/**
+ Print all the special key binding functions to string buffer used for standard output.
+
+ */
+static void builtin_bind_function_names()
+{
+ wcstring_list_t names = input_function_get_names();
+
+ for (size_t i=0; i<names.size(); i++)
+ {
+ const wchar_t *seq = names.at(i).c_str();
+ append_format(stdout_buffer, L"%ls\n", seq);
+ }
+}
+
+// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed.
+static int get_terminfo_sequence(const wchar_t *seq, wcstring *out_seq)
+{
+ if (input_terminfo_get_sequence(seq, out_seq))
+ {
+ return 1;
+ }
+ wcstring eseq = escape_string(seq, 0);
+ switch (errno)
+ {
+ case ENOENT:
+ {
+ append_format(stderr_buffer, _(L"%ls: No key with name '%ls' found\n"), L"bind", eseq.c_str());
+ break;
+ }
+
+ case EILSEQ:
+ {
+ append_format(stderr_buffer, _(L"%ls: Key with name '%ls' does not have any mapping\n"), L"bind", eseq.c_str());
+ break;
+ }
+
+ default:
+ {
+ append_format(stderr_buffer, _(L"%ls: Unknown error trying to bind to key named '%ls'\n"), L"bind", eseq.c_str());
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ Add specified key binding.
+ */
+static int builtin_bind_add(const wchar_t *seq, const wchar_t **cmds, size_t cmds_len,
+ const wchar_t *mode, const wchar_t *sets_mode, int terminfo)
+{
+
+ if (terminfo)
+ {
+ wcstring seq2;
+ if (get_terminfo_sequence(seq, &seq2))
+ {
+ input_mapping_add(seq2.c_str(), cmds, cmds_len, mode, sets_mode);
+ }
+ else
+ {
+ return 1;
+ }
+
+ }
+ else
+ {
+ input_mapping_add(seq, cmds, cmds_len, mode, sets_mode);
+ }
+
+ return 0;
+
+}
+
+/**
+ Erase specified key bindings
+
+ \param seq an array of all key bindings to erase
+ \param all if specified, _all_ key bindings will be erased
+ \param mode if specified, only bindings from that mode will be erased. If not given and \c all is \c false, \c DEFAULT_BIND_MODE will be used.
+ */
+static int builtin_bind_erase(wchar_t **seq, int all, const wchar_t *mode, int use_terminfo)
+{
+ if (all)
+ {
+ const std::vector<input_mapping_name_t> lst = input_mapping_get_names();
+ for (std::vector<input_mapping_name_t>::const_iterator it = lst.begin(), end = lst.end();
+ it != end;
+ ++it)
+ {
+ if (mode == NULL || mode == it->mode)
+ {
+ input_mapping_erase(it->seq, it->mode);
+ }
+ }
+
+ return 0;
+ }
+ else
+ {
+ int res = 0;
+
+ if (mode == NULL) mode = DEFAULT_BIND_MODE;
+
+ while (*seq)
+ {
+ if (use_terminfo)
+ {
+ wcstring seq2;
+ if (get_terminfo_sequence(*seq++, &seq2))
+ {
+ input_mapping_erase(seq2, mode);
+ }
+ else
+ {
+ res = 1;
+ }
+ }
+ else
+ {
+ input_mapping_erase(*seq++, mode);
+ }
+ }
+
+ return res;
+ }
+}
+
+
+/**
+ The bind builtin, used for setting character sequences
+*/
+static int builtin_bind(parser_t &parser, wchar_t **argv)
+{
+
+ enum
+ {
+ BIND_INSERT,
+ BIND_ERASE,
+ BIND_KEY_NAMES,
+ BIND_FUNCTION_NAMES
+ };
+
+ int argc=builtin_count_args(argv);
+ int mode = BIND_INSERT;
+ int res = STATUS_BUILTIN_OK;
+ int all = 0;
+
+ const wchar_t *bind_mode = DEFAULT_BIND_MODE;
+ bool bind_mode_given = false;
+ const wchar_t *sets_bind_mode = DEFAULT_BIND_MODE;
+ bool sets_bind_mode_given = false;
+
+ int use_terminfo = 0;
+
+ woptind=0;
+
+ static const struct woption long_options[] =
+ {
+ { L"all", no_argument, 0, 'a' },
+ { L"erase", no_argument, 0, 'e' },
+ { L"function-names", no_argument, 0, 'f' },
+ { L"help", no_argument, 0, 'h' },
+ { L"key", no_argument, 0, 'k' },
+ { L"key-names", no_argument, 0, 'K' },
+ { L"mode", required_argument, 0, 'M' },
+ { L"sets-mode", required_argument, 0, 'm' },
+ { 0, 0, 0, 0 }
+ };
+
+ while (1)
+ {
+ int opt_index = 0;
+ int opt = wgetopt_long(argc,
+ argv,
+ L"aehkKfM:m:",
+ long_options,
+ &opt_index);
+
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+
+ case 'a':
+ all = 1;
+ break;
+
+ case 'e':
+ mode = BIND_ERASE;
+ break;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 'k':
+ use_terminfo = 1;
+ break;
+
+ case 'K':
+ mode = BIND_KEY_NAMES;
+ break;
+
+ case 'f':
+ mode = BIND_FUNCTION_NAMES;
+ break;
+
+ case 'M':
+ bind_mode = woptarg;
+ bind_mode_given = true;
+ break;
+
+ case 'm':
+ sets_bind_mode = woptarg;
+ sets_bind_mode_given = true;
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+
+ }
+ }
+
+ /*
+ * if mode is given, but not new mode, default to new mode to mode
+ */
+ if (bind_mode_given && !sets_bind_mode_given)
+ {
+ sets_bind_mode = bind_mode;
+ }
+
+ switch (mode)
+ {
+
+ case BIND_ERASE:
+ {
+ if (builtin_bind_erase(&argv[woptind], all, bind_mode_given ? bind_mode : NULL, use_terminfo))
+ {
+ res = STATUS_BUILTIN_ERROR;
+ }
+ break;
+ }
+
+ case BIND_INSERT:
+ {
+ switch (argc-woptind)
+ {
+ case 0:
+ {
+ builtin_bind_list(bind_mode_given ? bind_mode : NULL);
+ break;
+ }
+
+ case 1:
+ {
+ wcstring seq;
+ if (use_terminfo)
+ {
+ if (!get_terminfo_sequence(argv[woptind], &seq))
+ {
+ res = STATUS_BUILTIN_ERROR;
+ // get_terminfo_sequence already printed the error
+ break;
+ }
+ }
+ else
+ {
+ seq = argv[woptind];
+ }
+ if (!builtin_bind_list_one(seq, bind_mode))
+ {
+ res = STATUS_BUILTIN_ERROR;
+ wcstring eseq = escape_string(argv[woptind], 0);
+ if (use_terminfo)
+ {
+ append_format(stderr_buffer, _(L"%ls: No binding found for key '%ls'\n"), argv[0], eseq.c_str());
+ }
+ else
+ {
+ append_format(stderr_buffer, _(L"%ls: No binding found for sequence '%ls'\n"), argv[0], eseq.c_str());
+ }
+ }
+ break;
+ }
+
+ default:
+ {
+ if (builtin_bind_add(argv[woptind], (const wchar_t **)argv + (woptind + 1), argc - (woptind + 1), bind_mode, sets_bind_mode, use_terminfo))
+ {
+ res = STATUS_BUILTIN_ERROR;
+ }
+ break;
+ }
+
+ }
+ break;
+ }
+
+ case BIND_KEY_NAMES:
+ {
+ builtin_bind_key_names(all);
+ break;
+ }
+
+
+ case BIND_FUNCTION_NAMES:
+ {
+ builtin_bind_function_names();
+ break;
+ }
+
+
+ default:
+ {
+ res = STATUS_BUILTIN_ERROR;
+ append_format(stderr_buffer, _(L"%ls: Invalid state\n"), argv[0]);
+ break;
+ }
+ }
+
+ return res;
+}
+
+
+/**
+ The block builtin, used for temporarily blocking events
+*/
+static int builtin_block(parser_t &parser, wchar_t **argv)
+{
+ enum
+ {
+ UNSET,
+ GLOBAL,
+ LOCAL,
+ }
+ ;
+
+ int scope=UNSET;
+ int erase = 0;
+ int argc=builtin_count_args(argv);
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ {
+ L"erase", no_argument, 0, 'e'
+ }
+ ,
+ {
+ L"local", no_argument, 0, 'l'
+ }
+ ,
+ {
+ L"global", no_argument, 0, 'g'
+ }
+ ,
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"elgh",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 'g':
+ scope = GLOBAL;
+ break;
+
+ case 'l':
+ scope = LOCAL;
+ break;
+
+ case 'e':
+ erase = 1;
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+
+ if (erase)
+ {
+ if (scope != UNSET)
+ {
+ append_format(stderr_buffer, _(L"%ls: Can not specify scope when removing block\n"), argv[0]);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if (parser.global_event_blocks.empty())
+ {
+ append_format(stderr_buffer, _(L"%ls: No blocks defined\n"), argv[0]);
+ return STATUS_BUILTIN_ERROR;
+ }
+ parser.global_event_blocks.pop_front();
+ }
+ else
+ {
+ size_t block_idx = 0;
+ block_t *block = parser.block_at_index(block_idx);
+
+ event_blockage_t eb = {};
+ eb.typemask = (1<<EVENT_ANY);
+
+ switch (scope)
+ {
+ case LOCAL:
+ {
+ // If this is the outermost block, then we're global
+ if (block_idx + 1 >= parser.block_count())
+ {
+ block = NULL;
+ }
+ break;
+ }
+ case GLOBAL:
+ {
+ block=NULL;
+ }
+ case UNSET:
+ {
+ while (block != NULL && block->type() != FUNCTION_CALL && block->type() != FUNCTION_CALL_NO_SHADOW)
+ {
+ // Set it in function scope
+ block = parser.block_at_index(++block_idx);
+ }
+ }
+ }
+ if (block)
+ {
+ block->event_blocks.push_front(eb);
+ }
+ else
+ {
+ parser.global_event_blocks.push_front(eb);
+ }
+ }
+
+ return STATUS_BUILTIN_OK;
+
+}
+
+/**
+ The builtin builtin, used for giving builtins precedence over
+ functions. Mostly handled by the parser. All this code does is some
+ additional operational modes, such as printing a list of all
+ builtins, printing help, etc.
+*/
+static int builtin_builtin(parser_t &parser, wchar_t **argv)
+{
+ int argc=builtin_count_args(argv);
+ int list=0;
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ {
+ L"names", no_argument, 0, 'n'
+ }
+ ,
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"nh",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+
+ return STATUS_BUILTIN_ERROR;
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 'n':
+ list=1;
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+
+ if (list)
+ {
+ wcstring_list_t names = builtin_get_names();
+ sort(names.begin(), names.end());
+
+ for (size_t i=0; i<names.size(); i++)
+ {
+ const wchar_t *el = names.at(i).c_str();
+
+ stdout_buffer.append(el);
+ stdout_buffer.append(L"\n");
+ }
+ }
+ return STATUS_BUILTIN_OK;
+}
+
+/**
+ Implementation of the builtin emit command, used to create events.
+ */
+static int builtin_emit(parser_t &parser, wchar_t **argv)
+{
+ int argc=builtin_count_args(argv);
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"h",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+
+ if (!argv[woptind])
+ {
+ append_format(stderr_buffer, L"%ls: expected event name\n", argv[0]);
+ return STATUS_BUILTIN_ERROR;
+ }
+ const wchar_t *eventname = argv[woptind];
+ wcstring_list_t args(argv + woptind + 1, argv + argc);
+ event_fire_generic(eventname, &args);
+
+ return STATUS_BUILTIN_OK;
+}
+
+
+/**
+ Implementation of the builtin 'command'. Actual command running is handled by
+ the parser, this just processes the flags.
+*/
+static int builtin_command(parser_t &parser, wchar_t **argv)
+{
+ int argc=builtin_count_args(argv);
+ int print_path=0;
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ { L"search", no_argument, 0, 's' },
+ { L"help", no_argument, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"svh",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 's':
+ case 'v':
+ print_path=1;
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+
+ if (!print_path)
+ {
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ int found=0;
+
+ for (int idx = woptind; argv[idx]; ++idx)
+ {
+ const wchar_t *command_name = argv[idx];
+ wcstring path;
+ if (path_get_path(command_name, &path))
+ {
+ append_format(stdout_buffer, L"%ls\n", path.c_str());
+ ++found;
+ }
+ }
+ return found ? STATUS_BUILTIN_OK : STATUS_BUILTIN_ERROR;
+}
+
+/**
+ A generic bultin that only supports showing a help message. This is
+ only a placeholder that prints the help message. Useful for
+ commands that live in the parser.
+*/
+static int builtin_generic(parser_t &parser, wchar_t **argv)
+{
+ int argc=builtin_count_args(argv);
+
+ /* Hackish - if we have no arguments other than the command, we are a "naked invocation" and we just print help */
+ if (argc == 1)
+ {
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ { L"help", no_argument, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"h",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+ return STATUS_BUILTIN_ERROR;
+}
+
+/**
+ Output a definition of the specified function to the specified
+ string. Used by the functions builtin.
+*/
+static void functions_def(const wcstring &name, wcstring &out)
+{
+ CHECK(! name.empty(),);
+
+ wcstring desc, def;
+ function_get_desc(name, &desc);
+ function_get_definition(name, &def);
+
+ event_t search(EVENT_ANY);
+
+ search.function_name = name;
+
+ std::vector<event_t *> ev;
+ event_get(search, &ev);
+
+ out.append(L"function ");
+
+ /* Typically we prefer to specify the function name first, e.g. "function foo --description bar"
+ But If the function name starts with a -, we'll need to output it after all the options. */
+ bool defer_function_name = (name.at(0) == L'-');
+ if (! defer_function_name)
+ {
+ out.append(escape_string(name, true));
+ }
+
+ if (! desc.empty())
+ {
+ wcstring esc_desc = escape_string(desc, true);
+ out.append(L" --description ");
+ out.append(esc_desc);
+ }
+
+ if (!function_get_shadows(name))
+ {
+ out.append(L" --no-scope-shadowing");
+ }
+
+ for (size_t i=0; i<ev.size(); i++)
+ {
+ event_t *next = ev.at(i);
+ switch (next->type)
+ {
+ case EVENT_SIGNAL:
+ {
+ append_format(out, L" --on-signal %ls", sig2wcs(next->param1.signal));
+ break;
+ }
+
+ case EVENT_VARIABLE:
+ {
+ append_format(out, L" --on-variable %ls", next->str_param1.c_str());
+ break;
+ }
+
+ case EVENT_EXIT:
+ {
+ if (next->param1.pid > 0)
+ append_format(out, L" --on-process-exit %d", next->param1.pid);
+ else
+ append_format(out, L" --on-job-exit %d", -next->param1.pid);
+ break;
+ }
+
+ case EVENT_JOB_ID:
+ {
+ const job_t *j = job_get(next->param1.job_id);
+ if (j)
+ append_format(out, L" --on-job-exit %d", j->pgid);
+ break;
+ }
+
+ case EVENT_GENERIC:
+ {
+ append_format(out, L" --on-event %ls", next->str_param1.c_str());
+ break;
+ }
+
+ }
+
+ }
+
+
+ wcstring_list_t named = function_get_named_arguments(name);
+ if (! named.empty())
+ {
+ append_format(out, L" --argument");
+ for (size_t i=0; i < named.size(); i++)
+ {
+ append_format(out, L" %ls", named.at(i).c_str());
+ }
+ }
+
+ /* Output the function name if we deferred it */
+ if (defer_function_name)
+ {
+ out.append(L" -- ");
+ out.append(escape_string(name, true));
+ }
+
+ /* Output any inherited variables as `set -l` lines */
+ std::map<wcstring,env_var_t> inherit_vars = function_get_inherit_vars(name);
+ for (std::map<wcstring,env_var_t>::const_iterator it = inherit_vars.begin(), end = inherit_vars.end(); it != end; ++it)
+ {
+ wcstring_list_t lst;
+ if (!it->second.missing())
+ {
+ tokenize_variable_array(it->second, lst);
+ }
+
+ /* This forced tab is crummy, but we don't know what indentation style the function uses */
+ append_format(out, L"\n\tset -l %ls", it->first.c_str());
+ for (wcstring_list_t::const_iterator arg_it = lst.begin(), arg_end = lst.end(); arg_it != arg_end; ++arg_it)
+ {
+ wcstring earg = escape_string(*arg_it, ESCAPE_ALL);
+ out.push_back(L' ');
+ out.append(earg);
+ }
+ }
+
+ /* This forced tab is sort of crummy - not all functions start with a tab */
+ append_format(out, L"\n\t%ls", def.c_str());
+
+ /* Append a newline before the 'end', unless there already is one there */
+ if (! string_suffixes_string(L"\n", def))
+ {
+ out.push_back(L'\n');
+ }
+ out.append(L"end\n");
+}
+
+
+/**
+ The functions builtin, used for listing and erasing functions.
+*/
+static int builtin_functions(parser_t &parser, wchar_t **argv)
+{
+ int i;
+ int erase=0;
+ wchar_t *desc=0;
+
+ int argc=builtin_count_args(argv);
+ int list=0;
+ int show_hidden=0;
+ int res = STATUS_BUILTIN_OK;
+ int query = 0;
+ int copy = 0;
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ {
+ L"erase", no_argument, 0, 'e'
+ }
+ ,
+ {
+ L"description", required_argument, 0, 'd'
+ }
+ ,
+ {
+ L"names", no_argument, 0, 'n'
+ }
+ ,
+ {
+ L"all", no_argument, 0, 'a'
+ }
+ ,
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ L"query", no_argument, 0, 'q'
+ }
+ ,
+ {
+ L"copy", no_argument, 0, 'c'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"ed:nahqc",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+
+ return STATUS_BUILTIN_ERROR;
+
+ case 'e':
+ erase=1;
+ break;
+
+ case 'd':
+ desc=woptarg;
+ break;
+
+ case 'n':
+ list=1;
+ break;
+
+ case 'a':
+ show_hidden=1;
+ break;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 'q':
+ query = 1;
+ break;
+
+ case 'c':
+ copy = 1;
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+
+ /*
+ Erase, desc, query, copy and list are mutually exclusive
+ */
+ if ((erase + (!!desc) + list + query + copy) > 1)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Invalid combination of options\n"),
+ argv[0]);
+
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if (erase)
+ {
+ int i;
+ for (i=woptind; i<argc; i++)
+ function_remove(argv[i]);
+ return STATUS_BUILTIN_OK;
+ }
+ else if (desc)
+ {
+ wchar_t *func;
+
+ if (argc-woptind != 1)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Expected exactly one function name\n"),
+ argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+ func = argv[woptind];
+ if (!function_exists(func))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Function '%ls' does not exist\n"),
+ argv[0],
+ func);
+
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ function_set_desc(func, desc);
+
+ return STATUS_BUILTIN_OK;
+ }
+ else if (list || (argc==woptind))
+ {
+ int is_screen = !builtin_out_redirect && isatty(1);
+ size_t i;
+ wcstring_list_t names = function_get_names(show_hidden);
+ std::sort(names.begin(), names.end());
+ if (is_screen)
+ {
+ wcstring buff;
+
+ for (i=0; i<names.size(); i++)
+ {
+ buff.append(names.at(i));
+ buff.append(L", ");
+ }
+
+ write_screen(buff, stdout_buffer);
+ }
+ else
+ {
+ for (i=0; i<names.size(); i++)
+ {
+ stdout_buffer.append(names.at(i).c_str());
+ stdout_buffer.append(L"\n");
+ }
+ }
+
+ return STATUS_BUILTIN_OK;
+ }
+ else if (copy)
+ {
+ wcstring current_func;
+ wcstring new_func;
+
+ if (argc-woptind != 2)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Expected exactly two names (current function name, and new function name)\n"),
+ argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+ current_func = argv[woptind];
+ new_func = argv[woptind+1];
+
+ if (!function_exists(current_func))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Function '%ls' does not exist\n"),
+ argv[0],
+ current_func.c_str());
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if ((wcsfuncname(new_func) != 0) || parser_keywords_is_reserved(new_func))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Illegal function name '%ls'\n"),
+ argv[0],
+ new_func.c_str());
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ // keep things simple: don't allow existing names to be copy targets.
+ if (function_exists(new_func))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Function '%ls' already exists. Cannot create copy '%ls'\n"),
+ argv[0],
+ new_func.c_str(),
+ current_func.c_str());
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if (function_copy(current_func, new_func))
+ return STATUS_BUILTIN_OK;
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ for (i=woptind; i<argc; i++)
+ {
+ if (!function_exists(argv[i]))
+ res++;
+ else
+ {
+ if (!query)
+ {
+ if (i != woptind)
+ stdout_buffer.append(L"\n");
+
+ functions_def(argv[i], stdout_buffer);
+ }
+ }
+ }
+
+ return res;
+}
+
+static unsigned int builtin_echo_digit(wchar_t wc, unsigned int base)
+{
+ // base must be hex or octal
+ assert(base == 8 || base == 16);
+ switch (wc)
+ {
+ case L'0':
+ return 0;
+ case L'1':
+ return 1;
+ case L'2':
+ return 2;
+ case L'3':
+ return 3;
+ case L'4':
+ return 4;
+ case L'5':
+ return 5;
+ case L'6':
+ return 6;
+ case L'7':
+ return 7;
+ }
+
+ 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;
+}
+
+/* Parse a numeric escape sequence in str, returning whether we succeeded.
+ Also return the number of characters consumed and the resulting value.
+ Supported escape sequences:
+
+ \0nnn: octal value, zero to three digits
+ \nnn: octal value, one to three digits
+ \xhh: hex value, one to two digits
+*/
+static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *consumed, unsigned char *out_val)
+{
+ bool success = false;
+ unsigned char val = 0; //resulting character
+ unsigned int start = 0; //the first character of the numeric part of the sequence
+
+ unsigned int base = 0, max_digits = 0;
+ if (builtin_echo_digit(str[0], 8) != UINT_MAX)
+ {
+ // Octal escape
+ base = 8;
+
+ // If the first digit is a 0, we allow four digits (including that zero)
+ // Otherwise we allow 3.
+ max_digits = (str[0] == L'0' ? 4 : 3);
+ }
+ else if (str[0] == L'x')
+ {
+ // Hex escape
+ base = 16;
+ max_digits = 2;
+
+ // Skip the x
+ start = 1;
+ }
+
+ if (base != 0)
+ {
+ unsigned int idx;
+ 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;
+}
+
+/** The echo builtin.
+ bash only respects -n if it's the first argument. We'll do the same.
+ We also support a new option -s to mean "no spaces"
+*/
+
+static int builtin_echo(parser_t &parser, wchar_t **argv)
+{
+ /* Skip first arg */
+ if (! *argv++)
+ return STATUS_BUILTIN_ERROR;
+
+ /* Process options. Options must come at the beginning - the first non-option kicks us out. */
+ bool print_newline = true, print_spaces = true, interpret_special_chars = false;
+ size_t option_idx = 0;
+ for (option_idx = 0; argv[option_idx] != NULL; option_idx++)
+ {
+ const wchar_t *arg = argv[option_idx];
+ assert(arg != NULL);
+ bool arg_is_valid_option = false;
+ if (arg[0] == L'-')
+ {
+ // We have a leading dash. Ensure that every subseqnent character is a valid option.
+ size_t i = 1;
+ while (arg[i] != L'\0' && wcschr(L"nesE", arg[i]) != NULL)
+ {
+ i++;
+ }
+ // We must have at least two characters to be a valid option, and have consumed the whole string
+ arg_is_valid_option = (i >= 2 && arg[i] == L'\0');
+ }
+
+ if (! arg_is_valid_option)
+ {
+ // This argument is not an option, so there are no more options
+ break;
+ }
+
+ // Ok, we are sure the argument is an option. Parse it.
+ assert(arg_is_valid_option);
+ for (size_t i=1; arg[i] != L'\0'; i++)
+ {
+ switch (arg[i])
+ {
+ case L'n':
+ print_newline = false;
+ break;
+ case L'e':
+ interpret_special_chars = true;
+ break;
+ case L's':
+ print_spaces = false;
+ break;
+ case L'E':
+ interpret_special_chars = false;
+ break;
+ default:
+ assert(0 && "Unexpected character in builtin_echo argument");
+ break;
+ }
+ }
+ }
+
+ /* The special character \c can be used to indicate no more output */
+ bool continue_output = true;
+
+ /* Skip over the options */
+ const wchar_t * const *args_to_echo = argv + option_idx;
+ for (size_t idx = 0; continue_output && args_to_echo[idx] != NULL; idx++)
+ {
+ if (print_spaces && idx > 0)
+ {
+ stdout_buffer.push_back(' ');
+ }
+
+ const wchar_t *str = args_to_echo[idx];
+ for (size_t j=0; continue_output && str[j]; j++)
+ {
+ if (! interpret_special_chars || str[j] != L'\\')
+ {
+ /* Not an escape */
+ stdout_buffer.push_back(str[j]);
+ }
+ else
+ {
+ /* Most escapes consume one character in addition to the backslash; the numeric sequences may consume more, while an unrecognized escape sequence consumes none. */
+ wchar_t wc;
+ size_t consumed = 1;
+ switch (str[j+1])
+ {
+ case L'a':
+ wc = L'\a';
+ break;
+ case L'b':
+ wc = L'\b';
+ break;
+ case L'e':
+ wc = L'\x1B';
+ break;
+ case L'f':
+ wc = L'\f';
+ break;
+ case L'n':
+ wc = L'\n';
+ break;
+ case L'r':
+ wc = L'\r';
+ break;
+ case L't':
+ wc = L'\t';
+ break;
+ case L'v':
+ wc = L'\v';
+ break;
+ case L'\\':
+ wc = L'\\';
+ break;
+
+ case L'c':
+ wc = 0;
+ continue_output = false;
+ break;
+
+ default:
+ {
+ /* Octal and hex escape sequences */
+ unsigned char narrow_val = 0;
+ if (builtin_echo_parse_numeric_sequence(str + j + 1, &consumed, &narrow_val))
+ {
+ /* Here consumed must have been set to something. The narrow_val is a literal byte that we want to output (#1894) */
+ wc = ENCODE_DIRECT_BASE + narrow_val % 256;
+ }
+ else
+ {
+ /* Not a recognized escape. We consume only the backslash. */
+ wc = L'\\';
+ consumed = 0;
+ }
+ break;
+ }
+ }
+
+ /* Skip over characters that were part of this escape sequence (but not the backslash, which will be handled by the loop increment */
+ j += consumed;
+
+ if (continue_output)
+ {
+ stdout_buffer.push_back(wc);
+ }
+ }
+ }
+ }
+ if (print_newline && continue_output)
+ {
+ stdout_buffer.push_back('\n');
+ }
+ return STATUS_BUILTIN_OK;
+}
+
+/** The pwd builtin. We don't respect -P to resolve symbolic links because we try to always resolve them. */
+static int builtin_pwd(parser_t &parser, wchar_t **argv)
+{
+ wchar_t dir_path[4096];
+ wchar_t *res = wgetcwd(dir_path, 4096);
+ if (res == NULL)
+ {
+ return STATUS_BUILTIN_ERROR;
+ }
+ else
+ {
+ stdout_buffer.append(dir_path);
+ stdout_buffer.push_back(L'\n');
+ return STATUS_BUILTIN_OK;
+ }
+}
+
+/** Adds a function to the function set. It calls into function.cpp to perform any heavy lifting. */
+int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstring &contents, int definition_line_offset, wcstring *out_err)
+{
+ assert(out_err != NULL);
+
+ /* wgetopt expects 'function' as the first argument. Make a new wcstring_list with that property. */
+ wcstring_list_t args;
+ args.push_back(L"function");
+ args.insert(args.end(), c_args.begin(), c_args.end());
+
+ /* Hackish const_cast matches the one in builtin_run */
+ const null_terminated_array_t<wchar_t> argv_array(args);
+ wchar_t **argv = const_cast<wchar_t **>(argv_array.get());
+
+ int argc = builtin_count_args(argv);
+ int res=STATUS_BUILTIN_OK;
+ wchar_t *desc=0;
+ std::vector<event_t> events;
+
+ bool has_named_arguments = false;
+ wcstring_list_t named_arguments;
+ wcstring_list_t inherit_vars;
+
+ bool shadows = true;
+
+ woptind=0;
+
+ wcstring_list_t wrap_targets;
+
+ /* If -a/--argument-names is specified before the function name,
+ then the function name is the last positional, e.g. `function -a arg1 arg2 name`.
+ If it is specified after the function name (or not specified at all) then the
+ function name is the first positional. This is the common case. */
+ bool name_is_first_positional = true;
+ wcstring_list_t positionals;
+
+ const struct woption long_options[] =
+ {
+ { L"description", required_argument, 0, 'd' },
+ { L"on-signal", required_argument, 0, 's' },
+ { L"on-job-exit", required_argument, 0, 'j' },
+ { L"on-process-exit", required_argument, 0, 'p' },
+ { L"on-variable", required_argument, 0, 'v' },
+ { L"on-event", required_argument, 0, 'e' },
+ { L"wraps", required_argument, 0, 'w' },
+ { L"help", no_argument, 0, 'h' },
+ { L"argument-names", no_argument, 0, 'a' },
+ { L"no-scope-shadowing", no_argument, 0, 'S' },
+ { L"inherit-variable", required_argument, 0, 'V' },
+ { 0, 0, 0, 0 }
+ };
+
+ while (1 && (!res))
+ {
+ int opt_index = 0;
+
+ // The leading - here specifies RETURN_IN_ORDER
+ int opt = wgetopt_long(argc,
+ argv,
+ L"-d:s:j:p:v:e:haSV:",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ 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;
+ break;
+
+ case 'd':
+ desc=woptarg;
+ break;
+
+ case 's':
+ {
+ int sig = wcs2sig(woptarg);
+
+ if (sig < 0)
+ {
+ append_format(*out_err,
+ _(L"%ls: Unknown signal '%ls'\n"),
+ argv[0],
+ woptarg);
+ res=1;
+ break;
+ }
+ events.push_back(event_t::signal_event(sig));
+ break;
+ }
+
+ case 'v':
+ {
+ if (wcsvarname(woptarg))
+ {
+ append_format(*out_err,
+ _(L"%ls: Invalid variable name '%ls'\n"),
+ argv[0],
+ woptarg);
+ res=STATUS_BUILTIN_ERROR;
+ break;
+ }
+
+ events.push_back(event_t::variable_event(woptarg));
+ break;
+ }
+
+
+ case 'e':
+ {
+ events.push_back(event_t::generic_event(woptarg));
+ break;
+ }
+
+ case 'j':
+ case 'p':
+ {
+ pid_t pid;
+ wchar_t *end;
+ event_t e(EVENT_ANY);
+
+ if ((opt == 'j') &&
+ (wcscasecmp(woptarg, L"caller") == 0))
+ {
+ int job_id = -1;
+
+ if (is_subshell)
+ {
+ size_t block_idx = 0;
+
+ /* Find the outermost substitution block */
+ for (block_idx = 0; ; block_idx++)
+ {
+ const block_t *b = parser.block_at_index(block_idx);
+ if (b == NULL || b->type() == SUBST)
+ break;
+ }
+
+ /* Go one step beyond that, to get to the caller */
+ const block_t *caller_block = parser.block_at_index(block_idx + 1);
+ if (caller_block != NULL && caller_block->job != NULL)
+ {
+ job_id = caller_block->job->job_id;
+ }
+ }
+
+ if (job_id == -1)
+ {
+ append_format(*out_err,
+ _(L"%ls: Cannot find calling job for event handler\n"),
+ argv[0]);
+ res=1;
+ }
+ else
+ {
+ e.type = EVENT_JOB_ID;
+ e.param1.job_id = job_id;
+ }
+
+ }
+ else
+ {
+ errno = 0;
+ pid = fish_wcstoi(woptarg, &end, 10);
+ if (errno || !end || *end)
+ {
+ append_format(*out_err,
+ _(L"%ls: Invalid process id %ls\n"),
+ argv[0],
+ woptarg);
+ res=1;
+ break;
+ }
+
+
+ e.type = EVENT_EXIT;
+ e.param1.pid = (opt=='j'?-1:1)*abs(pid);
+ }
+ if (res)
+ {
+ /* nothing */
+ }
+ else
+ {
+ events.push_back(e);
+ }
+ break;
+ }
+
+ case 'a':
+ has_named_arguments = true;
+ /* The function name is the first positional unless -a comes before all positionals */
+ name_is_first_positional = ! positionals.empty();
+ break;
+
+ case 'S':
+ shadows = 0;
+ break;
+
+ case 'w':
+ wrap_targets.push_back(woptarg);
+ break;
+
+ case 'V':
+ {
+ if (wcsvarname(woptarg))
+ {
+ append_format(*out_err, _(L"%ls: Invalid variable name '%ls'\n"), argv[0], woptarg);
+ res = STATUS_BUILTIN_ERROR;
+ break;
+ }
+
+ inherit_vars.push_back(woptarg);
+ break;
+ }
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 1:
+ assert(woptarg != NULL);
+ positionals.push_back(woptarg);
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ res = 1;
+ 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 (name_is_missing)
+ {
+ append_format(*out_err,
+ _(L"%ls: Expected function name\n"),
+ argv[0]);
+ res=1;
+ }
+ else if (wcsfuncname(function_name))
+ {
+ append_format(*out_err,
+ _(L"%ls: Illegal function name '%ls'\n"),
+ 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\n"),
+ argv[0],
+ function_name.c_str());
+
+ res=1;
+ }
+ else if (function_name.empty())
+ {
+ append_format(*out_err, _(L"%ls: No function name given\n"), argv[0]);
+ res=1;
+ }
+ 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'\n"),
+ 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 %d\n"),
+ argv[0],
+ positionals.size() + 1);
+ res=1;
+ }
+ }
+
+ /* Here we actually define the function! */
+ function_data_t d;
+
+ d.name = function_name;
+ if (desc)
+ d.description = desc;
+ d.events.swap(events);
+ d.shadows = shadows;
+ d.named_arguments.swap(named_arguments);
+ d.inherit_vars.swap(inherit_vars);
+
+ for (size_t i=0; i<d.events.size(); i++)
+ {
+ event_t &e = d.events.at(i);
+ e.function_name = d.name;
+ }
+
+ 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));
+ }
+ }
+
+ return res;
+}
+
+/**
+ The random builtin. For generating random numbers.
+*/
+static int builtin_random(parser_t &parser, wchar_t **argv)
+{
+ static int seeded=0;
+ static struct drand48_data seed_buffer;
+
+ int argc = builtin_count_args(argv);
+
+ woptind=0;
+
+ static const struct woption
+ long_options[] =
+ {
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"h",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ break;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ }
+
+ }
+
+ switch (argc-woptind)
+ {
+
+ case 0:
+ {
+ long res;
+
+ if (!seeded)
+ {
+ seeded=1;
+ srand48_r(time(0), &seed_buffer);
+ }
+ lrand48_r(&seed_buffer, &res);
+
+ append_format(stdout_buffer, L"%ld\n", labs(res%32767));
+ break;
+ }
+
+ case 1:
+ {
+ long foo;
+ wchar_t *end=0;
+
+ errno=0;
+ foo = wcstol(argv[woptind], &end, 10);
+ if (errno || *end)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Seed value '%ls' is not a valid number\n"),
+ argv[0],
+ argv[woptind]);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+ seeded=1;
+ srand48_r(foo, &seed_buffer);
+ break;
+ }
+
+ default:
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Expected zero or one argument, got %d\n"),
+ argv[0],
+ argc-woptind);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ }
+ return STATUS_BUILTIN_OK;
+}
+
+
+/**
+ The read builtin. Reads from stdin and stores the values in environment variables.
+*/
+static int builtin_read(parser_t &parser, wchar_t **argv)
+{
+ wcstring buff;
+ int i, argc = builtin_count_args(argv);
+ int place = ENV_USER;
+ const wchar_t *prompt = DEFAULT_READ_PROMPT;
+ const wchar_t *right_prompt = L"";
+ const wchar_t *commandline = L"";
+ int exit_res=STATUS_BUILTIN_OK;
+ const wchar_t *mode_name = READ_MODE_NAME;
+ int nchars=0;
+ wchar_t *end;
+ int shell = 0;
+ int array = 0;
+ bool split_null = false;
+
+ woptind=0;
+
+ while (1)
+ {
+ static const struct woption
+ long_options[] =
+ {
+ {
+ L"export", no_argument, 0, 'x'
+ }
+ ,
+ {
+ L"global", no_argument, 0, 'g'
+ }
+ ,
+ {
+ L"local", no_argument, 0, 'l'
+ }
+ ,
+ {
+ L"universal", no_argument, 0, 'U'
+ }
+ ,
+ {
+ L"unexport", no_argument, 0, 'u'
+ }
+ ,
+ {
+ L"prompt", required_argument, 0, 'p'
+ }
+ ,
+ {
+ L"right-prompt", required_argument, 0, 'R'
+ }
+ ,
+ {
+ L"command", required_argument, 0, 'c'
+ }
+ ,
+ {
+ L"mode-name", required_argument, 0, 'm'
+ }
+ ,
+ {
+ L"nchars", required_argument, 0, 'n'
+ }
+ ,
+ {
+ L"shell", no_argument, 0, 's'
+ }
+ ,
+ {
+ L"array", no_argument, 0, 'a'
+ }
+ ,
+ {
+ L"null", no_argument, 0, 'z'
+ }
+ ,
+ {
+ L"help", no_argument, 0, 'h'
+ }
+ ,
+ {
+ 0, 0, 0, 0
+ }
+ }
+ ;
+
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"xglUup:R:c:hm:n:saz",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+
+ case L'x':
+ place |= ENV_EXPORT;
+ break;
+
+ case L'g':
+ place |= ENV_GLOBAL;
+ break;
+
+ case L'l':
+ place |= ENV_LOCAL;
+ break;
+
+ case L'U':
+ place |= ENV_UNIVERSAL;
+ break;
+
+ case L'u':
+ place |= ENV_UNEXPORT;
+ break;
+
+ case L'p':
+ prompt = woptarg;
+ break;
+
+ case L'R':
+ right_prompt = woptarg;
+ break;
+
+ case L'c':
+ commandline = woptarg;
+ break;
+
+ case L'm':
+ mode_name = woptarg;
+ break;
+
+ case L'n':
+ errno = 0;
+ nchars = fish_wcstoi(woptarg, &end, 10);
+ if (errno || *end != 0)
+ {
+ switch (errno)
+ {
+ case ERANGE:
+ append_format(stderr_buffer,
+ _(L"%ls: Argument '%ls' is out of range\n"),
+ argv[0],
+ woptarg);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+ default:
+ append_format(stderr_buffer,
+ _(L"%ls: Argument '%ls' must be an integer\n"),
+ argv[0],
+ woptarg);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ }
+ break;
+
+ case 's':
+ shell = 1;
+ break;
+
+ case 'a':
+ array = 1;
+ break;
+
+ case L'z':
+ split_null = true;
+ break;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case L'?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ }
+
+ if ((place & ENV_UNEXPORT) && (place & ENV_EXPORT))
+ {
+ append_format(stderr_buffer,
+ BUILTIN_ERR_EXPUNEXP,
+ argv[0]);
+
+
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if ((place&ENV_LOCAL?1:0) + (place & ENV_GLOBAL?1:0) + (place & ENV_UNIVERSAL?1:0) > 1)
+ {
+ append_format(stderr_buffer,
+ BUILTIN_ERR_GLOCAL,
+ argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if (array && woptind+1 != argc)
+ {
+ append_format(stderr_buffer, _(L"%ls: --array option requires a single variable name.\n"), argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ /*
+ Verify all variable names
+ */
+ for (i=woptind; i<argc; i++)
+ {
+ wchar_t *src;
+
+ if (!wcslen(argv[i]))
+ {
+ append_format(stderr_buffer, BUILTIN_ERR_VARNAME_ZERO, argv[0]);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ for (src=argv[i]; *src; src++)
+ {
+ if ((!iswalnum(*src)) && (*src != L'_'))
+ {
+ append_format(stderr_buffer, BUILTIN_ERR_VARCHAR, argv[0], *src);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ }
+
+ }
+
+ /*
+ The call to reader_readline may change woptind, so we save it away here
+ */
+ i=woptind;
+
+ /*
+ Check if we should read interactively using \c reader_readline()
+ */
+ if (isatty(0) && builtin_stdin == 0 && !split_null)
+ {
+ const wchar_t *line;
+
+ reader_push(mode_name);
+ reader_set_left_prompt(prompt);
+ reader_set_right_prompt(right_prompt);
+ if (shell)
+ {
+ reader_set_complete_function(&complete);
+ reader_set_highlight_function(&highlight_shell);
+ reader_set_test_function(&reader_shell_test);
+ }
+ /* No autosuggestions or abbreviations in builtin_read */
+ reader_set_allow_autosuggesting(false);
+ reader_set_expand_abbreviations(false);
+ reader_set_exit_on_interrupt(true);
+
+ reader_set_buffer(commandline, wcslen(commandline));
+ proc_push_interactive(1);
+
+ event_fire_generic(L"fish_prompt");
+ line = reader_readline(nchars);
+ proc_pop_interactive();
+ if (line)
+ {
+ if (0 < nchars && nchars < wcslen(line))
+ {
+ // line may be longer than nchars if a keybinding used `commandline -i`
+ // note: we're deliberately throwing away the tail of the commandline.
+ // It shouldn't be unread because it was produced with `commandline -i`,
+ // not typed.
+ buff = wcstring(line, nchars);
+ }
+ else
+ {
+ buff = wcstring(line);
+ }
+ }
+ else
+ {
+ exit_res = STATUS_BUILTIN_ERROR;
+ }
+ reader_pop();
+ }
+ else
+ {
+ int eof=0;
+
+ buff.clear();
+
+ while (1)
+ {
+ int finished=0;
+
+ wchar_t res=0;
+ mbstate_t state = {};
+
+ while (!finished)
+ {
+ char b;
+ if (read_blocked(builtin_stdin, &b, 1) <= 0)
+ {
+ eof=1;
+ break;
+ }
+
+ 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;
+ case 0:
+ finished = 1;
+ break;
+
+ default:
+ finished=1;
+ break;
+
+ }
+ }
+
+ if (eof)
+ break;
+
+ if (!split_null && res == L'\n')
+ break;
+
+ if (split_null && res == L'\0')
+ break;
+
+ buff.push_back(res);
+
+ if (0 < nchars && (size_t)nchars <= buff.size())
+ {
+ break;
+ }
+ }
+
+ if (buff.empty() && eof)
+ {
+ exit_res = 1;
+ }
+ }
+
+ 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
+ {
+ 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
+ {
+ wcstring_range loc = wcstring_range(0,0);
+
+ while (i<argc)
+ {
+ loc = wcstring_tok(buff, (i+1<argc) ? ifs : L"", loc);
+ env_set(argv[i], loc.first == wcstring::npos ? L"" : &buff.c_str()[loc.first], place);
+
+ ++i;
+ }
+
+ }
+ }
+
+ return exit_res;
+}
+
+/**
+ The status builtin. Gives various status information on fish.
+*/
+static int builtin_status(parser_t &parser, wchar_t **argv)
+{
+
+ 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;
+
+ int argc = builtin_count_args(argv);
+ int res=STATUS_BUILTIN_OK;
+
+ woptind=0;
+
+
+ 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 = wgetopt_long(argc,
+ argv,
+ L":cbilfnhj:t",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+ case 'c':
+ mode = IS_SUBST;
+ break;
+
+ case 'b':
+ mode = IS_BLOCK;
+ break;
+
+ case 'i':
+ mode = IS_INTERACTIVE;
+ break;
+
+ case 'l':
+ mode = IS_LOGIN;
+ break;
+
+ case 'f':
+ mode = CURRENT_FILENAME;
+ break;
+
+ case 'n':
+ mode = CURRENT_LINE_NUMBER;
+ break;
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+ case 'j':
+ if (wcscmp(woptarg, L"full") == 0)
+ job_control_mode = JOB_CONTROL_ALL;
+ else if (wcscmp(woptarg, L"interactive") == 0)
+ job_control_mode = JOB_CONTROL_INTERACTIVE;
+ else if (wcscmp(woptarg, L"none") == 0)
+ job_control_mode = JOB_CONTROL_NONE;
+ else
+ {
+ append_format(stderr_buffer,
+ L"%ls: Invalid job control mode '%ls'\n",
+ L"status", woptarg);
+ res = 1;
+ }
+ mode = DONE;
+ break;
+
+ case 't':
+ mode = STACK_TRACE;
+ break;
+
+
+ case ':':
+ builtin_missing_argument(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[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");
+
+ append_format(stdout_buffer, L"%ls\n", fn);
+
+ break;
+ }
+
+ case CURRENT_LINE_NUMBER:
+ {
+ append_format(stdout_buffer, 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:
+ {
+ parser.stack_trace(0, stdout_buffer);
+ break;
+ }
+
+ case NORMAL:
+ {
+ if (is_login)
+ append_format(stdout_buffer, _(L"This is a login shell\n"));
+ else
+ append_format(stdout_buffer, _(L"This is not a login shell\n"));
+
+ append_format(stdout_buffer, _(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")));
+
+ parser.stack_trace(0, stdout_buffer);
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+
+/**
+ The exit builtin. Calls reader_exit to exit and returns the value specified.
+*/
+static int builtin_exit(parser_t &parser, 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)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Argument '%ls' must be an integer\n"),
+ argv[0],
+ argv[1]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ break;
+ }
+
+ default:
+ {
+ append_format(stderr_buffer,
+ BUILTIN_ERR_TOO_MANY_ARGUMENTS,
+ argv[0]);
+
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ }
+ reader_exit(1, 0);
+ return (int)ec;
+}
+
+/**
+ The cd builtin. Changes the current directory to the one specified
+ or to $HOME if none is specified. The directory can be relative to
+ any directory in the CDPATH variable.
+*/
+static int builtin_cd(parser_t &parser, wchar_t **argv)
+{
+ env_var_t dir_in;
+ wcstring dir;
+ int res=STATUS_BUILTIN_OK;
+
+
+ if (argv[1] == NULL)
+ {
+ dir_in = env_get_string(L"HOME");
+ if (dir_in.missing_or_empty())
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Could not find home directory\n"),
+ argv[0]);
+ }
+ }
+ else
+ {
+ dir_in = argv[1];
+ }
+
+ bool got_cd_path = false;
+ if (! dir_in.missing())
+ {
+ got_cd_path = path_get_cdpath(dir_in, &dir);
+ }
+
+ if (!got_cd_path)
+ {
+ if (errno == ENOTDIR)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: '%ls' is not a directory\n"),
+ argv[0],
+ dir_in.c_str());
+ }
+ else if (errno == ENOENT)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: The directory '%ls' does not exist\n"),
+ argv[0],
+ dir_in.c_str());
+ }
+ else if (errno == EROTTEN)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: '%ls' is a rotten symlink\n"),
+ argv[0],
+ dir_in.c_str());
+
+ }
+ else
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Unknown error trying to locate directory '%ls'\n"),
+ argv[0],
+ dir_in.c_str());
+
+ }
+
+
+ if (!get_is_interactive())
+ {
+ stderr_buffer.append(parser.current_line());
+ }
+
+ res = 1;
+ }
+ else if (wchdir(dir) != 0)
+ {
+ struct stat buffer;
+ int status;
+
+ status = wstat(dir, &buffer);
+ if (!status && S_ISDIR(buffer.st_mode))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Permission denied: '%ls'\n"),
+ argv[0],
+ dir.c_str());
+
+ }
+ else
+ {
+
+ append_format(stderr_buffer,
+ _(L"%ls: '%ls' is not a directory\n"),
+ argv[0],
+ dir.c_str());
+ }
+
+ if (!get_is_interactive())
+ {
+ stderr_buffer.append(parser.current_line());
+ }
+
+ res = 1;
+ }
+ else if (!env_set_pwd())
+ {
+ res=1;
+ append_format(stderr_buffer, _(L"%ls: Could not set PWD variable\n"), argv[0]);
+ }
+
+ return res;
+}
+
+/**
+ Implementation of the builtin count command, used to count the
+ number of arguments sent to it.
+ */
+static int builtin_count(parser_t &parser, wchar_t ** argv)
+{
+ int argc;
+ argc = builtin_count_args(argv);
+ append_format(stdout_buffer, L"%d\n", argc-1);
+ return !(argc-1);
+}
+
+/**
+ Implementation of the builtin contains command, used to check if a
+ specified string is part of a list.
+ */
+static int builtin_contains(parser_t &parser, wchar_t ** argv)
+{
+ int argc;
+ argc = builtin_count_args(argv);
+ wchar_t *needle;
+ bool should_output_index = false;
+
+ woptind=0;
+
+ const struct woption long_options[] =
+ {
+ { L"help", no_argument, 0, 'h' } ,
+ { L"index", no_argument, 0, 'i' },
+ { 0, 0, 0, 0 }
+ };
+
+ while (1)
+ {
+ int opt_index = 0;
+
+ int opt = wgetopt_long(argc,
+ argv,
+ L"+hi",
+ long_options,
+ &opt_index);
+ if (opt == -1)
+ break;
+
+ switch (opt)
+ {
+ case 0:
+ assert(opt_index >= 0 && (size_t)opt_index < sizeof long_options / sizeof *long_options);
+ if (long_options[opt_index].flag != 0)
+ break;
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ long_options[opt_index].name);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+
+
+ case ':':
+ builtin_missing_argument(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ case '?':
+ builtin_unknown_option(parser, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+
+ case 'i':
+ should_output_index = true;
+ break;
+ }
+
+ }
+
+ needle = argv[woptind];
+ if (!needle)
+ {
+ append_format(stderr_buffer, _(L"%ls: Key not specified\n"), argv[0]);
+ }
+
+
+ for (int i=woptind+1; i<argc; i++)
+ {
+
+ if (!wcscmp(needle, argv[i]))
+ {
+ if (should_output_index) append_format(stdout_buffer, L"%d\n", i-woptind);
+ return 0;
+ }
+ }
+ return 1;
+
+}
+
+
+/**
+ The . (dot) builtin, sometimes called source. Evaluates the contents of a file.
+*/
+static int builtin_source(parser_t &parser, wchar_t ** argv)
+{
+ ASSERT_IS_MAIN_THREAD();
+ int fd;
+ int res = STATUS_BUILTIN_OK;
+ struct stat buf;
+ int argc;
+
+ argc = builtin_count_args(argv);
+
+ const wchar_t *fn, *fn_intern;
+
+ if (argc < 2 || (wcscmp(argv[1], L"-") == 0))
+ {
+ fn = L"-";
+ fn_intern = fn;
+ fd = dup(builtin_stdin);
+ }
+ else
+ {
+
+ if ((fd = wopen_cloexec(argv[1], O_RDONLY)) == -1)
+ {
+ append_format(stderr_buffer, _(L"%ls: Error encountered while sourcing file '%ls':\n"), argv[0], argv[1]);
+ builtin_wperror(L"source");
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if (fstat(fd, &buf) == -1)
+ {
+ close(fd);
+ append_format(stderr_buffer, _(L"%ls: Error encountered while sourcing file '%ls':\n"), argv[0], argv[1]);
+ builtin_wperror(L"source");
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ if (!S_ISREG(buf.st_mode))
+ {
+ close(fd);
+ append_format(stderr_buffer, _(L"%ls: '%ls' is not a file\n"), argv[0], argv[1]);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ fn_intern = intern(argv[1]);
+ }
+
+ parser.push_block(new source_block_t(fn_intern));
+ reader_push_current_filename(fn_intern);
+
+ parse_util_set_argv((argc>2)?(argv+2):(argv+1), wcstring_list_t());
+
+ res = reader_read(fd, real_io ? *real_io : io_chain_t());
+
+ parser.pop_block();
+
+ if (res)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Error while reading file '%ls'\n"),
+ argv[0],
+ fn_intern == intern_static(L"-") ? L"<stdin>" : fn_intern);
+ }
+ else
+ {
+ res = proc_get_last_status();
+ }
+
+ /*
+ Do not close fd after calling reader_read. reader_read
+ automatically closes it before calling eval.
+ */
+
+ reader_pop_current_filename();
+
+ return res;
+}
+
+/**
+ Make the specified job the first job of the job list. Moving jobs
+ around in the list makes the list reflect the order in which the
+ jobs were used.
+*/
+static void make_first(job_t *j)
+{
+ job_promote(j);
+}
+
+
+/**
+ Builtin for putting a job in the foreground
+*/
+static int builtin_fg(parser_t &parser, wchar_t **argv)
+{
+ job_t *j=NULL;
+
+ if (argv[1] == 0)
+ {
+ /*
+ Select last constructed job (I.e. first job in the job que)
+ that is possible to put in the foreground
+ */
+
+ job_iterator_t jobs;
+ while ((j = jobs.next()))
+ {
+ if (job_get_flag(j, JOB_CONSTRUCTED) && (!job_is_completed(j)) &&
+ ((job_is_stopped(j) || (!job_get_flag(j, JOB_FOREGROUND))) && job_get_flag(j, JOB_CONTROL)))
+ {
+ break;
+ }
+ }
+ if (!j)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: There are no suitable jobs\n"),
+ argv[0]);
+ }
+ }
+ else if (argv[2] != 0)
+ {
+ /*
+ Specifying what more than one job to put to the foreground
+ is a syntax error, we still try to locate the job argv[1],
+ since we want to know if this is an ambigous job
+ specification or if this is an malformed job id
+ */
+ wchar_t *endptr;
+ int pid;
+ int found_job = 0;
+
+ errno = 0;
+ pid = fish_wcstoi(argv[1], &endptr, 10);
+ if (!(*endptr || errno))
+ {
+ j = job_get_from_pid(pid);
+ if (j)
+ found_job = 1;
+ }
+
+ if (found_job)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Ambiguous job\n"),
+ argv[0]);
+ }
+ else
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: '%ls' is not a job\n"),
+ argv[0],
+ argv[1]);
+ }
+
+ builtin_print_help(parser, argv[0], stderr_buffer);
+
+ j=0;
+
+ }
+ else
+ {
+ wchar_t *end;
+ int pid;
+ errno = 0;
+ pid = abs(fish_wcstoi(argv[1], &end, 10));
+
+ if (*end || errno)
+ {
+ append_format(stderr_buffer,
+ BUILTIN_ERR_NOT_NUMBER,
+ argv[0],
+ argv[1]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ }
+ else
+ {
+ j = job_get_from_pid(pid);
+ if (!j || !job_get_flag(j, JOB_CONSTRUCTED) || job_is_completed(j))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: No suitable job: %d\n"),
+ argv[0],
+ pid);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ j=0;
+ }
+ else if (!job_get_flag(j, JOB_CONTROL))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Can't put job %d, '%ls' to foreground because it is not under job control\n"),
+ argv[0],
+ pid,
+ j->command_wcstr());
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ j=0;
+ }
+ }
+ }
+
+ if (j)
+ {
+ if (builtin_err_redirect)
+ {
+ append_format(stderr_buffer,
+ 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_wcstr());
+ 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 j != 0;
+}
+
+/**
+ Helper function for builtin_bg()
+*/
+static int send_to_bg(parser_t &parser, job_t *j, const wchar_t *name)
+{
+ if (j == 0)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Unknown job '%ls'\n"),
+ L"bg",
+ name);
+ builtin_print_help(parser, L"bg", stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ else if (!job_get_flag(j, JOB_CONTROL))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Can't put job %d, '%ls' to background because it is not under job control\n"),
+ L"bg",
+ j->job_id,
+ j->command_wcstr());
+ builtin_print_help(parser, L"bg", stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ else
+ {
+ append_format(stderr_buffer,
+ _(L"Send job %d '%ls' to background\n"),
+ j->job_id,
+ j->command_wcstr());
+ }
+ make_first(j);
+ job_set_flag(j, JOB_FOREGROUND, 0);
+ job_continue(j, job_is_stopped(j));
+ return STATUS_BUILTIN_OK;
+}
+
+
+/**
+ Builtin for putting a job in the background
+*/
+static int builtin_bg(parser_t &parser, wchar_t **argv)
+{
+ int res = STATUS_BUILTIN_OK;
+
+ if (argv[1] == 0)
+ {
+ job_t *j;
+ job_iterator_t jobs;
+ while ((j = jobs.next()))
+ {
+ if (job_is_stopped(j) && job_get_flag(j, JOB_CONTROL) && (!job_is_completed(j)))
+ {
+ break;
+ }
+ }
+
+ if (!j)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: There are no suitable jobs\n"),
+ argv[0]);
+ res = 1;
+ }
+ else
+ {
+ res = send_to_bg(parser, j, _(L"(default)"));
+ }
+ }
+ else
+ {
+ wchar_t *end;
+ int i;
+ int pid;
+ int err = 0;
+
+ for (i=1; argv[i]; i++)
+ {
+ errno=0;
+ pid = fish_wcstoi(argv[i], &end, 10);
+ if (errno || pid < 0 || *end || !job_get_from_pid(pid))
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: '%ls' is not a job\n"),
+ argv[0],
+ argv[i]);
+ err = 1;
+ break;
+ }
+ }
+
+ if (!err)
+ {
+ for (i=1; !res && argv[i]; i++)
+ {
+ pid = fish_wcstoi(argv[i], 0, 10);
+ res |= send_to_bg(parser, job_get_from_pid(pid), *argv);
+ }
+ }
+ }
+
+ return res;
+}
+
+
+/**
+ This function handles both the 'continue' and the 'break' builtins
+ that are used for loop control.
+*/
+static int builtin_break_continue(parser_t &parser, wchar_t **argv)
+{
+ int is_break = (wcscmp(argv[0],L"break")==0);
+ int argc = builtin_count_args(argv);
+
+
+ if (argc != 1)
+ {
+ append_format(stderr_buffer,
+ BUILTIN_ERR_UNKNOWN,
+ argv[0],
+ argv[1]);
+
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ /* Find the index of the enclosing for or while loop. Recall that incrementing loop_idx goes 'up' to outer blocks */
+ size_t loop_idx;
+ for (loop_idx = 0; loop_idx < parser.block_count(); loop_idx++)
+ {
+ const block_t *b = parser.block_at_index(loop_idx);
+ if (b->type() == WHILE || b->type() == FOR)
+ break;
+ }
+
+ if (loop_idx >= parser.block_count())
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Not inside of loop\n"),
+ argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ /* Skip blocks interior to the loop */
+ size_t block_idx = loop_idx;
+ while (block_idx--)
+ {
+ parser.block_at_index(block_idx)->skip = true;
+ }
+
+ /* Skip the loop itself */
+ block_t *loop_block = parser.block_at_index(loop_idx);
+ loop_block->skip = true;
+ loop_block->loop_status = is_break ? LOOP_BREAK : LOOP_CONTINUE;
+ return STATUS_BUILTIN_OK;
+}
+
+/**
+ Implementation of the builtin breakpoint command, used to launch the
+ interactive debugger.
+ */
+
+static int builtin_breakpoint(parser_t &parser, wchar_t **argv)
+{
+ parser.push_block(new breakpoint_block_t());
+
+ reader_read(STDIN_FILENO, real_io ? *real_io : io_chain_t());
+
+ parser.pop_block();
+
+ return proc_get_last_status();
+}
+
+
+/**
+ Function for handling the \c return builtin
+*/
+static int builtin_return(parser_t &parser, 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)
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Argument '%ls' must be an integer\n"),
+ argv[0],
+ argv[1]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ break;
+ }
+ default:
+ append_format(stderr_buffer,
+ _(L"%ls: Too many arguments\n"),
+ argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ /* Find the function block */
+ size_t function_block_idx;
+ for (function_block_idx = 0; function_block_idx < parser.block_count(); function_block_idx++)
+ {
+ const block_t *b = parser.block_at_index(function_block_idx);
+ if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW)
+ break;
+ }
+
+ if (function_block_idx >= parser.block_count())
+ {
+ append_format(stderr_buffer,
+ _(L"%ls: Not inside of function\n"),
+ argv[0]);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+
+ /* Skip everything up to (and then including) the function block */
+ for (size_t i=0; i < function_block_idx; i++)
+ {
+ block_t *b = parser.block_at_index(i);
+ b->skip = true;
+ }
+ parser.block_at_index(function_block_idx)->skip = true;
+ return status;
+}
+
+/**
+ History of commands executed by user
+*/
+static int builtin_history(parser_t &parser, wchar_t **argv)
+{
+ int argc = builtin_count_args(argv);
+
+ bool search_history = false;
+ bool delete_item = false;
+ bool search_prefix = false;
+ bool save_history = false;
+ bool clear_history = false;
+ bool merge_history = false;
+
+ static const struct woption long_options[] =
+ {
+ { L"prefix", no_argument, 0, 'p' },
+ { L"delete", no_argument, 0, 'd' },
+ { L"search", no_argument, 0, 's' },
+ { L"contains", no_argument, 0, 'c' },
+ { L"save", no_argument, 0, 'v' },
+ { L"clear", no_argument, 0, 'l' },
+ { L"merge", no_argument, 0, 'm' },
+ { L"help", no_argument, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+
+ int opt = 0;
+ int opt_index = 0;
+ woptind = 0;
+ history_t *history = reader_get_history();
+
+ /* Use the default history if we have none (which happens if invoked non-interactively, e.g. from webconfig.py */
+ if (! history)
+ history = &history_t::history_with_name(L"fish");
+
+ while ((opt = wgetopt_long_only(argc, argv, L"pdscvl", long_options, &opt_index)) != EOF)
+ {
+ switch (opt)
+ {
+ case 'p':
+ search_prefix = true;
+ break;
+ case 'd':
+ delete_item = true;
+ break;
+ case 's':
+ search_history = true;
+ break;
+ case 'c':
+ break;
+ case 'v':
+ save_history = true;
+ break;
+ case 'l':
+ clear_history = true;
+ break;
+ case 'm':
+ merge_history = true;
+ break;
+ case 'h':
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+ break;
+ case '?':
+ append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+ break;
+ default:
+ append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]);
+ return STATUS_BUILTIN_ERROR;
+ }
+ }
+
+ /* Everything after is an argument */
+ const wcstring_list_t args(argv + woptind, argv + argc);
+
+ if (argc == 1)
+ {
+ wcstring full_history;
+ history->get_string_representation(&full_history, wcstring(L"\n"));
+ stdout_buffer.append(full_history);
+ stdout_buffer.push_back('\n');
+ return STATUS_BUILTIN_OK;
+ }
+
+ if (merge_history)
+ {
+ history->incorporate_external_changes();
+ }
+
+ if (search_history)
+ {
+ int res = STATUS_BUILTIN_ERROR;
+ for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter)
+ {
+ const wcstring &search_string = *iter;
+ if (search_string.empty())
+ {
+ append_format(stderr_buffer, BUILTIN_ERR_COMBO2, argv[0], L"Use --search with either --contains or --prefix");
+ return res;
+ }
+
+ history_search_t searcher = history_search_t(*history, search_string, search_prefix?HISTORY_SEARCH_TYPE_PREFIX:HISTORY_SEARCH_TYPE_CONTAINS);
+ while (searcher.go_backwards())
+ {
+ stdout_buffer.append(searcher.current_string());
+ stdout_buffer.append(L"\n");
+ res = STATUS_BUILTIN_OK;
+ }
+ }
+ return res;
+ }
+
+ if (delete_item)
+ {
+ for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter)
+ {
+ wcstring delete_string = *iter;
+ if (delete_string[0] == '"' && delete_string[delete_string.length() - 1] == '"')
+ delete_string = delete_string.substr(1, delete_string.length() - 2);
+
+ history->remove(delete_string);
+ }
+ return STATUS_BUILTIN_OK;
+ }
+
+ if (save_history)
+ {
+ history->save();
+ return STATUS_BUILTIN_OK;
+ }
+
+ if (clear_history)
+ {
+ history->clear();
+ history->save();
+ return STATUS_BUILTIN_OK;
+ }
+
+ return STATUS_BUILTIN_ERROR;
+}
+
+int builtin_parse(parser_t &parser, wchar_t **argv)
+{
+ struct sigaction act;
+ sigemptyset(& act.sa_mask);
+ act.sa_flags=0;
+ act.sa_handler=SIG_DFL;
+ sigaction(SIGINT, &act, 0);
+
+ std::vector<char> txt;
+ for (;;)
+ {
+ char buff[256];
+ ssize_t amt = read_loop(builtin_stdin, buff, sizeof buff);
+ if (amt <= 0) break;
+ txt.insert(txt.end(), buff, buff + amt);
+ }
+ if (! txt.empty())
+ {
+ 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);
+ if (! success)
+ {
+ stdout_buffer.append(L"Parsing failed:\n");
+ for (size_t i=0; i < errors.size(); i++)
+ {
+ stdout_buffer.append(errors.at(i).describe(src));
+ stdout_buffer.push_back(L'\n');
+ }
+
+ stdout_buffer.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);
+ }
+ const wcstring dump = parse_dump_tree(parse_tree, src);
+ stdout_buffer.append(dump);
+ }
+ return STATUS_BUILTIN_OK;
+}
+
+int builtin_true(parser_t &parser, wchar_t **argv)
+{
+ return STATUS_BUILTIN_OK;
+}
+
+int builtin_false(parser_t &parser, wchar_t **argv)
+{
+ return STATUS_BUILTIN_ERROR;
+}
+
+/*
+ END OF BUILTIN COMMANDS
+ Below are functions for handling the builtin commands.
+ THESE MUST BE SORTED BY NAME! Completion lookup uses binary search.
+*/
+
+/**
+ Data about all the builtin commands in fish.
+ Functions that are bound to builtin_generic are handled directly by the parser.
+ NOTE: These must be kept in sorted order!
+*/
+static const builtin_data_t builtin_datas[]=
+{
+ { L"[", &builtin_test, N_(L"Test a condition") },
+#if 0
+ { L"__fish_parse", &builtin_parse, N_(L"Try out the new parser") },
+#endif
+ { L"and", &builtin_generic, N_(L"Execute command if previous command suceeded") },
+ { L"begin", &builtin_generic, N_(L"Create a block of code") },
+ { L"bg", &builtin_bg, N_(L"Send job to background") },
+ { L"bind", &builtin_bind, N_(L"Handle fish key bindings") },
+ { L"block", &builtin_block, N_(L"Temporarily block delivery of events") },
+ { L"break", &builtin_break_continue, N_(L"Stop the innermost loop") },
+ { L"breakpoint", &builtin_breakpoint, N_(L"Temporarily halt execution of a script and launch an interactive debug prompt") },
+ { L"builtin", &builtin_builtin, N_(L"Run a builtin command instead of a function") },
+ { L"case", &builtin_generic, N_(L"Conditionally execute a block of commands") },
+ { L"cd", &builtin_cd, N_(L"Change working directory") },
+ { L"command", &builtin_command, N_(L"Run a program instead of a function or builtin") },
+ { L"commandline", &builtin_commandline, N_(L"Set or get the commandline") },
+ { L"complete", &builtin_complete, N_(L"Edit command specific completions") },
+ { L"contains", &builtin_contains, N_(L"Search for a specified string in a list") },
+ { L"continue", &builtin_break_continue, N_(L"Skip the rest of the current lap of the innermost loop") },
+ { L"count", &builtin_count, N_(L"Count the number of arguments") },
+ { L"echo", &builtin_echo, N_(L"Print arguments") },
+ { L"else", &builtin_generic, N_(L"Evaluate block if condition is false") },
+ { L"emit", &builtin_emit, N_(L"Emit an event") },
+ { L"end", &builtin_generic, N_(L"End a block of commands") },
+ { L"exec", &builtin_generic, N_(L"Run command in current process") },
+ { L"exit", &builtin_exit, N_(L"Exit the shell") },
+ { L"false", &builtin_false, N_(L"Return an unsuccessful result") },
+ { L"fg", &builtin_fg, N_(L"Send job to foreground") },
+ { L"for", &builtin_generic, N_(L"Perform a set of commands multiple times") },
+ { L"function", &builtin_generic, N_(L"Define a new function") },
+ { L"functions", &builtin_functions, N_(L"List or remove functions") },
+ { L"history", &builtin_history, N_(L"History of commands executed by user") },
+ { L"if", &builtin_generic, N_(L"Evaluate block if condition is true") },
+ { L"jobs", &builtin_jobs, N_(L"Print currently running jobs") },
+ { L"not", &builtin_generic, N_(L"Negate exit status of job") },
+ { L"or", &builtin_generic, N_(L"Execute command if previous command failed") },
+ { L"printf", &builtin_printf, N_(L"Prints formatted text") },
+ { L"pwd", &builtin_pwd, N_(L"Print the working directory") },
+ { L"random", &builtin_random, N_(L"Generate random number") },
+ { L"read", &builtin_read, N_(L"Read a line of input into variables") },
+ { L"return", &builtin_return, N_(L"Stop the currently evaluated function") },
+ { L"set", &builtin_set, N_(L"Handle environment variables") },
+ { L"set_color", &builtin_set_color, N_(L"Set the terminal color") },
+ { L"source", &builtin_source, N_(L"Evaluate contents of file") },
+ { L"status", &builtin_status, N_(L"Return status information about fish") },
+ { L"switch", &builtin_generic, N_(L"Conditionally execute a block of commands") },
+ { L"test", &builtin_test, N_(L"Test a condition") },
+ { L"true", &builtin_true, N_(L"Return a successful result") },
+ { L"ulimit", &builtin_ulimit, N_(L"Set or get the shells resource usage limits") },
+ { L"while", &builtin_generic, N_(L"Perform a command multiple times") }
+};
+
+#define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas)
+
+static const builtin_data_t *builtin_lookup(const wcstring &name)
+{
+ const builtin_data_t *array_end = builtin_datas + BUILTIN_COUNT;
+ const builtin_data_t *found = std::lower_bound(builtin_datas, array_end, name);
+ if (found != array_end && name == found->name)
+ {
+ return found;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void builtin_init()
+{
+
+ wopterr = 0;
+ for (size_t i=0; i < BUILTIN_COUNT; i++)
+ {
+ intern_static(builtin_datas[i].name);
+ }
+}
+
+void builtin_destroy()
+{
+}
+
+int builtin_exists(const wcstring &cmd)
+{
+ return !!builtin_lookup(cmd);
+}
+
+/**
+ Return true if the specified builtin should handle it's own help,
+ false otherwise.
+*/
+static int internal_help(const wchar_t *cmd)
+{
+ CHECK(cmd, 0);
+ return contains(cmd, L"for", L"while", L"function",
+ L"if", L"end", L"switch", L"case", L"count", L"printf");
+}
+
+
+int builtin_run(parser_t &parser, const wchar_t * const *argv, const io_chain_t &io)
+{
+ int (*cmd)(parser_t &parser, const wchar_t * const *argv)=0;
+
+ scoped_push<const io_chain_t*> set_real_io(&real_io, &io);
+
+ CHECK(argv, STATUS_BUILTIN_ERROR);
+ CHECK(argv[0], STATUS_BUILTIN_ERROR);
+
+ const builtin_data_t *data = builtin_lookup(argv[0]);
+ cmd = (int (*)(parser_t &parser, const wchar_t * const*))(data ? data->func : NULL);
+
+ if (argv[1] != 0 && !internal_help(argv[0]))
+ {
+ if (argv[2] == 0 && (parse_util_argument_is_help(argv[1], 0)))
+ {
+ builtin_print_help(parser, argv[0], stdout_buffer);
+ return STATUS_BUILTIN_OK;
+ }
+ }
+
+ if (data != NULL)
+ {
+ int status;
+
+ status = cmd(parser, argv);
+ return status;
+
+ }
+ else
+ {
+ debug(0, UNKNOWN_BUILTIN_ERR_MSG, argv[0]);
+ }
+ return STATUS_BUILTIN_ERROR;
+}
+
+
+wcstring_list_t builtin_get_names(void)
+{
+ wcstring_list_t result;
+ result.reserve(BUILTIN_COUNT);
+ for (size_t i=0; i < BUILTIN_COUNT; i++)
+ {
+ result.push_back(builtin_datas[i].name);
+ }
+ return result;
+}
+
+void builtin_get_names(std::vector<completion_t> &list)
+{
+ for (size_t i=0; i < BUILTIN_COUNT; i++)
+ {
+ append_completion(list, builtin_datas[i].name);
+ }
+}
+
+wcstring builtin_get_desc(const wcstring &name)
+{
+ wcstring result;
+ const builtin_data_t *builtin = builtin_lookup(name);
+ if (builtin)
+ {
+ result = _(builtin->desc);
+ }
+ return result;
+}
+
+void builtin_push_io(parser_t &parser, int in)
+{
+ ASSERT_IS_MAIN_THREAD();
+ if (builtin_stdin != -1)
+ {
+ struct io_stack_elem_t elem = {builtin_stdin, stdout_buffer, stderr_buffer};
+ io_stack.push(elem);
+ }
+ builtin_stdin = in;
+ stdout_buffer.clear();
+ stderr_buffer.clear();
+}
+
+void builtin_pop_io(parser_t &parser)
+{
+ ASSERT_IS_MAIN_THREAD();
+ builtin_stdin = 0;
+ if (! io_stack.empty())
+ {
+ struct io_stack_elem_t &elem = io_stack.top();
+ stderr_buffer = elem.err;
+ stdout_buffer = elem.out;
+ builtin_stdin = elem.in;
+ io_stack.pop();
+ }
+ else
+ {
+ stdout_buffer.clear();
+ stderr_buffer.clear();
+ builtin_stdin = 0;
+ }
+}