JLine NonBlockingReader的合同似乎已经破裂

时间:2018-04-25 07:41:41

标签: java peek jline3

关注my previous question关于JLine。操作系统:W10,使用Cygwin。

def terminal = org.jline.terminal.TerminalBuilder.builder().jna( true ).system( true ).build()
terminal.enterRawMode()
// NB the Terminal I get is class org.jline.terminal.impl.PosixSysTerminal
def reader = terminal.reader()
// class org.jline.utils.NonBlocking$NonBlockingInputStreamReader

def bytes = [] // NB class ArrayList
int readInt = -1
while( readInt != 13 && readInt != 10 ) {
    readInt = reader.read()
    byte convertedByte = (byte)readInt
    // see what the binary looks like:
    String binaryString = String.format("%8s", Integer.toBinaryString( convertedByte & 0xFF)).replace(' ', '0')
    println "binary |$binaryString|"
    bytes << (byte)readInt // NB means "append to list"

    // these seem to block forever, whatever the param... 
    // int peek = reader.peek( 50 ) 
    int peek = reader.peek( 0 )

}
// strip final byte (13 or 10)
bytes = bytes[0..-2]
def response = new String( (byte[])bytes.toArray(), 'UTF-8' )

根据Javadoc(源自本地制作)peek看起来像这样:

  

public int peek(long timeout)

     

查看输入流中是否有字节等待没有   实际上消耗了字节。

     

参数:       超时 - 等待的时间,0 ==永远返回:       在eof上为-1,如果超时到期且没有可用输入或读取的字符(不使用它),则为-2。

它没有说明这里涉及的时间单位...我假设毫秒,但我也尝试过&#34; 1&#34;,以防它的秒数。

这个peek命令功能足够,因为它代表你能够检测到多字节的Unicode输入,并带有一点超时的独创性:一个假定多字节Unicode字符的字节将会到达的时间比一个人可以输入的更快......

但是,如果它永远不会解除阻塞,则意味着您必须将peek命令置于超时机制中,您必须自行滚动。下一个字符输入当然会解锁。如果这是Enter,则while循环将结束。但是,如果你想在输入下一个字符之前打印一个字符(或做任何事情),那么peek的超时似乎没有效果,这会妨碍你这样做。< / p>

3 个答案:

答案 0 :(得分:1)

尝试使用

 jshell> " ẃ".getBytes()
 $1 ==> byte[8] { -16, -112, -112, -73, 32, -31, -70, -125 }

 jshell> " ẃ".chars().toArray()
 $2 ==> int[4] { 55297, 56375, 32, 7811 }

 jshell> " ẃ".codePoints() .toArray()
 $3 ==> int[3] { 66615, 32, 7811 }

答案 1 :(得分:0)

JLine使用通常的java语义:streams获取字节,读取器/写入器使用chars。处理代码点的唯一部分(即单个值中可能的32位字符)是BindingReaderNonBlockingReader遵循Reader语义,只需添加一些超时的方法,可以返回-2表示超时。

如果您想进行解码,则需要使用Character.isHighSurrogate https://github.com/jline/jline3/blob/master/reader/src/main/java/org/jline/keymap/BindingReader.java#L124-L144

所采用的BindingReader方法
int s = 0;
int c = c = reader.read(100L);
if (c >= 0 && Character.isHighSurrogate((char) c)) {
    s = c;
    c = reader.read(100L);
}
return s != 0 ? Character.toCodePoint((char) s, (char) c) : c;

答案 2 :(得分:0)

我已经找到了一个特定于Cywin的解决方案...而且可能是(?)拦截,隔离和识别键盘控制的唯一方法&#34;字符输入。

使用JLine和Cygwin获取正确的Unicode输入
在我自己对一年前问过的问题的回答中引用了here,Cygwin(在我的设置中)无论如何都需要某种额外的缓冲和编码,用于控制台输入和输出,如果要正确处理Unicode的话

要应用此AND以同时应用JLine,我在执行terminal.enterRawMode()之后执行此操作:

BufferedReader br = new BufferedReader( new InputStreamReader( terminal.input(), 'UTF-8' ))

NB terminal.input()会返回org.jline.utils.NonBlockingInputStream个实例。

