]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/ide/activegrid/tool/CodeEditor.py
Don't use the old wxPython namespace
[wxWidgets.git] / wxPython / samples / ide / activegrid / tool / CodeEditor.py
1 #----------------------------------------------------------------------------
2 # Name: CodeEditor.py
3 # Purpose: Abstract Code Editor for pydocview tbat uses the Styled Text Control
4 #
5 # Author: Peter Yared
6 #
7 # Created: 8/10/03
8 # CVS-ID: $Id$
9 # Copyright: (c) 2004-2005 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
12
13
14 import STCTextEditor
15 import wx
16 import wx.lib.docview
17 import OutlineService
18 import os
19 import re
20 import string
21 import sys
22 import MarkerService
23 from UICommon import CaseInsensitiveCompare
24 _ = wx.GetTranslation
25 if wx.Platform == '__WXMSW__':
26 _WINDOWS = True
27 else:
28 _WINDOWS = False
29
30
31 EXPAND_TEXT_ID = wx.NewId()
32 COLLAPSE_TEXT_ID = wx.NewId()
33 EXPAND_TOP_ID = wx.NewId()
34 COLLAPSE_TOP_ID = wx.NewId()
35 EXPAND_ALL_ID = wx.NewId()
36 COLLAPSE_ALL_ID = wx.NewId()
37 CHECK_CODE_ID = wx.NewId()
38 AUTO_COMPLETE_ID = wx.NewId()
39 CLEAN_WHITESPACE = wx.NewId()
40 COMMENT_LINES_ID = wx.NewId()
41 UNCOMMENT_LINES_ID = wx.NewId()
42 INDENT_LINES_ID = wx.NewId()
43 DEDENT_LINES_ID = wx.NewId()
44 USE_TABS_ID = wx.NewId()
45 SET_INDENT_WIDTH_ID = wx.NewId()
46 FOLDING_ID = wx.NewId()
47
48
49 class CodeDocument(STCTextEditor.TextDocument):
50 pass
51
52
53 class CodeView(STCTextEditor.TextView):
54
55
56 #----------------------------------------------------------------------------
57 # Overridden methods
58 #----------------------------------------------------------------------------
59
60
61 def GetCtrlClass(self):
62 """ Used in split window to instantiate new instances """
63 return CodeCtrl
64
65
66 def ProcessEvent(self, event):
67 id = event.GetId()
68 if id == EXPAND_TEXT_ID:
69 self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine())
70 return True
71 elif id == COLLAPSE_TEXT_ID:
72 self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine())
73 return True
74 elif id == EXPAND_TOP_ID:
75 self.GetCtrl().ToggleFoldAll(expand = True, topLevelOnly = True)
76 return True
77 elif id == COLLAPSE_TOP_ID:
78 self.GetCtrl().ToggleFoldAll(expand = False, topLevelOnly = True)
79 return True
80 elif id == EXPAND_ALL_ID:
81 self.GetCtrl().ToggleFoldAll(expand = True)
82 return True
83 elif id == COLLAPSE_ALL_ID:
84 self.GetCtrl().ToggleFoldAll(expand = False)
85 return True
86 elif id == CHECK_CODE_ID:
87 self.OnCheckCode()
88 return True
89 elif id == AUTO_COMPLETE_ID:
90 self.OnAutoComplete()
91 return True
92 elif id == CLEAN_WHITESPACE:
93 self.OnCleanWhiteSpace()
94 return True
95 elif id == SET_INDENT_WIDTH_ID:
96 self.OnSetIndentWidth()
97 return True
98 elif id == USE_TABS_ID:
99 self.GetCtrl().SetUseTabs(not self.GetCtrl().GetUseTabs())
100 return True
101 elif id == INDENT_LINES_ID:
102 self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_TAB)
103 return True
104 elif id == DEDENT_LINES_ID:
105 self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_BACKTAB)
106 return True
107 elif id == COMMENT_LINES_ID:
108 self.OnCommentLines()
109 return True
110 elif id == UNCOMMENT_LINES_ID:
111 self.OnUncommentLines()
112 return True
113 else:
114 return STCTextEditor.TextView.ProcessEvent(self, event)
115
116
117 def ProcessUpdateUIEvent(self, event):
118 if not self.GetCtrl():
119 return False
120 id = event.GetId()
121 if id == EXPAND_TEXT_ID:
122 if self.GetCtrl().GetViewFolding():
123 event.Enable(self.GetCtrl().CanLineExpand(self.GetCtrl().GetCurrentLine()))
124 else:
125 event.Enable(False)
126 return True
127 elif id == COLLAPSE_TEXT_ID:
128 if self.GetCtrl().GetViewFolding():
129 event.Enable(self.GetCtrl().CanLineCollapse(self.GetCtrl().GetCurrentLine()))
130 else:
131 event.Enable(False)
132 return True
133 elif (id == EXPAND_TOP_ID
134 or id == COLLAPSE_TOP_ID
135 or id == EXPAND_ALL_ID
136 or id == COLLAPSE_ALL_ID):
137 if self.GetCtrl().GetViewFolding():
138 event.Enable(self.GetCtrl().GetTextLength() > 0)
139 else:
140 event.Enable(False)
141 return True
142 elif (id == AUTO_COMPLETE_ID
143 or id == CLEAN_WHITESPACE
144 or id == INDENT_LINES_ID
145 or id == DEDENT_LINES_ID
146 or id == COMMENT_LINES_ID
147 or id == UNCOMMENT_LINES_ID):
148 event.Enable(self.GetCtrl().GetTextLength() > 0)
149 return True
150 elif id == CHECK_CODE_ID:
151 event.Enable(False)
152 return True
153 elif id == SET_INDENT_WIDTH_ID:
154 event.Enable(True)
155 return True
156 elif id == FOLDING_ID:
157 event.Enable(self.GetCtrl().GetViewFolding())
158 return True
159 elif id == USE_TABS_ID:
160 event.Enable(True)
161 event.Check(self.GetCtrl().GetUseTabs())
162 return True
163 else:
164 return STCTextEditor.TextView.ProcessUpdateUIEvent(self, event)
165
166
167 #----------------------------------------------------------------------------
168 # Methods for OutlineService
169 #----------------------------------------------------------------------------
170
171 def OnChangeFilename(self):
172 wx.lib.docview.View.OnChangeFilename(self)
173 self.LoadOutline(force=True)
174
175
176 def ClearOutline(self):
177 outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
178 if not outlineService:
179 return
180
181 outlineView = outlineService.GetView()
182 if not outlineView:
183 return
184
185 outlineView.ClearTreeCtrl()
186
187
188 def LoadOutline(self, force=False):
189 outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
190 if not outlineService:
191 return
192 outlineService.LoadOutline(self, force=force)
193
194
195 def DoLoadOutlineCallback(self, force=False):
196 outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
197 if not outlineService:
198 return False
199
200 outlineView = outlineService.GetView()
201 if not outlineView:
202 return False
203
204 treeCtrl = outlineView.GetTreeCtrl()
205 if not treeCtrl:
206 return False
207
208 view = treeCtrl.GetCallbackView()
209 newCheckSum = self.GenCheckSum()
210 if not force:
211 if view and view is self:
212 if self._checkSum == newCheckSum:
213 return False
214 self._checkSum = newCheckSum
215
216 treeCtrl.DeleteAllItems()
217
218 document = self.GetDocument()
219 if not document:
220 return True
221
222 filename = document.GetFilename()
223 if filename:
224 rootItem = treeCtrl.AddRoot(os.path.basename(filename))
225 treeCtrl.SetDoSelectCallback(rootItem, self, (0,0))
226 else:
227 return True
228
229 text = self.GetValue()
230 if not text:
231 return True
232
233 CLASS_PATTERN = 'class[ \t]+\w+.*?:'
234 DEF_PATTERN = 'def[ \t]+\w+\(.*?\)'
235 classPat = re.compile(CLASS_PATTERN, re.M|re.S)
236 defPat= re.compile(DEF_PATTERN, re.M|re.S)
237 pattern = re.compile('^[ \t]*((' + CLASS_PATTERN + ')|('+ DEF_PATTERN +'.*?:)).*?$', re.M|re.S)
238
239 iter = pattern.finditer(text)
240 indentStack = [(0, rootItem)]
241 for pattern in iter:
242 line = pattern.string[pattern.start(0):pattern.end(0)]
243 classLine = classPat.search(line)
244 if classLine:
245 indent = classLine.start(0)
246 itemStr = classLine.string[classLine.start(0):classLine.end(0)-1] # don't take the closing ':'
247 itemStr = itemStr.replace("\n", "").replace("\r", "").replace(",\\", ",").replace(" ", "") # remove line continuations and spaces from outline view
248 else:
249 defLine = defPat.search(line)
250 if defLine:
251 indent = defLine.start(0)
252 itemStr = defLine.string[defLine.start(0):defLine.end(0)]
253 itemStr = itemStr.replace("\n", "").replace("\r", "").replace(",\\", ",").replace(" ", "") # remove line continuations and spaces from outline view
254
255 if indent == 0:
256 parentItem = rootItem
257 else:
258 lastItem = indentStack.pop()
259 while lastItem[0] >= indent:
260 lastItem = indentStack.pop()
261 indentStack.append(lastItem)
262 parentItem = lastItem[1]
263
264 item = treeCtrl.AppendItem(parentItem, itemStr)
265 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
266 indentStack.append((indent, item))
267
268 treeCtrl.Expand(rootItem)
269
270 return True
271
272
273 def DoSelectCallback(self, data):
274 if data:
275 self.EnsureVisibleEnforcePolicy(self.LineFromPosition(data[0]))
276 # wxBug: need to select in reverse order (end, start) to place cursor at begining of line,
277 # otherwise, display is scrolled over to the right hard and is hard to view
278 self.SetSelection(data[1], data[0])
279
280
281 ## def checksum(self, bytes):
282 ## def rotate_right(c):
283 ## if c&1:
284 ## return (c>>1)|0x8000
285 ## else:
286 ## return c>>1
287 ##
288 ## result = 0
289 ## for ch in bytes:
290 ## ch = ord(ch) & 0xFF
291 ## result = (rotate_right(result)+ch) & 0xFFFF
292 ## return result
293 ##
294
295 def GenCheckSum(self):
296 """ Poor man's checksum. We'll assume most changes will change the length of the file.
297 """
298 text = self.GetValue()
299 if text:
300 return len(text)
301 else:
302 return 0
303
304
305 #----------------------------------------------------------------------------
306 # Format methods
307 #----------------------------------------------------------------------------
308
309 def OnCheckCode(self):
310 """ Need to overshadow this for each specific subclass """
311 if 0:
312 try:
313 code = self.GetCtrl().GetText()
314 codeObj = compile(code, self.GetDocument().GetFilename(), 'exec')
315 self._GetParentFrame().SetStatusText(_("The file successfully compiled"))
316 except SyntaxError, (message, (fileName, line, col, text)):
317 pos = self.GetCtrl().PositionFromLine(line - 1) + col - 1
318 self.GetCtrl().SetSelection(pos, pos)
319 self._GetParentFrame().SetStatusText(_("Syntax Error: %s") % message)
320 except:
321 self._GetParentFrame().SetStatusText("%s: %s" % (sys.exc_info()[0], sys.exc_info()[1]))
322
323
324 def OnAutoComplete(self):
325 self.GetCtrl().AutoCompCancel()
326 self.GetCtrl().AutoCompSetAutoHide(0)
327 self.GetCtrl().AutoCompSetChooseSingle(True)
328 self.GetCtrl().AutoCompSetIgnoreCase(True)
329 context, hint = self.GetAutoCompleteHint()
330 replaceList, replaceLen = self.GetAutoCompleteKeywordList(context, hint)
331 if replaceList and len(replaceList) != 0:
332 self.GetCtrl().AutoCompShow(replaceLen, replaceList)
333
334
335 def GetAutoCompleteHint(self):
336 """ Replace this method with Editor specific method """
337 pos = self.GetCtrl().GetCurrentPos()
338 if pos == 0:
339 return None, None
340 if chr(self.GetCtrl().GetCharAt(pos - 1)) == '.':
341 pos = pos - 1
342 hint = None
343 else:
344 hint = ''
345
346 validLetters = string.letters + string.digits + '_.'
347 word = ''
348 while (True):
349 pos = pos - 1
350 if pos < 0:
351 break
352 char = chr(self.GetCtrl().GetCharAt(pos))
353 if char not in validLetters:
354 break
355 word = char + word
356
357 context = word
358 if hint is not None:
359 lastDot = word.rfind('.')
360 if lastDot != -1:
361 context = word[0:lastDot]
362 hint = word[lastDot+1:]
363
364 return context, hint
365
366
367 def GetAutoCompleteDefaultKeywords(self):
368 """ Replace this method with Editor specific keywords """
369 return ['Put', 'Editor Specific', 'Keywords', 'Here']
370
371
372 def GetAutoCompleteKeywordList(self, context, hint):
373 """ Replace this method with Editor specific keywords """
374 kw = self.GetAutoCompleteDefaultKeywords()
375
376 if hint and len(hint):
377 lowerHint = hint.lower()
378 filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw) # remove variables and methods that don't match hint
379 kw = filterkw
380
381 if hint:
382 replaceLen = len(hint)
383 else:
384 replaceLen = 0
385
386 kw.sort(CaseInsensitiveCompare)
387 return " ".join(kw), replaceLen
388
389
390 def OnCleanWhiteSpace(self):
391 newText = ""
392 for lineNo in self._GetSelectedLineNumbers():
393 lineText = string.rstrip(self.GetCtrl().GetLine(lineNo))
394 indent = 0
395 lstrip = 0
396 for char in lineText:
397 if char == '\t':
398 indent = indent + self.GetCtrl().GetIndent()
399 lstrip = lstrip + 1
400 elif char in string.whitespace:
401 indent = indent + 1
402 lstrip = lstrip + 1
403 else:
404 break
405 if self.GetCtrl().GetUseTabs():
406 indentText = (indent / self.GetCtrl().GetIndent()) * '\t' + (indent % self.GetCtrl().GetIndent()) * ' '
407 else:
408 indentText = indent * ' '
409 lineText = indentText + lineText[lstrip:] + '\n'
410 newText = newText + lineText
411 self._ReplaceSelectedLines(newText)
412
413
414 def OnSetIndentWidth(self):
415 dialog = wx.TextEntryDialog(self._GetParentFrame(), _("Enter new indent width (2-10):"), _("Set Indent Width"), "%i" % self.GetCtrl().GetIndent())
416 dialog.CenterOnParent()
417 if dialog.ShowModal() == wx.ID_OK:
418 try:
419 indent = int(dialog.GetValue())
420 if indent >= 2 and indent <= 10:
421 self.GetCtrl().SetIndent(indent)
422 self.GetCtrl().SetTabWidth(indent)
423 except:
424 pass
425 dialog.Destroy()
426
427
428 def GetIndentWidth(self):
429 return self.GetCtrl().GetIndent()
430
431
432 def OnCommentLines(self):
433 newText = ""
434 for lineNo in self._GetSelectedLineNumbers():
435 lineText = self.GetCtrl().GetLine(lineNo)
436 if (len(lineText) > 1 and lineText[0] == '#') or (len(lineText) > 2 and lineText[:2] == '##'):
437 newText = newText + lineText
438 else:
439 newText = newText + "##" + lineText
440 self._ReplaceSelectedLines(newText)
441
442
443 def OnUncommentLines(self):
444 newText = ""
445 for lineNo in self._GetSelectedLineNumbers():
446 lineText = self.GetCtrl().GetLine(lineNo)
447 if len(lineText) >= 2 and lineText[:2] == "##":
448 lineText = lineText[2:]
449 elif len(lineText) >= 1 and lineText[:1] == "#":
450 lineText = lineText[1:]
451 newText = newText + lineText
452 self._ReplaceSelectedLines(newText)
453
454
455 def _GetSelectedLineNumbers(self):
456 selStart, selEnd = self._GetPositionsBoundingSelectedLines()
457 return range(self.GetCtrl().LineFromPosition(selStart), self.GetCtrl().LineFromPosition(selEnd))
458
459
460 def _GetPositionsBoundingSelectedLines(self):
461 startPos = self.GetCtrl().GetCurrentPos()
462 endPos = self.GetCtrl().GetAnchor()
463 if startPos > endPos:
464 temp = endPos
465 endPos = startPos
466 startPos = temp
467 if endPos == self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos)):
468 endPos = endPos - 1 # If it's at the very beginning of a line, use the line above it as the ending line
469 selStart = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(startPos))
470 selEnd = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos) + 1)
471 return selStart, selEnd
472
473
474 def _ReplaceSelectedLines(self, text):
475 if len(text) == 0:
476 return
477 selStart, selEnd = self._GetPositionsBoundingSelectedLines()
478 self.GetCtrl().SetSelection(selStart, selEnd)
479 self.GetCtrl().ReplaceSelection(text)
480 self.GetCtrl().SetSelection(selStart + len(text), selStart)
481
482
483 def OnUpdate(self, sender = None, hint = None):
484 if wx.lib.docview.View.OnUpdate(self, sender, hint):
485 return
486
487 if hint == "ViewStuff":
488 self.GetCtrl().SetViewDefaults()
489 elif hint == "Font":
490 font, color = self.GetCtrl().GetFontAndColorFromConfig()
491 self.GetCtrl().SetFont(font)
492 self.GetCtrl().SetFontColor(color)
493 else:
494 import DebuggerService
495 dbg_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
496 if dbg_service:
497 dbg_service.SetCurrentBreakpointMarkers(self)
498
499
500 class CodeService(STCTextEditor.TextService):
501
502
503 def __init__(self):
504 STCTextEditor.TextService.__init__(self)
505
506
507 def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
508 # TODO NEED TO DO INSTANCEOF CHECK HERE FOR SDI
509 #if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument:
510 # return
511 if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
512 return
513
514 viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
515 isWindows = (wx.Platform == '__WXMSW__')
516
517 if not menuBar.FindItemById(EXPAND_TEXT_ID): # check if below menu items have been already been installed
518 foldingMenu = wx.Menu()
519 if isWindows:
520 foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand\tNumpad-Plus"), _("Expands a collapsed block of text"))
521 else:
522 foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand"), _("Expands a collapsed block of text"))
523
524 wx.EVT_MENU(frame, EXPAND_TEXT_ID, frame.ProcessEvent)
525 wx.EVT_UPDATE_UI(frame, EXPAND_TEXT_ID, frame.ProcessUpdateUIEvent)
526
527 if isWindows:
528 foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse\tNumpad+Minus"), _("Collapse a block of text"))
529 else:
530 foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse"), _("Collapse a block of text"))
531 wx.EVT_MENU(frame, COLLAPSE_TEXT_ID, frame.ProcessEvent)
532 wx.EVT_UPDATE_UI(frame, COLLAPSE_TEXT_ID, frame.ProcessUpdateUIEvent)
533
534 if isWindows:
535 foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level\tCtrl+Numpad+Plus"), _("Expands the top fold levels in the document"))
536 else:
537 foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level"), _("Expands the top fold levels in the document"))
538 wx.EVT_MENU(frame, EXPAND_TOP_ID, frame.ProcessEvent)
539 wx.EVT_UPDATE_UI(frame, EXPAND_TOP_ID, frame.ProcessUpdateUIEvent)
540
541 if isWindows:
542 foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level\tCtrl+Numpad+Minus"), _("Collapses the top fold levels in the document"))
543 else:
544 foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level"), _("Collapses the top fold levels in the document"))
545 wx.EVT_MENU(frame, COLLAPSE_TOP_ID, frame.ProcessEvent)
546 wx.EVT_UPDATE_UI(frame, COLLAPSE_TOP_ID, frame.ProcessUpdateUIEvent)
547
548 if isWindows:
549 foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All\tShift+Numpad+Plus"), _("Expands all of the fold levels in the document"))
550 else:
551 foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All"), _("Expands all of the fold levels in the document"))
552 wx.EVT_MENU(frame, EXPAND_ALL_ID, frame.ProcessEvent)
553 wx.EVT_UPDATE_UI(frame, EXPAND_ALL_ID, frame.ProcessUpdateUIEvent)
554
555 if isWindows:
556 foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All\tShift+Numpad+Minus"), _("Collapses all of the fold levels in the document"))
557 else:
558 foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All"), _("Collapses all of the fold levels in the document"))
559 wx.EVT_MENU(frame, COLLAPSE_ALL_ID, frame.ProcessEvent)
560 wx.EVT_UPDATE_UI(frame, COLLAPSE_ALL_ID, frame.ProcessUpdateUIEvent)
561
562 viewMenu.AppendMenu(FOLDING_ID, _("&Folding"), foldingMenu)
563 wx.EVT_UPDATE_UI(frame, FOLDING_ID, frame.ProcessUpdateUIEvent)
564
565 formatMenuIndex = menuBar.FindMenu(_("&Format"))
566 if formatMenuIndex > -1:
567 formatMenu = menuBar.GetMenu(formatMenuIndex)
568 else:
569 formatMenu = wx.Menu()
570 if not menuBar.FindItemById(CHECK_CODE_ID): # check if below menu items have been already been installed
571 formatMenu.AppendSeparator()
572 formatMenu.Append(CHECK_CODE_ID, _("&Check Code"), _("Checks the document for syntax and indentation errors"))
573 wx.EVT_MENU(frame, CHECK_CODE_ID, frame.ProcessEvent)
574 wx.EVT_UPDATE_UI(frame, CHECK_CODE_ID, frame.ProcessUpdateUIEvent)
575 formatMenu.Append(AUTO_COMPLETE_ID, _("&Auto Complete\tCtrl+Space"), _("Provides suggestions on how to complete the current statement"))
576 wx.EVT_MENU(frame, AUTO_COMPLETE_ID, frame.ProcessEvent)
577 wx.EVT_UPDATE_UI(frame, AUTO_COMPLETE_ID, frame.ProcessUpdateUIEvent)
578 formatMenu.Append(CLEAN_WHITESPACE, _("Clean &Whitespace"), _("Converts leading spaces to tabs or vice versa per 'use tabs' and clears trailing spaces"))
579 wx.EVT_MENU(frame, CLEAN_WHITESPACE, frame.ProcessEvent)
580 wx.EVT_UPDATE_UI(frame, CLEAN_WHITESPACE, frame.ProcessUpdateUIEvent)
581 formatMenu.AppendSeparator()
582 formatMenu.Append(INDENT_LINES_ID, _("&Indent Lines\tTab"), _("Indents the selected lines one indent width"))
583 wx.EVT_MENU(frame, INDENT_LINES_ID, frame.ProcessEvent)
584 wx.EVT_UPDATE_UI(frame, INDENT_LINES_ID, frame.ProcessUpdateUIEvent)
585 formatMenu.Append(DEDENT_LINES_ID, _("&Dedent Lines\tShift+Tab"), _("Dedents the selected lines one indent width"))
586 wx.EVT_MENU(frame, DEDENT_LINES_ID, frame.ProcessEvent)
587 wx.EVT_UPDATE_UI(frame, DEDENT_LINES_ID, frame.ProcessUpdateUIEvent)
588 formatMenu.Append(COMMENT_LINES_ID, _("Comment &Lines\tCtrl+Q"), _("Comments out the selected lines be prefixing each one with a comment indicator"))
589 wx.EVT_MENU(frame, COMMENT_LINES_ID, frame.ProcessEvent)
590 wx.EVT_UPDATE_UI(frame, COMMENT_LINES_ID, frame.ProcessUpdateUIEvent)
591 formatMenu.Append(UNCOMMENT_LINES_ID, _("&Uncomment Lines\tCtrl+Shift+Q"), _("Removes comment prefixes from each of the selected lines"))
592 wx.EVT_MENU(frame, UNCOMMENT_LINES_ID, frame.ProcessEvent)
593 wx.EVT_UPDATE_UI(frame, UNCOMMENT_LINES_ID, frame.ProcessUpdateUIEvent)
594 formatMenu.AppendSeparator()
595 formatMenu.AppendCheckItem(USE_TABS_ID, _("Use &Tabs"), _("Toggles use of tabs or whitespaces for indents"))
596 wx.EVT_MENU(frame, USE_TABS_ID, frame.ProcessEvent)
597 wx.EVT_UPDATE_UI(frame, USE_TABS_ID, frame.ProcessUpdateUIEvent)
598 formatMenu.Append(SET_INDENT_WIDTH_ID, _("&Set Indent Width..."), _("Sets the indent width"))
599 wx.EVT_MENU(frame, SET_INDENT_WIDTH_ID, frame.ProcessEvent)
600 wx.EVT_UPDATE_UI(frame, SET_INDENT_WIDTH_ID, frame.ProcessUpdateUIEvent)
601 if formatMenuIndex == -1:
602 viewMenuIndex = menuBar.FindMenu(_("&View"))
603 menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format"))
604
605 ## accelTable = wx.AcceleratorTable([
606 ## (wx.ACCEL_NORMAL, wx.WXK_TAB, INDENT_LINES_ID),
607 ## (wx.ACCEL_SHIFT, wx.WXK_TAB, DEDENT_LINES_ID),
608 ## eval(_("wx.ACCEL_CTRL, ord('Q'), COMMENT_LINES_ID")),
609 ## eval(_("wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord('Q'), UNCOMMENT_LINES_ID"))
610 ## ])
611 ## frame.SetAcceleratorTable(accelTable)
612
613 def ProcessUpdateUIEvent(self, event):
614 id = event.GetId()
615 if (id == EXPAND_TEXT_ID
616 or id == COLLAPSE_TEXT_ID
617 or id == EXPAND_TOP_ID
618 or id == COLLAPSE_TOP_ID
619 or id == EXPAND_ALL_ID
620 or id == COLLAPSE_ALL_ID
621 or id == CHECK_CODE_ID
622 or id == AUTO_COMPLETE_ID
623 or id == CLEAN_WHITESPACE
624 or id == SET_INDENT_WIDTH_ID
625 or id == USE_TABS_ID
626 or id == INDENT_LINES_ID
627 or id == DEDENT_LINES_ID
628 or id == COMMENT_LINES_ID
629 or id == UNCOMMENT_LINES_ID
630 or id == FOLDING_ID):
631 event.Enable(False)
632 return True
633 else:
634 return STCTextEditor.TextService.ProcessUpdateUIEvent(self, event)
635
636
637 class CodeCtrl(STCTextEditor.TextCtrl):
638 CURRENT_LINE_MARKER_NUM = 2
639 BREAKPOINT_MARKER_NUM = 1
640 CURRENT_LINE_MARKER_MASK = 0x4
641 BREAKPOINT_MARKER_MASK = 0x2
642
643
644 def __init__(self, parent, id=-1, style = wx.NO_FULL_REPAINT_ON_RESIZE, clearTab=True):
645 STCTextEditor.TextCtrl.__init__(self, parent, id, style)
646
647 self.UsePopUp(False)
648 self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
649 self.SetProperty("fold", "1")
650
651 # Setup a margin to hold fold markers
652 #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
653 self.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL)
654 self.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS)
655 self.SetMarginSensitive(2, True)
656
657 self.SetMarginSensitive(1, False)
658 self.SetMarginMask(1, 0x4)
659
660 self.SetMarginSensitive(0, True)
661 self.SetMarginType(0, wx.stc.STC_MARGIN_SYMBOL)
662 self.SetMarginMask(0, 0x3)
663 self.SetMarginWidth(0, 12)
664
665 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUSCONNECTED, "white", "black")
666 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "black")
667 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER, "white", "black")
668 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_LCORNER, "white", "black")
669 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_VLINE, "white", "black")
670 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS, "white", "black")
671 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS, "white", "black")
672 # Define the current line marker
673 self.MarkerDefine(CodeCtrl.CURRENT_LINE_MARKER_NUM, wx.stc.STC_MARK_SHORTARROW, wx.BLACK, (255,255,128))
674 # Define the breakpoint marker
675 self.MarkerDefine(CodeCtrl.BREAKPOINT_MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, (255,0,0))
676
677 if _WINDOWS and clearTab: # should test to see if menu item exists, if it does, add this workaround
678 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
679
680 wx.stc.EVT_STC_MARGINCLICK(self, self.GetId(), self.OnMarginClick)
681 wx.EVT_KEY_DOWN(self, self.OnKeyPressed)
682 if self.GetMatchingBraces():
683 wx.stc.EVT_STC_UPDATEUI(self, self.GetId(), self.OnUpdateUI)
684
685 self.StyleClearAll()
686 self.UpdateStyles()
687
688
689 def OnRightUp(self, event):
690 #Hold onto the current line number, no way to get it later.
691 self._rightClickPosition = self.PositionFromPoint(event.GetPosition())
692 self._rightClickLine = self.LineFromPosition(self._rightClickPosition)
693 self.PopupMenu(self.CreatePopupMenu(), event.GetPosition())
694 self._rightClickLine = -1
695 self._rightClickPosition = -1
696
697
698 def CreatePopupMenu(self):
699 TOGGLEBREAKPOINT_ID = wx.NewId()
700 TOGGLEMARKER_ID = wx.NewId()
701 SYNCTREE_ID = wx.NewId()
702
703 menu = wx.Menu()
704
705 self.Bind(wx.EVT_MENU, self.OnPopSyncOutline, id=SYNCTREE_ID)
706 item = wx.MenuItem(menu, SYNCTREE_ID, _("Find in Outline View"))
707 menu.AppendItem(item)
708 menu.AppendSeparator()
709 self.Bind(wx.EVT_MENU, self.OnPopToggleBP, id=TOGGLEBREAKPOINT_ID)
710 item = wx.MenuItem(menu, TOGGLEBREAKPOINT_ID, _("Toggle Breakpoint"))
711 menu.AppendItem(item)
712 self.Bind(wx.EVT_MENU, self.OnPopToggleMarker, id=TOGGLEMARKER_ID)
713 item = wx.MenuItem(menu, TOGGLEMARKER_ID, _("Toggle Bookmark"))
714 menu.AppendItem(item)
715 menu.AppendSeparator()
716
717 itemIDs = [wx.ID_UNDO, wx.ID_REDO, None,
718 wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL]
719
720 menuBar = wx.GetApp().GetTopWindow().GetMenuBar()
721 for itemID in itemIDs:
722 if not itemID:
723 menu.AppendSeparator()
724 else:
725 item = menuBar.FindItemById(itemID)
726 if item:
727 menu.Append(itemID, item.GetLabel())
728 wx.EVT_MENU(self, itemID, self.DSProcessEvent) # wxHack: for customized right mouse menu doesn't work with new DynamicSashWindow
729 wx.EVT_UPDATE_UI(self, itemID, self.DSProcessUpdateUIEvent) # wxHack: for customized right mouse menu doesn't work with new DynamicSashWindow
730 return menu
731
732
733 def OnPopToggleBP(self, event):
734 """ Toggle break point on right click line, not current line """
735 import DebuggerService
736 wx.GetApp().GetService(DebuggerService.DebuggerService).OnToggleBreakpoint(event, line=self._rightClickLine)
737
738
739 def OnPopToggleMarker(self, event):
740 """ Toggle marker on right click line, not current line """
741 wx.GetApp().GetDocumentManager().GetCurrentView().MarkerToggle(lineNum = self._rightClickLine)
742
743
744 def OnPopSyncOutline(self, event):
745 wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(wx.GetApp().GetDocumentManager().GetCurrentView(), position=self._rightClickPosition)
746
747
748 def HasSelection(self):
749 return self.GetSelectionStart() - self.GetSelectionEnd() != 0
750
751
752 def ClearCurrentLineMarkers(self):
753 self.MarkerDeleteAll(CodeCtrl.CURRENT_LINE_MARKER_NUM)
754
755
756 def ClearCurrentBreakpoinMarkers(self):
757 self.MarkerDeleteAll(CodeCtrl.BREAKPOINT_MARKER_NUM)
758
759
760 def GetDefaultFont(self):
761 if wx.Platform == '__WXMSW__':
762 font = "Courier New"
763 else:
764 font = "Courier"
765 return wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font)
766
767
768 def GetMatchingBraces(self):
769 """ Overwrite this method for language specific braces """
770 return "[]{}()"
771
772
773 def CanWordWrap(self):
774 return False
775
776
777 def SetFont(self, font):
778 self._font = font
779
780
781 def SetFontColor(self, fontColor):
782 self._fontColor = fontColor
783
784
785 def UpdateStyles(self):
786
787 if not self.GetFont():
788 return
789
790 faces = { 'font' : self.GetFont().GetFaceName(),
791 'size' : self.GetFont().GetPointSize(),
792 'size2': self.GetFont().GetPointSize() - 2,
793 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
794 }
795
796 # Global default styles for all languages
797 self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(font)s,fore:#FFFFFF,size:%(size)d" % faces)
798 self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, "face:%(font)s,back:#C0C0C0,face:%(font)s,size:%(size2)d" % faces)
799 self.StyleSetSpec(wx.stc.STC_STYLE_CONTROLCHAR, "face:%(font)s" % faces)
800 self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT, "face:%(font)s,fore:#000000,back:#70FFFF,size:%(size)d" % faces)
801 self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD, "face:%(font)s,fore:#000000,back:#FF0000,size:%(size)d" % faces)
802
803
804 def OnKeyPressed(self, event):
805 if self.CallTipActive():
806 self.CallTipCancel()
807 key = event.KeyCode()
808 if False: # key == wx.WXK_SPACE and event.ControlDown():
809 pos = self.GetCurrentPos()
810 # Tips
811 if event.ShiftDown():
812 self.CallTipSetBackground("yellow")
813 self.CallTipShow(pos, 'param1, param2')
814 # Code completion
815 else:
816 #lst = []
817 #for x in range(50000):
818 # lst.append('%05d' % x)
819 #st = string.join(lst)
820 #print len(st)
821 #self.AutoCompShow(0, st)
822
823 kw = keyword.kwlist[:]
824 kw.append("zzzzzz")
825 kw.append("aaaaa")
826 kw.append("__init__")
827 kw.append("zzaaaaa")
828 kw.append("zzbaaaa")
829 kw.append("this_is_a_longer_value")
830 kw.append("this_is_a_much_much_much_much_much_much_much_longer_value")
831
832 kw.sort() # Python sorts are case sensitive
833 self.AutoCompSetIgnoreCase(False) # so this needs to match
834
835 self.AutoCompShow(0, string.join(kw))
836 elif key == wx.WXK_RETURN:
837 self.DoIndent()
838 else:
839 STCTextEditor.TextCtrl.OnKeyPressed(self, event)
840
841
842 def DoIndent(self):
843 self.AddText('\n')
844 self.EnsureCaretVisible()
845 # Need to do a default one for all languges
846
847
848 def OnMarginClick(self, evt):
849 # fold and unfold as needed
850 if evt.GetMargin() == 2:
851 if evt.GetShift() and evt.GetControl():
852 lineCount = self.GetLineCount()
853 expanding = True
854
855 # find out if we are folding or unfolding
856 for lineNum in range(lineCount):
857 if self.GetFoldLevel(lineNum) & wx.stc.STC_FOLDLEVELHEADERFLAG:
858 expanding = not self.GetFoldExpanded(lineNum)
859 break;
860
861 self.ToggleFoldAll(expanding)
862 else:
863 lineClicked = self.LineFromPosition(evt.GetPosition())
864 if self.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG:
865 if evt.GetShift():
866 self.SetFoldExpanded(lineClicked, True)
867 self.Expand(lineClicked, True, True, 1)
868 elif evt.GetControl():
869 if self.GetFoldExpanded(lineClicked):
870 self.SetFoldExpanded(lineClicked, False)
871 self.Expand(lineClicked, False, True, 0)
872 else:
873 self.SetFoldExpanded(lineClicked, True)
874 self.Expand(lineClicked, True, True, 100)
875 else:
876 self.ToggleFold(lineClicked)
877
878 elif evt.GetMargin() == 0:
879 #This is used to toggle breakpoints via the debugger service.
880 import DebuggerService
881 db_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
882 if db_service:
883 db_service.OnToggleBreakpoint(evt, line=self.LineFromPosition(evt.GetPosition()))
884
885
886 def OnUpdateUI(self, evt):
887 braces = self.GetMatchingBraces()
888
889 # check for matching braces
890 braceAtCaret = -1
891 braceOpposite = -1
892 charBefore = None
893 caretPos = self.GetCurrentPos()
894 if caretPos > 0:
895 charBefore = self.GetCharAt(caretPos - 1)
896 styleBefore = self.GetStyleAt(caretPos - 1)
897
898 # check before
899 if charBefore and chr(charBefore) in braces:
900 braceAtCaret = caretPos - 1
901
902 # check after
903 if braceAtCaret < 0:
904 charAfter = self.GetCharAt(caretPos)
905 styleAfter = self.GetStyleAt(caretPos)
906 if charAfter and chr(charAfter) in braces:
907 braceAtCaret = caretPos
908
909 if braceAtCaret >= 0:
910 braceOpposite = self.BraceMatch(braceAtCaret)
911
912 if braceAtCaret != -1 and braceOpposite == -1:
913 self.BraceBadLight(braceAtCaret)
914 else:
915 self.BraceHighlight(braceAtCaret, braceOpposite)
916
917 evt.Skip()
918
919
920 def ToggleFoldAll(self, expand = True, topLevelOnly = False):
921 i = 0
922 lineCount = self.GetLineCount()
923 while i < lineCount:
924 if not topLevelOnly or (topLevelOnly and self.GetFoldLevel(i) & wx.stc.STC_FOLDLEVELNUMBERMASK == wx.stc.STC_FOLDLEVELBASE):
925 if (expand and self.CanLineExpand(i)) or (not expand and self.CanLineCollapse(i)):
926 self.ToggleFold(i)
927 i = i + 1
928
929
930 def CanLineExpand(self, line):
931 return not self.GetFoldExpanded(line)
932
933
934 def CanLineCollapse(self, line):
935 return self.GetFoldExpanded(line) and self.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG
936
937
938 def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
939 lastChild = self.GetLastChild(line, level)
940 line = line + 1
941 while line <= lastChild:
942 if force:
943 if visLevels > 0:
944 self.ShowLines(line, line)
945 else:
946 self.HideLines(line, line)
947 else:
948 if doExpand:
949 self.ShowLines(line, line)
950
951 if level == -1:
952 level = self.GetFoldLevel(line)
953
954 if level & wx.stc.STC_FOLDLEVELHEADERFLAG:
955 if force:
956 if visLevels > 1:
957 self.SetFoldExpanded(line, True)
958 else:
959 self.SetFoldExpanded(line, False)
960 line = self.Expand(line, doExpand, force, visLevels-1)
961
962 else:
963 if doExpand and self.GetFoldExpanded(line):
964 line = self.Expand(line, True, force, visLevels-1)
965 else:
966 line = self.Expand(line, False, force, visLevels-1)
967 else:
968 line = line + 1;
969
970 return line
971
972