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