about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorMichael Vetter <jubalh@iodoru.org>2020-02-14 11:09:45 +0100
committerGitHub <noreply@github.com>2020-02-14 11:09:45 +0100
commitc995f3150e8582907c749968e2c0c8b1022a43c1 (patch)
treeb8c0dd70d0a260b289400473eee909b3fffc45ea
parent968006e1ddf97b5d9e8245c9dabbb0cfcfca2c3a (diff)
parent4241917fbaa7da4c108236eaec27fdfff84d72fe (diff)
downloadprofani-tty-c995f3150e8582907c749968e2c0c8b1022a43c1.tar.gz
Merge pull request #1267 from profanity-im/feature/xep-0308-lmc
XEP-0308 Last Message Correction
-rw-r--r--src/command/cmd_ac.c59
-rw-r--r--src/command/cmd_defs.c35
-rw-r--r--src/command/cmd_funcs.c79
-rw-r--r--src/command/cmd_funcs.h4
-rw-r--r--src/config/preferences.c29
-rw-r--r--src/config/preferences.h4
-rw-r--r--src/config/theme.c8
-rw-r--r--src/event/client_events.c123
-rw-r--r--src/event/client_events.h2
-rw-r--r--src/omemo/omemo.c6
-rw-r--r--src/omemo/omemo.h2
-rw-r--r--src/otr/otr.c16
-rw-r--r--src/otr/otr.h2
-rw-r--r--src/tools/parser.c4
-rw-r--r--src/ui/buffer.c7
-rw-r--r--src/ui/buffer.h7
-rw-r--r--src/ui/chatwin.c25
-rw-r--r--src/ui/console.c13
-rw-r--r--src/ui/mucwin.c23
-rw-r--r--src/ui/privwin.c2
-rw-r--r--src/ui/ui.h6
-rw-r--r--src/ui/win_types.h6
-rw-r--r--src/ui/window.c135
-rw-r--r--src/ui/window.h10
-rw-r--r--src/xmpp/capabilities.c7
-rw-r--r--src/xmpp/message.c87
-rw-r--r--src/xmpp/stanza.c12
-rw-r--r--src/xmpp/stanza.h2
-rw-r--r--src/xmpp/xmpp.h14
-rw-r--r--tests/unittests/otr/stub_otr.c2
-rw-r--r--tests/unittests/ui/stub_ui.c6
-rw-r--r--tests/unittests/xmpp/stub_xmpp.c9
-rw-r--r--theme_template1
-rw-r--r--themes/boothj51
34 files changed, 594 insertions, 154 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 5b61da6a..386e8939 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -113,6 +113,8 @@ static char* _status_autocomplete(ProfWin *window, const char *const input, gboo
 static char* _logging_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _color_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _avatar_autocomplete(ProfWin *window, const char *const input, gboolean previous);
+static char* _correction_autocomplete(ProfWin *window, const char *const input, gboolean previous);
+static char* _correct_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 
 static char* _script_autocomplete_func(const char *const prefix, gboolean previous, void *context);
 
@@ -234,6 +236,7 @@ static Autocomplete status_ac;
 static Autocomplete status_state_ac;
 static Autocomplete logging_ac;
 static Autocomplete color_ac;
+static Autocomplete correction_ac;
 
 void
 cmd_ac_init(void)
@@ -928,6 +931,11 @@ cmd_ac_init(void)
     autocomplete_add(color_ac, "off");
     autocomplete_add(color_ac, "redgreen");
     autocomplete_add(color_ac, "blue");
+
+    correction_ac = autocomplete_new();
+    autocomplete_add(correction_ac, "on");
+    autocomplete_add(correction_ac, "off");
+    autocomplete_add(correction_ac, "char");
 }
 
 void
@@ -1233,6 +1241,7 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(status_state_ac);
     autocomplete_reset(logging_ac);
     autocomplete_reset(color_ac);
+    autocomplete_reset(correction_ac);
 
     autocomplete_reset(script_ac);
     if (script_show_ac) {
@@ -1380,6 +1389,7 @@ cmd_ac_uninit(void)
     autocomplete_free(status_state_ac);
     autocomplete_free(logging_ac);
     autocomplete_free(color_ac);
+    autocomplete_free(correction_ac);
 }
 
 static void
@@ -1630,6 +1640,8 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
     g_hash_table_insert(ac_funcs, "/logging",       _logging_autocomplete);
     g_hash_table_insert(ac_funcs, "/color",         _color_autocomplete);
     g_hash_table_insert(ac_funcs, "/avatar",        _avatar_autocomplete);
+    g_hash_table_insert(ac_funcs, "/correction",    _correction_autocomplete);
+    g_hash_table_insert(ac_funcs, "/correct",       _correct_autocomplete);
 
     int len = strlen(input);
     char parsed[len+1];
@@ -3713,3 +3725,50 @@ _avatar_autocomplete(ProfWin *window, const char *const input, gboolean previous
 
     return NULL;
 }
+
+static char*
+_correction_autocomplete(ProfWin *window, const char *const input, gboolean previous)
+{
+    char *result = NULL;
+
+    result = autocomplete_param_with_ac(input, "/correction", correction_ac, TRUE, previous);
+    if (result) {
+        return result;
+    }
+
+    return NULL;
+}
+
+static char*
+_correct_autocomplete(ProfWin *window, const char *const input, gboolean previous)
+{
+	char *last_message = NULL;
+	switch(window->type) {
+	case WIN_CHAT:
+	{
+		ProfChatWin *chatwin = (ProfChatWin*)window;
+		assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+		last_message = chatwin->last_message;
+		break;
+	}
+	case WIN_MUC:
+	{
+		ProfMucWin *mucwin = (ProfMucWin*)window;
+		assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+		last_message = mucwin->last_message;
+	}
+	default:
+		break;
+	}
+
+	if (last_message == NULL) {
+		return NULL;
+	}
+
+	GString *result_str = g_string_new("/correct ");
+	g_string_append(result_str, last_message);
+	char *result = result_str->str;
+	g_string_free(result_str, FALSE);
+
+	return result;
+}
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index b583a06b..cc8f0a7a 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -2361,6 +2361,41 @@ static struct cmd_t command_defs[] =
             { "on|off", ""})
         CMD_NOEXAMPLES
     },
+
+    { "/correction",
+        parse_args, 1, 2, &cons_correction_setting,
+        CMD_NOSUBFUNCS
+        CMD_MAINFUNC(cmd_correction)
+        CMD_TAGS(
+            CMD_TAG_UI,
+            CMD_TAG_CHAT,
+            CMD_TAG_GROUPCHAT)
+        CMD_SYN(
+            "/correction <on>|<off>",
+            "/correction char <char>")
+        CMD_DESC(
+            "Settings regarding Last Message Correction (XEP-0308). Caution: We do not yet check the 'from' field. So it could happen that someone else is overwriting the actual message.")
+        CMD_ARGS(
+            { "on|off", "Enable/Disable support for last message correction."},
+            { "char",    "Set character that will prefix corrected messages. Default: +"})
+        CMD_NOEXAMPLES
+    },
+
+    { "/correct",
+        parse_args, 1, -1, NULL,
+        CMD_NOSUBFUNCS
+        CMD_MAINFUNC(cmd_correct)
+        CMD_TAGS(
+            CMD_TAG_CHAT,
+            CMD_TAG_GROUPCHAT)
+        CMD_SYN(
+            "/correct <message>")
+        CMD_DESC(
+            "Correct and resend the last message (XEP-0308).")
+        CMD_ARGS(
+            { "message",    "The corrected message."})
+        CMD_NOEXAMPLES
+    },
 };
 
 static GHashTable *search_index;
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 930004b3..40f5d8f4 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -7529,7 +7529,7 @@ cmd_otr_start(ProfWin *window, const char *const command, gchar **args)
 
         if (!otr_is_secure(barejid)) {
             char *otr_query_message = otr_start_query();
-            char *id = message_send_chat_otr(barejid, otr_query_message, FALSE);
+            char *id = message_send_chat_otr(barejid, otr_query_message, FALSE, NULL);
             free(id);
             return TRUE;
         }
@@ -7562,7 +7562,8 @@ cmd_otr_start(ProfWin *window, const char *const command, gchar **args)
         }
 
         char *otr_query_message = otr_start_query();
-        char *id = message_send_chat_otr(chatwin->barejid, otr_query_message, FALSE);
+        char *id = message_send_chat_otr(chatwin->barejid, otr_query_message, FALSE, NULL);
+
         free(id);
         return TRUE;
     }
@@ -8651,3 +8652,77 @@ cmd_os(ProfWin *window, const char *const command, gchar **args)
 
     return TRUE;
 }
