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