Class: Redwood::ThreadViewMode
- Inherits:
-
LineCursorMode
show all
- Includes:
- CanAliasContacts
- Defined in:
- lib/sup/modes/thread-view-mode.rb
Defined Under Namespace
Classes: ChunkLayout, MessageLayout
Constant Summary
- DATE_FORMAT =
"%B %e %Y %l:%M%P"
- INDENT_SPACES =
how many spaces to indent child messages
2
- IDEAL_TOP_CONTEXT =
try and give 3 rows of top context
3
- IDEAL_LEFT_CONTEXT =
try and give 4 columns of left context
4
Constants inherited
from ScrollMode
ScrollMode::COL_JUMP
Instance Attribute Summary
#curpos
Attributes inherited from ScrollMode
#botline, #leftcol, #status, #topline
Attributes inherited from Mode
#buffer
Instance Method Summary
(collapse)
#alias_contact
#draw
Methods inherited from ScrollMode
#at_bottom?, #at_top?, #cancel_search!, #col_left, #col_right, #continue_search_in_buffer, #draw, #ensure_mode_validity, #half_page_down, #half_page_up, #in_search?, #jump_to_col, #jump_to_end, #jump_to_left, #jump_to_line, #jump_to_start, #line_down, #line_up, #page_down, #page_up, #resize, #rightcol, #search_goto_line, #search_goto_pos, #search_in_buffer, #search_start_line
Methods inherited from Mode
#blur, #cancel_search!, #draw, #focus, #handle_input, #help_text, #in_search?, #killable?, load_all_modes, make_name, #name, #pipe_to_process, register_keymap, #resize, #resolve_input, #save_to_file, #status
Constructor Details
- (ThreadViewMode) initialize(thread, hidden_labels = [], index_mode = nil)
there are a couple important instance variables we hold to format the
thread and to provide line-based functionality. @layout is a map from
Messages to MessageLayouts, and @chunk_layout from Chunks to ChunkLayouts.
@message_lines is a map from row #s to Message objects. @chunk_lines is a
map from row #s to Chunk objects. @person_lines is a map from row #s to
Person objects.
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
# File 'lib/sup/modes/thread-view-mode.rb', line 84
def initialize thread, hidden_labels=[], index_mode=nil
super()
@thread = thread
@hidden_labels = hidden_labels
@index_mode = index_mode
@dying = false
@layout = SavingHash.new { MessageLayout.new }
@chunk_layout = SavingHash.new { ChunkLayout.new }
earliest, latest = nil, nil
latest_date = nil
altcolor = false
@thread.each do |m, d, p|
next unless m
earliest ||= m
@layout[m].state = initial_state_for m
@layout[m].color = altcolor ? :alternate_patina_color : :message_patina_color
@layout[m].star_color = altcolor ? :alternate_starred_patina_color : :starred_patina_color
@layout[m].orig_new = m.has_label? :read
altcolor = !altcolor
if latest_date.nil? || m.date > latest_date
latest_date = m.date
latest = m
end
end
@layout[latest].state = :open if @layout[latest].state == :closed
@layout[earliest].state = :detailed if earliest.has_label?(:unread) || @thread.size == 1
@thread.remove_label :unread
regen_text
end
|
Instance Method Details
128
|
# File 'lib/sup/modes/thread-view-mode.rb', line 128
def [] i; @text[i]; end
|
- (Object) activate_chunk
called when someone presses enter when the cursor is highlighting a chunk.
for expandable chunks (including messages) we toggle open/closed state; for
viewable chunks (like attachments) we view.
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
# File 'lib/sup/modes/thread-view-mode.rb', line 235
def activate_chunk
chunk = @chunk_lines[curpos] or return
layout =
if chunk.is_a?(Message)
@layout[chunk]
elsif chunk.expandable?
@chunk_layout[chunk]
end
if layout
layout.state = (layout.state != :closed ? :closed : :open)
update
elsif chunk.viewable?
view chunk
end
end
|
176
177
178
179
180
|
# File 'lib/sup/modes/thread-view-mode.rb', line 176
def alias
p = @person_lines[curpos] or return
alias_contact p
update
end
|
- (Object) align_current_message
320
321
322
323
|
# File 'lib/sup/modes/thread-view-mode.rb', line 320
def align_current_message
m = @message_lines[curpos] or return
jump_to_message m
end
|
- (Object) archive_and_kill
397
|
# File 'lib/sup/modes/thread-view-mode.rb', line 397
def archive_and_kill; archive_and_then :kill end
|
- (Object) archive_and_next
402
|
# File 'lib/sup/modes/thread-view-mode.rb', line 402
def archive_and_next; archive_and_then :next end
|
- (Object) archive_and_prev
408
|
# File 'lib/sup/modes/thread-view-mode.rb', line 408
def archive_and_prev; archive_and_then :prev end
|
- (Object) archive_and_then(op)
414
415
416
417
418
419
|
# File 'lib/sup/modes/thread-view-mode.rb', line 414
def archive_and_then op
dispatch op do
@thread.remove_label :inbox
UpdateManager.relay self, :archived, @thread.first
end
end
|
393
394
395
|
# File 'lib/sup/modes/thread-view-mode.rb', line 393
def cleanup
@layout = @chunk_layout = @text = nil end
|
- (Object) collapse_non_new_messages
378
379
380
381
|
# File 'lib/sup/modes/thread-view-mode.rb', line 378
def collapse_non_new_messages
@layout.each { |m, l| l.state = l.orig_new ? :open : :closed }
update
end
|
189
190
191
192
193
194
195
196
|
# File 'lib/sup/modes/thread-view-mode.rb', line 189
def compose
p = @person_lines[curpos]
if p
ComposeMode.spawn_nicely :to_default => p
else
ComposeMode.spawn_nicely
end
end
|
- (Object) delete_and_kill
399
|
# File 'lib/sup/modes/thread-view-mode.rb', line 399
def delete_and_kill; delete_and_then :kill end
|
- (Object) delete_and_next
404
|
# File 'lib/sup/modes/thread-view-mode.rb', line 404
def delete_and_next; delete_and_then :next end
|
- (Object) delete_and_prev
410
|
# File 'lib/sup/modes/thread-view-mode.rb', line 410
def delete_and_prev; delete_and_then :prev end
|
- (Object) delete_and_then(op)
428
429
430
431
432
433
|
# File 'lib/sup/modes/thread-view-mode.rb', line 428
def delete_and_then op
dispatch op do
@thread.apply_label :deleted
UpdateManager.relay self, :deleted, @thread.first
end
end
|
- (Object) do_nothing_and_next
406
|
# File 'lib/sup/modes/thread-view-mode.rb', line 406
def do_nothing_and_next; do_nothing_and_then :next end
|
- (Object) do_nothing_and_prev
412
|
# File 'lib/sup/modes/thread-view-mode.rb', line 412
def do_nothing_and_prev; do_nothing_and_then :prev end
|
- (Object) do_nothing_and_then(op)
442
443
444
|
# File 'lib/sup/modes/thread-view-mode.rb', line 442
def do_nothing_and_then op
dispatch op
end
|
- (Object) draw_line(ln, opts = {})
120
121
122
123
124
125
126
|
# File 'lib/sup/modes/thread-view-mode.rb', line 120
def draw_line ln, opts={}
if ln == curpos
super ln, :highlight => true
else
super
end
end
|
- (Object) edit_as_new
252
253
254
255
256
257
|
# File 'lib/sup/modes/thread-view-mode.rb', line 252
def edit_as_new
m = @message_lines[curpos] or return
mode = ComposeMode.new(:body => m.quotable_body_lines, :to => m.to, :cc => m.cc, :subj => m.subj, :bcc => m.bcc, :refs => m.refs, :replytos => m.replytos)
BufferManager.spawn "edit as new", mode
mode.edit_message
end
|
- (Object) edit_draft
276
277
278
279
280
281
282
283
284
285
286
|
# File 'lib/sup/modes/thread-view-mode.rb', line 276
def edit_draft
m = @message_lines[curpos] or return
if m.is_draft?
mode = ResumeMode.new m
BufferManager.spawn "Edit message", mode
BufferManager.kill_buffer self.buffer
mode.edit_message
else
BufferManager.flash "Not a draft message!"
end
end
|
- (Object) edit_labels
198
199
200
201
202
203
204
205
206
207
|
# File 'lib/sup/modes/thread-view-mode.rb', line 198
def edit_labels
reserved_labels = @thread.labels.select { |l| LabelManager::RESERVED_LABELS.include? l }
new_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", @thread.labels
return unless new_labels
@thread.labels = (reserved_labels + new_labels).uniq
new_labels.each { |l| LabelManager << l }
update
UpdateManager.relay self, :labeled, @thread.first
end
|
- (Object) expand_all_messages
371
372
373
374
375
376
|
# File 'lib/sup/modes/thread-view-mode.rb', line 371
def expand_all_messages
@global_message_state ||= :closed
@global_message_state = (@global_message_state == :closed ? :open : :closed)
@layout.each { |m, l| l.state = @global_message_state }
update
end
|
- (Object) expand_all_quotes
383
384
385
386
387
388
389
390
391
|
# File 'lib/sup/modes/thread-view-mode.rb', line 383
def expand_all_quotes
if(m = @message_lines[curpos])
quotes = m.chunks.select { |c| (c.is_a?(Chunk::Quote) || c.is_a?(Chunk::Signature)) && c.lines.length > 1 }
numopen = quotes.inject(0) { |s, c| s + (@chunk_layout[c].state == :open ? 1 : 0) }
newstate = numopen > quotes.length / 2 ? :closed : :open
quotes.each { |c| @chunk_layout[c].state = newstate }
update
end
end
|
167
168
169
170
171
172
173
|
# File 'lib/sup/modes/thread-view-mode.rb', line 167
def forward
if(chunk = @chunk_lines[curpos]) && chunk.is_a?(Chunk::Attachment)
ForwardMode.spawn_nicely :attachments => [chunk]
elsif(m = @message_lines[curpos])
ForwardMode.spawn_nicely :message => m
end
end
|
- (Object) jump_to_first_open(loose_alignment = false)
300
301
302
303
304
305
306
307
|
# File 'lib/sup/modes/thread-view-mode.rb', line 300
def jump_to_first_open loose_alignment=false
m = @message_lines[0] or return
if @layout[m].state != :closed
jump_to_message m, loose_alignment
else
jump_to_next_open loose_alignment
end
end
|
- (Object) jump_to_message(m, loose_alignment = false)
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
# File 'lib/sup/modes/thread-view-mode.rb', line 345
def jump_to_message m, loose_alignment=false
l = @layout[m]
left = l.depth * INDENT_SPACES
right = left + l.width
if loose_alignment
jump_to_line [l.top - IDEAL_TOP_CONTEXT, 0].max else
jump_to_line l.top
end
ideal_left = left +
if loose_alignment
-IDEAL_LEFT_CONTEXT + (l.width - buffer.content_width + IDEAL_LEFT_CONTEXT + 1).clamp(0, IDEAL_LEFT_CONTEXT)
else
0
end
jump_to_col [ideal_left, 0].max
set_cursor_pos l.top
end
|
- (Object) jump_to_next_open(loose_alignment = false)
309
310
311
312
313
314
315
316
317
318
|
# File 'lib/sup/modes/thread-view-mode.rb', line 309
def jump_to_next_open loose_alignment=false
return continue_search_in_buffer if in_search? m = (curpos ... @message_lines.length).argfind { |i| @message_lines[i] }
return unless m
while nextm = @layout[m].next
break if @layout[nextm].state != :closed
m = nextm
end
jump_to_message nextm, loose_alignment if nextm
end
|
- (Object) jump_to_prev_open(loose_alignment = false)
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
|
# File 'lib/sup/modes/thread-view-mode.rb', line 325
def jump_to_prev_open loose_alignment=false
m = (0 .. curpos).to_a.reverse.argfind { |i| @message_lines[i] } return unless m
top = @layout[m].top
if curpos == top
while(prevm = @layout[m].prev)
break if @layout[prevm].state != :closed
m = prevm
end
jump_to_message prevm, loose_alignment if prevm
else
jump_to_message m, loose_alignment
end
end
|
127
|
# File 'lib/sup/modes/thread-view-mode.rb', line 127
def lines; @text.length; end
|
- (Object) pipe_message
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
|
# File 'lib/sup/modes/thread-view-mode.rb', line 468
def pipe_message
chunk = @chunk_lines[curpos]
chunk = nil unless chunk.is_a?(Chunk::Attachment)
message = @message_lines[curpos] unless chunk
return unless chunk || message
command = BufferManager.ask(:shell, "pipe command: ")
return if command.nil? || command.empty?
output = pipe_to_process(command) do |stream|
if chunk
stream.print chunk.raw_content
else
message.each_raw_message_line { |l| stream.print l }
end
end
if output
BufferManager.spawn "Output of '#{command}'", TextMode.new(output)
else
BufferManager.flash "'#{command}' done!"
end
end
|
143
144
145
146
147
|
# File 'lib/sup/modes/thread-view-mode.rb', line 143
def reply
m = @message_lines[curpos] or return
mode = ReplyMode.new m
BufferManager.spawn "Reply to #{m.subj}", mode
end
|
- (Object) save_to_disk
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
# File 'lib/sup/modes/thread-view-mode.rb', line 259
def save_to_disk
chunk = @chunk_lines[curpos] or return
case chunk
when Chunk::Attachment
default_dir = File.join(($config[:default_attachment_save_dir] || "."), chunk.filename)
fn = BufferManager.ask_for_filename :filename, "Save attachment to file: ", default_dir
save_to_file(fn) { |f| f.print chunk.raw_content } if fn
else
m = @message_lines[curpos]
fn = BufferManager.ask_for_filename :filename, "Save message to file: "
return unless fn
save_to_file(fn) do |f|
m.each_raw_message_line { |l| f.print l }
end
end
end
|
182
183
184
185
186
187
|
# File 'lib/sup/modes/thread-view-mode.rb', line 182
def search
p = @person_lines[curpos] or return
mode = PersonSearchResultsMode.new [p]
BufferManager.spawn "Search for #{p.name}", mode
mode.load_threads :num => mode.buffer.content_height
end
|
- (Object) send_draft
288
289
290
291
292
293
294
295
296
297
298
|
# File 'lib/sup/modes/thread-view-mode.rb', line 288
def send_draft
m = @message_lines[curpos] or return
if m.is_draft?
mode = ResumeMode.new m
BufferManager.spawn "Send message", mode
BufferManager.kill_buffer self.buffer
mode.send_message
else
BufferManager.flash "Not a draft message!"
end
end
|
130
131
132
133
134
135
|
# File 'lib/sup/modes/thread-view-mode.rb', line 130
def
m = @message_lines[curpos] or return
BufferManager.spawn_unless_exists("Full header for #{m.id}") do
TextMode.new m.
end
end
|
- (Object) spam_and_kill
398
|
# File 'lib/sup/modes/thread-view-mode.rb', line 398
def spam_and_kill; spam_and_then :kill end
|
- (Object) spam_and_next
403
|
# File 'lib/sup/modes/thread-view-mode.rb', line 403
def spam_and_next; spam_and_then :next end
|
- (Object) spam_and_prev
409
|
# File 'lib/sup/modes/thread-view-mode.rb', line 409
def spam_and_prev; spam_and_then :prev end
|
- (Object) spam_and_then(op)
421
422
423
424
425
426
|
# File 'lib/sup/modes/thread-view-mode.rb', line 421
def spam_and_then op
dispatch op do
@thread.apply_label :spam
UpdateManager.relay self, :spammed, @thread.first
end
end
|
- (Object) subscribe_to_list
149
150
151
152
153
154
155
156
|
# File 'lib/sup/modes/thread-view-mode.rb', line 149
def subscribe_to_list
m = @message_lines[curpos] or return
if m.list_subscribe && m.list_subscribe =~ /<mailto:(.*?)\?(subject=(.*?))>/
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => $3
else
BufferManager.flash "Can't find List-Subscribe header for this message."
end
end
|
137
138
139
140
141
|
# File 'lib/sup/modes/thread-view-mode.rb', line 137
def
m = @message_lines[curpos] or return
@layout[m].state = (@layout[m].state == :detailed ? :open : :detailed)
update
end
|
- (Object) toggle_label(m, label)
219
220
221
222
223
224
225
226
227
228
229
|
# File 'lib/sup/modes/thread-view-mode.rb', line 219
def toggle_label m, label
if m.has_label? label
m.remove_label label
else
m.add_label label
end
update
UpdateManager.relay self, :single_message_labeled, m
end
|
- (Object) toggle_new
214
215
216
217
|
# File 'lib/sup/modes/thread-view-mode.rb', line 214
def toggle_new
m = @message_lines[curpos] or return
toggle_label m, :unread
end
|
- (Object) toggle_starred
209
210
211
212
|
# File 'lib/sup/modes/thread-view-mode.rb', line 209
def toggle_starred
m = @message_lines[curpos] or return
toggle_label m, :starred
end
|
- (Object) unread_and_kill
400
|
# File 'lib/sup/modes/thread-view-mode.rb', line 400
def unread_and_kill; unread_and_then :kill end
|
- (Object) unread_and_next
405
|
# File 'lib/sup/modes/thread-view-mode.rb', line 405
def unread_and_next; unread_and_then :next end
|
- (Object) unread_and_prev
411
|
# File 'lib/sup/modes/thread-view-mode.rb', line 411
def unread_and_prev; unread_and_then :prev end
|
- (Object) unread_and_then(op)
435
436
437
438
439
440
|
# File 'lib/sup/modes/thread-view-mode.rb', line 435
def unread_and_then op
dispatch op do
@thread.apply_label :unread
UpdateManager.relay self, :unread, @thread.first
end
end
|
- (Object) unsubscribe_from_list
158
159
160
161
162
163
164
165
|
# File 'lib/sup/modes/thread-view-mode.rb', line 158
def unsubscribe_from_list
m = @message_lines[curpos] or return
if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)\?(subject=(.*?))>/
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => $3
else
BufferManager.flash "Can't find List-Unsubscribe header for this message."
end
end
|