~brian-sidebotham/wxwidgets-cmake/wxpython-2.9.4

« back to all changes in this revision

Viewing changes to wxPython/wx/tools/Editra/src/ed_basestc.py

  • Committer: Brian Sidebotham
  • Date: 2013-08-03 14:30:08 UTC
  • Revision ID: brian.sidebotham@gmail.com-20130803143008-c7806tkych1tp6fc
Initial import into Bazaar

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
###############################################################################
 
8
 
 
9
"""
 
10
The EditraBaseStc is the base StyledTextCtrl that provides automatic styling and
 
11
syntax highlighting of all supported filetypes.
 
12
 
 
13
@summary: Editra's base styled text ctrl.
 
14
 
 
15
"""
 
16
 
 
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 $"
 
20
 
 
21
#-----------------------------------------------------------------------------#
 
22
# Imports
 
23
import wx
 
24
import wx.stc
 
25
 
 
26
# Editra Imports
 
27
import ed_glob
 
28
import ed_style
 
29
import eclib
 
30
import ebmlib
 
31
import ed_msg
 
32
import ed_txt
 
33
from syntax import syntax
 
34
from syntax import synglob
 
35
import autocomp
 
36
from extern import vertedit
 
37
from profiler import Profile_Get
 
38
import plugin
 
39
import iface
 
40
import util
 
41
import ed_marker
 
42
 
 
43
#-----------------------------------------------------------------------------#
 
44
 
 
45
# Margins
 
46
MARK_MARGIN = 0
 
47
NUM_MARGIN  = 1
 
48
FOLD_MARGIN = 2
 
49
 
 
50
# Markers (3rd party)
 
51
MARKER_VERT_EDIT = ed_marker.NewMarkerId()
 
52
 
 
53
# Key code additions
 
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
 
56
 
 
57
#-----------------------------------------------------------------------------#
 
58
 
 
59
class EditraBaseStc(wx.stc.StyledTextCtrl, ed_style.StyleMgr):
 
60
    """Base StyledTextCtrl that provides all the base code editing
 
61
    functionality.
 
62
 
 
63
    """
 
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())
 
69
 
 
70
        # Attributes
 
71
        self.file = ed_txt.EdFile()
 
72
        self._code = dict(compsvc=autocomp.AutoCompService.GetCompleter(self),
 
73
                          synmgr=syntax.SyntaxMgr(ed_glob.CONFIG['CACHE_DIR']),
 
74
                          keywords=[ ' ' ],
 
75
                          comment=list(),
 
76
                          clexer=None,      # Container lexer method
 
77
                          indenter=None,    # Auto indenter
 
78
                          lang_id=0)        # Language ID from syntax module
 
79
 
 
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
 
83
 
 
84
        # Set Up Margins
 
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)
 
90
 
 
91
        ## Middle Left Margin Line Number Indication
 
92
        self.SetMarginType(NUM_MARGIN, wx.stc.STC_MARGIN_NUMBER)
 
93
        self.SetMarginMask(NUM_MARGIN, 0)
 
94
 
 
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)
 
99
 
 
100
        # Set Mac specific keybindings
 
101
        if wx.Platform == '__WXMAC__':
 
102
            for keys in _GetMacKeyBindings():
 
103
                self.CmdKeyAssign(*keys)
 
104
 
 
105
        # Set default EOL format
 
106
        if wx.Platform != '__WXMSW__':
 
107
            self.SetEOLMode(wx.stc.STC_EOL_LF)
 
108
 
 
109
        # Setup Auto-comp images
 
110
        # TODO: should be called on theme change messages
 
111
        self.RegisterImages()
 
112
 
 
113
        # Event Handlers
 
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)
 
118
 
 
119
    def OnDestroy(self, evt):
 
120
        if evt.GetId() == self.GetId():
 
121
            # Cleanup the file object callbacks
 
122
            self.file.RemoveModifiedCallback(self.FireModified)
 
123
            self.file.CleanUp()
 
124
        evt.Skip()
 
125
 
 
126
    #---- Public Methods ----#
 
127
 
 
128
    # General marker api
 
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
 
133
 
 
134
        """
 
135
        assert isinstance(marker, ed_marker.Marker)
 
136
        if line < 0:
 
137
            line = self.GetCurrentLine()
 
138
        marker.Set(self, line)
 
139
        return marker.Handle
 
140
 
 
141
    def RemoveMarker(self, marker, line):
 
142
        """Remove the book mark from the given line
 
143
        @param marker: ed_marker.Marker instance
 
144
        @param line: int
 
145
 
 
146
        """
 
147
        assert isinstance(marker, ed_marker.Marker)
 
148
        marker.Set(self, line, delete=True)
 
149
 
 
150
    def RemoveAllMarkers(self, marker):
 
151
        """Remove all the bookmarks in the buffer
 
152
        @param marker: ed_marker.Marker instance
 
153
 
 
154
        """
 
155
        assert isinstance(marker, ed_marker.Marker)
 
156
        marker.DeleteAll(self)
 
157
 
 
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)
 
164
 
 
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)
 
169
 
 
170
    def _SetBreakpoint(self, mobj, line=-1):
 
171
        """Set the breakpoint state
 
