Adding a nice trim for multi-line strings in Scala

Adding a nice trim for multi-line strings in Scala
Photo by chester wade / Unsplash

As already mentioned in my PHP article on this topic, I frequently store multi-line strings in my programs. Today, let's use ascii based game art for our example.

Language version used in this article: Scala 3.6.2

The Artwork

Here is the artwork of a book that I want to display in the console:

|\\\\ ////|
| \\\V/// |
|  |~~~|  |
|  |===|  |
 \ |   | /
  \|===|/

Basic Multi-Line String

In Scala, regular strings have to be defined on a single line. For example, the following results in a compiler error:

val a = "
  Hello World
  "

To define multi-line strings in this fashion, we have to use triple quotes """. This is how that would look like:

@main
def main(): Unit = {
  val sprite = """
    |\\\\ ////|
    | \\\V/// |
    |  |~~~|  |
    |  |===|  |
     \ |   | /
      \|===|/
  """

  println(sprite)
}

The output is kind of OK, but not really:


    |\\\\ ////|
    | \\\V/// |
    |  |~~~|  |
    |  |===|  |
     \ |   | /
      \|===|/
  

We get additional blank lines at the top and bottom and hardcoded whitespace on the left, that mirrors the indentation level of the source code. For our game this is not what we want. Each sprite should render without any additional margins.

Scala Book solution

If you follow the Scala Book, you will find this solution:

val message = 
"""|Multi
   |Line
   |String""".stripMargin

It avoids blank lines by opening and closing the string on the same lines where the content starts and ends.

Additionally, the call to stripMargin on the resulting string removes whitespace left to a designated trim character on each line. By default, that trim character is |.

In my case, | is also part of the artwork itself, so I chose to use a different trim character. I picked t, but it's as good as any other character.

@main
def main(): Unit = {
  val sprite = 
  """t|\\\\ ////|
     t| \\\V/// |
     t|  |~~~|  |
     t|  |===|  |
     t \ |   | /
     t  \|===|/""".stripMargin('t')

  println(sprite)
}

Output:

|\\\\ ////|
| \\\V/// |
|  |~~~|  |
|  |===|  |
 \ |   | /
  \|===|/

It works, but I lose the ability to easily copy / paste text snippets into and out of my code 😔

Custom String Extension

When I lamented about my problem on the Scala Discord server, I got some suggestions from the very helpful community within minutes. I chose to solve it with an extension method.

In this case, I add a nice method to the Java.lang.String type:

package StringExtensions

extension (input: String) {
  def nice: String = {
    val contentLines = input
      .split("\\r?\\n")
      .drop(1)
      .dropRight(1)

    val indent = contentLines
      .filter(_.trim.nonEmpty)           
      .map(_.takeWhile(_ == ' ').length)
      .minOption                         
      .getOrElse(0)

    def trimmed(line: String): String =
      if line.length >= indent
        then line.drop(indent)
        else line

    contentLines.map(trimmed).mkString("\n")
  }
}

What it does:

  • removes one line from the top and one line from the bottom
  • finds the minimum indentation
  • cuts off excessive whitespace from all the lines

And then we can use it from another package like this:

import StringExtensions.nice

@main
def main(): Unit = {
  val sprite = """
    |\\\\ ////|
    | \\\V/// |
    |  |~~~|  |
    |  |===|  |
     \ |   | /
      \|===|/
  """.nice

  println(sprite)
}

And once again we get the proper output:

|\\\\ ////|
| \\\V/// |
|  |~~~|  |
|  |===|  |
 \ |   | /
  \|===|/

Conclusion

Getting predictable and user-friendly multi-line strings to work in Scala was bit of a challenge. But this challenge gave me a good reason to try and extend the language myself, which was surprisingly easy and a lot of fun. With my nice helper method, I think I will be very productive going forward.