Information on creating a terminal menu that can be operated with arrow keys without using curses.
Since there were many unclear points, I wrote this while referencing other websites. Thanks.
It was a good study on escape sequences.
\033[
is a frequently appearing escape sequence, known as the CSI (Control Sequence Introducer), used for a wide range of purposes such as cursor movement and color changes.
\033[
is the octal notation. In hexadecimal, it is \x1b[
, and both represent the same character. (Incidentally, in decimal, it is 27, so chr(27)
+ [
is also the same)
It can also be expressed as ESC + [
.
Details about CSI can be found on the English version of Wikipedia. There doesn't seem to be a Japanese version.
https://en.wikipedia.org/wiki/ANSI_escape_code
It is necessary to update multiple drawn lines.
After drawing multiple lines, print \033[nA
(n is a number representing the number of lines) to move the cursor up and print again.
How to Output and Overwrite Multiple Lines in Python
It is necessary to accept key inputs. input()
is not suitable as it requires pressing the Enter key.
You need to use the readchar
library or read using tty
.
Note that readchar uses tty internally.
However, it did not work in the terminal within PyCharm. It worked without issues in iTerm.
Python Console Single Character Input - Emotion Explorer
Detecting Key Inputs in Python (tty) - Qiita
Changing the text color makes it easier to see. Use escape sequences to change the color.
[python] Try Adding Color to Print Statements – Nomura Mathematics Institute
#!/usr/bin/env python3
import readchar
menu = [
'Arcturus',
'Betelgeuse',
'Capella',
'Deneb',
'Eltanin',
'Fomalhaut',
'Gacrux',
]
class Color:
BLACK = '\033[30m' # (Text) Black
RED = '\033[31m' # (Text) Red
GREEN = '\033[32m' # (Text) Green
YELLOW = '\033[33m' # (Text) Yellow
BLUE = '\033[34m' # (Text) Blue
MAGENTA = '\033[35m' # (Text) Magenta
CYAN = '\033[36m' # (Text) Cyan
WHITE = '\033[37m' # (Text) White
COLOR_DEFAULT = '\033[39m' # Reset text color to default
BOLD = '\033[1m' # Bold text
UNDERLINE = '\033[4m' # Underline text
INVISIBLE = '\033[08m' # Invisible text
REVERCE = '\033[07m' # Reverse text and background color
BG_BLACK = '\033[40m' # (Background) Black
BG_RED = '\033[41m' # (Background) Red
BG_GREEN = '\033[42m' # (Background) Green
BG_YELLOW = '\033[43m' # (Background) Yellow
BG_BLUE = '\033[44m' # (Background) Blue
BG_MAGENTA = '\033[45m' # (Background) Magenta
BG_CYAN = '\033[46m' # (Background) Cyan
BG_WHITE = '\033[47m' # (Background) White
BG_DEFAULT = '\033[49m' # Reset background color to default
RESET = '\033[0m' # Reset all
def tint_color(text):
return f'{Color.BG_CYAN}{Color.BLACK}{text}{Color.RESET}'
def help_color(text):
return f'{Color.GREEN}{text}{Color.RESET}'
def strong_color(text):
return f'{Color.YELLOW}{text}{Color.RESET}'
def main():
current = 0
c = ''
while True:
for i, item in enumerate(menu):
if current == i:
print(tint_color(f'> {item}'))
else:
print(f' {item}')
print(help_color(f'c={repr(c)}, current={current}'))
c = readchar.readkey()
if c == 'q':
# Exit with q
break
elif c == '\r':
# Confirm with Enter
print(f'selected:{strong_color(menu[current])}')
break
if c == 'j' or c == '\x1b[B':
# Move cursor with j or ↓
if current < len(menu) - 1:
current += 1
if c == 'k' or c == '\x1b[A':
# Move cursor with k or ↑
if current > 0:
current -= 1
print(f'\033[{len(menu) + 1}A', end='')
if __name__ == '__main__':
main()
Comments