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