Package javarepl

Source Code of javarepl.Main$JlineCompletionHandler

package javarepl;

import com.googlecode.totallylazy.Mapper;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.Sequences;
import javarepl.client.EvaluationResult;
import javarepl.client.JavaREPLClient;
import javarepl.completion.CompletionCandidate;
import javarepl.completion.CompletionResult;
import jline.console.ConsoleReader;
import jline.console.CursorBuffer;
import jline.console.completer.CandidateListCompletionHandler;
import jline.console.completer.CompletionHandler;
import jline.console.history.MemoryHistory;
import org.fusesource.jansi.AnsiConsole;

import java.io.IOException;
import java.util.*;

import static com.googlecode.totallylazy.Callables.compose;
import static com.googlecode.totallylazy.Option.none;
import static com.googlecode.totallylazy.Option.some;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Strings.replaceAll;
import static com.googlecode.totallylazy.Strings.startsWith;
import static com.googlecode.totallylazy.numbers.Numbers.intValue;
import static com.googlecode.totallylazy.numbers.Numbers.valueOf;
import static java.lang.String.format;
import static java.lang.System.getProperty;
import static java.util.Arrays.asList;
import static javarepl.Utils.applicationVersion;
import static javarepl.Utils.randomServerPort;
import static javarepl.completion.CompletionCandidate.functions.candidateForms;
import static javarepl.completion.CompletionCandidate.functions.candidateValue;
import static javarepl.completion.CompletionResult.methods.fromJson;
import static javarepl.completion.CompletionResult.methods.toJson;
import static javax.tools.ToolProvider.getSystemJavaCompiler;

public class Main {

    private static Option<Process> process = none();
    private static ResultPrinter console;

    public static void main(String... args) throws Exception {
        console = new ResultPrinter(printColors(args));

        JavaREPLClient client = clientFor(hostname(args), port(args));
        ExpressionReader expressionReader = expressionReaderFor(client);

        Option<String> expression = none();
        Option<EvaluationResult> result = none();
        while (expression.isEmpty() || !result.isEmpty()) {
            expression = expressionReader.readExpression();

            if (!expression.isEmpty()) {
                result = client.execute(expression.get());
                if (!result.isEmpty())
                    console.printEvaluationResult(result.get());
            }
        }
    }

    private static JavaREPLClient clientFor(Option<String> hostname, Option<Integer> port) throws Exception {
        console.printInfo(format("Welcome to JavaREPL version %s (%s, Java %s)",
                applicationVersion(),
                getProperty("java.vm.name"),
                getProperty("java.version")));

        if (hostname.isEmpty() && port.isEmpty()) {
            return startNewLocalInstance("localhost", randomServerPort());
        } else {
            return connectToRemoteInstance(hostname.getOrElse("localhost"), port.getOrElse(randomServerPort()));
        }
    }

    private static JavaREPLClient connectToRemoteInstance(String hostname, Integer port) {
        JavaREPLClient replClient = new JavaREPLClient(hostname, port);

        if (!replClient.status().isRunning()) {
            console.printError("ERROR: Could not connect to remote REPL instance at http://" + hostname + ":" + port);
            System.exit(0);
        } else {
            console.printInfo("Connected to remote instance at http://" + hostname + ":" + port);
        }

        String remoteInstanceVersion = replClient.version();
        if (!remoteInstanceVersion.equals(applicationVersion())) {
            console.printError("WARNING: Client version (" + applicationVersion() + ") is different from remote instance version (" + remoteInstanceVersion + ")");
        }

        return replClient;
    }

