diff --git a/src/core/commands.c b/src/core/commands.c index b2151c1e..06c2add6 100644 --- a/src/core/commands.c +++ b/src/core/commands.c @@ -129,9 +129,6 @@ COMMAND_REC *command_find(const char *cmd) return NULL; } -#define iscmdtype(c) \ - ((c) == '-' || (c) == '+' || (c) == '@') - static GSList *optlist_find(GSList *optlist, const char *option) { while (optlist != NULL) { @@ -240,21 +237,41 @@ static char *cmd_get_quoted_param(char **data) return pos; } -static int option_find(char **array, const char *item) +/* Find specified option from list of options - the `option' might be + shortened version of the full command. Returns index where the + option was found, -1 if not found or -2 if there was multiple matches. */ +static int option_find(char **array, const char *option) { char **tmp; - int index; + int index, found, len; - g_return_val_if_fail(array != NULL, 0); - g_return_val_if_fail(item != NULL, 0); + g_return_val_if_fail(array != NULL, -1); + g_return_val_if_fail(option != NULL, -1); - index = 0; + len = strlen(option); + g_return_val_if_fail(len > 0, -1); + + found = -1; index = 0; for (tmp = array; *tmp != NULL; tmp++, index++) { - if (g_strcasecmp(*tmp + iscmdtype(**tmp), item) == 0) - return index; + const char *text = *tmp + iscmdtype(**tmp); + + if (g_strncasecmp(text, option, len) == 0) { + if (text[len] == '\0') { + /* full match */ + return index; + } + + if (found != -1) { + /* multiple matches - abort */ + return -2; + } + + /* partial match, check that it's the only one */ + found = index; + } } - return -1; + return found; } static int get_cmd_options(char **data, int ignore_unknown, @@ -295,7 +312,12 @@ static int get_cmd_options(char **data, int ignore_unknown, *data = option; return CMDERR_OPTION_UNKNOWN; } - if (pos != -1) { + if (pos == -2 && !ignore_unknown) { + /* multiple matches */ + *data = option; + return CMDERR_OPTION_AMBIGUOUS; + } + if (pos >= 0) { /* if we used a shortcut of parameter, put the whole parameter name in options table */ option = optlist[pos] + iscmdtype(*optlist[pos]); diff --git a/src/core/commands.h b/src/core/commands.h index 1f1dc8dd..fa553518 100644 --- a/src/core/commands.h +++ b/src/core/commands.h @@ -10,7 +10,8 @@ typedef struct { } COMMAND_REC; enum { - CMDERR_OPTION_UNKNOWN = -2, /* unknown -option */ + CMDERR_OPTION_UNKNOWN = -3, /* unknown -option */ + CMDERR_OPTION_AMBIGUOUS = -2, /* ambiguous -option */ CMDERR_OPTION_ARG_MISSING = -1, /* argument missing for -option */ CMDERR_ERRNO, /* get the error from errno */ @@ -22,15 +23,20 @@ enum { CMDERR_NOT_GOOD_IDEA /* not good idea to do, -yes overrides this */ }; +/* Return the full command for `alias' */ #define alias_find(alias) \ iconfig_get_str("aliases", alias, NULL) -#define cmd_return_error(a) { signal_emit("error command", 1, GINT_TO_POINTER(a)); signal_stop(); return; } -#define cmd_param_error(a) { cmd_params_free(free_arg); cmd_return_error(a); } +/* Returning from command function with error */ +#define cmd_return_error(a) \ + { signal_emit("error command", 1, GINT_TO_POINTER(a)); signal_stop(); return; } +#define cmd_param_error(a) \ + { cmd_params_free(free_arg); cmd_return_error(a); } extern GSList *commands; -extern char *current_command; +extern char *current_command; /* the command we're right now. */ +/* Bind command to specified function. */ void command_bind_to(int pos, const char *cmd, const char *category, SIGNAL_FUNC func); #define command_bind(a, b, c) command_bind_to(1, a, b, c) #define command_bind_first(a, b, c) command_bind_to(0, a, b, c) @@ -57,6 +63,8 @@ COMMAND_REC *command_find(const char *cmd); You can call this command multiple times for same command, options will be merged. If there's any conflicts with option types, the last call will override the previous */ +#define iscmdtype(c) \ + ((c) == '-' || (c) == '+' || (c) == '@') void command_set_options(const char *cmd, const char *options); /* count can have these flags: */ diff --git a/src/fe-common/core/completion.c b/src/fe-common/core/completion.c index 714d7934..6ed04964 100644 --- a/src/fe-common/core/completion.c +++ b/src/fe-common/core/completion.c @@ -173,6 +173,8 @@ GList *list_add_file(GList *list, const char *name) struct stat statbuf; char *fname; + g_return_val_if_fail(name != NULL, NULL); + fname = convert_home(name); if (stat(fname, &statbuf) == 0) { list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) : @@ -191,6 +193,8 @@ GList *filename_complete(const char *path) char *realpath, *dir, *basename, *name; int len; + g_return_val_if_fail(path != NULL, NULL); + list = NULL; /* get directory part of the path - expand ~/ */ @@ -237,6 +241,8 @@ static int is_base_command(const char *command) GSList *tmp; int len; + g_return_val_if_fail(command != NULL, FALSE); + /* find "command "s */ len = strlen(command); for (tmp = commands; tmp != NULL; tmp = tmp->next) { @@ -255,6 +261,8 @@ static GList *completion_get_settings(const char *key) GSList *tmp, *sets; int len; + g_return_val_if_fail(key != NULL, NULL); + sets = settings_get_sorted(); len = strlen(key); @@ -275,6 +283,8 @@ static GList *completion_get_bool_settings(const char *key) GSList *tmp, *sets; int len; + g_return_val_if_fail(key != NULL, NULL); + sets = settings_get_sorted(); len = strlen(key); @@ -297,6 +307,8 @@ static GList *completion_get_commands(const char *cmd, char cmdchar) char *word; int len; + g_return_val_if_fail(cmd != NULL, NULL); + len = strlen(cmd); complist = NULL; for (tmp = commands; tmp != NULL; tmp = tmp->next) { @@ -323,6 +335,8 @@ static GList *completion_get_subcommands(const char *cmd) char *spacepos; int len, skip; + g_return_val_if_fail(cmd != NULL, NULL); + /* get the number of chars to skip at the start of command. */ spacepos = strrchr(cmd, ' '); skip = spacepos == NULL ? 0 : @@ -345,12 +359,38 @@ static GList *completion_get_subcommands(const char *cmd) return complist; } +GList *completion_get_options(const char *cmd, const char *option) +{ + COMMAND_REC *rec; + GList *list; + char **tmp; + int len; + + g_return_val_if_fail(cmd != NULL, NULL); + g_return_val_if_fail(option != NULL, NULL); + + rec = command_find(cmd); + if (rec == NULL) return NULL; + + list = NULL; + len = strlen(option); + for (tmp = rec->options; *tmp != NULL; tmp++) { + if (len == 0 || g_strncasecmp(*tmp, option, len) == 0) + list = g_list_append(list, g_strconcat("-", *tmp + iscmdtype(**tmp), NULL)); + } + + return list; +} + /* split the line to command and arguments */ static char *line_get_command(const char *line, char **args, int aliases) { const char *ptr, *cmdargs; char *cmd, *checkcmd; + g_return_val_if_fail(line != NULL, NULL); + g_return_val_if_fail(args != NULL, NULL); + cmd = checkcmd = NULL; *args = ""; cmdargs = NULL; ptr = line; @@ -391,6 +431,8 @@ static char *expand_aliases(const char *line) { char *cmd, *args, *ret; + g_return_val_if_fail(line != NULL, NULL); + cmd = line_get_command(line, &args, TRUE); if (cmd == NULL) return g_strdup(line); if (*args == '\0') return cmd; @@ -448,16 +490,27 @@ static void sig_complete_word(GList **list, WINDOW_REC *window, return; } - /* complete parameters */ cmd = line_get_command(line, &args, FALSE); - if (cmd != NULL) { - signal = g_strconcat("complete command ", cmd, NULL); - signal_emit(signal, 5, list, window, word, args, want_space); - - g_free(signal); - g_free(cmd); + if (cmd == NULL) { + g_free(line); + return; } + /* we're completing -option? */ + if (*word == '-') { + *list = completion_get_options(cmd, word+1); + g_free(cmd); + g_free(line); + return; + } + + /* complete parameters */ + signal = g_strconcat("complete command ", cmd, NULL); + signal_emit(signal, 5, list, window, word, args, want_space); + + g_free(signal); + g_free(cmd); + g_free(line); } diff --git a/src/fe-common/core/fe-core-commands.c b/src/fe-common/core/fe-core-commands.c index 3b725bae..894eae6f 100644 --- a/src/fe-common/core/fe-core-commands.c +++ b/src/fe-common/core/fe-core-commands.c @@ -314,10 +314,13 @@ static void event_cmderror(gpointer errorp, const char *arg) printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, g_strerror(errno)); break; case CMDERR_OPTION_UNKNOWN: - printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown argument: %s", arg); + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_OPTION_UNKNOWN, arg); + break; + case CMDERR_OPTION_AMBIGUOUS: + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_OPTION_AMBIGUOUS, arg); break; case CMDERR_OPTION_ARG_MISSING: - printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Missing required argument for: %s", arg); + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_OPTION_MISSING_ARG, arg); break; default: printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error]); diff --git a/src/fe-common/core/module-formats.c b/src/fe-common/core/module-formats.c index a141d273..6f704e99 100644 --- a/src/fe-common/core/module-formats.c +++ b/src/fe-common/core/module-formats.c @@ -91,6 +91,9 @@ FORMAT_REC fecommon_core_formats[] = { { "not_toggle", "Value must be either ON, OFF or TOGGLE", 0 }, { "perl_error", "Perl error: $0", 1, { 0 } }, + { "option_unknown", "Unknown option: $0", 1, { 0 } }, + { "option_ambiguous", "Ambiguous option: $0", 1, { 0 } }, + { "option_missing_arg", "Missing required argument for: $0", 1, { 0 } }, { NULL, NULL, 0 } }; diff --git a/src/fe-common/core/module-formats.h b/src/fe-common/core/module-formats.h index cc38d4ae..9c5b1f15 100644 --- a/src/fe-common/core/module-formats.h +++ b/src/fe-common/core/module-formats.h @@ -63,7 +63,10 @@ enum { IRCTXT_FILL_6, IRCTXT_NOT_TOGGLE, - IRCTXT_PERL_ERROR + IRCTXT_PERL_ERROR, + IRCTXT_OPTION_UNKNOWN, + IRCTXT_OPTION_AMBIGUOUS, + IRCTXT_OPTION_MISSING_ARG }; extern FORMAT_REC fecommon_core_formats[];