sup 0.22.1 → 0.23
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 +5 -5
- data/.gitignore +1 -3
- data/.travis.yml +11 -6
- data/CONTRIBUTORS +13 -5
- data/Gemfile +2 -1
- data/History.txt +51 -0
- data/README.md +26 -5
- data/bin/sup +7 -5
- data/bin/sup-add +14 -14
- data/bin/sup-config +30 -44
- data/bin/sup-dump +2 -2
- data/bin/sup-import-dump +4 -4
- data/bin/sup-sync +3 -3
- data/bin/sup-sync-back-maildir +2 -2
- data/bin/sup-tweak-labels +5 -5
- data/ext/mkrf_conf_xapian.rb +1 -1
- data/lib/sup.rb +1 -0
- data/lib/sup/crypto.rb +17 -8
- data/lib/sup/hook.rb +8 -8
- data/lib/sup/index.rb +18 -5
- data/lib/sup/logger.rb +1 -1
- data/lib/sup/message.rb +20 -10
- data/lib/sup/message_chunks.rb +3 -2
- data/lib/sup/mode.rb +1 -0
- data/lib/sup/modes/contact_list_mode.rb +1 -0
- data/lib/sup/modes/reply_mode.rb +3 -1
- data/lib/sup/modes/thread_index_mode.rb +1 -1
- data/lib/sup/modes/thread_view_mode.rb +14 -11
- data/lib/sup/source.rb +1 -1
- data/lib/sup/util.rb +14 -19
- data/lib/sup/util/axe.rb +17 -0
- data/lib/sup/util/ncurses.rb +3 -3
- data/lib/sup/version.rb +10 -1
- data/sup.gemspec +7 -6
- data/test/fixtures/mailing-list-header.eml +80 -0
- data/test/fixtures/text-attachments-with-charset.eml +46 -0
- data/test/fixtures/zimbra-quote-with-bottom-post.eml +27 -0
- data/test/gnupg_test_home/gpg.conf +2 -1
- data/test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key +0 -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/regen_keys.sh +69 -18
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -22
- data/test/test_crypto.rb +2 -0
- data/test/test_message.rb +74 -0
- data/test/unit/util/test_query.rb +10 -4
- data/test/unit/util/test_string.rb +6 -0
- metadata +52 -38
- data/test/gnupg_test_home/key1.gen +0 -15
- data/test/gnupg_test_home/key2.gen +0 -15
- data/test/gnupg_test_home/key_ecc.gen +0 -13
data/bin/sup-dump
CHANGED
@@ -3,12 +3,12 @@
|
|
3
3
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
4
|
|
5
5
|
require 'xapian'
|
6
|
-
require '
|
6
|
+
require 'optimist'
|
7
7
|
require 'set'
|
8
8
|
|
9
9
|
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
10
10
|
|
11
|
-
$opts =
|
11
|
+
$opts = Optimist::options do
|
12
12
|
version "sup-dump"
|
13
13
|
banner <<EOS
|
14
14
|
Dumps all message state from the sup index to standard out. You can
|
data/bin/sup-import-dump
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
4
|
|
5
5
|
require 'uri'
|
6
|
-
require '
|
6
|
+
require 'optimist'
|
7
7
|
require "sup"
|
8
8
|
|
9
9
|
PROGRESS_UPDATE_INTERVAL = 15 # seconds
|
@@ -11,7 +11,7 @@ PROGRESS_UPDATE_INTERVAL = 15 # seconds
|
|
11
11
|
class AbortExecution < SystemExit
|
12
12
|
end
|
13
13
|
|
14
|
-
opts =
|
14
|
+
opts = Optimist::options do
|
15
15
|
version "sup-import-dump (sup #{Redwood::VERSION})"
|
16
16
|
banner <<EOS
|
17
17
|
Imports message state previously exported by sup-dump into the index.
|
@@ -36,8 +36,8 @@ EOS
|
|
36
36
|
|
37
37
|
conflicts :ignore_missing, :warn_missing, :abort_missing
|
38
38
|
end
|
39
|
-
|
40
|
-
|
39
|
+
Optimist::die "No dump file given" if ARGV.empty?
|
40
|
+
Optimist::die "Extra arguments given" if ARGV.length > 1
|
41
41
|
dump_name = ARGV.shift
|
42
42
|
missing_action = [:ignore_missing, :warn_missing, :abort_missing].find { |x| opts[x] } || :abort_missing
|
43
43
|
|
data/bin/sup-sync
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
4
|
|
5
5
|
require 'uri'
|
6
|
-
require '
|
6
|
+
require 'optimist'
|
7
7
|
require "sup"
|
8
8
|
|
9
9
|
PROGRESS_UPDATE_INTERVAL = 15 # seconds
|
@@ -30,7 +30,7 @@ def time
|
|
30
30
|
Time.now - startt
|
31
31
|
end
|
32
32
|
|
33
|
-
opts =
|
33
|
+
opts = Optimist::options do
|
34
34
|
version "sup-sync (sup #{Redwood::VERSION})"
|
35
35
|
banner <<EOS
|
36
36
|
Synchronizes the Sup index with one or more message sources by adding
|
@@ -112,7 +112,7 @@ begin
|
|
112
112
|
Redwood::SourceManager.usual_sources
|
113
113
|
else
|
114
114
|
ARGV.map do |uri|
|
115
|
-
Redwood::SourceManager.source_for uri or
|
115
|
+
Redwood::SourceManager.source_for uri or Optimist::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
data/bin/sup-sync-back-maildir
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
|
4
4
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
5
5
|
|
6
|
-
require '
|
6
|
+
require 'optimist'
|
7
7
|
require "sup"
|
8
8
|
|
9
|
-
opts =
|
9
|
+
opts = Optimist::options do
|
10
10
|
version "sup-sync-back-maildir (sup #{Redwood::VERSION})"
|
11
11
|
banner <<EOS
|
12
12
|
Export Xapian entries to Maildir sources on disk.
|
data/bin/sup-tweak-labels
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
4
|
|
5
|
-
require '
|
5
|
+
require 'optimist'
|
6
6
|
require "sup"
|
7
7
|
|
8
8
|
class Float
|
@@ -25,7 +25,7 @@ def time
|
|
25
25
|
Time.now - startt
|
26
26
|
end
|
27
27
|
|
28
|
-
opts =
|
28
|
+
opts = Optimist::options do
|
29
29
|
version "sup-tweak-labels (sup #{Redwood::VERSION})"
|
30
30
|
banner <<EOS
|
31
31
|
Batch modification of message state for messages already in the index.
|
@@ -58,7 +58,7 @@ opts[:verbose] = true if opts[:very_verbose]
|
|
58
58
|
add_labels = opts[:add].to_set_of_symbols ","
|
59
59
|
remove_labels = opts[:remove].to_set_of_symbols ","
|
60
60
|
|
61
|
-
|
61
|
+
Optimist::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
|
62
62
|
|
63
63
|
Redwood::start
|
64
64
|
index = Redwood::Index.init
|
@@ -71,10 +71,10 @@ begin
|
|
71
71
|
Redwood::SourceManager.sources
|
72
72
|
else
|
73
73
|
ARGV.map do |uri|
|
74
|
-
Redwood::SourceManager.source_for uri or
|
74
|
+
Redwood::SourceManager.source_for uri or Optimist::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
75
75
|
end
|
76
76
|
end.map { |s| s.id }
|
77
|
-
|
77
|
+
Optimist::die "nothing to do: no sources" if source_ids.empty?
|
78
78
|
|
79
79
|
query = "(" + source_ids.map { |id| "source_id:#{id}" }.join(" OR ") + ")"
|
80
80
|
if add_labels.empty?
|
data/ext/mkrf_conf_xapian.rb
CHANGED
data/lib/sup.rb
CHANGED
data/lib/sup/crypto.rb
CHANGED
@@ -127,18 +127,27 @@ EOS
|
|
127
127
|
def sign from, to, payload
|
128
128
|
return unknown_status(@not_working_reason) unless @not_working_reason.nil?
|
129
129
|
|
130
|
+
# We grab this from the GPG::Ctx below after signing, so that we can set
|
131
|
+
# micalg in Content-Type to match the hash algorithm GPG decided to use.
|
132
|
+
hash_algo = nil
|
133
|
+
|
130
134
|
gpg_opts = {:protocol => GPGME::PROTOCOL_OpenPGP, :armor => true, :textmode => true}
|
131
135
|
gpg_opts.merge!(gen_sign_user_opts(from))
|
132
136
|
gpg_opts = HookManager.run("gpg-options",
|
133
137
|
{:operation => "sign", :options => gpg_opts}) || gpg_opts
|
134
138
|
begin
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
139
|
+
input = GPGME::Data.new(format_payload(payload))
|
140
|
+
output = GPGME::Data.new()
|
141
|
+
GPGME::Ctx.new(gpg_opts) do |ctx|
|
142
|
+
if gpg_opts[:signer]
|
143
|
+
signers = GPGME::Key.find(:secret, gpg_opts[:signer], :sign)
|
144
|
+
ctx.add_signer(*signers)
|
145
|
+
end
|
146
|
+
ctx.sign(input, output, GPGME::SIG_MODE_DETACH)
|
147
|
+
hash_algo = GPGME::hash_algo_name(ctx.sign_result.signatures[0].hash_algo)
|
141
148
|
end
|
149
|
+
output.seek(0)
|
150
|
+
sig = output.read
|
142
151
|
rescue GPGME::Error => exc
|
143
152
|
raise Error, gpgme_exc_msg(exc.message)
|
144
153
|
end
|
@@ -150,7 +159,7 @@ EOS
|
|
150
159
|
end
|
151
160
|
|
152
161
|
envelope = RMail::Message.new
|
153
|
-
envelope.header["Content-Type"] =
|
162
|
+
envelope.header["Content-Type"] = "multipart/signed; protocol=application/pgp-signature; micalg=pgp-#{hash_algo.downcase}"
|
154
163
|
|
155
164
|
envelope.add_part payload
|
156
165
|
signature = RMail::Message.make_attachment sig, "application/pgp-signature", nil, "signature.asc"
|
@@ -233,7 +242,7 @@ EOS
|
|
233
242
|
end
|
234
243
|
|
235
244
|
if valid || !unknown
|
236
|
-
summary_line = simplify_sig_line(verify_result.signatures[0].to_s, all_trusted)
|
245
|
+
summary_line = simplify_sig_line(verify_result.signatures[0].to_s.dup, all_trusted)
|
237
246
|
end
|
238
247
|
|
239
248
|
if all_output_lines.length == 0
|
data/lib/sup/hook.rb
CHANGED
@@ -109,20 +109,20 @@ class HookManager
|
|
109
109
|
@descs[name] = desc
|
110
110
|
end
|
111
111
|
|
112
|
-
def print_hooks f=$stdout
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
EOS
|
117
|
-
|
118
|
-
HookManager.descs.sort.each do |name, desc|
|
119
|
-
f.puts <<EOS
|
112
|
+
def print_hooks pattern="", f=$stdout
|
113
|
+
matching_hooks = HookManager.descs.sort.keep_if {|name, desc| pattern.empty? or name.match(pattern)}.map do |name, desc|
|
114
|
+
<<EOS
|
120
115
|
#{name}
|
121
116
|
#{"-" * name.length}
|
122
117
|
File: #{fn_for name}
|
123
118
|
#{desc}
|
124
119
|
EOS
|
125
120
|
end
|
121
|
+
|
122
|
+
showing_str = matching_hooks.size == HookManager.descs.size ? "" : " (showing #{matching_hooks.size})"
|
123
|
+
f.puts "Have #{HookManager.descs.size} registered hooks#{showing_str}:"
|
124
|
+
f.puts
|
125
|
+
matching_hooks.each { |text| f.puts text }
|
126
126
|
end
|
127
127
|
|
128
128
|
def enabled? name; !hook_for(name).nil? end
|
data/lib/sup/index.rb
CHANGED
@@ -516,19 +516,32 @@ EOS
|
|
516
516
|
qp.stemmer = Xapian::Stem.new($config[:stem_language])
|
517
517
|
qp.stemming_strategy = Xapian::QueryParser::STEM_SOME
|
518
518
|
qp.default_op = Xapian::Query::OP_AND
|
519
|
-
|
520
|
-
|
521
|
-
|
519
|
+
valuerangeprocessor = Xapian::NumberValueRangeProcessor.new(DATE_VALUENO,
|
520
|
+
'date:', true)
|
521
|
+
qp.add_valuerangeprocessor(valuerangeprocessor)
|
522
|
+
NORMAL_PREFIX.each { |k,info| info[:prefix].each {
|
523
|
+
|v| qp.add_prefix k, v }
|
524
|
+
}
|
525
|
+
BOOLEAN_PREFIX.each { |k,info| info[:prefix].each {
|
526
|
+
|v| qp.add_boolean_prefix k, v, info[:exclusive] }
|
527
|
+
}
|
522
528
|
|
523
529
|
begin
|
524
|
-
xapian_query = qp.parse_query(subs, Xapian::QueryParser::FLAG_PHRASE|
|
530
|
+
xapian_query = qp.parse_query(subs, Xapian::QueryParser::FLAG_PHRASE |
|
531
|
+
Xapian::QueryParser::FLAG_BOOLEAN |
|
532
|
+
Xapian::QueryParser::FLAG_LOVEHATE |
|
533
|
+
Xapian::QueryParser::FLAG_WILDCARD)
|
525
534
|
rescue RuntimeError => e
|
526
535
|
raise ParseError, "xapian query parser error: #{e}"
|
527
536
|
end
|
528
537
|
|
529
538
|
debug "parsed xapian query: #{Util::Query.describe(xapian_query, subs)}"
|
530
539
|
|
531
|
-
|
540
|
+
if xapian_query.nil? or xapian_query.empty?
|
541
|
+
raise ParseError, "couldn't parse \"#{s}\" as xapian query " \
|
542
|
+
"(special characters aren't indexed)"
|
543
|
+
end
|
544
|
+
|
532
545
|
query[:qobj] = xapian_query
|
533
546
|
query[:text] = s
|
534
547
|
query
|
data/lib/sup/logger.rb
CHANGED
@@ -71,7 +71,7 @@ end
|
|
71
71
|
|
72
72
|
## include me to have top-level #debug, #info, etc. methods.
|
73
73
|
module LogsStuff
|
74
|
-
Logger::LEVELS.each { |l| define_method(l) { |s| Logger.instance.send(l, s) } }
|
74
|
+
Logger::LEVELS.each { |l| define_method(l) { |s, uplevel = 0| Logger.instance.send(l, s) } }
|
75
75
|
end
|
76
76
|
|
77
77
|
end
|
data/lib/sup/message.rb
CHANGED
@@ -136,6 +136,11 @@ class Message
|
|
136
136
|
header["list-post"] # just try the whole fucking thing
|
137
137
|
end
|
138
138
|
address && Person.from_address(address)
|
139
|
+
elsif header["mailing-list"]
|
140
|
+
address = if header["mailing-list"] =~ /list (.*?);/
|
141
|
+
$1
|
142
|
+
end
|
143
|
+
address && Person.from_address(address)
|
139
144
|
elsif header["x-mailing-list"]
|
140
145
|
Person.from_address header["x-mailing-list"]
|
141
146
|
end
|
@@ -264,14 +269,14 @@ class Message
|
|
264
269
|
parse_header rmsg.header
|
265
270
|
message_to_chunks rmsg
|
266
271
|
rescue SourceError, SocketError, RMail::EncodingUnsupportedError => e
|
267
|
-
|
272
|
+
warn_with_location "problem reading message #{id}"
|
268
273
|
debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
|
269
274
|
|
270
275
|
[Chunk::Text.new(error_message.split("\n"))]
|
271
276
|
|
272
277
|
rescue Exception => e
|
273
278
|
|
274
|
-
|
279
|
+
warn_with_location "problem reading message #{id}"
|
275
280
|
debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
|
276
281
|
|
277
282
|
raise e
|
@@ -404,19 +409,19 @@ private
|
|
404
409
|
|
405
410
|
def multipart_signed_to_chunks m
|
406
411
|
if m.body.size != 2
|
407
|
-
|
412
|
+
warn_with_location "multipart/signed with #{m.body.size} parts (expecting 2)"
|
408
413
|
return
|
409
414
|
end
|
410
415
|
|
411
416
|
payload, signature = m.body
|
412
417
|
if signature.multipart?
|
413
|
-
|
418
|
+
warn_with_location "multipart/signed with payload multipart #{payload.multipart?} and signature multipart #{signature.multipart?}"
|
414
419
|
return
|
415
420
|
end
|
416
421
|
|
417
422
|
## this probably will never happen
|
418
423
|
if payload.header.content_type && payload.header.content_type.downcase == "application/pgp-signature"
|
419
|
-
|
424
|
+
warn_with_location "multipart/signed with payload content type #{payload.header.content_type}"
|
420
425
|
return
|
421
426
|
end
|
422
427
|
|
@@ -431,23 +436,23 @@ private
|
|
431
436
|
|
432
437
|
def multipart_encrypted_to_chunks m
|
433
438
|
if m.body.size != 2
|
434
|
-
|
439
|
+
warn_with_location "multipart/encrypted with #{m.body.size} parts (expecting 2)"
|
435
440
|
return
|
436
441
|
end
|
437
442
|
|
438
443
|
control, payload = m.body
|
439
444
|
if control.multipart?
|
440
|
-
|
445
|
+
warn_with_location "multipart/encrypted with control multipart #{control.multipart?} and payload multipart #{payload.multipart?}"
|
441
446
|
return
|
442
447
|
end
|
443
448
|
|
444
449
|
if payload.header.content_type && payload.header.content_type.downcase != "application/octet-stream"
|
445
|
-
|
450
|
+
warn_with_location "multipart/encrypted with payload content type #{payload.header.content_type}"
|
446
451
|
return
|
447
452
|
end
|
448
453
|
|
449
454
|
if control.header.content_type && control.header.content_type.downcase != "application/pgp-encrypted"
|
450
|
-
|
455
|
+
warn_with_location "multipart/encrypted with control content type #{signature.header.content_type}"
|
451
456
|
return
|
452
457
|
end
|
453
458
|
|
@@ -691,7 +696,7 @@ private
|
|
691
696
|
newstate = :quote
|
692
697
|
elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE && !lines[(i+1)..-1].index { |l| l =~ /^-- $/ }
|
693
698
|
newstate = :sig
|
694
|
-
elsif line =~ BLOCK_QUOTE_PATTERN
|
699
|
+
elsif line =~ BLOCK_QUOTE_PATTERN && nextline !~ QUOTE_PATTERN
|
695
700
|
newstate = :block_quote
|
696
701
|
end
|
697
702
|
|
@@ -751,6 +756,11 @@ private
|
|
751
756
|
end
|
752
757
|
chunks
|
753
758
|
end
|
759
|
+
|
760
|
+
def warn_with_location msg
|
761
|
+
warn msg
|
762
|
+
warn "Message is in #{location.source.uri} at #{location.info}"
|
763
|
+
end
|
754
764
|
end
|
755
765
|
|
756
766
|
class Location
|
data/lib/sup/message_chunks.rb
CHANGED
@@ -128,7 +128,7 @@ EOS
|
|
128
128
|
|
129
129
|
text = case @content_type
|
130
130
|
when /^text\/plain\b/
|
131
|
-
@raw_content
|
131
|
+
@raw_content.force_encoding(encoded_content.charset || 'US-ASCII')
|
132
132
|
else
|
133
133
|
HookManager.run "mime-decode", :content_type => @content_type,
|
134
134
|
:filename => lambda { write_to_disk },
|
@@ -138,7 +138,7 @@ EOS
|
|
138
138
|
|
139
139
|
@lines = nil
|
140
140
|
if text
|
141
|
-
text = text.
|
141
|
+
text = text.encode($encoding, :invalid => :replace, :undef => :replace)
|
142
142
|
begin
|
143
143
|
@lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
144
144
|
rescue Encoding::CompatibilityError
|
@@ -160,6 +160,7 @@ EOS
|
|
160
160
|
end
|
161
161
|
end
|
162
162
|
def safe_filename; Shellwords.escape(@filename).gsub("/", "_") end
|
163
|
+
def filesafe_filename; @filename.gsub("/", "_") end
|
163
164
|
|
164
165
|
## an attachment is exapndable if we've managed to decode it into
|
165
166
|
## something we can display inline. otherwise, it's viewable.
|
data/lib/sup/mode.rb
CHANGED
@@ -108,6 +108,7 @@ class ContactListMode < LineCursorMode
|
|
108
108
|
def load
|
109
109
|
@num ||= (buffer.content_height * 2)
|
110
110
|
@user_contacts = ContactManager.contacts_with_aliases
|
111
|
+
@user_contacts += (HookManager.run("extra-contact-addresses") || []).map { |addr| Person.from_address addr }
|
111
112
|
num = [@num - @user_contacts.length, 0].max
|
112
113
|
BufferManager.say("Loading #{num} contacts from index...") do
|
113
114
|
recentc = Index.load_contacts AccountManager.user_emails, :num => num
|
data/lib/sup/modes/reply_mode.rb
CHANGED
@@ -36,6 +36,8 @@ Variables:
|
|
36
36
|
[:#{REPLY_TYPES * ', :'}]
|
37
37
|
The default behavior is equivalent to
|
38
38
|
([:list, :sender, :recipent] & modes)[0]
|
39
|
+
message: a message object representing the message being replied to
|
40
|
+
(useful values include message.is_list_message? and message.list_address)
|
39
41
|
Return value:
|
40
42
|
The reply mode you desire, or nil to use the default behavior.
|
41
43
|
EOS
|
@@ -130,7 +132,7 @@ EOS
|
|
130
132
|
types = REPLY_TYPES.select { |t| @headers.member?(t) }
|
131
133
|
@type_selector = HorizontalSelector.new "Reply to:", types, types.map { |x| TYPE_DESCRIPTIONS[x] }
|
132
134
|
|
133
|
-
hook_reply = HookManager.run "reply-to", :modes => types
|
135
|
+
hook_reply = HookManager.run "reply-to", :modes => types, :message => @m
|
134
136
|
|
135
137
|
@type_selector.set_to(
|
136
138
|
if types.include? type_arg
|
@@ -10,8 +10,6 @@ class ThreadViewMode < LineCursorMode
|
|
10
10
|
attr_accessor :state
|
11
11
|
end
|
12
12
|
|
13
|
-
INDENT_SPACES = 2 # how many spaces to indent child messages
|
14
|
-
|
15
13
|
HookManager.register "detailed-headers", <<EOS
|
16
14
|
Add or remove headers from the detailed header display of a message.
|
17
15
|
Variables:
|
@@ -54,6 +52,7 @@ EOS
|
|
54
52
|
k.add :toggle_detailed_header, "Toggle detailed header", 'h'
|
55
53
|
k.add :show_header, "Show full message header", 'H'
|
56
54
|
k.add :show_message, "Show full message (raw form)", 'V'
|
55
|
+
k.add :reload, "Update message in thread", '@'
|
57
56
|
k.add :activate_chunk, "Expand/collapse or activate item", :enter
|
58
57
|
k.add :expand_all_messages, "Expand/collapse all messages", 'E'
|
59
58
|
k.add :edit_draft, "Edit draft", 'e'
|
@@ -127,6 +126,7 @@ EOS
|
|
127
126
|
## objects. @person_lines is a map from row #s to Person objects.
|
128
127
|
|
129
128
|
def initialize thread, hidden_labels=[], index_mode=nil
|
129
|
+
@indent_spaces = $config[:indent_spaces]
|
130
130
|
super :slip_rows => $config[:slip_rows]
|
131
131
|
@thread = thread
|
132
132
|
@hidden_labels = hidden_labels
|
@@ -205,6 +205,10 @@ EOS
|
|
205
205
|
@layout[m].state = (@layout[m].state == :detailed ? :open : :detailed)
|
206
206
|
update
|
207
207
|
end
|
208
|
+
|
209
|
+
def reload
|
210
|
+
update
|
211
|
+
end
|
208
212
|
|
209
213
|
def reply type_arg=nil
|
210
214
|
m = @message_lines[curpos] or return
|
@@ -389,7 +393,7 @@ EOS
|
|
389
393
|
when Chunk::Attachment
|
390
394
|
default_dir = $config[:default_attachment_save_dir]
|
391
395
|
default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty?
|
392
|
-
default_fn = File.expand_path File.join(default_dir, chunk.
|
396
|
+
default_fn = File.expand_path File.join(default_dir, chunk.filesafe_filename)
|
393
397
|
fn = BufferManager.ask_for_filename :filename, "Save attachment to file or directory: ", default_fn, true
|
394
398
|
|
395
399
|
# if user selects directory use file name from message
|
@@ -418,7 +422,7 @@ EOS
|
|
418
422
|
num_errors = 0
|
419
423
|
m.chunks.each do |chunk|
|
420
424
|
next unless chunk.is_a?(Chunk::Attachment)
|
421
|
-
fn = File.join(folder, chunk.
|
425
|
+
fn = File.join(folder, chunk.filesafe_filename)
|
422
426
|
num_errors += 1 unless save_to_file(fn, false) { |f| f.print chunk.raw_content }
|
423
427
|
num += 1
|
424
428
|
end
|
@@ -561,7 +565,7 @@ EOS
|
|
561
565
|
l = @layout[m]
|
562
566
|
|
563
567
|
## boundaries of the message
|
564
|
-
message_left = l.depth *
|
568
|
+
message_left = l.depth * @indent_spaces
|
565
569
|
message_right = message_left + l.width
|
566
570
|
|
567
571
|
## calculate leftmost colum
|
@@ -769,14 +773,13 @@ EOS
|
|
769
773
|
# ]
|
770
774
|
|
771
775
|
linetext = @text.slice(curpos, @text.length).flatten(1)
|
772
|
-
.take_while{|d| d[0]
|
776
|
+
.take_while{|d| [:text_color, :sig_color].include?(d[0]) and d[1].strip != ""} # Only take up to the first "" alone on its line
|
773
777
|
.map{|d| d[1].strip}.join("").strip
|
774
778
|
|
775
779
|
found = false
|
776
|
-
(linetext || "").
|
780
|
+
URI.extract(linetext || "").each do |match|
|
777
781
|
begin
|
778
|
-
|
779
|
-
u = URI.parse(link)
|
782
|
+
u = URI.parse(match)
|
780
783
|
next unless u.absolute?
|
781
784
|
next unless ["http", "https"].include?(u.scheme)
|
782
785
|
|
@@ -886,7 +889,7 @@ private
|
|
886
889
|
(0 ... text.length).each do |i|
|
887
890
|
@chunk_lines[@text.length + i] = c
|
888
891
|
@message_lines[@text.length + i] = m
|
889
|
-
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.display_length }.sum - (depth *
|
892
|
+
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.display_length }.sum - (depth * @indent_spaces)
|
890
893
|
l.width = lw if lw > l.width
|
891
894
|
end
|
892
895
|
@text += text
|
@@ -991,7 +994,7 @@ private
|
|
991
994
|
|
992
995
|
## todo: check arguments on this overly complex function
|
993
996
|
def chunk_to_lines chunk, state, start, depth, parent=nil, color=nil, star_color=nil
|
994
|
-
prefix = " " *
|
997
|
+
prefix = " " * @indent_spaces * depth
|
995
998
|
case chunk
|
996
999
|
when :fake_root
|
997
1000
|
[[[:missing_message_color, "#{prefix}<one or more unreceived messages>"]]]
|