sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/

lib/sup/colormap.rb (8398B) - raw

      1 module Ncurses
      2   COLOR_DEFAULT = -1
      3 
      4   NUM_COLORS = `tput colors`.to_i
      5   MAX_PAIRS = `tput pairs`.to_i
      6 
      7   def self.color! name, value
      8     const_set "COLOR_#{name.to_s.upcase}", value
      9   end
     10 
     11   ## numeric colors
     12   Ncurses::NUM_COLORS.times { |x| color! x, x }
     13 
     14   if Ncurses::NUM_COLORS == 256
     15     ## xterm 6x6x6 color cube
     16     6.times { |x| 6.times { |y| 6.times { |z| color! "c#{x}#{y}#{z}", 16 + z + 6*y + 36*x } } }
     17 
     18     ## xterm 24-shade grayscale
     19     24.times { |x| color! "g#{x}", (16+6*6*6) + x }
     20   elsif Ncurses::NUM_COLORS == -1
     21     ## Terminal emulator doesn't appear to support colors
     22     fail "sup must be run in a terminal with color support, please check your TERM variable."
     23   end
     24 end
     25 
     26 module Redwood
     27 
     28 class Colormap
     29   @@instance = nil
     30 
     31   DEFAULT_COLORS = {
     32     :text => { :fg => "white", :bg => "black" },
     33     :status => { :fg => "white", :bg => "blue", :attrs => ["bold"] },
     34     :index_old => { :fg => "white", :bg => "default" },
     35     :index_new => { :fg => "white", :bg => "default", :attrs => ["bold"] },
     36     :index_starred => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     37     :index_draft => { :fg => "red", :bg => "default", :attrs => ["bold"] },
     38     :labellist_old => { :fg => "white", :bg => "default" },
     39     :labellist_new => { :fg => "white", :bg => "default", :attrs => ["bold"] },
     40     :twiddle => { :fg => "blue", :bg => "default" },
     41     :label => { :fg => "yellow", :bg => "default" },
     42     :message_patina => { :fg => "black", :bg => "green" },
     43     :alternate_patina => { :fg => "black", :bg => "blue" },
     44     :missing_message => { :fg => "black", :bg => "red" },
     45     :attachment => { :fg => "cyan", :bg => "default" },
     46     :cryptosig_valid => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     47     :cryptosig_valid_untrusted => { :fg => "yellow", :bg => "blue", :attrs => ["bold"] },
     48     :cryptosig_unknown => { :fg => "cyan", :bg => "default" },
     49     :cryptosig_invalid => { :fg => "yellow", :bg => "red", :attrs => ["bold"] },
     50     :generic_notice_patina => { :fg => "cyan", :bg => "default" },
     51     :quote_patina => { :fg => "yellow", :bg => "default" },
     52     :sig_patina => { :fg => "yellow", :bg => "default" },
     53     :quote => { :fg => "yellow", :bg => "default" },
     54     :sig => { :fg => "yellow", :bg => "default" },
     55     :to_me => { :fg => "green", :bg => "default" },
     56     :with_attachment => { :fg => "green", :bg => "default" },
     57     :starred => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     58     :starred_patina => { :fg => "yellow", :bg => "green", :attrs => ["bold"] },
     59     :alternate_starred_patina => { :fg => "yellow", :bg => "blue", :attrs => ["bold"] },
     60     :snippet => { :fg => "cyan", :bg => "default" },
     61     :option => { :fg => "white", :bg => "default" },
     62     :tagged => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     63     :draft_notification => { :fg => "red", :bg => "default", :attrs => ["bold"] },
     64     :completion_character => { :fg => "white", :bg => "default", :attrs => ["bold"] },
     65     :horizontal_selector_selected => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     66     :horizontal_selector_unselected => { :fg => "cyan", :bg => "default" },
     67     :search_highlight => { :fg => "black", :bg => "yellow", :attrs => ["bold"] },
     68     :system_buf => { :fg => "blue", :bg => "default" },
     69     :regular_buf => { :fg => "white", :bg => "default" },
     70     :modified_buffer => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     71     :date => { :fg => "white", :bg => "default"},
     72     :size_widget => { :fg => "white", :bg => "default"},
     73   }
     74 
     75   def initialize
     76     raise "only one instance can be created" if @@instance
     77     @@instance = self
     78     @color_pairs = {[Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK] => 0}
     79     @users = []
     80     @next_id = 0
     81     reset
     82     yield self if block_given?
     83   end
     84 
     85   def reset
     86     @entries = {}
     87     @highlights = { :none => highlight_sym(:none)}
     88     @entries[highlight_sym(:none)] = highlight_for(Ncurses::COLOR_WHITE,
     89                                                    Ncurses::COLOR_BLACK,
     90                                                    []) + [nil]
     91   end
     92 
     93   def add sym, fg, bg, attr=nil, highlight=nil
     94     raise ArgumentError, "color for #{sym} already defined" if @entries.member? sym
     95     raise ArgumentError, "color '#{fg}' unknown" unless (-1...Ncurses::NUM_COLORS).include? fg
     96     raise ArgumentError, "color '#{bg}' unknown" unless (-1...Ncurses::NUM_COLORS).include? bg
     97     attrs = [attr].flatten.compact
     98 
     99     @entries[sym] = [fg, bg, attrs, nil]
    100 
    101     if not highlight
    102       highlight = highlight_sym(sym)
    103       @entries[highlight] = highlight_for(fg, bg, attrs) + [nil]
    104     end
    105 
    106     @highlights[sym] = highlight
    107   end
    108 
    109   def highlight_sym sym
    110     "#{sym}_highlight".intern
    111   end
    112 
    113   def highlight_for fg, bg, attrs
    114     hfg =
    115       case fg
    116       when Ncurses::COLOR_BLUE
    117         Ncurses::COLOR_WHITE
    118       when Ncurses::COLOR_YELLOW, Ncurses::COLOR_GREEN
    119         fg
    120       else
    121         Ncurses::COLOR_BLACK
    122       end
    123 
    124     hbg =
    125       case bg
    126       when Ncurses::COLOR_CYAN
    127         Ncurses::COLOR_YELLOW
    128       when Ncurses::COLOR_YELLOW
    129         Ncurses::COLOR_BLUE
    130       else
    131         Ncurses::COLOR_CYAN
    132       end
    133 
    134     attrs =
    135       if fg == Ncurses::COLOR_WHITE && attrs.include?(Ncurses::A_BOLD)
    136         [Ncurses::A_BOLD]
    137       else
    138         case hfg
    139         when Ncurses::COLOR_BLACK
    140           []
    141         else
    142           [Ncurses::A_BOLD]
    143         end
    144       end
    145     [hfg, hbg, attrs]
    146   end
    147 
    148   def color_for sym, highlight=false
    149     sym = @highlights[sym] if highlight
    150     return Ncurses::COLOR_BLACK if sym == :none
    151     raise ArgumentError, "undefined color #{sym}" unless @entries.member? sym
    152 
    153     ## if this color is cached, return it
    154     fg, bg, attrs, color = @entries[sym]
    155     return color if color
    156 
    157     if(cp = @color_pairs[[fg, bg]])
    158       ## nothing
    159     else ## need to get a new colorpair
    160       @next_id = (@next_id + 1) % Ncurses::MAX_PAIRS
    161       @next_id += 1 if @next_id == 0 # 0 is always white on black
    162       id = @next_id
    163       debug "colormap: for color #{sym}, using id #{id} -> #{fg}, #{bg}"
    164       Ncurses.init_pair id, fg, bg or raise ArgumentError,
    165         "couldn't initialize curses color pair #{fg}, #{bg} (key #{id})"
    166 
    167       cp = @color_pairs[[fg, bg]] = Ncurses.COLOR_PAIR(id)
    168       ## delete the old mapping, if it exists
    169       if @users[cp]
    170         @users[cp].each do |usym|
    171           warn "dropping color #{usym} (#{id})"
    172           @entries[usym][3] = nil
    173         end
    174         @users[cp] = []
    175       end
    176     end
    177 
    178     ## by now we have a color pair
    179     color = attrs.inject(cp) { |color, attr| color | attr }
    180     @entries[sym][3] = color # fill the cache
    181     (@users[cp] ||= []) << sym # record entry as a user of that color pair
    182     color
    183   end
    184 
    185   def sym_is_defined sym
    186       return sym if @entries.member? sym
    187   end
    188 
    189   ## Try to use the user defined colors, in case of an error fall back
    190   ## to the default ones.
    191   def populate_colormap
    192     user_colors = if File.exist? Redwood::COLOR_FN
    193       debug "loading user colors from #{Redwood::COLOR_FN}"
    194       Redwood::load_yaml_obj Redwood::COLOR_FN
    195     end
    196 
    197     ## Set attachment sybmol to sane default for existing colorschemes
    198     if user_colors and user_colors.has_key? :to_me
    199       user_colors[:with_attachment] = user_colors[:to_me] unless user_colors.has_key? :with_attachment
    200     end
    201 
    202     Colormap::DEFAULT_COLORS.merge(user_colors||{}).each_pair do |k, v|
    203       fg = begin
    204         Ncurses.const_get "COLOR_#{v[:fg].to_s.upcase}"
    205       rescue NameError
    206         warn "there is no color named \"#{v[:fg]}\""
    207         Ncurses::COLOR_GREEN
    208       end
    209 
    210       bg = begin
    211         Ncurses.const_get "COLOR_#{v[:bg].to_s.upcase}"
    212       rescue NameError
    213         warn "there is no color named \"#{v[:bg]}\""
    214         Ncurses::COLOR_RED
    215       end
    216 
    217       attrs = (v[:attrs]||[]).map do |a|
    218         begin
    219           Ncurses.const_get "A_#{a.upcase}"
    220         rescue NameError
    221           warn "there is no attribute named \"#{a}\", using fallback."
    222           nil
    223         end
    224       end.compact
    225 
    226       highlight_symbol = v[:highlight] ? :"#{v[:highlight]}_color" : nil
    227 
    228       symbol = (k.to_s + "_color").to_sym
    229       add symbol, fg, bg, attrs, highlight_symbol
    230     end
    231   end
    232 
    233   def self.instance; @@instance; end
    234   def self.method_missing meth, *a
    235     Colormap.new unless @@instance
    236     @@instance.send meth, *a
    237   end
    238   # Performance shortcut
    239   def self.color_for(*a); @@instance.color_for(*a); end
    240 end
    241 
    242 end