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,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
|