there are a couple important instance variables we hold to format the thread and to provide line-based functionality. @layout is a map from Messages to MessageLayouts, and @chunk_layout from Chunks to ChunkLayouts. @message_lines is a map from row s to Message objects. @chunk_lines is a map from row s to Chunk objects. @person_lines is a map from row s to Person objects.
# File lib/sup/modes/thread-view-mode.rb, line 103 def initialize thread, hidden_labels=[], index_mode=nil super() @thread = thread @hidden_labels = hidden_labels ## used for dispatch-and-next @index_mode = index_mode @dying = false @layout = SavingHash.new { MessageLayout.new } @chunk_layout = SavingHash.new { ChunkLayout.new } earliest, latest = nil, nil latest_date = nil altcolor = false @thread.each do |m, d, p| next unless m earliest ||= m @layout[m].state = initial_state_for m @layout[m].color = altcolor ? :alternate_patina_color : :message_patina_color @layout[m].star_color = altcolor ? :alternate_starred_patina_color : :starred_patina_color @layout[m].orig_new = m.has_label? :read altcolor = !altcolor if latest_date.nil? || m.date > latest_date latest_date = m.date latest = m end end @wrap = true @layout[latest].state = :open if @layout[latest].state == :closed @layout[earliest].state = :detailed if earliest.has_label?(:unread) || @thread.size == 1 @thread.remove_label :unread Index.save_thread @thread end
# File lib/sup/modes/thread-view-mode.rb, line 155 def [] i; @text[i]; end
called when someone presses enter when the cursor is highlighting a chunk. for expandable chunks (including messages) we toggle open/closed state; for viewable chunks (like attachments) we view.
# File lib/sup/modes/thread-view-mode.rb, line 314 def activate_chunk chunk = @chunk_lines[curpos] or return if chunk.is_a? Chunk::Text ## if the cursor is over a text region, expand/collapse the ## entire message chunk = @message_lines[curpos] end layout = if chunk.is_a?(Message) @layout[chunk] elsif chunk.expandable? @chunk_layout[chunk] end if layout layout.state = (layout.state != :closed ? :closed : :open) #cursor_down if layout.state == :closed # too annoying update elsif chunk.viewable? view chunk end if chunk.is_a?(Message) jump_to_message chunk jump_to_next_open if layout.state == :closed end end
# File lib/sup/modes/thread-view-mode.rb, line 247 def alias p = @person_lines[curpos] or return alias_contact p update end
# File lib/sup/modes/thread-view-mode.rb, line 435 def align_current_message m = @message_lines[curpos] or return jump_to_message m, true end
# File lib/sup/modes/thread-view-mode.rb, line 507 def archive_and_kill; archive_and_then :kill end
# File lib/sup/modes/thread-view-mode.rb, line 512 def archive_and_next; archive_and_then :next end
# File lib/sup/modes/thread-view-mode.rb, line 518 def archive_and_prev; archive_and_then :prev end
# File lib/sup/modes/thread-view-mode.rb, line 524 def archive_and_then op dispatch op do @thread.remove_label :inbox UpdateManager.relay self, :archived, @thread.first Index.save_thread @thread UndoManager.register "archiving 1 thread" do @thread.apply_label :inbox Index.save_thread @thread UpdateManager.relay self, :unarchived, @thread.first end end end
# File lib/sup/modes/thread-view-mode.rb, line 219 def bounce m = @message_lines[curpos] or return to = BufferManager.ask_for_contacts(:people, "Bounce To: ") or return defcmd = AccountManager.default_account.bounce_sendmail cmd = case (hookcmd = HookManager.run "bounce-command", :from => m.from, :to => to) when nil, /^$/ then defcmd else hookcmd end + ' ' + to.map { |t| t.email }.join(' ') bt = to.size > 1 ? "#{to.size} recipients" : to.to_s if BufferManager.ask_yes_or_no "Really bounce to #{bt}?" debug "bounce command: #{cmd}" begin IO.popen(cmd, 'w') do |sm| sm.puts m.raw_message end raise SendmailCommandFailed, "Couldn't execute #{cmd}" unless $? == 0 rescue SystemCallError, SendmailCommandFailed => e warn "problem sending mail: #{e.message}" BufferManager.flash "Problem sending mail: #{e.message}" end end end
a little hacky---since regen_text can depend on buffer features like the content_width, we don't call it in the constructor, and instead call it here, which is set before we're responsible for drawing ourself.
# File lib/sup/modes/thread-view-mode.rb, line 160 def buffer= b super regen_text end
# File lib/sup/modes/thread-view-mode.rb, line 503 def cleanup @layout = @chunk_layout = @text = nil # for good luck end
# File lib/sup/modes/thread-view-mode.rb, line 488 def collapse_non_new_messages @layout.each { |m, l| l.state = l.orig_new ? :open : :closed } update end
# File lib/sup/modes/thread-view-mode.rb, line 260 def compose p = @person_lines[curpos] if p ComposeMode.spawn_nicely :to_default => p else ComposeMode.spawn_nicely end end
# File lib/sup/modes/thread-view-mode.rb, line 509 def delete_and_kill; delete_and_then :kill end
# File lib/sup/modes/thread-view-mode.rb, line 514 def delete_and_next; delete_and_then :next end
# File lib/sup/modes/thread-view-mode.rb, line 520 def delete_and_prev; delete_and_then :prev end
# File lib/sup/modes/thread-view-mode.rb, line 550 def delete_and_then op dispatch op do @thread.apply_label :deleted UpdateManager.relay self, :deleted, @thread.first Index.save_thread @thread UndoManager.register "deleting 1 thread" do @thread.remove_label :deleted Index.save_thread @thread UpdateManager.relay self, :undeleted, @thread.first end end end
# File lib/sup/modes/thread-view-mode.rb, line 516 def do_nothing_and_next; do_nothing_and_then :next end
# File lib/sup/modes/thread-view-mode.rb, line 522 def do_nothing_and_prev; do_nothing_and_then :prev end
# File lib/sup/modes/thread-view-mode.rb, line 571 def do_nothing_and_then op dispatch op end
# File lib/sup/modes/thread-view-mode.rb, line 147 def draw_line ln, opts={} if ln == curpos super ln, :highlight => true else super end end
# File lib/sup/modes/thread-view-mode.rb, line 339 def edit_as_new m = @message_lines[curpos] or return mode = ComposeMode.new(:body => m.quotable_body_lines, :to => m.to, :cc => m.cc, :subj => m.subj, :bcc => m.bcc, :refs => m.refs, :replytos => m.replytos) BufferManager.spawn "edit as new", mode mode.edit_message end
# File lib/sup/modes/thread-view-mode.rb, line 391 def edit_draft m = @message_lines[curpos] or return if m.is_draft? mode = ResumeMode.new m BufferManager.spawn "Edit message", mode BufferManager.kill_buffer self.buffer mode.edit_message else BufferManager.flash "Not a draft message!" end end
# File lib/sup/modes/thread-view-mode.rb, line 269 def edit_labels old_labels = @thread.labels reserved_labels = old_labels.select { |l| LabelManager::RESERVED_LABELS.include? l } new_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", @thread.labels return unless new_labels @thread.labels = Set.new(reserved_labels) + new_labels new_labels.each { |l| LabelManager << l } update UpdateManager.relay self, :labeled, @thread.first Index.save_thread @thread UndoManager.register "labeling thread" do @thread.labels = old_labels Index.save_thread @thread UpdateManager.relay self, :labeled, @thread.first end end
# File lib/sup/modes/thread-view-mode.rb, line 481 def expand_all_messages @global_message_state ||= :closed @global_message_state = (@global_message_state == :closed ? :open : :closed) @layout.each { |m, l| l.state = @global_message_state } update end
# File lib/sup/modes/thread-view-mode.rb, line 493 def expand_all_quotes if(m = @message_lines[curpos]) quotes = m.chunks.select { |c| (c.is_a?(Chunk::Quote) || c.is_a?(Chunk::Signature)) && c.lines.length > 1 } numopen = quotes.inject(0) { |s, c| s + (@chunk_layout[c].state == :open ? 1 : 0) } newstate = numopen > quotes.length / 2 ? :closed : :open quotes.each { |c| @chunk_layout[c].state = newstate } update end end
# File lib/sup/modes/thread-view-mode.rb, line 211 def forward if(chunk = @chunk_lines[curpos]) && chunk.is_a?(Chunk::Attachment) ForwardMode.spawn_nicely :attachments => [chunk] elsif(m = @message_lines[curpos]) ForwardMode.spawn_nicely :message => m end end
# File lib/sup/modes/thread-view-mode.rb, line 415 def jump_to_first_open m = @message_lines[0] or return if @layout[m].state != :closed jump_to_message m#, true else jump_to_next_open #true end end
# File lib/sup/modes/thread-view-mode.rb, line 458 def jump_to_message m, force_alignment=false l = @layout[m] ## boundaries of the message message_left = l.depth * INDENT_SPACES message_right = message_left + l.width ## calculate leftmost colum left = if force_alignment # force mode: align exactly message_left else # regular: minimize cursor movement ## leftmost and rightmost are boundaries of all valid left-column ## alignments. leftmost = [message_left, message_right - buffer.content_width + 1].min rightmost = message_left leftcol.clamp(leftmost, rightmost) end jump_to_line l.top # move vertically jump_to_col left # move horizontally set_cursor_pos l.top # set cursor pos end
# File lib/sup/modes/thread-view-mode.rb, line 424 def jump_to_next_open force_alignment=nil return continue_search_in_buffer if in_search? # hack: allow 'n' to apply to both operations m = (curpos ... @message_lines.length).argfind { |i| @message_lines[i] } return unless m while nextm = @layout[m].next break if @layout[nextm].state != :closed m = nextm end jump_to_message nextm, force_alignment if nextm end
# File lib/sup/modes/thread-view-mode.rb, line 440 def jump_to_prev_open m = (0 .. curpos).to_a.reverse.argfind { |i| @message_lines[i] } # bah, .to_a return unless m ## jump to the top of the current message if we're in the body; ## otherwise, to the previous message top = @layout[m].top if curpos == top while(prevm = @layout[m].prev) break if @layout[prevm].state != :closed m = prevm end jump_to_message prevm if prevm else jump_to_message m end end
# File lib/sup/modes/thread-view-mode.rb, line 154 def lines; @text.length; end
# File lib/sup/modes/thread-view-mode.rb, line 597 def pipe_message chunk = @chunk_lines[curpos] chunk = nil unless chunk.is_a?(Chunk::Attachment) message = @message_lines[curpos] unless chunk return unless chunk || message command = BufferManager.ask(:shell, "pipe command: ") return if command.nil? || command.empty? output = pipe_to_process(command) do |stream| if chunk stream.print chunk.raw_content else message.each_raw_message_line { |l| stream.print l } end end if output BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii) else BufferManager.flash "'#{command}' done!" end end
# File lib/sup/modes/thread-view-mode.rb, line 191 def reply_all; reply :all; end
# File lib/sup/modes/thread-view-mode.rb, line 365 def save_all_to_disk m = @message_lines[curpos] or return default_dir = ($config[:default_attachment_save_dir] || ".") folder = BufferManager.ask_for_filename :filename, "Save all attachments to folder: ", default_dir, true return unless folder num = 0 num_errors = 0 m.chunks.each do |chunk| next unless chunk.is_a?(Chunk::Attachment) fn = File.join(folder, chunk.filename) num_errors += 1 unless save_to_file(fn, false) { |f| f.print chunk.raw_content } num += 1 end if num == 0 BufferManager.flash "Didn't find any attachments!" else if num_errors == 0 BufferManager.flash "Wrote #{num.pluralize 'attachment'} to #{folder}." else BufferManager.flash "Wrote #{(num - num_errors).pluralize 'attachment'} to #{folder}; couldn't write #{num_errors} of them (see log)." end end end
# File lib/sup/modes/thread-view-mode.rb, line 346 def save_to_disk chunk = @chunk_lines[curpos] or return case chunk when Chunk::Attachment default_dir = $config[:default_attachment_save_dir] default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty? default_fn = File.expand_path File.join(default_dir, chunk.filename) fn = BufferManager.ask_for_filename :filename, "Save attachment to file: ", default_fn save_to_file(fn) { |f| f.print chunk.raw_content } if fn else m = @message_lines[curpos] fn = BufferManager.ask_for_filename :filename, "Save message to file: " return unless fn save_to_file(fn) do |f| m.each_raw_message_line { |l| f.print l } end end end
# File lib/sup/modes/thread-view-mode.rb, line 253 def search p = @person_lines[curpos] or return mode = PersonSearchResultsMode.new [p] BufferManager.spawn "Search for #{p.name}", mode mode.load_threads :num => mode.buffer.content_height end
# File lib/sup/modes/thread-view-mode.rb, line 403 def send_draft m = @message_lines[curpos] or return if m.is_draft? mode = ResumeMode.new m BufferManager.spawn "Send message", mode BufferManager.kill_buffer self.buffer mode.send_message else BufferManager.flash "Not a draft message!" end end
# File lib/sup/modes/thread-view-mode.rb, line 165 def show_header m = @message_lines[curpos] or return BufferManager.spawn_unless_exists("Full header for #{m.id}") do TextMode.new m.raw_header.ascii end end
# File lib/sup/modes/thread-view-mode.rb, line 172 def show_message m = @message_lines[curpos] or return BufferManager.spawn_unless_exists("Raw message for #{m.id}") do TextMode.new m.raw_message.ascii end end
# File lib/sup/modes/thread-view-mode.rb, line 508 def spam_and_kill; spam_and_then :kill end
# File lib/sup/modes/thread-view-mode.rb, line 513 def spam_and_next; spam_and_then :next end
# File lib/sup/modes/thread-view-mode.rb, line 519 def spam_and_prev; spam_and_then :prev end
# File lib/sup/modes/thread-view-mode.rb, line 537 def spam_and_then op dispatch op do @thread.apply_label :spam UpdateManager.relay self, :spammed, @thread.first Index.save_thread @thread UndoManager.register "marking 1 thread as spam" do @thread.remove_label :spam Index.save_thread @thread UpdateManager.relay self, :unspammed, @thread.first end end end
# File lib/sup/modes/thread-view-mode.rb, line 193 def subscribe_to_list m = @message_lines[curpos] or return if m.list_subscribe && m.list_subscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/ ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => ($3 || "subscribe") else BufferManager.flash "Can't find List-Subscribe header for this message." end end
# File lib/sup/modes/thread-view-mode.rb, line 179 def toggle_detailed_header m = @message_lines[curpos] or return @layout[m].state = (@layout[m].state == :detailed ? :open : :detailed) update end
# File lib/sup/modes/thread-view-mode.rb, line 297 def toggle_label m, label if m.has_label? label m.remove_label label else m.add_label label end ## TODO: don't recalculate EVERYTHING just to add a stupid little ## star to the display update UpdateManager.relay self, :single_message_labeled, m Index.save_thread @thread end
# File lib/sup/modes/thread-view-mode.rb, line 292 def toggle_new m = @message_lines[curpos] or return toggle_label m, :unread end
# File lib/sup/modes/thread-view-mode.rb, line 287 def toggle_starred m = @message_lines[curpos] or return toggle_label m, :starred end
# File lib/sup/modes/thread-view-mode.rb, line 141 def toggle_wrap @wrap = !@wrap regen_text buffer.mark_dirty if buffer end
# File lib/sup/modes/thread-view-mode.rb, line 510 def unread_and_kill; unread_and_then :kill end
# File lib/sup/modes/thread-view-mode.rb, line 515 def unread_and_next; unread_and_then :next end
# File lib/sup/modes/thread-view-mode.rb, line 521 def unread_and_prev; unread_and_then :prev end
# File lib/sup/modes/thread-view-mode.rb, line 563 def unread_and_then op dispatch op do @thread.apply_label :unread UpdateManager.relay self, :unread, @thread.first Index.save_thread @thread end end
# File lib/sup/modes/thread-view-mode.rb, line 202 def unsubscribe_from_list m = @message_lines[curpos] or return if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/ ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => ($3 || "unsubscribe") else BufferManager.flash "Can't find List-Unsubscribe header for this message." end end
Generated with the Darkfish Rdoc Generator 2.