From ae3ca52bd235bd4cce03c125f2cf1bc96cf0def2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 15 Nov 2024 09:11:03 +0000 Subject: [PATCH] Allow tab completing users in brackets (#28460) * Allow tab completing users in brackets Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Account for range offsets when tab completing to not replace unrelated characters Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/UserProvider.tsx | 2 +- src/editor/autocomplete.ts | 4 +++- src/editor/model.ts | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 18c93d0cd0..a8f50ceccb 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -37,7 +37,7 @@ const USER_REGEX = /\B@\S*/g; // used when you hit 'tab' - we allow some separator chars at the beginning // to allow you to tab-complete /mat into /(matthew) -const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g; +const FORCED_USER_REGEX = /[^/,.():; \t\n]\S*/g; export default class UserProvider extends AutocompleteProvider { public matcher: QueryMatcher; diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts index 28f86ddf77..542a2bbea5 100644 --- a/src/editor/autocomplete.ts +++ b/src/editor/autocomplete.ts @@ -10,11 +10,12 @@ import { KeyboardEvent } from "react"; import { Part, CommandPartCreator, PartCreator } from "./parts"; import DocumentPosition from "./position"; -import { ICompletion } from "../autocomplete/Autocompleter"; +import { ICompletion, ISelectionRange } from "../autocomplete/Autocompleter"; import Autocomplete from "../components/views/rooms/Autocomplete"; export interface ICallback { replaceParts?: Part[]; + range?: ISelectionRange; close?: boolean; } @@ -82,6 +83,7 @@ export default class AutocompleteWrapperModel { this.updateCallback({ replaceParts: this.partForCompletion(completion), close: true, + range: completion.range, }); } diff --git a/src/editor/model.ts b/src/editor/model.ts index 67b19a3999..efe294cd21 100644 --- a/src/editor/model.ts +++ b/src/editor/model.ts @@ -250,14 +250,24 @@ export default class EditorModel { return Promise.resolve(); } - private onAutoComplete = ({ replaceParts, close }: ICallback): void => { + private onAutoComplete = ({ replaceParts, close, range }: ICallback): void => { let pos: DocumentPosition | undefined; if (replaceParts) { const autoCompletePartIdx = this.autoCompletePartIdx || 0; - this._parts.splice(autoCompletePartIdx, this.autoCompletePartCount, ...replaceParts); + + this.replaceRange( + new DocumentPosition(autoCompletePartIdx, range?.start ?? 0), + new DocumentPosition( + autoCompletePartIdx + this.autoCompletePartCount - 1, + range?.end ?? this.parts[autoCompletePartIdx + this.autoCompletePartCount - 1].text.length, + ), + replaceParts, + ); + this.autoCompletePartCount = replaceParts.length; const lastPart = replaceParts[replaceParts.length - 1]; - const lastPartIndex = autoCompletePartIdx + replaceParts.length - 1; + // `replaceRange` merges adjacent parts so we need to find it in the new parts list + const lastPartIndex = this.parts.indexOf(lastPart); pos = new DocumentPosition(lastPartIndex, lastPart.text.length); } if (close) {