]> git.saurik.com Git - wxWidgets.git/blame_incremental - wxPython/samples/ide/activegrid/tool/CodeEditor.py
Docview and IDE patch from Morag Hua with fix for bug #1217890
[wxWidgets.git] / wxPython / samples / ide / activegrid / tool / CodeEditor.py
... / ...
CommitLineData
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
14import STCTextEditor
15import wx
16import wx.lib.docview
17import OutlineService
18import os
19import re
20import string
21import sys
22import DebuggerService
23import MarkerService
24_ = wx.GetTranslation
25if wx.Platform == '__WXMSW__':
26 _WINDOWS = True
27else:
28 _WINDOWS = False
29
30
31EXPAND_TEXT_ID = wx.NewId()
32COLLAPSE_TEXT_ID = wx.NewId()
33EXPAND_TOP_ID = wx.NewId()
34COLLAPSE_TOP_ID = wx.NewId()
35EXPAND_ALL_ID = wx.NewId()
36COLLAPSE_ALL_ID = wx.NewId()
37CHECK_CODE_ID = wx.NewId()
38AUTO_COMPLETE_ID = wx.NewId()
39CLEAN_WHITESPACE = wx.NewId()
40COMMENT_LINES_ID = wx.NewId()
41UNCOMMENT_LINES_ID = wx.NewId()
42INDENT_LINES_ID = wx.NewId()
43DEDENT_LINES_ID = wx.NewId()
44USE_TABS_ID = wx.NewId()
45SET_INDENT_WIDTH_ID = wx.NewId()
46FOLDING_ID = wx.NewId()
47
48
49class CodeDocument(STCTextEditor.TextDocument):
50 pass
51
52
53class 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
492class 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
629class 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 STCTextEditor.TextCtrl.__init__(self, parent, id, style)
638
639 self.UsePopUp(False)
640 self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
641 self.SetProperty("fold", "1")
642
643 # Setup a margin to hold fold markers
644 #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
645 self.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL)
646 self.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS)
647 self.SetMarginSensitive(2, True)
648 self.SetMarginWidth(2, 12)
649
650 self.SetMarginSensitive(1, False)
651 self.SetMarginMask(1, 0x4)
652
653 self.SetMarginSensitive(0, True)
654 self.SetMarginType(0, wx.stc.STC_MARGIN_SYMBOL)
655 self.SetMarginMask(0, 0x3)
656 self.SetMarginWidth(0, 12)
657
658 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUSCONNECTED, "white", "black")
659 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "black")
660 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER, "white", "black")
661 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_LCORNER, "white", "black")
662 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_VLINE, "white", "black")
663 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS, "white", "black")
664 self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS, "white", "black")
665 # Define the current line marker
666 self.MarkerDefine(CodeCtrl.CURRENT_LINE_MARKER_NUM, wx.stc.STC_MARK_SHORTARROW, wx.BLACK, (255,255,128))
667 # Define the breakpoint marker
668 self.MarkerDefine(CodeCtrl.BREAKPOINT_MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, (255,0,0))
669
670 if _WINDOWS: # should test to see if menu item exists, if it does, add this workaround
671 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
672
673 wx.stc.EVT_STC_MARGINCLICK(self, self.GetId(), self.OnMarginClick)
674 wx.EVT_KEY_DOWN(self, self.OnKeyPressed)
675 if self.GetMatchingBraces():
676 wx.stc.EVT_STC_UPDATEUI(self, self.GetId(), self.OnUpdateUI)
677
678 self.StyleClearAll()
679 self.UpdateStyles()
680
681
682 def OnRightUp(self, event):
683 #Hold onto the current line number, no way to get it later.
684 self._rightClickPosition = self.PositionFromPoint(event.GetPosition())
685 self._rightClickLine = self.LineFromPosition(self._rightClickPosition)
686 self.PopupMenu(self.CreatePopupMenu(), event.GetPosition())
687 self._rightClickLine = -1
688 self._rightClickPosition = -1
689
690
691 def CreatePopupMenu(self):
692 TOGGLEBREAKPOINT_ID = wx.NewId()
693 TOGGLEMARKER_ID = wx.NewId()
694 SYNCTREE_ID = wx.NewId()
695
696 menu = wx.Menu()
697
698 self.Bind(wx.EVT_MENU, self.OnPopSyncOutline, id=SYNCTREE_ID)
699 item = wx.MenuItem(menu, SYNCTREE_ID, _("Find in Outline View"))
700 menu.AppendItem(item)
701 menu.AppendSeparator()
702 self.Bind(wx.EVT_MENU, self.OnPopToggleBP, id=TOGGLEBREAKPOINT_ID)
703 item = wx.MenuItem(menu, TOGGLEBREAKPOINT_ID, _("Toggle Breakpoint"))
704 menu.AppendItem(item)
705 self.Bind(wx.EVT_MENU, self.OnPopToggleMarker, id=TOGGLEMARKER_ID)
706 item = wx.MenuItem(menu, TOGGLEMARKER_ID, _("Toggle Marker"))
707 menu.AppendItem(item)
708 menu.AppendSeparator()
709
710 itemIDs = [wx.ID_UNDO, wx.ID_REDO, None,
711 wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL]
712
713 menuBar = wx.GetApp().GetTopWindow().GetMenuBar()
714 for itemID in itemIDs:
715 if not itemID:
716 menu.AppendSeparator()
717 else:
718 item = menuBar.FindItemById(itemID)
719 if item:
720 menu.Append(itemID, item.GetLabel())
721 wx.EVT_MENU(self, itemID, self.DSProcessEvent) # wxHack: for customized right mouse menu doesn't work with new DynamicSashWindow
722 wx.EVT_UPDATE_UI(self, itemID, self.DSProcessUpdateUIEvent) # wxHack: for customized right mouse menu doesn't work with new DynamicSashWindow
723 return menu
724
725
726 def OnPopToggleBP(self, event):
727 """ Toggle break point on right click line, not current line """
728 wx.GetApp().GetService(DebuggerService.DebuggerService).OnToggleBreakpoint(event, line=self._rightClickLine)
729
730
731 def OnPopToggleMarker(self, event):
732 """ Toggle marker on right click line, not current line """
733 wx.GetApp().GetDocumentManager().GetCurrentView().MarkerToggle(lineNum = self._rightClickLine)
734
735
736 def OnPopSyncOutline(self, event):
737 wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(wx.GetApp().GetDocumentManager().GetCurrentView(), position=self._rightClickPosition)
738
739
740 def HasSelection(self):
741 return self.GetSelectionStart() - self.GetSelectionEnd() != 0
742
743
744 def ClearCurrentLineMarkers(self):
745 self.MarkerDeleteAll(CodeCtrl.CURRENT_LINE_MARKER_NUM)
746
747
748 def ClearCurrentBreakpoinMarkers(self):
749 self.MarkerDeleteAll(CodeCtrl.BREAKPOINT_MARKER_NUM)
750
751
752 def GetDefaultFont(self):
753 if wx.Platform == '__WXMSW__':
754 font = "Courier New"
755 else:
756 font = "Courier"
757 return wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font)
758
759
760 def GetMatchingBraces(self):
761 """ Overwrite this method for language specific braces """
762 return "[]{}()"
763
764
765 def CanWordWrap(self):
766 return False
767
768
769 def SetFont(self, font):
770 self._font = font
771
772
773 def SetFontColor(self, fontColor):
774 self._fontColor = fontColor
775
776
777 def UpdateStyles(self):
778
779 if not self.GetFont():
780 return
781
782 faces = { 'font' : self.GetFont().GetFaceName(),
783 'size' : self.GetFont().GetPointSize(),
784 'size2': self.GetFont().GetPointSize() - 2,
785 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
786 }
787
788 # Global default styles for all languages
789 self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(font)s,fore:#FFFFFF,size:%(size)d" % faces)
790 self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, "face:%(font)s,back:#C0C0C0,face:%(font)s,size:%(size2)d" % faces)
791 self.StyleSetSpec(wx.stc.STC_STYLE_CONTROLCHAR, "face:%(font)s" % faces)
792 self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT, "face:%(font)s,fore:#000000,back:#70FFFF,size:%(size)d" % faces)
793 self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD, "face:%(font)s,fore:#000000,back:#FF0000,size:%(size)d" % faces)
794
795
796 def OnKeyPressed(self, event):
797 if self.CallTipActive():
798 self.CallTipCancel()
799 key = event.KeyCode()
800 if False: # key == wx.WXK_SPACE and event.ControlDown():
801 pos = self.GetCurrentPos()
802 # Tips
803 if event.ShiftDown():
804 self.CallTipSetBackground("yellow")
805 self.CallTipShow(pos, 'param1, param2')
806 # Code completion
807 else:
808 #lst = []
809 #for x in range(50000):
810 # lst.append('%05d' % x)
811 #st = string.join(lst)
812 #print len(st)
813 #self.AutoCompShow(0, st)
814
815 kw = keyword.kwlist[:]
816 kw.append("zzzzzz")
817 kw.append("aaaaa")
818 kw.append("__init__")
819 kw.append("zzaaaaa")
820 kw.append("zzbaaaa")
821 kw.append("this_is_a_longer_value")
822 kw.append("this_is_a_much_much_much_much_much_much_much_longer_value")
823
824 kw.sort() # Python sorts are case sensitive
825 self.AutoCompSetIgnoreCase(False) # so this needs to match
826
827 self.AutoCompShow(0, string.join(kw))
828 elif key == wx.WXK_RETURN:
829 self.DoIndent()
830 else:
831 STCTextEditor.TextCtrl.OnKeyPressed(self, event)
832
833
834 def DoIndent(self):
835 self.AddText('\n')
836 self.EnsureCaretVisible()
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