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

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