mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
split builtin history into its own module
This commit is contained in:
parent
2079b4292e
commit
ef8a0c93ea
4 changed files with 309 additions and 263 deletions
|
@ -100,6 +100,7 @@ HAVE_DOXYGEN=@HAVE_DOXYGEN@
|
|||
#
|
||||
FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bind.o obj/builtin_block.o \
|
||||
obj/builtin_commandline.o obj/builtin_emit.o obj/builtin_functions.o \
|
||||
obj/builtin_history.o \
|
||||
obj/builtin_complete.o obj/builtin_jobs.o obj/builtin_printf.o \
|
||||
obj/builtin_set.o obj/builtin_set_color.o obj/builtin_string.o \
|
||||
obj/builtin_test.o obj/builtin_ulimit.o obj/color.o obj/common.o \
|
||||
|
@ -946,7 +947,7 @@ obj/autoload.o: config.h src/autoload.h src/common.h src/fallback.h
|
|||
obj/autoload.o: src/signal.h src/lru.h src/env.h src/exec.h src/wutil.h
|
||||
obj/builtin.o: config.h src/signal.h src/builtin.h src/common.h
|
||||
obj/builtin.o: src/fallback.h src/builtin_bind.h src/builtin_block.h src/builtin_functions.h
|
||||
obj/builtin.o: src/builtin_commandline.h src/builtin_complete.h
|
||||
obj/builtin.o: src/builtin_commandline.h src/builtin_complete.h src/builtin_history.h
|
||||
obj/builtin.o: src/builtin_emit.h src/builtin_jobs.h src/builtin_printf.h
|
||||
obj/builtin.o: src/builtin_set.h src/builtin_set_color.h src/builtin_string.h
|
||||
obj/builtin.o: src/builtin_test.h src/builtin_ulimit.h src/complete.h
|
||||
|
|
263
src/builtin.cpp
263
src/builtin.cpp
|
@ -42,6 +42,7 @@
|
|||
#include "builtin_complete.h"
|
||||
#include "builtin_emit.h"
|
||||
#include "builtin_functions.h"
|
||||
#include "builtin_history.h"
|
||||
#include "builtin_jobs.h"
|
||||
#include "builtin_printf.h"
|
||||
#include "builtin_set.h"
|
||||
|
@ -58,7 +59,6 @@
|
|||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "function.h"
|
||||
#include "highlight.h"
|
||||
#include "history.h"
|
||||
#include "intern.h"
|
||||
#include "io.h"
|
||||
#include "parse_constants.h"
|
||||
|
@ -2314,267 +2314,6 @@ static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **arg
|
|||
return status;
|
||||
}
|
||||
|
||||
enum hist_cmd_t { HIST_SEARCH = 1, HIST_DELETE, HIST_CLEAR, HIST_MERGE, HIST_SAVE, HIST_UNDEF };
|
||||
// Must be sorted by string, not enum or random.
|
||||
const enum_map<hist_cmd_t> hist_enum_map[] = {{HIST_CLEAR, L"clear"}, {HIST_DELETE, L"delete"},
|
||||
{HIST_MERGE, L"merge"}, {HIST_SAVE, L"save"},
|
||||
{HIST_SEARCH, L"search"}, {HIST_UNDEF, NULL}};
|
||||
#define hist_enum_map_len (sizeof hist_enum_map / sizeof *hist_enum_map)
|
||||
|
||||
/// Remember the history subcommand and disallow selecting more than one history subcommand.
|
||||
static bool set_hist_cmd(wchar_t *const cmd, hist_cmd_t *hist_cmd, hist_cmd_t sub_cmd,
|
||||
io_streams_t &streams) {
|
||||
if (*hist_cmd != HIST_UNDEF) {
|
||||
wchar_t err_text[1024];
|
||||
const wchar_t *subcmd_str1 = enum_to_str(*hist_cmd, hist_enum_map);
|
||||
const wchar_t *subcmd_str2 = enum_to_str(sub_cmd, hist_enum_map);
|
||||
swprintf(err_text, sizeof(err_text) / sizeof(wchar_t),
|
||||
_(L"you cannot do both '%ls' and '%ls' in the same invocation"), subcmd_str1,
|
||||
subcmd_str2);
|
||||
streams.err.append_format(BUILTIN_ERR_COMBO2, cmd, err_text);
|
||||
return false;
|
||||
}
|
||||
|
||||
*hist_cmd = sub_cmd;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) \
|
||||
if (history_search_type_defined || show_time_format || null_terminate) { \
|
||||
const wchar_t *subcmd_str = enum_to_str(hist_cmd, hist_enum_map); \
|
||||
streams.err.append_format(_(L"%ls: you cannot use any options with the %ls command\n"), \
|
||||
cmd, subcmd_str); \
|
||||
status = STATUS_INVALID_ARGS; \
|
||||
break; \
|
||||
} \
|
||||
if (args.size() != 0) { \
|
||||
const wchar_t *subcmd_str = enum_to_str(hist_cmd, hist_enum_map); \
|
||||
streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 0, args.size()); \
|
||||
status = STATUS_INVALID_ARGS; \
|
||||
break; \
|
||||
}
|
||||
|
||||
/// Manipulate history of interactive commands executed by the user.
|
||||
static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
wchar_t *cmd = argv[0];
|
||||
int argc = builtin_count_args(argv);
|
||||
hist_cmd_t hist_cmd = HIST_UNDEF;
|
||||
history_search_type_t search_type = (history_search_type_t)-1;
|
||||
long max_items = LONG_MAX;
|
||||
bool history_search_type_defined = false;
|
||||
const wchar_t *show_time_format = NULL;
|
||||
bool case_sensitive = false;
|
||||
bool null_terminate = false;
|
||||
|
||||
// Use the default history if we have none (which happens if invoked non-interactively, e.g.
|
||||
// from webconfig.py.
|
||||
history_t *history = reader_get_history();
|
||||
if (!history) history = &history_t::history_with_name(L"fish");
|
||||
|
||||
/// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to
|
||||
/// the non-flag subcommand form. While many of these flags are deprecated they must be
|
||||
/// supported at least until fish 3.0 and possibly longer to avoid breaking everyones
|
||||
/// config.fish and other scripts.
|
||||
static const wchar_t *short_options = L":Cmn:epchtz";
|
||||
static const struct woption long_options[] = {{L"prefix", no_argument, NULL, 'p'},
|
||||
{L"contains", no_argument, NULL, 'c'},
|
||||
{L"help", no_argument, NULL, 'h'},
|
||||
{L"show-time", optional_argument, NULL, 't'},
|
||||
{L"with-time", optional_argument, NULL, 't'},
|
||||
{L"exact", no_argument, NULL, 'e'},
|
||||
{L"max", required_argument, NULL, 'n'},
|
||||
{L"null", no_argument, 0, 'z'},
|
||||
{L"case-sensitive", no_argument, 0, 'C'},
|
||||
{L"delete", no_argument, NULL, 1},
|
||||
{L"search", no_argument, NULL, 2},
|
||||
{L"save", no_argument, NULL, 3},
|
||||
{L"clear", no_argument, NULL, 4},
|
||||
{L"merge", no_argument, NULL, 5},
|
||||
{NULL, 0, NULL, 0}};
|
||||
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 1: {
|
||||
if (!set_hist_cmd(cmd, &hist_cmd, HIST_DELETE, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
if (!set_hist_cmd(cmd, &hist_cmd, HIST_SEARCH, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
if (!set_hist_cmd(cmd, &hist_cmd, HIST_SAVE, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
if (!set_hist_cmd(cmd, &hist_cmd, HIST_CLEAR, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
if (!set_hist_cmd(cmd, &hist_cmd, HIST_MERGE, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
case_sensitive = true;
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
search_type = HISTORY_SEARCH_TYPE_PREFIX;
|
||||
history_search_type_defined = true;
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
search_type = HISTORY_SEARCH_TYPE_CONTAINS;
|
||||
history_search_type_defined = true;
|
||||
break;
|
||||
}
|
||||
case 'e': {
|
||||
search_type = HISTORY_SEARCH_TYPE_EXACT;
|
||||
history_search_type_defined = true;
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
show_time_format = w.woptarg ? w.woptarg : L"# %c%n";
|
||||
break;
|
||||
}
|
||||
case 'n': {
|
||||
max_items = fish_wcstol(w.woptarg);
|
||||
if (errno) {
|
||||
streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"),
|
||||
argv[0], w.woptarg);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'z': {
|
||||
null_terminate = true;
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
builtin_print_help(parser, streams, cmd, streams.out);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
case ':': {
|
||||
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case '?': {
|
||||
// Try to parse it as a number; e.g., "-123".
|
||||
max_items = fish_wcstol(argv[w.woptind - 1] + 1);
|
||||
if (errno) {
|
||||
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
w.nextchar = NULL;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected retval from wgetopt_long");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (max_items <= 0) {
|
||||
streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"), argv[0],
|
||||
w.woptarg);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// If a history command hasn't already been specified via a flag check the first word.
|
||||
// Note that this can be simplified after we eliminate allowing subcommands as flags.
|
||||
// See the TODO above regarding the `long_options` array.
|
||||
if (w.woptind < argc) {
|
||||
hist_cmd_t subcmd = str_to_enum(argv[w.woptind], hist_enum_map, hist_enum_map_len);
|
||||
if (subcmd != HIST_UNDEF) {
|
||||
if (!set_hist_cmd(cmd, &hist_cmd, subcmd, streams)) {
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
w.woptind++;
|
||||
}
|
||||
}
|
||||
|
||||
// Every argument that we haven't consumed already is an argument for a subcommand (e.g., a
|
||||
// search term).
|
||||
const wcstring_list_t args(argv + w.woptind, argv + argc);
|
||||
|
||||
// Establish appropriate defaults.
|
||||
if (hist_cmd == HIST_UNDEF) hist_cmd = HIST_SEARCH;
|
||||
if (!history_search_type_defined) {
|
||||
if (hist_cmd == HIST_SEARCH) search_type = HISTORY_SEARCH_TYPE_CONTAINS;
|
||||
if (hist_cmd == HIST_DELETE) search_type = HISTORY_SEARCH_TYPE_EXACT;
|
||||
}
|
||||
|
||||
int status = STATUS_CMD_OK;
|
||||
switch (hist_cmd) {
|
||||
case HIST_SEARCH: {
|
||||
if (!history->search(search_type, args, show_time_format, max_items, case_sensitive,
|
||||
null_terminate, streams)) {
|
||||
status = STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HIST_DELETE: {
|
||||
// TODO: Move this code to the history module and support the other search types
|
||||
// including case-insensitive matches. At this time we expect the non-exact deletions to
|
||||
// be handled only by the history function's interactive delete feature.
|
||||
if (search_type != HISTORY_SEARCH_TYPE_EXACT) {
|
||||
streams.err.append_format(_(L"builtin history delete only supports --exact\n"));
|
||||
status = STATUS_INVALID_ARGS;
|
||||
break;
|
||||
}
|
||||
if (!case_sensitive) {
|
||||
streams.err.append_format(
|
||||
_(L"builtin history delete only supports --case-sensitive\n"));
|
||||
status = STATUS_INVALID_ARGS;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HIST_CLEAR: {
|
||||
CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd)
|
||||
history->clear();
|
||||
history->save();
|
||||
break;
|
||||
}
|
||||
case HIST_MERGE: {
|
||||
CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd)
|
||||
history->incorporate_external_changes();
|
||||
break;
|
||||
}
|
||||
case HIST_SAVE: {
|
||||
CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd)
|
||||
history->save();
|
||||
break;
|
||||
}
|
||||
case HIST_UNDEF: {
|
||||
DIE("Unexpected HIST_UNDEF seen");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int builtin_true(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
UNUSED(parser);
|
||||
UNUSED(streams);
|
||||
|
|
297
src/builtin_history.cpp
Normal file
297
src/builtin_history.cpp
Normal file
|
@ -0,0 +1,297 @@
|
|||
// Implementation of the history builtin.
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "builtin.h"
|
||||
#include "builtin_history.h"
|
||||
#include "common.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "history.h"
|
||||
#include "io.h"
|
||||
#include "reader.h"
|
||||
#include "wgetopt.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
enum hist_cmd_t { HIST_SEARCH = 1, HIST_DELETE, HIST_CLEAR, HIST_MERGE, HIST_SAVE, HIST_UNDEF };
|
||||
|
||||
// Must be sorted by string, not enum or random.
|
||||
const enum_map<hist_cmd_t> hist_enum_map[] = {{HIST_CLEAR, L"clear"}, {HIST_DELETE, L"delete"},
|
||||
{HIST_MERGE, L"merge"}, {HIST_SAVE, L"save"},
|
||||
{HIST_SEARCH, L"search"}, {HIST_UNDEF, NULL}};
|
||||
#define hist_enum_map_len (sizeof hist_enum_map / sizeof *hist_enum_map)
|
||||
|
||||
struct history_opts {
|
||||
bool print_help = false;
|
||||
hist_cmd_t hist_cmd = HIST_UNDEF;
|
||||
history_search_type_t search_type = (history_search_type_t)-1;
|
||||
long max_items = LONG_MAX;
|
||||
bool history_search_type_defined = false;
|
||||
const wchar_t *show_time_format = NULL;
|
||||
bool case_sensitive = false;
|
||||
bool null_terminate = false;
|
||||
};
|
||||
|
||||
/// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to
|
||||
/// the non-flag subcommand form. While many of these flags are deprecated they must be
|
||||
/// supported at least until fish 3.0 and possibly longer to avoid breaking everyones
|
||||
/// config.fish and other scripts.
|
||||
static const wchar_t *short_options = L":Cmn:epchtz";
|
||||
static const struct woption long_options[] = {{L"prefix", no_argument, NULL, 'p'},
|
||||
{L"contains", no_argument, NULL, 'c'},
|
||||
{L"help", no_argument, NULL, 'h'},
|
||||
{L"show-time", optional_argument, NULL, 't'},
|
||||
{L"with-time", optional_argument, NULL, 't'},
|
||||
{L"exact", no_argument, NULL, 'e'},
|
||||
{L"max", required_argument, NULL, 'n'},
|
||||
{L"null", no_argument, NULL, 'z'},
|
||||
{L"case-sensitive", no_argument, NULL, 'C'},
|
||||
{L"delete", no_argument, NULL, 1},
|
||||
{L"search", no_argument, NULL, 2},
|
||||
{L"save", no_argument, NULL, 3},
|
||||
{L"clear", no_argument, NULL, 4},
|
||||
{L"merge", no_argument, NULL, 5},
|
||||
{NULL, 0, NULL, 0}};
|
||||
|
||||
/// Remember the history subcommand and disallow selecting more than one history subcommand.
|
||||
static bool set_hist_cmd(wchar_t *const cmd, hist_cmd_t *hist_cmd, hist_cmd_t sub_cmd,
|
||||
io_streams_t &streams) {
|
||||
if (*hist_cmd != HIST_UNDEF) {
|
||||
wchar_t err_text[1024];
|
||||
const wchar_t *subcmd_str1 = enum_to_str(*hist_cmd, hist_enum_map);
|
||||
const wchar_t *subcmd_str2 = enum_to_str(sub_cmd, hist_enum_map);
|
||||
swprintf(err_text, sizeof(err_text) / sizeof(wchar_t),
|
||||
_(L"you cannot do both '%ls' and '%ls' in the same invocation"), subcmd_str1,
|
||||
subcmd_str2);
|
||||
streams.err.append_format(BUILTIN_ERR_COMBO2, cmd, err_text);
|
||||
return false;
|
||||
}
|
||||
|
||||
*hist_cmd = sub_cmd;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) \
|
||||
if (opts.history_search_type_defined || opts.show_time_format || opts.null_terminate) { \
|
||||
const wchar_t *subcmd_str = enum_to_str(hist_cmd, hist_enum_map); \
|
||||
streams.err.append_format(_(L"%ls: you cannot use any options with the %ls command\n"), \
|
||||
cmd, subcmd_str); \
|
||||
status = STATUS_INVALID_ARGS; \
|
||||
break; \
|
||||
} \
|
||||
if (args.size() != 0) { \
|
||||
const wchar_t *subcmd_str = enum_to_str(hist_cmd, hist_enum_map); \
|
||||
streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 0, args.size()); \
|
||||
status = STATUS_INVALID_ARGS; \
|
||||
break; \
|
||||
}
|
||||
|
||||
static int parse_history_opts(struct history_opts *opts, int *optind, //!OCLINT(high ncss method)
|
||||
int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) {
|
||||
wchar_t *cmd = argv[0];
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 1: {
|
||||
if (!set_hist_cmd(cmd, &opts->hist_cmd, HIST_DELETE, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
if (!set_hist_cmd(cmd, &opts->hist_cmd, HIST_SEARCH, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
if (!set_hist_cmd(cmd, &opts->hist_cmd, HIST_SAVE, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
if (!set_hist_cmd(cmd, &opts->hist_cmd, HIST_CLEAR, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
if (!set_hist_cmd(cmd, &opts->hist_cmd, HIST_MERGE, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
opts->case_sensitive = true;
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
opts->search_type = HISTORY_SEARCH_TYPE_PREFIX;
|
||||
opts->history_search_type_defined = true;
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
opts->search_type = HISTORY_SEARCH_TYPE_CONTAINS;
|
||||
opts->history_search_type_defined = true;
|
||||
break;
|
||||
}
|
||||
case 'e': {
|
||||
opts->search_type = HISTORY_SEARCH_TYPE_EXACT;
|
||||
opts->history_search_type_defined = true;
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
opts->show_time_format = w.woptarg ? w.woptarg : L"# %c%n";
|
||||
break;
|
||||
}
|
||||
case 'n': {
|
||||
opts->max_items = fish_wcstol(w.woptarg);
|
||||
if (errno) {
|
||||
streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"),
|
||||
cmd, w.woptarg);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'z': {
|
||||
opts->null_terminate = true;
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
opts->print_help = true;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
case ':': {
|
||||
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case '?': {
|
||||
// Try to parse it as a number; e.g., "-123".
|
||||
opts->max_items = fish_wcstol(argv[w.woptind - 1] + 1);
|
||||
if (errno) {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
w.nextchar = NULL;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected retval from wgetopt_long");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*optind = w.woptind;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
/// Manipulate history of interactive commands executed by the user.
|
||||
int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
wchar_t *cmd = argv[0];
|
||||
int argc = builtin_count_args(argv);
|
||||
struct history_opts opts;
|
||||
|
||||
int optind;
|
||||
int retval = parse_history_opts(&opts, &optind, argc, argv, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
if (opts.print_help) {
|
||||
builtin_print_help(parser, streams, cmd, streams.out);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// Use the default history if we have none (which happens if invoked non-interactively, e.g.
|
||||
// from webconfig.py.
|
||||
history_t *history = reader_get_history();
|
||||
if (!history) history = &history_t::history_with_name(L"fish");
|
||||
|
||||
// If a history command hasn't already been specified via a flag check the first word.
|
||||
// Note that this can be simplified after we eliminate allowing subcommands as flags.
|
||||
// See the TODO above regarding the `long_options` array.
|
||||
if (optind < argc) {
|
||||
hist_cmd_t subcmd = str_to_enum(argv[optind], hist_enum_map, hist_enum_map_len);
|
||||
if (subcmd != HIST_UNDEF) {
|
||||
if (!set_hist_cmd(cmd, &opts.hist_cmd, subcmd, streams)) {
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
optind++;
|
||||
}
|
||||
}
|
||||
|
||||
// Every argument that we haven't consumed already is an argument for a subcommand (e.g., a
|
||||
// search term).
|
||||
const wcstring_list_t args(argv + optind, argv + argc);
|
||||
|
||||
// Establish appropriate defaults.
|
||||
if (opts.hist_cmd == HIST_UNDEF) opts.hist_cmd = HIST_SEARCH;
|
||||
if (!opts.history_search_type_defined) {
|
||||
if (opts.hist_cmd == HIST_SEARCH) opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS;
|
||||
if (opts.hist_cmd == HIST_DELETE) opts.search_type = HISTORY_SEARCH_TYPE_EXACT;
|
||||
}
|
||||
|
||||
int status = STATUS_CMD_OK;
|
||||
switch (opts.hist_cmd) {
|
||||
case HIST_SEARCH: {
|
||||
if (!history->search(opts.search_type, args, opts.show_time_format, opts.max_items,
|
||||
opts.case_sensitive, opts.null_terminate, streams)) {
|
||||
status = STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HIST_DELETE: {
|
||||
// TODO: Move this code to the history module and support the other search types
|
||||
// including case-insensitive matches. At this time we expect the non-exact deletions to
|
||||
// be handled only by the history function's interactive delete feature.
|
||||
if (opts.search_type != HISTORY_SEARCH_TYPE_EXACT) {
|
||||
streams.err.append_format(_(L"builtin history delete only supports --exact\n"));
|
||||
status = STATUS_INVALID_ARGS;
|
||||
break;
|
||||
}
|
||||
if (!opts.case_sensitive) {
|
||||
streams.err.append_format(
|
||||
_(L"builtin history delete only supports --case-sensitive\n"));
|
||||
status = STATUS_INVALID_ARGS;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HIST_CLEAR: {
|
||||
CHECK_FOR_UNEXPECTED_HIST_ARGS(opts.hist_cmd)
|
||||
history->clear();
|
||||
history->save();
|
||||
break;
|
||||
}
|
||||
case HIST_MERGE: {
|
||||
CHECK_FOR_UNEXPECTED_HIST_ARGS(opts.hist_cmd)
|
||||
history->incorporate_external_changes();
|
||||
break;
|
||||
}
|
||||
case HIST_SAVE: {
|
||||
CHECK_FOR_UNEXPECTED_HIST_ARGS(opts.hist_cmd)
|
||||
history->save();
|
||||
break;
|
||||
}
|
||||
case HIST_UNDEF: {
|
||||
DIE("Unexpected HIST_UNDEF seen");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
9
src/builtin_history.h
Normal file
9
src/builtin_history.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Prototypes for executing builtin_history function.
|
||||
#ifndef FISH_BUILTIN_HISTORY_H
|
||||
#define FISH_BUILTIN_HISTORY_H
|
||||
|
||||
class parser_t;
|
||||
struct io_streams_t;
|
||||
|
||||
int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv);
|
||||
#endif
|
Loading…
Reference in a new issue