#-----------------------------------------------------------------------------
# Name: BicycleRepairMan.plug-in.py
# Purpose: Integrating Boa Constructor and Bicycle Repair Man
#
# Author: Riaan Booysen
#
# Created: 2003/04/26
# RCS-ID: $Id: BicycleRepairMan.plug-in.py,v 1.11 2007/07/05 14:03:34 riaan Exp $
# Copyright: (c) 2003 - 2007
# Licence: BSD
#-----------------------------------------------------------------------------
""" A Boa Constructor Plug-in that integrates the Bicycle Repair Man into the IDE.
The refactoring actions are published under the Edit menu of Python Source Views,
this is also the source's context menu.
There is a global refactoring context shared between all open modules.
This is exposed as editor.brm_context.
Also clearing the current context can be called from the Explorer menu.
"""
import os, linecache, traceback
from thread import start_new_thread
import bike
import wx
import Preferences, Utils, Plugins
from Utils import _
from Views import PySourceView,SourceViews
from Models import PythonEditorModels,PythonControllers
from Explorers import ExplorerNodes,FileExplorer
import ErrorStack, Editor
Editor.EditorFrame.brm_context = None
# Issues
# * On Windows filenames aren't guaranteed to always match those in the Editor
# * When the Boa IDE framework supports it, the BRM support should have
# it's own menu, e.g. Tools->BicycleRepairMan->[Actions]
# or a submenu under Edit, e.g. Edit->BicycleRepairMan->[Actions]
# Notes
# * There is an off-by-one issue between BRM and Scintilla,
# before passing a scintilla lineno to BRM, add 1
# before passing a BRM lineno to Scintilla, minus 1
# Settings to control how the collection of files that BRM tracks is built up
Plugins.registerPreference('BicycleRepairMan', 'brmProgressLogger',
"'ProgressStatusLogger'",
['Destination for progress messages from BRM.'],
"options: 'ProgressStatusLogger', 'ProgressErrOutLogger'")
#-Editor plugins----------------------------------------------------------------
class CancelOperation(Exception): pass
from bike.query.findReferences import CouldntFindDefinitionException
class BRMViewPlugin:
""" Plugin class for View classes that exposes the refactoring API.
Handles """
def __init__(self, model, view, actions):
self.model = model
self.view = view
actions.extend( (
('-', None, '-', ''),
(_('BRM - Find references'), self.OnFindReferences, '-', ''),
(_('BRM - Find definition'), self.OnFindDefinition, '-', ''),
('-', None, '-', ''),
(_('BRM - Rename'), self.OnRename, '-', ''),
(_('BRM - Extract method'), self.OnExtractMethod, '-', ''),
('-', None, '-', ''),
(_('BRM - Extract local variable'), self.OnExtractLocalVar, '-', ''),
(_('BRM - Inline local variable'), self.OnInlineLocalVar, '-', ''),
('-', None, '-', ''),
#('BRM - Get type of expression', self., '-', ''), # not useful currently
(_('BRM - Undo last'), self.OnUndoLast, '-', ''),
) )
def prepareForSelectOperation(self):
selection = self.view.GetSelectedText().strip()
if not selection:
raise Exception, _('No text selected.')
ctx = self.model.editor.brm_context
filename = self.model.checkLocalFile()
pos = self.view.GetSelectionStart()
lineno = self.view.LineFromPosition(pos)+1
column = self.view.GetColumn(pos)
return ctx, selection, filename, lineno, column
def prepareOutputDisplay(self, txt):
errout = self.model.editor.erroutFrm
errout.runningDir = ''
errout.tracebackType = 'Info'
tree = errout.errorStackTC
tree.DeleteAllItems()
root = tree.AddRoot(txt)
errout.display()
return errout, tree, root
def saveAndRefresh(self):
editor = self.model.editor
savedfiles = editor.brm_context.save()
for filename in savedfiles:
editor.explorerModifyNotify('file://'+os.path.abspath(filename))
def checkUnsavedChanges(self):
model = self.model
ctx = model.editor.brm_context
for path in ctx.brmctx.paths:
uri = 'file://'+os.path.abspath(path)
if uri in model.editor.modules:
try:
model.editor.prepareForCloseModule(
model.editor.modules[uri], True,
_('Save changes before Refactoring operation'))
except Editor.CancelClose:
wx.LogError(_('Operation aborted.'))
return False
return True
def getExtractionInfo(self, xtype, caption):
selection = self.view.GetSelectedText().strip()
if not selection:
raise CancelOperation, \
_('No text selected. Highlight the region you want to extract.')
filename = self.model.checkLocalFile()
name = wx.GetTextFromUser(_('New %s name:')%xtype, caption)
if not name:
raise CancelOperation, _('Empty names are invalid.')
startpos, endpos = self.view.GetSelection()
startline = self.view.LineFromPosition(startpos)
startcol = self.view.GetColumn(startpos)
endline = self.view.LineFromPosition(endpos)
endcol = self.view.GetColumn(endpos)
return filename, startline+1, startcol, endline+1, endcol, name
def OnFindReferences(self, event=None):
ctx, sel, filename, lineno, column = self.prepareForSelectOperation()
start_new_thread(self.findReferencesThread,
(ctx, filename, lineno, column, sel) )
self.model.editor.setStatus(_('BRM - Finding references...'))
def findReferencesThread(self, ctx, fname, lineno, column, sel):
try:
matches = [ReferencesMatcher([ref])
for ref in ctx.findReferencesByCoordinates(fname, lineno, column)]
except CouldntFindDefinitionException:
wx.CallAfter(self.findReferencesFindDefinition)
except Exception, err:
wx.CallAfter(wx.LogError,
''.join(traceback.format_exception(*sys.exc_info())))
wx.CallAfter(self.model.editor.setStatus,
_('BRM - Error %s')%str(err), 'Error')
else:
wx.CallAfter(self.findReferencesFinished, matches, sel)
def findReferencesFindDefinition(self):
self.model.editor.setStatus(_('BRM - Could not find definition'))
if wx.MessageBox(_('Perform Find Definition?'), _('Find References'),
wx.YES_NO | wx.ICON_WARNING) == wx.YES:
self.OnFindDefinition()
def findReferencesFinished(self, matches, sel):
errout, tree, root = self.prepareOutputDisplay(
_('BRM - References for %s')%sel)
x = 0
for entry in matches:
x = errout.addTracebackNode(entry, x)
tree.SetItemHasChildren(root, True)
tree.Expand(root)
if x:
errout.notebook.SetSelection(0)
self.model.editor.setStatus(_('BRM - %s reference(s) found')%x)
def OnFindDefinition(self, event=None):
ctx, sel, filename, lineno, column = self.prepareForSelectOperation()
start_new_thread(self.findDefinitionThread,
(ctx, filename, lineno, column, sel))
self.model.editor.setStatus(_('BRM - Finding definition...'))
def findDefinitionThread(self, ctx, filename, lineno, column, sel):
try:
defs = ctx.findDefinitionByCoordinates(filename, lineno, column)
if defs:
try:
match = defs.next()
except StopIteration:
wx.CallAfter(wx.LogError, _("Couldn't find definition"))
wx.CallAfter(self.model.editor.setStatus,
_('BRM - Could not find definition'), 'Error')
else:
wx.CallAfter(self.findDefinitionFinished, match, sel, defs)
else:
wx.CallAfter(wx.LogError, _("Couldn't find definition"))
wx.CallAfter(self.model.editor.setStatus,
_('BRM - Could not find definition'), 'Error')
except Exception, err:
wx.CallAfter(wx.LogError,
''.join(traceback.format_exception(*sys.exc_info())))
wx.CallAfter(self.model.editor.setStatus,
_('BRM - Error %s')%str(err), 'Error')
def findDefinitionFinished(self, match, sel, defs):
mod, ctr = self.model.editor.openOrGotoModule(match.filename)
view = mod.getSourceView()
view.GotoLine(match.lineno-1)
view.setLinePtr(match.lineno-1)
errout, tree, root = self.prepareOutputDisplay(
_('BRM - Definition for %s')%sel)
x = 1
x = errout.addTracebackNode(ReferencesMatcher([match]), x)
for ref in defs:
entry = ReferencesMatcher([ref])
x = errout.addTracebackNode(entry, x)
tree.SetItemHasChildren(root, True)
tree.Expand(root)
if x:
errout.notebook.SetSelection(0)
self.model.editor.setStatus(_('BRM - %s matches(s) found')%x)
def OnRename(self, event):
if not self.checkUnsavedChanges():
return
ctx, sel, filename, lineno, column = self.prepareForSelectOperation()
ctx.setRenameMethodPromptCallback(self.renameCallback)
newname = wx.GetTextFromUser(_('Rename to:'), _('Rename'), sel)
if not newname:
return
ctx.renameByCoordinates(filename, lineno, column, newname)
self.saveAndRefresh()
def renameCallback(self, filename, lineno, colbegin, colend):
currfile = self.model.filename
model, ctr = self.model.editor.openOrGotoModule(filename)
view = model.getSourceView()
edge = view.PositionFromLine(lineno-1)
view.SetSelection(edge+colbegin, edge+colend)
res = wx.MessageBox(_('Cannot deduce the type of highlighted object'
' reference.\nRename this declaration?'), _('Rename?'),
wx.YES_NO | wx.ICON_QUESTION) == wx.YES
self.model.editor.openOrGotoModule(currfile)
return res
def OnExtractMethod(self, event):
if not self.checkUnsavedChanges():
return
ctx = self.model.editor.brm_context
try:
ctx.extractMethod(*self.getExtractionInfo('Method', _('Extract method')))
except CancelOperation, err:
wx.LogError(str(err))
else:
self.view.SetSelectionEnd(self.view.GetSelectionStart())
self.saveAndRefresh()
def OnUndoLast(self, event):
try:
self.model.editor.brm_context.undo()
except bike.UndoStackEmptyException, msg:
wx.LogWarning(_('Nothing to undo, the undo stack is empty.'))
else:
self.saveAndRefresh()
def OnExtractLocalVar(self, event):
if not self.checkUnsavedChanges():
return
ctx = self.model.editor.brm_context
try:
filename, startline, startcol, endline, endcol, variablename = \
self.getExtractionInfo('Variable', _('Extract variable'))
except CancelOperation, err:
wx.LogError(str(err))
else:
ctx.extractLocalVariable(filename, startline, startcol, endline,
endcol, variablename)
self.view.SetSelectionEnd(self.view.GetSelectionStart())
self.saveAndRefresh()
def OnInlineLocalVar(self, event):
if not self.checkUnsavedChanges():
return
ctx, sel, filename, lineno, column = self.prepareForSelectOperation()
ctx.inlineLocalVariable(filename, lineno, column)
self.saveAndRefresh()
def OnGetExprType(self, event):
ctx, sel, filename, lineno, column = self.prepareForSelectOperation()
wx.LogMessage(
`ctx.getTypeOfExpression(filename, lineno, column, column+len(sel))`)
PySourceView.PythonSourceView.plugins += (BRMViewPlugin,)
##class BRMControllerPlugin:
## """ Plugin class for Controller classes that plublishes the Model interface """
## def __init__(self, controller):
## self.controller = controller
##
## def actions(self, model):
## return []
## return [('-', None, '', ''),
## ('BRM - Import module(s)', self.OnImportModules, '-', ''),
## ]
##
## def OnImportModules(self, event):
## model = self.controller.getModel()
## cnt = model.plugins['BRM'].importModules()
## if cnt:
## model.editor.setStatus('BRM - Parsed %s modules'%cnt)
##
##PythonControllers.ModuleController.plugins += (BRMControllerPlugin,)
class BRMModelPlugin:
""" Plugin class for Model classes that creates the context and performs
various ways importing into the refactoring context. """
name = 'BRM'
def __init__(self, model):
self.model = model
# the first one created starts the context
if not hasattr(model.editor, 'brm_context') or model.editor.brm_context is None:
model.editor.brm_context = context = bike.init()
context.setProgressLogger(
globals()[Preferences.brmProgressLogger](model.editor))
def update(self):
pass
PythonEditorModels.ModuleModel.plugins += (BRMModelPlugin,)
#-------------------------------------------------------------------------------
class ProgressErrOutLogger:
""" File like logger that uses the Output tab at the bottom of the Editor """
def __init__(self, editor):
self.errout = editor.erroutFrm
def write(self, txt):
wx.CallAfter(self.errout.appendToOutput, txt)
class ProgressStatusLogger:
""" File like logger that uses the Editor statusbar as output """
def __init__(self, editor):
self.editor = editor
self._buffer = ''
def write(self, txt):
self._buffer += txt
if txt.endswith('\n'):
wx.CallAfter(self.editor.setStatus, self._buffer.strip())
self._buffer = ''
class ReferencesMatcher(ErrorStack.StackErrorParser):
""" Needed to build "traceback objects" that appear on Find definition """
def parse(self):
for ref in self.lines:
self.error = ['%s%% confidence for: %s:%s'% (
ref.confidence, ref.filename, ref.lineno)]
self.stack.append(ErrorStack.StackEntry(ref.filename, ref.lineno,
linecache.getline(ref.filename, ref.lineno), self.error))
#-Explorer plugins--------------------------------------------------------------
##class BRMTranspController:
## """ Transport Controller that extends the context menu of Filesystem objects """
## def __init__(self, controller, editor):
## self.controller = controller
## self.editor = editor
##
## def menuDefs(self):
## return []
## return [
## (-1, '-', None, '-'),
## (wx.NewId(), 'BRM - Import selection', self.OnImportItem, '-'),
## (wx.NewId(), 'BRM - Clear refactoring context', self.OnClearContext, '-'),
## (-1, '-', None, '-'),
## ]
##
## def OnImportItem(self, event):
## list = self.controller.list
## if list.node and self.editor.brm_context:
## nodes = self.controller.getNodesForSelection(list.getMultiSelection())
## for node in nodes:
## filename = node.resourcepath
## # only import dirs and modules
## if os.path.splitext(filename)[1] in ('', '.py'):
## self.editor.brm_context.load(filename)
##
## def OnClearContext(self, event):
## self.editor.brm_context = context = bike.init()
## context.setProgressLogger(
## globals()[Preferences.brmProgressLogger](self.editor))
##
###-------------------------------------------------------------------------------
##FileExplorer.FileSysController.plugins += (BRMTranspController,)
|