All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.projectnessie.nessie.cli.completer.CliCompleter Maven / Gradle / Ivy

/*
 * Copyright (C) 2024 Dremio
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.projectnessie.nessie.cli.completer;

import static java.util.Locale.ROOT;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.projectnessie.nessie.cli.grammar.CompletionType;
import org.projectnessie.nessie.cli.grammar.NessieCliParser;
import org.projectnessie.nessie.cli.grammar.ParseException;
import org.projectnessie.nessie.cli.grammar.Token;
import org.projectnessie.nessie.cli.grammar.Token.TokenType;

public abstract class CliCompleter {
  private final String source;
  private final NessieCliParser parser;

  private final Consumer producer;

  public CliCompleter(
      String input,
      int cursor,
      Function parserForSource,
      Consumer producer) {
    this.source = input.substring(0, cursor);

    this.parser = parserForSource.apply(source);

    this.producer = producer;
  }

  public NessieCliParser parser() {
    return parser;
  }

  /**
   * Runs a completion attempt.
   *
   * 

Provides completions for tokens via {@link #tokenCandidateStartsWith(String, TokenType)} et * al and calls {@link #completeWithLiteral(CompletionType, String, String, boolean)} for * literals. * * @return {@code true} if the input could not be parsed, {@code false} if the input could be * successfully parsed. */ public boolean tryStatement() { try { producer.accept(parser); // Input was successfully parsed. If the last token is an identifier, try to complete it. if (parser.getToken(0).getType().isEOF()) { Token prev = parser.getToken(-1); switch (prev.getType()) { case IDENTIFIER: case STRING_LITERAL: case URI: completeWithIdentifier( source.substring(0, prev.getEndOffset()), source.substring(prev.getBeginOffset())); break; default: // cannot have "unfinished" keywords for a successfully parsed statement break; } } List optionalTokens = parser.optionalNextTokenTypes(); if (!optionalTokens.isEmpty()) { String preceding = source; if (!preceding.isEmpty() && !Character.isWhitespace(preceding.charAt(preceding.length() - 1))) { preceding += " "; } for (TokenType optionalToken : optionalTokens) { if (parser.isTokenActive(optionalToken)) { tokenCandidateOther(preceding, optionalToken); } } } return false; } catch (ParseException e) { int index = 0; Token currentToken = parser.getToken(index); // Go to the token on which the cursor is. int cursor = source.length(); while (true) { if (currentToken.getBeginOffset() <= cursor && cursor <= currentToken.getEndOffset()) { break; } if (cursor < currentToken.getEndOffset()) { Token prev = parser.getToken(--index); if (prev == null) { break; } currentToken = prev; } if (cursor >= currentToken.getEndOffset()) { Token next = parser.getToken(++index); if (next == null || next.getType().isEOF()) { break; } currentToken = next; } } // Go back to the first invalid token. This finds the start of incomplete string literals, // for example for an input like 'CREATE BRANCH "foo bar', it sets `currentToken` to the // invalid token '"foo'. Have to skip over all non-INVALID tokens. for (int i = index; ; i--) { Token c = parser.getToken(i); if (c == null) { break; } else if (c.getType().isInvalid()) { currentToken = c; } } String trimmed; if (currentToken.isInvalid()) { trimmed = source.substring(0, currentToken.getBeginOffset()); } else { trimmed = source; if (!trimmed.isEmpty() && !Character.isWhitespace(trimmed.charAt(trimmed.length() - 1))) { trimmed += ' '; } } String currentSource = source.substring(currentToken.getBeginOffset(), cursor); String currentUpper = currentSource.toUpperCase(ROOT); boolean currentIsEmpty = currentSource.trim().isEmpty(); boolean handledIdentifier = false; List expectedTokenTypes = new ArrayList<>(parser.optionalNextTokenTypes()); for (TokenType expected : e.getExpectedTokenTypes()) { switch (expected) { case EOF: case DUMMY: case WHITESPACE: break; default: if (parser.isTokenActive(expected)) { expectedTokenTypes.add(expected); } } } for (TokenType expected : expectedTokenTypes) { switch (expected) { case IDENTIFIER: case STRING_LITERAL: case URI: // Don't call 'completeWithIdentifier()' twice for the same input. if (!handledIdentifier) { completeWithIdentifier(trimmed, currentSource); handledIdentifier = true; } break; default: // Replace the underscore with a space for "multi-word" tokens. String tokenString = parser.tokenToLiteral(expected); String tokenUpper = tokenString.toUpperCase(ROOT); if (currentIsEmpty) { tokenCandidateOther(trimmed, expected); } else if (tokenUpper.startsWith(currentUpper)) { tokenCandidateStartsWith(trimmed, expected); } else if (tokenUpper.contains(currentUpper)) { tokenCandidateContains(trimmed, expected); } else { tokenCandidateOther(trimmed, expected); } break; } } return true; } } private void completeWithIdentifier(String preceding, String toComplete) { boolean quoted = false; char c = !toComplete.isEmpty() ? toComplete.charAt(0) : 0; if (c == '\"' || c == '\'' || c == '`') { // in STRING_LITERAL quoted = true; toComplete = toComplete.substring(1); if (toComplete.endsWith("" + c)) { toComplete = toComplete.substring(0, toComplete.length() - 1); } } completeWithLiteral(parser.completionType(), preceding, toComplete, quoted); } protected abstract void completeWithLiteral( CompletionType completionType, String preceding, String toComplete, boolean quoted); protected abstract void tokenCandidateStartsWith(String preceding, TokenType toComplete); protected abstract void tokenCandidateContains(String preceding, TokenType toComplete); protected abstract void tokenCandidateOther(String preceding, TokenType toComplete); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy