From b4f53143b0e05fd3061cdf2e65e17a6a2904090b Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jul 2015 00:50:58 -0700 Subject: 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 --- src/builtin.cpp | 4287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4287 insertions(+) create mode 100644 src/builtin.cpp (limited to 'src/builtin.cpp') 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: + + static int builtin_NAME( parser_t &parser, wchar_t ** args ) + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + +/** + 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 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 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 lst = input_mapping_get_names(); + + for (std::vector::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 lst = input_mapping_get_names(); + for (std::vector::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<= 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 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; itype) + { + 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 inherit_vars = function_get_inherit_vars(name); + for (std::map::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 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 argv_array(args); + wchar_t **argv = const_cast(argv_array.get()); + + int argc = builtin_count_args(argv); + int res=STATUS_BUILTIN_OK; + wchar_t *desc=0; + std::vector 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 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 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= 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; i2)?(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"" : 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 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 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 &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; + } +} -- cgit v1.2.3