sup 0.12.1 → 0.13.0
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.tar.gz.sig +1 -0
- data/CONTRIBUTORS +25 -12
- data/History.txt +6 -1
- data/README.md +70 -0
- data/ReleaseNotes +5 -0
- data/bin/sup +22 -15
- data/bin/sup-add +3 -3
- data/bin/sup-config +3 -4
- data/bin/sup-import-dump +1 -1
- data/bin/sup-sync +1 -1
- data/bin/sup-sync-back +2 -2
- data/bin/sup-tweak-labels +1 -1
- data/lib/sup.rb +39 -23
- data/lib/sup/account.rb +4 -0
- data/lib/sup/buffer.rb +4 -7
- data/lib/sup/colormap.rb +10 -2
- data/lib/sup/contact.rb +11 -5
- data/lib/sup/crypto.rb +278 -101
- data/lib/sup/draft.rb +3 -2
- data/lib/sup/horizontal-selector.rb +5 -2
- data/lib/sup/index.rb +47 -42
- data/lib/sup/label.rb +1 -1
- data/lib/sup/message-chunks.rb +4 -2
- data/lib/sup/message.rb +14 -3
- data/lib/sup/modes/buffer-list-mode.rb +1 -1
- data/lib/sup/modes/compose-mode.rb +1 -1
- data/lib/sup/modes/contact-list-mode.rb +2 -2
- data/lib/sup/modes/edit-message-async-mode.rb +109 -0
- data/lib/sup/modes/edit-message-mode.rb +148 -16
- data/lib/sup/modes/file-browser-mode.rb +2 -2
- data/lib/sup/modes/forward-mode.rb +4 -4
- data/lib/sup/modes/line-cursor-mode.rb +2 -2
- data/lib/sup/modes/reply-mode.rb +34 -30
- data/lib/sup/modes/resume-mode.rb +4 -1
- data/lib/sup/modes/scroll-mode.rb +8 -6
- data/lib/sup/modes/text-mode.rb +1 -1
- data/lib/sup/modes/thread-index-mode.rb +44 -25
- data/lib/sup/modes/thread-view-mode.rb +26 -24
- data/lib/sup/person.rb +18 -7
- data/lib/sup/poll.rb +1 -1
- data/lib/sup/rfc2047.rb +1 -1
- data/lib/sup/sent.rb +2 -2
- data/lib/sup/source.rb +1 -1
- data/lib/sup/textfield.rb +38 -1
- data/lib/sup/thread.rb +1 -1
- data/lib/sup/time.rb +83 -0
- data/lib/sup/util.rb +38 -74
- data/lib/sup/version.rb +3 -0
- metadata +333 -168
- metadata.gz.sig +0 -0
- data/README.txt +0 -128
- data/bin/sup-cmd +0 -138
- data/bin/sup-server +0 -44
- data/lib/sup/client.rb +0 -92
- data/lib/sup/protocol.rb +0 -161
- data/lib/sup/server.rb +0 -116
@@ -9,6 +9,9 @@ class ResumeMode < EditMessageMode
|
|
9
9
|
header.delete "Date"
|
10
10
|
|
11
11
|
super :header => header, :body => body, :have_signature => true
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
DraftManager.discard @m
|
14
|
+
BufferManager.flash "Draft deleted outside of sup."
|
12
15
|
end
|
13
16
|
|
14
17
|
def unsaved?; !@safe end
|
@@ -35,7 +38,7 @@ class ResumeMode < EditMessageMode
|
|
35
38
|
|
36
39
|
def send_message
|
37
40
|
if super
|
38
|
-
DraftManager.discard @m
|
41
|
+
DraftManager.discard @m
|
39
42
|
@safe = true
|
40
43
|
end
|
41
44
|
end
|
@@ -12,8 +12,6 @@ class ScrollMode < Mode
|
|
12
12
|
|
13
13
|
attr_reader :status, :topline, :botline, :leftcol
|
14
14
|
|
15
|
-
COL_JUMP = 2
|
16
|
-
|
17
15
|
register_keymap do |k|
|
18
16
|
k.add :line_down, "Down one line", :down, 'j', 'J', "\C-e"
|
19
17
|
k.add :line_up, "Up one line", :up, 'k', 'K', "\C-y"
|
@@ -98,19 +96,23 @@ class ScrollMode < Mode
|
|
98
96
|
def search_start_line; @topline end
|
99
97
|
def search_goto_line line; jump_to_line line end
|
100
98
|
|
99
|
+
def col_jump
|
100
|
+
$config[:col_jump] || 2
|
101
|
+
end
|
102
|
+
|
101
103
|
def col_left
|
102
104
|
return unless @leftcol > 0
|
103
|
-
@leftcol -=
|
105
|
+
@leftcol -= col_jump
|
104
106
|
buffer.mark_dirty
|
105
107
|
end
|
106
108
|
|
107
109
|
def col_right
|
108
|
-
@leftcol +=
|
110
|
+
@leftcol += col_jump
|
109
111
|
buffer.mark_dirty
|
110
112
|
end
|
111
113
|
|
112
114
|
def jump_to_col col
|
113
|
-
col = col - (col %
|
115
|
+
col = col - (col % col_jump)
|
114
116
|
buffer.mark_dirty unless @leftcol == col
|
115
117
|
@leftcol = col
|
116
118
|
end
|
@@ -185,7 +187,7 @@ protected
|
|
185
187
|
if in_search?
|
186
188
|
## seems like there ought to be a better way of doing this
|
187
189
|
array = []
|
188
|
-
s.each do |color, text|
|
190
|
+
s.each do |color, text|
|
189
191
|
if text =~ regex
|
190
192
|
array += matching_text_array text, regex, color
|
191
193
|
else
|
data/lib/sup/modes/text-mode.rb
CHANGED
@@ -113,7 +113,7 @@ EOS
|
|
113
113
|
t.each_with_index do |(m, *o), i|
|
114
114
|
next unless m
|
115
115
|
BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1
|
116
|
-
m.load_from_source!
|
116
|
+
m.load_from_source!
|
117
117
|
end
|
118
118
|
end
|
119
119
|
mode = ThreadViewMode.new t, @hidden_labels, self
|
@@ -124,6 +124,9 @@ EOS
|
|
124
124
|
## the first draw_screen is needed before topline and botline
|
125
125
|
## are set, and the second to show the cursor having moved
|
126
126
|
|
127
|
+
t.remove_label :unread
|
128
|
+
Index.save_thread t
|
129
|
+
|
127
130
|
update_text_for_line curpos
|
128
131
|
UpdateManager.relay self, :read, t.first
|
129
132
|
when_done.call if when_done
|
@@ -161,15 +164,15 @@ EOS
|
|
161
164
|
b.call
|
162
165
|
end
|
163
166
|
end
|
164
|
-
|
167
|
+
|
165
168
|
def handle_single_message_labeled_update sender, m
|
166
|
-
## no need to do anything different here; we don't differentiate
|
169
|
+
## no need to do anything different here; we don't differentiate
|
167
170
|
## messages from their containing threads
|
168
171
|
handle_labeled_update sender, m
|
169
172
|
end
|
170
173
|
|
171
174
|
def handle_labeled_update sender, m
|
172
|
-
if(t = thread_containing(m))
|
175
|
+
if(t = thread_containing(m))
|
173
176
|
l = @lines[t] or return
|
174
177
|
update_text_for_line l
|
175
178
|
elsif is_relevant?(m)
|
@@ -273,9 +276,9 @@ EOS
|
|
273
276
|
regen_text
|
274
277
|
end
|
275
278
|
end
|
276
|
-
end
|
279
|
+
end
|
277
280
|
|
278
|
-
def toggle_starred
|
281
|
+
def toggle_starred
|
279
282
|
t = cursor_thread or return
|
280
283
|
undo = actually_toggle_starred t
|
281
284
|
UndoManager.register "toggling thread starred status", undo, lambda { Index.save_thread t }
|
@@ -362,7 +365,7 @@ EOS
|
|
362
365
|
end
|
363
366
|
end
|
364
367
|
|
365
|
-
def toggle_archived
|
368
|
+
def toggle_archived
|
366
369
|
t = cursor_thread or return
|
367
370
|
undo = actually_toggle_archived t
|
368
371
|
UndoManager.register "deleting/undeleting thread #{t.first.id}", undo, lambda { update_text_for_line curpos },
|
@@ -473,30 +476,42 @@ EOS
|
|
473
476
|
|
474
477
|
## m-m-m-m-MULTI-KILL
|
475
478
|
def multi_kill threads
|
476
|
-
UndoManager.register "killing #{threads.size.pluralize '
|
479
|
+
UndoManager.register "killing/unkilling #{threads.size.pluralize 'threads'}" do
|
477
480
|
threads.each do |t|
|
478
|
-
t.
|
479
|
-
|
481
|
+
if t.toggle_label :killed
|
482
|
+
add_or_unhide t.first
|
483
|
+
else
|
484
|
+
hide_thread t
|
485
|
+
end
|
486
|
+
end.each do |t|
|
487
|
+
UpdateManager.relay self, :labeled, t.first
|
480
488
|
Index.save_thread t
|
481
489
|
end
|
482
490
|
regen_text
|
483
491
|
end
|
484
492
|
|
485
493
|
threads.each do |t|
|
486
|
-
t.
|
487
|
-
|
494
|
+
if t.toggle_label :killed
|
495
|
+
hide_thread t
|
496
|
+
else
|
497
|
+
add_or_unhide t.first
|
498
|
+
end
|
499
|
+
end.each do |t|
|
500
|
+
# send 'labeled'... this might be more specific
|
501
|
+
UpdateManager.relay self, :labeled, t.first
|
502
|
+
Index.save_thread t
|
488
503
|
end
|
489
504
|
|
505
|
+
killed, unkilled = threads.partition { |t| t.has_label? :killed }.map(&:size)
|
506
|
+
BufferManager.flash "#{killed.pluralize 'thread'} killed, #{unkilled} unkilled"
|
490
507
|
regen_text
|
491
|
-
BufferManager.flash "#{threads.size.pluralize 'thread'} killed."
|
492
|
-
threads.each { |t| Index.save_thread t }
|
493
508
|
end
|
494
509
|
|
495
510
|
def cleanup
|
496
511
|
UpdateManager.unregister self
|
497
512
|
|
498
513
|
if @load_thread
|
499
|
-
@load_thread.kill
|
514
|
+
@load_thread.kill
|
500
515
|
BufferManager.clear @mbid if @mbid
|
501
516
|
sleep 0.1 # TODO: necessary?
|
502
517
|
BufferManager.erase_flash
|
@@ -512,7 +527,7 @@ EOS
|
|
512
527
|
update_text_for_line curpos
|
513
528
|
cursor_down
|
514
529
|
end
|
515
|
-
|
530
|
+
|
516
531
|
def toggle_tagged_all
|
517
532
|
@mutex.synchronize { @threads.each { |t| @tags.toggle_tag_for t } }
|
518
533
|
regen_text
|
@@ -542,7 +557,7 @@ EOS
|
|
542
557
|
|
543
558
|
keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
|
544
559
|
|
545
|
-
user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl, @hidden_labels
|
560
|
+
user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl.sort_by {|x| x.to_s}, @hidden_labels
|
546
561
|
return unless user_labels
|
547
562
|
|
548
563
|
thread.labels = Set.new(keepl) + user_labels
|
@@ -755,7 +770,7 @@ protected
|
|
755
770
|
|
756
771
|
def update_text_for_line l
|
757
772
|
return unless l # not sure why this happens, but it does, occasionally
|
758
|
-
|
773
|
+
|
759
774
|
need_update = false
|
760
775
|
|
761
776
|
@mutex.synchronize do
|
@@ -792,8 +807,8 @@ protected
|
|
792
807
|
authors = t.map do |m, *o|
|
793
808
|
next unless m && m.from
|
794
809
|
new[m.from] ||= m.has_label?(:unread)
|
795
|
-
next if seen[m.from
|
796
|
-
seen[m.from
|
810
|
+
next if seen[m.from]
|
811
|
+
seen[m.from] = true
|
797
812
|
m.from
|
798
813
|
end.compact
|
799
814
|
|
@@ -849,7 +864,7 @@ protected
|
|
849
864
|
if last
|
850
865
|
name[0 ... (from_width - cur_width)]
|
851
866
|
else
|
852
|
-
name[0 ... (from_width - cur_width - 1)] + ","
|
867
|
+
name[0 ... (from_width - cur_width - 1)] + ","
|
853
868
|
end
|
854
869
|
end
|
855
870
|
|
@@ -872,7 +887,9 @@ protected
|
|
872
887
|
:index_new_color
|
873
888
|
elsif starred
|
874
889
|
:index_starred_color
|
875
|
-
|
890
|
+
elsif Colormap.sym_is_defined(:index_subject_color)
|
891
|
+
:index_subject_color
|
892
|
+
else
|
876
893
|
:index_old_color
|
877
894
|
end
|
878
895
|
|
@@ -882,18 +899,20 @@ protected
|
|
882
899
|
date_padding = @date_widget_width - date_widget.display_length
|
883
900
|
date_widget_text = sprintf "%#{date_padding}s%s", "", date_widget
|
884
901
|
|
885
|
-
[
|
902
|
+
[
|
886
903
|
[:tagged_color, @tags.tagged?(t) ? ">" : " "],
|
887
904
|
[:date_color, date_widget_text],
|
888
905
|
[:starred_color, (starred ? "*" : " ")],
|
889
906
|
] +
|
890
907
|
from +
|
891
908
|
[
|
892
|
-
[
|
909
|
+
[:size_widget_color, size_widget_text],
|
893
910
|
[:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
|
894
911
|
[:to_me_color, dp ? ">" : (p ? '+' : " ")],
|
895
912
|
] +
|
896
|
-
(t.labels - @hidden_labels).
|
913
|
+
(t.labels - @hidden_labels).sort_by {|x| x.to_s}.map {
|
914
|
+
|label| [Colormap.sym_is_defined("label_#{label}_color".to_sym) || :label_color, "#{label} "]
|
915
|
+
} +
|
897
916
|
[
|
898
917
|
[subj_color, t.subj + (t.subj.empty? ? "" : " ")],
|
899
918
|
[:snippet_color, t.snippet],
|
@@ -146,9 +146,6 @@ EOS
|
|
146
146
|
|
147
147
|
@layout[latest].state = :open if @layout[latest].state == :closed
|
148
148
|
@layout[earliest].state = :detailed if earliest.has_label?(:unread) || @thread.size == 1
|
149
|
-
|
150
|
-
@thread.remove_label :unread
|
151
|
-
Index.save_thread @thread
|
152
149
|
end
|
153
150
|
|
154
151
|
def toggle_wrap
|
@@ -268,7 +265,7 @@ EOS
|
|
268
265
|
mode = PersonSearchResultsMode.new [p]
|
269
266
|
BufferManager.spawn "Search for #{p.name}", mode
|
270
267
|
mode.load_threads :num => mode.buffer.content_height
|
271
|
-
end
|
268
|
+
end
|
272
269
|
|
273
270
|
def compose
|
274
271
|
p = @person_lines[curpos]
|
@@ -277,12 +274,12 @@ EOS
|
|
277
274
|
else
|
278
275
|
ComposeMode.spawn_nicely
|
279
276
|
end
|
280
|
-
end
|
277
|
+
end
|
281
278
|
|
282
279
|
def edit_labels
|
283
280
|
old_labels = @thread.labels
|
284
281
|
reserved_labels = old_labels.select { |l| LabelManager::RESERVED_LABELS.include? l }
|
285
|
-
new_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", @thread.labels
|
282
|
+
new_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", @thread.labels.sort_by {|x| x.to_s}
|
286
283
|
|
287
284
|
return unless new_labels
|
288
285
|
@thread.labels = Set.new(reserved_labels) + new_labels
|
@@ -507,7 +504,7 @@ EOS
|
|
507
504
|
return unless m
|
508
505
|
## jump to the top of the current message if we're in the body;
|
509
506
|
## otherwise, to the previous message
|
510
|
-
|
507
|
+
|
511
508
|
top = @layout[m].top
|
512
509
|
if curpos == top
|
513
510
|
while(prevm = @layout[m].prev)
|
@@ -722,7 +719,7 @@ private
|
|
722
719
|
|
723
720
|
## build the patina
|
724
721
|
text = chunk_to_lines m, l.state, @text.length, depth, parent, l.color, l.star_color
|
725
|
-
|
722
|
+
|
726
723
|
l.top = @text.length
|
727
724
|
l.bot = @text.length + text.length # updated below
|
728
725
|
l.prev = prevm
|
@@ -739,7 +736,7 @@ private
|
|
739
736
|
end
|
740
737
|
|
741
738
|
@text += text
|
742
|
-
prevm = m
|
739
|
+
prevm = m
|
743
740
|
if l.state != :closed
|
744
741
|
m.chunks.each do |c|
|
745
742
|
cl = @chunk_layout[c]
|
@@ -782,13 +779,13 @@ private
|
|
782
779
|
when :open
|
783
780
|
@person_lines[start] = m.from
|
784
781
|
[[prefix_widget, open_widget, new_widget, attach_widget, starred_widget,
|
785
|
-
[color,
|
782
|
+
[color,
|
786
783
|
"#{m.from ? m.from.mediumname : '?'} to #{m.recipients.map { |l| l.shortname }.join(', ')} #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})"]]]
|
787
784
|
|
788
785
|
when :closed
|
789
786
|
@person_lines[start] = m.from
|
790
787
|
[[prefix_widget, open_widget, new_widget, attach_widget, starred_widget,
|
791
|
-
[color,
|
788
|
+
[color,
|
792
789
|
"#{m.from ? m.from.mediumname : '?'}, #{m.date.to_nice_s} (#{m.date.to_nice_distance_s}) #{m.snippet}"]]]
|
793
790
|
|
794
791
|
when :detailed
|
@@ -823,7 +820,7 @@ private
|
|
823
820
|
end
|
824
821
|
|
825
822
|
HookManager.run "detailed-headers", :message => m, :headers => headers
|
826
|
-
|
823
|
+
|
827
824
|
from_line + (addressee_lines + headers.map { |k, v| " #{k}: #{v}" }).map { |l| [[color, prefix + " " + l]] }
|
828
825
|
end
|
829
826
|
end
|
@@ -831,7 +828,7 @@ private
|
|
831
828
|
def format_person_list prefix, people
|
832
829
|
ptext = people.map { |p| format_person p }
|
833
830
|
pad = " " * prefix.display_length
|
834
|
-
[prefix + ptext.first + (ptext.length > 1 ? "," : "")] +
|
831
|
+
[prefix + ptext.first + (ptext.length > 1 ? "," : "")] +
|
835
832
|
ptext[1 .. -1].map_with_index do |e, i|
|
836
833
|
pad + e + (i == ptext.length - 1 ? "" : ",")
|
837
834
|
end
|
@@ -841,6 +838,19 @@ private
|
|
841
838
|
p.longname + (ContactManager.is_aliased_contact?(p) ? " (#{ContactManager.alias_for p})" : "")
|
842
839
|
end
|
843
840
|
|
841
|
+
def maybe_wrap_text lines
|
842
|
+
if @wrap
|
843
|
+
config_width = $config[:wrap_width]
|
844
|
+
if config_width and config_width != 0
|
845
|
+
width = [config_width, buffer.content_width].min
|
846
|
+
else
|
847
|
+
width = buffer.content_width
|
848
|
+
end
|
849
|
+
lines = lines.map { |l| l.chomp.wrap width if l }.flatten
|
850
|
+
end
|
851
|
+
return lines
|
852
|
+
end
|
853
|
+
|
844
854
|
## todo: check arguments on this overly complex function
|
845
855
|
def chunk_to_lines chunk, state, start, depth, parent=nil, color=nil, star_color=nil
|
846
856
|
prefix = " " * INDENT_SPACES * depth
|
@@ -856,23 +866,15 @@ private
|
|
856
866
|
else
|
857
867
|
raise "Bad chunk: #{chunk.inspect}" unless chunk.respond_to?(:inlineable?) ## debugging
|
858
868
|
if chunk.inlineable?
|
859
|
-
lines = chunk.lines
|
860
|
-
if @wrap
|
861
|
-
config_width = $config[:wrap_width]
|
862
|
-
if config_width and config_width != 0
|
863
|
-
width = [config_width, buffer.content_width].min
|
864
|
-
else
|
865
|
-
width = buffer.content_width
|
866
|
-
end
|
867
|
-
lines = lines.map { |l| l.chomp.wrap width }.flatten
|
868
|
-
end
|
869
|
+
lines = maybe_wrap_text(chunk.lines)
|
869
870
|
lines.map { |line| [[chunk.color, "#{prefix}#{line}"]] }
|
870
871
|
elsif chunk.expandable?
|
871
872
|
case state
|
872
873
|
when :closed
|
873
874
|
[[[chunk.patina_color, "#{prefix}+ #{chunk.patina_text}"]]]
|
874
875
|
when :open
|
875
|
-
|
876
|
+
lines = maybe_wrap_text(chunk.lines)
|
877
|
+
[[[chunk.patina_color, "#{prefix}- #{chunk.patina_text}"]]] + lines.map { |line| [[chunk.color, "#{prefix}#{line}"]] }
|
876
878
|
end
|
877
879
|
else
|
878
880
|
[[[chunk.patina_color, "#{prefix}x #{chunk.patina_text}"]]]
|
data/lib/sup/person.rb
CHANGED
@@ -9,6 +9,7 @@ class Person
|
|
9
9
|
@name = if name
|
10
10
|
name = name.strip.gsub(/\s+/, " ")
|
11
11
|
name =~ /^(['"]\s*)(.*?)(\s*["'])$/ ? $2 : name
|
12
|
+
name.gsub('\\\\', '\\')
|
12
13
|
end
|
13
14
|
|
14
15
|
@email = email.strip.gsub(/\s+/, " ").downcase
|
@@ -43,19 +44,23 @@ class Person
|
|
43
44
|
|
44
45
|
def mediumname; @name || @email; end
|
45
46
|
|
46
|
-
def full_address
|
47
|
-
if
|
48
|
-
if
|
49
|
-
"#{
|
47
|
+
def Person.full_address name, email
|
48
|
+
if name && email
|
49
|
+
if name =~ /[",@]/
|
50
|
+
"#{name.inspect} <#{email}>" # escape quotes
|
50
51
|
else
|
51
|
-
"#{
|
52
|
+
"#{name} <#{email}>"
|
52
53
|
end
|
53
54
|
else
|
54
55
|
email
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
|
-
|
59
|
+
def full_address
|
60
|
+
Person.full_address @name, @email
|
61
|
+
end
|
62
|
+
|
63
|
+
## when sorting addresses, sort by this
|
59
64
|
def sort_by_me
|
60
65
|
case @name
|
61
66
|
when /^(\S+), \S+/
|
@@ -71,6 +76,12 @@ class Person
|
|
71
76
|
end.downcase
|
72
77
|
end
|
73
78
|
|
79
|
+
## return "canonical" person using contact manager or create one if
|
80
|
+
## not found or contact manager not available
|
81
|
+
def self.from_name_and_email name, email
|
82
|
+
ContactManager.instantiated? && ContactManager.person_for(email) || Person.new(name, email)
|
83
|
+
end
|
84
|
+
|
74
85
|
def self.from_address s
|
75
86
|
return nil if s.nil?
|
76
87
|
|
@@ -100,7 +111,7 @@ class Person
|
|
100
111
|
[nil, s]
|
101
112
|
end
|
102
113
|
|
103
|
-
|
114
|
+
from_name_and_email name, email
|
104
115
|
end
|
105
116
|
|
106
117
|
def self.from_address_list ss
|