#!/usr/bin/env python import os import sys import time import signal def usage(retval): sys.stderr.write('Usage: %s --filename replay_filename --help --turn-off-bell\n' % \ sys.argv[0]) sys.stderr.write('\t--add-carriage-returns --guess-carriage-returns --gtk --curses\n') sys.stderr.write('\n--gtk is the default. --curses is the other option.\n') sys.stderr.write('\n--curses implies --add-carriage-returns\n') sys.stderr.write('\nPlease note that --turn-off-bell can break some xterm escape sequences,\n') sys.stderr.write('making some lines seem not to display properly. The "set xterm title"\n') sys.stderr.write('function is known to have this problem. Others may as well.\n') sys.exit(retval) replay_filename='./typescript' bell = 1 add_cr = 0 guess_cr = 0 use_gtk = 0 use_curses = 0 while sys.argv[1:]: if sys.argv[1] == '--filename': replay_filename = sys.argv[2] del sys.argv[1] elif sys.argv[1] == '--help': usage(0) elif sys.argv[1] == '--gtk': use_gtk = 1 elif sys.argv[1] == '--curses': use_curses = 1 elif sys.argv[1] == '--turn-off-bell': bell = 0 elif sys.argv[1] == '--add-carriage-returns': add_cr = 1 elif sys.argv[1] == '--guess-carriage-returns': add_cr = 2 else: sys.stderr.write('%s: Illegal option: %s\n' % (sys.argv[0], sys.argv[1])) usage(1) del sys.argv[1] if use_gtk + use_curses != 1: sys.stderr.write('%s: You must use exactly one of --gtk or --curses\n' % sys.argv[0]) usage(1) if use_gtk: import pygtk import gtk elif use_curses: import curses import signal add_cr = 1 else: sys.stderr.write('%s: Internal error: No display method selected\n' % sys.argv[0]) def ctrl(ch): o = ord(ch) if ch >= 'a' and ch <= 'z': return chr(o - ord('a') + 1) elif ch >= 'A' and ch <= 'Z': return chr(o - ord('A') + 1) else: return ch class line_class: def __init__(self, text): self.text = text self.length = len(text) def __str__(self): return 'length %d, ===> %s <===' % (self.length, self.text) class cursor_class: def __init__(self, replay_filename, bell, add_cr, replay_lines=300): # IOError: [Errno 2] No such file or directory: 'input_fil' try: file = open(replay_filename, 'r') except IOError, (x, y): sys.stderr.write('%s: %s: %s\n' % (sys.argv[0], replay_filename, y)) sys.exit(1) self.bell = bell self.line = map(line_class, \ ['-=> START OF FILE <=-\n'] + file.readlines() + ['-=> END OF FILE <=-\n']) file.close() self.num_lines = len(self.line) print "Got %d lines of input" % self.num_lines #time.sleep(10) # there will always be at least two lines. If this is an empty file, # they'll be numbered 0 and 1 self.lineno = 0 self.chrno = self.line[self.lineno].length if add_cr == 2: self.add_cr = 1 for i in xrange(self.num_lines): if '\r' in self.line[i].text: self.add_cr = 0 break else: self.add_cr = add_cr if use_gtk: self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(sys.argv[0]) self.window.connect("delete_event", self.delete_event) self.window.show() self.vbox = gtk.VBox() self.vbox.show() self.window.add(self.vbox) self.quit_button = gtk.Button('Quit') self.quit_button.show() self.quit_button.connect('clicked', self.delete_event) self.vbox.pack_start(self.quit_button) self.table = gtk.Table(2, 2, False) self.table.show() self.vbox.pack_start(self.table) self.back_chr_button = gtk.Button('Back character') self.back_line_button = gtk.Button('Back line') self.forward_chr_button = gtk.Button('Forward character') self.forward_line_button = gtk.Button('Forward line') self.back_chr_button.show() self.back_line_button.show() self.forward_chr_button.show() self.forward_line_button.show() self.table.attach(self.back_chr_button, 0, 1, 0, 1) self.table.attach(self.back_line_button, 1, 2, 0, 1) self.table.attach(self.forward_chr_button, 0, 1, 1, 2) self.table.attach(self.forward_line_button, 1, 2, 1, 2) self.back_chr_button.connect("clicked", self.back_chr) self.back_line_button.connect("clicked", self.back_line) self.forward_chr_button.connect("clicked", self.forward_chr) self.forward_line_button.connect("clicked", self.forward_line) self.beginning_button = gtk.Button('Beginning') self.end_button = gtk.Button('End') self.beginning_button.show() self.end_button.show() self.end_button.connect("clicked", self.end) self.beginning_button.connect("clicked", self.beginning) self.vbox.pack_start(self.end_button) self.vbox.pack_start(self.beginning_button) self.line_chr_label = gtk.Label('foo!') self.line_chr_label.show() self.vbox.pack_start(self.line_chr_label) else: self.screen = curses.initscr() curses.noecho() curses.cbreak() self.screen.keypad(1) self.help() self.show_status() self.clear() self.replay_lines = replay_lines self.replay() def show_status(self): if use_gtk: self.line_chr_label.set_text('Line %d, character %d' % (self.lineno, self.chrno)) def delete_event(self, *ignored_arguments): gtk.main_quit() sys.exit(0) def clear(self): if use_curses: self.screen.clear() self.screen.refresh() else: # I'm sure there's a better way... os.system('tput clear') def putch(self, ch): if ch == ctrl('g'): # beeps can be too annoying :) if self.bell: os.write(1,ch) elif ch == '\n': if self.add_cr: os.write(1,'\r') os.write(1,ch) else: os.write(1,ch) def puts(self, s): for i in range(len(s)): self.putch(s[i]) def replay(self): self.clear() first_lineno = self.lineno - self.replay_lines if first_lineno < 0: first_lineno = 0 last_lineno = self.lineno if last_lineno >= self.num_lines: last_lineno = self.num_lines - 1 # put whole lines from a ways back. If we go back far enough, there'll either # be a clear screen or enough lines to scroll away ancient history for temp_lineno in range(first_lineno, last_lineno): self.puts(self.line[temp_lineno].text) # put the fractional current line self.puts(self.line[self.lineno].text[:self.chrno+1]) def beep(self): self.puts(ctrl('g')) def forward_line(self, *dummy): self.lineno += 1 if self.lineno >= self.num_lines: self.lineno = self.num_lines - 1 self.chrno = len(self.line[self.lineno].text) self.replay() self.show_status() def back_line(self, *dummy): self.lineno -= 1 if self.lineno < 0: self.lineno = 0 self.chrno = self.line[self.lineno].length self.replay() self.show_status() def forward_chr(self, *dummy): old_chrno = self.chrno old_lineno = self.lineno self.chrno += 1 if self.chrno >= self.line[self.lineno].length: self.lineno += 1 self.chrno = 0 if self.lineno >= self.num_lines: self.lineno = old_lineno self.chrno = old_chrno self.replay() self.show_status() def back_chr(self, *dummy): old_chrno = self.chrno old_lineno = self.lineno self.chrno -= 1 if self.chrno < 0: self.lineno -= 1 self.chrno = self.line[self.lineno].length - 1 if self.lineno < 1: self.lineno = old_lineno self.chrno = old_chrno self.replay() self.show_status() def cleanup(self, *ignored_arguments): if use_gtk: gtk.main_quit() else: curses.nocbreak() self.screen.keypad(0) curses.echo() curses.endwin() sys.exit(0) def beginning(self, *ignored_arguments): self.lineno = 0 self.chrno = 0 self.replay() self.show_status() def end(self, *ignored_arguments): self.lineno = self.num_lines - 1 self.chrno = self.line[self.lineno].length - 1 self.replay() self.show_status() if use_curses: def getch(self): return self.screen.getch() def help(self): self.puts("Use ? for help\n") self.puts('\n') self.puts("Use j, control-n or down arrow to go down a line\n") self.puts("Use k, control-p or up arrow to go up a line\n") self.puts('\n') self.puts("Use l, control-f or right arrow to go forward one character\n") self.puts("Use h, control-f, left arrow or backspace to go back one character\n") self.puts('\n') self.puts("Use q or control-C (your interrupt character) to exit the program\n") self.puts('\n') self.puts("Hit any key to continue\n") dummy = sys.stdin.read(1) cursor = cursor_class(replay_filename, bell, add_cr) #def sigint_handler(x, y): # cursor.cleanup() # sys.exit(0) signal.signal(signal.SIGINT, cursor.cleanup) if use_gtk: gtk.main() else: while 1: ch = cursor.getch() #print chr(8),'got',ch #time.sleep(2) if ch in [ord('h'), ord(ctrl('b')), curses.KEY_LEFT, curses.KEY_BACKSPACE]: cursor.back_chr() elif ch in [ord('l'), ord(ctrl('f')), curses.KEY_RIGHT]: cursor.forward_chr() elif ch in [ord('k'), ord(ctrl('p')), curses.KEY_UP]: cursor.back_line() elif ch in [ord('j'), ord(ctrl('n')), ord('\n'), curses.KEY_DOWN]: cursor.forward_line() elif ch in [ord('0'), curses.KEY_HOME]: cursor.beginning() elif ch in [ord('9'), curses.KEY_END]: cursor.end() elif ch == ord('?'): cursor.clear() cursor.help() cursor.replay() elif ch == ord('q'): cursor.cleanup() sys.exit(0) # else: # cursor.beep()