Raw String Literals indentation management

Jim Laskey james.laskey at oracle.com
Mon Jun 11 16:59:19 UTC 2018


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> 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/b639762f/attachment-0001.html>


More information about the amber-spec-experts mailing list