sup 0.0.8 → 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.

Files changed (57) hide show
  1. data/HACKING +6 -36
  2. data/History.txt +11 -0
  3. data/Manifest.txt +5 -0
  4. data/README.txt +13 -31
  5. data/Rakefile +3 -3
  6. data/bin/sup +167 -89
  7. data/bin/sup-add +39 -29
  8. data/bin/sup-config +57 -31
  9. data/bin/sup-sync +60 -54
  10. data/bin/sup-sync-back +143 -0
  11. data/doc/FAQ.txt +56 -19
  12. data/doc/Philosophy.txt +34 -33
  13. data/doc/TODO +76 -46
  14. data/doc/UserGuide.txt +142 -122
  15. data/lib/sup.rb +76 -36
  16. data/lib/sup/account.rb +27 -19
  17. data/lib/sup/buffer.rb +130 -44
  18. data/lib/sup/contact.rb +1 -1
  19. data/lib/sup/draft.rb +1 -2
  20. data/lib/sup/imap.rb +64 -19
  21. data/lib/sup/index.rb +95 -16
  22. data/lib/sup/keymap.rb +1 -1
  23. data/lib/sup/label.rb +31 -5
  24. data/lib/sup/maildir.rb +7 -5
  25. data/lib/sup/mbox.rb +34 -15
  26. data/lib/sup/mbox/loader.rb +30 -12
  27. data/lib/sup/mbox/ssh-loader.rb +7 -5
  28. data/lib/sup/message.rb +93 -44
  29. data/lib/sup/modes/buffer-list-mode.rb +1 -1
  30. data/lib/sup/modes/completion-mode.rb +55 -0
  31. data/lib/sup/modes/compose-mode.rb +6 -25
  32. data/lib/sup/modes/contact-list-mode.rb +1 -1
  33. data/lib/sup/modes/edit-message-mode.rb +119 -29
  34. data/lib/sup/modes/file-browser-mode.rb +108 -0
  35. data/lib/sup/modes/forward-mode.rb +3 -20
  36. data/lib/sup/modes/inbox-mode.rb +9 -12
  37. data/lib/sup/modes/label-list-mode.rb +28 -46
  38. data/lib/sup/modes/label-search-results-mode.rb +1 -16
  39. data/lib/sup/modes/line-cursor-mode.rb +44 -5
  40. data/lib/sup/modes/person-search-results-mode.rb +1 -16
  41. data/lib/sup/modes/reply-mode.rb +18 -31
  42. data/lib/sup/modes/resume-mode.rb +6 -6
  43. data/lib/sup/modes/scroll-mode.rb +6 -5
  44. data/lib/sup/modes/search-results-mode.rb +6 -17
  45. data/lib/sup/modes/thread-index-mode.rb +70 -28
  46. data/lib/sup/modes/thread-view-mode.rb +65 -29
  47. data/lib/sup/person.rb +71 -30
  48. data/lib/sup/poll.rb +13 -4
  49. data/lib/sup/rfc2047.rb +61 -0
  50. data/lib/sup/sent.rb +7 -5
  51. data/lib/sup/source.rb +12 -9
  52. data/lib/sup/suicide.rb +36 -0
  53. data/lib/sup/tagger.rb +6 -6
  54. data/lib/sup/textfield.rb +76 -14
  55. data/lib/sup/thread.rb +97 -123
  56. data/lib/sup/util.rb +167 -1
  57. metadata +30 -5
@@ -1,22 +1,14 @@
1
1
  module Redwood
2
2
 
3
3
  class ForwardMode < EditMessageMode
4
- attr_reader :body, :header
5
-
6
4
  def initialize m
7
- super()
8
- @header = {
5
+ super :header => {
9
6
  "From" => AccountManager.default_account.full_address,
10
7
  "Subject" => "Fwd: #{m.subj}",
11
- "Message-Id" => gen_message_id,
12
- }
13
- @body = forward_body_lines(m) + sig_lines
14
- regen_text
8
+ },
9
+ :body => forward_body_lines(m)
15
10
  end
16
11
 
17
- def lines; @text.length; end
18
- def [] i; @text[i]; end
19
-
20
12
  protected
21
13
 
22
14
  def forward_body_lines m
@@ -24,15 +16,6 @@ protected
24
16
  m.basic_header_lines + [""] + m.basic_body_lines +
25
17
  ["--- End forwarded message ---"]
26
18
  end
27
-
28
- def handle_new_text new_header, new_body
29
- @header = new_header
30
- @body = new_body
31
- end
32
-
33
- def regen_text
34
- @text = header_lines(@header - NON_EDITABLE_HEADERS) + [""] + @body
35
- end
36
19
  end
37
20
 
38
21
  end
@@ -9,12 +9,20 @@ class InboxMode < ThreadIndexMode
9
9
  end
10
10
 
11
11
  def initialize
12
- super [:inbox], [:inbox]
12
+ super [:inbox, :sent], { :label => :inbox }
13
+ raise "can't have more than one!" if defined? @@instance
14
+ @@instance = self
13
15
  end
14
16
 
17
+ def is_relevant? m; m.has_label? :inbox; end
18
+
19
+ ## label-list-mode wants to be able to raise us if the user selects
20
+ ## the "inbox" label, so we need to keep our singletonness around
21
+ def self.instance; @@instance; end
15
22
  def killable?; false; end
16
23
 
17
24
  def archive
25
+ return unless cursor_thread
18
26
  cursor_thread.remove_label :inbox
19
27
  hide_thread cursor_thread
20
28
  regen_text
@@ -44,17 +52,6 @@ class InboxMode < ThreadIndexMode
44
52
  def status
45
53
  super + " #{Index.size} messages in index"
46
54
  end
47
-
48
- def is_relevant? m; m.has_label? :inbox; end
49
-
50
- def load_threads opts={}
51
- n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
52
- load_n_threads_background n, :label => :inbox,
53
- :when_done => (lambda do |num|
54
- opts[:when_done].call if opts[:when_done]
55
- BufferManager.flash "Added #{num} threads."
56
- end)
57
- end
58
55
  end
59
56
 
60
57
  end
@@ -2,79 +2,61 @@ module Redwood
2
2
 
3
3
  class LabelListMode < LineCursorMode
4
4
  register_keymap do |k|
5
- k.add :view_results, "View messages with the selected label", :enter
6
- k.add :reload, "Discard results and reload", 'D'
5
+ k.add :select_label, "Select label", :enter
6
+ k.add :reload, "Discard label list and reload", 'D'
7
7
  end
8
8
 
9
+ bool_reader :done
10
+ attr_reader :value
11
+
9
12
  def initialize
10
13
  @labels = []
11
14
  @text = []
12
- super()
15
+ @done = false
16
+ @value = nil
17
+ super
18
+ regen_text
13
19
  end
14
20
 
15
- def lines; @text.length; end
16
- def [] i; @text[i]; end
17
-
18
- def load_in_background
19
- Redwood::reporting_thread do
20
- BufferManager.say("Counting labels...") { regen_text }
21
- BufferManager.draw_screen
22
- end
23
- end
21
+ def lines; @text.length end
22
+ def [] i; @text[i] end
24
23
 
25
24
  protected
26
25
 
27
26
  def reload
28
- buffer.mark_dirty
29
- BufferManager.draw_screen
30
- load_in_background
27
+ regen_text
28
+ buffer.mark_dirty if buffer
31
29
  end
32
30
 
33
31
  def regen_text
34
32
  @text = []
35
- @labels = (LabelManager::LISTABLE_LABELS + LabelManager.user_labels).sort_by { |t| t.to_s }
33
+ @labels = LabelManager.listable_label_strings
36
34
 
37
- counts = @labels.map do |t|
38
- total = Index.num_results_for :label => t
39
- unread = Index.num_results_for :labels => [t, :unread]
40
- [t, total, unread]
35
+ counts = @labels.map do |string|
36
+ label = LabelManager.label_for string
37
+ total = Index.num_results_for :label => label
38
+ unread = Index.num_results_for :labels => [label, :unread]
39
+ [label, string, total, unread]
41
40
  end
42
41
 
43
- width = @labels.map { |t| t.to_s.length }.max
42
+ width = @labels.max_of { |string| string.length }
44
43
 
