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

jdk.internal.jshell.tool.Feedback Maven / Gradle / Ivy

There is a newer version: 9-dev-r4023-3
Show newest version
/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.internal.jshell.tool;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Feedback customization support
 *
 * @author Robert Field
 */
class Feedback {

    // Patern for substituted fields within a customized format string
    private static final Pattern FIELD_PATTERN = Pattern.compile("\\{(.*?)\\}");

    // Current mode
    private Mode mode = new Mode("", false); // initial value placeholder during start-up

    // Mapping of mode names to mode modes
    private final Map modeMap = new HashMap<>();

    public boolean shouldDisplayCommandFluff() {
        return mode.commandFluff;
    }

    public String getPre() {
        return mode.pre;
    }

    public String getPost() {
        return mode.post;
    }

    public String getErrorPre() {
        return mode.errorPre;
    }

    public String getErrorPost() {
        return mode.errorPost;
    }

    public String getFormat(FormatCase fc, FormatWhen fw, FormatAction fa, FormatResolve fr,
            boolean hasName, boolean hasType, boolean hasResult) {
        return mode.getFormat(fc, fw, fa, fr, hasName, hasType, hasResult);
    }

    public String getPrompt(String nextId) {
        return mode.getPrompt(nextId);
    }

    public String getContinuationPrompt(String nextId) {
        return mode.getContinuationPrompt(nextId);
    }

    public boolean setFeedback(JShellTool tool, ArgTokenizer at) {
        return new FormatSetter(tool, at).setFeedback();
    }

    public boolean setField(JShellTool tool, ArgTokenizer at) {
        return new FormatSetter(tool, at).setField();
    }

    public boolean setFormat(JShellTool tool, ArgTokenizer at) {
        return new FormatSetter(tool, at).setFormat();
    }

    public boolean setNewMode(JShellTool tool, ArgTokenizer at) {
        return new FormatSetter(tool, at).setNewMode();
    }

    public boolean setPrompt(JShellTool tool, ArgTokenizer at) {
        return new FormatSetter(tool, at).setPrompt();
    }

    public void printFeedbackHelp(JShellTool tool) {
        new FormatSetter(tool, null).printFeedbackHelp();
    }

    public void printFieldHelp(JShellTool tool) {
        new FormatSetter(tool, null).printFieldHelp();
    }

    public void printFormatHelp(JShellTool tool) {
        new FormatSetter(tool, null).printFormatHelp();
    }

    public void printNewModeHelp(JShellTool tool) {
        new FormatSetter(tool, null).printNewModeHelp();
    }

    public void printPromptHelp(JShellTool tool) {
        new FormatSetter(tool, null).printPromptHelp();
    }

    /**
     * Holds all the context of a mode mode
     */
    private class Mode {

        // Use name of mode mode

        final String name;

        // Display command verification/information
        final boolean commandFluff;

        // event cases: class, method
        final EnumMap>> cases;

        // action names: add. modified, replaced, ...
        final EnumMap> actions;

        // resolution status description format with %s for unresolved
        final EnumMap> resolves;

        // primary snippet vs update
        final EnumMap whens;

        // fixed map of how to get format string for a field, given a specific formatting contet
        final EnumMap> fields;

        // format wrappers for name, type, and result
        String fname = "%s";
        String ftype = "%s";
        String fresult = "%s";

        // start and end, also used by hard-coded output
        String pre = "|  ";
        String post = "\n";
        String errorPre = "|  Error: ";
        String errorPost = "\n";

        String prompt = "\n-> ";
        String continuationPrompt = ">> ";

        /**
         * The context of a specific mode to potentially display.
         */
        class Context {

            final FormatCase fc;
            final FormatAction fa;
            final FormatResolve fr;
            final FormatWhen fw;
            final boolean hasName;
            final boolean hasType;
            final boolean hasResult;

            Context(FormatCase fc, FormatWhen fw, FormatAction fa, FormatResolve fr,
                    boolean hasName, boolean hasType, boolean hasResult) {
                this.fc = fc;
                this.fa = fa;
                this.fr = fr;
                this.fw = fw;
                this.hasName = hasName;
                this.hasType = hasType;
                this.hasResult = hasResult;
            }

            String when() {
                return whens.get(fw);
            }

            String action() {
                return actions.get(fa).get(fw);
            }

            String resolve() {
                return String.format(resolves.get(fr).get(fw), FormatField.RESOLVE.form);
            }

            String name() {
                return hasName
                        ? String.format(fname, FormatField.NAME.form)
                        : "";
            }

            String type() {
                return hasType
                        ? String.format(ftype, FormatField.TYPE.form)
                        : "";
            }

            String result() {
                return hasResult
                        ? String.format(fresult, FormatField.RESULT.form)
                        : "";
            }

            /**
             * Lookup format based on case, action, and whether it update.
             * Replace fields with context specific formats.
             *
             * @return format string
             */
            String format() {
                String format = cases.get(fc).get(fa).get(fw);
                if (format == null) {
                    return "";
                }
                Matcher m = FIELD_PATTERN.matcher(format);
                StringBuffer sb = new StringBuffer(format.length());
                while (m.find()) {
                    String fieldName = m.group(1).toUpperCase(Locale.US);
                    String sub = null;
                    for (FormatField f : FormatField.values()) {
                        if (f.name().startsWith(fieldName)) {
                            sub = fields.get(f).apply(this);
                            break;
                        }
                    }
                    if (sub != null) {
                        m.appendReplacement(sb, Matcher.quoteReplacement(sub));
                    }
                }
                m.appendTail(sb);
                return sb.toString();
            }
        }

        {
            // set fixed mappings of fields
            fields = new EnumMap<>(FormatField.class);
            fields.put(FormatField.WHEN, c -> c.when());
            fields.put(FormatField.ACTION, c -> c.action());
            fields.put(FormatField.RESOLVE, c -> c.resolve());
            fields.put(FormatField.NAME, c -> c.name());
            fields.put(FormatField.TYPE, c -> c.type());
            fields.put(FormatField.RESULT, c -> c.result());
            fields.put(FormatField.PRE, c -> pre);
            fields.put(FormatField.POST, c -> post);
            fields.put(FormatField.ERRORPRE, c -> errorPre);
            fields.put(FormatField.ERRORPOST, c -> errorPost);
        }

        /**
         * Set up an empty mode.
         *
         * @param name
         * @param commandFluff True if should display command fluff messages
         */
        Mode(String name, boolean commandFluff) {
            this.name = name;
            this.commandFluff = commandFluff;
            cases = new EnumMap<>(FormatCase.class);
            for (FormatCase fc : FormatCase.values()) {
                EnumMap> ac = new EnumMap<>(FormatAction.class);
                cases.put(fc, ac);
                for (FormatAction fa : FormatAction.values()) {
                    EnumMap aw = new EnumMap<>(FormatWhen.class);
                    ac.put(fa, aw);
                    for (FormatWhen fw : FormatWhen.values()) {
                        aw.put(fw, "");
                    }
                }
            }

            actions = new EnumMap<>(FormatAction.class);
            for (FormatAction fa : FormatAction.values()) {
                EnumMap afw = new EnumMap<>(FormatWhen.class);
                actions.put(fa, afw);
                for (FormatWhen fw : FormatWhen.values()) {
                    afw.put(fw, fa.name() + "-" + fw.name());
                }
            }

            resolves = new EnumMap<>(FormatResolve.class);
            for (FormatResolve fr : FormatResolve.values()) {
                EnumMap arw = new EnumMap<>(FormatWhen.class);
                resolves.put(fr, arw);
                for (FormatWhen fw : FormatWhen.values()) {
                    arw.put(fw, fr.name() + "-" + fw.name() + ": %s");
                }
            }

            whens = new EnumMap<>(FormatWhen.class);
            for (FormatWhen fw : FormatWhen.values()) {
                whens.put(fw, fw.name());
            }
        }

