mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
diff kitten: Automatically change colors on terminal color scheme change
This commit is contained in:
@@ -20,7 +20,8 @@ var path_name_map, remote_dirs map[string]string
|
|||||||
var mimetypes_cache, data_cache, hash_cache *utils.LRUCache[string, string]
|
var mimetypes_cache, data_cache, hash_cache *utils.LRUCache[string, string]
|
||||||
var size_cache *utils.LRUCache[string, int64]
|
var size_cache *utils.LRUCache[string, int64]
|
||||||
var lines_cache *utils.LRUCache[string, []string]
|
var lines_cache *utils.LRUCache[string, []string]
|
||||||
var highlighted_lines_cache *utils.LRUCache[string, []string]
|
var light_highlighted_lines_cache *utils.LRUCache[string, []string]
|
||||||
|
var dark_highlighted_lines_cache *utils.LRUCache[string, []string]
|
||||||
var is_text_cache *utils.LRUCache[string, bool]
|
var is_text_cache *utils.LRUCache[string, bool]
|
||||||
|
|
||||||
func init_caches() {
|
func init_caches() {
|
||||||
@@ -32,7 +33,8 @@ func init_caches() {
|
|||||||
data_cache = utils.NewLRUCache[string, string](sz)
|
data_cache = utils.NewLRUCache[string, string](sz)
|
||||||
is_text_cache = utils.NewLRUCache[string, bool](sz)
|
is_text_cache = utils.NewLRUCache[string, bool](sz)
|
||||||
lines_cache = utils.NewLRUCache[string, []string](sz)
|
lines_cache = utils.NewLRUCache[string, []string](sz)
|
||||||
highlighted_lines_cache = utils.NewLRUCache[string, []string](sz)
|
light_highlighted_lines_cache = utils.NewLRUCache[string, []string](sz)
|
||||||
|
dark_highlighted_lines_cache = utils.NewLRUCache[string, []string](sz)
|
||||||
hash_cache = utils.NewLRUCache[string, string](sz)
|
hash_cache = utils.NewLRUCache[string, string](sz)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +152,14 @@ func highlighted_lines_for_path(path string) ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ans, found := highlighted_lines_cache.Get(path); found && len(ans) == len(plain_lines) {
|
var ans []string
|
||||||
|
var found bool
|
||||||
|
if use_light_colors {
|
||||||
|
ans, found = light_highlighted_lines_cache.Get(path)
|
||||||
|
} else {
|
||||||
|
ans, found = dark_highlighted_lines_cache.Get(path)
|
||||||
|
}
|
||||||
|
if found && len(ans) == len(plain_lines) {
|
||||||
return ans, nil
|
return ans, nil
|
||||||
}
|
}
|
||||||
return plain_lines, nil
|
return plain_lines, nil
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ func ansi_formatter(w io.Writer, style *chroma.Style, it chroma.Iterator) (err e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolved_chroma_style() *chroma.Style {
|
func resolved_chroma_style(use_light_colors bool) *chroma.Style {
|
||||||
name := utils.IfElse(use_dark_colors, conf.Dark_pygments_style, conf.Pygments_style)
|
name := utils.IfElse(use_light_colors, conf.Pygments_style, conf.Dark_pygments_style)
|
||||||
var style *chroma.Style
|
var style *chroma.Style
|
||||||
if name == "default" {
|
if name == "default" {
|
||||||
style = DefaultStyle()
|
style = DefaultStyle()
|
||||||
@@ -185,7 +185,7 @@ func resolved_chroma_style() *chroma.Style {
|
|||||||
var tokens_map map[string][]chroma.Token
|
var tokens_map map[string][]chroma.Token
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
||||||
func highlight_file(path string) (highlighted string, err error) {
|
func highlight_file(path string, use_light_colors bool) (highlighted string, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(error)
|
e, ok := r.(error)
|
||||||
@@ -235,19 +235,24 @@ func highlight_file(path string) (highlighted string, err error) {
|
|||||||
formatter := chroma.FormatterFunc(ansi_formatter)
|
formatter := chroma.FormatterFunc(ansi_formatter)
|
||||||
w := strings.Builder{}
|
w := strings.Builder{}
|
||||||
w.Grow(len(text) * 2)
|
w.Grow(len(text) * 2)
|
||||||
err = formatter.Format(&w, resolved_chroma_style(), chroma.Literator(tokens...))
|
err = formatter.Format(&w, resolved_chroma_style(use_light_colors), chroma.Literator(tokens...))
|
||||||
// os.WriteFile(filepath.Base(path+".highlighted"), []byte(w.String()), 0o600)
|
// os.WriteFile(filepath.Base(path+".highlighted"), []byte(w.String()), 0o600)
|
||||||
return w.String(), err
|
return w.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func highlight_all(paths []string) {
|
func highlight_all(paths []string, light bool) {
|
||||||
ctx := images.Context{}
|
ctx := images.Context{}
|
||||||
ctx.Parallel(0, len(paths), func(nums <-chan int) {
|
ctx.Parallel(0, len(paths), func(nums <-chan int) {
|
||||||
for i := range nums {
|
for i := range nums {
|
||||||
path := paths[i]
|
path := paths[i]
|
||||||
raw, err := highlight_file(path)
|
raw, err := highlight_file(path, light)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
highlighted_lines_cache.Set(path, text_to_lines(raw))
|
continue
|
||||||
|
}
|
||||||
|
if light {
|
||||||
|
light_highlighted_lines_cache.Set(path, text_to_lines(raw))
|
||||||
|
} else {
|
||||||
|
dark_highlighted_lines_cache.Set(path, text_to_lines(raw))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
|
lp.ColorSchemeChangeNotifications()
|
||||||
h := Handler{left: left, right: right, lp: lp}
|
h := Handler{left: left, right: right, lp: lp}
|
||||||
lp.OnInitialize = func() (string, error) {
|
lp.OnInitialize = func() (string, error) {
|
||||||
lp.SetCursorVisible(false)
|
lp.SetCursorVisible(false)
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ var format_as_sgr struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var statusline_format, added_count_format, removed_count_format, message_format func(...any) string
|
var statusline_format, added_count_format, removed_count_format, message_format func(...any) string
|
||||||
var use_dark_colors bool = true
|
var use_light_colors bool = false
|
||||||
|
|
||||||
type ResolvedColors struct {
|
type ResolvedColors struct {
|
||||||
Added_bg style.RGBA
|
Added_bg style.RGBA
|
||||||
@@ -189,7 +189,7 @@ var resolved_colors ResolvedColors
|
|||||||
|
|
||||||
func create_formatters() {
|
func create_formatters() {
|
||||||
rc := &resolved_colors
|
rc := &resolved_colors
|
||||||
if use_dark_colors {
|
if !use_light_colors {
|
||||||
rc.Added_bg = conf.Dark_added_bg
|
rc.Added_bg = conf.Dark_added_bg
|
||||||
rc.Added_margin_bg = conf.Dark_added_margin_bg
|
rc.Added_margin_bg = conf.Dark_added_margin_bg
|
||||||
rc.Background = conf.Dark_background
|
rc.Background = conf.Dark_background
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ func set_terminal_colors(lp *loop.Loop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *Handler) on_capabilities_received(tc loop.TerminalCapabilities) {
|
func (self *Handler) on_capabilities_received(tc loop.TerminalCapabilities) {
|
||||||
|
var use_dark_colors bool
|
||||||
switch conf.Color_scheme {
|
switch conf.Color_scheme {
|
||||||
case Color_scheme_auto:
|
case Color_scheme_auto:
|
||||||
use_dark_colors = tc.ColorPreference != loop.LIGHT_COLOR_PREFERENCE
|
use_dark_colors = tc.ColorPreference != loop.LIGHT_COLOR_PREFERENCE
|
||||||
@@ -132,14 +133,30 @@ func (self *Handler) on_capabilities_received(tc loop.TerminalCapabilities) {
|
|||||||
case Color_scheme_dark:
|
case Color_scheme_dark:
|
||||||
use_dark_colors = true
|
use_dark_colors = true
|
||||||
}
|
}
|
||||||
|
use_light_colors = !use_dark_colors
|
||||||
set_terminal_colors(self.lp)
|
set_terminal_colors(self.lp)
|
||||||
self.terminal_capabilities_received = true
|
self.terminal_capabilities_received = true
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Handler) on_color_scheme_change(cp loop.ColorPreference) error {
|
||||||
|
if conf.Color_scheme != Color_scheme_auto {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
light := cp == loop.LIGHT_COLOR_PREFERENCE
|
||||||
|
if use_light_colors != light {
|
||||||
|
use_light_colors = light
|
||||||
|
set_terminal_colors(self.lp)
|
||||||
|
self.highlight_all()
|
||||||
|
self.draw_screen()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Handler) initialize() {
|
func (self *Handler) initialize() {
|
||||||
self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"})
|
self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"})
|
||||||
self.lp.OnEscapeCode = self.on_escape_code
|
self.lp.OnEscapeCode = self.on_escape_code
|
||||||
|
self.lp.OnColorSchemeChange = self.on_color_scheme_change
|
||||||
image_collection = graphics.NewImageCollection()
|
image_collection = graphics.NewImageCollection()
|
||||||
self.current_context_count = opts.Context
|
self.current_context_count = opts.Context
|
||||||
if self.current_context_count < 0 {
|
if self.current_context_count < 0 {
|
||||||
@@ -195,11 +212,22 @@ func (self *Handler) on_wakeup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dark_highlight_started bool
|
||||||
|
var light_highlight_started bool
|
||||||
|
|
||||||
func (self *Handler) highlight_all() {
|
func (self *Handler) highlight_all() {
|
||||||
|
if (use_light_colors && light_highlight_started) || (!use_light_colors && dark_highlight_started) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if use_light_colors {
|
||||||
|
light_highlight_started = true
|
||||||
|
} else {
|
||||||
|
dark_highlight_started = true
|
||||||
|
}
|
||||||
text_files := utils.Filter(self.collection.paths_to_highlight.AsSlice(), is_path_text)
|
text_files := utils.Filter(self.collection.paths_to_highlight.AsSlice(), is_path_text)
|
||||||
go func() {
|
go func() {
|
||||||
r := AsyncResult{rtype: HIGHLIGHT}
|
r := AsyncResult{rtype: HIGHLIGHT}
|
||||||
highlight_all(text_files)
|
highlight_all(text_files, use_light_colors)
|
||||||
self.async_results <- r
|
self.async_results <- r
|
||||||
self.lp.WakeupMainThread()
|
self.lp.WakeupMainThread()
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ type Loop struct {
|
|||||||
|
|
||||||
// Called when capabilities response is received
|
// Called when capabilities response is received
|
||||||
OnCapabilitiesReceived func(TerminalCapabilities) error
|
OnCapabilitiesReceived func(TerminalCapabilities) error
|
||||||
|
|
||||||
|
// Called when the terminal's color scheme changes
|
||||||
|
OnColorSchemeChange func(ColorPreference) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(options ...func(self *Loop)) (*Loop, error) {
|
func New(options ...func(self *Loop)) (*Loop, error) {
|
||||||
@@ -203,6 +206,15 @@ func NoRestoreColors(self *Loop) {
|
|||||||
self.terminal_options.restore_colors = false
|
self.terminal_options.restore_colors = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Loop) ColorSchemeChangeNotifications() *Loop {
|
||||||
|
self.terminal_options.color_scheme_change_notification = true
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColorSchemeChangeNotifications(self *Loop) {
|
||||||
|
self.terminal_options.color_scheme_change_notification = true
|
||||||
|
}
|
||||||
|
|
||||||
func NoInBandResizeNotifications(self *Loop) {
|
func NoInBandResizeNotifications(self *Loop) {
|
||||||
self.terminal_options.in_band_resize_notification = false
|
self.terminal_options.in_band_resize_notification = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func new_loop() *Loop {
|
|||||||
l.terminal_options.Alternate_screen = true
|
l.terminal_options.Alternate_screen = true
|
||||||
l.terminal_options.restore_colors = true
|
l.terminal_options.restore_colors = true
|
||||||
l.terminal_options.in_band_resize_notification = true
|
l.terminal_options.in_band_resize_notification = true
|
||||||
|
l.terminal_options.color_scheme_change_notification = false
|
||||||
l.terminal_options.kitty_keyboard_mode = DISAMBIGUATE_KEYS | REPORT_ALTERNATE_KEYS | REPORT_ALL_KEYS_AS_ESCAPE_CODES | REPORT_TEXT_WITH_KEYS
|
l.terminal_options.kitty_keyboard_mode = DISAMBIGUATE_KEYS | REPORT_ALTERNATE_KEYS | REPORT_ALL_KEYS_AS_ESCAPE_CODES | REPORT_TEXT_WITH_KEYS
|
||||||
l.escape_code_parser.HandleCSI = l.handle_csi
|
l.escape_code_parser.HandleCSI = l.handle_csi
|
||||||
l.escape_code_parser.HandleOSC = l.handle_osc
|
l.escape_code_parser.HandleOSC = l.handle_osc
|
||||||
@@ -144,6 +145,17 @@ func (self *Loop) handle_csi(raw []byte) (err error) {
|
|||||||
self.TerminalCapabilities.KeyboardProtocol = true
|
self.TerminalCapabilities.KeyboardProtocol = true
|
||||||
self.TerminalCapabilities.KeyboardProtocolResponseReceived = true
|
self.TerminalCapabilities.KeyboardProtocolResponseReceived = true
|
||||||
}
|
}
|
||||||
|
} else if self.terminal_options.color_scheme_change_notification && strings.HasPrefix(csi, "?997;") && strings.HasSuffix(csi, "n") {
|
||||||
|
switch csi[len(csi)-2] {
|
||||||
|
case '1':
|
||||||
|
self.TerminalCapabilities.ColorPreference = DARK_COLOR_PREFERENCE
|
||||||
|
case '2':
|
||||||
|
self.TerminalCapabilities.ColorPreference = LIGHT_COLOR_PREFERENCE
|
||||||
|
}
|
||||||
|
self.TerminalCapabilities.ColorPreferenceResponseReceived = true
|
||||||
|
if self.OnColorSchemeChange != nil {
|
||||||
|
return self.OnColorSchemeChange(self.TerminalCapabilities.ColorPreference)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if self.OnEscapeCode != nil {
|
if self.OnEscapeCode != nil {
|
||||||
return self.OnEscapeCode(CSI, raw)
|
return self.OnEscapeCode(CSI, raw)
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const (
|
|||||||
ALTERNATE_SCREEN Mode = 1049 | private
|
ALTERNATE_SCREEN Mode = 1049 | private
|
||||||
BRACKETED_PASTE Mode = 2004 | private
|
BRACKETED_PASTE Mode = 2004 | private
|
||||||
PENDING_UPDATE Mode = 2026 | private
|
PENDING_UPDATE Mode = 2026 | private
|
||||||
|
COLOR_SCHEME_CHANGE_NOTIFICATION Mode = 2031 | private
|
||||||
INBAND_RESIZE_NOTIFICATION Mode = 2048 | private
|
INBAND_RESIZE_NOTIFICATION Mode = 2048 | private
|
||||||
HANDLE_TERMIOS_SIGNALS Mode = kitty.HandleTermiosSignals | private
|
HANDLE_TERMIOS_SIGNALS Mode = kitty.HandleTermiosSignals | private
|
||||||
)
|
)
|
||||||
@@ -101,6 +102,7 @@ type TerminalStateOptions struct {
|
|||||||
mouse_tracking MouseTracking
|
mouse_tracking MouseTracking
|
||||||
kitty_keyboard_mode KeyboardStateBits
|
kitty_keyboard_mode KeyboardStateBits
|
||||||
in_band_resize_notification bool
|
in_band_resize_notification bool
|
||||||
|
color_scheme_change_notification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func set_modes(sb *strings.Builder, modes ...Mode) {
|
func set_modes(sb *strings.Builder, modes ...Mode) {
|
||||||
@@ -133,6 +135,9 @@ func (self *TerminalStateOptions) SetStateEscapeCodes() string {
|
|||||||
if self.in_band_resize_notification {
|
if self.in_band_resize_notification {
|
||||||
set_modes(&sb, INBAND_RESIZE_NOTIFICATION)
|
set_modes(&sb, INBAND_RESIZE_NOTIFICATION)
|
||||||
}
|
}
|
||||||
|
if self.color_scheme_change_notification {
|
||||||
|
set_modes(&sb, COLOR_SCHEME_CHANGE_NOTIFICATION)
|
||||||
|
}
|
||||||
if self.Alternate_screen {
|
if self.Alternate_screen {
|
||||||
set_modes(&sb, ALTERNATE_SCREEN)
|
set_modes(&sb, ALTERNATE_SCREEN)
|
||||||
sb.WriteString(CLEAR_SCREEN)
|
sb.WriteString(CLEAR_SCREEN)
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ func NewLRUCache[K comparable, V any](max_size int) *LRUCache[K, V] {
|
|||||||
return &ans
|
return &ans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *LRUCache[K, V]) Clear() {
|
||||||
|
self.lock.RLock()
|
||||||
|
clear(self.data)
|
||||||
|
self.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (self *LRUCache[K, V]) Get(key K) (ans V, found bool) {
|
func (self *LRUCache[K, V]) Get(key K) (ans V, found bool) {
|
||||||
self.lock.RLock()
|
self.lock.RLock()
|
||||||
ans, found = self.data[key]
|
ans, found = self.data[key]
|
||||||
|
|||||||
Reference in New Issue
Block a user