8148316: jshell tool: Configurable output format
authorrfield
Tue, 08 Mar 2016 11:53:35 -0800
changeset 36494 4175f47b2a50
parent 36493 1e87cd35c980
child 36495 dd9141ca331a
8148316: jshell tool: Configurable output format 8148317: jshell tool: unify commands into /set 8149524: JShell: CompletenessAnalysis fails on class Case<E1 extends Enum<E1>, E2 extends Enum<E2>, E3 extends Enum<E3>> {} Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ArgTokenizer.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java
langtools/test/jdk/jshell/CommandCompletionTest.java
langtools/test/jdk/jshell/CompletenessTest.java
langtools/test/jdk/jshell/ExternalEditorTest.java
langtools/test/jdk/jshell/ReplToolTesting.java
langtools/test/jdk/jshell/ToolBasicTest.java
langtools/test/jdk/jshell/ToolFormatTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ArgTokenizer.java	Tue Mar 08 11:53:35 2016 -0800
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 1995, 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.Arrays;
+import java.util.stream.Stream;
+
+/**
+ * Parse command arguments, derived from StreamTokenizer by
+ * @author  James Gosling
+ */
+class ArgTokenizer {
+
+    private final String str;
+    private final int length;
+    private int next = 0;
+    private char buf[] = new char[20];
+    private int mark;
+
+    private final byte ctype[] = new byte[256];
+    private static final byte CT_ALPHA = 0;
+    private static final byte CT_WHITESPACE = 1;
+    private static final byte CT_QUOTE = 8;
+
+    private String sval;
+    private boolean isQuoted = false;
+
+    ArgTokenizer(String arg) {
+        this.str = arg;
+        this.length = arg.length();
+        quoteChar('"');
+        quoteChar('\'');
+        whitespaceChars(0x09, 0x0D);
+        whitespaceChars(0x1C, 0x20);
+        whitespaceChars(0x85, 0x85);
+        whitespaceChars(0xA0, 0xA0);
+    }
+
+    String next() {
+        nextToken();
+        return sval;
+    }
+
+    String[] next(String... strings) {
+        return next(Arrays.stream(strings));
+    }
+
+    String[] next(Stream<String> stream) {
+        nextToken();
+        if (sval == null) {
+            return null;
+        }
+        String[] matches = stream
+                .filter(s -> s.startsWith(sval))
+                .toArray(size -> new String[size]);
+        return matches;
+    }
+
+    String val() {
+        return sval;
+    }
+
+    boolean isQuoted() {
+        return isQuoted;
+    }
+
+    String whole() {
+        return str;
+    }
+
+    void mark() {
+        mark = next;
+    }
+
+    void rewind() {
+        next = mark;
+    }
+
+    /**
+     * Reads a single character.
+     *
+     * @return The character read, or -1 if the end of the stream has been
+     * reached
+     */
+    private int read() {
+        if (next >= length) {
+            return -1;
+        }
+        return str.charAt(next++);
+    }
+
+    /**
+     * Specifies that all characters <i>c</i> in the range
+     * <code>low&nbsp;&lt;=&nbsp;<i>c</i>&nbsp;&lt;=&nbsp;high</code>
+     * are white space characters. White space characters serve only to
+     * separate tokens in the input stream.
+     *
+     * <p>Any other attribute settings for the characters in the specified
+     * range are cleared.
+     *
+     * @param   low   the low end of the range.
+     * @param   hi    the high end of the range.
+     */
+    private void whitespaceChars(int low, int hi) {
+        if (low < 0)
+            low = 0;
+        if (hi >= ctype.length)
+            hi = ctype.length - 1;
+        while (low <= hi)
+            ctype[low++] = CT_WHITESPACE;
+    }
+
+    /**
+     * Specifies that matching pairs of this character delimit string
+     * constants in this tokenizer.
+     * <p>
+     * If a string quote character is encountered, then a string is
+     * recognized, consisting of all characters after (but not including)
+     * the string quote character, up to (but not including) the next
+     * occurrence of that same string quote character, or a line
+     * terminator, or end of file. The usual escape sequences such as
+     * {@code "\u005Cn"} and {@code "\u005Ct"} are recognized and
+     * converted to single characters as the string is parsed.
+     *
+     * <p>Any other attribute settings for the specified character are cleared.
+     *
+     * @param   ch   the character.
+     */
+    private void quoteChar(int ch) {
+        if (ch >= 0 && ch < ctype.length)
+            ctype[ch] = CT_QUOTE;
+    }
+
+    private int unicode2ctype(int c) {
+        switch (c) {
+            case 0x1680:
+            case 0x180E:
+            case 0x200A:
+            case 0x202F:
+            case 0x205F:
+            case 0x3000:
+                return CT_WHITESPACE;
+            default:
+                return CT_ALPHA;
+        }
+    }
+
+    /**
+     * Parses the next token of this tokenizer.
+     */
+    public void nextToken() {
+        byte ct[] = ctype;
+        int c;
+        int lctype;
+        sval = null;
+        isQuoted = false;
+
+        do {
+            c = read();
+            if (c < 0) {
+                return;
+            }
+            lctype = (c < 256) ? ct[c] : unicode2ctype(c);
+        } while (lctype == CT_WHITESPACE);
+
+        if (lctype == CT_ALPHA) {
+            int i = 0;
+            do {
+                if (i >= buf.length) {
+                    buf = Arrays.copyOf(buf, buf.length * 2);
+                }
+                buf[i++] = (char) c;
+                c = read();
+                lctype = c < 0 ? CT_WHITESPACE : (c < 256)? ct[c] : unicode2ctype(c);
+            } while (lctype == CT_ALPHA);
+            if (c >= 0) --next; // push last back
+            sval = String.copyValueOf(buf, 0, i);
+            return;
+        }
+
+        if (lctype == CT_QUOTE) {
+            int quote = c;
+            int i = 0;
+            /* Invariants (because \Octal needs a lookahead):
+             *   (i)  c contains char value
+             *   (ii) d contains the lookahead
+             */
+            int d = read();
+            while (d >= 0 && d != quote) {
+                if (d == '\\') {
+                    c = read();
+                    int first = c;   /* To allow \377, but not \477 */
+                    if (c >= '0' && c <= '7') {
+                        c = c - '0';
+                        int c2 = read();
+                        if ('0' <= c2 && c2 <= '7') {
+                            c = (c << 3) + (c2 - '0');
+                            c2 = read();
+                            if ('0' <= c2 && c2 <= '7' && first <= '3') {
+                                c = (c << 3) + (c2 - '0');
+                                d = read();
+                            } else
+                                d = c2;
+                        } else
+                          d = c2;
+                    } else {
+                        switch (c) {
+                        case 'a':
+                            c = 0x7;
+                            break;
+                        case 'b':
+                            c = '\b';
+                            break;
+                        case 'f':
+                            c = 0xC;
+                            break;
+                        case 'n':
+                            c = '\n';
+                            break;
+                        case 'r':
+                            c = '\r';
+                            break;
+                        case 't':
+                            c = '\t';
+                            break;
+                        case 'v':
+                            c = 0xB;
+                            break;
+                        }
+                        d = read();
+                    }
+                } else {
+                    c = d;
+                    d = read();
+                }
+                if (i >= buf.length) {
+                    buf = Arrays.copyOf(buf, buf.length * 2);
+                }
+                buf[i++] = (char)c;
+            }
+
+            if (d == quote) {
+                isQuoted = true;
+            }
+            sval = String.copyValueOf(buf, 0, i);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java	Tue Mar 08 11:53:35 2016 -0800
@@ -0,0 +1,1049 @@
+/*
+ * 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<String, Mode> 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<FormatCase, EnumMap<FormatAction, EnumMap<FormatWhen, String>>> cases;
+
+        // action names: add. modified, replaced, ...
+        final EnumMap<FormatAction, EnumMap<FormatWhen, String>> actions;
+
+        // resolution status description format with %s for unresolved
+        final EnumMap<FormatResolve, EnumMap<FormatWhen, String>> resolves;
+
+        // primary snippet vs update
+        final EnumMap<FormatWhen, String> whens;
+
+        // fixed map of how to get format string for a field, given a specific formatting contet
+        final EnumMap<FormatField, Function<Context, String>> 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<FormatAction, EnumMap<FormatWhen, String>> ac = new EnumMap<>(FormatAction.class);
+                cases.put(fc, ac);
+                for (FormatAction fa : FormatAction.values()) {
+                    EnumMap<FormatWhen, String> 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<FormatWhen, String> 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<FormatWhen, String> 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<FormatAction, EnumMap<FormatWhen, String>> ac = new EnumMap<>(FormatAction.class);
+                EnumMap<FormatAction, EnumMap<FormatWhen, String>> mc = m.cases.get(fc);
+                cases.put(fc, ac);
+                for (FormatAction fa : FormatAction.values()) {
+                    EnumMap<FormatWhen, String> aw = new EnumMap<>(mc.get(fa));
+                    ac.put(fa, aw);
+                }
+            }
+
+            actions = new EnumMap<>(FormatAction.class);
+            for (FormatAction fa : FormatAction.values()) {
+                EnumMap<FormatWhen, String> afw = new EnumMap<>(m.actions.get(fa));
+                actions.put(fa, afw);
+            }
+
+            resolves = new EnumMap<>(FormatResolve.class);
+            for (FormatResolve fr : FormatResolve.values()) {
+                EnumMap<FormatWhen, String> 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<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw) {
+            for (FormatCase fc : cc) {
+                EnumMap<FormatAction, EnumMap<FormatWhen, String>> ma = cases.get(fc);
+                for (FormatAction fa : ca) {
+                    EnumMap<FormatWhen, String> mw = ma.get(fa);
+                    for (FormatWhen fw : cw) {
+                        mw.put(fw, format);
+                    }
+                }
+            }
+        }
+
+        void setActions(String format, Collection<FormatAction> ca, Collection<FormatWhen> cw) {
+            for (FormatAction fa : ca) {
+                EnumMap<FormatWhen, String> mw = actions.get(fa);
+                for (FormatWhen fw : cw) {
+                    mw.put(fw, format);
+                }
+            }
+        }
+
+        void setResolves(String format, Collection<FormatResolve> cr, Collection<FormatWhen> cw) {
+            for (FormatResolve fr : cr) {
+                EnumMap<FormatWhen, String> mw = resolves.get(fr);
+                for (FormatWhen fw : cw) {
+                    mw.put(fw, format);
+                }
+            }
+        }
+
+        void setWhens(String format, Collection<FormatWhen> 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<E1 extends Enum<E1>, E2 extends Enum<E2>, E3 extends Enum<E3>> {
+
+            Set<E1> e1;
+            Set<E2> e2;
+            Set<E3> e3;
+
+            Case(Set<E1> e1, Set<E2> e2, Set<E3> e3) {
+                this.e1 = e1;
+                this.e2 = e2;
+                this.e3 = e3;
+            }
+
+            Case(Set<E1> e1, Set<E2> 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);
+        }
+
+        <E extends Enum<E>> void hardEnums(EnumSet<E> es, Function<E, String> e2s) {
+            hardPairs(es.stream(), ev -> ev.name().toLowerCase(Locale.US), e2s);
+        }
+
+        <T> void hardPairs(Stream<T> stream, Function<T, String> a, Function<T, String> 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 <mode> "<prompt>" "<continuation-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 <new-mode> [command|quiet [<old-mode>]]
+        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 <mode>
+        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 <mode> "<format>" <selector>...
+        boolean setFormat() {
+            Mode m = nextMode();
+            String format = nextFormat();
+            if (valid) {
+                List<Case<FormatCase, FormatAction, FormatWhen>> 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 <field> "<format>" <selector>...
+        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<Case<FormatAction, FormatWhen, FormatWhen>> 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<Case<FormatResolve, FormatWhen, FormatWhen>> 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<Case<FormatWhen, FormatWhen, FormatWhen>> 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<FormatCase> parseFormatCase(String[] s, int i) {
+            return parseFormatSelectorStar(s, i, FormatCase.class, EnumSet.allOf(FormatCase.class), "case");
+        }
+
+        final Set<FormatAction> parseFormatAction(String[] s, int i) {
+            return parseFormatSelectorStar(s, i, FormatAction.class,
+                    EnumSet.of(FormatAction.ADDED, FormatAction.MODIFIED, FormatAction.REPLACED), "action");
+        }
+
+        final Set<FormatResolve> parseFormatResolve(String[] s, int i) {
+            return parseFormatSelectorStar(s, i, FormatResolve.class,
+                    EnumSet.of(FormatResolve.DEFINED, FormatResolve.NOTDEFINED), "resolve");
+        }
+
+        final Set<FormatWhen> 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 <E> 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 <E extends Enum<E>> Set<E> parseFormatSelectorStar(String[] sa, int i, Class<E> klass, EnumSet<E> defaults, String label) {
+            String s = sa.length > i
+                    ? sa[i]
+                    : null;
+            if (s == null || s.isEmpty()) {
+                return defaults;
+            }
+            Set<E> set = EnumSet.noneOf(klass);
+            EnumSet<E> 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 <E> 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 extends Enum<E>> E parseFormatSelector(String s, EnumSet<E> 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 <mode> \"<format>\" <selector>...");
+            hard("");
+            hard("Where <mode> is the name of a previously defined feedback mode -- see '/help /set newmode'.");
+            hard("Where <format> 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 <selector> is the context in which the format is applied.");
+            hard("The structure of selector is: <case>[-<action>[-<when>]]");
+            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 <mode> <field> \"<format>\" <selector>...");
+            hard("");
+            hard("Where <mode> is the name of a previously defined feedback mode -- see '/set newmode'.");
+            hard("Where <field> is context-specific format to set, each with its own selector structure:");
+            hard("   action    == The action. The selector: <action>-<when>.");
+            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: <resolve>-<when>.");
+            hard("   result    == The result value.  '%%s' is the result value. No selectors.");
+            hard("   when      == The entered snippet or a resultant update. The selector: <when>");
+            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 <format> is a quoted string -- see the description specific to the field (above).");
+            hard("Where <selector> 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 <mode>");
+            hard("");
+            hard("Where <mode> 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 <new-mode> [command|quiet [<old-mode>]]");
+            hard("");
+            hard("Where <new-mode> is the name of a mode you wish to create.");
+            hard("Where <old-mode> is the name of a previously defined feedback mode.");
+            hard("If <old-mode> 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 <mode> \"<prompt>\" \"<continuation-propmt>\"");
+            hard("");
+            hard("Where <mode> is the name of a previously defined feedback mode.");
+            hard("Where <prompt> and <continuation-propmt> 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.");
+        }
+    }
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Mar 08 11:53:35 2016 -0800
@@ -1,6 +1,5 @@
-
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -35,7 +34,6 @@
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.io.Reader;
-import java.io.StreamTokenizer;
 import java.io.StringReader;
 import java.nio.charset.Charset;
 import java.nio.file.AccessDeniedException;
@@ -66,24 +64,25 @@
 
 import jdk.internal.jshell.debug.InternalDebugControl;
 import jdk.internal.jshell.tool.IOContext.InputInterruptedException;
+import jdk.jshell.DeclarationSnippet;
 import jdk.jshell.Diag;
 import jdk.jshell.EvalException;
+import jdk.jshell.ExpressionSnippet;
+import jdk.jshell.ImportSnippet;
 import jdk.jshell.JShell;
-import jdk.jshell.Snippet;
-import jdk.jshell.DeclarationSnippet;
-import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.JShell.Subscription;
 import jdk.jshell.MethodSnippet;
 import jdk.jshell.PersistentSnippet;
-import jdk.jshell.VarSnippet;
-import jdk.jshell.ExpressionSnippet;
+import jdk.jshell.Snippet;
 import jdk.jshell.Snippet.Status;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.SnippetEvent;
 import jdk.jshell.SourceCodeAnalysis;
 import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
 import jdk.jshell.SourceCodeAnalysis.Suggestion;
-import jdk.jshell.SnippetEvent;
+import jdk.jshell.TypeDeclSnippet;
 import jdk.jshell.UnresolvedReferenceException;
-import jdk.jshell.Snippet.SubKind;
-import jdk.jshell.JShell.Subscription;
+import jdk.jshell.VarSnippet;
 
 import static java.nio.file.StandardOpenOption.CREATE;
 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
@@ -94,7 +93,12 @@
 import java.util.Spliterators;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import jdk.internal.jshell.tool.Feedback.FormatAction;
+import jdk.internal.jshell.tool.Feedback.FormatCase;
+import jdk.internal.jshell.tool.Feedback.FormatResolve;
+import jdk.internal.jshell.tool.Feedback.FormatWhen;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
 import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
 
 /**
@@ -103,6 +107,7 @@
  */
 public class JShellTool {
 
+    private static final String LINE_SEP = System.getProperty("line.separator");
     private static final Pattern LINEBREAK = Pattern.compile("\\R");
     private static final Pattern HISTORY_ALL_START_FILENAME = Pattern.compile(
             "((?<cmd>(all|history|start))(\\z|\\p{javaWhitespace}+))?(?<filename>.*)");
@@ -116,6 +121,8 @@
     final PrintStream userout;
     final PrintStream usererr;
 
+    final Feedback feedback = new Feedback();
+
     /**
      * The constructor for the tool (used by tool launch via main and by test
      * harnesses to capture ins and outs.
@@ -137,6 +144,70 @@
         this.userin = userin;
         this.userout = userout;
         this.usererr = usererr;
+        initializeFeedbackModes();
+    }
+
+    /**
+     * Create the default set of feedback modes
+     */
+    final void initializeFeedbackModes() {
+        // Initialize normal feedback mode
+        cmdSet("newmode normal command");
+        cmdSet("prompt normal '\n-> ' '>> '");
+        cmdSet("field normal pre '|  '");
+        cmdSet("field normal post '%n'");
+        cmdSet("field normal errorpre '|  '");
+        cmdSet("field normal errorpost '%n'");
+        cmdSet("field normal action 'Added' added-primary");
+        cmdSet("field normal action 'Modified' modified-primary");
+        cmdSet("field normal action 'Replaced' replaced-primary");
+        cmdSet("field normal action 'Overwrote' overwrote-primary");
+        cmdSet("field normal action 'Dropped' dropped-primary");
+        cmdSet("field normal action 'Rejected' rejected-primary");
+        cmdSet("field normal action '  Update added' added-update");
+        cmdSet("field normal action '  Update modified' modified-update");
+        cmdSet("field normal action '  Update replaced' replaced-update");
+        cmdSet("field normal action '  Update overwrote' overwrote-update");
+        cmdSet("field normal action '  Update dropped' dropped-update");
+        cmdSet("field normal action '  Update rejected' rejected-update");
+        cmdSet("field normal resolve '' ok-*");
+        cmdSet("field normal resolve ', however, it cannot be invoked until%s is declared' defined-primary");
+        cmdSet("field normal resolve ', however, it cannot be referenced until%s is declared' notdefined-primary");
+        cmdSet("field normal resolve ' which cannot be invoked until%s is declared' defined-update");
+        cmdSet("field normal resolve ' which cannot be referenced until%s is declared' notdefined-update");
+        cmdSet("field normal name '%s'");
+        cmdSet("field normal type '%s'");
+        cmdSet("field normal result '%s'");
+
+        cmdSet("format normal '' *-*-*");
+
+        cmdSet("format normal '{pre}{action} class {name}{resolve}{post}' class");
+        cmdSet("format normal '{pre}{action} interface {name}{resolve}{post}' interface");
+        cmdSet("format normal '{pre}{action} enum {name}{resolve}{post}' enum");
+        cmdSet("format normal '{pre}{action} annotation interface {name}{resolve}{post}' annotation");
+
+        cmdSet("format normal '{pre}{action} method {name}({type}){resolve}{post}' method");
+
+        cmdSet("format normal '{pre}{action} variable {name} of type {type}{resolve}{post}' vardecl");
+        cmdSet("format normal '{pre}{action} variable {name} of type {type} with initial value {result}{resolve}{post}' varinit");
+        cmdSet("format normal '{pre}{action} variable {name}{resolve}{post}' vardeclrecoverable");
+        cmdSet("format normal '{pre}{action} variable {name}, reset to null{post}' varreset-*-update");
+
+        cmdSet("format normal '{pre}Expression value is: {result}{post}" +
+                "{pre}  assigned to temporary variable {name} of type {type}{post}' expression");
+        cmdSet("format normal '{pre}Variable {name} of type {type} has value {result}{post}' varvalue");
+        cmdSet("format normal '{pre}Variable {name} has been assigned the value {result}{post}' assignment");
+
+        cmdSet("feedback normal");
+
+        // Initialize off feedback mode
+        cmdSet("newmode off quiet");
+        cmdSet("prompt off '-> ' '>> '");
+        cmdSet("field off pre '|  '");
+        cmdSet("field off post '%n'");
+        cmdSet("field off errorpre '|  '");
+        cmdSet("field off errorpost '%n'");
+        cmdSet("format off '' *-*-*");
     }
 
     private IOContext input = null;
@@ -150,7 +221,6 @@
     private boolean debug = false;
     private boolean displayPrompt = true;
     public boolean testPrompt = false;
-    private Feedback feedback = Feedback.Default;
     private String cmdlineClasspath = null;
     private String cmdlineStartup = null;
     private String[] editor = null;
@@ -185,6 +255,15 @@
 
     Map<Snippet,SnippetInfo> mapSnippet;
 
+    /**
+     * Is the input/output currently interactive
+     *
+     * @return true if console
+     */
+    boolean interactive() {
+        return input != null && input.interactiveOutput();
+    }
+
     void debug(String format, Object... args) {
         if (debug) {
             cmderr.printf(format + "\n", args);
@@ -192,38 +271,98 @@
     }
 
     /**
-     * For more verbose feedback modes
+     * Base output for command output -- no pre- or post-fix
+     *
+     * @param printf format
+     * @param printf args
+     */
+    void rawout(String format, Object... args) {
+        cmdout.printf(format, args);
+    }
+
+    /**
+     * Must show command output
+     *
+     * @param format printf format
+     * @param args printf args
+     */
+    void hard(String format, Object... args) {
+        rawout(feedback.getPre() + format + feedback.getPost(), args);
+    }
+
+    /**
+     * Error command output
+     *
+     * @param format printf format
+     * @param args printf args
+     */
+    void error(String format, Object... args) {
+        rawout(feedback.getErrorPre() + format + feedback.getErrorPost(), args);
+    }
+
+    /**
+     * Optional output
+     *
      * @param format printf format
      * @param args printf args
      */
     void fluff(String format, Object... args) {
-        if (feedback() != Feedback.Off && feedback() != Feedback.Concise) {
+        if (feedback.shouldDisplayCommandFluff() && interactive()) {
             hard(format, args);
         }
     }
 
     /**
-     * For concise feedback mode only
+     * Optional output -- with embedded per- and post-fix
+     *
      * @param format printf format
      * @param args printf args
      */
-    void concise(String format, Object... args) {
-        if (feedback() == Feedback.Concise) {
-            hard(format, args);
+    void fluffRaw(String format, Object... args) {
+        if (feedback.shouldDisplayCommandFluff() && interactive()) {
+            rawout(format, args);
+        }
+    }
+
+    <T> void hardPairs(Stream<T> stream, Function<T, String> a, Function<T, String> b) {
+        Map<String, String> a2b = stream.collect(toMap(a, b,
+                (m1, m2) -> m1,
+                () -> new LinkedHashMap<>()));
+        int aLen = 0;
+        for (String av : a2b.keySet()) {
+            aLen = Math.max(aLen, av.length());
+        }
+        String format = "   %-" + aLen + "s -- %s";
+        String indentedNewLine = LINE_SEP + feedback.getPre()
+                + String.format("   %-" + (aLen + 4) + "s", "");
+        for (Entry<String, String> e : a2b.entrySet()) {
+            hard(format, e.getKey(), e.getValue().replaceAll("\n", indentedNewLine));
         }
     }
 
     /**
-     * For all feedback modes -- must show
-     * @param format printf format
-     * @param args printf args
+     * User custom feedback mode only
+     *
+     * @param fcase Event to report
+     * @param update Is this an update (rather than primary)
+     * @param fa Action
+     * @param fr Resolution status
+     * @param name Name string
+     * @param type Type string or null
+     * @param result Result value or null
+     * @param unresolved The unresolved symbols
      */
-    void hard(String format, Object... args) {
-        cmdout.printf("|  " + format + "\n", args);
+    void custom(FormatCase fcase, boolean update, FormatAction fa, FormatResolve fr,
+            String name, String type, String unresolved, String result) {
+        String format = feedback.getFormat(fcase,
+                (update ? FormatWhen.UPDATE : FormatWhen.PRIMARY), fa, fr,
+                name != null, type != null, result != null);
+        fluffRaw(format, unresolved, name, type, result);
     }
 
     /**
      * Trim whitespace off end of string
+     *
      * @param s
      * @return
      */
@@ -276,8 +415,8 @@
         }
 
         if (regenerateOnDeath) {
-            fluff("Welcome to JShell -- Version %s", version());
-            fluff("Type /help for help");
+            hard("Welcome to JShell -- Version %s", version());
+            hard("Type /help for help");
         }
 
         try {
@@ -369,14 +508,14 @@
     }
 
     private void printUsage() {
-        cmdout.printf("Usage:   jshell <options> <load files>\n");
-        cmdout.printf("where possible options include:\n");
-        cmdout.printf("  -classpath <path>          Specify where to find user class files\n");
-        cmdout.printf("  -cp <path>                 Specify where to find user class files\n");
-        cmdout.printf("  -startup <file>            One run replacement for the start-up definitions\n");
-        cmdout.printf("  -nostartup                 Do not run the start-up definitions\n");
-        cmdout.printf("  -help                      Print a synopsis of standard options\n");
-        cmdout.printf("  -version                   Version information\n");
+        rawout("Usage:   jshell <options> <load files>\n");
+        rawout("where possible options include:\n");
+        rawout("  -classpath <path>          Specify where to find user class files\n");
+        rawout("  -cp <path>                 Specify where to find user class files\n");
+        rawout("  -startup <file>            One run replacement for the start-up definitions\n");
+        rawout("  -nostartup                 Do not run the start-up definitions\n");
+        rawout("  -help                      Print a synopsis of standard options\n");
+        rawout("  -version                   Version information\n");
     }
 
     private void resetState() {
@@ -460,10 +599,8 @@
                                             ? "\u0005" //ENQ
                                             : "\u0006" //ACK
                                     : incomplete.isEmpty()
-                                            ? feedback() == Feedback.Concise
-                                                    ? "-> "
-                                                    : "\n-> "
-                                            : ">> "
+                                            ? feedback.getPrompt(currentNameSpace.tidNext())
+                                            : feedback.getContinuationPrompt(currentNameSpace.tidNext())
                     ;
                 } else {
                     prompt = "";
@@ -541,7 +678,7 @@
         Command[] candidates = findCommand(cmd, c -> c.kind.isRealCommand);
         if (candidates.length == 0) {
             if (!rerunHistoryEntryById(cmd.substring(1))) {
-                hard("No such command or snippet id: %s", cmd);
+                error("No such command or snippet id: %s", cmd);
                 fluff("Type /help for help.");
             }
         } else if (candidates.length == 1) {
@@ -552,7 +689,7 @@
                 addToReplayHistory((command.command + " " + arg).trim());
             }
         } else {
-            hard("Command: %s is ambiguous: %s", cmd, Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", ")));
+            error("Command: %s is ambiguous: %s", cmd, Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", ")));
             fluff("Type /help for help.");
         }
     }
@@ -635,45 +772,6 @@
         }
     }
 
-    class ArgTokenizer extends StreamTokenizer {
-
-        ArgTokenizer(String arg) {
-            super(new StringReader(arg));
-            resetSyntax();
-            wordChars(0x00, 0xFF);
-            quoteChar('"');
-            quoteChar('\'');
-
-            whitespaceChars(0x09, 0x0D);
-            whitespaceChars(0x1C, 0x20);
-            whitespaceChars(0x85, 0x85);
-            whitespaceChars(0xA0, 0xA0);
-            whitespaceChars(0x1680, 0x1680);
-            whitespaceChars(0x180E, 0x180E);
-            whitespaceChars(0x2000, 0x200A);
-            whitespaceChars(0x202F, 0x202F);
-            whitespaceChars(0x205F, 0x205F);
-            whitespaceChars(0x3000, 0x3000);
-        }
-
-        String next() {
-            try {
-                nextToken();
-            } catch (Throwable t) {
-                return null;
-            }
-            return sval;
-        }
-
-        String val() {
-            return sval;
-        }
-
-        boolean isQuoted() {
-            return ttype == '\'' || ttype == '"';
-        }
-    }
-
     static final class FixedCompletionProvider implements CompletionProvider {
 
         private final String[] alternatives;
@@ -801,16 +899,9 @@
                 "  -- List the snippet with the specified snippet id\n",
                 arg -> cmdList(arg),
                 editKeywordCompletion()));
