Python で矢印キーで操作するターミナルメニューを作る (エスケープシーケンスを駆使する)

投稿者: ytyng 1年, 8ヶ月 前

curses を使わずに、矢印キーで操作できるターミナルメニューを作る場合の情報。

不明点が多かったので、他のサイトを参考にしながら書きました。感謝。

エスケープシーケンスの勉強になりました。

CSI

\033[ というエスケープシーケンスが多く出てきますが、これは「 CSI (Control Sequence Introducer)」とよばれるシーケンスで、カーソルの移動や色の変更など、広い用途に使われます。

\033[ は8進数表記の場合です。16進表記だと \x1b[ となり、両者は同じ文字です。(ちなみに10進だと27 なので chr(27) + [ としても同じです )

ESC + [ とも表現します。

CSIについては、英語版 Wikipedia に詳しく記載されています。日本語版は無いようです。

https://en.wikipedia.org/wiki/ANSI_escape_code

描画

描画した複数行を更新する必要がある。

複数行を描画したあと、\033[nA (nは数字。行数) をプリントしてカーソルを上に移動し、再び print する。

参考サイト

pythonで複数行の標準出力を上書きしながら出力する方法

キー操作

キー入力を受け付ける必要がある。input() だと Enter キーの入力をしなければいけないのでだめ。

readchar ライブラリを使うか、tty を使って読み取る必要がある。

readchar も中で tty を使っている。

ただし、PyCharm の中のターミナルでは動作しなかった。iTerm では問題なく動作した。

参考サイト

pythonでコンソールから1文字入力 - Emotion Explorer

pythonでキー入力を検出する(tty) - Qiita

テキストの色を変えると見やすい。エスケープシーケンスで色を変える。

参考サイト

[python]print文で色をつけてみよう – 野村数学研究所

コード

#!/usr/bin/env python3

import readchar

menu = [
'Arcturus',
'Betelgeuse',
'Capella',
'Deneb',
'Eltanin',
'Fomalhaut',
'Gacrux',
]


class Color:
BLACK = '\033[30m' # (文字)黒
RED = '\033[31m' # (文字)赤
GREEN = '\033[32m' # (文字)緑
YELLOW = '\033[33m' # (文字)黄
BLUE = '\033[34m' # (文字)青
MAGENTA = '\033[35m' # (文字)マゼンタ
CYAN = '\033[36m' # (文字)シアン
WHITE = '\033[37m' # (文字)白
COLOR_DEFAULT = '\033[39m' # 文字色をデフォルトに戻す
BOLD = '\033[1m' # 太字
UNDERLINE = '\033[4m' # 下線
INVISIBLE = '\033[08m' # 不可視
REVERCE = '\033[07m' # 文字色と背景色を反転
BG_BLACK = '\033[40m' # (背景)黒
BG_RED = '\033[41m' # (背景)赤
BG_GREEN = '\033[42m' # (背景)緑
BG_YELLOW = '\033[43m' # (背景)黄
BG_BLUE = '\033[44m' # (背景)青
BG_MAGENTA = '\033[45m' # (背景)マゼンタ
BG_CYAN = '\033[46m' # (背景)シアン
BG_WHITE = '\033[47m' # (背景)白
BG_DEFAULT = '\033[49m' # 背景色をデフォルトに戻す
RESET = '\033[0m' # 全てリセット


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':
# q で終了
break
elif c == '\r':
# エンターで決定
print(f'selected:{strong_color(menu[current])}')
break
if c == 'j' or c == '\x1b[B':
# j か ↓ でカーソル移動
if current < len(menu) - 1:
current += 1
if c == 'k' or c == '\x1b[A':
# k か ↑でカーソル移動
if current > 0:
current -= 1
print(f'\033[{len(menu) + 1}A', end='')


if __name__ == '__main__':
main()

現在の評価: 1

コメント

アーカイブ

2024
2023
2022
2021
2020
2019
2018
2017
2016
2015
2014
2013
2012
2011