Skip to content

Commit

Permalink
wip emoji: Rank "popular" > custom > other emoji; TODO test
Browse files Browse the repository at this point in the history
  • Loading branch information
gnprice committed Dec 8, 2024
1 parent 5dba1dc commit e8ae668
Showing 1 changed file with 33 additions and 6 deletions.
39 changes: 33 additions & 6 deletions lib/model/emoji.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,17 @@ class EmojiStoreImpl with EmojiStore {
/// retrieving the data.
Map<String, List<String>>? _serverEmojiData;

static final _popularEmojiCodes = (() {
assert(zulipPopularEmojis.every((c) =>
c.emojiType == ReactionType.unicodeEmoji));
return Set.of(zulipPopularEmojis.map((c) => c.emojiCode));
})();

static bool _isPopularEmoji(EmojiCandidate candidate) {
return candidate.emojiType == ReactionType.unicodeEmoji
&& _popularEmojiCodes.contains(candidate.emojiCode);
}

List<EmojiCandidate>? _allEmojiCandidates;

EmojiCandidate _emojiCandidateFor({
Expand Down Expand Up @@ -376,21 +387,28 @@ class EmojiAutocompleteView extends AutocompleteView<EmojiAutocompleteQuery, Emo
EmojiAutocompleteResult? _testCandidate(EmojiAutocompleteQuery query, EmojiCandidate candidate) {
final match = query.match(candidate);
if (match == null) return null;
return EmojiAutocompleteResult(_rankResult(match), candidate);
return EmojiAutocompleteResult(_rankResult(match, candidate), candidate);
}

/// A measure of the result's quality in the context of the query,
/// ranked from 0 (best) to one less than [_numResultRanks].
static int _rankResult(EmojiMatchQuality matchQuality) {
static int _rankResult(EmojiMatchQuality matchQuality, EmojiCandidate candidate) {
// Compare sort_emojis in Zulip web:shared/src/typeahead.ts .
//
// Behavior differences we should or might copy, TODO(#1068):
// * Web ranks popular emoji > custom emoji > others; we don't yet.
// * Web ranks matches starting at a word boundary ahead of
// other non-prefix matches; we don't yet.
// * Relatedly, web favors popular emoji only upon a word-aligned match.
// * Web ranks each name of a Unicode emoji separately.
//
// Behavior differences that web should probably fix, TODO(web):
// * Among popular emoji with non-exact matches,
// web doesn't prioritize prefix over word-aligned; we do.
// (This affects just one case: for query "o",
// we put :octopus: before :working_on_it:.)
// * Web only counts an emoji as "popular" for ranking if the query
// is a prefix of a single word in the name; so "thumbs_" or "working_on_i"
// lose the ranking boost for :thumbs_up: and :working_on_it: respectively.
// * Web starts with only case-sensitive exact matches ("perfect matches"),
// and puts case-insensitive exact matches just ahead of prefix matches;
// it also distinguishes prefix matches by case-sensitive vs. not.
Expand All @@ -401,15 +419,24 @@ class EmojiAutocompleteView extends AutocompleteView<EmojiAutocompleteQuery, Emo
// because emoji with the same name will mostly both match or both not;
// but it breaks if the Unicode emoji was a literal match.

final isPopular = EmojiStoreImpl._isPopularEmoji(candidate);
final isCustomEmoji = switch (candidate.emojiType) {
// The web implementation calls this condition `is_realm_emoji`,
// but its actual semantics is it's true for the Zulip extra emoji too.
// See `zulip_emoji` in web:src/emoji.ts .
ReactionType.realmEmoji || ReactionType.zulipExtraEmoji => true,
ReactionType.unicodeEmoji => false,
};
return switch (matchQuality) {
EmojiMatchQuality.exact => 0,
EmojiMatchQuality.prefix => 1,
EmojiMatchQuality.other => 2,
EmojiMatchQuality.prefix => isPopular ? 1 : isCustomEmoji ? 3 : 4,
// TODO word-boundary vs. not
EmojiMatchQuality.other => isPopular ? 2 : isCustomEmoji ? 5 : 6,
};
}

/// The number of possible values returned by [_rankResult].
static const _numResultRanks = 3;
static const _numResultRanks = 7;
}

class EmojiAutocompleteQuery extends ComposeAutocompleteQuery {
Expand Down

0 comments on commit e8ae668

Please sign in to comment.