LineCursorMode
subclasses should implement:
is_relevant?
# File lib/sup/modes/thread-index-mode.rb, line 53 def initialize hidden_labels=[], load_thread_opts={} super() @mutex = Mutex.new # covers the following variables: @threads = {} @hidden_threads = {} @size_widget_width = nil @size_widgets = {} @tags = Tagger.new self ## these guys, and @text and @lines, are not covered @load_thread = nil @load_thread_opts = load_thread_opts @hidden_labels = hidden_labels + LabelManager::HIDDEN_RESERVED_LABELS @date_width = DATE_WIDTH @interrupt_search = false initialize_threads # defines @ts and @ts_mutex update # defines @text and @lines UpdateManager.register self @save_thread_mutex = Mutex.new @last_load_more_size = nil to_load_more do |size| next if @last_load_more_size == 0 load_threads :num => size, :when_done => lambda { |num| @last_load_more_size = num } end end
# File lib/sup/modes/thread-index-mode.rb, line 87 def [] i; @text[i]; end
returns an undo lambda
# File lib/sup/modes/thread-index-mode.rb, line 286 def actually_toggle_archived t thread = t pos = curpos if t.has_label? :inbox t.remove_label :inbox UpdateManager.relay self, :archived, t.first lambda do thread.apply_label :inbox update_text_for_line pos UpdateManager.relay self,:unarchived, thread.first end else t.apply_label :inbox UpdateManager.relay self, :unarchived, t.first lambda do thread.remove_label :inbox update_text_for_line pos UpdateManager.relay self, :unarchived, thread.first end end end
returns an undo lambda
# File lib/sup/modes/thread-index-mode.rb, line 333 def actually_toggle_deleted t if t.has_label? :deleted t.remove_label :deleted add_or_unhide t.first UpdateManager.relay self, :undeleted, t.first lambda do t.apply_label :deleted hide_thread t UpdateManager.relay self, :deleted, t.first end else t.apply_label :deleted hide_thread t UpdateManager.relay self, :deleted, t.first lambda do t.remove_label :deleted add_or_unhide t.first UpdateManager.relay self, :undeleted, t.first end end end
returns an undo lambda
# File lib/sup/modes/thread-index-mode.rb, line 309 def actually_toggle_spammed t thread = t if t.has_label? :spam t.remove_label :spam add_or_unhide t.first UpdateManager.relay self, :unspammed, t.first lambda do thread.apply_label :spam self.hide_thread thread UpdateManager.relay self,:spammed, thread.first end else t.apply_label :spam hide_thread t UpdateManager.relay self, :spammed, t.first lambda do thread.remove_label :spam add_or_unhide thread.first UpdateManager.relay self,:unspammed, thread.first end end end
returns an undo lambda
# File lib/sup/modes/thread-index-mode.rb, line 247 def actually_toggle_starred t pos = curpos if t.has_label? :starred # if ANY message has a star t.remove_label :starred # remove from all UpdateManager.relay self, :unstarred, t.first lambda do t.first.add_label :starred UpdateManager.relay self, :starred, t.first regen_text end else t.first.add_label :starred # add only to first UpdateManager.relay self, :starred, t.first lambda do t.remove_label :starred UpdateManager.relay self, :unstarred, t.first regen_text end end end
# File lib/sup/modes/thread-index-mode.rb, line 524 def apply_to_tagged; @tags.apply_to_tagged; end
# File lib/sup/modes/thread-index-mode.rb, line 658 def cancel_search @interrupt_search = true end
# File lib/sup/modes/thread-index-mode.rb, line 485 def cleanup UpdateManager.unregister self if @load_thread @load_thread.kill BufferManager.clear @mbid if @mbid sleep 0.1 # TODO: necessary? BufferManager.erase_flash end dirty_threads = @mutex.synchronize { (@threads + @hidden_threads.keys).select { |t| t.dirty? } } fail "dirty threads remain" unless dirty_threads.empty? super end
# File lib/sup/modes/thread-index-mode.rb, line 88 def contains_thread? t; @threads.include?(t) end
# File lib/sup/modes/thread-index-mode.rb, line 526 def edit_labels thread = cursor_thread or return speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq old_labels = thread.labels pos = curpos keepl, modifyl = thread.labels.partition { |t| speciall.member? t } user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl, @hidden_labels return unless user_labels thread.labels = Set.new(keepl) + user_labels user_labels.each { |l| LabelManager << l } update_text_for_line curpos UndoManager.register "labeling thread" do thread.labels = old_labels update_text_for_line pos UpdateManager.relay self, :labeled, thread.first Index.save_thread thread end UpdateManager.relay self, :labeled, thread.first Index.save_thread thread end
# File lib/sup/modes/thread-index-mode.rb, line 235 def edit_message return unless(t = cursor_thread) message, *crap = t.find { |m, *o| m.has_label? :draft } if message mode = ResumeMode.new message BufferManager.spawn "Edit message", mode else BufferManager.flash "Not a draft message!" end end
# File lib/sup/modes/thread-index-mode.rb, line 458 def flush_index @flush_id = BufferManager.say "Flushing index..." Index.save_index BufferManager.clear @flush_id end
# File lib/sup/modes/thread-index-mode.rb, line 603 def forward t = cursor_thread or return m = t.latest_message return if m.nil? # probably won't happen m.load_from_source! ForwardMode.spawn_nicely :message => m end
# File lib/sup/modes/thread-index-mode.rb, line 187 def handle_added_update sender, m add_or_unhide m BufferManager.draw_screen end
# File lib/sup/modes/thread-index-mode.rb, line 200 def handle_deleted_update sender, m t = @ts_mutex.synchronize { @ts.thread_for m } return unless t hide_thread t update end
# File lib/sup/modes/thread-index-mode.rb, line 163 def handle_labeled_update sender, m if(t = thread_containing(m)) l = @lines[t] or return update_text_for_line l elsif is_relevant?(m) add_or_unhide m end end
# File lib/sup/modes/thread-index-mode.rb, line 172 def handle_simple_update sender, m t = thread_containing(m) or return l = @lines[t] or return update_text_for_line l end
# File lib/sup/modes/thread-index-mode.rb, line 192 def handle_single_message_deleted_update sender, m @ts_mutex.synchronize do return unless @ts.contains? m @ts.remove_id m.id end update end
# File lib/sup/modes/thread-index-mode.rb, line 157 def handle_single_message_labeled_update sender, m ## no need to do anything different here; we don't differentiate ## messages from their containing threads handle_labeled_update sender, m end
# File lib/sup/modes/thread-index-mode.rb, line 207 def handle_spammed_update sender, m t = @ts_mutex.synchronize { @ts.thread_for m } return unless t hide_thread t update end
# File lib/sup/modes/thread-index-mode.rb, line 214 def handle_undeleted_update sender, m add_or_unhide m end
overwrite me!
# File lib/sup/modes/thread-index-mode.rb, line 185 def is_relevant? m; false; end
# File lib/sup/modes/thread-index-mode.rb, line 391 def join_threads ## this command has no non-tagged form. as a convenience, allow this ## command to be applied to tagged threads without hitting ';'. @tags.apply_to_tagged :join_threads end
# File lib/sup/modes/thread-index-mode.rb, line 404 def jump_to_next_new n = @mutex.synchronize do ((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } || (0 ... curpos).find { |i| @threads[i].has_label? :unread } end if n ## jump there if necessary jump_to_line n unless n >= topline && n < botline set_cursor_pos n else BufferManager.flash "No new messages." end end
# File lib/sup/modes/thread-index-mode.rb, line 453 def kill t = cursor_thread or return multi_kill [t] end
# File lib/sup/modes/thread-index-mode.rb, line 140 def launch_another_thread thread, direction, &b l = @lines[thread] or return target_l = l + direction t = @mutex.synchronize do if target_l >= 0 && target_l < @threads.length @threads[target_l] end end if t # there's a next thread set_cursor_pos target_l # move out of mutex? select t, b elsif b # no next thread. call the block anyways b.call end end
these two methods are called by thread-view-modes when the user wants to view the previous/next thread without going back to index-mode. we update the cursor as a convenience.
# File lib/sup/modes/thread-index-mode.rb, line 132 def launch_next_thread_after thread, &b launch_another_thread thread, 1, &b end
# File lib/sup/modes/thread-index-mode.rb, line 136 def launch_prev_thread_before thread, &b launch_another_thread thread, -1, &b end
# File lib/sup/modes/thread-index-mode.rb, line 86 def lines; @text.length; end
# File lib/sup/modes/thread-index-mode.rb, line 662 def load_all_threads load_threads :num => -1 end
TODO: figure out @ts_mutex in this method
# File lib/sup/modes/thread-index-mode.rb, line 621 def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={} @interrupt_search = false @mbid = BufferManager.say "Searching for threads..." ts_to_load = n ts_to_load = ts_to_load + @ts.size unless n == -1 # -1 means all threads orig_size = @ts.size last_update = Time.now @ts.load_n_threads(ts_to_load, opts) do |i| if (Time.now - last_update) >= 0.25 BufferManager.say "Loaded #{i.pluralize 'thread'}...", @mbid update BufferManager.draw_screen last_update = Time.now end ::Thread.pass break if @interrupt_search end @ts.threads.each { |th| th.labels.each { |l| LabelManager << l } } update BufferManager.clear @mbid @mbid = nil BufferManager.draw_screen @ts.size - orig_size end
# File lib/sup/modes/thread-index-mode.rb, line 611 def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={} return if @load_thread # todo: wrap in mutex @load_thread = Redwood::reporting_thread("load threads for thread-index-mode") do num = load_n_threads n, opts opts[:when_done].call(num) if opts[:when_done] @load_thread = nil end end
# File lib/sup/modes/thread-index-mode.rb, line 666 def load_threads opts={} if opts[:num].nil? n = ThreadIndexMode::LOAD_MORE_THREAD_NUM else n = opts[:num] end myopts = @load_thread_opts.merge({ :when_done => (lambda do |num| opts[:when_done].call(num) if opts[:when_done] if num > 0 BufferManager.flash "Found #{num.pluralize 'thread'}." else BufferManager.flash "No matches." end end)}) if opts[:background] || opts[:background].nil? load_n_threads_background n, myopts else load_n_threads n, myopts end end
# File lib/sup/modes/thread-index-mode.rb, line 553 def multi_edit_labels threads user_labels = BufferManager.ask_for_labels :labels, "Add/remove labels (use -label to remove): ", [], @hidden_labels return unless user_labels user_labels.map! { |l| (l.to_s =~ /^-/)? [l.to_s.gsub(/^-?/, '').to_sym, true] : [l, false] } hl = user_labels.select { |(l,_)| @hidden_labels.member? l } unless hl.empty? BufferManager.flash "'#{hl}' is a reserved label!" return end old_labels = threads.map { |t| t.labels.dup } threads.each do |t| user_labels.each do |(l, to_remove)| if to_remove t.remove_label l else t.apply_label l LabelManager << l end end UpdateManager.relay self, :labeled, t.first end regen_text UndoManager.register "labeling #{threads.size.pluralize 'thread'}" do threads.zip(old_labels).map do |t, old_labels| t.labels = old_labels UpdateManager.relay self, :labeled, t.first Index.save_thread t end regen_text end threads.each { |t| Index.save_thread t } end
# File lib/sup/modes/thread-index-mode.rb, line 397 def multi_join_threads threads @ts.join_threads threads or return threads.each { |t| Index.save_thread t } @tags.drop_all_tags # otherwise we have tag pointers to invalid threads! update end
m-m-m-m-MULTI-KILL
# File lib/sup/modes/thread-index-mode.rb, line 465 def multi_kill threads UndoManager.register "killing #{threads.size.pluralize 'thread'}" do threads.each do |t| t.remove_label :killed add_or_unhide t.first Index.save_thread t end regen_text end threads.each do |t| t.apply_label :killed hide_thread t end regen_text BufferManager.flash "#{threads.size.pluralize 'thread'} killed." threads.each { |t| Index.save_thread t } end
# File lib/sup/modes/thread-index-mode.rb, line 125 def multi_select threads threads.each { |t| select t } end
# File lib/sup/modes/thread-index-mode.rb, line 364 def multi_toggle_archived threads undos = threads.map { |t| actually_toggle_archived t } UndoManager.register "deleting/undeleting #{threads.size.pluralize 'thread'}", undos, lambda { regen_text }, lambda { threads.each { |t| Index.save_thread t } } regen_text threads.each { |t| Index.save_thread t } end
see comment for multi_toggle_spam
# File lib/sup/modes/thread-index-mode.rb, line 445 def multi_toggle_deleted threads undos = threads.map { |t| actually_toggle_deleted t } UndoManager.register "deleting/undeleting #{threads.size.pluralize 'thread'}", undos, lambda { regen_text }, lambda { threads.each { |t| Index.save_thread t } } regen_text threads.each { |t| Index.save_thread t } end
# File lib/sup/modes/thread-index-mode.rb, line 380 def multi_toggle_new threads threads.each { |t| t.toggle_label :unread } regen_text threads.each { |t| Index.save_thread t } end
both spam and deleted have the curious characteristic that you always want to hide the thread after either applying or removing that label. in all thread-index-views except for label-search-results-mode, when you mark a message as spam or deleted, you want it to disappear immediately; in LSRM, you only see deleted or spam emails, and when you undelete or unspam them you also want them to disappear immediately.
# File lib/sup/modes/thread-index-mode.rb, line 430 def multi_toggle_spam threads undos = threads.map { |t| actually_toggle_spammed t } threads.each { |t| HookManager.run("mark-as-spam", :thread => t) } UndoManager.register "marking/unmarking #{threads.size.pluralize 'thread'} as spam", undos, lambda { regen_text }, lambda { threads.each { |t| Index.save_thread t } } regen_text threads.each { |t| Index.save_thread t } end
# File lib/sup/modes/thread-index-mode.rb, line 277 def multi_toggle_starred threads UndoManager.register "toggling #{threads.size.pluralize 'thread'} starred status", threads.map { |t| actually_toggle_starred t }, lambda { threads.each { |t| Index.save_thread t } } regen_text threads.each { |t| Index.save_thread t } end
# File lib/sup/modes/thread-index-mode.rb, line 386 def multi_toggle_tagged threads @mutex.synchronize { @tags.drop_all_tags } regen_text end
# File lib/sup/modes/thread-index-mode.rb, line 90 def reload drop_all_threads UndoManager.clear BufferManager.draw_screen load_threads :num => buffer.content_height end
# File lib/sup/modes/thread-index-mode.rb, line 601 def reply_all; reply :all; end
# File lib/sup/modes/thread-index-mode.rb, line 691 def resize rows, cols regen_text super end
open up a thread view window
# File lib/sup/modes/thread-index-mode.rb, line 98 def select t=nil, when_done=nil t ||= cursor_thread or return Redwood::reporting_thread("load messages for thread-view-mode") do num = t.size message = "Loading #{num.pluralize 'message body'}..." BufferManager.say(message) do |sid| t.each_with_index do |(m, *o), i| next unless m BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1 m.load_from_source! end end mode = ThreadViewMode.new t, @hidden_labels, self BufferManager.spawn t.subj, mode BufferManager.draw_screen mode.jump_to_first_open BufferManager.draw_screen # lame TODO: make this unnecessary ## the first draw_screen is needed before topline and botline ## are set, and the second to show the cursor having moved update_text_for_line curpos UpdateManager.relay self, :read, t.first when_done.call if when_done end end
# File lib/sup/modes/thread-index-mode.rb, line 650 def status if (l = lines) == 0 "line 0 of 0" else "line #{curpos + 1} of #{l} #{dirty? ? '*modified*' : ''}" end end
# File lib/sup/modes/thread-index-mode.rb, line 511 def tag_matching query = BufferManager.ask :search, "tag threads matching (regex): " return if query.nil? || query.empty? query = begin /#{query}/ rescue RegexpError => e BufferManager.flash "error interpreting '#{query}': #{e.message}" return end @mutex.synchronize { @threads.each { |t| @tags.tag t if thread_matches?(t, query) } } regen_text end
# File lib/sup/modes/thread-index-mode.rb, line 355 def toggle_archived t = cursor_thread or return undo = actually_toggle_archived t UndoManager.register "deleting/undeleting thread #{t.first.id}", undo, lambda { update_text_for_line curpos }, lambda { Index.save_thread t } update_text_for_line curpos Index.save_thread t end
# File lib/sup/modes/thread-index-mode.rb, line 439 def toggle_deleted t = cursor_thread or return multi_toggle_deleted [t] end
# File lib/sup/modes/thread-index-mode.rb, line 372 def toggle_new t = cursor_thread or return t.toggle_label :unread update_text_for_line curpos cursor_down Index.save_thread t end
# File lib/sup/modes/thread-index-mode.rb, line 418 def toggle_spam t = cursor_thread or return multi_toggle_spam [t] end
# File lib/sup/modes/thread-index-mode.rb, line 268 def toggle_starred t = cursor_thread or return undo = actually_toggle_starred t UndoManager.register "toggling thread starred status", undo, lambda { Index.save_thread t } update_text_for_line curpos cursor_down Index.save_thread t end
# File lib/sup/modes/thread-index-mode.rb, line 499 def toggle_tagged t = cursor_thread or return @mutex.synchronize { @tags.toggle_tag_for t } update_text_for_line curpos cursor_down end
# File lib/sup/modes/thread-index-mode.rb, line 506 def toggle_tagged_all @mutex.synchronize { @threads.each { |t| @tags.toggle_tag_for t } } regen_text end
# File lib/sup/modes/thread-index-mode.rb, line 218 def undo UndoManager.undo end
# File lib/sup/modes/thread-index-mode.rb, line 85 def unsaved?; dirty? end
# File lib/sup/modes/thread-index-mode.rb, line 222 def update old_cursor_thread = cursor_thread @mutex.synchronize do ## let's see you do THIS in python @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| [t.date, t.first.id] }.reverse @size_widgets = @threads.map { |t| size_widget_for_thread t } @size_widget_width = @size_widgets.max_of { |w| w.display_length } end set_cursor_pos @threads.index(old_cursor_thread)||curpos regen_text end
# File lib/sup/modes/thread-index-mode.rb, line 698 def add_or_unhide m @ts_mutex.synchronize do if (is_relevant?(m) || @ts.is_relevant?(m)) && !@ts.contains?(m) @ts.load_thread_for_message m, @load_thread_opts end @hidden_threads.delete @ts.thread_for(m) end update end
# File lib/sup/modes/thread-index-mode.rb, line 722 def cursor_thread; @mutex.synchronize { @threads[curpos] }; end
# File lib/sup/modes/thread-index-mode.rb, line 873 def dirty?; @mutex.synchronize { (@hidden_threads.keys + @threads).any? { |t| t.dirty? } } end
# File lib/sup/modes/thread-index-mode.rb, line 724 def drop_all_threads @tags.drop_all_tags initialize_threads update end
# File lib/sup/modes/thread-index-mode.rb, line 730 def hide_thread t @mutex.synchronize do i = @threads.index(t) or return raise "already hidden" if @hidden_threads[t] @hidden_threads[t] = true @threads.delete_at i @size_widgets.delete_at i @tags.drop_tag_for t end end
# File lib/sup/modes/thread-index-mode.rb, line 761 def regen_text threads = @mutex.synchronize { @threads } @text = threads.map_with_index { |t, i| text_for_thread_at i } @lines = threads.map_with_index { |t, i| [t, i] }.to_h buffer.mark_dirty if buffer end
# File lib/sup/modes/thread-index-mode.rb, line 718 def size_widget_for_thread t HookManager.run("index-mode-size-widget", :thread => t) || default_size_widget_for(t) end
# File lib/sup/modes/thread-index-mode.rb, line 800 def text_for_thread_at line t, size_widget = @mutex.synchronize { [@threads[line], @size_widgets[line]] } date = t.date.to_nice_s starred = t.has_label? :starred ## format the from column cur_width = 0 ann = author_names_and_newness_for_thread t, AUTHOR_LIMIT from = [] ann.each_with_index do |(name, newness), i| break if cur_width >= from_width last = i == ann.length - 1 abbrev = if cur_width + name.display_length > from_width name[0 ... (from_width - cur_width - 1)] + "." elsif cur_width + name.display_length == from_width name[0 ... (from_width - cur_width)] else if last name[0 ... (from_width - cur_width)] else name[0 ... (from_width - cur_width - 1)] + "," end end cur_width += abbrev.display_length if last && from_width > cur_width abbrev += " " * (from_width - cur_width) end from << [(newness ? :index_new_color : (starred ? :index_starred_color : :index_old_color)), abbrev] end dp = t.direct_participants.any? { |p| AccountManager.is_account? p } p = dp || t.participants.any? { |p| AccountManager.is_account? p } subj_color = if t.has_label?(:draft) :index_draft_color elsif t.has_label?(:unread) :index_new_color elsif starred :index_starred_color else :index_old_color end snippet = t.snippet + (t.snippet.empty? ? "" : "...") size_widget_text = sprintf "%#{ @size_widget_width}s", size_widget [ [:tagged_color, @tags.tagged?(t) ? ">" : " "], [:date_color, sprintf("%#{@date_width}s", date)], (starred ? [:starred_color, "*"] : [:none, " "]), ] + from + [ [subj_color, size_widget_text], [:to_me_color, t.labels.member?(:attachment) ? "@" : " "], [:to_me_color, dp ? ">" : (p ? '+' : " ")], ] + (t.labels - @hidden_labels).map { |label| [:label_color, "#{label} "] } + [ [subj_color, t.subj + (t.subj.empty? ? "" : " ")], [:snippet_color, snippet], ] end
# File lib/sup/modes/thread-index-mode.rb, line 710 def thread_containing m; @ts_mutex.synchronize { @ts.thread_for m } end
used to tag threads by query. this can be made a lot more sophisticated, but for right now we'll do the obvious this.
# File lib/sup/modes/thread-index-mode.rb, line 714 def thread_matches? t, query t.subj =~ query || t.snippet =~ query || t.participants.any? { |x| x.longname =~ query } end
# File lib/sup/modes/thread-index-mode.rb, line 741 def update_text_for_line l return unless l # not sure why this happens, but it does, occasionally need_update = false @mutex.synchronize do @size_widgets[l] = size_widget_for_thread @threads[l] ## if the widget size has increased, we need to redraw everyone need_update = @size_widgets[l].size > @size_widget_width end if need_update update else @text[l] = text_for_thread_at l buffer.mark_dirty if buffer end end
Generated with the Darkfish Rdoc Generator 2.