lib/sup/modes/search_list_mode.rb (6354B) - raw
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