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