code.gitea.io/gitea@v1.19.3/modules/emoji/emoji.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // Copyright 2015 Kenneth Shaw 3 // SPDX-License-Identifier: MIT 4 5 package emoji 6 7 import ( 8 "io" 9 "sort" 10 "strings" 11 "sync" 12 ) 13 14 // Gemoji is a set of emoji data. 15 type Gemoji []Emoji 16 17 // Emoji represents a single emoji and associated data. 18 type Emoji struct { 19 Emoji string 20 Description string 21 Aliases []string 22 UnicodeVersion string 23 SkinTones bool 24 } 25 26 var ( 27 // codeMap provides a map of the emoji unicode code to its emoji data. 28 codeMap map[string]int 29 30 // aliasMap provides a map of the alias to its emoji data. 31 aliasMap map[string]int 32 33 // emptyReplacer is the string replacer for emoji codes. 34 emptyReplacer *strings.Replacer 35 36 // codeReplacer is the string replacer for emoji codes. 37 codeReplacer *strings.Replacer 38 39 // aliasReplacer is the string replacer for emoji aliases. 40 aliasReplacer *strings.Replacer 41 42 once sync.Once 43 ) 44 45 func loadMap() { 46 once.Do(func() { 47 // initialize 48 codeMap = make(map[string]int, len(GemojiData)) 49 aliasMap = make(map[string]int, len(GemojiData)) 50 51 // process emoji codes and aliases 52 codePairs := make([]string, 0) 53 emptyPairs := make([]string, 0) 54 aliasPairs := make([]string, 0) 55 56 // sort from largest to small so we match combined emoji first 57 sort.Slice(GemojiData, func(i, j int) bool { 58 return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji) 59 }) 60 61 for i, e := range GemojiData { 62 if e.Emoji == "" || len(e.Aliases) == 0 { 63 continue 64 } 65 66 // setup codes 67 codeMap[e.Emoji] = i 68 codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":") 69 emptyPairs = append(emptyPairs, e.Emoji, e.Emoji) 70 71 // setup aliases 72 for _, a := range e.Aliases { 73 if a == "" { 74 continue 75 } 76 77 aliasMap[a] = i 78 aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji) 79 } 80 } 81 82 // create replacers 83 emptyReplacer = strings.NewReplacer(emptyPairs...) 84 codeReplacer = strings.NewReplacer(codePairs...) 85 aliasReplacer = strings.NewReplacer(aliasPairs...) 86 }) 87 } 88 89 // FromCode retrieves the emoji data based on the provided unicode code (ie, 90 // "\u2618" will return the Gemoji data for "shamrock"). 91 func FromCode(code string) *Emoji { 92 loadMap() 93 i, ok := codeMap[code] 94 if !ok { 95 return nil 96 } 97 98 return &GemojiData[i] 99 } 100 101 // FromAlias retrieves the emoji data based on the provided alias in the form 102 // "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji 103 // data for "shamrock"). 104 func FromAlias(alias string) *Emoji { 105 loadMap() 106 if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") { 107 alias = alias[1 : len(alias)-1] 108 } 109 110 i, ok := aliasMap[alias] 111 if !ok { 112 return nil 113 } 114 115 return &GemojiData[i] 116 } 117 118 // ReplaceCodes replaces all emoji codes with the first corresponding emoji 119 // alias (in the form of ":alias:") (ie, "\u2618" will be converted to 120 // ":shamrock:"). 121 func ReplaceCodes(s string) string { 122 loadMap() 123 return codeReplacer.Replace(s) 124 } 125 126 // ReplaceAliases replaces all aliases of the form ":alias:" with its 127 // corresponding unicode value. 128 func ReplaceAliases(s string) string { 129 loadMap() 130 return aliasReplacer.Replace(s) 131 } 132 133 type rememberSecondWriteWriter struct { 134 pos int 135 idx int 136 end int 137 writecount int 138 } 139 140 func (n *rememberSecondWriteWriter) Write(p []byte) (int, error) { 141 n.writecount++ 142 if n.writecount == 2 { 143 n.idx = n.pos 144 n.end = n.pos + len(p) 145 n.pos += len(p) 146 return len(p), io.EOF 147 } 148 n.pos += len(p) 149 return len(p), nil 150 } 151 152 func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) { 153 n.writecount++ 154 if n.writecount == 2 { 155 n.idx = n.pos 156 n.end = n.pos + len(s) 157 n.pos += len(s) 158 return len(s), io.EOF 159 } 160 n.pos += len(s) 161 return len(s), nil 162 } 163 164 // FindEmojiSubmatchIndex returns index pair of longest emoji in a string 165 func FindEmojiSubmatchIndex(s string) []int { 166 loadMap() 167 secondWriteWriter := rememberSecondWriteWriter{} 168 169 // A faster and clean implementation would copy the trie tree formation in strings.NewReplacer but 170 // we can be lazy here. 171 // 172 // The implementation of strings.Replacer.WriteString is such that the first index of the emoji 173 // submatch is simply the second thing that is written to WriteString in the writer. 174 // 175 // Therefore we can simply take the index of the second write as our first emoji 176 // 177 // FIXME: just copy the trie implementation from strings.NewReplacer 178 _, _ = emptyReplacer.WriteString(&secondWriteWriter, s) 179 180 // if we wrote less than twice then we never "replaced" 181 if secondWriteWriter.writecount < 2 { 182 return nil 183 } 184 185 return []int{secondWriteWriter.idx, secondWriteWriter.end} 186 }