Styled text

The term styled text refers to text printed out to a terminal (or other ::IO) with either color or other style (e.g., bold, italic) information. This is done by adding ANSI escape sequences in a string being printed to the terminal. These escape sequences are not rendered as caracters but add the style information.

Style macros

The easiest way to add style information to a String in Term is using the dedicated macros:

using Term
println(@green "this is green")
println(@blue "and this is blue")
print("\n")
println(@bold "this is bold")
println(@underline "and this is underlined")
this is green
and this is blue

this is bold
and this is underlined

To color you can use these macros: @black, @red, @green, @yellow, @blue, @magenta, @cyan, @white, @default. While for styling you have: @bold @dim @italic @underline.

Note that the styling macros return a string, so you can combine the resulting strings as you would normally:

println(
    @green("This is green") * " and " * @red("this is red")
)
println(
    "Make your text $(@underline("stand out"))!"
)
This is green and this is red
Make your text stand out!

With these style macros you can do some simply styling, but it gets clunky when you want to go beyond adding some color. Let's say you want you text to be blue, bold and underlined; do you really need to use three macros?

Of course not, you can use the @style macro!

mytext = @style "this is my text" blue bold underline
println(mytext)
this is my text

Like the macros you already know, @style returns a string with the desired style, except that now you can specify multiple styles at once!

Markup text

The basic styling macros are great for handling simple cases where you just want to add a bit of style to a piece of text. More realistically, you might want more control about exactly which parts of your text have what style.

As a way of example, let's say you want every other word of yours string:

str="Every other word has colors!"

to have a color. You could do it with macros:

e = @red "Every"
o = "other"
w = @green "word"
h = "has"
c = @blue "colors!"

print(join((e, o, w, h, c), " "))
Every other word has colors!

but that's awful. String interpolation would also not be of much help here. Instead, it would be great if we could specify styles directly inside a normal string and let Term figure it out.

Well, that's exactly what we're going to do:

import Term: tprint
tprint(
    "[red]Every[/red] other [green]word[/green] has [blue]colors![/blue]"
)
Every other word has colors!

Woah! What just happened!! Two things happened: 1) Term styling machinery detects strings segments like "[red]Every[/red]" as meaning that the text between "[...]" and "[/...]" should be colored red and 2) tprint (short for term print) detects this style information and applies it to your text before printing.

Not bad huh? Even better, the style information inside a parentheses can be more than just color:

tprint(
    "[bold black underline on_red]So much [gold3 bold]STYLE[/gold3 bold] in this text[/bold black underline on_red]"
)
So much STYLE in this text

that's right, Term.jl can also color the background of your text (by adding on_C to your color C you set it as the background, see colors page). As you can see you can pass multiple style information tags as space separated words within the "[...]". Also, you might have noticed, Term can also handle nested style tags!

If you just want to use Term.jl's style functionality, just make sure to read the admonition below. If you're curious about what's happening under the hood, read on below!

A note on style tags

The style tags used by Term.jl have an opening "[style]" and closing "[/style]" syntax. The style is applied to everything inbetween. For "[/style]" to close "[style]" the text in the parentheses must match exactly (excuding /), up to the number and position of spaces and the words order. So:

"[red] wohoo [/red]"  # works
"[red] wohoo [/red ]" # doesn't
"[bold blue] wohoo [/bold blue]" # works
"[bold blue] wohoo [/blue bold]" # doesn't
Tip

Occasionally you can do without the closing tag:

tprint("[red]text")

Term.jl will add the closing tag to the end of the string for you. Generally though, when multiple styles are applied to the same string, it's better to be explicit in exactly where each style starts and ends.

Under the hood

If you're reading here you're curious about what exactly is happening under the hood. So let's get started. Term.jl, like rich in python, defines a simple markup language to specify the style of bits of strings. As we saw, the syntax is very simple with an opening and closing tag specifying the style and marking the start and end of the styled text.

So the first thing that needs to happen is the detection of these markup tags. This is surprisingly hard because there's so many possible combinations. You can have markup tags whose style information varies considerably, you can have nested tags, you can have tags spread across lines and you can have nested tags spread across lines:

tprint(
    """
And [blue] somehow
it [bold red] all [/bold red]
has to [green underline] always
work [/green underline] correctly [/blue]
somehow.
    """
)
And  somehow
it  all 
has to  always
work  correctly 
somehow.

All of this is taken care of by Term.markup.extract_markup which returns a vector of Term.markup.MarkupTag objects. Each of these stores the opening and close tags (as SingleTag objects), the text inbetween them as well as a reference to all MarkupTags nested in it.

Normally, you should never use extract_markup directly. Instead you can let Term.style.apply_style handle it. When passed a String, apply_style will use extract_markup to extract style information before applying it to the string. This is done one MarkupTag at the time and recursively (i.e., dealing with nested tags before moving on to the next) until no markup information is found.

After extracting style information, apply_style replaces the MarkupTag information with the appropriate ANSI escape codes. This is done by parsing the markup information (the text bewteen [...]) into a Term.style.MarkupStyle object which stores the style information. Finally, get_style_codes get the ANSI codes corresponding to the required style. So in summary:

apply_style("[red]text[/red]")

will return a string with style information

"\e[31mtext\e[39m"

which printed to the console looks like:

text