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

about command completion #654

Closed
jxnu-liguobin opened this issue Feb 22, 2021 · 8 comments
Closed

about command completion #654

jxnu-liguobin opened this issue Feb 22, 2021 · 8 comments

Comments

@jxnu-liguobin
Copy link

Here is the scala programming language source code, repl module code.
Construct the part of Jline3

    val jlineTerminal = TerminalBuilder.builder().jna(true).build()
    val completer = new Completion(completion)
    val parser    = new ReplParser(repl)
    val history   = new DefaultHistory
    if (jlineTerminal.getWidth() == 0 || jlineTerminal.getHeight() == 0) {//for test
      jlineTerminal.setSize(new Size(120, 40));
    }
    val builder =
      LineReaderBuilder.builder()
      .appName("scala")
      .completer(completer)
      .history(history)
      .parser(parser)
      .terminal(jlineTerminal)

    locally {
      import LineReader._, Option._
      builder
        .option(AUTO_GROUP, false)
        .option(LIST_PACKED, true)  // TODO
        .option(INSERT_TAB, true)   // At the beginning of the line, insert tab instead of completing
        .variable(HISTORY_FILE, config.historyFile) // Save history to file
        .variable(SECONDARY_PROMPT_PATTERN, config.encolor(config.continueText)) // Continue prompt
        .variable(WORDCHARS, LineReaderImpl.DEFAULT_WORDCHARS.filterNot("*?.[]~=/&;!#%^(){}<>".toSet))
        .option(Option.DISABLE_EVENT_EXPANSION, true) // Otherwise `scala> println(raw"\n".toList)` gives `List(n)` !!
    }

This is the parser implementation

  class ReplParser(repl: Repl) extends Parser {
    val scalaParser = new ScalaParser(repl)
    val commandParser = new CommandParser(repl)
    def parse(line: String, cursor: Int, context: ParseContext): ParsedLine =
      if (line.startsWith(":")) commandParser.parse(line, cursor, context)
      else scalaParser.parse(line, cursor, context)
  }

CommandParser is a class from Jline3. Now there are some problems, but I don't know if it has something to do with it.

The phenomenon is:

: p will show paste and power, but :p will not, if there is not have a space.
After my test, it seems to be related to this method.

    protected void defaultMatchers(Map<LineReader.Option, Boolean> options, boolean prefix, CompletingParsedLine line
            , boolean caseInsensitive, int errors, String originalGroupName) {
        // Find matchers
        // TODO: glob completion
        if (prefix) {
            String wd = line.word();
            String wdi = caseInsensitive ? wd.toLowerCase() : wd;
            String wp = wdi.substring(0, line.wordCursor());
            matchers = new ArrayList<>(Arrays.asList(
                    simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wp)),
                    simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wp)),
                    typoMatcher(wp, errors, caseInsensitive, originalGroupName)
            ));
            exact = s -> caseInsensitive ? s.equalsIgnoreCase(wp) : s.equals(wp);
        } else if (LineReader.Option.COMPLETE_IN_WORD.isSet(options)) {
            String wd = line.word();
            String wdi = caseInsensitive ? wd.toLowerCase() : wd;
            String wp = wdi.substring(0, line.wordCursor());
            String ws = wdi.substring(line.wordCursor());
            Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
            Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
            matchers = new ArrayList<>(Arrays.asList(
                    simpleMatcher(s -> p1.matcher(caseInsensitive ? s.toLowerCase() : s).matches()),
                    simpleMatcher(s -> p2.matcher(caseInsensitive ? s.toLowerCase() : s).matches()),
                    typoMatcher(wdi, errors, caseInsensitive, originalGroupName)
            ));
            exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
        } else {
            //  It seems to be here.
            String wd = line.word();
            String wdi = caseInsensitive ? wd.toLowerCase() : wd;
            if (LineReader.Option.EMPTY_WORD_OPTIONS.isSet(options) || wd.length() > 0) {
                matchers = new ArrayList<>(Arrays.asList(
                        simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wdi)),
                        simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wdi)),
                        typoMatcher(wdi, errors, caseInsensitive, originalGroupName)
                ));
            } else {
                matchers = new ArrayList<>(Collections.singletonList(simpleMatcher(s -> !s.startsWith("-"))));
            }
            exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
        }
    }

It looks strange. Is it the wrong way I use it? Or is there a lack of configuration?
What we expect is that :p can be displayed correctly, not : p

@mattirn
Copy link
Collaborator

mattirn commented Feb 22, 2021

IMHO the problem is in your completer implementation...

@jxnu-liguobin
Copy link
Author

@mattirn Yes, originally I thought so, but I debugged to the DefaultParser and found that word was wrong.
It should be parser before compilation?

@mattirn
Copy link
Collaborator

mattirn commented Feb 23, 2021

I do not know what you have seen but testing DefaultParser using your input I see that it works as designed

Screenshot from 2021-02-23 17-24-17

mattirn added a commit that referenced this issue Feb 23, 2021
@mattirn
Copy link
Collaborator

mattirn commented Feb 23, 2021

Added ColonCommandCompletionTest hopefully it will help you to resolve scala/bug#12264 :)

@jxnu-liguobin
Copy link
Author

@mattirn The behavior is different.
image

image

@jxnu-liguobin
Copy link
Author

} else if (quoteStart < 0 && isDelimiter(line, i)) {
// Delimiter
if (current.length() > 0) {
words.add(current.toString());
current.setLength(0); // reset the arg
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart;
}
}
rawWordStart = i + 1;
} else {
if (!isEscapeChar(line, i)) {
current.append(line.charAt(i));
if (quoteStart < 0) {
bracketChecker.check(line, i);
}
} else if (context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
}
}
}
All cases are at the last else, So variable current is not reset.

@mattirn
Copy link
Collaborator

mattirn commented Feb 24, 2021

DefaultParser when parsing line

  1. ':p' will return word list [':p']
  2. ': p' will return word list [':', 'p']

That's the way it has been designed and you cannot change this without breaking everything.
You can implement an other parser or extend DefaultParser if it does not fit in your requirements.
Though I think the cleanest way to solve the command completion bug is to fix your completer.

@jxnu-liguobin
Copy link
Author

The only thing we can do is to append ":" to the front of the candidates so that the match can be found.

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