-        registerCommand(new Command("/seteditor", "<command>", "set the external editor command to use",
-                "Specify the command to launch for the /edit command.\n" +
-                "The command is an operating system dependent string.\n" +
-                "The command may include space-separated arguments (such as flags).\n" +
-                "When /edit is used, temporary file to edit will be appended as the last argument.\n",
-                arg -> cmdSetEditor(arg),
-                EMPTY_COMPLETION_PROVIDER));
         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
                 "Edit a snippet or snippets of source in an external editor.\n" +
-                "The editor to use is set with /seteditor.\n" +
+                "The editor to use is set with /set editor.\n" +
                 "If no editor has been set, a simple editor will be launched.\n\n" +
                 "/edit <name>\n" +
                 "  -- Edit the snippet or snippets with the specified name (preference for active snippets)\n" +
@@ -875,7 +966,7 @@
                 "   * Start-up code is re-executed.\n" +
                 "   * The execution state is restarted.\n" +
                 "   * The classpath is cleared.\n" +
-                "Tool settings are maintained: /feedback, /prompt, and /seteditor\n" +
+                "Tool settings are maintained, as set with: /set ...\n" +
                 "Save any work before using this command\n",
                 arg -> cmdReset(),
                 EMPTY_COMPLETION_PROVIDER));
