Colored Python Prompt in Windows?
Asked Answered
W

2

0

Here's my script that currently sets up my prompts for all of my computers (whether they're Windows, Red Hat, or OS X):

import sys
import datetime
import platform

if platform.system() is 'Windows':
    tealUText   = ""
    tealText    = ""
    greenText   = ""
    defaultText = ""

else:
    tealUText   = "\001\033[4;36m\002"
    tealText    = "\001\033[0;36m\002"
    greenText   = "\001\033[0;32m\002"
    defaultText = "\001\033[0;0m\002"

class ClockPS1(object):
    def __repr__(self):
        now = datetime.datetime.now()
        clock = str(now.strftime("%H:%M:%S"))
        return tealUText + clock + greenText + " >>> " + defaultText

sys.ps1 = ClockPS1()
sys.ps2 = greenText + "         ... " + defaultText

On all systems this prints out the current time followed by the normal ">>>" prompt on the first line, and then if I have a multiline input it has the normal "..." prompt, but indented so that it aligns with the ">>>" prompt (remember that that prompt is prefixed by the current time).

Here's the question though: On every platform besides Windows, the current time prints in teal (and underlined), the prompts are in green, and whatever I type shows up in a normal color. How can I achieve this same thing in Windows? I've seen a few solutions suggested, but they rely on calling functions while the message is printing, which I don't think would work for me on account of the fact that the ps variables just call __repr__ on whatever is assigned to them, right?

(By the way, I got this time trick from here: python: display elapsed time on shell)

Wingspan answered 30/5, 2014 at 13:39 Comment(1)
Don't hardcode the color escapes. Use a module like termcolor or colorama that is more portable (the homepage of colorama shows that it is able to work on Windows, although not all properties are supported).Afternoons
W
0

It occurred to me that there's no particular reason to limit myself to only finding the current time in my PS1 class, and in fact, why return the current time as the __repr__ when instead I could just print the time and prompt as a side-effect of the __repr__ function and instead return an empty string?

So I added the following code (with proper platform checks mixed in - I'm leaving those out just so I can show the meat and potatoes of making this work on Windows):

from ctypes import *
# ... Skipped a lot of code which is the same as before...
STD_OUTPUT_HANDLE_ID = c_ulong(0xfffffff5)
windll.Kernel32.GetStdHandle.restype = c_ulong
std_output_hdl = windll.Kernel32.GetStdHandle(STD_OUTPUT_HANDLE_ID)
textText    = 11
greenText   = 10
defaultText = 15
class PS1(object):
    def __repr__(self):
        # ... Skipping a lot of code which is the same as before ...
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, tealText)
        sys.stdout.write(clock)
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, greenText)
        sys.stdout.write(" >>> ")
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, defaultText)
        return ""

So now I get the clock in teal and the prompt in green - I'd like to emphasis that THIS MUCH WORKED!

I tried to do a similar thing with PS2:

class PS2(object):
    def __repr__(self):
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, greenText)
        sys.stdout.write("         ... ")
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, defaultText)
        return ""

THIS DID NOT WORK! When I tried doing this, I found that the interpreter would instantly print out PS1 and PS2 back to back and then not display PS2 on subsequent lines. It would seem that it normally gets all of the PS# __repr__s at the very start and stores the results to display later. But since this method relies on side effects, it's exposed as the hack that it is.

So for now I'm sticking with just a normal " ... " for sys.ps2.

I'd love to hear suggestions for making those ...'s in green (without also making anything I type green) but I suspect that it may not be possible. I'll be happy to accept any answer which proves me wrong - if none arrives within 2 days I'll probably just accept this one until someone else comes up with something better.

Wingspan answered 30/5, 2014 at 16:10 Comment(1)
I spent some time digging through Python's source today. It uses a C function to convert sys.ps2 to a C string and stores that C string until it's used by PyOS_StdioReadline() - another C function - to print out the prompt to the user (using fprintf()). So you can't hijack this without modifying the interpreter's source and rebuilding it. Way too much work for way too little benefit.Wingspan
C
0

On my machine (Windows 7) Python runs in the command prompt 'terminal' when executed and as far as I'm aware you can only change the colour for all text inside that terminal, as in all the text will be the same colour.

I recall someone talking about a library called 'clint' that should support MAC, Linux and Windows terminals. It would mean adding a bit extra to your existing script though.

Chaumont answered 30/5, 2014 at 14:25 Comment(1)
I'm perfectly willing to add more to my script. I just want using Python on Windows to be as pleasant as it is on my other systems.Wingspan
W
0

It occurred to me that there's no particular reason to limit myself to only finding the current time in my PS1 class, and in fact, why return the current time as the __repr__ when instead I could just print the time and prompt as a side-effect of the __repr__ function and instead return an empty string?

So I added the following code (with proper platform checks mixed in - I'm leaving those out just so I can show the meat and potatoes of making this work on Windows):

from ctypes import *
# ... Skipped a lot of code which is the same as before...
STD_OUTPUT_HANDLE_ID = c_ulong(0xfffffff5)
windll.Kernel32.GetStdHandle.restype = c_ulong
std_output_hdl = windll.Kernel32.GetStdHandle(STD_OUTPUT_HANDLE_ID)
textText    = 11
greenText   = 10
defaultText = 15
class PS1(object):
    def __repr__(self):
        # ... Skipping a lot of code which is the same as before ...
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, tealText)
        sys.stdout.write(clock)
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, greenText)
        sys.stdout.write(" >>> ")
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, defaultText)
        return ""

So now I get the clock in teal and the prompt in green - I'd like to emphasis that THIS MUCH WORKED!

I tried to do a similar thing with PS2:

class PS2(object):
    def __repr__(self):
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, greenText)
        sys.stdout.write("         ... ")
        windll.Kernel32.SetConsoleTextAttribute(std_output_hdl, defaultText)
        return ""

THIS DID NOT WORK! When I tried doing this, I found that the interpreter would instantly print out PS1 and PS2 back to back and then not display PS2 on subsequent lines. It would seem that it normally gets all of the PS# __repr__s at the very start and stores the results to display later. But since this method relies on side effects, it's exposed as the hack that it is.

So for now I'm sticking with just a normal " ... " for sys.ps2.

I'd love to hear suggestions for making those ...'s in green (without also making anything I type green) but I suspect that it may not be possible. I'll be happy to accept any answer which proves me wrong - if none arrives within 2 days I'll probably just accept this one until someone else comes up with something better.

Wingspan answered 30/5, 2014 at 16:10 Comment(1)
I spent some time digging through Python's source today. It uses a C function to convert sys.ps2 to a C string and stores that C string until it's used by PyOS_StdioReadline() - another C function - to print out the prompt to the user (using fprintf()). So you can't hijack this without modifying the interpreter's source and rebuilding it. Way too much work for way too little benefit.Wingspan

© 2022 - 2024 — McMap. All rights reserved.