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

autocomplete using up and down arrow key #17

Closed
mokun opened this issue Oct 2, 2018 · 16 comments
Closed

autocomplete using up and down arrow key #17

mokun opened this issue Oct 2, 2018 · 16 comments

Comments

@mokun
Copy link

mokun commented Oct 2, 2018

how do I even begin to implement some sort of autocomplete at the input cursor by using up and down arrow keys ?

I suppose this will be a nice feature for text-io that others are interested in.

Say I have List<String> names = Arrays.asList("albert", "alice", "ava", "betty", "cathy");

So when asked about inputting a name, at the input prompt, if I type in the first letter as "a", (but before typing in the second letter), and I hit the up arrow key, I would like the word "albert" to pop up at the input prompt.

I suppose I'll need to write my own methods to implement the nitty gritty.

Can someone show me first on how to capture the input before hitting enter ?

How do I make use of the up and down arrow keys. Is it by registering handlers and how ?

Thanks!

@siordache
Copy link
Member

A generic solution that works on all terminals is not possible with the current API.
I suppose that you use exclusively the SwingTextTerminal, so I added a few methods to it in release 3.3.0.

This example application shows how to register handlers for the up and down arrow keys and how to manipulate the input.
To keep the things simple, the application doesn't implement autocompletion. It just lets you scroll through a list of choices, but I think this is enough to get you started.

@mokun
Copy link
Author

mokun commented Oct 3, 2018

@siordache, thanks much for the update and the new demo.

I just tried it out and works well.

However, in my implementation, I use the ContactInfo.java demo as an example and implement the use of Ctrl-U to go back to the previous question.

What I found out is that I canNOT register both the Ctrl-U handler and the UP and DOWN key handler at the same time.

addString(textIO, "First name", () -> contact.firstName, s -> contact.firstName = s);
addString(textIO, "Last name", () -> contact.lastName, s -> contact.lastName = s);

It will throw the followings :

org.beryx.textio.ReadAbortedException
	at org.beryx.textio.InputReader.readWithPrompt(InputReader.java:452)
	at org.beryx.textio.InputReader.lambda$read$3(InputReader.java:382)
	at org.beryx.textio.TextTerminal.applyWithPropertiesConfigurator(TextTerminal.java:243)
	at org.beryx.textio.InputReader.executeWithTerminal(InputReader.java:461)
	at org.beryx.textio.InputReader.read(InputReader.java:380)
	at org.beryx.textio.InputReader.read(InputReader.java:368)
	at org.mars_sim.msp.core.terminal.CommanderProfile.addString(CommanderProfile.java:206)

The incompatibility is caused by the fact that I need to hide the s string within valueSetter.accept() if I want to make Ctrl-U handler works.

But I need to take s outside of valueSetter.accept() if I want to make UP/DOWN handler works.

And logically I cannot get both working at the same time !

See my addString() as follows :

private void addString(TextIO textIO, String prompt, Supplier<String> defaultValueSupplier, Consumer<String> valueSetter) {
    	setChoices("alice", "ava", "joyce");
    		
    	String s = textIO.newStringInputReader()
                .withDefaultValue(defaultValueSupplier.get())
                .read(prompt);
    	
        operations.add(() -> valueSetter.accept(s));
        
//    	   setChoices("alice", "ava", "joyce");	
//        operations.add(() -> valueSetter.accept(textIO.newStringInputReader()
//                .withDefaultValue(defaultValueSupplier.get())
//                .read(prompt)));
}

So when I hit Ctrl-U, it will end in that exception at the line

 	String s = textIO.newStringInputReader()
                .withDefaultValue(defaultValueSupplier.get())
                .read(prompt);

What can I do ?

@siordache
Copy link
Member

siordache commented Oct 4, 2018

@mokun I changed the example application to resemble the ContactInfo demo.
Now you can both scroll through choices and go back to the previous field.
The trick was to include the call to setChoices into the Runnable added to operations:

operations.add(() -> {
    setChoices(choices);
    valueSetter.accept(textIO.newStringInputReader()
	.withDefaultValue(defaultValueSupplier.get())
	.read(prompt));
});

@mokun
Copy link
Author

mokun commented Oct 4, 2018

oic !

@mokun
Copy link
Author

mokun commented Oct 4, 2018

it seems that I can't replace the string array with int array even after I refactor the setChoices() method to accept an int array and process integers.

It complains that the API's replace method accepts string only and is unable to take in integer yet.

@mokun
Copy link
Author

mokun commented Oct 4, 2018

Also, how would you make it detect the partial input even before hitting the enter key ?

e.g. if I type in just "ha" and start to use the UP and DOWN key to scroll through the names that have the first 2 letters matching "ha", then "harold", "harry", "hamon", etc will show up.

I suppose I'll use stream to filter off the original list.

Is there a way to implement a listener to sense the letters that have been input at the prompt in real-time (without using the enter key) ?

