1
0
mirror of https://github.com/Tha14/toxic.git synced 2024-06-26 20:57:48 +02:00

auto-completion for paths when sending file & improved auto-complete algorithm to do partial matches

This commit is contained in:
Jfreegman 2014-07-18 01:29:46 -04:00
parent e61d070def
commit ea3fcd5b79
11 changed files with 334 additions and 121 deletions

View File

@ -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

251
src/autocomplete.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <string.h>
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/dir.h>
#else
#include <dirent.h>
#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 "<incomplete-dir>" 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);
}

43
src/autocomplete.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
#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 "<incomplete-dir>" 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 */

View File

@ -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);

View File

@ -26,9 +26,9 @@
#include <resolv.h>
#ifdef __APPLE__
#include <arpa/nameser_compat.h>
#include <arpa/nameser_compat.h>
#else
#include <arpa/nameser.h>
#include <arpa/nameser.h>
#endif /* ifdef __APPLE__ */
#include <tox/toxdns.h>

View File

@ -41,6 +41,7 @@
#include "settings.h"
#include "input.h"
#include "help.h"
#include "autocomplete.h"
extern char *DATA_FILE;

View File

@ -25,6 +25,7 @@
#include <string.h>
#include <time.h>
#include <limits.h>
#include <dirent.h>
#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;
}

View File

@ -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 */

View File

@ -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;

View File

@ -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;
}

View File

@ -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);