+
+gboolean
+cmd_correction(ProfWin *window, const char *const command, gchar **args)
+{
+    // enable/disable
+    if (g_strcmp0(args[0], "on") == 0) {
+        _cmd_set_boolean_preference(args[0], command, "Last Message Correction", PREF_CORRECTION_ALLOW);
+        caps_add_feature(XMPP_FEATURE_LAST_MESSAGE_CORRECTION);
+        return TRUE;
+    } else if (g_strcmp0(args[0], "off") == 0) {
+        _cmd_set_boolean_preference(args[0], command, "Last Message Correction", PREF_CORRECTION_ALLOW);
+        caps_remove_feature(XMPP_FEATURE_LAST_MESSAGE_CORRECTION);
+        return TRUE;
+    }
+
+    // char
+    if (g_strcmp0(args[0], "char") == 0) {
+        if (args[1] == NULL) {
+            cons_bad_cmd_usage(command);
+        } else if (strlen(args[1]) != 1) {
+            cons_bad_cmd_usage(command);
+        } else {
+            prefs_set_correction_char(args[1][0]);
+            cons_show("LMC char set to %c.", args[1][0]);
+        }
+    }
+
+    return TRUE;
+}
+
+gboolean
+cmd_correct(ProfWin *window, const char *const command, gchar **args)
+{
+    jabber_conn_status_t conn_status = connection_get_status();
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are currently not connected.");
+        return TRUE;
+    }
+
+    if (window->type == WIN_CHAT) {
+        ProfChatWin *chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+
+        if (chatwin->last_msg_id == NULL || chatwin->last_message == NULL) {
+            win_println(window, THEME_DEFAULT, '!', "No last message to correct.");
+            return TRUE;
+        }
+
+        // send message again, with replace flag
+        gchar *message = g_strjoinv(" ", args);
+        cl_ev_send_msg_correct(chatwin, message, FALSE, TRUE);
+
+        free(message);
+        return TRUE;
+    } else if (window->type == WIN_MUC) {
+        ProfMucWin *mucwin = (ProfMucWin*)window;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+
+        if (mucwin->last_msg_id == NULL || mucwin->last_message == NULL) {
+            win_println(window, THEME_DEFAULT, '!', "No last message to correct.");
+            return TRUE;
+        }
+
+        // send message again, with replace flag
+        gchar *message = g_strjoinv(" ", args);
+        cl_ev_send_muc_msg_corrected(mucwin, message, FALSE, TRUE);
+
+        free(message);
+        return TRUE;
+    }
+
+    win_println(window, THEME_DEFAULT, '!', "Command /correct only valid in regular chat windows.");
+    return TRUE;
+}
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index a743951f..f283c910 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -54,7 +54,7 @@ typedef struct cmd_help_t {
  * cmd - The command string including leading '/'
  * parser - The function used to parse arguments
  * min_args - Minimum number of arguments
- * max_args - Maximum number of arguments
+ * max_args - Maximum number of arguments, -1 for infinite
  * setting_func - Function to display current settings to the console
  * sub_funcs - Optional list of functions mapped to the first argument
  * func - Main function to call when no arguments, or sub_funcs not implemented
@@ -227,4 +227,6 @@ gboolean cmd_paste(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_color(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_avatar(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_os(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_correction(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_correct(ProfWin *window, const char *const command, gchar **args);
 #endif
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 6cc2bb56..dea6a529 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -1201,6 +1201,32 @@ prefs_set_roster_presence_indent(gint value)
     g_key_file_set_integer(prefs, PREF_GROUP_UI, "roster.presence.indent", value);
 }
 
+char
+prefs_get_correction_char(void)
+{
+    char result = '+';
+
+    char *resultstr = g_key_file_get_string(prefs, PREF_GROUP_UI, "correction.char", NULL);
+    if (!resultstr) {
+        result =  '+';
+    } else {
+        result = resultstr[0];
+    }
+    free(resultstr);
+
+    return result;
+}
+
+void
+prefs_set_correction_char(char ch)
+{
+    char str[2];
+    str[0] = ch;
+    str[1] = '\0';
+
+    g_key_file_set_string(prefs, PREF_GROUP_UI, "correction.char", str);
+}
+
 gboolean
 prefs_add_room_notify_trigger(const char * const text)
 {
@@ -1760,6 +1786,7 @@ _get_group(preference_t pref)
         case PREF_RECEIPTS_REQUEST:
         case PREF_REVEAL_OS:
         case PREF_TLS_CERTPATH:
+        case PREF_CORRECTION_ALLOW:
             return PREF_GROUP_CONNECTION;
         case PREF_OTR_LOG:
         case PREF_OTR_POLICY:
@@ -2010,6 +2037,8 @@ _get_key(preference_t pref)
             return "log";
         case PREF_OMEMO_POLICY:
             return "policy";
+        case PREF_CORRECTION_ALLOW:
+            return "correction.allow";
         default:
             return NULL;
     }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index e11eec46..4c903272 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -161,6 +161,7 @@ typedef enum {
     PREF_OMEMO_LOG,
     PREF_OMEMO_POLICY,
     PREF_OCCUPANTS_WRAP,
+    PREF_CORRECTION_ALLOW,
 } preference_t;
 
 typedef struct prof_alias_t {
@@ -268,6 +269,9 @@ void prefs_set_roster_presence_indent(gint value);
 gint prefs_get_occupants_indent(void);
 void prefs_set_occupants_indent(gint value);
 
+char prefs_get_correction_char(void);
+void prefs_set_correction_char(char ch);
+
 void prefs_add_login(const char *jid);
 
 void prefs_set_tray_timer(gint value);
diff --git a/src/config/theme.c b/src/config/theme.c
index f081bd21..f7842bab 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -483,6 +483,14 @@ _load_preferences(void)
         }
     }
 
+    if (g_key_file_has_key(theme, "ui", "correction.char", NULL)) {
+        gchar *ch = g_key_file_get_string(theme, "ui", "correction.char", NULL);
+        if (ch && strlen(ch) > 0) {
+            prefs_set_omemo_char(ch[0]);
+            g_free(ch);
+        }
+    }
+
     // load window positions
     if (g_key_file_has_key(theme, "ui", "titlebar.position", NULL) &&
             g_key_file_has_key(theme, "ui", "mainwin.position", NULL) &&
diff --git a/src/event/client_events.c b/src/event/client_events.c
index 6716b7e2..e0ea85d7 100644
--- a/src/event/client_events.c
+++ b/src/event/client_events.c
@@ -115,45 +115,37 @@ cl_ev_presence_send(const resource_presence_t presence_type, const int idle_secs
 }
 
 void
-cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oob_url)
+cl_ev_send_msg_correct(ProfChatWin *chatwin, const char *const msg, const char *const oob_url, gboolean correct_last_msg)
 {
     chat_state_active(chatwin->state);
 
-    gboolean request_receipt = FALSE;
-    if (prefs_get_boolean(PREF_RECEIPTS_REQUEST)) {
-        char *session_jid = chat_session_get_jid(chatwin->barejid);
-        if (session_jid) {
-            Jid *session_jidp = jid_create(session_jid);
-            if (session_jidp && session_jidp->resourcepart) {
-                if (caps_jid_has_feature(session_jid, XMPP_FEATURE_RECEIPTS)) {
-                    request_receipt = TRUE;
-                }
-            }
-            jid_destroy(session_jidp);
-            free(session_jid);
-        }
-    }
+    gboolean request_receipt = prefs_get_boolean(PREF_RECEIPTS_REQUEST);
 
     char *plugin_msg = plugins_pre_chat_message_send(chatwin->barejid, msg);
     if (plugin_msg == NULL) {
         return;
     }
 
+    char *replace_id = NULL;
+    if (correct_last_msg) {
+        replace_id = chatwin->last_msg_id;
+    }
+
 // OTR suported, PGP supported, OMEMO unsupported
 #ifdef HAVE_LIBOTR
 #ifdef HAVE_LIBGPGME
 #ifndef HAVE_OMEMO
     if (chatwin->pgp_send) {
-        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
+        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt, replace_id);
         chat_log_pgp_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt, replace_id);
         free(id);
     } else {
-        gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt);
+        gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt, replace_id);
         if (!handled) {
-            char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+            char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
             chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
             free(id);
         }
     }
@@ -169,9 +161,9 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifdef HAVE_LIBOTR
 #ifndef HAVE_LIBGPGME
 #ifndef HAVE_OMEMO
-    gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt);
+    gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt, replace_id);
     if (!handled) {
-        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
         chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
         chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
         free(id);
@@ -189,14 +181,14 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifdef HAVE_LIBGPGME
 #ifndef HAVE_OMEMO
     if (chatwin->pgp_send) {
-        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
+        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt, replace_id);
         chat_log_pgp_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt, replace_id);
         free(id);
     } else {
-        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
         chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
         free(id);
     }
 
@@ -212,14 +204,14 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifndef HAVE_LIBGPGME
 #ifdef HAVE_OMEMO
     if (chatwin->is_omemo) {
-        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
+        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE, replace_id);
         chat_log_omemo_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt, replace_id);
         free(id);
     } else {
-        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
         chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
         free(id);
     }
 
@@ -235,16 +227,16 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifndef HAVE_LIBGPGME
 #ifdef HAVE_OMEMO
     if (chatwin->is_omemo) {
-        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
+        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE, replace_id);
         chat_log_omemo_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt, replace_id);
         free(id);
     } else {
-        gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt);
+        gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt, replace_id);
         if (!handled) {
-            char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+            char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
             chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
             free(id);
         }
     }
@@ -261,19 +253,19 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifdef HAVE_LIBGPGME
 #ifdef HAVE_OMEMO
     if (chatwin->is_omemo) {
-        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
+        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE, replace_id);
         chat_log_omemo_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt, replace_id);
         free(id);
     } else if (chatwin->pgp_send) {
-        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
+        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt, replace_id);
         chat_log_pgp_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt, replace_id);
         free(id);
     } else {
-        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+        char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
         chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
         free(id);
     }
 
@@ -289,21 +281,21 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifdef HAVE_LIBGPGME
 #ifdef HAVE_OMEMO
     if (chatwin->is_omemo) {
-        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
+        char *id = omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE, replace_id);
         chat_log_omemo_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt, replace_id);
         free(id);
     } else if (chatwin->pgp_send) {
-        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
+        char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt, replace_id);
         chat_log_pgp_msg_out(chatwin->barejid, plugin_msg, NULL);
