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,328 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
require 'shellwords'
|
|
6
|
+
|
|
7
|
+
## Here we define all the "chunks" that a message is parsed
|
|
8
|
+
## into. Chunks are used by ThreadViewMode to render a message. Chunks
|
|
9
|
+
## are used for both MIME stuff like attachments, for Sup's parsing of
|
|
10
|
+
## the message body into text, quote, and signature regions, and for
|
|
11
|
+
## notices like "this message was decrypted" or "this message contains
|
|
12
|
+
## a valid signature"---basically, anything we want to differentiate
|
|
13
|
+
## at display time.
|
|
14
|
+
##
|
|
15
|
+
## A chunk can be inlineable, expandable, or viewable. If it's
|
|
16
|
+
## inlineable, #color and #lines are called and the output is treated
|
|
17
|
+
## as part of the message text. This is how Text and one-line Quotes
|
|
18
|
+
## and Signatures work.
|
|
19
|
+
##
|
|
20
|
+
## If it's not inlineable but is expandable, #patina_color and
|
|
21
|
+
## #patina_text are called to generate a "patina" (a one-line widget,
|
|
22
|
+
## basically), and the user can press enter to toggle the display of
|
|
23
|
+
## the chunk content, which is generated from #color and #lines as
|
|
24
|
+
## above. This is how Quote, Signature, and most widgets
|
|
25
|
+
## work. Exandable chunks can additionally define #initial_state to be
|
|
26
|
+
## :open if they want to start expanded (default is to start collapsed).
|
|
27
|
+
##
|
|
28
|
+
## If it's not expandable but is viewable, a patina is displayed using
|
|
29
|
+
## #patina_color and #patina_text, but no toggling is allowed. Instead,
|
|
30
|
+
## if #view! is defined, pressing enter on the widget calls view! and
|
|
31
|
+
## (if that returns false) #to_s. Otherwise, enter does nothing. This
|
|
32
|
+
## is how non-inlineable attachments work.
|
|
33
|
+
##
|
|
34
|
+
## Independent of all that, a chunk can be quotable, in which case it's
|
|
35
|
+
## included as quoted text during a reply. Text, Quotes, and mime-parsed
|
|
36
|
+
## attachments are quotable; Signatures are not.
|
|
37
|
+
|
|
38
|
+
## monkey-patch time: make temp files have the right extension
|
|
39
|
+
## Backport from Ruby 1.9.2 for versions lower than 1.8.7
|
|
40
|
+
if RUBY_VERSION < '1.8.7'
|
|
41
|
+
class Tempfile
|
|
42
|
+
def make_tmpname(prefix_suffix, n)
|
|
43
|
+
case prefix_suffix
|
|
44
|
+
when String
|
|
45
|
+
prefix = prefix_suffix
|
|
46
|
+
suffix = ""
|
|
47
|
+
when Array
|
|
48
|
+
prefix = prefix_suffix[0]
|
|
49
|
+
suffix = prefix_suffix[1]
|
|
50
|
+
else
|
|
51
|
+
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
|
|
52
|
+
end
|
|
53
|
+
t = Time.now.strftime("%Y%m%d")
|
|
54
|
+
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
|
|
55
|
+
path << "-#{n}" if n
|
|
56
|
+
path << suffix
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
module Redwood
|
|
63
|
+
module Chunk
|
|
64
|
+
class Attachment
|
|
65
|
+
HookManager.register "mime-decode", <<EOS
|
|
66
|
+
Decodes a MIME attachment into text form. The text will be displayed
|
|
67
|
+
directly in Sup. For attachments that you wish to use a separate program
|
|
68
|
+
to view (e.g. images), you should use the mime-view hook instead.
|
|
69
|
+
|
|
70
|
+
Variables:
|
|
71
|
+
content_type: the content-type of the attachment
|
|
72
|
+
charset: the charset of the attachment, if applicable
|
|
73
|
+
filename: the filename of the attachment as saved to disk
|
|
74
|
+
sibling_types: if this attachment is part of a multipart MIME attachment,
|
|
75
|
+
an array of content-types for all attachments. Otherwise,
|
|
76
|
+
the empty array.
|
|
77
|
+
Return value:
|
|
78
|
+
The decoded text of the attachment, or nil if not decoded.
|
|
79
|
+
EOS
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
HookManager.register "mime-view", <<EOS
|
|
83
|
+
Views a non-text MIME attachment. This hook allows you to run
|
|
84
|
+
third-party programs for attachments that require such a thing (e.g.
|
|
85
|
+
images). To instead display a text version of the attachment directly in
|
|
86
|
+
Sup, use the mime-decode hook instead.
|
|
87
|
+
|
|
88
|
+
Note that by default (at least on systems that have a run-mailcap command),
|
|
89
|
+
Sup uses the default mailcap handler for the attachment's MIME type. If
|
|
90
|
+
you want a particular behavior to be global, you may wish to change your
|
|
91
|
+
mailcap instead.
|
|
92
|
+
|
|
93
|
+
Variables:
|
|
94
|
+
content_type: the content-type of the attachment
|
|
95
|
+
filename: the filename of the attachment as saved to disk
|
|
96
|
+
Return value:
|
|
97
|
+
True if the viewing was successful, false otherwise. If false, calling
|
|
98
|
+
/usr/bin/run-mailcap will be tried.
|
|
99
|
+
EOS
|
|
100
|
+
#' stupid ruby-mode
|
|
101
|
+
|
|
102
|
+
## raw_content is the post-MIME-decode content. this is used for
|
|
103
|
+
## saving the attachment to disk.
|
|
104
|
+
attr_reader :content_type, :filename, :lines, :raw_content
|
|
105
|
+
bool_reader :quotable
|
|
106
|
+
|
|
107
|
+
## store tempfile objects as class variables so that they
|
|
108
|
+
## are not removed when the viewing process returns. they
|
|
109
|
+
## should be garbage collected when the class variable is removed.
|
|
110
|
+
@@view_tempfiles = []
|
|
111
|
+
|
|
112
|
+
def initialize content_type, filename, encoded_content, sibling_types
|
|
113
|
+
@content_type = content_type.downcase
|
|
114
|
+
if Shellwords.escape(@content_type) != @content_type
|
|
115
|
+
warn "content_type #{@content_type} is not safe, changed to application/octet-stream"
|
|
116
|
+
@content_type = 'application/octet-stream'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
@filename = filename
|
|
120
|
+
@quotable = false # changed to true if we can parse it through the
|
|
121
|
+
# mime-decode hook, or if it's plain text
|
|
122
|
+
@raw_content =
|
|
123
|
+
if encoded_content.body
|
|
124
|
+
encoded_content.decode
|
|
125
|
+
else
|
|
126
|
+
"For some bizarre reason, RubyMail was unable to parse this attachment.\n"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
text = case @content_type
|
|
130
|
+
when /^text\/plain\b/
|
|
131
|
+
@raw_content
|
|
132
|
+
else
|
|
133
|
+
HookManager.run "mime-decode", :content_type => @content_type,
|
|
134
|
+
:filename => lambda { write_to_disk },
|
|
135
|
+
:charset => encoded_content.charset,
|
|
136
|
+
:sibling_types => sibling_types
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
@lines = nil
|
|
140
|
+
if text
|
|
141
|
+
text = text.transcode(encoded_content.charset || $encoding, text.encoding)
|
|
142
|
+
begin
|
|
143
|
+
@lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
|
144
|
+
rescue Encoding::CompatibilityError
|
|
145
|
+
@lines = text.fix_encoding!.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
|
146
|
+
debug "error while decoding message text, falling back to default encoding, expect errors in encoding: #{text.fix_encoding!}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
@quotable = true
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def color; :text_color end
|
|
154
|
+
def patina_color; :attachment_color end
|
|
155
|
+
def patina_text
|
|
156
|
+
if expandable?
|
|
157
|
+
"Attachment: #{filename} (#{lines.length} lines)"
|
|
158
|
+
else
|
|
159
|
+
"Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
## an attachment is exapndable if we've managed to decode it into
|
|
164
|
+
## something we can display inline. otherwise, it's viewable.
|
|
165
|
+
def inlineable?; false end
|
|
166
|
+
def expandable?; !viewable? end
|
|
167
|
+
def initial_state; :open end
|
|
168
|
+
def viewable?; @lines.nil? end
|
|
169
|
+
def view_default! path
|
|
170
|
+
case RbConfig::CONFIG['arch']
|
|
171
|
+
when /darwin/
|
|
172
|
+
cmd = "open #{path}"
|
|
173
|
+
else
|
|
174
|
+
cmd = "/usr/bin/run-mailcap --action=view #{@content_type}:#{path}"
|
|
175
|
+
end
|
|
176
|
+
debug "running: #{cmd.inspect}"
|
|
177
|
+
BufferManager.shell_out(cmd)
|
|
178
|
+
$? == 0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def view!
|
|
182
|
+
write_to_disk do |path|
|
|
183
|
+
ret = HookManager.run "mime-view", :content_type => @content_type,
|
|
184
|
+
:filename => path
|
|
185
|
+
ret || view_default!(path)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def write_to_disk
|
|
190
|
+
begin
|
|
191
|
+
# Add the original extension to the generated tempfile name only if the
|
|
192
|
+
# extension is "safe" (won't be interpreted by the shell). Since
|
|
193
|
+
# Tempfile.new always generates safe file names this should prevent
|
|
194
|
+
# attacking the user with funny attachment file names.
|
|
195
|
+
tempname = if (File.extname @filename) =~ /^\.[[:alnum:]]+$/ then
|
|
196
|
+
["sup-attachment", File.extname(@filename)]
|
|
197
|
+
else
|
|
198
|
+
"sup-attachment"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
file = Tempfile.new(tempname)
|
|
202
|
+
file.print @raw_content
|
|
203
|
+
file.flush
|
|
204
|
+
|
|
205
|
+
@@view_tempfiles.push file # make sure the tempfile is not garbage collected before sup stops
|
|
206
|
+
|
|
207
|
+
yield file.path if block_given?
|
|
208
|
+
return file.path
|
|
209
|
+
ensure
|
|
210
|
+
file.close
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
## used when viewing the attachment as text
|
|
215
|
+
def to_s
|
|
216
|
+
@lines || @raw_content
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class Text
|
|
221
|
+
|
|
222
|
+
attr_reader :lines
|
|
223
|
+
def initialize lines
|
|
224
|
+
@lines = lines
|
|
225
|
+
## trim off all empty lines except one
|
|
226
|
+
@lines.pop while @lines.length > 1 && @lines[-1] =~ /^\s*$/ && @lines[-2] =~ /^\s*$/
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def inlineable?; true end
|
|
230
|
+
def quotable?; true end
|
|
231
|
+
def expandable?; false end
|
|
232
|
+
def viewable?; false end
|
|
233
|
+
def color; :text_color end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Quote
|
|
237
|
+
attr_reader :lines
|
|
238
|
+
def initialize lines
|
|
239
|
+
@lines = lines
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def inlineable?; @lines.length == 1 end
|
|
243
|
+
def quotable?; true end
|
|
244
|
+
def expandable?; !inlineable? end
|
|
245
|
+
def viewable?; false end
|
|
246
|
+
|
|
247
|
+
def patina_color; :quote_patina_color end
|
|
248
|
+
def patina_text; "(#{lines.length} quoted lines)" end
|
|
249
|
+
def color; :quote_color end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class Signature
|
|
253
|
+
attr_reader :lines
|
|
254
|
+
def initialize lines
|
|
255
|
+
@lines = lines
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def inlineable?; @lines.length == 1 end
|
|
259
|
+
def quotable?; false end
|
|
260
|
+
def expandable?; !inlineable? end
|
|
261
|
+
def viewable?; false end
|
|
262
|
+
|
|
263
|
+
def patina_color; :sig_patina_color end
|
|
264
|
+
def patina_text; "(#{lines.length}-line signature)" end
|
|
265
|
+
def color; :sig_color end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
class EnclosedMessage
|
|
269
|
+
attr_reader :lines
|
|
270
|
+
def initialize from, to, cc, date, subj
|
|
271
|
+
@from = from ? "unknown sender" : from.full_address
|
|
272
|
+
@to = to ? "" : to.map { |p| p.full_address }.join(", ")
|
|
273
|
+
@cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
|
|
274
|
+
if date
|
|
275
|
+
@date = date.rfc822
|
|
276
|
+
else
|
|
277
|
+
@date = ""
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
@subj = subj
|
|
281
|
+
|
|
282
|
+
@lines = "\nFrom: #{from}\n"
|
|
283
|
+
@lines += "To: #{to}\n"
|
|
284
|
+
if !cc.empty?
|
|
285
|
+
@lines += "Cc: #{cc}\n"
|
|
286
|
+
end
|
|
287
|
+
@lines += "Date: #{date}\n"
|
|
288
|
+
@lines += "Subject: #{subj}\n\n"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def inlineable?; false end
|
|
292
|
+
def quotable?; false end
|
|
293
|
+
def expandable?; true end
|
|
294
|
+
def initial_state; :closed end
|
|
295
|
+
def viewable?; false end
|
|
296
|
+
|
|
297
|
+
def patina_color; :generic_notice_patina_color end
|
|
298
|
+
def patina_text; "Begin enclosed message sent on #{@date}" end
|
|
299
|
+
|
|
300
|
+
def color; :quote_color end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
class CryptoNotice
|
|
304
|
+
attr_reader :lines, :status, :patina_text
|
|
305
|
+
|
|
306
|
+
def initialize status, description, lines=[]
|
|
307
|
+
@status = status
|
|
308
|
+
@patina_text = description
|
|
309
|
+
@lines = lines
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def patina_color
|
|
313
|
+
case status
|
|
314
|
+
when :valid then :cryptosig_valid_color
|
|
315
|
+
when :valid_untrusted then :cryptosig_valid_untrusted_color
|
|
316
|
+
when :invalid then :cryptosig_invalid_color
|
|
317
|
+
else :cryptosig_unknown_color
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
def color; patina_color end
|
|
321
|
+
|
|
322
|
+
def inlineable?; false end
|
|
323
|
+
def quotable?; false end
|
|
324
|
+
def expandable?; !@lines.empty? end
|
|
325
|
+
def viewable?; false end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
data/lib/sup/mode.rb
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
module Redwood
|
|
3
|
+
|
|
4
|
+
class Mode
|
|
5
|
+
attr_accessor :buffer
|
|
6
|
+
@@keymaps = {}
|
|
7
|
+
|
|
8
|
+
def self.register_keymap keymap=nil, &b
|
|
9
|
+
keymap = Keymap.new(&b) if keymap.nil?
|
|
10
|
+
@@keymaps[self] = keymap
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.keymap
|
|
14
|
+
@@keymaps[self] || register_keymap
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.keymaps
|
|
18
|
+
@@keymaps
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@buffer = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.make_name s; s.gsub(/.*::/, "").camel_to_hyphy; end
|
|
26
|
+
def name; Mode.make_name self.class.name; end
|
|
27
|
+
|
|
28
|
+
def self.load_all_modes dir
|
|
29
|
+
Dir[File.join(dir, "*.rb")].each do |f|
|
|
30
|
+
$stderr.puts "## loading mode #{f}"
|
|
31
|
+
require f
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def killable?; true; end
|
|
36
|
+
def unsaved?; false end
|
|
37
|
+
def draw; end
|
|
38
|
+
def focus; end
|
|
39
|
+
def blur; end
|
|
40
|
+
def cancel_search!; end
|
|
41
|
+
def in_search?; false end
|
|
42
|
+
def status; ""; end
|
|
43
|
+
def resize rows, cols; end
|
|
44
|
+
def cleanup
|
|
45
|
+
@buffer = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def resolve_input c
|
|
49
|
+
ancestors.each do |klass| # try all keymaps in order of ancestry
|
|
50
|
+
next unless @@keymaps.member?(klass)
|
|
51
|
+
action = BufferManager.resolve_input_with_keymap c, @@keymaps[klass]
|
|
52
|
+
return action if action
|
|
53
|
+
end
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def handle_input c
|
|
58
|
+
action = resolve_input(c) or return false
|
|
59
|
+
send action
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def help_text
|
|
64
|
+
used_keys = {}
|
|
65
|
+
ancestors.map do |klass|
|
|
66
|
+
km = @@keymaps[klass] or next
|
|
67
|
+
title = "Keybindings from #{Mode.make_name klass.name}"
|
|
68
|
+
s = <<EOS
|
|
69
|
+
#{title}
|
|
70
|
+
#{'-' * title.display_length}
|
|
71
|
+
|
|
72
|
+
#{km.help_text used_keys}
|
|
73
|
+
EOS
|
|
74
|
+
begin
|
|
75
|
+
used_keys.merge! km.keysyms.to_boolean_h
|
|
76
|
+
rescue ArgumentError
|
|
77
|
+
raise km.keysyms.inspect
|
|
78
|
+
end
|
|
79
|
+
s
|
|
80
|
+
end.compact.join "\n"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
### helper functions
|
|
84
|
+
|
|
85
|
+
def save_to_file fn, talk=true
|
|
86
|
+
if File.exists? fn
|
|
87
|
+
unless BufferManager.ask_yes_or_no "File \"#{fn}\" exists. Overwrite?"
|
|
88
|
+
info "Not overwriting #{fn}"
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
begin
|
|
93
|
+
File.open(fn, "w") { |f| yield f }
|
|
94
|
+
BufferManager.flash "Successfully wrote #{fn}." if talk
|
|
95
|
+
true
|
|
96
|
+
rescue SystemCallError, IOError => e
|
|
97
|
+
m = "Error writing file: #{e.message}"
|
|
98
|
+
info m
|
|
99
|
+
BufferManager.flash m
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def pipe_to_process command
|
|
105
|
+
Open3.popen3(command) do |input, output, error|
|
|
106
|
+
err, data, * = IO.select [error], [input], nil
|
|
107
|
+
|
|
108
|
+
unless err.empty?
|
|
109
|
+
message = err.first.read
|
|
110
|
+
if message =~ /^\s*$/
|
|
111
|
+
warn "error running #{command} (but no error message)"
|
|
112
|
+
BufferManager.flash "Error running #{command}!"
|
|
113
|
+
else
|
|
114
|
+
warn "error running #{command}: #{message}"
|
|
115
|
+
BufferManager.flash "Error: #{message}"
|
|
116
|
+
end
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
data = data.first
|
|
121
|
+
data.sync = false # buffer input
|
|
122
|
+
|
|
123
|
+
yield data
|
|
124
|
+
data.close # output will block unless input is closed
|
|
125
|
+
|
|
126
|
+
## BUG?: shows errors or output but not both....
|
|
127
|
+
data, * = IO.select [output, error], nil, nil
|
|
128
|
+
data = data.first
|
|
129
|
+
|
|
130
|
+
if data.eof
|
|
131
|
+
BufferManager.flash "'#{command}' done!"
|
|
132
|
+
nil
|
|
133
|
+
else
|
|
134
|
+
data.read
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|