#!/usr/bin/env python

#  GTK Interactive Console
#  (C) 2003, Jon Anderson
#  (C) 2007 Imperial College London and others.
# Please see the AUTHORS file for a full list of contributors.

#  See www.python.org/2.2/license.htm for
#  license details.
#

import gtk
import gtk.gdk
import code
import sys
import pango

import __builtin__
import __main__

import debug

banner = """GTK Interactive Python Console
%s
""" % sys.version

class Completer:
  """
  Taken from rlcompleter, with readline references stripped, and a local dictionary to use.
  """
  def __init__(self, locals):
    self.locals = locals

  def complete(self, text, state):
    """Return the next possible completion for 'text'.
    This is called successively with state == 0, 1, 2, ... until it
    returns None.  The completion should begin with 'text'.

    """
    if state == 0:
      if "." in text:
        self.matches = self.attr_matches(text)
      else:
        self.matches = self.global_matches(text)
    try:
      return self.matches[state]
    except IndexError:
      return None

  def global_matches(self, text):
    """Compute matches when text is a simple name.

    Return a list of all keywords, built-in functions and names
    currently defines in __main__ that match.

    """
    import keyword
    matches = []
    n = len(text)
    for list in [keyword.kwlist,__builtin__.__dict__.keys(),__main__.__dict__.keys(), self.locals.keys()]:
      for word in list:
        if word[:n] == text and word != "__builtins__":
          matches.append(word)
    return matches

  def attr_matches(self, text):
    """Compute matches when text contains a dot.

    Assuming the text is of the form NAME.NAME....[NAME], and is
    evaluatable in the globals of __main__, it will be evaluated
    and its attributes (as revealed by dir()) are used as possible
    completions.  (For class instances, class members are are also
    considered.)

    WARNING: this can still invoke arbitrary C code, if an object
    with a __getattr__ hook is evaluated.

    """
    import re
    m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
    if not m:
      return
    expr, attr = m.group(1, 3)
    object = eval(expr, __main__.__dict__, self.locals)
    words = dir(object)
    if hasattr(object,'__class__'):
      words.append('__class__')
      words = words + get_class_members(object.__class__)
    matches = []
    n = len(attr)
    for word in words:
      if word[:n] == attr and word != "__builtins__":
        matches.append("%s.%s" % (expr, word))
    return matches

def get_class_members(klass):
  ret = dir(klass)
  if hasattr(klass,'__bases__'):
     for base in klass.__bases__:
       ret = ret + get_class_members(base)
  return ret



class OutputStream:
  """
  A Multiplexing output stream.
  It can replace another stream, and tee output to the original stream and too
  a GTK textview.
  """
  def __init__(self,view,old_out,style):
    self.view = view
    self.buffer = view.get_buffer()
    self.mark = self.buffer.create_mark("End",self.buffer.get_end_iter(), False )
    self.out = old_out
    self.style = style
    self.tee = 1

  def write(self,text):
    if self.tee:
      debug.dwrite(self.out, text, newline = False, flush = False)

    end = self.buffer.get_end_iter()

    if not self.view  == None:
      self.view.scroll_to_mark(self.mark, 0, True, 1, 1)

    self.buffer.insert_with_tags(end,text,self.style)
    
  def flush(self):
    self.view.queue_draw()
  
    return

class GTKInterpreterConsole(gtk.ScrolledWindow):
  """
  An InteractiveConsole for GTK. It's an actual widget,
  so it can be dropped in just about anywhere.
  """
  def __init__(self, locals = None):
    gtk.ScrolledWindow.__init__(self)
    self.set_policy (gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)

    self.text = gtk.TextView()
    self.text.set_wrap_mode(True)

    self.interpreter = code.InteractiveInterpreter(locals)

    self.completer = Completer(self.interpreter.locals)
    self.buffer = []
    self.history = []
    self.banner = banner
    self.ps1 = ">>> "
    self.ps2 = "... "

    self.text.add_events( gtk.gdk.KEY_PRESS_MASK )
    self.text.connect( "key_press_event", self.key_pressed )

    self.current_history = -1

    self.mark = self.text.get_buffer().create_mark("End",self.text.get_buffer().get_end_iter(), False )

            #setup colors
    self.style_banner = gtk.TextTag("banner")
    self.style_banner.set_property( "foreground", "saddle brown" )

    self.style_ps1 = gtk.TextTag("ps1")
    self.style_ps1.set_property( "foreground", "DarkOrchid4" )
    self.style_ps1.set_property( "editable", False )
    self.style_ps1.set_property("font", "courier" )

    self.style_ps2 = gtk.TextTag("ps2")
    self.style_ps2.set_property( "foreground", "DarkOliveGreen" )
    self.style_ps2.set_property( "editable", False  )
    self.style_ps2.set_property("font", "courier" )

    self.style_out = gtk.TextTag("stdout")
    self.style_out.set_property( "foreground", "midnight blue" )
    self.style_err = gtk.TextTag("stderr")
    self.style_err.set_property( "style", pango.STYLE_ITALIC )
    self.style_err.set_property( "foreground", "red" )

    self.text.get_buffer().get_tag_table().add(self.style_banner)
    self.text.get_buffer().get_tag_table().add(self.style_ps1)
    self.text.get_buffer().get_tag_table().add(self.style_ps2)
    self.text.get_buffer().get_tag_table().add(self.style_out)
    self.text.get_buffer().get_tag_table().add(self.style_err)

    self.stdout = OutputStream(self.text,sys.stdout,self.style_out)
    self.stderr = OutputStream(self.text,sys.stderr,self.style_err)

    sys.stderr = self.stderr
    sys.stdout = self.stdout

    self.current_prompt = None

    self.write_line(self.banner, self.style_banner)
    self.prompt_ps1()

    self.add(self.text)
    self.text.show()



  def reset_history(self):
    self.history = []

  def reset_buffer(self):
    self.buffer = []

  def prompt_ps1(self):
    self.current_prompt = self.prompt_ps1
    self.write_line(self.ps1,self.style_ps1)

  def prompt_ps2(self):
    self.current_prompt = self.prompt_ps2
    self.write_line(self.ps2,self.style_ps2)

  def write_line(self,text,style=None):
    start,end = self.text.get_buffer().get_bounds()
    if style==None:
      self.text.get_buffer().insert(end,text)
    else:
      self.text.get_buffer().insert_with_tags(end,text,style)

    self.text.scroll_to_mark(self.mark, 0, True, 1, 1)

  def push(self, line):

    self.buffer.append(line)
    if len(line) > 0:
      self.history.append(line)

    source = "\n".join(self.buffer)

    more = self.interpreter.runsource(source, "<<console>>")

    if not more:
      self.reset_buffer()

    return more

  def key_pressed(self,widget,event):
    if event.keyval == gtk.gdk.keyval_from_name('Return'):
      return self.execute_line()

    if event.keyval == gtk.gdk.keyval_from_name('Up'):
      self.current_history = self.current_history - 1
      if self.current_history < - len(self.history):
        self.current_history = - len(self.history)
      return self.show_history()
    elif event.keyval == gtk.gdk.keyval_from_name('Down'):
      self.current_history = self.current_history + 1
      if self.current_history > 0:
        self.current_history = 0
      return self.show_history()
    elif event.keyval == gtk.gdk.keyval_from_name( 'Home'):
      l = self.text.get_buffer().get_line_count() - 1
      start = self.text.get_buffer().get_iter_at_line_offset(l,4)
      self.text.get_buffer().place_cursor(start)
      return True
    elif event.keyval == gtk.gdk.keyval_from_name( 'space') and event.state & gtk.gdk.CONTROL_MASK:
      return self.complete_line()
    return False

  def show_history(self):
    if self.current_history == 0:
      return True
    else:
      self.replace_line( self.history[self.current_history] )
      return True

  def current_line(self):
    start,end = self.current_line_bounds()
    return self.text.get_buffer().get_text(start,end, True)

  def current_line_bounds(self):
    txt_buffer = self.text.get_buffer()
    l = txt_buffer.get_line_count() - 1

    start = txt_buffer.get_iter_at_line(l)
    if start.get_chars_in_line() >= 4:
      start.forward_chars(4)
    end = txt_buffer.get_end_iter()
    return start,end

  def replace_line(self,txt):
    start,end = self.current_line_bounds()
    self.text.get_buffer().delete(start,end)
    self.write_line(txt)

  def execute_line(self):
    line = self.current_line()

    self.write_line("\n")

    more = self.push(line)

    self.text.get_buffer().place_cursor(self.text.get_buffer().get_end_iter())

    if more:
        self.prompt_ps2()
    else:
        self.prompt_ps1()


    self.current_history = 0

    return True

  def complete_line(self):
    line = self.current_line()
    tokens = line.split()
    token = tokens[-1]

    completions = []
    p = self.completer.complete(token,len(completions))
    while p != None:
      completions.append(p)
      p = self.completer.complete(token, len(completions))

    if len(completions) != 1:
      self.write_line("\n")
      self.write_line("\n".join(completions), self.style_ps1)
      self.write_line("\n")
      self.current_prompt()
      self.write_line(line)
    else:
      i = line.rfind(token)
      line = line[0:i] + completions[0]
      self.replace_line(line)

    return True
