about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/command/command.c77
-rw-r--r--src/command/parser.c18
-rw-r--r--src/config/theme.c14
-rw-r--r--src/config/theme.h2
-rw-r--r--src/contact.c14
-rw-r--r--src/contact.h1
-rw-r--r--src/tools/autocomplete.c103
-rw-r--r--src/tools/history.c4
-rw-r--r--src/ui/console.c117
-rw-r--r--src/ui/inputwin.c4
-rw-r--r--src/xmpp/roster.c18
-rw-r--r--tests/test_history.c6
12 files changed, 248 insertions, 130 deletions
diff --git a/src/command/command.c b/src/command/command.c
index 50f7b08f..63d003f4 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -163,7 +163,7 @@ static struct cmd_t command_defs[] =
           "Example : /help presence",
           "Example : /help who",
           "",
-          "For more details help, see the user guide at http://www.profanity.im/userguide.html.",
+          "For more detailed help, see the user guide at http://www.profanity.im/userguide.html.",
           NULL } } },
 
     { "/about",
@@ -197,11 +197,11 @@ static struct cmd_t command_defs[] =
 
     { "/msg",
         _cmd_msg, parse_args_with_freetext, 1, 2, NULL,
-        { "/msg jid|nick [message]", "Start chat with user.",
-        { "/msg jid|nick [message]",
-          "-----------------------",
-          "Open a chat window with for the user JID (Jabber ID) and send the message if one is supplied.",
-          "When in a chat room, supply the nickname to start private chat with the room member.",
+        { "/msg contact|nick [message]", "Start chat with user.",
+        { "/msg contact|nick [message]",
+          "---------------------------",
+          "Open a chat window for the contact and send the message if one is supplied.",
+          "When in a chat room, supply a nickname to start private chat with a room member.",
           "Use quotes if the nickname includes spaces.",
           "",
           "Example : /msg myfriend@server.com Hey, here's a message!",
@@ -232,32 +232,32 @@ static struct cmd_t command_defs[] =
 
     { "/group",
         _cmd_group, parse_args_with_freetext, 0, 3, NULL,
-        { "/group show|add|remove [group] [contact]", "Manage roster groups.",
-        { "/group show|add|remove [group] [contact]",
-          "-------------------------------------",
+        { "/group [show|add|remove] [group] [contact]", "Manage roster groups.",
+        { "/group [show|add|remove] [group] [contact]",
+          "------------------------------------------",
           "View, add to, and remove from roster groups.",
           "Passing no argument will list all roster groups.",
           "The 'show' command takes 'group' as an argument, and lists all roster items in that group.",
-          "The 'add' command takes 'group' and 'contact' arguments, and add the contact to the group.",
-          "The 'remove' command takes 'group' and 'contact' arguments and removed the contact from the group,",
+          "The 'add' command takes 'group' and 'contact' arguments, and adds the contact to the group.",
+          "The 'remove' command takes 'group' and 'contact' arguments and removes the contact from the group,",
           "",
           "Example : /group",
           "Example : /group show friends",
           "Example : /group add friends newfriend@server.org",
-          "Example : /group add family brother (using contacts nickname)",
+          "Example : /group add family Brother (using contacts nickname)",
           "Example : /group remove colleagues boss@work.com",
           NULL } } },
 
     { "/info",
         _cmd_info, parse_args, 0, 1, NULL,
-        { "/info [jid|nick]", "Show basic information about a contact, or room member.",
-        { "/info [jid|nick]",
-          "----------------",
+        { "/info [contact|nick]", "Show basic information about a contact, or room member.",
+        { "/info [contact|nick]",
+          "--------------------",
           "Show information including current subscription status and summary information for each connected resource.",
           "If in a chat window the parameter is not required, the current recipient will be used.",
           "",
-          "Example : /info mybuddy@chat.server.org (contact)",
-          "Example : /info kai (room member)",
+          "Example : /info mybuddy@chat.server.org",
+          "Example : /info kai",
           NULL } } },
 
     { "/caps",
@@ -284,7 +284,7 @@ static struct cmd_t command_defs[] =
           "If in the console window or a regular chat window, a full JID is required.",
           "If in a chat room, the nickname is required.",
           "If in private chat, no parameter is required.",
-          "If the contacts software does not support software version requests, nothing will be displayed.",
+          "If the contact's software does not support software version requests, nothing will be displayed.",
           "",
           "Example : /software mybuddy@chat.server.org/laptop (contact's laptop resource)",
           "Example : /software mybuddy@chat.server.org/phone (contact's phone resource)",
@@ -293,14 +293,14 @@ static struct cmd_t command_defs[] =
 
     { "/status",
         _cmd_status, parse_args, 0, 1, NULL,
-        { "/status [jid|nick]", "Find out your contacts presence information.",
-        { "/status [jid|nick]",
-          "------------------",
+        { "/status [contact|nick]", "Find out a contacts presence information.",
+        { "/status [contact|nick]",
+          "----------------------",
           "Find out a contact, or room members presence information.",
           "If in a chat window the parameter is not required, the current recipient will be used.",
           "",
-          "Example : /status buddy@server.com (contact)",
-          "Example : /status jon (room member)",
+          "Example : /status buddy@server.com",
+          "Example : /status jon",
           NULL } } },
 
     { "/join",
@@ -311,7 +311,7 @@ static struct cmd_t command_defs[] =
           "Join a chat room at the conference server.",
           "If nick is specified you will join with this nickname.",
           "Otherwise the 'localpart' of your JID (before the @) will be used.",
-          "If no server is supplied, it will default to 'conference.<domain-part>'",
+          "If no server is supplied, a default of 'conference.<domain-part>' will be used.",
           "If the room doesn't exist, and the server allows it, a new one will be created.",
           "",
           "Example : /join jdev@conference.jabber.org",
@@ -329,11 +329,10 @@ static struct cmd_t command_defs[] =
 
     { "/invite",
         _cmd_invite, parse_args_with_freetext, 1, 2, NULL,
-        { "/invite jid [message]", "Invite contact to chat room.",
-        { "/invite jid [message]",
-          "--------------------------",
+        { "/invite contact [message]", "Invite contact to chat room.",
+        { "/invite contact [message]",
+          "-------------------------",
           "Send a direct invite to the specified contact to the current chat room.",
-          "The jid must be a contact in your roster.",
           "If a message is supplied it will be send as the reason for the invite.",
           NULL } } },
 
@@ -404,7 +403,7 @@ static struct cmd_t command_defs[] =
         { "/wins [tidy|prune]",
           "------------------",
           "Passing no argument will list all currently active windows and information about their usage.",
-          "tidy  : Shuffle windows so there are no gaps between used windows.",
+          "tidy  : Shuffle windows so there are no gaps.",
           "prune : Close all windows with no unread messages, and then tidy as above.",
           NULL } } },
 
@@ -438,7 +437,7 @@ static struct cmd_t command_defs[] =
           "---------",
           "Send the url as a tiny url.",
           "",
-          "Example : /tiny http://www.google.com",
+          "Example : /tiny http://www.profanity.im",
           NULL } } },
 
     { "/duck",
@@ -522,7 +521,6 @@ static struct cmd_t command_defs[] =
           "        : on|off",
           "invite  : Notifications for chat room invites.",
           "        : on|off",
-          "",
           "sub     : Notifications for subscription requests.",
           "        : on|off",
           "",
@@ -644,7 +642,7 @@ static struct cmd_t command_defs[] =
         { "/history on|off", "Chat history in message windows.",
         { "/history on|off",
           "---------------",
-          "Switch chat history on or off, requires /chlog will automatically be enabled when this setting in on.",
+          "Switch chat history on or off, /chlog will automatically be enabled when this setting is on.",
           "When history is enabled, previous messages are shown in chat windows.",
           NULL } } },
 
@@ -663,7 +661,7 @@ static struct cmd_t command_defs[] =
         { "/reconnect seconds",
           "------------------",
           "Set the reconnect attempt interval in seconds for when the connection is lost.",
-          "A value of 0 will switch of reconnect attempts.",
+          "A value of 0 will switch off reconnect attempts.",
           NULL } } },
 
     { "/autoping",
@@ -872,7 +870,7 @@ cmd_init(void)
     autocomplete_add(help_ac, strdup("chatting"));
     autocomplete_add(help_ac, strdup("groupchat"));
     autocomplete_add(help_ac, strdup("presence"));
-    autocomplete_add(help_ac, strdup("roster"));
+    autocomplete_add(help_ac, strdup("contacts"));
     autocomplete_add(help_ac, strdup("service"));
     autocomplete_add(help_ac, strdup("settings"));
     autocomplete_add(help_ac, strdup("other"));
@@ -1463,7 +1461,7 @@ _cmd_account(gchar **args, struct cmd_help_t help)
                     cons_show("");
                 } else if (strcmp(property, "status") == 0) {
                     if (!valid_resource_presence_string(value) && (strcmp(value, "last") != 0)) {
-                        cons_show("Invalud status: %s", value);
+                        cons_show("Invalid status: %s", value);
                     } else {
                         accounts_set_login_presence(account_name, value);
                         cons_show("Updated login status for account %s: %s", account_name, value);
@@ -1741,7 +1739,7 @@ _cmd_help(gchar **args, struct cmd_help_t help)
             "/xa" };
         _cmd_show_filtered_help("Presence commands", filter, ARRAY_SIZE(filter));
 
-    } else if (strcmp(args[0], "roster") == 0) {
+    } else if (strcmp(args[0], "contacts") == 0) {
         gchar *filter[] = { "/group", "/roster", "/sub", "/who" };
         _cmd_show_filtered_help("Roster commands", filter, ARRAY_SIZE(filter));
 
@@ -2690,7 +2688,7 @@ _cmd_join(gchar **args, struct cmd_help_t help)
     } else {
         g_string_append(room_str, args[0]);
         g_string_append(room_str, "@conference.");
-        g_string_append(room_str, strdup(my_jid->domainpart));
+        g_string_append(room_str, my_jid->domainpart);
         room = room_str->str;
     }
 
@@ -2711,6 +2709,7 @@ _cmd_join(gchar **args, struct cmd_help_t help)
     ui_room_join(room_jid);
     muc_remove_invite(room);
 
+    jid_destroy(room_arg);
     jid_destroy(room_jid);
     jid_destroy(my_jid);
     g_string_free(room_str, TRUE);
@@ -2788,7 +2787,7 @@ _cmd_rooms(gchar **args, struct cmd_help_t help)
     if (args[0] == NULL) {
         Jid *jid = jid_create(jabber_get_fulljid());
         GString *conference_node = g_string_new("conference.");
-        g_string_append(conference_node, strdup(jid->domainpart));
+        g_string_append(conference_node, jid->domainpart);
         jid_destroy(jid);
         iq_room_list_request(conference_node->str);
         g_string_free(conference_node, TRUE);
@@ -2814,7 +2813,7 @@ _cmd_disco(gchar **args, struct cmd_help_t help)
         jid = g_string_append(jid, args[1]);
     } else {
         Jid *jidp = jid_create(jabber_get_fulljid());
-        jid = g_string_append(jid, strdup(jidp->domainpart));
+        jid = g_string_append(jid, jidp->domainpart);
         jid_destroy(jidp);
     }
 
diff --git a/src/command/parser.c b/src/command/parser.c
index af01ddcc..d7dfebab 100644
--- a/src/command/parser.c
+++ b/src/command/parser.c
@@ -201,8 +201,14 @@ parse_args_with_freetext(const char * const inp, int min, int max)
                     in_quotes = TRUE;
                     i++;
                 }
-                token_start = &copy[i];
-                token_size++;
+                if (copy[i] == '"') {
+                    token_start = &copy[i+1];
+                } else {
+                    token_start = &copy[i];
+                }
+                if (copy[i] != '"') {
+                    token_size++;
+                }
             }
         } else {
             if (in_quotes) {
@@ -213,7 +219,9 @@ parse_args_with_freetext(const char * const inp, int min, int max)
                     in_token = FALSE;
                     in_quotes = FALSE;
                 } else {
-                    token_size++;
+                    if (copy[i] != '"') {
+                        token_size++;
+                    }
                 }
             } else {
                 if ((!in_freetext && copy[i] == ' ') || copy[i] == '\0') {
@@ -222,7 +230,9 @@ parse_args_with_freetext(const char * const inp, int min, int max)
                     token_size = 0;
                     in_token = FALSE;
                 } else {
-                    token_size++;
+                    if (copy[i] != '"') {
+                        token_size++;
+                    }
                 }
             }
         }
diff --git a/src/config/theme.c b/src/config/theme.c
index 5ca19969..1c0d176a 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -72,6 +72,8 @@ static struct colours_t {
         NCURSES_COLOR_T inputtext;
         NCURSES_COLOR_T timetext;
         NCURSES_COLOR_T splashtext;
+        NCURSES_COLOR_T subscribed;
+        NCURSES_COLOR_T unsubscribed;
         NCURSES_COLOR_T online;
         NCURSES_COLOR_T away;
         NCURSES_COLOR_T xa;
@@ -220,6 +222,10 @@ theme_init_colours(void)
     // states
     init_pair(60, colour_prefs.typing, colour_prefs.bkgnd);
     init_pair(61, colour_prefs.gone, colour_prefs.bkgnd);
+
+    // subscription status
+    init_pair(70, colour_prefs.subscribed, colour_prefs.bkgnd);
+    init_pair(71, colour_prefs.unsubscribed, colour_prefs.bkgnd);
 }
 
 static NCURSES_COLOR_T
@@ -306,6 +312,14 @@ _load_colours(void)
     _set_colour(timetext_val, &colour_prefs.timetext, COLOR_WHITE);
     g_free(timetext_val);
 
+    gchar *subscribed_val = g_key_file_get_string(theme, "colours", "subscribed", NULL);
+    _set_colour(subscribed_val, &colour_prefs.subscribed, COLOR_GREEN);
+    g_free(subscribed_val);
+
+    gchar *unsubscribed_val = g_key_file_get_string(theme, "colours", "unsubscribed", NULL);
+    _set_colour(unsubscribed_val, &colour_prefs.unsubscribed, COLOR_RED);
+    g_free(unsubscribed_val);
+
     gchar *online_val = g_key_file_get_string(theme, "colours", "online", NULL);
     _set_colour(online_val, &colour_prefs.online, COLOR_GREEN);
     g_free(online_val);
diff --git a/src/config/theme.h b/src/config/theme.h
index d87b69f2..8878d7b2 100644
--- a/src/config/theme.h
+++ b/src/config/theme.h
@@ -55,6 +55,8 @@
 #define COLOUR_XA               COLOR_PAIR(55)
 #define COLOUR_TYPING           COLOR_PAIR(60)
 #define COLOUR_GONE             COLOR_PAIR(61)
+#define COLOUR_SUBSCRIBED       COLOR_PAIR(70)
+#define COLOUR_UNSUBSCRIBED     COLOR_PAIR(71)
 
 void theme_init(const char * const theme_name);
 void theme_init_colours(void);
diff --git a/src/contact.c b/src/contact.c
index bab3d89c..44cbd4b4 100644
--- a/src/contact.c
+++ b/src/contact.c
@@ -252,6 +252,20 @@ p_contact_subscription(const PContact contact)
     return contact->subscription;
 }
 
+gboolean
+p_contact_subscribed(const PContact contact)
+{
+    if (contact->subscription == NULL) {
+        return FALSE;
+    } else if (strcmp(contact->subscription, "to") == 0) {
+        return TRUE;
+    } else if (strcmp(contact->subscription, "both") == 0) {
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
 Resource *
 p_contact_get_resource(const PContact contact, const char * const resource)
 {
diff --git a/src/contact.h b/src/contact.h
index cfd8b4d4..141587a0 100644
--- a/src/contact.h
+++ b/src/contact.h
@@ -53,5 +53,6 @@ Resource * p_contact_get_resource(const PContact contact, const char * const res
 void p_contact_set_groups(const PContact contact, GSList *groups);
 GSList * p_contact_groups(const PContact contact);
 gboolean p_contact_in_group(const PContact contact, const char * const group);
+gboolean p_contact_subscribed(const PContact contact);
 
 #endif
diff --git a/src/tools/autocomplete.c b/src/tools/autocomplete.c
index e3829f7f..849aaf1d 100644
--- a/src/tools/autocomplete.c
+++ b/src/tools/autocomplete.c
@@ -260,6 +260,80 @@ autocomplete_param_with_ac(char *input, int *size, char *command,
     return auto_msg;
 }
 
+int
+_count_tokens(char *string)
+{
+    int num_tokens = 0;
+
+    // if no quotes, use glib
+    if (g_strrstr(string, "\"") == NULL) {
+        gchar **tokens = g_strsplit(string, " ", 0);
+        num_tokens = g_strv_length(tokens);
+        g_strfreev(tokens);
+
+    // else count tokens including quoted
+    } else {
+        int length = strlen(string);
+        int i = 0;
+        gboolean in_quotes = FALSE;
+
+        // include first token
+        num_tokens++;
+
+        for (i = 0; i < length; i++) {
+            if (string[i] == ' ') {
+                if (!in_quotes) {
+                    num_tokens++;
+                }
+            } else if (string[i] == '"') {
+                if (in_quotes) {
+                    in_quotes = FALSE;
+                } else {
+                    in_quotes = TRUE;
+                }
+            }
+        }
+    }
+
+    return num_tokens;
+}
+
+char *
+_get_start(char *string, int tokens)
+{
+    char *result_str = NULL;
+    int num_tokens = 0;
+    int length = strlen(string);
+    int i = 0;
+    gboolean in_quotes = FALSE;
+    GString *result = g_string_new("");
+
+    // include first token
+    num_tokens++;
+
+    for (i = 0; i < length; i++) {
+        if (num_tokens < tokens) {
+            g_string_append_c(result, string[i]);
+        }
+        if (string[i] == ' ') {
+            if (!in_quotes) {
+                num_tokens++;
+            }
+        } else if (string[i] == '"') {
+            if (in_quotes) {
+                in_quotes = FALSE;
+            } else {
+                in_quotes = TRUE;
+            }
+        }
+    }
+
+    result_str = result->str;
+    g_string_free(result, FALSE);
+
+    return result_str;
+}
+
 char *
 autocomplete_param_no_with_func(char *input, int *size, char *command,
     int arg_number, autocomplete_func func)
@@ -267,44 +341,31 @@ autocomplete_param_no_with_func(char *input, int *size, char *command,
     char *result = NULL;
     if (strncmp(input, command, strlen(command)) == 0 && (*size > strlen(command))) {
         int i = 0;
-        int quote_count = 0;
         char *found = NULL;
         GString *result_str = NULL;
 
-        // copy and null terminate input, count quotes
+        // copy and null terminate input
         gchar inp_cpy[*size];
         for (i = 0; i < *size; i++) {
-            if (input[i] == '"') {
-                quote_count++;
-            }
             inp_cpy[i] = input[i];
         }
         inp_cpy[i] = '\0';
         g_strstrip(inp_cpy);
 
-        // count tokens
-        gchar **tokens = g_strsplit(inp_cpy, " ", 0);
-        int num_tokens = g_strv_length(tokens);
+        // count tokens properly
+        int num_tokens = _count_tokens(inp_cpy);
 
-        // if num tokens, or 2 quotes then candidate for autocompletion of last param
-        if (((num_tokens > arg_number - 1) && quote_count == 0) || quote_count == 2) {
-
-            gchar *comp_str = NULL;
-
-            // find start of autocompletion string
-            if (num_tokens > 3 && quote_count == 0) {
-                comp_str = g_strrstr(inp_cpy, tokens[arg_number - 1]);
-            } else {
-                comp_str = g_strrstr(inp_cpy, "\"");
-                comp_str = comp_str + 2;
-            }
+        // if correct number of tokens, then candidate for autocompletion of last param
+        if (num_tokens == arg_number) {
+            gchar *start_str = _get_start(inp_cpy, arg_number);
+            gchar *comp_str = g_strdup(&inp_cpy[strlen(start_str)]);
 
             // autocomplete param
             if (comp_str != NULL) {
                 found = func(comp_str);
                 if (found != NULL) {
                     result_str = g_string_new("");
-                    g_string_append(result_str, g_strndup(inp_cpy, strlen(inp_cpy) - strlen(comp_str)));
+                    g_string_append(result_str, start_str);
                     g_string_append(result_str, found);
                     result = result_str->str;
                     g_string_free(result_str, FALSE);
diff --git a/src/tools/history.c b/src/tools/history.c
index 5ac41969..b80a9555 100644
--- a/src/tools/history.c
+++ b/src/tools/history.c
@@ -146,6 +146,10 @@ history_next(History history, char *item)
         return NULL;
     }
 
+    if (g_list_next(history->session.sess_curr) == NULL) {
+        return NULL;
+    }
+
     char *copied = "";
     if (item != NULL) {
         copied = strdup(item);
diff --git a/src/ui/console.c b/src/ui/console.c
index 67f3fa28..ee5ad221 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -43,6 +43,7 @@
 static ProfWin* console;
 
 static void _cons_splash_logo(void);
+void _show_roster_contacts(GSList *list, gboolean show_groups);
 
 ProfWin *
 cons_create(void)
@@ -1307,17 +1308,9 @@ cons_navigation_help(void)
 }
 
 void
-cons_show_roster_group(const char * const group, GSList *list)
+_show_roster_contacts(GSList *list, gboolean show_groups)
 {
     GSList *curr = list;
-    cons_show("");
-
-    if (curr != NULL) {
-        cons_show("%s:", group);
-    } else {
-        cons_show("No group named %s exists.", group);
-    }
-
     while(curr) {
 
         PContact contact = curr->data;
@@ -1328,10 +1321,24 @@ cons_show_roster_group(const char * const group, GSList *list)
             title = g_string_append(title, strdup(p_contact_name(contact)));
             title = g_string_append(title, ")");
         }
-        cons_show(title->str);
+
+        const char *presence = p_contact_presence(contact);
+        win_print_time(console, '-');
+        if (p_contact_subscribed(contact)) {
+            win_presence_colour_on(console, presence);
+            wprintw(console->win, "%s\n", title->str);
+            win_presence_colour_off(console, presence);
+        } else {
+            win_presence_colour_on(console, "offline");
+            wprintw(console->win, "%s\n", title->str);
+            win_presence_colour_off(console, "offline");
+        }
+
         g_string_free(title, TRUE);
 
-        GString *sub = g_string_new("    Subscription : ");
+        win_print_time(console, '-');
+        wprintw(console->win, "    Subscription : ");
+        GString *sub = g_string_new("");
         sub = g_string_append(sub, p_contact_subscription(contact));
         if (p_contact_pending_out(contact)) {
             sub = g_string_append(sub, ", request sent");
@@ -1339,12 +1346,54 @@ cons_show_roster_group(const char * const group, GSList *list)
         if (presence_sub_request_exists(p_contact_barejid(contact))) {
             sub = g_string_append(sub, ", request received");
         }
-        cons_show(sub->str);
+        if (p_contact_subscribed(contact)) {
+            wattron(console->win, COLOUR_SUBSCRIBED);
+        } else {
+            wattron(console->win, COLOUR_UNSUBSCRIBED);
+        }
+        wprintw(console->win, "%s\n", sub->str);
+        if (p_contact_subscribed(contact)) {
+            wattroff(console->win, COLOUR_SUBSCRIBED);
+        } else {
+            wattroff(console->win, COLOUR_UNSUBSCRIBED);
+        }
+
         g_string_free(sub, TRUE);
 
+        if (show_groups) {
+            GSList *groups = p_contact_groups(contact);
+            if (groups != NULL) {
+                GString *groups_str = g_string_new("    Groups : ");
+                while (groups != NULL) {
+                    g_string_append(groups_str, strdup(groups->data));
+                    if (g_slist_next(groups) != NULL) {
+                        g_string_append(groups_str, ", ");
+                    }
+                    groups = g_slist_next(groups);
+                }
+
+                cons_show(groups_str->str);
+                g_string_free(groups_str, TRUE);
+            }
+        }
+
         curr = g_slist_next(curr);
     }
 
+}
+
+void
+cons_show_roster_group(const char * const group, GSList *list)
+{
+    cons_show("");
+
+    if (list != NULL) {
+        cons_show("%s:", group);
+    } else {
+        cons_show("No group named %s exists.", group);
+    }
+
+    _show_roster_contacts(list, FALSE);
     ui_console_dirty();
     cons_alert();
 }
@@ -1352,52 +1401,10 @@ cons_show_roster_group(const char * const group, GSList *list)
 void
 cons_show_roster(GSList *list)
 {
-    GSList *curr = list;
     cons_show("");
     cons_show("Roster:");
 
-    while(curr) {
-
-        PContact contact = curr->data;
-        GString *title = g_string_new("  ");
-        title = g_string_append(title, p_contact_barejid(contact));
-        if (p_contact_name(contact) != NULL) {
-            title = g_string_append(title, " (");
-            title = g_string_append(title, strdup(p_contact_name(contact)));
-            title = g_string_append(title, ")");
-        }
-        cons_show(title->str);
-        g_string_free(title, TRUE);
-
-        GString *sub = g_string_new("    Subscription : ");
-        sub = g_string_append(sub, p_contact_subscription(contact));
-        if (p_contact_pending_out(contact)) {
-            sub = g_string_append(sub, ", request sent");
-        }
-        if (presence_sub_request_exists(p_contact_barejid(contact))) {
-            sub = g_string_append(sub, ", request received");
-        }
-        cons_show(sub->str);
-        g_string_free(sub, TRUE);
-
-        GSList *groups = p_contact_groups(contact);
-        if (groups != NULL) {
-            GString *groups_str = g_string_new("    Groups : ");
-            while (groups != NULL) {
-                g_string_append(groups_str, strdup(groups->data));
-                if (g_slist_next(groups) != NULL) {
-                    g_string_append(groups_str, ", ");
-                }
-                groups = g_slist_next(groups);
-            }
-
-            cons_show(groups_str->str);
-            g_string_free(groups_str, TRUE);
-        }
-
-        curr = g_slist_next(curr);
-    }
-
+    _show_roster_contacts(list, TRUE);
     ui_console_dirty();
     cons_alert();
 }
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index 3f3d2ab5..feb4a0ba 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -490,6 +490,10 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
             next = cmd_history_next(input, size);
             if (next) {
                 inp_replace_input(input, next, size);
+            } else if (*size != 0) {
+                input[*size] = '\0';
+                cmd_history_append(input);
+                inp_replace_input(input, "", size);
             }
             return 1;
 
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index f96a3f84..ed72d502 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -562,14 +562,16 @@ _roster_handle_push(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 
         // remove each fulljid
         PContact contact = roster_get_contact(barejid);
-        GList *resources = p_contact_get_available_resources(contact);
-        while (resources != NULL) {
-            GString *fulljid = g_string_new(strdup(barejid));
-            g_string_append(fulljid, "/");
-            g_string_append(fulljid, strdup(resources->data));
-            autocomplete_remove(fulljid_ac, fulljid->str);
-            g_string_free(fulljid, TRUE);
-            resources = g_list_next(resources);
+        if (contact != NULL) {
+            GList *resources = p_contact_get_available_resources(contact);
+            while (resources != NULL) {
+                GString *fulljid = g_string_new(strdup(barejid));
+                g_string_append(fulljid, "/");
+                g_string_append(fulljid, strdup(resources->data));
+                autocomplete_remove(fulljid_ac, fulljid->str);
+                g_string_free(fulljid, TRUE);
+                resources = g_list_next(resources);
+            }
         }
 
         // remove the contact
diff --git a/tests/test_history.c b/tests/test_history.c
index 91e5aa99..6af96946 100644
--- a/tests/test_history.c
+++ b/tests/test_history.c
@@ -93,7 +93,7 @@ void prev_with_val_then_next_returns_val(void)
     assert_string_equals("Oioi", item2);
 }
 
-void prev_with_val_then_next_twice_returns_val(void)
+void prev_with_val_then_next_twice_returns_null(void)
 {
     History history = history_new(10);
     history_append(history, "Hello");
@@ -102,7 +102,7 @@ void prev_with_val_then_next_twice_returns_val(void)
     char *item2 = history_next(history, item1);
     char *item3 = history_next(history, item2);
 
-    assert_string_equals("Oioi", item3);
+    assert_is_null(item3);
 }
 
 void navigate_then_append_new(void)
@@ -225,7 +225,7 @@ void register_history_tests(void)
     TEST(previous_goes_to_correct_element);
     TEST(prev_then_next_returns_empty);
     TEST(prev_with_val_then_next_returns_val);
-    TEST(prev_with_val_then_next_twice_returns_val);
+    TEST(prev_with_val_then_next_twice_returns_null);
     TEST(navigate_then_append_new);
     TEST(edit_item_mid_history);
     TEST(edit_previous_and_append);