sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/

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