diff mcabber/mcabber/screen.c @ 2304:fa8365fb6ac2

[PATCH 1/3] New option: vi_mode If the new vi_mode option is set to 1, let MCabber's non-chat mode accept a few commands loosely based on those available in vi(1)'s normal mode, e.g.: A Call "/roster unread_first". a Call "/roster unread_next". F Call "/roster group_prev". f Call "/roster group_next". G Call "/roster bottom". gg Call "/roster top". i Enter chat mode. [<n>]j Call "/roster down [<n>]". [<n>]k Call "/roster up [<n>]". n Repeat the previous search (if any). O Call "/roster unread_first" and open chat window. o Call "/roster unread_next" and open chat window. ZZ Call "/quit". zM Call "/group fold" for all groups. zR Call "/group unfold" for all groups. <Space> Call "/group toggle" for the current group. '' Call "/roster alternate". ! Toggle attention flag for current buddy. # Toggle unread messages flag for current buddy. /<str> Call "/roster search <str>". :q Call "/quit". :wq Call "/quit". :x Call "/quit". :<n> Jump to line <n> in the roster. :<cmd> Call "/<cmd>" (unless <cmd> matches one of the above commands).
author Holger Weiß <holger@zedat.fu-berlin.de>
date Wed, 22 Jul 2015 19:25:22 +0200
parents f181418db215
children 5b1a63dc2b1a
line wrap: on
line diff
--- a/mcabber/mcabber/screen.c	Mon Jan 30 18:46:15 2017 +0100
+++ b/mcabber/mcabber/screen.c	Wed Jul 22 19:25:22 2015 +0200
@@ -95,6 +95,9 @@
 static void spellcheck(char *, char *);
 #endif
 
+static void open_chat_window(void);
+static void clear_inputline(void);
+
 static GHashTable *winbufhash;
 
 typedef struct {
@@ -4175,7 +4178,8 @@
 static inline void refresh_inputline(void)
 {
 #if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-  if (settings_opt_get_int("spell_enable")) {
+  if (settings_opt_get_int("spell_enable") &&
+      (chatmode || !settings_opt_get_int("vi_mode"))) {
     memset(maskLine, 0, INPUTLINE_LENGTH+1);
     spellcheck(inputLine, maskLine);
   }
@@ -4426,12 +4430,53 @@
 #endif
 }
 
+static void scr_process_vi_arrow_key(int key)
+{
+  const char *l;
+  char mask[INPUTLINE_LENGTH+1] = "/roster search ";
+  size_t cmd_len = strlen(mask);
+  size_t str_len = strlen(inputLine) - 1;
+
+  switch (inputLine[0]) {
+    case ':':
+        inputLine[0] = '/';
+        if (key == KEY_UP)
+          l = scr_cmdhisto_prev(inputLine, ptr_inputline - inputLine);
+        else
+          l = scr_cmdhisto_next(inputLine, ptr_inputline - inputLine);
+        if (l)
+          strcpy(inputLine, l);
+        inputLine[0] = ':';
+        break;
+    case '/':
+        if (cmd_len + str_len > INPUTLINE_LENGTH)
+          return;
+
+        memcpy(mask + cmd_len, inputLine + 1, str_len + 1);
+        if (key == KEY_UP)
+          l = scr_cmdhisto_prev(mask, ptr_inputline - inputLine + cmd_len - 1);
+        else
+          l = scr_cmdhisto_next(mask, ptr_inputline - inputLine + cmd_len - 1);
+        if (l)
+          strcpy(inputLine + 1, l + cmd_len);
+        break;
+    default:
+        if (key == KEY_UP)
+          process_command(mkcmdstr("roster up"), TRUE);
+        else
+          process_command(mkcmdstr("roster down"), TRUE);
+        break;
+  }
+}
+
 //  scr_process_key(key)
 // Handle the pressed key, in the command line (bottom).
 void scr_process_key(keycode kcode)
 {
   int key = kcode.value;
   int display_char = FALSE;
+  int vi_completion = FALSE;
+  static int ex_or_search_mode = FALSE;
 
   lock_chatstate = FALSE;
 
@@ -4448,6 +4493,253 @@
         key = ERR; // Do not process any further
   }
 
+  if (settings_opt_get_int("vi_mode") && !chatmode) {
+    int got_cmd_prefix = FALSE;
+    int unrecognized = FALSE;
+    static char search_cmd[INPUTLINE_LENGTH+1] = "/roster search ";
+
+    if (key == KEY_UP || key == KEY_DOWN) {
+      scr_process_vi_arrow_key(key);
+      key = ERR;    // Do not process any further
+    } else if (ex_or_search_mode) {
+      switch (key) {
+        case 27:    // Escape
+            clear_inputline();
+            ex_or_search_mode = FALSE;
+            break;
+        case 9:     // Tab
+        case 353:   // Shift-Tab
+            if (inputLine[0] == ':') {
+              inputLine[0] = '/';
+              vi_completion = TRUE;
+            }
+            break;
+        case 13:    // Enter
+        case 343:   // Enter on Maemo
+            switch (inputLine[0]) {
+              case ':':
+                  {
+                    char *p = strchr(inputLine, 0);
+
+                    while (*--p == ' ' && p > inputLine)
+                      *p = 0;
+                  }
+                  if (!strcmp(inputLine, ":x") ||
+                      !strcmp(inputLine, ":q") ||
+                      !strcmp(inputLine, ":wq"))
+                    strcpy(inputLine, ":quit");
+                  if (isdigit((int)(unsigned char)inputLine[1]) &&
+                      strlen(inputLine) <= 9) {
+                    process_command(mkcmdstr("roster top"), TRUE);
+                    memcpy(inputLine + 13, inputLine + 1, 10);
+                    memcpy(inputLine + 1, "roster down ", 12);
+                  }
+                  inputLine[0] = '/';
+                  process_command(inputLine, TRUE);
+                  scr_cmdhisto_addline(inputLine);
+                  break;
+              case '/':
+                  {
+                    size_t cmd_len = sizeof("/roster search ") - 1;
+                    size_t str_len = strlen(inputLine) - 1;
+
+                    if (cmd_len + str_len > INPUTLINE_LENGTH)
+                      return;
+
+                    memcpy(search_cmd + cmd_len, inputLine + 1,
+                           str_len + 1);
+                  }
+                  process_command(search_cmd, TRUE);
+                  scr_cmdhisto_addline(search_cmd);
+                  break;
+            }
+            ex_or_search_mode = FALSE;
+            break;
+      }
+    } else if (key >= '0' && key <= '9') {
+      got_cmd_prefix = TRUE;
+    } else {
+      switch (key) {
+        case '/':
+        case ':':
+            ex_or_search_mode = TRUE;
+            break;
+        case ' ':
+            process_command(mkcmdstr("group toggle"), TRUE);
+            break;
+        case '!':
+            {
+              const char *bjid = buddy_getjid(BUDDATA(current_buddy));
+
+              if (bjid) {
+                guint type = buddy_gettype(BUDDATA(current_buddy));
+                guint prio = buddy_getuiprio(BUDDATA(current_buddy));
+
+                if (type & ROSTER_TYPE_ROOM &&
+                    prio < ROSTER_UI_PRIO_MUC_HL_MESSAGE) {
+                  roster_setuiprio(bjid, FALSE,
+                                   ROSTER_UI_PRIO_MUC_HL_MESSAGE, prio_set);
+                  roster_msg_setflag(bjid, FALSE, TRUE);
+                } else if (type & ROSTER_TYPE_USER &&
+                           prio < ROSTER_UI_PRIO_ATTENTION_MESSAGE) {
+                  roster_setuiprio(bjid, FALSE,
+                                   ROSTER_UI_PRIO_ATTENTION_MESSAGE, prio_set);
+                  roster_msg_setflag(bjid, FALSE, TRUE);
+                } else {
+                  roster_msg_setflag(bjid, FALSE, FALSE);
+                }
+                scr_update_roster();
+              }
+            }
+            break;
+        case '#':
+            {
+              const char *bjid = buddy_getjid(BUDDATA(current_buddy));
+
+              if (bjid) {
+                unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
+
+                if (bflags & ROSTER_FLAG_MSG)
+                  roster_msg_setflag(bjid, FALSE, FALSE);
+                else
+                  roster_msg_setflag(bjid, FALSE, TRUE);
+
+                scr_update_roster();
+              }
+            }
+            break;
+        case '\'':
+            if (inputLine[0] == '\'')
+              process_command(mkcmdstr("roster alternate"), TRUE);
+            else
+              got_cmd_prefix = TRUE;
+            break;
+        case 'A':
+            process_command(mkcmdstr("roster unread_first"), TRUE);
+            break;
+        case 'a':
+            process_command(mkcmdstr("roster unread_next"), TRUE);
+            break;
+        case 'F':
+            process_command(mkcmdstr("roster group_prev"), TRUE);
+            break;
+        case 'f':
+            process_command(mkcmdstr("roster group_next"), TRUE);
+            break;
+        case 'G':
+            process_command(mkcmdstr("roster bottom"), TRUE);
+            break;
+        case 'g':
+            if (inputLine[0] == 'g')
+              process_command(mkcmdstr("roster top"), TRUE);
+            else {
+              clear_inputline();
+              got_cmd_prefix = TRUE;
+            }
+            break;
+        case 'i':
+            open_chat_window();
+            break;
+        case 'j':
+            if (isdigit((int)(unsigned char)inputLine[0]) &&
+                strlen(inputLine) <= 9) {
+              char down_cmd[32] = "/roster down ";
+
+              strcat(down_cmd, inputLine);
+              process_command(down_cmd, TRUE);
+            } else
+              process_command(mkcmdstr("roster down"), TRUE);
+            break;
+        case 'k':
+            if (isdigit((int)(unsigned char)inputLine[0]) &&
+                strlen(inputLine) <= 9) {
+              char up_cmd[32] = "/roster up ";
+
+              strcat(up_cmd, inputLine);
+              process_command(up_cmd, TRUE);
+            } else
+              process_command(mkcmdstr("roster up "), TRUE);
+            break;
+        case 'M':
+            if (inputLine[0] == 'z') {
+              GSList *groups = compl_list(ROSTER_TYPE_GROUP);
+              GSList *g;
+
+              for (g = groups; g; g = g_slist_next(g)) {
+                char fold_cmd[128] = "/group fold ";
+                size_t cmd_len = strlen(fold_cmd);
+                size_t grp_len = strlen(g->data);
+
+                if (cmd_len + grp_len + 1 > sizeof(fold_cmd))
+                  continue;
+                memcpy(fold_cmd + cmd_len, g->data, grp_len + 1);
+                process_command(fold_cmd, TRUE);
+                g_free(g->data);
+              }
+              g_slist_free(groups);
+            } else
+              unrecognized = TRUE;
+            break;
+        case 'n':
+            process_command(search_cmd, TRUE);
+            break;
+        case 'O':
+            process_command(mkcmdstr("roster unread_first"), TRUE);
+            open_chat_window();
+            break;
+        case 'o':
+            process_command(mkcmdstr("roster unread_next"), TRUE);
+            open_chat_window();
+            break;
+        case 'R':
+            if (inputLine[0] == 'z') {
+              GSList *groups = compl_list(ROSTER_TYPE_GROUP);
+              GSList *g;
+
+              for (g = groups; g; g = g_slist_next(g)) {
+                char fold_cmd[128] = "/group unfold ";
+                size_t cmd_len = strlen(fold_cmd);
+                size_t grp_len = strlen(g->data);
+
+                if (cmd_len + grp_len + 1 > sizeof(fold_cmd))
+                  continue;
+                memcpy(fold_cmd + cmd_len, g->data, grp_len + 1);
+                process_command(fold_cmd, TRUE);
+                g_free(g->data);
+              }
+              g_slist_free(groups);
+            } else
+              unrecognized = TRUE;
+            break;
+        case 'Z':
+            if (inputLine[0] == 'Z')
+              process_command(mkcmdstr("quit"), TRUE);
+            else {
+              clear_inputline();
+              got_cmd_prefix = TRUE;
+            }
+            break;
+        case 'z':
+            clear_inputline();
+            got_cmd_prefix = TRUE;
+            break;
+        case 13:    // Enter
+        case 343:   // Enter on Maemo
+            break;
+        default:
+            unrecognized = TRUE;
+            break;
+      }
+      cmdhisto_cur = NULL;
+    }
+    if (!ex_or_search_mode && !got_cmd_prefix) {
+      clear_inputline();
+      if (!unrecognized)
+        key = ERR;  // Do not process any further
+    }
+    lock_chatstate = TRUE;
+  }
+
   if (kcode.utf8) {
     if (key != ERR && !kcode.mcode)
       display_char = TRUE;
@@ -4513,8 +4805,13 @@
 
   if (completion_started && key != 9 && key != 353 && key != KEY_RESIZE)
     scr_end_current_completion();
+  else if (vi_completion)
+    inputLine[0] = ':';
   refresh_inputline();
 
+  if (ex_or_search_mode && inputLine[0] != ':' && inputLine[0] != '/')
+    ex_or_search_mode = FALSE;
+
   if (!lock_chatstate) {
     // Set chat state to composing (1) if the user is currently composing,
     // i.e. not an empty line and not a command line.
@@ -4693,4 +4990,19 @@
 }
 #endif
 
+static void open_chat_window(void)
+{
+  last_activity_buddy = current_buddy;
+  scr_check_auto_away(TRUE);
+  scr_set_chatmode(TRUE);
+  scr_show_buddy_window();
+}
+
+static void clear_inputline(void)
+{
+  ptr_inputline = inputLine;
+  *ptr_inputline = 0;
+  inputline_offset = 0;
+}
+
 /* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */