1

我之前关于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(长时间超时)

查看输入流中是否有一个字节在等待而不实际消耗该字节。

参数: timeout - 等待的时间量,0 == 永远 返回:-1 在 eof 上,-2 如果超时过期而没有可用的输入或读取的字符(不消耗它)。

它没有说明这里涉及什么时间单位......我假设毫秒,但我也尝试使用“1”,以防万一它是秒。

peek命令功能足够强大,因为它代表您能够检测多字节 Unicode 输入,并具有一点超时独创性:假设多字节 Unicode 字符的字节到达速度将比人输入的速度快。 ..

但是,如果它永远不会解除阻塞,这意味着您必须将peek命令放入超时机制中,您必须自己滚动。下一个字符输入当然会解除阻塞。如果这是一个Enter循环while将结束。但是,如果你想在输入下一个字符之前打印一个字符(或做任何事情),那么peek超时似乎不起作用的事实会阻止你这样做。

4

3 回答 3

1

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

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

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;
于 2018-04-25T08:06:06.323 回答
1

我已经找到了一个 Cywin 特定的解决方案......而且为什么可能是(?)拦截、隔离和识别“键盘控制”字符输入的唯一方法。

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

要同时应用这个 AND 来应用 JLine,我会这样做,之后terminal.enterRawMode()

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

NBterminal.input()返回一个org.jline.utils.NonBlockingInputStream实例。

输入“ẃ”(英国扩展键盘中的 AltGr + W)然后在一个br.read()命令中使用,int生成的值为 7811,即正确的代码点值。Hurrah:已正确使用了不在 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。我还没有找到这样的序列在哪里可能会记录在案:如果有人知道,请添加评论。

然后有必要隔离这些“分组字节”:即你有一个字节流:你怎么知道这3个(或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()

上面的代码使我能够成功地“隔离”各个多字节键盘控制字符:

println "...TAB"行中的 3 个点在用户按下 TAB 后立即打印在同一行上(上面的代码不会打印在输入行上)。这为在某些 BASH 命令中执行诸如“自动完成”行之类的操作打开了大门……

这个 500000 纳秒(0.5 毫秒)的设置是否足够快?也许!

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

注意,如果我们想拦截和处理转义序列,我们现在似乎对上面的代码有问题:上面的代码块在转义序列末尾的循环br.read()内部。nanoDiff while这没关系,因为我们可以跟踪我们在while循环发生时接收到的字节序列(在它阻塞之前)。

于 2018-04-25T17:01:08.177 回答
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 }
于 2018-04-26T19:20:58.550 回答