From ea3fcd5b797853b249c7086b0a812f0ef28d2daf Mon Sep 17 00:00:00 2001 From: Jfreegman Date: Fri, 18 Jul 2014 01:29:46 -0400 Subject: [PATCH] auto-completion for paths when sending file & improved auto-complete algorithm to do partial matches --- build/Makefile | 2 +- src/autocomplete.c | 251 ++++++++++++++++++++++++++++++++++++++++++++ src/autocomplete.h | 43 ++++++++ src/chat.c | 28 ++--- src/dns.c | 4 +- src/groupchat.c | 1 + src/misc_tools.c | 17 ++- src/misc_tools.h | 4 + src/prompt.c | 1 + src/toxic_strings.c | 94 ----------------- src/toxic_strings.h | 10 -- 11 files changed, 334 insertions(+), 121 deletions(-) create mode 100644 src/autocomplete.c create mode 100644 src/autocomplete.h diff --git a/build/Makefile b/build/Makefile index 96295b7..3a8dce5 100644 --- a/build/Makefile +++ b/build/Makefile @@ -22,7 +22,7 @@ CFLAGS += $(USER_CFLAGS) LDFLAGS = $(USER_LDFLAGS) OBJ = chat.o chat_commands.o configdir.o dns.o execute.o file_senders.o -OBJ += friendlist.o global_commands.o groupchat.o line_info.o input.o help.o +OBJ += friendlist.o global_commands.o groupchat.o line_info.o input.o help.o autocomplete.o OBJ += log.o misc_tools.o prompt.o settings.o toxic.o toxic_strings.o windows.o # Variables for audio support diff --git a/src/autocomplete.c b/src/autocomplete.c new file mode 100644 index 0000000..8cb2477 --- /dev/null +++ b/src/autocomplete.c @@ -0,0 +1,251 @@ +/* autocomplete.c + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#include +#include + +#ifdef __APPLE__ + #include + #include +#else + #include +#endif /* ifdef __APPLE__ */ + +#include "windows.h" +#include "toxic.h" +#include "misc_tools.h" +#include "line_info.h" +#include "execute.h" + +/* puts match in match buffer. if more than one match, add first n chars that are identical. + e.g. if matches contains: [foo, foobar, foe] we put fo in matches. */ +static void get_str_match(char *match, char (*matches)[MAX_STR_SIZE], int n) +{ + if (n == 1) { + strcpy(match, matches[0]); + return; + } + + int i; + int shortest = MAX_STR_SIZE; + + for (i = 0; i < n; ++i) { + int m_len = strlen(matches[i]); + + if (m_len < shortest) + shortest = m_len; + } + + for (i = 0; i < shortest; ++i) { + char ch = matches[0][i]; + int j; + + for (j = 0; j < n; ++j) { + if (matches[j][i] != ch) { + strcpy(match, matches[0]); + match[i] = '\0'; + return; + } + } + } + + strcpy(match, matches[0]); +} + +/* looks for the first instance in list that begins with the last entered word in line according to pos, + then fills line with the complete word. e.g. "Hello jo" would complete the line + with "Hello john". Works slightly differently for directory paths with the same results. + + list is a pointer to the list of strings being compared, n_items is the number of items + in the list, and size is the size of each item in the list. + + Returns the difference between the old len and new len of line on success, -1 if error */ +int complete_line(ChatContext *ctx, const void *list, int n_items, int size) +{ + if (ctx->pos <= 0 || ctx->len <= 0 || ctx->len >= MAX_STR_SIZE || size > MAX_STR_SIZE) + return -1; + + const char *L = (char *) list; + + bool dir_search = false; + const char *endchrs = " "; + char ubuf[MAX_STR_SIZE]; + + /* work with multibyte string copy of buf for simplicity */ + if (wcs_to_mbs_buf(ubuf, ctx->line, sizeof(ubuf)) == -1) + return -1; + + /* isolate substring from space behind pos to pos */ + char tmp[MAX_STR_SIZE]; + snprintf(tmp, sizeof(tmp), "%s", ubuf); + tmp[ctx->pos] = '\0'; + + const char *s = strrchr(tmp, ' '); + char *sub = malloc(strlen(ubuf) + 1); + + if (sub == NULL) + exit_toxic_err("failed in complete_line", FATALERR_MEMORY); + + if (!s) { + strcpy(sub, tmp); + + if (sub[0] != '/') + endchrs = ": "; + } else { + strcpy(sub, &s[1]); + + if (strncmp(ubuf, "/sendfile", strlen("/sendfile")) == 0) { + dir_search = true; + int sub_len = strlen(sub); + int si = char_rfind(sub, '/', sub_len); + memmove(sub, &sub[si + 1], sub_len - si); + } + } + + if (string_is_empty(sub)) { + free(sub); + return -1; + } + + int s_len = strlen(sub); + const char *str; + int n_matches = 0; + char matches[n_items][MAX_STR_SIZE]; + int i = 0; + + /* put all list matches in matches array */ + for (i = 0; i < n_items; ++i) { + str = &L[i * size]; + + if (strncasecmp(str, sub, s_len) == 0) + strcpy(matches[n_matches++], str); + } + + free(sub); + + if (!n_matches) + return -1; + + char match[size]; + get_str_match(match, matches, n_matches); + + if (dir_search) { + if (n_matches == 1) + endchrs = char_rfind(match, '.', strlen(match)) ? "\"" : "/"; + else + endchrs = ""; + } else if (n_matches > 1) { + endchrs = ""; + } + + /* put match in correct spot in buf and append endchars */ + int n_endchrs = strlen(endchrs); + int m_len = strlen(match); + int strt = ctx->pos - s_len; + int diff = m_len - s_len + n_endchrs; + + if (ctx->len + diff > MAX_STR_SIZE) + return -1; + + char tmpend[MAX_STR_SIZE]; + strcpy(tmpend, &ubuf[ctx->pos]); + strcpy(&ubuf[strt], match); + strcpy(&ubuf[strt + m_len], endchrs); + strcpy(&ubuf[strt + m_len + n_endchrs], tmpend); + + /* convert to widechar and copy back to original buf */ + wchar_t newbuf[MAX_STR_SIZE]; + + if (mbs_to_wcs_buf(newbuf, ubuf, MAX_STR_SIZE) == -1) + return -1; + + wcscpy(ctx->line, newbuf); + + ctx->len += diff; + ctx->pos += diff; + + return diff; +} + +/* matches /sendfile "" line to matching directories. + + if only one match, auto-complete line and return diff between old len and new len. + return 0 if > 1 match and print out all the matches + return -1 if no matches */ + +#define MAX_DIRS 256 + +int dir_match(ToxWindow *self, Tox *m, const wchar_t *line) +{ + char b_path[MAX_STR_SIZE]; + char b_name[MAX_STR_SIZE]; + + if (wcs_to_mbs_buf(b_path, line, sizeof(b_path)) == -1) + return -1; + + int si = char_rfind(b_path, '/', strlen(b_path)); + + if (!b_path[0]) { /* list everything in pwd */ + strcpy(b_path, "."); + } else if (!si && b_path[0] != '/') { /* look for matches in pwd */ + char tmp[MAX_STR_SIZE]; + snprintf(tmp, sizeof(tmp), ".%s", b_path); + strcpy(b_path, tmp); + } + + strcpy(b_name, &b_path[si + 1]); + b_path[si + 1] = '\0'; + int b_name_len = strlen(b_name); + + DIR *dp = opendir(b_path); + + if (dp == NULL) + return -1; + + char dirnames[MAX_DIRS][NAME_MAX]; + struct dirent *entry; + int dircount = 0; + + while ((entry = readdir(dp)) && dircount < MAX_DIRS) { + if (strncmp(entry->d_name, b_name, b_name_len) == 0) { + snprintf(dirnames[dircount], sizeof(dirnames[dircount]), "%s", entry->d_name); + ++dircount; + } + } + + if (dircount == 0) + return -1; + + if (dircount > 1) { + execute(self->chatwin->history, self, m, "/clear", GLOBAL_COMMAND_MODE); + + int i; + + for (i = 0; i < dircount; ++i) + line_info_add(self, NULL, NULL, NULL, dirnames[i], SYS_MSG, 0, 0); + + complete_line(self->chatwin, dirnames, dircount, NAME_MAX); + return 0; + } + + return complete_line(self->chatwin, dirnames, dircount, NAME_MAX); +} diff --git a/src/autocomplete.h b/src/autocomplete.h new file mode 100644 index 0000000..b97d8dc --- /dev/null +++ b/src/autocomplete.h @@ -0,0 +1,43 @@ +/* autocomplete.h + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifndef _autocomplete_h +#define _autocomplete_h + +/* looks for the first instance in list that begins with the last entered word in line according to pos, + then fills line with the complete word. e.g. "Hello jo" would complete the line + with "Hello john". Works slightly differently for directory paths with the same results. + + list is a pointer to the list of strings being compared, n_items is the number of items + in the list, and size is the size of each item in the list. + + Returns the difference between the old len and new len of line on success, -1 if error */ +int complete_line(ChatContext *ctx, const void *list, int n_items, int size); + +/* matches /sendfile "" line to matching directories. + + if only one match, auto-complete line and return diff between old len and new len. + return 0 if > 1 match and print out all the matches + return -1 if no matches */ +int dir_match(ToxWindow *self, Tox *m, const wchar_t *line); + +#endif /* #define _autocomplete_h */ \ No newline at end of file diff --git a/src/chat.c b/src/chat.c index 38efd17..b85971d 100644 --- a/src/chat.c +++ b/src/chat.c @@ -41,6 +41,7 @@ #include "settings.h" #include "input.h" #include "help.h" +#include "autocomplete.h" #ifdef _SUPPORT_AUDIO #include "audio_call.h" @@ -685,21 +686,22 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) input_handle(self, key, x, y, x2, y2); - if (key == '\t') { /* TAB key: auto-completes command */ - if (ctx->len > 1 && ctx->line[0] == '/') { - int diff = complete_line(ctx, chat_cmd_list, AC_NUM_CHAT_COMMANDS, MAX_CMDNAME_SIZE); + if (key == '\t' && ctx->len > 1 && ctx->line[0] == '/') { /* TAB key: auto-complete */ + int diff = -1; + int sf_len = 11; - if (diff != -1) { - if (x + diff > x2 - 1) { - wmove(self->window, y, x + diff); - ctx->start += diff; - } else { - wmove(self->window, y, x + diff); - } - } else - beep(); - } else + if (wcsncmp(ctx->line, L"/sendfile \"", sf_len) == 0) { + diff = dir_match(self, m, &ctx->line[sf_len]); + } else { + diff = complete_line(ctx, chat_cmd_list, AC_NUM_CHAT_COMMANDS, MAX_CMDNAME_SIZE); + } + + if (diff != -1) { + if (x + diff > x2 - 1) + ctx->start += diff; + } else { beep(); + } } else if (key == '\n') { rm_trailing_spaces_buf(ctx); diff --git a/src/dns.c b/src/dns.c index 5f3c611..4331541 100644 --- a/src/dns.c +++ b/src/dns.c @@ -26,9 +26,9 @@ #include #ifdef __APPLE__ -#include + #include #else -#include + #include #endif /* ifdef __APPLE__ */ #include diff --git a/src/groupchat.c b/src/groupchat.c index 09c5e52..c501f3d 100644 --- a/src/groupchat.c +++ b/src/groupchat.c @@ -41,6 +41,7 @@ #include "settings.h" #include "input.h" #include "help.h" +#include "autocomplete.h" extern char *DATA_FILE; diff --git a/src/misc_tools.c b/src/misc_tools.c index 13ad33e..2090e87 100644 --- a/src/misc_tools.c +++ b/src/misc_tools.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "toxic.h" #include "windows.h" @@ -247,7 +248,21 @@ int char_find(int idx, const char *s, char ch) { int i = idx; - for ( ; s[i]; ++i) { + for (i = idx; s[i]; ++i) { + if (s[i] == ch) + break; + } + + return i; +} + +/* returns index of the last instance of ch in s starting at len + returns 0 if char not found (skips 0th index) */ +int char_rfind(const char *s, char ch, int len) +{ + int i = 0; + + for (i = len; i > 0; --i) { if (s[i] == ch) break; } diff --git a/src/misc_tools.h b/src/misc_tools.h index 49e0b28..37894de 100644 --- a/src/misc_tools.h +++ b/src/misc_tools.h @@ -93,4 +93,8 @@ int get_nick_truncate(Tox *m, char *buf, int friendnum); returns length of s if char not found */ int char_find(int idx, const char *s, char ch); +/* returns index of the last instance of ch in s + returns 0 if char not found */ +int char_rfind(const char *s, char ch, int len); + #endif /* #define _misc_tools_h */ diff --git a/src/prompt.c b/src/prompt.c index efaa9ca..0989152 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -39,6 +39,7 @@ #include "settings.h" #include "input.h" #include "help.h" +#include "autocomplete.h" char pending_frnd_requests[MAX_FRIENDS_NUM][TOX_CLIENT_ID_SIZE]; uint16_t num_frnd_requests = 0; diff --git a/src/toxic_strings.c b/src/toxic_strings.c index 4e92d43..932a0c8 100644 --- a/src/toxic_strings.c +++ b/src/toxic_strings.c @@ -208,97 +208,3 @@ void fetch_hist_item(ChatContext *ctx, int key_dir) ctx->pos = h_len; ctx->len = h_len; } - -/* looks for the first instance in list that begins with the last entered word in line according to pos, - then fills line with the complete word. e.g. "Hello jo" would complete the line - with "Hello john". - - list is a pointer to the list of strings being compared, n_items is the number of items - in the list, and size is the size of each item in the list. - - Returns the difference between the old len and new len of line on success, -1 if error */ -int complete_line(ChatContext *ctx, const void *list, int n_items, int size) -{ - if (ctx->pos <= 0 || ctx->len <= 0 || ctx->len >= MAX_STR_SIZE) - return -1; - - const char *L = (char *) list; - - char ubuf[MAX_STR_SIZE]; - - /* work with multibyte string copy of buf for simplicity */ - if (wcs_to_mbs_buf(ubuf, ctx->line, sizeof(ubuf)) == -1) - return -1; - - /* isolate substring from space behind pos to pos */ - char tmp[MAX_STR_SIZE]; - snprintf(tmp, sizeof(tmp), "%s", ubuf); - tmp[ctx->pos] = '\0'; - const char *s = strrchr(tmp, ' '); - int n_endchrs = 1; /* 1 = append space to end of match, 2 = append ": " */ - - char *sub = malloc(strlen(ubuf) + 1); - - if (sub == NULL) - exit_toxic_err("failed in complete_line", FATALERR_MEMORY); - - if (!s) { - strcpy(sub, tmp); - - if (sub[0] != '/') /* make sure it's not a command */ - n_endchrs = 2; - } else { - strcpy(sub, &s[1]); - } - - if (string_is_empty(sub)) { - free(sub); - return -1; - } - - int s_len = strlen(sub); - const char *match; - bool is_match = false; - int i; - - /* look for a match in list */ - for (i = 0; i < n_items; ++i) { - match = &L[i * size]; - - if ((is_match = strncasecmp(match, sub, s_len) == 0)) - break; - } - - free(sub); - - if (!is_match) - return -1; - - /* put match in correct spot in buf and append endchars (space or ": ") */ - const char *endchrs = n_endchrs == 1 ? " " : ": "; - int m_len = strlen(match); - int strt = ctx->pos - s_len; - int diff = m_len - s_len + n_endchrs; - - if (ctx->len + diff > MAX_STR_SIZE) - return -1; - - char tmpend[MAX_STR_SIZE]; - strcpy(tmpend, &ubuf[ctx->pos]); - strcpy(&ubuf[strt], match); - strcpy(&ubuf[strt + m_len], endchrs); - strcpy(&ubuf[strt + m_len + n_endchrs], tmpend); - - /* convert to widechar and copy back to original buf */ - wchar_t newbuf[MAX_STR_SIZE]; - - if (mbs_to_wcs_buf(newbuf, ubuf, MAX_STR_SIZE) == -1) - return -1; - - wcscpy(ctx->line, newbuf); - - ctx->len += diff; - ctx->pos += diff; - - return diff; -} diff --git a/src/toxic_strings.h b/src/toxic_strings.h index 3d907c1..fde51d7 100644 --- a/src/toxic_strings.h +++ b/src/toxic_strings.h @@ -52,16 +52,6 @@ int yank_buf(ChatContext *ctx); /* Removes trailing spaces from line. */ void rm_trailing_spaces_buf(ChatContext *ctx); -/* looks for the first instance in list that begins with the last entered word in line according to pos, - then fills line with the complete word. e.g. "Hello jo" would complete the line - with "Hello john". - - list is a pointer to the list of strings being compared, n_items is the number of items - in the list, and size is the size of each item in the list. - - Returns the difference between the old len and new len of line on success, -1 if error */ -int complete_line(ChatContext *ctx, const void *list, int n_items, int size); - /* adds a line to the ln_history buffer at hst_pos and sets hst_pos to last history item. */ void add_line_to_hist(ChatContext *ctx);