    private static JavaREPLClient startNewLocalInstance(String hostname, Integer port) throws Exception {
        if (getSystemJavaCompiler() == null) {
            console.printError("\nERROR: Java compiler not found.\n" +
                    "This can occur when JavaREPL was run with JRE instead of JDK or JDK is not configured correctly.");
            System.exit(0);
        }

        console.printInfo("Type expression to evaluate, \u001B[32m:help\u001B[0m for more options or press \u001B[32mtab\u001B[0m to auto-complete.");

        ProcessBuilder builder = new ProcessBuilder("java", "-cp", System.getProperty("java.class.path"), Repl.class.getCanonicalName(), "--port=" + port);
        builder.redirectErrorStream(true);

        process = some(builder.start());
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                console.printInfo("\nTerminating...");
                process.get().destroy();
            }
        });

        JavaREPLClient replClient = new JavaREPLClient(hostname, port);
        if (!waitUntilInstanceStarted(replClient)) {
            console.printError("\nERROR: Could not start REPL instance at http://" + hostname + ":" + port);
            System.exit(0);
        }

        return replClient;
    }

    private static boolean waitUntilInstanceStarted(JavaREPLClient client) throws Exception {
        for (int i = 0; i < 500; i++) {
            Thread.sleep(10);
            if (client.status().isRunning())
                return true;
        }

        return false;
    }

    private static Option<Integer> port(String[] args) {
        return sequence(args).find(startsWith("--port=")).map(compose(replaceAll("--port=", ""), compose(valueOf, intValue)));
    }

    private static Option<String> hostname(String[] args) {
        return sequence(args).find(startsWith("--hostname=")).map(replaceAll("--hostname=", ""));
    }

    private static Boolean printColors(String[] args) {
        return !sequence(args).contains("--noColors");
    }

    private static ExpressionReader expressionReaderFor(final JavaREPLClient client) throws IOException {
        return new ExpressionReader(new Mapper<Sequence<String>, String>() {
            private final ConsoleReader consoleReader;

            {
                consoleReader = new ConsoleReader(System.in, AnsiConsole.out);
                consoleReader.setCompletionHandler(new JlineCompletionHandler());
                consoleReader.setHistoryEnabled(true);
                consoleReader.setExpandEvents(false);
                consoleReader.addCompleter(clientCompleter());
            }

            public String call(Sequence<String> lines) throws Exception {
                consoleReader.setPrompt(console.ansiColored(lines.isEmpty() ? "\u001B[1mjava> \u001B[0m" : "    \u001B[1m| \u001B[0m"));
                consoleReader.setHistory(clientHistory());
                return consoleReader.readLine();
            }

            private MemoryHistory clientHistory() throws Exception {
                MemoryHistory history = new MemoryHistory();
                for (String historyItem : client.history()) {
                    history.add(historyItem);
                }
                return history;
            }

            private jline.console.completer.Completer clientCompleter() {
                return new jline.console.completer.Completer() {
                    public int complete(String expression, int cursor, List<CharSequence> candidates) {
                        try {
                            CompletionResult result = client.completions(expression);
                            candidates.addAll(asList(toJson(result)));
                            return result.candidates().isEmpty() ? -1 : result.position();
                        } catch (Exception e) {
                            return -1;
                        }
                    }
                };
            }
        });
    }


    /**
     * Copied from JLine sourcecode and heavily modified
     * <p/>
     * Original sources: https://raw.github.com/jline/jline2/master/src/main/java/jline/console/completer/CandidateListCompletionHandler.java
     * <p/>
     * Copyright (c) 2002-2012, the original author or authors.
     * <p/>
     * This software is distributable under the BSD license. See the terms of the
     * BSD license in the documentation provided with this software.
     * <p/>
     * http://www.opensource.org/licenses/bsd-license.php
     * <p/>
     * A {@link jline.console.completer.CompletionHandler} that deals with multiple distinct completions
     * by outputting the complete list of possibilities to the console. This
     * mimics the behavior of the
     * <a href="http://www.gnu.org/directory/readline.html">readline</a> library.
     *
     * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
     * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
     * @since 2.3
     */
    public static class JlineCompletionHandler implements CompletionHandler {
        // TODO: handle quotes and escaped quotes && enable automatic escaping of whitespace

        public boolean complete(final ConsoleReader reader, final List<CharSequence> candidatesJson, final int pos) throws IOException {
            CursorBuffer buf = reader.getCursorBuffer();
            CompletionResult completionResult = fromJson(sequence(candidatesJson).head().toString());
            Sequence<String> candidatesToPrint = Sequences.empty(String.class);

            // if there is only one completion, then fill in the buffer
            if (completionResult.candidates().size() == 1) {
                CharSequence value = completionResult.candidates().head().value();

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

                setBuffer(reader, value, pos);
                candidatesToPrint = completionResult.candidates().flatMap(candidateForms());
            } else if (completionResult.candidates().size() > 1) {
                String value = getUnambiguousCompletions(completionResult.candidates());
                setBuffer(reader, value, pos);
                candidatesToPrint = completionResult.candidates().map(candidateValue());
            }

            printCandidates(reader, candidatesToPrint.safeCast(CharSequence.class).toList());

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

            return true;
        }

        public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws
                IOException {
            while ((reader.getCursorBuffer().cursor > offset) && reader.backspace()) {
                // empty
            }

            reader.putString(value);
            reader.setCursorPosition(offset + value.length());
        }

        /**
         * Print out the candidates. If the size of the candidates is greater than the
         * {@link ConsoleReader#getAutoprintThreshold}, they prompt with a warning.
         *
         * @param candidates the list of candidates to print
         */
        public static void printCandidates(final ConsoleReader reader, Collection<CharSequence> candidates) throws
                IOException {
            Set<CharSequence> distinct = new HashSet<CharSequence>(candidates);

            if (distinct.size() > reader.getAutoprintThreshold()) {
                //noinspection StringConcatenation
                reader.print(Messages.DISPLAY_CANDIDATES.format(candidates.size()));
                reader.flush();

                int c;

                String noOpt = Messages.DISPLAY_CANDIDATES_NO.format();
                String yesOpt = Messages.DISPLAY_CANDIDATES_YES.format();
                char[] allowed = {yesOpt.charAt(0), noOpt.charAt(0)};

                while ((c = reader.readCharacter(allowed)) != -1) {
                    String tmp = new String(new char[]{(char) c});

                    if (noOpt.startsWith(tmp)) {
                        reader.println();
                        return;
                    } else if (yesOpt.startsWith(tmp)) {
                        break;
                    } else {
                        reader.beep();
                    }
                }
            }

            // copy the values and make them distinct, without otherwise affecting the ordering. Only do it if the sizes differ.
            if (distinct.size() != candidates.size()) {
                Collection<CharSequence> copy = new ArrayList<CharSequence>();

                for (CharSequence next : candidates) {
                    if (!copy.contains(next)) {
                        copy.add(next);
                    }
                }

                candidates = copy;
            }

            reader.println();
            reader.printColumns(candidates);
        }

        /**
         * Returns a root that matches all the {@link String} elements of the specified {@link List},
         * or null if there are no commonalities. For example, if the list contains
         * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the method will return <i>foob</i>.
         */
        private String getUnambiguousCompletions(final Sequence<CompletionCandidate> candidates) {
            if (candidates == null || candidates.isEmpty()) {
                return null;
            }

            // convert to an array for speed
            String[] strings = candidates.map(candidateValue()).toArray(new String[candidates.size()]);

            String first = strings[0];
            StringBuilder candidate = new StringBuilder();

            for (int i = 0; i < first.length(); i++) {
                if (startsWith(first.substring(0, i + 1), strings)) {
                    candidate.append(first.charAt(i));
                } else {
                    break;
                }
            }

            return candidate.toString();
        }

        /**
         * @return true is all the elements of <i>candidates</i> start with <i>starts</i>
         */
        private boolean startsWith(final String starts, final String[] candidates) {
            for (String candidate : candidates) {
                if (!candidate.startsWith(starts)) {
                    return false;
                }
            }

            return true;
        }

        private static enum Messages {
            DISPLAY_CANDIDATES,
            DISPLAY_CANDIDATES_YES,
            DISPLAY_CANDIDATES_NO,;

            private static final
            ResourceBundle
                    bundle =
                    ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName(), Locale.getDefault());

            public String format(final Object... args) {
                if (bundle == null)
                    return "";
                else
                    return String.format(bundle.getString(name()), args);
            }
        }
    }


}
TOP

Related Classes of javarepl.Main$JlineCompletionHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.