From 57dd6bd1ff695e8a10190951b50b1b5ea43dfaf6 Mon Sep 17 00:00:00 2001 From: elliottcable Date: Sat, 14 Mar 2009 21:41:17 -0400 Subject: [PATCH] =?utf8?q?Added=20a=20really=20fragile=20UTF-8=20parser.?= =?utf8?q?=20Really=20needs=20a=20lot=20of=20work:=20*=20Should=20be=20eas?= =?utf8?q?ily=20spec-able,=20so=20why=20didn't=20I=20BDD=20this=3F=20*=20I?= =?utf8?q?=20have=20no=20idea=20what=20will=20happen=20with=20encodings=20?= =?utf8?q?other=20than=20ASCII=20or=20UTF-8,=20need=20to=20try=20switching?= =?utf8?q?=20my=20terminal=20around=20and=20may=20have=20to=20write=20othe?= =?utf8?q?r=20parsers=20for=20other=20encodings=20*=20Although=20the=20par?= =?utf8?q?ser=20will=20handle=20higher-order=20UTF=20properly,=20I=20can't?= =?utf8?q?=20currently=20figure=20out=20how=20to=20get=20Ncurses=20to=20pr?= =?utf8?q?int=20it=20correctly.=20So=20simple=20echo-ing=20examples=20fail?= =?utf8?q?.=20Try=20writing=20input=20to=20a=20file,=20you'll=20see=20-=20?= =?utf8?q?a=20string=20such=20as=20"=C3=A6=E1=8F=9C=F0=90=90=A6"=20should?= =?utf8?q?=20be=20handled=20correctly,=20including=203-byte,=204-byte,=20a?= =?utf8?q?nd=207-byte=20Unicode=20characters.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- lib/nfoiled.rb | 2 +- lib/nfoiled/window.rb | 106 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/lib/nfoiled.rb b/lib/nfoiled.rb index 97955f5..42e7b2b 100644 --- a/lib/nfoiled.rb +++ b/lib/nfoiled.rb @@ -74,7 +74,7 @@ module Nfoiled # (if such a block has been defined). See `getch(3X)`. def read! while true - key = Terminal.current.acceptor.gets + key = Terminal.current.acceptor.getk # TODO: This should be handled more naturally by a Key handler or something exit if [Key.new(:etx), Key.new(:eot)].include? key # ^C, ^D Terminal.current.acceptor.on_key[key] if key diff --git a/lib/nfoiled/window.rb b/lib/nfoiled/window.rb index 2ced2fb..0e3eaad 100644 --- a/lib/nfoiled/window.rb +++ b/lib/nfoiled/window.rb @@ -31,6 +31,17 @@ module Nfoiled # The `Terminal` that this `Window` pertains to attr_reader :owner + # The status of the global 'input lock' on this window. While locked, all + # `#getk` calls will return nil. + # + # If you plan to use `#getb`, always check for a lock first - otherwise + # you could grab a Unicode byte right from the waiting hands of a Unicode + # character `Thread`! + attr_accessor :input_locked + alias_method :input_locked?, :input_locked + def lock_input!; self.input_locked = true; end + def unlock_input!; self.input_locked = false; end + # The proc to be run when a character is received attr_accessor :on_key @@ -69,14 +80,99 @@ module Nfoiled # ========= ## - # Gets a single character from the input buffer for this window. Returns - # nil if there are no new characters in the buffer. See `wgetch(3X)`. - def gets - chr = Key.process ::Ncurses.wgetch(wrapee) - chr == -1 ? nil : chr + # Gets a single byte from the input buffer for this window. Returns nil if + # there are no new characters in the buffer. See `wgetch(3X)`. + def getb + byte = ::Ncurses.wgetch(wrapee) + byte == -1 ? nil : byte end ## + # Gets a single `Key` from the input buffer for this window. + # + # This will asynchronously yield the `Key` to a block you provide, if such + # a block is given - in this mode, this method will return `true` if a + # `getk` is currently possible (input may become temporarily locked under + # certain circumstances), and `nil` if there is no `Key` to get. + # + # For the most part, you can expect blocks to be yielded extremely + # quickly; however, don't count on this (a sudden, large paste of long- + # byte UTF-8 characters could cause each subsequent `getk` to take longer + # to yield). + # + # This method can also be employed in a synchronous manner if no block is + # given; in this mode it acts as no more than a wrapper for `Window#getb` + # that automatically processes the ASCII char into a `Key` object. Higher- + # byte sequences such as Unicode UTF-8 will be treated as errors in this + # mode, returning `false`. Otherwise the `Key`-wrapped ASCII is returned. + def getk + return nil if self.input_locked? + byte = getb + return byte unless byte + if block_given? + case byte + when 0..127 + yield Key.ascii byte + when 128..191, 192..193, 254..255 + yield Key.new "�" + when 194..223, 224..239, 240..244, 245..247, 248..251, 252..253 + handle_unicode byte, &Proc.new + end + return true + else # We'll synchronously return the ASCII value wrapped in a `Key`. + case byte + when 0..127 + return Key.ascii byte + else + return Key.new "�" + end + end + end + + private + ## + # Handles a UTF-8 sequence from `getk`. + def handle_unicode byte, &handler + case byte + when 194..223; then bytes = 2 # 2 byte sequence + when 224..239; then bytes = 3 # 3 byte sequence + when 240..244; then bytes = 4 # 4 byte sequence below 10FFFF + when 245..247; then bytes = 4 # 4 byte sequence above 10FFFF + when 248..251; then bytes = 5 # 5 byte sequence + when 252..253; then bytes = 6 # 6 byte sequence + end + + Thread.start(self, byte, bytes, handler) do |window, first_byte, bytes, handler| + uba = [first_byte] + + Thread.pass while window.input_locked? + window.lock_input! + + until uba.length >= bytes + byte = getb + if byte + case byte + when 0..127, 192..193, 254..255, 194..223, 224..239, 240..244, 245..247, 248..251, 252..253 + Thread.start(handler) {|handler| handler[Key.new "�"] } + window.unlock_input! + Thread.kill + when 128..191 + uba << byte + end + end + + Thread.pass + end + + Thread.start(handler) {|handler| handler[Key.new uba.pack('C*')] } + window.unlock_input! + end + end + + + public + + ## # This sets this `Window` as the current `Terminal.acceptor`. def focus! owner.acceptor = self -- 2.11.4.GIT