commit 7e7858c8e731daec7ab7ac758bff7a1ee884945c
parent a6977bed247f428072ce2bf82b6dd596526e50d7
Author: Paweł Wilk <siefca@gnu.org>
Date: Mon, 16 Dec 2013 15:34:44 +0100
Merge #183: Wrap form_driver_w for wide char input
form_driver does not work correctly on all systems (specifically Mac OS X),
it is difficult or impossible to determine whether the input char is a
char or key code resulting from a char sequence in a multibyte char.
If no form_driver_w is present we fall back to form_driver.
form_driver_w is included in ncursesw-1.4.2 with the Ncurses 5.9 20131207
patchlevel.
http://lists.gnu.org/archive/html/bug-ncurses/2013-12/msg00010.html
original patch:
http://lists.gnu.org/archive/html/bug-ncurses/2013-11/msg00015.html
Author: Paweł Wilk
Squashed commit of the following:
commit 7a513f25d755523576268bfa5c77fafc838564b6
Author: Paweł Wilk
Date: Tue Nov 26 19:49:30 2013 +0100
Added conditional select in Ncurses::nonblocking_getwch to prevent erroneous CharCode spawning
commit 0b6859eb1b5b02e98f07214e45baab550e6f07e7
Author: Paweł Wilk
Date: Sun Nov 24 21:14:52 2013 +0100
BufferManager.sigwinch_happened! modified to run once if it already happened
commit 857cb60058f966e22bd0f06950c5b8fa3d8e622f
Author: Paweł Wilk
Date: Sun Nov 24 20:44:21 2013 +0100
Added workaround for screen redrawing when sigwinch happened
commit d72d72fb27b23af8d386faa36e1d78c0087c1115
Author: Paweł Wilk
Date: Sun Nov 24 20:43:46 2013 +0100
Optimizations in Ncurses::CharCode.get
commit c2139b6df4aba9551adf4275cb1d8bc70e4727e2
Author: Paweł Wilk
Date: Sun Nov 24 18:19:57 2013 +0100
Fixed uninitialized instance variable warning
commit 72fb7e43b7237ebc582ca58fe2df115ea3747ef3
Author: Paweł Wilk
Date: Sun Nov 24 17:43:16 2013 +0100
Added critical patches for Ncurses::Form::DriverHelpers#form_driver and Ncurses.get_wch
commit 340fb7f249b799cafb2b1e40f8b8b31e82f449d2
Author: Paweł Wilk
Date: Sun Nov 24 17:42:24 2013 +0100
Ncurses::Form::DriverHelpers improved; now all wrappers are reusing core wrapper
commit ca362c294c08cd7ee81bf52722c819c4c31c6a04
Author: Paweł Wilk
Date: Sun Nov 24 17:40:38 2013 +0100
Ncurses::CharCode#enc_char improved
commit 380079ea0627c9f1440b5097563e6dece352cae4
Author: Paweł Wilk
Date: Sun Nov 24 17:40:01 2013 +0100
Added Ncurses::CharCode::Empty.empty override
commit 8da83d9952fd6020175ac4b89e404adc5a72d6fe
Author: Paweł Wilk
Date: Sun Nov 24 17:39:28 2013 +0100
Cleanups
commit de9f7bc1b2cd5d71d870c02d0fe2242786f00445
Author: Paweł Wilk
Date: Sun Nov 24 17:39:02 2013 +0100
Added Ncurses::CharCode.dumb! switch to control behavior when CharCode cannot detect keycodes
commit ffad7e5e9d6951ae0834be9c453b91e305dbbcdf
Author: Paweł Wilk
Date: Sun Nov 24 17:35:30 2013 +0100
Added Ncurses::CharCode.generate to save objects from spawning when ERR is returned by get(w)ch
commit ad6f6f2c0c8020ff3b5affe242c40972d6a3a4ba
Author: Paweł Wilk
Date: Sun Nov 24 03:02:01 2013 +0100
Removed printing of warning about a missing form_driver_w() function
commit ea277c19791330943a3696cb97d9a6ad99454d80
Author: Paweł Wilk
Date: Sun Nov 24 02:55:23 2013 +0100
Cleanups
commit 505532e288072be92ade36bec94555044dce9b2d
Author: Paweł Wilk
Date: Sun Nov 24 01:24:08 2013 +0100
TextField now uses form_driver helpers from Ncurses::Form::DriverHelpers
commit 91037291c28f00cee2da5ea505657aa814ddcffc
Author: Paweł Wilk
Date: Sun Nov 24 01:23:22 2013 +0100
Added Form::DriverHelpers module with methods that ease usage of form_driver
commit 05c2b5611d5e96d68e4685c4c27a5d267cde4a48
Author: Paweł Wilk
Date: Sun Nov 24 01:22:08 2013 +0100
Improved Ncurses::CharCode#replace method (detects if input is a charcode or a character)
commit a2f0dac449bc743236f3925b35aa6de43fc189d0
Author: Paweł Wilk
Date: Sun Nov 24 01:21:11 2013 +0100
Removed previous wrapper for form_driver_w()
commit 3a5cbdc8c1941745bcd1498644be02e2487e7905
Author: Paweł Wilk
Date: Sun Nov 24 01:20:12 2013 +0100
Cleanups
commit a9585535f6b647c3094f5f4485f8128f127c4357
Author: Paweł Wilk
Date: Sun Nov 24 01:19:51 2013 +0100
Replacement wrapper for form_driver_w() moved to Ncurses::prepare_form_driver and called after initscr
commit 7766cbfb20941dd6d67456008af1125b99d6a6be
Author: Paweł Wilk
Date: Sun Nov 24 01:17:37 2013 +0100
Multiple typos fixed
commit 8bf705c135d9458be759cd72ebef7eef98085a8d
Author: Paweł Wilk
Date: Sat Nov 23 20:58:54 2013 +0100
Key for continuing search in buffer in Buffer#handle_input is now compared directly
commit 1dcfabd56c423762734b4a7594d209ceeb07fe16
Author: Paweł Wilk
Date: Sat Nov 23 20:57:22 2013 +0100
Moved ncurses tweaks to lib/sup/util/ncurses.rb, added CharCode::Empty singleton class
commit ed85b526c52b21c58b029921b51c0183aa747876
Author: Paweł Wilk
Date: Sat Nov 23 17:37:50 2013 +0100
TextField class is now making use of Ncurses::Form.form_driver_w
commit 9ce61341efdba172fddf41b10e0277e646177208
Author: Paweł Wilk
Date: Sat Nov 23 17:37:10 2013 +0100
Cleanups in buffer.rb
commit f8f7116c542de63c2ce3f04e96d18779fe28e3b0
Author: Paweł Wilk
Date: Sat Nov 23 17:36:49 2013 +0100
Added compat proxy for the Ncurses::Form.form_driver_w module method
commit 171eada22b63b3e105eae4f80b157f0e2bdd0a0d
Author: Paweł Wilk
Date: Fri Nov 22 02:36:27 2013 +0100
Fixed TextField#handle_input (keysyms taken into account) and prepared for form_driver workaroud
commit 3217a655a09fe6adb35049fdaa16af7e1788d0ee
Author: Paweł Wilk
Date: Fri Nov 22 02:32:28 2013 +0100
Added Ncurses::curyx module method (helper for reading whole cursor position)
commit 60b02d6f6257601737086fddfcd33fba60d033a6
Author: Paweł Wilk
Date: Thu Nov 21 19:08:51 2013 +0100
Ncurses::CharCode::get now uses chr to create character based on code returned by get_wch
commit 58088c4f5fa27dce2234c43d3c1beba7539ce87b
Author: Paweł Wilk
Date: Thu Nov 21 17:55:45 2013 +0100
Typo fixed
commit ca8e98447c209680988b162e9f980ff69cd693db
Author: Paweł Wilk
Date: Thu Nov 21 17:21:49 2013 +0100
Ncurses::CharCode class refactored, code adapted to use its instances
Details:
- changed Ncurses::CharCode to work with wide characters,
- removed select wrapper when reading input,
- moved input reading into Ncurses::CharCode class,
- implemented passing Ncurses::CharCode objects as far as possible.
commit f0168bea3ada9cd5c3168c9fc324ed3aa458119e
Author: Paweł Wilk
Date: Tue Nov 19 23:20:13 2013 +0100
Adapted Keymap, Buffer and TextField classes to make use of CharCode objects
commit dfea936ae8661e71b80618f44a8bd9f3ca68634c
Author: Paweł Wilk
Date: Tue Nov 19 23:17:11 2013 +0100
Added Ncurses::CharCode helper class for storing and transferring wide characters
Diffstat:
8 files changed, 365 insertions(+), 107 deletions(-)
diff --git a/History.txt b/History.txt
@@ -1,3 +1,8 @@
+== 0.15.2 /
+
+* Use the form_driver_w routine for inputing multibyte chars when
+ available.
+
== 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.
+
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/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/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/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
@@ -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