Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit bd2917a

Browse files
authored
Add a limit option for autocomplete results (#6016)
1 parent eea6ba6 commit bd2917a

File tree

11 files changed

+75
-20
lines changed

11 files changed

+75
-20
lines changed

src/autocomplete/AutocompleteProvider.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,12 @@ export default class AutocompleteProvider {
9393
};
9494
}
9595

96-
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
96+
async getCompletions(
97+
query: string,
98+
selection: ISelectionRange,
99+
force = false,
100+
limit = -1,
101+
): Promise<ICompletion[]> {
97102
return [];
98103
}
99104

src/autocomplete/Autocompleter.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,24 @@ export default class Autocompleter {
8282
});
8383
}
8484

85-
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<IProviderCompletions[]> {
85+
async getCompletions(
86+
query: string,
87+
selection: ISelectionRange,
88+
force = false,
89+
limit = -1,
90+
): Promise<IProviderCompletions[]> {
8691
/* Note: This intentionally waits for all providers to return,
8792
otherwise, we run into a condition where new completions are displayed
8893
while the user is interacting with the list, which makes it difficult
8994
to predict whether an action will actually do what is intended
9095
*/
9196
// list of results from each provider, each being a list of completions or null if it times out
92-
const completionsList: ICompletion[][] = await Promise.all(this.providers.map(provider => {
93-
return timeout(provider.getCompletions(query, selection, force), null, PROVIDER_COMPLETION_TIMEOUT);
97+
const completionsList: ICompletion[][] = await Promise.all(this.providers.map(async provider => {
98+
return await timeout(
99+
provider.getCompletions(query, selection, force, limit),
100+
null,
101+
PROVIDER_COMPLETION_TIMEOUT,
102+
);
94103
}));
95104

96105
// map then filter to maintain the index for the map-operation, for this.providers to line up

src/autocomplete/CommandProvider.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ export default class CommandProvider extends AutocompleteProvider {
3838
});
3939
}
4040

41-
async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise<ICompletion[]> {
41+
async getCompletions(
42+
query: string,
43+
selection: ISelectionRange,
44+
force?: boolean,
45+
limit = -1,
46+
): Promise<ICompletion[]> {
4247
const {command, range} = this.getCurrentCommand(query, selection);
4348
if (!command) return [];
4449

@@ -55,10 +60,11 @@ export default class CommandProvider extends AutocompleteProvider {
5560
} else {
5661
if (query === '/') {
5762
// If they have just entered `/` show everything
63+
// We exclude the limit on purpose to have a comprehensive list
5864
matches = Commands;
5965
} else {
6066
// otherwise fuzzy match against all of the fields
61-
matches = this.matcher.match(command[1]);
67+
matches = this.matcher.match(command[1], limit);
6268
}
6369
}
6470

src/autocomplete/CommunityProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ export default class CommunityProvider extends AutocompleteProvider {
5050
});
5151
}
5252

53-
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
53+
async getCompletions(
54+
query: string,
55+
selection: ISelectionRange,
56+
force = false,
57+
limit = -1,
58+
): Promise<ICompletion[]> {
5459
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
5560

5661
// Disable autocompletions when composing commands because of various issues
@@ -81,7 +86,7 @@ export default class CommunityProvider extends AutocompleteProvider {
8186
this.matcher.setObjects(groups);
8287

8388
const matchedString = command[0];
84-
completions = this.matcher.match(matchedString);
89+
completions = this.matcher.match(matchedString, limit);
8590
completions = sortBy(completions, [
8691
(c) => score(matchedString, c.groupId),
8792
(c) => c.groupId.length,

src/autocomplete/DuckDuckGoProvider.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
3636
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
3737
}
3838

39-
async getCompletions(query: string, selection: ISelectionRange, force= false): Promise<ICompletion[]> {
39+
async getCompletions(
40+
query: string,
41+
selection: ISelectionRange,
42+
force = false,
43+
limit = -1,
44+
): Promise<ICompletion[]> {
4045
const {command, range} = this.getCurrentCommand(query, selection);
4146
if (!query || !command) {
4247
return [];
@@ -46,7 +51,8 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
4651
method: 'GET',
4752
});
4853
const json = await response.json();
49-
const results = json.Results.map((result) => {
54+
const maxLength = limit > -1 ? limit : json.Results.length;
55+
const results = json.Results.slice(0, maxLength).map((result) => {
5056
return {
5157
completion: result.Text,
5258
component: (

src/autocomplete/EmojiProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,12 @@ export default class EmojiProvider extends AutocompleteProvider {
8484
});
8585
}
8686

87-
async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise<ICompletion[]> {
87+
async getCompletions(
88+
query: string,
89+
selection: ISelectionRange,
90+
force?: boolean,
91+
limit = -1,
92+
): Promise<ICompletion[]> {
8893
if (!SettingsStore.getValue("MessageComposerInput.suggestEmoji")) {
8994
return []; // don't give any suggestions if the user doesn't want them
9095
}
@@ -93,7 +98,7 @@ export default class EmojiProvider extends AutocompleteProvider {
9398
const {command, range} = this.getCurrentCommand(query, selection);
9499
if (command) {
95100
const matchedString = command[0];
96-
completions = this.matcher.match(matchedString);
101+
completions = this.matcher.match(matchedString, limit);
97102

98103
// Do second match with shouldMatchWordsOnly in order to match against 'name'
99104
completions = completions.concat(this.nameMatcher.match(matchedString));

src/autocomplete/NotifProvider.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ export default class NotifProvider extends AutocompleteProvider {
3333
this.room = room;
3434
}
3535

36-
async getCompletions(query: string, selection: ISelectionRange, force= false): Promise<ICompletion[]> {
36+
async getCompletions(
37+
query: string,
38+
selection: ISelectionRange,
39+
force = false,
40+
limit = -1,
41+
): Promise<ICompletion[]> {
3742
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
3843

3944
const client = MatrixClientPeg.get();

src/autocomplete/QueryMatcher.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export default class QueryMatcher<T extends Object> {
8787
}
8888
}
8989

90-
match(query: string): T[] {
90+
match(query: string, limit = -1): T[] {
9191
query = this.processQuery(query);
9292
if (this._options.shouldMatchWordsOnly) {
9393
query = query.replace(/[^\w]/g, '');
@@ -129,7 +129,10 @@ export default class QueryMatcher<T extends Object> {
129129
});
130130

131131
// Now map the keys to the result objects. Also remove any duplicates.
132-
return uniq(matches.map((match) => match.object));
132+
const dedupped = uniq(matches.map((match) => match.object));
133+
const maxLength = limit === -1 ? dedupped.length : limit;
134+
135+
return dedupped.slice(0, maxLength);
133136
}
134137

135138
private processQuery(query: string): string {

src/autocomplete/RoomProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ export default class RoomProvider extends AutocompleteProvider {
5858
});
5959
}
6060

61-
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
61+
async getCompletions(
62+
query: string,
63+
selection: ISelectionRange,
64+
force = false,
65+
limit = -1,
66+
): Promise<ICompletion[]> {
6267
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
6368

6469
const client = MatrixClientPeg.get();
@@ -90,7 +95,7 @@ export default class RoomProvider extends AutocompleteProvider {
9095

9196
this.matcher.setObjects(matcherObjects);
9297
const matchedString = command[0];
93-
completions = this.matcher.match(matchedString);
98+
completions = this.matcher.match(matchedString, limit);
9499
completions = sortBy(completions, [
95100
(c) => score(matchedString, c.displayedAlias),
96101
(c) => c.displayedAlias.length,

src/autocomplete/UserProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,12 @@ export default class UserProvider extends AutocompleteProvider {
102102
this.users = null;
103103
};
104104

105-
async getCompletions(rawQuery: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
105+
async getCompletions(
106+
rawQuery: string,
107+
selection: ISelectionRange,
108+
force = false,
109+
limit = -1,
110+
): Promise<ICompletion[]> {
106111
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
107112

108113
// lazy-load user list into matcher
@@ -118,7 +123,7 @@ export default class UserProvider extends AutocompleteProvider {
118123
if (fullMatch && fullMatch !== '@') {
119124
// Don't include the '@' in our search query - it's only used as a way to trigger completion
120125
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
121-
completions = this.matcher.match(query).map((user) => {
126+
completions = this.matcher.match(query, limit).map((user) => {
122127
const displayName = (user.name || user.userId || '');
123128
return {
124129
// Length of completion should equal length of text in decorator. draft-js

0 commit comments

Comments
 (0)