JLine контракт на NonBlockingReader кажется нарушенным
Следует из моего предыдущего вопроса о 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 == forever. Возвращает: -1 при eof, -2, если время ожидания истекло без доступного ввода или прочитанного символа (без его использования).
Здесь не говорится, какие единицы времени здесь задействованы... Я предполагаю миллисекунды, но я также пробовал с "1", на случай, если это секунды.
это peek
Команда достаточно функциональна, так как она позволяет вам обнаруживать многобайтовый ввод Unicode с некоторой изобретательностью тайм-аута: предполагается, что байты многобайтового символа Unicode будут поступать быстрее, чем может набрать человек.,
Однако, если он никогда не разблокируется, это означает, что вы должны поставить peek
Команда внутри механизма тайм-аута, который вы должны запустить самостоятельно. Следующий ввод символов, конечно, разблокирует вещи. Если это Enter
while
цикл закончится. Но если, скажем, вы хотели напечатать символ (или сделать что-нибудь) до того, как следующий символ введет тот факт, что peek
Тайм-аут не работает, мешает вам сделать это.
3 ответа
Попробуйте поиграть с
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 }
Я нашел Cywin-специфичное решение для этого... и также может быть (?) Единственным способом перехватить, изолировать и идентифицировать ввод символов с клавиатуры.
Получение правильного ввода Unicode с использованием JLine и Cygwin
Как указано здесь в моем собственном ответе на вопрос, который я задал год назад, Cygwin (в любом случае, в моей настройке) требуется какая-то дополнительная буферизация и кодирование, как для ввода, так и для вывода на консоль, если это необходимо для правильной обработки Unicode.
Чтобы применить это и одновременно применить JLine, я делаю это после того, как terminal.enterRawMode()
:
BufferedReader br = new BufferedReader( new InputStreamReader( terminal.input(), 'UTF-8' ))
NB terminal.input()
возвращает org.jline.utils.NonBlockingInputStream
пример.
ввод "ẃ" (AltGr + W на клавиатуре UK Extd) затем используется в одном br.read()
команда и int
полученное значение 7811, правильное значение кодовой точки. Ура: символ Юникода, не входящий в BMP (базовая многоязычная плоскость), был правильно использован.
Обработка управляющих символов клавиатуры:
Но я также хочу перехватывать, изолировать и правильно идентифицировать байты, соответствующие различным управляющим символам. TAB является однобайтовым (9), BACKSPACE является однобайтовым (127), с ним легко иметь дело, но стрелка вверх поставляется в виде 3 отдельно читаемых байтов, то есть трех отдельных br.read()
команды разблокированы, даже используя выше BufferedReader
, Некоторые управляющие последовательности содержат 7 таких байтов, например, Ctrl-Shift-F5 равен 27 (escape), за которыми следуют 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()
Приведенный выше код позволяет мне успешно "изолировать" отдельные многобайтовые символы управления клавиатурой:
3 точки в println "...TAB"
строки печатаются в одной строке сразу после нажатия пользователем клавиши TAB (которая с указанным кодом не печатается в строке ввода). Это открывает двери для таких вещей, как "автозаполнение" строк, как в некоторых командах BASH...
Достаточно ли быстр этот параметр 500000 наносекунд (0,5 мс)? Может быть!
Самые быстрые машинистки могут печатать со скоростью 220 слов в минуту. Предполагая, что среднее количество символов в слове составляет 8 (что кажется высоким), получается 29 символов в секунду или примерно 34 мс на символ. В теории все должно быть в порядке. Но "мошенническое" нажатие двух клавиш одновременно может означать, что они нажаты менее чем за 0,5 мс друг от друга... однако для приведенного выше кода это имеет значение, только если обе из них являются escape-последовательностями. Кажется, работает нормально. По моим экспериментам оно не может быть намного меньше 500000 нс, потому что это может занять до 70000 - 80000 нс между каждым байтом в многобайтовой последовательности (хотя обычно это занимает меньше времени)... и всевозможные прерывания или смешно происходящие события могут, конечно, мешать доставке этих байтов. На самом деле установка 1000000 (1 мс), кажется, работает нормально.
Обратите внимание, что теперь у нас, похоже, есть проблема с приведенным выше кодом, если мы хотим перехватить и обработать escape-последовательности: вышеуказанные блоки кода на br.read()
внутри nanoDiff
while
цикл в конце escape-последовательности. Это нормально, потому что мы можем отслеживать последовательность байтов, которую мы получаем как while
цикл происходит (прежде чем он блокируется).
JLine использует обычную семантику Java: потоки получают байты, читатель / писатель использует символы. Единственная часть, которая имеет дело с кодовыми точками (т.е. возможными 32-битными символами в одном значении), является BindingReader
, NonBlockingReader
следует за Reader
семантическая, просто добавление некоторых методов с тайм-аутом, которые могут возвращать -2, чтобы указать тайм-аут.
Если вы хотите сделать декодирование, вам нужно использовать Character.isHighSurrogate
метод, как это сделано BindingReader
https://github.com/jline/jline3/blob/master/reader/src/main/java/org/jline/keymap/BindingReader.java#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;