Use JLine to Complete Multiple Commands on One Line
Asked Answered
P

1

6

I was wondering how I could implement an ArgumentCompleter such that if I complete a full and valid command, then it would begin tab completing for a new command.

I would have assumed it could be constructed doing something like this:

final ConsoleReader consoleReader = new ConsoleReader()

final ArgumentCompleter cyclicalArgument = new ArgumentCompleter();
cyclicalArgument.getCompleters().addAll(Arrays.asList(
        new StringsCompleter("foo"), 
        new StringsCompleter("bar"), 
        cyclicalArgument));

consoleReader.addCompleter(cyclicalArgument);
consoleReader.readLine();

However right now this stops working after tab completeing the first foo bar

Is anyone familiar enough with the library to tell me how I would go about implementing this? Or is there a known way to do this that I am missing? Also this is using JLine2.

Plummer answered 25/1, 2016 at 15:58 Comment(0)
R
5

That was quite a task :-)

It is handled by the completer you are using. The complete() method of the completer has to use for the search only what comes after the last blank.

If you look for example at the FileNameCompleter of the library: this is not done at all, so you will find no completion, because the completer searches for <input1> <input2> and not only for <input2> :-)

You will have to do your own implementation of a completer that is able to find input2.

Additionally the CompletionHandler has to append what you found to what you already typed.

Here is a basic implementation changing the default FileNameCompleter:

  protected int matchFiles(final String buffer, final String translated, final File[] files,
         final List<CharSequence> candidates) {
      // THIS IS NEW
      String[] allWords = translated.split(" ");
      String lastWord = allWords[allWords.length - 1];
      // the lastWord is used when searching the files now
      // ---

      if (files == null) {
         return -1;
      }

      int matches = 0;

      // first pass: just count the matches
      for (File file : files) {
         if (file.getAbsolutePath().startsWith(lastWord)) {
            matches++;
         }
      }
      for (File file : files) {
         if (file.getAbsolutePath().startsWith(lastWord)) {
            CharSequence name = file.getName() + (matches == 1 && file.isDirectory() ? this.separator() : " ");
            candidates.add(this.render(file, name).toString());
         }
      }

      final int index = buffer.lastIndexOf(this.separator());

      return index + this.separator().length();
   }

And here the complete()-Method of the CompletionHandler changing the default CandidateListCompletionHandler:

  @Override
   public boolean complete(final ConsoleReader reader, final List<CharSequence> candidates, final int pos)
         throws IOException {
      CursorBuffer buf = reader.getCursorBuffer();

      // THIS IS NEW
      String[] allWords = buf.toString().split(" ");
      String firstWords = "";
      if (allWords.length > 1) {
         for (int i = 0; i < allWords.length - 1; i++) {
            firstWords += allWords[i] + " ";
         }
      }
      //-----

      // if there is only one completion, then fill in the buffer
      if (candidates.size() == 1) {
         String value = Ansi.stripAnsi(candidates.get(0).toString());

         if (buf.cursor == buf.buffer.length() && this.printSpaceAfterFullCompletion && !value.endsWith(" ")) {
            value += " ";
         }

         // fail if the only candidate is the same as the current buffer
         if (value.equals(buf.toString())) {
            return false;
         }

         CandidateListCompletionHandler.setBuffer(reader, firstWords + " " + value, pos);

         return true;
      } else if (candidates.size() > 1) {
         String value = this.getUnambiguousCompletions(candidates);
         CandidateListCompletionHandler.setBuffer(reader, value, pos);
      }

      CandidateListCompletionHandler.printCandidates(reader, candidates);

      // redraw the current console buffer
      reader.drawLine();

      return true;
   }
Rochellrochella answered 10/2, 2016 at 18:55 Comment(2)
Hmm,. so this is gonna take some larger changes than I thought. Thanks for the insight!Plummer
At least it seems you cannot activate it by some config. But you can make a standard implementation and inherit it for your next classes.Rochellrochella

© 2022 - 2024 — McMap. All rights reserved.