45
- counts.map_with_index do |(t, total, unread), i|
46
- if total == 0 && !LabelManager::LISTABLE_LABELS.include?(t)
47
- Redwood::log "no hits for label #{t}, deleting"
48
- LabelManager.delete t
49
- @labels.delete t
44
+ counts.map do |label, string, total, unread|
45
+ if total == 0 && !LabelManager::RESERVED_LABELS.include?(label)
46
+ Redwood::log "no hits for label #{label}, deleting"
47
+ LabelManager.delete label
50
48
  next
51
49
  end
52
50
 
53
- label =
54
- case t
55
- when *LabelManager::LISTABLE_LABELS
56
- t.to_s.ucfirst
57
- else
58
- t.to_s
59
- end
60
51
  @text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
61
- sprintf("%#{width + 1}s %5d %s, %5d unread", label, total, total == 1 ? " message" : "messages", unread)]]
52
+ sprintf("%#{width + 1}s %5d %s, %5d unread", string, total, total == 1 ? " message" : "messages", unread)]]
62
53
  yield i if block_given?
63
54
  end.compact
64
-
65
- buffer.mark_dirty
66
55
  end
67
56
 
68
- def view_results
69
- label = @labels[curpos]
70
- if label == :inbox
71
- BufferManager.raise_to_front BufferManager["inbox"]
72
- else
73
- b = BufferManager.spawn_unless_exists(label) do
74
- mode = LabelSearchResultsMode.new [label]
75
- end
76
- b.mode.load_threads :num => b.content_height
77
- end
57
+ def select_label
58
+ @value, string = @labels[curpos]
59
+ @done = true if @value
78
60
  end
79
61
  end
80
62
 
@@ -3,25 +3,10 @@ module Redwood
3
3
  class LabelSearchResultsMode < ThreadIndexMode
4
4
  def initialize labels
5
5
  @labels = labels
6
- super
6
+ super [], { :labels => @labels }
7
7
  end
8
8
 
9
9
  def is_relevant? m; @labels.all? { |l| m.has_label? l }; end
10
-
11
- def load_threads opts={}
12
- n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
13
- load_n_threads_background n, :labels => @labels,
14
- :load_killed => true,
15
- :load_spam => false,
16
- :when_done =>(lambda do |num|
17
- opts[:when_done].call if opts[:when_done]
18
- if num > 0
19
- BufferManager.flash "Found #{num} threads"
20
- else
21
- BufferManager.flash "No matches"
22
- end
23
- end)
24
- end
25
10
  end
26
11
 
27
12
  end
@@ -1,5 +1,6 @@
1
1
  module Redwood
2
2
 
3
+ ## extends ScrollMode to have a line-based cursor.
3
4
  class LineCursorMode < ScrollMode
4
5
  register_keymap do |k|
5
6
  ## overwrite scrollmode binding on arrow keys for cursor movement
@@ -11,9 +12,11 @@ class LineCursorMode < ScrollMode
11
12
 
12
13
  attr_reader :curpos
13
14
 
14
- def initialize cursor_top=0, opts={}
15
- @cursor_top = cursor_top
16
- @curpos = cursor_top
15
+ def initialize opts={}
16
+ @cursor_top = @curpos = opts.delete(:skip_top_rows) || 0
17
+ @load_more_callbacks = []
18
+ @load_more_callbacks_m = Mutex.new
19
+ @load_more_callbacks_active = false
17
20
  super opts
18
21
  end
19
22
 
@@ -24,6 +27,11 @@ class LineCursorMode < ScrollMode
24
27
 
25
28
  protected
26
29
 
30
+ ## callbacks when the cursor is asked to go beyond the bottom
31
+ def to_load_more &b
32
+ @load_more_callbacks << b
33
+ end
34
+
27
35
  def draw_line ln, opts={}
28
36
  if ln == @curpos
29
37
  super ln, :highlight => true, :debug => opts[:debug]
@@ -49,6 +57,7 @@ protected
49
57
 
50
58
  def line_down # overwrite scrollmode
51
59
  super
60
+ call_load_more_callbacks([topline + buffer.content_height - lines, 10].max) if topline + buffer.content_height > lines
52
61
  set_cursor_pos topline if @curpos < topline
53
62
  end
54
63
 