-        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt);
+        chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PGP, request_receipt, replace_id);
         free(id);
     } else {
-        gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt);
+        gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt, replace_id);
         if (!handled) {
-            char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+            char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
             chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+            chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
             free(id);
         }
     }
@@ -319,9 +311,9 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 #ifndef HAVE_LIBOTR
 #ifndef HAVE_LIBGPGME
 #ifndef HAVE_OMEMO
-    char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
+    char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt, replace_id);
     chat_log_msg_out(chatwin->barejid, plugin_msg, NULL);
-    chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt);
+    chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
     free(id);
 
     plugins_post_chat_message_send(chatwin->barejid, plugin_msg);
@@ -333,23 +325,34 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
 }
 
 void
-cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const oob_url)
+cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oob_url)
+{
+    cl_ev_send_msg_correct(chatwin, msg, oob_url, FALSE);
+}
+
+void
+cl_ev_send_muc_msg_corrected(ProfMucWin *mucwin, const char *const msg, const char *const oob_url, gboolean correct_last_msg)
 {
     char *plugin_msg = plugins_pre_room_message_send(mucwin->roomjid, msg);
     if (plugin_msg == NULL) {
         return;
     }
 
+    char *replace_id = NULL;
+    if (correct_last_msg) {
+        replace_id = mucwin->last_msg_id;
+    }
+
 #ifdef HAVE_OMEMO
     if (mucwin->is_omemo) {
-        char *id = omemo_on_message_send((ProfWin *)mucwin, plugin_msg, FALSE, TRUE);
+        char *id = omemo_on_message_send((ProfWin *)mucwin, plugin_msg, FALSE, TRUE, replace_id);
         groupchat_log_omemo_msg_out(mucwin->roomjid, plugin_msg);
-        mucwin_outgoing_msg(mucwin, plugin_msg, id, PROF_MSG_ENC_OMEMO);
+        mucwin_outgoing_msg(mucwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, replace_id);
         free(id);
     } else {
-        char *id = message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url);
+        char *id = message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url, replace_id);
         groupchat_log_msg_out(mucwin->roomjid, plugin_msg);
-        mucwin_outgoing_msg(mucwin, plugin_msg, id, PROF_MSG_ENC_PLAIN);
+        mucwin_outgoing_msg(mucwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, replace_id);
         free(id);
     }
 
@@ -359,9 +362,9 @@ cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const
 #endif
 
 #ifndef HAVE_OMEMO
-    char *id = message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url);
+    char *id = message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url, replace_id);
     groupchat_log_msg_out(mucwin->roomjid, plugin_msg);
-    mucwin_outgoing_msg(mucwin, plugin_msg, id, PROF_MSG_ENC_PLAIN);
+    mucwin_outgoing_msg(mucwin, plugin_msg, id, PROF_MSG_ENC_PLAIN, replace_id);
     free(id);
 
     plugins_post_room_message_send(mucwin->roomjid, plugin_msg);
@@ -371,6 +374,12 @@ cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const
 }
 
 void
+cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const oob_url)
+{
+    cl_ev_send_muc_msg_corrected(mucwin, msg, oob_url, FALSE);
+}
+
+void
 cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg, const char *const oob_url)
 {
     if (privwin->occupant_offline) {
diff --git a/src/event/client_events.h b/src/event/client_events.h
index 82150f2e..87276331 100644
--- a/src/event/client_events.h
+++ b/src/event/client_events.h
@@ -45,7 +45,9 @@ void cl_ev_disconnect(void);
 
 void cl_ev_presence_send(const resource_presence_t presence_type, const int idle_secs);
 
+void cl_ev_send_msg_correct(ProfChatWin *chatwin, const char *const msg, const char *const oob_url, gboolean correct_last_msg);
 void cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oob_url);
+void cl_ev_send_muc_msg_corrected(ProfMucWin *mucwin, const char *const msg, const char *const oob_url, gboolean correct_last_msg);
 void cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const oob_url);
 void cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg, const char *const oob_url);
 
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index 0c981db1..e44a4d71 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -671,7 +671,7 @@ out:
 }
 
 char *
-omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_receipt, gboolean muc)
+omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_receipt, gboolean muc, const char *const replace_id)
 {
     char *id = NULL;
     int res;
@@ -809,11 +809,11 @@ omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_
     if (muc) {
         ProfMucWin *mucwin = (ProfMucWin *)win;
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
-        id = message_send_chat_omemo(mucwin->roomjid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt, TRUE);
+        id = message_send_chat_omemo(mucwin->roomjid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt, TRUE, replace_id);
     } else {
         ProfChatWin *chatwin = (ProfChatWin *)win;
         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
-        id = message_send_chat_omemo(chatwin->barejid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt, FALSE);
+        id = message_send_chat_omemo(chatwin->barejid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt, FALSE, replace_id);
     }
 
 out:
diff --git a/src/omemo/omemo.h b/src/omemo/omemo.h
index dfd23fd8..740654a2 100644
--- a/src/omemo/omemo.h
+++ b/src/omemo/omemo.h
@@ -92,5 +92,5 @@ void omemo_start_muc_sessions(const char *const roomjid);
 void omemo_start_device_session(const char *const jid, uint32_t device_id, GList *prekeys, uint32_t signed_prekey_id, const unsigned char *const signed_prekey, size_t signed_prekey_len, const unsigned char *const signature, size_t signature_len, const unsigned char *const identity_key, size_t identity_key_len);
 
 gboolean omemo_loaded(void);
-char * omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_receipt, gboolean muc);
+char * omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_receipt, gboolean muc, const char *const replace_id);
 char * omemo_on_message_recv(const char *const from, uint32_t sid, const unsigned char *const iv, size_t iv_len, GList *keys, const unsigned char *const payload, size_t payload_len, gboolean muc, gboolean *trusted);
diff --git a/src/otr/otr.c b/src/otr/otr.c
index 40684857..5ce34758 100644
--- a/src/otr/otr.c
+++ b/src/otr/otr.c
@@ -118,7 +118,7 @@ static void
 cb_inject_message(void *opdata, const char *accountname,
     const char *protocol, const char *recipient, const char *message)
 {
-    char *id = message_send_chat_otr(recipient, message, FALSE);
+    char *id = message_send_chat_otr(recipient, message, FALSE, NULL);
     free(id);
 }
 
@@ -315,7 +315,7 @@ otr_on_message_recv(const char *const barejid, const char *const resource, const
                 memmove(whitespace_base, whitespace_base+tag_length, tag_length);
                 char *otr_query_message = otr_start_query();
                 cons_show("OTR Whitespace pattern detected. Attempting to start OTR session...");
-                char *id = message_send_chat_otr(barejid, otr_query_message, FALSE);
+                char *id = message_send_chat_otr(barejid, otr_query_message, FALSE, NULL);
                 free(id);
             }
         }