        /**
         * Set up a copied mode.
         *
         * @param name
         * @param commandFluff True if should display command fluff messages
         * @param m Mode to copy
         */
        Mode(String name, boolean commandFluff, Mode m) {
            this.name = name;
            this.commandFluff = commandFluff;
            cases = new EnumMap<>(FormatCase.class);
            for (FormatCase fc : FormatCase.values()) {
                EnumMap> ac = new EnumMap<>(FormatAction.class);
                EnumMap> mc = m.cases.get(fc);
                cases.put(fc, ac);
                for (FormatAction fa : FormatAction.values()) {
                    EnumMap aw = new EnumMap<>(mc.get(fa));
                    ac.put(fa, aw);
                }
            }

            actions = new EnumMap<>(FormatAction.class);
            for (FormatAction fa : FormatAction.values()) {
                EnumMap afw = new EnumMap<>(m.actions.get(fa));
                actions.put(fa, afw);
            }

            resolves = new EnumMap<>(FormatResolve.class);
            for (FormatResolve fr : FormatResolve.values()) {
                EnumMap arw = new EnumMap<>(m.resolves.get(fr));
                resolves.put(fr, arw);
            }

            whens = new EnumMap<>(m.whens);

            this.fname = m.fname;
            this.ftype = m.ftype;
            this.fresult = m.fresult;
            this.pre = m.pre;
            this.post = m.post;
            this.errorPre = m.errorPre;
            this.errorPost = m.errorPost;
            this.prompt = m.prompt;
            this.continuationPrompt = m.continuationPrompt;
        }

        String getFormat(FormatCase fc, FormatWhen fw, FormatAction fa, FormatResolve fr,
                boolean hasName, boolean hasType, boolean hasResult) {
            Context context = new Context(fc, fw, fa, fr,
                    hasName, hasType, hasResult);
            return context.format();
        }

        void setCases(String format, Collection cc, Collection ca, Collection cw) {
            for (FormatCase fc : cc) {
                EnumMap> ma = cases.get(fc);
                for (FormatAction fa : ca) {
                    EnumMap mw = ma.get(fa);
                    for (FormatWhen fw : cw) {
                        mw.put(fw, format);
                    }
                }
            }
        }

        void setActions(String format, Collection ca, Collection cw) {
            for (FormatAction fa : ca) {
                EnumMap mw = actions.get(fa);
                for (FormatWhen fw : cw) {
                    mw.put(fw, format);
                }
            }
        }

        void setResolves(String format, Collection cr, Collection cw) {
            for (FormatResolve fr : cr) {
                EnumMap mw = resolves.get(fr);
                for (FormatWhen fw : cw) {
                    mw.put(fw, format);
                }
            }
        }

        void setWhens(String format, Collection cw) {
            for (FormatWhen fw : cw) {
                whens.put(fw, format);
            }
        }

        void setName(String s) {
            fname = s;
        }

        void setType(String s) {
            ftype = s;
        }

        void setResult(String s) {
            fresult = s;
        }

        void setPre(String s) {
            pre = s;
        }

        void setPost(String s) {
            post = s;
        }

        void setErrorPre(String s) {
            errorPre = s;
        }

        void setErrorPost(String s) {
            errorPost = s;
        }

        String getPre() {
            return pre;
        }

        String getPost() {
            return post;
        }

        String getErrorPre() {
            return errorPre;
        }

        String getErrorPost() {
            return errorPost;
        }

        void setPrompts(String prompt, String continuationPrompt) {
            this.prompt = prompt;
            this.continuationPrompt = continuationPrompt;
        }

        String getPrompt(String nextId) {
            return String.format(prompt, nextId);
        }

        String getContinuationPrompt(String nextId) {
            return String.format(continuationPrompt, nextId);
        }
    }

    /**
     * The brace delimited substitutions
     */
    public enum FormatField {
        WHEN,
        ACTION,
        RESOLVE("%1$s"),
        NAME("%2$s"),
        TYPE("%3$s"),
        RESULT("%4$s"),
        PRE,
        POST,
        ERRORPRE,
        ERRORPOST;
        String form;

        FormatField(String s) {
            this.form = s;
        }

        FormatField() {
            this.form = null;
        }
    }

    /**
     * The event cases
     */
    public enum FormatCase {
        IMPORT("import declaration: {action} {name}"),
        CLASS("class, interface, enum, or annotation declaration: {action} {name} {resolve}"),
        INTERFACE("class, interface, enum, or annotation declaration: {action} {name} {resolve}"),
        ENUM("class, interface, enum, or annotation declaration: {action} {name} {resolve}"),
        ANNOTATION("annotation interface declaration: {action} {name} {resolve}"),
        METHOD("method declaration: {action} {name} {type}==parameter-types {resolve}"),
        VARDECL("variable declaration: {action} {name} {type} {resolve}"),
        VARDECLRECOVERABLE("recoverably failed variable declaration: {action} {name} {resolve}"),
        VARINIT("variable declaration with init: {action} {name} {type} {resolve} {result}"),
        VARRESET("variable reset on update: {action} {name}"),
        EXPRESSION("expression: {action}=='Saved to scratch variable' {name} {type} {result}"),
        VARVALUE("variable value expression: {action} {name} {type} {result}"),
        ASSIGNMENT("assign variable: {action} {name} {type} {result}"),
        STATEMENT("statement: {action}");
        String doc;

        private FormatCase(String doc) {
            this.doc = doc;
        }
    }

    /**
     * The event actions
     */
    public enum FormatAction {
        ADDED("snippet has been added"),
        MODIFIED("an existing snippet has been modified"),
        REPLACED("an existing snippet has been replaced with a new snippet"),
        OVERWROTE("an existing snippet has been overwritten"),
        DROPPED("snippet has been dropped"),
        REJECTED("snippet has failed and been rejected");
        String doc;

        private FormatAction(String doc) {
            this.doc = doc;
        }
    }

    /**
     * When the event occurs: primary or update
     */
    public enum FormatWhen {
        PRIMARY("the entered snippet"),
        UPDATE("an update to a dependent snippet");
        String doc;

        private FormatWhen(String doc) {
            this.doc = doc;
        }
    }

    /**
     * Resolution problems with event
     */
    public enum FormatResolve {
        OK("resolved correctly"),
        DEFINED("defined despite recoverably unresolved references"),
        NOTDEFINED("not defined because of recoverably unresolved references");
        String doc;

        private FormatResolve(String doc) {
            this.doc = doc;
        }
    }

    // Class used to set custom eval output formats
    // For both /set format  and /set field -- Parse arguments, setting custom format, or printing error
    private class FormatSetter {

        private final ArgTokenizer at;
        private final JShellTool tool;
        boolean valid = true;

        class Case, E2 extends Enum, E3 extends Enum> {

            Set e1;
            Set e2;
            Set e3;

            Case(Set e1, Set e2, Set e3) {
                this.e1 = e1;
                this.e2 = e2;
                this.e3 = e3;
            }

            Case(Set e1, Set e2) {
                this.e1 = e1;
                this.e2 = e2;
            }
        }

        FormatSetter(JShellTool tool, ArgTokenizer at) {
            this.tool = tool;
            this.at = at;
        }

        void hard(String format, Object... args) {
            tool.hard(format, args);
        }

        > void hardEnums(EnumSet es, Function e2s) {
            hardPairs(es.stream(), ev -> ev.name().toLowerCase(Locale.US), e2s);
        }

         void hardPairs(Stream stream, Function a, Function b) {
            tool.hardPairs(stream, a, b);
        }

        void fluff(String format, Object... args) {
            tool.fluff(format, args);
        }

        void error(String format, Object... args) {
            tool.error(format, args);
        }

        void errorat(String format, Object... args) {
            Object[] a2 = Arrays.copyOf(args, args.length + 1);
            a2[args.length] = at.whole();
            tool.error(format + " -- /set %s", a2);
        }

        void fluffRaw(String format, Object... args) {
            tool.fluffRaw(format, args);
        }

        // For /set prompt  "" ""
        boolean setPrompt() {
            Mode m = nextMode();
            String prompt = nextFormat();
            String continuationPrompt = nextFormat();
            if (valid) {
                m.setPrompts(prompt, continuationPrompt);
            } else {
                fluff("See '/help /set prompt' for help");
            }
            return valid;
        }

        // For /set newmode  [command|quiet []]
        boolean setNewMode() {
            String umode = at.next();
            if (umode == null) {
                errorat("Expected new feedback mode");
                valid = false;
            }
            if (modeMap.containsKey(umode)) {
                errorat("Expected a new feedback mode name. %s is a known feedback mode", umode);
                valid = false;
            }
            String[] fluffOpt = at.next("command", "quiet");
            boolean fluff = fluffOpt == null || fluffOpt.length != 1 || "command".equals(fluffOpt[0]);
            if (fluffOpt != null && fluffOpt.length != 1) {
                errorat("Specify either 'command' or 'quiet'");
                valid = false;
            }
            Mode om = null;
            String omode = at.next();
            if (omode != null) {
                om = toMode(omode);
            }
            if (valid) {
                Mode nm = (om != null)
                        ? new Mode(umode, fluff, om)
                        : new Mode(umode, fluff);
                modeMap.put(umode, nm);
                fluff("Created new feedback mode: %s", nm.name);
            } else {
                fluff("See '/help /set newmode' for help");
            }
            return valid;
        }

