123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
- package unicode_input
- import (
- "fmt"
- "strconv"
- "strings"
- "kitty/tools/unicode_names"
- "kitty/tools/utils"
- "kitty/tools/utils/style"
- "kitty/tools/wcswidth"
- "golang.org/x/exp/slices"
- )
- var _ = fmt.Print
- func resolved_char(ch rune, emoji_variation string) string {
- ans := string(ch)
- if wcswidth.IsEmojiPresentationBase(ch) {
- switch emoji_variation {
- case "text":
- ans += "\ufe0e"
- case "graphic":
- ans += "\ufe0f"
- }
- }
- return ans
- }
- func decode_hint(text string) int {
- x, err := strconv.ParseUint(text, INDEX_BASE, 32)
- if err != nil {
- return -1
- }
- return int(x)
- }
- func encode_hint(num int) string {
- return strconv.FormatUint(uint64(num), INDEX_BASE)
- }
- func ljust(s string, sz int) string {
- x := wcswidth.Stringwidth(s)
- if x < sz {
- s += strings.Repeat(" ", sz-x)
- }
- return s
- }
- type scroll_data struct {
- num_items_per_page int
- scroll_rows int
- }
- type table struct {
- emoji_variation string
- layout_dirty bool
- last_rows, last_cols int
- codepoints []rune
- current_idx int
- scroll_data scroll_data
- text string
- num_cols, num_rows int
- mode Mode
- green, reversed, intense_gray func(...any) string
- }
- func (self *table) initialize(emoji_variation string, ctx style.Context) {
- self.emoji_variation = emoji_variation
- self.layout_dirty = true
- self.last_cols, self.last_rows = -1, -1
- self.green = ctx.SprintFunc("fg=green")
- self.reversed = ctx.SprintFunc("reverse=true")
- self.intense_gray = ctx.SprintFunc("fg=intense-gray")
- }
- func (self *table) current_codepoint() rune {
- if len(self.codepoints) > 0 {
- return self.codepoints[self.current_idx]
- }
- return InvalidChar
- }
- func (self *table) set_codepoints(codepoints []rune, mode Mode, current_idx int) {
- delta := len(codepoints) - len(self.codepoints)
- self.codepoints = codepoints
- if self.codepoints != nil && mode != FAVORITES && mode != HEX {
- slices.Sort(self.codepoints)
- }
- self.mode = mode
- self.layout_dirty = true
- if current_idx > -1 && current_idx < len(self.codepoints) {
- self.current_idx = current_idx
- }
- if self.current_idx >= len(self.codepoints) {
- self.current_idx = 0
- }
- if delta != 0 {
- self.scroll_data = scroll_data{}
- }
- }
- func (self *table) codepoint_at_hint(hint string) rune {
- idx := decode_hint(hint)
- if idx >= 0 && idx < len(self.codepoints) {
- return self.codepoints[idx]
- }
- return InvalidChar
- }
- type cell_data struct {
- idx, ch, desc string
- }
- func title(x string) string {
- if len(x) > 1 {
- x = strings.ToUpper(x[:1]) + x[1:]
- }
- return x
- }
- func (self *table) layout(rows, cols int) string {
- if !self.layout_dirty && self.last_cols == cols && self.last_rows == rows {
- return self.text
- }
- self.last_cols, self.last_rows = cols, rows
- self.layout_dirty = false
- var as_parts func(int, rune) cell_data
- var cell func(int, cell_data)
- var idx_size, space_for_desc int
- output := strings.Builder{}
- output.Grow(4096)
- switch self.mode {
- case NAME:
- as_parts = func(i int, codepoint rune) cell_data {
- return cell_data{idx: ljust(encode_hint(i), idx_size), ch: resolved_char(codepoint, self.emoji_variation), desc: title(unicode_names.NameForCodePoint(codepoint))}
- }
- cell = func(i int, cd cell_data) {
- is_current := i == self.current_idx
- text := self.green(cd.idx) + " " + cd.ch + " "
- w := wcswidth.Stringwidth(cd.ch)
- if w < 2 {
- text += strings.Repeat(" ", (2 - w))
- }
- desc_width := wcswidth.Stringwidth(cd.desc)
- if desc_width > space_for_desc {
- text += cd.desc[:space_for_desc-1] + "…"
- } else {
- text += cd.desc
- extra := space_for_desc - desc_width
- if extra > 0 {
- text += strings.Repeat(" ", extra)
- }
- }
- if is_current {
- text = self.reversed(text)
- }
- output.WriteString(text)
- }
- default:
- as_parts = func(i int, codepoint rune) cell_data {
- return cell_data{idx: ljust(encode_hint(i), idx_size), ch: resolved_char(codepoint, self.emoji_variation)}
- }
- cell = func(i int, cd cell_data) {
- output.WriteString(self.green(cd.idx))
- output.WriteString(" ")
- output.WriteString(self.intense_gray(cd.ch))
- w := wcswidth.Stringwidth(cd.ch)
- if w < 2 {
- output.WriteString(strings.Repeat(" ", (2 - w)))
- }
- }
- }
- num := len(self.codepoints)
- if num < 1 {
- self.text = ""
- self.num_cols = 0
- self.num_rows = 0
- return self.text
- }
- idx_size = len(encode_hint(num - 1))
- parts := make([]cell_data, len(self.codepoints))
- for i, ch := range self.codepoints {
- parts[i] = as_parts(i, ch)
- }
- longest := 0
- switch self.mode {
- case NAME:
- for _, p := range parts {
- longest = utils.Max(longest, idx_size+2+len(p.desc)+2)
- }
- default:
- longest = idx_size + 3
- }
- col_width := longest + 2
- col_width = utils.Min(col_width, 40)
- self.num_cols = utils.Max(cols/col_width, 1)
- if self.num_cols == 1 {
- col_width = cols
- }
- space_for_desc = col_width - 2 - idx_size - 4
- self.num_rows = rows
- rows_left := rows
- if self.scroll_data.num_items_per_page != self.num_cols*self.num_rows {
- self.update_scroll_data()
- }
- skip_scroll := self.scroll_data.scroll_rows * self.num_cols
- for i, cd := range parts {
- if skip_scroll > 0 {
- skip_scroll -= 1
- continue
- }
- cell(i, cd)
- output.WriteString(" ")
- if self.num_cols == 1 || (i > 0 && (i+1)%self.num_cols == 0) {
- rows_left -= 1
- if rows_left == 0 {
- break
- }
- output.WriteString("\r\n")
- }
- }
- self.text = output.String()
- return self.text
- }
- func (self *table) update_scroll_data() {
- self.scroll_data.num_items_per_page = self.num_rows * self.num_cols
- page_num := self.current_idx / self.scroll_data.num_items_per_page
- self.scroll_data.scroll_rows = self.num_rows * page_num
- }
- func (self *table) move_current(rows, cols int) {
- if len(self.codepoints) == 0 {
- return
- }
- if cols != 0 {
- self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
- self.layout_dirty = true
- }
- if rows != 0 {
- amt := rows * self.num_cols
- self.current_idx += amt
- self.current_idx = utils.Max(0, utils.Min(self.current_idx, len(self.codepoints)-1))
- self.layout_dirty = true
- }
- self.update_scroll_data()
- }
|