我已经找到了一个 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
循环发生时接收到的字节序列(在它阻塞之前)。