sup 0.0.2 → 0.0.3
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/History.txt +11 -0
- data/Manifest.txt +40 -37
- data/README.txt +56 -32
- data/Rakefile +1 -1
- data/bin/sup +43 -65
- data/bin/sup-import +58 -14
- data/bin/sup-recover-sources +100 -0
- data/doc/FAQ.txt +18 -17
- data/doc/Philosophy.txt +33 -26
- data/doc/TODO +7 -0
- data/lib/sup.rb +36 -6
- data/lib/sup/buffer.rb +101 -66
- data/lib/sup/draft.rb +3 -8
- data/lib/sup/imap.rb +120 -36
- data/lib/sup/index.rb +52 -78
- data/lib/sup/label.rb +1 -4
- data/lib/sup/logger.rb +2 -1
- data/lib/sup/mbox.rb +2 -0
- data/lib/sup/mbox/loader.rb +21 -9
- data/lib/sup/mbox/ssh-file.rb +239 -0
- data/lib/sup/mbox/ssh-loader.rb +88 -0
- data/lib/sup/message.rb +70 -46
- data/lib/sup/modes/inbox-mode.rb +1 -1
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/line-cursor-mode.rb +0 -1
- data/lib/sup/modes/scroll-mode.rb +12 -2
- data/lib/sup/modes/search-results-mode.rb +3 -4
- data/lib/sup/modes/text-mode.rb +1 -1
- data/lib/sup/modes/thread-index-mode.rb +10 -8
- data/lib/sup/modes/thread-view-mode.rb +13 -12
- data/lib/sup/person.rb +43 -40
- data/lib/sup/poll.rb +11 -9
- data/lib/sup/source.rb +44 -18
- data/lib/sup/util.rb +31 -3
- metadata +53 -40
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
$opts = {
|
6
|
+
:unusual => false,
|
7
|
+
:archive => false,
|
8
|
+
:scan_num => 10,
|
9
|
+
}
|
10
|
+
|
11
|
+
|
12
|
+
OPTIONPARSERSUCKS = "\n" + " " * 38
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = <<EOS
|
15
|
+
Usage: sup-recover-sources [options] <source>+
|
16
|
+
|
17
|
+
Rebuilds a lost sources.yaml file by reading messages from a list of
|
18
|
+
sources and determining, for each source, the most prevalent
|
19
|
+
'source_id' field of messages from that source in the index.
|
20
|
+
|
21
|
+
The only non-deterministic component to this is that if the same
|
22
|
+
message appears in multiple sources, those sources may be
|
23
|
+
mis-diagnosed by this program.
|
24
|
+
|
25
|
+
If the first N messages (--scan-num below) all have the same source_id
|
26
|
+
in the index, the source will be added to sources.yaml. Otherwise, the
|
27
|
+
distribution will be printed, and you will have to add it by hand.
|
28
|
+
|
29
|
+
The offset pointer into the sources will be set to the end of the source,
|
30
|
+
so you will have to run sup-import --rebuild for each new source after
|
31
|
+
doing this.
|
32
|
+
|
33
|
+
Options include:
|
34
|
+
EOS
|
35
|
+
|
36
|
+
opts.on("--unusual", "Mark sources as 'unusual'. Only usual#{OPTIONPARSERSUCKS}sources will be polled by hand. Default:#{OPTIONPARSERSUCKS}#{$opts[:unusual]}.") { $opts[:unusual] = true }
|
37
|
+
|
38
|
+
opts.on("--archive", "Mark sources as 'archive'. New messages#{OPTIONPARSERSUCKS}from these sources will not appear in#{OPTIONPARSERSUCKS}the inbox. Default: #{$opts[:archive]}.") { $opts[:archive] = true }
|
39
|
+
|
40
|
+
opts.on("--scan-num N", Integer, "Number of messages to scan per source.#{OPTIONPARSERSUCKS}Default: #{$opts[:scan_num]}.") do |n|
|
41
|
+
$opts[:scan_num] = n
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
end.parse(ARGV)
|
49
|
+
|
50
|
+
require "sup"
|
51
|
+
puts "loading index..."
|
52
|
+
index = Redwood::Index.new
|
53
|
+
index.load
|
54
|
+
puts "loaded index of #{index.size} messages"
|
55
|
+
|
56
|
+
ARGV.each do |fn|
|
57
|
+
next if index.source_for fn
|
58
|
+
|
59
|
+
## TODO: merge this code with the same snippet in import
|
60
|
+
source =
|
61
|
+
case fn
|
62
|
+
when %r!^imaps?://!
|
63
|
+
print "Username for #{fn}: "
|
64
|
+
username = $stdin.gets.chomp
|
65
|
+
print "Password for #{fn} (warning: cleartext): "
|
66
|
+
password = $stdin.gets.chomp
|
67
|
+
Redwood::IMAP.new(fn, username, password, nil, !$opts[:unusual], $opts[:archive])
|
68
|
+
else
|
69
|
+
Redwood::MBox::Loader.new(fn, nil, !$opts[:unusual], $opts[:archive])
|
70
|
+
end
|
71
|
+
|
72
|
+
source_ids = {}
|
73
|
+
count = 0
|
74
|
+
source.each do |offset, labels|
|
75
|
+
begin
|
76
|
+
m = Redwood::Message.new :source => source, :source_info => offset
|
77
|
+
docid, entry = index.load_entry_for_id m.id
|
78
|
+
next unless entry
|
79
|
+
#puts "# #{source} #{offset} #{entry[:source_id]}"
|
80
|
+
|
81
|
+
source_ids[entry[:source_id]] = (source_ids[entry[:source_id]] || 0) + 1
|
82
|
+
count += 1
|
83
|
+
break if count == $opts[:scan_num]
|
84
|
+
rescue Redwood::MessageFormatError => e
|
85
|
+
puts "# #{e.message}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if source_ids.size == 1
|
90
|
+
id = source_ids.keys.first.to_i
|
91
|
+
puts "assigned #{source} to #{source_ids.keys.first}"
|
92
|
+
source.id = id
|
93
|
+
source.seek_to! source.total
|
94
|
+
index.add_source source
|
95
|
+
else
|
96
|
+
puts ">> unable to determine #{source}: #{source_ids.inspect}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
index.save
|
data/doc/FAQ.txt
CHANGED
@@ -1,28 +1,23 @@
|
|
1
1
|
Sup FAQ
|
2
2
|
-------
|
3
3
|
Q: What does Sup stand for?
|
4
|
-
A: "What's up?".
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
A: It stands for "what's up?", which is more or less the question I'm
|
6
|
+
asking when I read my mail.
|
7
|
+
|
8
|
+
Q: If you love GMail so much, why not just use it?
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
improvement that directly affected sup. Ferret has become a
|
13
|
-
first-class piece of software, and it's due to the tremendous
|
14
|
-
amount of time and effort he's put in to it.
|
10
|
+
A: I hate using a mouse, and I hate ads, and I hate non-programmability
|
11
|
+
and non-extensibility.
|
15
12
|
|
16
13
|
Q: Why the console?
|
17
|
-
A:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
- A few keystrokes can accomplish the work of a hundred mouse
|
22
|
-
clicks.
|
14
|
+
A: There are many advantages to the console. As any Unix user knows, a
|
15
|
+
few keystrokes can accomplish the work of a hundred mouse clicks.
|
16
|
+
Also, you don't need web browser, and you get instantaneous response
|
17
|
+
and a simple interface.
|
23
18
|
|
24
|
-
|
25
|
-
|
19
|
+
That said, a good Ajax programmer could probably replicate GMail
|
20
|
+
pretty easily using Sup as the backend.
|
26
21
|
|
27
22
|
Q: How does Sup deal with spam?
|
28
23
|
A: You can manually mark messages as spam, which prevents them from
|
@@ -38,3 +33,9 @@ A: That was Sup's original name. (Think pine, elm. Although I am a
|
|
38
33
|
Maybe one day I'll do a huge search-and-replace on the code, but it
|
39
34
|
doesn't seem that important at this point.
|
40
35
|
|
36
|
+
Q: How is Sup possible?
|
37
|
+
A: Sup is only possible through the hard work of Dave Balmain, the
|
38
|
+
author of ferret, which is the search engine behind Sup. Ferret is
|
39
|
+
really a first-class piece of software, and it's due to the
|
40
|
+
tremendous amount of time and effort he's put in to it.
|
41
|
+
|
data/doc/Philosophy.txt
CHANGED
@@ -11,31 +11,36 @@ it. Keeping up with the all the new traffic is painful, even with
|
|
11
11
|
Mutt's excellent threading features, simply because there's so much of
|
12
12
|
it---a single thread can span several pages, and God help you if you
|
13
13
|
lag behind. And Mutt is probably the best email client out there in
|
14
|
-
terms of threading and mailing list support.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
14
|
+
terms of threading and mailing list support. God help me if I try and
|
15
|
+
throw Thunderbird at that.
|
16
|
+
|
17
|
+
The principle problem with traditional clients is that they deal with
|
18
|
+
individual pieces of email, and place a high mental cost on the user
|
19
|
+
for each incoming email, by forcing them to ask: Should I keep this
|
20
|
+
email, or delete it? If I keep it, where should I file it?
|
21
|
+
|
22
|
+
I've spent the last 10 years of my life laboriously hand-filing every
|
23
|
+
email message I received and feeling a mild sense of panic every time
|
24
|
+
an email was both "from Mom" and "about school". The massive amounts
|
25
|
+
of email that many people receive, and the cheap cost of storage, have
|
26
|
+
made these questions both more costly and less useful to answer.
|
27
|
+
|
28
|
+
As a long-time Mutt user, when I first watched people use GMail, I saw
|
29
|
+
them use email differently from how I had ever used it. I saw that
|
30
|
+
making certain operations quantitatively easier (namely, search)
|
31
|
+
resulted in a qualitative difference in usage: you don't have to worry
|
32
|
+
about filing correctly, because you can always find things later by
|
33
|
+
search. And I saw that thread-centrism had many advantages over
|
34
|
+
message-centrism when message volume was high.
|
34
35
|
|
35
36
|
So, in many ways, I believe GMail has taken the right approach to
|
36
37
|
handle both of the factors above, and much of the inspiration for Sup
|
37
|
-
was based on GMail.
|
38
|
-
|
38
|
+
was based on GMail. I think it's to the GMail designers' credit that
|
39
|
+
they started with a somewhat ad-hoc idea (hey, we're really good at
|
40
|
+
search engines, so can we build an email client on top of one?) and
|
41
|
+
managed to build something that was actually better than everything
|
42
|
+
else out there. But ultimately, GMail wasn't right for me (see FAQ),
|
43
|
+
which is why the idea for Sup was born.
|
39
44
|
|
40
45
|
Sup is based on the following principles, which I more or less stole
|
41
46
|
directly from GMail:
|
@@ -44,15 +49,17 @@ directly from GMail:
|
|
44
49
|
entire email archive eliminates most of the need for folders,
|
45
50
|
and eliminates the necessity of having to ever delete email.
|
46
51
|
|
47
|
-
- Labels eliminate
|
52
|
+
- Labels eliminate what little need for folders that search doesn't
|
53
|
+
eliminate.
|
48
54
|
|
49
55
|
- A thread-centric approach to the UI is much more in line with how
|
50
|
-
people operate than dealing with individual messages is.
|
51
|
-
|
52
|
-
|
56
|
+
people operate than dealing with individual messages is. In the vast
|
57
|
+
majority of cases, a message and its context should be subject to
|
58
|
+
the same treatment.
|
53
59
|
|
54
60
|
Sup is also based on many ideas from mutt and Emacs and vi, having to
|
55
61
|
do with the fantastic productivity of a console- and keyboard-based
|
56
62
|
application, the usefulness of multiple buffers, the necessity of
|
57
63
|
handling multiple email accounts, etc.
|
58
64
|
|
65
|
+
Give it a go and let me know what you think.
|
data/doc/TODO
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
support for message-content modules such as ruby-talk:XXXXX detection
|
2
|
+
use Net::SMTP
|
1
3
|
search for other messages from author in thread-view-mode
|
2
4
|
forward attachments
|
3
5
|
tab completion on labels, contacts
|
@@ -12,7 +14,12 @@ annotations on messages
|
|
12
14
|
gmail
|
13
15
|
pop
|
14
16
|
move sup-import argument handling to getopt or something
|
17
|
+
mark individual messages as spam in thread-view-mode
|
15
18
|
|
19
|
+
x move sup-import username/password prompts to highline
|
20
|
+
x support different remote servers per user account
|
21
|
+
x 'R' to quick-resume most recent draft
|
22
|
+
x mbox+ssh
|
16
23
|
x handle broken sources better
|
17
24
|
x imap
|
18
25
|
x word wrap
|
data/lib/sup.rb
CHANGED
@@ -4,23 +4,22 @@ require 'zlib'
|
|
4
4
|
require 'thread'
|
5
5
|
require 'fileutils'
|
6
6
|
|
7
|
-
Thread.abort_on_exception = true # make debugging possible
|
8
|
-
|
9
7
|
class Object
|
10
8
|
## this is for debugging purposes because i keep calling #id on the
|
11
9
|
## wrong object and i want it to throw an exception
|
12
10
|
def id
|
13
|
-
raise "wrong id called"
|
11
|
+
raise "wrong id called on #{self.inspect}"
|
14
12
|
end
|
15
13
|
end
|
16
14
|
|
17
15
|
module Redwood
|
18
|
-
VERSION = "0.0.
|
16
|
+
VERSION = "0.0.3"
|
19
17
|
|
20
|
-
BASE_DIR = File.join(ENV["HOME"], ".sup")
|
18
|
+
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
21
19
|
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
|
22
20
|
SOURCE_FN = File.join(BASE_DIR, "sources.yaml")
|
23
21
|
LABEL_FN = File.join(BASE_DIR, "labels.txt")
|
22
|
+
PERSON_FN = File.join(BASE_DIR, "people.txt")
|
24
23
|
CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
|
25
24
|
DRAFT_DIR = File.join(BASE_DIR, "drafts")
|
26
25
|
SENT_FN = File.join(BASE_DIR, "sent.mbox")
|
@@ -28,6 +27,20 @@ module Redwood
|
|
28
27
|
YAML_DOMAIN = "masanjin.net"
|
29
28
|
YAML_DATE = "2006-10-01"
|
30
29
|
|
30
|
+
## record exceptions thrown in threads nicely
|
31
|
+
$exception = nil
|
32
|
+
def reporting_thread
|
33
|
+
::Thread.new do
|
34
|
+
begin
|
35
|
+
yield
|
36
|
+
rescue Exception => e
|
37
|
+
$exception ||= e
|
38
|
+
raise
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
module_function :reporting_thread
|
43
|
+
|
31
44
|
## one-stop shop for yamliciousness
|
32
45
|
def register_yaml klass, props
|
33
46
|
vars = props.map { |p| "@#{p}" }
|
@@ -61,7 +74,24 @@ module Redwood
|
|
61
74
|
end
|
62
75
|
end
|
63
76
|
|
64
|
-
|
77
|
+
def start
|
78
|
+
Redwood::PersonManager.new Redwood::PERSON_FN
|
79
|
+
Redwood::SentManager.new Redwood::SENT_FN
|
80
|
+
Redwood::ContactManager.new Redwood::CONTACT_FN
|
81
|
+
Redwood::LabelManager.new Redwood::LABEL_FN
|
82
|
+
Redwood::AccountManager.new $config[:accounts]
|
83
|
+
Redwood::DraftManager.new Redwood::DRAFT_DIR
|
84
|
+
Redwood::UpdateManager.new
|
85
|
+
Redwood::PollManager.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def finish
|
89
|
+
Redwood::LabelManager.save
|
90
|
+
Redwood::ContactManager.save
|
91
|
+
Redwood::PersonManager.save
|
92
|
+
end
|
93
|
+
|
94
|
+
module_function :register_yaml, :save_yaml_obj, :load_yaml_obj, :start, :finish
|
65
95
|
end
|
66
96
|
|
67
97
|
## set up default configuration file
|
data/lib/sup/buffer.rb
CHANGED
@@ -13,6 +13,9 @@ module Ncurses
|
|
13
13
|
lamer.first
|
14
14
|
end
|
15
15
|
|
16
|
+
def mutex; @mutex ||= Mutex.new; end
|
17
|
+
def sync &b; mutex.synchronize &b; end
|
18
|
+
|
16
19
|
## aaahhh, user input. who would have though that such a simple
|
17
20
|
## idea would be SO FUCKING COMPLICATED?! because apparently
|
18
21
|
## Ncurses.getch (and Curses.getch), even in cbreak mode, BLOCKS
|
@@ -37,7 +40,7 @@ module Ncurses
|
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
40
|
-
module_function :rows, :cols, :nonblocking_getch
|
43
|
+
module_function :rows, :cols, :nonblocking_getch, :mutex, :sync
|
41
44
|
|
42
45
|
KEY_CANCEL = "\a"[0] # ctrl-g
|
43
46
|
end
|
@@ -60,9 +63,11 @@ class Buffer
|
|
60
63
|
def content_height; @height - 1; end
|
61
64
|
def content_width; @width; end
|
62
65
|
|
63
|
-
def resize rows, cols
|
66
|
+
def resize rows, cols
|
67
|
+
return if cols == @width && rows == @height
|
64
68
|
@width = cols
|
65
69
|
@height = rows
|
70
|
+
@dirty = true
|
66
71
|
mode.resize rows, cols
|
67
72
|
end
|
68
73
|
|
@@ -71,6 +76,7 @@ class Buffer
|
|
71
76
|
draw_status
|
72
77
|
commit
|
73
78
|
end
|
79
|
+
|
74
80
|
def mark_dirty; @dirty = true; end
|
75
81
|
|
76
82
|
def commit
|
@@ -132,7 +138,7 @@ class BufferManager
|
|
132
138
|
@minibuf_stack = []
|
133
139
|
@textfields = {}
|
134
140
|
@flash = nil
|
135
|
-
@
|
141
|
+
@shelled = false
|
136
142
|
|
137
143
|
self.class.i_am_the_instance self
|
138
144
|
end
|
@@ -140,8 +146,7 @@ class BufferManager
|
|
140
146
|
def buffers; @name_map.to_a; end
|
141
147
|
|
142
148
|
def focus_on buf
|
143
|
-
raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless
|
144
|
-
@buffers.member? buf
|
149
|
+
raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
|
145
150
|
return if buf == @focus_buf
|
146
151
|
@focus_buf.blur if @focus_buf
|
147
152
|
@focus_buf = buf
|
@@ -149,8 +154,7 @@ class BufferManager
|
|
149
154
|
end
|
150
155
|
|
151
156
|
def raise_to_front buf
|
152
|
-
raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless
|
153
|
-
@buffers.member? buf
|
157
|
+
raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
|
154
158
|
@buffers.delete buf
|
155
159
|
@buffers.push buf
|
156
160
|
focus_on buf
|
@@ -178,36 +182,49 @@ class BufferManager
|
|
178
182
|
end
|
179
183
|
|
180
184
|
def completely_redraw_screen
|
181
|
-
return if @
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
+
return if @shelled
|
186
|
+
|
187
|
+
Ncurses.sync do
|
188
|
+
@dirty = true
|
189
|
+
Ncurses.clear
|
190
|
+
draw_screen :sync => false
|
191
|
+
end
|
185
192
|
end
|
186
193
|
|
187
194
|
def handle_resize
|
188
|
-
return if @
|
195
|
+
return if @shelled
|
189
196
|
rows, cols = Ncurses.rows, Ncurses.cols
|
190
|
-
@buffers.each { |b| b.resize rows -
|
197
|
+
@buffers.each { |b| b.resize rows - minibuf_lines, cols }
|
191
198
|
completely_redraw_screen
|
192
|
-
flash "
|
199
|
+
flash "Resized to #{rows}x#{cols}"
|
193
200
|
end
|
194
201
|
|
195
|
-
def draw_screen
|
196
|
-
return if @
|
202
|
+
def draw_screen opts={}
|
203
|
+
return if @shelled
|
204
|
+
|
205
|
+
Ncurses.mutex.lock unless opts[:sync] == false
|
197
206
|
|
198
207
|
## disabling this for the time being, to help with debugging
|
199
208
|
## (currently we only have one buffer visible at a time).
|
200
209
|
## TODO: reenable this if we allow multiple buffers
|
201
210
|
false && @buffers.inject(@dirty) do |dirty, buf|
|
202
|
-
|
203
|
-
dirty
|
211
|
+
buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
|
212
|
+
@dirty ? buf.draw : buf.redraw
|
204
213
|
end
|
214
|
+
|
205
215
|
## quick hack
|
206
|
-
true
|
207
|
-
|
208
|
-
|
216
|
+
if true
|
217
|
+
buf = @buffers.last
|
218
|
+
buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
|
219
|
+
File.open("asdf.txt", "a") { |f| f.puts "dirty #@dirty, (re)drawing #{buf.mode.name}" }
|
220
|
+
@dirty ? buf.draw : buf.redraw
|
221
|
+
end
|
222
|
+
|
223
|
+
draw_minibuf :sync => false unless opts[:skip_minibuf]
|
209
224
|
@dirty = false
|
210
225
|
Ncurses.doupdate
|
226
|
+
Ncurses.refresh if opts[:refresh]
|
227
|
+
Ncurses.mutex.unlock unless opts[:sync] == false
|
211
228
|
end
|
212
229
|
|
213
230
|
## gets the mode from the block, which is only called if the buffer
|
@@ -228,11 +245,10 @@ class BufferManager
|
|
228
245
|
realtitle = title
|
229
246
|
num = 2
|
230
247
|
while @name_map.member? realtitle
|
231
|
-
realtitle = "#{title}
|
248
|
+
realtitle = "#{title} <#{num}>"
|
232
249
|
num += 1
|
233
250
|
end
|
234
251
|
|
235
|
-
Redwood::log "spawning buffer \"#{realtitle}\""
|
236
252
|
width = opts[:width] || Ncurses.cols
|
237
253
|
height = opts[:height] || Ncurses.rows - 1
|
238
254
|
|
@@ -243,8 +259,6 @@ class BufferManager
|
|
243
259
|
## w = Ncurses::WINDOW.new(height, width, (opts[:top] || 0),
|
244
260
|
## (opts[:left] || 0))
|
245
261
|
w = Ncurses.stdscr
|
246
|
-
raise "nil window" unless w
|
247
|
-
|
248
262
|
b = Buffer.new w, mode, width, height, :title => realtitle
|
249
263
|
mode.buffer = b
|
250
264
|
@name_map[realtitle] = b
|
@@ -264,7 +278,6 @@ class BufferManager
|
|
264
278
|
|
265
279
|
def kill_buffer buf
|
266
280
|
raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
|
267
|
-
Redwood::log "killing buffer \"#{buf.title}\""
|
268
281
|
|
269
282
|
buf.mode.cleanup
|
270
283
|
@buffers.delete buf
|
@@ -279,8 +292,7 @@ class BufferManager
|
|
279
292
|
end
|
280
293
|
|
281
294
|
def ask domain, question, default=nil
|
282
|
-
@textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0,
|
283
|
-
Ncurses.cols
|
295
|
+
@textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0, Ncurses.cols
|
284
296
|
tf = @textfields[domain]
|
285
297
|
|
286
298
|
## this goddamn ncurses form shit is a fucking 1970's
|
@@ -288,19 +300,19 @@ class BufferManager
|
|
288
300
|
## that needs to happen in order to display a form and have the
|
289
301
|
## entire screen not disappear and have the cursor in the right
|
290
302
|
## place is TOO FUCKING COMPLICATED.
|
291
|
-
|
292
|
-
|
293
|
-
|
303
|
+
Ncurses.sync do
|
304
|
+
tf.activate question, default
|
305
|
+
@dirty = true
|
306
|
+
draw_screen :skip_minibuf => true, :sync => false
|
307
|
+
end
|
294
308
|
|
295
309
|
ret = nil
|
296
|
-
@freeze = true
|
297
310
|
tf.position_cursor
|
298
|
-
Ncurses.refresh
|
311
|
+
Ncurses.sync { Ncurses.refresh }
|
299
312
|
while tf.handle_input(Ncurses.nonblocking_getch); end
|
300
|
-
@freeze = false
|
301
313
|
|
302
314
|
ret = tf.value
|
303
|
-
tf.deactivate
|
315
|
+
Ncurses.sync { tf.deactivate }
|
304
316
|
@dirty = true
|
305
317
|
|
306
318
|
ret
|
@@ -311,13 +323,15 @@ class BufferManager
|
|
311
323
|
accept = accept.split(//).map { |x| x[0] } if accept
|
312
324
|
|
313
325
|
flash question
|
314
|
-
Ncurses.
|
315
|
-
|
316
|
-
|
326
|
+
Ncurses.sync do
|
327
|
+
Ncurses.curs_set 1
|
328
|
+
Ncurses.move Ncurses.rows - 1, question.length + 1
|
329
|
+
Ncurses.refresh
|
330
|
+
end
|
317
331
|
|
318
332
|
ret = nil
|
319
333
|
done = false
|
320
|
-
@
|
334
|
+
@shelled = true
|
321
335
|
until done
|
322
336
|
key = Ncurses.nonblocking_getch
|
323
337
|
if key == Ncurses::KEY_CANCEL
|
@@ -327,11 +341,15 @@ class BufferManager
|
|
327
341
|
done = true
|
328
342
|
end
|
329
343
|
end
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
344
|
+
|
345
|
+
@shelled = false
|
346
|
+
|
347
|
+
Ncurses.sync do
|
348
|
+
Ncurses.curs_set 0
|
349
|
+
erase_flash
|
350
|
+
draw_screen :sync => false
|
351
|
+
Ncurses.curs_set 0
|
352
|
+
end
|
335
353
|
|
336
354
|
ret
|
337
355
|
end
|
@@ -348,20 +366,38 @@ class BufferManager
|
|
348
366
|
end
|
349
367
|
end
|
350
368
|
|
351
|
-
def
|
352
|
-
|
369
|
+
def minibuf_lines; [(@flash ? 1 : 0) + @minibuf_stack.compact.size, 1].max; end
|
370
|
+
|
371
|
+
def draw_minibuf opts={}
|
372
|
+
m = @minibuf_stack.compact
|
373
|
+
m << @flash if @flash
|
374
|
+
m << "" if m.empty?
|
353
375
|
|
376
|
+
Ncurses.mutex.lock unless opts[:sync] == false
|
354
377
|
Ncurses.attrset Colormap.color_for(:none)
|
355
|
-
|
356
|
-
|
378
|
+
m.each_with_index do |s, i|
|
379
|
+
Ncurses.mvaddstr Ncurses.rows - i - 1, 0, s + (" " * [Ncurses.cols - s.length, 0].max)
|
380
|
+
end
|
381
|
+
Ncurses.refresh if opts[:refresh]
|
382
|
+
Ncurses.mutex.unlock unless opts[:sync] == false
|
357
383
|
end
|
358
384
|
|
359
385
|
def say s, id=nil
|
386
|
+
new_id = id.nil?
|
360
387
|
id ||= @minibuf_stack.length
|
361
388
|
@minibuf_stack[id] = s
|
362
|
-
|
363
|
-
draw_screen
|
364
|
-
|
389
|
+
if new_id
|
390
|
+
draw_screen :refresh => true
|
391
|
+
else
|
392
|
+
draw_minibuf :refresh => true
|
393
|
+
end
|
394
|
+
|
395
|
+
if block_given?
|
396
|
+
begin
|
397
|
+
yield
|
398
|
+
ensure
|
399
|
+
clear id
|
400
|
+
end
|
365
401
|
end
|
366
402
|
id
|
367
403
|
end
|
@@ -370,33 +406,32 @@ class BufferManager
|
|
370
406
|
|
371
407
|
def flash s
|
372
408
|
@flash = s
|
373
|
-
|
374
|
-
draw_screen
|
375
|
-
Ncurses.refresh
|
376
|
-
end
|
409
|
+
draw_screen :refresh => true
|
377
410
|
end
|
378
411
|
|
412
|
+
## a little tricky because we can't just delete_at id because ids
|
413
|
+
## are relative (they're positions into the array).
|
379
414
|
def clear id
|
380
415
|
@minibuf_stack[id] = nil
|
381
416
|
if id == @minibuf_stack.length - 1
|
382
417
|
id.downto(0) do |i|
|
383
|
-
break
|
418
|
+
break if @minibuf_stack[i]
|
384
419
|
@minibuf_stack.delete_at i
|
385
420
|
end
|
386
421
|
end
|
387
|
-
|
388
|
-
|
389
|
-
Ncurses.refresh
|
390
|
-
end
|
422
|
+
|
423
|
+
draw_screen :refresh => true
|
391
424
|
end
|
392
425
|
|
393
426
|
def shell_out command
|
394
|
-
@
|
395
|
-
Ncurses.
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
427
|
+
@shelled = true
|
428
|
+
Ncurses.sync do
|
429
|
+
Ncurses.endwin
|
430
|
+
system command
|
431
|
+
Ncurses.refresh
|
432
|
+
Ncurses.curs_set 0
|
433
|
+
end
|
434
|
+
@shelled = false
|
400
435
|
end
|
401
436
|
end
|
402
437
|
end
|