sup 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/HACKING +50 -0
- data/History.txt +12 -0
- data/Manifest.txt +2 -0
- data/Rakefile +5 -1
- data/bin/sup +24 -8
- data/bin/sup-add +106 -0
- data/bin/sup-import +85 -165
- data/doc/FAQ.txt +48 -11
- data/doc/TODO +45 -12
- data/doc/UserGuide.txt +54 -42
- data/lib/sup.rb +6 -2
- data/lib/sup/buffer.rb +20 -4
- data/lib/sup/contact.rb +22 -12
- data/lib/sup/draft.rb +27 -7
- data/lib/sup/imap.rb +107 -97
- data/lib/sup/index.rb +35 -25
- data/lib/sup/label.rb +2 -2
- data/lib/sup/mbox.rb +20 -15
- data/lib/sup/mbox/loader.rb +0 -1
- data/lib/sup/mbox/ssh-file.rb +58 -47
- data/lib/sup/mbox/ssh-loader.rb +13 -20
- data/lib/sup/message.rb +15 -9
- data/lib/sup/mode.rb +16 -6
- data/lib/sup/modes/compose-mode.rb +7 -3
- data/lib/sup/modes/contact-list-mode.rb +50 -25
- data/lib/sup/modes/edit-message-mode.rb +11 -8
- data/lib/sup/modes/inbox-mode.rb +19 -3
- data/lib/sup/modes/label-list-mode.rb +4 -12
- data/lib/sup/modes/log-mode.rb +6 -0
- data/lib/sup/modes/reply-mode.rb +19 -6
- data/lib/sup/modes/resume-mode.rb +13 -9
- data/lib/sup/modes/scroll-mode.rb +13 -2
- data/lib/sup/modes/thread-index-mode.rb +94 -43
- data/lib/sup/modes/thread-view-mode.rb +198 -130
- data/lib/sup/person.rb +1 -1
- data/lib/sup/poll.rb +60 -47
- data/lib/sup/sent.rb +3 -2
- data/lib/sup/source.rb +14 -6
- data/lib/sup/textfield.rb +1 -0
- data/lib/sup/thread.rb +76 -23
- data/lib/sup/update.rb +2 -3
- data/lib/sup/util.rb +13 -13
- metadata +14 -2
@@ -9,6 +9,7 @@ class EditMessageMode < LineCursorMode
|
|
9
9
|
NON_EDITABLE_HEADERS = %w(Message-Id Date)
|
10
10
|
|
11
11
|
attr_reader :status
|
12
|
+
bool_reader :edited
|
12
13
|
|
13
14
|
register_keymap do |k|
|
14
15
|
k.add :send_message, "Send message", 'y'
|
@@ -23,7 +24,7 @@ class EditMessageMode < LineCursorMode
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def edit
|
26
|
-
@file = Tempfile.new "
|
27
|
+
@file = Tempfile.new "sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}"
|
27
28
|
@file.puts header_lines(header - NON_EDITABLE_HEADERS)
|
28
29
|
@file.puts
|
29
30
|
@file.puts body
|
@@ -41,10 +42,14 @@ class EditMessageMode < LineCursorMode
|
|
41
42
|
update
|
42
43
|
end
|
43
44
|
|
45
|
+
def killable?
|
46
|
+
!edited? || BufferManager.ask_yes_or_no("Discard message?")
|
47
|
+
end
|
48
|
+
|
44
49
|
protected
|
45
50
|
|
46
51
|
def gen_message_id
|
47
|
-
"<#{Time.now.to_i}-
|
52
|
+
"<#{Time.now.to_i}-sup-#{rand 10000}@#{Socket.gethostname}>"
|
48
53
|
end
|
49
54
|
|
50
55
|
def update
|
@@ -55,13 +60,13 @@ protected
|
|
55
60
|
def parse_file fn
|
56
61
|
File.open(fn) do |f|
|
57
62
|
header = MBox::read_header f
|
58
|
-
body =
|
63
|
+
body = f.readlines
|
59
64
|
|
60
65
|
header.delete_if { |k, v| NON_EDITABLE_HEADERS.member? k }
|
61
66
|
header.each do |k, v|
|
62
67
|
next unless MULTI_HEADERS.include?(k) && !v.empty?
|
63
68
|
header[k] = v.split_on_commas.map do |name|
|
64
|
-
(p = ContactManager.
|
69
|
+
(p = ContactManager.person_with(name)) && p.full_address || name
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
@@ -101,7 +106,7 @@ protected
|
|
101
106
|
end
|
102
107
|
|
103
108
|
def send_message
|
104
|
-
return
|
109
|
+
return unless edited? || BufferManager.ask_yes_or_no("Message unedited. Really send?")
|
105
110
|
|
106
111
|
raise "no message id!" unless header["Message-Id"]
|
107
112
|
date = Time.now
|
@@ -114,20 +119,18 @@ protected
|
|
114
119
|
|
115
120
|
acct = AccountManager.account_for(from_email) || AccountManager.default_account
|
116
121
|
SentManager.write_sent_message(date, from_email) { |f| write_message f, true, date }
|
117
|
-
BufferManager.flash "
|
122
|
+
BufferManager.flash "Sending..."
|
118
123
|
|
119
124
|
IO.popen(acct.sendmail, "w") { |p| write_message p, true, date }
|
120
125
|
|
121
126
|
BufferManager.kill_buffer buffer
|
122
127
|
BufferManager.flash "Message sent!"
|
123
|
-
true
|
124
128
|
end
|
125
129
|
|
126
130
|
def save_as_draft
|
127
131
|
DraftManager.write_draft { |f| write_message f, false }
|
128
132
|
BufferManager.kill_buffer buffer
|
129
133
|
BufferManager.flash "Saved for later editing."
|
130
|
-
true
|
131
134
|
end
|
132
135
|
|
133
136
|
def sig_lines
|
data/lib/sup/modes/inbox-mode.rb
CHANGED
@@ -15,7 +15,8 @@ class InboxMode < ThreadIndexMode
|
|
15
15
|
def killable?; false; end
|
16
16
|
|
17
17
|
def archive
|
18
|
-
|
18
|
+
cursor_thread.remove_label :inbox
|
19
|
+
hide_thread cursor_thread
|
19
20
|
regen_text
|
20
21
|
end
|
21
22
|
|
@@ -24,13 +25,28 @@ class InboxMode < ThreadIndexMode
|
|
24
25
|
regen_text
|
25
26
|
end
|
26
27
|
|
28
|
+
def handle_archived_update sender, t
|
29
|
+
if contains_thread? t
|
30
|
+
hide_thread t
|
31
|
+
regen_text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# not quite working, and not sure if i like it anyways
|
36
|
+
# def handle_unarchived_update sender, t
|
37
|
+
# Redwood::log "unarchived #{t.subj}"
|
38
|
+
# show_thread t
|
39
|
+
# end
|
40
|
+
|
41
|
+
def status
|
42
|
+
super + " #{Index.size} messages in index"
|
43
|
+
end
|
44
|
+
|
27
45
|
def is_relevant? m; m.has_label? :inbox; end
|
28
46
|
|
29
47
|
def load_threads opts={}
|
30
48
|
n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
31
49
|
load_n_threads_background n, :label => :inbox,
|
32
|
-
:load_killed => false,
|
33
|
-
:load_spam => false,
|
34
50
|
:when_done => (lambda do |num|
|
35
51
|
opts[:when_done].call if opts[:when_done]
|
36
52
|
BufferManager.flash "Added #{num} threads."
|
@@ -15,18 +15,9 @@ class LabelListMode < LineCursorMode
|
|
15
15
|
def lines; @text.length; end
|
16
16
|
def [] i; @text[i]; end
|
17
17
|
|
18
|
-
def load; regen_text; end
|
19
|
-
|
20
18
|
def load_in_background
|
21
19
|
Redwood::reporting_thread do
|
22
|
-
regen_text
|
23
|
-
if i % 10 == 0
|
24
|
-
buffer.mark_dirty
|
25
|
-
BufferManager.draw_screen
|
26
|
-
sleep 0.1 # ok, dirty trick.
|
27
|
-
end
|
28
|
-
end
|
29
|
-
buffer.mark_dirty
|
20
|
+
BufferManager.say("Counting labels...") { regen_text }
|
30
21
|
BufferManager.draw_screen
|
31
22
|
end
|
32
23
|
end
|
@@ -41,8 +32,7 @@ protected
|
|
41
32
|
|
42
33
|
def regen_text
|
43
34
|
@text = []
|
44
|
-
@labels = LabelManager::LISTABLE_LABELS.sort_by { |t| t.to_s }
|
45
|
-
LabelManager.user_labels.sort_by { |t| t.to_s }
|
35
|
+
@labels = (LabelManager::LISTABLE_LABELS + LabelManager.user_labels).sort_by { |t| t.to_s }
|
46
36
|
|
47
37
|
counts = @labels.map do |t|
|
48
38
|
total = Index.num_results_for :label => t
|
@@ -71,6 +61,8 @@ protected
|
|
71
61
|
sprintf("%#{width + 1}s %5d %s, %5d unread", label, total, total == 1 ? " message" : "messages", unread)]]
|
72
62
|
yield i if block_given?
|
73
63
|
end.compact
|
64
|
+
|
65
|
+
buffer.mark_dirty
|
74
66
|
end
|
75
67
|
|
76
68
|
def view_results
|
data/lib/sup/modes/log-mode.rb
CHANGED
@@ -3,6 +3,7 @@ module Redwood
|
|
3
3
|
class LogMode < TextMode
|
4
4
|
register_keymap do |k|
|
5
5
|
k.add :toggle_follow, "Toggle follow mode", 'f'
|
6
|
+
k.add :save_to_disk, "Save log to disk", 's'
|
6
7
|
end
|
7
8
|
|
8
9
|
def initialize
|
@@ -36,6 +37,11 @@ class LogMode < TextMode
|
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
40
|
+
def save_to_disk
|
41
|
+
fn = BufferManager.ask :filename, "Save log to file: "
|
42
|
+
save_to_file(fn) { |f| f.puts text } if fn
|
43
|
+
end
|
44
|
+
|
39
45
|
def status
|
40
46
|
super + " (follow: #@follow)"
|
41
47
|
end
|
data/lib/sup/modes/reply-mode.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class ReplyMode < EditMessageMode
|
4
|
-
REPLY_TYPES = [:sender, :list, :all, :user]
|
4
|
+
REPLY_TYPES = [:sender, :recipient, :list, :all, :user]
|
5
5
|
TYPE_DESCRIPTIONS = {
|
6
6
|
:sender => "Reply to sender",
|
7
|
+
:recipient => "Reply to recipient",
|
7
8
|
:all => "Reply to all",
|
8
9
|
:list => "Reply to mailing list",
|
9
10
|
:user => "Customized reply"
|
@@ -30,7 +31,8 @@ class ReplyMode < EditMessageMode
|
|
30
31
|
(@m.to + @m.cc).find { |p| AccountManager.is_account? p }
|
31
32
|
end || AccountManager.default_account
|
32
33
|
|
33
|
-
from_email = @m.recipient_email || from.email
|
34
|
+
#from_email = @m.recipient_email || from.email
|
35
|
+
from_email = from.email
|
34
36
|
|
35
37
|
## ignore reply-to for list messages because it's typically set to
|
36
38
|
## the list address anyways
|
@@ -41,7 +43,12 @@ class ReplyMode < EditMessageMode
|
|
41
43
|
@headers[:sender] = {
|
42
44
|
"From" => "#{from.name} <#{from_email}>",
|
43
45
|
"To" => [to.full_address],
|
44
|
-
}
|
46
|
+
} unless AccountManager.is_account? to
|
47
|
+
|
48
|
+
@headers[:recipient] = {
|
49
|
+
"From" => "#{from.name} <#{from_email}>",
|
50
|
+
"To" => cc.map { |p| p.full_address },
|
51
|
+
} unless cc.empty? || @m.is_list_message?
|
45
52
|
|
46
53
|
@headers[:user] = {
|
47
54
|
"From" => "#{from.name} <#{from_email}>",
|
@@ -73,7 +80,14 @@ class ReplyMode < EditMessageMode
|
|
73
80
|
end
|
74
81
|
|
75
82
|
@type_labels = REPLY_TYPES.select { |t| @headers.member?(t) }
|
76
|
-
@selected_type =
|
83
|
+
@selected_type =
|
84
|
+
if @m.is_list_message?
|
85
|
+
:list
|
86
|
+
elsif @headers.member? :sender
|
87
|
+
:sender
|
88
|
+
else
|
89
|
+
:recipient
|
90
|
+
end
|
77
91
|
|
78
92
|
@body += sig_lines
|
79
93
|
regen_text
|
@@ -114,14 +128,13 @@ protected
|
|
114
128
|
|
115
129
|
if new_header.size != header.size ||
|
116
130
|
header.any? { |k, v| new_header[k] != v }
|
117
|
-
#raise "nhs: #{new_header.size} hs: #{header.size} new: #{new_header.inspect} old: #{header.inspect}"
|
118
131
|
@selected_type = :user
|
119
132
|
@headers[:user] = new_header
|
120
133
|
end
|
121
134
|
end
|
122
135
|
|
123
136
|
def regen_text
|
124
|
-
@text = header_lines(
|
137
|
+
@text = header_lines(header - NON_EDITABLE_HEADERS) + [""] + body
|
125
138
|
end
|
126
139
|
|
127
140
|
def gen_references
|
@@ -12,18 +12,22 @@ class ResumeMode < ComposeMode
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def killable?
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
return true if @safe
|
16
|
+
|
17
|
+
case BufferManager.ask_yes_or_no "Discard draft?"
|
18
|
+
when true
|
19
|
+
DraftManager.discard @id
|
20
|
+
BufferManager.flash "Draft discarded."
|
21
|
+
true
|
22
|
+
when false
|
23
|
+
if edited?
|
24
|
+
DraftManager.write_draft { |f| write_message f, false }
|
18
25
|
DraftManager.discard @id
|
19
|
-
BufferManager.flash "Draft discarded."
|
20
|
-
true
|
21
|
-
when false
|
22
26
|
BufferManager.flash "Draft saved."
|
23
|
-
true
|
24
|
-
else
|
25
|
-
false
|
26
27
|
end
|
28
|
+
true
|
29
|
+
else
|
30
|
+
false
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
@@ -1,7 +1,16 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class ScrollMode < Mode
|
4
|
-
|
4
|
+
## we define topline and botline as the top and bottom lines of any
|
5
|
+
## content in the currentview.
|
6
|
+
|
7
|
+
## we left leftcol and rightcol as the left and right columns of any
|
8
|
+
## content in the current view. but since we're operating in a
|
9
|
+
## line-centric fashion, rightcol is always leftcol + the buffer
|
10
|
+
## width. (whereas botline is topline + at most the buffer height,
|
11
|
+
## and can be == to topline in the case that there's no content.)
|
12
|
+
|
13
|
+
attr_reader :status, :topline, :botline, :leftcol
|
5
14
|
|
6
15
|
COL_JUMP = 2
|
7
16
|
|
@@ -25,6 +34,8 @@ class ScrollMode < Mode
|
|
25
34
|
super()
|
26
35
|
end
|
27
36
|
|
37
|
+
def rightcol; @leftcol + buffer.content_width; end
|
38
|
+
|
28
39
|
def draw
|
29
40
|
ensure_mode_validity
|
30
41
|
(@topline ... @botline).each { |ln| draw_line ln }
|
@@ -80,7 +91,7 @@ class ScrollMode < Mode
|
|
80
91
|
end
|
81
92
|
|
82
93
|
def resize *a
|
83
|
-
super
|
94
|
+
super(*a)
|
84
95
|
ensure_mode_validity
|
85
96
|
end
|
86
97
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'thread'
|
1
2
|
module Redwood
|
2
3
|
|
3
4
|
## subclasses should implement load_threads
|
@@ -16,6 +17,7 @@ class ThreadIndexMode < LineCursorMode
|
|
16
17
|
k.add :edit_labels, "Edit or add labels for a thread", 'l'
|
17
18
|
k.add :edit_message, "Edit message (drafts only)", 'e'
|
18
19
|
k.add :mark_as_spam, "Mark thread as spam", 'S'
|
20
|
+
k.add :delete, "Mark thread for deletion", 'd'
|
19
21
|
k.add :kill, "Kill thread (never to be seen in inbox again)", '&'
|
20
22
|
k.add :save, "Save changes now", '$'
|
21
23
|
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
@@ -44,6 +46,7 @@ class ThreadIndexMode < LineCursorMode
|
|
44
46
|
|
45
47
|
def lines; @text.length; end
|
46
48
|
def [] i; @text[i]; end
|
49
|
+
def contains_thread? t; !@lines[t].nil?; end
|
47
50
|
|
48
51
|
def reload
|
49
52
|
drop_all_threads
|
@@ -55,11 +58,22 @@ class ThreadIndexMode < LineCursorMode
|
|
55
58
|
def select t=nil
|
56
59
|
t ||= @threads[curpos]
|
57
60
|
|
61
|
+
## this isn't working entirely. TODO:figure out why
|
62
|
+
# t = t.clone # required so that messages added later on don't completely
|
63
|
+
# screw everything up
|
64
|
+
|
58
65
|
## TODO: don't regen text completely
|
59
66
|
Redwood::reporting_thread do
|
67
|
+
BufferManager.say("Loading message bodies...") do |sid|
|
68
|
+
t.each { |m, *o| m.load_from_source! if m }
|
69
|
+
end
|
60
70
|
mode = ThreadViewMode.new t, @hidden_labels
|
61
71
|
BufferManager.spawn t.subj, mode
|
62
72
|
BufferManager.draw_screen
|
73
|
+
mode.jump_to_first_open
|
74
|
+
BufferManager.draw_screen # lame TODO: make this unnecessary
|
75
|
+
## the first draw_screen is needed before topline and botline
|
76
|
+
## are set, and the second to show the cursor having moved
|
63
77
|
end
|
64
78
|
end
|
65
79
|
|
@@ -67,45 +81,49 @@ class ThreadIndexMode < LineCursorMode
|
|
67
81
|
threads.each { |t| select t }
|
68
82
|
end
|
69
83
|
|
70
|
-
def handle_starred_update m
|
71
|
-
|
72
|
-
@
|
73
|
-
update_text_for_line
|
84
|
+
def handle_starred_update sender, m
|
85
|
+
t = @ts.thread_for(m) or return
|
86
|
+
l = @lines[t] or return
|
87
|
+
update_text_for_line l
|
88
|
+
BufferManager.draw_screen
|
74
89
|
end
|
75
90
|
|
76
|
-
def handle_read_update
|
77
|
-
|
78
|
-
@new_cache[t] = false
|
91
|
+
def handle_read_update sender, t
|
92
|
+
l = @lines[t] or return
|
79
93
|
update_text_for_line @lines[t]
|
94
|
+
BufferManager.draw_screen
|
80
95
|
end
|
81
96
|
|
97
|
+
def handle_archived_update *a; handle_read_update(*a); end
|
98
|
+
|
82
99
|
## overwrite me!
|
83
100
|
def is_relevant? m; false; end
|
84
101
|
|
85
|
-
def handle_add_update m
|
102
|
+
def handle_add_update sender, m
|
86
103
|
if is_relevant?(m) || @ts.is_relevant?(m)
|
87
104
|
@ts.load_thread_for_message m
|
88
|
-
@new_cache.delete @ts.thread_for(m) # force recalculation of newness
|
89
105
|
update
|
106
|
+
BufferManager.draw_screen
|
90
107
|
end
|
91
108
|
end
|
92
109
|
|
93
|
-
def handle_delete_update mid
|
110
|
+
def handle_delete_update sender, mid
|
94
111
|
if @ts.contains_id? mid
|
95
112
|
@ts.remove mid
|
96
113
|
update
|
114
|
+
BufferManager.draw_screen
|
97
115
|
end
|
98
116
|
end
|
99
117
|
|
100
118
|
def update
|
101
119
|
## let's see you do THIS in python
|
102
|
-
@threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
|
120
|
+
@threads = @ts.threads.select { |t| !@hidden_threads[t] && !t.has_label?(:killed) }.sort_by { |t| t.date }.reverse
|
103
121
|
@size_width = (@threads.map { |t| t.size }.max || 0).num_digits
|
104
122
|
regen_text
|
105
123
|
end
|
106
124
|
|
107
125
|
def edit_message
|
108
|
-
t = @threads[curpos]
|
126
|
+
return unless(t = @threads[curpos])
|
109
127
|
message, *crap = t.find { |m, *o| m.has_label? :draft }
|
110
128
|
if message
|
111
129
|
mode = ResumeMode.new message
|
@@ -115,39 +133,56 @@ class ThreadIndexMode < LineCursorMode
|
|
115
133
|
end
|
116
134
|
end
|
117
135
|
|
118
|
-
def
|
136
|
+
def actually_toggle_starred t
|
137
|
+
if t.has_label? :starred # if ANY message has a star
|
138
|
+
t.remove_label :starred # remove from all
|
139
|
+
else
|
140
|
+
t.first.add_label :starred # add only to first
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def toggle_starred
|
119
145
|
t = @threads[curpos] or return
|
120
|
-
|
146
|
+
actually_toggle_starred t
|
121
147
|
update_text_for_line curpos
|
122
148
|
cursor_down
|
123
149
|
end
|
124
150
|
|
125
151
|
def multi_toggle_starred threads
|
126
|
-
threads.each { |t|
|
152
|
+
threads.each { |t| actually_toggle_starred t }
|
127
153
|
regen_text
|
128
154
|
end
|
129
155
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
156
|
+
def actually_toggle_archived t
|
157
|
+
if t.has_label? :inbox
|
158
|
+
t.remove_label :inbox
|
159
|
+
UpdateManager.relay self, :archived, t
|
160
|
+
else
|
161
|
+
t.apply_label :inbox
|
162
|
+
UpdateManager.relay self, :unarchived, t
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def toggle_archived
|
167
|
+
t = @threads[curpos] or return
|
168
|
+
actually_toggle_archived t
|
133
169
|
update_text_for_line curpos
|
134
|
-
cursor_down
|
135
170
|
end
|
136
171
|
|
137
172
|
def multi_toggle_archived threads
|
138
|
-
threads.each { |t| t
|
173
|
+
threads.each { |t| actually_toggle_archived t }
|
139
174
|
regen_text
|
140
175
|
end
|
141
176
|
|
142
177
|
def toggle_new
|
143
178
|
t = @threads[curpos] or return
|
144
|
-
|
179
|
+
t.toggle_label :unread
|
145
180
|
update_text_for_line curpos
|
146
181
|
cursor_down
|
147
182
|
end
|
148
183
|
|
149
184
|
def multi_toggle_new threads
|
150
|
-
threads.each { |t|
|
185
|
+
threads.each { |t| t.toggle_label :unread }
|
151
186
|
regen_text
|
152
187
|
end
|
153
188
|
|
@@ -157,9 +192,8 @@ class ThreadIndexMode < LineCursorMode
|
|
157
192
|
end
|
158
193
|
|
159
194
|
def jump_to_next_new
|
160
|
-
|
161
|
-
n = (
|
162
|
-
n = (0 ... curpos).find { |i| @new_cache[@threads[i]] } unless n
|
195
|
+
n = ((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread }
|
196
|
+
n = (0 ... curpos).find { |i| @threads[i].has_label? :unread } unless n
|
163
197
|
if n
|
164
198
|
set_cursor_pos n
|
165
199
|
else
|
@@ -180,6 +214,19 @@ class ThreadIndexMode < LineCursorMode
|
|
180
214
|
regen_text
|
181
215
|
end
|
182
216
|
|
217
|
+
def delete
|
218
|
+
t = @threads[curpos] or return
|
219
|
+
multi_delete [t]
|
220
|
+
end
|
221
|
+
|
222
|
+
def multi_delete threads
|
223
|
+
threads.each do |t|
|
224
|
+
t.toggle_label :deleted
|
225
|
+
hide_thread t
|
226
|
+
end
|
227
|
+
regen_text
|
228
|
+
end
|
229
|
+
|
183
230
|
def kill
|
184
231
|
t = @threads[curpos] or return
|
185
232
|
multi_kill [t]
|
@@ -232,6 +279,7 @@ class ThreadIndexMode < LineCursorMode
|
|
232
279
|
speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq
|
233
280
|
keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
|
234
281
|
label_string = modifyl.join(" ")
|
282
|
+
label_string += " " unless label_string.empty?
|
235
283
|
|
236
284
|
answer = BufferManager.ask :edit_labels, "edit labels: ", label_string
|
237
285
|
return unless answer
|
@@ -266,6 +314,7 @@ class ThreadIndexMode < LineCursorMode
|
|
266
314
|
t = @threads[curpos] or return
|
267
315
|
m = t.latest_message
|
268
316
|
return if m.nil? # probably won't happen
|
317
|
+
m.load_from_source!
|
269
318
|
mode = ReplyMode.new m
|
270
319
|
BufferManager.spawn "Reply to #{m.subj}", mode
|
271
320
|
end
|
@@ -274,6 +323,7 @@ class ThreadIndexMode < LineCursorMode
|
|
274
323
|
t = @threads[curpos] or return
|
275
324
|
m = t.latest_message
|
276
325
|
return if m.nil? # probably won't happen
|
326
|
+
m.load_from_source!
|
277
327
|
mode = ForwardMode.new m
|
278
328
|
BufferManager.spawn "Forward of #{m.subj}", mode
|
279
329
|
mode.edit
|
@@ -291,11 +341,13 @@ class ThreadIndexMode < LineCursorMode
|
|
291
341
|
def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
|
292
342
|
@mbid = BufferManager.say "Searching for threads..."
|
293
343
|
orig_size = @ts.size
|
344
|
+
last_update = Time.now - 9999 # oh yeah
|
294
345
|
@ts.load_n_threads(@ts.size + n, opts) do |i|
|
295
346
|
BufferManager.say "Loaded #{i} threads...", @mbid
|
296
|
-
if
|
347
|
+
if (Time.now - last_update) >= 0.25
|
297
348
|
update
|
298
349
|
BufferManager.draw_screen
|
350
|
+
last_update = Time.now
|
299
351
|
end
|
300
352
|
end
|
301
353
|
@ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }
|
@@ -325,11 +377,6 @@ protected
|
|
325
377
|
update
|
326
378
|
end
|
327
379
|
|
328
|
-
def remove_label_and_hide_thread t, label
|
329
|
-
t.remove_label label
|
330
|
-
hide_thread t
|
331
|
-
end
|
332
|
-
|
333
380
|
def hide_thread t
|
334
381
|
raise "already hidden" if @hidden_threads[t]
|
335
382
|
@hidden_threads[t] = true
|
@@ -337,6 +384,15 @@ protected
|
|
337
384
|
@tags.drop_tag_for t
|
338
385
|
end
|
339
386
|
|
387
|
+
def show_thread t
|
388
|
+
if @hidden_threads[t]
|
389
|
+
@hidden_threads.delete t
|
390
|
+
else
|
391
|
+
@ts.add_thread t
|
392
|
+
end
|
393
|
+
update
|
394
|
+
end
|
395
|
+
|
340
396
|
def update_text_for_line l
|
341
397
|
return unless l # not sure why this happens, but it does, occasionally
|
342
398
|
@text[l] = text_for_thread @threads[l]
|
@@ -358,18 +414,18 @@ protected
|
|
358
414
|
end
|
359
415
|
|
360
416
|
def text_for_thread t
|
361
|
-
date =
|
362
|
-
from =
|
417
|
+
date = t.date.to_nice_s(Time.now)
|
418
|
+
from = author_text_for_thread(t)
|
363
419
|
if from.length > @from_width
|
364
420
|
from = from[0 ... (@from_width - 1)]
|
365
421
|
from += "." unless from[-1] == ?\s
|
366
422
|
end
|
367
423
|
|
368
|
-
new =
|
369
|
-
starred =
|
424
|
+
new = t.has_label?(:unread)
|
425
|
+
starred = t.has_label?(:starred)
|
370
426
|
|
371
|
-
dp =
|
372
|
-
p =
|
427
|
+
dp = t.direct_participants.any? { |p| AccountManager.is_account? p }
|
428
|
+
p = dp || t.participants.any? { |p| AccountManager.is_account? p }
|
373
429
|
|
374
430
|
base_color = (new ? :index_new_color : :index_old_color)
|
375
431
|
[
|
@@ -392,12 +448,7 @@ private
|
|
392
448
|
|
393
449
|
def initialize_threads
|
394
450
|
@ts = ThreadSet.new Index.instance
|
395
|
-
@
|
396
|
-
@who_cache = {}
|
397
|
-
@dp_cache = {}
|
398
|
-
@p_cache = {}
|
399
|
-
@new_cache = {}
|
400
|
-
@starred_cache = {}
|
451
|
+
@ts_mutex = Mutex.new
|
401
452
|
@hidden_threads = {}
|
402
453
|
end
|
403
454
|
end
|