diff --git a/src/core/commands.c b/src/core/commands.c index 06c2add6..311097e2 100644 --- a/src/core/commands.c +++ b/src/core/commands.c @@ -36,6 +36,22 @@ char *current_command; static GSList *cmdget_funcs; static int signal_default_command; +COMMAND_REC *command_find(const char *cmd) +{ + GSList *tmp; + + g_return_val_if_fail(cmd != NULL, NULL); + + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strcasecmp(rec->cmd, cmd) == 0) + return rec; + } + + return NULL; +} + void command_bind_to(int pos, const char *cmd, const char *category, SIGNAL_FUNC func) { COMMAND_REC *rec; @@ -43,10 +59,14 @@ void command_bind_to(int pos, const char *cmd, const char *category, SIGNAL_FUNC g_return_if_fail(cmd != NULL); - rec = g_new0(COMMAND_REC, 1); - rec->cmd = g_strdup(cmd); - rec->category = category == NULL ? NULL : g_strdup(category); - commands = g_slist_append(commands, rec); + rec = command_find(cmd); + if (rec == NULL) { + rec = g_new0(COMMAND_REC, 1); + rec->cmd = g_strdup(cmd); + rec->category = category == NULL ? NULL : g_strdup(category); + commands = g_slist_append(commands, rec); + } + rec->count++; if (func != NULL) { str = g_strconcat("command ", cmd, NULL); @@ -70,19 +90,14 @@ void command_free(COMMAND_REC *rec) void command_unbind(const char *cmd, SIGNAL_FUNC func) { - GSList *tmp; + COMMAND_REC *rec; char *str; g_return_if_fail(cmd != NULL); - for (tmp = commands; tmp != NULL; tmp = tmp->next) { - COMMAND_REC *rec = tmp->data; - - if (g_strcasecmp(rec->cmd, cmd) == 0) { - command_free(rec); - break; - } - } + rec = command_find(cmd); + if (rec != NULL && --rec->count == 0) + command_free(rec); if (func != NULL) { str = g_strconcat("command ", cmd, NULL); @@ -91,42 +106,81 @@ void command_unbind(const char *cmd, SIGNAL_FUNC func) } } +/* Expand `cmd' - returns `cmd' if not found, NULL if more than one + match is found */ +static const char *command_expand(char *cmd) +{ + GSList *tmp; + const char *match; + int len; + + g_return_val_if_fail(cmd != NULL, NULL); + + match = NULL; + len = strlen(cmd); + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strncasecmp(rec->cmd, cmd, len) == 0 && + strchr(rec->cmd+len, ' ') == NULL) { + if (match != NULL) { + /* multiple matches */ + signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd); + return NULL; + } + + if (rec->cmd[len] == '\0') { + /* full match */ + return rec->cmd; + } + + /* check that this is the only match */ + match = rec->cmd; + } + } + + return match != NULL ? match : cmd; +} + void command_runsub(const char *cmd, const char *data, void *server, void *item) { - char *subcmd, *defcmd, *args; + const char *newcmd; + char *orig, *subcmd, *defcmd, *args; g_return_if_fail(data != NULL); + if (*data == '\0') { + /* no subcommand given - unknown command? */ + signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), cmd); + return; + } + /* get command.. */ - subcmd = g_strdup_printf("command %s %s", cmd, data); - args = strchr(subcmd+9 + strlen(cmd), ' '); + orig = subcmd = g_strdup_printf("command %s %s", cmd, data); + args = strchr(subcmd+8 + strlen(cmd)+1, ' '); if (args != NULL) *args++ = '\0'; else args = ""; while (*args == ' ') args++; + /* check if this command can be expanded */ + newcmd = command_expand(subcmd+8); + if (newcmd == NULL) { + /* ambiguous command */ + g_free(orig); + return; + } + + subcmd = g_strconcat("command ", newcmd, NULL); + g_strdown(subcmd); if (!signal_emit(subcmd, 3, args, server, item)) { defcmd = g_strdup_printf("default command %s", cmd); if (!signal_emit(defcmd, 3, data, server, item)) - signal_emit("unknown command", 3, strchr(subcmd, ' ')+1, server, item); + signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8); g_free(defcmd); } + g_free(subcmd); -} - -COMMAND_REC *command_find(const char *cmd) -{ - GSList *tmp; - - g_return_val_if_fail(cmd != NULL, NULL); - - for (tmp = commands; tmp != NULL; tmp = tmp->next) { - COMMAND_REC *rec = tmp->data; - - if (g_strcasecmp(rec->cmd, cmd) == 0) - return rec; - } - - return NULL; + g_free(orig); } static GSList *optlist_find(GSList *optlist, const char *option) @@ -475,30 +529,42 @@ void cmd_get_remove_func(CMD_GET_FUNC func) static void parse_command(const char *command, int expand_aliases, SERVER_REC *server, void *item) { - const char *alias; - char *cmd, *str, *args, *oldcmd; + const char *alias, *newcmd; + char *cmd, *orig, *args, *oldcmd; - cmd = str = g_strconcat("command ", command, NULL); + cmd = orig = g_strconcat("command ", command, NULL); args = strchr(cmd+8, ' '); if (args != NULL) *args++ = '\0'; else args = ""; /* check if there's an alias for command */ alias = expand_aliases ? alias_find(cmd+8) : NULL; - if (alias != NULL) + if (alias != NULL) { eval_special_string(alias, args, server, item); - else { - if (server != NULL) - server_redirect_default((SERVER_REC *) server, cmd); - - g_strdown(cmd); - oldcmd = current_command; - current_command = cmd+8; - if (!signal_emit(cmd, 3, args, server, item)) - signal_emit_id(signal_default_command, 3, command, server, item); - current_command = oldcmd; + g_free(orig); + return; } - g_free(str); + /* check if this command can be expanded */ + newcmd = command_expand(cmd+8); + if (newcmd == NULL) { + /* ambiguous command */ + g_free(orig); + return; + } + + cmd = g_strconcat("command ", newcmd, NULL); + if (server != NULL) + server_redirect_default((SERVER_REC *) server, cmd); + + g_strdown(cmd); + oldcmd = current_command; + current_command = cmd+8; + if (!signal_emit(cmd, 3, args, server, item)) + signal_emit_id(signal_default_command, 3, command, server, item); + current_command = oldcmd; + + g_free(cmd); + g_free(orig); } static void event_command(const char *line, SERVER_REC *server, void *item) diff --git a/src/core/commands.h b/src/core/commands.h index fa553518..29ddbfb4 100644 --- a/src/core/commands.h +++ b/src/core/commands.h @@ -4,6 +4,7 @@ #include "signals.h" typedef struct { + int count; char *category; char *cmd; char **options; @@ -14,6 +15,9 @@ enum { CMDERR_OPTION_AMBIGUOUS = -2, /* ambiguous -option */ CMDERR_OPTION_ARG_MISSING = -1, /* argument missing for -option */ + CMDERR_UNKNOWN, /* unknown command */ + CMDERR_AMBIGUOUS, /* ambiguous command */ + CMDERR_ERRNO, /* get the error from errno */ CMDERR_NOT_ENOUGH_PARAMS, /* not enough parameters given */ CMDERR_NOT_CONNECTED, /* not connected to IRC server */ diff --git a/src/fe-common/core/fe-core-commands.c b/src/fe-common/core/fe-core-commands.c index 894eae6f..b1edbf78 100644 --- a/src/fe-common/core/fe-core-commands.c +++ b/src/fe-common/core/fe-core-commands.c @@ -30,14 +30,19 @@ #include "windows.h" -static const char *ret_texts[] = { - NULL, - "Not enough parameters given", - "Not connected to IRC server yet", - "Not joined to any channels yet", - "Not joined to such channel", - "Channel not fully synchronized yet, try again after a while", - "Doing this is not a good idea. Add -YES if you really mean it", +static int ret_texts[] = { + IRCTXT_OPTION_UNKNOWN, + IRCTXT_OPTION_AMBIGUOUS, + IRCTXT_OPTION_MISSING_ARG, + IRCTXT_COMMAND_UNKNOWN, + IRCTXT_COMMAND_AMBIGUOUS, + -1, + IRCTXT_NOT_ENOUGH_PARAMS, + IRCTXT_NOT_CONNECTED, + IRCTXT_NOT_JOINED, + IRCTXT_CHAN_NOT_FOUND, + IRCTXT_CHAN_NOT_SYNCED, + IRCTXT_NOT_GOOD_IDEA }; /* keep the whole command line here temporarily. we need it in @@ -269,17 +274,6 @@ static void cmd_beep(void) printbeep(); } -static void cmd_unknown(const char *data, void *server, WI_ITEM_REC *item) -{ - char *cmd; - - cmd = g_strdup(data); g_strup(cmd); - printtext(server, item == NULL ? NULL : item->name, MSGLEVEL_CRAP, "Unknown command: %s", cmd); - g_free(cmd); - - signal_stop(); -} - static void event_command(const char *data) { current_cmdline = data; @@ -287,21 +281,28 @@ static void event_command(const char *data) static void event_default_command(const char *data, void *server, WI_ITEM_REC *item) { - const char *cmd; + const char *ptr; + char *cmd, *p; - cmd = data; - while (*cmd != '\0' && *cmd != ' ') { - if (strchr(settings_get_str("cmdchars"), *cmd)) { + ptr = data; + while (*ptr != '\0' && *ptr != ' ') { + if (strchr(settings_get_str("cmdchars"), *ptr)) { /* command character inside command .. we probably want to send this text to channel. for example when pasting a path /usr/bin/xxx. */ signal_emit("send text", 3, current_cmdline, server, item); return; } - cmd++; + ptr++; } - cmd_unknown(data, server, item); + cmd = g_strdup(data); + p = strchr(cmd, ' '); + if (p != NULL) *p = '\0'; + + signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), cmd); + + g_free(cmd); } static void event_cmderror(gpointer errorp, const char *arg) @@ -309,21 +310,12 @@ static void event_cmderror(gpointer errorp, const char *arg) int error; error = GPOINTER_TO_INT(errorp); - switch (error) { - case CMDERR_ERRNO: + if (error == CMDERR_ERRNO) { + /* errno is special */ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, g_strerror(errno)); - break; - case CMDERR_OPTION_UNKNOWN: - 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: - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, IRCTXT_OPTION_MISSING_ARG, arg); - break; - default: - printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error]); + } else { + /* others */ + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error + -CMDERR_OPTION_UNKNOWN], arg); } } @@ -335,7 +327,6 @@ void fe_core_commands_init(void) command_bind("cat", NULL, (SIGNAL_FUNC) cmd_cat); command_bind("beep", NULL, (SIGNAL_FUNC) cmd_beep); - signal_add("unknown command", (SIGNAL_FUNC) cmd_unknown); signal_add("send command", (SIGNAL_FUNC) event_command); signal_add("default command", (SIGNAL_FUNC) event_default_command); signal_add("error command", (SIGNAL_FUNC) event_cmderror); @@ -349,7 +340,6 @@ void fe_core_commands_deinit(void) command_unbind("cat", (SIGNAL_FUNC) cmd_cat); command_unbind("beep", (SIGNAL_FUNC) cmd_beep); - signal_remove("unknown command", (SIGNAL_FUNC) cmd_unknown); signal_remove("send command", (SIGNAL_FUNC) event_command); signal_remove("default command", (SIGNAL_FUNC) event_default_command); signal_remove("error command", (SIGNAL_FUNC) event_cmderror); diff --git a/src/fe-common/core/fe-log.c b/src/fe-common/core/fe-log.c index 1da7e229..e0277029 100644 --- a/src/fe-common/core/fe-log.c +++ b/src/fe-common/core/fe-log.c @@ -176,7 +176,10 @@ static void cmd_log_list(void) static void cmd_log(const char *data, SERVER_REC *server, void *item) { - command_runsub("log", data, server, item); + if (*data == '\0') + cmd_log_list(); + else + command_runsub("log", data, server, item); } static LOG_REC *log_find_item(const char *item) @@ -438,7 +441,6 @@ void fe_log_init(void) command_bind("log close", NULL, (SIGNAL_FUNC) cmd_log_close); command_bind("log start", NULL, (SIGNAL_FUNC) cmd_log_start); command_bind("log stop", NULL, (SIGNAL_FUNC) cmd_log_stop); - command_bind("log ", NULL, (SIGNAL_FUNC) cmd_log_list); command_bind("window log", NULL, (SIGNAL_FUNC) cmd_window_log); command_bind("window logfile", NULL, (SIGNAL_FUNC) cmd_window_logfile); signal_add_first("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); @@ -461,7 +463,6 @@ void fe_log_deinit(void) command_unbind("log close", (SIGNAL_FUNC) cmd_log_close); command_unbind("log start", (SIGNAL_FUNC) cmd_log_start); command_unbind("log stop", (SIGNAL_FUNC) cmd_log_stop); - command_unbind("log ", (SIGNAL_FUNC) cmd_log_list); command_unbind("window log", (SIGNAL_FUNC) cmd_window_log); command_unbind("window logfile", (SIGNAL_FUNC) cmd_window_logfile); signal_remove("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); diff --git a/src/fe-common/core/module-formats.c b/src/fe-common/core/module-formats.c index 6f704e99..f7ff757f 100644 --- a/src/fe-common/core/module-formats.c +++ b/src/fe-common/core/module-formats.c @@ -94,6 +94,14 @@ FORMAT_REC fecommon_core_formats[] = { { "option_unknown", "Unknown option: $0", 1, { 0 } }, { "option_ambiguous", "Ambiguous option: $0", 1, { 0 } }, { "option_missing_arg", "Missing required argument for: $0", 1, { 0 } }, + { "command_unknown", "Unknown command: $0", 1, { 0 } }, + { "command_ambiguous", "Ambiguous command: $0", 1, { 0 } }, + { "not_enough_params", "Not enough parameters given", 0 }, + { "not_connected", "Not connected to IRC server yet", 0 }, + { "not_joined", "Not joined to any channels yet", 0 }, + { "chan_not_found", "Not joined to such channel", 0 }, + { "chan_not_synced", "Channel not fully synchronized yet, try again after a while", 0 }, + { "not_good_idea", "Doing this is not a good idea. Add -YES if you really mean it", 0 }, { NULL, NULL, 0 } }; diff --git a/src/fe-common/core/module-formats.h b/src/fe-common/core/module-formats.h index 9c5b1f15..f8249d33 100644 --- a/src/fe-common/core/module-formats.h +++ b/src/fe-common/core/module-formats.h @@ -66,7 +66,15 @@ enum { IRCTXT_PERL_ERROR, IRCTXT_OPTION_UNKNOWN, IRCTXT_OPTION_AMBIGUOUS, - IRCTXT_OPTION_MISSING_ARG + IRCTXT_OPTION_MISSING_ARG, + IRCTXT_COMMAND_UNKNOWN, + IRCTXT_COMMAND_AMBIGUOUS, + IRCTXT_NOT_ENOUGH_PARAMS, + IRCTXT_NOT_CONNECTED, + IRCTXT_NOT_JOINED, + IRCTXT_CHAN_NOT_FOUND, + IRCTXT_CHAN_NOT_SYNCED, + IRCTXT_NOT_GOOD_IDEA }; extern FORMAT_REC fecommon_core_formats[]; diff --git a/src/fe-common/irc/dcc/fe-dcc.c b/src/fe-common/irc/dcc/fe-dcc.c index ae3cee81..39b26565 100644 --- a/src/fe-common/irc/dcc/fe-dcc.c +++ b/src/fe-common/irc/dcc/fe-dcc.c @@ -416,6 +416,14 @@ static void cmd_dcc_list(const char *data) printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_LIST_FOOTER); } +static void cmd_dcc(const char *data) +{ + if (*data == '\0') { + cmd_dcc_list(data); + signal_stop(); + } +} + static void sig_dcc_send_complete(GList **list, WINDOW_REC *window, const char *word, const char *line, int *want_space) { @@ -459,7 +467,7 @@ void fe_irc_dcc_init(void) command_bind("me", NULL, (SIGNAL_FUNC) cmd_me); command_bind("action", NULL, (SIGNAL_FUNC) cmd_action); command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp); - command_bind("dcc ", NULL, (SIGNAL_FUNC) cmd_dcc_list); + command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc); command_bind("dcc list", NULL, (SIGNAL_FUNC) cmd_dcc_list); theme_register(fecommon_irc_dcc_formats); @@ -492,6 +500,6 @@ void fe_irc_dcc_deinit(void) command_unbind("me", (SIGNAL_FUNC) cmd_me); command_unbind("action", (SIGNAL_FUNC) cmd_action); command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp); - command_unbind("dcc ", (SIGNAL_FUNC) cmd_dcc_list); + command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc); command_unbind("dcc list", (SIGNAL_FUNC) cmd_dcc_list); } diff --git a/src/fe-common/irc/fe-channels.c b/src/fe-common/irc/fe-channels.c index c17325eb..8eb1ebf9 100644 --- a/src/fe-common/irc/fe-channels.c +++ b/src/fe-common/irc/fe-channels.c @@ -182,12 +182,12 @@ static void cmd_channel_list(void) static void cmd_channel(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { - if (ischannel(*data)) { + if (*data == '\0') + cmd_channel_list_joined(); + else if (ischannel(*data)) signal_emit("command join", 2, data, server); - return; - } - - command_runsub("channel", data, server, item); + else + command_runsub("channel", data, server, item); } static void cmd_channel_add(const char *data) @@ -260,7 +260,6 @@ void fe_channels_init(void) command_bind("wjoin", NULL, (SIGNAL_FUNC) cmd_wjoin); command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel); - command_bind("channel ", NULL, (SIGNAL_FUNC) cmd_channel_list_joined); command_bind("channel add", NULL, (SIGNAL_FUNC) cmd_channel_add); command_bind("channel remove", NULL, (SIGNAL_FUNC) cmd_channel_remove); command_bind("channel list", NULL, (SIGNAL_FUNC) cmd_channel_list); @@ -278,7 +277,6 @@ void fe_channels_deinit(void) command_unbind("wjoin", (SIGNAL_FUNC) cmd_wjoin); command_unbind("channel", (SIGNAL_FUNC) cmd_channel); - command_unbind("channel ", (SIGNAL_FUNC) cmd_channel_list_joined); command_unbind("channel add", (SIGNAL_FUNC) cmd_channel_add); command_unbind("channel remove", (SIGNAL_FUNC) cmd_channel_remove); command_unbind("channel list", (SIGNAL_FUNC) cmd_channel_list); diff --git a/src/fe-common/irc/fe-ircnet.c b/src/fe-common/irc/fe-ircnet.c index adc4f03c..9a361bce 100644 --- a/src/fe-common/irc/fe-ircnet.c +++ b/src/fe-common/irc/fe-ircnet.c @@ -28,11 +28,6 @@ #include "irc-server.h" #include "ircnet-setup.h" -static void cmd_ircnet(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) -{ - command_runsub("ircnet", data, server, item); -} - static void cmd_ircnet_list(void) { GString *str; @@ -155,10 +150,17 @@ static void cmd_ircnet_remove(const char *data) } } +static void cmd_ircnet(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + if (*data == '\0') + cmd_ircnet_list(); + else + command_runsub("ircnet", data, server, item); +} + void fe_ircnet_init(void) { command_bind("ircnet", NULL, (SIGNAL_FUNC) cmd_ircnet); - command_bind("ircnet ", NULL, (SIGNAL_FUNC) cmd_ircnet_list); command_bind("ircnet add", NULL, (SIGNAL_FUNC) cmd_ircnet_add); command_bind("ircnet remove", NULL, (SIGNAL_FUNC) cmd_ircnet_remove); @@ -168,7 +170,6 @@ void fe_ircnet_init(void) void fe_ircnet_deinit(void) { command_unbind("ircnet", (SIGNAL_FUNC) cmd_ircnet); - command_unbind("ircnet ", (SIGNAL_FUNC) cmd_ircnet_list); command_unbind("ircnet add", (SIGNAL_FUNC) cmd_ircnet_add); command_unbind("ircnet remove", (SIGNAL_FUNC) cmd_ircnet_remove); } diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c index 6d7626de..fb4e338b 100644 --- a/src/irc/core/irc-commands.c +++ b/src/irc/core/irc-commands.c @@ -463,7 +463,7 @@ static void cmd_who(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) if (strcmp(channel, "*") == 0 || *channel == '\0') { if (!irc_item_channel(item)) - cmd_return_error(CMDERR_NOT_JOINED); + cmd_param_error(CMDERR_NOT_JOINED); channel = item->name; } diff --git a/src/perl/xs/Irssi-core.xs b/src/perl/xs/Irssi-core.xs index 324637ae..d8a87c50 100644 --- a/src/perl/xs/Irssi-core.xs +++ b/src/perl/xs/Irssi-core.xs @@ -90,16 +90,8 @@ CODE: char *signal; GSList *tmp; - /* Don't add the command twice */ if (*category == '\0') category = "Perl scripts' commands"; - for (tmp = commands; tmp != NULL; tmp = tmp->next) { - COMMAND_REC *rec = tmp->data; - - if (g_strcasecmp(rec->cmd, cmd) == 0 && - g_strcasecmp(rec->category, category) == 0) - break; - } - if (tmp == NULL) command_bind(cmd, category, NULL); + command_bind(cmd, category, NULL); signal = g_strconcat("command ", cmd, NULL); perl_signal_add(signal, func); g_free(signal);