172
        @param mtype: Marker object
 
173
        @return: int (-1 if already set)
 
174
 
 
175
        """
 
176
        handle = -1
 
177
        if line < 0:
 
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)
 
184
            handle = mobj.Handle
 
185
        return handle
 
186
 
 
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
 
192
 
 
193
        """
 
194
        if not disabled:
 
195
            handle = self._SetBreakpoint(ed_marker.Breakpoint(), line)
 
196
        else:
 
197
            handle = self._SetBreakpoint(ed_marker.BreakpointDisabled(), line)
 
198
        return handle
 
199
 
 
200
    def ShowStepMarker(self, line=-1, show=True):
 
201
        """Show the step (arrow) marker to the given line."""
 
202
        if line < 0:
 
203
            line = self.GetCurrentLine()
 
204
        mark = ed_marker.BreakpointStep()
 
205
        if show:
 
206
            mark.Set(self, line, delete=False)
 
207
        else:
 
208
            mark.DeleteAll(self)
 
209
 
 
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
 
215
 
 
216
        """
 
217
        if before:
 
218
            self.LineUp()
 
219
 
 
220
        self.LineEnd()
 
221
 
 
222
        if indent:
 
223
            self.AutoIndent()
 
224
        else:
 
225
            self.InsertText(self.GetCurrentPos(), self.GetEOLChar())
 
226
            self.LineDown()
 
227
 
 
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
 
232
        behavior.
 
233
 
 
234
        """
 
235
        cpos = self.GetCurrentPos()
 
236
 
 
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())
 
241
            self.EndUndoAction()
 
242
        else:
 
243
            # Default Indenter
 
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()
 
249
                return
 
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' ')
 
254
            self.AddText(txt)
 
255
 
 
256
        self.EnsureCaretVisible()
 
257
 
 
258
    def BackTab(self):
 
259
        """Unindent or remove excess whitespace to left of cursor"""
 
260
        sel = self.GetSelection()
 
261
        if sel[0] == sel[1]:
 
262
            # There is no selection
 
263
            cpos = self.GetCurrentPos()
 
264
            cline = self.GetCurrentLine()
 
265
            cipos = self.GetLineIndentPosition(cline)
 
266
            if cpos <= cipos:
 
267
                # In indentation so simply backtab
 
268
                super(EditraBaseStc, self).BackTab()
 
269
            else:
 
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():
 
274
 
 
275
                    # Find the end of the whitespace
 
276
                    end = column
 
277
                    while end < len(text) and \
 
278
                          text[end].isspace() and \
 
279
                          text[end] not in '\r\n':
 
280
                        end += 1
 
281
 
 
282
                    # Find the start of the whitespace
 
283
                    end -= 1
 
284
                    start = end
 
285
                    while end > 0 and text[start].isspace():
 
286
                        start -= 1
 
287
 
 
288
                    diff = end - start
 
289
                    if diff > 1:
 
290
                        # There is space to compress
 
291
                        isize = self.GetIndent()
 
292
                        if isize < diff:
 
293
                            # More space than indent to remove
 
294
                            repeat = isize
 
295
                        else:
 
296
                            # Less than one indent width to remove
 
297
                            repeat = end - (start + 1)
 
298
 
 
299
                        # Update the control
 
300
                        self.BeginUndoAction()
 
301
                        self.SetCurrentPos(cpos + (end - column))
 
302
                        for x in range(repeat):
 
303
                            self.DeleteBack()
 
304
                        self.EndUndoAction()
 
305
 
 
306
        else:
 
307
            # There is a selection
 
308
            super(EditraBaseStc, self).BackTab()
 
309
 
 
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)
 
314
        else:
 
315
            # Alternatively, just make the caret a bit thicker!
 
316
            # best we can do on 2.8
 
317
            self.SetCaretWidth(3)
 
318
 
 
319
    def SetLineCaret(self):
 
320
        """Change caret style to line"""
 
321
        if hasattr(self, 'SetCaretStyle'):
 
322
            self.SetCaretStyle(wx.stc.STC_CARETSTYLE_LINE)
 
323
        else:
 
324
            pwidth = Profile_Get('CARETWIDTH', default=1)
 
325
            self.SetCaretWidth(pwidth)
 
326
 
 
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
 
330
 
 
331
        """
 
332
        # Check if we are still alive or not, as this may be called
 
333
        # after we have been deleted.
 
334
        if self:
 
335
            super(EditraBaseStc, self).BraceBadLight(pos)
 
336
 
 
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
 
341
 
 
342
        """
 
343
        # Check if we are still alive or not, as this may be called
 
344
        # after we have been deleted.
 
345
        if self:
 
346
            super(EditraBaseStc, self).BraceHighlight(pos1, pos2)
 
347
 
 
348
    def CanCopy(self):
 
349
        """Check if copy/cut is possible"""
 
350
        return self.HasSelection()
 
351
 
 
352
    CanCut = CanCopy
 
