sup 0.0.1
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/LICENSE +280 -0
- data/Manifest.txt +52 -0
- data/README.txt +119 -0
- data/Rakefile +45 -0
- data/bin/sup +229 -0
- data/bin/sup-import +162 -0
- data/doc/FAQ.txt +38 -0
- data/doc/Philosophy.txt +59 -0
- data/doc/TODO +31 -0
- data/lib/sup.rb +141 -0
- data/lib/sup/account.rb +53 -0
- data/lib/sup/buffer.rb +391 -0
- data/lib/sup/colormap.rb +118 -0
- data/lib/sup/contact.rb +40 -0
- data/lib/sup/draft.rb +105 -0
- data/lib/sup/index.rb +353 -0
- data/lib/sup/keymap.rb +89 -0
- data/lib/sup/label.rb +41 -0
- data/lib/sup/logger.rb +42 -0
- data/lib/sup/mbox.rb +51 -0
- data/lib/sup/mbox/loader.rb +116 -0
- data/lib/sup/message.rb +302 -0
- data/lib/sup/mode.rb +79 -0
- data/lib/sup/modes/buffer-list-mode.rb +37 -0
- data/lib/sup/modes/compose-mode.rb +33 -0
- data/lib/sup/modes/contact-list-mode.rb +121 -0
- data/lib/sup/modes/edit-message-mode.rb +162 -0
- data/lib/sup/modes/forward-mode.rb +38 -0
- data/lib/sup/modes/help-mode.rb +19 -0
- data/lib/sup/modes/inbox-mode.rb +45 -0
- data/lib/sup/modes/label-list-mode.rb +89 -0
- data/lib/sup/modes/label-search-results-mode.rb +29 -0
- data/lib/sup/modes/line-cursor-mode.rb +133 -0
- data/lib/sup/modes/log-mode.rb +44 -0
- data/lib/sup/modes/person-search-results-mode.rb +29 -0
- data/lib/sup/modes/poll-mode.rb +24 -0
- data/lib/sup/modes/reply-mode.rb +136 -0
- data/lib/sup/modes/resume-mode.rb +18 -0
- data/lib/sup/modes/scroll-mode.rb +106 -0
- data/lib/sup/modes/search-results-mode.rb +31 -0
- data/lib/sup/modes/text-mode.rb +51 -0
- data/lib/sup/modes/thread-index-mode.rb +389 -0
- data/lib/sup/modes/thread-view-mode.rb +338 -0
- data/lib/sup/person.rb +120 -0
- data/lib/sup/poll.rb +80 -0
- data/lib/sup/sent.rb +46 -0
- data/lib/sup/tagger.rb +40 -0
- data/lib/sup/textfield.rb +83 -0
- data/lib/sup/thread.rb +358 -0
- data/lib/sup/update.rb +21 -0
- data/lib/sup/util.rb +260 -0
- metadata +123 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ResumeMode < ComposeMode
|
4
|
+
def initialize m
|
5
|
+
super()
|
6
|
+
@id = m.id
|
7
|
+
@header, @body = parse_file m.draft_filename
|
8
|
+
@header.delete "Date"
|
9
|
+
@header["Message-Id"] = gen_message_id # generate a new'n
|
10
|
+
regen_text
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_message
|
14
|
+
DraftManager.discard @id if super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ScrollMode < Mode
|
4
|
+
attr_reader :status, :topline, :botline
|
5
|
+
|
6
|
+
COL_JUMP = 2
|
7
|
+
|
8
|
+
register_keymap do |k|
|
9
|
+
k.add :line_down, "Down one line", :down, 'j', 'J'
|
10
|
+
k.add :line_up, "Up one line", :up, 'k', 'K'
|
11
|
+
k.add :col_left, "Left one column", :left, 'h'
|
12
|
+
k.add :col_right, "Right one column", :right, 'l'
|
13
|
+
k.add :page_down, "Down one page", :page_down, 'n', ' '
|
14
|
+
k.add :page_up, "Up one page", :page_up, 'p', :backspace
|
15
|
+
k.add :jump_to_home, "Jump to top", :home, '^', '1'
|
16
|
+
k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize opts={}
|
20
|
+
@topline, @botline, @leftcol = 0, 0, 0
|
21
|
+
@slip_rows = opts[:slip_rows] || 0 # when we pgup/pgdown,
|
22
|
+
# how many lines do we keep?
|
23
|
+
@twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
def draw
|
28
|
+
ensure_mode_validity
|
29
|
+
(@topline ... @botline).each { |ln| draw_line ln }
|
30
|
+
((@botline - @topline) ... buffer.content_height).each do |ln|
|
31
|
+
if @twiddles
|
32
|
+
buffer.write ln, 0, "~", :color => :twiddle_color
|
33
|
+
else
|
34
|
+
buffer.write ln, 0, ""
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@status = "lines #{@topline + 1}:#{@botline}/#{lines}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def col_left
|
41
|
+
return unless @leftcol > 0
|
42
|
+
@leftcol -= COL_JUMP
|
43
|
+
buffer.mark_dirty
|
44
|
+
end
|
45
|
+
|
46
|
+
def col_right
|
47
|
+
@leftcol += COL_JUMP
|
48
|
+
buffer.mark_dirty
|
49
|
+
end
|
50
|
+
|
51
|
+
## set top line to l
|
52
|
+
def jump_to_line l
|
53
|
+
l = l.clamp 0, lines - 1
|
54
|
+
return if @topline == l
|
55
|
+
@topline = l
|
56
|
+
@botline = [l + buffer.content_height, lines].min
|
57
|
+
buffer.mark_dirty
|
58
|
+
end
|
59
|
+
|
60
|
+
def line_down; jump_to_line @topline + 1; end
|
61
|
+
def line_up; jump_to_line @topline - 1; end
|
62
|
+
def page_down; jump_to_line @topline + buffer.content_height - @slip_rows; end
|
63
|
+
def page_up; jump_to_line @topline - buffer.content_height + @slip_rows; end
|
64
|
+
def jump_to_home; jump_to_line 0; end
|
65
|
+
def jump_to_end; jump_to_line lines - buffer.content_height; end
|
66
|
+
|
67
|
+
def ensure_mode_validity
|
68
|
+
@topline = @topline.clamp 0, lines - 1
|
69
|
+
@topline = 0 if @topline < 0 # empty
|
70
|
+
@botline = [@topline + buffer.content_height, lines].min
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def draw_line ln, opts={}
|
76
|
+
case(s = self[ln])
|
77
|
+
when String
|
78
|
+
buffer.write ln - @topline, 0, s[@leftcol .. -1],
|
79
|
+
:highlight => opts[:highlight]
|
80
|
+
when Array
|
81
|
+
xpos = 0
|
82
|
+
s.each do |color, text|
|
83
|
+
raise "nil text for color '#{color}'" if text.nil?
|
84
|
+
if xpos + text.length < @leftcol
|
85
|
+
buffer.write ln - @topline, 0, "", :color => color,
|
86
|
+
:highlight => opts[:highlight]
|
87
|
+
xpos += text.length
|
88
|
+
## nothing
|
89
|
+
elsif xpos < @leftcol
|
90
|
+
## partial
|
91
|
+
buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
|
92
|
+
:color => color,
|
93
|
+
:highlight => opts[:highlight]
|
94
|
+
xpos += text.length
|
95
|
+
else
|
96
|
+
buffer.write ln - @topline, xpos - @leftcol, text,
|
97
|
+
:color => color, :highlight => opts[:highlight]
|
98
|
+
xpos += text.length
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
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
|
+
def initialize content
|
9
|
+
raise ArgumentError, "no content" if content =~ /^\s*$/
|
10
|
+
@content = content.gsub(/[\(\)]/) { |x| "\\" + x }
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
## TODO: think about this
|
15
|
+
def is_relevant? m; super; end
|
16
|
+
|
17
|
+
def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
18
|
+
load_n_threads_background n, :content => @content,
|
19
|
+
:load_killed => true,
|
20
|
+
:load_spam => false,
|
21
|
+
:when_done =>(lambda do |num|
|
22
|
+
if num > 0
|
23
|
+
BufferManager.flash "Found #{num} threads"
|
24
|
+
else
|
25
|
+
BufferManager.flash "No matches"
|
26
|
+
end
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class TextMode < ScrollMode
|
4
|
+
attr_reader :text
|
5
|
+
|
6
|
+
def initialize text=""
|
7
|
+
@text = text
|
8
|
+
update_lines
|
9
|
+
buffer.mark_dirty if buffer
|
10
|
+
super()
|
11
|
+
end
|
12
|
+
|
13
|
+
def text= t
|
14
|
+
@text = t
|
15
|
+
update_lines
|
16
|
+
if buffer
|
17
|
+
ensure_mode_validity
|
18
|
+
buffer.mark_dirty
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def << line
|
23
|
+
@lines = [0] if @text.empty?
|
24
|
+
@text << line
|
25
|
+
@lines << @text.length
|
26
|
+
if buffer
|
27
|
+
ensure_mode_validity
|
28
|
+
buffer.mark_dirty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def lines
|
33
|
+
@lines.length - 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def [] i
|
37
|
+
return nil unless i < @lines.length
|
38
|
+
@text[@lines[i] ... (i + 1 < @lines.length ? @lines[i + 1] - 1 : @text.length)]
|
39
|
+
# (@lines[i] ... (i + 1 < @lines.length ? @lines[i + 1] - 1 : @text.length)).inspect
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def update_lines
|
45
|
+
pos = @text.find_all_positions("\n")
|
46
|
+
pos.push @text.length unless pos.last == @text.length - 1
|
47
|
+
@lines = [0] + pos.map { |x| x + 1 }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ThreadIndexMode < LineCursorMode
|
4
|
+
DATE_WIDTH = Time::TO_NICE_S_MAX_LEN
|
5
|
+
FROM_WIDTH = 15
|
6
|
+
LOAD_MORE_THREAD_NUM = 20
|
7
|
+
|
8
|
+
register_keymap do |k|
|
9
|
+
k.add :toggle_archived, "Toggle archived status", 'a'
|
10
|
+
k.add :toggle_starred, "Star or unstar all messages in thread", '*'
|
11
|
+
k.add :toggle_new, "Toggle new/read status of all messages in thread", 'N'
|
12
|
+
k.add :edit_labels, "Edit or add labels for a thread", 'l'
|
13
|
+
k.add :edit_message, "Edit message (drafts only)", 'e'
|
14
|
+
k.add :mark_as_spam, "Mark thread as spam", 'S'
|
15
|
+
k.add :kill, "Kill thread (never to be seen in inbox again)", 'K'
|
16
|
+
k.add :save, "Save changes now", '$'
|
17
|
+
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
18
|
+
k.add :reply, "Reply to a thread", 'r'
|
19
|
+
k.add :forward, "Forward a thread", 'f'
|
20
|
+
k.add :toggle_tagged, "Tag/untag current line", 't'
|
21
|
+
k.add :apply_to_tagged, "Apply next command to all tagged threads", ';'
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize required_labels=[], hidden_labels=[]
|
25
|
+
super()
|
26
|
+
@load_thread = nil
|
27
|
+
@required_labels = required_labels
|
28
|
+
@hidden_labels = hidden_labels + LabelManager::HIDDEN_LABELS
|
29
|
+
@date_width = DATE_WIDTH
|
30
|
+
@from_width = FROM_WIDTH
|
31
|
+
@size_width = nil
|
32
|
+
|
33
|
+
@tags = Tagger.new self
|
34
|
+
|
35
|
+
initialize_threads
|
36
|
+
update
|
37
|
+
|
38
|
+
UpdateManager.register self
|
39
|
+
end
|
40
|
+
|
41
|
+
def lines; @text.length; end
|
42
|
+
def [] i; @text[i]; end
|
43
|
+
|
44
|
+
## open up a thread view window
|
45
|
+
def select
|
46
|
+
this_curpos = curpos
|
47
|
+
t = @threads[this_curpos]
|
48
|
+
|
49
|
+
## TODO: don't regen text completely
|
50
|
+
mode = ThreadViewMode.new t, @hidden_labels
|
51
|
+
BufferManager.spawn t.subj, mode
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_starred_update m
|
55
|
+
return unless(t = @ts.thread_for m)
|
56
|
+
@starred_cache[t] = t.has_label? :starred
|
57
|
+
update_text_for_line @lines[t]
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_read_update m
|
61
|
+
return unless(t = @ts.thread_for m)
|
62
|
+
@new_cache[t] = false
|
63
|
+
update_text_for_line @lines[t]
|
64
|
+
end
|
65
|
+
|
66
|
+
## overwrite me!
|
67
|
+
def is_relevant? m; false; end
|
68
|
+
|
69
|
+
def handle_add_update m
|
70
|
+
if is_relevant?(m) || @ts.is_relevant?(m)
|
71
|
+
@ts.load_thread_for_message m
|
72
|
+
update
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def handle_delete_update mid
|
77
|
+
if @ts.contains_id? mid
|
78
|
+
@ts.remove mid
|
79
|
+
update
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def update
|
84
|
+
## let's see you do THIS in python
|
85
|
+
@threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
|
86
|
+
@size_width = (@threads.map { |t| t.size }.max || 0).num_digits
|
87
|
+
regen_text
|
88
|
+
end
|
89
|
+
|
90
|
+
def edit_message
|
91
|
+
t = @threads[curpos] or return
|
92
|
+
message, *crap = t.find { |m, *o| m.has_label? :draft }
|
93
|
+
if message
|
94
|
+
mode = ResumeMode.new message
|
95
|
+
BufferManager.spawn "Edit message", mode
|
96
|
+
else
|
97
|
+
BufferManager.flash "Not a draft message!"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def toggle_starred
|
102
|
+
t = @threads[curpos] or return
|
103
|
+
@starred_cache[t] = t.toggle_label :starred
|
104
|
+
update_text_for_line curpos
|
105
|
+
cursor_down
|
106
|
+
end
|
107
|
+
|
108
|
+
def multi_toggle_starred threads
|
109
|
+
threads.each { |t| @starred_cache[t] = t.toggle_label :starred }
|
110
|
+
regen_text
|
111
|
+
end
|
112
|
+
|
113
|
+
def toggle_archived
|
114
|
+
return unless(t = @threads[curpos])
|
115
|
+
t.toggle_label :inbox
|
116
|
+
update_text_for_line curpos
|
117
|
+
cursor_down
|
118
|
+
end
|
119
|
+
|
120
|
+
def multi_toggle_archived threads
|
121
|
+
threads.each { |t| t.toggle_label :inbox }
|
122
|
+
regen_text
|
123
|
+
end
|
124
|
+
|
125
|
+
def toggle_new
|
126
|
+
t = @threads[curpos] or return
|
127
|
+
@new_cache[t] = t.toggle_label :unread
|
128
|
+
update_text_for_line curpos
|
129
|
+
cursor_down
|
130
|
+
end
|
131
|
+
|
132
|
+
def multi_toggle_new threads
|
133
|
+
threads.each { |t| @new_cache[t] = t.toggle_label :unread }
|
134
|
+
regen_text
|
135
|
+
end
|
136
|
+
|
137
|
+
def multi_toggle_tagged threads
|
138
|
+
@tags.drop_all_tags
|
139
|
+
regen_text
|
140
|
+
end
|
141
|
+
|
142
|
+
def jump_to_next_new
|
143
|
+
t = @threads[curpos] or return
|
144
|
+
n = ((curpos + 1) .. lines).find { |i| @new_cache[@threads[i]] }
|
145
|
+
n = (0 ... curpos).find { |i| @new_cache[@threads[i]] } unless n
|
146
|
+
if n
|
147
|
+
set_cursor_pos n
|
148
|
+
else
|
149
|
+
BufferManager.flash "No new messages"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def mark_as_spam
|
154
|
+
t = @threads[curpos] or return
|
155
|
+
multi_mark_as_spam [t]
|
156
|
+
end
|
157
|
+
|
158
|
+
def multi_mark_as_spam threads
|
159
|
+
threads.each do |t|
|
160
|
+
t.apply_label :spam
|
161
|
+
hide_thread t
|
162
|
+
end
|
163
|
+
regen_text
|
164
|
+
end
|
165
|
+
|
166
|
+
def kill
|
167
|
+
t = @threads[curpos] or return
|
168
|
+
multi_kill [t]
|
169
|
+
end
|
170
|
+
|
171
|
+
def multi_kill threads
|
172
|
+
threads.each do |t|
|
173
|
+
t.apply_label :killed
|
174
|
+
hide_thread t
|
175
|
+
end
|
176
|
+
regen_text
|
177
|
+
end
|
178
|
+
|
179
|
+
def save
|
180
|
+
threads = @threads + @hidden_threads.keys
|
181
|
+
mbid = BufferManager.say "Saving threads..."
|
182
|
+
threads.each_with_index do |t, i|
|
183
|
+
BufferManager.say "Saving thread #{i + 1} of #{threads.length}...",
|
184
|
+
mbid
|
185
|
+
t.save Index
|
186
|
+
end
|
187
|
+
BufferManager.clear mbid
|
188
|
+
end
|
189
|
+
|
190
|
+
def cleanup
|
191
|
+
UpdateManager.unregister self
|
192
|
+
|
193
|
+
if @load_thread
|
194
|
+
@load_thread.kill
|
195
|
+
BufferManager.clear @mbid if @mbid
|
196
|
+
sleep 0.1 # TODO: necessary?
|
197
|
+
BufferManager.erase_flash
|
198
|
+
end
|
199
|
+
save
|
200
|
+
super
|
201
|
+
end
|
202
|
+
|
203
|
+
def toggle_tagged
|
204
|
+
t = @threads[curpos] or return
|
205
|
+
@tags.toggle_tag_for t
|
206
|
+
update_text_for_line curpos
|
207
|
+
cursor_down
|
208
|
+
end
|
209
|
+
|
210
|
+
def apply_to_tagged; @tags.apply_to_tagged; end
|
211
|
+
|
212
|
+
def edit_labels
|
213
|
+
thread = @threads[curpos]
|
214
|
+
speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq
|
215
|
+
keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
|
216
|
+
label_string = modifyl.join(" ")
|
217
|
+
|
218
|
+
answer = BufferManager.ask :edit_labels, "edit labels: ", label_string
|
219
|
+
return unless answer
|
220
|
+
user_labels = answer.split(/\s+/).map { |l| l.intern }
|
221
|
+
|
222
|
+
hl = user_labels.select { |l| speciall.member? l }
|
223
|
+
if hl.empty?
|
224
|
+
thread.labels = keepl + user_labels
|
225
|
+
user_labels.each { |l| LabelManager << l }
|
226
|
+
else
|
227
|
+
BufferManager.flash "'#{hl}' is a reserved label!"
|
228
|
+
end
|
229
|
+
update_text_for_line curpos
|
230
|
+
end
|
231
|
+
|
232
|
+
def multi_edit_labels threads
|
233
|
+
answer = BufferManager.ask :add_labels, "add labels: "
|
234
|
+
return unless answer
|
235
|
+
user_labels = answer.split(/\s+/).map { |l| l.intern }
|
236
|
+
|
237
|
+
hl = user_labels.select { |l| @hidden_labels.member? l }
|
238
|
+
if hl.empty?
|
239
|
+
threads.each { |t| user_labels.each { |l| t.apply_label l } }
|
240
|
+
user_labels.each { |l| LabelManager << l }
|
241
|
+
else
|
242
|
+
BufferManager.flash "'#{hl}' is a reserved label!"
|
243
|
+
end
|
244
|
+
regen_text
|
245
|
+
end
|
246
|
+
|
247
|
+
def reply
|
248
|
+
t = @threads[curpos] or return
|
249
|
+
m = t.latest_message
|
250
|
+
return if m.nil? # probably won't happen
|
251
|
+
mode = ReplyMode.new m
|
252
|
+
BufferManager.spawn "Reply to #{m.subj}", mode
|
253
|
+
end
|
254
|
+
|
255
|
+
def forward
|
256
|
+
t = @threads[curpos] or return
|
257
|
+
m = t.latest_message
|
258
|
+
return if m.nil? # probably won't happen
|
259
|
+
mode = ForwardMode.new m
|
260
|
+
BufferManager.spawn "Forward of #{m.subj}", mode
|
261
|
+
mode.edit
|
262
|
+
end
|
263
|
+
|
264
|
+
def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
|
265
|
+
return if @load_thread
|
266
|
+
@load_thread = ::Thread.new do
|
267
|
+
begin
|
268
|
+
num = load_n_threads n, opts
|
269
|
+
opts[:when_done].call(num) if opts[:when_done]
|
270
|
+
rescue Exception => e
|
271
|
+
$exception ||= e
|
272
|
+
raise
|
273
|
+
end
|
274
|
+
@load_thread = nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
|
279
|
+
@mbid = BufferManager.say "Searching for threads..."
|
280
|
+
orig_size = @ts.size
|
281
|
+
@ts.load_n_threads(@ts.size + n, opts) do |i|
|
282
|
+
BufferManager.say "Loaded #{i} threads...", @mbid
|
283
|
+
if i % 5 == 0
|
284
|
+
update
|
285
|
+
BufferManager.draw_screen
|
286
|
+
end
|
287
|
+
end
|
288
|
+
@ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }
|
289
|
+
|
290
|
+
update
|
291
|
+
BufferManager.clear @mbid
|
292
|
+
@mbid = nil
|
293
|
+
|
294
|
+
BufferManager.draw_screen
|
295
|
+
|
296
|
+
@ts.size - orig_size
|
297
|
+
end
|
298
|
+
|
299
|
+
def status
|
300
|
+
"line #{curpos + 1} of #{lines} #{dirty? ? '*modified*' : ''}"
|
301
|
+
end
|
302
|
+
|
303
|
+
protected
|
304
|
+
|
305
|
+
def cursor_thread; @threads[curpos]; end
|
306
|
+
|
307
|
+
def drop_all_threads
|
308
|
+
@tags.drop_all_tags
|
309
|
+
initialize_threads
|
310
|
+
update
|
311
|
+
end
|
312
|
+
|
313
|
+
def remove_label_and_hide_thread t, label
|
314
|
+
t.remove_label label
|
315
|
+
hide_thread t
|
316
|
+
end
|
317
|
+
|
318
|
+
def hide_thread t
|
319
|
+
raise "already hidden" if @hidden_threads[t]
|
320
|
+
@hidden_threads[t] = true
|
321
|
+
@threads.delete t
|
322
|
+
@tags.drop_tag_for t
|
323
|
+
end
|
324
|
+
|
325
|
+
def update_text_for_line l
|
326
|
+
@text[l] = text_for_thread @threads[l]
|
327
|
+
buffer.mark_dirty if buffer
|
328
|
+
end
|
329
|
+
|
330
|
+
def regen_text
|
331
|
+
@text = @threads.map_with_index { |t, i| text_for_thread t }
|
332
|
+
@lines = @threads.map_with_index { |t, i| [t, i] }.to_h
|
333
|
+
buffer.mark_dirty if buffer
|
334
|
+
end
|
335
|
+
|
336
|
+
def author_text_for_thread t
|
337
|
+
if t.authors.size == 1
|
338
|
+
t.authors.first.mediumname
|
339
|
+
else
|
340
|
+
t.authors.map { |p| AccountManager.is_account?(p) ? "me" : p.shortname }.join ", "
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def text_for_thread t
|
345
|
+
date = (@date_cache[t] ||= t.date.to_nice_s(Time.now))
|
346
|
+
from = (@who_cache[t] ||= author_text_for_thread(t))
|
347
|
+
if from.length > @from_width
|
348
|
+
from = from[0 ... (@from_width - 1)]
|
349
|
+
from += "." unless from[-1] == ?\s
|
350
|
+
end
|
351
|
+
|
352
|
+
new = @new_cache.member?(t) ? @new_cache[t] : @new_cache[t] = t.has_label?(:unread)
|
353
|
+
starred = @starred_cache.member?(t) ? @starred_cache[t] : @starred_cache[t] = t.has_label?(:starred)
|
354
|
+
|
355
|
+
dp = (@dp_cache[t] ||= t.direct_participants.any? { |p| AccountManager.is_account? p })
|
356
|
+
p = (@p_cache[t] ||= (dp || t.participants.any? { |p| AccountManager.is_account? p }))
|
357
|
+
|
358
|
+
base_color = (new ? :index_new_color : :index_old_color)
|
359
|
+
[
|
360
|
+
[:tagged_color, @tags.tagged?(t) ? ">" : " "],
|
361
|
+
[:none, sprintf("%#{@date_width}s ", date)],
|
362
|
+
[base_color, sprintf("%-#{@from_width}s ", from)],
|
363
|
+
[:starred_color, starred ? "*" : " "],
|
364
|
+
[:none, t.size == 1 ? " " * (@size_width + 2) : sprintf("(%#{@size_width}d)", t.size)],
|
365
|
+
[:to_me_color, dp ? " >" : (p ? ' -' : " ")],
|
366
|
+
[base_color, t.subj]
|
367
|
+
] +
|
368
|
+
(t.labels - @hidden_labels).map { |label| [:label_color, " +#{label}"] } +
|
369
|
+
[[:snippet_color, " " + t.snippet]
|
370
|
+
]
|
371
|
+
end
|
372
|
+
|
373
|
+
def dirty?; (@hidden_threads.keys + @threads).any? { |t| t.dirty? }; end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def initialize_threads
|
378
|
+
@ts = ThreadSet.new Index.instance
|
379
|
+
@date_cache = {}
|
380
|
+
@who_cache = {}
|
381
|
+
@dp_cache = {}
|
382
|
+
@p_cache = {}
|
383
|
+
@new_cache = {}
|
384
|
+
@starred_cache = {}
|
385
|
+
@hidden_threads = {}
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|