# Xsrcwin.py; receives messages from PDB as to current file and line
# number, and updates the display of the nearby portion of the source
# code accordingly

# Author:  Norm Matloff

# Notes:
#    Fails if stop in interpreter. 

# usage:  python Xsrcwin.py port_number

import curses, socket, sys

class gb:  # globals
   scrn = None  # will point to window object
   row = None  # current row position
   src = None  # current source file
   srclen = None  # length in lines of the current source file
   winlen = None  # length in lines of the window
   winwid = None  # width in characters of the window
   srclines = None  # the contents of the source file
   maxdigits = None  # number of digits in the longest line number
   firstdisplayedlineno = None  # source line number now displayed at
                                # top of window
   currsrclineno = None  # source line number to be executed next
   restarting = None  # value of True indicates that a run has just begun

def ndigs(n):  # finds the number of decimal digits in n
   return len(str(n))

# this function displays the source file, starting at the top of the
# screen, and beginning with the row srcstartrow in gb.srclines
def dispsrc(srcstartrow):
   gb.scrn.clear()
   winrow = 0
   nlinestoshow = min(gb.srclen-srcstartrow,gb.winlen)
   for i in range(srcstartrow,srcstartrow+nlinestoshow):
      try:
         gb.scrn.addstr(winrow,0,gb.srclines[i])
      except: pass  # for easier debugging
      winrow += 1
   gb.firstdisplayedlineno = srcstartrow + 1
   gb.scrn.refresh()

# this function reads in the source file from disk, and copies it to the
# list gb.srclines, with each source file being prepended by the line
# number
def inputsrc(filename,lineno):
   gb.src = open(filename,'r')
   lns = gb.src.readlines()
   gb.srclen = len(lns)
   gb.maxdigits = ndigs(len(lns))
   lnno = 1
   gb.srclines = []
   for l in lns:
      # first form the line number, with padding on the left
      ndl = ndigs(lnno)
      tmp = (gb.maxdigits - ndl) * ' '
      tmp = tmp + str(lnno) + ' '
      # add room for current line marker
      tmp = tmp + '  '
      # now add the source line itself, truncated to fit the window
      # width, if necessary
      tmp = tmp + l[:-1]
      ntrunclinechars = min(gb.winwid,len(tmp))
      gb.srclines.append(tmp[:ntrunclinechars])
      lnno += 1
   dispsrc(max(0,lineno-5))

def updatewin(filename,lineno): 
   # 3 possible situations:
   #   1.  This current Xsrcwin.py session has now received its first
   #       PDB connection (gb.src = None).
   #   2.  Debuggee is restarting (gb.restarting = True).
   #   3.  This current Xsrcwin.py session has now received a PDB
   #       connection for a different debuggee (gb.src.name != filename).
   if not gb.src or gb.restarting or (gb.src.name != filename):
      if gb.src:  gb.src.close()
      inputsrc(filename,lineno)
      gb.restarting = False
   # check for new line being out of current screen range
   elif lineno < gb.firstdisplayedlineno or \
        lineno >= gb.firstdisplayedlineno + curses.LINES:
           dispsrc(max(0,lineno-5))
   # erase the 'N' at the old line, if it exists and is on the current
   # screen
   if gb.currsrclineno:
      csl = gb.currsrclineno
      if csl >= gb.firstdisplayedlineno and \
         csl < gb.firstdisplayedlineno + curses.LINES:
            currwinrow = csl - gb.firstdisplayedlineno
            gb.scrn.addstr(currwinrow,gb.maxdigits+1,' ')
   currwinrow = lineno - gb.firstdisplayedlineno
   thisrow = gb.srclines[lineno]
   try:
      gb.scrn.addstr(currwinrow,gb.maxdigits+1,'N')
   except: pass  # for easier debugging
   gb.currsrclineno = lineno
   gb.scrn.refresh()

def main():
   # set up server
   ls = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
   port = int(sys.argv[1])
   # listen for PDB connections 
   ls.bind(('', port))
   ls.listen(1)
   # switch to curses
   gb.scrn = curses.initscr()
   curses.noecho()
   curses.cbreak()
   while True:  # one iteration of this loop handles one PDB connection
      (conn, addr) = ls.accept()
      # set up use of makefile()
      flo = conn.makefile('r',0)
      gb.scrn.clear()
      gb.scrn.refresh()
      # other inits 
      gb.winlen = curses.LINES
      gb.winwid = curses.COLS
      gb.restarting = True  # added 8/31/07
      while True:  # one iteration of this loop handles one PDB command 
                   # of the current debuggee, e.g. one "continue" or one
                   # "next"
         # read from PDB, removing EOL; PDB will have sent us one of the
         # following:
         #    1.  The debuggee file name and the execution line number.
         #    2.  The string 'restarting'.
         #    3.  An empty line, to indicate that the user at PDB quit
         #        (by hitting the q key, not by hitting ctrl-c).
         frompdb = flo.readline()[:-1]  
         if frompdb == '': break
         if frompdb == 'restarting':  
            gb.restarting = True
            # read from PDB again to get file name and execution line number
            frompdb = flo.readline()[:-1]  
         (filename,lineno) = frompdb.split()
         lineno = int(lineno)
         if filename != '<string>':  # pass if in interpreter
            updatewin(filename,lineno)
   
if __name__ =='__main__': main()
