diff --git a/tools/cmd/themes/list.go b/tools/cmd/themes/list.go index 3a2629333..68d32fb26 100644 --- a/tools/cmd/themes/list.go +++ b/tools/cmd/themes/list.go @@ -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 diff --git a/tools/cmd/themes/main.go b/tools/cmd/themes/main.go index 4825a9916..2ba3b5038 100644 --- a/tools/cmd/themes/main.go +++ b/tools/cmd/themes/main.go @@ -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 diff --git a/tools/cmd/themes/ui.go b/tools/cmd/themes/ui.go index 309d1a2d3..dde5eba58 100644 --- a/tools/cmd/themes/ui.go +++ b/tools/cmd/themes/ui.go @@ -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 } diff --git a/tools/themes/collection.go b/tools/themes/collection.go index 15d0cbe62..9bf13c66f 100644 --- a/tools/themes/collection.go +++ b/tools/themes/collection.go @@ -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 } diff --git a/tools/tui/readline/actions.go b/tools/tui/readline/actions.go index 79fc16476..75b268742 100644 --- a/tools/tui/readline/actions.go +++ b/tools/tui/readline/actions.go @@ -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() }