@@ -58,7 +67,9 @@ protected
58
67
  end
59
68
 
60
69
  def cursor_down
70
+ call_load_more_callbacks buffer.content_height if @curpos == lines - 1
61
71
  return false unless @curpos < lines - 1
72
+
62
73
  if @curpos >= botline - 1
63
74
  page_down
64
75
  set_cursor_pos topline
@@ -102,9 +113,21 @@ protected
102
113
  end
103
114
  end
104
115
 
116
+ ## more complicated than one might think. three behaviors.
105
117
  def page_down
106
- if topline >= lines - buffer.content_height
118
+ ## if we're on the last page, and it's not a full page, just move
119
+ ## the cursor down to the bottom and assume we can't load anything
120
+ ## else via the callbacks.
121
+ if topline > lines - buffer.content_height
107
122
  set_cursor_pos(lines - 1)
123
+
124
+ ## if we're on the last page, and it's a full page, try and load
125
+ ## more lines via the callbacks and then shift the page down
126
+ elsif topline == lines - buffer.content_height
127
+ call_load_more_callbacks buffer.content_height
128
+ super
129
+
130
+ ## otherwise, just move down
108
131
  else
109
132
  relpos = @curpos - topline
110
133
  super
@@ -112,7 +135,7 @@ protected
112
135
  end
113
136
  end
114
137
 
115
- def jump_to_home
138
+ def jump_to_start
116
139
  super
117
140
  set_cursor_pos @cursor_top
118
141
  end
@@ -129,6 +152,22 @@ private
129
152
  @status = l > 0 ? "line #{@curpos + 1} of #{l}" : ""
130
153
  end
131
154
 
155
+ def call_load_more_callbacks size
156
+ go =
157
+ @load_more_callbacks_m.synchronize do
158
+ if @load_more_callbacks_active
159
+ false
160
+ else
161
+ @load_more_callbacks_active = true
162
+ end
163
+ end
164
+
165
+ return unless go
166
+
167
+ @load_more_callbacks.each { |c| c.call size }
168
+ @load_more_callbacks_active = false
169
+ end
170
+
132
171
  end
133
172
 
134
173
  end
@@ -3,25 +3,10 @@ module Redwood
3
3
  class PersonSearchResultsMode < ThreadIndexMode
4
4
  def initialize people
5
5
  @people = people
6
- super
6
+ super [], { :participants => @people }
7
7
  end
8
8
 
9
9
  def is_relevant? m; @people.any? { |p| m.from == p }; end
10
-
11
- def load_threads opts={}
12
- n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
13
- load_n_threads_background n, :participants => @people,
14
- :load_killed => true,
15
- :load_spam => false,
16
- :when_done =>(lambda do |num|
17
- opts[:when_done].call if opts[:when_done]
18
- if num > 0
19
- BufferManager.flash "Found #{num} threads"
20
- else
21
- BufferManager.flash "No matches"
22
- end
23
- end)
24
- end
25
10
  end
26
11
 
27
12
  end
@@ -16,26 +16,24 @@ class ReplyMode < EditMessageMode
16
16
  end
17
17
 
18
18
  def initialize message
19
- super 2, :twiddles => false
20
19
  @m = message
21
20
 
22
21
  ## it's important to put this early because it forces a read of
23
22
  ## the full headers (most importantly the list-post header, if
24
23
  ## any)
25
- @body = reply_body_lines(message)
24
+ body = reply_body_lines message
26
25
 
27
26
  from =
28
27
  if @m.recipient_email
29
- AccountManager.account_for(@m.recipient_email)
28
+ AccountManager.account_for @m.recipient_email
30
29
  else
31
30
  (@m.to + @m.cc).find { |p| AccountManager.is_account? p }
32
31
  end || AccountManager.default_account
33
32
 
34
- #from_email = @m.recipient_email || from.email
35
33
  from_email = from.email
36
34
 
37
35
  ## ignore reply-to for list messages because it's typically set to
38
- ## the list address anyways
36
+ ## the list address, which we explicitly treat with :list
39
37
  to = @m.is_list_message? ? @m.from : (@m.replyto || @m.from)
40
38
  cc = (@m.to + @m.cc - [from, to]).uniq
