JLine контракт на NonBlockingReader кажется нарушенным - PullRequest
0 голосов
/ 25 апреля 2018

следует из моего предыдущего вопроса о 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)

Просматривает, есть ли байт, ожидающий во входном потоке без фактического расхода байта.

Параметры: timeout - Время ожидания, 0 == forever Возвращает: -1на eof, -2, если время ожидания истекло без доступного ввода или прочитанного символа (без его использования).

В нем не указано, какие единицы времени используются здесь ... Я предполагаюмиллисекунды, но я также пытался использовать «1», на всякий случай, если это секунды.

Эта команда peek достаточно функциональна, так как она позволяет вам определять многобайтовый ввод Unicode с небольшимизобретательности тайм-аута: предполагается, что байты многобайтового символа Юникода будут приходить быстрее, чем может набрать человек ...

Однако, если он никогда не разблокируется, это означает, что вы должны поставить peek команда внутри времениУф механизм, который ты должен сам катить.Следующий ввод символов, конечно, разблокирует вещи.Если это Enter, цикл while завершится.Но если, скажем, вы хотели напечатать символ (или сделать что-нибудь) до того, как будет введен следующий символ, тот факт, что тайм-аут peek не работает, мешает вам сделать это.

Ответы [ 3 ]

0 голосов
/ 25 апреля 2018

Я нашел Cywin-специфическое решение для этого ... и также может быть (?) Единственным способом перехватить, изолировать и идентифицировать ввод символов с клавиатуры.

Получение правильного ввода Unicode с использованием JLine и Cygwin
Как указано здесь в моем собственном ответе на вопрос, который я задал год назад, Cygwin (в любом случае в моей настройке) требуется какая-то дополнительная буферизация и кодирование, как для ввода, так и для вывода на консоль, если это необходимо для обработки Юникод правильно.

Чтобы применить это И для одновременного применения 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, правильное значение кодовой точки. Ура: символ Unicode отсутствует в BMP (базовая многоязычная плоскость) был правильно использован.

Обработка байтов управляющих символов клавиатуры:
Но я также хочу перехватывать, изолировать и правильно идентифицировать байты, соответствующие различным управляющим символам. TAB является однобайтовым (9), BACKSPACE является однобайтовым (127), с ним легко иметь дело, но стрелка вверх поставляется в виде 3 отдельно читаемых байтов , то есть трех отдельных * Команды 1029 * разблокированы, даже с использованием вышеуказанного 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 (до его блокировки).

0 голосов
/ 26 апреля 2018

Попробуйте поиграть с

 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 }
0 голосов
/ 25 апреля 2018

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;
...