sup 0.15.1 → 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
data/CONTRIBUTORS CHANGED
@@ -6,6 +6,7 @@ Hamish Downer <dmishd at the gmail dot coms>
6
6
  Damien Leone <damien.leone at the fensalir dot frs>
7
7
  Sascha Silbe <sascha-pgp at the silbe dot orgs>
8
8
  Eric Weikl <eric.weikl at the gmx dot nets>
9
+ Paweł Wilk <siefca at the gnu dot orgs>
9
10
  Ismo Puustinen <ismo at the iki dot fis>
10
11
  Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
11
12
  Michael Stapelberg <michael at the stapelberg dot des>
@@ -29,43 +30,44 @@ Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
29
30
  Israel Herraiz <israel.herraiz at the gmail dot coms>
30
31
  Bo Borgerson <gigabo at the gmail dot coms>
31
32
  Michael Hamann <michael at the content-space dot des>
32
- William Erik Baxter <web at the superscript dot coms>
33
33
  Jonathan Lassoff <jof at the thejof dot coms>
34
+ William Erik Baxter <web at the superscript dot coms>
34
35
  Grant Hollingworth <grant at the antiflux dot orgs>
35
36
  Adeodato Simó <dato at the net.com.org dot ess>
36
- Ico Doornekamp <ico at the pruts dot nls>
37
37
  Markus Klinik <markus.klinik at the gmx dot des>
38
+ Ico Doornekamp <ico at the pruts dot nls>
38
39
  Daniel Schoepe <daniel.schoepe at the googlemail dot coms>
39
40
  James Taylor <james at the jamestaylor dot orgs>
40
41
  Jason Petsod <jason at the petsod dot orgs>
41
- Steve Goldman <sgoldman at the tower-research dot coms>
42
42
  Robin Burchell <viroteck at the viroteck dot nets>
43
+ Steve Goldman <sgoldman at the tower-research dot coms>
43
44
  Peter Harkins <ph at the malaprop dot orgs>
44
45
  Decklin Foster <decklin at the red-bean dot coms>
45
46
  Cameron Matheson <cam+sup at the cammunism dot orgs>
46
- Carl Worth <cworth at the cworth dot orgs>
47
47
  Alex Vandiver <alex at the chmrr dot nets>
48
+ Carl Worth <cworth at the cworth dot orgs>
48
49
  Andrew Pimlott <andrew at the pimlott dot nets>
49
50
  Jeff Balogh <its.jeff.balogh at the gmail dot coms>
50
51
  Matías Aguirre <matiasaguirre at the gmail dot coms>
51
52
  Kornilios Kourtis <kkourt at the cslab.ece.ntua dot grs>
52
- Giorgio Lando <patroclo7 at the gmail dot coms>
53
53
  Kevin Riggle <kevinr at the free-dissociation dot coms>
54
+ Giorgio Lando <patroclo7 at the gmail dot coms>
54
55
  Benoît PIERRE <benoit.pierre at the gmail dot coms>
55
- Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
56
56
  Steven Lawrance <stl at the koffein dot nets>
57
+ Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
57
58
  Jonah <Jonah at the GoodCoffee dot cas>
58
59
  ian <itaylor at the uark dot edus>
59
- Adam Lloyd <adam at the alloy-d dot nets>
60
60
  Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
61
61
  0xACE <0xACE at the users.noreply.github dot coms>
62
- Per Andersson <avtobiff at the gmail dot coms>
63
- MichaelRevell <mikearevell at the gmail dot coms>
62
+ Adam Lloyd <adam at the alloy-d dot nets>
64
63
  Todd Eisenberger <teisenbe at the andrew.cmu dot edus>
64
+ MichaelRevell <mikearevell at the gmail dot coms>
65
+ Per Andersson <avtobiff at the gmail dot coms>
65
66
  Steven Walter <swalter at the monarch.(none)>
66
- Matthias Vallentin <vallentin at the icir dot orgs>
67
- akojo <atte.kojo at the gmail dot coms>
68
67
  Jon M. Dugan <jdugan at the es dot nets>
68
+ Matthias Vallentin <vallentin at the icir dot orgs>
69
69
  Horacio Sanson <horacio at the skillupjapan.co dot jps>
70
70
  Stefan Lundström <lundst at the snabb.(none)>
71
+ akojo <atte.kojo at the gmail dot coms>
72
+ Johannes Larsen <johs.a.larsen at the gmail dot coms>
71
73
  Kirill Smelkov <kirr at the landau.phys.spbu dot rus>
data/History.txt CHANGED
@@ -1,3 +1,10 @@
1
+ == 0.15.2 / 2013-12-20
2
+
3
+ * Use the form_driver_w routine for inputing multibyte chars when
4
+ available.
5
+ * Add hidden_alternates configuration option: hidden aliases for the
6
+ account.
7
+
1
8
  == 0.15.1 / 2013-12-04
