org.neo4j.shell.terminal.StatementJlineParser Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.shell.terminal;
import static org.jline.reader.Parser.ParseContext.COMPLETE;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.jline.reader.CompletingParsedLine;
import org.jline.reader.EOFError;
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
import org.jline.reader.SyntaxError;
import org.jline.reader.impl.DefaultParser;
import org.neo4j.shell.log.Logger;
import org.neo4j.shell.parser.StatementParser;
import org.neo4j.shell.parser.StatementParser.CommandStatement;
import org.neo4j.shell.parser.StatementParser.CypherStatement;
import org.neo4j.shell.parser.StatementParser.ParsedStatement;
import org.neo4j.shell.parser.StatementParser.ParsedStatements;
import org.neo4j.shell.terminal.JlineTerminal.ParsedLineStatements;
/**
* Jline Parser that parse Cypher Shell statements.
*/
public class StatementJlineParser extends DefaultParser implements Parser {
private static final Logger log = Logger.create();
private final StatementParser statementParser;
private boolean enableStatementParsing;
public StatementJlineParser(StatementParser statementParser) {
this.statementParser = statementParser;
}
@Override
public ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError {
if (!enableStatementParsing) {
return new UnparsedLine(line, cursor);
} else if (context == COMPLETE) {
return parseForCompletion(line, cursor);
}
return parseForExecution(line, cursor);
}
private SimpleParsedStatements parseForExecution(String line, int cursor) {
var parsed = parse(line);
if (parsed.hasIncompleteStatement()) {
// This will trigger line continuation in jline
throw new EOFError(-1, cursor, "Incomplete statement");
}
return new SimpleParsedStatements(parsed, line, cursor);
}
private ParsedLine parseForCompletion(String line, int cursor) {
return parse(line)
.statementAtOffset(cursor)
.flatMap(s -> completingStatement(s, line, cursor))
.orElseGet(() -> new BlankCompletion(line, cursor));
}
private Optional completingStatement(ParsedStatement statement, String line, int cursor) {
if (statement instanceof CommandStatement command && command.args().isEmpty()) {
return Optional.of(new CommandCompletion(command, line, cursor));
} else if (statement instanceof CypherStatement cypher) {
return Optional.of(completingCypher(cypher, line, cursor));
}
return Optional.empty();
}
private String findLastToken(String query) {
if (query.isEmpty()) {
return "";
}
int i = query.length() - 1;
boolean spaceFound = false;
while (i >= 0 && !spaceFound) {
if (Character.isWhitespace(query.charAt(i))) {
spaceFound = true;
} else {
--i;
}
}
return query.substring(i + 1);
}
private CypherCompletion completingCypher(CypherStatement statement, String line, int cursor) {
var word = findLastToken(statement.statement());
return new CypherCompletion(statement, line, cursor, word, 0);
}
private ParsedStatements parse(String line) {
try {
return statementParser.parse(line);
} catch (IOException e) {
log.error("Failed to parse " + line, e);
throw new RuntimeException("Failed to parse `" + line + "`: " + e.getMessage(), e);
}
}
/** If enable is false this parser will be disabled and pass through all lines without parsing and completion */
public void setEnableStatementParsing(boolean enable) {
this.enableStatementParsing = enable;
}
@Override
public boolean isEscapeChar(char ch) {
return false;
}
@Override
public boolean validCommandName(String name) {
return false;
}
@Override
public boolean validVariableName(String name) {
return false;
}
@Override
public String getCommand(String line) {
return "";
}
@Override
public String getVariable(String line) {
return null;
}
protected record CommandCompletion(CommandStatement statement, String line, int cursor)
implements CompletingWord, CompletingStatements {
@Override
public String word() {
return statement.name();
}
@Override
public int wordCursor() {
return 0;
}
}
protected record BlankCompletion(String line, int cursor) implements NoWordsParsedLine {}
protected record CypherCompletion(CypherStatement statement, String line, int cursor, String word, int wordCursor)
implements CompletingWord, CompletingStatements {}
protected record SimpleParsedStatements(ParsedStatements statements, String line, int cursor)
implements ParsedLineStatements, NoWordsParsedLine {}
protected record UnparsedLine(String line, int cursor) implements NoWordsParsedLine {}
interface CompletingStatements {
ParsedStatement statement();
}
protected interface NoWordsParsedLine extends CompletingWord {
@Override
default String word() {
return "";
}
@Override
default int wordCursor() {
return 0;
}
}
private interface CompletingWord extends CompletingParsedLine {
@Override
default CharSequence escape(CharSequence candidate, boolean complete) {
return candidate;
}
@Override
default int rawWordCursor() {
return wordCursor();
}
@Override
default int rawWordLength() {
return word().length();
}
@Override
default int wordIndex() {
return 0;
}
@Override
default List words() {
return Collections.singletonList(word());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy