X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Fmodes%2Fthread-index-mode.rb;h=82f258b689681183614b65d6475829ba10d0563e;hb=d4c1bd840f55a07fd5aeb4d08a1f43c3f7c1850e;hp=66c9123a5e002be18d096170b00a7b96a3ee8c7b;hpb=e79b083fd8edcb88c169575ed5ff97c17f129023;p=sup diff --git a/lib/sup/modes/thread-index-mode.rb b/lib/sup/modes/thread-index-mode.rb index 66c9123..82f258b 100644 --- a/lib/sup/modes/thread-index-mode.rb +++ b/lib/sup/modes/thread-index-mode.rb @@ -1,3 +1,5 @@ +require 'set' + module Redwood ## subclasses should implement: @@ -38,12 +40,14 @@ EOS k.add :save, "Save changes now", '$' k.add :jump_to_next_new, "Jump to next new thread", :tab k.add :reply, "Reply to latest message in a thread", 'r' + k.add :reply_all, "Reply to all participants of the latest message in a thread", 'G' k.add :forward, "Forward latest message in a thread", 'f' k.add :toggle_tagged, "Tag/untag selected thread", 't' k.add :toggle_tagged_all, "Tag/untag all threads", 'T' k.add :tag_matching, "Tag matching threads", 'g' k.add :apply_to_tagged, "Apply next command to all tagged threads", '+', '=' k.add :join_threads, "Force tagged threads to be joined into the same thread", '#' + k.add :undo, "Undo the previous action", 'u' end def initialize hidden_labels=[], load_thread_opts={} @@ -73,8 +77,7 @@ EOS @last_load_more_size = nil to_load_more do |size| next if @last_load_more_size == 0 - load_threads :num => 1, :background => false - load_threads :num => (size - 1), + load_threads :num => size, :when_done => lambda { |num| @last_load_more_size = num } end end @@ -86,6 +89,7 @@ EOS def reload drop_all_threads + UndoManager.clear BufferManager.draw_screen load_threads :num => buffer.content_height end @@ -107,7 +111,7 @@ EOS mode = ThreadViewMode.new t, @hidden_labels, self BufferManager.spawn t.subj, mode BufferManager.draw_screen - mode.jump_to_first_open true + 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 @@ -211,6 +215,10 @@ EOS add_or_unhide m end + def undo + UndoManager.undo + end + def update @mutex.synchronize do ## let's see you do THIS in python @@ -233,66 +241,122 @@ EOS end end + ## returns an undo lambda 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 def toggle_starred t = cursor_thread or return - actually_toggle_starred t + undo = actually_toggle_starred t + UndoManager.register "toggling thread starred status", undo update_text_for_line curpos cursor_down end def multi_toggle_starred threads - threads.each { |t| actually_toggle_starred t } + UndoManager.register "toggling #{threads.size.pluralize 'thread'} starred status", + threads.map { |t| actually_toggle_starred t } regen_text end + ## returns an undo lambda 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 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 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 def toggle_archived t = cursor_thread or return - actually_toggle_archived t + undo = actually_toggle_archived t + UndoManager.register "deleting/undeleting thread #{t.first.id}", undo, lambda { update_text_for_line curpos } update_text_for_line curpos end def multi_toggle_archived threads - threads.each { |t| actually_toggle_archived t } + undos = threads.map { |t| actually_toggle_archived t } + UndoManager.register "deleting/undeleting #{threads.size.pluralize 'thread'}", undos, lambda { regen_text } regen_text end @@ -353,10 +417,9 @@ EOS ## see deleted or spam emails, and when you undelete or unspam them ## you also want them to disappear immediately. def multi_toggle_spam threads - threads.each do |t| - actually_toggle_spammed t - hide_thread t - end + undos = threads.map { |t| actually_toggle_spammed t } + UndoManager.register "marking/unmarking #{threads.size.pluralize 'thread'} as spam", + undos, lambda { regen_text } regen_text end @@ -367,10 +430,9 @@ EOS ## see comment for multi_toggle_spam def multi_toggle_deleted threads - threads.each do |t| - actually_toggle_deleted t - hide_thread t - end + undos = threads.map { |t| actually_toggle_deleted t } + UndoManager.register "deleting/undeleting #{threads.size.pluralize 'thread'}", + undos, lambda { regen_text } regen_text end @@ -379,13 +441,23 @@ EOS multi_kill [t] end + ## m-m-m-m-MULTI-KILL 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 + 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." + BufferManager.flash "#{threads.size.pluralize 'thread'} killed." end def save background=true @@ -405,7 +477,7 @@ EOS BufferManager.say("Saving threads...") do |say_id| dirty_threads.each_with_index do |t, i| BufferManager.say "Saving modified thread #{i + 1} of #{dirty_threads.length}...", say_id - t.save Index + t.save_state Index end end end @@ -454,14 +526,25 @@ EOS 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 = keepl + 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 + end + UpdateManager.relay self, :labeled, thread.first end @@ -471,32 +554,47 @@ EOS 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 } - if hl.empty? - threads.each do |t| - user_labels.each do |(l, to_remove)| - if to_remove - t.remove_label l - else - t.apply_label l - end + 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 - user_labels.each { |(l,_)| LabelManager << l } - else - BufferManager.flash "'#{hl}' is a reserved label!" + 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 + end + regen_text + end end - def reply + def reply type_arg=nil t = cursor_thread or return m = t.latest_message return if m.nil? # probably won't happen m.load_from_source! - mode = ReplyMode.new m + mode = ReplyMode.new m, type_arg BufferManager.spawn "Reply to #{m.subj}", mode end + def reply_all; reply :all; end + def forward t = cursor_thread or return m = t.latest_message @@ -531,6 +629,7 @@ EOS 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 } } @@ -663,10 +762,12 @@ protected def authors; map { |m, *o| m.from if m }.compact.uniq; end - def author_names_and_newness_for_thread t + def author_names_and_newness_for_thread t, limit=nil new = {} - authors = t.map do |m, *o| + authors = Set.new + t.each do |m, *o| next unless m + break if limit and authors.size >= limit name = if AccountManager.is_account?(m.from) @@ -678,22 +779,23 @@ protected end new[name] ||= m.has_label?(:unread) - name + authors << name end - authors.compact.uniq.map { |a| [a, new[a]] } + authors.to_a.map { |a| [a, new[a]] } end + AUTHOR_LIMIT = 5 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) + starred = t.has_label? :starred ## format the from column cur_width = 0 - ann = author_names_and_newness_for_thread t + 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 @@ -749,10 +851,11 @@ protected [subj_color, size_widget_text], [:to_me_color, t.labels.member?(:attachment) ? "@" : " "], [:to_me_color, dp ? ">" : (p ? '+' : " ")], - [subj_color, t.subj + (t.subj.empty? ? "" : " ")], ] + - (t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } + - [[:snippet_color, snippet] + (t.labels - @hidden_labels).map { |label| [:label_color, "#{label} "] } + + [ + [subj_color, t.subj + (t.subj.empty? ? "" : " ")], + [:snippet_color, snippet], ] end