pyreadline-refactor: Merge ansi patch and tab patch from trunk.

This commit is contained in:
jstenar
2006-09-11 20:00:23 +00:00
parent 14a0325175
commit 89b5a62de1
12 changed files with 398 additions and 50 deletions
+9
View File
@@ -1,3 +1,12 @@
2006-09-11 Jörgen Stenarson <jorgen.stenarson -at- bostream.nu>
* Added logserver. Socket based server that can receive logmessage.
To be used when debugging keypresses, could be a security risk as a keyboard sniffer.
* Added log_sock call to send logging to logserver
* Merging ANSI parsing from trunk
* Parsing escape sequence for up down key in parse_bind
* Merging bugfix for self_insert of tabs from trunk
* Add bindable function that prints keybindings
2006-07-13 Jörgen Stenarson <jorgen.stenarson -at- bostream.nu>
* Work to get selection between ironpython and cpython to work
* Some editing works but there are issues with control keys for ironpython
+175
View File
@@ -0,0 +1,175 @@
# -*- coding: ISO-8859-1 -*-
import re,sys,os,pprint
pprint=pprint.pprint
terminal_escape = re.compile('(\001?\033\\[[0-9;]*m\002?)')
escape_parts = re.compile('\001?\033\\[([0-9;]*)m\002?')
class AnsiState(object):
def __init__(self,bold=False,inverse=False,color="white",background="black",backgroundbold=False):
self.bold=bold
self.inverse=inverse
self.color=color
self.background=background
self.backgroundbold=backgroundbold
trtable={"black":0,"red":4,"green":2,"yellow":6,"blue":1,"magenta":5,"cyan":3,"white":7}
revtable=dict(zip(trtable.values(),trtable.keys()))
def get_winattr(self):
attr=0
if self.bold:
attr|=0x0008
if self.backgroundbold:
attr|=0x0080
if self.inverse:
attr|=0x4000
attr|=self.trtable[self.color]
attr|=(self.trtable[self.background]<<4)
return attr
def set_winattr(self,attr):
self.bold=bool(attr&0x0008)
self.backgroundbold=bool(attr&0x0080)
self.inverse=bool(attr&0x4000)
self.color=self.revtable[attr&0x0007]
self.background=self.revtable[(attr&0x0070)>>4]
winattr=property(get_winattr,set_winattr)
def __repr__(self):
return 'AnsiState(bold=%s,inverse=%s,color=%9s,background=%9s,backgroundbold=%s)# 0x%x'%(self.bold,
self.inverse,
'"%s"'%self.color,
'"%s"'%self.background,
self.backgroundbold,
self.winattr)
def copy(self):
x=AnsiState()
x.bold=self.bold
x.inverse=self.inverse
x.color=self.color
x.background=self.background
x.backgroundbold=self.backgroundbold
return x
defaultstate=AnsiState(False,False,"white")
trtable={0:"black",1:"red",2:"green",3:"yellow",4:"blue",5:"magenta",6:"cyan",7:"white"}
class AnsiWriter(object):
def __init__(self,default=defaultstate):
if isinstance(defaultstate,AnsiState):
self.defaultstate=default
else:
self.defaultstate=AnsiState()
self.defaultstate.winattr=defaultstate
def write_color(self,text, attr=None):
'''write text at current cursor position and interpret color escapes.
return the number of characters written.
'''
if isinstance(attr,AnsiState):
defaultstate=attr
elif attr is None: #use attribute form initial console
attr = self.defaultstate.copy()
else:
defaultstate=AnsiState()
defaultstate.winattr=attr
attr=defaultstate
chunks = terminal_escape.split(text)
n = 0 # count the characters we actually write, omitting the escapes
res=[]
for chunk in chunks:
m = escape_parts.match(chunk)
if m:
for part in m.group(1).split(";"):
if part == "0": # No text attribute
attr = self.defaultstate.copy()
elif part == "7": # switch on reverse
attr.inverse=True
elif part == "1": # switch on bold (i.e. intensify foreground color)
attr.bold=True
elif len(part) == 2 and "30" <= part <= "37": # set foreground color
attr.color = trtable[int(part)-30]
elif len(part) == 2 and "40" <= part <= "47": # set background color
attr.color = trtable[int(part)-40]
continue
n += len(chunk)
if True:
res.append((attr.copy(),chunk))
return n,res
def parse_color(self,text, attr=None):
n,res=self.write_color(text,attr)
return n,[attr.winattr for attr,text in res]
def write_color(text,attr=None):
a=AnsiWriter(defaultstate)
return a.write_color(text,attr)
def write_color_old( text, attr=None):
'''write text at current cursor position and interpret color escapes.
return the number of characters written.
'''
res=[]
chunks = terminal_escape.split(text)
n = 0 # count the characters we actually write, omitting the escapes
if attr is None:#use attribute from initial console
attr = 15
for chunk in chunks:
m = escape_parts.match(chunk)
if m:
for part in m.group(1).split(";"):
if part == "0": # No text attribute
attr = 0
elif part == "7": # switch on reverse
attr |= 0x4000
if part == "1": # switch on bold (i.e. intensify foreground color)
attr |= 0x08
elif len(part) == 2 and "30" <= part <= "37": # set foreground color
part = int(part)-30
# we have to mirror bits
attr = (attr & ~0x07) | ((part & 0x1) << 2) | (part & 0x2) | ((part & 0x4) >> 2)
elif len(part) == 2 and "40" <= part <= "47": # set background color
part = int(part)-40
# we have to mirror bits
attr = (attr & ~0x70) | ((part & 0x1) << 6) | ((part & 0x2) << 4) | ((part & 0x4) << 2)
# ignore blink, underline and anything we don't understand
continue
n += len(chunk)
if chunk:
res.append(("0x%x"%attr,chunk))
return res
#trtable={0:"black",1:"red",2:"green",3:"yellow",4:"blue",5:"magenta",6:"cyan",7:"white"}
if __name__=="__main__":
import startup
s="\033[0;31mred\033[0;32mgreen\033[0;33myellow\033[0;34mblue\033[0;35mmagenta\033[0;36mcyan\033[0;37mwhite\033[0m"
pprint (write_color(s))
pprint (write_color_old(s))
s="\033[1;31mred\033[1;32mgreen\033[1;33myellow\033[1;34mblue\033[1;35mmagenta\033[1;36mcyan\033[1;37mwhite\033[0m"
pprint (write_color(s))
pprint (write_color_old(s))
s="\033[0;7;31mred\033[0;7;32mgreen\033[0;7;33myellow\033[0;7;34mblue\033[0;7;35mmagenta\033[0;7;36mcyan\033[0;7;37mwhite\033[0m"
pprint (write_color(s))
pprint (write_color_old(s))
s="\033[1;7;31mred\033[1;7;32mgreen\033[1;7;33myellow\033[1;7;34mblue\033[1;7;35mmagenta\033[1;7;36mcyan\033[1;7;37mwhite\033[0m"
pprint (write_color(s))
pprint (write_color_old(s))
if __name__=="__main__":
import console
c=console.Console()
c.write_color("dhsjdhs")
c.write_color("\033[0;32mIn [\033[1;32m1\033[0;32m]:")
print
pprint (write_color("\033[0;32mIn [\033[1;32m1\033[0;32m]:"))
+20 -1
View File
@@ -16,7 +16,7 @@ This was modeled after the C extension of the same name by Fredrik Lundh.
import sys
import traceback
import re
from pyreadline.logger import log
from pyreadline.logger import log,log_sock
try:
# I developed this with ctypes 0.6
@@ -27,6 +27,7 @@ except ImportError:
# my code
from pyreadline.keysyms import make_KeyPress
from pyreadline.console.ansi import AnsiState,AnsiWriter
# some constants we need
STD_INPUT_HANDLE = -10
@@ -183,6 +184,11 @@ class Console(object):
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
self.attr = info.wAttributes
self.saveattr = info.wAttributes # remember the initial colors
self.defaultstate=AnsiState()
self.defaultstate.winattr=info.wAttributes
self.ansiwriter=AnsiWriter(self.defaultstate)
background = self.attr & 0xf0
for escape in self.escape_to_color:
if self.escape_to_color[escape] is not None:
@@ -336,6 +342,17 @@ class Console(object):
self.WriteConsoleA(self.hout, chunk, len(chunk), byref(junk), None)
return n
def write_color(self, text, attr=None):
n,res= self.ansiwriter.write_color(text,attr)
junk = c_int(0)
for attr,chunk in res:
log(str(attr))
log(str(chunk))
self.SetConsoleTextAttribute(self.hout, attr.winattr)
self.WriteConsoleA(self.hout, chunk, len(chunk), byref(junk), None)
return n
def write_plain(self, text, attr=None):
'''write text at current cursor position.'''
log('write("%s", %s)' %(text,attr))
@@ -453,6 +470,7 @@ class Console(object):
status = self.ReadConsoleInputA(self.hin, byref(Cevent), 1, byref(count))
if status and count.value == 1:
e = event(self, Cevent)
log_sock(str(e.keyinfo),"keypress")
return e
def getkeypress(self):
@@ -469,6 +487,7 @@ class Console(object):
return e
elif e.type == 'KeyRelease' and e.keyinfo==(True, False, False, 83):
log("getKeypress:%s,%s,%s"%(e.keyinfo,e.keycode,e.type))
# log_sock(str(e))
return e
def getchar(self):
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#*****************************************************************************
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#*****************************************************************************
class ReadlineError(Exception):
pass
class GetSetError(ReadlineError):
pass
+9 -5
View File
@@ -29,6 +29,8 @@ validkey =set(['cancel', 'backspace', 'tab', 'clear',
'numpad8', 'numpad9', 'divide', 'multiply',
'add', 'subtract', 'vk_decimal'])
escape_sequence_to_special_key={"\\e[a":"up","\\e[b":"down","del":"delete"}
class KeyPress(object):
def __init__(self,char="",shift=False,control=False,meta=False,keyname=""):
if control or meta or shift:
@@ -65,7 +67,9 @@ class KeyPress(object):
def make_KeyPress_from_keydescr(keydescr):
keyinfo=KeyPress()
if len(keydescr)>2 and keydescr[:1]=='"' and keydescr[-1:]=='"':
keydescr=keydescr[1:-1]
while 1:
lkeyname = keydescr.lower()
if lkeyname.startswith('control-'):
@@ -74,14 +78,14 @@ def make_KeyPress_from_keydescr(keydescr):
elif lkeyname.startswith('ctrl-'):
keyinfo.control = True
keydescr = keydescr[5:]
elif keydescr.startswith('\\C-'):
elif keydescr.lower().startswith('\\c-'):
keyinfo.control = True
keydescr = keydescr[3:]
elif keydescr.startswith('\\M-'):
elif keydescr.lower().startswith('\\m-'):
keyinfo.meta = True
keydescr = keydescr[3:]
elif keydescr.startswith('\\e-'):
keydescr = "escape"+keydescr[3:]
elif keydescr in escape_sequence_to_special_key:
keydescr = escape_sequence_to_special_key[keydescr]
elif lkeyname.startswith('meta-'):
keyinfo.meta = True
keydescr = keydescr[5:]
+25 -19
View File
@@ -21,6 +21,8 @@ import exceptions
class EscapeHistory(exceptions.Exception):
pass
from pyreadline.logger import log_sock
class LineHistory(object):
def __init__(self):
self.history=[]
@@ -96,15 +98,19 @@ class LineHistory(object):
self.history_cursor=len(self.history)
current.set_line(self.history[-1].get_line_text())
def reverse_search_history(self,searchfor):
res=[(idx,line) for idx,line in enumerate(self.history[self.history_cursor:0:-1]) if searchfor in line]
def reverse_search_history(self,searchfor,startpos=None):
if startpos is None:
startpos=self.history_cursor
res=[(idx,line) for idx,line in enumerate(self.history[startpos:0:-1]) if searchfor in line]
if res:
self.history_cursor-=res[0][0]
return res[0][1].get_line_text()
return ""
def forward_search_history(self,searchfor):
res=[(idx,line) for idx,line in enumerate(self.history[self.history_cursor:]) if searchfor in line]
def forward_search_history(self,searchfor,startpos=None):
if startpos is None:
startpos=self.history_cursor
res=[(idx,line) for idx,line in enumerate(self.history[startpos:]) if searchfor in line]
if res:
self.history_cursor+=res[0][0]
return res[0][1].get_line_text()
@@ -121,41 +127,41 @@ class LineHistory(object):
pyreadline.rl._clear_after()
event = c.getkeypress()
if event.keysym == 'BackSpace':
log_sock(str(event))
if event.keyinfo.keyname == 'backspace':
if len(query) > 0:
query = query[:-1]
else:
break
elif event.char in string.letters + string.digits + string.punctuation + ' ':
query += event.char
elif event.keysym == 'Return':
elif event.keyinfo.keyname == 'return':
break
else:
pyreadline.rl._bell()
log_sock(query)
res=""
if query:
hc = self.history_cursor - 1
while (direction < 0 and hc >= 0) or (direction > 0 and hc < len(self.history)):
if self.history[hc].startswith(query) >= 0:
current=self.history[hc]
self.history_cursor = hc
return
hc += direction
if direction==-1:
res=self.reverse_search_history(query)
else:
pyreadline.rl._bell()
res=self.forward_search_history(query)
log_sock(res)
return lineobj.ReadLineTextBuffer(res,point=0)
def non_incremental_reverse_search_history(self,current): # (M-p)
'''Search backward starting at the current line and moving up
through the history as necessary using a non-incremental search for
a string supplied by the user.'''
self._non_i_search(-1,current)
return self._non_i_search(-1,current)
def non_incremental_forward_search_history(self,current): # (M-n)
'''Search forward starting at the current line and moving down
through the the history as necessary using a non-incremental search
for a string supplied by the user.'''
self._non_i_search(1,current)
return self._non_i_search(1,current)
def _search(self, direction, partial):
if (self.lastcommand != self.history_search_forward and
+1 -1
View File
@@ -223,7 +223,7 @@ class TextLine(object):
def visible_line_width(self,position=Point):
"""Return the visible width of the text in line buffer up to position."""
return len(self[:position].quoted_text())
return len(self[:position].quoted_text())+self[:position].line_buffer.count("\t")*7
def quoted_text(self):
quoted = [ quote_char(c) for c in self.line_buffer ]
+20 -1
View File
@@ -6,7 +6,7 @@
# the file COPYING, distributed as part of this software.
#*****************************************************************************
import socket
_logfile=False
def start_log(on,filename):
@@ -20,3 +20,22 @@ def log(s):
if _logfile:
print >>_logfile, s
_logfile.flush()
host="localhost"
port=8081
logsocket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
show_event=["keypress","bound_function"]
show_event=["bound_function"]
def log_sock(s,event_type=None):
if event_type is None:
logsocket.sendto(s,(host,port))
elif event_type in show_event:
logsocket.sendto(s,(host,port))
else:
pass
log_sock("Starting pyreadline")
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#*****************************************************************************
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#*****************************************************************************
import socket
try:
import msvcrt
except ImportError:
msvcrt=None
print "problem"
port =8081
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind(("",port))
s.settimeout(0.05)
print "Starting logserver on port:",port
print "Press q to quit logserver",port
singleline=False
def check_key():
if msvcrt is None:
return False
else:
if msvcrt.kbhit()!=0:
q=msvcrt.getch()
return q in "q"
else:
return False
while 1:
try:
data,addr=s.recvfrom(1024)
except socket.timeout:
if check_key():
print "Quitting logserver"
break
else:
continue
if data.startswith("@@"):
continue
if singleline:
print "\r"," "*78,"\r",data,#,addr
else:
print data
+8
View File
@@ -13,6 +13,7 @@ from pyreadline.keysyms.common import make_KeyPress_from_keydescr
import pyreadline.lineeditor.lineobj as lineobj
import pyreadline.lineeditor.history as history
import pyreadline.clipboard as clipboard
from pyreadline.error import ReadlineError,GetSetError
class BaseMode(object):
mode="base"
@@ -64,6 +65,10 @@ class BaseMode(object):
enable_ipython_paste_for_paths=property(_g("enable_ipython_paste_for_paths"))
_bell=property(_g("_bell"))
_history=property(_g("_history"))
prompt_end_pos=property(_g("prompt_end_pos"))
prompt_begin_pos=property(_g("prompt_begin_pos"))
rl_settings_to_string=property(_g("rl_settings_to_string"))
def _readline_from_keyboard(self):
raise NotImplementedError
@@ -75,6 +80,9 @@ class BaseMode(object):
def _bind_key(self, key, func):
'''setup the mapping from key to call the function.'''
if type(func) != type(self._bind_key):
print "Trying to bind non method to keystroke:%s,%s"%(key,func)
raise PyreadlineError("Trying to bind non method to keystroke:%s,%s,%s,%s"%(key,func,type(func),type(self._bind_key)))
keyinfo = make_KeyPress_from_keydescr(key.lower()).tuple()
self.key_dispatch[keyinfo] = func
+52 -16
View File
@@ -8,11 +8,19 @@
#*****************************************************************************
import os
import pyreadline.logger as logger
from pyreadline.logger import log
from pyreadline.logger import log,log_sock
from pyreadline.lineeditor.lineobj import Point
import pyreadline.lineeditor.lineobj as lineobj
import pyreadline.lineeditor.history as history
import basemode
import string
def format(keyinfo):
if len(keyinfo[-1])!=1:
k=keyinfo+(-1,)
else:
k=keyinfo+(ord(keyinfo[-1]),)
return "(%s,%s,%s,%s,%x)"%k
class EmacsMode(basemode.BaseMode):
mode="emacs"
@@ -30,6 +38,8 @@ class EmacsMode(basemode.BaseMode):
def _readline_from_keyboard(self):
c=self.console
def nop(e):
pass
while 1:
self._update_line()
event = c.getkeypress()
@@ -39,12 +49,18 @@ class EmacsMode(basemode.BaseMode):
event.keyinfo = (control, True, shift, code)
#Process exit keys. Only exit on empty line
if event.keyinfo.tuple() in self.exit_dispatch:
keyinfo=event.keyinfo.tuple()
if keyinfo in self.exit_dispatch:
if lineobj.EndOfLine(self.l_buffer) == 0:
raise EOFError
if len(keyinfo[-1])>1:
default=nop
else:
default=self.self_insert
dispatch_func = self.key_dispatch.get(keyinfo,default)
dispatch_func = self.key_dispatch.get(event.keyinfo.tuple(),self.self_insert)
log("readline from keyboard:%s"%(event.keyinfo.tuple(),))
log("readline from keyboard:%s,%s"%(keyinfo,dispatch_func))
log_sock("%s|%s"%(format(keyinfo),dispatch_func.__name__))
r = None
if dispatch_func:
r = dispatch_func(event)
@@ -118,11 +134,12 @@ class EmacsMode(basemode.BaseMode):
def _i_search(self, searchfun, direction, init_event):
c = self.console
line = self.get_line_buffer()
line = self.l_buffer.get_line_text()
log_sock(str(line))
query = ''
if (self.previous_func != self.history_search_forward and
self.previous_func != self.history_search_backward):
self.query = ''.join(self.line_buffer[0:self.point].get_line_text())
self.query = ''.join(self.l_buffer[0:Point].get_line_text())
hc_start = self._history.history_cursor #+ direction
while 1:
x, y = self.prompt_end_pos
@@ -137,24 +154,25 @@ class EmacsMode(basemode.BaseMode):
self._clear_after()
event = c.getkeypress()
if event.keysym == 'BackSpace':
if event.keyinfo.keyname == 'backspace':
query = query[:-1]
if len(query) > 0:
query = query[:-1]
self._history.history_cursor = hc_start
#self._history.history_cursor = hc_start #forces search to restart when search empty
line=searchfun(query)
else:
self._bell()
line="" #empty query means no search result
elif event.char in string.letters + string.digits + string.punctuation + ' ':
self._history.history_cursor = hc_start
#self._history.history_cursor = hc_start
query += event.char
line=searchfun(query)
elif event.keyinfo == init_event.keyinfo:
self._history.history_cursor += direction
line=searchfun(query)
pass
else:
if event.keysym != 'Return':
if event.keyinfo.keyname != 'return':
self._bell()
break
line=searchfun(query)
px, py = self.prompt_begin_pos
c.pos(0, py)
@@ -177,13 +195,15 @@ class EmacsMode(basemode.BaseMode):
'''Search backward starting at the current line and moving up
through the history as necessary using a non-incremental search for
a string supplied by the user.'''
self._history.non_incremental_reverse_search_history(self.l_buffer)
q=self._history.non_incremental_reverse_search_history(self.l_buffer)
self.l_buffer=q
def non_incremental_forward_search_history(self, e): # (M-n)
'''Search forward starting at the current line and moving down
through the the history as necessary using a non-incremental search
for a string supplied by the user.'''
self._history.non_incremental_reverse_search_history(self.l_buffer)
q=self._history.non_incremental_reverse_search_history(self.l_buffer)
self.l_buffer=q
def history_search_forward(self, e): # ()
'''Search forward through the history for the string of characters
@@ -487,6 +507,7 @@ class EmacsMode(basemode.BaseMode):
self._bind_exit_key('Control-z')
# I often accidentally hold the shift or control while typing space
self._bind_key('space', self.self_insert)
self._bind_key('Shift-space', self.self_insert)
self._bind_key('Control-space', self.self_insert)
self._bind_key('Return', self.accept_line)
@@ -540,6 +561,21 @@ class EmacsMode(basemode.BaseMode):
self._bind_key("Shift-Control-Left", self.backward_word_extend_selection)
self._bind_key("Shift-Home", self.beginning_of_line_extend_selection)
self._bind_key("Shift-End", self.end_of_line_extend_selection)
self._bind_key("numpad0", self.self_insert)
self._bind_key("numpad1", self.self_insert)
self._bind_key("numpad2", self.self_insert)
self._bind_key("numpad3", self.self_insert)
self._bind_key("numpad4", self.self_insert)
self._bind_key("numpad5", self.self_insert)
self._bind_key("numpad6", self.self_insert)
self._bind_key("numpad7", self.self_insert)
self._bind_key("numpad8", self.self_insert)
self._bind_key("numpad9", self.self_insert)
self._bind_key("add", self.self_insert)
self._bind_key("subtract", self.self_insert)
self._bind_key("multiply", self.self_insert)
self._bind_key("divide", self.self_insert)
self._bind_key("vk_decimal", self.self_insert)
# make it case insensitive
def commonprefix(m):
+5 -7
View File
@@ -20,6 +20,7 @@ import exceptions
import clipboard,logger,console
from logger import log
from error import ReadlineError,GetSetError
from pyreadline.keysyms.common import make_KeyPress_from_keydescr
import pyreadline.lineeditor.lineobj as lineobj
@@ -32,14 +33,9 @@ def quote_char(c):
if ord(c)>0:
return c
class ReadlineError(exceptions.Exception):
pass
def inword(buffer,point):
return buffer[point:point+1] in [A-Za-z0-9]
class GetSetError(ReadlineError):
pass
class Readline(object):
def __init__(self):
@@ -245,7 +241,7 @@ class Readline(object):
## Internal functions
def rl_settings_to_string(self):
def rl_settings_to_string(self,e=None):
out=["%-20s: %s"%("show all if ambigous",self.show_all_if_ambiguous)]
out.append("%-20s: %s"%("mark_directories",self.mark_directories))
out.append("%-20s: %s"%("bell_style",self.bell_style))
@@ -253,7 +249,7 @@ class Readline(object):
out.append("------------- key bindings ------------")
tablepat="%-7s %-7s %-7s %-15s %-15s "
out.append(tablepat%("Control","Meta","Shift","Keycode/char","Function"))
bindings=[(k[0],k[1],k[2],k[3],v.__name__)for k,v in self.mode.key_dispatch.iteritems()]
bindings=[(k[0],k[1],k[2],k[3],v.__name__) for k,v in self.mode.key_dispatch.iteritems()]
#print self.mode.key_dispatch
#bindings=[str(v) for k,v in self.mode.key_dispatch.iteritems()]
bindings.sort()
@@ -261,6 +257,8 @@ class Readline(object):
pass
# out.append(str(key))
out.append(tablepat%(key))
if e:
print "\n".join(out)
return out
def _bell(self):