commit 2475a1732dac0db1889a40cf0d15787a12d65dfc
parent 5cc075a85334b2ded420286c7139538599578f9d
Author: Gaute Hope <eg@gaute.vetsj.com>
Date: Fri, 20 Dec 2013 10:25:28 +0100
Merge branch 'develop'
Diffstat:
15 files changed, 435 insertions(+), 139 deletions(-)
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
@@ -6,6 +6,7 @@ Hamish Downer <dmishd at the gmail dot coms>
Damien Leone <damien.leone at the fensalir dot frs>
Sascha Silbe <sascha-pgp at the silbe dot orgs>
Eric Weikl <eric.weikl at the gmx dot nets>
+Paweł Wilk <siefca at the gnu dot orgs>
Ismo Puustinen <ismo at the iki dot fis>
Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
Michael Stapelberg <michael at the stapelberg dot des>
@@ -29,43 +30,44 @@ Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
Israel Herraiz <israel.herraiz at the gmail dot coms>
Bo Borgerson <gigabo at the gmail dot coms>
Michael Hamann <michael at the content-space dot des>
-William Erik Baxter <web at the superscript dot coms>
Jonathan Lassoff <jof at the thejof dot coms>
+William Erik Baxter <web at the superscript dot coms>
Grant Hollingworth <grant at the antiflux dot orgs>
Adeodato Simó <dato at the net.com.org dot ess>
-Ico Doornekamp <ico at the pruts dot nls>
Markus Klinik <markus.klinik at the gmx dot des>
+Ico Doornekamp <ico at the pruts dot nls>
Daniel Schoepe <daniel.schoepe at the googlemail dot coms>
James Taylor <james at the jamestaylor dot orgs>
Jason Petsod <jason at the petsod dot orgs>
-Steve Goldman <sgoldman at the tower-research dot coms>
Robin Burchell <viroteck at the viroteck dot nets>
+Steve Goldman <sgoldman at the tower-research dot coms>
Peter Harkins <ph at the malaprop dot orgs>
Decklin Foster <decklin at the red-bean dot coms>
Cameron Matheson <cam+sup at the cammunism dot orgs>
-Carl Worth <cworth at the cworth dot orgs>
Alex Vandiver <alex at the chmrr dot nets>
+Carl Worth <cworth at the cworth dot orgs>
Andrew Pimlott <andrew at the pimlott dot nets>
Jeff Balogh <its.jeff.balogh at the gmail dot coms>
Matías Aguirre <matiasaguirre at the gmail dot coms>
Kornilios Kourtis <kkourt at the cslab.ece.ntua dot grs>
-Giorgio Lando <patroclo7 at the gmail dot coms>
Kevin Riggle <kevinr at the free-dissociation dot coms>
+Giorgio Lando <patroclo7 at the gmail dot coms>
Benoît PIERRE <benoit.pierre at the gmail dot coms>
-Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
Steven Lawrance <stl at the koffein dot nets>
+Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
Jonah <Jonah at the GoodCoffee dot cas>
ian <itaylor at the uark dot edus>
-Adam Lloyd <adam at the alloy-d dot nets>
Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
0xACE <0xACE at the users.noreply.github dot coms>
-Per Andersson <avtobiff at the gmail dot coms>
-MichaelRevell <mikearevell at the gmail dot coms>
+Adam Lloyd <adam at the alloy-d dot nets>
Todd Eisenberger <teisenbe at the andrew.cmu dot edus>
+MichaelRevell <mikearevell at the gmail dot coms>
+Per Andersson <avtobiff at the gmail dot coms>
Steven Walter <swalter at the monarch.(none)>
-Matthias Vallentin <vallentin at the icir dot orgs>
-akojo <atte.kojo at the gmail dot coms>
Jon M. Dugan <jdugan at the es dot nets>
+Matthias Vallentin <vallentin at the icir dot orgs>
Horacio Sanson <horacio at the skillupjapan.co dot jps>
Stefan Lundström <lundst at the snabb.(none)>
+akojo <atte.kojo at the gmail dot coms>
+Johannes Larsen <johs.a.larsen at the gmail dot coms>
Kirill Smelkov <kirr at the landau.phys.spbu dot rus>
diff --git a/History.txt b/History.txt
@@ -1,3 +1,10 @@
+== 0.15.2 / 2013-12-20
+
+* Use the form_driver_w routine for inputing multibyte chars when
+ available.
+* Add hidden_alternates configuration option: hidden aliases for the
+ account.
+
== 0.15.1 / 2013-12-04
* Thread children are sorted last-activity latest (bottom).
diff --git a/ReleaseNotes b/ReleaseNotes
@@ -1,3 +1,7 @@
+Release 0.15.2:
+
+Use form_driver_w when available. New hidden_alternates option.
+
Release 0.15.1:
Sort threads last-activity-first and bug fix.
diff --git a/bin/sup b/bin/sup
@@ -4,9 +4,10 @@
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
require 'rubygems'
-
require 'ncursesw'
+require 'sup/util/ncurses'
+
no_gpgme = false
begin
require 'gpgme'
@@ -136,6 +137,7 @@ def start_cursing
Ncurses.use_default_colors
Ncurses.curs_set 0
Ncurses.start_color
+ Ncurses.prepare_form_driver
$cursing = true
end
@@ -236,14 +238,14 @@ begin
until Redwood::exceptions.nonempty? || $die
c = begin
- Ncurses.nonblocking_getch
+ Ncurses::CharCode.get false
rescue Interrupt
raise if BufferManager.ask_yes_or_no "Die ungracefully now?"
BufferManager.draw_screen
- nil
+ Ncurses::CharCode.empty
end
- if c.nil?
+ if c.empty?
if BufferManager.sigwinch_happened?
debug "redrawing screen on sigwinch"
BufferManager.completely_redraw_screen
@@ -253,7 +255,7 @@ begin
IdleManager.ping
- if c == 410
+ if c.is_keycode? 410
## this is ncurses's way of telling us it's detected a refresh.
## since we have our own sigwinch handler, we don't do anything.
next
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -357,6 +357,7 @@ EOM
:name => name.dup.fix_encoding!,
:email => email.dup.fix_encoding!,
:alternates => [],
+ :hidden_alternates => [],
:sendmail => "/usr/sbin/sendmail -oem -ti",
:signature => File.join(ENV["HOME"], ".signature"),
:gpgkey => ""
diff --git a/lib/sup/account.rb b/lib/sup/account.rb
@@ -31,6 +31,8 @@ class AccountManager
def initialize accounts
@email_map = {}
+ @hidden_email_map = {}
+ @email_map_dirty = false
@accounts = {}
@regexen = {}
@default_account = nil
@@ -40,7 +42,7 @@ class AccountManager
end
def user_accounts; @accounts.keys; end
- def user_emails; @email_map.keys.select { |e| String === e }; end
+ def user_emails(type = :all); email_map(type).keys.select { |e| String === e }; end
## must be called first with the default account. fills in missing
## values from the default account.
@@ -50,7 +52,9 @@ class AccountManager
[:name, :sendmail, :signature, :gpgkey].each { |k| hash[k] ||= @default_account.send(k) }
end
hash[:alternates] ||= []
+ hash[:hidden_alternates] ||= []
fail "alternative emails are not an array: #{hash[:alternates]}" unless hash[:alternates].kind_of? Array
+ fail "hidden alternative emails are not an array: #{hash[:hidden_alternates]}" unless hash[:hidden_alternates].kind_of? Array
[:name, :signature].each { |x| hash[x] ? hash[x].fix_encoding! : nil }
@@ -63,8 +67,11 @@ class AccountManager
end
([hash[:email]] + hash[:alternates]).each do |email|
- next if @email_map.member? email
- @email_map[email] = a
+ add_email_to_map(:shown, email, a)
+ end
+
+ hash[:hidden_alternates].each do |email|
+ add_email_to_map(:hidden, email, a)
end
hash[:regexen].each do |re|
@@ -72,19 +79,44 @@ class AccountManager
end if hash[:regexen]
end
- def is_account? p; is_account_email? p.email end
+ def is_account? p; is_account_email? p.email end
def is_account_email? email; !account_for(email).nil? end
+
def account_for email
- if(a = @email_map[email])
- a
- else
- @regexen.argfind { |re, a| re =~ email && a }
- end
+ a = email_map[email]
+ a.nil? ? @regexen.argfind { |re, a| re =~ email && a } : a
end
+
def full_address_for email
a = account_for email
Person.full_address a.name, email
end
-end
+
+ private
+
+ def add_email_to_map(type, email, acc)
+ type = :shown if type != :hidden
+ m = email_map(type)
+ unless m.member? email
+ m[email] = acc
+ @email_map_dirty = true
+ end
+ end
+
+ def email_map(type = nil)
+ case type
+ when :shown, :public then @email_map
+ when :hidden then @hidden_email_map
+ else
+ if @email_map_dirty
+ @email_map_all = @hidden_email_map.merge(@email_map)
+ if @email_map_all.count != @email_map.count + @hidden_email_map.count
+ @hidden_email_map.reject! { |m| @email_map.member? m }
+ end
+ end
+ @email_map_all ||= {}
+ end
+ end
+end # class AccountManager
end
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
@@ -2,67 +2,9 @@
require 'etc'
require 'thread'
-
require 'ncursesw'
-if defined? Ncurses
-module Ncurses
- def rows
- lame, lamer = [], []
- stdscr.getmaxyx lame, lamer
- lame.first
- end
-
- def cols
- lame, lamer = [], []
- stdscr.getmaxyx lame, lamer
- lamer.first
- end
-
- def curx
- lame, lamer = [], []
- stdscr.getyx lame, lamer
- lamer.first
- end
-
- def mutex; @mutex ||= Mutex.new; end
- def sync &b; mutex.synchronize(&b); end
-
- ## magically, this stuff seems to work now. i could swear it didn't
- ## before. hm.
- def nonblocking_getch
- ## INSANTIY
- ## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
- ## background threads will be BLOCKED. (except in very modern versions
- ## of libncurses-ruby. the current one on ubuntu seems to work well.)
- if IO.select([$stdin], nil, nil, 0.5)
- if Redwood::BufferManager.shelled?
- # If we get input while we're shelled, we'll ignore it for the
- # moment and use Ncurses.sync to wait until the shell_out is done.
- Ncurses.sync { nil }
- else
- Ncurses.getch
- end
- end
- end
-
- ## pretends ctrl-c's are ctrl-g's
- def safe_nonblocking_getch
- nonblocking_getch
- rescue Interrupt
- KEY_CANCEL
- end
-
- module_function :rows, :cols, :curx, :nonblocking_getch, :safe_nonblocking_getch, :mutex, :sync
-
- remove_const :KEY_ENTER
- remove_const :KEY_CANCEL
-
- KEY_ENTER = 10
- KEY_CANCEL = 7 # ctrl-g
- KEY_TAB = 9
-end
-end
+require 'sup/util/ncurses'
module Redwood
@@ -214,7 +156,14 @@ EOS
@sigwinch_mutex = Mutex.new
end
- def sigwinch_happened!; @sigwinch_mutex.synchronize { @sigwinch_happened = true } end
+ def sigwinch_happened!
+ @sigwinch_mutex.synchronize do
+ return if @sigwinch_happened
+ @sigwinch_happened = true
+ Ncurses.ungetch ?\C-l.ord
+ end
+ end
+
def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end
def buffers; @name_map.to_a; end
@@ -266,7 +215,7 @@ EOS
def handle_input c
if @focus_buf
- if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY.ord
+ if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY
@focus_buf.mode.cancel_search!
@focus_buf.mark_dirty
end
@@ -398,9 +347,9 @@ EOS
draw_screen
until mode.done?
- c = Ncurses.safe_nonblocking_getch
- next unless c # getch timeout
- break if c == Ncurses::KEY_CANCEL
+ c = Ncurses::CharCode.get
+ next unless c.present? # getch timeout
+ break if c.is_keycode? Ncurses::KEY_CANCEL
begin
mode.handle_input c
rescue InputSequenceAborted # do nothing
@@ -590,8 +539,8 @@ EOS
end
while true
- c = Ncurses.safe_nonblocking_getch
- next unless c # getch timeout
+ c = Ncurses::CharCode.get
+ next unless c.present? # getch timeout
break unless tf.handle_input c # process keystroke
if tf.new_completions?
@@ -643,10 +592,11 @@ EOS
ret = nil
done = false
until done
- key = Ncurses.safe_nonblocking_getch or next
- if key == Ncurses::KEY_CANCEL
+ key = Ncurses::CharCode.get
+ next if key.empty?
+ if key.is_keycode? Ncurses::KEY_CANCEL
done = true
- elsif accept.nil? || accept.empty? || accept.member?(key)
+ elsif accept.nil? || accept.empty? || accept.member?(key.code)
ret = key
done = true
end
@@ -664,7 +614,7 @@ EOS
## returns true (y), false (n), or nil (ctrl-g / cancel)
def ask_yes_or_no question
case(r = ask_getch question, "ynYN")
- when ?y.ord, ?Y.ord
+ when ?y, ?Y
true
when nil
nil
@@ -683,7 +633,7 @@ EOS
action, text = keymap.action_for c
while action.is_a? Keymap # multi-key commands, prompt
key = BufferManager.ask_getch text
- unless key # user canceled, abort
+ unless key.empty? # user canceled, abort
erase_flash
raise InputSequenceAborted
end
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
@@ -134,7 +134,7 @@ EOS
def add_message m; sync_message m, true end
def update_message m; sync_message m, true end
- def update_message_state m; sync_message m, false end
+ def update_message_state m; sync_message m[0], false, m[1] end
def save_index
info "Flushing Xapian updates to disk. This may take a while..."
@@ -530,18 +530,18 @@ EOS
query
end
- def save_message m
+ def save_message m, sync_back = true
if @sync_worker
- @sync_queue << m
+ @sync_queue << [m, sync_back]
else
- update_message_state m
+ update_message_state [m, sync_back]
end
m.clear_dirty
end
- def save_thread t
+ def save_thread t, sync_back = true
t.each_dirty_message do |m|
- save_message m
+ save_message m, sync_back
end
end
@@ -676,10 +676,10 @@ EOS
end
end
- def sync_message m, overwrite
+ def sync_message m, overwrite, sync_back = true
## TODO: we should not save the message if the sync_back failed
## since it would overwrite the location field
- m.sync_back
+ m.sync_back if sync_back
doc = synchronize { find_doc(m.id) }
existed = doc != nil
diff --git a/lib/sup/keymap.rb b/lib/sup/keymap.rb
@@ -1,3 +1,5 @@
+require 'sup/util/ncurses'
+
module Redwood
class Keymap
@@ -96,11 +98,11 @@ EOS
end
def action_for kc
- action, help, keys = @map[kc]
+ action, help, keys = @map[kc.code]
[action, help]
end
- def has_key? k; @map[k] end
+ def has_key? k; @map[k.code] end
def keysyms; @map.values.map { |action, help, keys| keys }.flatten; end
diff --git a/lib/sup/message_chunks.rb b/lib/sup/message_chunks.rb
@@ -205,6 +205,7 @@ EOS
begin
file = Tempfile.new(["sup", Shellwords.escape(@filename.gsub("/", "_")) || "sup-attachment"])
file.print @raw_content
+ file.flush
yield file if block_given?
return file.path
ensure
diff --git a/lib/sup/modes/thread_index_mode.rb b/lib/sup/modes/thread_index_mode.rb
@@ -207,7 +207,7 @@ EOS
@ts.delete_message m
@ts.add_message m
end
- Index.save_thread t
+ Index.save_thread t, sync_back = false
update_text_for_line l
end
diff --git a/lib/sup/tagger.rb b/lib/sup/tagger.rb
@@ -1,3 +1,5 @@
+require 'sup/util/ncurses'
+
module Redwood
class Tagger
@@ -27,7 +29,7 @@ class Tagger
unless action
c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
- return if c.nil? # user cancelled
+ return if c.empty? # user cancelled
action = @mode.resolve_input c
end
diff --git a/lib/sup/textfield.rb b/lib/sup/textfield.rb
@@ -1,3 +1,5 @@
+require 'sup/util/ncurses'
+
module Redwood
## a fully-functional text field supporting completions, expansions,
@@ -16,6 +18,8 @@ module Redwood
## in sup, completion support is implemented through BufferManager#ask
## and CompletionMode.
class TextField
+ include Ncurses::Form::DriverHelpers
+
def initialize
@i = nil
@history = []
@@ -48,8 +52,8 @@ class TextField
@w.attrset Colormap.color_for(:none)
@w.mvaddstr @y, 0, @question
Ncurses.curs_set 1
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED
+ form_driver_key Ncurses::Form::REQ_END_FIELD
+ form_driver_key Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED
end
def deactivate
@@ -63,7 +67,7 @@ class TextField
def handle_input c
## short-circuit exit paths
- case c
+ case c.code
when Ncurses::KEY_ENTER # submit!
@value = get_cursed_value
@history.push @value unless @value =~ /^\s*$/
@@ -97,39 +101,27 @@ class TextField
reset_completion_state
@value = nil
- d =
- case c
+ # ctrl_c: control char
+ ctrl_c =
+ case c.keycode # only test for keycodes
when Ncurses::KEY_LEFT
Ncurses::Form::REQ_PREV_CHAR
when Ncurses::KEY_RIGHT
Ncurses::Form::REQ_NEXT_CHAR
when Ncurses::KEY_DC
Ncurses::Form::REQ_DEL_CHAR
- when Ncurses::KEY_BACKSPACE, 127 # 127 is also a backspace keysym
+ when Ncurses::KEY_BACKSPACE
Ncurses::Form::REQ_DEL_PREV
- when ?\C-a.ord, Ncurses::KEY_HOME
+ when Ncurses::KEY_HOME
nop
Ncurses::Form::REQ_BEG_FIELD
- when ?\C-e.ord, Ncurses::KEY_END
+ when Ncurses::KEY_END
Ncurses::Form::REQ_END_FIELD
- when ?\C-k.ord
- Ncurses::Form::REQ_CLR_EOF
- when ?\C-u.ord
- set_cursed_value cursed_value_after_point
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
- nop
- Ncurses::Form::REQ_BEG_FIELD
- when ?\C-w.ord
- while action = remove_extra_space
- Ncurses::Form.form_driver @form, action
- end
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_PREV_CHAR
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_WORD
when Ncurses::KEY_UP, Ncurses::KEY_DOWN
unless !@i || @history.empty?
value = get_cursed_value
#debug "history before #{@history.inspect}"
- @i = @i + (c == Ncurses::KEY_UP ? -1 : 1)
+ @i = @i + (c.is_keycode?(Ncurses::KEY_UP) ? -1 : 1)
@i = 0 if @i < 0
@i = @history.size if @i > @history.size
@value = @history[@i] || ''
@@ -138,10 +130,37 @@ class TextField
Ncurses::Form::REQ_END_FIELD
end
else
- c
+ # return other keycode or nil if it's not a keycode
+ c.dumb? ? nil : c.keycode
end
- Ncurses::Form.form_driver @form, d if d
+ # handle keysyms
+ # ctrl_c: control char
+ ctrl_c = case c
+ when ?\177 # backspace (octal)
+ Ncurses::Form::REQ_DEL_PREV
+ when ?\C-a # home
+ nop
+ Ncurses::Form::REQ_BEG_FIELD
+ when ?\C-e # end keysym
+ Ncurses::Form::REQ_END_FIELD
+ when ?\C-k
+ Ncurses::Form::REQ_CLR_EOF
+ when ?\C-u
+ set_cursed_value cursed_value_after_point
+ form_driver_key Ncurses::Form::REQ_END_FIELD
+ nop
+ Ncurses::Form::REQ_BEG_FIELD
+ when ?\C-w
+ while action = remove_extra_space
+ form_driver_key action
+ end
+ form_driver_key Ncurses::Form::REQ_PREV_CHAR
+ form_driver_key Ncurses::Form::REQ_DEL_WORD
+ end if ctrl_c.nil?
+
+ c.replace(ctrl_c).keycode! if ctrl_c # no effect for dumb CharCode
+ form_driver c if c.present?
true
end
@@ -159,7 +178,7 @@ private
return nil unless @field
x = Ncurses.curx
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
+ form_driver_key Ncurses::Form::REQ_VALIDATION
v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
## cursor <= end of text
@@ -175,7 +194,7 @@ private
# system locale and also hopefully the terminal/input encoding. an
# incorrectly configured terminal encoding (not matching the system
# encoding) will produce erronous results, but will also do that for
- # a log of other programs since it is impossible to detect which is
+ # a lot of other programs since it is impossible to detect which is
# which and what encoding the inputted byte chars are supposed to have.
v.force_encoding($encoding).fix_encoding!
end
@@ -183,7 +202,7 @@ private
def remove_extra_space
return nil unless @field
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
+ form_driver_key Ncurses::Form::REQ_VALIDATION
x = Ncurses.curx
v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
v_index = x - @question.length
@@ -226,8 +245,8 @@ private
## this is almost certainly unnecessary, but it's the only way
## i could get ncurses to remember my form's value
def nop
- Ncurses::Form.form_driver @form, " ".ord
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_PREV
+ form_driver_char " "
+ form_driver_key Ncurses::Form::REQ_DEL_PREV
end
end
end
diff --git a/lib/sup/util/ncurses.rb b/lib/sup/util/ncurses.rb
@@ -0,0 +1,274 @@
+require 'ncursesw'
+require 'sup/util'
+
+if defined? Ncurses
+module Ncurses
+
+ ## Helper class for storing keycodes
+ ## and multibyte characters.
+ class CharCode < String
+ ## Status code allows us to detect
+ ## printable characters and control codes.
+ attr_reader :status
+
+ ## Reads character from user input.
+ def self.nonblocking_getwch
+ # If we get input while we're shelled, we'll ignore it for the
+ # moment and use Ncurses.sync to wait until the shell_out is done.
+ begin
+ s, c = Redwood::BufferManager.shelled? ? Ncurses.sync { nil } : Ncurses.get_wch
+ break if s != Ncurses::ERR
+ end until IO.select([$stdin], nil, nil, 2)
+ [s, c]
+ end
+
+ ## Returns empty singleton.
+ def self.empty
+ Empty.instance
+ end
+
+ ## Creates new instance of CharCode
+ ## that keeps a given keycode.
+ def self.keycode(c)
+ generate c, Ncurses::KEY_CODE_YES
+ end
+
+ ## Creates new instance of CharCode
+ ## that keeps a printable character.
+ def self.character(c)
+ generate c, Ncurses::OK
+ end
+
+ ## Generates new object like new
+ ## but for empty or erroneous objects
+ ## it returns empty singleton.
+ def self.generate(c = nil, status = Ncurses::OK)
+ if status == Ncurses::ERR || c.nil? || c === Ncurses::ERR
+ empty
+ else
+ new(c, status)
+ end
+ end
+
+ ## Gets character from input.
+ ## Pretends ctrl-c's are ctrl-g's.
+ def self.get handle_interrupt=true
+ begin
+ status, code = nonblocking_getwch
+ generate code, status
+ rescue Interrupt => e
+ raise e unless handle_interrupt
+ keycode Ncurses::KEY_CANCEL
+ end
+ end
+
+ ## Enables dumb mode for any new instance.
+ def self.dumb!
+ @dumb = true
+ end
+
+ ## Asks if dumb mode was set
+ def self.dumb?
+ defined?(@dumb) && @dumb
+ end
+
+ def initialize(c = "", status = Ncurses::OK)
+ @status = status
+ c = "" if c.nil?
+ return super("") if status == Ncurses::ERR
+ c = enc_char(c) if c.is_a?(Fixnum)
+ super c.length > 1 ? c[0,1] : c
+ end
+
+ ## Proxy method for String's replace
+ def replace(c)
+ return self if c.object_id == object_id
+ if c.is_a?(self.class)
+ @status = c.status
+ super(c)
+ else
+ @status = Ncurses::OK
+ c = "" if c.nil?
+ c = enc_char(c) if c.is_a?(Fixnum)
+ super c.length > 1 ? c[0,1] : c
+ end
+ end
+
+ def to_character ; character? ? self : "<#{code}>" end ## Returns character or code as a string
+ def to_keycode ; keycode? ? code : Ncurses::ERR end ## Returns keycode or ERR if it's not a keycode
+ def to_sequence ; bytes.to_a end ## Returns unpacked sequence of bytes for a character
+ def code ; ord end ## Returns decimal representation of a character
+ def is_keycode?(c) ; keycode? && code == c end ## Tests if keycode matches
+ def is_character?(c); character? && self == c end ## Tests if character matches
+ def try_keycode ; keycode? ? code : nil end ## Returns dec. code if keycode, nil otherwise
+ def try_character ; character? ? self : nil end ## Returns character if character, nil otherwise
+ def keycode ; try_keycode end ## Alias for try_keycode
+ def character ; try_character end ## Alias for try_character
+ def character? ; dumb? || @status == Ncurses::OK end ## Returns true if character
+ def character! ; @status = Ncurses::OK ; self end ## Sets character flag
+ def keycode? ; dumb? || @status == Ncurses::KEY_CODE_YES end ## Returns true if keycode
+ def keycode! ; @status = Ncurses::KEY_CODE_YES ; self end ## Sets keycode flag
+ def keycode=(c) ; replace(c); keycode! ; self end ## Sets keycode
+ def present? ; not empty? end ## Proxy method
+ def printable? ; character? end ## Alias for character?
+ def dumb? ; self.class.dumb? end ## True if we cannot distinguish keycodes from characters
+
+ # Empty singleton that
+ # keeps GC from going crazy.
+ class Empty < CharCode
+ include Singleton
+
+ ## Wrap methods that may change us
+ ## and generate new object instead.
+ [ :"[]=", :"<<", :replace, :insert, :prepend, :append, :concat, :force_encoding, :setbyte ].
+ select{ |m| public_method_defined?(m) }.
+ concat(public_instance_methods.grep(/!\z/)).
+ each do |m|
+ class_eval <<-EVAL
+ def #{m}(*args)
+ CharCode.new.#{m}(*args)
+ end
+ EVAL
+ end
+
+ ## proxy with class-level instance variable delegation
+ def self.dumb?
+ superclass.dumb? or !!@dumb
+ end
+
+ def self.empty
+ instance
+ end
+
+ def initialize
+ super("", Ncurses::ERR)
+ end
+
+ def empty? ; true end ## always true
+ def present? ; false end ## always false
+ def clear ; self end ## always self
+
+ self
+ end.init # CharCode::Empty
+
+ private
+
+ ## Tries to make external character right.
+ def enc_char(c)
+ begin
+ character = c.chr($encoding)
+ rescue RangeError, ArgumentError
+ begin
+ character = [c].pack('U')
+ rescue RangeError
+ begin
+ character = c.chr
+ rescue
+ begin
+ character = [c].pack('C')
+ rescue
+ character = ""
+ @status = Ncurses::ERR
+ end
+ end
+ end
+ character.fix_encoding!
+ end
+ end
+ end # class CharCode
+
+ def rows
+ lame, lamer = [], []
+ stdscr.getmaxyx lame, lamer
+ lame.first
+ end
+
+ def cols
+ lame, lamer = [], []
+ stdscr.getmaxyx lame, lamer
+ lamer.first
+ end
+
+ def curx
+ lame, lamer = [], []
+ stdscr.getyx lame, lamer
+ lamer.first
+ end
+
+ ## Create replacement wrapper for form_driver_w (), which is not (yet) a standard
+ ## function in ncurses. Some systems (Mac OS X) does not have a working
+ ## form_driver that accepts wide chars. We are just falling back to form_driver, expect problems.
+ def prepare_form_driver
+ if not defined? Form.form_driver_w
+ warn "Your Ncursesw does not have a form_driver_w function (wide char aware), " \
+ "non-ASCII chars may not work on your system."
+ Form.module_eval <<-FRM_DRV, __FILE__, __LINE__ + 1
+ def form_driver_w form, status, c
+ form_driver form, c
+ end
+ module_function :form_driver_w
+ module DriverHelpers
+ def form_driver c
+ if !c.dumb? && c.printable?
+ c.each_byte do |code|
+ Ncurses::Form.form_driver @form, code
+ end
+ else
+ Ncurses::Form.form_driver @form, c.code
+ end
+ end
+ end
+ FRM_DRV
+ end # if not defined? Form.form_driver_w
+ if not defined? Ncurses.get_wch
+ warn "Your Ncursesw does not have a get_wch function (wide char aware), " \
+ "non-ASCII chars may not work on your system."
+ Ncurses.module_eval <<-GET_WCH, __FILE__, __LINE__ + 1
+ def get_wch
+ c = getch
+ c == Ncurses::ERR ? [c, 0] : [Ncurses::OK, c]
+ end
+ module_function :get_wch
+ GET_WCH
+ CharCode.dumb!
+ end # if not defined? Ncurses.get_wch
+ end
+
+ def mutex; @mutex ||= Mutex.new; end
+ def sync &b; mutex.synchronize(&b); end
+
+ module_function :rows, :cols, :curx, :mutex, :sync, :prepare_form_driver
+
+ remove_const :KEY_ENTER
+ remove_const :KEY_CANCEL
+
+ KEY_ENTER = 10
+ KEY_CANCEL = 7 # ctrl-g
+ KEY_TAB = 9
+
+ module Form
+ ## This module contains helpers that ease
+ ## using form_driver_ methods when @form is present.
+ module DriverHelpers
+ private
+
+ ## Ncurses::Form.form_driver_w wrapper for keycodes and control characters.
+ def form_driver_key c
+ form_driver CharCode.keycode(c)
+ end
+
+ ## Ncurses::Form.form_driver_w wrapper for printable characters.
+ def form_driver_char c
+ form_driver CharCode.character(c)
+ #c.is_a?(Fixnum) ? c : c.ord
+ end
+
+ ## Ncurses::Form.form_driver_w wrapper for charcodes.
+ def form_driver c
+ Ncurses::Form.form_driver_w @form, c.status, c.code
+ end
+ end # module DriverHelpers
+ end # module Form
+
+end # module Ncurses
+end # if defined? Ncurses
diff --git a/sup.gemspec b/sup.gemspec
@@ -48,7 +48,7 @@ SUP: If you are upgrading Sup from before version 0.14.0: Please
s.required_ruby_version = '>= 1.9.2'
s.add_runtime_dependency "xapian-ruby", "~> 1.2.15"
- s.add_runtime_dependency "ncursesw-sup", "~> 1.3.1"
+ s.add_runtime_dependency "ncursesw", "~> 1.4.0"
s.add_runtime_dependency "rmail-sup", "~> 1.0.1"
s.add_runtime_dependency "highline"
s.add_runtime_dependency "trollop", ">= 1.12"