sup 0.2 → 0.3
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/History.txt +10 -0
- data/bin/sup +50 -68
- data/doc/NewUserGuide.txt +11 -7
- data/doc/TODO +34 -22
- data/lib/sup.rb +30 -24
- data/lib/sup/buffer.rb +124 -39
- data/lib/sup/colormap.rb +4 -4
- data/lib/sup/draft.rb +1 -1
- data/lib/sup/hook.rb +18 -5
- data/lib/sup/imap.rb +11 -13
- data/lib/sup/index.rb +52 -14
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/logger.rb +1 -0
- data/lib/sup/maildir.rb +9 -0
- data/lib/sup/mbox.rb +3 -1
- data/lib/sup/message-chunks.rb +21 -7
- data/lib/sup/message.rb +31 -15
- data/lib/sup/mode.rb +2 -0
- data/lib/sup/modes/buffer-list-mode.rb +7 -3
- data/lib/sup/modes/compose-mode.rb +14 -16
- data/lib/sup/modes/contact-list-mode.rb +2 -2
- data/lib/sup/modes/edit-message-mode.rb +55 -23
- data/lib/sup/modes/forward-mode.rb +22 -5
- data/lib/sup/modes/inbox-mode.rb +3 -7
- data/lib/sup/modes/label-list-mode.rb +30 -10
- data/lib/sup/modes/label-search-results-mode.rb +12 -0
- data/lib/sup/modes/line-cursor-mode.rb +13 -0
- data/lib/sup/modes/log-mode.rb +0 -6
- data/lib/sup/modes/poll-mode.rb +0 -3
- data/lib/sup/modes/reply-mode.rb +19 -11
- data/lib/sup/modes/scroll-mode.rb +111 -20
- data/lib/sup/modes/search-results-mode.rb +21 -0
- data/lib/sup/modes/text-mode.rb +10 -2
- data/lib/sup/modes/thread-index-mode.rb +200 -90
- data/lib/sup/modes/thread-view-mode.rb +27 -10
- data/lib/sup/person.rb +1 -0
- data/lib/sup/poll.rb +15 -7
- data/lib/sup/source.rb +6 -1
- data/lib/sup/suicide.rb +1 -1
- data/lib/sup/textfield.rb +14 -14
- data/lib/sup/thread.rb +6 -2
- data/lib/sup/util.rb +111 -9
- metadata +13 -6
@@ -1,19 +1,36 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class ForwardMode < EditMessageMode
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
## todo: share some of this with reply-mode
|
6
|
+
def initialize m, opts={}
|
7
|
+
header = {
|
6
8
|
"From" => AccountManager.default_account.full_address,
|
7
9
|
"Subject" => "Fwd: #{m.subj}",
|
8
|
-
}
|
9
|
-
|
10
|
+
}
|
11
|
+
|
12
|
+
header["To"] = opts[:to].map { |p| p.full_address }.join(", ") if opts[:to]
|
13
|
+
header["Cc"] = opts[:cc].map { |p| p.full_address }.join(", ") if opts[:cc]
|
14
|
+
header["Bcc"] = opts[:bcc].map { |p| p.full_address }.join(", ") if opts[:bcc]
|
15
|
+
|
16
|
+
super :header => header, :body => forward_body_lines(m)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.spawn_nicely m, opts={}
|
20
|
+
to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ") or return
|
21
|
+
cc = opts[:cc] || BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc]
|
22
|
+
bcc = opts[:bcc] || BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc]
|
23
|
+
|
24
|
+
mode = ForwardMode.new m, :to => to, :cc => cc, :bcc => bcc
|
25
|
+
BufferManager.spawn "Forwarding #{m.subj}", mode
|
26
|
+
mode.edit_message
|
10
27
|
end
|
11
28
|
|
12
29
|
protected
|
13
30
|
|
14
31
|
def forward_body_lines m
|
15
32
|
["--- Begin forwarded message from #{m.from.mediumname} ---"] +
|
16
|
-
m.
|
33
|
+
m.quotable_header_lines + [""] + m.quotable_body_lines +
|
17
34
|
["--- End forwarded message ---"]
|
18
35
|
end
|
19
36
|
end
|
data/lib/sup/modes/inbox-mode.rb
CHANGED
@@ -14,7 +14,9 @@ class InboxMode < ThreadIndexMode
|
|
14
14
|
@@instance = self
|
15
15
|
end
|
16
16
|
|
17
|
-
def is_relevant? m
|
17
|
+
def is_relevant? m
|
18
|
+
m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
|
19
|
+
end
|
18
20
|
|
19
21
|
## label-list-mode wants to be able to raise us if the user selects
|
20
22
|
## the "inbox" label, so we need to keep our singletonness around
|
@@ -43,12 +45,6 @@ class InboxMode < ThreadIndexMode
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
# not quite working, and not sure if i like it anyways
|
47
|
-
# def handle_unarchived_update sender, t
|
48
|
-
# Redwood::log "unarchived #{t.subj}"
|
49
|
-
# show_thread t
|
50
|
-
# end
|
51
|
-
|
52
48
|
def status
|
53
49
|
super + " #{Index.size} messages in index"
|
54
50
|
end
|
@@ -2,18 +2,16 @@ module Redwood
|
|
2
2
|
|
3
3
|
class LabelListMode < LineCursorMode
|
4
4
|
register_keymap do |k|
|
5
|
-
k.add :select_label, "
|
5
|
+
k.add :select_label, "Search by label", :enter
|
6
6
|
k.add :reload, "Discard label list and reload", '@'
|
7
|
+
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
8
|
+
k.add :toggle_show_unread_only, "Toggle between showing all labels and those with unread mail", 'u'
|
7
9
|
end
|
8
10
|
|
9
|
-
bool_reader :done
|
10
|
-
attr_reader :value
|
11
|
-
|
12
11
|
def initialize
|
13
12
|
@labels = []
|
14
13
|
@text = []
|
15
|
-
@
|
16
|
-
@value = nil
|
14
|
+
@unread_only = false
|
17
15
|
super
|
18
16
|
regen_text
|
19
17
|
end
|
@@ -21,13 +19,28 @@ class LabelListMode < LineCursorMode
|
|
21
19
|
def lines; @text.length end
|
22
20
|
def [] i; @text[i] end
|
23
21
|
|
22
|
+
def jump_to_next_new
|
23
|
+
n = ((curpos + 1) ... lines).find { |i| @labels[i][1] > 0 } || (0 ... curpos).find { |i| @labels[i][1] > 0 }
|
24
|
+
if n
|
25
|
+
## jump there if necessary
|
26
|
+
jump_to_line n unless n >= topline && n < botline
|
27
|
+
set_cursor_pos n
|
28
|
+
else
|
29
|
+
BufferManager.flash "No labels messages with unread messages."
|
30
|
+
end
|
31
|
+
end
|
24
32
|
protected
|
25
33
|
|
34
|
+
def toggle_show_unread_only
|
35
|
+
@unread_only = !@unread_only
|
36
|
+
reload
|
37
|
+
end
|
38
|
+
|
26
39
|
def reload
|
27
40
|
regen_text
|
28
41
|
buffer.mark_dirty if buffer
|
29
42
|
end
|
30
|
-
|
43
|
+
|
31
44
|
def regen_text
|
32
45
|
@text = []
|
33
46
|
labels = LabelManager.listable_labels
|
@@ -41,6 +54,10 @@ protected
|
|
41
54
|
|
42
55
|
width = counts.max_of { |l, s, t, u| s.length }
|
43
56
|
|
57
|
+
if @unread_only
|
58
|
+
counts.delete_if { | l, s, t, u | u == 0 }
|
59
|
+
end
|
60
|
+
|
44
61
|
@labels = []
|
45
62
|
counts.map do |label, string, total, unread|
|
46
63
|
if total == 0 && !LabelManager::RESERVED_LABELS.include?(label)
|
@@ -51,14 +68,17 @@ protected
|
|
51
68
|
|
52
69
|
@text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
|
53
70
|
sprintf("%#{width + 1}s %5d %s, %5d unread", string, total, total == 1 ? " message" : "messages", unread)]]
|
54
|
-
@labels << label
|
71
|
+
@labels << [label, unread]
|
55
72
|
yield i if block_given?
|
56
73
|
end.compact
|
74
|
+
|
75
|
+
BufferManager.flash "No labels with unread messages!" if counts.empty? && @unread_only
|
57
76
|
end
|
58
77
|
|
59
78
|
def select_label
|
60
|
-
|
61
|
-
|
79
|
+
label, num_unread = @labels[curpos]
|
80
|
+
return unless label
|
81
|
+
LabelSearchResultsMode.spawn_nicely label
|
62
82
|
end
|
63
83
|
end
|
64
84
|
|
@@ -10,6 +10,18 @@ class LabelSearchResultsMode < ThreadIndexMode
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def is_relevant? m; @labels.all? { |l| m.has_label? l }; end
|
13
|
+
|
14
|
+
def self.spawn_nicely label
|
15
|
+
label = LabelManager.label_for(label) unless label.is_a?(Symbol)
|
16
|
+
case label
|
17
|
+
when nil
|
18
|
+
when :inbox
|
19
|
+
BufferManager.raise_to_front InboxMode.instance.buffer
|
20
|
+
else
|
21
|
+
b, new = BufferManager.spawn_unless_exists("All threads with label '#{label}'") { LabelSearchResultsMode.new [label] }
|
22
|
+
b.mode.load_threads :num => b.content_height if new
|
23
|
+
end
|
24
|
+
end
|
13
25
|
end
|
14
26
|
|
15
27
|
end
|
@@ -55,6 +55,19 @@ protected
|
|
55
55
|
buffer.mark_dirty
|
56
56
|
end
|
57
57
|
|
58
|
+
## override search behavior to be cursor-based
|
59
|
+
def search_goto_line line
|
60
|
+
while line > botline
|
61
|
+
page_down
|
62
|
+
end
|
63
|
+
while line < topline
|
64
|
+
page_up
|
65
|
+
end
|
66
|
+
set_cursor_pos line
|
67
|
+
end
|
68
|
+
|
69
|
+
def search_start_line; @curpos end
|
70
|
+
|
58
71
|
def line_down # overwrite scrollmode
|
59
72
|
super
|
60
73
|
call_load_more_callbacks([topline + buffer.content_height - lines, 10].max) if topline + buffer.content_height > lines
|
data/lib/sup/modes/log-mode.rb
CHANGED
@@ -3,7 +3,6 @@ module Redwood
|
|
3
3
|
class LogMode < TextMode
|
4
4
|
register_keymap do |k|
|
5
5
|
k.add :toggle_follow, "Toggle follow mode", 'f'
|
6
|
-
k.add :save_to_disk, "Save log to disk", 's'
|
7
6
|
end
|
8
7
|
|
9
8
|
def initialize
|
@@ -37,11 +36,6 @@ class LogMode < TextMode
|
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
|
-
def save_to_disk
|
41
|
-
fn = BufferManager.ask_for_filename :filename, "Save log to file: "
|
42
|
-
save_to_file(fn) { |f| f.puts text } if fn
|
43
|
-
end
|
44
|
-
|
45
39
|
def status
|
46
40
|
super + " (follow: #@follow)"
|
47
41
|
end
|
data/lib/sup/modes/poll-mode.rb
CHANGED
data/lib/sup/modes/reply-mode.rb
CHANGED
@@ -38,13 +38,20 @@ class ReplyMode < EditMessageMode
|
|
38
38
|
cc = (@m.to + @m.cc - [from, to]).uniq
|
39
39
|
|
40
40
|
@headers = {}
|
41
|
-
@headers[:sender] = {
|
42
|
-
"To" => [to.full_address],
|
43
|
-
} unless AccountManager.is_account? to
|
44
41
|
|
42
|
+
## if there's no cc, then the sender is the person you want to reply
|
43
|
+
## to. if it's a list message, then the list address is. otherwise,
|
44
|
+
## the cc contains a recipient.
|
45
|
+
useful_recipient = !(cc.empty? || @m.is_list_message?)
|
46
|
+
|
45
47
|
@headers[:recipient] = {
|
46
48
|
"To" => cc.map { |p| p.full_address },
|
47
|
-
}
|
49
|
+
} if useful_recipient
|
50
|
+
|
51
|
+
## typically we don't want to have a reply-to-sender option if the sender
|
52
|
+
## is a user account. however, if the cc is empty, it's a message to
|
53
|
+
## ourselves, so for the lack of any other options, we'll add it.
|
54
|
+
@headers[:sender] = { "To" => [to.full_address], } if !AccountManager.is_account?(to) || !useful_recipient
|
48
55
|
|
49
56
|
@headers[:user] = {}
|
50
57
|
|
@@ -58,6 +65,7 @@ class ReplyMode < EditMessageMode
|
|
58
65
|
} if @m.is_list_message?
|
59
66
|
|
60
67
|
refs = gen_references
|
68
|
+
|
61
69
|
@headers.each do |k, v|
|
62
70
|
@headers[k] = {
|
63
71
|
"From" => "#{from.name} <#{from.email}>",
|
@@ -102,8 +110,7 @@ class ReplyMode < EditMessageMode
|
|
102
110
|
protected
|
103
111
|
|
104
112
|
def reply_body_lines m
|
105
|
-
lines = ["Excerpts from #{@m.from.name}'s message of #{@m.date}:"] +
|
106
|
-
m.basic_body_lines.map { |l| "> #{l}" }
|
113
|
+
lines = ["Excerpts from #{@m.from.name}'s message of #{@m.date}:"] + m.quotable_body_lines.map { |l| "> #{l}" }
|
107
114
|
lines.pop while lines.last =~ /^\s*$/
|
108
115
|
lines
|
109
116
|
end
|
@@ -121,11 +128,12 @@ protected
|
|
121
128
|
(@m.refs + [@m.id]).map { |x| "<#{x}>" }.join(" ")
|
122
129
|
end
|
123
130
|
|
124
|
-
def edit_field
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
131
|
+
def edit_field field
|
132
|
+
edited_field = super
|
133
|
+
if edited_field && edited_field != "Subject"
|
134
|
+
@selected_type = :user
|
135
|
+
update
|
136
|
+
end
|
129
137
|
end
|
130
138
|
|
131
139
|
def move_cursor_left
|
@@ -24,6 +24,8 @@ class ScrollMode < Mode
|
|
24
24
|
k.add :jump_to_start, "Jump to top", :home, '^', '1'
|
25
25
|
k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
|
26
26
|
k.add :jump_to_left, "Jump to the left", '['
|
27
|
+
k.add :search_in_buffer, "Search in current buffer", '/'
|
28
|
+
k.add :continue_search_in_buffer, "Jump to next search occurrence in buffer", BufferManager::CONTINUE_IN_BUFFER_SEARCH_KEY
|
27
29
|
end
|
28
30
|
|
29
31
|
def initialize opts={}
|
@@ -31,6 +33,9 @@ class ScrollMode < Mode
|
|
31
33
|
@slip_rows = opts[:slip_rows] || 0 # when we pgup/pgdown,
|
32
34
|
# how many lines do we keep?
|
33
35
|
@twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
|
36
|
+
@search_query = nil
|
37
|
+
@search_line = nil
|
38
|
+
@status = ""
|
34
39
|
super()
|
35
40
|
end
|
36
41
|
|
@@ -49,6 +54,41 @@ class ScrollMode < Mode
|
|
49
54
|
@status = "lines #{@topline + 1}:#{@botline}/#{lines}"
|
50
55
|
end
|
51
56
|
|
57
|
+
def in_search?; @search_line end
|
58
|
+
def cancel_search!; @search_line = nil end
|
59
|
+
|
60
|
+
def continue_search_in_buffer
|
61
|
+
unless @search_query
|
62
|
+
BufferManager.flash "No current search!"
|
63
|
+
return
|
64
|
+
end
|
65
|
+
|
66
|
+
start = @search_line || search_start_line
|
67
|
+
line = find_text @search_query, start
|
68
|
+
if line.nil? && (start > 0)
|
69
|
+
line = find_text @search_query, 0
|
70
|
+
BufferManager.flash "Search wrapped to top!" if line
|
71
|
+
end
|
72
|
+
if line
|
73
|
+
@search_line = line + 1
|
74
|
+
search_goto_line line
|
75
|
+
buffer.mark_dirty
|
76
|
+
else
|
77
|
+
BufferManager.flash "Not found!"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def search_in_buffer
|
82
|
+
query = BufferManager.ask :search, "search in buffer: "
|
83
|
+
return if query.nil? || query.empty?
|
84
|
+
@search_query = Regexp.escape query
|
85
|
+
continue_search_in_buffer
|
86
|
+
end
|
87
|
+
|
88
|
+
## subclasses can override these two!
|
89
|
+
def search_goto_line line; jump_to_line line end
|
90
|
+
def search_start_line; @topline end
|
91
|
+
|
52
92
|
def col_left
|
53
93
|
return unless @leftcol > 0
|
54
94
|
@leftcol -= COL_JUMP
|
@@ -98,40 +138,91 @@ class ScrollMode < Mode
|
|
98
138
|
|
99
139
|
protected
|
100
140
|
|
141
|
+
def find_text query, start_line
|
142
|
+
regex = /#{query}/i
|
143
|
+
(start_line ... lines).each do |i|
|
144
|
+
case(s = self[i])
|
145
|
+
when String
|
146
|
+
return i if s =~ regex
|
147
|
+
when Array
|
148
|
+
return i if s.any? { |color, string| string =~ regex }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
101
154
|
def draw_line ln, opts={}
|
155
|
+
regex = /(#{@search_query})/i
|
102
156
|
case(s = self[ln])
|
103
157
|
when String
|
104
|
-
|
105
|
-
|
158
|
+
if in_search?
|
159
|
+
draw_line_from_array ln, matching_text_array(s, regex), opts
|
160
|
+
else
|
161
|
+
draw_line_from_string ln, s, opts
|
162
|
+
end
|
106
163
|
when Array
|
107
|
-
|
164
|
+
if in_search?
|
165
|
+
## seems like there ought to be a better way of doing this
|
166
|
+
array = []
|
167
|
+
s.each do |color, text|
|
168
|
+
if text =~ regex
|
169
|
+
array += matching_text_array text, regex, color
|
170
|
+
else
|
171
|
+
array << [color, text]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
draw_line_from_array ln, array, opts
|
175
|
+
else
|
176
|
+
draw_line_from_array ln, s, opts
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise "unknown drawable object: #{s.inspect}" # good for debugging
|
180
|
+
end
|
108
181
|
|
109
182
|
## speed test
|
110
183
|
# str = s.map { |color, text| text }.join
|
111
184
|
# buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
|
112
185
|
# return
|
186
|
+
end
|
113
187
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
:highlight => opts[:highlight]
|
125
|
-
xpos += text.length
|
126
|
-
else
|
127
|
-
buffer.write ln - @topline, xpos - @leftcol, text,
|
128
|
-
:color => color, :highlight => opts[:highlight]
|
129
|
-
xpos += text.length
|
130
|
-
end
|
188
|
+
def matching_text_array s, regex, oldcolor=:none
|
189
|
+
s.split(regex).map do |text|
|
190
|
+
next if text.empty?
|
191
|
+
if text =~ regex
|
192
|
+
[:search_highlight_color, text]
|
193
|
+
else
|
194
|
+
[oldcolor, text]
|
195
|
+
end
|
196
|
+
end.compact + [[oldcolor, ""]]
|
197
|
+
end
|
131
198
|
|
199
|
+
def draw_line_from_array ln, a, opts
|
200
|
+
xpos = 0
|
201
|
+
a.each do |color, text|
|
202
|
+
raise "nil text for color '#{color}'" if text.nil? # good for debugging
|
203
|
+
|
204
|
+
if xpos + text.length < @leftcol
|
205
|
+
buffer.write ln - @topline, 0, "", :color => color,
|
206
|
+
:highlight => opts[:highlight]
|
207
|
+
xpos += text.length
|
208
|
+
elsif xpos < @leftcol
|
209
|
+
## partial
|
210
|
+
buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
|
211
|
+
:color => color,
|
212
|
+
:highlight => opts[:highlight]
|
213
|
+
xpos += text.length
|
214
|
+
else
|
215
|
+
buffer.write ln - @topline, xpos - @leftcol, text,
|
216
|
+
:color => color, :highlight => opts[:highlight]
|
217
|
+
xpos += text.length
|
132
218
|
end
|
133
219
|
end
|
134
220
|
end
|
221
|
+
|
222
|
+
def draw_line_from_string ln, s, opts
|
223
|
+
buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight]
|
224
|
+
end
|
135
225
|
end
|
136
226
|
|
137
227
|
end
|
228
|
+
|