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.

@@ -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
@@ -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
- Q: How is Sup even possible?
7
- A: Sup is only possible through the hard work of Dave Balmain, the
8
- author of ferret.
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
- I started using Ferret when it was still slightly buggy, and it
11
- seemed like every week Dave released a bugfix or a speed
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: As the millions (ok, hundreds) of mutt users will tell you, there are
18
- many advantages to the console:
19
- - You don't need web browser.
20
- - Instantaneous interaction.
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
- Q: If you love GMail so much, why not just use it?
25
- A: I hate using a mouse, and I hate ads, and I hate non-programmability.
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
+
@@ -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
- The principle problem with traditional clients is that they place a
17
- high mental cost on the user for each incoming email, by forcing them
18
- to ask:
19
- - Should I keep this email, or delete it?
20
- - If I keep it, where should I file it?
21
-
22
- For example, I've spent the last 10 years of my life laboriously
23
- hand-filing every email message I received and feeling a mild sense of
24
- panic every time an email was both "from Mom" and "about school". The
25
- massive amounts of email that many people receive, and the cheap cost
26
- of storage, have made these questions both more costly and less useful
27
- to answer.
28
-
29
- As a long-time Mutt user, when I watched people use GMail, I saw them
30
- use email differently from how I had ever used it. I saw that making
31
- certain operations quantitatively easier (namely, search) resulted in
32
- a qualitative difference in usage. And I saw that thread-centrism had
33
- many advantages over message-centrism, especially when volume was high.
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. Ultimately, GMail wasn't right for me, which is
38
- why the idea for Sup was born.
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 the remaining need for folders.
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. A message
51
- and its content deserve the same treatment in the vast majority
52
- of cases.
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.2"
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
- module_function :register_yaml, :save_yaml_obj, :load_yaml_obj
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
@@ -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
- @freeze = false
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 @freeze
182
- Ncurses.clear
183
- @dirty = true
184
- draw_screen
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 @freeze
195
+ return if @shelled
189
196
  rows, cols = Ncurses.rows, Ncurses.cols
190
- @buffers.each { |b| b.resize rows - 1, cols }
197
+ @buffers.each { |b| b.resize rows - minibuf_lines, cols }
191
198
  completely_redraw_screen
192
- flash "resized to #{rows}x#{cols}"
199
+ flash "Resized to #{rows}x#{cols}"
193
200
  end
194
201
 
195
- def draw_screen skip_minibuf=false
196
- return if @freeze
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
- dirty ? buf.draw : buf.redraw
203
- dirty || buf.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 && (@dirty ? @buffers.last.draw : @buffers.last.redraw)
207
-
208
- draw_minibuf unless skip_minibuf
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} #{num}"
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
- tf.activate question, default
292
- @dirty = true
293
- draw_screen true
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.curs_set 1
315
- Ncurses.move Ncurses.rows - 1, question.length + 1
316
- Ncurses.refresh
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
- @freeze = true
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
- @freeze = false
331
- Ncurses.curs_set 0
332
- erase_flash
333
- draw_screen
334
- Ncurses.curs_set 0
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 draw_minibuf
352
- s = @flash || @minibuf_stack.reverse.find { |x| x } || ""
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
- Ncurses.mvaddstr Ncurses.rows - 1, 0, s + (" " * [Ncurses.cols - s.length,
356
- 0].max)
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
- unless @freeze
363
- draw_screen
364
- Ncurses.refresh
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
- unless @freeze
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 unless @minibuf_stack[i].nil?
418
+ break if @minibuf_stack[i]
384
419
  @minibuf_stack.delete_at i
385
420
  end
386
421
  end
387
- unless @freeze
388
- draw_screen
389
- Ncurses.refresh
390
- end
422
+
423
+ draw_screen :refresh => true
391
424
  end
392
425
 
393
426
  def shell_out command
394
- @freeze = true
395
- Ncurses.endwin
396
- system command
397
- Ncurses.refresh
398
- Ncurses.curs_set 0
399
- @freeze = false
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