sup 0.8.1 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/CONTRIBUTORS +13 -6
- data/History.txt +19 -0
- data/ReleaseNotes +35 -0
- data/bin/sup +82 -77
- data/bin/sup-add +7 -7
- data/bin/sup-config +104 -85
- data/bin/sup-dump +4 -5
- data/bin/sup-recover-sources +9 -10
- data/bin/sup-sync +121 -100
- data/bin/sup-sync-back +18 -15
- data/bin/sup-tweak-labels +24 -21
- data/lib/sup.rb +53 -33
- data/lib/sup/account.rb +0 -2
- data/lib/sup/buffer.rb +47 -22
- data/lib/sup/colormap.rb +6 -6
- data/lib/sup/contact.rb +0 -2
- data/lib/sup/crypto.rb +34 -23
- data/lib/sup/draft.rb +6 -14
- data/lib/sup/ferret_index.rb +471 -0
- data/lib/sup/hook.rb +30 -43
- data/lib/sup/hook.rb.BACKUP.8625.rb +158 -0
- data/lib/sup/hook.rb.BACKUP.8681.rb +158 -0
- data/lib/sup/hook.rb.BASE.8625.rb +155 -0
- data/lib/sup/hook.rb.BASE.8681.rb +155 -0
- data/lib/sup/hook.rb.LOCAL.8625.rb +142 -0
- data/lib/sup/hook.rb.LOCAL.8681.rb +142 -0
- data/lib/sup/hook.rb.REMOTE.8625.rb +145 -0
- data/lib/sup/hook.rb.REMOTE.8681.rb +145 -0
- data/lib/sup/imap.rb +18 -8
- data/lib/sup/index.rb +70 -528
- data/lib/sup/interactive-lock.rb +74 -0
- data/lib/sup/keymap.rb +26 -26
- data/lib/sup/label.rb +2 -4
- data/lib/sup/logger.rb +54 -35
- data/lib/sup/maildir.rb +41 -6
- data/lib/sup/mbox.rb +1 -1
- data/lib/sup/mbox/loader.rb +18 -6
- data/lib/sup/mbox/ssh-file.rb +1 -7
- data/lib/sup/message-chunks.rb +36 -23
- data/lib/sup/message.rb +126 -46
- data/lib/sup/mode.rb +3 -2
- data/lib/sup/modes/console-mode.rb +108 -0
- data/lib/sup/modes/edit-message-mode.rb +15 -5
- data/lib/sup/modes/inbox-mode.rb +2 -4
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/line-cursor-mode.rb +18 -18
- data/lib/sup/modes/log-mode.rb +29 -16
- data/lib/sup/modes/poll-mode.rb +7 -9
- data/lib/sup/modes/reply-mode.rb +5 -3
- data/lib/sup/modes/scroll-mode.rb +2 -2
- data/lib/sup/modes/search-results-mode.rb +9 -11
- data/lib/sup/modes/text-mode.rb +2 -2
- data/lib/sup/modes/thread-index-mode.rb +26 -16
- data/lib/sup/modes/thread-view-mode.rb +84 -39
- data/lib/sup/person.rb +6 -8
- data/lib/sup/poll.rb +46 -47
- data/lib/sup/rfc2047.rb +1 -5
- data/lib/sup/sent.rb +27 -20
- data/lib/sup/source.rb +90 -13
- data/lib/sup/textfield.rb +4 -4
- data/lib/sup/thread.rb +15 -13
- data/lib/sup/undo.rb +0 -1
- data/lib/sup/update.rb +0 -1
- data/lib/sup/util.rb +51 -43
- data/lib/sup/xapian_index.rb +566 -0
- metadata +57 -46
- data/lib/sup/suicide.rb +0 -36
data/lib/sup/mode.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'open3'
|
1
2
|
module Redwood
|
2
3
|
|
3
4
|
class Mode
|
@@ -92,10 +93,10 @@ EOS
|
|
92
93
|
unless err.empty?
|
93
94
|
message = err.first.read
|
94
95
|
if message =~ /^\s*$/
|
95
|
-
|
96
|
+
warn "error running #{command} (but no error message)"
|
96
97
|
BufferManager.flash "Error running #{command}!"
|
97
98
|
else
|
98
|
-
|
99
|
+
warn "error running #{command}: #{message}"
|
99
100
|
BufferManager.flash "Error: #{message}"
|
100
101
|
end
|
101
102
|
return
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module Redwood
|
4
|
+
|
5
|
+
class Console
|
6
|
+
def initialize mode
|
7
|
+
@mode = mode
|
8
|
+
end
|
9
|
+
|
10
|
+
def query(query)
|
11
|
+
Enumerable::Enumerator.new(Index, :each_message, Index.parse_query(query))
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_labels(query, *labels)
|
15
|
+
query(query).each { |m| m.labels += labels; m.save Index }
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_labels(query, *labels)
|
19
|
+
query(query).each { |m| m.labels -= labels; m.save Index }
|
20
|
+
end
|
21
|
+
|
22
|
+
def xapian; Index.instance.instance_variable_get :@xapian; end
|
23
|
+
def ferret; Index.instance.instance_variable_get :@index; end
|
24
|
+
def special_methods; methods - Object.methods end
|
25
|
+
|
26
|
+
## files that won't cause problems when reloaded
|
27
|
+
## TODO expand this list / convert to blacklist
|
28
|
+
RELOAD_WHITELIST = %w(sup/xapian_index.rb sup/modes/console-mode.rb)
|
29
|
+
|
30
|
+
def reload
|
31
|
+
old_verbose = $VERBOSE
|
32
|
+
$VERBOSE = nil
|
33
|
+
old_features = $".dup
|
34
|
+
begin
|
35
|
+
fs = $".grep(/^sup\//)
|
36
|
+
fs.reject! { |f| not RELOAD_WHITELIST.member? f }
|
37
|
+
fs.each { |f| $".delete f }
|
38
|
+
fs.each do |f|
|
39
|
+
@mode << "reloading #{f}\n"
|
40
|
+
begin
|
41
|
+
require f
|
42
|
+
rescue LoadError => e
|
43
|
+
raise unless e.message =~ /no such file to load/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue Exception
|
47
|
+
$".clear
|
48
|
+
$".concat old_features
|
49
|
+
raise
|
50
|
+
ensure
|
51
|
+
$VERBOSE = old_verbose
|
52
|
+
end
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear_hooks
|
57
|
+
HookManager.clear
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ConsoleMode < LogMode
|
63
|
+
register_keymap do |k|
|
64
|
+
k.add :run, "Restart evaluation", 'e'
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
super
|
69
|
+
@console = Console.new self
|
70
|
+
@binding = @console.instance_eval { binding }
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute cmd
|
74
|
+
begin
|
75
|
+
self << ">> #{cmd}\n"
|
76
|
+
ret = eval cmd, @binding
|
77
|
+
self << "=> #{ret.pretty_inspect}\n"
|
78
|
+
rescue Exception
|
79
|
+
self << "#{$!.class}: #{$!.message}\n"
|
80
|
+
clean_backtrace = []
|
81
|
+
$!.backtrace.each { |l| break if l =~ /console-mode/; clean_backtrace << l }
|
82
|
+
clean_backtrace.each { |l| self << "#{l}\n" }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def prompt
|
87
|
+
BufferManager.ask :console, ">> "
|
88
|
+
end
|
89
|
+
|
90
|
+
def run
|
91
|
+
self << <<EOS
|
92
|
+
Sup v#{VERSION} console session started.
|
93
|
+
Available extra commands: #{(@console.special_methods) * ", "}
|
94
|
+
Ctrl-G stops evaluation; 'e' restarts it.
|
95
|
+
|
96
|
+
EOS
|
97
|
+
while true
|
98
|
+
if(cmd = prompt)
|
99
|
+
execute cmd
|
100
|
+
else
|
101
|
+
self << "Console session ended."
|
102
|
+
break
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -2,7 +2,10 @@ require 'tempfile'
|
|
2
2
|
require 'socket' # just for gethostname!
|
3
3
|
require 'pathname'
|
4
4
|
require 'rmail'
|
5
|
-
|
5
|
+
|
6
|
+
# from jcode.rb, not included in ruby 1.9
|
7
|
+
PATTERN_UTF8 = '[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf]'
|
8
|
+
RE_UTF8 = Regexp.new(PATTERN_UTF8, 0, 'n')
|
6
9
|
|
7
10
|
module Redwood
|
8
11
|
|
@@ -70,7 +73,14 @@ EOS
|
|
70
73
|
@attachment_names = []
|
71
74
|
end
|
72
75
|
|
73
|
-
|
76
|
+
begin
|
77
|
+
hostname = File.open("/etc/mailname", "r").gets.chomp
|
78
|
+
rescue
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
hostname = Socket.gethostname if hostname.nil? or hostname.empty?
|
82
|
+
|
83
|
+
@message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{hostname}>"
|
74
84
|
@edited = false
|
75
85
|
@selectors = []
|
76
86
|
@selector_label_width = 0
|
@@ -181,7 +191,7 @@ protected
|
|
181
191
|
end
|
182
192
|
|
183
193
|
def mime_encode_subject string
|
184
|
-
return string unless string.match(
|
194
|
+
return string unless string.match(RE_UTF8)
|
185
195
|
mime_encode string
|
186
196
|
end
|
187
197
|
|
@@ -190,7 +200,7 @@ protected
|
|
190
200
|
# Encode "bælammet mitt <user@example.com>" into
|
191
201
|
# "=?utf-8?q?b=C3=A6lammet_mitt?= <user@example.com>
|
192
202
|
def mime_encode_address string
|
193
|
-
return string unless string.match(
|
203
|
+
return string unless string.match(RE_UTF8)
|
194
204
|
string.sub(RE_ADDRESS) { |match| mime_encode($1) + $2 }
|
195
205
|
end
|
196
206
|
|
@@ -315,7 +325,7 @@ protected
|
|
315
325
|
BufferManager.flash "Message sent!"
|
316
326
|
true
|
317
327
|
rescue SystemCallError, SendmailCommandFailed, CryptoManager::Error => e
|
318
|
-
|
328
|
+
warn "Problem sending mail: #{e.message}"
|
319
329
|
BufferManager.flash "Problem sending mail: #{e.message}"
|
320
330
|
false
|
321
331
|
end
|
data/lib/sup/modes/inbox-mode.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'sup'
|
2
2
|
|
3
3
|
module Redwood
|
4
4
|
|
@@ -15,9 +15,7 @@ class InboxMode < ThreadIndexMode
|
|
15
15
|
@@instance = self
|
16
16
|
end
|
17
17
|
|
18
|
-
def is_relevant? m
|
19
|
-
m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
|
20
|
-
end
|
18
|
+
def is_relevant? m; (m.labels & [:spam, :deleted, :killed, :inbox]) == Set.new([:inbox]) end
|
21
19
|
|
22
20
|
## label-list-mode wants to be able to raise us if the user selects
|
23
21
|
## the "inbox" label, so we need to keep our singletonness around
|
@@ -73,7 +73,7 @@ protected
|
|
73
73
|
## TODO make the labelmanager responsible for label counts
|
74
74
|
## and then it can listen to labeled and unlabeled events, etc.
|
75
75
|
if total == 0 && !LabelManager::RESERVED_LABELS.include?(label) && !LabelManager.new_label?(label)
|
76
|
-
|
76
|
+
debug "no hits for label #{label}, deleting"
|
77
77
|
LabelManager.delete label
|
78
78
|
next
|
79
79
|
end
|
@@ -15,11 +15,24 @@ class LineCursorMode < ScrollMode
|
|
15
15
|
def initialize opts={}
|
16
16
|
@cursor_top = @curpos = opts.delete(:skip_top_rows) || 0
|
17
17
|
@load_more_callbacks = []
|
18
|
-
@
|
19
|
-
@
|
18
|
+
@load_more_q = Queue.new
|
19
|
+
@load_more_thread = ::Thread.new do
|
20
|
+
while true
|
21
|
+
e = @load_more_q.pop
|
22
|
+
@load_more_callbacks.each { |c| c.call e }
|
23
|
+
sleep 0.5
|
24
|
+
@load_more_q.pop until @load_more_q.empty?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
20
28
|
super opts
|
21
29
|
end
|
22
30
|
|
31
|
+
def cleanup
|
32
|
+
@load_more_thread.kill
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
23
36
|
def draw
|
24
37
|
super
|
25
38
|
set_status
|
@@ -77,7 +90,7 @@ protected
|
|
77
90
|
end
|
78
91
|
|
79
92
|
def cursor_down
|
80
|
-
call_load_more_callbacks buffer.content_height if @curpos
|
93
|
+
call_load_more_callbacks buffer.content_height if @curpos >= lines - [buffer.content_height/2,1].max
|
81
94
|
return false unless @curpos < lines - 1
|
82
95
|
|
83
96
|
if @curpos >= botline - 1
|
@@ -163,21 +176,8 @@ private
|
|
163
176
|
end
|
164
177
|
|
165
178
|
def call_load_more_callbacks size
|
166
|
-
|
167
|
-
|
168
|
-
if @load_more_callbacks_active
|
169
|
-
false
|
170
|
-
else
|
171
|
-
@load_more_callbacks_active = true
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
return unless go
|
176
|
-
|
177
|
-
@load_more_callbacks.each { |c| c.call size }
|
178
|
-
@load_more_callbacks_active = false
|
179
|
-
end
|
180
|
-
|
179
|
+
@load_more_q.push size
|
180
|
+
end
|
181
181
|
end
|
182
182
|
|
183
183
|
end
|
data/lib/sup/modes/log-mode.rb
CHANGED
@@ -1,36 +1,43 @@
|
|
1
|
+
require 'stringio'
|
1
2
|
module Redwood
|
2
3
|
|
4
|
+
## a variant of text mode that allows the user to automatically follow text,
|
5
|
+
## and respawns when << is called if necessary.
|
6
|
+
|
3
7
|
class LogMode < TextMode
|
4
8
|
register_keymap do |k|
|
5
9
|
k.add :toggle_follow, "Toggle follow mode", 'f'
|
6
10
|
end
|
7
11
|
|
8
|
-
|
12
|
+
## if buffer_name is supplied, this mode will spawn a buffer
|
13
|
+
## upon receiving the << message. otherwise, it will act like
|
14
|
+
## a regular buffer.
|
15
|
+
def initialize autospawn_buffer_name=nil
|
9
16
|
@follow = true
|
10
|
-
|
17
|
+
@autospawn_buffer_name = autospawn_buffer_name
|
18
|
+
@on_kill = []
|
19
|
+
super()
|
11
20
|
end
|
12
21
|
|
22
|
+
## register callbacks for when the buffer is killed
|
23
|
+
def on_kill &b; @on_kill << b end
|
24
|
+
|
13
25
|
def toggle_follow
|
14
26
|
@follow = !@follow
|
15
|
-
if
|
16
|
-
|
17
|
-
jump_to_line lines - buffer.content_height + 1 # leave an empty line at bottom
|
18
|
-
end
|
19
|
-
buffer.mark_dirty
|
27
|
+
if @follow
|
28
|
+
jump_to_line(lines - buffer.content_height + 1) # leave an empty line at bottom
|
20
29
|
end
|
30
|
+
buffer.mark_dirty
|
21
31
|
end
|
22
32
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
follow_top = lines - buffer.content_height + 1
|
27
|
-
jump_to_line follow_top if topline < follow_top
|
33
|
+
def << s
|
34
|
+
if buffer.nil? && @autospawn_buffer_name
|
35
|
+
BufferManager.spawn @autospawn_buffer_name, self, :hidden => true, :system => true
|
28
36
|
end
|
29
|
-
end
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
if
|
38
|
+
s.split("\n").each { |l| super(l + "\n") } # insane. different << semantics.
|
39
|
+
|
40
|
+
if @follow
|
34
41
|
follow_top = lines - buffer.content_height + 1
|
35
42
|
jump_to_line follow_top if topline < follow_top
|
36
43
|
end
|
@@ -39,6 +46,12 @@ class LogMode < TextMode
|
|
39
46
|
def status
|
40
47
|
super + " (follow: #@follow)"
|
41
48
|
end
|
49
|
+
|
50
|
+
def cleanup
|
51
|
+
@on_kill.each { |cb| cb.call self }
|
52
|
+
self.text = ""
|
53
|
+
super
|
54
|
+
end
|
42
55
|
end
|
43
56
|
|
44
57
|
end
|
data/lib/sup/modes/poll-mode.rb
CHANGED
@@ -3,18 +3,16 @@ module Redwood
|
|
3
3
|
class PollMode < LogMode
|
4
4
|
def initialize
|
5
5
|
@new = true
|
6
|
-
super
|
7
|
-
end
|
8
|
-
|
9
|
-
def puts s=""
|
10
|
-
self << s + "\n"
|
6
|
+
super "poll for new messages"
|
11
7
|
end
|
12
8
|
|
13
9
|
def poll
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
unless @new
|
11
|
+
@new = false
|
12
|
+
self << "\n"
|
13
|
+
end
|
14
|
+
self << "Poll started at #{Time.now}\n"
|
15
|
+
PollManager.do_poll { |s| self << (s + "\n") }
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
data/lib/sup/modes/reply-mode.rb
CHANGED
@@ -40,7 +40,7 @@ Return value:
|
|
40
40
|
The reply mode you desire, or nil to use the default behavior.
|
41
41
|
EOS
|
42
42
|
|
43
|
-
def initialize message
|
43
|
+
def initialize message, type_arg=nil
|
44
44
|
@m = message
|
45
45
|
|
46
46
|
## it's important to put this early because it forces a read of
|
@@ -56,7 +56,7 @@ EOS
|
|
56
56
|
## don't check that it's an Account, though; assume they know what they're
|
57
57
|
## doing.
|
58
58
|
if hook_reply_from && !(hook_reply_from.is_a? Person)
|
59
|
-
|
59
|
+
info "reply-from returned non-Person, using default from."
|
60
60
|
hook_reply_from = nil
|
61
61
|
end
|
62
62
|
|
@@ -138,7 +138,9 @@ EOS
|
|
138
138
|
hook_reply = HookManager.run "reply-to", :modes => types
|
139
139
|
|
140
140
|
@type_selector.set_to(
|
141
|
-
if types.include?
|
141
|
+
if types.include? type_arg
|
142
|
+
type_arg
|
143
|
+
elsif types.include? hook_reply
|
142
144
|
hook_reply
|
143
145
|
elsif @m.is_list_message?
|
144
146
|
:list
|
@@ -3,7 +3,7 @@ module Redwood
|
|
3
3
|
class ScrollMode < Mode
|
4
4
|
## we define topline and botline as the top and bottom lines of any
|
5
5
|
## content in the currentview.
|
6
|
-
|
6
|
+
|
7
7
|
## we left leftcol and rightcol as the left and right columns of any
|
8
8
|
## content in the current view. but since we're operating in a
|
9
9
|
## line-centric fashion, rightcol is always leftcol + the buffer
|
@@ -223,7 +223,7 @@ protected
|
|
223
223
|
raise "nil text for color '#{color}'" if text.nil? # good for debugging
|
224
224
|
l = text.display_length
|
225
225
|
no_fill = i != a.size - 1
|
226
|
-
|
226
|
+
|
227
227
|
if xpos + l < @leftcol
|
228
228
|
buffer.write ln - @topline, 0, "", :color => color,
|
229
229
|
:highlight => opts[:highlight]
|
@@ -1,11 +1,9 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class SearchResultsMode < ThreadIndexMode
|
4
|
-
def initialize
|
5
|
-
@
|
6
|
-
|
7
|
-
|
8
|
-
super [], { :qobj => @qobj }.merge(@qopts)
|
4
|
+
def initialize query
|
5
|
+
@query = query
|
6
|
+
super [], query
|
9
7
|
end
|
10
8
|
|
11
9
|
register_keymap do |k|
|
@@ -13,9 +11,9 @@ class SearchResultsMode < ThreadIndexMode
|
|
13
11
|
end
|
14
12
|
|
15
13
|
def refine_search
|
16
|
-
|
17
|
-
return unless
|
18
|
-
SearchResultsMode.spawn_from_query
|
14
|
+
text = BufferManager.ask :search, "refine query: ", (@query[:text] + " ")
|
15
|
+
return unless text && text !~ /^\s*$/
|
16
|
+
SearchResultsMode.spawn_from_query text
|
19
17
|
end
|
20
18
|
|
21
19
|
## a proper is_relevant? method requires some way of asking ferret
|
@@ -26,10 +24,10 @@ class SearchResultsMode < ThreadIndexMode
|
|
26
24
|
|
27
25
|
def self.spawn_from_query text
|
28
26
|
begin
|
29
|
-
|
30
|
-
return unless
|
27
|
+
query = Index.parse_query(text)
|
28
|
+
return unless query
|
31
29
|
short_text = text.length < 20 ? text : text[0 ... 20] + "..."
|
32
|
-
mode = SearchResultsMode.new
|
30
|
+
mode = SearchResultsMode.new query
|
33
31
|
BufferManager.spawn "search: \"#{short_text}\"", mode
|
34
32
|
mode.load_threads :num => mode.buffer.content_height
|
35
33
|
rescue Index::ParseError => e
|