From 2aabbf0aa6ded0d97a08c13aece00d6019395de5 Mon Sep 17 00:00:00 2001
From: fedrunov <fedrunov@element.io>
Date: Tue, 11 Jan 2022 12:23:26 +0100
Subject: [PATCH] added aliases to commands

---
 .../command/AutocompleteCommandPresenter.kt   |   2 +-
 .../im/vector/app/features/command/Command.kt |  78 +++---
 .../app/features/command/CommandParser.kt     | 222 ++++++++----------
 .../home/room/detail/RoomDetailFragment.kt    |   2 +-
 .../composer/MessageComposerViewModel.kt      |   2 +-
 5 files changed, 147 insertions(+), 159 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt
index 5ad31aeaa6..9888f1e35e 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt
@@ -50,7 +50,7 @@ class AutocompleteCommandPresenter @Inject constructor(context: Context,
                     if (query.isNullOrEmpty()) {
                         true
                     } else {
-                        it.command.startsWith(query, 1, true)
+                        it.startsWith(query)
                     }
                 }
         controller.setData(data)
diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt
index c90d06a7b0..4edd62a488 100644
--- a/vector/src/main/java/im/vector/app/features/command/Command.kt
+++ b/vector/src/main/java/im/vector/app/features/command/Command.kt
@@ -24,42 +24,46 @@ import im.vector.app.R
  * the user can write theses messages to perform some actions
  * the list will be displayed in this order
  */