        // For /set feedback 
        boolean setFeedback() {
            Mode m = nextMode();
            if (valid && m != null) {
                mode = m;
                fluff("Feedback mode: %s", mode.name);
            } else {
                fluff("See '/help /set feedback' for help");
            }
            return valid;
        }

        // For /set format  "" ...
        boolean setFormat() {
            Mode m = nextMode();
            String format = nextFormat();
            if (valid) {
                List> specs = new ArrayList<>();
                String s;
                while ((s = at.next()) != null) {
                    String[] d = s.split("-");
                    specs.add(new Case<>(
                            parseFormatCase(d, 0),
                            parseFormatAction(d, 1),
                            parseFormatWhen(d, 2)
                    ));
                }
                if (valid && specs.isEmpty()) {
                    errorat("At least one selector required");
                    valid = false;
                }
                if (valid) {
                    // set the format in the specified cases
                    specs.stream()
                            .forEach(c -> m.setCases(format, c.e1, c.e2, c.e3));
                }
            }
            if (!valid) {
                fluff("See '/help /set format' for help");
            }
            return valid;
        }

        // For /set field mode  "" ...
        boolean setField() {
            Mode m = nextMode();
            String fieldName = at.next();
            FormatField field = parseFormatSelector(fieldName, EnumSet.allOf(FormatField.class), "field");
            String format = nextFormat();
            if (valid) {
                switch (field) {
                    case ACTION: {
                        List> specs = new ArrayList<>();
                        String s;
                        while ((s = at.next()) != null) {
                            String[] d = s.split("-");
                            specs.add(new Case<>(
                                    parseFormatAction(d, 0),
                                    parseFormatWhen(d, 1)
                            ));
                        }
                        if (valid && specs.isEmpty()) {
                            errorat("At least one selector required");
                            valid = false;
                        }
                        if (valid) {
                            // set the format of the specified actions
                            specs.stream()
                                    .forEach(c -> m.setActions(format, c.e1, c.e2));
                        }
                        break;
                    }
                    case RESOLVE: {
                        List> specs = new ArrayList<>();
                        String s;
                        while ((s = at.next()) != null) {
                            String[] d = s.split("-");
                            specs.add(new Case<>(
                                    parseFormatResolve(d, 0),
                                    parseFormatWhen(d, 1)
                            ));
                        }
                        if (valid && specs.isEmpty()) {
                            errorat("At least one selector required");
                            valid = false;
                        }
                        if (valid) {
                            // set the format of the specified resolves
                            specs.stream()
                                    .forEach(c -> m.setResolves(format, c.e1, c.e2));
                        }
                        break;
                    }
                    case WHEN: {
                        List> specs = new ArrayList<>();
                        String s;
                        while ((s = at.next()) != null) {
                            String[] d = s.split("-");
                            specs.add(new Case<>(
                                    parseFormatWhen(d, 1),
                                    null
                            ));
                        }
                        if (valid && specs.isEmpty()) {
                            errorat("At least one selector required");
                            valid = false;
                        }
                        if (valid) {
                            // set the format of the specified whens
                            specs.stream()
                                    .forEach(c -> m.setWhens(format, c.e1));
                        }
                        break;
                    }
                    case NAME: {
                        m.setName(format);
                        break;
                    }
                    case TYPE: {
                        m.setType(format);
                        break;
                    }
                    case RESULT: {
                        m.setResult(format);
                        break;
                    }
                    case PRE: {
                        m.setPre(format);
                        break;
                    }
                    case POST: {
                        m.setPost(format);
                        break;
                    }
                    case ERRORPRE: {
                        m.setErrorPre(format);
                        break;
                    }
                    case ERRORPOST: {
                        m.setErrorPost(format);
                        break;
                    }
                }
            }
            if (!valid) {
                fluff("See '/help /set field' for help");
            }
            return valid;
        }

        Mode nextMode() {
            String umode = at.next();
            return toMode(umode);
        }

