mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-13 03:59:23 +02:00
Start work on porting unicode input kitten to Go
This commit is contained in:
233
tools/cmd/unicode_input/table.go
Normal file
233
tools/cmd/unicode_input/table.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
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 table struct {
|
||||
emoji_variation string
|
||||
layout_dirty bool
|
||||
last_rows, last_cols int
|
||||
codepoints []rune
|
||||
current_idx, scroll_rows int
|
||||
text string
|
||||
num_cols, num_rows int
|
||||
mode Mode
|
||||
|
||||
green, reversed, not_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.not_reversed = ctx.SprintFunc("reverse=false")
|
||||
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) {
|
||||
self.codepoints = codepoints
|
||||
self.mode = mode
|
||||
self.layout_dirty = true
|
||||
if self.current_idx >= len(self.codepoints) {
|
||||
self.current_idx = 0
|
||||
}
|
||||
self.scroll_rows = 0
|
||||
}
|
||||
|
||||
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 (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: unicode_names.NameForCodePoint(codepoint)}
|
||||
}
|
||||
|
||||
cell = func(i int, cd cell_data) {
|
||||
is_current := i == self.current_idx
|
||||
text := self.green(cd.idx) + " \x1b[49m" + cd.ch + " "
|
||||
w := wcswidth.Stringwidth(cd.ch)
|
||||
if w < 2 {
|
||||
text += strings.Repeat(" ", (2 - w))
|
||||
}
|
||||
if len(cd.desc) > space_for_desc {
|
||||
text += cd.desc[:space_for_desc-1] + "…"
|
||||
} else {
|
||||
text += cd.desc
|
||||
extra := space_for_desc - len(cd.desc)
|
||||
if extra > 0 {
|
||||
text += strings.Repeat(" ", extra)
|
||||
}
|
||||
}
|
||||
if is_current {
|
||||
text = self.reversed(text)
|
||||
} else {
|
||||
text = self.not_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)
|
||||
space_for_desc = col_width - 2 - idx_size - 4
|
||||
self.num_cols = utils.Max(cols/col_width, 1)
|
||||
self.num_rows = rows
|
||||
rows_left := rows
|
||||
skip_scroll := self.scroll_rows * self.num_cols
|
||||
|
||||
for i, cd := range parts {
|
||||
if skip_scroll > 0 {
|
||||
skip_scroll -= 1
|
||||
continue
|
||||
}
|
||||
cell(i, cd)
|
||||
output.WriteString(" ")
|
||||
if 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) 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
|
||||
}
|
||||
first_visible := self.scroll_rows * self.num_cols
|
||||
last_visible := first_visible + ((self.num_cols * self.num_rows) - 1)
|
||||
scroll_amount := self.num_rows
|
||||
if self.current_idx < first_visible {
|
||||
self.scroll_rows = utils.Max(self.scroll_rows-scroll_amount, 0)
|
||||
}
|
||||
if self.current_idx > last_visible {
|
||||
self.scroll_rows += scroll_amount
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user