diff --git a/lib/textbringer/commands/windows.rb b/lib/textbringer/commands/windows.rb index 575d341..bc53bbe 100644 --- a/lib/textbringer/commands/windows.rb +++ b/lib/textbringer/commands/windows.rb @@ -84,6 +84,20 @@ module Commands end end + define_command(:set_foreground_color, doc: <<~EOD) do + Set the default foreground color. + EOD + |color = read_from_minibuffer("Foreground color: ")| + Window.set_default_colors(color, nil) + end + + define_command(:set_background_color, doc: <<~EOD) do + Set the default background color. + EOD + |color = read_from_minibuffer("Background color: ")| + Window.set_default_colors(nil, color) + end + define_command(:list_buffers, doc: <<~EOD) do |buffers = Buffer.list| List the existing buffers. EOD diff --git a/lib/textbringer/theme.rb b/lib/textbringer/theme.rb index dcadbfd..f633d75 100644 --- a/lib/textbringer/theme.rb +++ b/lib/textbringer/theme.rb @@ -73,6 +73,7 @@ def initialize(name) @name = name @palettes = {} @face_definitions = [] + @default_colors = nil end attr_reader :name @@ -87,6 +88,10 @@ def face(name, **attrs) @face_definitions << [name, attrs] end + def default_colors(foreground:, background:) + @default_colors = { foreground: foreground, background: background } + end + def activate mode = self.class.background_mode tier = self.class.color_tier @@ -113,6 +118,33 @@ def activate Face.define(face_name, **resolved) end @@current = self + apply_default_colors(palette, tier) + end + + private + + def apply_default_colors(palette, tier) + if @default_colors + fg = resolve_default_color(@default_colors[:foreground], palette, tier) + bg = resolve_default_color(@default_colors[:background], palette, tier) + else + fg = "default" + bg = "default" + end + Window.set_default_colors(fg, bg) + end + + def resolve_default_color(val, palette, tier) + if val.is_a?(Symbol) + color = palette.resolve(val, tier) + unless color + raise EditorError, + "Unknown palette color :#{val} for default_colors" + end + color + else + val || "default" + end end private_class_method def self.detect_background_via_osc11 diff --git a/lib/textbringer/themes/catppuccin.rb b/lib/textbringer/themes/catppuccin.rb index b7167fa..a7a54c0 100644 --- a/lib/textbringer/themes/catppuccin.rb +++ b/lib/textbringer/themes/catppuccin.rb @@ -68,6 +68,8 @@ p.color :rosewater, hex: "#d78787", ansi: "red" end + t.default_colors foreground: :text, background: :base + # Programming faces (from catppuccin/nvim syntax.lua) t.face :comment, foreground: :overlay2 t.face :preprocessing_directive, foreground: :pink diff --git a/lib/textbringer/themes/github.rb b/lib/textbringer/themes/github.rb index a3c481c..cd6db30 100644 --- a/lib/textbringer/themes/github.rb +++ b/lib/textbringer/themes/github.rb @@ -48,6 +48,8 @@ p.color :darkblue, hex: "#c1daec", ansi: "blue" # dcolors.darkblue = blue1 end + t.default_colors foreground: :fg, background: :bg + # Programming faces t.face :comment, foreground: :comment # Comment = base2 t.face :preprocessing_directive, foreground: :red # PreProc = red diff --git a/lib/textbringer/themes/gruvbox.rb b/lib/textbringer/themes/gruvbox.rb index 86b3285..12f9c7f 100644 --- a/lib/textbringer/themes/gruvbox.rb +++ b/lib/textbringer/themes/gruvbox.rb @@ -47,6 +47,8 @@ p.color :orange, hex: "#d75f00", ansi: "red" end + t.default_colors foreground: :fg1, background: :bg0 + # Programming faces t.face :comment, foreground: :gray t.face :preprocessing_directive, foreground: :aqua diff --git a/lib/textbringer/themes/molokai.rb b/lib/textbringer/themes/molokai.rb index 7b76066..a3c5cbc 100644 --- a/lib/textbringer/themes/molokai.rb +++ b/lib/textbringer/themes/molokai.rb @@ -27,6 +27,8 @@ p.color :search, hex: "#ffd787", ansi: "yellow" # 222 Search bg end + t.default_colors foreground: :fg, background: :bg + # Programming faces t.face :comment, foreground: :comment t.face :preprocessing_directive, foreground: :green diff --git a/lib/textbringer/themes/sonokai.rb b/lib/textbringer/themes/sonokai.rb index 0940556..7a38358 100644 --- a/lib/textbringer/themes/sonokai.rb +++ b/lib/textbringer/themes/sonokai.rb @@ -26,6 +26,8 @@ p.color :purple, hex: "#d787d7", ansi: "magenta" end + t.default_colors foreground: :fg, background: :bg0 + # Programming faces (from sonokai highlight groups) t.face :comment, foreground: :grey t.face :preprocessing_directive, foreground: :red diff --git a/lib/textbringer/themes/tokyonight.rb b/lib/textbringer/themes/tokyonight.rb index abb6ee2..26bbc5c 100644 --- a/lib/textbringer/themes/tokyonight.rb +++ b/lib/textbringer/themes/tokyonight.rb @@ -29,6 +29,8 @@ p.color :blue5, hex: "#89ddff", ansi: "cyan" # Operator, punctuation delimiters end + t.default_colors foreground: :fg, background: :bg + # Programming faces t.face :comment, foreground: :comment t.face :preprocessing_directive, foreground: :cyan # PreProc = c.cyan diff --git a/lib/textbringer/window.rb b/lib/textbringer/window.rb index 1cdae58..3555866 100644 --- a/lib/textbringer/window.rb +++ b/lib/textbringer/window.rb @@ -33,6 +33,8 @@ class Window @@minibuffer_selected = nil @@echo_area = nil @@has_colors = false + @@default_fg = "default" + @@default_bg = "default" def self.list(include_echo_area: false) if include_echo_area @@ -133,8 +135,13 @@ def self.colors end def self.set_default_colors(fg, bg) - Curses.assume_default_colors(Color[fg], Color[bg]) - Window.redraw + new_fg = fg || @@default_fg + new_bg = bg || @@default_bg + Curses.assume_default_colors(Color[new_fg], Color[new_bg]) + @@default_fg = new_fg + @@default_bg = new_bg + Face.define(:default, foreground: new_fg, background: new_bg) + Window.redraw if @@started end def self.load_faces @@ -708,7 +715,8 @@ def redisplay_mode_line @mode_line.addstr(" #{line},#{column}") @mode_line.addstr(" (#{@buffer.mode_names.join(' ')})") @mode_line.addstr(" " * (columns - @mode_line.curx)) - @mode_line.attr_set(0, 0) + default = Face[:default] + @mode_line.attr_set(default&.text_attrs || 0, default&.color_pair || 0) @mode_line.noutrefresh end @@ -948,6 +956,7 @@ def get_char end def apply_face_attrs(win, face) + face ||= Face[:default] win.attr_set(face&.text_attrs || 0, face&.color_pair || 0) end @@ -967,7 +976,8 @@ def apply_window_attrs(win) end win.attr_set(text_attrs, face.color_pair) else - win.attr_set(0, 0) + default = Face[:default] + win.attr_set(default&.text_attrs || 0, default&.color_pair || 0) end end end @@ -1016,7 +1026,8 @@ def redisplay @window.addstr(@buffer.input_method_status) end @window.setpos(0, 0) - @window.attr_set(0, 0) + default = Face[:default] + @window.attr_set(default&.text_attrs || 0, default&.color_pair || 0) @in_region = false @in_isearch = false @current_hl_face = nil diff --git a/test/textbringer/test_theme.rb b/test/textbringer/test_theme.rb index 6f38eed..8f7f0bf 100644 --- a/test/textbringer/test_theme.rb +++ b/test/textbringer/test_theme.rb @@ -106,6 +106,60 @@ def test_activate_with_inherit Face.delete(:derived_kw) end + def test_activate_sets_default_colors + Theme.define "test_defcol" do |t| + t.palette :dark do |p| + p.color :fg, hex: "#ffffff", ansi: "white" + p.color :bg, hex: "#000000", ansi: "black" + end + t.default_colors foreground: :fg, background: :bg + t.face :test_defcol_face, foreground: :fg + end + Theme["test_defcol"].activate + fg_num = Color["#ffffff"] + bg_num = Color["#000000"] + assert_equal([fg_num, bg_num], Curses.default_colors) + ensure + Face.delete(:test_defcol_face) + end + + def test_activate_resets_default_colors_when_not_specified + Theme.define "test_with_defcol" do |t| + t.palette :dark do |p| + p.color :fg, hex: "#ffffff", ansi: "white" + p.color :bg, hex: "#000000", ansi: "black" + end + t.default_colors foreground: :fg, background: :bg + t.face :test_wdc_face, foreground: :fg + end + Theme["test_with_defcol"].activate + assert_not_equal([-1, -1], Curses.default_colors) + + Theme.define "test_no_defcol" do |t| + t.palette :dark do |p| + p.color :fg, hex: "#ffffff", ansi: "white" + end + t.face :test_ndc_face, foreground: :fg + end + Theme["test_no_defcol"].activate + assert_equal([-1, -1], Curses.default_colors) + ensure + Face.delete(:test_wdc_face) + Face.delete(:test_ndc_face) + end + + def test_activate_default_colors_raises_on_unknown_palette + Theme.define "test_bad_defcol" do |t| + t.palette :dark do |p| + p.color :fg, hex: "#ffffff", ansi: "white" + end + t.default_colors foreground: :nonexistent, background: :fg + end + assert_raise(EditorError) do + Theme["test_bad_defcol"].activate + end + end + def test_load_default_activates_faces face = Face[:comment] assert_not_nil(face) diff --git a/test/textbringer/test_window.rb b/test/textbringer/test_window.rb index d0c482a..b99f36d 100644 --- a/test/textbringer/test_window.rb +++ b/test/textbringer/test_window.rb @@ -432,6 +432,34 @@ def test_s_set_default_colors assert_equal([-1, -1], Curses.default_colors) end + def test_s_set_default_colors_partial_update + red = Color["red"] + blue = Color["blue"] + green = Color["green"] + Window.set_default_colors("red", "blue") + assert_equal([red, blue], Curses.default_colors) + + # Update only bg, fg should be preserved + Window.set_default_colors(nil, "default") + assert_equal([red, -1], Curses.default_colors) + + # Update only fg, bg should be preserved + Window.set_default_colors("green", nil) + assert_equal([green, -1], Curses.default_colors) + end + + def test_s_set_default_colors_empty_string_ignored + red = Color["red"] + blue = Color["blue"] + Window.set_default_colors("red", "blue") + assert_equal([red, blue], Curses.default_colors) + + # Empty string should be treated as error + assert_raise(EditorError) do + Window.set_default_colors("", "") + end + end + private def window_string(window)