353
 
 
354
    def Comment(self, start, end, uncomment=False):
 
355
        """(Un)Comments a line or a selected block of text
 
356
        in a document.
 
357
        @param start: beginning line (int)
 
358
        @param end: end line (int)
 
359
        @keyword uncomment: uncomment selection
 
360
 
 
361
        """
 
362
        if len(self._code['comment']):
 
363
            sel = self.GetSelection()
 
364
            c_start = self._code['comment'][0]
 
365
            c_end = u''
 
366
            if len(self._code['comment']) > 1:
 
367
                c_end = self._code['comment'][1]
 
368
 
 
369
            # Modify the selected line(s)
 
370
            self.BeginUndoAction()
 
371
            try:
 
372
                nchars = 0
 
373
                lines = range(start, end+1)
 
374
                lines.reverse()
 
375
                for line_num in lines:
 
376
                    lstart = self.PositionFromLine(line_num)
 
377
                    lend = self.GetLineEndPosition(line_num)
 
378
                    text = self.GetTextRange(lstart, lend)
 
379
                    tmp = text.strip()
 
380
                    if len(tmp):
 
381
                        if uncomment:
 
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)
 
387
                        else:
 
388
                            text = c_start + text + c_end
 
389
                            nchars = nchars + len(c_start + c_end)
 
390
 
 
391
                        self.SetTargetStart(lstart)
 
392
                        self.SetTargetEnd(lend)
 
393
                        self.ReplaceTarget(text)
 
394
            finally:
 
395
                self.EndUndoAction()
 
396
                if sel[0] != sel[1]:
 
397
                    self.SetSelection(sel[0], sel[1] + nchars)
 
398
                else:
 
399
                    if len(self._code['comment']) > 1:
 
400
                        nchars = nchars - len(self._code['comment'][1])
 
401
                    self.GotoPos(sel[0] + nchars)
 
402
 
 
403
    def ConfigureAutoComp(self):
 
404
        """Sets up the Autocompleter, the autocompleter
 
405
        configuration depends on the currently set lexer
 
406
        @postcondition: autocomp is configured
 
407
 
 
408
        """
 
409
        self.AutoCompSetAutoHide(False)
 
410
        self.InitCompleter()
 
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())
 
420
 
 
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
 
424
 
 
425
        """
 
426
        syn_data = self._code['synmgr'].GetSyntaxData(file_ext)
 
427
 
 
428
        # Set the ID of the selected lexer
 
429
        self._code['lang_id'] = syn_data.LangId
 
430
 
 
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]:
 
436
            self.SetStyleBits(7)
 
437
        elif lexer == wx.stc.STC_LEX_NULL:
 
438
            self.SetStyleBits(5)
 
439
            self.SetLexer(lexer)
 
440
            self.ClearDocumentStyle()
 
441
            self.UpdateBaseStyles()
 
442
            return True
 
443
        else:
 
444
            self.SetStyleBits(5)
 
445
 
 
446
        # Set Lexer
 
447
        self.SetLexer(lexer)
 
448
        # Set Keywords
 
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
 
456
 
 
457
        # Get Extension Features
 
458
        clexer = syn_data.GetFeature(synglob.FEATURE_STYLETEXT)
 
459
        indenter = syn_data.GetFeature(synglob.FEATURE_AUTOINDENT)
 
460
 
 
461
        # Set the Container Lexer Method
 
462
        self._code['clexer'] = clexer
 
463
        # Auto-indenter function
 
464
        self._code['indenter'] = indenter
 
465
 
 
466
    def DefineMarkers(self):
 
467
        """Defines the folder and bookmark icons for this control
 
468
        @postcondition: all margin markers are defined
 
469
 
 
470
        """
 
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])
 
476
 
 
477
        fore = style.GetBack()
 
478
        rgb = eclib.HexToRGB(fore[1:])
 
479
        fore = wx.Colour(red=rgb[0], green=rgb[1], blue=rgb[2])
 
480
 
 
481
        # Buffer background highlight
 
482
        caret_line = self.GetItemByName('caret_line').GetBack()
 
483
        rgb = eclib.HexToRGB(caret_line[1:])
 
484
        clback = wx.Colour(*rgb)
 
485
 
 
486
        # Code Folding markers
 
487
        folder = ed_marker.FoldMarker()
 
488
        folder.Foreground = fore
 
489
        folder.Background = back
 
490
        folder.RegisterWithStc(self)
 
491
 
 
492
        # Bookmarks
 
493
        ed_marker.Bookmark().RegisterWithStc(self)
 
494
 
 
495
        # Breakpoints
 
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)
 
502
 
 
503
        # Other markers
 
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)
 
511
        # Lint Marker
 
512
        ed_marker.LintMarker().RegisterWithStc(self)
 
513
        ed_marker.LintMarkerWarning().RegisterWithStc(self)
 
514
        ed_marker.LintMarkerError().RegisterWithStc(self)
 
515
 
 
516
    def DoZoom(self, mode):
 
517
        """Zoom control in or out
 
518
        @param mode: either zoom in or out
 
