aca310e5cc
New Features: In Tab-View mode, Ctrl-number will take the user to the numbered tab view. Modified files now show an '*' astrisk in the view title. Debugger framework can now support PHP debugging. Not important for python development, but at least that means the debugger framework is more generalized. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@38852 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
973 lines
38 KiB
Python
973 lines
38 KiB
Python
#----------------------------------------------------------------------------
|
|
# Name: CodeEditor.py
|
|
# Purpose: Abstract Code Editor for pydocview tbat uses the Styled Text Control
|
|
#
|
|
# Author: Peter Yared
|
|
#
|
|
# Created: 8/10/03
|
|
# CVS-ID: $Id$
|
|
# Copyright: (c) 2004-2005 ActiveGrid, Inc.
|
|
# License: wxWindows License
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
import STCTextEditor
|
|
import wx
|
|
import wx.lib.docview
|
|
import OutlineService
|
|
import os
|
|
import re
|
|
import string
|
|
import sys
|
|
import MarkerService
|
|
from UICommon import CaseInsensitiveCompare
|
|
_ = wx.GetTranslation
|
|
if wx.Platform == '__WXMSW__':
|
|
_WINDOWS = True
|
|
else:
|
|
_WINDOWS = False
|
|
|
|
|
|
EXPAND_TEXT_ID = wx.NewId()
|
|
COLLAPSE_TEXT_ID = wx.NewId()
|
|
EXPAND_TOP_ID = wx.NewId()
|
|
COLLAPSE_TOP_ID = wx.NewId()
|
|
EXPAND_ALL_ID = wx.NewId()
|
|
COLLAPSE_ALL_ID = wx.NewId()
|
|
CHECK_CODE_ID = wx.NewId()
|
|
AUTO_COMPLETE_ID = wx.NewId()
|
|
CLEAN_WHITESPACE = wx.NewId()
|
|
COMMENT_LINES_ID = wx.NewId()
|
|
UNCOMMENT_LINES_ID = wx.NewId()
|
|
INDENT_LINES_ID = wx.NewId()
|
|
DEDENT_LINES_ID = wx.NewId()
|
|
USE_TABS_ID = wx.NewId()
|
|
SET_INDENT_WIDTH_ID = wx.NewId()
|
|
FOLDING_ID = wx.NewId()
|
|
|
|
|
|
class CodeDocument(STCTextEditor.TextDocument):
|
|
pass
|
|
|
|
|
|
class CodeView(STCTextEditor.TextView):
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
# Overridden methods
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
def GetCtrlClass(self):
|
|
""" Used in split window to instantiate new instances """
|
|
return CodeCtrl
|
|
|
|
|
|
def ProcessEvent(self, event):
|
|
id = event.GetId()
|
|
if id == EXPAND_TEXT_ID:
|
|
self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine())
|
|
return True
|
|
elif id == COLLAPSE_TEXT_ID:
|
|
self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine())
|
|
return True
|
|
elif id == EXPAND_TOP_ID:
|
|
self.GetCtrl().ToggleFoldAll(expand = True, topLevelOnly = True)
|
|
return True
|
|
elif id == COLLAPSE_TOP_ID:
|
|
self.GetCtrl().ToggleFoldAll(expand = False, topLevelOnly = True)
|
|
return True
|
|
elif id == EXPAND_ALL_ID:
|
|
self.GetCtrl().ToggleFoldAll(expand = True)
|
|
return True
|
|
elif id == COLLAPSE_ALL_ID:
|
|
self.GetCtrl().ToggleFoldAll(expand = False)
|
|
return True
|
|
elif id == CHECK_CODE_ID:
|
|
self.OnCheckCode()
|
|
return True
|
|
elif id == AUTO_COMPLETE_ID:
|
|
self.OnAutoComplete()
|
|
return True
|
|
elif id == CLEAN_WHITESPACE:
|
|
self.OnCleanWhiteSpace()
|
|
return True
|
|
elif id == SET_INDENT_WIDTH_ID:
|
|
self.OnSetIndentWidth()
|
|
return True
|
|
elif id == USE_TABS_ID:
|
|
self.GetCtrl().SetUseTabs(not self.GetCtrl().GetUseTabs())
|
|
return True
|
|
elif id == INDENT_LINES_ID:
|
|
self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_TAB)
|
|
return True
|
|
elif id == DEDENT_LINES_ID:
|
|
self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_BACKTAB)
|
|
return True
|
|
elif id == COMMENT_LINES_ID:
|
|
self.OnCommentLines()
|
|
return True
|
|
elif id == UNCOMMENT_LINES_ID:
|
|
self.OnUncommentLines()
|
|
return True
|
|
else:
|
|
return STCTextEditor.TextView.ProcessEvent(self, event)
|
|
|
|
|
|
def ProcessUpdateUIEvent(self, event):
|
|
if not self.GetCtrl():
|
|
return False
|
|
id = event.GetId()
|
|
if id == EXPAND_TEXT_ID:
|
|
if self.GetCtrl().GetViewFolding():
|
|
event.Enable(self.GetCtrl().CanLineExpand(self.GetCtrl().GetCurrentLine()))
|
|
else:
|
|
event.Enable(False)
|
|
return True
|
|
elif id == COLLAPSE_TEXT_ID:
|
|
if self.GetCtrl().GetViewFolding():
|
|
event.Enable(self.GetCtrl().CanLineCollapse(self.GetCtrl().GetCurrentLine()))
|
|
else:
|
|
event.Enable(False)
|
|
return True
|
|
elif (id == EXPAND_TOP_ID
|
|
or id == COLLAPSE_TOP_ID
|
|
or id == EXPAND_ALL_ID
|
|
or id == COLLAPSE_ALL_ID):
|
|
if self.GetCtrl().GetViewFolding():
|
|
event.Enable(self.GetCtrl().GetTextLength() > 0)
|
|
else:
|
|
event.Enable(False)
|
|
return True
|
|
elif (id == AUTO_COMPLETE_ID
|
|
or id == CLEAN_WHITESPACE
|
|
or id == INDENT_LINES_ID
|
|
or id == DEDENT_LINES_ID
|
|
or id == COMMENT_LINES_ID
|
|
or id == UNCOMMENT_LINES_ID):
|
|
event.Enable(self.GetCtrl().GetTextLength() > 0)
|
|
return True
|
|
elif id == CHECK_CODE_ID:
|
|
event.Enable(False)
|
|
return True
|
|
elif id == SET_INDENT_WIDTH_ID:
|
|
event.Enable(True)
|
|
return True
|
|
elif id == FOLDING_ID:
|
|
event.Enable(self.GetCtrl().GetViewFolding())
|
|
return True
|
|
elif id == USE_TABS_ID:
|
|
event.Enable(True)
|
|
event.Check(self.GetCtrl().GetUseTabs())
|
|
return True
|
|
else:
|
|
return STCTextEditor.TextView.ProcessUpdateUIEvent(self, event)
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
# Methods for OutlineService
|
|
#----------------------------------------------------------------------------
|
|
|
|
def OnChangeFilename(self):
|
|
wx.lib.docview.View.OnChangeFilename(self)
|
|
self.LoadOutline(force=True)
|
|
|
|
|
|
def ClearOutline(self):
|
|
outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
|
|
if not outlineService:
|
|
return
|
|
|
|
outlineView = outlineService.GetView()
|
|
if not outlineView:
|
|
return
|
|
|
|
outlineView.ClearTreeCtrl()
|
|
|
|
|
|
def LoadOutline(self, force=False):
|
|
outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
|
|
if not outlineService:
|
|
return
|
|
outlineService.LoadOutline(self, force=force)
|
|
|
|
|
|
def DoLoadOutlineCallback(self, force=False):
|
|
outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
|
|
if not outlineService:
|
|
return False
|
|
|
|
outlineView = outlineService.GetView()
|
|
if not outlineView:
|
|
return False
|
|
|
|
treeCtrl = outlineView.GetTreeCtrl()
|
|
if not treeCtrl:
|
|
return False
|
|
|
|
view = treeCtrl.GetCallbackView()
|
|
newCheckSum = self.GenCheckSum()
|
|
if not force:
|
|
if view and view is self:
|
|
if self._checkSum == newCheckSum:
|
|
return False
|
|
self._checkSum = newCheckSum
|
|
|
|
treeCtrl.DeleteAllItems()
|
|
|
|
document = self.GetDocument()
|
|
if not document:
|
|
return True
|
|
|
|
filename = document.GetFilename()
|
|
if filename:
|
|
rootItem = treeCtrl.AddRoot(os.path.basename(filename))
|
|
treeCtrl.SetDoSelectCallback(rootItem, self, (0,0))
|
|
else:
|
|
return True
|
|
|
|
text = self.GetValue()
|
|
if not text:
|
|
return True
|
|
|
|
CLASS_PATTERN = 'class[ \t]+\w+.*?:'
|
|
DEF_PATTERN = 'def[ \t]+\w+\(.*?\)'
|
|
classPat = re.compile(CLASS_PATTERN, re.M|re.S)
|
|
defPat= re.compile(DEF_PATTERN, re.M|re.S)
|
|
pattern = re.compile('^[ \t]*((' + CLASS_PATTERN + ')|('+ DEF_PATTERN +'.*?:)).*?$', re.M|re.S)
|
|
|
|
iter = pattern.finditer(text)
|
|
indentStack = [(0, rootItem)]
|
|
for pattern in iter:
|
|
line = pattern.string[pattern.start(0):pattern.end(0)]
|
|
classLine = classPat.search(line)
|
|
if classLine:
|
|
indent = classLine.start(0)
|
|
itemStr = classLine.string[classLine.start(0):classLine.end(0)-1] # don't take the closing ':'
|
|
itemStr = itemStr.replace("\n", "").replace("\r", "").replace(",\\", ",").replace(" ", "") # remove line continuations and spaces from outline view
|
|
else:
|
|
defLine = defPat.search(line)
|
|
if defLine:
|
|
indent = defLine.start(0)
|
|
itemStr = defLine.string[defLine.start(0):defLine.end(0)]
|
|
itemStr = itemStr.replace("\n", "").replace("\r", "").replace(",\\", ",").replace(" ", "") # remove line continuations and spaces from outline view
|
|
|
|
if indent == 0:
|
|
parentItem = rootItem
|
|
else:
|
|
lastItem = indentStack.pop()
|
|
while lastItem[0] >= indent:
|
|
lastItem = indentStack.pop()
|
|
indentStack.append(lastItem)
|
|
parentItem = lastItem[1]
|
|
|
|
item = treeCtrl.AppendItem(parentItem, itemStr)
|
|
treeCtrl.SetDoSelectCallback(item, self, (pattern.end(0), pattern.start(0) + indent)) # select in reverse order because we want the cursor to be at the start of the line so it wouldn't scroll to the right
|
|
indentStack.append((indent, item))
|
|
|
|
treeCtrl.Expand(rootItem)
|
|
|
|
return True
|
|
|
|
|
|
def DoSelectCallback(self, data):
|
|
if data:
|
|
self.EnsureVisibleEnforcePolicy(self.LineFromPosition(data[0]))
|
|
# wxBug: need to select in reverse order (end, start) to place cursor at begining of line,
|
|
# otherwise, display is scrolled over to the right hard and is hard to view
|
|
self.SetSelection(data[1], data[0])
|
|
|
|
|
|
## def checksum(self, bytes):
|
|
## def rotate_right(c):
|
|
## if c&1:
|
|
## return (c>>1)|0x8000
|
|
## else:
|
|
## return c>>1
|
|
##
|
|
## result = 0
|
|
## for ch in bytes:
|
|
## ch = ord(ch) & 0xFF
|
|
## result = (rotate_right(result)+ch) & 0xFFFF
|
|
## return result
|
|
##
|
|
|
|
def GenCheckSum(self):
|
|
""" Poor man's checksum. We'll assume most changes will change the length of the file.
|
|
"""
|
|
text = self.GetValue()
|
|
if text:
|
|
return len(text)
|
|
else:
|
|
return 0
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
# Format methods
|
|
#----------------------------------------------------------------------------
|
|
|
|
def OnCheckCode(self):
|
|
""" Need to overshadow this for each specific subclass """
|
|
if 0:
|
|
try:
|
|
code = self.GetCtrl().GetText()
|
|
codeObj = compile(code, self.GetDocument().GetFilename(), 'exec')
|
|
self._GetParentFrame().SetStatusText(_("The file successfully compiled"))
|
|
except SyntaxError, (message, (fileName, line, col, text)):
|
|
pos = self.GetCtrl().PositionFromLine(line - 1) + col - 1
|
|
self.GetCtrl().SetSelection(pos, pos)
|
|
self._GetParentFrame().SetStatusText(_("Syntax Error: %s") % message)
|
|
except:
|
|
self._GetParentFrame().SetStatusText("%s: %s" % (sys.exc_info()[0], sys.exc_info()[1]))
|
|
|
|
|
|
def OnAutoComplete(self):
|
|
self.GetCtrl().AutoCompCancel()
|
|
self.GetCtrl().AutoCompSetAutoHide(0)
|
|
self.GetCtrl().AutoCompSetChooseSingle(True)
|
|
self.GetCtrl().AutoCompSetIgnoreCase(True)
|
|
context, hint = self.GetAutoCompleteHint()
|
|
replaceList, replaceLen = self.GetAutoCompleteKeywordList(context, hint)
|
|
if replaceList and len(replaceList) != 0:
|
|
self.GetCtrl().AutoCompShow(replaceLen, replaceList)
|
|
|
|
|
|
def GetAutoCompleteHint(self):
|
|
""" Replace this method with Editor specific method """
|
|
pos = self.GetCtrl().GetCurrentPos()
|
|
if pos == 0:
|
|
return None, None
|
|
if chr(self.GetCtrl().GetCharAt(pos - 1)) == '.':
|
|
pos = pos - 1
|
|
hint = None
|
|
else:
|
|
hint = ''
|
|
|
|
validLetters = string.letters + string.digits + '_.'
|
|
word = ''
|
|
while (True):
|
|
pos = pos - 1
|
|
if pos < 0:
|
|
break
|
|
char = chr(self.GetCtrl().GetCharAt(pos))
|
|
if char not in validLetters:
|
|
break
|
|
word = char + word
|
|
|
|
context = word
|
|
if hint is not None:
|
|
lastDot = word.rfind('.')
|
|
if lastDot != -1:
|
|
context = word[0:lastDot]
|
|
hint = word[lastDot+1:]
|
|
|
|
return context, hint
|
|
|
|
|
|
def GetAutoCompleteDefaultKeywords(self):
|
|
""" Replace this method with Editor specific keywords """
|
|
return ['Put', 'Editor Specific', 'Keywords', 'Here']
|
|
|
|
|
|
def GetAutoCompleteKeywordList(self, context, hint):
|
|
""" Replace this method with Editor specific keywords """
|
|
kw = self.GetAutoCompleteDefaultKeywords()
|
|
|
|
if hint and len(hint):
|
|
lowerHint = hint.lower()
|
|
filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw) # remove variables and methods that don't match hint
|
|
kw = filterkw
|
|
|
|
if hint:
|
|
replaceLen = len(hint)
|
|
else:
|
|
replaceLen = 0
|
|
|
|
kw.sort(CaseInsensitiveCompare)
|
|
return " ".join(kw), replaceLen
|
|
|
|
|
|
def OnCleanWhiteSpace(self):
|
|
newText = ""
|
|
for lineNo in self._GetSelectedLineNumbers():
|
|
lineText = string.rstrip(self.GetCtrl().GetLine(lineNo))
|
|
indent = 0
|
|
lstrip = 0
|
|
for char in lineText:
|
|
if char == '\t':
|
|
indent = indent + self.GetCtrl().GetIndent()
|
|
lstrip = lstrip + 1
|
|
elif char in string.whitespace:
|
|
indent = indent + 1
|
|
lstrip = lstrip + 1
|
|
else:
|
|
break
|
|
if self.GetCtrl().GetUseTabs():
|
|
indentText = (indent / self.GetCtrl().GetIndent()) * '\t' + (indent % self.GetCtrl().GetIndent()) * ' '
|
|
else:
|
|
indentText = indent * ' '
|
|
lineText = indentText + lineText[lstrip:] + '\n'
|
|
newText = newText + lineText
|
|
self._ReplaceSelectedLines(newText)
|
|
|
|
|
|
def OnSetIndentWidth(self):
|
|
dialog = wx.TextEntryDialog(self._GetParentFrame(), _("Enter new indent width (2-10):"), _("Set Indent Width"), "%i" % self.GetCtrl().GetIndent())
|
|
dialog.CenterOnParent()
|
|
if dialog.ShowModal() == wx.ID_OK:
|
|
try:
|
|
indent = int(dialog.GetValue())
|
|
if indent >= 2 and indent <= 10:
|
|
self.GetCtrl().SetIndent(indent)
|
|
self.GetCtrl().SetTabWidth(indent)
|
|
except:
|
|
pass
|
|
dialog.Destroy()
|
|
|
|
|
|
def GetIndentWidth(self):
|
|
return self.GetCtrl().GetIndent()
|
|
|
|
|
|
def OnCommentLines(self):
|
|
newText = ""
|
|
for lineNo in self._GetSelectedLineNumbers():
|
|
lineText = self.GetCtrl().GetLine(lineNo)
|
|
if (len(lineText) > 1 and lineText[0] == '#') or (len(lineText) > 2 and lineText[:2] == '##'):
|
|
newText = newText + lineText
|
|
else:
|
|
newText = newText + "##" + lineText
|
|
self._ReplaceSelectedLines(newText)
|
|
|
|
|
|
def OnUncommentLines(self):
|
|
newText = ""
|
|
for lineNo in self._GetSelectedLineNumbers():
|
|
lineText = self.GetCtrl().GetLine(lineNo)
|
|
if len(lineText) >= 2 and lineText[:2] == "##":
|
|
lineText = lineText[2:]
|
|
elif len(lineText) >= 1 and lineText[:1] == "#":
|
|
lineText = lineText[1:]
|
|
newText = newText + lineText
|
|
self._ReplaceSelectedLines(newText)
|
|
|
|
|
|
def _GetSelectedLineNumbers(self):
|
|
selStart, selEnd = self._GetPositionsBoundingSelectedLines()
|
|
return range(self.GetCtrl().LineFromPosition(selStart), self.GetCtrl().LineFromPosition(selEnd))
|
|
|
|
|
|
def _GetPositionsBoundingSelectedLines(self):
|
|
startPos = self.GetCtrl().GetCurrentPos()
|
|
endPos = self.GetCtrl().GetAnchor()
|
|
if startPos > endPos:
|
|
temp = endPos
|
|
endPos = startPos
|
|
startPos = temp
|
|
if endPos == self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos)):
|
|
endPos = endPos - 1 # If it's at the very beginning of a line, use the line above it as the ending line
|
|
selStart = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(startPos))
|
|
selEnd = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos) + 1)
|
|
return selStart, selEnd
|
|
|
|
|
|
def _ReplaceSelectedLines(self, text):
|
|
if len(text) == 0:
|
|
return
|
|
selStart, selEnd = self._GetPositionsBoundingSelectedLines()
|
|
self.GetCtrl().SetSelection(selStart, selEnd)
|
|
self.GetCtrl().ReplaceSelection(text)
|
|
self.GetCtrl().SetSelection(selStart + len(text), selStart)
|
|
|
|
|
|
def OnUpdate(self, sender = None, hint = None):
|
|
if wx.lib.docview.View.OnUpdate(self, sender, hint):
|
|
return
|
|
|
|
if hint == "ViewStuff":
|
|
self.GetCtrl().SetViewDefaults()
|
|
elif hint == "Font":
|
|
font, color = self.GetCtrl().GetFontAndColorFromConfig()
|
|
self.GetCtrl().SetFont(font)
|
|
self.GetCtrl().SetFontColor(color)
|
|
else:
|
|
import DebuggerService
|
|
dbg_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
|
|
if dbg_service:
|
|
dbg_service.SetCurrentBreakpointMarkers(self)
|
|
|
|
|
|
class CodeService(STCTextEditor.TextService):
|
|
|
|
|
|
def __init__(self):
|
|
STCTextEditor.TextService.__init__(self)
|
|
|
|
|
|
def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
|
|
# TODO NEED TO DO INSTANCEOF CHECK HERE FOR SDI
|
|
#if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument:
|
|
# return
|
|
if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
|
|
return
|
|
|
|
viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
|
|
isWindows = (wx.Platform == '__WXMSW__')
|
|
|
|
if not menuBar.FindItemById(EXPAND_TEXT_ID): # check if below menu items have been already been installed
|
|
foldingMenu = wx.Menu()
|
|
if isWindows:
|
|
foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand\tNumpad-Plus"), _("Expands a collapsed block of text"))
|
|
else:
|
|
foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand"), _("Expands a collapsed block of text"))
|
|
|
|
wx.EVT_MENU(frame, EXPAND_TEXT_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, EXPAND_TEXT_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
if isWindows:
|
|
foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse\tNumpad+Minus"), _("Collapse a block of text"))
|
|
else:
|
|
foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse"), _("Collapse a block of text"))
|
|
wx.EVT_MENU(frame, COLLAPSE_TEXT_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, COLLAPSE_TEXT_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
if isWindows:
|
|
foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level\tCtrl+Numpad+Plus"), _("Expands the top fold levels in the document"))
|
|
else:
|
|
foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level"), _("Expands the top fold levels in the document"))
|
|
wx.EVT_MENU(frame, EXPAND_TOP_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, EXPAND_TOP_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
if isWindows:
|
|
foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level\tCtrl+Numpad+Minus"), _("Collapses the top fold levels in the document"))
|
|
else:
|
|
foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level"), _("Collapses the top fold levels in the document"))
|
|
wx.EVT_MENU(frame, COLLAPSE_TOP_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, COLLAPSE_TOP_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
if isWindows:
|
|
foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All\tShift+Numpad+Plus"), _("Expands all of the fold levels in the document"))
|
|
else:
|
|
foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All"), _("Expands all of the fold levels in the document"))
|
|
wx.EVT_MENU(frame, EXPAND_ALL_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, EXPAND_ALL_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
if isWindows:
|
|
foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All\tShift+Numpad+Minus"), _("Collapses all of the fold levels in the document"))
|
|
else:
|
|
foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All"), _("Collapses all of the fold levels in the document"))
|
|
wx.EVT_MENU(frame, COLLAPSE_ALL_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, COLLAPSE_ALL_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
viewMenu.AppendMenu(FOLDING_ID, _("&Folding"), foldingMenu)
|
|
wx.EVT_UPDATE_UI(frame, FOLDING_ID, frame.ProcessUpdateUIEvent)
|
|
|
|
formatMenuIndex = menuBar.FindMenu(_("&Format"))
|
|
if formatMenuIndex > -1:
|
|
formatMenu = menuBar.GetMenu(formatMenuIndex)
|
|
else:
|
|
formatMenu = wx.Menu()
|
|
if not menuBar.FindItemById(CHECK_CODE_ID): # check if below menu items have been already been installed
|
|
formatMenu.AppendSeparator()
|
|
formatMenu.Append(CHECK_CODE_ID, _("&Check Code"), _("Checks the document for syntax and indentation errors"))
|
|
wx.EVT_MENU(frame, CHECK_CODE_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, CHECK_CODE_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.Append(AUTO_COMPLETE_ID, _("&Auto Complete\tCtrl+Space"), _("Provides suggestions on how to complete the current statement"))
|
|
wx.EVT_MENU(frame, AUTO_COMPLETE_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, AUTO_COMPLETE_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.Append(CLEAN_WHITESPACE, _("Clean &Whitespace"), _("Converts leading spaces to tabs or vice versa per 'use tabs' and clears trailing spaces"))
|
|
wx.EVT_MENU(frame, CLEAN_WHITESPACE, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, CLEAN_WHITESPACE, frame.ProcessUpdateUIEvent)
|
|
formatMenu.AppendSeparator()
|
|
formatMenu.Append(INDENT_LINES_ID, _("&Indent Lines\tTab"), _("Indents the selected lines one indent width"))
|
|
wx.EVT_MENU(frame, INDENT_LINES_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, INDENT_LINES_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.Append(DEDENT_LINES_ID, _("&Dedent Lines\tShift+Tab"), _("Dedents the selected lines one indent width"))
|
|
wx.EVT_MENU(frame, DEDENT_LINES_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, DEDENT_LINES_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.Append(COMMENT_LINES_ID, _("Comment &Lines\tCtrl+Q"), _("Comments out the selected lines be prefixing each one with a comment indicator"))
|
|
wx.EVT_MENU(frame, COMMENT_LINES_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, COMMENT_LINES_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.Append(UNCOMMENT_LINES_ID, _("&Uncomment Lines\tCtrl+Shift+Q"), _("Removes comment prefixes from each of the selected lines"))
|
|
wx.EVT_MENU(frame, UNCOMMENT_LINES_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, UNCOMMENT_LINES_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.AppendSeparator()
|
|
formatMenu.AppendCheckItem(USE_TABS_ID, _("Use &Tabs"), _("Toggles use of tabs or whitespaces for indents"))
|
|
wx.EVT_MENU(frame, USE_TABS_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, USE_TABS_ID, frame.ProcessUpdateUIEvent)
|
|
formatMenu.Append(SET_INDENT_WIDTH_ID, _("&Set Indent Width..."), _("Sets the indent width"))
|
|
wx.EVT_MENU(frame, SET_INDENT_WIDTH_ID, frame.ProcessEvent)
|
|
wx.EVT_UPDATE_UI(frame, SET_INDENT_WIDTH_ID, frame.ProcessUpdateUIEvent)
|
|
if formatMenuIndex == -1:
|
|
viewMenuIndex = menuBar.FindMenu(_("&View"))
|
|
menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format"))
|
|
|
|
## accelTable = wx.AcceleratorTable([
|
|
## (wx.ACCEL_NORMAL, wx.WXK_TAB, INDENT_LINES_ID),
|
|
## (wx.ACCEL_SHIFT, wx.WXK_TAB, DEDENT_LINES_ID),
|
|
## eval(_("wx.ACCEL_CTRL, ord('Q'), COMMENT_LINES_ID")),
|
|
## eval(_("wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord('Q'), UNCOMMENT_LINES_ID"))
|
|
## ])
|
|
## frame.SetAcceleratorTable(accelTable)
|
|
|
|
def ProcessUpdateUIEvent(self, event):
|
|
id = event.GetId()
|
|
if (id == EXPAND_TEXT_ID
|
|
or id == COLLAPSE_TEXT_ID
|
|
or id == EXPAND_TOP_ID
|
|
or id == COLLAPSE_TOP_ID
|
|
or id == EXPAND_ALL_ID
|
|
or id == COLLAPSE_ALL_ID
|
|
or id == CHECK_CODE_ID
|
|
or id == AUTO_COMPLETE_ID
|
|
or id == CLEAN_WHITESPACE
|
|
or id == SET_INDENT_WIDTH_ID
|
|
or id == USE_TABS_ID
|
|
or id == INDENT_LINES_ID
|
|
or id == DEDENT_LINES_ID
|
|
or id == COMMENT_LINES_ID
|
|
or id == UNCOMMENT_LINES_ID
|
|
or id == FOLDING_ID):
|
|
event.Enable(False)
|
|
return True
|
|
else:
|
|
return STCTextEditor.TextService.ProcessUpdateUIEvent(self, event)
|
|
|
|
|
|
class CodeCtrl(STCTextEditor.TextCtrl):
|
|
CURRENT_LINE_MARKER_NUM = 2
|
|
BREAKPOINT_MARKER_NUM = 1
|
|
CURRENT_LINE_MARKER_MASK = 0x4
|
|
BREAKPOINT_MARKER_MASK = 0x2
|
|
|
|
|
|
def __init__(self, parent, id=-1, style = wx.NO_FULL_REPAINT_ON_RESIZE, clearTab=True):
|
|
STCTextEditor.TextCtrl.__init__(self, parent, id, style)
|
|
|
|
self.UsePopUp(False)
|
|
self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
|
|
self.SetProperty("fold", "1")
|
|
|
|
# Setup a margin to hold fold markers
|
|
#self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
|
|
self.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL)
|
|
self.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS)
|
|
self.SetMarginSensitive(2, True)
|
|
|
|
self.SetMarginSensitive(1, False)
|
|
self.SetMarginMask(1, 0x4)
|
|
|
|
self.SetMarginSensitive(0, True)
|
|
self.SetMarginType(0, wx.stc.STC_MARGIN_SYMBOL)
|
|
self.SetMarginMask(0, 0x3)
|
|
self.SetMarginWidth(0, 12)
|
|
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUSCONNECTED, "white", "black")
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "black")
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER, "white", "black")
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_LCORNER, "white", "black")
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_VLINE, "white", "black")
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS, "white", "black")
|
|
self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS, "white", "black")
|
|
# Define the current line marker
|
|
self.MarkerDefine(CodeCtrl.CURRENT_LINE_MARKER_NUM, wx.stc.STC_MARK_SHORTARROW, wx.BLACK, (255,255,128))
|
|
# Define the breakpoint marker
|
|
self.MarkerDefine(CodeCtrl.BREAKPOINT_MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, (255,0,0))
|
|
|
|
if _WINDOWS and clearTab: # should test to see if menu item exists, if it does, add this workaround
|
|
self.CmdKeyClear(wx.stc.STC_KEY_TAB, 0) # menu item "Indent Lines" from CodeService.InstallControls() generates another INDENT_LINES_ID event, so we'll explicitly disable the tab processing in the editor
|
|
|
|
wx.stc.EVT_STC_MARGINCLICK(self, self.GetId(), self.OnMarginClick)
|
|
wx.EVT_KEY_DOWN(self, self.OnKeyPressed)
|
|
if self.GetMatchingBraces():
|
|
wx.stc.EVT_STC_UPDATEUI(self, self.GetId(), self.OnUpdateUI)
|
|
|
|
self.StyleClearAll()
|
|
self.UpdateStyles()
|
|
|
|
|
|
def OnRightUp(self, event):
|
|
#Hold onto the current line number, no way to get it later.
|
|
self._rightClickPosition = self.PositionFromPoint(event.GetPosition())
|
|
self._rightClickLine = self.LineFromPosition(self._rightClickPosition)
|
|
self.PopupMenu(self.CreatePopupMenu(), event.GetPosition())
|
|
self._rightClickLine = -1
|
|
self._rightClickPosition = -1
|
|
|
|
|
|
def CreatePopupMenu(self):
|
|
TOGGLEBREAKPOINT_ID = wx.NewId()
|
|
TOGGLEMARKER_ID = wx.NewId()
|
|
SYNCTREE_ID = wx.NewId()
|
|
|
|
menu = wx.Menu()
|
|
|
|
self.Bind(wx.EVT_MENU, self.OnPopSyncOutline, id=SYNCTREE_ID)
|
|
item = wx.MenuItem(menu, SYNCTREE_ID, _("Find in Outline View"))
|
|
menu.AppendItem(item)
|
|
menu.AppendSeparator()
|
|
self.Bind(wx.EVT_MENU, self.OnPopToggleBP, id=TOGGLEBREAKPOINT_ID)
|
|
item = wx.MenuItem(menu, TOGGLEBREAKPOINT_ID, _("Toggle Breakpoint"))
|
|
menu.AppendItem(item)
|
|
self.Bind(wx.EVT_MENU, self.OnPopToggleMarker, id=TOGGLEMARKER_ID)
|
|
item = wx.MenuItem(menu, TOGGLEMARKER_ID, _("Toggle Bookmark"))
|
|
menu.AppendItem(item)
|
|
menu.AppendSeparator()
|
|
|
|
itemIDs = [wx.ID_UNDO, wx.ID_REDO, None,
|
|
wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL]
|
|
|
|
menuBar = wx.GetApp().GetTopWindow().GetMenuBar()
|
|
for itemID in itemIDs:
|
|
if not itemID:
|
|
menu.AppendSeparator()
|
|
else:
|
|
item = menuBar.FindItemById(itemID)
|
|
if item:
|
|
menu.Append(itemID, item.GetLabel())
|
|
wx.EVT_MENU(self, itemID, self.DSProcessEvent) # wxHack: for customized right mouse menu doesn't work with new DynamicSashWindow
|
|
wx.EVT_UPDATE_UI(self, itemID, self.DSProcessUpdateUIEvent) # wxHack: for customized right mouse menu doesn't work with new DynamicSashWindow
|
|
return menu
|
|
|
|
|
|
def OnPopToggleBP(self, event):
|
|
""" Toggle break point on right click line, not current line """
|
|
import DebuggerService
|
|
wx.GetApp().GetService(DebuggerService.DebuggerService).OnToggleBreakpoint(event, line=self._rightClickLine)
|
|
|
|
|
|
def OnPopToggleMarker(self, event):
|
|
""" Toggle marker on right click line, not current line """
|
|
wx.GetApp().GetDocumentManager().GetCurrentView().MarkerToggle(lineNum = self._rightClickLine)
|
|
|
|
|
|
def OnPopSyncOutline(self, event):
|
|
wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(wx.GetApp().GetDocumentManager().GetCurrentView(), position=self._rightClickPosition)
|
|
|
|
|
|
def HasSelection(self):
|
|
return self.GetSelectionStart() - self.GetSelectionEnd() != 0
|
|
|
|
|
|
def ClearCurrentLineMarkers(self):
|
|
self.MarkerDeleteAll(CodeCtrl.CURRENT_LINE_MARKER_NUM)
|
|
|
|
|
|
def ClearCurrentBreakpoinMarkers(self):
|
|
self.MarkerDeleteAll(CodeCtrl.BREAKPOINT_MARKER_NUM)
|
|
|
|
|
|
def GetDefaultFont(self):
|
|
if wx.Platform == '__WXMSW__':
|
|
font = "Courier New"
|
|
else:
|
|
font = "Courier"
|
|
return wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font)
|
|
|
|
|
|
def GetMatchingBraces(self):
|
|
""" Overwrite this method for language specific braces """
|
|
return "[]{}()"
|
|
|
|
|
|
def CanWordWrap(self):
|
|
return False
|
|
|
|
|
|
def SetFont(self, font):
|
|
self._font = font
|
|
|
|
|
|
def SetFontColor(self, fontColor):
|
|
self._fontColor = fontColor
|
|
|
|
|
|
def UpdateStyles(self):
|
|
|
|
if not self.GetFont():
|
|
return
|
|
|
|
faces = { 'font' : self.GetFont().GetFaceName(),
|
|
'size' : self.GetFont().GetPointSize(),
|
|
'size2': self.GetFont().GetPointSize() - 2,
|
|
'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
|
|
}
|
|
|
|
# Global default styles for all languages
|
|
self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(font)s,fore:#FFFFFF,size:%(size)d" % faces)
|
|
self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, "face:%(font)s,back:#C0C0C0,face:%(font)s,size:%(size2)d" % faces)
|
|
self.StyleSetSpec(wx.stc.STC_STYLE_CONTROLCHAR, "face:%(font)s" % faces)
|
|
self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT, "face:%(font)s,fore:#000000,back:#70FFFF,size:%(size)d" % faces)
|
|
self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD, "face:%(font)s,fore:#000000,back:#FF0000,size:%(size)d" % faces)
|
|
|
|
|
|
def OnKeyPressed(self, event):
|
|
if self.CallTipActive():
|
|
self.CallTipCancel()
|
|
key = event.KeyCode()
|
|
if False: # key == wx.WXK_SPACE and event.ControlDown():
|
|
pos = self.GetCurrentPos()
|
|
# Tips
|
|
if event.ShiftDown():
|
|
self.CallTipSetBackground("yellow")
|
|
self.CallTipShow(pos, 'param1, param2')
|
|
# Code completion
|
|
else:
|
|
#lst = []
|
|
#for x in range(50000):
|
|
# lst.append('%05d' % x)
|
|
#st = string.join(lst)
|
|
#print len(st)
|
|
#self.AutoCompShow(0, st)
|
|
|
|
kw = keyword.kwlist[:]
|
|
kw.append("zzzzzz")
|
|
kw.append("aaaaa")
|
|
kw.append("__init__")
|
|
kw.append("zzaaaaa")
|
|
kw.append("zzbaaaa")
|
|
kw.append("this_is_a_longer_value")
|
|
kw.append("this_is_a_much_much_much_much_much_much_much_longer_value")
|
|
|
|
kw.sort() # Python sorts are case sensitive
|
|
self.AutoCompSetIgnoreCase(False) # so this needs to match
|
|
|
|
self.AutoCompShow(0, string.join(kw))
|
|
elif key == wx.WXK_RETURN:
|
|
self.DoIndent()
|
|
else:
|
|
STCTextEditor.TextCtrl.OnKeyPressed(self, event)
|
|
|
|
|
|
def DoIndent(self):
|
|
self.AddText('\n')
|
|
self.EnsureCaretVisible()
|
|
# Need to do a default one for all languges
|
|
|
|
|
|
def OnMarginClick(self, evt):
|
|
# fold and unfold as needed
|
|
if evt.GetMargin() == 2:
|
|
if evt.GetShift() and evt.GetControl():
|
|
lineCount = self.GetLineCount()
|
|
expanding = True
|
|
|
|
# find out if we are folding or unfolding
|
|
for lineNum in range(lineCount):
|
|
if self.GetFoldLevel(lineNum) & wx.stc.STC_FOLDLEVELHEADERFLAG:
|
|
expanding = not self.GetFoldExpanded(lineNum)
|
|
break;
|
|
|
|
self.ToggleFoldAll(expanding)
|
|
else:
|
|
lineClicked = self.LineFromPosition(evt.GetPosition())
|
|
if self.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG:
|
|
if evt.GetShift():
|
|
self.SetFoldExpanded(lineClicked, True)
|
|
self.Expand(lineClicked, True, True, 1)
|
|
elif evt.GetControl():
|
|
if self.GetFoldExpanded(lineClicked):
|
|
self.SetFoldExpanded(lineClicked, False)
|
|
self.Expand(lineClicked, False, True, 0)
|
|
else:
|
|
self.SetFoldExpanded(lineClicked, True)
|
|
self.Expand(lineClicked, True, True, 100)
|
|
else:
|
|
self.ToggleFold(lineClicked)
|
|
|
|
elif evt.GetMargin() == 0:
|
|
#This is used to toggle breakpoints via the debugger service.
|
|
import DebuggerService
|
|
db_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
|
|
if db_service:
|
|
db_service.OnToggleBreakpoint(evt, line=self.LineFromPosition(evt.GetPosition()))
|
|
|
|
|
|
def OnUpdateUI(self, evt):
|
|
braces = self.GetMatchingBraces()
|
|
|
|
# check for matching braces
|
|
braceAtCaret = -1
|
|
braceOpposite = -1
|
|
charBefore = None
|
|
caretPos = self.GetCurrentPos()
|
|
if caretPos > 0:
|
|
charBefore = self.GetCharAt(caretPos - 1)
|
|
styleBefore = self.GetStyleAt(caretPos - 1)
|
|
|
|
# check before
|
|
if charBefore and chr(charBefore) in braces:
|
|
braceAtCaret = caretPos - 1
|
|
|
|
# check after
|
|
if braceAtCaret < 0:
|
|
charAfter = self.GetCharAt(caretPos)
|
|
styleAfter = self.GetStyleAt(caretPos)
|
|
if charAfter and chr(charAfter) in braces:
|
|
braceAtCaret = caretPos
|
|
|
|
if braceAtCaret >= 0:
|
|
braceOpposite = self.BraceMatch(braceAtCaret)
|
|
|
|
if braceAtCaret != -1 and braceOpposite == -1:
|
|
self.BraceBadLight(braceAtCaret)
|
|
else:
|
|
self.BraceHighlight(braceAtCaret, braceOpposite)
|
|
|
|
evt.Skip()
|
|
|
|
|
|
def ToggleFoldAll(self, expand = True, topLevelOnly = False):
|
|
i = 0
|
|
lineCount = self.GetLineCount()
|
|
while i < lineCount:
|
|
if not topLevelOnly or (topLevelOnly and self.GetFoldLevel(i) & wx.stc.STC_FOLDLEVELNUMBERMASK == wx.stc.STC_FOLDLEVELBASE):
|
|
if (expand and self.CanLineExpand(i)) or (not expand and self.CanLineCollapse(i)):
|
|
self.ToggleFold(i)
|
|
i = i + 1
|
|
|
|
|
|
def CanLineExpand(self, line):
|
|
return not self.GetFoldExpanded(line)
|
|
|
|
|
|
def CanLineCollapse(self, line):
|
|
return self.GetFoldExpanded(line) and self.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG
|
|
|
|
|
|
def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
|
|
lastChild = self.GetLastChild(line, level)
|
|
line = line + 1
|
|
while line <= lastChild:
|
|
if force:
|
|
if visLevels > 0:
|
|
self.ShowLines(line, line)
|
|
else:
|
|
self.HideLines(line, line)
|
|
else:
|
|
if doExpand:
|
|
self.ShowLines(line, line)
|
|
|
|
if level == -1:
|
|
level = self.GetFoldLevel(line)
|
|
|
|
if level & wx.stc.STC_FOLDLEVELHEADERFLAG:
|
|
if force:
|
|
if visLevels > 1:
|
|
self.SetFoldExpanded(line, True)
|
|
else:
|
|
self.SetFoldExpanded(line, False)
|
|
line = self.Expand(line, doExpand, force, visLevels-1)
|
|
|
|
else:
|
|
if doExpand and self.GetFoldExpanded(line):
|
|
line = self.Expand(line, True, force, visLevels-1)
|
|
else:
|
|
line = self.Expand(line, False, force, visLevels-1)
|
|
else:
|
|
line = line + 1;
|
|
|
|
return line
|
|
|
|
|