41
39
 
@@ -57,7 +55,7 @@ class ReplyMode < EditMessageMode
57
55
  @headers[:all] = {
58
56
  "From" => "#{from.name} <#{from_email}>",
59
57
  "To" => [to.full_address],
60
- "Cc" => cc.map { |p| p.full_address },
58
+ "Cc" => cc.select { |p| !AccountManager.is_account?(p) }.map { |p| p.full_address },
61
59
  } unless cc.empty?
62
60
 
63
61
  @headers[:list] = {
@@ -66,7 +64,6 @@ class ReplyMode < EditMessageMode
66
64
  } if @m.is_list_message?
67
65
 
68
66
  refs = gen_references
69
- mid = gen_message_id
70
67
  @headers.each do |k, v|
71
68
  @headers[k] = {
72
69
  "To" => "",
@@ -74,7 +71,6 @@ class ReplyMode < EditMessageMode
74
71
  "Bcc" => "",
75
72
  "In-Reply-To" => "<#{@m.id}>",
76
73
  "Subject" => Message.reify_subj(@m.subj),
77
- "Message-Id" => mid,
78
74
  "References" => refs,
79
75
  }.merge v
80
76
  end
@@ -89,33 +85,27 @@ class ReplyMode < EditMessageMode
89
85
  :recipient
90
86
  end
91
87
 
92
- @body += sig_lines
93
- regen_text
88
+ super :header => @headers[@selected_type], :body => body,
89
+ :skip_top_rows => 2, :twiddles => false
94
90
  end
95
91
 
96
- def lines; @text.length + 2; end
92
+ def lines; super + 2; end
97
93
  def [] i
98
94
  case i
99
95
  when 0
100
- lame = []
101
- @type_labels.each do |t|
102
- lame << [(t == @selected_type ? :none_highlight : :none),
103
- "#{TYPE_DESCRIPTIONS[t]}"]
104
- lame << [:none, " "]
105
- end
106
- lame + [[:none, ""]]
96
+ @type_labels.inject([]) do |array, t|
97
+ array + [[(t == @selected_type ? :none_highlight : :none),
98
+ "#{TYPE_DESCRIPTIONS[t]}"], [:none, " "]]
99
+ end + [[:none, ""]]
107
100
  when 1
108
101
  ""
109
102
  else
110
- @text[i - 2]
103
+ super(i - 2)
111
104
  end
112
105
  end
113
106
 
114
107
  protected
115
108
 
116
- def body; @body; end
117
- def header; @headers[@selected_type]; end
118
-
119
109
  def reply_body_lines m
120
110
  lines = ["Excerpts from #{@m.from.name}'s message of #{@m.date}:"] +
121
111
  m.basic_body_lines.map { |l| "> #{l}" }
@@ -124,19 +114,14 @@ protected
124
114
  end
125
115
 
126
116
  def handle_new_text new_header, new_body
127
- @body = new_body
128
-
129
- if new_header.size != header.size ||
130
- header.any? { |k, v| new_header[k] != v }
117
+ old_header = @headers[@selected_type]
118
+ if new_header.size != old_header.size || old_header.any? { |k, v| new_header[k] != v }
131
119
  @selected_type = :user
132
- @headers[:user] = new_header
120
+ self.header = @headers[:user] = new_header
121
+ update
133
122
  end
134
123
  end
135
124
 
136
- def regen_text
137
- @text = header_lines(header - NON_EDITABLE_HEADERS) + [""] + body
138
- end
139
-
140
125
  def gen_references
141
126
  (@m.refs + [@m.id]).map { |x| "<#{x}>" }.join(" ")
142
127
  end
@@ -144,12 +129,14 @@ protected
144
129
  def move_cursor_left
145
130
  i = @type_labels.index @selected_type
146
131
  @selected_type = @type_labels[(i - 1) % @type_labels.length]
132
+ self.header = @headers[@selected_type]
147
133
  update
148
134
  end
149
135
 
150
136
  def move_cursor_right
151
137
  i = @type_labels.index @selected_type
152
138
  @selected_type = @type_labels[(i + 1) % @type_labels.length]
139
+ self.header = @headers[@selected_type]
153
140
  update
154
141
  end
155
142
  end