sup 0.0.8 → 0.1
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/HACKING +6 -36
- data/History.txt +11 -0
- data/Manifest.txt +5 -0
- data/README.txt +13 -31
- data/Rakefile +3 -3
- data/bin/sup +167 -89
- data/bin/sup-add +39 -29
- data/bin/sup-config +57 -31
- data/bin/sup-sync +60 -54
- data/bin/sup-sync-back +143 -0
- data/doc/FAQ.txt +56 -19
- data/doc/Philosophy.txt +34 -33
- data/doc/TODO +76 -46
- data/doc/UserGuide.txt +142 -122
- data/lib/sup.rb +76 -36
- data/lib/sup/account.rb +27 -19
- data/lib/sup/buffer.rb +130 -44
- data/lib/sup/contact.rb +1 -1
- data/lib/sup/draft.rb +1 -2
- data/lib/sup/imap.rb +64 -19
- data/lib/sup/index.rb +95 -16
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/label.rb +31 -5
- data/lib/sup/maildir.rb +7 -5
- data/lib/sup/mbox.rb +34 -15
- data/lib/sup/mbox/loader.rb +30 -12
- data/lib/sup/mbox/ssh-loader.rb +7 -5
- data/lib/sup/message.rb +93 -44
- data/lib/sup/modes/buffer-list-mode.rb +1 -1
- data/lib/sup/modes/completion-mode.rb +55 -0
- data/lib/sup/modes/compose-mode.rb +6 -25
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +119 -29
- data/lib/sup/modes/file-browser-mode.rb +108 -0
- data/lib/sup/modes/forward-mode.rb +3 -20
- data/lib/sup/modes/inbox-mode.rb +9 -12
- data/lib/sup/modes/label-list-mode.rb +28 -46
- data/lib/sup/modes/label-search-results-mode.rb +1 -16
- data/lib/sup/modes/line-cursor-mode.rb +44 -5
- data/lib/sup/modes/person-search-results-mode.rb +1 -16
- data/lib/sup/modes/reply-mode.rb +18 -31
- data/lib/sup/modes/resume-mode.rb +6 -6
- data/lib/sup/modes/scroll-mode.rb +6 -5
- data/lib/sup/modes/search-results-mode.rb +6 -17
- data/lib/sup/modes/thread-index-mode.rb +70 -28
- data/lib/sup/modes/thread-view-mode.rb +65 -29
- data/lib/sup/person.rb +71 -30
- data/lib/sup/poll.rb +13 -4
- data/lib/sup/rfc2047.rb +61 -0
- data/lib/sup/sent.rb +7 -5
- data/lib/sup/source.rb +12 -9
- data/lib/sup/suicide.rb +36 -0
- data/lib/sup/tagger.rb +6 -6
- data/lib/sup/textfield.rb +76 -14
- data/lib/sup/thread.rb +97 -123
- data/lib/sup/util.rb +167 -1
- metadata +30 -5
data/lib/sup.rb
CHANGED
@@ -12,8 +12,26 @@ class Object
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
class Module
|
16
|
+
def yaml_properties *props
|
17
|
+
props = props.map { |p| p.to_s }
|
18
|
+
vars = props.map { |p| "@#{p}" }
|
19
|
+
klass = self
|
20
|
+
path = klass.name.gsub(/::/, "/")
|
21
|
+
|
22
|
+
klass.instance_eval do
|
23
|
+
define_method(:to_yaml_properties) { vars }
|
24
|
+
define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
|
25
|
+
end
|
26
|
+
|
27
|
+
YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
|
28
|
+
klass.new(*props.map { |p| val[p] })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
15
33
|
module Redwood
|
16
|
-
VERSION = "0.
|
34
|
+
VERSION = "0.1"
|
17
35
|
|
18
36
|
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
19
37
|
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
|
@@ -23,46 +41,51 @@ module Redwood
|
|
23
41
|
CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
|
24
42
|
DRAFT_DIR = File.join(BASE_DIR, "drafts")
|
25
43
|
SENT_FN = File.join(BASE_DIR, "sent.mbox")
|
44
|
+
LOCK_FN = File.join(BASE_DIR, "lock")
|
45
|
+
SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
|
26
46
|
|
27
47
|
YAML_DOMAIN = "masanjin.net"
|
28
48
|
YAML_DATE = "2006-10-01"
|
29
49
|
|
50
|
+
## determine encoding and character set
|
51
|
+
## probably a better way to do this
|
52
|
+
$ctype = ENV["LC_CTYPE"] || ENV["LANG"] || "en-US.utf-8"
|
53
|
+
$encoding =
|
54
|
+
if $ctype =~ /\.(.*)?/
|
55
|
+
$1
|
56
|
+
else
|
57
|
+
"utf-8"
|
58
|
+
end
|
59
|
+
|
30
60
|
## record exceptions thrown in threads nicely
|
31
61
|
$exception = nil
|
32
62
|
def reporting_thread
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
63
|
+
if $opts[:no_threads]
|
64
|
+
yield
|
65
|
+
else
|
66
|
+
::Thread.new do
|
67
|
+
begin
|
68
|
+
yield
|
69
|
+
rescue Exception => e
|
70
|
+
File.open("sup-exception-log.txt", "w") do |f|
|
71
|
+
f.puts "--- #{e.class.name} at #{Time.now}"
|
72
|
+
f.puts e.message, e.backtrace
|
73
|
+
end
|
74
|
+
$exception ||= e
|
75
|
+
raise
|
40
76
|
end
|
41
|
-
$exception ||= e
|
42
|
-
raise
|
43
77
|
end
|
44
78
|
end
|
45
79
|
end
|
46
80
|
module_function :reporting_thread
|
47
81
|
|
48
82
|
## one-stop shop for yamliciousness
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
define_method(:to_yaml_type) { "!#{YAML_DOMAIN},#{YAML_DATE}/#{path}" }
|
56
|
-
end
|
57
|
-
|
58
|
-
YAML.add_domain_type("#{YAML_DOMAIN},#{YAML_DATE}", path) do |type, val|
|
59
|
-
klass.new(*props.map { |p| val[p] })
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def save_yaml_obj object, fn, compress=false
|
64
|
-
if compress
|
65
|
-
Zlib::GzipWriter.open(fn) { |f| f.puts object.to_yaml }
|
83
|
+
def save_yaml_obj object, fn, safe=false
|
84
|
+
if safe
|
85
|
+
safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
|
86
|
+
mode = File.stat(fn) if File.exists? fn
|
87
|
+
File.open(safe_fn, "w", mode) { |f| f.puts object.to_yaml }
|
88
|
+
FileUtils.mv safe_fn, fn
|
66
89
|
else
|
67
90
|
File.open(fn, "w") { |f| f.puts object.to_yaml }
|
68
91
|
end
|
@@ -87,13 +110,14 @@ module Redwood
|
|
87
110
|
Redwood::DraftManager.new Redwood::DRAFT_DIR
|
88
111
|
Redwood::UpdateManager.new
|
89
112
|
Redwood::PollManager.new
|
113
|
+
Redwood::SuicideManager.new Redwood::SUICIDE_FN
|
90
114
|
end
|
91
115
|
|
92
116
|
def finish
|
93
|
-
Redwood::LabelManager.save
|
94
|
-
Redwood::ContactManager.save
|
95
|
-
Redwood::PersonManager.save
|
96
|
-
Redwood::BufferManager.deinstantiate!
|
117
|
+
Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
|
118
|
+
Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
|
119
|
+
Redwood::PersonManager.save if Redwood::PersonManager.instantiated?
|
120
|
+
Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
|
97
121
|
end
|
98
122
|
|
99
123
|
## not really a good place for this, so I'll just dump it here.
|
@@ -110,7 +134,7 @@ Hi there. It looks like one or more message sources is reporting
|
|
110
134
|
errors. Until this is corrected, messages from these sources cannot
|
111
135
|
be viewed, and new messages will not be detected.
|
112
136
|
|
113
|
-
#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n ")}.join(
|
137
|
+
#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n ")}.join("\n\n")}
|
114
138
|
EOM
|
115
139
|
#' stupid ruby-mode
|
116
140
|
end
|
@@ -138,24 +162,37 @@ EOM
|
|
138
162
|
end
|
139
163
|
end
|
140
164
|
|
141
|
-
module_function :
|
165
|
+
module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
|
166
|
+
:report_broken_sources
|
142
167
|
end
|
143
168
|
|
144
169
|
## set up default configuration file
|
145
170
|
if File.exists? Redwood::CONFIG_FN
|
146
171
|
$config = Redwood::load_yaml_obj Redwood::CONFIG_FN
|
147
172
|
else
|
173
|
+
require 'etc'
|
174
|
+
require 'socket'
|
175
|
+
name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first
|
176
|
+
email = ENV["USER"] + "@" +
|
177
|
+
begin
|
178
|
+
Socket.gethostbyname(Socket.gethostname).first
|
179
|
+
rescue SocketError
|
180
|
+
Socket.gethostname
|
181
|
+
end
|
182
|
+
|
148
183
|
$config = {
|
149
184
|
:accounts => {
|
150
185
|
:default => {
|
151
|
-
:name =>
|
152
|
-
:email =>
|
186
|
+
:name => name,
|
187
|
+
:email => email,
|
153
188
|
:alternates => [],
|
154
189
|
:sendmail => "/usr/sbin/sendmail -oem -ti",
|
155
190
|
:signature => File.join(ENV["HOME"], ".signature")
|
156
191
|
}
|
157
192
|
},
|
158
|
-
:editor => ENV["EDITOR"] || "/usr/bin/
|
193
|
+
:editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
|
194
|
+
:thread_by_subject => false,
|
195
|
+
:edit_signature => false,
|
159
196
|
}
|
160
197
|
begin
|
161
198
|
FileUtils.mkdir_p Redwood::BASE_DIR
|
@@ -167,6 +204,7 @@ end
|
|
167
204
|
|
168
205
|
require "sup/util"
|
169
206
|
require "sup/update"
|
207
|
+
require "sup/suicide"
|
170
208
|
require "sup/message"
|
171
209
|
require "sup/source"
|
172
210
|
require "sup/mbox"
|
@@ -206,6 +244,8 @@ require "sup/modes/inbox-mode"
|
|
206
244
|
require "sup/modes/buffer-list-mode"
|
207
245
|
require "sup/modes/log-mode"
|
208
246
|
require "sup/modes/poll-mode"
|
247
|
+
require "sup/modes/file-browser-mode"
|
248
|
+
require "sup/modes/completion-mode"
|
209
249
|
require "sup/logger"
|
210
250
|
require "sup/sent"
|
211
251
|
|
data/lib/sup/account.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class Account < Person
|
4
|
-
attr_accessor :sendmail, :
|
4
|
+
attr_accessor :sendmail, :signature
|
5
5
|
|
6
|
-
def initialize h
|
7
|
-
super h[:name],
|
6
|
+
def initialize email, h
|
7
|
+
super h[:name], email, 0, true
|
8
8
|
@sendmail = h[:sendmail]
|
9
|
-
@
|
9
|
+
@signature = h[:signature]
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -17,37 +17,45 @@ class AccountManager
|
|
17
17
|
|
18
18
|
def initialize accounts
|
19
19
|
@email_map = {}
|
20
|
-
@alternate_map = {}
|
21
20
|
@accounts = {}
|
22
21
|
@default_account = nil
|
23
22
|
|
24
|
-
|
23
|
+
add_account accounts[:default], true
|
24
|
+
accounts.each { |k, v| add_account v unless k == :default }
|
25
25
|
|
26
26
|
self.class.i_am_the_instance self
|
27
27
|
end
|
28
28
|
|
29
29
|
def user_accounts; @accounts.keys; end
|
30
|
-
def user_emails;
|
30
|
+
def user_emails; @email_map.keys.select { |e| String === e }; end
|
31
31
|
|
32
|
+
## must be called first with the default account. fills in missing
|
33
|
+
## values from the default account.
|
32
34
|
def add_account hash, default=false
|
33
|
-
email
|
35
|
+
raise ArgumentError, "no email specified for account" unless hash[:email]
|
36
|
+
unless default
|
37
|
+
[:name, :sendmail, :signature].each { |k| hash[k] ||= @default_account.send(k) }
|
38
|
+
end
|
39
|
+
hash[:alternates] ||= []
|
40
|
+
|
41
|
+
main_email = hash[:email]
|
42
|
+
([hash[:email]] + hash[:alternates]).each do |email|
|
43
|
+
next if @email_map.member? email
|
44
|
+
a = Account.new main_email, hash
|
45
|
+
PersonManager.register a
|
46
|
+
@accounts[a] = true
|
47
|
+
@email_map[email] = a
|
48
|
+
end
|
34
49
|
|
35
|
-
next if @email_map.member? email
|
36
|
-
a = Account.new hash
|
37
|
-
@accounts[a] = true
|
38
|
-
@email_map[email] = a
|
39
|
-
hash[:alternates].each { |aa| @alternate_map[aa] = a }
|
40
50
|
if default
|
41
51
|
raise ArgumentError, "multiple default accounts" if @default_account
|
42
|
-
@default_account =
|
52
|
+
@default_account = @email_map[main_email]
|
43
53
|
end
|
44
54
|
end
|
45
55
|
|
46
|
-
def is_account? p;
|
47
|
-
def account_for email
|
48
|
-
|
49
|
-
end
|
50
|
-
def is_account_email? email; !account_for(email).nil?; end
|
56
|
+
def is_account? p; is_account_email? p.email end
|
57
|
+
def account_for email; @email_map[email] end
|
58
|
+
def is_account_email? email; !account_for(email).nil? end
|
51
59
|
end
|
52
60
|
|
53
61
|
end
|
data/lib/sup/buffer.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'etc'
|
1
2
|
require 'thread'
|
2
3
|
|
3
4
|
module Ncurses
|
@@ -16,24 +17,10 @@ module Ncurses
|
|
16
17
|
def mutex; @mutex ||= Mutex.new; end
|
17
18
|
def sync &b; mutex.synchronize(&b); end
|
18
19
|
|
19
|
-
##
|
20
|
-
##
|
21
|
-
## Ncurses.getch (and Curses.getch), even in cbreak mode, BLOCKS
|
22
|
-
## ALL THREAD ACTIVITY. as in, no threads anywhere will run while
|
23
|
-
## it's waiting for input. ok, fine, so we wrap it in a select. Of
|
24
|
-
## course we also rely on Ncurses.getch to tell us when an xterm
|
25
|
-
## resize has occurred, which select won't catch, so we won't
|
26
|
-
## resize outselves after a sigwinch until the user hits a key.
|
27
|
-
## and installing our own sigwinch handler means that the screen
|
28
|
-
## size returned by getmaxyx() DOESN'T UPDATE! and Kernel#trap
|
29
|
-
## RETURNS NIL as the previous handler!
|
30
|
-
##
|
31
|
-
## so basically, resizing with multi-threaded ruby Ncurses
|
32
|
-
## applications will always be broken.
|
33
|
-
##
|
34
|
-
## i've coined a new word for this: lametarded.
|
20
|
+
## magically, this stuff seems to work now. i could swear it didn't
|
21
|
+
## before. hm.
|
35
22
|
def nonblocking_getch
|
36
|
-
if IO.select([$stdin], nil, nil,
|
23
|
+
if IO.select([$stdin], nil, nil, 1)
|
37
24
|
Ncurses.getch
|
38
25
|
else
|
39
26
|
nil
|
@@ -42,7 +29,9 @@ module Ncurses
|
|
42
29
|
|
43
30
|
module_function :rows, :cols, :nonblocking_getch, :mutex, :sync
|
44
31
|
|
45
|
-
|
32
|
+
KEY_ENTER = 10
|
33
|
+
KEY_CANCEL = ?\a # ctrl-g
|
34
|
+
KEY_TAB = 9
|
46
35
|
end
|
47
36
|
|
48
37
|
module Redwood
|
@@ -196,6 +185,7 @@ class BufferManager
|
|
196
185
|
def [] n; @name_map[n]; end
|
197
186
|
def []= n, b
|
198
187
|
raise ArgumentError, "duplicate buffer name" if b && @name_map.member?(n)
|
188
|
+
raise ArgumentError, "title must be a string" unless n.is_a? String
|
199
189
|
@name_map[n] = b
|
200
190
|
end
|
201
191
|
|
@@ -209,14 +199,6 @@ class BufferManager
|
|
209
199
|
end
|
210
200
|
end
|
211
201
|
|
212
|
-
def handle_resize
|
213
|
-
return if @shelled
|
214
|
-
rows, cols = Ncurses.rows, Ncurses.cols
|
215
|
-
@buffers.each { |b| b.resize rows - minibuf_lines, cols }
|
216
|
-
completely_redraw_screen
|
217
|
-
flash "Resized to #{rows}x#{cols}"
|
218
|
-
end
|
219
|
-
|
220
202
|
def draw_screen opts={}
|
221
203
|
return if @shelled
|
222
204
|
|
@@ -227,7 +209,9 @@ class BufferManager
|
|
227
209
|
## TODO: reenable this if we allow multiple buffers
|
228
210
|
false && @buffers.inject(@dirty) do |dirty, buf|
|
229
211
|
buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
|
230
|
-
|
212
|
+
#dirty ? buf.draw : buf.redraw
|
213
|
+
buf.draw
|
214
|
+
dirty
|
231
215
|
end
|
232
216
|
|
233
217
|
## quick hack
|
@@ -238,6 +222,7 @@ class BufferManager
|
|
238
222
|
end
|
239
223
|
|
240
224
|
draw_minibuf :sync => false unless opts[:skip_minibuf]
|
225
|
+
|
241
226
|
@dirty = false
|
242
227
|
Ncurses.doupdate
|
243
228
|
Ncurses.refresh if opts[:refresh]
|
@@ -258,6 +243,7 @@ class BufferManager
|
|
258
243
|
end
|
259
244
|
|
260
245
|
def spawn title, mode, opts={}
|
246
|
+
raise ArgumentError, "title must be a string" unless title.is_a? String
|
261
247
|
realtitle = title
|
262
248
|
num = 2
|
263
249
|
while @name_map.member? realtitle
|
@@ -288,11 +274,29 @@ class BufferManager
|
|
288
274
|
b
|
289
275
|
end
|
290
276
|
|
277
|
+
## requires the mode to have #done? and #value methods
|
278
|
+
def spawn_modal title, mode, opts={}
|
279
|
+
b = spawn title, mode, opts
|
280
|
+
draw_screen
|
281
|
+
|
282
|
+
until mode.done?
|
283
|
+
c = Ncurses.nonblocking_getch
|
284
|
+
next unless c # getch timeout
|
285
|
+
break if c == Ncurses::KEY_CANCEL
|
286
|
+
mode.handle_input c
|
287
|
+
draw_screen
|
288
|
+
erase_flash
|
289
|
+
end
|
290
|
+
|
291
|
+
kill_buffer b
|
292
|
+
mode.value
|
293
|
+
end
|
294
|
+
|
291
295
|
def kill_all_buffers_safely
|
292
296
|
until @buffers.empty?
|
293
297
|
## inbox mode always claims it's unkillable. we'll ignore it.
|
294
|
-
return false unless @buffers.
|
295
|
-
kill_buffer @buffers.
|
298
|
+
return false unless @buffers.last.mode.is_a?(InboxMode) || @buffers.last.mode.killable?
|
299
|
+
kill_buffer @buffers.last
|
296
300
|
end
|
297
301
|
true
|
298
302
|
end
|
@@ -322,20 +326,63 @@ class BufferManager
|
|
322
326
|
end
|
323
327
|
end
|
324
328
|
|
325
|
-
|
326
|
-
|
329
|
+
def ask_with_completions domain, question, completions, default=nil
|
330
|
+
ask domain, question, default do |s|
|
331
|
+
completions.select { |x| x =~ /^#{s}/i }.map { |x| [x.downcase, x] }
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
## returns an ARRAY of filenames!
|
336
|
+
def ask_for_filenames domain, question, default=nil
|
337
|
+
answer = ask domain, question, default do |s|
|
338
|
+
if s =~ /(~([^\s\/]*))/ # twiddle directory expansion
|
339
|
+
full = $1
|
340
|
+
name = $2.empty? ? Etc.getlogin : $2
|
341
|
+
dir = Etc.getpwnam(name).dir rescue nil
|
342
|
+
if dir
|
343
|
+
[[s.sub(full, dir), "~#{name}"]]
|
344
|
+
else
|
345
|
+
users.select { |u| u =~ /^#{name}/ }.map do |u|
|
346
|
+
[s.sub("~#{name}", "~#{u}"), "~#{u}"]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
else # regular filename completion
|
350
|
+
Dir["#{s}*"].sort.map do |fn|
|
351
|
+
suffix = File.directory?(fn) ? "/" : ""
|
352
|
+
[fn + suffix, File.basename(fn) + suffix]
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
if answer
|
358
|
+
answer =
|
359
|
+
if answer.empty?
|
360
|
+
spawn_modal "file browser", FileBrowserMode.new
|
361
|
+
elsif File.directory?(answer)
|
362
|
+
spawn_modal "file browser", FileBrowserMode.new(answer)
|
363
|
+
else
|
364
|
+
[answer]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
answer || []
|
369
|
+
end
|
370
|
+
|
371
|
+
def ask domain, question, default=nil, &block
|
327
372
|
raise "impossible!" if @asking
|
373
|
+
@asking = true
|
328
374
|
|
329
375
|
@textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0, Ncurses.cols
|
330
376
|
tf = @textfields[domain]
|
377
|
+
completion_buf = nil
|
331
378
|
|
332
|
-
## this goddamn ncurses form shit is a fucking 1970's
|
333
|
-
##
|
334
|
-
##
|
335
|
-
##
|
336
|
-
##
|
379
|
+
## this goddamn ncurses form shit is a fucking 1970's nightmare.
|
380
|
+
## jesus christ. the exact sequence of ncurses events that needs
|
381
|
+
## to happen in order to display a form and have the entire screen
|
382
|
+
## not disappear and have the cursor in the right place is TOO
|
383
|
+
## FUCKING COMPLICATED.
|
337
384
|
Ncurses.sync do
|
338
|
-
tf.activate question, default
|
385
|
+
tf.activate question, default, &block
|
339
386
|
@dirty = true
|
340
387
|
draw_screen :skip_minibuf => true, :sync => false
|
341
388
|
end
|
@@ -344,15 +391,42 @@ class BufferManager
|
|
344
391
|
tf.position_cursor
|
345
392
|
Ncurses.sync { Ncurses.refresh }
|
346
393
|
|
347
|
-
|
348
|
-
|
349
|
-
|
394
|
+
while true
|
395
|
+
c = Ncurses.nonblocking_getch
|
396
|
+
next unless c # getch timeout
|
397
|
+
break unless tf.handle_input c # process keystroke
|
398
|
+
|
399
|
+
if tf.new_completions?
|
400
|
+
kill_buffer completion_buf if completion_buf
|
401
|
+
|
402
|
+
prefix_len =
|
403
|
+
if tf.value =~ /\/$/
|
404
|
+
0
|
405
|
+
else
|
406
|
+
File.basename(tf.value).length
|
407
|
+
end
|
408
|
+
|
409
|
+
mode = CompletionMode.new tf.completions.map { |full, short| short }, :header => "Possible completions for \"#{tf.value}\": ", :prefix_len => prefix_len
|
410
|
+
completion_buf = spawn "<completions>", mode, :height => 10
|
411
|
+
|
412
|
+
draw_screen :skip_minibuf => true
|
413
|
+
tf.position_cursor
|
414
|
+
elsif tf.roll_completions?
|
415
|
+
completion_buf.mode.roll
|
416
|
+
|
417
|
+
draw_screen :skip_minibuf => true
|
418
|
+
tf.position_cursor
|
419
|
+
end
|
350
420
|
|
351
|
-
|
421
|
+
Ncurses.sync { Ncurses.refresh }
|
422
|
+
end
|
423
|
+
|
352
424
|
Ncurses.sync { tf.deactivate }
|
425
|
+
kill_buffer completion_buf if completion_buf
|
353
426
|
@dirty = true
|
354
|
-
|
355
|
-
|
427
|
+
@asking = false
|
428
|
+
draw_screen
|
429
|
+
tf.value
|
356
430
|
end
|
357
431
|
|
358
432
|
## some pretty lame code in here!
|
@@ -370,7 +444,7 @@ class BufferManager
|
|
370
444
|
done = false
|
371
445
|
@shelled = true
|
372
446
|
until done
|
373
|
-
key = Ncurses.nonblocking_getch
|
447
|
+
key = Ncurses.nonblocking_getch or next
|
374
448
|
if key == Ncurses::KEY_CANCEL
|
375
449
|
done = true
|
376
450
|
elsif (accept && accept.member?(key)) || !accept
|
@@ -487,5 +561,17 @@ class BufferManager
|
|
487
561
|
end
|
488
562
|
@shelled = false
|
489
563
|
end
|
564
|
+
|
565
|
+
private
|
566
|
+
|
567
|
+
def users
|
568
|
+
unless @users
|
569
|
+
@users = []
|
570
|
+
while(u = Etc.getpwent)
|
571
|
+
@users << u.name
|
572
|
+
end
|
573
|
+
end
|
574
|
+
@users
|
575
|
+
end
|
490
576
|
end
|
491
577
|
end
|