sup 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|