mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
189 lines
5.0 KiB
Go
189 lines
5.0 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// Copyright 2015 Kenneth Shaw
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package emoji
|
|
|
|
import (
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
"github.com/hanzoai/git/modules/setting"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
type globalVarsStruct struct {
|
|
codeMap map[string]int // emoji unicode code to its emoji data.
|
|
aliasMap map[string]int // the alias to its emoji data.
|
|
emptyReplacer *strings.Replacer // string replacer for emoji codes, used for finding emoji positions.
|
|
codeReplacer *strings.Replacer // string replacer for emoji codes.
|
|
aliasReplacer *strings.Replacer // string replacer for emoji aliases.
|
|
}
|
|
|
|
var globalVarsStore atomic.Pointer[globalVarsStruct]
|
|
|
|
func globalVars() *globalVarsStruct {
|
|
vars := globalVarsStore.Load()
|
|
if vars != nil {
|
|
return vars
|
|
}
|
|
// although there can be concurrent calls, the result should be the same, and there is no performance problem
|
|
vars = &globalVarsStruct{}
|
|
vars.codeMap = make(map[string]int, len(GemojiData))
|
|
vars.aliasMap = make(map[string]int, len(GemojiData))
|
|
|
|
// process emoji codes and aliases
|
|
codePairs := make([]string, 0)
|
|
emptyPairs := 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 idx, emoji := range GemojiData {
|
|
if emoji.Emoji == "" || len(emoji.Aliases) == 0 {
|
|
continue
|
|
}
|
|
|
|
// process aliases
|
|
firstAlias := ""
|
|
for _, alias := range emoji.Aliases {
|
|
if alias == "" {
|
|
continue
|
|
}
|
|
enabled := len(setting.UI.EnabledEmojisSet) == 0 || setting.UI.EnabledEmojisSet.Contains(alias)
|
|
if !enabled {
|
|
continue
|
|
}
|
|
if firstAlias == "" {
|
|
firstAlias = alias
|
|
}
|
|
vars.aliasMap[alias] = idx
|
|
aliasPairs = append(aliasPairs, ":"+alias+":", emoji.Emoji)
|
|
}
|
|
|
|
// process emoji code
|
|
if firstAlias != "" {
|
|
vars.codeMap[emoji.Emoji] = idx
|
|
codePairs = append(codePairs, emoji.Emoji, ":"+emoji.Aliases[0]+":")
|
|
emptyPairs = append(emptyPairs, emoji.Emoji, emoji.Emoji)
|
|
}
|
|
}
|
|
|
|
// create replacers
|
|
vars.emptyReplacer = strings.NewReplacer(emptyPairs...)
|
|
vars.codeReplacer = strings.NewReplacer(codePairs...)
|
|
vars.aliasReplacer = strings.NewReplacer(aliasPairs...)
|
|
globalVarsStore.Store(vars)
|
|
return vars
|
|
}
|
|
|
|
// 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 {
|
|
i, ok := globalVars().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 {
|
|
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
|
|
alias = alias[1 : len(alias)-1]
|
|
}
|
|
|
|
i, ok := globalVars().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 {
|
|
return globalVars().codeReplacer.Replace(s)
|
|
}
|
|
|
|
// ReplaceAliases replaces all aliases of the form ":alias:" with its
|
|
// corresponding unicode value.
|
|
func ReplaceAliases(s string) string {
|
|
return globalVars().aliasReplacer.Replace(s)
|
|
}
|
|
|
|
type rememberSecondWriteWriter struct {
|
|
pos int
|
|
idx int
|
|
end int
|
|
writecount int
|
|
}
|
|
|
|
func (n *rememberSecondWriteWriter) Write(p []byte) (int, error) {
|
|
n.writecount++
|
|
if n.writecount == 2 {
|
|
n.idx = n.pos
|
|
n.end = n.pos + len(p)
|
|
n.pos += len(p)
|
|
return len(p), io.EOF
|
|
}
|
|
n.pos += len(p)
|
|
return len(p), nil
|
|
}
|
|
|
|
func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) {
|
|
n.writecount++
|
|
if n.writecount == 2 {
|
|
n.idx = n.pos
|
|
n.end = n.pos + len(s)
|
|
n.pos += len(s)
|
|
return len(s), io.EOF
|
|
}
|
|
n.pos += len(s)
|
|
return len(s), nil
|
|
}
|
|
|
|
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
|
|
func FindEmojiSubmatchIndex(s string) []int {
|
|
secondWriteWriter := rememberSecondWriteWriter{}
|
|
|
|
// A faster and clean implementation would copy the trie tree formation in strings.NewReplacer but
|
|
// we can be lazy here.
|
|
//
|
|
// The implementation of strings.Replacer.WriteString is such that the first index of the emoji
|
|
// submatch is simply the second thing that is written to WriteString in the writer.
|
|
//
|
|
// Therefore we can simply take the index of the second write as our first emoji
|
|
//
|
|
// FIXME: just copy the trie implementation from strings.NewReplacer
|
|
_, _ = globalVars().emptyReplacer.WriteString(&secondWriteWriter, s)
|
|
|
|
// if we wrote less than twice then we never "replaced"
|
|
if secondWriteWriter.writecount < 2 {
|
|
return nil
|
|
}
|
|
|
|
return []int{secondWriteWriter.idx, secondWriteWriter.end}
|
|
}
|