From ffcbd66a7e0bf35c9196b20a593bb5056197b2a9 Mon Sep 17 00:00:00 2001 From: Tony Olagbaiye Date: Sun, 13 May 2018 04:54:20 +0100 Subject: [PATCH] Implement Standard Slack Emoji completion using /input command_* hooks See commit 5217251e94b2e5ea54a48ac660cb303257489f91 for details --- README.org | 7 +- debian/control | 2 +- slack-completion.c | 8 ++ slack-emoji.c | 193 ++++++++++++++++++++++++++++++++++----------- slack-emoji.h | 8 ++ 5 files changed, 168 insertions(+), 50 deletions(-) diff --git a/README.org b/README.org index b7842af..4fd6d44 100644 --- a/README.org +++ b/README.org @@ -35,7 +35,7 @@ - libwebsockets (static, submodule) - json-c (static, submodule) - - weechat (>= v1.4) + - weechat (>= v1.7) * Building @@ -84,6 +84,11 @@ ** TODO [#B] Implement websocket ping and pong (milestone v0.4) - [ ] Add ping timer and pong handler (see [[http://github.com/bqv/weechat-slack/issues/9][#9]]) ** TODO [#C] Implement remaining api endpoints and events (milestone v0.5) + - [ ] Support all channel types + - [X] +Channels+ + - [ ] Groups + - [ ] MPIMs + - [ ] IMs - [ ] Complete api endpoint set - [ ] Complete api event set ** TODO [#C] Implement full weechat functionality (milestone v0.6) diff --git a/debian/control b/debian/control index 71cf7cc..26b93b6 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,7 @@ Vcs-Browser: https://github.com/bqv/weechat-slack Package: weechat-slack Architecture: any Depends: - weechat (>= 1.4), + weechat (>= 1.7), ${shlibs:Depends}, ${misc:Depends} Description: Fast, light and extensible chat client - Slack plugin diff --git a/slack-completion.c b/slack-completion.c index 8cd19a2..b0b3c6f 100644 --- a/slack-completion.c +++ b/slack-completion.c @@ -13,6 +13,14 @@ void slack_completion_init() { + weechat_hook_command_run("/input return", + &slack_emoji_input_replace_cb, + NULL, NULL); + + weechat_hook_command_run("/input complete*", + &slack_emoji_input_complete_cb, + NULL, NULL); + weechat_hook_completion("slack_emoji", N_("slack emoji"), &slack_emoji_complete_by_name_cb, diff --git a/slack-emoji.c b/slack-emoji.c index 8de99ba..42e3eb9 100644 --- a/slack-emoji.c +++ b/slack-emoji.c @@ -4,6 +4,7 @@ #include #include +#include #include "weechat-plugin.h" #include "slack.h" @@ -12,6 +13,7 @@ #include "slack-emoji.inc" #define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) static int emoji_byname_cmp(const void *p1, const void *p2) { @@ -25,64 +27,121 @@ static int emoji_bytext_cmp(const void *p1, const void *p2) ((struct t_slack_emoji_by_text *)p2)->text); } -static size_t levenshtein_dist(const char *s, size_t len_s, const char *t, size_t len_t) -{ - size_t cost; - - /* base case: empty strings */ - if (len_s == 0) return len_t; - if (len_t == 0) return len_s; +static size_t modified_wagner_fischer(const char *src, const char *targ) +{ + size_t len = strlen(targ) + 1; + size_t above[len], below[len]; + for (size_t *k = above, c = 0; k < above + len; ++k, ++c) *k = c; - /* test if last characters of the strings match */ - if (s[len_s-1] == t[len_t-1]) - cost = 0; - else - cost = 1; + const char *src_at = src, *targ_at; + for (size_t j = 1; j < strlen(src) + 1; ++j) + { + *below = j; + targ_at = targ; + for (size_t *d = above, *a = above + 1, *l = below, *c = below + 1; + c < below + len; ++d, ++a, ++l, ++c) + { + /* |-------------replace-----------| |isrt| |delt| */ + *c = MIN( *src_at == *targ_at ? *d : *d + 1, MIN( *a + 0, *l + 1 ) ); + ++targ_at; + } + for (size_t *a = above, *b = below; a < above + len; ++a, ++b) *a = *b; + ++src_at; + } - /* delete char from s, delete char from t, and delete char from both */ - size_t delete = levenshtein_dist(s, len_s - 1, t, len_t ) + 1; - size_t insert = levenshtein_dist(s, len_s , t, len_t - 1) + 1; - size_t replace = levenshtein_dist(s, len_s - 1, t, len_t - 1) + cost; - return MIN( MIN( delete, insert ), replace ); + return above[len-1]; } -static size_t wagner_fischer(const char *src, const char *targ) +static size_t longest_common_substring(const char *X, const char *Y) { - size_t len = strlen(targ) + 1; - size_t above[len], below[len]; - for (size_t *k = above, c = 0; k < above+len; ++k, ++c) *k=c; - - const char *src_at = src, *targ_at; - for (size_t j = 1; j < strlen(src)+1; ++j) - { - *below = j; - targ_at = targ; - for (size_t *d = above, *a = above+1, *l = below, *c = below + 1; - c < below + len; ++d, ++a, ++l, ++c) - { - *c = MIN( *src_at == *targ_at ? *d : *d + 1, MIN( *a + 1, *l + 1 ) ); - ++targ_at; - } - for (size_t *a = above, *b = below; a < above + len; ++a, ++b) *a = *b; - ++src_at; - } - - return above[len-1]; + const size_t n = strlen(X); + const size_t m = strlen(Y); + size_t i, j, result = 0; + size_t **L; + + L = malloc(sizeof(size_t *) * (n + 1)); + L[0] = malloc(sizeof(size_t) * (m + 1) * (n + 1)); + + for (i = 0; i <= n; i++) + L[i] = (*L + (m + 1) * i); + + /* Following steps build L[n+1][m+1] in bottom up fashion. Note + that L[i][j] contains length of LCS of X[0..i-1] and Y[0..j-1] */ + for (i = 0; i <= n; i++) + { + for (j = 0; j <= m; j++) + { + if (i == 0 || j == 0) + { + L[i][j] = 0; + } + else if (X[i-1] == Y[j-1]) + { + L[i][j] = L[i - 1][j - 1] + 1; + if (result < L[i][j]) + result = L[i][j]; + } + else + { + L[i][j] = 0; + } + } + } + + /* result now contains length of LCS for X[0..n-1] and Y[0..m-1] */ + free(L[0]); + free(L); + return result; } int slack_emoji_complete_by_name_cb(const void *pointer, void *data, const char *completion_item, struct t_gui_buffer *buffer, struct t_gui_completion *completion) +{ + (void) pointer; + (void) data; + (void) completion_item; + (void) buffer; + + size_t i, emoji_count = sizeof(slack_emoji_by_name) + / sizeof(struct t_slack_emoji_by_name); + + for (i = 0; i < emoji_count; i++) + weechat_hook_completion_list_add(completion, + slack_emoji_by_name[i].name, + 0, WEECHAT_LIST_POS_END); + + return WEECHAT_RC_OK; +} + +int slack_emoji_input_complete_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *command) { struct t_slack_emoji_by_name *closest_emoji; + int input_pos, input_length, start, end; + char *new_string, *word, *new_pos; + const char *input_string; (void) pointer; (void) data; + (void) command; + + input_string = weechat_buffer_get_string(buffer, "input"); + input_length = strlen(input_string); + input_pos = weechat_buffer_get_integer(buffer, "input_pos"); + for (start = input_pos; start > 0 && input_string[start] != ':'; start--) + if (input_string[start] == ' ') { break; } + for (end = input_pos; end < input_length && input_string[end] != ' '; end++) + if (input_string[end] == ':') { end++; break; } - weechat_printf(NULL, "Completing!"); + if (input_string[start] != ':') + return WEECHAT_RC_OK; + else + word = strndup(&input_string[start], end - start); - size_t i, emoji_count = sizeof(slack_emoji_by_name) + size_t emoji_count = sizeof(slack_emoji_by_name) / sizeof(struct t_slack_emoji_by_name); closest_emoji = malloc(sizeof(slack_emoji_by_name)); memcpy(closest_emoji, slack_emoji_by_name, @@ -90,20 +149,58 @@ int slack_emoji_complete_by_name_cb(const void *pointer, void *data, int edit_dist_cmp(const void *p1, const void *p2) { - return 0; + const struct t_slack_emoji_by_name *e1 = p1; + const struct t_slack_emoji_by_name *e2 = p2; + size_t d1 = modified_wagner_fischer(e1->name, word); + size_t d2 = modified_wagner_fischer(e2->name, word); + if (d1 == d2) + { + size_t l1 = longest_common_substring(e1->name, word); + size_t l2 = longest_common_substring(e2->name, word); + return (l1 < l2) - (l1 > l2); + } + return (d1 > d2) - (d1 < d2); }; qsort(closest_emoji, emoji_count, sizeof(struct t_slack_emoji_by_name), edit_dist_cmp); - for (i = 0; i < emoji_count; i++) - { - weechat_printf(NULL, closest_emoji[i].name); - weechat_hook_completion_list_add(completion, closest_emoji[i].name, - 0, WEECHAT_LIST_POS_END); - } - + size_t new_length = snprintf(NULL, 0, "%.*s%s%s", + start, input_string, + closest_emoji[0].name, + &input_string[end]) + 1; + new_string = malloc(new_length); + snprintf(new_string, new_length, "%.*s%s%s", + start, input_string, + closest_emoji[0].name, + &input_string[end]); + weechat_buffer_set(buffer, "input", new_string); + + size_t new_pos_len = snprintf(NULL, 0, "%lu", + (unsigned long)(start + + strlen(closest_emoji[0].name) - 1)); + new_pos = malloc(new_pos_len); + snprintf(new_pos, new_pos_len, "%lu", + (unsigned long)(start + + strlen(closest_emoji[0].name) - 1)); + weechat_buffer_set(buffer, "input_pos", new_pos); + + free(new_pos); + free(new_string); free(closest_emoji); + free(word); + return WEECHAT_RC_OK_EAT; +} + +int slack_emoji_input_replace_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *command) +{ + (void) pointer; + (void) data; + (void) buffer; + (void) command; + return WEECHAT_RC_OK; } diff --git a/slack-emoji.h b/slack-emoji.h index 906c684..9fc688d 100644 --- a/slack-emoji.h +++ b/slack-emoji.h @@ -10,6 +10,14 @@ int slack_emoji_complete_by_name_cb(const void *pointer, void *data, struct t_gui_buffer *buffer, struct t_gui_completion *completion); +int slack_emoji_input_complete_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *command); + +int slack_emoji_input_replace_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *command); + const char *slack_emoji_get_unicode_by_name(const char *name); const char *slack_emoji_get_unicode_by_text(const char *text);