Catyのコンソールシェルは、名前の通りコンソールウィンドウで動くので、しばらく使っていれば、プロンプトがコンソールウィンドウの一番下に降りてしまいます。画面クリアして、カーソルとプロンプトを最上段に持ってきたいですよね。
僕のごときジイサマだと、画面クリアといえば、ANSIエスケープシーケンス ESC, '[', '2', 'J' を思い起こします。で、print "\x1b[2J"
とやってみました。が、Windowsのコンソールウィンドウ(コマンドプロンプト)ではダメでした。ええー、最近のWindowsコンソールはANSIエスケープシーケンスをサポートしてないのぉー。
ちょっと探したら、WConio -- Windows CONsole I/O for Pythonというライブラリが見つかったのですが、これの実体は WConiomodule.c というCモジュールです。特に画面クリアは次の関数。
// clear the screen static int int_clrscr (void) { HANDLE hConOut; CONSOLE_SCREEN_BUFFER_INFO csbi; COORD coord; DWORD dwDummy; hConOut = GetConOut(); if (hConOut != INVALID_HANDLE_VALUE) { if (GetConsoleScreenBufferInfo (hConOut, &csbi)) { coord.X = 0; coord.Y = 0; FillConsoleOutputCharacter (hConOut, (TCHAR)' ', csbi.dwSize.X * csbi.dwSize.Y, coord, &dwDummy); FillConsoleOutputAttribute (hConOut, csbi.wAttributes, csbi.dwSize.X * csbi.dwSize.Y, coord, &dwDummy); SetConsoleCursorPosition (hConOut, coord); } ReleaseConOut (hConOut); return 0; } return -1; }
Cで書いたライブラリをダイナミックリンクするのはあんまり嬉しくないなー。と思っていたら、ctypesというPythonモジュールを使えば、Windows APIをPythonから直接叩けることを知りました。試してみるべ。Bring Colors to the Windows Console with Python という記事が参考になりました。これをパクっています。
ctypesの力を借りて、上のC関数を直訳したのが下のPythonコードです。
# -*- coding: utf-8 -*- # win32cls.py # from ctypes import windll, Structure, c_short, c_ushort, c_uint, byref # Win32の型の定義: # SHORT, WORD, DWORD, # COORD, SMALL_RECT, CONSOLE_SCREEN_BUFFER_INFO SHORT = c_short WORD = c_ushort DWORD = c_uint class COORD(Structure): """struct in wincon.h.""" _fields_ = [ ("X", SHORT), ("Y", SHORT)] class SMALL_RECT(Structure): """struct in wincon.h.""" _fields_ = [ ("Left", SHORT), ("Top", SHORT), ("Right", SHORT), ("Bottom", SHORT)] class CONSOLE_SCREEN_BUFFER_INFO(Structure): """struct in wincon.h.""" _fields_ = [ ("dwSize", COORD), ("dwCursorPosition", COORD), ("wAttributes", WORD), ("srWindow", SMALL_RECT), ("dwMaximumWindowSize", COORD)] # 定数の定義 winbase.h STD_OUTPUT_HANDLE = -11 # Win32の変数と関数 stdout_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition # 画面クリア def clear_screen (): csbi = CONSOLE_SCREEN_BUFFER_INFO() coord = COORD() # 初期値 coord.X = 0, coord.Y = 0 dwDummy = DWORD() hConOut = stdout_handle if GetConsoleScreenBufferInfo (hConOut, byref(csbi)): FillConsoleOutputCharacterA (hConOut, 32, csbi.dwSize.X * csbi.dwSize.Y, coord, byref(dwDummy)) FillConsoleOutputAttribute (hConOut, csbi.wAttributes, csbi.dwSize.X * csbi.dwSize.Y, coord, byref(dwDummy)) SetConsoleCursorPosition (hConOut, coord)
右から左に翻訳しただけで、メカニズムの詳細を理解してはいないのですが、動くには動きました。
ともかくも、Catyシェルの画面クリアがなくて不便していたので、次のようにしてCatyコマンドに仕立てました。
# -*- coding: utf-8 -*- # cons.py (console.py はshellで使ってます) # from caty.command import Command import sys if sys.platform == 'win32': from win32cls import clear_screen else: def clear_screen(): pass # スタブ # Catyコマンド Cls class Cls(Command): def execute(self, input): clear_screen() return None
/* -*- coding: utf-8 -*-
* cons.casm (conて名前はWindowsでは使えないよ)
*/
module cons;@console
command cls :: void -> void
refers python:cons.Cls;
console, conて名前が安易に使えない点に注意してくださいね。Catyコンソールシェルからは、cons:cls として使っています*1。画面クリアは、OSシェルとは関係ないので、cygwin環境でもWindows標準コンソールを使っている限り機能します。こんなとき、ctypesが便利なのはよく分かりましたが、ANSIエスケープシーケンスのほうが簡単だったよなー、とかジイサマは思うのでした。Windows以外で同じことやるなら、termcap/terminfo, cursesとか使うことになるだろうし、あーメンド。
[追記]実は、上記ソースコードの次の部分は現状ではうまく動きません。
if sys.platform == 'win32': from win32cls import clear_screen
win32cls.pyとcons.pyを CATY_HOME/common/commands/ に放り込むとして、コマンドローダーがcons.pyを読んでメモリ内に配備するとき、win32cls.py は見えてないので、importが失敗します。これを回避するには、事前にPython自体のモジュールサーチパスに CATY_HOME/common/commands/ を入れておく必要があります。[/追記]
*1:モジュール名修飾無しの cls とすることもできますが、現在、仕様が若干曖昧です。