#! python: #w#VIC terminal.py module # # Copyright 2002, 2003 by Timothy Rue <3seas@threeseas.net> # # VIC terminal.py module: version 0.5.1.python (BETA) # # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation version 2 # of the License. http://www.gnu.org/copyleft/gpl.html # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Or access http://www.gnu.org/copyleft/gpl.html ########################################################################## #August 29, 2003 made file IQ parseable 0.5.1 #July 24, 2003 initial release 0.5 # ########################################################################## ### ### Terminal Module ### Implements a editable, historyable command input line ### Or a simple work-around if you don't have curses ### ### To use it you need: ### history = [] ### stdscr = cursesOn() ### while (!exit) ### command = commandLine(stdscr,history,"Input>","") ### history.append(command) ### cursesOff(stdscr) ### #s#system modules import os import sys #s#curses global_have_curses = 0 #s# only import curses if this OS is likely to have it if (os.name == "posix"): #global global_have_curses import curses global_have_curses = 1 #s# Keeps track of the insert button state global_insert_mode = 1 ### #w# Draw the content of the input field and position the cursor. ### If overstrike mode is on (off at start) the prompt is ### drawn inverted ### #s#def rePaint def rePaint(prompt,prompt_reverse,content,cursor,stdscr): if (global_have_curses): height,width = stdscr.getmaxyx() if (prompt_reverse): stdscr.addstr(height-1,0,prompt,curses.A_REVERSE) else: stdscr.addstr(height-1,0,prompt) stdscr.addstr(height-1,len(prompt),content+" ") stdscr.move(height-1,cursor+len(prompt)) stdscr.refresh() #s#def writeToConsole def writeToConsole(stdscr,message): if (global_have_curses): height,width = stdscr.getmaxyx() stdscr.scrollok(1) stdscr.scroll(1) stdscr.addstr(height-2,0,message) stdscr.refresh() else: print message ### #w# Get's a key-press from the terminal. ### Depending on the terminal, sometimes when you push a key ### You get an escape sequence, so we watch for these and ### convert it into the proper keyboard code ### #s#def getKeyNonBlocking def getKeyNonBlocking(window): return getKey(window,1) #s#def getKey def getKey(window,non_blocking=0): try: if (non_blocking): window.timeout(100) key = window.getch() if (non_blocking): if (key == -1): return None,None window.timeout(-1) except (KeyboardInterrupt): window.timeout(-1) return curses.KEY_CANCEL,[] debug = 0 if (key == 10): rest = [10] return curses.KEY_ENTER,rest elif (key == 13): rest = [13] return curses.KEY_ENTER,rest elif (key == 127): rest = [127] return curses.KEY_BACKSPACE,rest ### try to read an escape sequence, but only for 20 milliseconds rest = [] if (key == 27): window.timeout(20) while (-1): try: next_key = window.getch() except (KeyboardInterrupt): return curses.KEY_CANCEL,[] if (next_key != -1): rest.append(next_key) else: break window.timeout(-1) if (rest == [91,68]): key = curses.KEY_LEFT elif (rest == [91,67]): key = curses.KEY_RIGHT elif (rest == [79,99]): ### CTRL-RARROW key = curses.KEY_NEXT elif (rest == [79,100]): ### CTRL-LARROW key = curses.KEY_PREVIOUS elif (rest == [91,66] or rest == [91, 53, 126]): key = curses.KEY_DOWN elif (rest == [91,65] or rest == [91, 54, 126]): key = curses.KEY_UP elif (rest == [91,52,126]): key = curses.KEY_END elif (rest == [91,49,126]): key = curses.KEY_HOME elif (rest == [91,50,126]): key = curses.KEY_IC ### [Insert] elif (rest == [91,51,126]): key = curses.KEY_DC ### [Delete] else: if (rest != [] and debug): ### might have just got [Esc] print "UNMAPPED KEY: ",rest rest = [27].append(rest) ### return the converted key in but include the ### original scan codes in return key,rest ### #w# Handles changes to the content of the field ### append, prepend, insert and overstrike are all ### implemented here ### #s#def addToStrAt def addToStrAt(content,char,cursor): if (cursor >= len(content)): content = content + char elif (global_insert_mode): if (cursor == 0): content = char + content else: content = content[0:cursor] + char + content[cursor:len(content)] else: if (cursor == 0): content = key + content[1:len(content)] else: content = content[0:cursor] + char + content[cursor+1:len(content)] return content #s#def CursesOn def cursesOn(): if (global_have_curses): stdscr = curses.initscr() curses.noecho() curses.cbreak() return stdscr else: print "NO CURSES" return "no curses" #s#def cursesOff def cursesOff(stdscr): if (global_have_curses): curses.echo() curses.nocbreak() curses.endwin() stdscr.scrollok(1) stdscr.scroll(1) ### #w# Implement an 'edit-field' style command line. ### Allow use of arrow keys for moving the cursor about, command history ### insert and overstrike, and other general function keys ### like [Home] and [End] ### ### Returns the content of the field. If the user does not have curses ### implement a simple stdin input. ### ### If the line of command ends in a back-slash '\', assume the command ### spans more than one line and imput some more ### #s#def commandLine def commandLine(stdscr,hist,prompt,content,idle_exec_list=None): if (not global_have_curses): import string handle = sys.stdin full_command = "" done = 0 while (not done): if (full_command == ""): print prompt, else: print "...> ", try: command = handle.readline() command = string.strip(command) except KeyboardInterrupt: full_command = "" command = "exit" if (command == "" or command[-1] != "\\"): done = 1 full_command = full_command + command else: full_command = full_command + command[:-1] return full_command else: prompt_reverse = 0 original = content height,width = stdscr.getmaxyx() field_width = width - len(prompt) global global_insert_mode history = hist + [content] history_pos = len(history)-1 key_history = [] cursor = len(content) key = 0 while (key != curses.KEY_ENTER and key != curses.KEY_CANCEL): rePaint(prompt,prompt_reverse,content,cursor,stdscr) if (idle_exec_list == None): ### Nothing to wait on, get a key with a blocking read key,key_history = getKey(stdscr) else: ### simulate multitasking by executing anything waiting ### while waiting for the user to press a key while 1: key,key_history = getKeyNonBlocking(stdscr) if (key == None and key_history == None): idle_exec_list[0](idle_exec_list[1]) else: ### some sort of 10ms delay would be good here break if (key >= ord(' ') and key <= ord('~')): if (cursor == field_width-2 or (len(content) == field_width-2 and global_insert_mode)): curses.beep() else: content = addToStrAt(content,chr(key),cursor) cursor = cursor + 1 elif (key == curses.KEY_LEFT): if (cursor > 0): cursor = cursor - 1 elif (key == curses.KEY_BACKSPACE): if (cursor > 0): content = content[0:cursor-1] + content[cursor:len(content)] cursor = cursor - 1 elif (key == curses.KEY_RIGHT): if (cursor < len(content)): cursor = cursor + 1 elif (key == curses.KEY_UP): if (history_pos > 0): history[history_pos] = content history_pos = history_pos - 1 content = history[history_pos] cursor=len(content) stdscr.deleteln() elif (key == curses.KEY_DOWN): if (history_pos < len(history)-1): history[history_pos] = content history_pos = history_pos + 1 content = history[history_pos] cursor=len(content) stdscr.deleteln() elif (key == curses.KEY_HOME): cursor = 0 elif (key == curses.KEY_END): cursor = len(content) elif (key == curses.KEY_CANCEL or key == 27): stdscr.deleteln() content = original cursor = len(content) history_pos = 0 history[0] = content elif (key == curses.KEY_IC): if (global_insert_mode): global_insert_mode = 0 prompt_reverse = 1 else: global_insert_mode = 1 prompt_reverse = 0 # else: # if (key != curses.KEY_ENTER): # print "KEY: ",key,key_history stdscr.move(0,0) clear = "" for i in range(len(prompt)): ### Re-print without the prompt, so clean off clear = clear + " " rePaint("",0,content+clear,len(content),stdscr) stdscr.scrollok(1) stdscr.scroll(1) return content