Raw String Literals indentation management

Brian Goetz brian.goetz at oracle.com
Mon Jun 11 17:16:29 UTC 2018


OK, so let me summarize and see if I understand it:

  - there are fixed indenting methods, .indent(n), which never touch NLs;
  - the magic align() method _ignores_ blank lines now, uses the 
non-blank lines to identify a maximal common prefix to remove, and 
removes that, and then removes a leading blank line if any, and if the 
trailing line is blank, strips out all whitespace (so it ends in NL);
  - the methods align(n) are just align().indent(n).



On 6/11/2018 12:59 PM, Jim Laskey wrote:
> Some follow up. Now that we allow additional indentation to be 
> specified when using .align(n), we’re dropping the “closing delimiter 
> trick” that was initially proposed.
>
> Example:
>
>     // With the trick of allowing the line that includes the closing 
> quote to influence the indentation.
>     String a = `
>                abc
>    def
>    ghi
>        `.align();
>
> Result:
>     abc
>     def
>     ghi
>
> This was accomplished by only filtering empty lines.
>
> public String align(int n) {
>     if (isEmpty()) {
>         return "";
>     }
>     int min = lines().skip(1)
> *.filter(not(String::isEmpty))*
>  .mapToInt(String::indexOfNonWhitespace)
>                      .min()
>                      .orElse(0);
>     return indent(n - min, true);
> }
>
> By change the filter to .filter(not(String::isBlank)), we only measure 
> lines that have visible (non-white space) characters and hence ignore 
> the last line.
>
> Examples:
>
>     String a = `
>                  abc
>      def
>      ghi
>        `.align(4);
>
>     String a = `
>    abc
>                def
>                ghi
>      `.align(4);
>
>     String a = `
>  abc
>  def
>  ghi
>  `.align(4);
>
>   String a = `
>           abc
>       def
>       ghi
>  `.align(4);
>
> All produce the same result:
>     abc
>     def
>     ghi
>
> Note that the first line is skipped so that
>
>     String a = `abc
>               def
>   ghi`.align(4);
>
>     String a = `abc
>               def
>   ghi
> `.align(4);
>
> also produces the same result (not enforcing a style convention.)
>
> — Jim
>
>
>> On Jun 8, 2018, at 12:43 PM, Guy Steele <guy.steele at oracle.com 
>> <mailto:guy.steele at oracle.com>> wrote:
>>
>> I think “align” is an excellent choice of method name.
>>
>> In the doc, "The result is realigned” => "The result is then realigned” ?
>>
>> And the simplification to just `.lines(m, n)` is welcome.  If you 
>> really want to eliminate blank lines in the middle, just say 
>> `.lines(m, n).filter(not(String::isEmpty))`.
>>
>> —Guy
>>
>>> On Jun 8, 2018, at 10:36 AM, Jim Laskey <james.laskey at oracle.com 
>>> <mailto:james.laskey at oracle.com>> wrote:
>>>
>>> We’re going to try align.
>>>
>>> Example:
>>>
>>> String a = `
>>>            abc
>>>            def
>>>            ghi
>>>  `.align();
>>>
>>> Result:
>>>     abc
>>>     def
>>>     ghi
>>>
>>> Example:
>>>
>>> String b = `
>>>  abc
>>>  def
>>>  ghi
>>> `.align(4);
>>> Result:
>>> abc
>>> def
>>> ghi
>>>
>>> Example:
>>>
>>> String c = `
>>>  abc
>>>  def
>>>  ghi
>>> `.align(-1);
>>> Result:
>>> abc
>>> def
>>> ghi
>>>
>>> Implementation:
>>>
>>> /**
>>>  * When applied to a string, left justifies
>>>  * lines without loss of relative indentation. This is
>>>  * accomplished by removing an equal number of
>>>  * {@link Character#isWhitespace(int) white space} characters
>>>  * from each line so that at least one line has a non-white
>>>  * space character in the left-most position.
>>>  * The result is realigned by indenting {@code n}
>>>  * {@link Character#isWhitespace(int) white space}
>>>  * characters.
>>>  * First and last blank lines introduced to allow
>>>  * bracketing  delimiters to appear on separate source lines
>>>  * are also removed.
>>>  * <p>
>>>  * @apinote All
>>>  *  {@link Character#isWhitespace(int) white space characters},
>>>  *          including tab, are treated as a single space.
>>>  * <p>
>>>  * @apiNote The all line terminators in the result will be
>>>  *          replaced with line feed {@code "\n"} ({@code U+000A}).
>>>  *
>>>  * @param n  number of leading white space characters
>>>  *           to adjust
>>>  *
>>>  * @return string left justified
>>>  *
>>>  * @see Character#isWhitespace(int)
>>>  * @see String#indent(int)
>>>  * @see String#lines()
>>>  * @see String#lines(LinesOptions... options)
>>>  *
>>>  * @since 11
>>>  */
>>> public String align(int n) {
>>>     if (isEmpty()) {
>>>         return "";
>>>     }
>>>     int min = lines().skip(1)
>>>  .filter(not(String::isEmpty))
>>>  .mapToInt(String::indexOfNonWhitespace)
>>>                      .min()
>>>                      .orElse(0);
>>>     return indent(n - min, true);
>>> }
>>>
>>> /**
>>>  * When applied to a string, left justifies
>>>  * lines without loss of relative indentation. This is
>>>  * accomplished by removing an equal number of
>>>  * {@link Character#isWhitespace(int) white space} characters
>>>  * from each line so that at least one line has a non-white
>>>  * space character in the left-most position.
>>>  * First and last blank lines introduced to allow
>>>  * bracketing  delimiters to appear on separate source lines
>>>  * are also removed.
>>>  * <p>
>>>  * @apinote All
>>>  *  {@link Character#isWhitespace(int) white space characters},
>>>  *          including tab, are treated as a single space.
>>>  * <p>
>>>  * @apiNote The all line terminators in the result will be
>>>  *          replaced with line feed {@code "\n"} ({@code U+000A}).
>>>  *
>>>  * @return string left justified
>>>  *
>>>  * @see Character#isWhitespace(int)
>>>  * @see String#indent(int)
>>>  * @see String#lines()
>>>  * @see String#lines(LinesOptions... options)
>>>  *
>>>  * @since 11
>>>  */
>>> public String align() {
>>>     return align(0);
>>> }
>>>
>>> With lines, we’re going to drop the LinesOptions silliness and just 
>>> go with maxLeading and maxTrailing.
>>>
>>> Example:
>>>
>>>     int d = `
>>>         abc
>>>         def
>>>         ghi
>>>     `.lines().count();
>>>
>>> Result:
>>> 5
>>>
>>> Example:
>>>
>>>     int e = `
>>>         abc
>>>         def
>>>         ghi
>>>     `.lines(1, 1).count();
>>>
>>> Result:
>>> 3
>>>
>>> Example:
>>>
>>>     int f = `
>>>
>>>
>>>
>>>         abc
>>>         def
>>>         ghi
>>>
>>>
>>>
>>> `.lines(Integer.Max_VALUE,Integer.Max_VALUE).count();
>>>
>>> Result:
>>> 3
>>>
>>> Implementation:
>>>
>>> /**
>>>  * Returns a stream of substrings extracted from this string
>>>  * partitioned by line terminators.
>>>  * <p>
>>>  * Line terminators recognized are line feed
>>>  * {@code "\n"} ({@code U+000A}),
>>>  * carriage return
>>>  * {@code "\r"} ({@code U+000D})
>>>  * and a carriage return followed immediately by a line feed
>>>  * {@code "\r\n"} ({@code U+000D U+000A}).
>>>  * <p>
>>>  * The stream returned by this method contains each line of
>>>  * this string that is terminated by a line terminator except that
>>>  * the last line can either be terminated by a line terminator or the
>>>  * end of the string.
>>>  * The lines in the stream are in the order in which
>>>  * they occur in this string and do not include the line terminators
>>>  * partitioning the lines.
>>>  * <p>
>>>  * The {@code maxLeading} and {@code maxTrailing} arguments can be
>>>  * used to remove incidental blank lines from the beginning and
>>>  * end of a multi-line sequence. A value of {@code 1} will remove
>>>  * at most one blank line. A value of {@link Integer.MAX_VALUE}
>>>  * will all leading or trailing blank lines.
>>>  *
>>>  * @implNote This method provides better performance than
>>>  *           split("\R") by supplying elements lazily and
>>>  *           by faster search of new line terminators.
>>>  *
>>>  * @param  maxLeading   the maximum number of leading blank lines
>>>  *                      to remove
>>>  *
>>>  * @param  maxTrailing  the maximum number of trailing blank lines
>>>  *                      to remove
>>>  *
>>>  * @return  the stream of strings extracted from this string
>>>  *          partitioned by line terminators
>>>  *
>>>  * @throws  IllegalArgumentException if {@code maxLeading} or
>>>  *          {@code maxTrailing} is negative.
>>>  *
>>>  * @since 11
>>>  */
>>> public Stream<String> lines(int maxLeading, int maxTrailing) {
>>>     if (maxLeading < 0) {
>>>         throw new IllegalArgumentException("maxLeading is negative: 
>>> " + maxLeading);
>>>     }
>>>     if (maxTrailing < 0) {
>>>         throw new IllegalArgumentException("maxTrailing is negative: 
>>> " + maxTrailing);
>>>     }
>>>     Stream<String> stream = isLatin1() ? StringLatin1.lines(value, 
>>> maxLeading, maxTrailing)
>>>  : StringUTF16.lines(value, maxLeading, maxTrailing);
>>>     return stream;
>>> }
>>>
>>> /**
>>>  * Returns a stream of substrings extracted from this string
>>>  * partitioned by line terminators.
>>>  * <p>
>>>  * Line terminators recognized are line feed
>>>  * {@code "\n"} ({@code U+000A}),
>>>  * carriage return
>>>  * {@code "\r"} ({@code U+000D})
>>>  * and a carriage return followed immediately by a line feed
>>>  * {@code "\r\n"} ({@code U+000D U+000A}).
>>>  * <p>
>>>  * The stream returned by this method contains each line of
>>>  * this string that is terminated by a line terminator except that
>>>  * the last line can either be terminated by a line terminator or the
>>>  * end of the string.
>>>  * The lines in the stream are in the order in which
>>>  * they occur in this string and do not include the line terminators
>>>  * partitioning the lines.
>>>  *
>>>  * @implNote This method provides better performance than
>>>  *           split("\R") by supplying elements lazily and
>>>  *           by faster search of new line terminators.
>>>  *
>>>  * @return  the stream of strings extracted from this string
>>>  *          partitioned by line terminators
>>>  *
>>>  * @since 11
>>>  */
>>> public Stream<String> lines() {
>>>     return lines(0, 0);
>>> }
>>>
>>>> On Jun 7, 2018, at 2:49 PM, Guy Steele <guy.steele at oracle.com 
>>>> <mailto:guy.steele at oracle.com>> wrote:
>>>>
>>>>
>>>>> On Jun 7, 2018, at 1:48 PM, Brian Goetz <brian.goetz at oracle.com 
>>>>> <mailto:brian.goetz at oracle.com>> wrote:
>>>>>
>>>>>>
>>>>>> Part of the goal was to shorten the name to reduce the annoyance 
>>>>>> quotient. “reindent” speaks to me more than “leftMargin” (starts 
>>>>>> searching thesaurus.) The combo makes sense too. Then you start 
>>>>>> thinking in terms of indent is just reindent without the magic.
>>>>>
>>>>> reindent() is OK by me; its a little longer but it says what it 
>>>>> means, and still less fussy than trimMargin() or autoMagicReindent().
>>>>>
>>>>> undent() is shorter, but sounds more like what happens at an 
>>>>> auto-body repair shop :)
>>>>
>>>> Or redent(), which happens a week later.  :-)
>>>>
>>>
>>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20180611/34d1617e/attachment-0001.html>


More information about the amber-spec-experts mailing list