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.

Files changed (56) hide show
  1. data.tar.gz.sig +1 -0
  2. data/CONTRIBUTORS +25 -12
  3. data/History.txt +6 -1
  4. data/README.md +70 -0
  5. data/ReleaseNotes +5 -0
  6. data/bin/sup +22 -15
  7. data/bin/sup-add +3 -3
  8. data/bin/sup-config +3 -4
  9. data/bin/sup-import-dump +1 -1
  10. data/bin/sup-sync +1 -1
  11. data/bin/sup-sync-back +2 -2
  12. data/bin/sup-tweak-labels +1 -1
  13. data/lib/sup.rb +39 -23
  14. data/lib/sup/account.rb +4 -0
  15. data/lib/sup/buffer.rb +4 -7
  16. data/lib/sup/colormap.rb +10 -2
  17. data/lib/sup/contact.rb +11 -5
  18. data/lib/sup/crypto.rb +278 -101
  19. data/lib/sup/draft.rb +3 -2
  20. data/lib/sup/horizontal-selector.rb +5 -2
  21. data/lib/sup/index.rb +47 -42
  22. data/lib/sup/label.rb +1 -1
  23. data/lib/sup/message-chunks.rb +4 -2
  24. data/lib/sup/message.rb +14 -3
  25. data/lib/sup/modes/buffer-list-mode.rb +1 -1
  26. data/lib/sup/modes/compose-mode.rb +1 -1
  27. data/lib/sup/modes/contact-list-mode.rb +2 -2
  28. data/lib/sup/modes/edit-message-async-mode.rb +109 -0
  29. data/lib/sup/modes/edit-message-mode.rb +148 -16
  30. data/lib/sup/modes/file-browser-mode.rb +2 -2
  31. data/lib/sup/modes/forward-mode.rb +4 -4
  32. data/lib/sup/modes/line-cursor-mode.rb +2 -2
  33. data/lib/sup/modes/reply-mode.rb +34 -30
  34. data/lib/sup/modes/resume-mode.rb +4 -1
  35. data/lib/sup/modes/scroll-mode.rb +8 -6
  36. data/lib/sup/modes/text-mode.rb +1 -1
  37. data/lib/sup/modes/thread-index-mode.rb +44 -25
  38. data/lib/sup/modes/thread-view-mode.rb +26 -24
  39. data/lib/sup/person.rb +18 -7
  40. data/lib/sup/poll.rb +1 -1
  41. data/lib/sup/rfc2047.rb +1 -1
  42. data/lib/sup/sent.rb +2 -2
  43. data/lib/sup/source.rb +1 -1
  44. data/lib/sup/textfield.rb +38 -1
  45. data/lib/sup/thread.rb +1 -1
  46. data/lib/sup/time.rb +83 -0
  47. data/lib/sup/util.rb +38 -74
  48. data/lib/sup/version.rb +3 -0
  49. metadata +333 -168
  50. metadata.gz.sig +0 -0
  51. data/README.txt +0 -128
  52. data/bin/sup-cmd +0 -138
  53. data/bin/sup-server +0 -44
  54. data/lib/sup/client.rb +0 -92
  55. data/lib/sup/protocol.rb +0 -161
  56. 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 -= COL_JUMP
105
+ @leftcol -= col_jump
104
106
  buffer.mark_dirty
105
107
  end
106
108
 
107
109
  def col_right
108
- @leftcol += COL_JUMP
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 % COL_JUMP)
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
@@ -40,7 +40,7 @@ class TextMode < ScrollMode
40
40
  update_lines
41
41
  if buffer
42
42
  ensure_mode_validity
43
- buffer.mark_dirty
43
+ buffer.mark_dirty
44
44
  end
45
45
  end
46
46
 
@@ -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 'thread'}" do
479
+ UndoManager.register "killing/unkilling #{threads.size.pluralize 'threads'}" do
477
480
  threads.each do |t|
478
- t.remove_label :killed
479
- add_or_unhide t.first
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.apply_label :killed
487
- hide_thread t
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.mediumname]
796
- seen[m.from.mediumname] = true
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
- else
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
- [subj_color, size_widget_text],
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).map { |label| [:label_color, "#{label} "] } +
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
- [[[chunk.patina_color, "#{prefix}- #{chunk.patina_text}"]]] + chunk.lines.map { |line| [[chunk.color, "#{prefix}#{line}"]] }
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 @name && @email
48
- if @name =~ /[",@]/
49
- "#{@name.inspect} <#{@email}>" # escape quotes
47
+ def Person.full_address name, email
48
+ if name && email
49
+ if name =~ /[",@]/
50
+ "#{name.inspect} <#{email}>" # escape quotes
50
51
  else
51
- "#{@name} <#{@email}>"
52
+ "#{name} <#{email}>"
52
53
  end
53
54
  else
54
55
  email
55
56
  end
56
57
  end
57
58
 
58
- ## when sorting addresses, sort by this
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
- Person.new name, email
114
+ from_name_and_email name, email
104
115
  end
105
116
 
106
117
  def self.from_address_list ss