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/bin/sup-tweak-labels
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'trollop'
|
5
|
+
require 'enumerator'
|
5
6
|
require "sup"
|
6
7
|
|
7
8
|
class Float
|
@@ -37,8 +38,8 @@ be seen by running "sup-add --help".
|
|
37
38
|
|
38
39
|
Options:
|
39
40
|
EOS
|
40
|
-
opt :add, "One or more labels (comma-separated) to add to every message from the specified sources", :
|
41
|
-
opt :remove, "One or more labels (comma-separated) to remove from every message from the specified sources, if those labels are present", :
|
41
|
+
opt :add, "One or more labels (comma-separated) to add to every message from the specified sources", :default => ""
|
42
|
+
opt :remove, "One or more labels (comma-separated) to remove from every message from the specified sources, if those labels are present", :default => ""
|
42
43
|
opt :query, "A Sup search query", :type => String
|
43
44
|
|
44
45
|
text <<EOS
|
@@ -53,23 +54,24 @@ EOS
|
|
53
54
|
end
|
54
55
|
opts[:verbose] = true if opts[:very_verbose]
|
55
56
|
|
56
|
-
add_labels =
|
57
|
-
remove_labels =
|
57
|
+
add_labels = opts[:add].to_set_of_symbols ","
|
58
|
+
remove_labels = opts[:remove].to_set_of_symbols ","
|
58
59
|
|
59
60
|
Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
|
61
|
+
Trollop::die "no sources specified" if ARGV.empty?
|
60
62
|
|
61
63
|
Redwood::start
|
64
|
+
index = Redwood::Index.init
|
65
|
+
index.lock_interactively or exit
|
62
66
|
begin
|
63
|
-
index = Redwood::Index.new
|
64
67
|
index.load
|
65
68
|
|
66
|
-
source_ids =
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
69
|
+
source_ids = if opts[:all_sources]
|
70
|
+
Redwood::SourceManager.sources
|
71
|
+
else
|
72
|
+
ARGV.map do |uri|
|
73
|
+
Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
74
|
+
end
|
73
75
|
end.map { |s| s.id }
|
74
76
|
Trollop::die "nothing to do: no sources" if source_ids.empty?
|
75
77
|
|
@@ -81,29 +83,30 @@ begin
|
|
81
83
|
end
|
82
84
|
query += ' ' + opts[:query] if opts[:query]
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
+
parsed_query = index.parse_query query
|
87
|
+
parsed_query.merge! :load_spam => true, :load_deleted => true, :load_killed => true
|
88
|
+
ids = Enumerable::Enumerator.new(index, :each_id, parsed_query).map
|
89
|
+
num_total = ids.size
|
86
90
|
|
87
91
|
$stderr.puts "Found #{num_total} documents across #{source_ids.length} sources. Scanning..."
|
88
92
|
|
89
93
|
num_changed = num_scanned = 0
|
90
94
|
last_info_time = start_time = Time.now
|
91
|
-
|
95
|
+
ids.each do |id|
|
92
96
|
num_scanned += 1
|
93
97
|
|
94
98
|
m = index.build_message id
|
95
|
-
old_labels = m.labels.
|
99
|
+
old_labels = m.labels.dup
|
96
100
|
|
97
101
|
m.labels += add_labels
|
98
102
|
m.labels -= remove_labels
|
99
|
-
m.labels = m.labels.uniq
|
100
103
|
|
101
|
-
unless m.labels
|
104
|
+
unless m.labels == old_labels
|
102
105
|
num_changed += 1
|
103
106
|
puts "From #{m.from}, subject: #{m.subj}" if opts[:very_verbose]
|
104
|
-
puts "#{m.id}: {#{old_labels.join ','}} => {#{m.labels.join ','}}" if opts[:verbose]
|
107
|
+
puts "#{m.id}: {#{old_labels.to_a.join ','}} => {#{m.labels.to_a.join ','}}" if opts[:verbose]
|
105
108
|
puts if opts[:very_verbose]
|
106
|
-
index.
|
109
|
+
index.update_message_state m unless opts[:dry_run]
|
107
110
|
end
|
108
111
|
|
109
112
|
if Time.now - last_info_time > 60
|
@@ -118,7 +121,7 @@ begin
|
|
118
121
|
|
119
122
|
unless num_changed == 0
|
120
123
|
$stderr.puts "Optimizing index..."
|
121
|
-
index.
|
124
|
+
index.optimize unless opts[:dry_run]
|
122
125
|
end
|
123
126
|
|
124
127
|
rescue Exception => e
|
data/lib/sup.rb
CHANGED
@@ -5,6 +5,10 @@ require 'thread'
|
|
5
5
|
require 'fileutils'
|
6
6
|
require 'gettext'
|
7
7
|
require 'curses'
|
8
|
+
begin
|
9
|
+
require 'fastthread'
|
10
|
+
rescue LoadError
|
11
|
+
end
|
8
12
|
|
9
13
|
class Object
|
10
14
|
## this is for debugging purposes because i keep calling #id on the
|
@@ -20,7 +24,7 @@ class Module
|
|
20
24
|
vars = props.map { |p| "@#{p}" }
|
21
25
|
klass = self
|
22
26
|
path = klass.name.gsub(/::/, "/")
|
23
|
-
|
27
|
+
|
24
28
|
klass.instance_eval do
|
25
29
|
define_method(:to_yaml_properties) { vars }
|
26
30
|
define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
|
@@ -33,7 +37,7 @@ class Module
|
|
33
37
|
end
|
34
38
|
|
35
39
|
module Redwood
|
36
|
-
VERSION = "0.
|
40
|
+
VERSION = "0.9"
|
37
41
|
|
38
42
|
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
39
43
|
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
|
@@ -50,6 +54,8 @@ module Redwood
|
|
50
54
|
YAML_DOMAIN = "masanjin.net"
|
51
55
|
YAML_DATE = "2006-10-01"
|
52
56
|
|
57
|
+
DEFAULT_INDEX = 'ferret'
|
58
|
+
|
53
59
|
## record exceptions thrown in threads nicely
|
54
60
|
@exceptions = []
|
55
61
|
@exception_mutex = Mutex.new
|
@@ -79,38 +85,52 @@ module Redwood
|
|
79
85
|
module_function :reporting_thread, :record_exception, :exceptions
|
80
86
|
|
81
87
|
## one-stop shop for yamliciousness
|
82
|
-
def save_yaml_obj
|
88
|
+
def save_yaml_obj o, fn, safe=false
|
89
|
+
o = if o.is_a?(Array)
|
90
|
+
o.map { |x| (x.respond_to?(:before_marshal) && x.before_marshal) || x }
|
91
|
+
elsif o.respond_to? :before_marshal
|
92
|
+
o.before_marshal
|
93
|
+
else
|
94
|
+
o
|
95
|
+
end
|
96
|
+
|
83
97
|
if safe
|
84
98
|
safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
|
85
99
|
mode = File.stat(fn).mode if File.exists? fn
|
86
|
-
File.open(safe_fn, "w", mode) { |f| f.puts
|
100
|
+
File.open(safe_fn, "w", mode) { |f| f.puts o.to_yaml }
|
87
101
|
FileUtils.mv safe_fn, fn
|
88
102
|
else
|
89
|
-
File.open(fn, "w") { |f| f.puts
|
103
|
+
File.open(fn, "w") { |f| f.puts o.to_yaml }
|
90
104
|
end
|
91
105
|
end
|
92
106
|
|
93
107
|
def load_yaml_obj fn, compress=false
|
94
|
-
if File.exists? fn
|
108
|
+
o = if File.exists? fn
|
95
109
|
if compress
|
96
110
|
Zlib::GzipReader.open(fn) { |f| YAML::load f }
|
97
111
|
else
|
98
112
|
YAML::load_file fn
|
99
113
|
end
|
100
114
|
end
|
115
|
+
if o.is_a?(Array)
|
116
|
+
o.each { |x| x.after_unmarshal! if x.respond_to?(:after_unmarshal!) }
|
117
|
+
else
|
118
|
+
o.after_unmarshal! if o.respond_to?(:after_unmarshal!)
|
119
|
+
end
|
120
|
+
o
|
101
121
|
end
|
102
122
|
|
103
123
|
def start
|
104
|
-
Redwood::SentManager.
|
105
|
-
Redwood::ContactManager.
|
106
|
-
Redwood::LabelManager.
|
107
|
-
Redwood::AccountManager.
|
108
|
-
Redwood::DraftManager.
|
109
|
-
Redwood::UpdateManager.
|
110
|
-
Redwood::PollManager.
|
111
|
-
Redwood::
|
112
|
-
Redwood::
|
113
|
-
Redwood::
|
124
|
+
Redwood::SentManager.init $config[:sent_source] || 'sup://sent'
|
125
|
+
Redwood::ContactManager.init Redwood::CONTACT_FN
|
126
|
+
Redwood::LabelManager.init Redwood::LABEL_FN
|
127
|
+
Redwood::AccountManager.init $config[:accounts]
|
128
|
+
Redwood::DraftManager.init Redwood::DRAFT_DIR
|
129
|
+
Redwood::UpdateManager.init
|
130
|
+
Redwood::PollManager.init
|
131
|
+
Redwood::CryptoManager.init
|
132
|
+
Redwood::UndoManager.init
|
133
|
+
Redwood::SourceManager.init
|
114
134
|
end
|
115
135
|
|
116
136
|
def finish
|
@@ -126,7 +146,7 @@ module Redwood
|
|
126
146
|
def report_broken_sources opts={}
|
127
147
|
return unless BufferManager.instantiated?
|
128
148
|
|
129
|
-
broken_sources =
|
149
|
+
broken_sources = SourceManager.sources.select { |s| s.error.is_a? FatalSourceError }
|
130
150
|
unless broken_sources.empty?
|
131
151
|
BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
|
132
152
|
TextMode.new(<<EOM)
|
@@ -143,7 +163,7 @@ EOM
|
|
143
163
|
end
|
144
164
|
end
|
145
165
|
|
146
|
-
desynced_sources =
|
166
|
+
desynced_sources = SourceManager.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
|
147
167
|
unless desynced_sources.empty?
|
148
168
|
BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
|
149
169
|
TextMode.new(<<EOM)
|
@@ -175,6 +195,7 @@ end
|
|
175
195
|
## set up default configuration file
|
176
196
|
if File.exists? Redwood::CONFIG_FN
|
177
197
|
$config = Redwood::load_yaml_obj Redwood::CONFIG_FN
|
198
|
+
abort "#{Redwood::CONFIG_FN} is not a valid configuration file (it's a #{$config.class}, not a hash)" unless $config.is_a?(Hash)
|
178
199
|
else
|
179
200
|
require 'etc'
|
180
201
|
require 'socket'
|
@@ -207,6 +228,7 @@ else
|
|
207
228
|
:confirm_top_posting => true,
|
208
229
|
:discard_snippets_from_encrypted_messages => false,
|
209
230
|
:default_attachment_save_dir => "",
|
231
|
+
:sent_source => "sup://sent"
|
210
232
|
}
|
211
233
|
begin
|
212
234
|
FileUtils.mkdir_p Redwood::BASE_DIR
|
@@ -222,33 +244,29 @@ require "sup/hook"
|
|
222
244
|
## we have to initialize this guy first, because other classes must
|
223
245
|
## reference it in order to register hooks, and they do that at parse
|
224
246
|
## time.
|
225
|
-
Redwood::HookManager.
|
247
|
+
Redwood::HookManager.init Redwood::HOOK_DIR
|
226
248
|
|
227
249
|
## everything we need to get logging working
|
228
|
-
require "sup/buffer"
|
229
|
-
require "sup/keymap"
|
230
|
-
require "sup/mode"
|
231
|
-
require "sup/modes/scroll-mode"
|
232
|
-
require "sup/modes/text-mode"
|
233
|
-
require "sup/modes/log-mode"
|
234
250
|
require "sup/logger"
|
235
|
-
|
236
|
-
|
237
|
-
module_function :log
|
238
|
-
end
|
251
|
+
Redwood::Logger.init.add_sink $stderr
|
252
|
+
include Redwood::LogsStuff
|
239
253
|
|
240
254
|
## determine encoding and character set
|
241
255
|
$encoding = Locale.current.charset
|
242
256
|
if $encoding
|
243
|
-
|
257
|
+
debug "using character set encoding #{$encoding.inspect}"
|
244
258
|
else
|
245
|
-
|
259
|
+
warn "can't find character set by using locale, defaulting to utf-8"
|
246
260
|
$encoding = "UTF-8"
|
247
261
|
end
|
248
262
|
|
249
|
-
|
263
|
+
require "sup/buffer"
|
264
|
+
require "sup/keymap"
|
265
|
+
require "sup/mode"
|
266
|
+
require "sup/modes/scroll-mode"
|
267
|
+
require "sup/modes/text-mode"
|
268
|
+
require "sup/modes/log-mode"
|
250
269
|
require "sup/update"
|
251
|
-
require "sup/suicide"
|
252
270
|
require "sup/message-chunks"
|
253
271
|
require "sup/message"
|
254
272
|
require "sup/source"
|
@@ -258,6 +276,7 @@ require "sup/imap"
|
|
258
276
|
require "sup/person"
|
259
277
|
require "sup/account"
|
260
278
|
require "sup/thread"
|
279
|
+
require "sup/interactive-lock"
|
261
280
|
require "sup/index"
|
262
281
|
require "sup/textfield"
|
263
282
|
require "sup/colormap"
|
@@ -288,6 +307,7 @@ require "sup/modes/buffer-list-mode"
|
|
288
307
|
require "sup/modes/poll-mode"
|
289
308
|
require "sup/modes/file-browser-mode"
|
290
309
|
require "sup/modes/completion-mode"
|
310
|
+
require "sup/modes/console-mode"
|
291
311
|
require "sup/sent"
|
292
312
|
|
293
313
|
$:.each do |base|
|
data/lib/sup/account.rb
CHANGED
data/lib/sup/buffer.rb
CHANGED
@@ -25,17 +25,24 @@ module Ncurses
|
|
25
25
|
def mutex; @mutex ||= Mutex.new; end
|
26
26
|
def sync &b; mutex.synchronize(&b); end
|
27
27
|
|
28
|
-
## magically, this stuff seems to work now. i could swear it didn't
|
29
|
-
## before. hm.
|
30
28
|
def nonblocking_getch
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
## INSANTIY
|
30
|
+
## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
|
31
|
+
## background threads will be BLOCKED. (except in very modern versions
|
32
|
+
## of libncurses-ruby. the current one on ubuntu seems to work well.)
|
33
|
+
if IO.select([$stdin], nil, nil, 0.5)
|
34
|
+
c = Ncurses.getch
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
## pretends ctrl-c's are ctrl-g's
|
39
|
+
def safe_nonblocking_getch
|
40
|
+
nonblocking_getch
|
41
|
+
rescue Interrupt
|
42
|
+
KEY_CANCEL
|
43
|
+
end
|
44
|
+
|
45
|
+
module_function :rows, :cols, :curx, :nonblocking_getch, :safe_nonblocking_getch, :mutex, :sync
|
39
46
|
|
40
47
|
remove_const :KEY_ENTER
|
41
48
|
remove_const :KEY_CANCEL
|
@@ -70,7 +77,7 @@ class Buffer
|
|
70
77
|
def content_height; @height - 1; end
|
71
78
|
def content_width; @width; end
|
72
79
|
|
73
|
-
def resize rows, cols
|
80
|
+
def resize rows, cols
|
74
81
|
return if cols == @width && rows == @height
|
75
82
|
@width = cols
|
76
83
|
@height = rows
|
@@ -196,10 +203,13 @@ EOS
|
|
196
203
|
@flash = nil
|
197
204
|
@shelled = @asking = false
|
198
205
|
@in_x = ENV["TERM"] =~ /(xterm|rxvt|screen)/
|
199
|
-
|
200
|
-
|
206
|
+
@sigwinch_happened = false
|
207
|
+
@sigwinch_mutex = Mutex.new
|
201
208
|
end
|
202
209
|
|
210
|
+
def sigwinch_happened!; @sigwinch_mutex.synchronize { @sigwinch_happened = true } end
|
211
|
+
def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end
|
212
|
+
|
203
213
|
def buffers; @name_map.to_a; end
|
204
214
|
|
205
215
|
def focus_on buf
|
@@ -230,14 +240,20 @@ EOS
|
|
230
240
|
## have to change this. but it's not clear that we will ever actually
|
231
241
|
## do that.
|
232
242
|
def roll_buffers
|
233
|
-
|
234
|
-
|
243
|
+
bufs = rollable_buffers
|
244
|
+
bufs.last.force_to_top = false
|
245
|
+
raise_to_front bufs.first
|
235
246
|
end
|
236
247
|
|
237
248
|
def roll_buffers_backwards
|
238
|
-
|
239
|
-
|
240
|
-
|
249
|
+
bufs = rollable_buffers
|
250
|
+
return unless bufs.length > 1
|
251
|
+
bufs.last.force_to_top = false
|
252
|
+
raise_to_front bufs[bufs.length - 2]
|
253
|
+
end
|
254
|
+
|
255
|
+
def rollable_buffers
|
256
|
+
@buffers.select { |b| !b.system? || @buffers.last == b }
|
241
257
|
end
|
242
258
|
|
243
259
|
def handle_input c
|
@@ -261,6 +277,14 @@ EOS
|
|
261
277
|
def completely_redraw_screen
|
262
278
|
return if @shelled
|
263
279
|
|
280
|
+
## this magic makes Ncurses get the new size of the screen
|
281
|
+
Ncurses.endwin
|
282
|
+
Ncurses.stdscr.keypad 1
|
283
|
+
Ncurses.curs_set 0
|
284
|
+
Ncurses.refresh
|
285
|
+
@sigwinch_mutex.synchronize { @sigwinch_happened = false }
|
286
|
+
debug "new screen size is #{Ncurses.rows} x #{Ncurses.cols}"
|
287
|
+
|
264
288
|
status, title = get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
|
265
289
|
|
266
290
|
Ncurses.sync do
|
@@ -366,7 +390,7 @@ EOS
|
|
366
390
|
draw_screen
|
367
391
|
|
368
392
|
until mode.done?
|
369
|
-
c = Ncurses.
|
393
|
+
c = Ncurses.safe_nonblocking_getch
|
370
394
|
next unless c # getch timeout
|
371
395
|
break if c == Ncurses::KEY_CANCEL
|
372
396
|
begin
|
@@ -468,7 +492,7 @@ EOS
|
|
468
492
|
end
|
469
493
|
|
470
494
|
if answer
|
471
|
-
answer =
|
495
|
+
answer =
|
472
496
|
if answer.empty?
|
473
497
|
spawn_modal "file browser", FileBrowserMode.new
|
474
498
|
elsif File.directory?(answer)
|
@@ -484,7 +508,7 @@ EOS
|
|
484
508
|
## returns an array of labels
|
485
509
|
def ask_for_labels domain, question, default_labels, forbidden_labels=[]
|
486
510
|
default_labels = default_labels - forbidden_labels - LabelManager::RESERVED_LABELS
|
487
|
-
default = default_labels.join(" ")
|
511
|
+
default = default_labels.to_a.join(" ")
|
488
512
|
default += " " unless default.empty?
|
489
513
|
|
490
514
|
# here I would prefer to give more control and allow all_labels instead of
|
@@ -495,7 +519,7 @@ EOS
|
|
495
519
|
|
496
520
|
return unless answer
|
497
521
|
|
498
|
-
user_labels = answer.
|
522
|
+
user_labels = answer.to_set_of_symbols
|
499
523
|
user_labels.each do |l|
|
500
524
|
if forbidden_labels.include?(l) || LabelManager::RESERVED_LABELS.include?(l)
|
501
525
|
BufferManager.flash "'#{l}' is a reserved label!"
|
@@ -508,7 +532,7 @@ EOS
|
|
508
532
|
def ask_for_contacts domain, question, default_contacts=[]
|
509
533
|
default = default_contacts.map { |s| s.to_s }.join(" ")
|
510
534
|
default += " " unless default.empty?
|
511
|
-
|
535
|
+
|
512
536
|
recent = Index.load_contacts(AccountManager.user_emails, :num => 10).map { |c| [c.full_address, c.email] }
|
513
537
|
contacts = ContactManager.contacts.map { |c| [ContactManager.alias_for(c), c.full_address, c.email] }
|
514
538
|
|
@@ -542,7 +566,7 @@ EOS
|
|
542
566
|
end
|
543
567
|
|
544
568
|
while true
|
545
|
-
c = Ncurses.
|
569
|
+
c = Ncurses.safe_nonblocking_getch
|
546
570
|
next unless c # getch timeout
|
547
571
|
break unless tf.handle_input c # process keystroke
|
548
572
|
|
@@ -595,7 +619,7 @@ EOS
|
|
595
619
|
ret = nil
|
596
620
|
done = false
|
597
621
|
until done
|
598
|
-
key = Ncurses.
|
622
|
+
key = Ncurses.safe_nonblocking_getch or next
|
599
623
|
if key == Ncurses::KEY_CANCEL
|
600
624
|
done = true
|
601
625
|
elsif accept.nil? || accept.empty? || accept.member?(key)
|
@@ -723,6 +747,7 @@ EOS
|
|
723
747
|
Ncurses.sync do
|
724
748
|
Ncurses.endwin
|
725
749
|
system command
|
750
|
+
Ncurses.stdscr.keypad 1
|
726
751
|
Ncurses.refresh
|
727
752
|
Ncurses.curs_set 0
|
728
753
|
end
|