mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-24 18:56:11 +01:00
ea1ed802a3
* Fix emoji detection certain cases Previous tests weren't complicated enough so there were some situations where emojis were't detected properly. Find the earliest occurance in addition to checking for the longest combination. Fixes #12312 * ok spell bot Co-authored-by: Lauris BH <lauris@nix.lv>
165 lines
3.7 KiB
Go
165 lines
3.7 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// Copyright 2015 Kenneth Shaw
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package emoji
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Gemoji is a set of emoji data.
|
|
type Gemoji []Emoji
|
|
|
|
// Emoji represents a single emoji and associated data.
|
|
type Emoji struct {
|
|
Emoji string
|
|
Description string
|
|
Aliases []string
|
|
UnicodeVersion string
|
|
SkinTones bool
|
|
}
|
|
|
|
var (
|
|
// codeMap provides a map of the emoji unicode code to its emoji data.
|
|
codeMap map[string]int
|
|
|
|
// aliasMap provides a map of the alias to its emoji data.
|
|
aliasMap map[string]int
|
|
|
|
// codeReplacer is the string replacer for emoji codes.
|
|
codeReplacer *strings.Replacer
|
|
|
|
// aliasReplacer is the string replacer for emoji aliases.
|
|
aliasReplacer *strings.Replacer
|
|
|
|
once sync.Once
|
|
)
|
|
|
|
func loadMap() {
|
|
|
|
once.Do(func() {
|
|
|
|
// initialize
|
|
codeMap = make(map[string]int, len(GemojiData))
|
|
aliasMap = make(map[string]int, len(GemojiData))
|
|
|
|
// process emoji codes and aliases
|
|
codePairs := make([]string, 0)
|
|
aliasPairs := make([]string, 0)
|
|
|
|
// sort from largest to small so we match combined emoji first
|
|
sort.Slice(GemojiData, func(i, j int) bool {
|
|
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
|
|
})
|
|
|
|
for i, e := range GemojiData {
|
|
if e.Emoji == "" || len(e.Aliases) == 0 {
|
|
continue
|
|
}
|
|
|
|
// setup codes
|
|
codeMap[e.Emoji] = i
|
|
codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":")
|
|
|
|
// setup aliases
|
|
for _, a := range e.Aliases {
|
|
if a == "" {
|
|
continue
|
|
}
|
|
|
|
aliasMap[a] = i
|
|
aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
|
|
}
|
|
}
|
|
|
|
// create replacers
|
|
codeReplacer = strings.NewReplacer(codePairs...)
|
|
aliasReplacer = strings.NewReplacer(aliasPairs...)
|
|
})
|
|
|
|
}
|
|
|
|
// FromCode retrieves the emoji data based on the provided unicode code (ie,
|
|
// "\u2618" will return the Gemoji data for "shamrock").
|
|
func FromCode(code string) *Emoji {
|
|
loadMap()
|
|
i, ok := codeMap[code]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return &GemojiData[i]
|
|
}
|
|
|
|
// FromAlias retrieves the emoji data based on the provided alias in the form
|
|
// "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji
|
|
// data for "shamrock").
|
|
func FromAlias(alias string) *Emoji {
|
|
loadMap()
|
|
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
|
|
alias = alias[1 : len(alias)-1]
|
|
}
|
|
|
|
i, ok := aliasMap[alias]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return &GemojiData[i]
|
|
}
|
|
|
|
// ReplaceCodes replaces all emoji codes with the first corresponding emoji
|
|
// alias (in the form of ":alias:") (ie, "\u2618" will be converted to
|
|
// ":shamrock:").
|
|
func ReplaceCodes(s string) string {
|
|
loadMap()
|
|
return codeReplacer.Replace(s)
|
|
}
|
|
|
|
// ReplaceAliases replaces all aliases of the form ":alias:" with its
|
|
// corresponding unicode value.
|
|
func ReplaceAliases(s string) string {
|
|
loadMap()
|
|
return aliasReplacer.Replace(s)
|
|
}
|
|
|
|
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
|
|
func FindEmojiSubmatchIndex(s string) []int {
|
|
loadMap()
|
|
found := make(map[int]int)
|
|
keys := make([]int, 0)
|
|
|
|
//see if there are any emoji in string before looking for position of specific ones
|
|
//no performance difference when there is a match but 10x faster when there are not
|
|
if s == ReplaceCodes(s) {
|
|
return nil
|
|
}
|
|
|
|
// get index of first emoji occurrence while also checking for longest combination
|
|
for j := range GemojiData {
|
|
i := strings.Index(s, GemojiData[j].Emoji)
|
|
if i != -1 {
|
|
if _, ok := found[i]; !ok {
|
|
if len(keys) == 0 || i < keys[0] {
|
|
found[i] = j
|
|
keys = []int{i}
|
|
}
|
|
if i == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(keys) > 0 {
|
|
index := keys[0]
|
|
return []int{index, index + len(GemojiData[found[index]].Emoji)}
|
|
}
|
|
|
|
return nil
|
|
}
|