diff --git a/docs/changelog.rst b/docs/changelog.rst index 1e1939572f..ce14f06c96 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -87,6 +87,8 @@ Detailed list of changes 0.38.2 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- diff kitten: Automatically use dark/light color scheme based on the color scheme of the parent terminal. Can be controlled via the new :opt:`kitten-diff.color_scheme` option. Note that this is a **behavior change** by default. (:iss:`8170`) + - When mapping a custom kitten allow using shell escaping for the kitten path (:iss:`8178`) - Fix border colors not being changed by auto light/dark themes at startup (:iss:`8180`) diff --git a/kittens/diff/highlight.go b/kittens/diff/highlight.go index 9a5de1886a..47b30de624 100644 --- a/kittens/diff/highlight.go +++ b/kittens/diff/highlight.go @@ -158,6 +158,30 @@ func ansi_formatter(w io.Writer, style *chroma.Style, it chroma.Iterator) (err e return nil } +func resolved_chroma_style() *chroma.Style { + name := utils.IfElse(use_dark_colors, conf.Dark_pygments_style, conf.Pygments_style) + var style *chroma.Style + if name == "default" { + style = DefaultStyle() + } else { + style = styles.Get(name) + } + if style == nil { + if resolved_colors.Background.IsDark() && !resolved_colors.Foreground.IsDark() { + style = styles.Get("monokai") + if style == nil { + style = styles.Get("github-dark") + } + } else { + style = DefaultStyle() + } + if style == nil { + style = styles.Fallback + } + } + return style +} + func highlight_file(path string) (highlighted string, err error) { filename_for_detection := filepath.Base(path) ext := filepath.Ext(filename_for_detection) @@ -174,34 +198,12 @@ func highlight_file(path string) (highlighted string, err error) { } lexer := lexers.Match(filename_for_detection) if lexer == nil { - if err == nil { - lexer = lexers.Analyse(text) - } + lexer = lexers.Analyse(text) } if lexer == nil { return "", fmt.Errorf("Cannot highlight %#v: %w", path, ErrNoLexer) } lexer = chroma.Coalesce(lexer) - name := conf.Pygments_style - var style *chroma.Style - if name == "default" { - style = DefaultStyle() - } else { - style = styles.Get(name) - } - if style == nil { - if conf.Background.IsDark() && !conf.Foreground.IsDark() { - style = styles.Get("monokai") - if style == nil { - style = styles.Get("github-dark") - } - } else { - style = DefaultStyle() - } - if style == nil { - style = styles.Fallback - } - } iterator, err := lexer.Tokenise(nil, text) if err != nil { return "", err @@ -209,7 +211,7 @@ func highlight_file(path string) (highlighted string, err error) { formatter := chroma.FormatterFunc(ansi_formatter) w := strings.Builder{} w.Grow(len(text) * 2) - err = formatter.Format(&w, style, iterator) + err = formatter.Format(&w, resolved_chroma_style(), iterator) // os.WriteFile(filepath.Base(path+".highlighted"), []byte(w.String()), 0o600) return w.String(), err } diff --git a/kittens/diff/main.go b/kittens/diff/main.go index 164a5caff6..69de45616e 100644 --- a/kittens/diff/main.go +++ b/kittens/diff/main.go @@ -112,7 +112,6 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { return 1, err } init_caches() - create_formatters() defer func() { for tdir := range remote_dirs { os.RemoveAll(tdir) @@ -154,6 +153,7 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { if !tc.KeyboardProtocol { return fmt.Errorf("This terminal does not support the kitty keyboard protocol, or you are running inside a terminal multiplexer that is blocking querying for kitty keyboard protocol support. The diff kitten cannot function without it.") } + h.on_capabilities_received(tc) return nil } lp.OnWakeup = h.on_wakeup diff --git a/kittens/diff/main.py b/kittens/diff/main.py index 26d15a2103..1ce68400b6 100644 --- a/kittens/diff/main.py +++ b/kittens/diff/main.py @@ -71,103 +71,90 @@ def main(args: List[str]) -> None: # colors {{{ agr('colors', 'Colors') -opt('pygments_style', 'default', - long_text=''' +opt('color_scheme', 'auto', choices=('auto', 'light', 'dark'), long_text=''' +Whether to use the light or dark colors. The default of :code:`auto` means +to follow the parent terminal color scheme. +''') + +opt('pygments_style', 'default', long_text=''' The pygments color scheme to use for syntax highlighting. See :link:`pygments builtin styles ` for a list of schemes. Note that this **does not** change the colors used for diffing, only the colors used for syntax highlighting. To change the general colors use the settings below. +This sets the colors used for light color schemes, use :opt:`dark_pygments_style` to change the +colors for dark color schemes. ''' ) -opt('foreground', 'black', - option_type='to_color', - long_text='Basic colors' - ) +opt('dark_pygments_style', 'github-dark', long_text=''' +The pygments color scheme to use for syntax highlighting with dark colors. See :link:`pygments +builtin styles ` for a list of schemes. Note that +this **does not** change the colors used for diffing, +only the colors used for syntax highlighting. To change the general colors use the settings below. +This sets the colors used for dark color schemes, use :opt:`pygments_style` to change the +colors for light color schemes.''') -opt('background', 'white', - option_type='to_color', - ) +opt('foreground', 'black', option_type='to_color', long_text='Basic colors') +opt('dark_foreground', '#f8f8f2', option_type='to_color', long_text='Basic colors') -opt('title_fg', 'black', - option_type='to_color', - long_text='Title colors' - ) +dark_bg = '#212830' +opt('background', 'white', option_type='to_color',) +opt('dark_background', dark_bg, option_type='to_color',) -opt('title_bg', 'white', - option_type='to_color', - ) +opt('title_fg', 'black', option_type='to_color', long_text='Title colors') +opt('dark_title_fg', 'white', option_type='to_color') -opt('margin_bg', '#fafbfc', - option_type='to_color', - long_text='Margin colors' - ) +opt('title_bg', 'white', option_type='to_color',) +opt('dark_title_bg', dark_bg, option_type='to_color',) -opt('margin_fg', '#aaaaaa', - option_type='to_color', - ) +opt('margin_bg', '#fafbfc', option_type='to_color', long_text='Margin colors') +opt('dark_margin_bg', dark_bg, option_type='to_color') -opt('removed_bg', '#ffeef0', - option_type='to_color', - long_text='Removed text backgrounds' - ) +opt('margin_fg', '#aaaaaa', option_type='to_color') +opt('dark_margin_fg', '#aaaaaa', option_type='to_color') -opt('highlight_removed_bg', '#fdb8c0', - option_type='to_color', - ) +opt('removed_bg', '#ffeef0', option_type='to_color', long_text='Removed text backgrounds') +opt('dark_removed_bg', '#352c33', option_type='to_color') -opt('removed_margin_bg', '#ffdce0', - option_type='to_color', - ) +opt('highlight_removed_bg', '#fdb8c0', option_type='to_color') +opt('dark_highlight_removed_bg', '#5c3539', option_type='to_color') -opt('added_bg', '#e6ffed', - option_type='to_color', - long_text='Added text backgrounds' - ) +opt('removed_margin_bg', '#ffdce0', option_type='to_color') +opt('dark_removed_margin_bg', '#5c3539', option_type='to_color') -opt('highlight_added_bg', '#acf2bd', - option_type='to_color', - ) +opt('added_bg', '#e6ffed', option_type='to_color', long_text='Added text backgrounds') +opt('dark_added_bg', '#263834', option_type='to_color') -opt('added_margin_bg', '#cdffd8', - option_type='to_color', - ) +opt('highlight_added_bg', '#acf2bd', option_type='to_color') +opt('dark_highlight_added_bg', '#31503d', option_type='to_color') -opt('filler_bg', '#fafbfc', - option_type='to_color', - long_text='Filler (empty) line background' - ) +opt('added_margin_bg', '#cdffd8', option_type='to_color') +opt('dark_added_margin_bg', '#31503d', option_type='to_color') -opt('margin_filler_bg', 'none', - option_type='to_color_or_none', - long_text='Filler (empty) line background in margins, defaults to the filler background' - ) +opt('filler_bg', '#fafbfc', option_type='to_color', long_text='Filler (empty) line background') +opt('dark_filler_bg', '#262c36', option_type='to_color') -opt('hunk_margin_bg', '#dbedff', - option_type='to_color', - long_text='Hunk header colors' - ) +opt('margin_filler_bg', 'none', option_type='to_color_or_none', long_text='Filler (empty) line background in margins, defaults to the filler background') +opt('dark_margin_filler_bg', 'none', option_type='to_color_or_none') -opt('hunk_bg', '#f1f8ff', - option_type='to_color', - ) -opt('search_bg', '#444', - option_type='to_color', - long_text='Highlighting' - ) +opt('hunk_margin_bg', '#dbedff', option_type='to_color', long_text='Hunk header colors') +opt('dark_hunk_margin_bg', '#0c2d6b', option_type='to_color') -opt('search_fg', 'white', - option_type='to_color', - ) +opt('hunk_bg', '#f1f8ff', option_type='to_color') +opt('dark_hunk_bg', '#253142', option_type='to_color') -opt('select_bg', '#b4d5fe', - option_type='to_color', - ) +opt('search_bg', '#444', option_type='to_color', long_text='Highlighting') +opt('dark_search_bg', '#2c599c', option_type='to_color') -opt('select_fg', 'black', - option_type='to_color_or_none', - ) +opt('search_fg', 'white', option_type='to_color') +opt('dark_search_fg', 'white', option_type='to_color') + +opt('select_bg', '#b4d5fe', option_type='to_color') +opt('dark_select_bg', '#2c599c', option_type='to_color') + +opt('select_fg', 'black', option_type='to_color_or_none') +opt('dark_select_fg', 'white', option_type='to_color_or_none') egr() # }}} # shortcuts {{{ diff --git a/kittens/diff/render.go b/kittens/diff/render.go index a7460d3961..874bab8fbe 100644 --- a/kittens/diff/render.go +++ b/kittens/diff/render.go @@ -160,37 +160,107 @@ var format_as_sgr struct { } var statusline_format, added_count_format, removed_count_format, message_format func(...any) string +var use_dark_colors bool = true + +type ResolvedColors struct { + Added_bg style.RGBA + Added_margin_bg style.RGBA + Background style.RGBA + Filler_bg style.RGBA + Foreground style.RGBA + Highlight_added_bg style.RGBA + Highlight_removed_bg style.RGBA + Hunk_bg style.RGBA + Hunk_margin_bg style.RGBA + Margin_bg style.RGBA + Margin_fg style.RGBA + Margin_filler_bg style.NullableColor + Removed_bg style.RGBA + Removed_margin_bg style.RGBA + Search_bg style.RGBA + Search_fg style.RGBA + Select_bg style.RGBA + Select_fg style.NullableColor + Title_bg style.RGBA + Title_fg style.RGBA +} + +var resolved_colors ResolvedColors func create_formatters() { + rc := &resolved_colors + if use_dark_colors { + rc.Added_bg = conf.Dark_added_bg + rc.Added_margin_bg = conf.Dark_added_margin_bg + rc.Background = conf.Dark_background + rc.Filler_bg = conf.Dark_filler_bg + rc.Foreground = conf.Dark_foreground + rc.Highlight_added_bg = conf.Dark_highlight_added_bg + rc.Highlight_removed_bg = conf.Dark_highlight_removed_bg + rc.Hunk_bg = conf.Dark_hunk_bg + rc.Hunk_margin_bg = conf.Dark_hunk_margin_bg + rc.Margin_bg = conf.Dark_margin_bg + rc.Margin_fg = conf.Dark_margin_fg + rc.Margin_filler_bg = conf.Dark_margin_filler_bg + rc.Removed_bg = conf.Dark_removed_bg + rc.Removed_margin_bg = conf.Dark_removed_margin_bg + rc.Search_bg = conf.Dark_search_bg + rc.Search_fg = conf.Dark_search_fg + rc.Select_bg = conf.Dark_select_bg + rc.Select_fg = conf.Dark_select_fg + rc.Title_bg = conf.Dark_title_bg + rc.Title_fg = conf.Dark_title_fg + } else { + rc.Added_bg = conf.Added_bg + rc.Added_margin_bg = conf.Added_margin_bg + rc.Background = conf.Background + rc.Filler_bg = conf.Filler_bg + rc.Foreground = conf.Foreground + rc.Highlight_added_bg = conf.Highlight_added_bg + rc.Highlight_removed_bg = conf.Highlight_removed_bg + rc.Hunk_bg = conf.Hunk_bg + rc.Hunk_margin_bg = conf.Hunk_margin_bg + rc.Margin_bg = conf.Margin_bg + rc.Margin_fg = conf.Margin_fg + rc.Margin_filler_bg = conf.Margin_filler_bg + rc.Removed_bg = conf.Removed_bg + rc.Removed_margin_bg = conf.Removed_margin_bg + rc.Search_bg = conf.Search_bg + rc.Search_fg = conf.Search_fg + rc.Select_bg = conf.Select_bg + rc.Select_fg = conf.Select_fg + rc.Title_bg = conf.Title_bg + rc.Title_fg = conf.Title_fg + } ctx := style.Context{AllowEscapeCodes: true} only_open := func(x string) string { ans := ctx.SprintFunc(x)("|") ans, _, _ = strings.Cut(ans, "|") return ans } - format_as_sgr.filler = only_open("bg=" + conf.Filler_bg.AsRGBSharp()) - if conf.Margin_filler_bg.IsSet { - format_as_sgr.margin_filler = only_open("bg=" + conf.Margin_filler_bg.Color.AsRGBSharp()) + format_as_sgr.filler = only_open("bg=" + rc.Filler_bg.AsRGBSharp()) + if rc.Margin_filler_bg.IsSet { + format_as_sgr.margin_filler = only_open("bg=" + rc.Margin_filler_bg.Color.AsRGBSharp()) } else { - format_as_sgr.margin_filler = only_open("bg=" + conf.Filler_bg.AsRGBSharp()) - } - format_as_sgr.added = only_open("bg=" + conf.Added_bg.AsRGBSharp()) - format_as_sgr.added_margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Added_margin_bg.AsRGBSharp())) - format_as_sgr.removed = only_open("bg=" + conf.Removed_bg.AsRGBSharp()) - format_as_sgr.removed_margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Removed_margin_bg.AsRGBSharp())) - format_as_sgr.title = only_open(fmt.Sprintf("fg=%s bg=%s bold", conf.Title_fg.AsRGBSharp(), conf.Title_bg.AsRGBSharp())) - format_as_sgr.margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Margin_bg.AsRGBSharp())) - format_as_sgr.hunk = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_bg.AsRGBSharp())) - format_as_sgr.hunk_margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_margin_bg.AsRGBSharp())) - format_as_sgr.search = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Search_fg.AsRGBSharp(), conf.Search_bg.AsRGBSharp())) - statusline_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", conf.Margin_fg.AsRGBSharp())) - added_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", conf.Highlight_added_bg.AsRGBSharp())) - removed_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", conf.Highlight_removed_bg.AsRGBSharp())) + format_as_sgr.margin_filler = only_open("bg=" + rc.Filler_bg.AsRGBSharp()) + } + format_as_sgr.added = only_open("bg=" + rc.Added_bg.AsRGBSharp()) + format_as_sgr.added_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Added_margin_bg.AsRGBSharp())) + format_as_sgr.removed = only_open("bg=" + rc.Removed_bg.AsRGBSharp()) + format_as_sgr.removed_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Removed_margin_bg.AsRGBSharp())) + format_as_sgr.title = only_open(fmt.Sprintf("fg=%s bg=%s bold", rc.Title_fg.AsRGBSharp(), rc.Title_bg.AsRGBSharp())) + format_as_sgr.margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Margin_bg.AsRGBSharp())) + format_as_sgr.hunk = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Hunk_bg.AsRGBSharp())) + format_as_sgr.hunk_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Hunk_margin_bg.AsRGBSharp())) + format_as_sgr.search = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Search_fg.AsRGBSharp(), rc.Search_bg.AsRGBSharp())) + statusline_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", rc.Margin_fg.AsRGBSharp())) + added_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", rc.Highlight_added_bg.AsRGBSharp())) + removed_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", rc.Highlight_removed_bg.AsRGBSharp())) message_format = ctx.SprintFunc("bold") - if conf.Select_fg.IsSet { - format_as_sgr.selection = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Select_fg.Color.AsRGBSharp(), conf.Select_bg.AsRGBSharp())) + if rc.Select_fg.IsSet { + format_as_sgr.selection = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Select_fg.Color.AsRGBSharp(), rc.Select_bg.AsRGBSharp())) } else { - format_as_sgr.selection = only_open("bg=" + conf.Select_bg.AsRGBSharp()) + format_as_sgr.selection = only_open("bg=" + rc.Select_bg.AsRGBSharp()) } } @@ -198,9 +268,9 @@ func center_span(ltype string, offset, size int) *sgr.Span { ans := sgr.NewSpan(offset, size) switch ltype { case "add": - ans.SetBackground(conf.Highlight_added_bg).SetClosingBackground(conf.Added_bg) + ans.SetBackground(resolved_colors.Highlight_added_bg).SetClosingBackground(resolved_colors.Added_bg) case "remove": - ans.SetBackground(conf.Highlight_removed_bg).SetClosingBackground(conf.Removed_bg) + ans.SetBackground(resolved_colors.Highlight_removed_bg).SetClosingBackground(resolved_colors.Removed_bg) } return ans } @@ -469,7 +539,7 @@ func hunk_title(hunk *Hunk) string { return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", hunk.left_start+1, hunk.left_count, hunk.right_start+1, hunk.right_count, hunk.title) } -func lines_for_context_chunk(data *DiffData, hunk_num int, chunk *Chunk, chunk_num int, ans []*LogicalLine) []*LogicalLine { +func lines_for_context_chunk(data *DiffData, _ int, chunk *Chunk, _ int, ans []*LogicalLine) []*LogicalLine { for i := 0; i < chunk.left_count; i++ { left_line_number := chunk.left_start + i right_line_number := chunk.right_start + i @@ -514,7 +584,7 @@ func render_half_line(line_number int, line, ltype string, available_cols int, c return ans } -func lines_for_diff_chunk(data *DiffData, hunk_num int, chunk *Chunk, chunk_num int, ans []*LogicalLine) []*LogicalLine { +func lines_for_diff_chunk(data *DiffData, _ int, chunk *Chunk, _ int, ans []*LogicalLine) []*LogicalLine { common := utils.Min(chunk.left_count, chunk.right_count) ll, rl := make([]HalfScreenLine, 0, 32), make([]HalfScreenLine, 0, 32) for i := 0; i < utils.Max(chunk.left_count, chunk.right_count); i++ { diff --git a/kittens/diff/ui.go b/kittens/diff/ui.go index 3c19447afd..5ee7886e80 100644 --- a/kittens/diff/ui.go +++ b/kittens/diff/ui.go @@ -61,6 +61,7 @@ type Handler struct { collection *Collection diff_map map[string]*Patch logical_lines *LogicalLines + terminal_capabilities_received bool lp *loop.Loop current_context_count, original_context_count int added_count, removed_count int @@ -111,6 +112,31 @@ func (self *Handler) finalize() { image_collection.Finalize(self.lp) } +func set_terminal_colors(lp *loop.Loop) { + create_formatters() + lp.SetDefaultColor(loop.FOREGROUND, resolved_colors.Foreground) + lp.SetDefaultColor(loop.CURSOR, resolved_colors.Foreground) + lp.SetDefaultColor(loop.BACKGROUND, resolved_colors.Background) + lp.SetDefaultColor(loop.SELECTION_BG, resolved_colors.Select_bg) + if resolved_colors.Select_fg.IsSet { + lp.SetDefaultColor(loop.SELECTION_FG, resolved_colors.Select_fg.Color) + } +} + +func (self *Handler) on_capabilities_received(tc loop.TerminalCapabilities) { + switch conf.Color_scheme { + case Color_scheme_auto: + use_dark_colors = tc.ColorPreference != loop.LIGHT_COLOR_PREFERENCE + case Color_scheme_light: + use_dark_colors = false + case Color_scheme_dark: + use_dark_colors = true + } + set_terminal_colors(self.lp) + self.terminal_capabilities_received = true + self.draw_screen() +} + func (self *Handler) initialize() { self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"}) self.lp.OnEscapeCode = self.on_escape_code @@ -122,13 +148,6 @@ func (self *Handler) initialize() { sz, _ := self.lp.ScreenSize() self.update_screen_size(sz) self.original_context_count = self.current_context_count - self.lp.SetDefaultColor(loop.FOREGROUND, conf.Foreground) - self.lp.SetDefaultColor(loop.CURSOR, conf.Foreground) - self.lp.SetDefaultColor(loop.BACKGROUND, conf.Background) - self.lp.SetDefaultColor(loop.SELECTION_BG, conf.Select_bg) - if conf.Select_fg.IsSet { - self.lp.SetDefaultColor(loop.SELECTION_FG, conf.Select_fg.Color) - } self.async_results = make(chan AsyncResult, 32) go func() { r := AsyncResult{} @@ -315,7 +334,7 @@ func (self *Handler) render_diff() (err error) { return nil } -func (self *Handler) draw_image(key string, num_rows, starting_row int) { +func (self *Handler) draw_image(key string, _, starting_row int) { image_collection.PlaceImageSubRect(self.lp, key, self.images_resized_to, 0, self.screen_size.cell_height*starting_row, -1, -1) } @@ -345,7 +364,7 @@ func (self *Handler) draw_screen() { } lp.MoveCursorTo(1, 1) lp.ClearToEndOfScreen() - if self.logical_lines == nil || self.diff_map == nil || self.collection == nil { + if self.logical_lines == nil || self.diff_map == nil || self.collection == nil || !self.terminal_capabilities_received { lp.Println(`Calculating diff, please wait...`) return }