12 unless Redwood::VERSION == BIN_VERSION
15 Error: version mismatch!
16 The sup executable is at version #{BIN_VERSION.inspect}.
17 The sup libraries are at version #{Redwood::VERSION.inspect}.
19 Is your development environment conflicting with rubygems?
25 $opts = Trollop::options do
26 version "sup v#{Redwood::VERSION}"
28 Sup is a curses-based email client.
35 opt :list_hooks, "List all hooks and descriptions, and quit."
36 opt :no_threads, "Turn off threading. Helps with debugging. (Necessarily disables background polling for new messages.)"
37 opt :no_initial_poll, "Don't poll for new messages when starting."
38 opt :search, "Search for threads on startup", :type => String
42 Redwood::HookManager.print_hooks
46 Thread.abort_on_exception = true # make debugging possible
50 global_keymap = Keymap.new do |k|
51 k.add :quit, "Quit Redwood", 'q'
52 k.add :help, "Show help", 'H', '?'
53 k.add :roll_buffers, "Switch to next buffer", 'b'
54 # k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
55 k.add :kill_buffer, "Kill the current buffer", 'x'
56 k.add :list_buffers, "List all buffers", 'B'
57 k.add :list_contacts, "List contacts", 'C'
58 k.add :redraw, "Redraw screen", :ctrl_l
59 k.add :search, "Search all messages", '\\', 'F'
60 k.add :list_labels, "List labels", 'L'
61 k.add :poll, "Poll for new messages", 'P'
62 k.add :compose, "Compose new message", 'm', 'c'
63 k.add :nothing, "Do nothing", :ctrl_g
64 k.add :recall_draft, "Edit most recent draft message", 'R'
71 Ncurses.stdscr.keypad 1
78 return unless $cursing
83 module_function :start_cursing, :stop_cursing
88 rescue Index::LockError => e
93 h.say Index.fancy_lock_error_message_for(e)
95 case h.ask("Should I ask that process to kill itself? ")
97 h.say "Ok, suggesting seppuku..."
98 FileUtils.touch Redwood::SUICIDE_FN
99 sleep SuicideManager::DELAY * 2
100 FileUtils.rm_f Redwood::SUICIDE_FN
101 h.say "Let's try that again."
105 Ok, giving up. If the process crashed and left a stale lockfile, you
106 can fix this by manually deleting #{Index.lockfile}.
116 if(s = Index.source_for DraftManager.source_name)
117 DraftManager.source = s
119 Redwood::log "no draft source, auto-adding..."
120 Index.add_source DraftManager.new_source
123 if(s = Index.source_for SentManager.source_name)
124 SentManager.source = s
126 Redwood::log "no sent mail source, auto-adding..."
127 Index.add_source SentManager.new_source
130 log "starting curses"
134 c.add :status_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLUE, Ncurses::A_BOLD
135 c.add :index_old_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK
136 c.add :index_new_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK,
138 c.add :index_starred_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK,
140 c.add :labellist_old_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK
141 c.add :labellist_new_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK,
143 c.add :twiddle_color, Ncurses::COLOR_BLUE, Ncurses::COLOR_BLACK
144 c.add :label_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
145 c.add :message_patina_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_GREEN
146 c.add :alternate_patina_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_BLUE
147 c.add :missing_message_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_RED
148 c.add :attachment_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
149 c.add :cryptosig_valid_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
150 c.add :cryptosig_unknown_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
151 c.add :cryptosig_invalid_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_RED, Ncurses::A_BOLD
152 c.add :generic_notice_patina_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
153 c.add :quote_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
154 c.add :sig_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
155 c.add :quote_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
156 c.add :sig_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
157 c.add :to_me_color, Ncurses::COLOR_GREEN, Ncurses::COLOR_BLACK
158 c.add :starred_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK,
160 c.add :starred_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_GREEN,
162 c.add :alternate_starred_patina_color, Ncurses::COLOR_YELLOW,
163 Ncurses::COLOR_BLUE, Ncurses::A_BOLD
164 c.add :snippet_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
165 c.add :option_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK
166 c.add :tagged_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK,
168 c.add :draft_notification_color, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK,
170 c.add :completion_character_color, Ncurses::COLOR_WHITE,
171 Ncurses::COLOR_BLACK, Ncurses::A_BOLD
172 c.add :horizontal_selector_selected_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
173 c.add :horizontal_selector_unselected_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
174 c.add :search_highlight_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_YELLOW, Ncurses::A_BOLD, :highlight => :search_highlight_color
177 bm = BufferManager.new
179 log "initializing mail index buffer"
180 imode = InboxMode.new
181 ibuf = bm.spawn "Inbox", imode
183 log "ready for interaction!"
188 Index.usual_sources.each do |s|
189 next unless s.respond_to? :connect
190 reporting_thread("call #connect on #{s}") do
193 rescue SourceError => e
194 Redwood::log "fatal error loading from #{s}: #{e.message}"
197 end unless $opts[:no_initial_poll]
199 imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
201 unless $opts[:no_threads]
204 Index.start_lock_update_thread
208 SearchResultsMode.spawn_from_query $opts[:search]
211 until $exceptions.nonempty? || SuicideManager.die?
212 c = Ncurses.nonblocking_getch
221 bm.resolve_input_with_keymap c, global_keymap
223 rescue InputSequenceAborted
229 break if bm.kill_all_buffers_safely
231 curmode = bm.focus_buf.mode
232 bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
235 when :roll_buffers_backwards
236 bm.roll_buffers_backwards
238 bm.kill_buffer_safely bm.focus_buf
240 bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
242 b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
243 b.mode.load_in_background if new
245 query = BufferManager.ask :search, "search all messages: "
246 next unless query && query !~ /^\s*$/
247 SearchResultsMode.spawn_from_query query
249 labels = LabelManager.listable_labels.map { |l| LabelManager.string_for l }
250 user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
251 unless user_label.nil?
253 bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty?
255 LabelSearchResultsMode.spawn_nicely user_label
259 ComposeMode.spawn_nicely
261 reporting_thread("user-invoked poll") { PollManager.poll }
263 case Index.num_results_for :label => :draft
265 bm.flash "No draft messages."
268 Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
269 r = ResumeMode.new(m)
270 BufferManager.spawn "Edit message", r
273 b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
274 b.mode.load_threads :num => b.content_height if new
276 when :nothing, InputSequenceAborted
278 bm.completely_redraw_screen
280 bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}."
286 bm.kill_all_buffers if SuicideManager.die?
287 rescue Exception => e
288 $exceptions << [e, "main"]
290 unless $opts[:no_threads]
291 PollManager.stop if PollManager.instantiated?
292 SuicideManager.stop if PollManager.instantiated?
293 Index.stop_lock_update_thread
298 Redwood::log "stopped cursing"
300 if SuicideManager.instantiated? && SuicideManager.die?
301 Redwood::log "I've been ordered to commit seppuku. I obey!"
304 if $exceptions.empty?
305 Redwood::log "no fatal errors. good job, william."
308 Redwood::log "oh crap, an exception"
314 unless $exceptions.empty?
315 File.open("sup-exception-log.txt", "w") do |f|
316 $exceptions.each do |e, name|
317 f.puts "--- #{e.class.name} from thread: #{name}"
318 f.puts e.message, e.backtrace
322 ----------------------------------------------------------------
323 I'm very sorry. It seems that an error occurred in Sup. Please
324 accept my sincere apologies. If you don't mind, please send the
325 contents of sup-exception-log.txt and a brief report of the
326 circumstances to sup-talk at rubyforge dot orgs so that I might
327 address this problem. Thank you!
331 ----------------------------------------------------------------
333 $exceptions.each do |e, name|
334 puts "--- #{e.class.name} from thread: #{name}"
335 puts e.message, e.backtrace