diff --git a/tools/cmd/themes/list.go b/tools/cmd/themes/list.go new file mode 100644 index 000000000..1161818a7 --- /dev/null +++ b/tools/cmd/themes/list.go @@ -0,0 +1,49 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package themes + +import ( + "fmt" + + "kitty/tools/themes" +) + +var _ = fmt.Print + +type ThemesList struct { + themes, all_themes *themes.Themes + current_search string + display_strings []string + widths []int + max_width, current_idx int +} + +func (self *ThemesList) Len() int { + if self.themes == nil { + return 0 + } + return self.themes.Len() +} + +func (self *ThemesList) Next(delta int, allow_wrapping bool) bool { + if len(self.display_strings) == 0 { + return false + } + idx := self.current_idx + delta + if !allow_wrapping && (idx < 0 || idx > self.Len()) { + return false + } + for idx < 0 { + idx += self.Len() + } + self.current_idx = idx % self.Len() + return true +} + +func (self *ThemesList) UpdateThemes(themes *themes.Themes) { + self.themes, self.all_themes = themes, themes + if self.current_search != "" { + self.themes = self.all_themes.Copy() + } else { + } +} diff --git a/tools/cmd/themes/main.go b/tools/cmd/themes/main.go index a01278dac..bec57c17d 100644 --- a/tools/cmd/themes/main.go +++ b/tools/cmd/themes/main.go @@ -9,6 +9,7 @@ import ( "kitty/tools/cli" "kitty/tools/themes" + "kitty/tools/tui/loop" "kitty/tools/utils" ) @@ -54,6 +55,35 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if len(args) == 1 { return non_interactive(opts, args[0]) } + lp, err := loop.New() + if err != nil { + return 1, err + } + cv := utils.NewCachedValues("unicode-input", &CachedData{Category: "All"}) + h := &handler{lp: lp, opts: opts, cached_data: cv.Load()} + defer cv.Save() + lp.OnInitialize = func() (string, error) { + lp.AllowLineWrapping(false) + lp.SetWindowTitle(`Choose a theme for kitty`) + h.initialize() + return "", nil + } + lp.OnWakeup = h.on_wakeup + lp.OnFinalize = func() string { + h.finalize() + lp.SetCursorVisible(true) + return `` + } + err = lp.Run() + if err != nil { + return 1, err + } + ds := lp.DeathSignalName() + if ds != "" { + fmt.Println("Killed by signal: ", ds) + lp.KillIfSignalled() + return 1, nil + } return } diff --git a/tools/cmd/themes/ui.go b/tools/cmd/themes/ui.go new file mode 100644 index 000000000..b8b54d0a4 --- /dev/null +++ b/tools/cmd/themes/ui.go @@ -0,0 +1,84 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package themes + +import ( + "fmt" + "io" + "kitty/tools/themes" + "kitty/tools/tui/loop" + "time" +) + +var _ = fmt.Print + +type State int + +const ( + FETCHING State = iota + BROWSING + SEARCHING + ACCEPTING +) + +type CachedData struct { + Recent []string `json:"recent"` + Category string `json:"category"` +} + +type fetch_data struct { + themes *themes.Themes + err error + closer io.Closer +} + +type handler struct { + lp *loop.Loop + opts *Options + cached_data *CachedData + + state State + fetch_result chan fetch_data + all_themes *themes.Themes + themes_closer io.Closer +} + +func (self *handler) fetch_themes() { + r := fetch_data{} + r.themes, r.closer, r.err = themes.LoadThemes(time.Duration(self.opts.CacheAge * float64(time.Hour*24))) + self.lp.WakeupMainThread() + self.fetch_result <- r +} + +func (self *handler) on_wakeup() error { + r := <-self.fetch_result + if r.err != nil { + return r.err + } + self.state = BROWSING + self.all_themes = r.themes + self.themes_closer = r.closer + self.redraw_after_category_change() + return nil +} + +func (self *handler) finalize() { + t := self.themes_closer + if t != nil { + t.Close() + } +} + +func (self *handler) initialize() { + self.fetch_result = make(chan fetch_data) + go self.fetch_themes() + self.draw_screen() +} + +func (self *handler) draw_screen() { + // TODO: Implement me +} + +func (self *handler) redraw_after_category_change() { + // TODO: Implement me +} diff --git a/tools/themes/collection.go b/tools/themes/collection.go index b4b89a4de..64de38d41 100644 --- a/tools/themes/collection.go +++ b/tools/themes/collection.go @@ -25,6 +25,7 @@ import ( "github.com/shirou/gopsutil/v3/process" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "golang.org/x/sys/unix" ) @@ -713,6 +714,12 @@ type Themes struct { index_map []string } +func (self *Themes) Copy() *Themes { + ans := &Themes{name_map: make(map[string]*Theme, len(self.name_map)), index_map: slices.Clone(self.index_map)} + maps.Copy(ans.name_map, self.name_map) + return ans +} + var camel_case_pat = (&utils.Once[*regexp.Regexp]{Run: func() *regexp.Regexp { return regexp.MustCompile(`([a-z])([A-Z])`) }}).Get @@ -724,6 +731,8 @@ func theme_name_from_file_name(fname string) string { return strings.Join(utils.Map(strings.Title, strings.Split(fname, " ")), " ") } +func (self *Themes) Len() int { return len(self.name_map) } + func (self *Themes) AddFromFile(path string) (*Theme, error) { m, conf, err := parse_theme_metadata(path) if err != nil { @@ -810,6 +819,38 @@ func (self *Themes) ThemeByName(name string) *Theme { return ans } +func match(expression string, items []string) []string { + return nil +} + +func (self *Themes) ApplySearch(expression string, marks ...string) []string { + mark_before, mark_after := "\033[33m", "\033[39m" + if len(marks) == 2 { + mark_before, mark_after = marks[0], marks[1] + } + results := match(expression, maps.Keys(self.name_map)) + name_map := make(map[string]*Theme, len(results)) + ans := make([]string, 0, len(results)) + for _, r := range results { + pos, k, _ := strings.Cut(r, ":") + positions := []int{} + for _, q := range strings.Split(pos, ",") { + i, _ := strconv.Atoi(q) + positions = append(positions, i) + text := k + 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.name_map = name_map + self.index_map = maps.Keys(name_map) + return ans +} + func LoadThemes(cache_age time.Duration) (ans *Themes, closer io.Closer, err error) { zip_path, err := FetchCached(cache_age) ans = &Themes{name_map: make(map[string]*Theme)}