+require 'thread'
module Redwood
+## subclasses should implement load_threads
+
class ThreadIndexMode < LineCursorMode
DATE_WIDTH = Time::TO_NICE_S_MAX_LEN
FROM_WIDTH = 15
LOAD_MORE_THREAD_NUM = 20
register_keymap do |k|
+ k.add :load_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
+ k.add :reload, "Discard threads and reload", 'D'
k.add :toggle_archived, "Toggle archived status", 'a'
k.add :toggle_starred, "Star or unstar all messages in thread", '*'
k.add :toggle_new, "Toggle new/read status of all messages in thread", 'N'
k.add :edit_labels, "Edit or add labels for a thread", 'l'
k.add :edit_message, "Edit message (drafts only)", 'e'
k.add :mark_as_spam, "Mark thread as spam", 'S'
- k.add :kill, "Kill thread (never to be seen in inbox again)", 'K'
+ k.add :delete, "Mark thread for deletion", 'd'
+ k.add :kill, "Kill thread (never to be seen in inbox again)", '&'
k.add :save, "Save changes now", '$'
k.add :jump_to_next_new, "Jump to next new thread", :tab
k.add :reply, "Reply to a thread", 'r'
def lines; @text.length; end
def [] i; @text[i]; end
+ def contains_thread? t; !@lines[t].nil?; end
+
+ def reload
+ drop_all_threads
+ BufferManager.draw_screen
+ load_threads :num => buffer.content_height
+ end
## open up a thread view window
- def select
- this_curpos = curpos
- t = @threads[this_curpos]
+ def select t=nil
+ t ||= @threads[curpos]
+
+ ## this isn't working entirely. TODO:figure out why
+ # t = t.clone # required so that messages added later on don't completely
+ # screw everything up
## TODO: don't regen text completely
Redwood::reporting_thread do
+ BufferManager.say("Loading message bodies...") do |sid|
+ t.each { |m, *o| m.load_from_source! if m }
+ end
mode = ThreadViewMode.new t, @hidden_labels
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
end
end
+
+ def multi_select threads
+ threads.each { |t| select t }
+ end
- def handle_starred_update m
+ def handle_starred_update sender, m
return unless(t = @ts.thread_for m)
- @starred_cache[t] = t.has_label? :starred
update_text_for_line @lines[t]
+ BufferManager.draw_screen
end
- def handle_read_update m
- return unless(t = @ts.thread_for m)
- @new_cache[t] = false
+ def handle_read_update sender, t
+ return unless @lines[t]
update_text_for_line @lines[t]
+ BufferManager.draw_screen
end
+ def handle_archived_update *a; handle_read_update *a; end
+
## overwrite me!
def is_relevant? m; false; end
- def handle_add_update m
+ def handle_add_update sender, m
if is_relevant?(m) || @ts.is_relevant?(m)
@ts.load_thread_for_message m
- @new_cache.delete @ts.thread_for(m) # force recalculation of newness
update
+ BufferManager.draw_screen
end
end
- def handle_delete_update mid
+ def handle_delete_update sender, mid
if @ts.contains_id? mid
@ts.remove mid
update
+ BufferManager.draw_screen
end
end
def update
## let's see you do THIS in python
- @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
+ @threads = @ts.threads.select { |t| !@hidden_threads[t] && !t.has_label?(:killed) }.sort_by { |t| t.date }.reverse
@size_width = (@threads.map { |t| t.size }.max || 0).num_digits
regen_text
end
def edit_message
- t = @threads[curpos] or return
+ return unless(t = @threads[curpos])
message, *crap = t.find { |m, *o| m.has_label? :draft }
if message
mode = ResumeMode.new message
end
end
- def toggle_starred
+ def actually_toggle_starred t
+ if t.has_label? :starred # if ANY message has a star
+ t.remove_label :starred # remove from all
+ else
+ t.first.add_label :starred # add only to first
+ end
+ end
+
+ def toggle_starred
t = @threads[curpos] or return
- @starred_cache[t] = t.toggle_label :starred
+ actually_toggle_starred t
update_text_for_line curpos
cursor_down
end
def multi_toggle_starred threads
- threads.each { |t| @starred_cache[t] = t.toggle_label :starred }
+ threads.each { |t| actually_toggle_starred t }
regen_text
end
- def toggle_archived
- return unless(t = @threads[curpos])
- t.toggle_label :inbox
+ def actually_toggle_archived t
+ if t.has_label? :inbox
+ t.remove_label :inbox
+ UpdateManager.relay self, :unarchived, t
+ else
+ t.add_label :inbox
+ UpdateManager.relay self, :archived, t
+ end
+ end
+
+ def toggle_archived
+ t = @threads[curpos] or return
+ actually_toggle_archived t
update_text_for_line curpos
- cursor_down
end
def multi_toggle_archived threads
- threads.each { |t| t.toggle_label :inbox }
+ threads.each { |t| actually_toggle_archived t }
regen_text
end
def toggle_new
t = @threads[curpos] or return
- @new_cache[t] = t.toggle_label :unread
+ t.toggle_label :unread
update_text_for_line curpos
cursor_down
end
def multi_toggle_new threads
- threads.each { |t| @new_cache[t] = t.toggle_label :unread }
+ threads.each { |t| t.toggle_label :unread }
regen_text
end
end
def jump_to_next_new
- t = @threads[curpos] or return
- n = ((curpos + 1) .. lines).find { |i| @new_cache[@threads[i]] }
- n = (0 ... curpos).find { |i| @new_cache[@threads[i]] } unless n
+ n = ((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread }
+ n = (0 ... curpos).find { |i| @threads[i].has_label? :unread } unless n
if n
set_cursor_pos n
else
def multi_mark_as_spam threads
threads.each do |t|
- t.apply_label :spam
+ t.toggle_label :spam
+ hide_thread t
+ end
+ regen_text
+ end
+
+ def delete
+ t = @threads[curpos] or return
+ multi_delete [t]
+ end
+
+ def multi_delete threads
+ threads.each do |t|
+ t.toggle_label :deleted
hide_thread t
end
regen_text
def save
threads = @threads + @hidden_threads.keys
- mbid = BufferManager.say "Saving threads..."
- threads.each_with_index do |t, i|
- if i % 5 == 0
- BufferManager.say "Saving thread #{i + 1} of #{threads.length}...",
- mbid
+
+ BufferManager.say("Saving threads...") do |say_id|
+ threads.each_with_index do |t, i|
+ next unless t.dirty?
+ BufferManager.say "Saving thread #{i +1} of #{threads.length}...", say_id
+ t.save Index
end
- t.save Index
end
- BufferManager.clear mbid
end
def cleanup
speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq
keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
label_string = modifyl.join(" ")
+ label_string += " " unless label_string.empty?
answer = BufferManager.ask :edit_labels, "edit labels: ", label_string
return unless answer
t = @threads[curpos] or return
m = t.latest_message
return if m.nil? # probably won't happen
+ m.load_from_source!
mode = ReplyMode.new m
BufferManager.spawn "Reply to #{m.subj}", mode
end
t = @threads[curpos] or return
m = t.latest_message
return if m.nil? # probably won't happen
+ m.load_from_source!
mode = ForwardMode.new m
BufferManager.spawn "Forward of #{m.subj}", mode
mode.edit
def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
@mbid = BufferManager.say "Searching for threads..."
orig_size = @ts.size
+ last_update = Time.now - 9999 # oh yeah
@ts.load_n_threads(@ts.size + n, opts) do |i|
BufferManager.say "Loaded #{i} threads...", @mbid
- if i % 5 == 0
+ if (Time.now - last_update) >= 0.25
update
BufferManager.draw_screen
+ last_update = Time.now
end
end
@ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }
end
def status
- "line #{curpos + 1} of #{lines} #{dirty? ? '*modified*' : ''}"
+ if (l = lines) == 0
+ ""
+ else
+ "line #{curpos + 1} of #{l} #{dirty? ? '*modified*' : ''}"
+ end
end
protected
update
end
- def remove_label_and_hide_thread t, label
- t.remove_label label
- hide_thread t
- end
-
def hide_thread t
raise "already hidden" if @hidden_threads[t]
@hidden_threads[t] = true
end
def text_for_thread t
- date = (@date_cache[t] ||= t.date.to_nice_s(Time.now))
- from = (@who_cache[t] ||= author_text_for_thread(t))
+ date = t.date.to_nice_s(Time.now)
+ from = author_text_for_thread(t)
if from.length > @from_width
from = from[0 ... (@from_width - 1)]
from += "." unless from[-1] == ?\s
end
- new = @new_cache.member?(t) ? @new_cache[t] : @new_cache[t] = t.has_label?(:unread)
- starred = @starred_cache.member?(t) ? @starred_cache[t] : @starred_cache[t] = t.has_label?(:starred)
+ new = t.has_label?(:unread)
+ starred = t.has_label?(:starred)
- dp = (@dp_cache[t] ||= t.direct_participants.any? { |p| AccountManager.is_account? p })
- p = (@p_cache[t] ||= (dp || t.participants.any? { |p| AccountManager.is_account? p }))
+ dp = t.direct_participants.any? { |p| AccountManager.is_account? p }
+ p = dp || t.participants.any? { |p| AccountManager.is_account? p }
base_color = (new ? :index_new_color : :index_old_color)
[
def initialize_threads
@ts = ThreadSet.new Index.instance
- @date_cache = {}
- @who_cache = {}
- @dp_cache = {}
- @p_cache = {}
- @new_cache = {}
- @starred_cache = {}
+ @ts_mutex = Mutex.new
@hidden_threads = {}
end
end