From 2ff2ff0e75fbd0c0d75683bc95054e0df4261107 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Thu, 29 Aug 2019 17:43:18 +0200
Subject: [PATCH] support autocomplete replacing text with multiple parts

and append ": " to user pills
---
 src/editor/autocomplete.js | 19 ++++++++-----------
 src/editor/model.js        | 19 +++++++++++--------
 2 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/src/editor/autocomplete.js b/src/editor/autocomplete.js
index 79a69c07a6..67015b72e1 100644
--- a/src/editor/autocomplete.js
+++ b/src/editor/autocomplete.js
@@ -27,8 +27,7 @@ export default class AutocompleteWrapperModel {
     onEscape(e) {
         this._getAutocompleterComponent().onEscape(e);
         this._updateCallback({
-            replacePart: this._partCreator.plain(this._queryPart.text),
-            caretOffset: this._queryOffset,
+            replaceParts: [this._partCreator.plain(this._queryPart.text)],
             close: true,
         });
     }
@@ -70,26 +69,24 @@ export default class AutocompleteWrapperModel {
         // cache the typed value and caret here
         // so we can restore it in onComponentSelectionChange when the value is undefined (meaning it should be the typed text)
         this._queryPart = part;
-        this._queryOffset = offset;
         return this._updateQuery(part.text);
     }
 
     onComponentSelectionChange(completion) {
         if (!completion) {
             this._updateCallback({
-                replacePart: this._queryPart,
-                caretOffset: this._queryOffset,
+                replaceParts: [this._queryPart],
             });
         } else {
             this._updateCallback({
-                replacePart: this._partForCompletion(completion),
+                replaceParts: this._partForCompletion(completion),
             });
         }
     }
 
     onComponentConfirm(completion) {
         this._updateCallback({
-            replacePart: this._partForCompletion(completion),
+            replaceParts: this._partForCompletion(completion),
             close: true,
         });
     }
@@ -101,16 +98,16 @@ export default class AutocompleteWrapperModel {
         switch (firstChr) {
             case "@": {
                 if (completionId === "@room") {
-                    return this._partCreator.atRoomPill(completionId);
+                    return [this._partCreator.atRoomPill(completionId)];
                 } else {
-                    return this._partCreator.userPill(text, completionId);
+                    return [this._partCreator.userPill(text, completionId), this._partCreator.plain(": ")];
                 }
             }
             case "#":
-                return this._partCreator.roomPill(completionId);
+                return [this._partCreator.roomPill(completionId)];
             // used for emoji and command completion replacement
             default:
-                return this._partCreator.plain(text);
+                return [this._partCreator.plain(text)];
         }
     }
 }
diff --git a/src/editor/model.js b/src/editor/model.js
index 59371cc3e6..4b8405adef 100644
--- a/src/editor/model.js
+++ b/src/editor/model.js
@@ -47,6 +47,7 @@ export default class EditorModel {
         this._activePartIdx = null;
         this._autoComplete = null;
         this._autoCompletePartIdx = null;
+        this._autoCompletePartCount = 0;
         this._transformCallback = null;
         this.setUpdateCallback(updateCallback);
     }
@@ -219,6 +220,7 @@ export default class EditorModel {
                         // make sure that react picks up the difference between both acs
                         this._autoComplete = ac;
                         this._autoCompletePartIdx = index;
+                        this._autoCompletePartCount = 1;
                     }
                 }
             }
@@ -230,23 +232,24 @@ export default class EditorModel {
             this._activePartIdx = null;
             this._autoComplete = null;
             this._autoCompletePartIdx = null;
+            this._autoCompletePartCount = 0;
         }
         return Promise.resolve();
     }
 
-    _onAutoComplete = ({replacePart, caretOffset, close}) => {
+    _onAutoComplete = ({replaceParts, close}) => {
         let pos;
-        if (replacePart) {
-            this._replacePart(this._autoCompletePartIdx, replacePart);
-            const index = this._autoCompletePartIdx;
-            if (caretOffset === undefined) {
-                caretOffset = replacePart.text.length;
-            }
-            pos = new DocumentPosition(index, caretOffset);
+        if (replaceParts) {
+            this._parts.splice(this._autoCompletePartIdx, this._autoCompletePartCount, ...replaceParts);
+            this._autoCompletePartCount = replaceParts.length;
+            const lastPart = replaceParts[replaceParts.length - 1];
+            const lastPartIndex = this._autoCompletePartIdx + replaceParts.length - 1;
+            pos = new DocumentPosition(lastPartIndex, lastPart.text.length);
         }
         if (close) {
             this._autoComplete = null;
             this._autoCompletePartIdx = null;
+            this._autoCompletePartCount = 0;
         }
         // rerender even if editor contents didn't change
         // to make sure the MessageEditor checks