@@ -329,7 +329,7 @@ otr_on_message_recv(const char *const barejid, const char *const resource, const
     if (policy == PROF_OTRPOLICY_ALWAYS && *decrypted == FALSE && !whitespace_base) {
         char *otr_query_message = otr_start_query();
         cons_show("Attempting to start OTR session...");
-        char *id = message_send_chat_otr(barejid, otr_query_message, FALSE);
+        char *id = message_send_chat_otr(barejid, otr_query_message, FALSE, NULL);
         free(id);
     }
 
@@ -337,7 +337,7 @@ otr_on_message_recv(const char *const barejid, const char *const resource, const
 }
 
 gboolean
-otr_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean request_receipt)
+otr_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean request_receipt, const char *const replace_id)
 {
     char *id = NULL;
     prof_otrpolicy_t policy = otr_get_policy(chatwin->barejid);
@@ -346,9 +346,9 @@ otr_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean re
     if (otr_is_secure(chatwin->barejid)) {
         char *encrypted = otr_encrypt_message(chatwin->barejid, message);
         if (encrypted) {
-            id = message_send_chat_otr(chatwin->barejid, encrypted, request_receipt);
+            id = message_send_chat_otr(chatwin->barejid, encrypted, request_receipt, replace_id);
             chat_log_otr_msg_out(chatwin->barejid, message, NULL);
-            chatwin_outgoing_msg(chatwin, message, id, PROF_MSG_ENC_OTR, request_receipt);
+            chatwin_outgoing_msg(chatwin, message, id, PROF_MSG_ENC_OTR, request_receipt, replace_id);
             otr_free_message(encrypted);
             free(id);
             return TRUE;
@@ -367,8 +367,8 @@ otr_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean re
     // tag and send for policy opportunistic
     if (policy == PROF_OTRPOLICY_OPPORTUNISTIC) {
         char *otr_tagged_msg = otr_tag_message(message);
-        id = message_send_chat_otr(chatwin->barejid, otr_tagged_msg, request_receipt);
-        chatwin_outgoing_msg(chatwin, message, id, PROF_MSG_ENC_PLAIN, request_receipt);
+        id = message_send_chat_otr(chatwin->barejid, otr_tagged_msg, request_receipt, replace_id);
+        chatwin_outgoing_msg(chatwin, message, id, PROF_MSG_ENC_PLAIN, request_receipt, replace_id);
         chat_log_msg_out(chatwin->barejid, message, NULL);
         free(otr_tagged_msg);
         free(id);
diff --git a/src/otr/otr.h b/src/otr/otr.h
index 5b9a09d3..58b6decf 100644
--- a/src/otr/otr.h
+++ b/src/otr/otr.h
@@ -73,7 +73,7 @@ void otr_poll(void);
 void otr_on_connect(ProfAccount *account);
 
 char* otr_on_message_recv(const char *const barejid, const char *const resource, const char *const message, gboolean *decrypted);
-gboolean otr_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean request_receipt);
+gboolean otr_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean request_receipt, const char *const replace_id);
 
 void otr_keygen(ProfAccount *account);
 
diff --git a/src/tools/parser.c b/src/tools/parser.c
index aa739330..fb21571c 100644
--- a/src/tools/parser.c
+++ b/src/tools/parser.c
@@ -48,7 +48,7 @@
  *
  * inp - The line of input
  * min - The minimum allowed number of arguments
- * max - The maximum allowed number of arguments
+ * max - The maximum allowed number of arguments, -1 for infinite
  *
  * Returns - An NULL terminated array of strings representing the arguments
  * of the command, or NULL if the validation fails.
@@ -135,7 +135,7 @@ parse_args(const char *const inp, int min, int max, gboolean *result)
     int num = g_slist_length(tokens) - 1;
 
     // if num args not valid return NULL
-    if ((num < min) || (num > max)) {
+    if ((num < min) || ((max != -1) && (num > max))) {
         g_slist_free_full(tokens, free);
         g_free(copy);
         *result = FALSE;
diff --git a/src/ui/buffer.c b/src/ui/buffer.c
index 916aba1a..86f87563 100644
--- a/src/ui/buffer.c
+++ b/src/ui/buffer.c
@@ -3,6 +3,7 @@
  * vim: expandtab:ts=4:sts=4:sw=4
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 - 2020 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -81,7 +82,7 @@ buffer_free(ProfBuff buffer)
 
 void
 buffer_append(ProfBuff buffer, const char show_char, int pad_indent, GDateTime *time,
-    int flags, theme_item_t theme_item, const char *const from, const char *const message, DeliveryReceipt *receipt, const char *const id)
+    int flags, theme_item_t theme_item, const char *const display_from, const char *const message, DeliveryReceipt *receipt, const char *const id)
 {
     ProfBuffEntry *e = malloc(sizeof(struct prof_buff_entry_t));
     e->show_char = show_char;
@@ -89,7 +90,7 @@ buffer_append(ProfBuff buffer, const char show_char, int pad_indent, GDateTime *
     e->flags = flags;
     e->theme_item = theme_item;
     e->time = g_date_time_ref(time);
-    e->from = from ? strdup(from) : NULL;
+    e->display_from = display_from ? strdup(display_from) : NULL;
     e->message = strdup(message);
     e->receipt = receipt;
     if (id) {
@@ -163,7 +164,7 @@ static void
 _free_entry(ProfBuffEntry *entry)
 {
     free(entry->message);
-    free(entry->from);
+    free(entry->display_from);
     free(entry->id);
     free(entry->receipt);
     g_date_time_unref(entry->time);
diff --git a/src/ui/buffer.h b/src/ui/buffer.h
index 10151d17..813ac38e 100644
--- a/src/ui/buffer.h
+++ b/src/ui/buffer.h
@@ -3,6 +3,7 @@
  * vim: expandtab:ts=4:sts=4:sw=4
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 - 2020 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -51,7 +52,9 @@ typedef struct prof_buff_entry_t {
     GDateTime *time;
     int flags;
     theme_item_t theme_item;
-    char *from;
+    // from as it is displayed
+    // might be nick, jid..
+    char *display_from;
     char *message;
     DeliveryReceipt *receipt;
     // message id, in case we have it
@@ -63,7 +66,7 @@ typedef struct prof_buff_t *ProfBuff;
 ProfBuff buffer_create();
 void buffer_free(ProfBuff buffer);
 void buffer_append(ProfBuff buffer, const char show_char, int pad_indent, GDateTime *time,
-    int flags, theme_item_t theme_item, const char *const from, const char *const message, DeliveryReceipt *receipt, const char *const id);
+    int flags, theme_item_t theme_item, const char *const display_from, const char *const message, DeliveryReceipt *receipt, const char *const id);
 void buffer_remove_entry_by_id(ProfBuff buffer, const char *const id);
 int buffer_size(ProfBuff buffer);
 ProfBuffEntry* buffer_get_entry(ProfBuff buffer, int entry);
diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c
index 8faf4934..4b0fd889 100644
--- a/src/ui/chatwin.c
+++ b/src/ui/chatwin.c
@@ -3,6 +3,7 @@
  * vim: expandtab:ts=4:sts=4:sw=4
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 - 2020 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -56,6 +57,7 @@
 #endif
 
 static void _chatwin_history(ProfChatWin *chatwin, const char *const contact);
+static void _chatwin_set_last_message(ProfChatWin *chatwin, const char *const id, const char *const message);
 
 ProfChatWin*
 chatwin_new(const char *const barejid)
@@ -308,7 +310,7 @@ chatwin_incoming_msg(ProfChatWin *chatwin, ProfMessage *message, gboolean win_cr
 
 void
 chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id, prof_enc_t enc_mode,
-    gboolean request_receipt)
+    gboolean request_receipt, const char *const replace_id)
 {
     assert(chatwin != NULL);
 
@@ -324,9 +326,14 @@ chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id,
     }
 
     if (request_receipt && id) {
-        win_print_with_receipt((ProfWin*)chatwin, enc_char, "me", message, id);
+        win_print_outgoing_with_receipt((ProfWin*)chatwin, enc_char, "me", message, id, replace_id);
     } else {
-        win_print_outgoing((ProfWin*)chatwin, enc_char, "%s", message);
+        win_print_outgoing((ProfWin*)chatwin, enc_char, id, replace_id, "%s", message);
+    }
+
+    // save last id and message for LMC
+    if (id) {
+        _chatwin_set_last_message(chatwin, id, message);
     }
 }
 
@@ -344,7 +351,7 @@ chatwin_outgoing_carbon(ProfChatWin *chatwin, ProfMessage *message)
 
     ProfWin *window = (ProfWin*)chatwin;
 
-    win_print_outgoing(window, enc_char, "%s", message->plain);
+    win_print_outgoing(window, enc_char, message->id, message->replace_id, "%s", message->plain);
     int num = wins_get_num(window);
     status_bar_active(num, WIN_CHAT, chatwin->barejid);
 }
@@ -496,3 +503,13 @@ _chatwin_history(ProfChatWin *chatwin, const char *const contact)
         g_slist_free_full(history, free);
     }
 }
+
+static void
+_chatwin_set_last_message(ProfChatWin *chatwin, const char *const id, const char *const message)
+{
+    free(chatwin->last_message);
+    chatwin->last_message = strdup(message);
+
+    free(chatwin->last_msg_id);
+    chatwin->last_msg_id = strdup(id);
+}
diff --git a/src/ui/console.c b/src/ui/console.c
index 1a716a7a..f7fa448d 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -2022,6 +2022,19 @@ cons_os_setting(void)
 }
 
 void
+cons_correction_setting(void)
+{
+    if (prefs_get_boolean(PREF_CORRECTION_ALLOW)) {
+        cons_show("Last Message Correction (XEP-0308) (/correction)                   : ON");
+    } else {
+        cons_show("Last Message Correction (XEP-0308) (/correction)                   : OFF");
+    }
+
+    char cc = prefs_get_correction_char();
+    cons_show("LMC indication char (/correction char)                             : %c", cc);
+}
+
+void
 cons_show_connection_prefs(void)
 {
     cons_show("Connection preferences:");
diff --git a/src/ui/mucwin.c b/src/ui/mucwin.c
index 0179c22b..28c3beea 100644
--- a/src/ui/mucwin.c
+++ b/src/ui/mucwin.c
@@ -50,6 +50,8 @@
 #include "omemo/omemo.h"
 #endif
 
+static void _mucwin_set_last_message(ProfMucWin *mucwin, const char *const id, const char *const message);
+
 ProfMucWin*
 mucwin_new(const char *const barejid)
 {
@@ -501,7 +503,7 @@ _mucwin_print_triggers(ProfWin *window, const char *const message, GList *trigge
 }
 
 void
-mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode)
+mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode, const char *const replace_id)
 {
     assert(mucwin != NULL);
 
@@ -519,7 +521,12 @@ mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *c
         ch = prefs_get_omemo_char();
     }
 
-    win_println_me_message(window, ch, mynick, "%s", message);
+    win_print_outgoing_muc_msg(window, ch, mynick, id, replace_id, "%s", message);
+
+    // save last id and message for LMC
+    if (id) {
+        _mucwin_set_last_message(mucwin, id, message);
+    }
 }
 
 void
@@ -559,7 +566,7 @@ mucwin_incoming_msg(ProfMucWin *mucwin, ProfMessage *message, GSList *mentions,
         win_print_them(window, THEME_ROOMTRIGGER, ch, flags, message->jid->resourcepart);
         _mucwin_print_triggers(window, message->plain, triggers);
     } else {
-        win_println_them_message(window, ch, flags, message->jid->resourcepart, "%s", message->plain);
+        win_println_incoming_muc_msg(window, ch, flags, message->jid->resourcepart, message->id, message->replace_id, "%s", message->plain);
     }
 }
 
@@ -951,3 +958,13 @@ mucwin_unset_message_char(ProfMucWin *mucwin)
         mucwin->message_char = NULL;
     }
 }