-enum class Command(val command: String, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean) {
-    EMOTE("/me", "<message>", R.string.command_description_emote, false),
-    BAN_USER("/ban", "<user-id> [reason]", R.string.command_description_ban_user, false),
-    UNBAN_USER("/unban", "<user-id> [reason]", R.string.command_description_unban_user, false),
-    IGNORE_USER("/ignore", "<user-id> [reason]", R.string.command_description_ignore_user, false),
-    UNIGNORE_USER("/unignore", "<user-id>", R.string.command_description_unignore_user, false),
-    SET_USER_POWER_LEVEL("/op", "<user-id> [<power-level>]", R.string.command_description_op_user, false),
-    RESET_USER_POWER_LEVEL("/deop", "<user-id>", R.string.command_description_deop_user, false),
-    ROOM_NAME("/roomname", "<name>", R.string.command_description_room_name, false),
-    INVITE("/invite", "<user-id> [reason]", R.string.command_description_invite_user, false),
-    JOIN_ROOM("/join", "<room-address> [reason]", R.string.command_description_join_room, false),
-    PART("/part", "[<room-address>]", R.string.command_description_part_room, false),
-    TOPIC("/topic", "<topic>", R.string.command_description_topic, false),
-    REMOVE_USER("/remove", "<user-id> [reason]", R.string.command_description_kick_user, false),
-    CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick, false),
-    CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", "<display-name>", R.string.command_description_nick_for_room, false),
-    ROOM_AVATAR("/roomavatar", "<mxc_url>", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */),
-    CHANGE_AVATAR_FOR_ROOM("/myroomavatar", "<mxc_url>", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */),
-    MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown, false),
-    RAINBOW("/rainbow", "<message>", R.string.command_description_rainbow, false),
-    RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote, false),
-    CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false),
-    SPOILER("/spoiler", "<message>", R.string.command_description_spoiler, false),
-    SHRUG("/shrug", "<message>", R.string.command_description_shrug, false),
-    LENNY("/lenny", "<message>", R.string.command_description_lenny, false),
-    PLAIN("/plain", "<message>", R.string.command_description_plain, false),
-    WHOIS("/whois", "<user-id>", R.string.command_description_whois, false),
-    DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false),
-    CONFETTI("/confetti", "<message>", R.string.command_confetti, false),
-    SNOWFALL("/snowfall", "<message>", R.string.command_snow, false),
-    CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space, true),
-    ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_add_to_space, true),
-    JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true),
-    LEAVE_ROOM("/leave", "<roomId?>", R.string.command_description_leave_room, true),
-    UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true);
+enum class Command(val command: String, val aliases: Array<CharSequence>?, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean) {
+    EMOTE("/me", null, "<message>", R.string.command_description_emote, false),
+    BAN_USER("/ban", null, "<user-id> [reason]", R.string.command_description_ban_user, false),
+    UNBAN_USER("/unban", null, "<user-id> [reason]", R.string.command_description_unban_user, false),
+    IGNORE_USER("/ignore", null, "<user-id> [reason]", R.string.command_description_ignore_user, false),
+    UNIGNORE_USER("/unignore", null, "<user-id>", R.string.command_description_unignore_user, false),
+    SET_USER_POWER_LEVEL("/op", null, "<user-id> [<power-level>]", R.string.command_description_op_user, false),
+    RESET_USER_POWER_LEVEL("/deop", null, "<user-id>", R.string.command_description_deop_user, false),
+    ROOM_NAME("/roomname", null, "<name>", R.string.command_description_room_name, false),
+    INVITE("/invite", null, "<user-id> [reason]", R.string.command_description_invite_user, false),
+    JOIN_ROOM("/join", null, "<room-address> [reason]", R.string.command_description_join_room, false),
+    PART("/part", null, "[<room-address>]", R.string.command_description_part_room, false),
+    TOPIC("/topic", null, "<topic>", R.string.command_description_topic, false),
+    REMOVE_USER("/remove", arrayOf("/kick"), "<user-id> [reason]", R.string.command_description_kick_user, false),
+    CHANGE_DISPLAY_NAME("/nick", null, "<display-name>", R.string.command_description_nick, false),
+    CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", null, "<display-name>", R.string.command_description_nick_for_room, false),
+    ROOM_AVATAR("/roomavatar", null, "<mxc_url>", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */),
+    CHANGE_AVATAR_FOR_ROOM("/myroomavatar", null, "<mxc_url>", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */),
+    MARKDOWN("/markdown", null, "<on|off>", R.string.command_description_markdown, false),
+    RAINBOW("/rainbow", null, "<message>", R.string.command_description_rainbow, false),
+    RAINBOW_EMOTE("/rainbowme", null, "<message>", R.string.command_description_rainbow_emote, false),
+    CLEAR_SCALAR_TOKEN("/clear_scalar_token", null, "", R.string.command_description_clear_scalar_token, false),
+    SPOILER("/spoiler", null, "<message>", R.string.command_description_spoiler, false),
+    SHRUG("/shrug", null, "<message>", R.string.command_description_shrug, false),
+    LENNY("/lenny", null, "<message>", R.string.command_description_lenny, false),
+    PLAIN("/plain", null, "<message>", R.string.command_description_plain, false),
+    WHOIS("/whois", null, "<user-id>", R.string.command_description_whois, false),
+    DISCARD_SESSION("/discardsession", null, "", R.string.command_description_discard_session, false),
+    CONFETTI("/confetti", null, "<message>", R.string.command_confetti, false),
+    SNOWFALL("/snowfall", null, "<message>", R.string.command_snow, false),
+    CREATE_SPACE("/createspace", null, "<name> <invitee>*", R.string.command_description_create_space, true),
+    ADD_TO_SPACE("/addToSpace", null, "spaceId", R.string.command_description_add_to_space, true),
+    JOIN_SPACE("/joinSpace", null, "spaceId", R.string.command_description_join_space, true),
+    LEAVE_ROOM("/leave", null, "<roomId?>", R.string.command_description_leave_room, true),
+    UPGRADE_ROOM("/upgraderoom", null, "newVersion", R.string.command_description_upgrade_room, true);
 
-    val length
-        get() = command.length + 1
+    val allAliases = arrayOf(command, *aliases.orEmpty())
+
+    fun matches(inputCommand: CharSequence) = allAliases.any { it.contentEquals(inputCommand, true) }
+
+    fun startsWith(input: CharSequence) =
+            allAliases.any { it.startsWith(input, 1, true) }
 }
diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
index 22e1091c24..c6de96859a 100644
--- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
@@ -32,12 +32,12 @@ object CommandParser {
      * @param textMessage   the text message
      * @return a parsed slash command (ok or error)
      */
-    fun parseSplashCommand(textMessage: CharSequence): ParsedCommand {
+    fun parseSlashCommand(textMessage: CharSequence): ParsedCommand {
         // check if it has the Slash marker
         if (!textMessage.startsWith("/")) {
             return ParsedCommand.ErrorNotACommand
         } else {
-            Timber.v("parseSplashCommand")
+            Timber.v("parseSlashCommand")
 
             // "/" only
             if (textMessage.length == 1) {
@@ -52,7 +52,7 @@ object CommandParser {
             val messageParts = try {
                 textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
             } catch (e: Exception) {
-                Timber.e(e, "## manageSplashCommand() : split failed")
+                Timber.e(e, "## manageSlashCommand() : split failed")
                 null
             }
 
@@ -61,35 +61,32 @@ object CommandParser {
                 return ParsedCommand.ErrorEmptySlashCommand
             }
 
-            return when (val slashCommand = messageParts.first()) {
-                Command.PLAIN.command                        -> {
-                    val text = textMessage.substring(Command.PLAIN.command.length).trim()
+            val slashCommand = messageParts.first()
+            val message = textMessage.substring(slashCommand.length).trim()
 
-                    if (text.isNotEmpty()) {
-                        ParsedCommand.SendPlainText(text)
+            return when {
+                Command.PLAIN.matches(slashCommand)                        -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.SendPlainText(message = message)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.PLAIN)
                     }
                 }
-                Command.CHANGE_DISPLAY_NAME.command          -> {
-                    val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim()
-
-                    if (newDisplayName.isNotEmpty()) {
-                        ParsedCommand.ChangeDisplayName(newDisplayName)
+                Command.CHANGE_DISPLAY_NAME.matches(slashCommand)          -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.ChangeDisplayName(displayName = message)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME)
                     }
                 }
-                Command.CHANGE_DISPLAY_NAME_FOR_ROOM.command -> {
-                    val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME_FOR_ROOM.command.length).trim()
-
-                    if (newDisplayName.isNotEmpty()) {
-                        ParsedCommand.ChangeDisplayNameForRoom(newDisplayName)
+                Command.CHANGE_DISPLAY_NAME_FOR_ROOM.matches(slashCommand) -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.ChangeDisplayNameForRoom(displayName = message)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME_FOR_ROOM)
                     }
                 }
-                Command.ROOM_AVATAR.command                  -> {
+                Command.ROOM_AVATAR.matches(slashCommand)                  -> {
                     if (messageParts.size == 2) {
                         val url = messageParts[1]
 
@@ -102,7 +99,7 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.ROOM_AVATAR)
                     }
                 }
-                Command.CHANGE_AVATAR_FOR_ROOM.command       -> {
+                Command.CHANGE_AVATAR_FOR_ROOM.matches(slashCommand)       -> {
                     if (messageParts.size == 2) {
                         val url = messageParts[1]
 
@@ -115,40 +112,42 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.CHANGE_AVATAR_FOR_ROOM)
                     }
                 }
-                Command.TOPIC.command                        -> {
-                    val newTopic = textMessage.substring(Command.TOPIC.command.length).trim()
-
-                    if (newTopic.isNotEmpty()) {
-                        ParsedCommand.ChangeTopic(newTopic)
+                Command.TOPIC.matches(slashCommand)                        -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.ChangeTopic(topic = message)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.TOPIC)
                     }
                 }
-                Command.EMOTE.command                        -> {
-                    val message = textMessage.subSequence(Command.EMOTE.command.length, textMessage.length).trim()
-
-                    ParsedCommand.SendEmote(message)
+                Command.EMOTE.matches(slashCommand)                        -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.SendEmote(message)
+                    } else {
+                        ParsedCommand.ErrorSyntax(Command.EMOTE)
+                    }
                 }
-                Command.RAINBOW.command                      -> {
-                    val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim()
-
-                    ParsedCommand.SendRainbow(message)
+                Command.RAINBOW.matches(slashCommand)                      -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.SendRainbow(message)
+                    } else {
+                        ParsedCommand.ErrorSyntax(Command.RAINBOW)
+                    }
                 }
-                Command.RAINBOW_EMOTE.command                -> {
-                    val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim()
-
-                    ParsedCommand.SendRainbowEmote(message)
+                Command.RAINBOW_EMOTE.matches(slashCommand)                -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.SendRainbowEmote(message)
+                    } else {
+                        ParsedCommand.ErrorSyntax(Command.RAINBOW_EMOTE)
+                    }
                 }
-                Command.JOIN_ROOM.command                    -> {
+                Command.JOIN_ROOM.matches(slashCommand)                    -> {
                     if (messageParts.size >= 2) {
                         val roomAlias = messageParts[1]
 
                         if (roomAlias.isNotEmpty()) {
                             ParsedCommand.JoinRoom(
                                     roomAlias,
-                                    textMessage.substring(Command.JOIN_ROOM.length + roomAlias.length)
-                                            .trim()
-                                            .takeIf { it.isNotBlank() }
+                                    trimParts(textMessage, messageParts.take(2))
                             )
                         } else {
                             ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
@@ -157,23 +156,21 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
                     }
                 }
-                Command.PART.command                         -> {
+                Command.PART.matches(slashCommand)                         -> {
                     when (messageParts.size) {
                         1    -> ParsedCommand.PartRoom(null)
                         2    -> ParsedCommand.PartRoom(messageParts[1])
                         else -> ParsedCommand.ErrorSyntax(Command.PART)
                     }
                 }
-                Command.ROOM_NAME.command                    -> {
-                    val newRoomName = textMessage.substring(Command.ROOM_NAME.command.length).trim()
-
-                    if (newRoomName.isNotEmpty()) {
-                        ParsedCommand.ChangeRoomName(newRoomName)
+                Command.ROOM_NAME.matches(slashCommand)                    -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.ChangeRoomName(name = message)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.ROOM_NAME)
                     }
                 }
-                Command.INVITE.command                       -> {
+                Command.INVITE.matches(slashCommand)                       -> {
                     if (messageParts.size >= 2) {
                         val userId = messageParts[1]
 
@@ -181,9 +178,7 @@ object CommandParser {
                             MatrixPatterns.isUserId(userId) -> {
                                 ParsedCommand.Invite(
                                         userId,
-                                        textMessage.substring(Command.INVITE.length + userId.length)
-                                                .trim()
-                                                .takeIf { it.isNotBlank() }
+                                        trimParts(textMessage, messageParts.take(2))
                                 )
                             }
                             userId.isEmail()                -> {
@@ -200,16 +195,14 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.INVITE)
                     }
                 }
-                Command.REMOVE_USER.command                  -> {
+                Command.REMOVE_USER.matches(slashCommand)                  -> {
                     if (messageParts.size >= 2) {
                         val userId = messageParts[1]
 
                         if (MatrixPatterns.isUserId(userId)) {
                             ParsedCommand.RemoveUser(
                                     userId,
-                                    textMessage.substring(Command.REMOVE_USER.length + userId.length)
-                                            .trim()
-                                            .takeIf { it.isNotBlank() }
+                                    trimParts(textMessage, messageParts.take(2))
                             )
                         } else {
                             ParsedCommand.ErrorSyntax(Command.REMOVE_USER)
@@ -218,16 +211,14 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.REMOVE_USER)
                     }
                 }
-                Command.BAN_USER.command                     -> {
+                Command.BAN_USER.matches(slashCommand)                     -> {
                     if (messageParts.size >= 2) {
                         val userId = messageParts[1]
 
                         if (MatrixPatterns.isUserId(userId)) {
                             ParsedCommand.BanUser(
                                     userId,
-                                    textMessage.substring(Command.BAN_USER.length + userId.length)
-                                            .trim()
-                                            .takeIf { it.isNotBlank() }
+                                    trimParts(textMessage, messageParts.take(2))
                             )
                         } else {
                             ParsedCommand.ErrorSyntax(Command.BAN_USER)
@@ -236,16 +227,14 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.BAN_USER)
                     }
                 }
-                Command.UNBAN_USER.command                   -> {
+                Command.UNBAN_USER.matches(slashCommand)                   -> {
                     if (messageParts.size >= 2) {
                         val userId = messageParts[1]
 
                         if (MatrixPatterns.isUserId(userId)) {
                             ParsedCommand.UnbanUser(
                                     userId,
-                                    textMessage.substring(Command.UNBAN_USER.length + userId.length)
-                                            .trim()
-                                            .takeIf { it.isNotBlank() }
+                                    trimParts(textMessage, messageParts.take(2))
                             )
                         } else {
                             ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
@@ -254,7 +243,7 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
                     }
                 }
-                Command.IGNORE_USER.command                  -> {
+                Command.IGNORE_USER.matches(slashCommand)                  -> {
                     if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
@@ -267,7 +256,7 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.IGNORE_USER)
                     }
                 }
-                Command.UNIGNORE_USER.command                -> {
+                Command.UNIGNORE_USER.matches(slashCommand)                -> {
                     if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
@@ -280,7 +269,7 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.UNIGNORE_USER)
                     }
                 }
-                Command.SET_USER_POWER_LEVEL.command         -> {
+                Command.SET_USER_POWER_LEVEL.matches(slashCommand)         -> {
                     if (messageParts.size == 3) {
                         val userId = messageParts[1]
                         if (MatrixPatterns.isUserId(userId)) {
@@ -300,7 +289,7 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
                     }
                 }
-                Command.RESET_USER_POWER_LEVEL.command       -> {
+                Command.RESET_USER_POWER_LEVEL.matches(slashCommand)       -> {
                     if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
@@ -313,7 +302,7 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
                     }
                 }
-                Command.MARKDOWN.command                     -> {
+                Command.MARKDOWN.matches(slashCommand)                     -> {
                     if (messageParts.size == 2) {
                         when {
                             "on".equals(messageParts[1], true)  -> ParsedCommand.SetMarkdown(true)
@@ -324,31 +313,34 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.MARKDOWN)
                     }
                 }
-                Command.CLEAR_SCALAR_TOKEN.command           -> {
+                Command.CLEAR_SCALAR_TOKEN.matches(slashCommand)           -> {
                     if (messageParts.size == 1) {
                         ParsedCommand.ClearScalarToken
                     } else {
                         ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN)
                     }
                 }
-                Command.SPOILER.command                      -> {
-                    val message = textMessage.substring(Command.SPOILER.command.length).trim()
-                    ParsedCommand.SendSpoiler(message)
+                Command.SPOILER.matches(slashCommand)                      -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.SendSpoiler(message)
+                    } else {
+                        ParsedCommand.ErrorSyntax(Command.SPOILER)
+                    }
                 }
-                Command.SHRUG.command                        -> {
-                    val message = textMessage.substring(Command.SHRUG.command.length).trim()
-
+                Command.SHRUG.matches(slashCommand)                        -> {
                     ParsedCommand.SendShrug(message)
                 }
-                Command.LENNY.command                        -> {
-                    val message = textMessage.substring(Command.LENNY.command.length).trim()
-
+                Command.LENNY.matches(slashCommand)                        -> {
                     ParsedCommand.SendLenny(message)
                 }
-                Command.DISCARD_SESSION.command              -> {
-                    ParsedCommand.DiscardSession
+                Command.DISCARD_SESSION.matches(slashCommand)              -> {
+                    if (messageParts.size == 1) {
+                        ParsedCommand.DiscardSession
+                    } else {
+                        ParsedCommand.ErrorSyntax(Command.DISCARD_SESSION)
+                    }
                 }
-                Command.WHOIS.command                        -> {
+                Command.WHOIS.matches(slashCommand)                        -> {
                     if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
@@ -361,57 +353,49 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.WHOIS)
                     }
                 }
-                Command.CONFETTI.command                     -> {
-                    val message = textMessage.substring(Command.CONFETTI.command.length).trim()
+                Command.CONFETTI.matches(slashCommand)                     -> {
                     ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message)
                 }
-                Command.SNOWFALL.command                     -> {
-                    val message = textMessage.substring(Command.SNOWFALL.command.length).trim()
+                Command.SNOWFALL.matches(slashCommand)                     -> {
                     ParsedCommand.SendChatEffect(ChatEffect.SNOWFALL, message)
                 }
-                Command.CREATE_SPACE.command                 -> {
-                    val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim()
-                    val split = rawCommand.split(" ").map { it.trim() }
-                    if (split.isEmpty()) {
-                        ParsedCommand.ErrorSyntax(Command.CREATE_SPACE)
-                    } else {
+                Command.CREATE_SPACE.matches(slashCommand)                 -> {
+                    if (messageParts.size >= 2) {
                         ParsedCommand.CreateSpace(
-                                split[0],
-                                split.subList(1, split.size)
+                                messageParts[1],
+                                messageParts.drop(2)
                         )
-                    }
-                }
-                Command.ADD_TO_SPACE.command                 -> {
-                    val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim()
-                    ParsedCommand.AddToSpace(
-                            rawCommand
-                    )
-                }
-                Command.JOIN_SPACE.command                   -> {
-                    val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim()
-                    ParsedCommand.JoinSpace(
-                            spaceIdOrAlias
-                    )
-                }
-                Command.LEAVE_ROOM.command                   -> {
-                    val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim()
-                    ParsedCommand.LeaveRoom(
-                            spaceIdOrAlias
-                    )
-                }
-                Command.UPGRADE_ROOM.command                 -> {
-                    val newVersion = textMessage.substring(Command.UPGRADE_ROOM.command.length).trim()
-                    if (newVersion.isEmpty()) {
-                        ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
                     } else {
-                        ParsedCommand.UpgradeRoom(newVersion)
+                        ParsedCommand.ErrorSyntax(Command.CREATE_SPACE)
                     }
                 }
-                else                                         -> {
+                Command.ADD_TO_SPACE.matches(slashCommand)                 -> {
+                    ParsedCommand.AddToSpace(spaceId = message)
+                }
+                Command.JOIN_SPACE.matches(slashCommand)                   -> {
+                    ParsedCommand.JoinSpace(spaceIdOrAlias = message)
+                }
+                Command.LEAVE_ROOM.matches(slashCommand)                   -> {
+                    ParsedCommand.LeaveRoom(roomId = message)
+                }
+                Command.UPGRADE_ROOM.matches(slashCommand)                 -> {
+                    if (message.isNotEmpty()) {
+                        ParsedCommand.UpgradeRoom(newVersion = message)
+                    } else {
+                        ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
+                    }
+                }
+                else                                                       -> {
                     // Unknown command
                     ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
                 }
             }
         }
     }
+
+    private fun trimParts(message: CharSequence, messageParts: List<String>): String? {
+        val partsSize = messageParts.sumOf { it.length }
+        val gapsNumber = messageParts.size - 1
+        return message.substring(partsSize + gapsNumber).trim().takeIf { it.isNotEmpty() }
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 566cb2d2de..eaa684dea1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -2098,7 +2098,7 @@ class RoomDetailFragment @Inject constructor(
                 userId == session.myUserId) {
             // Empty composer, current user: start an emote
             views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
-            views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.length)
+            views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1)
         } else {
             val roomMember = roomDetailViewModel.getMember(userId)
             // TODO move logic outside of fragment
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
index d787978aa9..e907db0328 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
@@ -161,7 +161,7 @@ class MessageComposerViewModel @AssistedInject constructor(
         withState { state ->
             when (state.sendMode) {
                 is SendMode.Regular -> {
-                    when (val slashCommandResult = CommandParser.parseSplashCommand(action.text)) {
+                    when (val slashCommandResult = CommandParser.parseSlashCommand(action.text)) {
                         is ParsedCommand.ErrorNotACommand         -> {
                             // Send the text message to the room
                             room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)