@siordache
Copy link
Member

@mokun The SwingTextTerminal provides (since 3.3.0) the getPartialInput() method.

I made some changes to the example application. Now it supports int and double arrays and implements a simple form of autocomplete.
I splitted the original file in two in order to separate the handler class from the code that uses it.
Note that primitive and boxed types behave a bit different: for primitive types, a default value is always displayed.

@mokun
Copy link
Author

mokun commented Oct 5, 2018

Wow that's very cool and now I can implement autocomplete with text-io !

but I do have an issue with in Eclipse.

Please see screen shot below in SwingHandler :

swing handler issue

In all those class such as StringTask, IntTask, etc.

It said,

Cannot refer to 'this' nor 'super' while explicitly invoking a constructor

What do you make of it ?

@mokun
Copy link
Author

mokun commented Oct 5, 2018

btw, I'm using Java 10 on windows OS and set to compile to Java 9 SE.

@siordache
Copy link
Member

This seems to be a known issue with Eclipse. I refactored the code to get rid of this error.

I also added two methods:

  • constrainInputToChoices- only values in the choice list will be accepted.
  • withInputReaderConfigurator - allows you to tweak the InputReader

See Demo.java for usage examples.

@mokun
Copy link
Author

mokun commented Oct 8, 2018

I love text-io !

Now that I can create an autocomplete list.

One minor issue I had is that at the very end of when the line Press enter to terminate... ​ shows up, if I ever press the up/down arrow key, the previous choices will pop up unexpectedly.

I was going to use setChoices() to null the previous choices but now I can't and I wonder why.

In the new version of SwingHandler, I can see the following :

    private void setChoices(List<String> choices) {
        this.originalInput = "";
        this.choiceIndex = -1;
        this.choices = choices;
    }

Now I have to add back the original setChoices() in which the parameter list is String... choices

    public void setChoices(String... choices) {
        this.originalInput = "";
        this.choiceIndex = -1;
        this.choices = Arrays.asList(choices);
    }

Also, I have to use Arrays.asList(choices) at the end to convert the string array back to the list.

This is just my workaround.

Can you tweak it so that we don't have to call setChoices() at the end of each prompt to avoid this artifact ? Thx !

@siordache
Copy link
Member

@mokun I fixed it by resetting the choice list before exiting Task.run().

@mokun
Copy link
Author

mokun commented Oct 12, 2018

Thx a lot !

In my implementation, my typical prompt is just ">". But I also want a customized prompt. I want the prompt to be changed to "Connected with ABC >" after I type in "ABC" in the previous attempt .

However, I run into the issue of not being able to have this customized prompt showing up properly.

So what I did was that I add a boolean parameter called showPrevious in Task inside SwingHandler so that I can insert extra words as well as the previous choice in the input prompt.

I'll just show my code below as a reference :

      public Task(String prompt, boolean showPrevious, Supplier<R> inputReaderSupplier, Supplier<T> defaultValueSupplier, Consumer<T> valueSetter) {
            this.prompt = prompt;
            this.showPrevious = showPrevious;
            this.inputReaderSupplier = inputReaderSupplier;
            this.defaultValueSupplier = defaultValueSupplier;
            this.valueSetter = valueSetter;
        }

        @Override
        public void run() {
            setChoices(choices.stream().map(Object::toString).collect(Collectors.toList()));
            try {
                R inputReader = inputReaderSupplier.get();
                if (showPrevious)
                	inputReader.withDefaultValue(defaultValueSupplier.get());
                if(inputReaderConfigurator != null) {
                    inputReaderConfigurator.accept(inputReader);
                }
                if(constrainedInput) {
                    inputReader.withValueChecker((val,name) -> choices.contains(val) ? null
                            : Arrays.asList("'" + val + "' is not in the choice list."));

                }
                valueSetter.accept(inputReader.read(prompt));
            } finally {
                setChoices(Collections.emptyList());
            }
        }

Of course, this will change the StringTask, IntTask, etc.

@mokun
Copy link
Author

mokun commented Oct 12, 2018

Also, if I deal with multiple questions, your Demo shows the use of calling handler.execute() at the end.

In my implementation, I don't know how many of the same or different question will be asked in run-time.

So in my case, I don't want to add them to the bookmark and I come up with a more flexible method called executeOneTask() as follows for your reference :

    public void executeOneTask() {
    	// Remove the last task
    	if (tasks.size() > 1)
    		tasks.remove(0);
        terminal.setBookmark("bookmark_" + 0);
        try {
            tasks.get(0).run();
        } catch (ReadAbortedException e) {
            terminal.resetToBookmark("bookmark_" + 0);
        }
    }

I'm sure you can figure out a better way to implement it but this is what I have now.

Again appreciate much for your text-io library !

@siordache
Copy link
Member

It looks good to me.

@mokun
Copy link
Author

mokun commented Nov 9, 2018

Thanks! I'm closing this thread now.

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