Start work on porting themes kitten to Go

This commit is contained in:
Kovid Goyal 2023-03-11 10:14:03 +05:30
parent 3741d3d1be
commit f9b0b54ee5
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 508 additions and 24 deletions

View File

@ -47,7 +47,7 @@ def main() -> None:
all_colors.append(opt.name)
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
patch_color_list('kittens/themes/collection.py', all_colors, 'ALL', ' ' * 8)
patch_color_list('tools/themes/collection.go', all_colors, 'ALL')
from kittens.diff.options.definition import definition as kd
write_output('kittens.diff', kd)

View File

@ -619,4 +619,5 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['short_desc'] = 'Manage kitty color schemes easily'
cd['args_completion'] = CompletionSpec.from_string('type:special group:complete_themes')

View File

@ -24,7 +24,7 @@ exec_kitty() {
is_wrapped_kitten() {
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh"
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes"
[ -n "$1" ] && {
case " $wrapped_kittens " in
*" $1 "*) printf "%s" "$1" ;;

View File

@ -4,11 +4,10 @@ package completion
import (
"fmt"
"os/exec"
"strings"
"kitty/tools/cli"
"kitty/tools/utils"
"kitty/tools/themes"
)
var _ = fmt.Print
@ -64,20 +63,7 @@ func complete_plus_open(completions *cli.Completions, word string, arg_num int)
}
func complete_themes(completions *cli.Completions, word string, arg_num int) {
kitty := utils.KittyExe()
if kitty != "" {
out, err := exec.Command(kitty, "+runpy", "from kittens.themes.collection import *; print_theme_names()").Output()
if err == nil {
mg := completions.AddMatchGroup("Themes")
scanner := utils.NewLineScanner(utils.UnsafeBytesToString(out))
for scanner.Scan() {
theme_name := strings.TrimSpace(scanner.Text())
if theme_name != "" && strings.HasPrefix(theme_name, word) {
mg.AddMatch(theme_name)
}
}
}
}
themes.CompleteThemes(completions, word, arg_num)
}
func EntryPoint(tool_root *cli.Command) {

62
tools/cmd/themes/main.go Normal file
View File

@ -0,0 +1,62 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package themes
import (
"fmt"
"strings"
"time"
"kitty/tools/cli"
"kitty/tools/themes"
"kitty/tools/utils"
)
var _ = fmt.Print
func complete_themes(completions *cli.Completions, word string, arg_num int) {
themes.CompleteThemes(completions, word, arg_num)
}
func non_interactive(opts *Options, theme_name string) (rc int, err error) {
themes, closer, err := themes.LoadThemes(time.Duration(opts.CacheAge * float64(time.Hour*24)))
if err != nil {
return 1, err
}
defer closer.Close()
theme := themes.ThemeByName(theme_name)
if theme == nil {
theme_name = strings.ReplaceAll(theme_name, `\`, ``)
theme = themes.ThemeByName(theme_name)
if theme == nil {
return 1, fmt.Errorf("No theme named: %s", theme_name)
}
}
if opts.DumpTheme {
code, err := theme.Code()
if err != nil {
return 1, err
}
fmt.Println(code)
} else {
err = theme.SaveInConf(utils.ConfigDir(), opts.ReloadIn, opts.ConfigFileName)
if err != nil {
return 1, err
}
}
return
}
func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
if len(args) > 1 {
args = []string{strings.Join(args, ` `)}
}
if len(args) == 1 {
return non_interactive(opts, args[0])
}
return
}
func EntryPoint(parent *cli.Command) {
create_cmd(parent, main)
}

View File

@ -15,6 +15,7 @@ import (
"kitty/tools/cmd/icat"
"kitty/tools/cmd/pytest"
"kitty/tools/cmd/ssh"
"kitty/tools/cmd/themes"
"kitty/tools/cmd/unicode_input"
"kitty/tools/cmd/update_self"
"kitty/tools/tui"
@ -45,6 +46,8 @@ func KittyToolEntryPoints(root *cli.Command) {
ask.EntryPoint(root)
// hints
hints.EntryPoint(root)
// themes
themes.EntryPoint(root)
// __pytest__
pytest.EntryPoint(root)
// __hold_till_enter__

View File

@ -9,9 +9,6 @@ import (
"fmt"
"io"
"io/fs"
"kitty/tools/config"
"kitty/tools/utils"
"kitty/tools/utils/style"
"net/http"
"os"
"path"
@ -21,11 +18,304 @@ import (
"strings"
"time"
"kitty/tools/cli"
"kitty/tools/config"
"kitty/tools/utils"
"kitty/tools/utils/style"
"github.com/shirou/gopsutil/v3/process"
"golang.org/x/exp/maps"
"golang.org/x/sys/unix"
)
var _ = fmt.Print
var AllColorSettingNames = map[string]bool{ // {{{
// generated by gen-config.py do not edit
// ALL_COLORS_START
"active_border_color": true,
"active_tab_background": true,
"active_tab_foreground": true,
"background": true,
"bell_border_color": true,
"color0": true,
"color1": true,
"color10": true,
"color100": true,
"color101": true,
"color102": true,
"color103": true,
"color104": true,
"color105": true,
"color106": true,
"color107": true,
"color108": true,
"color109": true,
"color11": true,
"color110": true,
"color111": true,
"color112": true,
"color113": true,
"color114": true,
"color115": true,
"color116": true,
"color117": true,
"color118": true,
"color119": true,
"color12": true,
"color120": true,
"color121": true,
"color122": true,
"color123": true,
"color124": true,
"color125": true,
"color126": true,
"color127": true,
"color128": true,
"color129": true,
"color13": true,
"color130": true,
"color131": true,
"color132": true,
"color133": true,
"color134": true,
"color135": true,
"color136": true,
"color137": true,
"color138": true,
"color139": true,
"color14": true,
"color140": true,
"color141": true,
"color142": true,
"color143": true,
"color144": true,
"color145": true,
"color146": true,
"color147": true,
"color148": true,
"color149": true,
"color15": true,
"color150": true,
"color151": true,
"color152": true,
"color153": true,
"color154": true,
"color155": true,
"color156": true,
"color157": true,
"color158": true,
"color159": true,
"color16": true,
"color160": true,
"color161": true,
"color162": true,
"color163": true,
"color164": true,
"color165": true,
"color166": true,
"color167": true,
"color168": true,
"color169": true,
"color17": true,
"color170": true,
"color171": true,
"color172": true,
"color173": true,
"color174": true,
"color175": true,
"color176": true,
"color177": true,
"color178": true,
"color179": true,
"color18": true,
"color180": true,
"color181": true,
"color182": true,
"color183": true,
"color184": true,
"color185": true,
"color186": true,
"color187": true,
"color188": true,
"color189": true,
"color19": true,
"color190": true,
"color191": true,
"color192": true,
"color193": true,
"color194": true,
"color195": true,
"color196": true,
"color197": true,
"color198": true,
"color199": true,
"color2": true,
"color20": true,
"color200": true,
"color201": true,
"color202": true,
"color203": true,
"color204": true,
"color205": true,
"color206": true,
"color207": true,
"color208": true,
"color209": true,
"color21": true,
"color210": true,
"color211": true,
"color212": true,
"color213": true,
"color214": true,
"color215": true,
"color216": true,
"color217": true,
"color218": true,
"color219": true,
"color22": true,
"color220": true,
"color221": true,
"color222": true,
"color223": true,
"color224": true,
"color225": true,
"color226": true,
"color227": true,
"color228": true,
"color229": true,
"color23": true,
"color230": true,
"color231": true,
"color232": true,
"color233": true,
"color234": true,
"color235": true,
"color236": true,
"color237": true,
"color238": true,
"color239": true,
"color24": true,
"color240": true,
"color241": true,
"color242": true,
"color243": true,
"color244": true,
"color245": true,
"color246": true,
"color247": true,
"color248": true,
"color249": true,
"color25": true,
"color250": true,
"color251": true,
"color252": true,
"color253": true,
"color254": true,
"color255": true,
"color26": true,
"color27": true,
"color28": true,
"color29": true,
"color3": true,
"color30": true,
"color31": true,
"color32": true,
"color33": true,
"color34": true,
"color35": true,
"color36": true,
"color37": true,
"color38": true,
"color39": true,
"color4": true,
"color40": true,
"color41": true,
"color42": true,
"color43": true,
"color44": true,
"color45": true,
"color46": true,
"color47": true,
"color48": true,
"color49": true,
"color5": true,
"color50": true,
"color51": true,
"color52": true,
"color53": true,
"color54": true,
"color55": true,
"color56": true,
"color57": true,
"color58": true,
"color59": true,
"color6": true,
"color60": true,
"color61": true,
"color62": true,
"color63": true,
"color64": true,
"color65": true,
"color66": true,
"color67": true,
"color68": true,
"color69": true,
"color7": true,
"color70": true,
"color71": true,
"color72": true,
"color73": true,
"color74": true,
"color75": true,
"color76": true,
"color77": true,
"color78": true,
"color79": true,
"color8": true,
"color80": true,
"color81": true,
"color82": true,
"color83": true,
"color84": true,
"color85": true,
"color86": true,
"color87": true,
"color88": true,
"color89": true,
"color9": true,
"color90": true,
"color91": true,
"color92": true,
"color93": true,
"color94": true,
"color95": true,
"color96": true,
"color97": true,
"color98": true,
"color99": true,
"cursor": true,
"cursor_text_color": true,
"foreground": true,
"inactive_border_color": true,
"inactive_tab_background": true,
"inactive_tab_foreground": true,
"macos_titlebar_color": true,
"mark1_background": true,
"mark1_foreground": true,
"mark2_background": true,
"mark2_foreground": true,
"mark3_background": true,
"mark3_foreground": true,
"selection_background": true,
"selection_foreground": true,
"tab_bar_background": true,
"tab_bar_margin_color": true,
"url_color": true,
"visual_bell_color": true,
"wayland_titlebar_color": true, // ALL_COLORS_END
} // }}}
type JSONMetadata struct {
Etag string `json:"etag"`
Timestamp string `json:"timestamp"`
@ -233,6 +523,111 @@ func (self *Theme) load_code() (string, error) {
return self.code, nil
}
func (self *Theme) Code() (string, error) {
return self.load_code()
}
func patch_conf(text, theme_name string) string {
addition := fmt.Sprintf("# BEGIN_KITTY_THEME\n# %s\ninclude current-theme.conf\n# END_KITTY_THEME", theme_name)
pat := utils.MustCompile(`(?ms)^# BEGIN_KITTY_THEME.+?# END_KITTY_THEME`)
replaced := false
ntext := pat.ReplaceAllStringFunc(text, func(string) string {
replaced = true
return addition
})
if !replaced {
if text != "" {
text += "\n\n"
}
ntext = text + addition
}
pat = utils.MustCompile(fmt.Sprintf(`(?m)^\s*(%s)\b`, strings.Join(maps.Keys(AllColorSettingNames), "|")))
return pat.ReplaceAllString(ntext, `# $1`)
}
func is_kitty_gui_cmdline(cmd ...string) bool {
if len(cmd) == 0 {
return false
}
if filepath.Base(cmd[0]) != "kitty" {
return false
}
if len(cmd) == 1 {
return true
}
s := cmd[1][:1]
switch s {
case `@`:
return false
case `+`:
if cmd[1] == `+` {
return len(cmd) > 2 && cmd[2] == `open`
}
return cmd[1] == `+open`
}
return true
}
type ReloadDestination string
const (
RELOAD_IN_PARENT ReloadDestination = "parent"
RELOAD_IN_ALL = "all"
)
func reload_config(reload_in ReloadDestination) bool {
switch reload_in {
case RELOAD_IN_PARENT:
if pid, err := strconv.Atoi(os.Getenv("KITTY_PID")); err == nil {
if p, err := process.NewProcess(int32(pid)); err == nil {
if c, err := p.CmdlineSlice(); err == nil && is_kitty_gui_cmdline(c...) {
return p.SendSignal(unix.SIGUSR1) == nil
}
}
}
case RELOAD_IN_ALL:
if all, err := process.Processes(); err == nil {
for _, p := range all {
if c, err := p.CmdlineSlice(); err == nil && is_kitty_gui_cmdline(c...) {
p.SendSignal(unix.SIGUSR1)
}
}
return true
}
}
return false
}
func (self *Theme) SaveInConf(config_dir, reload_in, config_file_name string) (err error) {
os.MkdirAll(config_dir, 0o755)
path := filepath.Join(config_dir, `current-theme.conf`)
code, err := self.Code()
if err != nil {
return err
}
err = utils.AtomicUpdateFile(path, utils.UnsafeStringToBytes(code), 0o644)
if err != nil {
return err
}
confpath := filepath.Join(config_dir, config_file_name)
if q, err := filepath.EvalSymlinks(confpath); err == nil {
confpath = q
}
raw, err := os.ReadFile(confpath)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
nraw := patch_conf(utils.UnsafeBytesToString(raw), self.metadata.Name)
if len(raw) > 0 {
os.WriteFile(confpath+".bak", raw, 0o600)
}
err = utils.AtomicUpdateFile(confpath, utils.UnsafeStringToBytes(nraw), 0o600)
if err != nil {
return err
}
reload_config(ReloadDestination(reload_in))
return
}
func (self *Theme) Settings() (map[string]string, error) {
if self.zip_reader != nil {
code, err := self.load_code()
@ -367,7 +762,7 @@ func (self *Themes) add_from_zip_file(zippath string) (io.Closer, error) {
return nil, err
}
name_map := make(map[string]*zip.File, len(r.File))
var themes []ThemeMetadata
var themes []*ThemeMetadata
theme_dir := ""
for _, file := range r.File {
name_map[file.Name] = file
@ -395,7 +790,7 @@ func (self *Themes) add_from_zip_file(zippath string) (io.Closer, error) {
key := path.Join(theme_dir, theme.Filepath)
f := name_map[key]
if f != nil {
t := Theme{metadata: &theme, zip_reader: f}
t := Theme{metadata: theme, zip_reader: f}
self.name_map[theme.Name] = &t
}
}
@ -403,7 +798,16 @@ func (self *Themes) add_from_zip_file(zippath string) (io.Closer, error) {
}
func (self *Themes) ThemeByName(name string) *Theme {
return self.name_map[name]
ans := self.name_map[name]
if ans == nil {
q := strings.ToLower(name)
for k, t := range self.name_map {
if strings.ToLower(k) == q {
return t
}
}
}
return ans
}
func LoadThemes(cache_age time.Duration) (ans *Themes, closer io.Closer, err error) {
@ -427,3 +831,31 @@ func ThemeFromFile(path string) (*Theme, error) {
ans := &Themes{name_map: make(map[string]*Theme)}
return ans.AddFromFile(path)
}
func GetThemeNames(cache_age time.Duration) (ans []string, err error) {
themes, closer, err := LoadThemes(cache_age)
if err != nil {
if errors.Is(err, ErrNoCacheFound) {
return []string{"Default"}, nil
}
return nil, err
}
defer closer.Close()
for name := range themes.name_map {
ans = append(ans, name)
}
return
}
func CompleteThemes(completions *cli.Completions, word string, arg_num int) {
names, err := GetThemeNames(-1)
if err != nil {
mg := completions.AddMatchGroup("Themes")
for _, theme_name := range names {
theme_name = strings.TrimSpace(theme_name)
if theme_name != "" && strings.HasPrefix(theme_name, word) {
mg.AddMatch(theme_name)
}
}
}
}