vim/sadness/bike/ide-integration/BicycleRepairMan_Idle.py @ cfd5d659d737

vim: sadness
author Steve Losh <steve@stevelosh.com>
date Mon, 22 Nov 2010 14:32:21 -0500
parents (none)
children (none)
# bicycle repair man idle extension
import bike
from bike.transformer.undo import UndoStackEmptyException
import bike.parsing.load
import os
from Tkinter import *
import tkFileDialog
import tkMessageBox
import tkSimpleDialog
import sys
import string
try:
    from idlelib.PathBrowser import *
    from idlelib.WindowList import ListedToplevel
    from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
    from idlelib import EditorWindow
    from idlelib.PyShell import PyShell
    from idlelib.OutputWindow import OutputWindow
    try:
        from idlelib.configHandler import idleConf
    except ImportError:
        pass



except ImportError:
    from PathBrowser import*
    from WindowList import ListedToplevel
    from TreeWidget import TreeNode, TreeItem, ScrolledCanvas
    import EditorWindow
    from PyShell import PyShell
    from OutputWindow import OutputWindow
    try:
        from configHandler import idleConf
    except ImportError:
        pass

brmctx = None
shellwin = None
loadingFiles = 0
matchwin = None

class NotHighlightedException(Exception):
    pass

class BicycleRepairMan_Idle:
    menudefs = [
    ('bicycleRepairMan', [
        ('----- Queries -----',''),
        ('_Find References','<<brm-find-references>>'),
        ('_Find Definition','<<brm-find-definition>>'),
        None,
        ('--- Refactoring ---',''),
        ('_Rename', '<<brm-rename>>'), 
        ('_Extract Method', '<<brm-extract-method>>'), 
        None,
        ('_Undo', '<<brm-undo>>'), 
        ])
    ]

    keydefs = {
    '<<brm-find-references>>':[],
    '<<brm-find-definition>>':[],
    '<<brm-rename>>':[], 
    '<<brm-extract-method>>':[], 
    '<<brm-undo>>':[], 
     }


    try:
        TRACE = idleConf.GetOption('extensions','BicycleRepairMan_Idle',
                                        'trace',default=1)        
    except NameError:   # hasnt imported idleconf - probably python 22
        TRACE = 1


    def __init__(self, editwin):
        self.editwin = editwin
        
        if self.TRACE == 1:
            self.progressLogger = ProgressLogger(self.editwin.flist)
            
        if not isinstance(editwin, PyShell):
            # sly'ly add the refactor menu to the window
            name, label = ("bicycleRepairMan", "_BicycleRepairMan")
            underline, label = EditorWindow.prepstr(label)
            mbar = editwin.menubar
            editwin.menudict[name] = menu = Menu(mbar, name = name)
            mbar.add_cascade(label = label, menu = menu, underline = underline)

            # Initialize Bicyclerepairman and import the code
            path = self.editwin.io.filename
            if path is not None:
                global brmctx
                if brmctx is None:
                    self.initbrm()
        else:
            global shellwin
            shellwin = editwin


    def initbrm(self):
        global brmctx
        brmctx = bike.init()
        if self.TRACE == 1:
            brmctx.setProgressLogger(self.progressLogger)
        

    def brm_find_references_event(self,event):
        try:
            if not self.confirm_all_buffers_saved():
                return

            if self.editwin.text.index("sel.first") == "":
                self.errorbox("Not highlighted", "Highlight the name of a Function, Class or Method and try again")
                return

            filename = os.path.normpath(self.editwin.io.filename)
            line, column = string.split(self.editwin.text.index("sel.first"),'.')

            numMatches = 0
            global matchwin
            if matchwin is None:
                matchwin = BRMMatchesWindow(self.editwin.flist, self)

            matchwin.clear()

            for ref in brmctx.findReferencesByCoordinates(filename,int(line),int(column)):
                print >>matchwin, "File \""+ref.filename+"\", line "+str(ref.lineno)+", "+str(ref.confidence)+"% confidence"
                numMatches +=1

            print >>matchwin, numMatches," matches"
            print >>matchwin, "(Hint: right-click to open locations.)"
        except:
            self._handleUnexpectedException()


    def brm_find_definition_event(self,event):
        try:
            if not self.confirm_all_buffers_saved():
                return
            filename = os.path.normpath(self.editwin.io.filename)

            if self.editwin.text.index("sel.first") != "":
                line, column = string.split(self.editwin.text.index("sel.first"),'.')
            else:
                line, column = string.split(self.editwin.text.index("insert"), '.')


            defns = brmctx.findDefinitionByCoordinates(filename,int(line),
                                                       int(column))

            try:
                
                firstref = defns.next()
                
                editwin = self.editwin.flist.open(firstref.filename)
                editwin.gotoline(firstref.lineno)
            except StopIteration:
                self.errorbox("Couldn't Find definition","Couldn't Find definition")
                pass
            else:
                numRefs = 1
                global matchwin
                if matchwin is None:
                    matchwin = BRMMatchesWindow(self.editwin.flist, self)
                    
                for ref in defns:
                    if numRefs == 1:
                        print >>matchwin, firstref.filename+":"+str(firstref.lineno)+":    "+str(firstref.confidence)+"% confidence"

                    numRefs += 1
                    print >>matchwin,ref.filename+":"+str(ref.lineno)+":    "+str(ref.confidence)+"% confidence"
                if matchwin is not None:
                    print >>matchwin, "(Hint: right-click to open locations.)"
                    
        except:
            self._handleUnexpectedException()


    def brm_rename_event(self, event):
        try:
            self.renameItemByCoordinates()
        except:
            self._handleUnexpectedException()

    def brm_extract_method_event(self, event):
        try:
            if not self.confirm_all_buffers_saved():
                return
            try:
                filename, newname, beginline, begincolumn, endline, endcolumn = self._getExtractionInformation("Method")
            except NotHighlightedException:
                return
            brmctx.extractMethod(filename, int(beginline), int(begincolumn), 
                                 int(endline), int(endcolumn), newname)
            savedfiles = brmctx.save()
            self.refreshWindows(savedfiles, beginline)
        except:
            self._handleUnexpectedException()


    def brm_undo_event(self, event):
        try:
            line, column = string.split(self.editwin.text.index("insert"), '.')
            brmctx.undo()
            savedfiles = brmctx.save()
            self.refreshWindows(savedfiles, line)
        except UndoStackEmptyException:
            self.errorbox("Undo Stack Empty", "Undo Stack is empty")
        except:
            self._handleUnexpectedException()

    def _handleUnexpectedException(self):
        import traceback
        traceback.print_exc()
        self.errorbox("Caught Exception", "Caught Exception "+str(sys.exc_info()[0]))

    def _getExtractionInformation(self, extracttype):
        if self.editwin.text.index("sel.first") == "":
            self.errorbox("Code not highlighted", "Highlight the region of code you want to extract and try again")
            raise NotHighlightedException()
        filename = os.path.normpath(self.editwin.io.filename)
        newname = tkSimpleDialog.askstring("Extract Method ", 
                                           "New "+extracttype+" Name:", 
                                           parent = self.editwin.text)
        beginline, begincolumn = string.split(self.editwin.text.index("sel.first"), '.')
        endline, endcolumn = string.split(self.editwin.text.index("sel.last"), '.')
        return filename, newname, beginline, begincolumn, endline, endcolumn


    def renameMethodPromptCallback(self, filename, line, colbegin, colend):

        editwin = self.editwin.flist.open(filename)
        originaltop = self.editwin.getwindowlines()[0]

        # select the method call and position the window
        editwin.text.tag_remove("sel", "1.0", "end")
        editwin.text.tag_add("sel", str(line)+"."+str(colbegin), 
                                  str(line)+"."+str(colend))

        line, column = string.split(editwin.text.index("sel.first"), '.')
        editwin.text.yview(str(int(line)-2)+".0")


        d = NoFocusDialog("Rename?", 
                     "Cannot deduce the type of highlighted object reference.\nRename this declaration?", 
                     parent = editwin.text)

        # put the window back where it was
        self.editwin.text.yview(float(originaltop))
        return d.answer

    def renameItemByCoordinates(self):
        if not self.confirm_all_buffers_saved():
            return
        if self.editwin.text.index("sel.first") == "":
            self.errorbox("Name not highlighted", "Double click the name of the declaration you want to rename (to highlight it) and try again")
            return

        brmctx.setRenameMethodPromptCallback(self.renameMethodPromptCallback)
        line, column = string.split(self.editwin.text.index("sel.first"), '.')
        filename = os.path.normpath(self.editwin.io.filename)
        newname = tkSimpleDialog.askstring("Rename", 
                                           "Rename to:", 
                                           parent = self.editwin.text)
        if newname is None: # cancel clicked
            return
        brmctx.renameByCoordinates(filename, int(line), int(column), newname)
        savedfiles = brmctx.save()
        self.refreshWindows(savedfiles, line)



    def refreshWindows(self, savedfiles, line):
        # refresh editor windows
        oldtop = self.editwin.getwindowlines()[0]

        global loadingFiles
        loadingFiles = 1
        for sf in savedfiles:
            normsf = os.path.normcase(sf)
            if normsf in self.editwin.flist.dict:
                editwin = self.editwin.flist.dict[normsf]
                editwin.io.loadfile(sf)
        loadingFiles = 0

        self.editwin.text.mark_set("insert", float(line))
        self.editwin.text.yview(float(oldtop))



    def confirm_all_buffers_saved(self):
        filelist = self.editwin.flist.dict.keys()
        for f in filelist:
            #editwin = self.editwin.flist.open(f)
            editwin = self.editwin.flist.dict[f]
            if self.confirm_buffer_is_saved(editwin) == 0:
                return 0
        return 1


    def confirm_buffer_is_saved(self, editwin):
        if not editwin.get_saved():
            name = (editwin.short_title()or
            editwin.long_title()or
            "Untitled")
            reply = tkMessageBox.askokcancel("Bicycle Repair Man",
                "The buffer for %s is not saved.\n\n"%name+
                "Save it and continue?",
                master = self.editwin.text)
          &nbs p; self.editwin.text.focus_set()
            if reply:
                editwin.io.save(None)
            else:
                return 0
        return 1

    def errorbox(self, title, message):
        tkMessageBox.showerror(title, message, master = self.editwin.text)
        self.editwin.text.focus_set()


class BRMTraceWindow(OutputWindow):
    def short_title(self):
        return "BicycleRepairMan Trace"

class ProgressLogger:
    def __init__(self,flist):
        self.flist = flist

    def write(self,txt):
        if not hasattr(self,"io"):
            self.io = BRMTraceWindow(self.flist)
        try:
            self.io.write(txt)
            self.io.flush()
        except IOError:
            pass

        
class NoFocusDialog(tkSimpleDialog._QueryDialog):
    def __init__(self, title, prompt, 
                 initialvalue = None, 
                 minvalue = None, maxvalue = None, 
                 parent = None):
        self.answer = 0

        if not parent:
            import Tkinter
            parent = Tkinter._default_root

        self.prompt = prompt
        self.minvalue = minvalue
        self.maxvalue = maxvalue

        self.initialvalue = initialvalue

        Toplevel.__init__(self, parent)

        if title:
            self.title(title)

        self.parent = parent

        self.result = None

        body = Frame(self)
        self.initial_focus = self.body(body)
        body.pack(padx = 5, pady = 5)

        self.buttonbox()

        self.grab_set()

        self.protocol("WM_DELETE_WINDOW", self.cancel)

        if self.parent is not None:
            self.geometry("+%d+%d"%(parent.winfo_rootx()+50, 
                                      parent.winfo_rooty()+50))
        self.wait_window(self)


    def getresult(self):
        self.answer = 1

    def body(self, master):
        w = Label(master, text = self.prompt, justify = LEFT)
        w.grid(row = 0, padx = 5, sticky = W)

    def buttonbox(self):
        box = Frame(self)

        w = Button(box, text = "Yes", width = 10, command = self.ok, default = ACTIVE)
        w.pack(side = LEFT, padx = 5, pady = 5)
        w = Button(box, text = "No", width = 10, command = self.cancel)
        w.pack(side = LEFT, padx = 5, pady = 5)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)
        box.pack()





class BRMMatchesWindow(OutputWindow):
    def __init__(self,flist,masterwin):
        OutputWindow.__init__(self,flist)
        self.masterwin = masterwin

    def close(self):
        global matchwin
        matchwin = None
        OutputWindow.close(self)

    def short_title(self):
        return "BicycleRepairMan Matches"
    
    def clear(self):
        self.text.delete("1.0","end-1c")