@@ -895,25 +986,6 @@
                 "  -- With the 'quiet' argument the replay is not shown.  Errors will display.\n",
                 arg -> cmdReload(arg),
                 reloadCompletion()));
-        registerCommand(new Command("/feedback", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
-                "Set the level of feedback describing the effect of commands and snippets.\n\n" +
-                "/feedback off\n" +
-                "  -- Give no feedback\n" +
-                "/feedback concise\n" +
-                "  -- Brief and generally symbolic feedback\n" +
-                "/feedback normal\n" +
-                "  -- Give a natural language description of the actions\n" +
-                "/feedback verbose\n" +
-                "  -- Like normal but with side-effects described\n" +
-                "/feedback default\n" +
-                "  -- Same as normal for user input, off for input from a file\n",
-                arg -> cmdFeedback(arg),
-                new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
-        registerCommand(new Command("/prompt", null, "toggle display of a prompt",
-                "Toggle between displaying an input prompt and not displaying a prompt.\n" +
-                "Particularly useful when pasting large amounts of text.\n",
-                arg -> cmdPrompt(),
-                EMPTY_COMPLETION_PROVIDER));
         registerCommand(new Command("/classpath", "<path>", "add a path to the classpath",
                 "Append a additional path to the classpath.\n",
                 arg -> cmdClasspath(arg),
@@ -923,10 +995,6 @@
                 "Display the history of snippet and command input since this jshell was launched.\n",
                 arg -> cmdHistory(),
                 EMPTY_COMPLETION_PROVIDER));
-        registerCommand(new Command("/setstart", "<file>", "read file and set as the new start-up definitions",
-                "The contents of the specified file become the default start-up snippets and commands.\n",
-                arg -> cmdSetStart(arg),
-                FILE_COMPLETION_PROVIDER));
         registerCommand(new Command("/debug", null, "toggle debugging of the jshell",
                 "Display debugging information for the jshelll implementation.\n" +
                 "0: Debugging off\n" +
@@ -951,6 +1019,37 @@
                 "  -- Display information about the specified help subject. Example: /help intro\n",
                 arg -> cmdHelp(arg),
                 EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/set", "editor|start|feedback|newmode|prompt|format|field ...", "set jshell configuration information",
+                "Set jshell configuration information, including:\n" +
+                "the external editor to use, the start-up definitions to use, a new feedback mode,\n" +
+                "the command prompt, the feedback mode to use, or the format of output.\n" +
+                "\n" +
+                "/set editor <command> <optional-arg>...\n" +
+                "  -- Specify the command to launch for the /edit command.\n" +
+                "     The <command> is an operating system dependent string.\n" +
+                "\n" +
+                "/set start <file>\n" +
+                "  -- The contents of the specified <file> become the default start-up snippets and commands.\n" +
+                "\n" +
+                "/set feedback <mode>\n" +
+                "  -- Set the feedback mode describing displayed feedback for entered snippets and commands.\n" +
+                "\n" +
+                "/set newmode <new-mode> [command|quiet [<old-mode>]]\n" +
+                "  -- Create a user-defined feedback mode, optionally copying from an existing mode.\n" +
+                "\n" +
+                "/set prompt <mode> \"<prompt>\" \"<continuation-prompt>\"\n" +
+                "  -- Set the displayed prompts for a given feedback mode.\n" +
+                "\n" +
+                "/set format <mode> \"<format>\" <selector>...\n" +
+                "  -- Configure a feedback mode by setting the format to use in a specified set of cases.\n" +
+                "\n" +
+                "/set field name|type|result|when|action|resolve|pre|post|errorpre|errorpost \"<format>\"  <format-case>...\n" +
+                "  -- Set the format of a field within the <format-string> of a \"/set format\" command\n" +
+                "\n" +
+                "To get more information about one of these forms, use /help with the form specified.\n" +
+                "For example:   /help /set format\n",
+                arg -> cmdSet(arg),
+                new FixedCompletionProvider("format", "field", "feedback", "prompt", "newmode", "start", "editor")));
         registerCommand(new Command("/?", "", "get information about jshell",
                 "Display information about jshell (abbreviation for /help).\n" +
                 "/?\n" +
@@ -1051,21 +1150,138 @@
 
     // --- Command implementations ---
 
-    boolean cmdSetEditor(String arg) {
-        if (arg.isEmpty()) {
-            hard("/seteditor requires a path argument");
+    private static final String[] setSub = new String[]{
+        "format", "field", "feedback", "newmode", "prompt", "editor", "start"};
+
+    // The /set command.  Currently /set format, /set field and /set feedback.
+    // Other commands will fold here, see: 8148317
+    final boolean cmdSet(String arg) {
+        ArgTokenizer at = new ArgTokenizer(arg.trim());
+        String which = setSubCommand(at);
+        if (which == null) {
             return false;
-        } else {
-            List<String> ed = new ArrayList<>();
-            ArgTokenizer at = new ArgTokenizer(arg);
-            String n;
-            while ((n = at.next()) != null) ed.add(n);
-            editor = ed.toArray(new String[ed.size()]);
-            fluff("Editor set to: %s", arg);
-            return true;
+        }
+        switch (which) {
+            case "format":
+                return feedback.setFormat(this, at);
+            case "field":
+                return feedback.setField(this, at);
+            case "feedback":
+                return feedback.setFeedback(this, at);
+            case "newmode":
+                return feedback.setNewMode(this, at);
+            case "prompt":
+                return feedback.setPrompt(this, at);
+            case "editor": {
+                String prog = at.next();
+                if (prog == null) {
+                    hard("The '/set editor' command requires a path argument");
+                    return false;
+                } else {
+                    List<String> ed = new ArrayList<>();
+                    ed.add(prog);
+                    String n;
+                    while ((n = at.next()) != null) {
+                        ed.add(n);
+                    }
+                    editor = ed.toArray(new String[ed.size()]);
+                    fluff("Editor set to: %s", arg);
+                    return true;
+                }
+            }
+            case "start": {
+                String filename = at.next();
+                if (filename == null) {
+                    hard("The '/set start' command requires a filename argument.");
+                } else {
+                    try {
+                        byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename));
+                        String init = new String(encoded);
+                        PREFS.put(STARTUP_KEY, init);
+                    } catch (AccessDeniedException e) {
+                        hard("File '%s' for /set start is not accessible.", filename);
+                        return false;
+                    } catch (NoSuchFileException e) {
+                        hard("File '%s' for /set start is not found.", filename);
+                        return false;
+                    } catch (Exception e) {
+                        hard("Exception while reading start set file: %s", e);
+                        return false;
+                    }
+                }
+                return true;
+            }
+            default:
+                hard("Error: Invalid /set argument: %s", which);
+                return false;
         }
     }
 
+    boolean printSetHelp(ArgTokenizer at) {
+        String which = setSubCommand(at);
+        if (which == null) {
+            return false;
+        }
+        switch (which) {
+            case "format":
+                feedback.printFormatHelp(this);
+                return true;
+            case "field":
+                feedback.printFieldHelp(this);
+                return true;
+            case "feedback":
+                feedback.printFeedbackHelp(this);
+                return true;
+            case "newmode":
+                feedback.printNewModeHelp(this);
+                return true;
+            case "prompt":
+                feedback.printPromptHelp(this);
+                return true;
+            case "editor":
+                hard("Specify the command to launch for the /edit command.");
+                hard("");
+                hard("/set editor <command> <optional-arg>...");
+                hard("");
+                hard("The <command> is an operating system dependent string.");
+                hard("The <command> may include space-separated arguments (such as flags) -- <optional-arg>....");
+                hard("When /edit is used, the temporary file to edit will be appended as the last argument.");
+                return true;
+            case "start":
+                hard("Set the start-up configuration -- a sequence of snippets and commands read at start-up.");
+                hard("");
+                hard("/set start <file>");
+                hard("");
+                hard("The contents of the specified <file> become the default start-up snippets and commands --");
+                hard("which are run when the jshell tool is started or reset.");
+                return true;
+            default:
+                hard("Error: Invalid /set argument: %s", which);
+                return false;
+        }
+    }
+
+    String setSubCommand(ArgTokenizer at) {
+        String[] matches = at.next(setSub);
+        if (matches == null) {
+            error("The /set command requires arguments. See: /help /set");
+            return null;
+        } else if (matches.length == 0) {
+            error("Not a valid argument to /set: %s", at.val());
+            fluff("/set is followed by one of: %s", Arrays.stream(setSub)
+                    .collect(Collectors.joining(", "))
+            );
+            return null;
+        } else if (matches.length > 1) {
+            error("Ambiguous argument to /set: %s", at.val());
+            fluff("Use one of: %s", Arrays.stream(matches)
+                    .collect(Collectors.joining(", "))
+            );
+            return null;
+        }
+        return matches[0];
+    }
+
     boolean cmdClasspath(String arg) {
         if (arg.isEmpty()) {
             hard("/classpath requires a path argument");
@@ -1137,91 +1353,50 @@
         return true;
     }
 
-    private boolean cmdFeedback(String arg) {
-        switch (arg) {
-            case "":
-            case "d":
-            case "default":
-                feedback = Feedback.Default;
-                break;
-            case "o":
-            case "off":
-                feedback = Feedback.Off;
-                break;
-            case "c":
-            case "concise":
-                feedback = Feedback.Concise;
-                break;
-            case "n":
-            case "normal":
-                feedback = Feedback.Normal;
-                break;
-            case "v":
-            case "verbose":
-                feedback = Feedback.Verbose;
-                break;
-            default:
-                hard("Follow /feedback with of the following:");
-                hard("  off       (errors and critical output only)");
-                hard("  concise");
-                hard("  normal");
-                hard("  verbose");
-                hard("  default");
-                hard("You may also use just the first letter, for example: /f c");
-                hard("In interactive mode 'default' is the same as 'normal', from a file it is the same as 'off'");
-                return false;
+    boolean cmdHelp(String arg) {
+        ArgTokenizer at = new ArgTokenizer(arg);
+        String subject = at.next();
+        if (subject != null) {
+            Command[] matches = commands.values().stream()
+                    .filter(c -> c.command.startsWith(subject))
+                    .toArray(size -> new Command[size]);
+            at.mark();
+            String sub = at.next();
+            if (sub != null && matches.length == 1 && matches[0].command.equals("/set")) {
+                at.rewind();
+                return printSetHelp(at);
+            }
+            if (matches.length > 0) {
+                for (Command c : matches) {
+                    hard("");
+                    hard("%s", c.command);
+                    hard("");
+                    hard("%s", c.help.replaceAll("\n", LINE_SEP + feedback.getPre()));
+                }
+                return true;
+            } else {
+                error("No commands or subjects start with the provided argument: %s\n\n", arg);
+            }
         }
-        fluff("Feedback mode: %s", feedback.name().toLowerCase());
-        return true;
-    }
-
-    boolean cmdHelp(String arg) {
-        if (!arg.isEmpty()) {
-            StringBuilder sb = new StringBuilder();
-            commands.values().stream()
-                    .filter(c -> c.command.startsWith(arg))
-                    .forEach(c -> {
-                        sb.append("\n");
-                        sb.append(c.command);
-                        sb.append("\n\n");
-                        sb.append(c.help);
-                        sb.append("\n");
-                    });
-            if (sb.length() > 0) {
-                cmdout.print(sb);
-                return true;
-            }
-            cmdout.printf("No commands or subjects start with the provided argument: %s\n\n", arg);
-        }
-        int synopsisLen = 0;
-        Map<String, String> synopsis2Description = new LinkedHashMap<>();
-        for (Command cmd : new LinkedHashSet<>(commands.values())) {
-            if (!cmd.kind.showInHelp)
-                continue;
-            StringBuilder synopsis = new StringBuilder();
-            synopsis.append(cmd.command);
-            if (cmd.params != null)
-                synopsis.append(" ").append(cmd.params);
-            synopsis2Description.put(synopsis.toString(), cmd.description);
-            synopsisLen = Math.max(synopsisLen, synopsis.length());
-        }
-        cmdout.println("Type a Java language expression, statement, or declaration.");
-        cmdout.println("Or type one of the following commands:\n");
-        for (Entry<String, String> e : synopsis2Description.entrySet()) {
-            cmdout.print(String.format("%-" + synopsisLen + "s", e.getKey()));
-            cmdout.print(" -- ");
-            String indentedNewLine = System.getProperty("line.separator") +
-                                     String.format("%-" + (synopsisLen + 4) + "s", "");
-            cmdout.println(e.getValue().replace("\n", indentedNewLine));
-        }
-        cmdout.println();
-        cmdout.println("For more information type '/help' followed by the name of command or a subject.");
-        cmdout.println("For example '/help /list' or '/help intro'.  Subjects:\n");
-        commands.values().stream()
-                .filter(c -> c.kind == CommandKind.HELP_SUBJECT)
-                .forEach(c -> {
-            cmdout.printf("%-12s -- %s\n", c.command, c.description);
-        });
+        hard("Type a Java language expression, statement, or declaration.");
+        hard("Or type one of the following commands:");
+        hard("");
+        hardPairs(commands.values().stream()
+                .filter(cmd -> cmd.kind.showInHelp),
+                cmd -> (cmd.params != null)
+                            ? cmd.command + " " + cmd.params
+                            : cmd.command,
+                cmd -> cmd.description
+        );
+        hard("");
+        hard("For more information type '/help' followed by the name of command or a subject.");
+        hard("For example '/help /list' or '/help intro'.  Subjects:");
+        hard("");
+        hardPairs(commands.values().stream()
+                .filter(cmd -> cmd.kind == CommandKind.HELP_SUBJECT),
+                cmd -> cmd.command,
+                cmd -> cmd.description
+        );
         return true;
     }
 
@@ -1482,13 +1657,6 @@
         return true;
     }
 
-    private boolean cmdPrompt() {
-        displayPrompt = !displayPrompt;
-        fluff("Prompt will %sdisplay. Use /prompt to toggle.", displayPrompt ? "" : "NOT ");
-        concise("Prompt: %s", displayPrompt ? "on" : "off");
-        return true;
-    }
-
     private boolean cmdReset() {
         live = false;
         fluff("Resetting state.");
@@ -1577,28 +1745,6 @@
         return true;
     }
 
-    private boolean cmdSetStart(String filename) {
-        if (filename.isEmpty()) {
-            hard("The /setstart command requires a filename argument.");
-        } else {
-            try {
-                byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename));
-                String init = new String(encoded);
-                PREFS.put(STARTUP_KEY, init);
-            } catch (AccessDeniedException e) {
-                hard("File '%s' for /setstart is not accessible.", filename);
-                return false;
-            } catch (NoSuchFileException e) {
-                hard("File '%s' for /setstart is not found.", filename);
-                return false;
-            } catch (Exception e) {
-                hard("Exception while reading start set file: %s", e);
-                return false;
-            }
-        }
-        return true;
-    }
-
     private boolean cmdVars() {
         for (VarSnippet vk : state.variables()) {
             String val = state.status(vk) == Status.VALID
@@ -1831,14 +1977,10 @@
             printDiagnostics(source, diagnostics, true);
         } else {
             // Update
-            SubKind subkind = sn.subKind();
-            if (sn instanceof DeclarationSnippet
-                    && (feedback() == Feedback.Verbose
-                    || ste.status() == Status.OVERWRITTEN
-                    || subkind == SubKind.VAR_DECLARATION_SUBKIND
-                    || subkind == SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND)) {
-                // Under the conditions, display update information
-                displayDeclarationAndValue(ste, true, null);
+            if (sn instanceof DeclarationSnippet) {
+                // display update information
+                displayDeclarationAndValue(ste, true, ste.value());
+
                 List<Diag> other = errorsOnly(diagnostics);
                 if (other.size() > 0) {
                     printDiagnostics(source, other, true);
@@ -1851,118 +1993,117 @@
     @SuppressWarnings("fallthrough")
     private void displayDeclarationAndValue(SnippetEvent ste, boolean update, String value) {
         Snippet key = ste.snippet();
-        String declared;
+        FormatAction action;
         Status status = ste.status();
         switch (status) {
             case VALID:
             case RECOVERABLE_DEFINED:
             case RECOVERABLE_NOT_DEFINED:
                 if (ste.previousStatus().isActive) {
-                    declared = ste.isSignatureChange()
-                        ? "Replaced"
-                        : "Modified";
+                    action = ste.isSignatureChange()
+                        ? FormatAction.REPLACED
+                        : FormatAction.MODIFIED;
                 } else {
-                    declared = "Added";
+                    action = FormatAction.ADDED;
                 }
                 break;
             case OVERWRITTEN:
-                declared = "Overwrote";
+                action = FormatAction.OVERWROTE;
                 break;
             case DROPPED:
-                declared = "Dropped";
+                action = FormatAction.DROPPED;
                 break;
             case REJECTED:
-                declared = "Rejected";
+                action = FormatAction.REJECTED;
                 break;
             case NONEXISTENT:
             default:
                 // Should not occur
-                declared = ste.previousStatus().toString() + "=>" + status.toString();
+                error("Unexpected status: " + ste.previousStatus().toString() + "=>" + status.toString());
+                return;
         }
-        if (update) {
-            declared = "  Update " + declared.toLowerCase();
-        }
-        String however;
+        FormatResolve resolution;
+        String unresolved;
         if (key instanceof DeclarationSnippet && (status == Status.RECOVERABLE_DEFINED || status == Status.RECOVERABLE_NOT_DEFINED)) {
-            String cannotUntil = (status == Status.RECOVERABLE_NOT_DEFINED)
-                    ? " cannot be referenced until"
-                    : " cannot be invoked until";
-            however = (update? " which" : ", however, it") + cannotUntil + unresolved((DeclarationSnippet) key);
+            resolution = (status == Status.RECOVERABLE_NOT_DEFINED)
+                    ? FormatResolve.NOTDEFINED
+                    : FormatResolve.DEFINED;
+            unresolved = unresolved((DeclarationSnippet) key);
         } else {
-            however = "";
+            resolution = FormatResolve.OK;
+            unresolved = "";
         }
         switch (key.subKind()) {
             case CLASS_SUBKIND:
-                fluff("%s class %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                custom(FormatCase.CLASS, update, action, resolution,
+                        ((TypeDeclSnippet) key).name(), null, unresolved, null);
                 break;
             case INTERFACE_SUBKIND:
-                fluff("%s interface %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                custom(FormatCase.INTERFACE, update, action, resolution,
+                        ((TypeDeclSnippet) key).name(), null, unresolved, null);
                 break;
             case ENUM_SUBKIND:
-                fluff("%s enum %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                custom(FormatCase.ENUM, update, action, resolution,
+                        ((TypeDeclSnippet) key).name(), null, unresolved, null);
                 break;
             case ANNOTATION_TYPE_SUBKIND:
-                fluff("%s annotation interface %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                custom(FormatCase.ANNOTATION, update, action, resolution,
+                        ((TypeDeclSnippet) key).name(), null, unresolved, null);
                 break;
             case METHOD_SUBKIND:
-                fluff("%s method %s(%s)%s", declared, ((MethodSnippet) key).name(),
-                        ((MethodSnippet) key).parameterTypes(), however);
+                custom(FormatCase.METHOD, update, action, resolution,
+                        ((MethodSnippet) key).name(), ((MethodSnippet) key).parameterTypes(), unresolved, null);
                 break;
             case VAR_DECLARATION_SUBKIND:
-                if (!update) {
-                    VarSnippet vk = (VarSnippet) key;
-                    if (status == Status.RECOVERABLE_NOT_DEFINED) {
-                        fluff("%s variable %s%s", declared, vk.name(), however);
-                    } else {
-                        fluff("%s variable %s of type %s%s", declared, vk.name(), vk.typeName(), however);
-                    }
-                    break;
-                }
-            // Fall through
             case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: {
                 VarSnippet vk = (VarSnippet) key;
                 if (status == Status.RECOVERABLE_NOT_DEFINED) {
-                    if (!update) {
-                        fluff("%s variable %s%s", declared, vk.name(), however);
-                        break;
-                    }
-                } else if (update) {
-                    if (ste.isSignatureChange()) {
-                        hard("%s variable %s, reset to null", declared, vk.name());
-                    }
+                    custom(FormatCase.VARDECLRECOVERABLE, update, action, resolution,
+                            vk.name(), null, unresolved, null);
+                } else if (update && ste.isSignatureChange()) {
+                    custom(FormatCase.VARRESET, update, action, resolution,
+                            vk.name(), null, unresolved, value);
+                } else if (key.subKind() == SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND) {
+                    custom(FormatCase.VARINIT, update, action, resolution,
+                            vk.name(), vk.typeName(), unresolved, value);
                 } else {
-                    fluff("%s variable %s of type %s with initial value %s",
-                            declared, vk.name(), vk.typeName(), value);
-                    concise("%s : %s", vk.name(), value);
+                    custom(FormatCase.VARDECL, update, action, resolution,
+                            vk.name(), vk.typeName(), unresolved, value);
                 }
                 break;
             }
             case TEMP_VAR_EXPRESSION_SUBKIND: {
                 VarSnippet vk = (VarSnippet) key;
-                if (update) {
-                    hard("%s temporary variable %s, reset to null", declared, vk.name());
-                 } else {
-                    fluff("Expression value is: %s", (value));
-                    fluff("  assigned to temporary variable %s of type %s", vk.name(), vk.typeName());
-                    concise("%s : %s", vk.name(), value);
-                }
+                custom(FormatCase.EXPRESSION, update, action, resolution,
+                        vk.name(), vk.typeName(), null, value);
                 break;
             }
             case OTHER_EXPRESSION_SUBKIND:
-                fluff("Expression value is: %s", (value));
+                error("Unexpected expression form -- value is: %s", (value));
                 break;
             case VAR_VALUE_SUBKIND: {
                 ExpressionSnippet ek = (ExpressionSnippet) key;
-                fluff("Variable %s of type %s has value %s", ek.name(), ek.typeName(), (value));
-                concise("%s : %s", ek.name(), value);
+                custom(FormatCase.VARVALUE, update, action, resolution,
+                        ek.name(), ek.typeName(), null, value);
                 break;
             }
             case ASSIGNMENT_SUBKIND: {
                 ExpressionSnippet ek = (ExpressionSnippet) key;
-                fluff("Variable %s has been assigned the value %s", ek.name(), (value));
-                concise("%s : %s", ek.name(), value);
+                custom(FormatCase.ASSIGNMENT, update, action, resolution,
+                        ek.name(), ek.typeName(), null, value);
                 break;
             }
+            case SINGLE_TYPE_IMPORT_SUBKIND:
+            case TYPE_IMPORT_ON_DEMAND_SUBKIND:
+            case SINGLE_STATIC_IMPORT_SUBKIND:
+            case STATIC_IMPORT_ON_DEMAND_SUBKIND:
+                custom(FormatCase.IMPORT, update, action, resolution,
+                        ((ImportSnippet) key).name(), null, null, null);
+                break;
+            case STATEMENT_SUBKIND:
+                custom(FormatCase.STATEMENT, update, action, resolution,
+                        null, null, null, null);
+                break;
         }
     }
     //where
@@ -2048,34 +2189,9 @@
                 sb.append(", ");
             }
         }
-        switch (unr.size()) {
-            case 0:
-                break;
-            case 1:
-                sb.append(" is declared");
-                break;
-            default:
-                sb.append(" are declared");
-                break;
-        }
         return sb.toString();
     }
 
-    enum Feedback {
-        Default,
-        Off,
-        Concise,
-        Normal,
-        Verbose
-    }
-
-    Feedback feedback() {
-        if (feedback == Feedback.Default) {
-            return input == null || input.interactiveOutput() ? Feedback.Normal : Feedback.Off;
-        }
-        return feedback;
-    }
-
     /** The current version number as a string.
      */
     static String version() {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java	Tue Mar 08 11:53:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -231,7 +231,7 @@
 
         // Declarations and type parameters (thus expressions)
         EXTENDS(TokenKind.EXTENDS, XEXPR|XDECL),  //  extends
-        COMMA(TokenKind.COMMA, XEXPR|XDECL|XSTART),  //  ,
+        COMMA(TokenKind.COMMA, XEXPR|XDECL),  //  ,
         AMP(TokenKind.AMP, XEXPR|XDECL),  //  &
         GT(TokenKind.GT, XEXPR|XDECL),  //  >
         LT(TokenKind.LT, XEXPR|XDECL1),  //  <
--- a/langtools/test/jdk/jshell/CommandCompletionTest.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/test/jdk/jshell/CommandCompletionTest.java	Tue Mar 08 11:53:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -50,13 +50,9 @@
 public class CommandCompletionTest extends ReplToolTesting {
 
     public void testCommand() {
-        assertCompletion("/f|", false, "/feedback ");
         assertCompletion("/deb|", false);
-        assertCompletion("/feedback v|", false, "verbose");
         assertCompletion("/c|", false, "/classes ", "/classpath ");
         assertCompletion("/h|", false, "/help ", "/history ");
-        assertCompletion("/feedback |", false,
-                "?", "concise", "default", "normal", "off", "verbose");
     }
 
     public void testList() {
@@ -108,7 +104,7 @@
 
     public void testSave() throws IOException {
         Compiler compiler = new Compiler();
-        assertCompletion("/s|", false, "/save ", "/seteditor ", "/setstart ");
+        assertCompletion("/s|", false, "/save ", "/set ");
         List<String> p1 = listFiles(Paths.get(""));
         Collections.addAll(p1, "all ", "history ", "start ");
         FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
--- a/langtools/test/jdk/jshell/CompletenessTest.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/test/jdk/jshell/CompletenessTest.java	Tue Mar 08 11:53:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -23,6 +23,7 @@
 
 /*
  * @test
+ * @bug 8149524
  * @summary Test SourceCodeAnalysis
  * @build KullaTesting TestingInputStream
  * @run testng CompletenessTest
@@ -60,6 +61,7 @@
         "try { } finally { }",
         "try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)) { }",
         "foo: while (true) { printf(\"Innn\"); break foo; }",
+        "class Case<E1 extends Enum<E1>, E2 extends Enum<E2>, E3 extends Enum<E3>> {}",
         ";",
     };
 
--- a/langtools/test/jdk/jshell/ExternalEditorTest.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/test/jdk/jshell/ExternalEditorTest.java	Tue Mar 08 11:53:35 2016 -0800
@@ -113,7 +113,7 @@
     @Override
     public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) {
         ReplTest[] t = new ReplTest[tests.length + 1];
-        t[0] = a -> assertCommandCheckOutput(a, "/seteditor " + executionScript,
+        t[0] = a -> assertCommandCheckOutput(a, "/set editor " + executionScript,
                 assertStartsWith("|  Editor set to: " + executionScript));
         System.arraycopy(tests, 0, t, 1, tests.length);
         super.testEditor(defaultStartup, args, t);
@@ -193,8 +193,8 @@
     @Test
     public void setUnknownEditor() {
         test(
-                a -> assertCommand(a, "/seteditor", "|  /seteditor requires a path argument\n"),
-                a -> assertCommand(a, "/seteditor UNKNOWN", "|  Editor set to: UNKNOWN\n"),
+                a -> assertCommand(a, "/set editor", "|  /set editor requires a path argument\n"),
+                a -> assertCommand(a, "/set editor UNKNOWN", "|  Editor set to: UNKNOWN\n"),
                 a -> assertCommand(a, "int a;", null),
                 a -> assertCommand(a, "/e 1",
                         "|  Edit Error: process IO failure: Cannot run program \"UNKNOWN\": error=2, No such file or directory\n")
@@ -204,7 +204,7 @@
     @Test(enabled = false)
     public void testRemoveTempFile() {
         test(new String[]{"-nostartup"},
-                a -> assertCommandCheckOutput(a, "/seteditor " + executionScript,
+                a -> assertCommandCheckOutput(a, "/set editor " + executionScript,
                         assertStartsWith("|  Editor set to: " + executionScript)),
                 a -> assertVariable(a, "int", "a", "0", "0"),
                 a -> assertEditOutput(a, "/e 1", assertStartsWith("|  Edit Error: Failure read edit file:"), () -> {
--- a/langtools/test/jdk/jshell/ReplToolTesting.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/test/jdk/jshell/ReplToolTesting.java	Tue Mar 08 11:53:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -397,6 +397,15 @@
         assertCommand(after, cmd, out, "", null, "", "");
     }
 
+    public void assertCommandOutputContains(boolean after, String cmd, String has) {
+        assertCommandCheckOutput(after, cmd, (s) ->
+                        assertTrue(s.contains(has), "Output: \'" + s + "' does not contain: " + has));
+    }
+
+    public void assertCommandOutputStartsWith(boolean after, String cmd, String starts) {
+        assertCommandCheckOutput(after, cmd, assertStartsWith(starts));
+    }
+
     public void assertCommandCheckOutput(boolean after, String cmd, Consumer<String> check) {
         if (!after) {
             assertCommand(false, cmd, null);
@@ -437,13 +446,13 @@
     }
 
     private List<String> computeCompletions(String code, boolean isSmart) {
-        JShellTool repl = this.repl != null ? this.repl
+        JShellTool js = this.repl != null ? this.repl
                                       : new JShellTool(null, null, null, null, null, null, null);
         int cursor =  code.indexOf('|');
         code = code.replace("|", "");
         assertTrue(cursor > -1, "'|' not found: " + code);
         List<Suggestion> completions =
-                repl.commandCompletionSuggestions(code, cursor, new int[1]); //XXX: ignoring anchor for now
+                js.commandCompletionSuggestions(code, cursor, new int[1]); //XXX: ignoring anchor for now
         return completions.stream()
                           .filter(s -> isSmart == s.isSmart)
                           .map(s -> s.continuation)
@@ -481,6 +490,15 @@
             return name.hashCode();
         }
 
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof MemberInfo) {
+                MemberInfo mi = (MemberInfo) o;
+                return name.equals(mi.name);
+            }
+            return false;
+        }
+
         public abstract Consumer<String> checkOutput();
 
         public String getSource() {
@@ -537,6 +555,11 @@
         }
 
         @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+
+        @Override
         public boolean equals(Object o) {
             if (o instanceof VariableInfo) {
                 VariableInfo v = (VariableInfo) o;
@@ -585,6 +608,10 @@
             return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s);
         }
 
+        @Override
+        public int hashCode() {
+            return (name.hashCode() << 2) ^ type.hashCode() ;
+        }
 
         @Override
         public boolean equals(Object o) {
@@ -616,6 +643,11 @@
         }
 
         @Override
+        public int hashCode() {
+            return name.hashCode() ;
+        }
+
+        @Override
         public boolean equals(Object o) {
             if (o instanceof ClassInfo) {
                 ClassInfo c = (ClassInfo) o;
@@ -641,6 +673,11 @@
         }
 
         @Override
+        public int hashCode() {
+            return (name.hashCode() << 2) ^ type.hashCode() ;
+        }
+
+        @Override
         public boolean equals(Object o) {
             if (o instanceof ImportInfo) {
                 ImportInfo i = (ImportInfo) o;
--- a/langtools/test/jdk/jshell/ToolBasicTest.java	Tue Mar 08 11:37:00 2016 -0800
+++ b/langtools/test/jdk/jshell/ToolBasicTest.java	Tue Mar 08 11:53:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886
+ * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317
  * @requires os.family != "solaris"
  * @summary Tests for Basic tests for REPL tool
  * @library /tools/lib
@@ -90,8 +90,7 @@
 
     public void elideStartUpFromList() {
         test(
-                (a) -> assertCommandCheckOutput(a, "123", (s) ->
-                        assertTrue(s.contains("type int"), s)),
+                (a) -> assertCommandOutputContains(a, "123", "type int"),
                 (a) -> assertCommandCheckOutput(a, "/list", (s) -> {
                     int cnt;
                     try (Scanner scanner = new Scanner(s)) {
@@ -112,8 +111,7 @@
         Compiler compiler = new Compiler();
         Path path = compiler.getPath("myfile");
         test(
-                (a) -> assertCommandCheckOutput(a, "123",
-                        (s) -> assertTrue(s.contains("type int"), s)),
+                (a) -> assertCommandOutputContains(a, "123", "type int"),
                 (a) -> assertCommand(a, "/save " + path.toString(), "")
         );
         try (Stream<String> lines = Files.lines(path)) {
@@ -594,12 +592,12 @@
                     (a) -> assertMethod(a, "void f() {}", "()V", "f"),
                     (a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
                     (a) -> assertCommand(a, "/save " + startUpFile.toString(), null),
-                    (a) -> assertCommand(a, "/setstart " + startUpFile.toString(), null)
+                    (a) -> assertCommand(a, "/set start " + startUpFile.toString(), null)
             );
             Path unknown = compiler.getPath("UNKNOWN");
             test(
-                    (a) -> assertCommand(a, "/setstart " + unknown.toString(),
-                            "|  File '" + unknown + "' for /setstart is not found.\n")
+                    (a) -> assertCommand(a, "/set start " + unknown.toString(),
+                            "|  File '" + unknown + "' for /set start is not found.\n")
             );
             test(false, new String[0],
                     (a) -> {
@@ -619,7 +617,7 @@
     }
 
     private void removeStartup() {
-        Preferences preferences = Preferences.userRoot().node("tool/REPL");
+        Preferences preferences = Preferences.userRoot().node("tool/JShell");
         if (preferences != null) {
             preferences.remove("STARTUP");
         }
@@ -636,7 +634,7 @@
     }
 
     public void testNoArgument() {
-        String[] commands = {"/save", "/open", "/setstart"};
+        String[] commands = {"/save", "/open", "/set start"};
         test(Stream.of(commands)
                 .map(cmd -> {
                     String c = cmd;
@@ -670,8 +668,7 @@
         test(
                 a -> assertVariable(a, "int", "x"),
                 a -> assertCommandCheckOutput(a, "/vars", assertVariables()),
-                a -> assertCommandCheckOutput(a, "System.exit(5);",  s ->
-                        assertTrue(s.contains("terminated"), s)),
+                a -> assertCommandOutputContains(a, "System.exit(5);", "terminated"),
                 a -> assertCommandCheckOutput(a, "/vars", s ->
                         assertTrue(s.trim().isEmpty(), s)),
                 a -> assertMethod(a, "void f() { }", "()void", "f"),
@@ -699,8 +696,7 @@
                         s -> assertEquals(s, "|  No definition or id named " + arg +
                                 " found.  There are no active definitions.\n")),
                 a -> assertVariable(a, "int", "aardvark"),
-                a -> assertCommandCheckOutput(a, "/list aardvark",
-                        s -> assertTrue(s.contains("aardvark"))),
+                a -> assertCommandOutputContains(a, "/list aardvark", "aardvark"),
                 a -> assertCommandCheckOutput(a, "/list start",
                         s -> checkLineToList(s, START_UP)),
                 a -> assertCommandCheckOutput(a, "/list all",
@@ -714,14 +710,14 @@
     }
 
     public void testFeedbackNegative() {
-        test(a -> assertCommandCheckOutput(a, "/feedback aaaa",
-                assertStartsWith("|  Follow /feedback with of the following")));
+        test(a -> assertCommandCheckOutput(a, "/set feedback aaaa",
+                assertStartsWith("|  Does not match any current feedback mode")));
     }
 
     public void testFeedbackOff() {
         for (String off : new String[]{"o", "off"}) {
             test(
-                    a -> assertCommand(a, "/feedback " + off, ""),
+                    a -> assertCommand(a, "/set feedback " + off, ""),
                     a -> assertCommand(a, "int a", ""),
                     a -> assertCommand(a, "void f() {}", ""),
                     a -> assertCommandCheckOutput(a, "aaaa", assertStartsWith("|  Error:")),
@@ -730,23 +726,6 @@
         }
     }
 
-    public void testFeedbackConcise() {
-        Compiler compiler = new Compiler();
-        Path testConciseFile = compiler.getPath("testConciseFeedback");
-        String[] sources = new String[] {"int a", "void f() {}", "class A {}", "a = 10"};
-        compiler.writeToFile(testConciseFile, sources);
-        for (String concise : new String[]{"c", "concise"}) {
-            test(
-                    a -> assertCommand(a, "/feedback " + concise, ""),
-                    a -> assertCommand(a, sources[0], ""),
-                    a -> assertCommand(a, sources[1], ""),
-                    a -> assertCommand(a, sources[2], ""),
-                    a -> assertCommand(a, sources[3], "|  a : 10\n"),
-                    a -> assertCommand(a, "/o " + testConciseFile.toString(), "|  a : 10\n")
-            );
-        }
-    }
-
     public void testFeedbackNormal() {
         Compiler compiler = new Compiler();
         Path testNormalFile = compiler.getPath("testConciseNormal");
@@ -759,58 +738,20 @@
                 "|  Variable a has been assigned the value 10\n"
         };
         compiler.writeToFile(testNormalFile, sources2);
-        for (String feedback : new String[]{"/f", "/feedback"}) {
-            for (String feedbackState : new String[]{"n", "normal", "v", "verbose"}) {
-                String f = null;
-                if (feedbackState.startsWith("n")) {
-                    f = "normal";
-                } else if (feedbackState.startsWith("v")) {
-                    f = "verbose";
-                }
-                final String finalF = f;
+        for (String feedback : new String[]{"/set f", "/set feedback"}) {
+            for (String feedbackState : new String[]{"n", "normal", "o", "off"}) {
                 test(
-                        a -> assertCommand(a, feedback + " " + feedbackState, "|  Feedback mode: " + finalF +"\n"),
+                        a -> assertCommand(a, feedback + " " + feedbackState, "|  Feedback mode: normal\n"),
                         a -> assertCommand(a, sources[0], output[0]),
                         a -> assertCommand(a, sources[1], output[1]),
                         a -> assertCommand(a, sources[2], output[2]),
                         a -> assertCommand(a, sources[3], output[3]),
-                        a -> assertCommand(a, "/o " + testNormalFile.toString(),
-                                "|  Modified variable a of type int\n" +
-                                "|  Modified method f()\n" +
-                                "|    Update overwrote method f()\n" +
-                                "|  Modified class A\n" +
-                                "|    Update overwrote class A\n" +
-                                "|  Variable a has been assigned the value 10\n")
+                        a -> assertCommand(a, "/o " + testNormalFile.toString(), "")
                 );
             }
         }
     }
 
-    public void testFeedbackDefault() {
-        Compiler compiler = new Compiler();
-        Path testDefaultFile = compiler.getPath("testDefaultFeedback");
-        String[] sources = new String[] {"int a", "void f() {}", "class A {}", "a = 10"};
-        String[] output = new String[] {
-                "|  Added variable a of type int\n",
-                "|  Added method f()\n",
-                "|  Added class A\n",
-                "|  Variable a has been assigned the value 10\n"
-        };
-        compiler.writeToFile(testDefaultFile, sources);
-        for (String defaultFeedback : new String[]{"", "d", "default"}) {
-            test(
-                    a -> assertCommand(a, "/feedback o", ""),
-                    a -> assertCommand(a, "int x", ""),
-                    a -> assertCommand(a, "/feedback " + defaultFeedback, "|  Feedback mode: default\n"),
-                    a -> assertCommand(a, sources[0], output[0]),
-                    a -> assertCommand(a, sources[1], output[1]),
-                    a -> assertCommand(a, sources[2], output[2]),
-                    a -> assertCommand(a, sources[3], output[3]),
-                    a -> assertCommand(a, "/o " + testDefaultFile.toString(), "")
-            );
-        }
-    }
-
     public void testDrop() {
         test(false, new String[]{"-nostartup"},
                 a -> assertVariable(a, "int", "a"),
@@ -906,7 +847,7 @@
 
     public void testCommandPrefix() {
         test(a -> assertCommandCheckOutput(a, "/s",
-                      assertStartsWith("|  Command: /s is ambiguous: /seteditor, /save, /setstart")),
+                      assertStartsWith("|  Command: /s is ambiguous: /save, /set")),
              a -> assertCommand(a, "int var", "|  Added variable var of type int\n"),
              a -> assertCommandCheckOutput(a, "/va",
                       assertStartsWith("|    int var = 0")),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolFormatTest.java	Tue Mar 08 11:53:35 2016 -0800
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8148316 8148317
+ * @summary Tests for output customization
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ToolBox Compiler
+ * @run testng ToolFormatTest
+ */
+import org.testng.annotations.Test;
+
+@Test
+public class ToolFormatTest extends ReplToolTesting {
+
+    public void testSetFormat() {
+        try {
+            test(
+                    (a) -> assertCommandOutputStartsWith(a, "/set newmode test command", "|  Created new feedback mode: test"),
+                    (a) -> assertCommand(a, "/set field test pre '$ '", ""),
+                    (a) -> assertCommand(a, "/set field test post ''", ""),
+                    (a) -> assertCommand(a, "/set field test action 'ADD ' added-primary", ""),
+                    (a) -> assertCommand(a, "/set field test action 'MOD ' modified-primary", ""),
+                    (a) -> assertCommand(a, "/set field test action 'REP ' replaced-primary", ""),
+                    (a) -> assertCommand(a, "/set field test action 'UP-ADD ' added-update", ""),
+                    (a) -> assertCommand(a, "/set field test action 'UP-MOD ' modified-update", ""),
+                    (a) -> assertCommand(a, "/set field test action 'UP-REP ' replaced-update", ""),
+                    (a) -> assertCommand(a, "/set field test resolve 'OK' ok-*", ""),
+                    (a) -> assertCommand(a, "/set field test resolve 'DEF' defined-*", ""),
+                    (a) -> assertCommand(a, "/set field test resolve 'NODEF' notdefined-*", ""),
+                    (a) -> assertCommand(a, "/set field test name ':%s ' ", ""),
+                    (a) -> assertCommand(a, "/set field test type '[%s]' ", ""),
+                    (a) -> assertCommand(a, "/set field test result '=%s ' ", ""),
+                    (a) -> assertCommand(a, "/set format test '{pre}{action}{type}{name}{result}{resolve}' *-*-*", ""),
+                    (a) -> assertCommand(a, "/set format test '{pre}HI this is enum' enum", ""),
+                    (a) -> assertCommand(a, "/set feedback test", "$ Feedback mode: test"),
+                    (a) -> assertCommand(a, "class D {}", "$ ADD :D OK"),
+                    (a) -> assertCommand(a, "void m() {}", "$ ADD []:m OK"),
+                    (a) -> assertCommand(a, "interface EX extends EEX {}", "$ ADD :EX NODEF"),
+                    (a) -> assertCommand(a, "56", "$ ADD [int]:$4 =56 OK"),
+                    (a) -> assertCommand(a, "class D { int hh; }", "$ REP :D OK$ OVERWROTE-UPDATE:D OK"),
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback normal", "|  Feedback mode: normal")
+            );
+        } finally {
+            assertCommandCheckOutput(false, "/set feedback normal", s -> {
+            });
+        }
+    }
+
+    public void testNewModeQuiet() {
+        try {
+            test(
+                    (a) -> assertCommandOutputStartsWith(a, "/set newmode nmq quiet normal", "|  Created new feedback mode: nmq"),
+                    (a) -> assertCommand(a, "/set feedback nmq", ""),
+                    (a) -> assertCommand(a, "/se ne nmq2 q nor", ""),
+                    (a) -> assertCommand(a, "/se fee nmq2", ""),
+                    (a) -> assertCommand(a, "/set newmode nmc command normal", ""),
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback nmc", "|  Feedback mode: nmc"),
+                    (a) -> assertCommandOutputStartsWith(a, "/set newmode nm", "|  Created new feedback mode: nm"),
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback nm", "|  Feedback mode: nm")
+            );
+        } finally {
+            assertCommandCheckOutput(false, "/set feedback normal", s -> {
+            });
+        }
+    }
+
+    public void testSetError() {
+        try {
+            test(
+                    (a) -> assertCommandOutputStartsWith(a, "/set newmode te command normal", "|  Created new feedback mode: te"),
+                    (a) -> assertCommand(a, "/set field te errorpre 'ERROR: '", ""),
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback te", ""),
+                    (a) -> assertCommandCheckOutput(a, "/set ", assertStartsWith("ERROR: The /set command requires arguments")),
+                    (a) -> assertCommandCheckOutput(a, "/set xyz", assertStartsWith("ERROR: Not a valid argument to /set")),
+                    (a) -> assertCommandCheckOutput(a, "/set f", assertStartsWith("ERROR: Ambiguous argument to /set")),
+                    (a) -> assertCommandCheckOutput(a, "/set feedback", assertStartsWith("ERROR: Expected a feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set feedback xyz", assertStartsWith("ERROR: Does not match any current feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set format", assertStartsWith("ERROR: Expected a feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set format xyz", assertStartsWith("ERROR: Does not match any current feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set format te", assertStartsWith("ERROR: Expected format missing")),
+                    (a) -> assertCommandCheckOutput(a, "/set format te aaa", assertStartsWith("ERROR: Format 'aaa' must be quoted")),
+                    (a) -> assertCommandCheckOutput(a, "/set format te 'aaa'", assertStartsWith("ERROR: At least one selector required")),
+                    (a) -> assertCommandCheckOutput(a, "/set format te 'aaa' frog", assertStartsWith("ERROR: Not a valid case")),
+                    (a) -> assertCommandCheckOutput(a, "/set format te 'aaa' import-frog", assertStartsWith("ERROR: Not a valid action")),
+                    (a) -> assertCommandCheckOutput(a, "/set newmode", assertStartsWith("ERROR: Expected new feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set newmode te", assertStartsWith("ERROR: Expected a new feedback mode name")),
+                    (a) -> assertCommandCheckOutput(a, "/set newmode x xyz", assertStartsWith("ERROR: Specify either 'command' or 'quiet'")),
+                    (a) -> assertCommandCheckOutput(a, "/set newmode x quiet y", assertStartsWith("ERROR: Does not match any current feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt", assertStartsWith("ERROR: Expected a feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt te", assertStartsWith("ERROR: Expected format missing")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt te aaa xyz", assertStartsWith("ERROR: Format 'aaa' must be quoted")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt te 'aaa' xyz", assertStartsWith("ERROR: Format 'xyz' must be quoted")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt", assertStartsWith("ERROR: Expected a feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt te", assertStartsWith("ERROR: Expected format missing")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt te aaa", assertStartsWith("ERROR: Format 'aaa' must be quoted")),
+                    (a) -> assertCommandCheckOutput(a, "/set prompt te 'aaa'", assertStartsWith("ERROR: Expected format missing")),
+                    (a) -> assertCommandCheckOutput(a, "/set field", assertStartsWith("ERROR: Expected a feedback mode")),
+                    (a) -> assertCommandCheckOutput(a, "/set field xyz", assertStartsWith("ERROR: Does not match any current feedback mode: xyz")),
+                    (a) -> assertCommandCheckOutput(a, "/set field te xyz", assertStartsWith("ERROR: Not a valid field: xyz, must be one of: when")),
+                    (a) -> assertCommandCheckOutput(a, "/set field te action", assertStartsWith("ERROR: Expected format missing")),
+                    (a) -> assertCommandCheckOutput(a, "/set field te action 'act'", assertStartsWith("ERROR: At least one selector required"))
+            );
+        } finally {
+            assertCommandCheckOutput(false, "/set feedback normal", s -> {
+            });
+        }
+    }
+
+    public void testSetHelp() {
+        try {
+            test(
+                    (a) -> assertCommandOutputContains(a, "/help /set", "command to launch"),
+                    (a) -> assertCommandOutputContains(a, "/help /set format", "vardecl"),
+                    (a) -> assertCommandOutputContains(a, "/hel /se for", "vardecl"),
+                    (a) -> assertCommandOutputContains(a, "/help /set editor", "temporary file")
+            );
+        } finally {
+            assertCommandCheckOutput(false, "/set feedback normal", s -> {
+            });
+        }
+    }
+
+    public void testSetHelpError() {
+        try {
+            test(
+                    (a) -> assertCommandOutputStartsWith(a, "/set newmode te command normal", "|  Created new feedback mode: te"),
+                    (a) -> assertCommand(a, "/set field te errorpre 'ERROR: '", ""),
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback te", "|  Feedback mode: te"),
+                    (a) -> assertCommandOutputContains(a, "/help /set xyz", "ERROR: Not a valid argument to /set: xyz"),
+                    (a) -> assertCommandOutputContains(a, "/help /set f", "ERROR: Ambiguous argument to /set: f")
+            );
+        } finally {
+            assertCommandCheckOutput(false, "/set feedback normal", s -> {
+            });
+        }
+    }
+}