2
9
 
3
10
  * Thread children are sorted last-activity latest (bottom).
data/ReleaseNotes CHANGED
@@ -1,3 +1,7 @@
1
+ Release 0.15.2:
2
+
3
+ Use form_driver_w when available. New hidden_alternates option.
4
+
1
5
  Release 0.15.1:
2
6
 
3
7
  Sort threads last-activity-first and bug fix.
data/bin/sup CHANGED
@@ -4,9 +4,10 @@
4
4
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
5
5
 
6
6
  require 'rubygems'
7
-
8
7
  require 'ncursesw'
9
8
 
9
+ require 'sup/util/ncurses'
10
+
10
11
  no_gpgme = false
11
12
  begin
12
13
  require 'gpgme'
@@ -136,6 +137,7 @@ def start_cursing
136
137
  Ncurses.use_default_colors
137
138
  Ncurses.curs_set 0
138
139
  Ncurses.start_color
140
+ Ncurses.prepare_form_driver
139
141
  $cursing = true
140
142
  end
141
143
 
@@ -236,14 +238,14 @@ begin
236
238
 
237
239
  until Redwood::exceptions.nonempty? || $die
238
240
  c = begin
239
- Ncurses.nonblocking_getch
241
+ Ncurses::CharCode.get false
240
242
  rescue Interrupt
241
243
  raise if BufferManager.ask_yes_or_no "Die ungracefully now?"
242
244
  BufferManager.draw_screen
243
- nil
245
+ Ncurses::CharCode.empty
244
246
  end
245
247
 
246
- if c.nil?
248
+ if c.empty?
247
249
  if BufferManager.sigwinch_happened?
248
250
  debug "redrawing screen on sigwinch"
249
251
  BufferManager.completely_redraw_screen
@@ -253,7 +255,7 @@ begin
253
255
 
254
256
  IdleManager.ping
255
257
 
256
- if c == 410
258
+ if c.is_keycode? 410
257
259
  ## this is ncurses's way of telling us it's detected a refresh.
258
260
  ## since we have our own sigwinch handler, we don't do anything.
259
261
  next
data/lib/sup/account.rb CHANGED
@@ -31,6 +31,8 @@ class AccountManager
31
31
 
32
32
  def initialize accounts
33
33
  @email_map = {}
34
+ @hidden_email_map = {}
35
+ @email_map_dirty = false
34
36
  @accounts = {}
35
37
  @regexen = {}
36
38
  @default_account = nil
@@ -40,7 +42,7 @@ class AccountManager
40
42
  end
41
43
 
42
44
  def user_accounts; @accounts.keys; end
43
- def user_emails; @email_map.keys.select { |e| String === e }; end
45
+ def user_emails(type = :all); email_map(type).keys.select { |e| String === e }; end
44
46
 
45
47
  ## must be called first with the default account. fills in missing
46
48
  ## values from the default account.
@@ -50,7 +52,9 @@ class AccountManager
50
52
  [:name, :sendmail, :signature, :gpgkey].each { |k| hash[k] ||= @default_account.send(k) }
51
53
  end
52
54
  hash[:alternates] ||= []
55
+ hash[:hidden_alternates] ||= []
53
56
  fail "alternative emails are not an array: #{hash[:alternates]}" unless hash[:alternates].kind_of? Array
57
+ fail "hidden alternative emails are not an array: #{hash[:hidden_alternates]}" unless hash[:hidden_alternates].kind_of? Array
54
58
 
55
59
  [:name, :signature].each { |x| hash[x] ? hash[x].fix_encoding! : nil }
56
60
 
@@ -63,8 +67,11 @@ class AccountManager
63
67
  end
64
68
 
65
69
  ([hash[:email]] + hash[:alternates]).each do |email|
66
- next if @email_map.member? email
67
- @email_map[email] = a
70
+ add_email_to_map(:shown, email, a)
71
+ end
72
+
73
+ hash[:hidden_alternates].each do |email|
74
+ add_email_to_map(:hidden, email, a)
68
75
  end
69
76
 
70
77
  hash[:regexen].each do |re|
@@ -72,19 +79,44 @@ class AccountManager
72
79
  end if hash[:regexen]
73
80
  end
74
81
 
75
- def is_account? p; is_account_email? p.email end
82
+ def is_account? p; is_account_email? p.email end
76
83
  def is_account_email? email; !account_for(email).nil? end
84
+
77
85
  def account_for email
78
- if(a = @email_map[email])
79
- a
80
- else
81
- @regexen.argfind { |re, a| re =~ email && a }
82
- end
86
+ a = email_map[email]
87
+ a.nil? ? @regexen.argfind { |re, a| re =~ email && a } : a
83
88
  end
89
+
84
90
  def full_address_for email
85
91
  a = account_for email
86
92
  Person.full_address a.name, email
87
93
  end
88
- end
94
+
95
+ private
96
+
97
+ def add_email_to_map(type, email, acc)
98
+ type = :shown if type != :hidden
99
+ m = email_map(type)
100
+ unless m.member? email
101
+ m[email] = acc
102
+ @email_map_dirty = true
103
+ end
104
+ end
105
+
106
+ def email_map(type = nil)
107
+ case type
108
+ when :shown, :public then @email_map
109
+ when :hidden then @hidden_email_map
110
+ else
111
+ if @email_map_dirty
112
+ @email_map_all = @hidden_email_map.merge(@email_map)
113
+ if @email_map_all.count != @email_map.count + @hidden_email_map.count
114
+ @hidden_email_map.reject! { |m| @email_map.member? m }
115
+ end
116
+ end
117
+ @email_map_all ||= {}
118
+ end
119
+ end
120
+ end # class AccountManager
89
121
 
90
122
  end
data/lib/sup/buffer.rb CHANGED
@@ -2,67 +2,9 @@
2
2
 
3
3
  require 'etc'
4
4
  require 'thread'
5
-
6
5
  require 'ncursesw'
7
6
 
8
- if defined? Ncurses
9
- module Ncurses
10
- def rows
11
- lame, lamer = [], []
12
- stdscr.getmaxyx lame, lamer
13
- lame.first
14
- end
15
-
16
- def cols
17
- lame, lamer = [], []
18
- stdscr.getmaxyx lame, lamer
19
- lamer.first
20
- end
21
-
22
- def curx
23
- lame, lamer = [], []
24
- stdscr.getyx lame, lamer
25
- lamer.first
26
- end
27
-
28
- def mutex; @mutex ||= Mutex.new; end
29
- def sync &b; mutex.synchronize(&b); end
30
-
31
- ## magically, this stuff seems to work now. i could swear it didn't
32
- ## before. hm.
33
- def nonblocking_getch
34
- ## INSANTIY
35
- ## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
36
- ## background threads will be BLOCKED. (except in very modern versions
37
- ## of libncurses-ruby. the current one on ubuntu seems to work well.)
38
- if IO.select([$stdin], nil, nil, 0.5)
39
- if Redwood::BufferManager.shelled?
40
- # If we get input while we're shelled, we'll ignore it for the
41
- # moment and use Ncurses.sync to wait until the shell_out is done.
42
- Ncurses.sync { nil }
43
- else
44
- Ncurses.getch
45
- end
46
- end
47
- end
48
-
49
- ## pretends ctrl-c's are ctrl-g's
50
- def safe_nonblocking_getch
51
- nonblocking_getch
52
- rescue Interrupt
53
- KEY_CANCEL
54
- end
55
-
56
- module_function :rows, :cols, :curx, :nonblocking_getch, :safe_nonblocking_getch, :mutex, :sync
57
-
58
- remove_const :KEY_ENTER
59
- remove_const :KEY_CANCEL
60
-
61
- KEY_ENTER = 10
62
- KEY_CANCEL = 7 # ctrl-g
63
- KEY_TAB = 9
64
- end
65
- end
7
+ require 'sup/util/ncurses'
66
8
 
67
9
  module Redwood
68
10
 
@@ -214,7 +156,14 @@ EOS
214
156
  @sigwinch_mutex = Mutex.new
215
157
  end
216
158
 
217
- def sigwinch_happened!; @sigwinch_mutex.synchronize { @sigwinch_happened = true } end
159
+ def sigwinch_happened!
160
+ @sigwinch_mutex.synchronize do
161
+ return if @sigwinch_happened
162
+ @sigwinch_happened = true
163
+ Ncurses.ungetch ?\C-l.ord
164
+ end
165
+ end
166
+
218
167
  def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end
219
168
 
220
169
  def buffers; @name_map.to_a; end
@@ -266,7 +215,7 @@ EOS
266
215
 
267
216
  def handle_input c
268
217
  if @focus_buf
269
- if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY.ord
218
+ if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY
270
219
  @focus_buf.mode.cancel_search!
271
220
  @focus_buf.mark_dirty
272
221
  end
@@ -398,9 +347,9 @@ EOS
398
347
  draw_screen
399
348
 
400
349
  until mode.done?
401
- c = Ncurses.safe_nonblocking_getch
402
- next unless c # getch timeout
403
- break if c == Ncurses::KEY_CANCEL
350
+ c = Ncurses::CharCode.get
351
+ next unless c.present? # getch timeout
352
+ break if c.is_keycode? Ncurses::KEY_CANCEL
404
353
  begin
405
354
  mode.handle_input c
406
355
  rescue InputSequenceAborted # do nothing
@@ -590,8 +539,8 @@ EOS
590
539
  end
591
540
 
592
541
  while true
593
- c = Ncurses.safe_nonblocking_getch
594
- next unless c # getch timeout
542
+ c = Ncurses::CharCode.get
543
+ next unless c.present? # getch timeout
595
544
  break unless tf.handle_input c # process keystroke
596
545
 
597
546
  if tf.new_completions?
@@ -643,10 +592,11 @@ EOS
643
592
  ret = nil
644
593
  done = false
645
594
  until done
646
- key = Ncurses.safe_nonblocking_getch or next
647
- if key == Ncurses::KEY_CANCEL
595
+ key = Ncurses::CharCode.get
596
+ next if key.empty?
597
+ if key.is_keycode? Ncurses::KEY_CANCEL
648
598
  done = true
649
- elsif accept.nil? || accept.empty? || accept.member?(key)
599
+ elsif accept.nil? || accept.empty? || accept.member?(key.code)
650
600
  ret = key
651
601
  done = true
652
602
  end
@@ -664,7 +614,7 @@ EOS
664
614
  ## returns true (y), false (n), or nil (ctrl-g / cancel)
665
615
  def ask_yes_or_no question
666
616
  case(r = ask_getch question, "ynYN")
667
- when ?y.ord, ?Y.ord
617
+ when ?y, ?Y
668
618
  true
669
619
  when nil
670
620
  nil
@@ -683,7 +633,7 @@ EOS
683
633
  action, text = keymap.action_for c
684
634
  while action.is_a? Keymap # multi-key commands, prompt
685
635
  key = BufferManager.ask_getch text
686
- unless key # user canceled, abort
636
+ unless key.empty? # user canceled, abort
687
637
  erase_flash
688
638
  raise InputSequenceAborted
689
639
  end
data/lib/sup/index.rb CHANGED
@@ -134,7 +134,7 @@ EOS
134
134
 
135
135
  def add_message m; sync_message m, true end
136
136
  def update_message m; sync_message m, true end
137
- def update_message_state m; sync_message m, false end
137
+ def update_message_state m; sync_message m[0], false, m[1] end
138
138
 
139
139
  def save_index
140
140
  info "Flushing Xapian updates to disk. This may take a while..."
@@ -530,18 +530,18 @@ EOS
530
530
  query
531
531
  end
532
532
 
533
- def save_message m
533
+ def save_message m, sync_back = true
534
534
  if @sync_worker
535
- @sync_queue << m
535
+ @sync_queue << [m, sync_back]
536
536
  else
537
- update_message_state m
537
+ update_message_state [m, sync_back]
538
538
  end
539
539
  m.clear_dirty
540
540
  end
541
541
 
542
- def save_thread t
542
+ def save_thread t, sync_back = true
543
543
  t.each_dirty_message do |m|
544
- save_message m
544
+ save_message m, sync_back
545
545
  end
546
546
  end
547
547
 
@@ -676,10 +676,10 @@ EOS
676
676
  end
677
677
  end
678
678
 
679
- def sync_message m, overwrite
679
+ def sync_message m, overwrite, sync_back = true
680
680
  ## TODO: we should not save the message if the sync_back failed
681
681
  ## since it would overwrite the location field
682
- m.sync_back
682
+ m.sync_back if sync_back
683
683
 
684
684
  doc = synchronize { find_doc(m.id) }
685
685
  existed = doc != nil
data/lib/sup/keymap.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'sup/util/ncurses'
2
+
1
3
  module Redwood
2
4
 
3
5
  class Keymap
@@ -96,11 +98,11 @@ EOS
96
98
  end
97
99
 
98
100
  def action_for kc
99
- action, help, keys = @map[kc]
101
+ action, help, keys = @map[kc.code]
100
102
  [action, help]
101
103
  end
102
104
 
103
- def has_key? k; @map[k] end
105
+ def has_key? k; @map[k.code] end
104
106
 
105
107
  def keysyms; @map.values.map { |action, help, keys| keys }.flatten; end
106
108
 
@@ -205,6 +205,7 @@ EOS
205
205
  begin
206
206
  file = Tempfile.new(["sup", Shellwords.escape(@filename.gsub("/", "_")) || "sup-attachment"])
207
207
  file.print @raw_content
208
+ file.flush
208
209
  yield file if block_given?
209
210
  return file.path
210
211
  ensure
@@ -207,7 +207,7 @@ EOS
207
207
  @ts.delete_message m
208
208
  @ts.add_message m
209
209
  end
210
- Index.save_thread t
210
+ Index.save_thread t, sync_back = false
211
211
  update_text_for_line l
212
212
  end
213
213
 
data/lib/sup/tagger.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'sup/util/ncurses'
2
+
1
3
  module Redwood
2
4
 
3
5
  class Tagger
@@ -27,7 +29,7 @@ class Tagger
27
29
 
28
30
  unless action
29
31
  c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
30
- return if c.nil? # user cancelled
32
+ return if c.empty? # user cancelled
31
33
  action = @mode.resolve_input c
32
34
  end
33
35
 
data/lib/sup/textfield.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'sup/util/ncurses'
2
+
1
3
  module Redwood
2
4
 
3
5
  ## a fully-functional text field supporting completions, expansions,
@@ -16,6 +18,8 @@ module Redwood
16
18
  ## in sup, completion support is implemented through BufferManager#ask
17
19
  ## and CompletionMode.
18
20
  class TextField
21
+ include Ncurses::Form::DriverHelpers
22
+
19
23
  def initialize
20
24
  @i = nil
21
25
  @history = []
@@ -48,8 +52,8 @@ class TextField
48
52
  @w.attrset Colormap.color_for(:none)
49
53
  @w.mvaddstr @y, 0, @question
50
54
  Ncurses.curs_set 1
51
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
52
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED
55
+ form_driver_key Ncurses::Form::REQ_END_FIELD
56
+ form_driver_key Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED
53
57
  end
54
58
 
55
59
  def deactivate
@@ -63,7 +67,7 @@ class TextField
63
67
 
64
68
  def handle_input c
65
69
  ## short-circuit exit paths
66
- case c
70
+ case c.code
67
71
  when Ncurses::KEY_ENTER # submit!
68
72
  @value = get_cursed_value
69
73
  @history.push @value unless @value =~ /^\s*$/
@@ -97,39 +101,27 @@ class TextField
97
101
  reset_completion_state
98
102
  @value = nil
99
103
 
100
- d =
101
- case c
104
+ # ctrl_c: control char
105
+ ctrl_c =
106
+ case c.keycode # only test for keycodes
102
107
  when Ncurses::KEY_LEFT
103
108
  Ncurses::Form::REQ_PREV_CHAR
104
109
  when Ncurses::KEY_RIGHT
105
110
  Ncurses::Form::REQ_NEXT_CHAR
106
111
  when Ncurses::KEY_DC
107
112
  Ncurses::Form::REQ_DEL_CHAR
108
- when Ncurses::KEY_BACKSPACE, 127 # 127 is also a backspace keysym
113
+ when Ncurses::KEY_BACKSPACE
109
114
  Ncurses::Form::REQ_DEL_PREV
110
- when ?\C-a.ord, Ncurses::KEY_HOME
115
+ when Ncurses::KEY_HOME
111
116
  nop
112
117
  Ncurses::Form::REQ_BEG_FIELD
113
- when ?\C-e.ord, Ncurses::KEY_END
118
+ when Ncurses::KEY_END
114
119
  Ncurses::Form::REQ_END_FIELD
115
- when ?\C-k.ord
116
- Ncurses::Form::REQ_CLR_EOF
117
- when ?\C-u.ord
118
- set_cursed_value cursed_value_after_point
119
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
120
- nop
121
- Ncurses::Form::REQ_BEG_FIELD
122
- when ?\C-w.ord
123
- while action = remove_extra_space
124
- Ncurses::Form.form_driver @form, action
125
- end
126
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_PREV_CHAR
127
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_WORD
128
120
  when Ncurses::KEY_UP, Ncurses::KEY_DOWN
129
121
  unless !@i || @history.empty?
130
122
  value = get_cursed_value
131
123
  #debug "history before #{@history.inspect}"
132
- @i = @i + (c == Ncurses::KEY_UP ? -1 : 1)
124
+ @i = @i + (c.is_keycode?(Ncurses::KEY_UP) ? -1 : 1)
133
125
  @i = 0 if @i < 0
134
126
  @i = @history.size if @i > @history.size
135
127
  @value = @history[@i] || ''
@@ -138,10 +130,37 @@ class TextField
138
130
  Ncurses::Form::REQ_END_FIELD
139
131
  end
140
132
  else
141
- c
133
+ # return other keycode or nil if it's not a keycode
134
+ c.dumb? ? nil : c.keycode
142
135
  end
143
136
 
144
- Ncurses::Form.form_driver @form, d if d
137
+ # handle keysyms
138
+ # ctrl_c: control char
139
+ ctrl_c = case c
140
+ when ?\177 # backspace (octal)
141
+ Ncurses::Form::REQ_DEL_PREV
142
+ when ?\C-a # home
143
+ nop
144
+ Ncurses::Form::REQ_BEG_FIELD
145
+ when ?\C-e # end keysym
146
+ Ncurses::Form::REQ_END_FIELD
147
+ when ?\C-k
148
+ Ncurses::Form::REQ_CLR_EOF
149
+ when ?\C-u
150
+ set_cursed_value cursed_value_after_point
151
+ form_driver_key Ncurses::Form::REQ_END_FIELD
152
+ nop
153
+ Ncurses::Form::REQ_BEG_FIELD
154
+ when ?\C-w
155
+ while action = remove_extra_space
156
+ form_driver_key action
157
+ end
158
+ form_driver_key Ncurses::Form::REQ_PREV_CHAR
159
+ form_driver_key Ncurses::Form::REQ_DEL_WORD
160
+ end if ctrl_c.nil?
161
+
162
+ c.replace(ctrl_c).keycode! if ctrl_c # no effect for dumb CharCode
163
+ form_driver c if c.present?
145
164
  true
146
165
  end
147
166
 
@@ -159,7 +178,7 @@ private
159
178
  return nil unless @field
160
179
 
161
180
  x = Ncurses.curx
162
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
181
+ form_driver_key Ncurses::Form::REQ_VALIDATION
163
182
  v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
164
183
 
165
184
  ## cursor <= end of text
@@ -175,7 +194,7 @@ private
175
194
  # system locale and also hopefully the terminal/input encoding. an
176
195
  # incorrectly configured terminal encoding (not matching the system
177
196
  # encoding) will produce erronous results, but will also do that for
178
- # a log of other programs since it is impossible to detect which is
197
+ # a lot of other programs since it is impossible to detect which is
179
198
  # which and what encoding the inputted byte chars are supposed to have.
180
199
  v.force_encoding($encoding).fix_encoding!
181
200
  end
@@ -183,7 +202,7 @@ private
183
202
  def remove_extra_space
184
203
  return nil unless @field
185
204
 
186
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
205
+ form_driver_key Ncurses::Form::REQ_VALIDATION
187
206
  x = Ncurses.curx
188
207
  v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
189
208
  v_index = x - @question.length
@@ -226,8 +245,8 @@ private
226
245
  ## this is almost certainly unnecessary, but it's the only way
227
246
  ## i could get ncurses to remember my form's value
228
247
  def nop
229
- Ncurses::Form.form_driver @form, " ".ord
230
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_PREV
248
+ form_driver_char " "
249
+ form_driver_key Ncurses::Form::REQ_DEL_PREV
231
250
  end
232
251
  end
233
252
  end
@@ -0,0 +1,274 @@
1
+ require 'ncursesw'
2
+ require 'sup/util'
3
+
4
+ if defined? Ncurses
5
+ module Ncurses
6
+
7
+ ## Helper class for storing keycodes
8
+ ## and multibyte characters.
9
+ class CharCode < String
10
+ ## Status code allows us to detect
11
+ ## printable characters and control codes.
12
+ attr_reader :status
13
+
14
+ ## Reads character from user input.
15
+ def self.nonblocking_getwch
16
+ # If we get input while we're shelled, we'll ignore it for the
17
+ # moment and use Ncurses.sync to wait until the shell_out is done.
18
+ begin
19
+ s, c = Redwood::BufferManager.shelled? ? Ncurses.sync { nil } : Ncurses.get_wch
20
+ break if s != Ncurses::ERR
21
+ end until IO.select([$stdin], nil, nil, 2)
22
+ [s, c]
23
+ end
24
+
25
+ ## Returns empty singleton.
26
+ def self.empty
27
+ Empty.instance
28
+ end
29
+
30
+ ## Creates new instance of CharCode
31
+ ## that keeps a given keycode.
32
+ def self.keycode(c)
33
+ generate c, Ncurses::KEY_CODE_YES
34
+ end
35
+
36
+ ## Creates new instance of CharCode
37
+ ## that keeps a printable character.
38
+ def self.character(c)
39
+ generate c, Ncurses::OK
40
+ end
41
+
42
+ ## Generates new object like new
43
+ ## but for empty or erroneous objects
44
+ ## it returns empty singleton.
45
+ def self.generate(c = nil, status = Ncurses::OK)
46
+ if status == Ncurses::ERR || c.nil? || c === Ncurses::ERR
47
+ empty
48
+ else
49
+ new(c, status)
50
+ end
51
+ end
52
+
53
+ ## Gets character from input.
54
+ ## Pretends ctrl-c's are ctrl-g's.
55
+ def self.get handle_interrupt=true
56
+ begin
57
+ status, code = nonblocking_getwch
58
+ generate code, status
59
+ rescue Interrupt => e
60
+ raise e unless handle_interrupt
61
+ keycode Ncurses::KEY_CANCEL
62
+ end
63
+ end
64
+
65
+ ## Enables dumb mode for any new instance.
66
+ def self.dumb!
67
+ @dumb = true
68
+ end
69
+
70
+ ## Asks if dumb mode was set
71
+ def self.dumb?
72
+ defined?(@dumb) && @dumb
73
+ end
74
+
75
+ def initialize(c = "", status = Ncurses::OK)
76
+ @status = status
77
+ c = "" if c.nil?
78
+ return super("") if status == Ncurses::ERR
79
+ c = enc_char(c) if c.is_a?(Fixnum)
80
+ super c.length > 1 ? c[0,1] : c
81
+ end
82
+
83
+ ## Proxy method for String's replace
84
+ def replace(c)
85
+ return self if c.object_id == object_id
86
+ if c.is_a?(self.class)
87
+ @status = c.status
88
+ super(c)
89
+ else
90
+ @status = Ncurses::OK
91
+ c = "" if c.nil?
92
+ c = enc_char(c) if c.is_a?(Fixnum)
93
+ super c.length > 1 ? c[0,1] : c
94
+ end
95
+ end
96
+
97
+ def to_character ; character? ? self : "<#{code}>" end ## Returns character or code as a string
98
+ def to_keycode ; keycode? ? code : Ncurses::ERR end ## Returns keycode or ERR if it's not a keycode
99
+ def to_sequence ; bytes.to_a end ## Returns unpacked sequence of bytes for a character
100
+ def code ; ord end ## Returns decimal representation of a character
101
+ def is_keycode?(c) ; keycode? && code == c end ## Tests if keycode matches
102
+ def is_character?(c); character? && self == c end ## Tests if character matches
103
+ def try_keycode ; keycode? ? code : nil end ## Returns dec. code if keycode, nil otherwise
104
+ def try_character ; character? ? self : nil end ## Returns character if character, nil otherwise
105
+ def keycode ; try_keycode end ## Alias for try_keycode
106
+ def character ; try_character end ## Alias for try_character
107
+ def character? ; dumb? || @status == Ncurses::OK end ## Returns true if character
108
+ def character! ; @status = Ncurses::OK ; self end ## Sets character flag
109
+ def keycode? ; dumb? || @status == Ncurses::KEY_CODE_YES end ## Returns true if keycode
110
+ def keycode! ; @status = Ncurses::KEY_CODE_YES ; self end ## Sets keycode flag
111
+ def keycode=(c) ; replace(c); keycode! ; self end ## Sets keycode
112
+ def present? ; not empty? end ## Proxy method
113
+ def printable? ; character? end ## Alias for character?
114
+ def dumb? ; self.class.dumb? end ## True if we cannot distinguish keycodes from characters
115
+
116
+ # Empty singleton that
117
+ # keeps GC from going crazy.
118
+ class Empty < CharCode
119
+ include Singleton
120
+
121
+ ## Wrap methods that may change us
122
+ ## and generate new object instead.
123
+ [ :"[]=", :"<<", :replace, :insert, :prepend, :append, :concat, :force_encoding, :setbyte ].
124
+ select{ |m| public_method_defined?(m) }.
125
+ concat(public_instance_methods.grep(/!\z/)).
126
+ each do |m|
127
+ class_eval <<-EVAL
128
+ def #{m}(*args)
129
+ CharCode.new.#{m}(*args)
130
+ end
131
+ EVAL
132
+ end
133
+
134
+ ## proxy with class-level instance variable delegation
135
+ def self.dumb?
136
+ superclass.dumb? or !!@dumb
137
+ end
138
+
139
+ def self.empty
140
+ instance
141
+ end
142
+
143
+ def initialize
144
+ super("", Ncurses::ERR)
145
+ end
146
+
147
+ def empty? ; true end ## always true
148
+ def present? ; false end ## always false
149
+ def clear ; self end ## always self
150
+
151
+ self
152
+ end.init # CharCode::Empty
153
+
154
+ private
155
+
156
+ ## Tries to make external character right.
157
+ def enc_char(c)
158
+ begin
159
+ character = c.chr($encoding)
160
+ rescue RangeError, ArgumentError
161
+ begin
162
+ character = [c].pack('U')
163
+ rescue RangeError
164
+ begin
165
+ character = c.chr
166
+ rescue
167
+ begin
168
+ character = [c].pack('C')
169
+ rescue
170
+ character = ""
171
+ @status = Ncurses::ERR
172
+ end
173
+ end
174
+ end
175
+ character.fix_encoding!
176
+ end
177
+ end
178
+ end # class CharCode
179
+
180
+ def rows
181
+ lame, lamer = [], []
182
+ stdscr.getmaxyx lame, lamer
183
+ lame.first
184
+ end
185
+
186
+ def cols
187
+ lame, lamer = [], []
188
+ stdscr.getmaxyx lame, lamer
189
+ lamer.first
190
+ end
191
+
192
+ def curx
193
+ lame, lamer = [], []
194
+ stdscr.getyx lame, lamer
195
+ lamer.first
196
+ end
197
+
198
+ ## Create replacement wrapper for form_driver_w (), which is not (yet) a standard
199
+ ## function in ncurses. Some systems (Mac OS X) does not have a working
200
+ ## form_driver that accepts wide chars. We are just falling back to form_driver, expect problems.
201
+ def prepare_form_driver
202
+ if not defined? Form.form_driver_w
203
+ warn "Your Ncursesw does not have a form_driver_w function (wide char aware), " \
204
+ "non-ASCII chars may not work on your system."
205
+ Form.module_eval <<-FRM_DRV, __FILE__, __LINE__ + 1
206
+ def form_driver_w form, status, c
207
+ form_driver form, c
208
+ end
209
+ module_function :form_driver_w
210
+ module DriverHelpers
211
+ def form_driver c
212
+ if !c.dumb? && c.printable?
213
+ c.each_byte do |code|
214
+ Ncurses::Form.form_driver @form, code
215
+ end
216
+ else
217
+ Ncurses::Form.form_driver @form, c.code
218
+ end
219
+ end
220
+ end
221
+ FRM_DRV
222
+ end # if not defined? Form.form_driver_w
223
+ if not defined? Ncurses.get_wch
224
+ warn "Your Ncursesw does not have a get_wch function (wide char aware), " \
225
+ "non-ASCII chars may not work on your system."
226
+ Ncurses.module_eval <<-GET_WCH, __FILE__, __LINE__ + 1
227
+ def get_wch
228
+ c = getch
229
+ c == Ncurses::ERR ? [c, 0] : [Ncurses::OK, c]
230
+ end
231
+ module_function :get_wch
232
+ GET_WCH
233
+ CharCode.dumb!
234
+ end # if not defined? Ncurses.get_wch
235
+ end
236
+
237
+ def mutex; @mutex ||= Mutex.new; end
238
+ def sync &b; mutex.synchronize(&b); end
239
+
240
+ module_function :rows, :cols, :curx, :mutex, :sync, :prepare_form_driver
241
+
242
+ remove_const :KEY_ENTER
243
+ remove_const :KEY_CANCEL
244
+
245
+ KEY_ENTER = 10
246
+ KEY_CANCEL = 7 # ctrl-g
247
+ KEY_TAB = 9
248
+
249
+ module Form
250
+ ## This module contains helpers that ease
251
+ ## using form_driver_ methods when @form is present.
252
+ module DriverHelpers
253
+ private
254
+
255
+ ## Ncurses::Form.form_driver_w wrapper for keycodes and control characters.
256
+ def form_driver_key c
257
+ form_driver CharCode.keycode(c)
258
+ end
259
+
260
+ ## Ncurses::Form.form_driver_w wrapper for printable characters.
261
+ def form_driver_char c
262
+ form_driver CharCode.character(c)
263
+ #c.is_a?(Fixnum) ? c : c.ord
264
+ end
265
+
266
+ ## Ncurses::Form.form_driver_w wrapper for charcodes.
267
+ def form_driver c
268
+ Ncurses::Form.form_driver_w @form, c.status, c.code
269
+ end
270
+ end # module DriverHelpers
271
+ end # module Form
272
+
273
+ end # module Ncurses
274
+ end # if defined? Ncurses
data/lib/sup/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Redwood
2
- VERSION = "0.15.1"
2
+ VERSION = "0.15.2"
3
3
  end