519
 
 
520
        """
 
521
        id_type = mode
 
522
        zoomlevel = self.GetZoom()
 
523
        if id_type == ed_glob.ID_ZOOM_OUT:
 
524
            if zoomlevel > -9:
 
525
                self.ZoomOut()
 
526
        elif id_type == ed_glob.ID_ZOOM_IN:
 
527
            if zoomlevel < 19:
 
528
                self.ZoomIn()
 
529
        else:
 
530
            self.SetZoom(0)
 
531
        return self.GetZoom()
 
532
 
 
533
    def EnableLineNumbers(self, enable=True):
 
534
        """Enable/Disable line number margin
 
535
        @keyword enable: bool
 
536
 
 
537
        """
 
538
        if enable:
 
539
            self.SetMarginWidth(NUM_MARGIN, 30)
 
540
        else:
 
541
            self.SetMarginWidth(NUM_MARGIN, 0)
 
542
        self._line_num = enable
 
543
 
 
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
 
547
 
 
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
 
553
 
 
554
        """
 
555
        text, pos = self.GetCurLine()
 
556
        oldpos = pos
 
557
        if not reverse:
 
558
            # search forward
 
559
            for i in range(repeat):
 
560
                pos = text.find(char, pos+1)
 
561
                if pos == -1:
 
562
                    return
 
563
        else:
 
564
            # search backward
 
565
            for i in range(repeat):
 
566
                pos = text.rfind(char, 0, pos)
 
567
                if pos == -1:
 
568
                    return
 
569
 
 
570
        newpos = pos + extra_offset
 
571
        if newpos in range(len(text)):
 
572
            self.MoveCaretPos(newpos - oldpos)
 
573
 
 
574
    @property
 
575
    def File(self):
 
576
        """Reference to this buffers file object"""
 
577
        return self.file
 
578
 
 
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
 
583
 
 
584
        """
 
585
        if set_ext != u'':
 
586
            ext = set_ext.lower()
 
587
        else:
 
588
            ext = self.file.GetExtension().lower()
 
589
 
 
590
        if ext == u'':
 
591
            fname = self.GetFileName()
 
592
            ext = ebmlib.GetFileName(fname).lower()
 
593
 
 
594
        self.ClearDocumentStyle()
 
595
 
 
596
        # Configure Lexer from File Extension
 
597
        self.ConfigureLexer(ext)
 
598
 
 
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"-":
 
607
                    interp = interp[-1]
 
608
                elif len(interp):
 
609
                    interp = interp[0]
 
610
                else:
 
611
                    interp = u''
 
612
                # TODO: should check user config to ensure the explict
 
613
                #       extension is still associated with the expected
 
614
                #       file type.
 
615
                ex_map = { "python" : "py", "wish" : "tcl", "ruby" : "rb",
 
616
                           "bash" : "sh", "csh" : "csh", "perl" : "pl",
 
617
                           "ksh" : "ksh", "php" : "php", "booi" : "boo",
 
618
                           "pike" : "pike"}
 
619
                self.ConfigureLexer(ex_map.get(interp, interp))
 
620
        self.Colourise(0, -1)
 
621
 
 
622
    def FireModified(self):
 
623
        """Fire a modified event"""
 
624
        self.OnChanged(wx.stc.StyledTextEvent(wx.stc.wxEVT_STC_CHANGE,
 
625
                                              self.GetId()))
 
626
 
 
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
 
633
 
 
634
        """
 
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)
 
641
        return cmd
 
642
 
 
643
    def GetCommentChars(self):
 
644
        """Return the list of characters used to comment a string in the
 
645
        current language.
 
646
        @return: list of strings
 
647
 
 
648
        """
 
649
        return self._code['comment']
 
650
 
 
651
    def GetCompleter(self):
 
652
        """Get this buffers completer object
 
653
        @return: Completer
 
654
 
 
655
        """
 
656
        return self._code['compsvc']
 
657
 
 
658
    def GetDocument(self):
 
659
        """Return a reference to the document object represented in this buffer.
 
660
        @return: EdFile
 
661
        @see: L{ed_txt.EdFile}
 
662
 
 
663
        """
 
664
        return self.file
 
665
 
 
666
    def GetEOLChar(self):
 
667
        """Gets the eol character used in document
 
668
        @return: the character used for eol in this document
 
669
 
 
670
        """
 
671
        m_id = self.GetEOLMode()
 
672
        if m_id == wx.stc.STC_EOL_CR:
 
673
            return u'\r'
 
674
        elif m_id == wx.stc.STC_EOL_CRLF:
 
675
            return u'\r\n'
 
676
        else:
 
677
            return u'\n'
 
678
 
 
679
    def GetFileName(self):
 
680
        """Returns the full path name of the current file
 
681
        @return: full path name of document
 
682
 
 
683
        """
 
684
        return self.file.GetPath()
 
685
 
 
686
    def GetIndentChar(self):
 
687
        """Gets the indentation char used in document
 
688
        @return: indentation char used either space or tab
 
689
 
 
690
        """
 
