mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-25 17:52:02 +02:00
More work on porting themes
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"kitty/tools/themes"
|
"kitty/tools/themes"
|
||||||
|
"kitty/tools/utils"
|
||||||
|
"kitty/tools/wcswidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Print
|
var _ = fmt.Print
|
||||||
@@ -40,10 +42,58 @@ func (self *ThemesList) Next(delta int, allow_wrapping bool) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func limit_lengths(text string) string {
|
||||||
|
t, x := wcswidth.TruncateToVisualLengthWithWidth(text, 31)
|
||||||
|
if x >= len(text) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return t + "…"
|
||||||
|
}
|
||||||
|
|
||||||
func (self *ThemesList) UpdateThemes(themes *themes.Themes) {
|
func (self *ThemesList) UpdateThemes(themes *themes.Themes) {
|
||||||
self.themes, self.all_themes = themes, themes
|
self.themes, self.all_themes = themes, themes
|
||||||
if self.current_search != "" {
|
if self.current_search != "" {
|
||||||
self.themes = self.all_themes.Copy()
|
self.themes = self.all_themes.Copy()
|
||||||
|
self.display_strings = utils.Map(limit_lengths, self.themes.ApplySearch(self.current_search))
|
||||||
} else {
|
} else {
|
||||||
|
self.display_strings = utils.Map(limit_lengths, self.themes.Names())
|
||||||
}
|
}
|
||||||
|
self.widths = utils.Map(wcswidth.Stringwidth, self.display_strings)
|
||||||
|
self.max_width = utils.Max(0, self.widths...)
|
||||||
|
self.current_idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ThemesList) UpdateSearch(query string) bool {
|
||||||
|
if query == self.current_search || self.all_themes == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
self.current_search = query
|
||||||
|
self.UpdateThemes(self.all_themes)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Line struct {
|
||||||
|
text string
|
||||||
|
width int
|
||||||
|
is_current bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ThemesList) Lines(num_rows int) []Line {
|
||||||
|
if num_rows < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ans := make([]Line, 0, len(self.display_strings))
|
||||||
|
before_num := utils.Min(self.current_idx, num_rows-1)
|
||||||
|
start := self.current_idx - before_num
|
||||||
|
for i := start; i < utils.Min(start+num_rows, len(self.display_strings)); i++ {
|
||||||
|
ans = append(ans, Line{self.display_strings[i], self.widths[i], i == self.current_idx})
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ThemesList) CurrentTheme() *themes.Theme {
|
||||||
|
if self.themes == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self.themes.At(self.current_idx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
|||||||
lp.SetCursorVisible(true)
|
lp.SetCursorVisible(true)
|
||||||
return ``
|
return ``
|
||||||
}
|
}
|
||||||
|
lp.OnResize = func(_, _ loop.ScreenSize) error {
|
||||||
|
h.draw_screen()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lp.OnKeyEvent = h.on_key_event
|
||||||
err = lp.Run()
|
err = lp.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
|
|||||||
@@ -5,9 +5,20 @@ package themes
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"kitty/tools/config"
|
||||||
"kitty/tools/themes"
|
"kitty/tools/themes"
|
||||||
"kitty/tools/tui/loop"
|
"kitty/tools/tui/loop"
|
||||||
"time"
|
"kitty/tools/tui/readline"
|
||||||
|
"kitty/tools/utils"
|
||||||
|
"kitty/tools/wcswidth"
|
||||||
|
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Print
|
var _ = fmt.Print
|
||||||
@@ -20,6 +31,7 @@ const (
|
|||||||
SEARCHING
|
SEARCHING
|
||||||
ACCEPTING
|
ACCEPTING
|
||||||
)
|
)
|
||||||
|
const SEPARATOR = "║"
|
||||||
|
|
||||||
type CachedData struct {
|
type CachedData struct {
|
||||||
Recent []string `json:"recent"`
|
Recent []string `json:"recent"`
|
||||||
@@ -32,17 +44,38 @@ type fetch_data struct {
|
|||||||
closer io.Closer
|
closer io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var category_filters = map[string]func(*themes.Theme) bool{
|
||||||
|
"all": func(*themes.Theme) bool { return true },
|
||||||
|
"dark": func(t *themes.Theme) bool { return t.IsDark() },
|
||||||
|
"light": func(t *themes.Theme) bool { return !t.IsDark() },
|
||||||
|
"user": func(t *themes.Theme) bool { return t.IsUserDefined() },
|
||||||
|
}
|
||||||
|
|
||||||
|
func recent_filter(items []string) func(*themes.Theme) bool {
|
||||||
|
allowed := utils.NewSetWithItems(items...)
|
||||||
|
return func(t *themes.Theme) bool {
|
||||||
|
return allowed.Has(t.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
lp *loop.Loop
|
lp *loop.Loop
|
||||||
opts *Options
|
opts *Options
|
||||||
cached_data *CachedData
|
cached_data *CachedData
|
||||||
|
|
||||||
state State
|
state State
|
||||||
fetch_result chan fetch_data
|
fetch_result chan fetch_data
|
||||||
all_themes *themes.Themes
|
all_themes *themes.Themes
|
||||||
themes_closer io.Closer
|
themes_closer io.Closer
|
||||||
|
themes_list *ThemesList
|
||||||
|
category_filters map[string]func(*themes.Theme) bool
|
||||||
|
colors_set_once bool
|
||||||
|
tabs []string
|
||||||
|
rl *readline.Readline
|
||||||
|
quit_on_next_key_release int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetching {{{
|
||||||
func (self *handler) fetch_themes() {
|
func (self *handler) fetch_themes() {
|
||||||
r := fetch_data{}
|
r := fetch_data{}
|
||||||
r.themes, r.closer, r.err = themes.LoadThemes(time.Duration(self.opts.CacheAge * float64(time.Hour*24)))
|
r.themes, r.closer, r.err = themes.LoadThemes(time.Duration(self.opts.CacheAge * float64(time.Hour*24)))
|
||||||
@@ -50,6 +83,14 @@ func (self *handler) fetch_themes() {
|
|||||||
self.fetch_result <- r
|
self.fetch_result <- r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *handler) on_fetching_key_event(ev *loop.KeyEvent) error {
|
||||||
|
if ev.MatchesRelease("esc") {
|
||||||
|
self.lp.Quit(0)
|
||||||
|
ev.Handled = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (self *handler) on_wakeup() error {
|
func (self *handler) on_wakeup() error {
|
||||||
r := <-self.fetch_result
|
r := <-self.fetch_result
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
@@ -62,23 +103,490 @@ func (self *handler) on_wakeup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_fetching_screen() {
|
||||||
|
self.lp.Println("Downloading themes from repository, please wait...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
func (self *handler) finalize() {
|
func (self *handler) finalize() {
|
||||||
t := self.themes_closer
|
t := self.themes_closer
|
||||||
if t != nil {
|
if t != nil {
|
||||||
t.Close()
|
t.Close()
|
||||||
|
self.themes_closer = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *handler) initialize() {
|
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.themes_list = &ThemesList{}
|
||||||
self.fetch_result = make(chan fetch_data)
|
self.fetch_result = make(chan fetch_data)
|
||||||
|
self.category_filters = make(map[string]func(*themes.Theme) bool, len(category_filters)+1)
|
||||||
|
maps.Copy(self.category_filters, category_filters)
|
||||||
|
self.category_filters["recent"] = recent_filter(self.cached_data.Recent)
|
||||||
go self.fetch_themes()
|
go self.fetch_themes()
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *handler) enforce_cursor_state() {
|
||||||
|
self.lp.SetCursorVisible(self.state == FETCHING)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *handler) draw_screen() {
|
func (self *handler) draw_screen() {
|
||||||
// TODO: Implement me
|
self.lp.StartAtomicUpdate()
|
||||||
|
defer self.lp.EndAtomicUpdate()
|
||||||
|
self.lp.ClearScreen()
|
||||||
|
self.enforce_cursor_state()
|
||||||
|
switch self.state {
|
||||||
|
case FETCHING:
|
||||||
|
self.draw_fetching_screen()
|
||||||
|
case BROWSING, SEARCHING:
|
||||||
|
self.draw_browsing_screen()
|
||||||
|
case ACCEPTING:
|
||||||
|
self.draw_accepting_screen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) current_category() string {
|
||||||
|
ans := self.cached_data.Category
|
||||||
|
if self.category_filters[ans] == nil {
|
||||||
|
ans = "all"
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) set_current_category(category string) {
|
||||||
|
if self.category_filters[category] == nil {
|
||||||
|
category = "all"
|
||||||
|
}
|
||||||
|
self.cached_data.Category = category
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadKittyColorSettings() map[string]string {
|
||||||
|
settings := make(map[string]string, 512)
|
||||||
|
handle_line := func(key, val string) error {
|
||||||
|
if themes.AllColorSettingNames[key] {
|
||||||
|
settings[key] = val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cp := config.ConfigParser{LineHandler: handle_line}
|
||||||
|
cp.ParseFiles(filepath.Join(utils.ConfigDir(), "kitty.conf"))
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) set_colors_to_current_theme() bool {
|
||||||
|
if self.themes_list == nil && self.colors_set_once {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
self.colors_set_once = true
|
||||||
|
if self.themes_list != nil {
|
||||||
|
t := self.themes_list.CurrentTheme()
|
||||||
|
if t != nil {
|
||||||
|
raw, err := t.AsEscapeCodes()
|
||||||
|
if err == nil {
|
||||||
|
self.lp.QueueWriteString(raw)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.lp.QueueWriteString(themes.ColorSettingsAsEscapeCodes(ReadKittyColorSettings()))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *handler) redraw_after_category_change() {
|
func (self *handler) redraw_after_category_change() {
|
||||||
// TODO: Implement me
|
self.themes_list.UpdateThemes(self.all_themes.Filtered(self.category_filters[self.current_category()]))
|
||||||
|
self.set_colors_to_current_theme()
|
||||||
|
self.draw_screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *handler) on_key_event(ev *loop.KeyEvent) error {
|
||||||
|
if self.quit_on_next_key_release > -1 && ev.Type == loop.RELEASE {
|
||||||
|
self.lp.Quit(self.quit_on_next_key_release)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch self.state {
|
||||||
|
case FETCHING:
|
||||||
|
return self.on_fetching_key_event(ev)
|
||||||
|
case BROWSING:
|
||||||
|
return self.on_browsing_key_event(ev)
|
||||||
|
case SEARCHING:
|
||||||
|
return self.on_searching_key_event(ev)
|
||||||
|
case ACCEPTING:
|
||||||
|
return self.on_accepting_key_event(ev)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// browsing ... {{{
|
||||||
|
|
||||||
|
func (self *handler) next_category(delta int) {
|
||||||
|
idx := slices.Index(self.tabs, self.current_category()) + delta + len(self.tabs)
|
||||||
|
self.set_current_category(self.tabs[idx%len(self.tabs)])
|
||||||
|
self.redraw_after_category_change()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) next(delta int, allow_wrapping bool) {
|
||||||
|
if self.themes_list.Next(delta, allow_wrapping) {
|
||||||
|
self.set_colors_to_current_theme()
|
||||||
|
self.draw_screen()
|
||||||
|
} else {
|
||||||
|
self.lp.Beep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) on_browsing_key_event(ev *loop.KeyEvent) error {
|
||||||
|
if ev.MatchesRelease("esc") || ev.MatchesRelease("q") {
|
||||||
|
self.lp.Quit(0)
|
||||||
|
ev.Handled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, cat := range self.tabs {
|
||||||
|
if ev.MatchesRelease(cat[0:1]) || ev.MatchesRelease("alt+"+cat[0:1]) {
|
||||||
|
ev.Handled = true
|
||||||
|
if cat != self.current_category() {
|
||||||
|
self.set_current_category(cat)
|
||||||
|
self.redraw_after_category_change()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("left") || ev.MatchesRelease("shift+tab") {
|
||||||
|
self.next_category(-1)
|
||||||
|
ev.Handled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("right") || ev.MatchesRelease("tab") {
|
||||||
|
self.next_category(1)
|
||||||
|
ev.Handled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("j") || ev.MatchesRelease("down") {
|
||||||
|
self.next(1, true)
|
||||||
|
ev.Handled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("k") || ev.MatchesRelease("up") {
|
||||||
|
self.next(-1, true)
|
||||||
|
ev.Handled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("page_down") {
|
||||||
|
ev.Handled = true
|
||||||
|
sz, err := self.lp.ScreenSize()
|
||||||
|
if err == nil {
|
||||||
|
self.next(int(sz.HeightCells)-3, false)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("page_up") {
|
||||||
|
ev.Handled = true
|
||||||
|
sz, err := self.lp.ScreenSize()
|
||||||
|
if err == nil {
|
||||||
|
self.next(3-int(sz.HeightCells), false)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("s") || ev.MatchesRelease("/") {
|
||||||
|
ev.Handled = true
|
||||||
|
self.start_search()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) start_search() {
|
||||||
|
self.state = SEARCHING
|
||||||
|
self.rl.SetText(self.themes_list.current_search)
|
||||||
|
self.draw_screen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_browsing_screen() {
|
||||||
|
self.draw_tab_bar()
|
||||||
|
sz, err := self.lp.ScreenSize()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
num_rows := int(sz.HeightCells) - 2
|
||||||
|
mw := self.themes_list.max_width + 1
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.themes_list != nil && self.themes_list.Len() > 0 {
|
||||||
|
self.draw_theme_demo()
|
||||||
|
}
|
||||||
|
if self.state == BROWSING {
|
||||||
|
self.draw_bottom_bar()
|
||||||
|
} else {
|
||||||
|
self.draw_search_bar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_bottom_bar() {
|
||||||
|
sz, err := self.lp.ScreenSize()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.lp.MoveCursorTo(1, int(sz.HeightCells))
|
||||||
|
self.lp.PrintStyled("reverse", strings.Repeat(" ", int(sz.WidthCells)))
|
||||||
|
self.lp.QueueWriteString("\r")
|
||||||
|
|
||||||
|
draw_tab := func(t, sc string) {
|
||||||
|
text := self.mark_shortcut(utils.Capitalize(t), sc)
|
||||||
|
self.lp.PrintStyled("reverse", " "+text+" ")
|
||||||
|
}
|
||||||
|
draw_tab("search (/)", "s")
|
||||||
|
draw_tab("accept (⏎)", "c")
|
||||||
|
self.lp.QueueWriteString("\x1b[m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_search_bar() {
|
||||||
|
sz, err := self.lp.ScreenSize()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.lp.MoveCursorTo(1, int(sz.HeightCells))
|
||||||
|
self.lp.ClearToEndOfLine()
|
||||||
|
self.rl.RedrawNonAtomic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) mark_shortcut(text, acc string) string {
|
||||||
|
acc_idx := strings.Index(strings.ToLower(text), strings.ToLower(acc))
|
||||||
|
return text[:acc_idx] + self.lp.SprintStyled("underline bold", text[acc_idx:acc_idx+1]) + text[acc_idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_tab_bar() {
|
||||||
|
sz, err := self.lp.ScreenSize()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.lp.PrintStyled("reverse", strings.Repeat(` `, int(sz.WidthCells)))
|
||||||
|
self.lp.QueueWriteString("\r")
|
||||||
|
cc := self.current_category()
|
||||||
|
draw_tab := func(text, name, acc string) {
|
||||||
|
is_active := name == cc
|
||||||
|
if is_active {
|
||||||
|
text := self.lp.SprintStyled("italic", fmt.Sprintf("%s #%d", text, self.themes_list.Len()))
|
||||||
|
self.lp.Printf(" %s ", text)
|
||||||
|
} else {
|
||||||
|
text = self.mark_shortcut(text, acc)
|
||||||
|
self.lp.PrintStyled("reverse", " "+text+" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, title := range self.tabs {
|
||||||
|
draw_tab(utils.Capitalize(title), title, string([]rune(title)[0]))
|
||||||
|
}
|
||||||
|
self.lp.Println("\x1b[m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func center_string(x string, width int) string {
|
||||||
|
l := wcswidth.Stringwidth(x)
|
||||||
|
spaces := int(float64(width-l) / 2)
|
||||||
|
return strings.Repeat(" ", spaces) + x + strings.Repeat(" ", width-(spaces+l))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_theme_demo() {
|
||||||
|
ssz, err := self.lp.ScreenSize()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
theme := self.themes_list.CurrentTheme()
|
||||||
|
if theme == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xstart := self.themes_list.max_width + 3
|
||||||
|
sz := int(ssz.WidthCells) - xstart
|
||||||
|
if sz < 20 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sz--
|
||||||
|
y := 0
|
||||||
|
colors := strings.Split(`black red green yellow blue magenta cyan white`, ` `)
|
||||||
|
trunc := sz/8 - 1
|
||||||
|
pat := regexp.MustCompile(`\s+`)
|
||||||
|
|
||||||
|
next_line := func() {
|
||||||
|
self.lp.QueueWriteString("\r")
|
||||||
|
y++
|
||||||
|
self.lp.MoveCursorTo(xstart, y+1)
|
||||||
|
self.lp.QueueWriteString(SEPARATOR + " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
write_para := func(text string) {
|
||||||
|
text = pat.ReplaceAllLiteralString(text, " ")
|
||||||
|
for text != "" {
|
||||||
|
t, sp := wcswidth.TruncateToVisualLengthWithWidth(text, sz)
|
||||||
|
self.lp.QueueWriteString(t)
|
||||||
|
next_line()
|
||||||
|
text = text[sp:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_colors := func(bg string) {
|
||||||
|
for _, intense := range []bool{false, true} {
|
||||||
|
buf := strings.Builder{}
|
||||||
|
buf.Grow(1024)
|
||||||
|
for _, c := range colors {
|
||||||
|
s := c
|
||||||
|
if intense {
|
||||||
|
s = "bright-" + s
|
||||||
|
}
|
||||||
|
buf.WriteString(self.lp.SprintStyled("fg="+c, c[:trunc]))
|
||||||
|
buf.WriteString(" ")
|
||||||
|
}
|
||||||
|
text := strings.TrimSpace(buf.String())
|
||||||
|
if bg == "" {
|
||||||
|
self.lp.QueueWriteString(text)
|
||||||
|
} else {
|
||||||
|
s := bg
|
||||||
|
if intense {
|
||||||
|
s = "bright-" + s
|
||||||
|
}
|
||||||
|
self.lp.PrintStyled("bg="+s, text)
|
||||||
|
}
|
||||||
|
next_line()
|
||||||
|
}
|
||||||
|
next_line()
|
||||||
|
}
|
||||||
|
self.lp.MoveCursorTo(1, 1)
|
||||||
|
next_line()
|
||||||
|
self.lp.PrintStyled("fg=green bold", center_string(theme.Name(), sz))
|
||||||
|
next_line()
|
||||||
|
if theme.Author() != "" {
|
||||||
|
self.lp.PrintStyled("italic", center_string(theme.Author(), sz))
|
||||||
|
next_line()
|
||||||
|
}
|
||||||
|
if theme.Blurb() != "" {
|
||||||
|
next_line()
|
||||||
|
write_para(theme.Blurb())
|
||||||
|
next_line()
|
||||||
|
}
|
||||||
|
write_colors("")
|
||||||
|
for _, bg := range colors {
|
||||||
|
write_colors(bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// accepting {{{
|
||||||
|
|
||||||
|
func (self *handler) on_accepting_key_event(ev *loop.KeyEvent) error {
|
||||||
|
if ev.MatchesRelease("q") || ev.MatchesRelease("esc") {
|
||||||
|
ev.Handled = true
|
||||||
|
self.lp.Quit(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("a") {
|
||||||
|
ev.Handled = true
|
||||||
|
self.state = BROWSING
|
||||||
|
self.draw_screen()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("p") {
|
||||||
|
ev.Handled = true
|
||||||
|
self.themes_list.CurrentTheme().SaveInDir(utils.ConfigDir())
|
||||||
|
self.update_recent()
|
||||||
|
self.lp.Quit(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ev.MatchesRelease("m") {
|
||||||
|
ev.Handled = true
|
||||||
|
self.themes_list.CurrentTheme().SaveInConf(utils.ConfigDir(), self.opts.ReloadIn, self.opts.ConfigFileName)
|
||||||
|
self.update_recent()
|
||||||
|
self.lp.Quit(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) update_recent() {
|
||||||
|
if self.themes_list != nil {
|
||||||
|
recent := slices.Clone(self.cached_data.Recent)
|
||||||
|
name := self.themes_list.CurrentTheme().Name()
|
||||||
|
recent = utils.Remove(recent, name)
|
||||||
|
recent = append([]string{name}, recent...)
|
||||||
|
self.cached_data.Recent = recent[:20]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *handler) draw_accepting_screen() {
|
||||||
|
name := self.themes_list.CurrentTheme().Name()
|
||||||
|
name = self.lp.SprintStyled("fg=green bold", name)
|
||||||
|
kc := self.lp.SprintStyled("italic", self.opts.ConfigFileName)
|
||||||
|
|
||||||
|
ac := func(x string) string {
|
||||||
|
return self.lp.SprintStyled("fg=red", x)
|
||||||
|
}
|
||||||
|
self.lp.AllowLineWrapping(true)
|
||||||
|
defer self.lp.AllowLineWrapping(false)
|
||||||
|
self.lp.Printf(`You have chosen the %s theme`, name)
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Println(`What would you like to do?`)
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Printf(` %sodify %s to load %s`, ac("M"), kc, name)
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Printf(` %slace the theme file in %s but do not modify %s`, ac("P"), utils.ConfigDir(), kc)
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Printf(` %sbort and return to list of themes`, ac("A"))
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Println()
|
||||||
|
self.lp.Printf(` %suit`, ac("Q"))
|
||||||
|
self.lp.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// searching {{{
|
||||||
|
func (self *handler) on_searching_key_event(ev *loop.KeyEvent) error {
|
||||||
|
if ev.MatchesRelease("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") {
|
||||||
|
ev.Handled = true
|
||||||
|
self.state = BROWSING
|
||||||
|
self.themes_list.UpdateSearch("")
|
||||||
|
self.set_colors_to_current_theme()
|
||||||
|
self.draw_screen()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := self.rl.OnKeyEvent(ev)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|||||||
@@ -508,6 +508,12 @@ type Theme struct {
|
|||||||
is_user_defined bool
|
is_user_defined bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Theme) Name() string { return self.metadata.Name }
|
||||||
|
func (self *Theme) Author() string { return self.metadata.Author }
|
||||||
|
func (self *Theme) Blurb() string { return self.metadata.Blurb }
|
||||||
|
func (self *Theme) IsDark() bool { return self.metadata.Is_dark }
|
||||||
|
func (self *Theme) IsUserDefined() bool { return self.is_user_defined }
|
||||||
|
|
||||||
func (self *Theme) load_code() (string, error) {
|
func (self *Theme) load_code() (string, error) {
|
||||||
if self.zip_reader != nil {
|
if self.zip_reader != nil {
|
||||||
f, err := self.zip_reader.Open()
|
f, err := self.zip_reader.Open()
|
||||||
@@ -599,6 +605,15 @@ func reload_config(reload_in ReloadDestination) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Theme) SaveInDir(dirpath string) (err error) {
|
||||||
|
path := filepath.Join(dirpath, self.Name()+".conf")
|
||||||
|
code, err := self.Code()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return utils.AtomicUpdateFile(path, utils.UnsafeStringToBytes(code), 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Theme) SaveInConf(config_dir, reload_in, config_file_name string) (err error) {
|
func (self *Theme) SaveInConf(config_dir, reload_in, config_file_name string) (err error) {
|
||||||
os.MkdirAll(config_dir, 0o755)
|
os.MkdirAll(config_dir, 0o755)
|
||||||
path := filepath.Join(config_dir, `current-theme.conf`)
|
path := filepath.Join(config_dir, `current-theme.conf`)
|
||||||
@@ -656,6 +671,10 @@ func (self *Theme) AsEscapeCodes() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return ColorSettingsAsEscapeCodes(settings), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColorSettingsAsEscapeCodes(settings map[string]string) string {
|
||||||
w := strings.Builder{}
|
w := strings.Builder{}
|
||||||
w.Grow(4096)
|
w.Grow(4096)
|
||||||
|
|
||||||
@@ -707,7 +726,7 @@ func (self *Theme) AsEscapeCodes() (string, error) {
|
|||||||
set_color(i, rgba.AsRGBSharp())
|
set_color(i, rgba.AsRGBSharp())
|
||||||
}
|
}
|
||||||
w.WriteString("\033\\")
|
w.WriteString("\033\\")
|
||||||
return w.String(), nil
|
return w.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Themes struct {
|
type Themes struct {
|
||||||
@@ -733,6 +752,24 @@ func theme_name_from_file_name(fname string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *Themes) Len() int { return len(self.name_map) }
|
func (self *Themes) Len() int { return len(self.name_map) }
|
||||||
|
func (self *Themes) At(x int) *Theme {
|
||||||
|
if x >= len(self.index_map) || x < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self.name_map[self.index_map[x]]
|
||||||
|
}
|
||||||
|
func (self *Themes) Names() []string { return self.index_map }
|
||||||
|
|
||||||
|
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)
|
||||||
|
return &ans
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Themes) AddFromFile(path string) (*Theme, error) {
|
func (self *Themes) AddFromFile(path string) (*Theme, error) {
|
||||||
m, conf, err := parse_theme_metadata(path)
|
m, conf, err := parse_theme_metadata(path)
|
||||||
@@ -826,8 +863,13 @@ func match(expression string, items []string) []*subseq.Match {
|
|||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MARK_BEFORE = "\033[33m"
|
||||||
|
MARK_AFTER = "\033[39m"
|
||||||
|
)
|
||||||
|
|
||||||
func (self *Themes) ApplySearch(expression string, marks ...string) []string {
|
func (self *Themes) ApplySearch(expression string, marks ...string) []string {
|
||||||
mark_before, mark_after := "\033[33m", "\033[39m"
|
mark_before, mark_after := MARK_BEFORE, MARK_AFTER
|
||||||
if len(marks) == 2 {
|
if len(marks) == 2 {
|
||||||
mark_before, mark_after = marks[0], marks[1]
|
mark_before, mark_after = marks[0], marks[1]
|
||||||
}
|
}
|
||||||
@@ -847,6 +889,7 @@ func (self *Themes) ApplySearch(expression string, marks ...string) []string {
|
|||||||
}
|
}
|
||||||
self.name_map = name_map
|
self.name_map = name_map
|
||||||
self.index_map = maps.Keys(name_map)
|
self.index_map = maps.Keys(name_map)
|
||||||
|
self.index_map = utils.StableSortWithKey(self.index_map, strings.ToLower)
|
||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ func (self *Loop) SetCursorVisible(visible bool) {
|
|||||||
|
|
||||||
const MoveCursorToTemplate = "\x1b[%d;%dH"
|
const MoveCursorToTemplate = "\x1b[%d;%dH"
|
||||||
|
|
||||||
func (self *Loop) MoveCursorTo(x, y int) {
|
func (self *Loop) MoveCursorTo(x, y int) { // 1, 1 is top left
|
||||||
if x > 0 && y > 0 {
|
if x > 0 && y > 0 {
|
||||||
self.QueueWriteString(fmt.Sprintf(MoveCursorToTemplate, y, x))
|
self.QueueWriteString(fmt.Sprintf(MoveCursorToTemplate, y, x))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user