Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LineReader.readline(String) interferes with BindingReader.readBinding(Map, Map, boolean) #732

Open
PMarci opened this issue Oct 21, 2021 · 2 comments

Comments

@PMarci
Copy link

PMarci commented Oct 21, 2021

Expected behavior

I'm not intricately familiar with the codebase or Java stdin handling, but I wouldn't expect one method call to break the other even though they use the same NonBlockingReader instance. I get this impression because the calls shouldn't be even remotely overlapping.

Actual behavior

The first call to LineReader's readLine(String) method using a Terminal instance's NonBlockingReader breaks arrow key navigation for a BindingReader's readBinding(Map, Map, boolean) that uses the same NonBlockingReader, until the application is restarted.

Description of issue

I'm developing an app which I built somewhat based on the builtin Nano.
In the main loop, there's a call to BindingReader.readBinding(Map, Map, boolean) that looks like this:

// ...
        Op input;
        while (isRunAllowed()) {
            input = bindingReader.readBinding(keyMap, keyMap, true);
            if (input != null) {
                switch (input) {
// ...

This main loop is within a Runnable class' run() function, which gets called like this:

    public void doStart() {
        ThreadFactory factory = r -> {
            String threadName = "\"The\" thread";
            Thread result = new Thread(r, threadName);
            // the JVM instaquits if only daemon (user) threads are running, so set this to false
            result.setDaemon(false);
            return result;
        };

        this.executor = Executors.newSingleThreadExecutor(factory);
        executor.execute(this);
    }

I'm using it with a Terminal-s NonBlockingReader:

//... input class constructor
        this.bindingReader = new BindingReader(terminal.reader());
//...

when a certain Op is matched by the BindingReader, I handle it by prompting the user for input:

// ...  in handler method called from switch case for the user prompt
            String nInput;
            nInput = selectionReader.readLine(prompt);
// ...

As soon as selectionReader.readLine(prompt) is called, arrow keys presses aren't recognized by the BindingReader anymore, and instead get recognized as collections of the characters ],[,O,A,B,C,D, like what one sees when pressing arrow keys in a terminal program that doesn't consume the input.
Before this happens, the readBinding call in my main loop correctly distinguishes and receives ENTER_CHAR and arrow key presses. Afterwards, a single arrow key gets read as one or multiple ENTER_CHAR bindings.
The arrow key bindings are bound like this, analogous to jline3 examples:

        bind(keyMap, Op.UP, key(terminal, InfoCmp.Capability.key_up));
        bind(keyMap, Op.DOWN, key(terminal, InfoCmp.Capability.key_down));
        bind(keyMap, Op.LEFT, key(terminal, InfoCmp.Capability.key_left));
        bind(keyMap, Op.RIGHT, key(terminal, InfoCmp.Capability.key_right));

It would seem that the readLine call is capturing some subsequences of the bytes that would be otherwise received by readBinding.
When I looked a bit deeper, for example in KeyMap.getBound(Charsequence, int[]) I found that even at this level, the received bytes were incorrect for the mapping. The codes almost always started with the byte 27, which is the correct place for the arrow key mappings, but subsequent bytes wouldn't point to the four mappings below that.
Interestingly enough, this isn't breaking in all terminals:

OS Shell/Terminal Works without issue
Windows 10 cmd.exe in Windows Terminal
Windows 10 powershell.exe in Windows Terminal
Windows 10/WSL2/Ubuntu 18.04 zsh in Windows Terminal
OSX zsh in Terminal
OSX zsh in iTerm

I tried some dumb things to begin with, but couldn't resolve the issue. Because the two read operations rely on a shared NonBlockingReader I expect that there is a workaround and I'm just using the resources incorrectly.

Thanks for the help in advance!

@PMarci PMarci changed the title LineReader.readline(String) seems to interfere with BindingReader.readBinding(Map, Map, boolean) LineReader.readline(String) interferes with BindingReader.readBinding(Map, Map, boolean) Oct 21, 2021
@gnodet
Copy link
Member

gnodet commented Oct 21, 2021

Thx for the detailed explanation. That does not ring any bell though. Would it be possible to create a small reproductible project so that I can investigate ?

@PMarci
Copy link
Author

PMarci commented Oct 21, 2021

It will take some time, but I'm invested in getting this resolved. I could try and rip out the relevant parts in the coming days.
If it's not an issue to you, you could take the entire project to test in the meantime. Let me know if you gave it a shot.

Installation

git clone git@github.com:PMarci/ejigma.git
cd ./ejigma
mvn clean package
# optional for running jar in a command-like way instead of a parameter to java
./build
# optional
install ./ejigma /usr/local/bin

Reproduction

  1. run the jar any way you prefer, but without arguments (no params to the command-like or -Dexec.args to maven)
  2. This should launch the app in interactive mode. Press any key if the title screen doesn't go away.
  3. the screen should clear
  4. press some keys, encrypted characters should appear
  5. verify that moving the cursor by arrow keys works
  6. press Ctrl + I or Ctrl + E or Ctrl + F to bring up the prompt by LineReader.readLine(String)
  7. Press Ctrl + C to cancel the prompt. The next arrow key press should produce no input or movement. This is where the truncated byte sequences start.
  8. pressing the arrow keys at this point produces letters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants