#-----------------------------------------------------------------------------
# Name:        PySourceView.py
# Purpose:     Python Source code View
#
# Author:      Riaan Booysen
#
# Created:     2000/04/26
# RCS-ID:      $Id: PySourceView.py,v 1.62 2007/07/02 15:01:16 riaan Exp $
# Copyright:   (c) 1999 - 2007 Riaan Booysen
# Licence:     GPL
#-----------------------------------------------------------------------------
print 'importing Views.PySourceView'

import os, string, bdb, sys, types, keyword

import wx
import wx.stc

import ProfileView, Search, Help, Preferences, ShellEditor, Utils, SourceViews
from Utils import _

from SourceViews import EditorStyledTextCtrl
from StyledTextCtrls import PythonStyledTextCtrlMix, BrowseStyledTextCtrlMix,\
     FoldingStyledTextCtrlMix, AutoCompleteCodeHelpSTCMix, \
     CallTipCodeHelpSTCMix, DebuggingViewSTCMix, idWord, word_delim, object_delim
from Preferences import keyDefs
import methodparse, moduleparse, sourceconst
import wxNamespace

mrkCnt = SourceViews.markerCnt
brkPtMrk, tmpBrkPtMrk, disabledBrkPtMrk, stepPosMrk = range(mrkCnt+1, mrkCnt+5)
SourceViews.markerCnt = mrkCnt + 4

brwsIndc, synErrIndc = range(0, 2)
lineNoMrg, symbolMrg, foldMrg = range(0, 3)

class ShellNameNotFound(Exception): pass

class PythonSourceView(EditorStyledTextCtrl, PythonStyledTextCtrlMix,
                       BrowseStyledTextCtrlMix, FoldingStyledTextCtrlMix,
                       AutoCompleteCodeHelpSTCMix, CallTipCodeHelpSTCMix,
                       DebuggingViewSTCMix):
    viewName = 'Source'
    viewTitle = _('Source')
    
    breakBmp = 'Images/Debug/Breakpoints.png'
    runCrsBmp = 'Images/Editor/RunToCursor.png'
    modInfoBmp = 'Images/Modules/InfoBlock.png'
    def __init__(self, parent, model):
        a1 = (('-', None, '', ''),
              (_('Comment'), self.OnComment, '-', 'Comment'),
              (_('Uncomment'), self.OnUnComment, '-', 'Uncomment'),
              (_('Indent'), self.OnIndent, '-', 'Indent'),
              (_('Dedent'), self.OnDedent, '-', 'Dedent'),
              ('-', None, '-', ''),
              (_('Run to cursor'), self.OnRunToCursor, self.runCrsBmp, ''),
              # evts defined in DebuggingViewSTCMix
              (_('Toggle breakpoint'), self.OnSetBreakPoint, self.breakBmp, 'ToggleBrk'),
              (_('Load breakpoints'), self.OnLoadBreakPoints, '-', ''),
              (_('Save breakpoints'), self.OnSaveBreakPoints, '-', ''),
              ('-', None, '', ''),
              (_('Add module runner'), self.OnAddSimpleApp, '-', ''),
              ('-', None, '-', ''),
              (_('Add module info'), self.OnAddModuleInfo, self.modInfoBmp, ''),
              (_('Add comment line'), self.OnAddCommentLine, '-', 'DashLine'),
              (_('Code transformation'), self.OnCodeTransform, '-', 'CodeXform'),
              (_('Code completion'), self.OnCompleteCode, '-', 'CodeComplete'),
              (_('Call tips'), self.OnParamTips, '-', 'CallTips'),
              (_('Browse to'), self.OnBrowseTo, '-', 'BrowseTo'),
              ('-', None, '-', ''),
              (_('Context help'), self.OnContextHelp, '-', 'ContextHelp'))

        wxID_PYTHONSOURCEVIEW = wx.NewId()

        EditorStyledTextCtrl.__init__(self, parent, wxID_PYTHONSOURCEVIEW,
          model, a1, -1)
        PythonStyledTextCtrlMix.__init__(self, wxID_PYTHONSOURCEVIEW,
              (lineNoMrg, Preferences.STCLineNumMarginWidth))
        BrowseStyledTextCtrlMix.__init__(self, brwsIndc)
        FoldingStyledTextCtrlMix.__init__(self, wxID_PYTHONSOURCEVIEW, foldMrg)
        AutoCompleteCodeHelpSTCMix.__init__(self)
        CallTipCodeHelpSTCMix.__init__(self)
        DebuggingViewSTCMix.__init__(self, (brkPtMrk, tmpBrkPtMrk,
              disabledBrkPtMrk, stepPosMrk))

        self.lsp = 0
        # XXX These could be persisted
        self.lastRunParams = ''
        self.lastDebugParams = ''

        # last line # that was edited
        self.damagedLine = -1

        self.setupDebuggingMargin(symbolMrg)

        # Error indicator
        self.IndicatorSetStyle(synErrIndc, wx.stc.STC_INDIC_SQUIGGLE)
        self.IndicatorSetForeground(synErrIndc, Preferences.STCSyntaxErrorColour)

        self.SetIndentationGuides(Preferences.STCIndentationGuides)

        self.Bind(wx.stc.EVT_STC_CHARADDED, self.OnAddChar, id=wxID_PYTHONSOURCEVIEW)

        # still too buggy
        self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModified, id=wxID_PYTHONSOURCEVIEW)
        #self.Bind(wx.EVT_FIX_PASTE, self.OnFixPaste)

        self.active = True

    def saveNotification(self):
        EditorStyledTextCtrl.saveNotification(self)
        # XXX update breakpoint filename

    def refreshCtrl(self):
        EditorStyledTextCtrl.refreshCtrl(self)
        self.setInitialBreakpoints()

    def processComment(self, textLst, idntBlock):
        return ['##%s'%l for l in textLst]

    def processUncomment(self, textLst, idntBlock):
        for idx in range(len(textLst)):
            if len(textLst[idx]) >= 2 and textLst[idx][:2] == '##':
                textLst[idx] = textLst[idx][2:]
        return textLst

    def processIndent(self, textLst, idntBlock):
        return ['%s%s'%(idntBlock, t) for t in textLst]

    def processDedent(self, textLst, idntBlock):
        indentLevel=len(idntBlock)
        for idx in range(len(textLst)):
            if len(textLst[idx]) >= indentLevel and \
              textLst[idx][:indentLevel] == idntBlock:
                textLst[idx] = textLst[idx][indentLevel:]
        return textLst

    def checkCallTipHighlight(self):
        if self.CallTipActive():
            pass