691
        if self.GetUseTabs():
 
692
            return u'\t'
 
693
        else:
 
694
            return u' ' * self.GetIndent()
 
695
 
 
696
    def GetKeywords(self):
 
697
        """Get the keyword set for the current document.
 
698
        @return: list of strings
 
699
 
 
700
        """
 
701
        return self._code['keywords']
 
702
 
 
703
    def GetLangId(self):
 
704
        """Returns the language identifier of this control
 
705
        @return: language identifier of document
 
706
 
 
707
        """
 
708
        return self._code['lang_id']
 
709
 
 
710
    def GetModTime(self):
 
711
        """Get the value of the buffers file last modtime"""
 
712
        return self.file.ModTime
 
713
 
 
714
    def GetPos(self):
 
715
        """Update Line/Column information
 
716
        @return: tuple (line, column)
 
717
 
 
718
        """
 
719
        return (self.GetCurrentLine() + 1, self.GetColumn(self.GetCurrentPos()))
 
720
 
 
721
    GetRange = wx.stc.StyledTextCtrl.GetTextRange
 
722
 
 
723
    def GetWordFromPosition(self, pos):
 
724
        """Get the word at the given position
 
725
        @param pos: int
 
726
        @return: (string, int_start, int_end)
 
727
 
 
728
        """
 
729
        end = self.WordEndPosition(pos, True)
 
730
        start = self.WordStartPosition(pos, True)
 
731
        word = self.GetTextRange(start, end)
 
732
        return (word, start, end)
 
733
 
 
734
    def IsColumnMode(self):
 
735
        """Is the buffer in column edit mode
 
736
        @return: bool
 
737
 
 
738
        """
 
739
        return self.VertEdit.Enabled
 
740
 
 
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
 
744
        @return: bool
 
745
 
 
746
        """
 
747
        pos = max(0, pos-1)
 
748
        return 'comment' in self.FindTagById(self.GetStyleAt(pos))
 
749
 
 
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
 
753
        @return: bool
 
754
 
 
755
        """
 
756
        style = self.GetStyleAt(pos)
 
757
        return self.FindTagById(style) in ('string_style', 'char_style')
 
758
 
 
759
    def IsNonCode(self, pos):
 
760
        """Is the passed in position in a non code region
 
761
        @param pos: buffer position
 
762
        @return: bool
 
763
 
 
764
        """
 
765
        return self.IsComment(pos) or self.IsString(pos)
 
766
 
 
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
 
771
 
 
772
        """
 
773
        mask = self.MarkerGet(line)
 
774
        return bool(1<<marker & mask)
 
775
 
 
776
    def HasSelection(self):
 
777
        """Check if there is a selection in the buffer
 
778
        @return: bool
 
779
 
 
780
        """
 
781
        sel = super(EditraBaseStc, self).GetSelection()
 
782
        return sel[0] != sel[1]
 
783
 
 
784
    def HasMultilineSelection(self):
 
785
        """Is the selection over multiple lines?
 
786
        @return: bool
 
787
 
 
788
        """
 
789
        bMulti = False
 
790
        sel = super(EditraBaseStc, self).GetSelection()
 
791
        if sel[0] != sel[1]:
 
792
            sline = self.LineFromPosition(sel[0])
 
793
            eline = self.LineFromPosition(sel[1])
 
794
            bMulti = sline != eline
 
795
        return bMulti
 
796
 
 
797
    def CallTipCancel(self):
 
798
        """Cancel any active calltip(s)"""
 
799
        if self.CallTipActive():
 
800
            super(EditraBaseStc, self).CallTipCancel()
 
801
 
 
802
    def CallTipShow(self, position, tip):
 
803
        """Show a calltip at the given position in the control
 
804
        @param position: int
 
805
        @param tip: unicode
 
806
 
 
807
        """
 
808
        self.CallTipCancel()
 
809
        super(EditraBaseStc, self).CallTipShow(position, tip)
 
810
 
 
811
    def HidePopups(self):
 
812
        """Hide autocomp/calltip popup windows if any are active"""
 
813
        if self.AutoCompActive():
 
814
            self.AutoCompCancel()
 
815
 
 
816
        self.CallTipCancel()
 
817
 
 
818
    def InitCompleter(self):
 
819
        """(Re)Initialize a completer object for this buffer
 
820
        @todo: handle extended autocomp for plugins?
 
821
 
 
822
        """
 
823
        # Check for plugins that may extend or override functionality for this
 
824
        # file type.
 
825
        autocomp_ext = AutoCompExtension(wx.GetApp().GetPluginManager())
 
826
        completer = autocomp_ext.GetCompleter(self)
 
827
        if completer is not None:
 
828
            self._code['compsvc'] = completer
 
829
        else:
 
830
            extend = Profile_Get('AUTO_COMP_EX') # Using extended autocomp?
 
831
            self._code['compsvc'] = autocomp.AutoCompService.GetCompleter(self, extend)
 
832
 
 
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
 
837
        L{GetDocument}.
 
838
        @param path: path to file
 
839
 
 
840
        """
 
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()
 
