How to colourise user input in Python?

Viewed 186

I'm creating a command-line calculator tool, and I'd like to get it to format the user's input as they type, similar to what Fish and Powershell do (screenshots).

Currently, the only easy method for doing this that I can think of is getting characters one at a time, and then reprinting the formatted line to the screen.

# This code is probably very dodgy, 
# it's just to give you an idea what I'm thinking
# Use external getch module to get input without echoing to screen
from getch import getch 
inp = ""
while True:
    char = getch()
    if char == '\n': break
    if char == '\b': inp = inp[:-1]
    else: inp += char
    # Print with carriage return so that next input overwrites it
    print(colourFormatInput(inp) + '\r') 
# Outside loop: process input and devliver results

Whilst this would techincally work, I feel like it is a ton of manual effort, and would only become more complex if I wanted to add functionality such as using arrow keys to move the cursor's position.

Is there a simple way to get this kind of functionality without having to code all of it up myself?

3 Answers

I use the library colorama.

import colorama
import sys


def hook(tp, *args):
    if tp is KeyboardInterrupt:
        print(colorama.Fore.RESET)
        exit()


def colored_input(text: str, color):
    sys.excepthook = hook
    inp = input(text + color)
    print(colorama.Fore.RESET, end="", flush=True)
    sys.excepthook = sys.__excepthook__
    return inp


name = colored_input("What's your name? ", colorama.Fore.RED)
age = colored_input("What's your age? ", colorama.Fore.YELLOW)

print(f"Nice to meet you {name}({age})")

I use sys.excepthook to catch the KeyboardInterrupt so I can reset the color back when the user types CTRL+C, and then I set the original excepthook back (sys.__excepthook__)

enter image description here

You can do this with prompt_toolkit

here is the documentation if you need:

You can add Syntax Highlighting to input by the following as given in examples:

Adding syntax highlighting is as simple as adding a lexer. All of the Pygments lexers can be used after wrapping them in a PygmentsLexer. It is also possible to create a custom lexer by implementing the Lexer abstract base class.

from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer

text = prompt('Enter HTML: ', lexer=PygmentsLexer(HtmlLexer))
print('You said: %s' % text)

Output of the above

In the same way as above you can create custom prompt_toolkit.lexers.Lexer for calculator highlighting just like the following example. Here I create a custom helper class:

from typing import Callable
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text.base import StyleAndTextTuples
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.shortcuts import prompt
import prompt_toolkit.lexers
import re

class CustomRegexLexer(prompt_toolkit.lexers.Lexer):
    def __init__(self, regex_mapping):
        super().__init__()
        self.regex_mapping = regex_mapping

    def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
        def lex(_: int):
            line = document.text
            tokens = []
            while len(line) != 0:
                for pattern, style_string in self.regex_mapping.items():
                    match: re.Match = pattern.search(line)

                    if not match:
                        continue
                    else:
                        # print(f"found_match: {match}")
                        pass
                    match_string = line[:match.span()[1]]
                    line = line[match.span()[1]:]
                    tokens.append((style_string, match_string))
                    break
            return tokens
        return lex

Now with the above helper class implemented we can create our regex patterns and their respective styles, to learn more about what you can have in styling string go to this page

# Making regex for different operators. Make sure you add `^` anchor
# to the start of all the patterns
operators_allowed = ["+", "-", "/", "*", "(", ")", "=", "^"]
operators = re.compile("^["+''.join([f"\\{x}" for x in operators_allowed])+"]")
numbers = re.compile(r"^\d+(\.\d+)?")
text = re.compile(r"^.")


regex_mapping = {
    operators: "#ff70e5",  # Change colors according to your requirement
    numbers: "#ffa500",
    text: "#2ef5ff",
}

MyCalculatorLexer = CustomRegexLexer(regex_mapping)

With the lexers created you can now use the lexer in the function prompt:

text = prompt("Enter Equation: ", lexer=MyCalculatorLexer)

# Or

def input_maths(message):
    return prompt(message, lexer=MyCalculatorLexer)

text = input_maths("Enter Equation: ")

Here is some example output:

Example Output

And now everything works. Also do check out prompt_toolkit, you can create tons of custom things as shown in there gallery

Another example:


CGREEN = '\33[32m'
CYELLOW = '\33[33m'
CBLUE = '\33[34m'
CVIOLET = '\33[35m'
CBEIGE = '\33[36m'
CWHITE = '\33[37m'
CGREY = '\33[90m'
CRED = '\033[91m'
CYELLOW = '\33[33m'
CYELLOW2 = '\33[93m'
CEND = '\033[0m'

print(CGREEN + "This is green text" + CEND)
print(CYELLOW + "This is yellow text" + CEND)

# Another interesting example (courtesy: https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html)

for i in range(0, 16):
    for j in range(0, 16):
        code = str(i * 16 + j)
        colorCode = u"\u001b[48;5;" + code + "m"
        print(colorCode + " Color {} ".format(code) + CEND)
Related