        Mode toMode(String umode) {
            if (umode == null) {
                errorat("Expected a feedback mode");
                valid = false;
                return null;
            }
            Mode m = modeMap.get(umode);
            if (m != null) {
                return m;
            }
            // Failing an exact match, go searching
            Mode[] matches = modeMap.entrySet().stream()
                    .filter(e -> e.getKey().startsWith(umode))
                    .map(e -> e.getValue())
                    .toArray(size -> new Mode[size]);
            if (matches.length == 1) {
                return matches[0];
            } else {
                valid = false;
                if (matches.length == 0) {
                    errorat("Does not match any current feedback mode: %s", umode);
                } else {
                    errorat("Matchs more then one current feedback mode: %s", umode);
                }
                fluff("The feedback mode should be one of the following:");
                modeMap.keySet().stream()
                        .forEach(mk -> fluff("   %s", mk));
                fluff("You may also use just enough letters to make it unique.");
                return null;
            }
        }

        // Test if the format string is correctly
        final String nextFormat() {
            String format = at.next();
            if (format == null) {
                errorat("Expected format missing");
                valid = false;
                return null;
            }
            if (!at.isQuoted()) {
                errorat("Format '%s' must be quoted", format);
                valid = false;
                return null;
            }
            return format;
        }

        final Set parseFormatCase(String[] s, int i) {
            return parseFormatSelectorStar(s, i, FormatCase.class, EnumSet.allOf(FormatCase.class), "case");
        }

        final Set parseFormatAction(String[] s, int i) {
            return parseFormatSelectorStar(s, i, FormatAction.class,
                    EnumSet.of(FormatAction.ADDED, FormatAction.MODIFIED, FormatAction.REPLACED), "action");
        }

        final Set parseFormatResolve(String[] s, int i) {
            return parseFormatSelectorStar(s, i, FormatResolve.class,
                    EnumSet.of(FormatResolve.DEFINED, FormatResolve.NOTDEFINED), "resolve");
        }

        final Set parseFormatWhen(String[] s, int i) {
            return parseFormatSelectorStar(s, i, FormatWhen.class, EnumSet.of(FormatWhen.PRIMARY), "when");
        }

        /**
         * In a selector x-y-z , parse x, y, or z -- whether they are missing,
         * or a comma separated list of identifiers and stars.
         *
         * @param  The enum this selector should belong to
         * @param sa The array of selector strings
         * @param i The index of which selector string to use
         * @param klass The class of the enum that should be used
         * @param defaults The set of enum values to use if the selector is
         * missing
         * @return The set of enum values specified by this selector
         */
        final > Set parseFormatSelectorStar(String[] sa, int i, Class klass, EnumSet defaults, String label) {
            String s = sa.length > i
                    ? sa[i]
                    : null;
            if (s == null || s.isEmpty()) {
                return defaults;
            }
            Set set = EnumSet.noneOf(klass);
            EnumSet values = EnumSet.allOf(klass);
            for (String as : s.split(",")) {
                if (as.equals("*")) {
                    set.addAll(values);
                } else if (!as.isEmpty()) {
                    set.add(parseFormatSelector(as, values, label));
                }
            }
            return set;
        }

        /**
         * In a x-y-a,b selector, parse an x, y, a, or b -- that is an
         * identifier
         *
         * @param  The enum this selector should belong to
         * @param s The string to parse: x, y, or z
         * @param values The allowed of this enum
         * @return The enum value
         */
        final > E parseFormatSelector(String s, EnumSet values, String label) {
            if (s == null) {
                valid = false;
                return null;
            }
            String u = s.toUpperCase(Locale.US);
            for (E c : values) {
                if (c.name().startsWith(u)) {
                    return c;
                }
            }

            errorat("Not a valid %s: %s, must be one of: %s", label, s,
                    values.stream().map(v -> v.name().toLowerCase(Locale.US)).collect(Collectors.joining(" ")));
            valid = false;
            return values.iterator().next();
        }

