1
###############################################################################
2
# Name: ed_basestc.py #
3
# Purpose: Editra's base StyledTextCtrl. #
4
# Author: Cody Precord <cprecord@editra.org> #
5
# Copyright: (c) 2009 Cody Precord <staff@editra.org> #
6
# License: wxWindows License #
7
###############################################################################
10
The EditraBaseStc is the base StyledTextCtrl that provides automatic styling and
11
syntax highlighting of all supported filetypes.
13
@summary: Editra's base styled text ctrl.
17
__author__ = "Cody Precord <cprecord@editra.org>"
18
__svnid__ = "$Id: ed_basestc.py 72105 2012-07-15 15:12:05Z CJP $"
19
__revision__ = "$Revision: 72105 $"
21
#-----------------------------------------------------------------------------#
33
from syntax import syntax
34
from syntax import synglob
36
from extern import vertedit
37
from profiler import Profile_Get
43
#-----------------------------------------------------------------------------#
51
MARKER_VERT_EDIT = ed_marker.NewMarkerId()
54
ALT_SHIFT = wx.stc.STC_SCMOD_ALT|wx.stc.STC_SCMOD_SHIFT
55
CTRL_SHIFT = wx.stc.STC_SCMOD_CTRL|wx.stc.STC_SCMOD_SHIFT
57
#-----------------------------------------------------------------------------#
59
class EditraBaseStc(wx.stc.StyledTextCtrl, ed_style.StyleMgr):
60
"""Base StyledTextCtrl that provides all the base code editing
64
ED_STC_MASK_MARKERS = ~wx.stc.STC_MASK_FOLDERS
65
def __init__(self, parent, id_=wx.ID_ANY,
66
pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
67
wx.stc.StyledTextCtrl.__init__(self, parent, id_, pos, size, style)
68
ed_style.StyleMgr.__init__(self, self.GetStyleSheet())
71
self.file = ed_txt.EdFile()
72
self._code = dict(compsvc=autocomp.AutoCompService.GetCompleter(self),
73
synmgr=syntax.SyntaxMgr(ed_glob.CONFIG['CACHE_DIR']),
76
clexer=None, # Container lexer method
77
indenter=None, # Auto indenter
78
lang_id=0) # Language ID from syntax module
80
self.vert_edit = vertedit.VertEdit(self, markerNumber=MARKER_VERT_EDIT)
81
self._line_num = True # Show line numbers
82
self._last_cwidth = 1 # one pixel
85
## Outer Left Margin Bookmarks
86
self.SetMarginType(MARK_MARGIN, wx.stc.STC_MARGIN_SYMBOL)
87
self.SetMarginMask(MARK_MARGIN, EditraBaseStc.ED_STC_MASK_MARKERS)
88
self.SetMarginSensitive(MARK_MARGIN, True)
89
self.SetMarginWidth(MARK_MARGIN, 16)
91
## Middle Left Margin Line Number Indication
92
self.SetMarginType(NUM_MARGIN, wx.stc.STC_MARGIN_NUMBER)
93
self.SetMarginMask(NUM_MARGIN, 0)
95
## Inner Left Margin Setup Folders
96
self.SetMarginType(FOLD_MARGIN, wx.stc.STC_MARGIN_SYMBOL)
97
self.SetMarginMask(FOLD_MARGIN, wx.stc.STC_MASK_FOLDERS)
98
self.SetMarginSensitive(FOLD_MARGIN, True)
100
# Set Mac specific keybindings
101
if wx.Platform == '__WXMAC__':
102
for keys in _GetMacKeyBindings():
103
self.CmdKeyAssign(*keys)
105
# Set default EOL format
106
if wx.Platform != '__WXMSW__':
107
self.SetEOLMode(wx.stc.STC_EOL_LF)
109
# Setup Auto-comp images
110
# TODO: should be called on theme change messages
111
self.RegisterImages()
114
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self)
115
self.Bind(wx.stc.EVT_STC_CHANGE, self.OnChanged)
116
self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModified)
117
self.Bind(wx.stc.EVT_STC_AUTOCOMP_SELECTION, self.OnAutoCompSel)
119
def OnDestroy(self, evt):
120
if evt.GetId() == self.GetId():
121
# Cleanup the file object callbacks
122
self.file.RemoveModifiedCallback(self.FireModified)
126
#---- Public Methods ----#
129
def AddMarker(self, marker, line=-1):
130
"""Add a bookmark and return its handle
131
@param marker: ed_marker.Marker instance
132
@keyword line: if < 0 bookmark will be added to current line
135
assert isinstance(marker, ed_marker.Marker)
137
line = self.GetCurrentLine()
138
marker.Set(self, line)
141
def RemoveMarker(self, marker, line):
142
"""Remove the book mark from the given line
143
@param marker: ed_marker.Marker instance
147
assert isinstance(marker, ed_marker.Marker)
148
marker.Set(self, line, delete=True)
150
def RemoveAllMarkers(self, marker):
151
"""Remove all the bookmarks in the buffer
152
@param marker: ed_marker.Marker instance
155
assert isinstance(marker, ed_marker.Marker)
156
marker.DeleteAll(self)
158
#-- Breakpoint marker api --#
159
def DeleteAllBreakpoints(self):
160
"""Delete all the breakpoints in the buffer"""
161
ed_marker.Breakpoint().DeleteAll(self)
162
ed_marker.BreakpointDisabled().DeleteAll(self)
163
ed_marker.BreakpointStep().DeleteAll(self)
165
def DeleteBreakpoint(self, line):
166
"""Delete the breakpoint from the given line"""
167
ed_marker.Breakpoint().Set(self, line, delete=True)
168
ed_marker.BreakpointDisabled().Set(self, line, delete=True)
170
def _SetBreakpoint(self, mobj, line=-1):
171
"""Set the breakpoint state
172
@param mtype: Marker object
173
@return: int (-1 if already set)
178
line = self.GetCurrentLine()
179
if not mobj.IsSet(self, line):
180
# Clear other set breakpoint marker states on same line
181
ed_marker.Breakpoint().Set(self, line, delete=True)
182
ed_marker.BreakpointDisabled().Set(self, line, delete=True)
183
mobj.Set(self, line, delete=False)
187
def SetBreakpoint(self, line=-1, disabled=False):
188
"""Set a breakpoint marker on the given line
189
@keyword line: line number
190
@keyword disabled: bool
191
@return: breakpoint handle
195
handle = self._SetBreakpoint(ed_marker.Breakpoint(), line)
197
handle = self._SetBreakpoint(ed_marker.BreakpointDisabled(), line)
200
def ShowStepMarker(self, line=-1, show=True):
201
"""Show the step (arrow) marker to the given line."""
203
line = self.GetCurrentLine()
204
mark = ed_marker.BreakpointStep()
206
mark.Set(self, line, delete=False)
210
def AddLine(self, before=False, indent=False):
211
"""Add a new line to the document
212
@keyword before: whether to add the line before current pos or not
213
@keyword indent: autoindent the new line
214
@postcondition: a new line is added to the document
225
self.InsertText(self.GetCurrentPos(), self.GetEOLChar())
228
def AutoIndent(self):
229
"""Indent from the current position to match the indentation
230
of the previous line. Unless the current file type has registered
231
a custom AutoIndenter in which case it will implement its own
235
cpos = self.GetCurrentPos()
237
# Check if a special purpose indenter has been registered
238
if self._code['indenter'] is not None:
239
self.BeginUndoAction()
240
self._code['indenter'](self, cpos, self.GetIndentChar())
244
line = self.GetCurrentLine()
245
text = self.GetTextRange(self.PositionFromLine(line), cpos)
246
if text.strip() == u'':
247
self.AddText(self.GetEOLChar() + text)
248
self.EnsureCaretVisible()
250
indent = self.GetLineIndentation(line)
251
i_space = indent / self.GetTabWidth()
252
ndent = self.GetEOLChar() + self.GetIndentChar() * i_space
253
txt = ndent + ((indent - (self.GetTabWidth() * i_space)) * u' ')
256
self.EnsureCaretVisible()
259
"""Unindent or remove excess whitespace to left of cursor"""
260
sel = self.GetSelection()
262
# There is no selection
263
cpos = self.GetCurrentPos()
264
cline = self.GetCurrentLine()
265
cipos = self.GetLineIndentPosition(cline)
267
# In indentation so simply backtab
268
super(EditraBaseStc, self).BackTab()
270
# In middle of line somewhere
271
text = self.GetLine(cline)
272
column = max(0, self.GetColumn(cpos) - 1)
273
if len(text) > column and text[column].isspace():
275
# Find the end of the whitespace
277
while end < len(text) and \
278
text[end].isspace() and \
279
text[end] not in '\r\n':
282
# Find the start of the whitespace
285
while end > 0 and text[start].isspace():
290
# There is space to compress
291
isize = self.GetIndent()
293
# More space than indent to remove
296
# Less than one indent width to remove
297
repeat = end - (start + 1)
300
self.BeginUndoAction()
301
self.SetCurrentPos(cpos + (end - column))
302
for x in range(repeat):
307
# There is a selection
308
super(EditraBaseStc, self).BackTab()
310
def SetBlockCaret(self):
311
"""Change caret style to block"""
312
if hasattr(self, 'SetCaretStyle'): # wxPython 2.9 or greater
313
self.SetCaretStyle(wx.stc.STC_CARETSTYLE_BLOCK)
315
# Alternatively, just make the caret a bit thicker!
316
# best we can do on 2.8
317
self.SetCaretWidth(3)
319
def SetLineCaret(self):
320
"""Change caret style to line"""
321
if hasattr(self, 'SetCaretStyle'):
322
self.SetCaretStyle(wx.stc.STC_CARETSTYLE_LINE)
324
pwidth = Profile_Get('CARETWIDTH', default=1)
325
self.SetCaretWidth(pwidth)
327
def BraceBadLight(self, pos):
328
"""Highlight the character at the given position
329
@param pos: position of character to highlight with STC_STYLE_BRACEBAD
332
# Check if we are still alive or not, as this may be called
333
# after we have been deleted.
335
super(EditraBaseStc, self).BraceBadLight(pos)
337
def BraceHighlight(self, pos1, pos2):
338
"""Highlight characters at pos1 and pos2
339
@param pos1: position of char 1
340
@param pos2: position of char 2
343
# Check if we are still alive or not, as this may be called
344
# after we have been deleted.
346
super(EditraBaseStc, self).BraceHighlight(pos1, pos2)
349
"""Check if copy/cut is possible"""
350
return self.HasSelection()
354
def Comment(self, start, end, uncomment=False):
355
"""(Un)Comments a line or a selected block of text
357
@param start: beginning line (int)
358
@param end: end line (int)
359
@keyword uncomment: uncomment selection
362
if len(self._code['comment']):
363
sel = self.GetSelection()
364
c_start = self._code['comment'][0]
366
if len(self._code['comment']) > 1:
367
c_end = self._code['comment'][1]
369
# Modify the selected line(s)
370
self.BeginUndoAction()
373
lines = range(start, end+1)
375
for line_num in lines:
376
lstart = self.PositionFromLine(line_num)
377
lend = self.GetLineEndPosition(line_num)
378
text = self.GetTextRange(lstart, lend)
382
if tmp.startswith(c_start):
383
text = text.replace(c_start, u'', 1)
384
if c_end and tmp.endswith(c_end):
385
text = text.replace(c_end, u'', 1)
386
nchars = nchars - len(c_start + c_end)
388
text = c_start + text + c_end
389
nchars = nchars + len(c_start + c_end)
391
self.SetTargetStart(lstart)
392
self.SetTargetEnd(lend)
393
self.ReplaceTarget(text)
397
self.SetSelection(sel[0], sel[1] + nchars)
399
if len(self._code['comment']) > 1:
400
nchars = nchars - len(self._code['comment'][1])
401
self.GotoPos(sel[0] + nchars)
403
def ConfigureAutoComp(self):
404
"""Sets up the Autocompleter, the autocompleter
405
configuration depends on the currently set lexer
406
@postcondition: autocomp is configured
409
self.AutoCompSetAutoHide(False)
411
self.AutoCompSetChooseSingle(self._code['compsvc'].GetChooseSingle())
412
self.AutoCompSetIgnoreCase(not self._code['compsvc'].GetCaseSensitive())
413
self.AutoCompStops(self._code['compsvc'].GetAutoCompStops())
414
# TODO: come back to this it can cause some annoying behavior where
415
# it automatically completes strings that you don't want to be
416
# inserted in the buffer. (i.e typing self._value will bring up
417
# the autocomp list but if self._value is not in the list and you
418
# hit space it will automatically insert something from the list.)
419
# self.AutoCompSetFillUps(self._code['compsvc'].GetAutoCompFillups())
421
def ConfigureLexer(self, file_ext):
422
"""Sets Lexer and Lexer Keywords for the specified file extension
423
@param file_ext: a file extension to configure the lexer from
426
syn_data = self._code['synmgr'].GetSyntaxData(file_ext)
428
# Set the ID of the selected lexer
429
self._code['lang_id'] = syn_data.LangId
431
lexer = syn_data.Lexer
432
# Check for special cases
433
# TODO: add fetch method to check if container lexer requires extra
434
# style bytes beyond the default 5.
435
if lexer in [ wx.stc.STC_LEX_HTML, wx.stc.STC_LEX_XML]:
437
elif lexer == wx.stc.STC_LEX_NULL:
440
self.ClearDocumentStyle()
441
self.UpdateBaseStyles()
449
self.SetKeyWords(syn_data.Keywords)
450
# Set Lexer/Syntax Specifications
451
self.SetSyntax(syn_data.SyntaxSpec)
452
# Set Extra Properties
453
self.SetProperties(syn_data.Properties)
454
# Set Comment Pattern
455
self._code['comment'] = syn_data.CommentPattern
457
# Get Extension Features
458
clexer = syn_data.GetFeature(synglob.FEATURE_STYLETEXT)
459
indenter = syn_data.GetFeature(synglob.FEATURE_AUTOINDENT)
461
# Set the Container Lexer Method
462
self._code['clexer'] = clexer
463
# Auto-indenter function
464
self._code['indenter'] = indenter
466
def DefineMarkers(self):
467
"""Defines the folder and bookmark icons for this control
468
@postcondition: all margin markers are defined
471
# Get the colours for the various markers
472
style = self.GetItemByName('foldmargin_style')
473
back = style.GetFore()
474
rgb = eclib.HexToRGB(back[1:])
475
back = wx.Colour(red=rgb[0], green=rgb[1], blue=rgb[2])
477
fore = style.GetBack()
478
rgb = eclib.HexToRGB(fore[1:])
479
fore = wx.Colour(red=rgb[0], green=rgb[1], blue=rgb[2])
481
# Buffer background highlight
482
caret_line = self.GetItemByName('caret_line').GetBack()
483
rgb = eclib.HexToRGB(caret_line[1:])
484
clback = wx.Colour(*rgb)
486
# Code Folding markers
487
folder = ed_marker.FoldMarker()
488
folder.Foreground = fore
489
folder.Background = back
490
folder.RegisterWithStc(self)
493
ed_marker.Bookmark().RegisterWithStc(self)
496
ed_marker.Breakpoint().RegisterWithStc(self)
497
ed_marker.BreakpointDisabled().RegisterWithStc(self)
498
step = ed_marker.BreakpointStep()
499
step.Background = clback
500
step.RegisterWithStc(self)
501
ed_marker.StackMarker().RegisterWithStc(self)
504
errmk = ed_marker.ErrorMarker()
505
errsty = self.GetItemByName('error_style')
506
rgb = eclib.HexToRGB(errsty.GetBack()[1:])
507
errmk.Background = wx.Colour(*rgb)
508
rgb = eclib.HexToRGB(errsty.GetFore()[1:])
509
errmk.Foreground = wx.Colour(*rgb)
510
errmk.RegisterWithStc(self)
512
ed_marker.LintMarker().RegisterWithStc(self)
513
ed_marker.LintMarkerWarning().RegisterWithStc(self)
514
ed_marker.LintMarkerError().RegisterWithStc(self)
516
def DoZoom(self, mode):
517
"""Zoom control in or out
518
@param mode: either zoom in or out
522
zoomlevel = self.GetZoom()
523
if id_type == ed_glob.ID_ZOOM_OUT:
526
elif id_type == ed_glob.ID_ZOOM_IN:
531
return self.GetZoom()
533
def EnableLineNumbers(self, enable=True):
534
"""Enable/Disable line number margin
535
@keyword enable: bool
539
self.SetMarginWidth(NUM_MARGIN, 30)
541
self.SetMarginWidth(NUM_MARGIN, 0)
542
self._line_num = enable
544
def FindChar(self, char, repeat=1, reverse=False, extra_offset=0):
545
"""Find the position of the next (ith) 'char' character
546
on the current line and move caret to it
548
@note: used by vim motions for finding a character on a line (f,F,t,T)
549
@param char: the character to be found
550
@keyword repeat: how many times to repeat the search
551
@keyword reverse: whether to search backwards
552
@keyword extra_offset: extra offset to be applied to the movement
555
text, pos = self.GetCurLine()
559
for i in range(repeat):
560
pos = text.find(char, pos+1)
565
for i in range(repeat):
566
pos = text.rfind(char, 0, pos)
570
newpos = pos + extra_offset
571
if newpos in range(len(text)):
572
self.MoveCaretPos(newpos - oldpos)
576
"""Reference to this buffers file object"""
579
def FindLexer(self, set_ext=u''):
580
"""Sets Text Controls Lexer Based on File Extension
581
@param set_ext: explicit extension to use in search
582
@postcondition: lexer is configured for file
586
ext = set_ext.lower()
588
ext = self.file.GetExtension().lower()
591
fname = self.GetFileName()
592
ext = ebmlib.GetFileName(fname).lower()
594
self.ClearDocumentStyle()
596
# Configure Lexer from File Extension
597
self.ConfigureLexer(ext)
599
# If syntax auto detection fails from file extension try to
600
# see if there is an interpreter line that can be parsed.
601
if self.GetLexer() == wx.stc.STC_LEX_NULL:
602
interp = self.GetLine(0)
603
if interp != wx.EmptyString:
604
interp = interp.split(u"/")[-1]
605
interp = interp.strip().split()
606
if len(interp) and interp[-1][0] != u"-":
612
# TODO: should check user config to ensure the explict
613
# extension is still associated with the expected
615
ex_map = { "python" : "py", "wish" : "tcl", "ruby" : "rb",
616
"bash" : "sh", "csh" : "csh", "perl" : "pl",
617
"ksh" : "ksh", "php" : "php", "booi" : "boo",
619
self.ConfigureLexer(ex_map.get(interp, interp))
620
self.Colourise(0, -1)
622
def FireModified(self):
623
"""Fire a modified event"""
624
self.OnChanged(wx.stc.StyledTextEvent(wx.stc.wxEVT_STC_CHANGE,
627
def GetCommandStr(self, line=None, col=None):
628
"""Gets the command string to the left of the autocomp
629
activation character.
630
@keyword line: optional if None current cursor position used
631
@keyword col: optional if None current cursor position used
632
@return: the command string to the left of the autocomp char
635
if None in (line, col):
636
# NOTE: the column position returned by GetCurLine is not correct
637
# for multibyte characters.
638
line, col = self.GetCurLine()
639
col = self.GetColumn(self.GetCurrentPos())
640
cmd = self._code['compsvc'].GetCommandString(self, line, col)
643
def GetCommentChars(self):
644
"""Return the list of characters used to comment a string in the
646
@return: list of strings
649
return self._code['comment']
651
def GetCompleter(self):
652
"""Get this buffers completer object
656
return self._code['compsvc']
658
def GetDocument(self):
659
"""Return a reference to the document object represented in this buffer.
661
@see: L{ed_txt.EdFile}
666
def GetEOLChar(self):
667
"""Gets the eol character used in document
668
@return: the character used for eol in this document
671
m_id = self.GetEOLMode()
672
if m_id == wx.stc.STC_EOL_CR:
674
elif m_id == wx.stc.STC_EOL_CRLF:
679
def GetFileName(self):
680
"""Returns the full path name of the current file
681
@return: full path name of document
684
return self.file.GetPath()
686
def GetIndentChar(self):
687
"""Gets the indentation char used in document
688
@return: indentation char used either space or tab
691
if self.GetUseTabs():
694
return u' ' * self.GetIndent()
696
def GetKeywords(self):
697
"""Get the keyword set for the current document.
698
@return: list of strings
701
return self._code['keywords']
704
"""Returns the language identifier of this control
705
@return: language identifier of document
708
return self._code['lang_id']
710
def GetModTime(self):
711
"""Get the value of the buffers file last modtime"""
712
return self.file.ModTime
715
"""Update Line/Column information
716
@return: tuple (line, column)
719
return (self.GetCurrentLine() + 1, self.GetColumn(self.GetCurrentPos()))
721
GetRange = wx.stc.StyledTextCtrl.GetTextRange
723
def GetWordFromPosition(self, pos):
724
"""Get the word at the given position
726
@return: (string, int_start, int_end)
729
end = self.WordEndPosition(pos, True)
730
start = self.WordStartPosition(pos, True)
731
word = self.GetTextRange(start, end)
732
return (word, start, end)
734
def IsColumnMode(self):
735
"""Is the buffer in column edit mode
739
return self.VertEdit.Enabled
741
def IsComment(self, pos):
742
"""Is the given position in a comment region of the current buffer
743
@param pos: int position in buffer
748
return 'comment' in self.FindTagById(self.GetStyleAt(pos))
750
def IsString(self, pos):
751
"""Is the given position in a string region of the current buffer
752
@param pos: int position in buffer
756
style = self.GetStyleAt(pos)
757
return self.FindTagById(style) in ('string_style', 'char_style')
759
def IsNonCode(self, pos):
760
"""Is the passed in position in a non code region
761
@param pos: buffer position
765
return self.IsComment(pos) or self.IsString(pos)
767
def HasMarker(self, line, marker):
768
"""Check if the given line has the given marker set
769
@param line: line number
770
@param marker: marker id
773
mask = self.MarkerGet(line)
774
return bool(1<<marker & mask)
776
def HasSelection(self):
777
"""Check if there is a selection in the buffer
781
sel = super(EditraBaseStc, self).GetSelection()
782
return sel[0] != sel[1]
784
def HasMultilineSelection(self):
785
"""Is the selection over multiple lines?
790
sel = super(EditraBaseStc, self).GetSelection()
792
sline = self.LineFromPosition(sel[0])
793
eline = self.LineFromPosition(sel[1])
794
bMulti = sline != eline
797
def CallTipCancel(self):
798
"""Cancel any active calltip(s)"""
799
if self.CallTipActive():
800
super(EditraBaseStc, self).CallTipCancel()
802
def CallTipShow(self, position, tip):
803
"""Show a calltip at the given position in the control
809
super(EditraBaseStc, self).CallTipShow(position, tip)
811
def HidePopups(self):
812
"""Hide autocomp/calltip popup windows if any are active"""
813
if self.AutoCompActive():
814
self.AutoCompCancel()
818
def InitCompleter(self):
819
"""(Re)Initialize a completer object for this buffer
820
@todo: handle extended autocomp for plugins?
823
# Check for plugins that may extend or override functionality for this
825
autocomp_ext = AutoCompExtension(wx.GetApp().GetPluginManager())
826
completer = autocomp_ext.GetCompleter(self)
827
if completer is not None:
828
self._code['compsvc'] = completer
830
extend = Profile_Get('AUTO_COMP_EX') # Using extended autocomp?
831
self._code['compsvc'] = autocomp.AutoCompService.GetCompleter(self, extend)
833
def LoadFile(self, path):
834
"""Load the file at the given path into the buffer. Returns
835
True if no errors and False otherwise. To retrieve the errors
836
check the last error that was set in the file object returned by
838
@param path: path to file
841
# Post notification that a file load is starting
842
ed_msg.PostMessage(ed_msg.EDMSG_FILE_OPENING, path)
843
self.file.SetPath(path)
844
txt = self.file.Read()
846
if self.file.IsRawBytes() and not ebmlib.IsUnicode(txt):
847
self.AddStyledText(txt)
848
self.SetReadOnly(True) # Don't allow editing of raw bytes
852
self.file.SetPath('')
855
if self.file.GetLastError() != 'None':
856
# Return false if there was an encoding error and a fallback
857
# was used. So the caller knows to check the error status
862
def MoveCaretPos(self, offset):
863
"""Move caret by the given offset
864
@param offset: int (+ move right, - move left)
867
pos = max(self.GetCurrentPos() + offset, 0)
868
pos = min(pos, self.GetLength())
872
def OnAutoCompSel(self, evt):
873
"""Handle when an item is inserted from the autocomp list"""
875
cpos = evt.GetPosition()
876
self._code['compsvc'].OnCompletionInserted(cpos, text)
878
def OnChanged(self, evt):
879
"""Handles updates that need to take place after
880
the control has been modified.
881
@param evt: wx.stc.StyledTextEvent
885
# Adjust line number margin width to expand as needed when line
886
# number width over fills the area.
887
lines = self.GetLineCount()
888
mwidth = self.GetTextExtent(str(lines))[0]
891
if wx.Platform == '__WXMAC__':
894
nwidth = max(15, mwidth + adj)
895
if self.GetMarginWidth(NUM_MARGIN) != nwidth:
896
self.SetMarginWidth(NUM_MARGIN, nwidth)
898
wx.PostEvent(self.GetParent(), evt)
899
ed_msg.PostMessage(ed_msg.EDMSG_UI_STC_CHANGED, context=self)
901
def OnModified(self, evt):
902
"""Handle modify events, includes style changes!"""
903
if self.VertEdit.Enabled:
904
self.VertEdit.OnModified(evt)
908
def OnStyleNeeded(self, evt):
909
"""Perform custom styling when registered for a container lexer"""
910
if self._code['clexer'] is not None:
911
self._code['clexer'](self, self.GetEndStyled(), evt.GetPosition())
915
def PutText(self, text):
916
"""Put text in the buffer. Like AddText but does the right thing
917
depending upon the input mode and buffer state.
921
if not self.HasSelection():
922
cpos = self.GetCurrentPos()
923
lepos = self.GetLineEndPosition(self.GetCurrentLine())
924
if self.GetOvertype() and cpos != lepos:
929
self.ReplaceSelection(text)
931
def RegisterImages(self):
932
"""Register the images for the autocomp popup list"""
933
images = [(autocomp.TYPE_FUNCTION, ed_glob.ID_FUNCT_TYPE),
934
(autocomp.TYPE_METHOD, ed_glob.ID_METHOD_TYPE),
935
(autocomp.TYPE_PROPERTY, ed_glob.ID_PROPERTY_TYPE),
936
(autocomp.TYPE_ATTRIBUTE, ed_glob.ID_ATTR_TYPE),
937
(autocomp.TYPE_CLASS, ed_glob.ID_CLASS_TYPE),
938
(autocomp.TYPE_VARIABLE, ed_glob.ID_VARIABLE_TYPE),
939
(autocomp.TYPE_ELEMENT, ed_glob.ID_ELEM_TYPE)]
940
for idx, img in images:
941
bmp = wx.ArtProvider.GetBitmap(str(img), wx.ART_MENU)
943
self.RegisterImage(idx, bmp)
945
def SearchText(self, text, regex=False, back=False):
946
"""Search for text forward or backward
952
flags = wx.stc.STC_FIND_MATCHCASE
954
flags = flags | wx.stc.STC_FIND_REGEXP
959
res = self.SearchNext(flags, text)
961
# Nothing found, search from top
964
res = self.SearchNext(flags, text)
967
res = self.SearchPrev(flags, text)
969
# Nothing found, search from bottom
972
res = self.SearchPrev(flags, text)
973
return res # returns -1 if nothing found even after wrapping around
975
def SetDocument(self, doc):
976
"""Change the document object used.
977
@param doc: an L{ed_txt.EdFile} instance
983
def SetEncoding(self, enc):
984
"""Sets the encoding of the document
985
@param enc: encoding to set for document
988
self.file.SetEncoding(enc)
990
def GetEncoding(self):
991
"""Get the document objects encoding
995
return self.file.GetEncoding()
997
def SetFileName(self, path):
998
"""Set the buffers filename attributes from the given path"""
999
self.file.SetPath(path)
1001
def SetKeyWords(self, kw_lst):
1002
"""Sets the keywords from a list of keyword sets
1003
@param kw_lst: [ (KWLVL, "KEWORDS"), (KWLVL2, "KEYWORDS2"), ect...]
1006
# Parse Keyword Settings List simply ignoring bad values and badly
1008
self._code['keywords'] = list()
1014
if not isinstance(keyw[0], int) or \
1015
not isinstance(keyw[1], basestring):
1019
super(EditraBaseStc, self).SetKeyWords(keyw[0], keyw[1])
1021
# Can't have ? in scintilla autocomp list unless specifying an image
1022
# TODO: this should be handled by the autocomp service
1024
kwlist.replace('?', '')
1026
kwlist = kwlist.split() # Split into a list of words
1027
kwlist = list(set(kwlist)) # Remove duplicates from the list
1028
kwlist.sort() # Sort into alphabetical order
1030
self._code['keywords'] = kwlist
1032
def SetLexer(self, lexer):
1033
"""Set the buffers lexer
1034
@param lexer: lexer to use
1035
@note: Overrides StyledTextCtrl.SetLexer
1038
if lexer == wx.stc.STC_LEX_CONTAINER:
1039
# If setting a container lexer only bind the event if it hasn't
1041
if self._code['clexer'] is None:
1042
self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded)
1044
# If changing from a container lexer to a non container
1045
# lexer we need to unbind the event.
1046
if self._code['clexer'] is not None:
1047
self.Unbind(wx.stc.EVT_STC_STYLENEEDED)
1048
self._code['clexer'] = None
1050
super(EditraBaseStc, self).SetLexer(lexer)
1052
def SetModTime(self, modtime):
1053
"""Set the value of the files last modtime"""
1054
self.file.SetModTime(modtime)
1056
def SetProperties(self, prop_lst):
1057
"""Sets the Lexer Properties from a list of specifications
1058
@param prop_lst: [ ("PROPERTY", "VAL"), ("PROPERTY2", "VAL2) ]
1061
# Parses Property list, ignoring all bad values
1062
for prop in prop_lst:
1066
if not isinstance(prop[0], basestring) or not \
1067
isinstance(prop[1], basestring):
1070
self.SetProperty(prop[0], prop[1])
1073
def BaseSetSelection(self, start, end):
1074
"""Call base STC SetSelection method, for use with internal utf-8
1075
indexes in use by derived classes, STC hell...
1078
super(EditraBaseStc, self).SetSelection(start, end)
1080
def SetSelection(self, start, end):
1081
"""Override base method to make it work correctly using
1082
Unicode character positions instead of UTF-8.
1085
# STC HELL - some methods require UTF-8 offsets while others work
1087
# Calculate UTF-8 offsets in buffer
1088
unicode_txt = self.GetText()
1090
start = len(ed_txt.EncodeString(unicode_txt[0:start], 'utf-8'))
1092
end = len(ed_txt.EncodeString(unicode_txt[0:end], 'utf-8'))
1094
super(EditraBaseStc, self).SetSelection(start, end)
1096
def GetSelection(self):
1097
"""Get the selection positions in Unicode instead of UTF-8"""
1099
# Translate the UTF8 byte offsets to unicode
1100
start, end = super(EditraBaseStc, self).GetSelection()
1101
utf8_txt = self.GetTextUTF8()
1103
start = len(ed_txt.DecodeString(utf8_txt[0:start], 'utf-8'))
1105
end = len(ed_txt.DecodeString(utf8_txt[0:end], 'utf-8'))
1109
def ShowAutoCompOpt(self, command):
1110
"""Shows the autocompletion options list for the command
1111
@param command: command to look for autocomp options for
1114
pos = self.GetCurrentPos()
1115
# symList is a list(completer.Symbol)
1116
symList = self._code['compsvc'].GetAutoCompList(command)
1118
# Build a list that can be feed to Scintilla
1119
lst = map(unicode, symList)
1120
if lst is not None and len(lst):
1121
self.BeginUndoAction()
1122
lst = u' '.join(lst)
1125
self.AutoCompShow(pos - self.WordStartPosition(pos, True), lst)
1127
# Check if something was inserted due to there only being a
1128
# single choice returned from the completer and allow the completer
1129
# to adjust caret position as necessary.
1130
curpos = self.GetCurrentPos()
1132
text = self.GetTextRange(pos, curpos)
1133
self._code['compsvc'].OnCompletionInserted(pos, text)
1134
self.EndUndoAction()
1137
def GetViewWhiteSpace(self):
1138
"""Get if view whitespace is turned on
1142
val = super(EditraBaseStc, self).GetViewWhiteSpace()
1143
return val != wx.stc.STC_WS_INVISIBLE
1145
def SetViewWhiteSpace(self, viewws):
1146
"""Overrides base method to make it a simple bool toggle"""
1148
val = wx.stc.STC_WS_VISIBLEALWAYS
1150
val = wx.stc.STC_WS_INVISIBLE
1151
super(EditraBaseStc, self).SetViewWhiteSpace(val)
1153
def GetWrapMode(self):
1154
"""Get if word wrap is turned on
1158
val = super(EditraBaseStc, self).GetWrapMode()
1159
return val != wx.stc.STC_WRAP_NONE
1161
def SetWrapMode(self, wrap):
1162
"""Overrides base method to make it a simple toggle operation
1167
val = wx.stc.STC_WRAP_WORD
1169
val = wx.stc.STC_WRAP_NONE
1170
super(EditraBaseStc, self).SetWrapMode(val)
1172
def ShowCallTip(self, command):
1173
"""Shows call tip for given command
1174
@param command: command to look for calltips for
1177
self.CallTipCancel()
1179
tip = self._code['compsvc'].GetCallTip(command)
1181
curr_pos = self.GetCurrentPos()
1182
tip_pos = curr_pos - (len(command.split('.')[-1]) + 1)
1183
fail_safe = curr_pos - self.GetColumn(curr_pos)
1184
self.CallTipShow(max(tip_pos, fail_safe), tip)
1186
def ToggleColumnMode(self):
1187
"""Toggle the column edit mode"""
1188
self.VertEdit.enable(not self.VertEdit.Enabled)
1190
def ToggleComment(self):
1191
"""Toggle the comment of the selected region"""
1192
if len(self._code['comment']):
1193
sel = self.GetSelection()
1194
start = self.LineFromPosition(sel[0])
1195
end = self.LineFromPosition(sel[1])
1196
c_start = self._code['comment'][0]
1198
if end > start and self.GetColumn(sel[1]) == 0:
1201
# Analyze the selected line(s)
1203
for line in range(start, end+1):
1204
txt = self.GetLine(line)
1205
if txt.lstrip().startswith(c_start):
1208
lcount = end - start
1213
if comment > (lcount / 2) + mod:
1215
self.Comment(start, end, True)
1217
self.Comment(start, end, False)
1219
def ToggleLineNumbers(self, switch=None):
1220
"""Toggles the visibility of the line number margin
1221
@keyword switch: force a particular setting
1224
if (switch is None and \
1225
not self.GetMarginWidth(NUM_MARGIN)) or switch:
1226
self.EnableLineNumbers(True)
1228
self.EnableLineNumbers(False)
1232
"""Vertical edit mode accessor."""
1233
return self.vert_edit
1235
#---- Style Function Definitions ----#
1237
def RefreshStyles(self):
1238
"""Refreshes the colorization of the window by reloading any
1239
style tags that may have been modified.
1240
@postcondition: all style settings are refreshed in the control
1243
with eclib.Freezer(self) as _tmp:
1244
self.StyleClearAll()
1245
self.SetSyntax(self.GetSyntaxParams())
1246
self.DefineMarkers()
1249
def UpdateBaseStyles(self):
1250
"""Update the controls basic styles"""
1251
super(EditraBaseStc, self).UpdateBaseStyles()
1253
# Set control specific styles
1254
sback = self.GetItemByName('select_style')
1255
if not sback.IsNull():
1256
sback = sback.GetBack()
1258
sback = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
1259
self.VertEdit.SetBlockColor(sback)
1260
self.DefineMarkers()
1262
#-----------------------------------------------------------------------------#
1264
class AutoCompExtension(plugin.Plugin):
1265
"""Plugin that Extends the autocomp feature"""
1266
observers = plugin.ExtensionPoint(iface.AutoCompI)
1267
def GetCompleter(self, buff):
1268
"""Get the completer for the specified file type id
1269
@param buff: EditraStc instance
1272
ftypeid = buff.GetLangId()
1273
for observer in self.observers:
1275
if observer.GetFileTypeId() == ftypeid:
1276
return observer.GetCompleter(buff)
1277
except Exception, msg:
1278
util.Log("[ed_basestc][err] GetCompleter Extension: %s" % str(msg))
1282
#-----------------------------------------------------------------------------#
1284
def _GetMacKeyBindings():
1285
"""Returns a list of 3-element tuples defining the standard key
1286
bindings for Mac text editors -- i.e., the behavior of option-arrow,
1287
shift-delete, and so on.
1289
@return: list of (key code, modifier keys, STC action)
1292
# A good reference for these: http://www.yellowbrain.com/stc/keymap.html
1294
# Move/select/delete by word
1295
(wx.stc.STC_KEY_LEFT, wx.stc.STC_SCMOD_ALT,
1296
wx.stc.STC_CMD_WORDLEFT),
1297
(wx.stc.STC_KEY_RIGHT, wx.stc.STC_SCMOD_ALT,
1298
wx.stc.STC_CMD_WORDRIGHT),
1299
(wx.stc.STC_KEY_LEFT, ALT_SHIFT, wx.stc.STC_CMD_WORDLEFTEXTEND),
1300
(wx.stc.STC_KEY_RIGHT, ALT_SHIFT, wx.stc.STC_CMD_WORDRIGHTEXTEND),
1301
(wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_ALT,
1302
wx.stc.STC_CMD_DELWORDLEFT),
1303
(wx.stc.STC_KEY_DELETE, wx.stc.STC_SCMOD_ALT,
1304
wx.stc.STC_CMD_DELWORDRIGHT),
1305
(wx.stc.STC_KEY_BACK, ALT_SHIFT, wx.stc.STC_CMD_DELWORDRIGHT),
1306
(wx.stc.STC_KEY_DELETE, ALT_SHIFT, wx.stc.STC_CMD_DELWORDLEFT),
1308
# Move/select/delete by line
1309
(wx.stc.STC_KEY_LEFT, wx.stc.STC_SCMOD_CTRL,
1310
wx.stc.STC_CMD_VCHOME),
1311
(wx.stc.STC_KEY_LEFT, CTRL_SHIFT, wx.stc.STC_CMD_VCHOMEEXTEND),
1312
(wx.stc.STC_KEY_RIGHT, wx.stc.STC_SCMOD_CTRL,
1313
wx.stc.STC_CMD_LINEEND),
1314
(wx.stc.STC_KEY_RIGHT, CTRL_SHIFT, wx.stc.STC_CMD_LINEENDEXTEND),
1315
(wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_CTRL,
1316
wx.stc.STC_CMD_DELLINELEFT),
1317
(wx.stc.STC_KEY_DELETE, wx.stc.STC_SCMOD_CTRL,
1318
wx.stc.STC_CMD_DELLINERIGHT),
1319
(wx.stc.STC_KEY_BACK, CTRL_SHIFT, wx.stc.STC_CMD_DELLINERIGHT),
1320
(wx.stc.STC_KEY_DELETE, CTRL_SHIFT, wx.stc.STC_CMD_DELLINELEFT),
1322
# By-character deletion behavior
1323
(wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_NORM,
1324
wx.stc.STC_CMD_DELETEBACK),
1325
(wx.stc.STC_KEY_DELETE, wx.stc.STC_SCMOD_SHIFT,
1326
wx.stc.STC_CMD_DELETEBACK),
1328
# NOTE: The following two are a special case, since Scintilla
1329
# doesn't have a forward-delete action. So here we just cancel any
1330
# tip our auto-completion display, and then implement forward
1331
# delete in OnKeyDown.
1332
#(wx.stc.STC_KEY_DELETE, 0, wx.stc.STC_CMD_CANCEL),
1333
(wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_SHIFT,
1334
wx.stc.STC_CMD_CANCEL),