"""Extension to execute code outside the Python shell window.

This adds two commands (to the Edit menu, until there's a separate
Python menu):

- Run module (F5) is equivalent to either import or reload of the
current module.  The window must have been saved previously. The
module only gets added to sys.modules, it doesn't get added to
anyone's namespace; you can import it in the shell if you need to.  If
this generates any output to sys.stdout or sys.stderr, a new output
window is created to display that output. The two streams are
distinguished by their text color.

- Debug module (Control-F5) does the same but executes the module's
code in the debugger.

When an unhandled exception occurs in Run module, the stack viewer is
popped up.  This is not done in Debug module, because you've already
had an opportunity to view the stack.  In either case, the variables
sys.last_type, sys.last_value, sys.last_traceback are set to the
exception info.

"""

import sys
import os
import imp
import linecache
import traceback
import tkMessageBox
from OutputWindow import OutputWindow

# XXX These helper classes are more generally usable!

class OnDemandOutputWindow:

    tagdefs = {
        "stdout":  {"foreground": "blue"},
        "stderr":  {"foreground": "#007700"},
    }   
    
    def __init__(self, flist):
        self.flist = flist
        self.owin = None
    
    def write(self, s, tags, mark):
        if not self.owin:
            self.setup()
        self.owin.write(s, tags, mark)
    
    def setup(self):
        self.owin = owin = OutputWindow(self.flist)
        text = owin.text
        for tag, cnf in self.tagdefs.items():
            if cnf:
                apply(text.tag_configure, (tag,), cnf)
        text.tag_raise('sel')
        self.write = self.owin.write

class PseudoFile:

    def __init__(self, owin, tags, mark="end"):
        self.owin = owin
        self.tags = tags
        self.mark = mark

    def write(self, s):
        self.owin.write(s, self.tags, self.mark)

    def writelines(self, l):
        map(self.write, l)

    def flush(self):
        pass


class ScriptBinding:
    
    keydefs = {
        '<<run-module>>': ['<F5>'],
        '<<debug-module>>': ['<Control-F5>'],
    }
    
    menudefs = [
        ('edit', [None,
                  ('Run module', '<<run-module>>'),
                  ('Debug module', '<<debug-module>>'),
                 ]
        ),
    ]

    def __init__(self, editwin):
        self.editwin = editwin
        # Provide instance variables referenced by Debugger
        # XXX This should be done differently
        self.flist = self.editwin.flist
        self.root = self.flist.root

    def run_module_event(self, event, debugger=None):
        if not self.editwin.get_saved():
            tkMessageBox.showerror("Not saved",
                                   "Please save first!",
                                   master=self.editwin.text)
            self.editwin.text.focus_set()
            return
        filename = self.editwin.io.filename
        if not filename:
            tkMessageBox.showerror("No file name",
                                   "This window has no file name",
                                   master=self.editwin.text)
            self.editwin.text.focus_set()
            return
        modname, ext = os.path.splitext(os.path.basename(filename))
        if sys.modules.has_key(modname):
            mod = sys.modules[modname]
        else:
            mod = imp.new_module(modname)
            sys.modules[modname] = mod
        mod.__file__ = filename
        saveout = sys.stdout
        saveerr = sys.stderr
        owin = OnDemandOutputWindow(self.editwin.flist)
        try:
            sys.stderr = PseudoFile(owin, "stderr")
            try:
                sys.stdout = PseudoFile(owin, "stdout")
                try:
                    if debugger:
                        debugger.run("execfile(%s)" % `filename`, mod.__dict__)
                    else:
                        execfile(filename, mod.__dict__)
                except:
                    (sys.last_type, sys.last_value,
                     sys.last_traceback) = sys.exc_info()
                    linecache.checkcache()
                    traceback.print_exc()
                    if not debugger and \
                       self.editwin.getvar("<<toggle-jit-stack-viewer>>"):
                        from StackViewer import StackBrowser
                        sv = StackBrowser(self.root, self.flist)
            finally:
                sys.stdout = saveout
        finally:
            sys.stderr = saveerr

    def debug_module_event(self, event):
        import Debugger
        debugger = Debugger.Debugger(self)
        self.run_module_event(event, debugger)

    def close_debugger(self):
        # Method called by Debugger
        # XXX This should be done differently
        pass
