snapshot of https://github.com/go-gitea/gitea.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
cache-seeder / prune (push) Has been skipped
cache-seeder / frontend (push) Failing after 3s
cache-seeder / python (push) Failing after 3s
cache-seeder / gobuild (push) Failing after 4s
cache-seeder / lint (bindata, lint-backend) (push) Failing after 3s
giteabot backport / giteabot (push) Has been skipped
giteabot / giteabot (push) Has been skipped
cache-prune / prune (push) Has been skipped
release-nightly-snapcraft / build-and-publish (ubuntu-24.04) (push) Has been cancelled
release-nightly-snapcraft / build-and-publish (ubuntu-24.04-arm) (push) Has been cancelled
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled
cron-renovate / cron-renovate (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:41:59 +05:00
commit 52ae0c5802
6280 changed files with 624193 additions and 0 deletions

146
modules/emoji/emoji.go Normal file
View File

@@ -0,0 +1,146 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2015 Kenneth Shaw
// SPDX-License-Identifier: MIT
package emoji
import (
"sort"
"strings"
"sync/atomic"
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
)
// 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.
trie *util.TrieNode // trie for finding emoji positions.
isStartingByte [256]bool // fast-path skip for starting bytes.
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))
vars.trie = &util.TrieNode{}
// 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 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]+":")
vars.trie.Insert(emoji.Emoji)
vars.isStartingByte[emoji.Emoji[0]] = true
}
}
// create replacers
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
// e.g.: "\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:"
// e.g.: "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:"
// e.g.: "\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)
}
// FindEmojiSubmatchIndex returns index-pair of the first emoji in a string
func FindEmojiSubmatchIndex(s string) []int {
vars := globalVars()
for i := 0; i < len(s); i++ {
if !vars.isStartingByte[s[i]] {
continue
}
if matchLen := vars.trie.Match(s, i); matchLen > 0 {
return []int{i, i + matchLen}
}
}
return nil
}

125
modules/emoji/emoji_test.go Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2015 Kenneth Shaw
// SPDX-License-Identifier: MIT
package emoji
import (
"testing"
"gitea.dev/modules/container"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
"github.com/stretchr/testify/assert"
)
func TestLookup(t *testing.T) {
a := FromCode("\U0001f37a")
b := FromCode("🍺")
c := FromAlias(":beer:")
d := FromAlias("beer")
assert.Equal(t, a, b)
assert.Equal(t, b, c)
assert.Equal(t, c, d)
m := FromCode("\U0001f44d")
n := FromAlias(":thumbsup:")
o := FromAlias("+1")
assert.Equal(t, m, n)
assert.Equal(t, m, o)
defer test.MockVariableValue(&setting.UI.EnabledEmojisSet, container.SetOf("thumbsup"))()
defer globalVarsStore.Store(nil)
globalVarsStore.Store(nil)
a = FromCode("\U0001f37a")
c = FromAlias(":beer:")
m = FromCode("\U0001f44d")
n = FromAlias(":thumbsup:")
o = FromAlias("+1")
assert.Nil(t, a)
assert.Nil(t, c)
assert.NotNil(t, m)
assert.NotNil(t, n)
assert.Nil(t, o)
}
func TestReplacers(t *testing.T) {
tests := []struct {
f func(string) string
v, exp string
}{
{ReplaceCodes, ":thumbsup: +1 for \U0001f37a! 🍺 \U0001f44d", ":thumbsup: +1 for :beer:! :beer: :+1:"},
{ReplaceAliases, ":thumbsup: +1 :+1: :beer:", "\U0001f44d +1 \U0001f44d \U0001f37a"},
}
for i, x := range tests {
s := x.f(x.v)
assert.Equalf(t, x.exp, s, "test %d `%s` expected `%s`, got: `%s`", i, x.v, x.exp, s)
}
}
const (
testInputWithEmojis = "This is a test string containing some emojis like \U0001f44d and \U0001f37a and some text in between."
testInputNoEmojis = "This is a test string containing no emojis at all, just plain old ASCII text, which should ideally be scanned very quickly by our trie implementation."
)
func TestFindEmojiSubmatchIndex(t *testing.T) {
type testcase struct {
input string
expected []int
}
testCases := []testcase{
{
"\U0001f44d",
[]int{0, len("\U0001f44d")},
},
{
"\U0001f44d +1 \U0001f44d \U0001f37a",
[]int{0, 4},
},
{
" \U0001f44d",
[]int{1, 1 + len("\U0001f44d")},
},
{
"\u0001\U0001f44d",
[]int{1, 1 + len("\U0001f44d")},
},
{
// This package can handle keycap emoji if it is registered in the emoji data.
// However, many other places (e.g.: markup rendering) also might not handle such cases correctly.
// For example: how is "**{U+FE0F}{U+20E3}**" rendered in Markdown/Markup?
"a 8\U0000fe0f\U000020e3 b", // keycap emoji "8\ufe0f\u20e3" in emoji data
[]int{2, 2 + len("8\U0000fe0f\U000020e3")},
},
{
testInputWithEmojis,
[]int{50, 54},
},
{
testInputNoEmojis,
nil,
},
}
for _, tc := range testCases {
actual := FindEmojiSubmatchIndex(tc.input)
assert.Equal(t, tc.expected, actual)
}
}
func BenchmarkFindEmojiSubmatchIndex(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = FindEmojiSubmatchIndex(testInputWithEmojis)
}
}
func BenchmarkFindEmojiSubmatchIndexNoMatch(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = FindEmojiSubmatchIndex(testInputNoEmojis)
}
}