Colorized output breaks linewrapping with readline
Asked Answered
G

3

8

I'm working with colorizing some output using readline in Ruby, but I am not having any luck getting line wrapping to work properly. For example:

"\e[01;32mThis prompt is green and bold\e[00m > "

The desired result would be:

This prompt is green and bold > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

What I actually get is:

aaaaaaaaaaa is green and bold > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

If I remove the color codes, line wrapping works correctly. I know with bash, this can happen if the color codes are incorrectly terminated, but I have tried everything I can think of, including a few different gems, and the behavior is the same. It also occurs on multiple systems with different versions of Readline. This particular project is using rb-readline as opposed to C readline.

Grapple answered 10/1, 2012 at 16:23 Comment(2)
Have you tried using rubygems.org/gems/colored to color your output?Vocalize
Yeah, I tried that one as well as colorize. The output works fine, but when using it with a readline prompt, it breaks line wrapping.Grapple
M
7

I always throw this string extension in when I need to colorize strings for console. The problem in your code seems to be the terminator, there should be just one zero "\e[0m".

# encoding: utf-8
class String
    def console_red;          colorize(self, "\e[1m\e[31m");  end
    def console_dark_red;     colorize(self, "\e[31m");       end
    def console_green;        colorize(self, "\e[1m\e[32m");  end
    def console_dark_green;   colorize(self, "\e[32m");       end
    def console_yellow;       colorize(self, "\e[1m\e[33m");  end
    def console_dark_yellow;  colorize(self, "\e[33m");       end
    def console_blue;         colorize(self, "\e[1m\e[34m");  end
    def console_dark_blue;    colorize(self, "\e[34m");       end
    def console_purple;       colorize(self, "\e[1m\e[35m");  end

    def console_def;          colorize(self, "\e[1m");  end
    def console_bold;         colorize(self, "\e[1m");  end
    def console_blink;        colorize(self, "\e[5m");  end

    def colorize(text, color_code)  "#{color_code}#{text}\e[0m" end
end

puts "foo\nbar".console_dark_red
Maestricht answered 15/1, 2012 at 11:13 Comment(4)
Thanks. 2 0s or 1, doesnt matter, it does the same thing. This looks like a bug in readline to me.Grapple
Hm, you have "\e[01;32m" at the beginning I have "\e[1m\e[32m".Maestricht
Checkout this: hintsforums.macworld.com/showthread.php?t=17068 apparently you can add extra escapes to get the shell to ignore the color codes when calculating line length.Maestricht
sunkencity: Both formats are valid. Either way, I have the line wrapping problem, no matter what code/combination of codes I use. As for that article, that must be a bash thing to ignore the brackets. I tried it with this and it doesn't work, all it does is prints out the brackets. Fwiw, I'm writing a shell in Ruby, so this will allow colored prompts in the shell.Grapple
G
21

Ok, sunkencity gets the check mark because I ended up using most of his solution, but I had to modify it as follows:

# encoding: utf-8
class String
    def console_red;          colorize(self, "\001\e[1m\e[31m\002");  end
    def console_dark_red;     colorize(self, "\001\e[31m\002");       end
    def console_green;        colorize(self, "\001\e[1m\e[32m\002");  end
    def console_dark_green;   colorize(self, "\001\e[32m\002");       end
    def console_yellow;       colorize(self, "\001\e[1m\e[33m\002");  end
    def console_dark_yellow;  colorize(self, "\001\e[33m\002");       end
    def console_blue;         colorize(self, "\001\e[1m\e[34m\002");  end
    def console_dark_blue;    colorize(self, "\001\e[34m\002");       end
    def console_purple;       colorize(self, "\001\e[1m\e[35m\002");  end

    def console_def;          colorize(self, "\001\e[1m\002");  end
    def console_bold;         colorize(self, "\001\e[1m\002");  end
    def console_blink;        colorize(self, "\001\e[5m\002");  end

    def colorize(text, color_code)  "#{color_code}#{text}\001\e[0m\002" end
end

Each sequence needs to be wrapped in \001..\002 so that Readline knows to ignore non printing characters.

Grapple answered 18/1, 2012 at 19:43 Comment(1)
Excellent, thanks! Fixed a Pry prompt issue via github.com/pry/pry/issues/493#issuecomment-8799007Marsipobranch
M
7

I always throw this string extension in when I need to colorize strings for console. The problem in your code seems to be the terminator, there should be just one zero "\e[0m".

# encoding: utf-8
class String
    def console_red;          colorize(self, "\e[1m\e[31m");  end
    def console_dark_red;     colorize(self, "\e[31m");       end
    def console_green;        colorize(self, "\e[1m\e[32m");  end
    def console_dark_green;   colorize(self, "\e[32m");       end
    def console_yellow;       colorize(self, "\e[1m\e[33m");  end
    def console_dark_yellow;  colorize(self, "\e[33m");       end
    def console_blue;         colorize(self, "\e[1m\e[34m");  end
    def console_dark_blue;    colorize(self, "\e[34m");       end
    def console_purple;       colorize(self, "\e[1m\e[35m");  end

    def console_def;          colorize(self, "\e[1m");  end
    def console_bold;         colorize(self, "\e[1m");  end
    def console_blink;        colorize(self, "\e[5m");  end

    def colorize(text, color_code)  "#{color_code}#{text}\e[0m" end
end

puts "foo\nbar".console_dark_red
Maestricht answered 15/1, 2012 at 11:13 Comment(4)
Thanks. 2 0s or 1, doesnt matter, it does the same thing. This looks like a bug in readline to me.Grapple
Hm, you have "\e[01;32m" at the beginning I have "\e[1m\e[32m".Maestricht
Checkout this: hintsforums.macworld.com/showthread.php?t=17068 apparently you can add extra escapes to get the shell to ignore the color codes when calculating line length.Maestricht
sunkencity: Both formats are valid. Either way, I have the line wrapping problem, no matter what code/combination of codes I use. As for that article, that must be a bash thing to ignore the brackets. I tried it with this and it doesn't work, all it does is prints out the brackets. Fwiw, I'm writing a shell in Ruby, so this will allow colored prompts in the shell.Grapple
C
4

This problem is not ruby-specific - it occurs in bash too. If you put in a bash shell

PS1="\e[01;32mThis prompt is green and bold\e[00m > "

you will see the same result as above. But if you put in

PS1="\[\e[01;32m\]This prompt is green and bold\[\e[00m\] > "

you will get the result you wanted.

Conscientious answered 16/1, 2012 at 17:2 Comment(3)
BTW, if you're curious as to how this works: it looks like \[blah\] tells bash/readline/whatever that, for the purposes of estimating the number of characters in the line, you should ignore anything between \[ and \]. For a given terminal, the number of characters in a line is guessed/calculated/guestimated, and only pushes down to the next line once all non-ignored characters have been filled.Conscientious
I agree that this should be the fix, but when when I do this, my output turns into this: "[]This prompt is green and bold[] > " and the line wrapping is still a problem. This can be proven with the simple test script here: gist.github.com/1622119Grapple
"The bash-specific [ and ] are in fact translated to \001 and \002..." --answer from SuperUser.Kappel

© 2022 - 2024 — McMap. All rights reserved.