+
+static void
+_mucwin_set_last_message(ProfMucWin *mucwin, const char *const id, const char *const message)
+{
+    free(mucwin->last_message);
+    mucwin->last_message = strdup(message);
+
+    free(mucwin->last_msg_id);
+    mucwin->last_msg_id = strdup(id);
+}
diff --git a/src/ui/privwin.c b/src/ui/privwin.c
index 4befc325..8fc1bcc7 100644
--- a/src/ui/privwin.c
+++ b/src/ui/privwin.c
@@ -95,7 +95,7 @@ privwin_outgoing_msg(ProfPrivateWin *privwin, const char *const message)
 {
     assert(privwin != NULL);
 
-    win_print_outgoing((ProfWin*)privwin, '-', "%s", message);
+    win_print_outgoing((ProfWin*)privwin, '-', NULL, NULL ,"%s", message);
 }
 
 void
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 4783131e..77bdc1d5 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -124,8 +124,7 @@ ProfChatWin* chatwin_new(const char *const barejid);
 void chatwin_incoming_msg(ProfChatWin *chatwin, ProfMessage *message, gboolean win_created);
 void chatwin_receipt_received(ProfChatWin *chatwin, const char *const id);
 void chatwin_recipient_gone(ProfChatWin *chatwin);
-void chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id, prof_enc_t enc_mode,
-    gboolean request_receipt);
+void chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id, prof_enc_t enc_mode, gboolean request_receipt, const char *const replace_id);
 void chatwin_outgoing_carbon(ProfChatWin *chatwin, ProfMessage *message);
 void chatwin_contact_online(ProfChatWin *chatwin, Resource *resource, GDateTime *last_activity);
 void chatwin_contact_offline(ProfChatWin *chatwin, char *resource, char *status);
@@ -159,7 +158,7 @@ void mucwin_occupant_role_and_affiliation_change(ProfMucWin *mucwin, const char
     const char *const role, const char *const affiliation, const char *const actor, const char *const reason);
 void mucwin_roster(ProfMucWin *mucwin, GList *occupants, const char *const presence);
 void mucwin_history(ProfMucWin *mucwin, const char *const nick, GDateTime *timestamp, const char *const message);
-void mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode);
+void mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode, const char *const replace_id);
 void mucwin_incoming_msg(ProfMucWin *mucwin, ProfMessage *message, GSList *mentions, GList *triggers);
 void mucwin_subject(ProfMucWin *mucwin, const char *const nick, const char *const subject);
 void mucwin_requires_config(ProfMucWin *mucwin);
@@ -319,6 +318,7 @@ void cons_statusbar_setting(void);
 void cons_winpos_setting(void);
 void cons_color_setting(void);
 void cons_os_setting(void);
+void cons_correction_setting(void);
 void cons_show_contact_online(PContact contact, Resource *resource, GDateTime *last_activity);
 void cons_show_contact_offline(PContact contact, char *resource, char *status);
 void cons_theme_properties(void);
diff --git a/src/ui/win_types.h b/src/ui/win_types.h
index 68ed719e..81944bc0 100644
--- a/src/ui/win_types.h
+++ b/src/ui/win_types.h
@@ -160,6 +160,9 @@ typedef struct prof_chat_win_t {
     char *enctext;
     char *incoming_char;
     char *outgoing_char;
+    // For LMC
+    char *last_message;
+    char *last_msg_id;
 } ProfChatWin;
 
 typedef struct prof_muc_win_t {
@@ -175,6 +178,9 @@ typedef struct prof_muc_win_t {
     char *enctext;
     char *message_char;
     GDateTime *last_msg_timestamp;
+    // For LMC
+    char *last_message;
+    char *last_msg_id;
 } ProfMucWin;
 
 typedef struct prof_conf_win_t ProfConfWin;
diff --git a/src/ui/window.c b/src/ui/window.c
index c7bb359a..a1c71241 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -3,6 +3,7 @@
  * vim: expandtab:ts=4:sts=4:sw=4
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 - 2020 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -49,6 +50,7 @@
 #include <ncurses.h>
 #endif
 
+#include "log.h"
 #include "config/theme.h"
 #include "config/preferences.h"
 #include "ui/ui.h"
@@ -62,9 +64,8 @@
 
 #define CEILING(X) (X-(int)(X) > 0 ? (int)(X+1) : (int)(X))
 
-static void _win_printf(ProfWin *window, const char show_char, int pad_indent, GDateTime *timestamp,
-    int flags, theme_item_t theme_item, const char *const from, const char *const message, ...);
-static void _win_print(ProfWin *window, const char show_char, int pad_indent, GDateTime *time,
+static void _win_printf(ProfWin *window, const char show_char, int pad_indent, GDateTime *timestamp, int flags, theme_item_t theme_item, const char *const from, const char *const message_id, const char *const message, ...);
+static void _win_print_internal(ProfWin *window, const char show_char, int pad_indent, GDateTime *time,
     int flags, theme_item_t theme_item, const char *const from, const char *const message, DeliveryReceipt *receipt);
 static void _win_print_wrapped(WINDOW *win, const char *const message, size_t indent, int pad_indent);
 
@@ -151,6 +152,8 @@ win_create_chat(const char *const barejid)
     new_win->enctext = NULL;
     new_win->incoming_char = NULL;
     new_win->outgoing_char = NULL;
+    new_win->last_message = NULL;
+    new_win->last_msg_id = NULL;
 
     new_win->memcheck = PROFCHATWIN_MEMCHECK;
 
@@ -200,6 +203,8 @@ win_create_muc(const char *const roomjid)
     new_win->enctext = NULL;
     new_win->message_char = NULL;
     new_win->is_omemo = FALSE;
+    new_win->last_message = NULL;
+    new_win->last_msg_id = NULL;
 
     new_win->memcheck = PROFMUCWIN_MEMCHECK;
 
@@ -487,6 +492,8 @@ win_free(ProfWin* window)
         free(chatwin->enctext);
         free(chatwin->incoming_char);
         free(chatwin->outgoing_char);
+        free(chatwin->last_message);
+        free(chatwin->last_msg_id);
         chat_state_free(chatwin->state);
         break;
     }
@@ -1055,8 +1062,43 @@ win_show_status_string(ProfWin *window, const char *const from,
     win_appendln(window, presence_colour, "");
 }
 
+static void
+_win_correct(ProfWin *window, const char *const message, const char *const id, const char *const replace_id)
+{
+    ProfBuffEntry *entry = buffer_get_entry_by_id(window->layout->buffer, replace_id);
+    if (!entry) {
+        log_debug("Replace ID %s could not be found in buffer. Message: %s", replace_id, message);
+        return;
+    }
+
+    /*TODO: set date?
+    if (entry->date) {
+        if (entry->date->timestamp) {
+            g_date_time_unref(entry->date->timestamp);
+        }
+        free(entry->date);
+    }
+
+    entry->date = buffer_date_new_now();
+    */
+
+    entry->show_char = prefs_get_correction_char();
+
+    if (entry->message) {
+        free(entry->message);
+    }
+    entry->message = strdup(message);
+
+    if (entry->id) {
+        free(entry->id);
+    }
+    entry->id = strdup(id);
+
+    win_redraw(window);
+}
+
 void
-win_print_incoming(ProfWin *window, const char *const from, ProfMessage *message)
+win_print_incoming(ProfWin *window, const char *const display_name_from, ProfMessage *message)
 {
     char enc_char = '-';
     int flags = NO_ME;
@@ -1079,11 +1121,16 @@ win_print_incoming(ProfWin *window, const char *const from, ProfMessage *message
             } else if (message->enc == PROF_MSG_ENC_OMEMO) {
                 enc_char = prefs_get_omemo_char();
             }
-            _win_printf(window, enc_char, 0, message->timestamp, flags, THEME_TEXT_THEM, from, "%s", message->plain);
+
+            if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && message->replace_id) {
+                _win_correct(window, message->plain, message->id, message->replace_id);
+            } else {
+                _win_printf(window, enc_char, 0, message->timestamp, flags, THEME_TEXT_THEM, display_name_from, message->id, "%s", message->plain);
+            }
             break;
         }
         case WIN_PRIVATE:
-            _win_printf(window, '-', 0, message->timestamp, flags, THEME_TEXT_THEM, from, "%s", message->plain);
+            _win_printf(window, '-', 0, message->timestamp, flags, THEME_TEXT_THEM, display_name_from, message->id, "%s", message->plain);
             break;
         default:
             assert(FALSE);
@@ -1094,12 +1141,13 @@ win_print_incoming(ProfWin *window, const char *const from, ProfMessage *message
 void
 win_print_them(ProfWin *window, theme_item_t theme_item, char ch, int flags, const char *const them)
 {
-    _win_printf(window, ch, 0, NULL, flags | NO_ME | NO_EOL, theme_item, them, "");
+    _win_printf(window, ch, 0, NULL, flags | NO_ME | NO_EOL, theme_item, them, NULL, "");
 }
 
 void
-win_println_them_message(ProfWin *window, char ch, int flags, const char *const them, const char *const message, ...)
+win_println_incoming_muc_msg(ProfWin *window, char ch, int flags, const char *const them, const char *const id, const char *const replace_id, const char *const message, ...)
 {
+    //TODO: we always use current timestamp here. instead of the message->timestamp one if available. i think somewhere else we check whether it exists first.
     GDateTime *timestamp = g_date_time_new_now_local();
 
     va_list arg;
@@ -1107,9 +1155,14 @@ win_println_them_message(ProfWin *window, char ch, int flags, const char *const
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, message, arg);
 
-    buffer_append(window->layout->buffer, ch, 0, timestamp, flags | NO_ME, THEME_TEXT_THEM, them, fmt_msg->str, NULL, NULL);
+    if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && replace_id) {
+        _win_correct(window, fmt_msg->str, id, replace_id);
+    } else {
+        _win_printf(window, ch, 0, timestamp, flags | NO_ME, THEME_TEXT_THEM, them, id, "%s", fmt_msg->str);
+    }
+//    buffer_append(window->layout->buffer, ch, 0, timestamp, flags | NO_ME, THEME_TEXT_THEM, them, fmt_msg->str, NULL, NULL);
+ //   _win_print_internal(window, ch, 0, timestamp, flags | NO_ME, THEME_TEXT_THEM, them, fmt_msg->str, NULL);
 
-    _win_print(window, ch, 0, timestamp, flags | NO_ME, THEME_TEXT_THEM, them, fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1118,7 +1171,7 @@ win_println_them_message(ProfWin *window, char ch, int flags, const char *const
 }
 
 void
-win_println_me_message(ProfWin *window, char ch, const char *const me, const char *const message, ...)
+win_print_outgoing_muc_msg(ProfWin *window, char ch, const char *const me, const char *const id, const char *const replace_id, const char *const message, ...)
 {
     GDateTime *timestamp = g_date_time_new_now_local();
 
@@ -1127,9 +1180,14 @@ win_println_me_message(ProfWin *window, char ch, const char *const me, const cha
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, message, arg);
 
-    buffer_append(window->layout->buffer, ch, 0, timestamp, 0, THEME_TEXT_ME, me, fmt_msg->str, NULL, NULL);
+    if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && replace_id) {
+        _win_correct(window, fmt_msg->str, id, replace_id);
+    } else {
+        _win_printf(window, ch, 0, timestamp, 0, THEME_TEXT_ME, me, id, "%s", fmt_msg->str);
+    }
+//    buffer_append(window->layout->buffer, ch, 0, timestamp, 0, THEME_TEXT_ME, me, fmt_msg->str, NULL, NULL);
+//    _win_print_internal(window, ch, 0, timestamp, 0, THEME_TEXT_ME, me, fmt_msg->str, NULL);
 
-    _win_print(window, ch, 0, timestamp, 0, THEME_TEXT_ME, me, fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1138,7 +1196,7 @@ win_println_me_message(ProfWin *window, char ch, const char *const me, const cha
 }
 
 void
-win_print_outgoing(ProfWin *window, const char ch, const char *const message, ...)
+win_print_outgoing(ProfWin *window, const char ch, const char *const id, const char *const replace_id, const char *const message, ...)
 {
     GDateTime *timestamp = g_date_time_new_now_local();
 
@@ -1147,9 +1205,12 @@ win_print_outgoing(ProfWin *window, const char ch, const char *const message, ..
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, message, arg);
 
-    buffer_append(window->layout->buffer, ch, 0, timestamp, 0, THEME_TEXT_ME, "me", fmt_msg->str, NULL, NULL);
+    if (replace_id) {
+        _win_correct(window, fmt_msg->str, id, replace_id);
+    } else {
+        _win_printf(window, ch, 0, timestamp, 0, THEME_TEXT_THEM, "me", id, "%s", fmt_msg->str);
+    }
 
-    _win_print(window, ch, 0, timestamp, 0, THEME_TEXT_ME, "me", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1168,7 +1229,7 @@ win_print_history(ProfWin *window, GDateTime *timestamp, const char *const messa
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, '-', 0, timestamp, 0, THEME_TEXT_HISTORY, "", fmt_msg->str, NULL, NULL);
-    _win_print(window, '-', 0, timestamp, 0, THEME_TEXT_HISTORY, "", fmt_msg->str, NULL);
+    _win_print_internal(window, '-', 0, timestamp, 0, THEME_TEXT_HISTORY, "", fmt_msg->str, NULL);
 
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
@@ -1188,8 +1249,8 @@ win_print(ProfWin *window, theme_item_t theme_item, const char ch, const char *c
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, ch, 0, timestamp, NO_EOL, theme_item, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, ch, 0, timestamp, NO_EOL, theme_item, "", fmt_msg->str, NULL);
 
-    _win_print(window, ch, 0, timestamp, NO_EOL, theme_item, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1208,8 +1269,8 @@ win_println(ProfWin *window, theme_item_t theme_item, const char ch, const char
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, ch, 0, timestamp, 0, theme_item, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, ch, 0, timestamp, 0, theme_item, "", fmt_msg->str, NULL);
 
-    _win_print(window, ch, 0, timestamp, 0, theme_item, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1228,8 +1289,8 @@ win_println_indent(ProfWin *window, int pad, const char *const message, ...)
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, '-', pad, timestamp, 0, THEME_DEFAULT, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, '-', pad, timestamp, 0, THEME_DEFAULT, "", fmt_msg->str, NULL);
 
-    _win_print(window, '-', pad, timestamp, 0, THEME_DEFAULT, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1248,8 +1309,8 @@ win_append(ProfWin *window, theme_item_t theme_item, const char *const message,
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, '-', 0, timestamp, NO_DATE | NO_EOL, theme_item, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, '-', 0, timestamp, NO_DATE | NO_EOL, theme_item, "", fmt_msg->str, NULL);
 
-    _win_print(window, '-', 0, timestamp, NO_DATE | NO_EOL, theme_item, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1268,8 +1329,8 @@ win_appendln(ProfWin *window, theme_item_t theme_item, const char *const message
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, '-', 0, timestamp, NO_DATE, theme_item, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, '-', 0, timestamp, NO_DATE, theme_item, "", fmt_msg->str, NULL);
 
-    _win_print(window, '-', 0, timestamp, NO_DATE, theme_item, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1288,8 +1349,8 @@ win_append_highlight(ProfWin *window, theme_item_t theme_item, const char *const
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, '-', 0, timestamp, NO_DATE | NO_ME | NO_EOL, theme_item, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, '-', 0, timestamp, NO_DATE | NO_ME | NO_EOL, theme_item, "", fmt_msg->str, NULL);
 
-    _win_print(window, '-', 0, timestamp, NO_DATE | NO_ME | NO_EOL, theme_item, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1308,8 +1369,8 @@ win_appendln_highlight(ProfWin *window, theme_item_t theme_item, const char *con
     g_string_vprintf(fmt_msg, message, arg);
 
     buffer_append(window->layout->buffer, '-', 0, timestamp, NO_DATE | NO_ME, theme_item, "", fmt_msg->str, NULL, NULL);
+    _win_print_internal(window, '-', 0, timestamp, NO_DATE | NO_ME, theme_item, "", fmt_msg->str, NULL);
 
-    _win_print(window, '-', 0, timestamp, NO_DATE | NO_ME, theme_item, "", fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1320,19 +1381,24 @@ win_appendln_highlight(ProfWin *window, theme_item_t theme_item, const char *con
 void
 win_print_http_upload(ProfWin *window, const char *const message, char *url)
 {
-    win_print_with_receipt(window, '!', NULL, message, url);
+    win_print_outgoing_with_receipt(window, '!', NULL, message, url, NULL);
 }
 
 void
-win_print_with_receipt(ProfWin *window, const char show_char, const char *const from, const char *const message, char *id)
+win_print_outgoing_with_receipt(ProfWin *window, const char show_char, const char *const from, const char *const message, char *id, const char *const replace_id)
 {
     GDateTime *time = g_date_time_new_now_local();
 
     DeliveryReceipt *receipt = malloc(sizeof(struct delivery_receipt_t));
     receipt->received = FALSE;
 
-    buffer_append(window->layout->buffer, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt, id);
-    _win_print(window, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt);
+    if (replace_id) {
+        _win_correct(window, message, id, replace_id);
+    } else {
+        buffer_append(window->layout->buffer, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt, id);
+        _win_print_internal(window, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt);
+    }
+
     // TODO: cross-reference.. this should be replaced by a real event-based system
     inp_nonblocking(TRUE);
     g_date_time_unref(time);
@@ -1373,7 +1439,7 @@ win_newline(ProfWin *window)
 
 static void
 _win_printf(ProfWin *window, const char show_char, int pad_indent, GDateTime *timestamp,
-    int flags, theme_item_t theme_item, const char *const from, const char *const message, ...)
+    int flags, theme_item_t theme_item, const char *const from, const char *const message_id, const char *const message, ...)
 {
     if (timestamp == NULL) {
         timestamp = g_date_time_new_now_local();
@@ -1386,9 +1452,10 @@ _win_printf(ProfWin *window, const char show_char, int pad_indent, GDateTime *ti
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, message, arg);
 
-    buffer_append(window->layout->buffer, show_char, pad_indent, timestamp, flags, theme_item, from, fmt_msg->str, NULL, NULL);
+    buffer_append(window->layout->buffer, show_char, pad_indent, timestamp, flags, theme_item, from, fmt_msg->str, NULL, message_id);
+
+    _win_print_internal(window, show_char, pad_indent, timestamp, flags, theme_item, from, fmt_msg->str, NULL);
 
-    _win_print(window, show_char, pad_indent, timestamp, flags, theme_item, from, fmt_msg->str, NULL);
     inp_nonblocking(TRUE);
     g_date_time_unref(timestamp);
 
@@ -1397,7 +1464,7 @@ _win_printf(ProfWin *window, const char show_char, int pad_indent, GDateTime *ti
 }
 
 static void
-_win_print(ProfWin *window, const char show_char, int pad_indent, GDateTime *time,
+_win_print_internal(ProfWin *window, const char show_char, int pad_indent, GDateTime *time,
     int flags, theme_item_t theme_item, const char *const from, const char *const message, DeliveryReceipt *receipt)
 {
     // flags : 1st bit =  0/1 - me/not me
@@ -1676,12 +1743,12 @@ win_redraw(ProfWin *window)
     for (i = 0; i < size; i++) {
         ProfBuffEntry *e = buffer_get_entry(window->layout->buffer, i);
 
-        if (e->from == NULL && e->message && e->message[0] == '-') {
+        if (e->display_from == NULL && e->message && e->message[0] == '-') {
             // just an indicator to print the separator not the actual message
             win_print_separator(window);
         } else {
             // regular thing to print
-            _win_print(window, e->show_char, e->pad_indent, e->time, e->flags, e->theme_item, e->from, e->message, e->receipt);
+            _win_print_internal(window, e->show_char, e->pad_indent, e->time, e->flags, e->theme_item, e->display_from, e->message, e->receipt);
         }
     }
 }
diff --git a/src/ui/window.h b/src/ui/window.h
index b1f9a4af..2bd11621 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -3,6 +3,7 @@
  * vim: expandtab:ts=4:sts=4:sw=4
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 - 2020 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -62,17 +63,16 @@ void win_show_status_string(ProfWin *window, const char *const from,
     const char *const default_show);
 
 void win_print_them(ProfWin *window, theme_item_t theme_item, char ch, int flags, const char *const them);
-void win_println_them_message(ProfWin *window, char ch, int flags, const char *const them, const char *const message, ...);
-void win_println_me_message(ProfWin *window, char ch, const char *const me, const char *const message, ...);
+void win_println_incoming_muc_msg(ProfWin *window, char ch, int flags, const char *const them, const char *const id, const char *const replace_id, const char *const message, ...);
+void win_print_outgoing_muc_msg(ProfWin *window, char ch, const char *const me, const char *const id, const char *const replace_id, const char *const message, ...);
 
-void win_print_outgoing(ProfWin *window, const char ch, const char *const message, ...);
+void win_print_outgoing(ProfWin *window, const char ch, const char *const id, const char *const replace_id, const char *const message, ...);
 void win_print_incoming(ProfWin *window, const char *const from, ProfMessage *message);
 void win_print_history(ProfWin *window, GDateTime *timestamp, const char *const message, ...);
 
 void win_print_http_upload(ProfWin *window, const char *const message, char *url);
 
-void win_print_with_receipt(ProfWin *window, const char show_char, const char *const from, const char *const message,
-    char *id);
+void win_print_outgoing_with_receipt(ProfWin *window, const char show_char, const char *const from, const char *const message, char *id, const char *const replace_id);
 
 void win_newline(ProfWin *window);
 void win_redraw(ProfWin *window);
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c
index e1587ef8..a0fcecb0 100644
--- a/src/xmpp/capabilities.c
+++ b/src/xmpp/capabilities.c
@@ -104,12 +104,19 @@ caps_init(void)
     g_hash_table_add(prof_features, strdup(STANZA_NS_CHATSTATES));
     g_hash_table_add(prof_features, strdup(STANZA_NS_PING));
     g_hash_table_add(prof_features, strdup(STANZA_NS_STABLE_ID));
+
     if (prefs_get_boolean(PREF_RECEIPTS_SEND)) {
         g_hash_table_add(prof_features, strdup(STANZA_NS_RECEIPTS));
     }
+
     if (prefs_get_boolean(PREF_LASTACTIVITY)) {
         g_hash_table_add(prof_features, strdup(STANZA_NS_LASTACTIVITY));
     }
+
+    if (prefs_get_boolean(PREF_CORRECTION_ALLOW)) {
+        g_hash_table_add(prof_features, strdup(STANZA_NS_LAST_MESSAGE_CORRECTION));
+    }
+
     my_sha1 = NULL;
 }
 
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 1a97d9f1..0caef0f6 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -76,11 +76,11 @@ typedef struct p_message_handle_t {
 } ProfMessageHandler;
 
 static int _message_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
-static void _private_chat_handler(xmpp_stanza_t *const stanza);
 
 static void _handle_error(xmpp_stanza_t *const stanza);
 static void _handle_groupchat(xmpp_stanza_t *const stanza);
 static void _handle_muc_user(xmpp_stanza_t *const stanza);
+static void _handle_muc_private_message(xmpp_stanza_t *const stanza);
 static void _handle_conference(xmpp_stanza_t *const stanza);
 static void _handle_captcha(xmpp_stanza_t *const stanza);
 static void _handle_receipt_received(xmpp_stanza_t *const stanza);
@@ -188,6 +188,7 @@ message_init(void)
     message->jid = NULL;
     message->id = NULL;
     message->originid = NULL;
+    message->replace_id = NULL;
     message->body = NULL;
     message->encrypted = NULL;
     message->plain = NULL;
@@ -215,6 +216,10 @@ message_free(ProfMessage *message)
         xmpp_free(ctx, message->originid);
     }
 
+    if (message->replace_id) {
+        xmpp_free(ctx, message->replace_id);
+    }
+
     if (message->body) {
         xmpp_free(ctx, message->body);
     }
@@ -254,8 +259,7 @@ message_pubsub_event_handler_add(const char *const node, ProfMessageCallback fun
 }
 
 char*
-message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url,
-    gboolean request_receipt)
+message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url, gboolean request_receipt, const char *const replace_id)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
 
@@ -279,6 +283,10 @@ message_send_chat(const char *const barejid, const char *const msg, const char *
         stanza_attach_receipt_request(ctx, message);
     }
 
+    if (replace_id) {
+        stanza_attach_correction(ctx, message, replace_id);
+    }
+
     _send_message_stanza(message);
     xmpp_stanza_release(message);
 
@@ -286,7 +294,7 @@ message_send_chat(const char *const barejid, const char *const msg, const char *
 }
 
 char*
-message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt)
+message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
 
@@ -338,6 +346,10 @@ message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean
         stanza_attach_receipt_request(ctx, message);
     }
 
+    if (replace_id) {
+        stanza_attach_correction(ctx, message, replace_id);
+    }
+
     _send_message_stanza(message);
     xmpp_stanza_release(message);
 
@@ -345,7 +357,7 @@ message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean
 }
 
 char*
-message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt)
+message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
 
@@ -370,6 +382,10 @@ message_send_chat_otr(const char *const barejid, const char *const msg, gboolean
         stanza_attach_receipt_request(ctx, message);
     }
 
+    if (replace_id) {
+        stanza_attach_correction(ctx, message, replace_id);
+    }
+
     _send_message_stanza(message);
     xmpp_stanza_release(message);
 
@@ -381,7 +397,7 @@ char*
 message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys,
     const unsigned char *const iv, size_t iv_len,
     const unsigned char *const ciphertext, size_t ciphertext_len,
-    gboolean request_receipt, gboolean muc)
+    gboolean request_receipt, gboolean muc, const char *const replace_id)
 {
     char *state = chat_session_get_state(jid);
     xmpp_ctx_t * const ctx = connection_get_ctx();
@@ -481,6 +497,10 @@ message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys,
         stanza_attach_receipt_request(ctx, message);
     }
 
+    if (replace_id) {
+        stanza_attach_correction(ctx, message, replace_id);
+    }
+
     _send_message_stanza(message);
     xmpp_stanza_release(message);
 
@@ -508,7 +528,7 @@ message_send_private(const char *const fulljid, const char *const msg, const cha
 }
 
 char*
-message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url)
+message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url, const char *const replace_id)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
     char *id = connection_create_stanza_id();
@@ -521,6 +541,10 @@ message_send_groupchat(const char *const roomjid, const char *const msg, const c
         stanza_attach_x_oob_url(ctx, message, oob_url);
     }
 
+    if (replace_id) {
+        stanza_attach_correction(ctx, message, replace_id);
+    }
+
     _send_message_stanza(message);
     xmpp_stanza_release(message);
 
@@ -807,13 +831,23 @@ _handle_groupchat(xmpp_stanza_t *const stanza)
 
     ProfMessage *message = message_init();
     message->jid = jid;
+
     if (id) {
         message->id = strdup(id);
     }
+
     if (originid) {
         message->originid = strdup(originid);
     }
 
+    xmpp_stanza_t *replace_id_stanza = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_LAST_MESSAGE_CORRECTION);
+    if (replace_id_stanza) {
+        const char *replace_id = xmpp_stanza_get_id(replace_id_stanza);
+        if (replace_id) {
+            message->replace_id = strdup(replace_id);
+        }
+    }
+
     message->body = xmpp_message_get_body(stanza);
 
     // check omemo encryption
@@ -927,7 +961,7 @@ _receipt_request_handler(xmpp_stanza_t *const stanza)
 }
 
 static void
-_private_chat_handler(xmpp_stanza_t *const stanza)
+_handle_muc_private_message(xmpp_stanza_t *const stanza)
 {
     // standard chat message, use jid without resource
     ProfMessage *message = message_init();
@@ -936,6 +970,12 @@ _private_chat_handler(xmpp_stanza_t *const stanza)
     const gchar *from = xmpp_stanza_get_from(stanza);
     message->jid = jid_create(from);
 
+    // message stanza id
+    const char *id = xmpp_stanza_get_id(stanza);
+    if (id) {
+        message->id = strdup(id);
+    }
+
     // check omemo encryption
 #ifdef HAVE_OMEMO
     message->plain = omemo_receive_message(stanza, &message->trusted);
@@ -1015,6 +1055,21 @@ _handle_carbons(xmpp_stanza_t *const stanza)
         message->mucuser = TRUE;
     }
 
+    // id
+    const char *id = xmpp_stanza_get_id(message_stanza);
+    if (id) {
+        message->id = strdup(id);
+    }
+
+    // replace id
+    xmpp_stanza_t *replace_id_stanza = xmpp_stanza_get_child_by_ns(message_stanza, STANZA_NS_LAST_MESSAGE_CORRECTION);
+    if (replace_id_stanza) {
+        const char *replace_id = xmpp_stanza_get_id(replace_id_stanza);
+        if (replace_id) {
+            message->replace_id = strdup(replace_id);
+        }
+    }
+
     // check omemo encryption
 #ifdef HAVE_OMEMO
     message->plain = omemo_receive_message(message_stanza, &message->trusted);
@@ -1104,7 +1159,7 @@ _handle_chat(xmpp_stanza_t *const stanza)
 
     // private message from chat room use full jid (room/nick)
     if (muc_active(jid->barejid)) {
-        _private_chat_handler(stanza);
+        _handle_muc_private_message(stanza);
         jid_destroy(jid);
         return;
     }
@@ -1113,6 +1168,20 @@ _handle_chat(xmpp_stanza_t *const stanza)
     ProfMessage *message = message_init();
     message->jid = jid;
 
+    // message stanza id
+    const char *id = xmpp_stanza_get_id(stanza);
+    if (id) {
+        message->id = strdup(id);
+    }
+
+    xmpp_stanza_t *replace_id_stanza = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_LAST_MESSAGE_CORRECTION);
+    if (replace_id_stanza) {
+        const char *replace_id = xmpp_stanza_get_id(replace_id_stanza);
+        if (replace_id) {
+            message->replace_id = strdup(replace_id);
+        }
+    }
+
     if (mucuser) {
         message->mucuser = TRUE;
     }
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index f398a268..7b0db5c7 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -2606,3 +2606,15 @@ stanza_create_avatar_retrieve_data_request(xmpp_ctx_t *ctx, const char *stanza_i
 
     return iq;
 }
+
+xmpp_stanza_t*
+stanza_attach_correction(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const replace_id)
+{
+    xmpp_stanza_t *replace_stanza = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(replace_stanza, "replace");
+    xmpp_stanza_set_id(replace_stanza, replace_id);
+    xmpp_stanza_set_ns(replace_stanza, STANZA_NS_LAST_MESSAGE_CORRECTION);
+    xmpp_stanza_add_child(stanza, replace_stanza);
+
+    return stanza;
+}
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 7b5cbe35..ee5c6772 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -201,6 +201,7 @@
 #define STANZA_NS_STABLE_ID "urn:xmpp:sid:0"
 #define STANZA_NS_USER_AVATAR_DATA "urn:xmpp:avatar:data"
 #define STANZA_NS_USER_AVATAR_METADATA "urn:xmpp:avatar:metadata"
+#define STANZA_NS_LAST_MESSAGE_CORRECTION "urn:xmpp:message-correct:0"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
@@ -244,6 +245,7 @@ xmpp_stanza_t* stanza_attach_hints_store(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza)
 xmpp_stanza_t* stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
 xmpp_stanza_t* stanza_attach_x_oob_url(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const url);
 xmpp_stanza_t* stanza_attach_origin_id(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const id);
+xmpp_stanza_t* stanza_attach_correction(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const replace_id);
 
 xmpp_stanza_t* stanza_create_room_join_presence(xmpp_ctx_t *const ctx,
     const char *const full_room_jid, const char *const passwd);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index f5b4ce70..0563948a 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -68,6 +68,7 @@
 #define XMPP_FEATURE_PUBSUB "http://jabber.org/protocol/pubsub"
 #define XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS "http://jabber.org/protocol/pubsub#publish-options"
 #define XMPP_FEATURE_USER_AVATAR_METADATA_NOTIFY "urn:xmpp:avatar:metadata+notify"
+#define XMPP_FEATURE_LAST_MESSAGE_CORRECTION "urn:xmpp:message-correct:0"
 
 typedef enum {
     JABBER_CONNECTING,
@@ -130,6 +131,8 @@ typedef struct prof_message_t {
    char *id;
    /* </origin-id> XEP-0359 */
    char *originid;
+   /* <replace id> XEP-0308 LMC */
+   char *replace_id;
    /* The raw body from xmpp message, either plaintext or OTR encrypted text */
    char *body;
    /* The encrypted message as for PGP */
@@ -168,13 +171,12 @@ char* connection_jid_for_feature(const char *const feature);
 
 const char* connection_get_profanity_identifier(void);
 
-char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url,
-    gboolean request_receipt);
-char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt);
-char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt);
-char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt, gboolean muc);
+char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url, gboolean request_receipt, const char *const replace_id);
+char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id);
+char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id);
+char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt, gboolean muc, const char *const replace_id);
 void message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url);
