Kotlin string concatenation - preserving indents with multiline strings

Viewed 1467

Kotlin's multiline string handling is wonderful in so many ways, and it's ability to .trimIndent() allows you to keep a string literal indented with the rest of the code like so:

fun main(args: Array<String>) {
    val myString = """
    hello
    world
""".trimIndent()

println(myString)

Will print out:

hello
world

without the literal indents present in the code. But this breaks down when using Kotlin's powerful string templating system if the value being inserted is multiline:

fun main(args: Array<String>) {
    val myString = """
    hello
    world
""".trimIndent()
    
    println("""
        teststring2
        $myString
    """.trimIndent())
}

will print out

            teststring2
            hello
world

This appears to happen because the world is on a new line and doesn't receive the indentation that hello gets. Is there an elegant way to handle this?

2 Answers

Here's the first string literal.  (To avoid confusion, I'll use underscores to represent spaces.)

____hello
____world

Its trimIndent() call will remove the spaces, leaving it with no indentation, like this:

hello
world

The second string looks like this:

________teststring2
________$myString

Then the $myString will be substituted precisely with the contents of the first:

________teststring2
________hello
world

Note that the indentation that was before $myString remains, giving the spaces you see before the "hello"; but there's no indentation within myString, so you don't see any before "world".

Now, trimIndent() removes “a common minimal indent of all the input lines”.  But there's no common indent here, because of the last line.  So the second trimIndent() call does nothing.

As to what to do, there are probably several options.  If you don't want any indents, then the most obvious would be to remove all indents, not just common ones.  (I don't think there's a dedicated function in the standard library, but it should be easy enough to write one.  For example, you could do a regular expression replace of "^ *" with "".)

This is a common problem in practice, not trimIndent()'s problem. @gidds has explained it very clearly. If you want to achieve what you want, you can use trimMargin().

fun main(args: Array<String>) {
    val myString = """
    hello
    world
""".trimIndent()
    
    println("""
        |teststring2
        |$myString
    """.trimMargin())
}

Output:

teststring2
hello
world
Related