845
        if txt is not None:
 
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
 
849
            else:
 
850
                self.SetText(txt)
 
851
        else:
 
852
            self.file.SetPath('')
 
853
            return False
 
854
 
 
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
 
858
            return False
 
859
        else:
 
860
            return True
 
861
 
 
862
    def MoveCaretPos(self, offset):
 
863
        """Move caret by the given offset
 
864
        @param offset: int (+ move right, - move left)
 
865
 
 
866
        """
 
867
        pos = max(self.GetCurrentPos() + offset, 0)
 
868
        pos = min(pos, self.GetLength())
 
869
        self.GotoPos(pos)
 
870
        self.ChooseCaretX()
 
871
 
 
872
    def OnAutoCompSel(self, evt):
 
873
        """Handle when an item is inserted from the autocomp list"""
 
874
        text = evt.GetText()
 
875
        cpos = evt.GetPosition()
 
876
        self._code['compsvc'].OnCompletionInserted(cpos, text)
 
877
 
 
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
 
882
 
 
883
        """
 
884
        if self._line_num:
 
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]
 
889
 
 
890
            adj = 8
 
891
            if wx.Platform == '__WXMAC__':
 
892
                adj = 2
 
893
 
 
894
            nwidth = max(15, mwidth + adj)
 
895
            if self.GetMarginWidth(NUM_MARGIN) != nwidth:
 
896
                self.SetMarginWidth(NUM_MARGIN, nwidth)
 
897
 
 
898
        wx.PostEvent(self.GetParent(), evt)
 
899
        ed_msg.PostMessage(ed_msg.EDMSG_UI_STC_CHANGED, context=self)
 
900
 
 
901
    def OnModified(self, evt):
 
902
        """Handle modify events, includes style changes!"""
 
903
        if self.VertEdit.Enabled:
 
904
            self.VertEdit.OnModified(evt)
 
905
        else:
 
906
            evt.Skip()
 
907
 
 
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())
 
912
        else:
 
913
            evt.Skip()
 
914
 
 
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.
 
918
        @param text: string
 
919
 
 
920
        """
 
921
        if not self.HasSelection():
 
922
            cpos = self.GetCurrentPos()
 
923
            lepos = self.GetLineEndPosition(self.GetCurrentLine())
 
924
            if self.GetOvertype() and cpos != lepos:
 
925
                self.CharRight()
 
926
                self.DeleteBack()
 
927
            self.AddText(text)
 
928
        else:
 
929
            self.ReplaceSelection(text)
 
930
 
 
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)
 
942
            if bmp.IsOk():
 
943
                self.RegisterImage(idx, bmp)
 
944
 
 
945
    def SearchText(self, text, regex=False, back=False):
 
946
        """Search for text forward or backward
 
947
        @param text: string
 
948
        @keyword regex: bool
 
949
        @keyword back: bool
 
950
 
 
951
        """
 
952
        flags = wx.stc.STC_FIND_MATCHCASE
 
953
        if regex:
 
954
            flags = flags | wx.stc.STC_FIND_REGEXP
 
955
 
 
956
        self.SearchAnchor()
 
957
        if not back:
 
958
            # Search forward
 
959
            res = self.SearchNext(flags, text)
 
960
            if res == -1:
 
961
                # Nothing found, search from top
 
962
                self.DocumentStart()
 
963
                self.SearchAnchor()
 
964
                res = self.SearchNext(flags, text)
 
965
        else:
 
966
            # Search backward
 
967
            res = self.SearchPrev(flags, text)
 
968
            if res == -1:
 
969
                # Nothing found, search from bottom
 
970
                self.DocumentEnd()
 
971
                self.SearchAnchor()
 
972
                res = self.SearchPrev(flags, text)
 
973
        return res # returns -1 if nothing found even after wrapping around
 
974
 
 
975
    def SetDocument(self, doc):
 
976
        """Change the document object used.
 
977
        @param doc: an L{ed_txt.EdFile} instance
 
978
 
 
979
        """
 
980
        del self.file
 
981
        self.file = doc
 
982
 
 
983
    def SetEncoding(self, enc):
 
984
        """Sets the encoding of the document
 
985
        @param enc: encoding to set for document
 
986
 
 
987
        """
 
988
        self.file.SetEncoding(enc)
 
989
 
 
990
    def GetEncoding(self):
 
991
        """Get the document objects encoding
 
992
        @return: string
 
993
 
 
994
        """ 
 
995
        return self.file.GetEncoding()
 
996
 
 
997
    def SetFileName(self, path):
 
998
        """Set the buffers filename attributes from the given path"""
 
999
        self.file.SetPath(path)
 
1000
 
 
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...]
 
