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,109 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module Redwood
|
|
4
|
+
|
|
5
|
+
## meant to be spawned via spawn_modal!
|
|
6
|
+
class FileBrowserMode < LineCursorMode
|
|
7
|
+
RESERVED_ROWS = 1
|
|
8
|
+
|
|
9
|
+
register_keymap do |k|
|
|
10
|
+
k.add :back, "Go back to previous directory", "B"
|
|
11
|
+
k.add :view, "View file", "v"
|
|
12
|
+
k.add :select_file_or_follow_directory, "Select the highlighted file, or follow the directory", :enter
|
|
13
|
+
k.add :reload, "Reload file list", "R"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
bool_reader :done
|
|
17
|
+
attr_reader :value
|
|
18
|
+
|
|
19
|
+
def initialize dir="."
|
|
20
|
+
@dirs = [Pathname.new(dir).realpath]
|
|
21
|
+
@done = false
|
|
22
|
+
@value = nil
|
|
23
|
+
regen_text
|
|
24
|
+
super :skip_top_rows => RESERVED_ROWS
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cwd; @dirs.last end
|
|
28
|
+
def lines; @text.length; end
|
|
29
|
+
def [] i; @text[i]; end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def back
|
|
34
|
+
return if @dirs.size == 1
|
|
35
|
+
@dirs.pop
|
|
36
|
+
reload
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reload
|
|
40
|
+
regen_text
|
|
41
|
+
jump_to_start
|
|
42
|
+
buffer.mark_dirty
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def view
|
|
46
|
+
name, f = @files[curpos - RESERVED_ROWS]
|
|
47
|
+
return unless f && f.file?
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
BufferManager.spawn f.to_s, TextMode.new(f.read.ascii)
|
|
51
|
+
rescue SystemCallError => e
|
|
52
|
+
BufferManager.flash e.message
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def select_file_or_follow_directory
|
|
57
|
+
name, f = @files[curpos - RESERVED_ROWS]
|
|
58
|
+
return unless f
|
|
59
|
+
|
|
60
|
+
if f.directory? && f.to_s != "."
|
|
61
|
+
if f.readable?
|
|
62
|
+
@dirs.push f
|
|
63
|
+
reload
|
|
64
|
+
else
|
|
65
|
+
BufferManager.flash "Permission denied - #{f.realpath}"
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
begin
|
|
69
|
+
@value = f.realpath.to_s
|
|
70
|
+
@done = true
|
|
71
|
+
rescue SystemCallError => e
|
|
72
|
+
BufferManager.flash e.message
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def regen_text
|
|
78
|
+
@files =
|
|
79
|
+
begin
|
|
80
|
+
cwd.entries.sort_by do |f|
|
|
81
|
+
[f.directory? ? 0 : 1, f.basename.to_s]
|
|
82
|
+
end
|
|
83
|
+
rescue SystemCallError => e
|
|
84
|
+
BufferManager.flash "Error: #{e.message}"
|
|
85
|
+
[Pathname.new("."), Pathname.new("..")]
|
|
86
|
+
end.map do |f|
|
|
87
|
+
real_f = cwd + f
|
|
88
|
+
name = f.basename.to_s +
|
|
89
|
+
case
|
|
90
|
+
when real_f.symlink?
|
|
91
|
+
"@"
|
|
92
|
+
when real_f.directory?
|
|
93
|
+
"/"
|
|
94
|
+
else
|
|
95
|
+
""
|
|
96
|
+
end
|
|
97
|
+
[name, real_f]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
size_width = @files.max_of { |name, f| f.human_size.length }
|
|
101
|
+
time_width = @files.max_of { |name, f| f.human_time.length }
|
|
102
|
+
|
|
103
|
+
@text = ["#{cwd}:"] + @files.map do |name, f|
|
|
104
|
+
sprintf "%#{time_width}s %#{size_width}s %s", f.human_time, f.human_size, name
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Redwood
|
|
2
|
+
|
|
3
|
+
class ForwardMode < EditMessageMode
|
|
4
|
+
## TODO: share some of this with reply-mode
|
|
5
|
+
def initialize opts={}
|
|
6
|
+
header = {
|
|
7
|
+
"From" => AccountManager.default_account.full_address,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@m = opts[:message]
|
|
11
|
+
header["Subject"] =
|
|
12
|
+
if @m
|
|
13
|
+
"Fwd: " + @m.subj
|
|
14
|
+
elsif opts[:attachments]
|
|
15
|
+
"Fwd: " + opts[:attachments].keys.join(", ")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
header["To"] = opts[:to].map { |p| p.full_address }.join(", ") if opts[:to]
|
|
19
|
+
header["Cc"] = opts[:cc].map { |p| p.full_address }.join(", ") if opts[:cc]
|
|
20
|
+
header["Bcc"] = opts[:bcc].map { |p| p.full_address }.join(", ") if opts[:bcc]
|
|
21
|
+
|
|
22
|
+
body =
|
|
23
|
+
if @m
|
|
24
|
+
forward_body_lines @m
|
|
25
|
+
elsif opts[:attachments]
|
|
26
|
+
["Note: #{opts[:attachments].size.pluralize 'attachment'}."]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
super :header => header, :body => body, :attachments => opts[:attachments]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.spawn_nicely opts={}
|
|
33
|
+
to = opts[:to] || (BufferManager.ask_for_contacts(:people, "To: ") or return if ($config[:ask_for_to] != false))
|
|
34
|
+
cc = opts[:cc] || (BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc])
|
|
35
|
+
bcc = opts[:bcc] || (BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc])
|
|
36
|
+
|
|
37
|
+
attachment_hash = {}
|
|
38
|
+
attachments = opts[:attachments] || []
|
|
39
|
+
|
|
40
|
+
if(m = opts[:message])
|
|
41
|
+
m.load_from_source! # read the full message in. you know, maybe i should just make Message#chunks do this....
|
|
42
|
+
attachments += m.chunks.select { |c| c.is_a?(Chunk::Attachment) && !c.quotable? }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attachments.each do |c|
|
|
46
|
+
mime_type = MIME::Types[c.content_type].first || MIME::Types["application/octet-stream"].first
|
|
47
|
+
attachment_hash[c.filename] = RMail::Message.make_attachment c.raw_content, mime_type.content_type, mime_type.encoding, c.filename
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
mode = ForwardMode.new :message => opts[:message], :to => to, :cc => cc, :bcc => bcc, :attachments => attachment_hash
|
|
51
|
+
|
|
52
|
+
title = "Forwarding " +
|
|
53
|
+
if opts[:message]
|
|
54
|
+
opts[:message].subj
|
|
55
|
+
elsif attachments
|
|
56
|
+
attachment_hash.keys.join(", ")
|
|
57
|
+
else
|
|
58
|
+
"something"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
BufferManager.spawn title, mode
|
|
62
|
+
mode.default_edit_message
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
protected
|
|
66
|
+
|
|
67
|
+
def forward_body_lines m
|
|
68
|
+
["--- Begin forwarded message from #{m.from.mediumname} ---"] +
|
|
69
|
+
m.quotable_header_lines + [""] + m.quotable_body_lines +
|
|
70
|
+
["--- End forwarded message ---"]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def send_message
|
|
74
|
+
return unless super # super returns true if the mail has been sent
|
|
75
|
+
if @m
|
|
76
|
+
@m.add_label :forwarded
|
|
77
|
+
Index.save_message @m
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Redwood
|
|
2
|
+
|
|
3
|
+
class HelpMode < TextMode
|
|
4
|
+
def initialize mode, global_keymap
|
|
5
|
+
title = "Help for #{mode.name}"
|
|
6
|
+
super <<EOS
|
|
7
|
+
#{title}
|
|
8
|
+
#{'=' * title.length}
|
|
9
|
+
|
|
10
|
+
#{mode.help_text}
|
|
11
|
+
Global keybindings
|
|
12
|
+
------------------
|
|
13
|
+
#{global_keymap.help_text}
|
|
14
|
+
EOS
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require "sup/modes/thread_index_mode"
|
|
2
|
+
|
|
3
|
+
module Redwood
|
|
4
|
+
|
|
5
|
+
class InboxMode < ThreadIndexMode
|
|
6
|
+
register_keymap do |k|
|
|
7
|
+
## overwrite toggle_archived with archive
|
|
8
|
+
k.add :archive, "Archive thread (remove from inbox)", 'a'
|
|
9
|
+
k.add :refine_search, "Refine search", '|'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
super [:inbox, :sent, :draft], { :label => :inbox, :skip_killed => true }
|
|
14
|
+
raise "can't have more than one!" if defined? @@instance
|
|
15
|
+
@@instance = self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def is_relevant? m; (m.labels & [:spam, :deleted, :killed, :inbox]) == Set.new([:inbox]) end
|
|
19
|
+
|
|
20
|
+
def refine_search
|
|
21
|
+
text = BufferManager.ask :search, "refine inbox with query: "
|
|
22
|
+
return unless text && text !~ /^\s*$/
|
|
23
|
+
text = "label:inbox -label:spam -label:deleted " + text
|
|
24
|
+
SearchResultsMode.spawn_from_query text
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
## label-list-mode wants to be able to raise us if the user selects
|
|
28
|
+
## the "inbox" label, so we need to keep our singletonness around
|
|
29
|
+
def self.instance; @@instance; end
|
|
30
|
+
def killable?; false; end
|
|
31
|
+
|
|
32
|
+
def archive
|
|
33
|
+
return unless cursor_thread
|
|
34
|
+
thread = cursor_thread # to make sure lambda only knows about 'old' cursor_thread
|
|
35
|
+
|
|
36
|
+
UndoManager.register "archiving thread" do
|
|
37
|
+
thread.apply_label :inbox
|
|
38
|
+
add_or_unhide thread.first
|
|
39
|
+
Index.save_thread thread
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
cursor_thread.remove_label :inbox
|
|
43
|
+
hide_thread cursor_thread
|
|
44
|
+
regen_text
|
|
45
|
+
Index.save_thread thread
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def multi_archive threads
|
|
49
|
+
UndoManager.register "archiving #{threads.size.pluralize 'thread'}" do
|
|
50
|
+
threads.map do |t|
|
|
51
|
+
t.apply_label :inbox
|
|
52
|
+
add_or_unhide t.first
|
|
53
|
+
Index.save_thread t
|
|
54
|
+
end
|
|
55
|
+
regen_text
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
threads.each do |t|
|
|
59
|
+
t.remove_label :inbox
|
|
60
|
+
hide_thread t
|
|
61
|
+
end
|
|
62
|
+
regen_text
|
|
63
|
+
threads.each { |t| Index.save_thread t }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_unarchived_update sender, m
|
|
67
|
+
add_or_unhide m
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_archived_update sender, m
|
|
71
|
+
t = thread_containing(m) or return
|
|
72
|
+
hide_thread t
|
|
73
|
+
regen_text
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def handle_idle_update sender, idle_since
|
|
77
|
+
flush_index
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def status
|
|
81
|
+
super + " #{Index.size} messages in index"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Redwood
|
|
2
|
+
|
|
3
|
+
class LabelListMode < LineCursorMode
|
|
4
|
+
register_keymap do |k|
|
|
5
|
+
k.add :select_label, "Search by label", :enter
|
|
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'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
HookManager.register "label-list-filter", <<EOS
|
|
12
|
+
Filter the label list, typically to sort.
|
|
13
|
+
Variables:
|
|
14
|
+
counted: an array of counted labels.
|
|
15
|
+
Return value:
|
|
16
|
+
An array of counted labels with sort_by output structure.
|
|
17
|
+
EOS
|
|
18
|
+
|
|
19
|
+
HookManager.register "label-list-format", <<EOS
|
|
20
|
+
Create the sprintf format string for label-list-mode.
|
|
21
|
+
Variables:
|
|
22
|
+
width: the maximum label width
|
|
23
|
+
tmax: the maximum total message count
|
|
24
|
+
umax: the maximum unread message count
|
|
25
|
+
Return value:
|
|
26
|
+
A format string for sprintf
|
|
27
|
+
EOS
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
@labels = []
|
|
31
|
+
@text = []
|
|
32
|
+
@unread_only = false
|
|
33
|
+
super
|
|
34
|
+
UpdateManager.register self
|
|
35
|
+
regen_text
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def cleanup
|
|
39
|
+
UpdateManager.unregister self
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def lines; @text.length end
|
|
44
|
+
def [] i; @text[i] end
|
|
45
|
+
|
|
46
|
+
def jump_to_next_new
|
|
47
|
+
n = ((curpos + 1) ... lines).find { |i| @labels[i][1] > 0 } || (0 ... curpos).find { |i| @labels[i][1] > 0 }
|
|
48
|
+
if n
|
|
49
|
+
## jump there if necessary
|
|
50
|
+
jump_to_line n unless n >= topline && n < botline
|
|
51
|
+
set_cursor_pos n
|
|
52
|
+
else
|
|
53
|
+
BufferManager.flash "No labels messages with unread messages."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def focus
|
|
58
|
+
reload # make sure unread message counts are up-to-date
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def handle_added_update sender, m
|
|
62
|
+
reload
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
protected
|
|
66
|
+
|
|
67
|
+
def toggle_show_unread_only
|
|
68
|
+
@unread_only = !@unread_only
|
|
69
|
+
reload
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def reload
|
|
73
|
+
regen_text
|
|
74
|
+
buffer.mark_dirty if buffer
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def regen_text
|
|
78
|
+
@text = []
|
|
79
|
+
labels = LabelManager.all_labels
|
|
80
|
+
|
|
81
|
+
counted = labels.map do |label|
|
|
82
|
+
string = LabelManager.string_for label
|
|
83
|
+
total = Index.num_results_for :label => label
|
|
84
|
+
unread = (label == :unread)? total : Index.num_results_for(:labels => [label, :unread])
|
|
85
|
+
[label, string, total, unread]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if HookManager.enabled? "label-list-filter"
|
|
89
|
+
counts = HookManager.run "label-list-filter", :counted => counted
|
|
90
|
+
else
|
|
91
|
+
counts = counted.sort_by { |l, s, t, u| s.downcase }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
width = counts.max_of { |l, s, t, u| s.length }
|
|
95
|
+
tmax = counts.max_of { |l, s, t, u| t }
|
|
96
|
+
umax = counts.max_of { |l, s, t, u| u }
|
|
97
|
+
|
|
98
|
+
if @unread_only
|
|
99
|
+
counts.delete_if { | l, s, t, u | u == 0 }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@labels = []
|
|
103
|
+
counts.map do |label, string, total, unread|
|
|
104
|
+
## if we've done a search and there are no messages for this label, we can delete it from the
|
|
105
|
+
## list. BUT if it's a brand-new label, the user may not have sync'ed it to the index yet, so
|
|
106
|
+
## don't delete it in this case.
|
|
107
|
+
##
|
|
108
|
+
## this is all a hack. what should happen is:
|
|
109
|
+
## TODO make the labelmanager responsible for label counts
|
|
110
|
+
## and then it can listen to labeled and unlabeled events, etc.
|
|
111
|
+
if total == 0 && !LabelManager::RESERVED_LABELS.include?(label) && !LabelManager.new_label?(label)
|
|
112
|
+
debug "no hits for label #{label}, deleting"
|
|
113
|
+
LabelManager.delete label
|
|
114
|
+
next
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
fmt = HookManager.run "label-list-format", :width => width, :tmax => tmax, :umax => umax
|
|
118
|
+
if !fmt
|
|
119
|
+
fmt = "%#{width + 1}s %5d %s, %5d unread"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
|
|
123
|
+
sprintf(fmt, string, total, total == 1 ? " message" : "messages", unread)]]
|
|
124
|
+
@labels << [label, unread]
|
|
125
|
+
yield i if block_given?
|
|
126
|
+
end.compact
|
|
127
|
+
|
|
128
|
+
BufferManager.flash "No labels with unread messages!" if counts.empty? && @unread_only
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def select_label
|
|
132
|
+
label, num_unread = @labels[curpos]
|
|
133
|
+
return unless label
|
|
134
|
+
LabelSearchResultsMode.spawn_nicely label
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Redwood
|
|
2
|
+
|
|
3
|
+
class LabelSearchResultsMode < ThreadIndexMode
|
|
4
|
+
def initialize labels
|
|
5
|
+
@labels = labels
|
|
6
|
+
opts = { :labels => @labels }
|
|
7
|
+
opts[:load_deleted] = true if labels.include? :deleted
|
|
8
|
+
opts[:load_spam] = true if labels.include? :spam
|
|
9
|
+
super [], opts
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
register_keymap do |k|
|
|
13
|
+
k.add :refine_search, "Refine search", '|'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def refine_search
|
|
17
|
+
label_query = @labels.size > 1 ? "(#{@labels.join('||')})" : @labels.first
|
|
18
|
+
query = BufferManager.ask :search, "refine query: ", "+label:#{label_query} "
|
|
19
|
+
return unless query && query !~ /^\s*$/
|
|
20
|
+
SearchResultsMode.spawn_from_query query
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def is_relevant? m; @labels.all? { |l| m.has_label? l } end
|
|
24
|
+
|
|
25
|
+
def self.spawn_nicely label
|
|
26
|
+
label = LabelManager.label_for(label) unless label.is_a?(Symbol)
|
|
27
|
+
case label
|
|
28
|
+
when nil
|
|
29
|
+
when :inbox
|
|
30
|
+
BufferManager.raise_to_front InboxMode.instance.buffer
|
|
31
|
+
else
|
|
32
|
+
b, new = BufferManager.spawn_unless_exists("All threads with label '#{label}'") { LabelSearchResultsMode.new [label] }
|
|
33
|
+
b.mode.load_threads :num => b.content_height if new
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|