-char* message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url);
+char* message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url, const char *const replace_id);
 void message_send_groupchat_subject(const char *const roomjid, const char *const subject);
 void message_send_inactive(const char *const jid);
 void message_send_composing(const char *const jid);
diff --git a/tests/unittests/otr/stub_otr.c b/tests/unittests/otr/stub_otr.c
index 25c199a8..dffecfec 100644
--- a/tests/unittests/otr/stub_otr.c
+++ b/tests/unittests/otr/stub_otr.c
@@ -45,7 +45,7 @@ char* otr_on_message_recv(const char * const barejid, const char * const resourc
 {
     return NULL;
 }
-gboolean otr_on_message_send(ProfChatWin *chatwin, const char * const message, gboolean request_receipt)
+gboolean otr_on_message_send(ProfChatWin *chatwin, const char * const message, gboolean request_receipt, const char *const replace_id)
 {
     return FALSE;
 }
diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c
index 49a70892..ecfc55e0 100644
--- a/tests/unittests/ui/stub_ui.c
+++ b/tests/unittests/ui/stub_ui.c
@@ -160,8 +160,7 @@ void privwin_incoming_msg(ProfPrivateWin *privatewin, ProfMessage *message) {}
 void ui_disconnected(void) {}
 void chatwin_recipient_gone(ProfChatWin *chatwin) {}
 
-void chatwin_outgoing_msg(ProfChatWin *chatwin, const char * const message, char *id, prof_enc_t enc_mode,
-    gboolean request_receipt) {}
+void chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id, prof_enc_t enc_mode, gboolean request_receipt, const char *const replace_id) {}
 void chatwin_outgoing_carbon(ProfChatWin *chatwin, ProfMessage *message) {}
 void privwin_outgoing_msg(ProfPrivateWin *privwin, const char * const message) {}
 
