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