进入&#34;ẃ&#34; (然后在一个br.read()命令中使用(英国Extd键盘中的AltGr + W),并生成int值为7811,即正确的代码点值。 Hurray:正确使用了不在BMP(基本多语言平面)中的Unicode字符

处理键盘控制字符字节:
但我也希望拦截,隔离并正确识别与各种控制字符对应的字节。 TAB是一个字节(9),BACKSPACE是一个字节(127),因此很容易处理,但UP-ARROW以 3个单独读取字节的形式提供,即三个独立的br.read()命令已取消阻止,即使使用上述BufferedReader也是如此。一些控制序列包含7个这样的字节,例如Ctrl-Shift-F5是27(转义),然后是6个其他单独读取的字节,int值:91,49,53,59,54,126。我还没有找到这样的序列可能在哪里记录:如果有人知道请添加评论。

然后有必要隔离这些&#34;分组的字节&#34 ;:即你有一个字节流:你怎么知道这三个(或7 ...)必须联合解释

这是可能的,因为当为单个这样的控制字符传递多个字节时,它们在每个字节之间传递的时间少于一毫秒。也许并不令人惊讶。这个Groovy脚本似乎适用于我的目的:

import org.apache.commons.lang3.StringUtils
@Grab(group='org.jline', module='jline', version='3.7.0')
@Grab(group='org.apache.commons', module='commons-lang3', version='3.7')
def terminal = org.jline.terminal.TerminalBuilder.builder().jna( true ).system( true ).build()

terminal.enterRawMode()
// BufferedReader needed for correct Unicode input using Cygwin
BufferedReader br = new BufferedReader( new InputStreamReader(terminal.input(), 'UTF-8' ))
// PrintStream needed for correct Unicode output using Cygwin
outPS = new PrintStream(System.out, true, 'UTF-8' )
userResponse = ''
int readInt
boolean continueLoop = true

while( continueLoop ) {
    readInt = br.read()
    while( readInt == 27 ) {
        println "escape"
        long startNano = System.nanoTime()
        long nanoDiff = 0
        // figure of 500000 nanoseconds arrived at by experimentation: see below
        while( nanoDiff < 500000 ) {
            readInt = br.read()  
            long timeNow = System.nanoTime()
            nanoDiff = timeNow - startNano
            println "z readInt $readInt char ${(char)readInt} nanoDiff $nanoDiff"
            startNano = timeNow
        }
    }
    switch( readInt ) {
        case [10, 13]:
            println ''
            continueLoop = false
            break
        case 9:
            println '...TAB'
            continueLoop = false
            break
        case 127:
            // backspace
            if( ! userResponse.empty ) {
                print '\b \b'
                // chop off last character
                userResponse = StringUtils.chop( userResponse )
            }
            break
        default:
            char unicodeChar = (char)readInt
            outPS.print( unicodeChar )
            userResponse += unicodeChar
    }
}
outPS.print( "userResponse |$userResponse|")
br.close()
terminal.close()

以上代码使我能够成功地隔离&#34;单个多字节键盘控制字符:

println "...TAB"行中的3个点在用户按下TAB后立即打印在同一行上(输入行上没有打印上述代码)。这打开了做&#34; autocompletion&#34;等事情的大门。某些BASH命令中的行...

此设置是否足够快500000纳秒(0.5毫秒)?也许吧!

速度最快的打字员每分钟可输入220个单词。假设每个单词的平均字符数为8(看起来很高),则每秒29个字符,或每个字符大约34毫秒。从理论上讲,事情应该没问题。但是一个流氓&#34;同时按下两个键可能意味着它们彼此之间的按压时间不到0.5毫秒...但是,使用上面的代码,这只有在这两个都是转义序列时才有意义。它似乎工作正常。根据我的实验,它实际上可能远小于500000 ns,因为在多字节序列中每个字节之间可能需要70000 - 80000 ns(尽管通常需要更少)......以及各种中断或者有趣的事情当然会干扰这些字节的传递。实际上将其设置为1000000(1毫秒)似乎工作正常。

注意,如果我们想要拦截并处理转义序列,我们现在似乎遇到上述代码的问题:br.read() nanoDiff循环内while上的代码阻塞转义序列的结束。这是可以的,因为我们可以跟踪我们在while循环发生时(在它阻塞之前)接收的字节序列。