#---Call Tips-------------------------------------------------------------------

    def OnParamTips(self, event):
        if Preferences.autoRefreshOnCodeComplete:
            self.refreshModel()
        self.callTipCheck()

    def getTipValue(self, word, lnNo):
        """ Overwritten Mixin method, returns string to display as tool tip """
        module = self.model.getModule()
        objPth = word.split('.')
        safesplit = methodparse.safesplitfields

        if (len(objPth) == 2 or len(objPth) == 3) and objPth[0] == 'wx':
            wxPyTip = self.getFirstContinousBlock(
                       self.checkWxPyTips(module, word))
            if wxPyTip: return wxPyTip

        if len(objPth) == 1:
            res = self.getNameSig(objPth[0], module)
            if res is not None: return res

        cls = module.getClassForLineNo(lnNo)
        # inside classes
        if cls:
            if len(objPth) == 2 and objPth[0] == 'self':
                if cls.methods.has_key(objPth[1]):
                    return self.prepareModSigTip(objPth[1],
                          cls.methods[objPth[1]].signature)
                elif cls.super and type(cls.super[0]) is type(''):
                    return self.getFirstContinousBlock(
                      self.checkWxPyMethodTips(module, cls.super[0], objPth[1]))

            if len(objPth) == 2:
                methName, codeBlock = cls.getMethodForLineNo(lnNo)
                res = self.getNameMethodSig(objPth[0], objPth[1], module, codeBlock)
                if res is not None: return res


            if len(objPth) == 3 and objPth[0] == 'self':
                return self.getFirstContinousBlock(
                    self.getAttribSig(module, cls, objPth[1], objPth[2]))
        # global and function scope
        else:
            if len(objPth) == 2:
                codeBlock = module.getFunctionForLineNo(lnNo)
                res = self.getNameMethodSig(objPth[0], objPth[1], module, codeBlock)
                if res is not None: return res

        return self.checkShellTips(word)

    def getNameSig(self, name, module):
        if module.classes.has_key(name) and \
              module.classes[name].methods.has_key('__init__'):
            return self.prepareModSigTip(name,
                module.classes[name].methods['__init__'].signature)
        elif module.functions.has_key(name):
            return self.prepareModSigTip(name, module.functions[name].signature)
        elif __builtins__.has_key(name):
            return self.getFirstContinousBlock(__builtins__[name].__doc__)
        return None

    def checkShellTips(self, objPth):
        if self.model.editor.shell:
            return self.model.editor.shell.getTipValue(objPth, -1)
        else:
            return ''

    def checkWxPyTips(self, module, name):
        if module.imports.has_key('wx'):
            obj = wxNamespace.getWxObjPath(name)
            if obj:
                return self.prepareWxModSigTip(obj.__init__.__doc__)
        return ''

    def checkWxPyMethodTips(self, module, cls, name):
        if module.imports.has_key('wx'):
            Cls = wxNamespace.getWxObjPath(cls)
            if Cls and hasattr(Cls, name):
                meth = getattr(Cls, name)
                return self.prepareWxModSigTip(meth.__doc__)
        return ''

    def getNameMethodSig(self, name, method, module, codeBlock=None):
        if codeBlock:
            if name in codeBlock.locals:
                objType = codeBlock.locals[name].objtype
                res = self.getTypeSig(objType, method, module)
                if res is not None: return res
        if name in module.globals:
            objType = module.globals[name].signature
            res = self.getTypeSig(objType, method, module)
            if res is not None: return res

        return None

    def getAttribSig(self, module, cls, attrib, meth):
        if cls.attributes.has_key(attrib):
            objtype = cls.attributes[attrib][0].signature
            if module.classes.has_key(objtype) and \
                  module.classes[objtype].methods.has_key(meth):
                return module.classes[objtype].methods[meth].signature
            klass = wxNamespace.getWxClass(objtype)
            if klass:
                if hasattr(klass, meth):
                    return self.prepareWxModSigTip(getattr(klass, meth).__doc__)
        return ''

    sigTypeMap = {'dict': dict, 'list': list, 'string': str, 'tuple': tuple,
                  'number': int}

    def getTypeSig(self, objType, method, module):
        if self.sigTypeMap.has_key(objType):
            tpe = self.sigTypeMap[objType]
            try:
                return getattr(getattr(tpe, method), '__doc__')
            except AttributeError:
                pass

        if objType in module.classes and \
              module.classes[objType].methods.has_key(method):
            return self.prepareModSigTip(method,
                        module.classes[objType].methods[method].signature)
        meth = wxNamespace.getWxObjPath(objType+'.'+method)
        if meth and meth.__doc__:
            return self.prepareWxModSigTip(meth.__doc__)

        return None

    def prepareWxModSigTip(self, tip):
        if not tip:
            return ''
        paramStart = tip.find('(')
        if paramStart != -1:
            paramEnd = tip.rfind(')')
            if paramEnd != -1:
                return self.prepareModSigTip(tip[:paramStart],
                                             tip[paramStart+1:paramEnd]).strip()
        return tip.strip()

    def prepareModSigTip(self, name, paramsStr):
        if paramsStr.startswith('self,'):
            paramsStr = paramsStr[5:].strip()
        elif paramsStr == 'self':
            paramsStr = ''
        return '%s(%s)'%(name, paramsStr)

#---Code Completion-------------------------------------------------------------

    def OnCompleteCode(self, event):
        if Preferences.autoRefreshOnCodeComplete:
            self.refreshModel()
        self.codeCompCheck()

    def getCodeCompOptions(self, word, rootWord, matchWord, lnNo):
        #print 'getCodeCompOptions', word, rootWord, matchWord, lnNo
        """ Overwritten Mixin method, returns list of code completion names """
        objPth = rootWord.split('.')

        if objPth and objPth[0] == 'wx':
            return wxNamespace.getWxNamespaceForObjPath(rootWord)

        module = self.model.getModule()
        cls = module.getClassForLineNo(lnNo)
        if cls:
            if len(objPth) == 1:
                # obj attrs
                if objPth[0] == 'self':
                    return self.getAttribs(cls)
                # globals/locals
                elif objPth[0] == '':
                    if cls.super:
                        wrds = self.GetLine(lnNo).strip().split()
                        # check for def *, add inherited methods
                        if wrds and wrds[0] == 'def':
                            if isinstance(cls.super[0], moduleparse.Class):
                                return self.getAttribs(cls.super[0], methsOnly=True)
                            elif type(cls.super[0]) in types.StringTypes:
                                super = wxNamespace.getWxClass(cls.super[0])
                                if super:
                                    return self.getWxAttribs(super)
                    methName, meth = cls.getMethodForLineNo(lnNo)
                    return self.getCodeNamespace(module, meth)
                else:
                    methName, codeBlock = cls.getMethodForLineNo(lnNo)
                    res = self.getNameAttribs(objPth[0], module, codeBlock)
                    if res is not None: return res

            elif len(objPth) == 2:
                # attributes of attrs with known type
                if objPth[0] == 'self':
                    attrib = objPth[1]
                    return self.getAttribAttribs(module, cls, attrib)

        else:
            # handles, base classes in class declaration statement
            cls = module.getClassForLineNo(lnNo+1)
            if cls:
                return self.getAllClasses(module)
            # handles function and global scope
            if len(objPth) == 1 and objPth[0] == '':
                func = module.getFunctionForLineNo(lnNo)
                return self.getCodeNamespace(module, func)
            if len(objPth) == 1:
                codeBlock = module.getFunctionForLineNo(lnNo)
                res = self.getNameAttribs(objPth[0], module, codeBlock)
                if res is not None: return res

        return self.getShellNames(objPth)

    def importInShell(self, name, fromlist=()):
        locs = self.model.editor.shell.getShellLocals()
        try:
            mod = __import__(name, {}, locs, fromlist) 
        except Exception:
            # just swallow all errors, no way to guess what code could execute 
            # in the global scope of arbitrary modules
            return None
        
        if fromlist and hasattr(mod, fromlist[0]):
            return getattr(mod, fromlist[0])

        components = name.split('.') 
        for comp in components[1:]: 
            if hasattr(mod, comp):
                mod = getattr(mod, comp) 
        return mod 

    def getImportedShellObj(self, words):
        module = self.model.getModule()
        imports = module.imports.keys() + module.from_imports.keys() +\
                module.from_imports_names.keys()
        if self.model.editor.shell:
            shellLocals = self.model.editor.shell.getShellLocals()
            name = words[0]
            if name in imports:
                if Preferences.importOnCodeComplete and name not in shellLocals:
                    fromlist = []
                    if name in module.from_imports_names:
                        fromlist = [name]
                        name = module.from_imports_names[name]
                    
                    m = self.importInShell(name, fromlist)
                    if m is None:
                        raise ShellNameNotFound
                    shellLocals[name] = m

                if name in shellLocals:
                    obj = shellLocals[name]
                    for word in words[1:]:
                        if hasattr(obj, word):
                            obj = getattr(obj, word)
                        else:
                            raise ShellNameNotFound
                    return obj
        raise ShellNameNotFound

    def getShellNames(self, words):
        try:
            return ShellEditor.recdir(self.getImportedShellObj(words))
        except ShellNameNotFound:
            return []

    def getWxAttribs(self, cls, mems = None, methsOnly=False):
        if mems is None: mems = []

        for base in getattr(cls, '__bases__', ()):
            self.getWxAttribs(base, mems, methsOnly)

        # wx attrs are always methods
        mems.extend(dir(cls))
        return mems

    def getNameAttribs(self, name, module, codeBlock=None):
        if codeBlock:
            if name in codeBlock.locals:
                objType = codeBlock.locals[name].objtype
                res = self.getTypeAttribs(objType, module)
                if res is not None: return res
        if name in module.globals:
            objType = module.globals[name].signature
            res = self.getTypeAttribs(objType, module)
            if res is not None: return res

        return None

    def getTypeAttribs(self, objType, module):
        if self.attrTypeMap.has_key(objType):
            return self.attrTypeMap[objType]
        if objType in module.classes:
            return self.getAttribs(module.classes[objType])
        WxClass = wxNamespace.getWxClass(objType)
        if WxClass:
            return self.getWxAttribs(WxClass)
        return None

    def getAttribs(self, cls, methsOnly=False):
        loopCls = cls
        lst = []
        while loopCls:
            lst.extend(loopCls.methods.keys())
            if not methsOnly:
                lst.extend(loopCls.attributes.keys())

            if len(loopCls.super):
                prnt = loopCls.super[0]
                # Modules
                if isinstance(prnt, moduleparse.Class):
                    loopCls = prnt
                # Possible wxPython ancestor
                else:
                    WxClass = wxNamespace.getWxClass(prnt)
                    if WxClass:
                        lst.extend(self.getWxAttribs(WxClass))
                    loopCls = None
            else:
                loopCls = None

        return lst

    attrTypeMap = {'dict': dir({}), 'list': dir([]), 'string': dir(''),
                   'tuple': dir(()), 'number': dir(0)}

    def getAttribAttribs(self, module, cls, attrib):
        if cls.attributes.has_key(attrib):
            objtype = cls.attributes[attrib][0].signature
            if self.attrTypeMap.has_key(objtype):
                return self.attrTypeMap[objtype]
            elif module.classes.has_key(objtype):
                return self.getAttribs(module.classes[objtype])
            else:
                klass = wxNamespace.getWxClass(objtype)
                if klass:
                    return self.getWxAttribs(klass)
        return []

    def getCodeNamespace(self, module, block=None):
        names = []

        names.extend(module.imports.keys())
        names.extend(module.from_imports.keys())
        names.extend(module.from_imports_names.keys())
        names.extend(module.class_order)
        names.extend(module.function_order)
        names.extend(module.global_order)
        names.extend(__builtins__.keys())
        names.extend(keyword.kwlist)

        if block:
            names.extend(block.localnames())

        return names

    def getAllClasses(self, module):
        names = []
        names.extend(module.from_imports_names.keys())
        names.extend(module.class_order)

        return names

#-------Browsing----------------------------------------------------------------

    def findNameInModule(self, module, word):
        """ Find either a classname, global function or attribute in module """
        if module.classes.has_key(word):
            return module.classes[word].block.start-1
        elif module.functions.has_key(word):
            return module.functions[word].start-1
        elif module.globals.has_key(word):
            return module.globals[word].start-1
        else:
            return None

    def gotoName(self, module, word, currLineNo):
        line = self.findNameInModule(module, word)
        if line is not None:
            self.doClearBrwsLn()
            self.model.editor.addBrowseMarker(currLineNo)
            self.GotoLine(line)
            return True
        else:
            return False

    def handleBrwsObjAttrs(self, module, word, lineNo):
        cls = module.getClassForLineNo(lineNo)
        if cls:
            self.doClearBrwsLn()
            gotoLine = -1
            if cls.attributes.has_key(word):
                gotoLine = cls.attributes[word][0].start-1
            elif cls.methods.has_key(word):
                gotoLine = cls.methods[word].start-1
            elif cls.class_attributes.has_key(word):
                gotoLine = cls.class_attributes[word][0].start-1
            else:
                found, cls, block = module.find_declarer(cls, word, None)
                if found:
                    if type(block) == type([]):
                        gotoLine = block[0].start-1
                    else:
                        gotoLine = block.start-1
            if gotoLine != -1:
                self.model.editor.addBrowseMarker(lineNo)
                self.GotoLine(gotoLine)
            return True
        return False

    def handleBrwsWxNames(self, module, word, words, currLineNo):
        wxObj = wxNamespace.getWxObjPath(word)
        if wxObj is not None:
            if hasattr(wxObj, '__module__'):
                try: path, impType = self.model.findModule(wxObj.__module__)
                except ImportError: return False, None
                else:
                    if impType == 'package':
                        self.model.editor.addBrowseMarker(currLineNo)
                        model, ctrlr = self.model.editor.openOrGotoModule(
                              os.path.join(path, '__init__.py'))
                        return True, model
                    elif impType in ('name', 'module'):
                        self.model.editor.addBrowseMarker(currLineNo)
                        model, ctrlr = self.model.editor.openOrGotoModule(path)
                        if impType == 'name':
                            model.views['Source'].gotoName(model.getModule(), 
                                  words[-1], currLineNo)
                        return True, model
        return False, None
                

    def handleBrwsImports(self, module, word, currLineNo):
        words = word.split('.')

        # Standard imports
        if module.imports.has_key(word):
            self.doClearBrwsLn()

            try: path, impType = self.model.findModule(word)
            except ImportError: return False, None
            else:
                if impType == 'package':
                    self.model.editor.addBrowseMarker(currLineNo)
                    model, ctrlr = self.model.editor.openOrGotoModule(
                          os.path.join(path, '__init__.py'))
                    return True, model
                elif impType in ('name', 'module'):
                    self.model.editor.addBrowseMarker(currLineNo)
                    model, ctrlr = self.model.editor.openOrGotoModule(path)
                    return True, model
            return False, None
        # Handle module part of from module import name
        elif module.from_imports.has_key(word):
            try: path, impType = self.model.findModule(word)
            except ImportError: return False, None
            else:
                self.doClearBrwsLn()
                self.model.editor.addBrowseMarker(currLineNo)
                if impType == 'package':
                    path = os.path.join(path, '__init__.py')
                model, ctrlr = self.model.editor.openOrGotoModule(path)
                return True, model
        # Handle name part of from module import name
        elif module.from_imports_names.has_key(word):
            modName = module.from_imports_names[word]
            try: path, impType = self.model.findModule(modName, word)
            except ImportError: return False, None
            else:
                self.doClearBrwsLn()
                self.model.editor.addBrowseMarker(currLineNo)
                model, ctrlr = self.model.editor.openOrGotoModule(path)
                if impType == 'name':
                    model.views['Source'].gotoName(model.getModule(), word,
                          currLineNo)
                return True, model
        else:
            # Handle [package.]module.name
            if len(words) > 1:
                testMod = '.'.join(words[:-1])
                handled, model = self.handleBrwsImports(module, testMod, currLineNo)
                if handled is None:
                    return None, None # unhandled
                elif handled:
                    if not model.views['Source'].gotoName(model.getModule(),
                          words[-1], currLineNo):
                        wx.LogWarning(_('"%s" not found.')%words[-1])
                    return True, model
                else:
                    return False, None
            else:
                return None, None # unhandled

    def handleBrwsLocals(self, module, word, lineNo):
        # Check local names and parameters in methods and functions
        codeBlock = None
        cls = module.getClassForLineNo(lineNo)
        if cls:
            methName, codeBlock = cls.getMethodForLineNo(lineNo)
        else:
            codeBlock = module.getFunctionForLineNo(lineNo)

        if codeBlock:
            locals = codeBlock.localnames()
            if word in locals:
                self.doClearBrwsLn()
                self.model.editor.addBrowseMarker(lineNo)
                if word in codeBlock.locals.keys():
                    self.GotoLine(codeBlock.locals[word].lineno-1)
                else:
                    self.GotoLine(codeBlock.start-1)
                return True
        return False

    def handleBrwsRuntimeGlobals(self, word, currLineNo):
        # The current runtime values of the shell is inspected
        # as well as the wxPython namespaces
        if self.model.editor.shell:
            shellLocals = self.model.editor.shell.getShellLocals()
        else:
            shellLocals = {}

        if shellLocals.has_key(word):
            obj = shellLocals[word]
        #elif hasattr(wxNamespace, word):
        #    obj = getattr(wxNamespace, word)
        else:
            return False

        self.doClearBrwsLn()
        if hasattr(obj, '__init__') and type(obj.__init__) == types.MethodType:
            self.model.editor.addBrowseMarker(currLineNo)
            path = os.path.abspath(obj.__init__.im_func.func_code.co_filename)
            mod, cntrl = self.model.editor.openOrGotoModule(path)
            mod.views['Source'].GotoLine(obj.__init__.im_func.func_code.co_firstlineno -1)
        elif hasattr(obj, 'func_code'):
            self.model.editor.addBrowseMarker(currLineNo)
            path = os.path.abspath(obj.func_code.co_filename)
            mod, cntrl = self.model.editor.openOrGotoModule(path)
            mod.views['Source'].GotoLine(obj.func_code.co_firstlineno -1)
        else:
            return False
        return True

    def handleBrwsCheckImportStars(self, module, word, currLineNo):
        # try to match names from * imports modules currently open

        # Cache first time
        if not module.from_imports_star_cache:
            for starMod in module.from_imports_star:
                try:
                    module.from_imports_star_cache[starMod] = \
                          self.model.findModule(starMod)
                except ImportError: pass
        # work thru open * imported modules and try to match name
        for starMod in module.from_imports_star:
            try:
                res = module.from_imports_star_cache[starMod]
            except KeyError:
                res = None
                continue
            if res and self.model.editor.modules.has_key('file://'+res[0]):
                starModPage = self.model.editor.modules['file://'+res[0]]
                starModule = starModPage.model.getModule()
                lineNo = self.findNameInModule(starModule, word)
                if lineNo is not None:
                    self.model.editor.addBrowseMarker(currLineNo)
                    starModPage.focus()
                    starModPage.model.views['Source'].focus()
                    starModPage.model.views['Source'].GotoLine(lineNo)
                    return True
        return False

    def StyleVeto(self, style):
        """ Overridden from BrowseStyledTextCtrlMix, hook to block browsing
            over certain text styles """
        return style != 11

    def BrowseClick(self, word, line, lineNo, start, style):
        """ Overridden from BrowseStyledTextCtrlMix, jumps to declaration or
            opens module.

            Explicitly imported names are also handled.
        """
        module = self.model.getModule()
        words = word.split('.')
        debugger = self.model.editor.debugger

        if debugger and debugger.isDebugBrowsing():
            debugger.add_watch(word, True)
            return True
        # Obj names
        if len(words) >= 2 and words[0] == 'self':
            return self.handleBrwsObjAttrs(module, words[1], lineNo)
        # wx names
        if len(words) >= 2 and words[0] == 'wx':
            return self.handleBrwsWxNames(module, word, words, lineNo)
        # Imports
        handled, model = self.handleBrwsImports(module, word, lineNo)
        if handled is not None:
            return handled
        # Only non dotted names allowed past this point
        if len(words) != 1:
            return False
        # Check for module global classes, funcs and attrs
        if self.gotoName(module, word, lineNo):
            return True
        if self.handleBrwsLocals(module, word, lineNo):
            return True
        if self.handleBrwsRuntimeGlobals(word, lineNo):
            return True

        # As the last check, see if word is defined in any import * module
        if self.handleBrwsCheckImportStars(module, word, lineNo):
            return True

        return False

    def underlineWord(self, start, length):
        start, length = BrowseStyledTextCtrlMix.underlineWord(self, start, length)
        debugger = self.model.editor.debugger
        start, length = BrowseStyledTextCtrlMix.underlineWord(
            self, start, length)
        debugger = self.model.editor.debugger
        if debugger and debugger.isDebugBrowsing():
            word, line, lnNo, wordStart = self.getStyledWordElems(
                start, length)
            self.IndicatorSetForeground(0, Preferences.STCDebugBrowseColour)
            debugger.requestVarValue(word)
        else:
            self.IndicatorSetForeground(0, Preferences.STCCodeBrowseColour)

        return start, length

    def getBrowsableText(self, line, piv, lnStPs):
        return idWord(line, piv, lnStPs, object_delim)
