8154812: jshell tool: value printing truncation
authorrfield
Mon, 16 May 2016 21:25:44 -0700
changeset 38513 0ae85633d035
parent 38512 c71e1cdd6674
child 38514 f7df9ab653b0
8154812: jshell tool: value printing truncation Reviewed-by: vromero
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/internal/jshell/tool/resources/l10n.properties
langtools/test/jdk/jshell/ToolFormatTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java	Mon May 16 14:51:17 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java	Mon May 16 21:25:44 2016 -0700
@@ -47,6 +47,9 @@
     // Patern for substituted fields within a customized format string
     private static final Pattern FIELD_PATTERN = Pattern.compile("\\{(.*?)\\}");
 
+    // Internal field name for truncation length
+    private static final String TRUNCATION_FIELD = "<truncation>";
+
     // Current mode
     private Mode mode = new Mode("", false); // initial value placeholder during start-up
 
@@ -103,6 +106,10 @@
         return new Setter(messageHandler, at).setFormat();
     }
 
+    public boolean setTruncation(MessageHandler messageHandler, ArgTokenizer at) {
+        return new Setter(messageHandler, at).setTruncation();
+    }
+
     public boolean setNewMode(MessageHandler messageHandler, ArgTokenizer at) {
         return new Setter(messageHandler, at).setNewMode();
     }
@@ -251,13 +258,42 @@
             return sb.toString();
         }
 
+        // Compute the display output given full context and values
         String format(FormatCase fc, FormatAction fa, FormatWhen fw,
                     FormatResolve fr, FormatUnresolved fu, FormatErrors fe,
                     String name, String type, String value, String unresolved, List<String> errorLines) {
+            // Convert the context into a bit representation used as selectors for store field formats
             long bits = bits(fc, fa, fw, fr, fu, fe);
             String fname = name==null? "" : name;
             String ftype = type==null? "" : type;
-            String fvalue = value==null? "" : value;
+            // Compute the representation of value
+            String fvalue;
+            if (value==null) {
+                fvalue = "";
+            } else {
+                // Retrieve the truncation length
+                String truncField = format(TRUNCATION_FIELD, bits);
+                if (truncField.isEmpty()) {
+                    // No truncation set, use whole value
+                    fvalue = value;
+                } else {
+                    // Convert truncation length to int
+                    // this is safe since it has been tested before it is set
+                    int trunc = Integer.parseUnsignedInt(truncField);
+                    if (value.length() > trunc) {
+                        if (trunc <= 5) {
+                            // Very short truncations have no room for "..."
+                            fvalue = value.substring(0, trunc);
+                        } else {
+                            // Normal truncation, make total length equal truncation length
+                            fvalue = value.substring(0, trunc - 4) + " ...";
+                        }
+                    } else {
+                        // Within truncation length, use whole value
+                        fvalue = value;
+                    }
+                }
+            }
             String funresolved = unresolved==null? "" : unresolved;
             String errors = errorLines.stream()
                     .map(el -> String.format(
@@ -619,7 +655,32 @@
                 errorat("jshell.err.feedback.expected.field");
                 valid = false;
             }
-            String format = valid? nextFormat() : null;
+            String format = valid ? nextFormat() : null;
+            return installFormat(m, field, format, "/help /set format");
+        }
+
+        // For /set truncation <mode> <length> <selector>...
+        boolean setTruncation() {
+            Mode m = nextMode();
+            String length = at.next();
+            if (length == null) {
+                errorat("jshell.err.truncation.expected.length");
+                valid = false;
+            } else {
+                try {
+                    // Assure that integer format is correct
+                    Integer.parseUnsignedInt(length);
+                } catch (NumberFormatException ex) {
+                    errorat("jshell.err.truncation.length.not.integer", length);
+                    valid = false;
+                }
+            }
+            // install length into an internal format field
+            return installFormat(m, TRUNCATION_FIELD, length, "/help /set truncation");
+        }
+
+        // install the format of a field under parsed selectors
+        boolean installFormat(Mode m, String field, String format, String help) {
             String slRaw;
             List<SelectorList> slList = new ArrayList<>();
             while (valid && (slRaw = at.next()) != null) {
@@ -629,8 +690,10 @@
             }
             if (valid) {
                 if (slList.isEmpty()) {
+                    // No selectors specified, then always the format
                     m.set(field, ALWAYS, format);
                 } else {
+                    // Set the format of the field for specified selector
                     slList.stream()
                             .forEach(sl -> m.set(field,
                                 sl.cases.getSet(), sl.actions.getSet(), sl.whens.getSet(),
@@ -638,7 +701,7 @@
                                 format));
                 }
             } else {
-                fluffmsg("jshell.msg.see", "/help /set format");
+                fluffmsg("jshell.msg.see", help);
             }
             return valid;
         }
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Mon May 16 14:51:17 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Mon May 16 21:25:44 2016 -0700
@@ -1019,7 +1019,7 @@
                 EMPTY_COMPLETION_PROVIDER));
         registerCommand(new Command("/set",
                 arg -> cmdSet(arg),
-                new FixedCompletionProvider("format", "feedback", "prompt", "newmode", "start", "editor")));
+                new FixedCompletionProvider(SET_SUBCOMMANDS)));
         registerCommand(new Command("/?",
                 "help.quest",
                 arg -> cmdHelp(arg),
@@ -1094,7 +1094,7 @@
     // --- Command implementations ---
 
     private static final String[] SET_SUBCOMMANDS = new String[]{
-        "format", "feedback", "newmode", "prompt", "editor", "start"};
+        "format", "truncation", "feedback", "newmode", "prompt", "editor", "start"};
 
     final boolean cmdSet(String arg) {
         ArgTokenizer at = new ArgTokenizer("/set ", arg.trim());
@@ -1105,6 +1105,8 @@
         switch (which) {
             case "format":
                 return feedback.setFormat(this, at);
+            case "truncation":
+                return feedback.setTruncation(this, at);
             case "feedback":
                 return feedback.setFeedback(this, at);
             case "newmode":
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Mon May 16 14:51:17 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Mon May 16 21:25:44 2016 -0700
@@ -117,6 +117,9 @@
 jshell.msg.feedback.mode = Feedback mode: {0}
 jshell.msg.feedback.mode.following = The feedback mode should be one of the following:
 
+jshell.err.truncation.expected.length = Expected truncation length -- {0}
+jshell.err.truncation.length.not.integer = Truncation length must be an integer: {0} -- {1}
+
 jshell.console.see.more = <press tab to see more>
 jshell.console.do.nothing = Do nothing
 jshell.console.choice = Choice: \
@@ -318,6 +321,8 @@
      Create a user-defined feedback mode, optionally copying from an existing mode.\n\n\
 /set prompt <mode> "<prompt>" "<continuation-prompt>"\n\t\
      Set the displayed prompts for a given feedback mode.\n\n\
+/set truncation <mode> <length> <selector>...\n\t\
+     Set the maximum length of a displayed value\n\t\
 /set format <mode> <field> "<format>" <selector>...\n\t\
      Configure a feedback mode by setting the format of a field when the selector matchs.\n\n\
 To get more information about one of these forms, use /help with the form specified.\n\
@@ -462,6 +467,37 @@
 /set format myformat display '{pre}{action} variable {name}, reset to null{post}' replaced-vardecl,varinit-ok-update\n\n\
 Note that subsequent selectors for a field may overwrite some or all of previous used selectors -- last one wins\n
 
+help.set.truncation = \
+Set the max length a displayed value.\n\
+\n\t\
+/set truncation <mode> <length> <selector>...\n\
+\n\
+Where <mode> is the name of a previously defined feedback mode -- see '/help /set newmode'.\n\
+Where <length> is an unsigned integer representing a maximum length.\n\
+Where <format> is a quoted string which will be the value of the field if one of\n\
+Where <selector> is only needed if you wish to fine-tune value truncation length\n\
+by context, <selector> is the context in which the truncation is applied.\n\
+The structure of selector is a hyphen separated list of selector kind lists.\n\
+A selector kind list is a comma separated list of values of one selector kind.\n\
+A selector matches if each selector kind list matches; A selector kind list\n\
+matches if one of the values matches.\n\n\
+Below are the relevant selector kinds for truncation.\n\n\
+The case selector kind describes the kind of snippet.  The values are:\n\t\
+   vardecl    -- variable declaration without init\n\t\
+   varinit    -- variable declaration with init\n\t\
+   expression -- expression -- note: {name}==scratch-variable-name\n\t\
+   varvalue   -- variable value expression\n\t\
+   assignment -- assign variable\n\t\
+The action selector kind describes what happened to the snippet.  The values are:\n\t\
+   added     -- snippet has been added\n\t\
+   modified  -- an existing snippet has been modified\n\t\
+   replaced  -- an existing snippet has been replaced with a new snippet\n\
+Examples:\n\t\
+/set trunc mymode 80\n\t\
+/set truncation mymode 45 expression\n\t\
+/set truncation mymode 0 vardecl-modified,replaced\n\n\
+Note that subsequent selectors for a field may overwrite some or all of previous used selectors -- last one wins\n
+
 help.set.feedback = \
 Set the feedback mode describing displayed feedback for entered snippets and commands.\n\
 \n\t\
@@ -578,6 +614,9 @@
 /set format verbose display '{pre}attempted to use {typeKind} {name}{resolve}{post}'         used-class,interface,enum,annotation    \n\
 /set format verbose display '{pre}attempted to call method {name}({type}){resolve}{post}'    used-method    \n\
 \n\
+/set truncation verbose 80\n\
+/set truncation verbose 500                                                                  varvalue\n\
+\n\
 /set newmode normal command verbose    \n\
 /set format normal display ''                                                               added,modified,replaced,overwrote,dropped-update    \n\
 /set format normal display '{pre}{action} variable {name}, reset to null{post}'             replaced-vardecl,varinit-ok-update    \n\
--- a/langtools/test/jdk/jshell/ToolFormatTest.java	Mon May 16 14:51:17 2016 +0100
+++ b/langtools/test/jdk/jshell/ToolFormatTest.java	Mon May 16 21:25:44 2016 -0700
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8148316 8148317 8151755 8152246 8153551
+ * @bug 8148316 8148317 8151755 8152246 8153551 8154812
  * @summary Tests for output customization
  * @library /tools/lib
  * @modules jdk.compiler/com.sun.tools.javac.api
@@ -155,6 +155,29 @@
         }
     }
 
+    public void testSetTruncation() {
+        try {
+            test(
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback normal", ""),
+                    (a) -> assertCommand(a, "String s = java.util.stream.IntStream.range(65, 74)"+
+                            ".mapToObj(i -> \"\"+(char)i).reduce((a,b) -> a + b + a).get()",
+                            "s ==> \"ABACABADABACABAEABACABADABACABAFABACABADABACABAEABACABADABACABAGABACABADABA ..."),
+                    (a) -> assertCommandOutputStartsWith(a, "/set newmode test quiet", ""),
+                    (a) -> assertCommandOutputStartsWith(a, "/set feedback test", ""),
+                    (a) -> assertCommand(a, "/set format test display '{type}:{value}' primary", ""),
+                    (a) -> assertCommand(a, "/set truncation test 20", ""),
+                    (a) -> assertCommand(a, "/set trunc test 10 varvalue", ""),
+                    (a) -> assertCommand(a, "/set trunc test 3 assignment", ""),
+                    (a) -> assertCommand(a, "String r = s", "String:\"ABACABADABACABA ..."),
+                    (a) -> assertCommand(a, "r", "String:\"ABACA ..."),
+                    (a) -> assertCommand(a, "r=s", "String:\"AB")
+            );
+        } finally {
+            assertCommandCheckOutput(false, "/set feedback normal", s -> {
+            });
+        }
+    }
+
     public void testShowFeedbackModes() {
         test(
                 (a) -> assertCommandOutputContains(a, "/set feedback", "normal")
@@ -226,6 +249,12 @@
                             "ERROR: Selector kind in multiple sections of"),
                     (a) -> assertCommandOutputStartsWith(a, "/set format te fld 'aaa' import,added",
                             "ERROR: Different selector kinds in same sections of"),
+                    (a) -> assertCommandOutputStartsWith(a, "/set trunc te 20x",
+                            "ERROR: Truncation length must be an integer: 20x"),
+                    (a) -> assertCommandOutputStartsWith(a, "/set trunc te",
+                            "ERROR: Expected truncation length"),
+                    (a) -> assertCommandOutputStartsWith(a, "/set truncation te 111 import,added",
+                            "ERROR: Different selector kinds in same sections of"),
                     (a) -> assertCommandOutputStartsWith(a, "/set newmode",
                             "ERROR: Expected new feedback mode"),
                     (a) -> assertCommandOutputStartsWith(a, "/set newmode te",