table.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package unicode_input
  3. import (
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "kitty/tools/unicode_names"
  8. "kitty/tools/utils"
  9. "kitty/tools/utils/style"
  10. "kitty/tools/wcswidth"
  11. "golang.org/x/exp/slices"
  12. )
  13. var _ = fmt.Print
  14. func resolved_char(ch rune, emoji_variation string) string {
  15. ans := string(ch)
  16. if wcswidth.IsEmojiPresentationBase(ch) {
  17. switch emoji_variation {
  18. case "text":
  19. ans += "\ufe0e"
  20. case "graphic":
  21. ans += "\ufe0f"
  22. }
  23. }
  24. return ans
  25. }
  26. func decode_hint(text string) int {
  27. x, err := strconv.ParseUint(text, INDEX_BASE, 32)
  28. if err != nil {
  29. return -1
  30. }
  31. return int(x)
  32. }
  33. func encode_hint(num int) string {
  34. return strconv.FormatUint(uint64(num), INDEX_BASE)
  35. }
  36. func ljust(s string, sz int) string {
  37. x := wcswidth.Stringwidth(s)
  38. if x < sz {
  39. s += strings.Repeat(" ", sz-x)
  40. }
  41. return s
  42. }
  43. type scroll_data struct {
  44. num_items_per_page int
  45. scroll_rows int
  46. }
  47. type table struct {
  48. emoji_variation string
  49. layout_dirty bool
  50. last_rows, last_cols int
  51. codepoints []rune
  52. current_idx int
  53. scroll_data scroll_data
  54. text string
  55. num_cols, num_rows int
  56. mode Mode
  57. green, reversed, intense_gray func(...any) string
  58. }
  59. func (self *table) initialize(emoji_variation string, ctx style.Context) {
  60. self.emoji_variation = emoji_variation
  61. self.layout_dirty = true
  62. self.last_cols, self.last_rows = -1, -1
  63. self.green = ctx.SprintFunc("fg=green")
  64. self.reversed = ctx.SprintFunc("reverse=true")
  65. self.intense_gray = ctx.SprintFunc("fg=intense-gray")
  66. }
  67. func (self *table) current_codepoint() rune {
  68. if len(self.codepoints) > 0 {
  69. return self.codepoints[self.current_idx]
  70. }
  71. return InvalidChar
  72. }
  73. func (self *table) set_codepoints(codepoints []rune, mode Mode, current_idx int) {
  74. delta := len(codepoints) - len(self.codepoints)
  75. self.codepoints = codepoints
  76. if self.codepoints != nil && mode != FAVORITES && mode != HEX {
  77. slices.Sort(self.codepoints)
  78. }
  79. self.mode = mode
  80. self.layout_dirty = true
  81. if current_idx > -1 && current_idx < len(self.codepoints) {
  82. self.current_idx = current_idx
  83. }
  84. if self.current_idx >= len(self.codepoints) {
  85. self.current_idx = 0
  86. }
  87. if delta != 0 {
  88. self.scroll_data = scroll_data{}
  89. }
  90. }
  91. func (self *table) codepoint_at_hint(hint string) rune {
  92. idx := decode_hint(hint)
  93. if idx >= 0 && idx < len(self.codepoints) {
  94. return self.codepoints[idx]
  95. }
  96. return InvalidChar
  97. }
  98. type cell_data struct {
  99. idx, ch, desc string
  100. }
  101. func title(x string) string {
  102. if len(x) > 1 {
  103. x = strings.ToUpper(x[:1]) + x[1:]
  104. }
  105. return x
  106. }
  107. func (self *table) layout(rows, cols int) string {
  108. if !self.layout_dirty && self.last_cols == cols && self.last_rows == rows {
  109. return self.text
  110. }
  111. self.last_cols, self.last_rows = cols, rows
  112. self.layout_dirty = false
  113. var as_parts func(int, rune) cell_data
  114. var cell func(int, cell_data)
  115. var idx_size, space_for_desc int
  116. output := strings.Builder{}
  117. output.Grow(4096)
  118. switch self.mode {
  119. case NAME:
  120. as_parts = func(i int, codepoint rune) cell_data {
  121. return cell_data{idx: ljust(encode_hint(i), idx_size), ch: resolved_char(codepoint, self.emoji_variation), desc: title(unicode_names.NameForCodePoint(codepoint))}
  122. }
  123. cell = func(i int, cd cell_data) {
  124. is_current := i == self.current_idx
  125. text := self.green(cd.idx) + " " + cd.ch + " "
  126. w := wcswidth.Stringwidth(cd.ch)
  127. if w < 2 {
  128. text += strings.Repeat(" ", (2 - w))
  129. }
  130. desc_width := wcswidth.Stringwidth(cd.desc)
  131. if desc_width > space_for_desc {
  132. text += cd.desc[:space_for_desc-1] + "…"
  133. } else {
  134. text += cd.desc
  135. extra := space_for_desc - desc_width
  136. if extra > 0 {
  137. text += strings.Repeat(" ", extra)
  138. }
  139. }
  140. if is_current {
  141. text = self.reversed(text)
  142. }
  143. output.WriteString(text)
  144. }
  145. default:
  146. as_parts = func(i int, codepoint rune) cell_data {
  147. return cell_data{idx: ljust(encode_hint(i), idx_size), ch: resolved_char(codepoint, self.emoji_variation)}
  148. }
  149. cell = func(i int, cd cell_data) {
  150. output.WriteString(self.green(cd.idx))
  151. output.WriteString(" ")
  152. output.WriteString(self.intense_gray(cd.ch))
  153. w := wcswidth.Stringwidth(cd.ch)
  154. if w < 2 {
  155. output.WriteString(strings.Repeat(" ", (2 - w)))
  156. }
  157. }
  158. }
  159. num := len(self.codepoints)
  160. if num < 1 {
  161. self.text = ""
  162. self.num_cols = 0
  163. self.num_rows = 0
  164. return self.text
  165. }
  166. idx_size = len(encode_hint(num - 1))
  167. parts := make([]cell_data, len(self.codepoints))
  168. for i, ch := range self.codepoints {
  169. parts[i] = as_parts(i, ch)
  170. }
  171. longest := 0
  172. switch self.mode {
  173. case NAME:
  174. for _, p := range parts {
  175. longest = utils.Max(longest, idx_size+2+len(p.desc)+2)
  176. }
  177. default:
  178. longest = idx_size + 3
  179. }
  180. col_width := longest + 2
  181. col_width = utils.Min(col_width, 40)
  182. self.num_cols = utils.Max(cols/col_width, 1)
  183. if self.num_cols == 1 {
  184. col_width = cols
  185. }
  186. space_for_desc = col_width - 2 - idx_size - 4
  187. self.num_rows = rows
  188. rows_left := rows
  189. if self.scroll_data.num_items_per_page != self.num_cols*self.num_rows {
  190. self.update_scroll_data()
  191. }
  192. skip_scroll := self.scroll_data.scroll_rows * self.num_cols
  193. for i, cd := range parts {
  194. if skip_scroll > 0 {
  195. skip_scroll -= 1
  196. continue
  197. }
  198. cell(i, cd)
  199. output.WriteString(" ")
  200. if self.num_cols == 1 || (i > 0 && (i+1)%self.num_cols == 0) {
  201. rows_left -= 1
  202. if rows_left == 0 {
  203. break
  204. }
  205. output.WriteString("\r\n")
  206. }
  207. }
  208. self.text = output.String()
  209. return self.text
  210. }
  211. func (self *table) update_scroll_data() {
  212. self.scroll_data.num_items_per_page = self.num_rows * self.num_cols
  213. page_num := self.current_idx / self.scroll_data.num_items_per_page
  214. self.scroll_data.scroll_rows = self.num_rows * page_num
  215. }
  216. func (self *table) move_current(rows, cols int) {
  217. if len(self.codepoints) == 0 {
  218. return
  219. }
  220. if cols != 0 {
  221. self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
  222. self.layout_dirty = true
  223. }
  224. if rows != 0 {
  225. amt := rows * self.num_cols
  226. self.current_idx += amt
  227. self.current_idx = utils.Max(0, utils.Min(self.current_idx, len(self.codepoints)-1))
  228. self.layout_dirty = true
  229. }
  230. self.update_scroll_data()
  231. }