Finish port of themes kitten to Go

This commit is contained in:
Kovid Goyal 2023-03-14 20:24:21 +05:30
parent 0c20a4d980
commit 0805330b77
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 116 additions and 53 deletions

View File

@ -6,11 +6,13 @@ import (
"fmt"
"kitty/tools/themes"
"kitty/tools/tty"
"kitty/tools/utils"
"kitty/tools/wcswidth"
)
var _ = fmt.Print
var DebugPrintln = tty.DebugPrintln
type ThemesList struct {
themes, all_themes *themes.Themes

View File

@ -79,6 +79,7 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
return nil
}
lp.OnKeyEvent = h.on_key_event
lp.OnText = h.on_text
err = lp.Run()
if err != nil {
return 1, err

View File

@ -84,8 +84,8 @@ func (self *handler) fetch_themes() {
}
func (self *handler) on_fetching_key_event(ev *loop.KeyEvent) error {
if ev.MatchesRelease("esc") {
self.lp.Quit(0)
if ev.MatchesPressOrRepeat("esc") {
self.quit_on_next_key_release = 0
ev.Handled = true
}
return nil
@ -120,7 +120,7 @@ func (self *handler) finalize() {
func (self *handler) initialize() {
self.quit_on_next_key_release = -1
self.tabs = strings.Split("all dark light recent user", " ")
self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/ "})
self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"})
self.themes_list = &ThemesList{}
self.fetch_result = make(chan fetch_data)
self.category_filters = make(map[string]func(*themes.Theme) bool, len(category_filters)+1)
@ -238,13 +238,13 @@ func (self *handler) next(delta int, allow_wrapping bool) {
}
func (self *handler) on_browsing_key_event(ev *loop.KeyEvent) error {
if ev.MatchesRelease("esc") || ev.MatchesRelease("q") {
self.lp.Quit(0)
if ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("q") {
self.quit_on_next_key_release = 0
ev.Handled = true
return nil
}
for _, cat := range self.tabs {
if ev.MatchesRelease(cat[0:1]) || ev.MatchesRelease("alt+"+cat[0:1]) {
if ev.MatchesPressOrRepeat(cat[0:1]) || ev.MatchesPressOrRepeat("alt+"+cat[0:1]) {
ev.Handled = true
if cat != self.current_category() {
self.set_current_category(cat)
@ -253,27 +253,27 @@ func (self *handler) on_browsing_key_event(ev *loop.KeyEvent) error {
}
}
}
if ev.MatchesRelease("left") || ev.MatchesRelease("shift+tab") {
if ev.MatchesPressOrRepeat("left") || ev.MatchesPressOrRepeat("shift+tab") {
self.next_category(-1)
ev.Handled = true
return nil
}
if ev.MatchesRelease("right") || ev.MatchesRelease("tab") {
if ev.MatchesPressOrRepeat("right") || ev.MatchesPressOrRepeat("tab") {
self.next_category(1)
ev.Handled = true
return nil
}
if ev.MatchesRelease("j") || ev.MatchesRelease("down") {
if ev.MatchesPressOrRepeat("j") || ev.MatchesPressOrRepeat("down") {
self.next(1, true)
ev.Handled = true
return nil
}
if ev.MatchesRelease("k") || ev.MatchesRelease("up") {
if ev.MatchesPressOrRepeat("k") || ev.MatchesPressOrRepeat("up") {
self.next(-1, true)
ev.Handled = true
return nil
}
if ev.MatchesRelease("page_down") {
if ev.MatchesPressOrRepeat("page_down") {
ev.Handled = true
sz, err := self.lp.ScreenSize()
if err == nil {
@ -281,7 +281,7 @@ func (self *handler) on_browsing_key_event(ev *loop.KeyEvent) error {
}
return nil
}
if ev.MatchesRelease("page_up") {
if ev.MatchesPressOrRepeat("page_up") {
ev.Handled = true
sz, err := self.lp.ScreenSize()
if err == nil {
@ -289,11 +289,20 @@ func (self *handler) on_browsing_key_event(ev *loop.KeyEvent) error {
}
return nil
}
if ev.MatchesRelease("s") || ev.MatchesRelease("/") {
if ev.MatchesPressOrRepeat("s") || ev.MatchesPressOrRepeat("/") {
ev.Handled = true
self.start_search()
return nil
}
if ev.MatchesPressOrRepeat("c") || ev.MatchesPressOrRepeat("enter") {
ev.Handled = true
if self.themes_list == nil || self.themes_list.Len() == 0 {
self.lp.Beep()
} else {
self.state = ACCEPTING
self.draw_screen()
}
}
return nil
}
@ -311,21 +320,19 @@ func (self *handler) draw_browsing_screen() {
}
num_rows := int(sz.HeightCells) - 2
mw := self.themes_list.max_width + 1
green_fg, _, _ := strings.Cut(self.lp.SprintStyled("fg=green", "|"), "|")
for _, l := range self.themes_list.Lines(num_rows) {
num_rows--
line := l.text
if l.is_current {
line = strings.ReplaceAll(line, themes.MARK_BEFORE, self.lp.SprintStyled("fg=green"))
if l.is_current {
self.lp.PrintStyled("fg=green", ">")
self.lp.PrintStyled("fg=green bold", line)
} else {
self.lp.PrintStyled("fg=green", " ")
self.lp.QueueWriteString(line)
}
self.lp.MoveCursorHorizontally(mw - l.width)
self.lp.Println(SEPARATOR)
line = strings.ReplaceAll(line, themes.MARK_AFTER, green_fg)
self.lp.PrintStyled("fg=green", ">")
self.lp.PrintStyled("fg=green bold", line)
} else {
self.lp.PrintStyled("fg=green", " ")
self.lp.QueueWriteString(line)
}
self.lp.MoveCursorHorizontally(mw - l.width)
self.lp.Println(SEPARATOR)
}
if self.themes_list != nil && self.themes_list.Len() > 0 {
self.draw_theme_demo()
@ -446,7 +453,10 @@ func (self *handler) draw_theme_demo() {
if intense {
s = "bright-" + s
}
buf.WriteString(self.lp.SprintStyled("fg="+c, c[:trunc]))
if len(c) > trunc {
c = c[:trunc]
}
buf.WriteString(self.lp.SprintStyled("fg="+c, c))
buf.WriteString(" ")
}
text := strings.TrimSpace(buf.String())
@ -487,25 +497,25 @@ func (self *handler) draw_theme_demo() {
// accepting {{{
func (self *handler) on_accepting_key_event(ev *loop.KeyEvent) error {
if ev.MatchesRelease("q") || ev.MatchesRelease("esc") {
if ev.MatchesPressOrRepeat("q") || ev.MatchesPressOrRepeat("esc") {
ev.Handled = true
self.lp.Quit(0)
self.quit_on_next_key_release = 0
return nil
}
if ev.MatchesRelease("a") {
if ev.MatchesPressOrRepeat("a") {
ev.Handled = true
self.state = BROWSING
self.draw_screen()
return nil
}
if ev.MatchesRelease("p") {
if ev.MatchesPressOrRepeat("p") {
ev.Handled = true
self.themes_list.CurrentTheme().SaveInDir(utils.ConfigDir())
self.update_recent()
self.lp.Quit(0)
return nil
}
if ev.MatchesRelease("m") {
if ev.MatchesPressOrRepeat("m") {
ev.Handled = true
self.themes_list.CurrentTheme().SaveInConf(utils.ConfigDir(), self.opts.ReloadIn, self.opts.ConfigFileName)
self.update_recent()
@ -556,18 +566,36 @@ func (self *handler) draw_accepting_screen() {
// }}}
// searching {{{
func (self *handler) update_search() {
text := self.rl.AllText()
if self.themes_list.UpdateSearch(text) {
self.set_colors_to_current_theme()
self.draw_screen()
} else {
self.draw_search_bar()
}
}
func (self *handler) on_text(text string, a, b bool) error {
if self.state == SEARCHING {
err := self.rl.OnText(text, a, b)
if err != nil {
return err
}
self.update_search()
}
return nil
}
func (self *handler) on_searching_key_event(ev *loop.KeyEvent) error {
if ev.MatchesRelease("enter") {
if ev.MatchesPressOrRepeat("enter") {
ev.Handled = true
self.state = BROWSING
self.draw_bottom_bar()
return nil
}
if ev.MatchesPressOrRepeat("enter") || ev.MatchesPressOrRepeat("esc") {
ev.Handled = true
return nil
}
if ev.MatchesRelease("esc") {
if ev.MatchesPressOrRepeat("esc") {
ev.Handled = true
self.state = BROWSING
self.themes_list.UpdateSearch("")
@ -579,12 +607,8 @@ func (self *handler) on_searching_key_event(ev *loop.KeyEvent) error {
if err != nil {
return err
}
text := self.rl.AllText()
if self.themes_list.UpdateSearch(text) {
self.set_colors_to_current_theme()
self.draw_screen()
} else {
self.draw_search_bar()
if ev.Handled {
self.update_search()
}
return nil
}

View File

@ -4,6 +4,7 @@ package themes
import (
"archive/zip"
"bytes"
"encoding/json"
"errors"
"fmt"
@ -20,6 +21,7 @@ import (
"kitty/tools/cli"
"kitty/tools/config"
"kitty/tools/tty"
"kitty/tools/tui/subseq"
"kitty/tools/utils"
"kitty/tools/utils/style"
@ -31,6 +33,7 @@ import (
)
var _ = fmt.Print
var DebugPrintln = tty.DebugPrintln
var AllColorSettingNames = map[string]bool{ // {{{
// generated by gen-config.py do not edit
@ -325,12 +328,33 @@ type JSONMetadata struct {
var ErrNoCacheFound = errors.New("No cache found and max cache age is negative")
func set_comment_in_zip_file(path string, comment string) error {
src, err := zip.OpenReader(path)
if err != nil {
return err
}
defer src.Close()
buf := bytes.Buffer{}
dest := zip.NewWriter(&buf)
dest.SetComment(comment)
for _, sf := range src.File {
err = dest.Copy(sf)
if err != nil {
return err
}
}
dest.Close()
utils.AtomicUpdateFile(path, buf.Bytes(), 0o644)
return nil
}
func fetch_cached(name, url, cache_path string, max_cache_age time.Duration) (string, error) {
cache_path = filepath.Join(cache_path, name+".zip")
zf, err := zip.OpenReader(cache_path)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return "", err
}
defer zf.Close()
var jm JSONMetadata
if err == nil {
@ -362,6 +386,12 @@ func fetch_cached(name, url, cache_path string, max_cache_age time.Duration) (st
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotModified {
jm.Timestamp = utils.ISO8601Format(time.Now())
comment, _ := json.Marshal(jm)
err = set_comment_in_zip_file(cache_path, utils.UnsafeBytesToString(comment))
if err != nil {
return "", err
}
return cache_path, nil
}
return "", fmt.Errorf("Failed to download %s with HTTP error: %s", url, resp.Status)
@ -760,14 +790,18 @@ func (self *Themes) At(x int) *Theme {
}
func (self *Themes) Names() []string { return self.index_map }
func (self *Themes) create_index_map() {
self.index_map = maps.Keys(self.name_map)
self.index_map = utils.StableSortWithKey(self.index_map, strings.ToLower)
}
func (self *Themes) Filtered(is_ok func(*Theme) bool) *Themes {
themes := utils.Filter(maps.Values(self.name_map), is_ok)
ans := Themes{name_map: make(map[string]*Theme, len(themes))}
for _, theme := range themes {
ans.name_map[theme.metadata.Name] = theme
}
ans.index_map = maps.Keys(ans.name_map)
ans.index_map = utils.StableSortWithKey(ans.index_map, strings.ToLower)
ans.create_index_map()
return &ans
}
@ -873,23 +907,24 @@ func (self *Themes) ApplySearch(expression string, marks ...string) []string {
if len(marks) == 2 {
mark_before, mark_after = marks[0], marks[1]
}
results := match(expression, maps.Keys(self.name_map))
results := utils.Filter(match(expression, self.index_map), func(x *subseq.Match) bool { return x.Score > 0 })
name_map := make(map[string]*Theme, len(results))
for _, m := range results {
name_map[m.Text] = self.name_map[m.Text]
}
self.name_map = name_map
self.index_map = self.index_map[:0]
ans := make([]string, 0, len(results))
for _, m := range results {
k := m.Text
text := m.Text
positions := m.Positions
for i := len(positions) - 1; i >= 0; i-- {
p := positions[i]
text = text[:p] + mark_before + text[p:p+1] + mark_after + text[p+1:]
}
name_map[k] = self.name_map[k]
ans = append(ans, text)
self.index_map = append(self.index_map, m.Text)
}
self.name_map = name_map
self.index_map = maps.Keys(name_map)
self.index_map = utils.StableSortWithKey(self.index_map, strings.ToLower)
return ans
}
@ -905,8 +940,7 @@ func LoadThemes(cache_age time.Duration) (ans *Themes, closer io.Closer, err err
if err = ans.add_from_dir(filepath.Join(utils.ConfigDir(), "themes")); err != nil {
return nil, nil, err
}
ans.index_map = maps.Keys(ans.name_map)
ans.index_map = utils.StableSortWithKey(ans.index_map, strings.ToLower)
ans.create_index_map()
return ans, closer, nil
}

View File

@ -55,7 +55,9 @@ func (self *Readline) all_text() string {
func (self *Readline) set_text(text string) {
self.move_to_start()
self.erase_chars_after_cursor(123456789, true)
self.add_text(text)
if text != "" {
self.add_text(text)
}
self.move_to_end()
}