1004
 
 
1005
        """
 
1006
        # Parse Keyword Settings List simply ignoring bad values and badly
 
1007
        # formed lists
 
1008
        self._code['keywords'] = list()
 
1009
        kwlist = ""
 
1010
        for keyw in kw_lst:
 
1011
            if len(keyw) != 2:
 
1012
                continue
 
1013
            else:
 
1014
                if not isinstance(keyw[0], int) or \
 
1015
                   not isinstance(keyw[1], basestring):
 
1016
                    continue
 
1017
                else:
 
1018
                    kwlist += keyw[1]
 
1019
                    super(EditraBaseStc, self).SetKeyWords(keyw[0], keyw[1])
 
1020
 
 
1021
        # Can't have ? in scintilla autocomp list unless specifying an image
 
1022
        # TODO: this should be handled by the autocomp service
 
1023
        if '?' in kwlist:
 
1024
            kwlist.replace('?', '')
 
1025
 
 
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
 
1029
 
 
1030
        self._code['keywords'] = kwlist
 
1031
 
 
1032
    def SetLexer(self, lexer):
 
1033
        """Set the buffers lexer
 
1034
        @param lexer: lexer to use
 
1035
        @note: Overrides StyledTextCtrl.SetLexer
 
1036
 
 
1037
        """
 
1038
        if lexer == wx.stc.STC_LEX_CONTAINER:
 
1039
            # If setting a container lexer only bind the event if it hasn't
 
1040
            # been done yet.
 
1041
            if self._code['clexer'] is None:
 
1042
                self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded)
 
1043
        else:
 
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
 
1049
 
 
1050
        super(EditraBaseStc, self).SetLexer(lexer)
 
1051
 
 
1052
    def SetModTime(self, modtime):
 
1053
        """Set the value of the files last modtime"""
 
1054
        self.file.SetModTime(modtime)
 
1055
 
 
1056
    def SetProperties(self, prop_lst):
 
1057
        """Sets the Lexer Properties from a list of specifications
 
1058
        @param prop_lst: [ ("PROPERTY", "VAL"), ("PROPERTY2", "VAL2) ]
 
1059
 
 
1060
        """
 
1061
        # Parses Property list, ignoring all bad values
 
1062
        for prop in prop_lst:
 
1063
            if len(prop) != 2:
 
1064
                continue
 
1065
            else:
 
1066
                if not isinstance(prop[0], basestring) or not \
 
1067
                   isinstance(prop[1], basestring):
 
1068
                    continue
 
1069
                else:
 
1070
                    self.SetProperty(prop[0], prop[1])
 
1071
        return True
 
1072
 
 
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...
 
1076
 
 
1077
        """
 
1078
        super(EditraBaseStc, self).SetSelection(start, end)
 
1079
 
 
1080
    def SetSelection(self, start, end):
 
1081
        """Override base method to make it work correctly using
 
1082
        Unicode character positions instead of UTF-8.
 
1083
 
 
1084
        """
 
1085
        # STC HELL - some methods require UTF-8 offsets while others work
 
1086
        #            with Unicode...
 
1087
        # Calculate UTF-8 offsets in buffer
 
1088
        unicode_txt = self.GetText()
 
1089
        if start != 0:
 
1090
            start = len(ed_txt.EncodeString(unicode_txt[0:start], 'utf-8'))
 
1091
        if end != 0:
 
1092
            end = len(ed_txt.EncodeString(unicode_txt[0:end], 'utf-8'))
 
1093
        del unicode_txt
 
1094
        super(EditraBaseStc, self).SetSelection(start, end)
 
1095
 
 
1096
    def GetSelection(self):
 
1097
        """Get the selection positions in Unicode instead of UTF-8"""
 
1098
        # STC HELL
 
1099
        # Translate the UTF8 byte offsets to unicode
 
1100
        start, end = super(EditraBaseStc, self).GetSelection()
 
1101
        utf8_txt = self.GetTextUTF8()
 
1102
        if start != 0:
 
1103
            start = len(ed_txt.DecodeString(utf8_txt[0:start], 'utf-8'))
 
1104
        if end != 0:
 
1105
            end = len(ed_txt.DecodeString(utf8_txt[0:end], 'utf-8'))
 
1106
        del utf8_txt
 
1107
        return start, end
 
1108
 
 
1109
    def ShowAutoCompOpt(self, command):
 
1110
        """Shows the autocompletion options list for the command
 
1111
        @param command: command to look for autocomp options for
 
1112
 
 
1113
        """
 
1114
        pos = self.GetCurrentPos()
 
1115
        # symList is a list(completer.Symbol)
 
1116
        symList = self._code['compsvc'].GetAutoCompList(command)
 
1117
        
 
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)
 
1123
            if lst.isspace():
 
1124
                return
 
1125
            self.AutoCompShow(pos - self.WordStartPosition(pos, True), lst)
 
1126
 
 
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()
 
1131
            if curpos != pos:
 
1132
                text = self.GetTextRange(pos, curpos)
 
1133
                self._code['compsvc'].OnCompletionInserted(pos, text)
 
1134
            self.EndUndoAction()
 
1135
            self.SetFocus()
 
1136
 
 
1137
    def GetViewWhiteSpace(self):
 
1138
        """Get if view whitespace is turned on
 
1139
        @return: bool
 
1140
 
 
1141
        """
 
1142
        val = super(EditraBaseStc, self).GetViewWhiteSpace()
 
1143
        return val != wx.stc.STC_WS_INVISIBLE
 
1144
 
 
1145
    def SetViewWhiteSpace(self, viewws):
 
1146
        """Overrides base method to make it a simple bool toggle"""
 
1147
        if viewws:
 
1148
            val = wx.stc.STC_WS_VISIBLEALWAYS
 
1149
        else:
 
1150
            val = wx.stc.STC_WS_INVISIBLE
 
1151
        super(EditraBaseStc, self).SetViewWhiteSpace(val)
 
1152
 
 
1153
    def GetWrapMode(self):
 
1154
        """Get if word wrap is turned on
 
1155
        @return: bool
 
1156
 
 
1157
        """
 
1158
        val = super(EditraBaseStc, self).GetWrapMode()
 
1159
        return val != wx.stc.STC_WRAP_NONE
 
1160
 
 
1161
    def SetWrapMode(self, wrap):
 
1162
        """Overrides base method to make it a simple toggle operation
 
1163
        @param wrap: bool
 
1164
 
 
1165
        """
 
1166
        if wrap:
 
1167
            val = wx.stc.STC_WRAP_WORD
 
1168
        else:
 
1169
            val = wx.stc.STC_WRAP_NONE
 
1170
        super(EditraBaseStc, self).SetWrapMode(val)
 
1171
 
 
1172
    def ShowCallTip(self, command):
 
1173
        """Shows call tip for given command
 
1174
        @param command: command to  look for calltips for
 
1175
 
 
1176
        """
 
1177
        self.CallTipCancel()
 
1178
 
 
1179
        tip = self._code['compsvc'].GetCallTip(command)
 
1180
        if len(tip):
 
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)
 
