sup 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTORS +84 -0
- data/Gemfile +3 -0
- data/HACKING +42 -0
- data/History.txt +361 -0
- data/LICENSE +280 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/ReleaseNotes +231 -0
- data/bin/sup +434 -0
- data/bin/sup-add +118 -0
- data/bin/sup-config +243 -0
- data/bin/sup-dump +43 -0
- data/bin/sup-import-dump +101 -0
- data/bin/sup-psych-ify-config-files +21 -0
- data/bin/sup-recover-sources +87 -0
- data/bin/sup-sync +210 -0
- data/bin/sup-sync-back-maildir +127 -0
- data/bin/sup-tweak-labels +140 -0
- data/contrib/colorpicker.rb +100 -0
- data/contrib/completion/_sup.zsh +114 -0
- data/devel/console.sh +3 -0
- data/devel/count-loc.sh +3 -0
- data/devel/load-index.rb +9 -0
- data/devel/profile.rb +12 -0
- data/devel/start-console.rb +5 -0
- data/doc/FAQ.txt +119 -0
- data/doc/Hooks.txt +79 -0
- data/doc/Philosophy.txt +69 -0
- data/lib/sup.rb +467 -0
- data/lib/sup/account.rb +90 -0
- data/lib/sup/buffer.rb +768 -0
- data/lib/sup/colormap.rb +239 -0
- data/lib/sup/contact.rb +67 -0
- data/lib/sup/crypto.rb +461 -0
- data/lib/sup/draft.rb +119 -0
- data/lib/sup/hook.rb +159 -0
- data/lib/sup/horizontal_selector.rb +59 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +882 -0
- data/lib/sup/interactive_lock.rb +89 -0
- data/lib/sup/keymap.rb +140 -0
- data/lib/sup/label.rb +87 -0
- data/lib/sup/logger.rb +77 -0
- data/lib/sup/logger/singleton.rb +10 -0
- data/lib/sup/maildir.rb +257 -0
- data/lib/sup/mbox.rb +187 -0
- data/lib/sup/message.rb +803 -0
- data/lib/sup/message_chunks.rb +328 -0
- data/lib/sup/mode.rb +140 -0
- data/lib/sup/modes/buffer_list_mode.rb +50 -0
- data/lib/sup/modes/completion_mode.rb +55 -0
- data/lib/sup/modes/compose_mode.rb +38 -0
- data/lib/sup/modes/console_mode.rb +125 -0
- data/lib/sup/modes/contact_list_mode.rb +148 -0
- data/lib/sup/modes/edit_message_async_mode.rb +110 -0
- data/lib/sup/modes/edit_message_mode.rb +728 -0
- data/lib/sup/modes/file_browser_mode.rb +109 -0
- data/lib/sup/modes/forward_mode.rb +82 -0
- data/lib/sup/modes/help_mode.rb +19 -0
- data/lib/sup/modes/inbox_mode.rb +85 -0
- data/lib/sup/modes/label_list_mode.rb +138 -0
- data/lib/sup/modes/label_search_results_mode.rb +38 -0
- data/lib/sup/modes/line_cursor_mode.rb +203 -0
- data/lib/sup/modes/log_mode.rb +57 -0
- data/lib/sup/modes/person_search_results_mode.rb +12 -0
- data/lib/sup/modes/poll_mode.rb +19 -0
- data/lib/sup/modes/reply_mode.rb +228 -0
- data/lib/sup/modes/resume_mode.rb +52 -0
- data/lib/sup/modes/scroll_mode.rb +252 -0
- data/lib/sup/modes/search_list_mode.rb +204 -0
- data/lib/sup/modes/search_results_mode.rb +59 -0
- data/lib/sup/modes/text_mode.rb +76 -0
- data/lib/sup/modes/thread_index_mode.rb +1033 -0
- data/lib/sup/modes/thread_view_mode.rb +941 -0
- data/lib/sup/person.rb +134 -0
- data/lib/sup/poll.rb +272 -0
- data/lib/sup/rfc2047.rb +56 -0
- data/lib/sup/search.rb +110 -0
- data/lib/sup/sent.rb +58 -0
- data/lib/sup/service/label_service.rb +45 -0
- data/lib/sup/source.rb +244 -0
- data/lib/sup/tagger.rb +50 -0
- data/lib/sup/textfield.rb +253 -0
- data/lib/sup/thread.rb +452 -0
- data/lib/sup/time.rb +93 -0
- data/lib/sup/undo.rb +38 -0
- data/lib/sup/update.rb +30 -0
- data/lib/sup/util.rb +747 -0
- data/lib/sup/util/ncurses.rb +274 -0
- data/lib/sup/util/path.rb +9 -0
- data/lib/sup/util/query.rb +17 -0
- data/lib/sup/util/uri.rb +15 -0
- data/lib/sup/version.rb +3 -0
- data/sup.gemspec +53 -0
- data/test/dummy_source.rb +61 -0
- data/test/gnupg_test_home/gpg.conf +1 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
- data/test/integration/test_label_service.rb +18 -0
- data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
- data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
- data/test/messages/missing-line.eml +9 -0
- data/test/test_crypto.rb +109 -0
- data/test/test_header_parsing.rb +168 -0
- data/test/test_helper.rb +7 -0
- data/test/test_message.rb +532 -0
- data/test/test_messages_dir.rb +147 -0
- data/test/test_yaml_migration.rb +85 -0
- data/test/test_yaml_regressions.rb +17 -0
- data/test/unit/service/test_label_service.rb +19 -0
- data/test/unit/test_horizontal_selector.rb +40 -0
- data/test/unit/util/test_query.rb +46 -0
- data/test/unit/util/test_string.rb +57 -0
- data/test/unit/util/test_uri.rb +19 -0
- metadata +423 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ResumeMode < EditMessageMode
|
4
|
+
def initialize m
|
5
|
+
@m = m
|
6
|
+
@safe = false
|
7
|
+
|
8
|
+
header, body = parse_file m.draft_filename
|
9
|
+
header.delete "Date"
|
10
|
+
|
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."
|
15
|
+
end
|
16
|
+
|
17
|
+
def unsaved?; !@safe end
|
18
|
+
|
19
|
+
def killable?
|
20
|
+
return true if @safe
|
21
|
+
|
22
|
+
case BufferManager.ask_yes_or_no "Discard draft?"
|
23
|
+
when true
|
24
|
+
DraftManager.discard @m
|
25
|
+
BufferManager.flash "Draft discarded."
|
26
|
+
true
|
27
|
+
when false
|
28
|
+
if edited?
|
29
|
+
DraftManager.write_draft { |f| write_message f, false }
|
30
|
+
DraftManager.discard @m
|
31
|
+
BufferManager.flash "Draft saved."
|
32
|
+
end
|
33
|
+
true
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_message
|
40
|
+
if super
|
41
|
+
DraftManager.discard @m
|
42
|
+
@safe = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_as_draft
|
47
|
+
@safe = true
|
48
|
+
DraftManager.discard @m if super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ScrollMode < Mode
|
4
|
+
## we define topline and botline as the top and bottom lines of any
|
5
|
+
## content in the currentview.
|
6
|
+
|
7
|
+
## we left leftcol and rightcol as the left and right columns of any
|
8
|
+
## content in the current view. but since we're operating in a
|
9
|
+
## line-centric fashion, rightcol is always leftcol + the buffer
|
10
|
+
## width. (whereas botline is topline + at most the buffer height,
|
11
|
+
## and can be == to topline in the case that there's no content.)
|
12
|
+
|
13
|
+
attr_reader :status, :topline, :botline, :leftcol
|
14
|
+
|
15
|
+
register_keymap do |k|
|
16
|
+
k.add :line_down, "Down one line", :down, 'j', 'J', "\C-e"
|
17
|
+
k.add :line_up, "Up one line", :up, 'k', 'K', "\C-y"
|
18
|
+
k.add :col_left, "Left one column", :left, 'h'
|
19
|
+
k.add :col_right, "Right one column", :right, 'l'
|
20
|
+
k.add :page_down, "Down one page", :page_down, ' ', "\C-f"
|
21
|
+
k.add :page_up, "Up one page", :page_up, 'p', :backspace, "\C-b"
|
22
|
+
k.add :half_page_down, "Down one half page", "\C-d"
|
23
|
+
k.add :half_page_up, "Up one half page", "\C-u"
|
24
|
+
k.add :jump_to_start, "Jump to top", :home, '^', '1'
|
25
|
+
k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
|
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
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize opts={}
|
32
|
+
@topline, @botline, @leftcol = 0, 0, 0
|
33
|
+
@slip_rows = opts[:slip_rows] || 0 # when we pgup/pgdown,
|
34
|
+
# how many lines do we keep?
|
35
|
+
@twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
|
36
|
+
@search_query = nil
|
37
|
+
@search_line = nil
|
38
|
+
@status = ""
|
39
|
+
super()
|
40
|
+
end
|
41
|
+
|
42
|
+
def rightcol; @leftcol + buffer.content_width; end
|
43
|
+
|
44
|
+
def draw
|
45
|
+
ensure_mode_validity
|
46
|
+
(@topline ... @botline).each { |ln| draw_line ln, :color => :text_color }
|
47
|
+
((@botline - @topline) ... buffer.content_height).each do |ln|
|
48
|
+
if @twiddles
|
49
|
+
buffer.write ln, 0, "~", :color => :twiddle_color
|
50
|
+
else
|
51
|
+
buffer.write ln, 0, "", :color => :text_color
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@status = "lines #{@topline + 1}:#{@botline}/#{lines}"
|
55
|
+
end
|
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, col = find_text @search_query, start
|
68
|
+
if line.nil? && (start > 0)
|
69
|
+
line, col = 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_pos line, col, col + @search_query.display_length
|
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 three!
|
89
|
+
def search_goto_pos line, leftcol, rightcol
|
90
|
+
search_goto_line line
|
91
|
+
|
92
|
+
if rightcol > self.rightcol # if it's occluded...
|
93
|
+
jump_to_col [rightcol - buffer.content_width + 1, 0].max # move right
|
94
|
+
end
|
95
|
+
end
|
96
|
+
def search_start_line; @topline end
|
97
|
+
def search_goto_line line; jump_to_line line end
|
98
|
+
|
99
|
+
def col_jump
|
100
|
+
$config[:col_jump] || 2
|
101
|
+
end
|
102
|
+
|
103
|
+
def col_left
|
104
|
+
return unless @leftcol > 0
|
105
|
+
@leftcol -= col_jump
|
106
|
+
buffer.mark_dirty
|
107
|
+
end
|
108
|
+
|
109
|
+
def col_right
|
110
|
+
@leftcol += col_jump
|
111
|
+
buffer.mark_dirty
|
112
|
+
end
|
113
|
+
|
114
|
+
def jump_to_col col
|
115
|
+
col = col - (col % col_jump)
|
116
|
+
buffer.mark_dirty unless @leftcol == col
|
117
|
+
@leftcol = col
|
118
|
+
end
|
119
|
+
|
120
|
+
def jump_to_left; jump_to_col 0; end
|
121
|
+
|
122
|
+
## set top line to l
|
123
|
+
def jump_to_line l
|
124
|
+
l = l.clamp 0, lines - 1
|
125
|
+
return if @topline == l
|
126
|
+
@topline = l
|
127
|
+
@botline = [l + buffer.content_height, lines].min
|
128
|
+
buffer.mark_dirty
|
129
|
+
end
|
130
|
+
|
131
|
+
def at_top?; @topline == 0 end
|
132
|
+
def at_bottom?; @botline == lines end
|
133
|
+
|
134
|
+
def line_down; jump_to_line @topline + 1; end
|
135
|
+
def line_up; jump_to_line @topline - 1; end
|
136
|
+
def page_down; jump_to_line @topline + buffer.content_height - @slip_rows; end
|
137
|
+
def page_up; jump_to_line @topline - buffer.content_height + @slip_rows; end
|
138
|
+
def half_page_down; jump_to_line @topline + buffer.content_height / 2; end
|
139
|
+
def half_page_up; jump_to_line @topline - buffer.content_height / 2; end
|
140
|
+
def jump_to_start; jump_to_line 0; end
|
141
|
+
def jump_to_end; jump_to_line lines - buffer.content_height; end
|
142
|
+
|
143
|
+
def ensure_mode_validity
|
144
|
+
@topline = @topline.clamp 0, [lines - 1, 0].max
|
145
|
+
@botline = [@topline + buffer.content_height, lines].min
|
146
|
+
end
|
147
|
+
|
148
|
+
def resize *a
|
149
|
+
super(*a)
|
150
|
+
ensure_mode_validity
|
151
|
+
end
|
152
|
+
|
153
|
+
protected
|
154
|
+
|
155
|
+
def find_text query, start_line
|
156
|
+
regex = /#{query}/i
|
157
|
+
(start_line ... lines).each do |i|
|
158
|
+
case(s = self[i])
|
159
|
+
when String
|
160
|
+
match = s =~ regex
|
161
|
+
return [i, match] if match
|
162
|
+
when Array
|
163
|
+
offset = 0
|
164
|
+
s.each do |color, string|
|
165
|
+
match = string =~ regex
|
166
|
+
if match
|
167
|
+
return [i, offset + match]
|
168
|
+
else
|
169
|
+
offset += string.display_length
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
|
177
|
+
def draw_line ln, opts={}
|
178
|
+
regex = /(#{@search_query})/i
|
179
|
+
case(s = self[ln])
|
180
|
+
when String
|
181
|
+
if in_search?
|
182
|
+
draw_line_from_array ln, matching_text_array(s, regex), opts
|
183
|
+
else
|
184
|
+
draw_line_from_string ln, s, opts
|
185
|
+
end
|
186
|
+
when Array
|
187
|
+
if in_search?
|
188
|
+
## seems like there ought to be a better way of doing this
|
189
|
+
array = []
|
190
|
+
s.each do |color, text|
|
191
|
+
if text =~ regex
|
192
|
+
array += matching_text_array text, regex, color
|
193
|
+
else
|
194
|
+
array << [color, text]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
draw_line_from_array ln, array, opts
|
198
|
+
else
|
199
|
+
draw_line_from_array ln, s, opts
|
200
|
+
end
|
201
|
+
else
|
202
|
+
raise "unknown drawable object: #{s.inspect} in #{self} for line #{ln}" # good for debugging
|
203
|
+
end
|
204
|
+
|
205
|
+
## speed test
|
206
|
+
# str = s.map { |color, text| text }.join
|
207
|
+
# buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
|
208
|
+
# return
|
209
|
+
end
|
210
|
+
|
211
|
+
def matching_text_array s, regex, oldcolor=:text_color
|
212
|
+
s.split(regex).map do |text|
|
213
|
+
next if text.empty?
|
214
|
+
if text =~ regex
|
215
|
+
[:search_highlight_color, text]
|
216
|
+
else
|
217
|
+
[oldcolor, text]
|
218
|
+
end
|
219
|
+
end.compact + [[oldcolor, ""]]
|
220
|
+
end
|
221
|
+
|
222
|
+
def draw_line_from_array ln, a, opts
|
223
|
+
xpos = 0
|
224
|
+
a.each_with_index do |(color, text), i|
|
225
|
+
raise "nil text for color '#{color}'" if text.nil? # good for debugging
|
226
|
+
l = text.display_length
|
227
|
+
no_fill = i != a.size - 1
|
228
|
+
|
229
|
+
if xpos + l < @leftcol
|
230
|
+
buffer.write ln - @topline, 0, "", :color => color,
|
231
|
+
:highlight => opts[:highlight]
|
232
|
+
elsif xpos < @leftcol
|
233
|
+
## partial
|
234
|
+
buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
|
235
|
+
:color => color,
|
236
|
+
:highlight => opts[:highlight], :no_fill => no_fill
|
237
|
+
else
|
238
|
+
buffer.write ln - @topline, xpos - @leftcol, text,
|
239
|
+
:color => color, :highlight => opts[:highlight],
|
240
|
+
:no_fill => no_fill
|
241
|
+
end
|
242
|
+
xpos += l
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def draw_line_from_string ln, s, opts
|
247
|
+
buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight], :color => opts[:color]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class SearchListMode < LineCursorMode
|
4
|
+
register_keymap do |k|
|
5
|
+
k.add :select_search, "Open search results", :enter
|
6
|
+
k.add :reload, "Discard saved search 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 saved searches and those with unread mail", 'u'
|
9
|
+
k.add :delete_selected_search, "Delete selected search", "X"
|
10
|
+
k.add :rename_selected_search, "Rename selected search", "r"
|
11
|
+
k.add :edit_selected_search, "Edit selected search", "e"
|
12
|
+
k.add :add_new_search, "Add new search", "a"
|
13
|
+
end
|
14
|
+
|
15
|
+
HookManager.register "search-list-filter", <<EOS
|
16
|
+
Filter the search list, typically to sort.
|
17
|
+
Variables:
|
18
|
+
counted: an array of counted searches.
|
19
|
+
Return value:
|
20
|
+
An array of counted searches with sort_by output structure.
|
21
|
+
EOS
|
22
|
+
|
23
|
+
HookManager.register "search-list-format", <<EOS
|
24
|
+
Create the sprintf format string for search-list-mode.
|
25
|
+
Variables:
|
26
|
+
n_width: the maximum search name width
|
27
|
+
tmax: the maximum total message count
|
28
|
+
umax: the maximum unread message count
|
29
|
+
s_width: the maximum search string width
|
30
|
+
Return value:
|
31
|
+
A format string for sprintf
|
32
|
+
EOS
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@searches = []
|
36
|
+
@text = []
|
37
|
+
@unread_only = false
|
38
|
+
super
|
39
|
+
UpdateManager.register self
|
40
|
+
regen_text
|
41
|
+
end
|
42
|
+
|
43
|
+
def cleanup
|
44
|
+
UpdateManager.unregister self
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def lines; @text.length end
|
49
|
+
def [] i; @text[i] end
|
50
|
+
|
51
|
+
def jump_to_next_new
|
52
|
+
n = ((curpos + 1) ... lines).find { |i| @searches[i][1] > 0 } || (0 ... curpos).find { |i| @searches[i][1] > 0 }
|
53
|
+
if n
|
54
|
+
## jump there if necessary
|
55
|
+
jump_to_line n unless n >= topline && n < botline
|
56
|
+
set_cursor_pos n
|
57
|
+
else
|
58
|
+
BufferManager.flash "No saved searches with unread messages."
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def focus
|
63
|
+
reload # make sure unread message counts are up-to-date
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_added_update sender, m
|
67
|
+
reload
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def toggle_show_unread_only
|
73
|
+
@unread_only = !@unread_only
|
74
|
+
reload
|
75
|
+
end
|
76
|
+
|
77
|
+
def reload
|
78
|
+
regen_text
|
79
|
+
buffer.mark_dirty if buffer
|
80
|
+
end
|
81
|
+
|
82
|
+
def regen_text
|
83
|
+
@text = []
|
84
|
+
searches = SearchManager.all_searches
|
85
|
+
|
86
|
+
counted = searches.map do |name|
|
87
|
+
search_string = SearchManager.search_string_for name
|
88
|
+
begin
|
89
|
+
if SearchManager.predefined_queries.has_key? search_string
|
90
|
+
query = SearchManager.predefined_queries[search_string]
|
91
|
+
else
|
92
|
+
query = Index.parse_query search_string
|
93
|
+
end
|
94
|
+
total = Index.num_results_for :qobj => query[:qobj]
|
95
|
+
unread = Index.num_results_for :qobj => query[:qobj], :label => :unread
|
96
|
+
rescue Index::ParseError => e
|
97
|
+
BufferManager.flash "Problem: #{e.message}!"
|
98
|
+
total = 0
|
99
|
+
unread = 0
|
100
|
+
end
|
101
|
+
[name, search_string, total, unread]
|
102
|
+
end
|
103
|
+
|
104
|
+
if HookManager.enabled? "search-list-filter"
|
105
|
+
counts = HookManager.run "search-list-filter", :counted => counted
|
106
|
+
else
|
107
|
+
counts = counted.sort_by { |n, s, t, u| n.downcase }
|
108
|
+
end
|
109
|
+
|
110
|
+
n_width = counts.max_of { |n, s, t, u| n.length }
|
111
|
+
tmax = counts.max_of { |n, s, t, u| t }
|
112
|
+
umax = counts.max_of { |n, s, t, u| u }
|
113
|
+
s_width = counts.max_of { |n, s, t, u| s.length }
|
114
|
+
|
115
|
+
if @unread_only
|
116
|
+
counts.delete_if { | n, s, t, u | u == 0 }
|
117
|
+
end
|
118
|
+
|
119
|
+
@searches = []
|
120
|
+
counts.each do |name, search_string, total, unread|
|
121
|
+
fmt = HookManager.run "search-list-format", :n_width => n_width, :tmax => tmax, :umax => umax, :s_width => s_width
|
122
|
+
if !fmt
|
123
|
+
fmt = "%#{n_width + 1}s %5d %s, %5d unread: %s"
|
124
|
+
end
|
125
|
+
@text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
|
126
|
+
sprintf(fmt, name, total, total == 1 ? " message" : "messages", unread, search_string)]]
|
127
|
+
@searches << [name, unread]
|
128
|
+
end
|
129
|
+
|
130
|
+
BufferManager.flash "No saved searches with unread messages!" if counts.empty? && @unread_only
|
131
|
+
end
|
132
|
+
|
133
|
+
def select_search
|
134
|
+
name, num_unread = @searches[curpos]
|
135
|
+
return unless name
|
136
|
+
SearchResultsMode.spawn_from_query SearchManager.search_string_for(name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def delete_selected_search
|
140
|
+
name, num_unread = @searches[curpos]
|
141
|
+
return unless name
|
142
|
+
reload if SearchManager.delete name
|
143
|
+
end
|
144
|
+
|
145
|
+
def rename_selected_search
|
146
|
+
old_name, num_unread = @searches[curpos]
|
147
|
+
return unless old_name
|
148
|
+
|
149
|
+
if SearchManager.predefined_searches.has_key? old_name
|
150
|
+
BufferManager.flash "Cannot be edited: predefined search."
|
151
|
+
return
|
152
|
+
end
|
153
|
+
|
154
|
+
new_name = BufferManager.ask :save_search, "Rename this saved search: ", old_name
|
155
|
+
return unless new_name && new_name !~ /^\s*$/ && new_name != old_name
|
156
|
+
new_name.strip!
|
157
|
+
unless SearchManager.valid_name? new_name
|
158
|
+
BufferManager.flash "Not renamed: " + SearchManager.name_format_hint
|
159
|
+
return
|
160
|
+
end
|
161
|
+
if SearchManager.all_searches.include? new_name
|
162
|
+
BufferManager.flash "Not renamed: \"#{new_name}\" already exists"
|
163
|
+
return
|
164
|
+
end
|
165
|
+
reload if SearchManager.rename old_name, new_name
|
166
|
+
set_cursor_pos @searches.index([new_name, num_unread])||curpos
|
167
|
+
end
|
168
|
+
|
169
|
+
def edit_selected_search
|
170
|
+
name, num_unread = @searches[curpos]
|
171
|
+
return unless name
|
172
|
+
|
173
|
+
if SearchManager.predefined_searches.has_key? name
|
174
|
+
BufferManager.flash "Cannot be edited: predefined search."
|
175
|
+
return
|
176
|
+
end
|
177
|
+
|
178
|
+
old_search_string = SearchManager.search_string_for name
|
179
|
+
new_search_string = BufferManager.ask :search, "Edit this saved search: ", (old_search_string + " ")
|
180
|
+
return unless new_search_string && new_search_string !~ /^\s*$/ && new_search_string != old_search_string
|
181
|
+
reload if SearchManager.edit name, new_search_string.strip
|
182
|
+
set_cursor_pos @searches.index([name, num_unread])||curpos
|
183
|
+
end
|
184
|
+
|
185
|
+
def add_new_search
|
186
|
+
search_string = BufferManager.ask :search, "New search: "
|
187
|
+
return unless search_string && search_string !~ /^\s*$/
|
188
|
+
name = BufferManager.ask :save_search, "Name this search: "
|
189
|
+
return unless name && name !~ /^\s*$/
|
190
|
+
name.strip!
|
191
|
+
unless SearchManager.valid_name? name
|
192
|
+
BufferManager.flash "Not saved: " + SearchManager.name_format_hint
|
193
|
+
return
|
194
|
+
end
|
195
|
+
if SearchManager.all_searches.include? name
|
196
|
+
BufferManager.flash "Not saved: \"#{name}\" already exists"
|
197
|
+
return
|
198
|
+
end
|
199
|
+
reload if SearchManager.add name, search_string.strip
|
200
|
+
set_cursor_pos @searches.index(@searches.assoc(name))||curpos
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|