sup 0.15.1 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CONTRIBUTORS +13 -11
- data/History.txt +7 -0
- data/ReleaseNotes +4 -0
- data/bin/sup +7 -5
- data/lib/sup/account.rb +42 -10
- data/lib/sup/buffer.rb +21 -71
- data/lib/sup/index.rb +8 -8
- data/lib/sup/keymap.rb +4 -2
- data/lib/sup/message_chunks.rb +1 -0
- data/lib/sup/modes/thread_index_mode.rb +1 -1
- data/lib/sup/tagger.rb +3 -1
- data/lib/sup/textfield.rb +48 -29
- data/lib/sup/util/ncurses.rb +274 -0
- data/lib/sup/version.rb +1 -1
- data/lib/sup.rb +1 -0
- metadata +8 -7
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
|
-
|
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
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.
|
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
|
-
|
245
|
+
Ncurses::CharCode.empty
|
244
246
|
end
|
245
247
|
|
246
|
-
if c.
|
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
|
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;
|
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
|
-
|
67
|
-
|
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;
|
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
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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.
|
402
|
-
next unless c # getch timeout
|
403
|
-
break if c
|
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.
|
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.
|
647
|
-
if key
|
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
|
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
|
|
data/lib/sup/message_chunks.rb
CHANGED
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.
|
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
|
-
|
52
|
-
|
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
|
-
|
101
|
-
|
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
|
113
|
+
when Ncurses::KEY_BACKSPACE
|
109
114
|
Ncurses::Form::REQ_DEL_PREV
|
110
|
-
when
|
115
|
+
when Ncurses::KEY_HOME
|
111
116
|
nop
|
112
117
|
Ncurses::Form::REQ_BEG_FIELD
|
113
|
-
when
|
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
|
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
|
-
|
133
|
+
# return other keycode or nil if it's not a keycode
|
134
|
+
c.dumb? ? nil : c.keycode
|
142
135
|
end
|
143
136
|
|
144
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
230
|
-
|
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
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.
|
4
|
+
version: 0.15.2
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 15
|
9
|
-
-
|
10
|
-
hash: -
|
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-
|
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
|
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.
|
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.
|
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
|