##        debugger = self.model.editor.debugger
##        if debugger and debugger.isDebugBrowsing():
##            return idWord(line, piv, lnStPs, object_delim)
##        else:
##            return BrowseStyledTextCtrlMix.getBrowsableText(self, line, piv, lnStPs)

    def disableSource(self, doDisable):
        self.SetReadOnly(doDisable)
        #self.grayout(doDisable)

    def OnBrowseTo(self, event):
        word, line, lnNo, start, startOffset = self.getWordAtCursor()
        self.BrowseClick(word, line, lnNo, start, None)

#---Syntax checking-------------------------------------------------------------

    def checkChangesAndSyntax(self, lineNo=None):
        """ Called before moving away from a line """
        if not Preferences.checkSyntax:
            return
        if lineNo is None:
            lineNo = self.GetCurrentLine()
        if not Preferences.onlyCheckIfLineModified or \
              lineNo == self.damagedLine:
            slb, sle = ( self.GetStyleAt(self.PositionFromLine(lineNo)),
                         self.GetStyleAt(self.GetLineEndPosition(lineNo)-1) )
            line = self.GetLine(lineNo)
            self.checkSyntax( (line,), lineNo +1, self.GetLine,
                lineStartStyle = slb, lineEndStyle = sle)

    def indicateError(self, lineNo, errOffset, errorHint):
        """ Underline the point of error at the given line """
        # Display red squigly indicator underneath error
        errPos = self.PositionFromLine(lineNo-1)
        lineLen = self.LineLength(lineNo-1)
        nextLineLen = self.LineLength(lineNo)
        lenAfterErr = lineLen-errOffset+1
        styleLen = min(3, lenAfterErr)

        self.StartStyling(errPos+errOffset-2, wx.stc.STC_INDICS_MASK)
        self.SetStyling(styleLen, wx.stc.STC_INDIC1_MASK)
        # XXX I have to set the styling past the cursor position, why????
        self.SetStyling(lenAfterErr+nextLineLen, 0)#wx.stc.STC_INDIC0_MASK)

        if errorHint:
            self.model.editor.statusBar.setHint(errorHint, 'Error')

    def stripComment(self, line):
        segs = methodparse.safesplitfields(line, '#')
        if len(segs) > 1:
            line = segs[0] + ' '*(len(line)-len(segs[0])-1)+'\n'

        return line

    if_keywords = {'else':    'if 1',
                   'elif':    'if  ',
                  }
    try_keywords = {'except':  'try:pass',
                    'finally': 'try:pass',
                   }

    line_conts = ('(', '[', '{', '\\', ',')
    line_conts_ends = (')', ']', '}', ':', ',', '%')

    # Multiline strings, ignored currently based on scintilla style info
    ignore_styles = {6 : "'''", 7 : '"""'}

    syntax_errors = ('invalid syntax', 'invalid token')

    def checkSyntax(self, prevlines, lineNo, getPrevLine, compprefix='',
          indent='', contLinesOffset=0, lineStartStyle=0, lineEndStyle=0):
        # XXX Should also check syntax before saving.
        # XXX Multiline without brackets not caught
        # XXX Should check indent errors (multilines make this tricky)

        # XXX Unhandled cases:
        # XXX     ZopeCompanions 278
        # XXX     print a, b,
        # XXX         c, d
        errstr = 'Line %d valid '%lineNo
        prevline = prevlines[-1]
        stripprevline = prevline.strip()

        # Special case for blank lines
        if not stripprevline:
            self.model.editor.statusBar.setHint(errstr)
            return

        # Ignore multiline strings
        if lineStartStyle in self.ignore_styles.keys() or \
              lineEndStyle in self.ignore_styles.keys():
            self.model.editor.statusBar.setHint(errstr)
            return

        # Special case for \ followed by whitespace (don't strip it!)
        compstr = ''
        if stripprevline[-1] == '\\' and not compprefix:
            strs = prevline.split('\\')
            if strs[-1] and not strs[-1].strip():
                compstr = prevline.lstrip()

        # note, removes (flattens) indent
        if not compstr:
            compstr = compprefix+'\n'.join(\
                  [indent+line.strip() for line in prevlines])+'\n'

        try: import __future__
        except ImportError: compflags = 0
        else:
            # XXX Should check future imports from parsed module object
            # XXX Too expensive for now
            # mod = self.model.getModule()
            compflags = 0
            try: compflags = compflags | __future__.generators.compiler_flag
            except AttributeError: pass

        try:
            try:
                compile(compstr, '<editor>', 'single', compflags, 1)
            except TypeError:
                compile(compstr, '<editor>', 'single')
        except SyntaxError, err:
            err.lineno = lineNo
            errstr = err.__class__.__name__+': '+str(err)
            indentpl = prevline.find(stripprevline)
            if err[0] == 'unexpected EOF while parsing':
                errstr = 'incomplete (%d)'%lineNo
            elif err[0] == "'return' outside function":
                self.checkSyntax(prevlines, lineNo, getPrevLine,
                      'def func():\n', ' ')
                return
            elif err[0] == "'yield' outside function":
                self.checkSyntax(prevlines, lineNo, getPrevLine,
                      'def func():\n', ' ')
                return
            elif err[0] == 'expected an indented block':
                errstr = errstr + ' ignored'
            elif err[0] in ("'break' outside loop",
                  "'continue' not properly in loop"):
                self.checkSyntax(prevlines, lineNo, getPrevLine,
                      'while 1:\n', ' ')
                return
            elif err[0] == 'invalid token':
                self.indicateError(lineNo,
                      err.offset + indentpl - len(indent) - contLinesOffset,
                      'SyntaxError: %s'%err[0])
            #"can't assign to function call"
            # Invalid syntax
            else:
                if len(prevlines) == 1 and err.offset is not None and not contLinesOffset:
                    # Check for dedenting keywords
                    possblkeyword = stripprevline[:err.offset-len(indent)]
                    if possblkeyword in self.if_keywords.keys():
                        self.checkSyntax( (prevline.replace(possblkeyword,
                              self.if_keywords[possblkeyword], 1),),
                            lineNo, getPrevLine)
                        return
                    elif possblkeyword in self.try_keywords.keys():
                        if stripprevline[-1] == ':':
                            prevline = prevline.rstrip()+'pass\n'

                        self.checkSyntax( (self.try_keywords[possblkeyword],
                                           prevline), lineNo, getPrevLine)
                        return

                    # Check for line continueations
                    # XXX Lines ending on line_conts should be ignored
                    errpos = err.offset-len(indent)-1
                    if errpos < len(stripprevline) and \
                          stripprevline[errpos] in self.line_conts_ends:
                        ln = lineNo - 2

                        if stripprevline[-1] == '\\':
                            lines = prevline.rstrip()[:-1]+' '
                        else:
                            lines = prevline.rstrip()

                        errOffsetOffset = 0
                        while ln >= 0:
                            line = self.stripComment(getPrevLine(ln)[:-1])

                            rstripline = line.rstrip()
                            if rstripline and rstripline[-1] in self.line_conts:
                                # replace else, elif's with ifs
                                lstripline = line.lstrip()
                                if len(lstripline) >= 4:
                                    possblifkeyword = lstripline[:4]
                                    if possblifkeyword in self.if_keywords.keys():
                                        ##print 'replace if kw'
                                        rstripline = rstripline.replace(
                                              possblifkeyword,
                                              self.if_keywords[possblifkeyword], 1)

                                if rstripline[-1] == '\\':
                                    lines = rstripline[:-1] +' '+ lines
                                else:
                                    lines = rstripline + lines
                                errOffsetOffset = errOffsetOffset + len(rstripline)
                                ln = ln -1
                            # ignore blank or entirely commented lines
                            elif not rstripline:
                                ln = ln -1
                            else:
                                break

                        if errOffsetOffset:
                            self.checkSyntax((lines,), lineNo, getPrevLine,
                                  contLinesOffset = errOffsetOffset)
                            return

                if not err.offset:
                    erroffset = 0
                else:
                    erroffset = err.offset

                self.indicateError(lineNo,
                    erroffset + indentpl - len(indent) - contLinesOffset,
                    errstr)
                self.damagedLine = -1
                return

        except Exception, err:
            errstr = err.__class__.__name__+': '+str(err)

        if errstr:
            self.model.editor.statusBar.setHint(errstr)

        self.damagedLine = -1

#-------Events------------------------------------------------------------------
    def OnMarginClick(self, event):
        if event.GetMargin() == symbolMrg:
            DebuggingViewSTCMix.OnMarginClick(self, event)
        else:
            FoldingStyledTextCtrlMix.OnMarginClick(self, event)

    def OnRunToCursor(self, event):
        line = self.LineFromPosition(self.GetCurrentPos()) + 1
        self.addBreakPoint(line, temp=1, notify_debugger=0)
        # Avoid a race condition by sending the breakpoint
        # along with the "continue" instruction.
        temp_breakpoint = (self.model.filename, line)
        if self.model.app:
            model = self.model.app
        else:
            model = self.model
        model.debug(cont_if_running=1, cont_always=1,
                         temp_breakpoint=temp_breakpoint)

    def getWordAtCursor(self, delim=word_delim):
        pos = self.GetCurrentPos()
        lnNo = self.GetCurrentLine()
        lnStPs = self.PositionFromLine(lnNo)
        line = self.GetCurLine()[0]
        piv = pos - lnStPs
        start, length = idWord(line, piv, lnStPs, delim)
        startOffset = start-lnStPs
        word = line[startOffset:startOffset+length]
        return word, line, lnNo, start, startOffset

    def OnContextHelp(self, event):
        Help.showContextHelp(self.getWordAtCursor()[0])

    def OnComment(self, event):
        selStartPos, selEndPos = self.GetSelection()
        if selStartPos != selEndPos:
            self.processSelectionBlock(self.processComment)
        else:
            self.GotoPos(self.PositionFromLine(self.LineFromPosition(selStartPos)))
            self.AddText('##')
            self.SetSelection(selStartPos, selStartPos)

    def OnUnComment(self, event):
        selStartPos, selEndPos = self.GetSelection()
        if selStartPos != selEndPos:
            self.processSelectionBlock(self.processUncomment)
        else:
            linePos = self.PositionFromLine(self.LineFromPosition(selStartPos))
            if self.GetTextRange(linePos, linePos+2) == '##':
                self.SetSelection(linePos, linePos+2)
                self.ReplaceSelection('')
                self.SetSelection(selStartPos, selStartPos)

    def OnIndent(self, event):
        selStartPos, selEndPos = self.GetSelection()
        if selStartPos != selEndPos:
            self.processSelectionBlock(self.processIndent)
        else:
            self.GotoPos(self.PositionFromLine(self.LineFromPosition(selStartPos)))
            self.AddText(Utils.getIndentBlock())
            self.SetSelection(selStartPos, selStartPos)

    def OnDedent(self, event):
        selStartPos, selEndPos = self.GetSelection()
        indentBlock = Utils.getIndentBlock()
        indentLevel = len(indentBlock)
        if selStartPos != selEndPos:
            self.processSelectionBlock(self.processDedent)
        else:
            linePos = self.PositionFromLine(self.LineFromPosition(selStartPos))
            if self.GetTextRange(linePos, linePos + indentLevel) == indentBlock:
                self.SetSelection(linePos, linePos + indentLevel)
                self.ReplaceSelection('')
                self.SetSelection(selStartPos, selStartPos)

    def OnAddSimpleApp(self, event):
        self.BeginUndoAction()
        try:
            self.InsertText(self.GetTextLength(), self.model.getSimpleRunnerSrc())
        finally:
            self.EndUndoAction()

    def OnStyle(self, event):
        pass

    def OnAddModuleInfo(self, event):
        self.refreshModel()
        prefs = Preferences.staticInfoPrefs.copy()
        self.model.addModuleInfo(prefs)
        self.updateEditor()

    def OnUpdateUI(self, event):
        EditorStyledTextCtrl.OnUpdateUI(self, event)

        if Preferences.braceHighLight:
            PythonStyledTextCtrlMix.OnUpdateUI(self, event)

    def OnAddChar(self, event):
        char = event.GetKey()
        # On enter indent to same indentation as line above
        # If ends in : indent xtra block
        lineNo = self.GetCurrentLine()
        self.damagedLine = lineNo
        if char == 10:
            pos = self.GetCurrentPos()
            prevline = self.GetLine(lineNo -1)

            self.doAutoIndent(prevline, pos)

            self.damagedLine = lineNo-1
            self.checkChangesAndSyntax(lineNo-1)
        elif char == 40 and Preferences.callTipsOnOpenParen:
            self.callTipCheck()

    def OnKeyDown(self, event):
        if self.CallTipActive():
            self.callTipCheck()

        key = event.GetKeyCode()

        # thx to Robert Boulanger
        if Preferences.handleSpecialEuropeanKeys:
            self.handleSpecialEuropeanKeys(event, Preferences.euroKeysCountry)

        if key in (wx.WXK_UP, wx.WXK_DOWN):
            self.checkChangesAndSyntax()
        # Tabbed indent
##        elif key == 9:
##            self.AddText(indentLevel*' ')
##            self.damagedLine = self.GetCurrentLine()
##            if not self.AutoCompActive(): return
        # Smart delete
        elif key == 8:
            selStartPos, selEndPos = self.GetSelection()
            if selStartPos == selEndPos:
                if self.GetUseTabs():
                    indtSze = 1
                else:
                    indtSze = self.GetTabWidth()
                line = self.GetCurLine()
                if len(line): line = line[0]
                else: line = ''
                pos = self.GetCurrentPos()
                self.damagedLine = self.LineFromPosition(pos)
                #ignore indenting when at start of line
                if self.PositionFromLine(self.LineFromPosition(pos)) != pos:
                    pos = pos -1
                    ln = self.LineFromPosition(pos)
                    ls = self.PositionFromLine(ln)
                    st = pos - ls
                    if not line[:st].strip():
                        self.SetSelection(ls + st/indtSze*indtSze, pos+1)
                        self.ReplaceSelection('')
                        return
        #event.Skip()
        BrowseStyledTextCtrlMix.OnKeyDown(self, event)

#---Meta comment----------------------------------------------------------------
    def OnAddCommentLine(self, event):
        pos = self.GetCurrentPos()
        ln = self.LineFromPosition(pos)
        ls = self.PositionFromLine(ln)
        self.InsertText(ls, '#-------------------------------------------'
                            '------------------------------------'+self.eol)
        self.SetCurrentPos(ls+4)
        self.SetAnchor(ls+4)

    def OnViewWhitespace(self, event):
        miid = self.menu.FindItem(_('View whitespace'))
        if self.menu.IsChecked(miid):
            mode = wx.stc.STC_WS_INVISIBLE
            self.menu.Check(miid, False)
        else:
            mode = wx.stc.STC_WS_VISIBLEALWAYS
            self.menu.Check(miid, True)

        self.SetViewWhiteSpace(mode)
#        self.menu.Check(miid, not self.menu.IsChecked(miid))

    def OnViewEOL(self, event):
        miid = self.menu.FindItem(_('View EOL characters'))
        check = not self.menu.IsChecked(miid)
        self.menu.Check(miid, check)
        self.SetViewEOL(check)

    # XXX 1st Xform, should maybe add beneath current method
    def OnCodeTransform(self, event):
        word, line, lnNo, start, startOffset = self.getWordAtCursor(object_delim)
        self.model.editor.addBrowseMarker(lnNo)
        # 1st Xform; Add method at cursor to the class
        if word.startswith('self.'):
            methName = word[5:]
            # Apply if there are changes to views
            self.model.refreshFromViews()
            module = self.model.getModule()
            cls = module.getClassForLineNo(lnNo)
            if cls and not cls.methods.has_key(methName):
                # Check if it looks like an event
                if len(methName) > 2 and methName[:2] == 'On' and (methName[2]
                                                       in string.uppercase+'_'):
                    parms = 'self, event'
                else:
                    parms = 'self, '

                module.addMethod(cls.name, methName, parms, [sourceconst.bodyIndent+'pass'], True)
                if cls.methods.has_key(methName):
                    lnNo = cls.methods[methName].start+1
                    self.model.refreshFromModule()
                    self.model.modified = True
                    self.model.editor.updateModulePage(self.model)
                    line2pos = self.PositionFromLine(lnNo)
                    self.SetCurrentPos(line2pos+8)
                    self.SetSelection(line2pos+8, line2pos+12)
                else:
                    print 'Method was not added'
        else:
            # 2nd Xform; Add inherited method call underneath method declation
            #            at cursor
            if line[:startOffset].strip() == 'def':
                self.model.refreshFromViews()
                module = self.model.getModule()
                cls = module.getClassForLineNo(lnNo)
                if cls and cls.super:
                    base1 = cls.super[0]
                    if type(base1) is type(''):
                        baseName = base1
                    else:
                        baseName = base1.name
                    methName, meth = cls.getMethodForLineNo(lnNo+1)
                    if meth:
                        # XXX Not tab aware
                        module.addLine('%s%s.%s(%s)'%(' '*startOffset, baseName,
                              word, meth.signature), lnNo+1)
                        self.model.refreshFromModule()
                        self.model.modified = True
                        self.model.editor.updateModulePage(self.model)

    def OnRefresh(self, event):
        if Preferences.autoReindent:
            self.model.reindent(False)
        EditorStyledTextCtrl.OnRefresh(self, event)

    def OnModified(self, event):
        modType = event.GetModificationType()
        linesAdded = event.GetLinesAdded()

        if self._blockUpdate: return

        # repair breakpoints
        if linesAdded:
            self.adjustBreakpoints(linesAdded, modType, event.GetPosition())

        # XXX The rest is too buggy
        event.Skip()
        return

        # Update module line numbers
        # module has to have been parsed at least once
        if linesAdded and self.model._module:
            lineNo = self.LineFromPosition(event.GetPosition())
            module = self.model.getModule()
            module.renumber(linesAdded, lineNo)

            #if linesAdded == 1:
            #    module.parseLineIsolated(self.GetLine(lineNo), lineNo)

            #self.model.editor.statusBar.setHint('update ren %d %d'%(linesAdded, lineNo))


class PythonDisView(EditorStyledTextCtrl, PythonStyledTextCtrlMix):
    viewName = 'Disassemble'
    viewTitle = _('Disassemble')
    
    breakBmp = 'Images/Debug/Breakpoints.png'
    def __init__(self, parent, model):
        wxID_PYTHONDISVIEW = wx.NewId()

        EditorStyledTextCtrl.__init__(self, parent, wxID_PYTHONDISVIEW,
          model, (), -1)
        PythonStyledTextCtrlMix.__init__(self, wxID_PYTHONDISVIEW, ())

        self.active = True

    def refreshModel(self):
        # Do not update model
        pass

    def getModelData(self):
        return self.model.disassembleSource()

    def setModelData(self, data):
        pass

    def updateFromAttrs(self):
        self.SetReadOnly(True)

#-------------------------------------------------------------------------------

from Explorers import ExplorerNodes
ExplorerNodes.langStyleInfoReg.insert(0, ('Python', 'python',
      PythonStyledTextCtrlMix, 'stc-styles.rc.cfg') )