@@ -191,7 +190,7 @@ void mucwin_occupant_role_and_affiliation_change(ProfMucWin *mucwin, const char
 void mucwin_roster(ProfMucWin *mucwin, GList *occupants, const char * const presence) {}
 void mucwin_history(ProfMucWin *mucwin, const char * const nick, GDateTime *timestamp, const char * const message) {}
 void mucwin_incoming_msg(ProfMucWin *mucwin, ProfMessage *message, GSList *mentions, GList *triggers) {}
-void mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode) {}
+void mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode, const char *const replace_id) {}
 void mucwin_subject(ProfMucWin *mucwin, const char * const nick, const char * const subject) {}
 void mucwin_requires_config(ProfMucWin *mucwin) {}
 void ui_room_destroy(const char * const roomjid) {}
@@ -452,6 +451,7 @@ void cons_winpos_setting(void) {}
 void cons_statusbar_setting(void) {}
 void cons_tray_setting(void) {}
 void cons_os_setting(void) {}
+void cons_correction_setting(void) {}
 
 void cons_show_contact_online(PContact contact, Resource *resource, GDateTime *last_activity)
 {
diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c
index f5cfb7ef..e9ff27e5 100644
--- a/tests/unittests/xmpp/stub_xmpp.c
+++ b/tests/unittests/xmpp/stub_xmpp.c
@@ -97,28 +97,27 @@ const char* connection_get_profanity_identifier(void) {
 }
 
 // message functions
-char* message_send_chat(const char * const barejid, const char * const msg, const char *const oob_url,
-    gboolean request_receipt)
+char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url, gboolean request_receipt, const char *const replace_id)
 {
     check_expected(barejid);
     check_expected(msg);
     return NULL;
 }
 
-char* message_send_chat_otr(const char * const barejid, const char * const msg, gboolean request_receipt)
+char* message_send_chat_otr(const char * const barejid, const char * const msg, gboolean request_receipt, const char *const replace_id)
 {
     check_expected(barejid);
     check_expected(msg);
     return NULL;
 }
 
-char* message_send_chat_pgp(const char * const barejid, const char * const msg, gboolean request_receipt)
+char* message_send_chat_pgp(const char * const barejid, const char * const msg, gboolean request_receipt, const char *const replace_id)
 {
     return NULL;
 }
 
 void message_send_private(const char * const fulljid, const char * const msg, const char *const oob_url) {}
-char* message_send_groupchat(const char * const roomjid, const char * const msg, const char *const oob_url)
+char* message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url, const char *const replace_id)
 {
     return NULL;
 }
diff --git a/theme_template b/theme_template
index 55de8521..3a512936 100644
--- a/theme_template
+++ b/theme_template
@@ -149,3 +149,4 @@ console.muc=
 console.chat=
 console.private=
 inputwin.position=
+correction.char=
diff --git a/themes/boothj5 b/themes/boothj5
index fee2a7d7..3955da6a 100644
--- a/themes/boothj5
+++ b/themes/boothj5
@@ -152,4 +152,5 @@ statusbar.tabs=10
 statusbar.tablen=7
 statusbar.show.name=true
 statusbar.show.number=true
+correction.char=+