sup 0.3 → 0.4
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 +31 -9
- data/History.txt +7 -0
- data/Manifest.txt +2 -0
- data/Rakefile +9 -5
- data/bin/sup +81 -57
- data/bin/sup-config +1 -1
- data/bin/sup-sync +3 -0
- data/bin/sup-tweak-labels +127 -0
- data/doc/TODO +23 -12
- data/lib/sup.rb +13 -11
- data/lib/sup/account.rb +25 -12
- data/lib/sup/buffer.rb +61 -41
- data/lib/sup/colormap.rb +2 -0
- data/lib/sup/contact.rb +28 -18
- data/lib/sup/crypto.rb +86 -31
- data/lib/sup/draft.rb +12 -6
- data/lib/sup/horizontal-selector.rb +47 -0
- data/lib/sup/imap.rb +50 -37
- data/lib/sup/index.rb +76 -13
- data/lib/sup/keymap.rb +27 -8
- data/lib/sup/maildir.rb +1 -1
- data/lib/sup/mbox/loader.rb +1 -1
- data/lib/sup/message-chunks.rb +43 -15
- data/lib/sup/message.rb +67 -31
- data/lib/sup/mode.rb +40 -9
- data/lib/sup/modes/completion-mode.rb +1 -1
- data/lib/sup/modes/compose-mode.rb +3 -3
- data/lib/sup/modes/contact-list-mode.rb +12 -8
- data/lib/sup/modes/edit-message-mode.rb +100 -36
- data/lib/sup/modes/file-browser-mode.rb +1 -0
- data/lib/sup/modes/forward-mode.rb +43 -8
- data/lib/sup/modes/inbox-mode.rb +8 -5
- data/lib/sup/modes/label-search-results-mode.rb +12 -1
- data/lib/sup/modes/line-cursor-mode.rb +4 -7
- data/lib/sup/modes/reply-mode.rb +59 -54
- data/lib/sup/modes/resume-mode.rb +6 -6
- data/lib/sup/modes/scroll-mode.rb +4 -3
- data/lib/sup/modes/search-results-mode.rb +8 -5
- data/lib/sup/modes/text-mode.rb +19 -2
- data/lib/sup/modes/thread-index-mode.rb +109 -40
- data/lib/sup/modes/thread-view-mode.rb +180 -49
- data/lib/sup/person.rb +3 -3
- data/lib/sup/poll.rb +9 -8
- data/lib/sup/rfc2047.rb +7 -1
- data/lib/sup/sent.rb +1 -1
- data/lib/sup/tagger.rb +10 -4
- data/lib/sup/textfield.rb +7 -7
- data/lib/sup/thread.rb +86 -49
- data/lib/sup/update.rb +11 -0
- data/lib/sup/util.rb +74 -34
- data/test/test_message.rb +441 -0
- metadata +136 -117
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'open3'
|
1
2
|
module Redwood
|
2
3
|
|
3
4
|
class ThreadViewMode < LineCursorMode
|
@@ -13,6 +14,16 @@ class ThreadViewMode < LineCursorMode
|
|
13
14
|
DATE_FORMAT = "%B %e %Y %l:%M%P"
|
14
15
|
INDENT_SPACES = 2 # how many spaces to indent child messages
|
15
16
|
|
17
|
+
HookManager.register "detailed-headers", <<EOS
|
18
|
+
Add or remove headers from the detailed header display of a message.
|
19
|
+
Variables:
|
20
|
+
message: The message whose headers are to be formatted.
|
21
|
+
headers: A hash of header (name, value) pairs, initialized to the default
|
22
|
+
headers.
|
23
|
+
Return value:
|
24
|
+
None. The variable 'headers' should be modified in place.
|
25
|
+
EOS
|
26
|
+
|
16
27
|
register_keymap do |k|
|
17
28
|
k.add :toggle_detailed_header, "Toggle detailed header", 'h'
|
18
29
|
k.add :show_header, "Show full message header", 'H'
|
@@ -23,20 +34,35 @@ class ThreadViewMode < LineCursorMode
|
|
23
34
|
k.add :expand_all_quotes, "Expand/collapse all quotes in a message", 'o'
|
24
35
|
k.add :jump_to_next_open, "Jump to next open message", 'n'
|
25
36
|
k.add :jump_to_prev_open, "Jump to previous open message", 'p'
|
37
|
+
k.add :align_current_message, "Align current message in buffer", 'z'
|
26
38
|
k.add :toggle_starred, "Star or unstar message", '*'
|
27
|
-
k.add :toggle_new, "Toggle
|
39
|
+
k.add :toggle_new, "Toggle unread/read status of message", 'N'
|
28
40
|
# k.add :collapse_non_new_messages, "Collapse all but unread messages", 'N'
|
29
41
|
k.add :reply, "Reply to a message", 'r'
|
30
|
-
k.add :forward, "Forward a message", 'f'
|
42
|
+
k.add :forward, "Forward a message or attachment", 'f'
|
31
43
|
k.add :alias, "Edit alias/nickname for a person", 'i'
|
32
44
|
k.add :edit_as_new, "Edit message as new", 'D'
|
33
45
|
k.add :save_to_disk, "Save message/attachment to disk", 's'
|
34
46
|
k.add :search, "Search for messages from particular people", 'S'
|
35
47
|
k.add :compose, "Compose message to person", 'm'
|
36
|
-
k.add :archive_and_kill, "Archive thread and kill buffer", 'a'
|
37
|
-
k.add :delete_and_kill, "Delete thread and kill buffer", 'd'
|
38
48
|
k.add :subscribe_to_list, "Subscribe to/unsubscribe from mailing list", "("
|
39
49
|
k.add :unsubscribe_from_list, "Subscribe to/unsubscribe from mailing list", ")"
|
50
|
+
k.add :pipe_message, "Pipe message or attachment to a shell command", '|'
|
51
|
+
|
52
|
+
k.add_multi "(A)rchive/(d)elete/mark as (s)pam/mark as u(N)read:", '.' do |kk|
|
53
|
+
kk.add :archive_and_kill, "Archive this thread and kill buffer", 'a'
|
54
|
+
kk.add :delete_and_kill, "Delete this thread and kill buffer", 'd'
|
55
|
+
kk.add :spam_and_kill, "Mark this thread as spam and kill buffer", 's'
|
56
|
+
kk.add :unread_and_kill, "Mark this thread as unread and kill buffer", 'N'
|
57
|
+
end
|
58
|
+
|
59
|
+
k.add_multi "(A)rchive/(d)elete/mark as (s)pam/mark as u(N)read/do (n)othing:", ',' do |kk|
|
60
|
+
kk.add :archive_and_next, "Archive this thread, kill buffer, and view next", 'a'
|
61
|
+
kk.add :delete_and_next, "Delete this thread, kill buffer, and view next", 'd'
|
62
|
+
kk.add :spam_and_next, "Mark this thread as spam, kill buffer, and view next", 's'
|
63
|
+
kk.add :unread_and_next, "Mark this thread as unread, kill buffer, and view next", 'N'
|
64
|
+
kk.add :do_nothing_and_next, "Kill buffer, and view next", 'n'
|
65
|
+
end
|
40
66
|
end
|
41
67
|
|
42
68
|
## there are a couple important instance variables we hold to format
|
@@ -46,11 +72,15 @@ class ThreadViewMode < LineCursorMode
|
|
46
72
|
## Message objects. @chunk_lines is a map from row #s to Chunk
|
47
73
|
## objects. @person_lines is a map from row #s to Person objects.
|
48
74
|
|
49
|
-
def initialize thread, hidden_labels=[]
|
75
|
+
def initialize thread, hidden_labels=[], index_mode=nil
|
50
76
|
super()
|
51
77
|
@thread = thread
|
52
78
|
@hidden_labels = hidden_labels
|
53
79
|
|
80
|
+
## used for dispatch-and-next
|
81
|
+
@index_mode = index_mode
|
82
|
+
@dying = false
|
83
|
+
|
54
84
|
@layout = SavingHash.new { MessageLayout.new }
|
55
85
|
@chunk_layout = SavingHash.new { ChunkLayout.new }
|
56
86
|
earliest, latest = nil, nil
|
@@ -90,7 +120,7 @@ class ThreadViewMode < LineCursorMode
|
|
90
120
|
|
91
121
|
def show_header
|
92
122
|
m = @message_lines[curpos] or return
|
93
|
-
BufferManager.spawn_unless_exists("Full header") do
|
123
|
+
BufferManager.spawn_unless_exists("Full header for #{m.id}") do
|
94
124
|
TextMode.new m.raw_header
|
95
125
|
end
|
96
126
|
end
|
@@ -126,8 +156,11 @@ class ThreadViewMode < LineCursorMode
|
|
126
156
|
end
|
127
157
|
|
128
158
|
def forward
|
129
|
-
|
130
|
-
|
159
|
+
if(chunk = @chunk_lines[curpos]) && chunk.is_a?(Chunk::Attachment)
|
160
|
+
ForwardMode.spawn_nicely :attachments => [chunk]
|
161
|
+
elsif(m = @message_lines[curpos])
|
162
|
+
ForwardMode.spawn_nicely :message => m
|
163
|
+
end
|
131
164
|
end
|
132
165
|
|
133
166
|
include CanAliasContacts
|
@@ -161,7 +194,7 @@ class ThreadViewMode < LineCursorMode
|
|
161
194
|
@thread.labels = (reserved_labels + new_labels).uniq
|
162
195
|
new_labels.each { |l| LabelManager << l }
|
163
196
|
update
|
164
|
-
UpdateManager.relay self, :
|
197
|
+
UpdateManager.relay self, :labeled, @thread.first
|
165
198
|
end
|
166
199
|
|
167
200
|
def toggle_starred
|
@@ -183,7 +216,7 @@ class ThreadViewMode < LineCursorMode
|
|
183
216
|
## TODO: don't recalculate EVERYTHING just to add a stupid little
|
184
217
|
## star to the display
|
185
218
|
update
|
186
|
-
UpdateManager.relay self, :
|
219
|
+
UpdateManager.relay self, :single_message_labeled, m
|
187
220
|
end
|
188
221
|
|
189
222
|
## called when someone presses enter when the cursor is highlighting
|
@@ -242,27 +275,34 @@ class ThreadViewMode < LineCursorMode
|
|
242
275
|
end
|
243
276
|
end
|
244
277
|
|
245
|
-
def jump_to_first_open
|
278
|
+
def jump_to_first_open loose_alignment=false
|
246
279
|
m = @message_lines[0] or return
|
247
280
|
if @layout[m].state != :closed
|
248
|
-
jump_to_message m
|
281
|
+
jump_to_message m, loose_alignment
|
249
282
|
else
|
250
|
-
jump_to_next_open
|
283
|
+
jump_to_next_open loose_alignment
|
251
284
|
end
|
252
285
|
end
|
253
286
|
|
254
|
-
def jump_to_next_open
|
287
|
+
def jump_to_next_open loose_alignment=false
|
255
288
|
return continue_search_in_buffer if in_search? # hack: allow 'n' to apply to both operations
|
256
|
-
m = @message_lines[
|
289
|
+
m = (curpos ... @message_lines.length).argfind { |i| @message_lines[i] }
|
290
|
+
return unless m
|
257
291
|
while nextm = @layout[m].next
|
258
292
|
break if @layout[nextm].state != :closed
|
259
293
|
m = nextm
|
260
294
|
end
|
261
|
-
jump_to_message nextm if nextm
|
295
|
+
jump_to_message nextm, loose_alignment if nextm
|
262
296
|
end
|
263
297
|
|
264
|
-
def
|
298
|
+
def align_current_message
|
265
299
|
m = @message_lines[curpos] or return
|
300
|
+
jump_to_message m
|
301
|
+
end
|
302
|
+
|
303
|
+
def jump_to_prev_open loose_alignment=false
|
304
|
+
m = (0 .. curpos).to_a.reverse.argfind { |i| @message_lines[i] } # bah, .to_a
|
305
|
+
return unless m
|
266
306
|
## jump to the top of the current message if we're in the body;
|
267
307
|
## otherwise, to the previous message
|
268
308
|
|
@@ -272,22 +312,32 @@ class ThreadViewMode < LineCursorMode
|
|
272
312
|
break if @layout[prevm].state != :closed
|
273
313
|
m = prevm
|
274
314
|
end
|
275
|
-
jump_to_message prevm if prevm
|
315
|
+
jump_to_message prevm, loose_alignment if prevm
|
276
316
|
else
|
277
|
-
jump_to_message m
|
317
|
+
jump_to_message m, loose_alignment
|
278
318
|
end
|
279
319
|
end
|
280
320
|
|
281
|
-
def jump_to_message m
|
321
|
+
def jump_to_message m, loose_alignment=false
|
282
322
|
l = @layout[m]
|
283
323
|
left = l.depth * INDENT_SPACES
|
284
324
|
right = left + l.width
|
285
325
|
|
286
|
-
## jump to the top line
|
287
|
-
|
326
|
+
## jump to the top line
|
327
|
+
if loose_alignment
|
328
|
+
jump_to_line [l.top - 3, 0].max # give 3 lines of top context
|
329
|
+
else
|
330
|
+
jump_to_line l.top
|
331
|
+
end
|
288
332
|
|
289
|
-
## jump to the left
|
290
|
-
|
333
|
+
## jump to the left column
|
334
|
+
if loose_alignment
|
335
|
+
## try and give 4 columns of left context, but not if it means that
|
336
|
+
## the right of the message is truncated.
|
337
|
+
jump_to_col [[left - 4, rightcol - l.width - 1].min, 0].max
|
338
|
+
else
|
339
|
+
jump_to_col left
|
340
|
+
end
|
291
341
|
|
292
342
|
## either way, move the cursor to the first line
|
293
343
|
set_cursor_pos l.top
|
@@ -319,16 +369,91 @@ class ThreadViewMode < LineCursorMode
|
|
319
369
|
@layout = @chunk_layout = @text = nil # for good luck
|
320
370
|
end
|
321
371
|
|
322
|
-
def archive_and_kill
|
323
|
-
|
324
|
-
|
325
|
-
|
372
|
+
def archive_and_kill; archive_and_then :kill end
|
373
|
+
def spam_and_kill; spam_and_then :kill end
|
374
|
+
def delete_and_kill; delete_and_then :kill end
|
375
|
+
def unread_and_kill; unread_and_then :kill end
|
376
|
+
|
377
|
+
def archive_and_next; archive_and_then :next end
|
378
|
+
def spam_and_next; spam_and_then :next end
|
379
|
+
def delete_and_next; delete_and_then :next end
|
380
|
+
def unread_and_next; unread_and_then :next end
|
381
|
+
def do_nothing_and_next; do_nothing_and_then :next end
|
382
|
+
|
383
|
+
def archive_and_then op
|
384
|
+
dispatch op do
|
385
|
+
@thread.remove_label :inbox
|
386
|
+
UpdateManager.relay self, :archived, @thread.first
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def spam_and_then op
|
391
|
+
dispatch op do
|
392
|
+
@thread.apply_label :spam
|
393
|
+
UpdateManager.relay self, :spammed, @thread.first
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def delete_and_then op
|
398
|
+
dispatch op do
|
399
|
+
@thread.apply_label :deleted
|
400
|
+
UpdateManager.relay self, :deleted, @thread.first
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def unread_and_then op
|
405
|
+
dispatch op do
|
406
|
+
@thread.apply_label :unread
|
407
|
+
UpdateManager.relay self, :unread, @thread.first
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def do_nothing_and_then op
|
412
|
+
dispatch op
|
413
|
+
end
|
414
|
+
|
415
|
+
def dispatch op
|
416
|
+
return if @dying
|
417
|
+
@dying = true
|
418
|
+
|
419
|
+
case op
|
420
|
+
when :next
|
421
|
+
@index_mode.launch_next_thread_after(@thread) do
|
422
|
+
@thread.save Index if block_given? && yield
|
423
|
+
BufferManager.kill_buffer_safely buffer
|
424
|
+
end
|
425
|
+
when :kill
|
426
|
+
@thread.save Index if yield
|
427
|
+
BufferManager.kill_buffer_safely buffer
|
428
|
+
else
|
429
|
+
raise ArgumentError, "unknown thread dispatch operation #{op.inspect}"
|
430
|
+
end
|
326
431
|
end
|
432
|
+
private :dispatch
|
433
|
+
|
434
|
+
def pipe_message
|
435
|
+
chunk = @chunk_lines[curpos]
|
436
|
+
chunk = nil unless chunk.is_a?(Chunk::Attachment)
|
437
|
+
message = @message_lines[curpos] unless chunk
|
327
438
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
439
|
+
return unless chunk || message
|
440
|
+
|
441
|
+
command = BufferManager.ask(:shell, "pipe command: ")
|
442
|
+
return if command.nil? || command.empty?
|
443
|
+
|
444
|
+
output = pipe_to_process(command) do |stream|
|
445
|
+
if chunk
|
446
|
+
stream.print chunk.raw_content
|
447
|
+
else
|
448
|
+
message.each_raw_message_line { |l| stream.print l }
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
if output
|
453
|
+
BufferManager.spawn "Output of '#{command}'", TextMode.new(output)
|
454
|
+
else
|
455
|
+
BufferManager.flash "'#{command}' done!"
|
456
|
+
end
|
332
457
|
end
|
333
458
|
|
334
459
|
private
|
@@ -439,32 +564,38 @@ private
|
|
439
564
|
|
440
565
|
when :detailed
|
441
566
|
@person_lines[start] = m.from
|
442
|
-
|
567
|
+
from_line = [[prefix_widget, open_widget, new_widget, starred_widget,
|
443
568
|
[color, "From: #{m.from ? format_person(m.from) : '?'}"]]]
|
444
569
|
|
445
|
-
|
570
|
+
addressee_lines = []
|
446
571
|
unless m.to.empty?
|
447
|
-
m.to.each_with_index { |p, i| @person_lines[start +
|
448
|
-
|
572
|
+
m.to.each_with_index { |p, i| @person_lines[start + addressee_lines.length + from_line.length + i] = p }
|
573
|
+
addressee_lines += format_person_list " To: ", m.to
|
449
574
|
end
|
450
575
|
unless m.cc.empty?
|
451
|
-
m.cc.each_with_index { |p, i| @person_lines[start +
|
452
|
-
|
576
|
+
m.cc.each_with_index { |p, i| @person_lines[start + addressee_lines.length + from_line.length + i] = p }
|
577
|
+
addressee_lines += format_person_list " Cc: ", m.cc
|
453
578
|
end
|
454
579
|
unless m.bcc.empty?
|
455
|
-
m.bcc.each_with_index { |p, i| @person_lines[start +
|
456
|
-
|
580
|
+
m.bcc.each_with_index { |p, i| @person_lines[start + addressee_lines.length + from_line.length + i] = p }
|
581
|
+
addressee_lines += format_person_list " Bcc: ", m.bcc
|
457
582
|
end
|
458
583
|
|
584
|
+
headers = OrderedHash.new
|
585
|
+
headers["Date"] = "#{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})"
|
586
|
+
headers["Subject"] = m.subj
|
587
|
+
|
459
588
|
show_labels = @thread.labels - LabelManager::HIDDEN_RESERVED_LABELS
|
460
|
-
|
461
|
-
"
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
589
|
+
unless show_labels.empty?
|
590
|
+
headers["Labels"] = show_labels.map { |x| x.to_s }.sort.join(', ')
|
591
|
+
end
|
592
|
+
if parent
|
593
|
+
headers["In reply to"] = "#{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}"
|
594
|
+
end
|
595
|
+
|
596
|
+
HookManager.run "detailed-headers", :message => m, :headers => headers
|
466
597
|
|
467
|
-
|
598
|
+
from_line + (addressee_lines + headers.map { |k, v| " #{k}: #{v}" }).map { |l| [[color, prefix + " " + l]] }
|
468
599
|
end
|
469
600
|
end
|
470
601
|
|
@@ -478,7 +609,7 @@ private
|
|
478
609
|
end
|
479
610
|
|
480
611
|
def format_person p
|
481
|
-
p.longname + (ContactManager.
|
612
|
+
p.longname + (ContactManager.is_aliased_contact?(p) ? " (#{ContactManager.alias_for p})" : "")
|
482
613
|
end
|
483
614
|
|
484
615
|
## todo: check arguments on this overly complex function
|
@@ -516,7 +647,7 @@ private
|
|
516
647
|
BufferManager.erase_flash
|
517
648
|
BufferManager.completely_redraw_screen
|
518
649
|
unless success
|
519
|
-
BufferManager.spawn "Attachment: #{chunk.filename}", TextMode.new(chunk.to_s)
|
650
|
+
BufferManager.spawn "Attachment: #{chunk.filename}", TextMode.new(chunk.to_s, chunk.filename)
|
520
651
|
BufferManager.flash "Couldn't execute view command, viewing as text."
|
521
652
|
end
|
522
653
|
end
|
data/lib/sup/person.rb
CHANGED
@@ -117,12 +117,12 @@ class Person
|
|
117
117
|
def full_address
|
118
118
|
if @name && @email
|
119
119
|
if @name =~ /[",@]/
|
120
|
-
"#{@name.inspect}
|
120
|
+
"#{@name.inspect} <#{@email}>" # escape quotes
|
121
121
|
else
|
122
|
-
"
|
122
|
+
"#{@name} <#{@email}>"
|
123
123
|
end
|
124
124
|
else
|
125
|
-
|
125
|
+
email
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
data/lib/sup/poll.rb
CHANGED
@@ -19,12 +19,13 @@ EOS
|
|
19
19
|
HookManager.register "after-poll", <<EOS
|
20
20
|
Executes immediately after a poll for new messages completes.
|
21
21
|
Variables:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
num: the total number of new messages added in this poll
|
23
|
+
num_inbox: the number of new messages added in this poll which
|
24
|
+
appear in the inbox (i.e. were not auto-archived).
|
25
|
+
num_inbox_total_unread: the total number of unread messages in the inbox
|
26
|
+
from_and_subj: an array of (from email address, subject) pairs
|
27
|
+
from_and_subj_inbox: an array of (from email address, subject) pairs for
|
28
|
+
only those messages appearing in the inbox
|
28
29
|
EOS
|
29
30
|
|
30
31
|
DELAY = 300
|
@@ -56,7 +57,7 @@ EOS
|
|
56
57
|
BufferManager.flash "No new messages."
|
57
58
|
end
|
58
59
|
|
59
|
-
HookManager.run "after-poll", :num => num, :num_inbox => numi, :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox
|
60
|
+
HookManager.run "after-poll", :num => num, :num_inbox => numi, :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox, :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] }
|
60
61
|
|
61
62
|
@polling = false
|
62
63
|
[num, numi]
|
@@ -157,7 +158,7 @@ EOS
|
|
157
158
|
HookManager.run "before-add-message", :message => m
|
158
159
|
m = yield(m, offset, entry) or next
|
159
160
|
Index.sync_message m, docid, entry
|
160
|
-
UpdateManager.relay self, :
|
161
|
+
UpdateManager.relay self, :added, m unless entry
|
161
162
|
rescue MessageFormatError => e
|
162
163
|
Redwood::log "ignoring erroneous message at #{source}##{offset}: #{e.message}"
|
163
164
|
end
|
data/lib/sup/rfc2047.rb
CHANGED
@@ -52,10 +52,16 @@ module Rfc2047
|
|
52
52
|
# WORD.
|
53
53
|
end
|
54
54
|
|
55
|
+
charset = "utf-8" if charset =~ /UTF_?8/i
|
56
|
+
|
55
57
|
# Convert:
|
56
58
|
#
|
57
59
|
# Remember - Iconv.open(to, from)!
|
58
|
-
|
60
|
+
begin
|
61
|
+
text = Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
|
62
|
+
rescue Iconv::InvalidCharacter
|
63
|
+
text
|
64
|
+
end
|
59
65
|
end
|
60
66
|
end
|
61
67
|
end
|
data/lib/sup/sent.rb
CHANGED
@@ -25,7 +25,7 @@ class SentManager
|
|
25
25
|
@source.each do |offset, labels|
|
26
26
|
m = Message.new :source => @source, :source_info => offset, :labels => @source.labels
|
27
27
|
Index.sync_message m
|
28
|
-
UpdateManager.relay self, :
|
28
|
+
UpdateManager.relay self, :added, m
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
data/lib/sup/tagger.rb
CHANGED
@@ -8,10 +8,12 @@ class Tagger
|
|
8
8
|
|
9
9
|
def tagged? o; @tagged[o]; end
|
10
10
|
def toggle_tag_for o; @tagged[o] = !@tagged[o]; end
|
11
|
+
def tag o; @tagged[o] = true; end
|
12
|
+
def untag o; @tagged[o] = false; end
|
11
13
|
def drop_all_tags; @tagged.clear; end
|
12
14
|
def drop_tag_for o; @tagged.delete o; end
|
13
15
|
|
14
|
-
def apply_to_tagged
|
16
|
+
def apply_to_tagged action=nil
|
15
17
|
targets = @tagged.select_by_value
|
16
18
|
num_tagged = targets.size
|
17
19
|
if num_tagged == 0
|
@@ -20,10 +22,14 @@ class Tagger
|
|
20
22
|
end
|
21
23
|
|
22
24
|
noun = num_tagged == 1 ? "thread" : "threads"
|
23
|
-
c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
|
24
|
-
return if c.nil? # user cancelled
|
25
25
|
|
26
|
-
|
26
|
+
unless action
|
27
|
+
c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
|
28
|
+
return if c.nil? # user cancelled
|
29
|
+
action = @mode.resolve_input c
|
30
|
+
end
|
31
|
+
|
32
|
+
if action
|
27
33
|
tagged_sym = "multi_#{action}".intern
|
28
34
|
if @mode.respond_to? tagged_sym
|
29
35
|
@mode.send tagged_sym, targets
|