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() внутри nanoDiffwhile цикл в конце 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;
Другие вопросы по тегам