sup 0.0.4 → 0.0.5
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/History.txt +5 -0
- data/Rakefile +1 -3
- data/bin/sup +8 -5
- data/bin/sup-import +10 -6
- data/doc/TODO +6 -2
- data/doc/UserGuide.txt +188 -30
- data/lib/sup.rb +2 -2
- data/lib/sup/buffer.rb +12 -5
- data/lib/sup/imap.rb +120 -83
- data/lib/sup/index.rb +2 -1
- data/lib/sup/mbox/loader.rb +1 -1
- data/lib/sup/mbox/ssh-file.rb +37 -21
- data/lib/sup/message.rb +14 -10
- data/lib/sup/modes/contact-list-mode.rb +6 -8
- data/lib/sup/modes/inbox-mode.rb +1 -9
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/label-search-results-mode.rb +1 -5
- data/lib/sup/modes/line-cursor-mode.rb +2 -1
- data/lib/sup/modes/person-search-results-mode.rb +1 -5
- data/lib/sup/modes/reply-mode.rb +7 -2
- data/lib/sup/modes/scroll-mode.rb +5 -3
- data/lib/sup/modes/search-results-mode.rb +1 -5
- data/lib/sup/modes/thread-index-mode.rb +29 -13
- data/lib/sup/modes/thread-view-mode.rb +40 -25
- data/lib/sup/person.rb +1 -1
- data/lib/sup/poll.rb +52 -44
- data/lib/sup/thread.rb +7 -2
- data/lib/sup/util.rb +5 -5
- metadata +2 -2
@@ -15,7 +15,7 @@ class ContactListMode < LineCursorMode
|
|
15
15
|
def initialize mode = :regular
|
16
16
|
@mode = mode
|
17
17
|
@tags = Tagger.new self
|
18
|
-
|
18
|
+
@num = 0
|
19
19
|
super()
|
20
20
|
end
|
21
21
|
|
@@ -37,10 +37,10 @@ class ContactListMode < LineCursorMode
|
|
37
37
|
def apply_to_tagged; @tags.apply_to_tagged; end
|
38
38
|
|
39
39
|
def load; regen_text; end
|
40
|
-
def load_more
|
41
|
-
@num +=
|
40
|
+
def load_more num=LOAD_MORE_CONTACTS_NUM
|
41
|
+
@num += num
|
42
42
|
regen_text
|
43
|
-
BufferManager.flash "
|
43
|
+
BufferManager.flash "Added #{num} contacts."
|
44
44
|
end
|
45
45
|
|
46
46
|
def multi_select people
|
@@ -60,7 +60,7 @@ class ContactListMode < LineCursorMode
|
|
60
60
|
def multi_search people
|
61
61
|
mode = PersonSearchResultsMode.new people
|
62
62
|
BufferManager.spawn "personal search results", mode
|
63
|
-
mode.
|
63
|
+
mode.load_threads :num => mode.buffer.content_height
|
64
64
|
end
|
65
65
|
|
66
66
|
def search
|
@@ -70,7 +70,6 @@ class ContactListMode < LineCursorMode
|
|
70
70
|
|
71
71
|
def reload
|
72
72
|
@tags.drop_all_tags
|
73
|
-
@num = LOAD_MORE_CONTACTS_NUM
|
74
73
|
load
|
75
74
|
end
|
76
75
|
|
@@ -101,8 +100,7 @@ protected
|
|
101
100
|
|
102
101
|
def regen_text
|
103
102
|
@user_contacts = ContactManager.contacts.invert
|
104
|
-
recent = Index.load_contacts AccountManager.user_emails,
|
105
|
-
:num => @num
|
103
|
+
recent = Index.load_contacts AccountManager.user_emails, :num => [@num - @user_contacts.length, 0].max
|
106
104
|
|
107
105
|
@contacts = (@user_contacts.keys + recent.select { |p| !@user_contacts[p] }).sort_by { |p| p.sort_by_me + (p.name || "") + p.email }.remove_successive_dupes
|
108
106
|
|
data/lib/sup/modes/inbox-mode.rb
CHANGED
@@ -6,8 +6,6 @@ class InboxMode < ThreadIndexMode
|
|
6
6
|
register_keymap do |k|
|
7
7
|
## overwrite toggle_archived with archive
|
8
8
|
k.add :archive, "Archive thread (remove from inbox)", 'a'
|
9
|
-
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
10
|
-
k.add :reload, "Discard threads and reload", 'D'
|
11
9
|
end
|
12
10
|
|
13
11
|
def initialize
|
@@ -28,7 +26,7 @@ class InboxMode < ThreadIndexMode
|
|
28
26
|
|
29
27
|
def is_relevant? m; m.has_label? :inbox; end
|
30
28
|
|
31
|
-
def
|
29
|
+
def load_threads opts={}
|
32
30
|
n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
33
31
|
load_n_threads_background n, :label => :inbox,
|
34
32
|
:load_killed => false,
|
@@ -38,12 +36,6 @@ class InboxMode < ThreadIndexMode
|
|
38
36
|
BufferManager.flash "Added #{num} threads."
|
39
37
|
end)
|
40
38
|
end
|
41
|
-
|
42
|
-
def reload
|
43
|
-
drop_all_threads
|
44
|
-
BufferManager.draw_screen
|
45
|
-
load_more_threads :num => buffer.content_height
|
46
|
-
end
|
47
39
|
end
|
48
40
|
|
49
41
|
end
|
@@ -1,10 +1,6 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class LabelSearchResultsMode < ThreadIndexMode
|
4
|
-
register_keymap do |k|
|
5
|
-
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
6
|
-
end
|
7
|
-
|
8
4
|
def initialize labels
|
9
5
|
@labels = labels
|
10
6
|
super
|
@@ -12,7 +8,7 @@ class LabelSearchResultsMode < ThreadIndexMode
|
|
12
8
|
|
13
9
|
def is_relevant? m; @labels.all? { |l| m.has_label? l }; end
|
14
10
|
|
15
|
-
def
|
11
|
+
def load_threads opts={}
|
16
12
|
n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
17
13
|
load_n_threads_background n, :labels => @labels,
|
18
14
|
:load_killed => true,
|
@@ -1,10 +1,6 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class PersonSearchResultsMode < ThreadIndexMode
|
4
|
-
register_keymap do |k|
|
5
|
-
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
6
|
-
end
|
7
|
-
|
8
4
|
def initialize people
|
9
5
|
@people = people
|
10
6
|
super
|
@@ -12,7 +8,7 @@ class PersonSearchResultsMode < ThreadIndexMode
|
|
12
8
|
|
13
9
|
def is_relevant? m; @people.any? { |p| m.from == p }; end
|
14
10
|
|
15
|
-
def
|
11
|
+
def load_threads opts={}
|
16
12
|
n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
17
13
|
load_n_threads_background n, :participants => @people,
|
18
14
|
:load_killed => true,
|
data/lib/sup/modes/reply-mode.rb
CHANGED
@@ -18,11 +18,16 @@ class ReplyMode < EditMessageMode
|
|
18
18
|
super 2, :twiddles => false
|
19
19
|
@m = message
|
20
20
|
|
21
|
+
## it's important to put this early because it forces a read of
|
22
|
+
## the full headers (most importantly the list-post header, if
|
23
|
+
## any)
|
24
|
+
@body = reply_body_lines(message)
|
25
|
+
|
21
26
|
from =
|
22
27
|
if @m.recipient_email
|
23
28
|
AccountManager.account_for(@m.recipient_email)
|
24
29
|
else
|
25
|
-
(@m.to + @m.cc).
|
30
|
+
(@m.to + @m.cc).find { |p| AccountManager.is_account? p }
|
26
31
|
end || AccountManager.default_account
|
27
32
|
|
28
33
|
from_email = @m.recipient_email || from.email
|
@@ -70,7 +75,7 @@ class ReplyMode < EditMessageMode
|
|
70
75
|
@type_labels = REPLY_TYPES.select { |t| @headers.member?(t) }
|
71
76
|
@selected_type = @m.is_list_message? ? :list : :sender
|
72
77
|
|
73
|
-
@body
|
78
|
+
@body += sig_lines
|
74
79
|
regen_text
|
75
80
|
end
|
76
81
|
|
@@ -49,11 +49,13 @@ class ScrollMode < Mode
|
|
49
49
|
buffer.mark_dirty
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
53
|
-
buffer.mark_dirty unless @leftcol ==
|
54
|
-
@leftcol =
|
52
|
+
def jump_to_col col
|
53
|
+
buffer.mark_dirty unless @leftcol == col
|
54
|
+
@leftcol = col
|
55
55
|
end
|
56
56
|
|
57
|
+
def jump_to_left; jump_to_col 0; end
|
58
|
+
|
57
59
|
## set top line to l
|
58
60
|
def jump_to_line l
|
59
61
|
l = l.clamp 0, lines - 1
|
@@ -1,10 +1,6 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class SearchResultsMode < ThreadIndexMode
|
4
|
-
register_keymap do |k|
|
5
|
-
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
6
|
-
end
|
7
|
-
|
8
4
|
def initialize qobj
|
9
5
|
@qobj = qobj
|
10
6
|
super
|
@@ -13,7 +9,7 @@ class SearchResultsMode < ThreadIndexMode
|
|
13
9
|
## TODO: think about this
|
14
10
|
def is_relevant? m; super; end
|
15
11
|
|
16
|
-
def
|
12
|
+
def load_threads opts={}
|
17
13
|
n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
18
14
|
load_n_threads_background n, :qobj => @qobj,
|
19
15
|
:load_killed => true,
|
@@ -1,18 +1,22 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
|
+
## subclasses should implement load_threads
|
4
|
+
|
3
5
|
class ThreadIndexMode < LineCursorMode
|
4
6
|
DATE_WIDTH = Time::TO_NICE_S_MAX_LEN
|
5
7
|
FROM_WIDTH = 15
|
6
8
|
LOAD_MORE_THREAD_NUM = 20
|
7
9
|
|
8
10
|
register_keymap do |k|
|
11
|
+
k.add :load_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
12
|
+
k.add :reload, "Discard threads and reload", 'D'
|
9
13
|
k.add :toggle_archived, "Toggle archived status", 'a'
|
10
14
|
k.add :toggle_starred, "Star or unstar all messages in thread", '*'
|
11
15
|
k.add :toggle_new, "Toggle new/read status of all messages in thread", 'N'
|
12
16
|
k.add :edit_labels, "Edit or add labels for a thread", 'l'
|
13
17
|
k.add :edit_message, "Edit message (drafts only)", 'e'
|
14
18
|
k.add :mark_as_spam, "Mark thread as spam", 'S'
|
15
|
-
k.add :kill, "Kill thread (never to be seen in inbox again)", '
|
19
|
+
k.add :kill, "Kill thread (never to be seen in inbox again)", '&'
|
16
20
|
k.add :save, "Save changes now", '$'
|
17
21
|
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
18
22
|
k.add :reply, "Reply to a thread", 'r'
|
@@ -41,10 +45,15 @@ class ThreadIndexMode < LineCursorMode
|
|
41
45
|
def lines; @text.length; end
|
42
46
|
def [] i; @text[i]; end
|
43
47
|
|
48
|
+
def reload
|
49
|
+
drop_all_threads
|
50
|
+
BufferManager.draw_screen
|
51
|
+
load_threads :num => buffer.content_height
|
52
|
+
end
|
53
|
+
|
44
54
|
## open up a thread view window
|
45
|
-
def select
|
46
|
-
|
47
|
-
t = @threads[this_curpos]
|
55
|
+
def select t=nil
|
56
|
+
t ||= @threads[curpos]
|
48
57
|
|
49
58
|
## TODO: don't regen text completely
|
50
59
|
Redwood::reporting_thread do
|
@@ -53,6 +62,10 @@ class ThreadIndexMode < LineCursorMode
|
|
53
62
|
BufferManager.draw_screen
|
54
63
|
end
|
55
64
|
end
|
65
|
+
|
66
|
+
def multi_select threads
|
67
|
+
threads.each { |t| select t }
|
68
|
+
end
|
56
69
|
|
57
70
|
def handle_starred_update m
|
58
71
|
return unless(t = @ts.thread_for m)
|
@@ -161,7 +174,7 @@ class ThreadIndexMode < LineCursorMode
|
|
161
174
|
|
162
175
|
def multi_mark_as_spam threads
|
163
176
|
threads.each do |t|
|
164
|
-
t.
|
177
|
+
t.toggle_label :spam
|
165
178
|
hide_thread t
|
166
179
|
end
|
167
180
|
regen_text
|
@@ -182,15 +195,14 @@ class ThreadIndexMode < LineCursorMode
|
|
182
195
|
|
183
196
|
def save
|
184
197
|
threads = @threads + @hidden_threads.keys
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
198
|
+
|
199
|
+
BufferManager.say("Saving threads...") do |say_id|
|
200
|
+
threads.each_with_index do |t, i|
|
201
|
+
next unless t.dirty?
|
202
|
+
BufferManager.say "Saving thread #{i +1} of #{threads.length}...", say_id
|
203
|
+
t.save Index
|
190
204
|
end
|
191
|
-
t.save Index
|
192
205
|
end
|
193
|
-
BufferManager.clear mbid
|
194
206
|
end
|
195
207
|
|
196
208
|
def cleanup
|
@@ -296,7 +308,11 @@ class ThreadIndexMode < LineCursorMode
|
|
296
308
|
end
|
297
309
|
|
298
310
|
def status
|
299
|
-
|
311
|
+
if (l = lines) == 0
|
312
|
+
""
|
313
|
+
else
|
314
|
+
"line #{curpos + 1} of #{l} #{dirty? ? '*modified*' : ''}"
|
315
|
+
end
|
300
316
|
end
|
301
317
|
|
302
318
|
protected
|
@@ -25,26 +25,20 @@ class ThreadViewMode < LineCursorMode
|
|
25
25
|
@state = {}
|
26
26
|
@hidden_labels = hidden_labels
|
27
27
|
|
28
|
-
earliest = nil
|
29
|
-
latest = nil
|
28
|
+
earliest, latest = nil, nil
|
30
29
|
latest_date = nil
|
31
30
|
@thread.each do |m, d, p|
|
32
31
|
next unless m
|
33
32
|
earliest ||= m
|
34
|
-
@state[m] =
|
35
|
-
if m.has_label?(:unread) && m == earliest
|
36
|
-
:detailed
|
37
|
-
elsif m.has_label?(:starred) || m.has_label?(:unread)
|
38
|
-
:open
|
39
|
-
else
|
40
|
-
:closed
|
41
|
-
end
|
33
|
+
@state[m] = initial_state_for m
|
42
34
|
if latest_date.nil? || m.date > latest_date
|
43
35
|
latest_date = m.date
|
44
36
|
latest = m
|
45
37
|
end
|
46
38
|
end
|
39
|
+
|
47
40
|
@state[latest] = :open if @state[latest] == :closed
|
41
|
+
@state[earliest] = :detailed if earliest.has_label?(:unread) || @thread.size == 1
|
48
42
|
|
49
43
|
BufferManager.say "Loading message bodies..." do
|
50
44
|
regen_chunks
|
@@ -106,6 +100,7 @@ class ThreadViewMode < LineCursorMode
|
|
106
100
|
case chunk
|
107
101
|
when Message, Message::Quote, Message::Signature
|
108
102
|
@state[chunk] = (@state[chunk] != :closed ? :closed : :open)
|
103
|
+
cursor_down if @state[chunk] == :closed
|
109
104
|
when Message::Attachment
|
110
105
|
view_attachment chunk
|
111
106
|
end
|
@@ -164,8 +159,8 @@ class ThreadViewMode < LineCursorMode
|
|
164
159
|
## otherwise, to the previous message
|
165
160
|
top = @messages[m][0]
|
166
161
|
if curpos == top
|
167
|
-
while
|
168
|
-
break if @state[prevm]
|
162
|
+
while(prevm = @messages[m][2])
|
163
|
+
break if @state[prevm] != :closed
|
169
164
|
m = prevm
|
170
165
|
end
|
171
166
|
jump_to_message prevm if prevm
|
@@ -175,9 +170,10 @@ class ThreadViewMode < LineCursorMode
|
|
175
170
|
end
|
176
171
|
|
177
172
|
def jump_to_message m
|
178
|
-
top, bot, prevm, nextm = @messages[m]
|
173
|
+
top, bot, prevm, nextm, depth = @messages[m]
|
179
174
|
jump_to_line top unless top >= topline &&
|
180
175
|
top <= botline && bot >= topline && bot <= botline
|
176
|
+
jump_to_col depth * 2 # sorry!!!! TODO: make this a constant
|
181
177
|
set_cursor_pos top
|
182
178
|
end
|
183
179
|
|
@@ -199,18 +195,19 @@ class ThreadViewMode < LineCursorMode
|
|
199
195
|
quotes = @chunks[m].select { |c| c.is_a?(Message::Quote) || c.is_a?(Message::Signature) }
|
200
196
|
open, closed = quotes.partition { |c| @state[c] == :open }
|
201
197
|
newstate = open.length > closed.length ? :closed : :open
|
202
|
-
Redwood::log "#{open.length} opened, #{closed.length} closed, new state is thus #{newstate}"
|
203
198
|
quotes.each { |c| @state[c] = newstate }
|
204
199
|
update
|
205
200
|
end
|
206
201
|
end
|
207
202
|
|
208
|
-
##
|
203
|
+
## kinda slow for large threads. TODO: fasterify
|
209
204
|
def cleanup
|
210
|
-
|
211
|
-
|
212
|
-
m.
|
213
|
-
|
205
|
+
BufferManager.say "Marking messages as read..." do
|
206
|
+
@thread.each do |m, d, p|
|
207
|
+
if m && m.has_label?(:unread)
|
208
|
+
m.remove_label :unread
|
209
|
+
UpdateManager.relay :read, m
|
210
|
+
end
|
214
211
|
end
|
215
212
|
end
|
216
213
|
@messages = @chunks = @text = nil
|
@@ -218,6 +215,14 @@ class ThreadViewMode < LineCursorMode
|
|
218
215
|
|
219
216
|
private
|
220
217
|
|
218
|
+
def initial_state_for m
|
219
|
+
if m.has_label?(:starred) || m.has_label?(:unread)
|
220
|
+
:open
|
221
|
+
else
|
222
|
+
:closed
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
221
226
|
def update
|
222
227
|
regen_text
|
223
228
|
buffer.mark_dirty if buffer
|
@@ -236,15 +241,27 @@ private
|
|
236
241
|
|
237
242
|
prev_m = nil
|
238
243
|
@thread.each do |m, depth, parent|
|
244
|
+
## we're occasionally called on @threads that have had messages
|
245
|
+
## added to them since initialization. luckily we regen_text on
|
246
|
+
## the entire thread every time the user does anything besides
|
247
|
+
## scrolling (basically), so we can just slap this on here.
|
248
|
+
##
|
249
|
+
## to pick nits, the niceness that i do in the constructor with
|
250
|
+
## 'latest' might not be valid, but i don't see that as a huge
|
251
|
+
## issue.
|
252
|
+
@state[m] ||= initial_state_for m if m
|
253
|
+
|
239
254
|
text = chunk_to_lines m, @state[m], @text.length, depth, parent
|
240
255
|
(0 ... text.length).each do |i|
|
241
256
|
@chunk_lines[@text.length + i] = m
|
242
257
|
@message_lines[@text.length + i] = m
|
243
258
|
end
|
244
259
|
|
245
|
-
|
260
|
+
## sorry i store all this shit in an array. very, very sorry.
|
261
|
+
## also sorry about the * 2. very, very sorry.
|
262
|
+
@messages[m] = [@text.length, @text.length + text.length, prev_m, nil, depth]
|
246
263
|
@messages[prev_m][3] = m if prev_m
|
247
|
-
prev_m = m
|
264
|
+
prev_m = m if m.is_a? Message
|
248
265
|
|
249
266
|
@text += text
|
250
267
|
if @state[m] != :closed && @chunks.member?(m)
|
@@ -283,7 +300,6 @@ private
|
|
283
300
|
[[prefix_widget, widget, imp_widget,
|
284
301
|
[:message_patina_color,
|
285
302
|
"#{m.from ? m.from.mediumname : '?'} to #{m.recipients.map { |l| l.shortname }.join(', ')} #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})"]]]
|
286
|
-
# (m.to.empty? ? [] : [[[:message_patina_color, prefix + " To: " + m.recipients.map { |x| x.mediumname }.join(", ")]]]) +
|
287
303
|
when :closed
|
288
304
|
[[prefix_widget, widget, imp_widget,
|
289
305
|
[:message_patina_color,
|
@@ -323,13 +339,12 @@ private
|
|
323
339
|
when Message
|
324
340
|
message_patina_lines(chunk, state, parent, prefix) +
|
325
341
|
(chunk.is_draft? ? [[[:draft_notification_color, prefix + " >>> This message is a draft. To edit, hit 'e'. <<<"]]] : [])
|
326
|
-
|
327
342
|
when Message::Attachment
|
328
343
|
[[[:mime_color, "#{prefix}+ MIME attachment #{chunk.content_type}#{chunk.desc ? ' (' + chunk.desc + ')': ''}"]]]
|
329
344
|
when Message::Text
|
330
345
|
t = chunk.lines
|
331
|
-
if t.last =~ /^\s*$/
|
332
|
-
t.pop while t[
|
346
|
+
if t.last =~ /^\s*$/ && t.length > 1
|
347
|
+
t.pop while t[-2] =~ /^\s*$/ # pop until only one file empty line
|
333
348
|
end
|
334
349
|
t.map { |line| [[:none, "#{prefix}#{line}"]] }
|
335
350
|
when Message::Quote
|