sup 0.19.0 → 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 +4 -1
- data/.gitmodules +3 -0
- data/.travis.yml +12 -6
- data/CONTRIBUTORS +28 -14
- data/Gemfile +5 -0
- data/History.txt +92 -0
- data/README.md +26 -5
- data/Rakefile +41 -1
- data/ReleaseNotes +17 -0
- data/bin/sup +12 -23
- data/bin/sup-add +15 -16
- data/bin/sup-config +30 -45
- data/bin/sup-dump +2 -3
- data/bin/sup-import-dump +5 -6
- data/bin/sup-sync +3 -4
- data/bin/sup-sync-back-maildir +3 -4
- data/bin/sup-tweak-labels +6 -7
- data/contrib/colorpicker.rb +0 -2
- data/contrib/completion/_sup.bash +102 -0
- data/devel/profile.rb +0 -1
- data/ext/mkrf_conf_xapian.rb +47 -0
- data/lib/sup.rb +10 -8
- data/lib/sup/buffer.rb +12 -0
- data/lib/sup/colormap.rb +5 -2
- data/lib/sup/contact.rb +4 -2
- data/lib/sup/crypto.rb +58 -16
- data/lib/sup/draft.rb +8 -8
- data/lib/sup/hook.rb +9 -9
- data/lib/sup/index.rb +20 -7
- data/lib/sup/label.rb +1 -1
- data/lib/sup/logger.rb +1 -1
- data/lib/sup/maildir.rb +16 -5
- data/lib/sup/mbox.rb +13 -5
- data/lib/sup/message.rb +36 -12
- data/lib/sup/message_chunks.rb +13 -4
- data/lib/sup/mode.rb +34 -28
- data/lib/sup/modes/contact_list_mode.rb +1 -0
- data/lib/sup/modes/edit_message_mode.rb +3 -2
- data/lib/sup/modes/forward_mode.rb +22 -3
- data/lib/sup/modes/line_cursor_mode.rb +1 -1
- data/lib/sup/modes/reply_mode.rb +3 -1
- data/lib/sup/modes/text_mode.rb +6 -1
- data/lib/sup/modes/thread_index_mode.rb +12 -2
- data/lib/sup/modes/thread_view_mode.rb +111 -14
- data/lib/sup/person.rb +68 -61
- data/lib/sup/search.rb +1 -1
- data/lib/sup/sent.rb +1 -1
- data/lib/sup/source.rb +1 -1
- data/lib/sup/util.rb +15 -94
- data/lib/sup/util/axe.rb +17 -0
- data/lib/sup/util/locale_fiddler.rb +24 -0
- data/lib/sup/util/ncurses.rb +3 -3
- data/lib/sup/version.rb +10 -1
- data/sup.gemspec +29 -11
- data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
- data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
- data/test/fixtures/blank-header-fields.eml +71 -0
- data/test/fixtures/contacts.txt +1 -0
- data/test/fixtures/mailing-list-header.eml +80 -0
- data/test/fixtures/malicious-attachment-names.eml +55 -0
- data/test/fixtures/missing-from-to.eml +18 -0
- data/test/{messages → fixtures}/missing-line.eml +0 -0
- data/test/fixtures/multi-part-2.eml +72 -0
- data/test/fixtures/multi-part.eml +61 -0
- data/test/fixtures/no-body.eml +18 -0
- data/test/fixtures/simple-message.eml +29 -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 +3 -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 +89 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -17
- data/test/integration/test_maildir.rb +75 -0
- data/test/integration/test_mbox.rb +69 -0
- data/test/test_crypto.rb +14 -2
- data/test/test_header_parsing.rb +1 -1
- data/test/test_helper.rb +6 -3
- data/test/test_message.rb +115 -341
- data/test/test_messages_dir.rb +4 -28
- data/test/test_yaml_regressions.rb +1 -1
- data/test/unit/test_contact.rb +33 -0
- data/test/unit/test_locale_fiddler.rb +15 -0
- data/test/unit/test_person.rb +37 -0
- data/test/unit/util/test_query.rb +10 -4
- data/test/unit/util/test_string.rb +6 -0
- metadata +137 -53
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
data/lib/sup/index.rb
CHANGED
@@ -105,7 +105,7 @@ EOS
|
|
105
105
|
|
106
106
|
def save
|
107
107
|
debug "saving index and sources..."
|
108
|
-
FileUtils.mkdir_p @dir unless File.
|
108
|
+
FileUtils.mkdir_p @dir unless File.exist? @dir
|
109
109
|
SourceManager.save_sources
|
110
110
|
save_index
|
111
111
|
end
|
@@ -116,7 +116,7 @@ EOS
|
|
116
116
|
|
117
117
|
def load_index failsafe=false
|
118
118
|
path = File.join(@dir, 'xapian')
|
119
|
-
if File.
|
119
|
+
if File.exist? path
|
120
120
|
@xapian = Xapian::WritableDatabase.new(path, Xapian::DB_OPEN)
|
121
121
|
db_version = @xapian.get_metadata 'version'
|
122
122
|
db_version = '0' if db_version.empty?
|
@@ -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/label.rb
CHANGED
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/maildir.rb
CHANGED
@@ -12,7 +12,15 @@ class Maildir < Source
|
|
12
12
|
def initialize uri, usual=true, archived=false, sync_back=true, id=nil, labels=[]
|
13
13
|
super uri, usual, archived, id
|
14
14
|
@expanded_uri = Source.expand_filesystem_uri(uri)
|
15
|
-
|
15
|
+
parts = @expanded_uri.match /^([a-zA-Z0-9]*:(\/\/)?)(.*)/
|
16
|
+
if parts
|
17
|
+
prefix = parts[1]
|
18
|
+
@path = parts[3]
|
19
|
+
uri = URI(prefix + URI.encode(@path, URI_ENCODE_CHARS))
|
20
|
+
else
|
21
|
+
uri = URI(URI.encode @expanded_uri, URI_ENCODE_CHARS)
|
22
|
+
@path = uri.path
|
23
|
+
end
|
16
24
|
|
17
25
|
raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
|
18
26
|
raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
|
@@ -22,7 +30,7 @@ class Maildir < Source
|
|
22
30
|
# sync by default if not specified
|
23
31
|
@sync_back = true if @sync_back.nil?
|
24
32
|
|
25
|
-
@dir = uri.path
|
33
|
+
@dir = URI.decode uri.path
|
26
34
|
@labels = Set.new(labels || [])
|
27
35
|
@mutex = Mutex.new
|
28
36
|
@ctimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
|
@@ -60,7 +68,7 @@ class Maildir < Source
|
|
60
68
|
File.safe_link tmp_path, new_path
|
61
69
|
stored = true
|
62
70
|
ensure
|
63
|
-
File.unlink tmp_path if File.
|
71
|
+
File.unlink tmp_path if File.exist? tmp_path
|
64
72
|
end
|
65
73
|
end #rescue Errno...
|
66
74
|
end #Dir.chdir
|
@@ -120,7 +128,10 @@ class Maildir < Source
|
|
120
128
|
@ctimes[d] = ctime
|
121
129
|
|
122
130
|
old_ids = benchmark(:maildir_read_index) { Index.instance.enum_for(:each_source_info, self.id, "#{d}/").to_a }
|
123
|
-
new_ids = benchmark(:maildir_read_dir) {
|
131
|
+
new_ids = benchmark(:maildir_read_dir) {
|
132
|
+
Dir.open(subdir).select {
|
133
|
+
|f| !File.directory? f}.map {
|
134
|
+
|x| File.join(d,File.basename(x)) }.sort }
|
124
135
|
added += new_ids - old_ids
|
125
136
|
deleted += old_ids - new_ids
|
126
137
|
debug "#{old_ids.size} in index, #{new_ids.size} in filesystem"
|
@@ -190,7 +201,7 @@ class Maildir < Source
|
|
190
201
|
def trashed? id; maildir_data(id)[2].include? "T"; end
|
191
202
|
|
192
203
|
def valid? id
|
193
|
-
File.
|
204
|
+
File.exist? File.join(@dir, id)
|
194
205
|
end
|
195
206
|
|
196
207
|
private
|
data/lib/sup/mbox.rb
CHANGED
@@ -19,16 +19,24 @@ class MBox < Source
|
|
19
19
|
case uri_or_fp
|
20
20
|
when String
|
21
21
|
@expanded_uri = Source.expand_filesystem_uri(uri_or_fp)
|
22
|
-
|
22
|
+
parts = @expanded_uri.match /^([a-zA-Z0-9]*:(\/\/)?)(.*)/
|
23
|
+
if parts
|
24
|
+
prefix = parts[1]
|
25
|
+
@path = parts[3]
|
26
|
+
uri = URI(prefix + URI.encode(@path, URI_ENCODE_CHARS))
|
27
|
+
else
|
28
|
+
uri = URI(URI.encode @expanded_uri, URI_ENCODE_CHARS)
|
29
|
+
@path = uri.path
|
30
|
+
end
|
31
|
+
|
23
32
|
raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
|
24
33
|
raise ArgumentError, "mbox URI ('#{uri}') cannot have a host: #{uri.host}" if uri.host
|
25
34
|
raise ArgumentError, "mbox URI must have a path component" unless uri.path
|
26
35
|
@f = nil
|
27
|
-
@path = uri.path
|
28
36
|
else
|
29
37
|
@f = uri_or_fp
|
30
38
|
@path = uri_or_fp.path
|
31
|
-
@expanded_uri = "mbox://#{@path}"
|
39
|
+
@expanded_uri = "mbox://#{URI.encode @path, URI_ENCODE_CHARS}"
|
32
40
|
end
|
33
41
|
|
34
42
|
super uri_or_fp, usual, archived, id
|
@@ -107,7 +115,7 @@ class MBox < Source
|
|
107
115
|
end
|
108
116
|
|
109
117
|
def store_message date, from_email, &block
|
110
|
-
need_blank = File.
|
118
|
+
need_blank = File.exist?(@path) && !File.zero?(@path)
|
111
119
|
File.open(@path, "ab") do |f|
|
112
120
|
f.puts if need_blank
|
113
121
|
f.puts "From #{from_email} #{date.asctime}"
|
@@ -172,7 +180,7 @@ class MBox < Source
|
|
172
180
|
time = $1
|
173
181
|
begin
|
174
182
|
## hack -- make Time.parse fail when trying to substitute values from Time.now
|
175
|
-
Time.parse time, 0
|
183
|
+
Time.parse time, Time.at(0)
|
176
184
|
true
|
177
185
|
rescue NoMethodError, ArgumentError
|
178
186
|
warn "found invalid date in potential mbox split line, not splitting: #{l.inspect}"
|
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,13 +269,27 @@ 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"))]
|
276
|
+
|
277
|
+
rescue Exception => e
|
278
|
+
|
279
|
+
warn_with_location "problem reading message #{id}"
|
280
|
+
debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
|
281
|
+
|
282
|
+
raise e
|
283
|
+
|
271
284
|
end
|
272
285
|
end
|
273
286
|
|
287
|
+
def reload_from_source!
|
288
|
+
@chunks = nil
|
289
|
+
load_from_source!
|
290
|
+
end
|
291
|
+
|
292
|
+
|
274
293
|
def error_message
|
275
294
|
<<EOS
|
276
295
|
#@snippet...
|
@@ -327,17 +346,17 @@ EOS
|
|
327
346
|
to.map { |p| p.indexable_content },
|
328
347
|
cc.map { |p| p.indexable_content },
|
329
348
|
bcc.map { |p| p.indexable_content },
|
330
|
-
indexable_chunks.map { |c| c.lines },
|
349
|
+
indexable_chunks.map { |c| c.lines.map { |l| l.fix_encoding! } },
|
331
350
|
indexable_subject,
|
332
351
|
].flatten.compact.join " "
|
333
352
|
end
|
334
353
|
|
335
354
|
def indexable_body
|
336
|
-
indexable_chunks.map { |c| c.lines }.flatten.compact.join " "
|
355
|
+
indexable_chunks.map { |c| c.lines }.flatten.compact.map { |l| l.fix_encoding! }.join " "
|
337
356
|
end
|
338
357
|
|
339
358
|
def indexable_chunks
|
340
|
-
chunks.select { |c| c.
|
359
|
+
chunks.select { |c| c.indexable? } || []
|
341
360
|
end
|
342
361
|
|
343
362
|
def indexable_subject
|
@@ -390,19 +409,19 @@ private
|
|
390
409
|
|
391
410
|
def multipart_signed_to_chunks m
|
392
411
|
if m.body.size != 2
|
393
|
-
|
412
|
+
warn_with_location "multipart/signed with #{m.body.size} parts (expecting 2)"
|
394
413
|
return
|
395
414
|
end
|
396
415
|
|
397
416
|
payload, signature = m.body
|
398
417
|
if signature.multipart?
|
399
|
-
|
418
|
+
warn_with_location "multipart/signed with payload multipart #{payload.multipart?} and signature multipart #{signature.multipart?}"
|
400
419
|
return
|
401
420
|
end
|
402
421
|
|
403
422
|
## this probably will never happen
|
404
423
|
if payload.header.content_type && payload.header.content_type.downcase == "application/pgp-signature"
|
405
|
-
|
424
|
+
warn_with_location "multipart/signed with payload content type #{payload.header.content_type}"
|
406
425
|
return
|
407
426
|
end
|
408
427
|
|
@@ -417,23 +436,23 @@ private
|
|
417
436
|
|
418
437
|
def multipart_encrypted_to_chunks m
|
419
438
|
if m.body.size != 2
|
420
|
-
|
439
|
+
warn_with_location "multipart/encrypted with #{m.body.size} parts (expecting 2)"
|
421
440
|
return
|
422
441
|
end
|
423
442
|
|
424
443
|
control, payload = m.body
|
425
444
|
if control.multipart?
|
426
|
-
|
445
|
+
warn_with_location "multipart/encrypted with control multipart #{control.multipart?} and payload multipart #{payload.multipart?}"
|
427
446
|
return
|
428
447
|
end
|
429
448
|
|
430
449
|
if payload.header.content_type && payload.header.content_type.downcase != "application/octet-stream"
|
431
|
-
|
450
|
+
warn_with_location "multipart/encrypted with payload content type #{payload.header.content_type}"
|
432
451
|
return
|
433
452
|
end
|
434
453
|
|
435
454
|
if control.header.content_type && control.header.content_type.downcase != "application/pgp-encrypted"
|
436
|
-
|
455
|
+
warn_with_location "multipart/encrypted with control content type #{signature.header.content_type}"
|
437
456
|
return
|
438
457
|
end
|
439
458
|
|
@@ -677,7 +696,7 @@ private
|
|
677
696
|
newstate = :quote
|
678
697
|
elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE && !lines[(i+1)..-1].index { |l| l =~ /^-- $/ }
|
679
698
|
newstate = :sig
|
680
|
-
elsif line =~ BLOCK_QUOTE_PATTERN
|
699
|
+
elsif line =~ BLOCK_QUOTE_PATTERN && nextline !~ QUOTE_PATTERN
|
681
700
|
newstate = :block_quote
|
682
701
|
end
|
683
702
|
|
@@ -737,6 +756,11 @@ private
|
|
737
756
|
end
|
738
757
|
chunks
|
739
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
|
740
764
|
end
|
741
765
|
|
742
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
|
@@ -159,11 +159,14 @@ EOS
|
|
159
159
|
"Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
|
160
160
|
end
|
161
161
|
end
|
162
|
+
def safe_filename; Shellwords.escape(@filename).gsub("/", "_") end
|
163
|
+
def filesafe_filename; @filename.gsub("/", "_") end
|
162
164
|
|
163
165
|
## an attachment is exapndable if we've managed to decode it into
|
164
166
|
## something we can display inline. otherwise, it's viewable.
|
165
167
|
def inlineable?; false end
|
166
168
|
def expandable?; !viewable? end
|
169
|
+
def indexable?; expandable? end
|
167
170
|
def initial_state; :open end
|
168
171
|
def viewable?; @lines.nil? end
|
169
172
|
def view_default! path
|
@@ -229,6 +232,7 @@ EOS
|
|
229
232
|
def inlineable?; true end
|
230
233
|
def quotable?; true end
|
231
234
|
def expandable?; false end
|
235
|
+
def indexable?; true end
|
232
236
|
def viewable?; false end
|
233
237
|
def color; :text_color end
|
234
238
|
end
|
@@ -242,6 +246,7 @@ EOS
|
|
242
246
|
def inlineable?; @lines.length == 1 end
|
243
247
|
def quotable?; true end
|
244
248
|
def expandable?; !inlineable? end
|
249
|
+
def indexable?; expandable? end
|
245
250
|
def viewable?; false end
|
246
251
|
|
247
252
|
def patina_color; :quote_patina_color end
|
@@ -258,6 +263,7 @@ EOS
|
|
258
263
|
def inlineable?; @lines.length == 1 end
|
259
264
|
def quotable?; false end
|
260
265
|
def expandable?; !inlineable? end
|
266
|
+
def indexable?; expandable? end
|
261
267
|
def viewable?; false end
|
262
268
|
|
263
269
|
def patina_color; :sig_patina_color end
|
@@ -291,6 +297,7 @@ EOS
|
|
291
297
|
def inlineable?; false end
|
292
298
|
def quotable?; false end
|
293
299
|
def expandable?; true end
|
300
|
+
def indexable?; true end
|
294
301
|
def initial_state; :closed end
|
295
302
|
def viewable?; false end
|
296
303
|
|
@@ -301,12 +308,13 @@ EOS
|
|
301
308
|
end
|
302
309
|
|
303
310
|
class CryptoNotice
|
304
|
-
attr_reader :lines, :status, :patina_text
|
311
|
+
attr_reader :lines, :status, :patina_text, :unknown_fingerprint
|
305
312
|
|
306
|
-
def initialize status, description, lines=[]
|
313
|
+
def initialize status, description, lines=[], unknown_fingerprint=nil
|
307
314
|
@status = status
|
308
315
|
@patina_text = description
|
309
316
|
@lines = lines
|
317
|
+
@unknown_fingerprint = unknown_fingerprint
|
310
318
|
end
|
311
319
|
|
312
320
|
def patina_color
|
@@ -322,6 +330,7 @@ EOS
|
|
322
330
|
def inlineable?; false end
|
323
331
|
def quotable?; false end
|
324
332
|
def expandable?; !@lines.empty? end
|
333
|
+
def indexable?; false end
|
325
334
|
def viewable?; false end
|
326
335
|
end
|
327
336
|
end
|
data/lib/sup/mode.rb
CHANGED
@@ -46,7 +46,7 @@ class Mode
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def resolve_input c
|
49
|
-
ancestors.each do |klass| # try all keymaps in order of ancestry
|
49
|
+
self.class.ancestors.each do |klass| # try all keymaps in order of ancestry
|
50
50
|
next unless @@keymaps.member?(klass)
|
51
51
|
action = BufferManager.resolve_input_with_keymap c, @@keymaps[klass]
|
52
52
|
return action if action
|
@@ -62,7 +62,7 @@ class Mode
|
|
62
62
|
|
63
63
|
def help_text
|
64
64
|
used_keys = {}
|
65
|
-
ancestors.map do |klass|
|
65
|
+
self.class.ancestors.map do |klass|
|
66
66
|
km = @@keymaps[klass] or next
|
67
67
|
title = "Keybindings from #{Mode.make_name klass.name}"
|
68
68
|
s = <<EOS
|
@@ -83,7 +83,8 @@ EOS
|
|
83
83
|
### helper functions
|
84
84
|
|
85
85
|
def save_to_file fn, talk=true
|
86
|
-
|
86
|
+
FileUtils.mkdir_p File.dirname(fn)
|
87
|
+
if File.exist? fn
|
87
88
|
unless BufferManager.ask_yes_or_no "File \"#{fn}\" exists. Overwrite?"
|
88
89
|
info "Not overwriting #{fn}"
|
89
90
|
return
|
@@ -102,37 +103,42 @@ EOS
|
|
102
103
|
end
|
103
104
|
|
104
105
|
def pipe_to_process command
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
106
|
+
begin
|
107
|
+
Open3.popen3(command) do |input, output, error|
|
108
|
+
err, data, * = IO.select [error], [input], nil
|
109
|
+
|
110
|
+
unless err.empty?
|
111
|
+
message = err.first.read
|
112
|
+
if message =~ /^\s*$/
|
113
|
+
warn "error running #{command} (but no error message)"
|
114
|
+
BufferManager.flash "Error running #{command}!"
|
115
|
+
else
|
116
|
+
warn "error running #{command}: #{message}"
|
117
|
+
BufferManager.flash "Error: #{message}"
|
118
|
+
end
|
119
|
+
return nil, false
|
116
120
|
end
|
117
|
-
return
|
118
|
-
end
|
119
121
|
|
120
|
-
|
121
|
-
|
122
|
+
data = data.first
|
123
|
+
data.sync = false # buffer input
|
122
124
|
|
123
|
-
|
124
|
-
|
125
|
+
yield data
|
126
|
+
data.close # output will block unless input is closed
|
125
127
|
|
126
|
-
|
127
|
-
|
128
|
-
|
128
|
+
## BUG?: shows errors or output but not both....
|
129
|
+
data, * = IO.select [output, error], nil, nil
|
130
|
+
data = data.first
|
129
131
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
132
|
+
if data.eof
|
133
|
+
BufferManager.flash "'#{command}' done!"
|
134
|
+
return nil, true
|
135
|
+
else
|
136
|
+
return data.read, true
|
137
|
+
end
|
135
138
|
end
|
139
|
+
rescue Errno::ENOENT
|
140
|
+
# If the command is invalid
|
141
|
+
return nil, false
|
136
142
|
end
|
137
143
|
end
|
138
144
|
end
|