        final void printFormatHelp() {
            hard("Set the format for reporting a snippet event.");
            hard("");
            hard("/set format  \"\" ...");
            hard("");
            hard("Where  is the name of a previously defined feedback mode -- see '/help /set newmode'.");
            hard("Where  is a quoted string which will have these field substitutions:");
            hard("   {action}    == The action, e.g.: Added, Modified, Assigned, ...");
            hard("   {name}      == The name, e.g.: the variable name, ...");
            hard("   {type}      == The type name");
            hard("   {resolve}   == Unresolved info, e.g.: ', however, it cannot be invoked until'");
            hard("   {result}    == The result value");
            hard("   {when}      == The entered snippet or a resultant update");
            hard("   {pre}       == The feedback prefix");
            hard("   {post}      == The feedback postfix");
            hard("   {errorpre}  == The error prefix");
            hard("   {errorpost} == The error postfix");
            hard("Use '/set field' to set the format of these substitutions.");
            hard("Where  is the context in which the format is applied.");
            hard("The structure of selector is: [-[-]]");
            hard("Where each field component may be missing (indicating defaults),");
            hard("star (indicating all), or a comma separated list of field values.");
            hard("For case, the field values are:");
            hardEnums(EnumSet.allOf(FormatCase.class), ev -> ev.doc);
            hard("For action, the field values are:");
            hardEnums(EnumSet.allOf(FormatAction.class), ev -> ev.doc);
            hard("For when, the field values are:");
            hardEnums(EnumSet.allOf(FormatWhen.class), ev -> ev.doc);
            hard("");
            hard("Example:");
            hard("   /set format example '{pre}{action} variable {name}, reset to null{post}' varreset-*-update");
        }

        final void printFieldHelp() {
            hard("Set the format of a field substitution as used in '/set format'.");
            hard("");
            hard("/set field   \"\" ...");
            hard("");
            hard("Where  is the name of a previously defined feedback mode -- see '/set newmode'.");
            hard("Where  is context-specific format to set, each with its own selector structure:");
            hard("   action    == The action. The selector: -.");
            hard("   name      == The name.  '%%s' is the name.  No selectors.");
            hard("   type      == The type name.  '%%s' is the type. No selectors.");
            hard("   resolve   == Unresolved info.  '%%s' is the unresolved list. The selector: -.");
            hard("   result    == The result value.  '%%s' is the result value. No selectors.");
            hard("   when      == The entered snippet or a resultant update. The selector: ");
            hard("   pre       == The feedback prefix. No selectors.");
            hard("   post      == The feedback postfix. No selectors.");
            hard("   errorpre  == The error prefix. No selectors.");
            hard("   errorpost == The error postfix. No selectors.");
            hard("Where  is a quoted string -- see the description specific to the field (above).");
            hard("Where  is the context in which the format is applied (see above).");
            hard("For action, the field values are:");
            hardEnums(EnumSet.allOf(FormatAction.class), ev -> ev.doc);
            hard("For when, the field values are:");
            hardEnums(EnumSet.allOf(FormatWhen.class), ev -> ev.doc);
            hard("For resolve, the field values are:");
            hardEnums(EnumSet.allOf(FormatResolve.class), ev -> ev.doc);
            hard("");
            hard("Example:");
            hard("   /set field example resolve ' which cannot be invoked until%%s is declared' defined-update");
        }

        final void printFeedbackHelp() {
            hard("Set the feedback mode describing displayed feedback for entered snippets and commands.");
            hard("");
            hard("/set feedback ");
            hard("");
            hard("Where  is the name of a previously defined feedback mode.");
            hard("Currently defined feedback modes:");
            modeMap.keySet().stream()
                    .forEach(m -> hard("   %s", m));
            hard("User-defined modes can be added, see '/help /set newmode'");
        }

        final void printNewModeHelp() {
            hard("Create a user-defined feedback mode, optionally copying from an existing mode.");
            hard("");
            hard("/set newmode  [command|quiet []]");
            hard("");
            hard("Where  is the name of a mode you wish to create.");
            hard("Where  is the name of a previously defined feedback mode.");
            hard("If  is present, its settings are copied to the new mode.");
            hard("'command' vs 'quiet' determines if informative/verifying command feedback is displayed.");
            hard("");
            hard("Once the new mode is created, use '/set format', '/set field', and '/set prompt' to configure it.");
            hard("Use '/set feedback' to use the new mode.");
        }

        final void printPromptHelp() {
            hard("Set the prompts.  Both the normal prompt and the continuation-prompt must be set.");
            hard("");
            hard("/set prompt  \"\" \"\"");
            hard("");
            hard("Where  is the name of a previously defined feedback mode.");
            hard("Where  and  are quoted strings printed as input promptds;");
            hard("Both may optionally contain '%%s' which will be substituted with the next snippet id --");
            hard("note that what is entered may not be assigned that id, for example it may be an error or command.");
            hard("The continuation-prompt is used on the second and subsequent lines of a multi-line snippet.");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy