Add LICENSE
[pygdb.git] / DbgTerminal.py
old mode 100755 (executable)
new mode 100644 (file)
index cef0d27..9d08866
 #!/usr/bin/python
+#shuber, 2008-06-04
 
+__author__ = "shuber"
 
-import select
-import sys
-import thread
-import threading
-import time
+
+import gobject
+import gtk
 import os
+import pango
 import pty
+import re
+import string
+import sys
+import time
+import threading
+import vte
 
+import ClientIOTerminal
 
-class DbgTerminal:
 
-       def __init__ (self, binary, gdbReadCallback, childReadCallback):
 
-               #Set some members
-               self.gdbReadCallback = gdbReadCallback
-               self.childReadCallback = childReadCallback
-               self.binary = binary
-               self.stopped = False
+class DbgTerminal (vte.Terminal):
 
-               #Connect to sub-process
-               self.__connect()
+       def __init__(self, clientCmd):
 
-       def __connect( self ):
+               vte.Terminal.__init__(self)
 
+               #Set members
+               self.childpid = None
+               self.history = [""]
+               self.isactive = True
+               self.lastc, self.lastr = 0,0
+               self.gotActiveCallback = []
+               self.gotInactiveCallback = []
+               self.activityChanged = None
 
+               #Start debugger
+               self.clientCmd = clientCmd
                #Open pseudo-terminal where to-be-debugged process reads/writes to
-               self.ptymaster, self.ptyslave = pty.openpty()
-               self.childout = os.fdopen(self.ptymaster, "r", 0)
-               self.childin = os.fdopen(self.ptymaster, "w", 0)
+               self.client_ptymaster, self.client_ptyslave = pty.openpty()
 
-               #Open the subprocess and get in- and out-streams
-               cmd = self.getCommand(self.binary)
-               self.gdbin, self.gdbout = os.popen4( cmd, bufsize=0)
+               #Set up terminal window and initialize debugger
+               self.connect("cursor-moved", self.contents_changed)
+               self.connect("child-exited", quitHandler)
+               gobject.timeout_add(50, self.checkActivityChanged)
 
+               #font description
+               fontdesc = pango.FontDescription("monospace 9")
+               self.set_font(fontdesc)
 
-               #Set up a reading thread to gdb output
-               self.gdbReadThread =  self.ReadThread(self.gdbout, self.gdbReadCallback)
-               self.gdbReadThread.start()
 
-               #Set up a reading thread to gdb output
-               self.childReadThread =  self.ReadThread(self.childout, self.childReadCallback)
-               self.childReadThread.start()
+       def initialize(self):
+               self.childpid = self.fork_command( self.getCommand(), self.getArgv())
+               self.waitForPrompt(0)
+               self.setPty(self.client_ptyslave)
 
-               #Set up tty gdb-childs process
-               time.sleep(0.1)
-               self.sendSetTTY(os.ttyname(self.ptyslave))
-               time.sleep(0.1)
+       def stopDbg(self):
 
+               if self.childpid != None:
+                       #9=KILL, 15=TERM
+                       os.kill(self.childpid, 15);
+                       self.childpid = None
 
+       def getClientExecuteable(self):
+               return string.split(self.clientCmd)[0]
 
-       def getCommand( self, binary ):
-               """Get the command to execute"""
-               raise NotImplementedError()
 
+       def toAbsPath(self, path):
+               """convert path to an absolute path relative to the client
+               executable we debug."""
+               
+               #Current working dir
+               pwd = os.getcwd() + "/"
 
-       def iseof( self ):
-               """Check if terminal is closed already"""
+               #executeable path
+               client = self.getClientExecuteable()
+               client = relToAbsPath(pwd, client)              
 
-               return self.gdbReadThread.eventFin.isSet()
+               return relToAbsPath(client, path)
 
 
-       def stop( self ):
+       def checkActivityChanged(self):
 
-               if not self.stopped:
+               try:
 
-                       self.stopped = True
+                       #There was activity
+                       if self.activityChanged != None:
 
-                       #Finish the reading-thread
-                       self.gdbReadThread.fin = True
-                       self.childReadThread.fin = True
-                       self.gdbReadThread.eventFin.wait(1)
-                       self.childReadThread.eventFin.wait(1)
+                               res = self.activityChanged
+                               self.activityChanged = None
 
+                               status, param = res
+                               if self.isActive():
+                                       print "got active: ", res
+                                       for cb in self.gotActiveCallback:
+                                               cb(status, param)
+                               else:
+                                       print "got inactive: ", res
+                                       for cb in self.gotInactiveCallback:
+                                               cb(status, param)
+               except Exception, e:
+                       import traceback
+                       traceback.print_exc()
+                       print e
 
-       def sendBreak(self, file, lineno):
-               raise NotImplementedError()
+               return True
 
-       def sendContinue(self):
-               raise NotImplementedError()
-       
-       def sendRun(self):
-               raise NotImplementedError()
 
-       def sendInspectVar(self, var):
-               raise NotImplementedError()
 
-       def sendInspectExpr(self, expr):
+       def contents_changed(self, term):
+               assert( self.getHistoryLen()>0 )
+
+               c,r = term.get_cursor_position()
+               text = self.get_text_range(self.lastr,0,r,c,lambda *w:True)
+               self.lastc, self.lastr = c,r
+
+               #Remove annoying \n at the end
+               assert(text[-1] == "\n")
+               text = text[:-1]
+
+               #Get the lines and remove empty lines
+               lines = string.split(text, "\n")
+
+               #Remove the incomplete line
+               len = max(0,self.getHistoryLen())
+               self.history[-1] = lines[0]
+               self.history += lines[1:]
+
+
+               #Check if activity status has been changed
+               for i in range(len, self.getHistoryLen()):
+                       line = self.history[i]
+                                       
+                       res = self.testForInactivity(i)
+                       if res != None:
+                               while self.activityChanged != None:
+                                       print "wait for pending activity"
+                                       gtk.main_iteration()
+
+                               self.setActive(False)
+                               self.activityChanged = res
+
+                       res = self.testForActivity(i)
+                       if res != None:
+                               while self.activityChanged != None:
+                                       print "wait for pending activity"
+                                       gtk.main_iteration()
+
+                               self.setActive(True)
+                               self.activityChanged = res
+
+
+
+
+       def waitForNewline(self):
+               l = self.getHistoryLen()
+               while not self.getHistoryLen() > l:
+                       gtk.main_iteration()
+
+       def getHistoryLen(self):
+               return len(self.history)
+
+       def waitForRx(self, pat, start):        
+
+               rx = re.compile(pat)
+               curr = start
+               while True:
+                       assert( curr>=start )
+                       for no in range(curr, self.getHistoryLen()):
+                               line = self.history[no]
+                               if rx.search(line):
+                                       return no, line
+
+                       #Do not forget the last line
+                       curr = max(start,self.getHistoryLen()-1)
+                       lr, lc = self.lastr, self.lastc
+
+                       while (self.lastr, self.lastc) == (lr,lc):
+                               gtk.main_iteration()
+
+
+       def getCommand(self):
+               return self.getArgv()[0];
+
+       def getArgv(self):
                raise NotImplementedError()
 
-       def sendSetTTY(self, ttyname):
+       def setPty(self, pty):
                raise NotImplementedError()
-       
-       def sendQuit(self):
+
+       def setRun(self):
                raise NotImplementedError()
 
+       def setContinue(self):
+               raise NotImplementedError()
 
-       class ReadThread (threading.Thread):
-               """Thread which reads from sub-process output"""
+       def setStepover(self):
+               raise NotImplementedError()
 
-               def __init__( self, stream, callback, sleep=0.1):
-                       self.stream = stream
-                       self.fin = False
-                       self.callback = callback
-                       self.sleep = sleep
-                       self.eventFin = threading.Event()
-                       threading.Thread.__init__(self)
+       def setStepin(self):
+               raise NotImplementedError()
 
-               def run(self):
+       def setStepout(self):
+               raise NotImplementedError()
 
-                       try:
-                               while True:
-                                       #Wait until data is available
-                                       rlist, wlist, xlist = select.select([self.stream], [], [], self.sleep)
+       def setQuit(self):
+               raise NotImplementedError()
 
-                                       #If we should finish, finish
-                                       if self.fin:
-                                               break
+       def setBreakpoint(self, file, lineno, condition=False):
+               raise NotImplementedError()
 
-                                       #Got new data
-                                       if len(rlist) > 0:
-                                               fd = rlist[0]
-                                               str = fd.read(1)
-                                               #Call callbacks
-                                               self.callback(str)
-                       except:
-                               pass
+       def delBreakpoint(self, breakpoint):
+               raise NotImplementedError()
 
-                       #Set the finished event
-                       self.eventFin.set()
+       def getExpression(self, expr):
+               raise NotImplementedError()
 
+       def listCodeSnippet(self):
+               raise NotImplementedError()
 
+       def getBacktrace(self):
+               raise NotImplementedError()
 
+       def waitForPrompt(self, his):           
+               raise NotImplementedError()
 
-class GdbTerminal (DbgTerminal):
+       def testForActivity(self, his):
+               raise NotImplementedError()
 
-       def getCommand( self, binary ):
-               return "gdb --fullname %s" % (binary,)
+       def testForInactivity(self, his):
+               raise NotImplementedError()
 
-       def sendBreak(self, file, lineno):
-               self.gdbin.write("break %s:%d\n" % (file, lineno))
+       def setActive(self, isactive):
+               self.isactive = isactive
 
-       def sendContinue(self):
-               self.gdbin.write("cont\n") 
+       def isActive(self):
+               return self.isactive
 
-       def sendRun(self):
-               self.gdbin.write("run\n") 
+       
 
-       def sendInspectVar(self, var):
-               self.sendInspectExpr(var)
+def quitHandler(*w):
+       try:
+               gtk.main_quit()
+       except:
+               pass
 
-       def sendInspectExpr(self, expr):
-               self.gdbin.write("print %s\n" % (expr,))
 
-       def sendSetTTY(self, ttyname):
-               self.gdbin.write("set inferior-tty %s\n" % (ttyname,))
+def relToAbsPath(absfile, relfile):
+       """When an absfile is given and a relfile is given by
+       relative paths relative to absfile, determine the abs
+       path of relfile"""
 
-       def sendQuit(self):
-               self.gdbin.write("quit\n")
-               DbgTerminal.stop(self)
+       #Get directories except for "." parts
+       relsplit = filter(lambda x: x!=".", string.split(relfile, os.sep))
+       #Get the directories of absfile withouth the trailing filename 
+       abssplit = string.split(absfile, os.sep)[:-1]
 
+       #Determine number of ".." and remove them
+       up=0
+       while relsplit[0] == "..":
+               up += 1
+               del relsplit[0]
+               del abssplit[-1]
 
+       return string.join(abssplit + relsplit, os.sep)
 
-if __name__ == "__main__":
 
-       def tostdout(str):
-               sys.stdout.write(str)
-               sys.stdout.flush()
+class DbgWindow (gtk.Window):
 
-       try:
+       clientIOWnd = None
 
-               term = GdbTerminal( "./main", tostdout, tostdout)
-               term.sendBreak("main.cpp", 13)
-               term.sendRun()
-               term.childin.write("1\n");
-               term.childin.write("2\n");
-               term.sendInspectVar("a+b")
-               term.sendContinue()
 
+       def __init__(self, terminal):
 
-               while not term.iseof():
+               #Set up some members
+               self.terminal = terminal
 
-                       cmd = sys.stdin.readline()
+               #Set up GTK stuff
+               gtk.Window.__init__(self)
+               self.connect("destroy", quitHandler)
 
-                       if term.iseof():
-                               break
+               #Set title and add terminal
+               self.set_title("Debugger I/O")
+               self.terminal.history = []
+               self.terminal.history_length = 5
+               self.add(self.terminal)
 
-                       if cmd == "quit\n":
-                               term.sendQuit()
-                       else:
-                               term.gdbin.write(cmd)
+               #Show the window
+               self.show_all()
 
-       except Exception, e:
-               print e
-       except:
-               pass
+       def toggleClientIOWindow(self):
+               if not self.clientIOWnd:
+                       self.clientIOWnd = ClientIOTerminal.ClientIOWindow(self, \
+                                       self.terminal.client_ptymaster)
+               else:
+                       self.clientIOWnd.destroy()
+                       self.clientIOWnd = None
 
+       def isClientIOWindowExisting(self):
+               return self.clientIOWnd != None
 
-       print "stopping..."
-       term.stop()
 
-