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 7, 2024
1 parent 5bed39f commit dc856ed
Showing 1 changed file with 50 additions and 6 deletions.
56 changes: 50 additions & 6 deletions lib/model/emoji.dart
Original file line number Diff line number Diff line change
Expand Up @@ -376,23 +376,67 @@ 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 .
// TODO(#1068): rank emoji results also by popular, realm, other
//
// Behavior differences we might copy, TODO:
// * 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.
// We use case-insensitive matches throughout;
// case seems unhelpful for emoji search.
// * Web suppresses Unicode emoji names shadowed by a realm emoji
// only if the latter is also a match for the query. That mostly works,
// 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 = _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;

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

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

class EmojiAutocompleteQuery extends ComposeAutocompleteQuery {
Expand Down

0 comments on commit dc856ed

Please sign in to comment.