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>
This commit is contained in:
Michael Telatynski 2024-11-15 09:11:03 +00:00 committed by GitHub
parent d36cfc37e2
commit ae3ca52bd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 17 additions and 5 deletions

View file

@ -37,7 +37,7 @@ const USER_REGEX = /\B@\S*/g;
// used when you hit 'tab' - we allow some separator chars at the beginning // used when you hit 'tab' - we allow some separator chars at the beginning
// to allow you to tab-complete /mat into /(matthew) // 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 { export default class UserProvider extends AutocompleteProvider {
public matcher: QueryMatcher<RoomMember>; public matcher: QueryMatcher<RoomMember>;

View file

@ -10,11 +10,12 @@ import { KeyboardEvent } from "react";
import { Part, CommandPartCreator, PartCreator } from "./parts"; import { Part, CommandPartCreator, PartCreator } from "./parts";
import DocumentPosition from "./position"; import DocumentPosition from "./position";
import { ICompletion } from "../autocomplete/Autocompleter"; import { ICompletion, ISelectionRange } from "../autocomplete/Autocompleter";
import Autocomplete from "../components/views/rooms/Autocomplete"; import Autocomplete from "../components/views/rooms/Autocomplete";
export interface ICallback { export interface ICallback {
replaceParts?: Part[]; replaceParts?: Part[];
range?: ISelectionRange;
close?: boolean; close?: boolean;
} }
@ -82,6 +83,7 @@ export default class AutocompleteWrapperModel {
this.updateCallback({ this.updateCallback({
replaceParts: this.partForCompletion(completion), replaceParts: this.partForCompletion(completion),
close: true, close: true,
range: completion.range,
}); });
} }

View file

@ -250,14 +250,24 @@ export default class EditorModel {
return Promise.resolve(); return Promise.resolve();
} }
private onAutoComplete = ({ replaceParts, close }: ICallback): void => { private onAutoComplete = ({ replaceParts, close, range }: ICallback): void => {
let pos: DocumentPosition | undefined; let pos: DocumentPosition | undefined;
if (replaceParts) { if (replaceParts) {
const autoCompletePartIdx = this.autoCompletePartIdx || 0; 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; this.autoCompletePartCount = replaceParts.length;
const lastPart = replaceParts[replaceParts.length - 1]; 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); pos = new DocumentPosition(lastPartIndex, lastPart.text.length);
} }
if (close) { if (close) {