fortranintel-fortran

How to print a colored text without including ANSI escape sequence in the output length?


!example
program main
implicit none

character(len=8)  :: red, reset
character(len=16) :: redO

red     = char(27) // '[31m'
reset   = char(27) // '[0m'

redO  = trim(adjustl(red // "O" // reset))
write (*,"(A)") redO
end program main

Above code for an example.

I am trying to print the letter 'O' in red using ANSI escape sequences, but the issue is that the escape sequences themselves are being counted as part of the string's length, causing unexpected spaces in the output.

So the result looks like:

"   O "

I want the output to appear as:

"O"

How can I avoid the escape sequences affecting the length of the output string?

I expected the output to be just the letter "O" printed in red, without any extra spaces or length caused by the escape sequences.


Solution

  • Firstly I assume you understand this isn't portable. Fortran doesn't say anything about ANSI escape sequences and how they will be handled will depend upon your environment. There is no guarantee of redness. That said the problem appears to be not related to the control sequences, but to how you are declaring your character variables.

    "I expected the output to be just the letter "O" printed in red, without any extra spaces or length caused by the escape sequences". You have declared red as 8 characters long. This means in Fortran it is always 8 characters long, yet you have filled only a subset of the characters - the rest will be filled with spaces, and these are the spaces you are seeing, nothing to do with any "control characters"; it can't be, your program doesn't contain anything outside standard 7 bit ASCII. And similarly for the other character variables.

    Probably the simplest way to avoid lots of careful counting is the following

    program main
    implicit none
    
    character(len=:), Allocatable :: red, reset
    character(len=:), Allocatable :: redO
    
    red     = char(27) // '[31m'
    reset   = char(27) // '[0m'
    
    redO  = trim(adjustl(red // "O" // reset))
    write (*,"(A,a)") redO, "next"
    end program main
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ gfortran i.f90
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ ./a.out
    Onext
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ ifx i.f90
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ ./a.out
    Onext
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$
    

    ( the "O" is read in both cases on my terminal) though personally in a "real" program I would prefer to count the length and declare the variables as such, having them allocatable when you don't need to them just needlessly introduces extra classes of bugs. Something like

    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ cat i2.f90
    program main
    implicit none
    
    character(len=5)  :: red
    character(len=4)  :: reset
    character(len=10) :: redO
    
    red     = char(27) // '[31m'
    reset   = char(27) // '[0m'
    
    redO  = trim(adjustl(red // "O" // reset))
    write (*,"(A,a)") redO, "next"
    end program main
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ gfortran i2.f90
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ ./a.out
    Onext
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ ifx i.f90
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$ ./a.out
    Onext
    ijb@LAPTOP-GUG8KQ9I:~/work/stack$