1185
 
 
1186
    def ToggleColumnMode(self):
 
1187
        """Toggle the column edit mode"""
 
1188
        self.VertEdit.enable(not self.VertEdit.Enabled)
 
1189
 
 
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]
 
1197
 
 
1198
            if end > start and self.GetColumn(sel[1]) == 0:
 
1199
                end = end - 1
 
1200
 
 
1201
            # Analyze the selected line(s)
 
1202
            comment = 0
 
1203
            for line in range(start, end+1):
 
1204
                txt = self.GetLine(line)
 
1205
                if txt.lstrip().startswith(c_start):
 
1206
                    comment += 1
 
1207
 
 
1208
            lcount = end - start
 
1209
            mod = 1
 
1210
            if lcount == 0:
 
1211
                mod = 0
 
1212
 
 
1213
            if comment > (lcount / 2) + mod:
 
1214
                # Uncomment
 
1215
                self.Comment(start, end, True)
 
1216
            else:
 
1217
                self.Comment(start, end, False)
 
1218
 
 
1219
    def ToggleLineNumbers(self, switch=None):
 
1220
        """Toggles the visibility of the line number margin
 
1221
        @keyword switch: force a particular setting
 
1222
 
 
1223
        """
 
1224
        if (switch is None and \
 
1225
            not self.GetMarginWidth(NUM_MARGIN)) or switch:
 
1226
            self.EnableLineNumbers(True)
 
1227
        else:
 
1228
            self.EnableLineNumbers(False)
 
1229
 
 
1230
    @property
 
1231
    def VertEdit(self):
 
1232
        """Vertical edit mode accessor."""
 
1233
        return self.vert_edit
 
1234
 
 
1235
    #---- Style Function Definitions ----#
 
1236
 
 
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
 
1241
 
 
1242
        """
 
1243
        with eclib.Freezer(self) as _tmp:
 
1244
            self.StyleClearAll()
 
1245
            self.SetSyntax(self.GetSyntaxParams())
 
1246
            self.DefineMarkers()
 
1247
        self.Refresh()
 
1248
 
 
1249
    def UpdateBaseStyles(self):
 
1250
        """Update the controls basic styles"""
 
1251
        super(EditraBaseStc, self).UpdateBaseStyles()
 
1252
 
 
1253
        # Set control specific styles
 
1254
        sback = self.GetItemByName('select_style')
 
1255
        if not sback.IsNull():
 
1256
            sback = sback.GetBack()
 
1257
        else:
 
1258
            sback = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
 
1259
        self.VertEdit.SetBlockColor(sback)
 
1260
        self.DefineMarkers()
 
1261
 
 
1262
#-----------------------------------------------------------------------------#
 
1263
 
 
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
 
1270
 
 
1271
        """
 
1272
        ftypeid = buff.GetLangId()
 
1273
        for observer in self.observers:
 
1274
            try:
 
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))
 
1279
        else:
 
1280
            return None
 
1281
 
 
1282
#-----------------------------------------------------------------------------#
 
1283
 
 
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.
 
1288
 
 
1289
    @return: list of (key code, modifier keys, STC action)
 
1290
 
 
1291
    """
 
1292
    # A good reference for these: http://www.yellowbrain.com/stc/keymap.html
 
1293
    return [
 
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),
 
1307
 
 
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),
 
1321
 
 
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),
 
1327
 
 
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),
 
1335
            ]