data/lib/sup.rb CHANGED
@@ -357,6 +357,7 @@ EOM
357
357
  :name => name.dup.fix_encoding!,
358
358
  :email => email.dup.fix_encoding!,
359
359
  :alternates => [],
360
+ :hidden_alternates => [],
360
361
  :sendmail => "/usr/sbin/sendmail -oem -ti",
361
362
  :signature => File.join(ENV["HOME"], ".signature"),
362
363
  :gpgkey => ""
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.15.2
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 15
9
- - 1
10
- hash: -3773962691868465541
9
+ - 2
10
+ hash: -2694534377760147359
11
11
  platform: ruby
12
12
  authors:
13
13
  - William Morgan
@@ -17,7 +17,7 @@ authors:
17
17
  autorequire:
18
18
  bindir: bin
19
19
  cert_chain: []
20
- date: 2013-12-04 00:00:00.000000000 Z
20
+ date: 2013-12-20 00:00:00.000000000 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: xapian-ruby
@@ -36,13 +36,13 @@ dependencies:
36
36
  - !ruby/object:Gem::Version
37
37
  version: 1.2.15
38
38
  - !ruby/object:Gem::Dependency
39
- name: ncursesw-sup
39
+ name: ncursesw
40
40
  requirement: !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 1.3.1
45
+ version: 1.4.0
46
46
  type: :runtime
47
47
  prerelease: false
48
48
  version_requirements: !ruby/object:Gem::Requirement
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: 1.3.1
53
+ version: 1.4.0
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: rmail-sup
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -301,6 +301,7 @@ files:
301
301
  - lib/sup/util/query.rb
302
302
  - lib/sup/util/uri.rb
303
303
  - lib/sup/util/path.rb
304
+ - lib/sup/util/ncurses.rb
304
305
  - lib/sup/horizontal_selector.rb
305
306
  - lib/sup/modes/line_cursor_mode.rb
306
307
  - lib/sup/modes/label_list_mode.rb