1#----------------------------------------------------------------------
2# Name:        sliceshell.py
3# Author:      David N. Mashburn, Patrick K. O'Brien
4# Tags:        phoenix-port
5#----------------------------------------------------------------------
6"""Slices is an interactive text control in which a user types in
7commands to be sent to the interpreter.  This particular shell is
8based on wxPython's wxStyledTextCtrl.
9
10Sponsored by Orbtech - Your source for Python programming expertise.
11Slices is a version of shell modified by David Mashburn."""
12
13__author__ = "David N. Mashburn <david.n.mashburn@gmail.com> / "
14__author__ += "Patrick K. O'Brien <pobrien@orbtech.com>"
15
16import wx
17from wx import stc
18from six import PY3
19
20import keyword
21import os
22import sys
23import time
24from functools import cmp_to_key
25
26from .buffer import Buffer
27from . import dispatcher
28from . import editor
29from . import editwindow
30from . import document
31from . import frame
32from .pseudo import PseudoFileIn
33from .pseudo import PseudoFileOut
34from .pseudo import PseudoFileErr
35from .version import VERSION
36from .magic import magic
37from .parse import testForContinuations
38from .path import ls,cd,pwd,sx
39
40
41sys.ps3 = '<-- '  # Input prompt.
42USE_MAGIC=True
43# Force updates from long-running commands after this many seconds
44PRINT_UPDATE_MAX_TIME=2
45
46NAVKEYS = (wx.WXK_HOME, wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
47           wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)
48
49GROUPING_SELECTING=0
50IO_SELECTING = 1
51
52GROUPING_START = 2
53GROUPING_START_FOLDED = 3
54GROUPING_MIDDLE = 4
55GROUPING_END = 5
56INPUT_START = 6
57INPUT_START_FOLDED = 7
58INPUT_MIDDLE = 8
59INPUT_END = 9
60OUTPUT_START = 10
61OUTPUT_START_FOLDED = 11
62OUTPUT_MIDDLE = 12
63OUTPUT_END = 13
64
65OUTPUT_BG = 14
66READLINE_BG = 15
67INPUT_READLINE = 16
68
69# Could add C integration right into the markers...
70# Non-editable file marker for auto-loaded files...
71# Weave VariableInput = 15
72# Weave C code = 16
73# C code = 17 (only for use with Pyrex)
74# Pyrex / Cython code = 18
75
76GROUPING_MASK = ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED |
77                  1<<GROUPING_MIDDLE | 1<<GROUPING_END )
78
79INPUT_MASK = ( 1<<INPUT_START | 1<<INPUT_START_FOLDED |
80               1<<INPUT_MIDDLE | 1<<INPUT_END )
81OUTPUT_MASK = ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED |
82                1<<OUTPUT_MIDDLE | 1<<OUTPUT_END )
83IO_MASK = ( INPUT_MASK | OUTPUT_MASK )
84
85IO_START_MASK = ( 1<<INPUT_START | 1<<OUTPUT_START )
86IO_START_FOLDED_MASK = ( 1<<INPUT_START_FOLDED | 1<<OUTPUT_START_FOLDED )
87IO_ANY_START_MASK = ( 1<<INPUT_START | 1<<OUTPUT_START |
88                      1<<INPUT_START_FOLDED | 1<<OUTPUT_START_FOLDED )
89IO_MIDDLE_MASK = ( 1<<INPUT_MIDDLE | 1<<OUTPUT_MIDDLE )
90IO_END_MASK = ( 1<<INPUT_END | 1<<OUTPUT_END )
91
92usrBinEnvPythonText = '#!/usr/bin/env python\n'
93pyslicesFormatHeaderText = ['#PySlices Save Format Version 1.1 (PySlices v0.9.7.8 and later)\n',
94                            '#PySlices Save Format Version 1.2 (PySlices v0.9.8 and later)\n']
95groupingStartText = '#PySlices Marker Information -- Begin Grouping Slice\n'
96inputStartText = '#PySlices Marker Information -- Begin Input Slice\n'
97outputStartText = '#PySlices Marker Information -- Begin Output Slice\n'
98
99tutorialText = """
100
101                            Tutorial!!!
102------------------------------------------------------------------------
103PySlices is the newest member of the Py suite!
104It is a modified version of PyCrust that supports multi-line commands.
105
106Input and output are contained in "Slices" shown as markers in the left margin.
107Input Slices have RED margins (active, editable).
108Output Slices have BLUE margins (frozen, not editable).
109
110Commands in slices can be on more than one line, as with Sage or Mathematica.
111For example, the command:
112a=1
113b=2
114print(a+b)
115will all run in sequence, much like a script.
116Try running the above Input Slice by clicking somewhere in its text and
117using Ctrl-Return, Shift-Return, or Numpad Enter to execute.
118Previous commands (Old Slices) can be re-edited and run again in place.
119
120Slices can also be:
121 * selceted (click on the margin, Shift-click for multiple selection)
122 * folded (click the margin twice)
123 * selected and deleted (hit delete while selected)
124 * divided (Ctrl-D)
125 * merged (Ctrl-M while selecting adjacent, like-colored slices)
126
127Try deleting the slice above this one by clicking on the red margin.
128
129If you want a more traditional shell feel, try enabling "Shell Mode" in
130"Options->Settings->Shell Mode" (or try PyCrust).
131In Shell Mode, two returns in a row executes the command, and
132    Ctrl-Return and Shift-Return always print newlines.
133
134Saving and opening "sessions" is now supported!  This is a little
135different to other shells where the history is saved.  With PySlices,
136the whole document is saved in a simple text format!
137
138To disable this Tutorial on startup, uncheck it in the menu at:
139"Options->Startup->Show PySlices tutorial"
140
141PySlices may not be the best thing since sliced bread, but
142I hope it makes using Python a little bit sweeter!
143"""
144
145class SlicesShellFrame(frame.Frame, frame.ShellFrameMixin):
146    """Frame containing the sliceshell component."""
147
148    name = 'SlicesShell Frame'
149
150    def __init__(self, parent=None, id=-1, title='PySlicesShell',
151                 pos=wx.DefaultPosition, size=wx.DefaultSize,
152                 style=wx.DEFAULT_FRAME_STYLE, locals=None,
153                 InterpClass=None,
154                 config=None, dataDir=None, filename=None,
155                 *args, **kwds):
156        """Create SlicesShellFrame instance."""
157        frame.Frame.__init__(self, parent, id, title, pos, size, style,shellName='PySlices')
158        frame.ShellFrameMixin.__init__(self, config, dataDir)
159
160        if size == wx.DefaultSize:
161            self.SetSize((750, 525))
162
163        intro = 'PySlices %s - The Flakiest Python Shell... Cut Up!' % VERSION
164        self.SetStatusText(intro.replace('\n', ', '))
165        self.sliceshell = SlicesShell(parent=self, id=-1, introText=intro,
166                               locals=locals, InterpClass=InterpClass,
167                               startupScript=self.startupScript,
168                               execStartupScript=self.execStartupScript,
169                               showPySlicesTutorial=self.showPySlicesTutorial,
170                               enableShellMode=self.enableShellMode,
171                               hideFoldingMargin=self.hideFoldingMargin,
172                               *args, **kwds)
173        self.shell = self.sliceshell
174        self.buffer = self.sliceshell.buffer
175
176        # Override the shell so that status messages go to the status bar.
177        self.sliceshell.setStatusText = self.SetStatusText
178
179        self.sliceshell.SetFocus()
180        self.LoadSettings()
181
182        self.currentDirectory = os.path.expanduser('~')
183
184        if filename!=None:
185            self.bufferOpen(filename)
186
187        self.Bind(wx.EVT_IDLE, self.OnIdle)
188
189
190    def OnClose(self, event):
191        """Event handler for closing."""
192        self.bufferClose()
193        # This isn't working the way I want, but I'll leave it for now.
194        #if self.sliceshell.waiting:
195        #    if event.CanVeto():
196        #        event.Veto(True)
197        #else:
198        #    # TODO: Add check for saving
199        #    self.SaveSettings()
200        #    self.sliceshell.destroy()
201        #    self.Destroy()
202
203    def OnAbout(self, event):
204        """Display an About window."""
205        title = 'About PySliceShell'
206        text = 'PySliceShell %s\n\n' % VERSION + \
207               'Yet another Python shell, only flakier.\n\n' + \
208               'Half-baked by Patrick K. O\'Brien,\n' + \
209               'the other half is still in the oven.\n\n' + \
210               'Platform: %s\n' % sys.platform + \
211               'Python Version: %s\n' % sys.version.split()[0] + \
212               'wxPython Version: %s\n' % wx.VERSION_STRING + \
213               ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:]))
214        dialog = wx.MessageDialog(self, text, title,
215                                  wx.OK | wx.ICON_INFORMATION)
216        dialog.ShowModal()
217        dialog.Destroy()
218
219
220    def OnHelp(self, event):
221        """Show a help dialog."""
222        frame.ShellFrameMixin.OnHelp(self, event)
223
224
225    def LoadSettings(self):
226        if self.config is not None:
227            frame.ShellFrameMixin.LoadSettings(self)
228            frame.Frame.LoadSettings(self, self.config)
229            self.sliceshell.LoadSettings(self.config)
230
231    def SaveSettings(self, force=False):
232        if self.config is not None:
233            frame.ShellFrameMixin.SaveSettings(self,force)
234            if self.autoSaveSettings or force:
235                frame.Frame.SaveSettings(self, self.config)
236                self.sliceshell.SaveSettings(self.config)
237
238    def DoSaveSettings(self):
239        if self.config is not None:
240            self.SaveSettings(force=True)
241            self.config.Flush()
242
243    def OnEnableShellMode(self,event):
244        """Change between Slices Mode and Shell Mode"""
245        frame.Frame.OnEnableShellMode(self,event)
246        self.sliceshell.ToggleShellMode(self.enableShellMode)
247
248    def OnHideFoldingMargin(self,event):
249        """Change between Slices Mode and Shell Mode"""
250        frame.Frame.OnHideFoldingMargin(self,event)
251        self.sliceshell.ToggleFoldingMargin(self.hideFoldingMargin)
252    # Copied Straight from crustslices.py (update both with any changes...)
253    # Stolen Straight from editor.EditorFrame
254    # Modified a little... :)
255    # ||
256    # \/
257    def OnIdle(self, event):
258        """Event handler for idle time."""
259        self._updateTitle()
260        event.Skip()
261
262    def _updateTitle(self):
263        """Show current title information."""
264        title = self.GetTitle()
265        if self.bufferHasChanged():
266            if title.startswith('* '):
267                pass
268            else:
269                self.SetTitle('* ' + title)
270        else:
271            if title.startswith('* '):
272                self.SetTitle(title[2:])
273
274    def hasBuffer(self):
275        """Return True if there is a current buffer."""
276        if self.buffer:
277            return True
278        else:
279            return False
280
281    def bufferClose(self):
282        """Close buffer."""
283        if self.buffer.hasChanged():
284            cancel = self.bufferSuggestSave()
285            if cancel:
286                #event.Veto()
287                return cancel
288        self.SaveSettings()
289        self.sliceshell.destroy()
290        self.bufferDestroy()
291        self.Destroy()
292
293        return False
294
295    def bufferCreate(self, filename=None):
296        """Create new buffer."""
297        self.bufferDestroy()
298        buffer = Buffer()
299        self.panel = panel = wx.Panel(parent=self, id=-1)
300        panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x)
301        editor = Editor(parent=panel)
302        panel.editor = editor
303        sizer = wx.BoxSizer(wx.VERTICAL)
304        sizer.Add(editor.window, 1, wx.EXPAND)
305        panel.SetSizer(sizer)
306        panel.SetAutoLayout(True)
307        sizer.Layout()
308        buffer.addEditor(editor)
309        buffer.open(filename)
310        self.setEditor(editor)
311        self.editor.setFocus()
312        self.SendSizeEvent()
313
314
315    def bufferDestroy(self):
316        """Destroy the current buffer."""
317        if self.buffer:
318            self.editor = None
319            self.buffer = None
320
321
322    def bufferHasChanged(self):
323        """Return True if buffer has changed since last save."""
324        if self.buffer:
325            return self.buffer.hasChanged()
326        else:
327            return False
328
329    def bufferNew(self):
330        """Create new buffer."""
331        cancel = self.bufferSuggestSave()
332        if cancel:
333            return cancel
334        #self.bufferCreate()
335        self.clear()
336        self.SetTitle( 'PySlices')
337        self.sliceshell.NeedsCheckForSave=False
338        self.sliceshell.SetSavePoint()
339        self.buffer.doc = document.Document()
340        self.buffer.name = 'This shell'
341        self.buffer.modulename = self.buffer.doc.filebase
342        cancel = False
343        return cancel
344
345    def bufferOpen(self,file=None):
346        """Open file in buffer."""
347        if self.bufferHasChanged():
348            cancel = self.bufferSuggestSave()
349            if cancel:
350                return cancel
351
352        if file==None:
353            file=wx.FileSelector('Open a PySlices File',
354                                 wildcard='*.pyslices',
355                                 default_path=self.currentDirectory)
356        if file!=None and file!=u'':
357            fid=open(file,'r')
358            self.sliceshell.LoadPySlicesFile(fid)
359            fid.close()
360            self.currentDirectory = os.path.split(file)[0]
361            self.SetTitle( os.path.split(file)[1] + ' - PySlices')
362            self.sliceshell.NeedsCheckForSave=False
363            self.sliceshell.SetSavePoint()
364            self.buffer.doc = document.Document(file)
365            self.buffer.name = self.buffer.doc.filename
366            self.buffer.modulename = self.buffer.doc.filebase
367            self.sliceshell.ScrollToLine(0)
368        return
369
370##     def bufferPrint(self):
371##         """Print buffer."""
372##         pass
373
374##     def bufferRevert(self):
375##         """Revert buffer to version of file on disk."""
376##         pass
377
378    # was self.buffer.save(self): # """Save buffer."""
379    def simpleSave(self,confirmed=False):
380        filepath = self.buffer.doc.filepath
381        self.buffer.confirmed = confirmed
382        if not filepath:
383            return  # XXX Get filename
384        if not os.path.exists(filepath):
385            self.buffer.confirmed = True
386        if not self.buffer.confirmed:
387            self.buffer.confirmed = self.buffer.overwriteConfirm(filepath)
388        if self.buffer.confirmed:
389            try:
390                fid = open(filepath, 'wb')
391                self.sliceshell.SavePySlicesFile(fid)
392            finally:
393                if fid:
394                    fid.close()
395            self.sliceshell.SetSavePoint()
396            self.SetTitle( os.path.split(filepath)[1] + ' - PySlices')
397            self.sliceshell.NeedsCheckForSave=False
398
399    def bufferSave(self):
400        """Save buffer to its file."""
401        if self.buffer.doc.filepath:
402            # self.buffer.save()
403            self.simpleSave(confirmed=True)
404            cancel = False
405        else:
406            cancel = self.bufferSaveAs()
407        return cancel
408
409    def bufferSaveAs(self):
410        """Save buffer to a new filename."""
411        if self.bufferHasChanged() and self.buffer.doc.filepath:
412            cancel = self.bufferSuggestSave()
413            if cancel:
414                return cancel
415        filedir = ''
416        if self.buffer and self.buffer.doc.filedir:
417            filedir = self.buffer.doc.filedir
418        result = editor.saveSingle(title='Save PySlices File',directory=filedir,
419                                   wildcard='PySlices Files (*.pyslices)|*.pyslices')
420        if result.path not in ['',None]:
421            if result.path[-9:]!=".pyslices":
422                result.path+=".pyslices"
423
424            self.buffer.doc = document.Document(result.path)
425            self.buffer.name = self.buffer.doc.filename
426            self.buffer.modulename = self.buffer.doc.filebase
427            self.simpleSave(confirmed=True) # allow overwrite
428            cancel = False
429        else:
430            cancel = True
431        return cancel
432
433    def bufferSaveACopy(self):
434        """Save buffer to a new filename."""
435        filedir = ''
436        if self.buffer and self.buffer.doc.filedir:
437            filedir = self.buffer.doc.filedir
438        result = editor.saveSingle(title='Save a Copy of PySlices File',directory=filedir,
439                                   wildcard='PySlices Files (*.pyslices)|*.pyslices')
440
441        if result.path not in ['',None]:
442            if result.path[-9:]!=".pyslices":
443                result.path+=".pyslices"
444
445            # if not os.path.exists(result.path):
446            try: # Allow overwrite...
447                fid = open(result.path, 'wb')
448                self.sliceshell.SavePySlicesFile(fid)
449            finally:
450                if fid:
451                    fid.close()
452
453            cancel = False
454        else:
455            cancel = True
456        return cancel
457
458    def bufferSuggestSave(self):
459        """Suggest saving changes.  Return True if user selected Cancel."""
460        result = editor.messageDialog(parent=None,
461                               message='%s has changed.\n'
462                                       'Would you like to save it first'
463                                       '?' % self.buffer.name,
464                               title='Save current file?',
465                               style=wx.YES_NO | wx.CANCEL | wx.NO_DEFAULT |
466                                     wx.CENTRE | wx.ICON_QUESTION )
467        if result.positive:
468            cancel = self.bufferSave()
469        else:
470            cancel = result.text == 'Cancel'
471        return cancel
472
473    def updateNamespace(self):
474        """Update the buffer namespace for autocompletion and calltips."""
475        if self.buffer.updateNamespace():
476            self.SetStatusText('Namespace updated')
477        else:
478            self.SetStatusText('Error executing, unable to update namespace')
479
480
481
482# TODO : Update the help text
483HELP_TEXT = """\
484* Key bindings:
485Home              Go to the beginning of the line.
486End               Go to the end of the line.
487Shift+Home        Select to the beginning of the line.
488Shift+End         Select to the end of the line.
489Ctrl-Home         Jump to the beginning of the slice;
490                  If already there, jump to beginning of previous slice
491Ctrl-End          Jump to the end of the slice;
492                  If already there, jump to end of next slice
493Ctrl-PageUp       Jump to the beginning of the shell
494Ctrl-PageDown     Jump to the end of the shell
495Ctrl+C            Copy selected text, removing prompts.
496Ctrl+Shift+C      Copy selected text, retaining prompts.
497Alt+C             Copy to the clipboard, including prefixed prompts.
498Ctrl+X            Cut selected text.
499Ctrl+V            Paste from clipboard.
500Ctrl+Shift+V      Paste and run multiple commands from clipboard.
501Ctrl+Up Arrow     Retrieve Previous History item.
502Alt+P             Retrieve Previous History item.
503Ctrl+Down Arrow   Retrieve Next History item.
504Alt+N             Retrieve Next History item.
505Shift+Up Arrow    Insert Previous History item.
506Shift+Down Arrow  Insert Next History item.
507F8                Command-completion of History item.
508                  (Type a few characters of a previous command and press F8.)
509Ctrl+]            Increase font size.
510Ctrl+[            Decrease font size.
511Ctrl+=            Default font size.
512
513Ctrl-Space        Show Auto Completion.
514Ctrl-Shift-Space  Show Call Tip.
515Ctrl-Shift-H      Complete Text from History.
516
517Ctrl+F            Search
518Ctrl+G            Search next
519F12               on/off "free-edit" mode
520                  For testing only -- This does not preserve markers!
521
522In "Slices Mode":
523Return            Insert new line
524Enter (Numpad)    Run command in slice
525Ctrl+Return       ""
526Shift+Return      ""
527
528In "Shell Mode":
529Return or Enter   Insert a new line
530Ctrl+Return       ""
531Shift+Return      ""
5322 Returns in a row   Run command in slice
533"""
534
535class SlicesShellFacade:
536    """Simplified interface to all shell-related functionality.
537
538    This is a semi-transparent facade, in that all attributes of other
539    are accessible, even though only some are visible to the user."""
540
541    name = 'SlicesShell Interface'
542
543    def __init__(self, other):
544        """Create a SlicesShellFacade instance."""
545        d = self.__dict__
546        d['other'] = other
547        d['helpText'] = HELP_TEXT
548
549    def help(self):
550        """Display some useful information about how to use the slices shell."""
551        self.write(self.helpText,type='Output')
552
553    def __getattr__(self, name):
554        if hasattr(self.other, name):
555            return getattr(self.other, name)
556        else:
557            raise AttributeError(name)
558
559    def __setattr__(self, name, value):
560        if name in self.__dict__:
561            self.__dict__[name] = value
562        elif hasattr(self.other, name):
563            setattr(self.other, name, value)
564        else:
565            raise AttributeError(name)
566
567    def _getAttributeNames(self):
568        """Return list of magic attributes to extend introspection."""
569        list = [
570            'about',
571            'ask',
572            'autoCallTip',
573            'autoComplete',
574            'autoCompleteAutoHide',
575            'autoCompleteCaseInsensitive',
576            'autoCompleteIncludeDouble',
577            'autoCompleteIncludeMagic',
578            'autoCompleteIncludeSingle',
579            'callTipInsert',
580            'clear',
581            'pause',
582            'prompt',
583            'quit',
584            'redirectStderr',
585            'redirectStdin',
586            'redirectStdout',
587            'run',
588            'runfile',
589            'wrap',
590            'zoom',
591            ]
592        list.sort()
593        return list
594
595DISPLAY_TEXT="""
596Author: %r
597Py Version: %s
598Python Version: %s
599wxPython Version: %s
600wxPython PlatformInfo: %s
601Platform: %s"""
602
603class SlicesShell(editwindow.EditWindow):
604    """Notebook Shell based on StyledTextCtrl."""
605
606    name = 'SlicesShell'
607
608    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
609                 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
610                 introText='', locals=None, InterpClass=None,
611                 startupScript=None, execStartupScript=True,
612                 showPySlicesTutorial=True,enableShellMode=False,
613                 useStockId=True,
614                 hideFoldingMargin=False, *args, **kwds):
615        """Create Shell instance."""
616        editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
617        self.wrap()
618        if locals is None:
619            import __main__
620            locals = __main__.__dict__
621
622        # Grab these so they can be restored by self.redirect* methods.
623        self.stdin = sys.stdin
624        self.stdout = sys.stdout
625        self.stderr = sys.stderr
626
627        # Import a default interpreter class if one isn't provided.
628        if InterpClass == None:
629            from .interpreter import Interpreter
630        else:
631            Interpreter = InterpClass
632
633        # Create a replacement for stdin.
634        self.reader = PseudoFileIn(self.readline, self.readlines)
635        self.reader.input = ''
636        self.reader.isreading = False
637
638        # Set up the interpreter.
639        self.interp = Interpreter(locals=locals,
640                                  rawin=self.raw_input,
641                                  stdin=self.reader,
642                                  stdout=PseudoFileOut(self.writeOut),
643                                  stderr=PseudoFileErr(self.writeErr),
644                                  *args, **kwds)
645
646        # Set up the buffer.
647        self.buffer = Buffer()
648        self.id = self.GetId()
649        self.buffer.addEditor(self)
650        self.buffer.name='This shell'
651        self.NeedsCheckForSave=False
652
653        # Find out for which keycodes the interpreter will autocomplete.
654        self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
655
656        # Keep track of the last non-continuation prompt positions.
657        # Removed all references to these... solved a lot of odd bugs...
658        # self.promptPosStart = 0
659        # self.promptPosEnd = 0
660
661        # Keep track of multi-line commands.
662        self.more = False
663
664        # Use Margins to track input / output / slice number
665        self.margins = True
666
667        # For use with forced updates during long-running scripts
668        self.lastUpdate=None
669
670        if self.margins:
671            # margin 1 is already defined for the line numbers
672            #  may eventually change it back to 0 like it ought to be...
673            self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
674            self.SetMarginType(3, stc.STC_MARGIN_SYMBOL)
675            self.SetMarginType(4, stc.STC_MARGIN_SYMBOL)
676            self.SetMarginWidth(2, 22)
677            self.SetMarginWidth(3, 22)
678            self.SetMarginWidth(4, 12)
679            self.SetMarginSensitive(2,True)
680            self.SetMarginSensitive(3,True)
681            self.SetMarginSensitive(4,True)
682            self.SetProperty("fold", "1")
683            # tabs are bad, use spaces
684            self.SetProperty("tab.timmy.whinge.level", "4")
685            self.SetMargins(0,0)
686
687
688            self.SetMarginMask(2, GROUPING_MASK | 1<<GROUPING_SELECTING )
689            # Display Markers -24...
690            self.SetMarginMask(3, IO_MASK | 1<<IO_SELECTING | 1<<READLINE_BG | 1<<INPUT_READLINE )
691            self.SetMarginMask(4, stc.STC_MASK_FOLDERS)
692            # Set the mask for the line markers, too...
693            self.SetMarginMask(1, 0)
694
695            if hideFoldingMargin:
696                self.SetMarginWidth(4, 0)
697            self.hideFoldingMargin=hideFoldingMargin
698
699            sel_color="#E0E0E0"
700            grouping_color="black"
701            input_color="red"
702            output_color="blue"
703
704            self.MarkerDefine(GROUPING_SELECTING,    stc.STC_MARK_FULLRECT,
705                              sel_color, sel_color)
706            self.MarkerDefine(IO_SELECTING,          stc.STC_MARK_FULLRECT,
707                              sel_color, sel_color)
708
709            self.MarkerDefine(GROUPING_START,        stc.STC_MARK_BOXMINUS,
710                              "white", grouping_color)
711            self.MarkerDefine(GROUPING_START_FOLDED, stc.STC_MARK_BOXPLUS,
712                              "white", grouping_color)
713            self.MarkerDefine(GROUPING_MIDDLE,       stc.STC_MARK_VLINE,
714                              "white", grouping_color)
715            self.MarkerDefine(GROUPING_END,          stc.STC_MARK_LCORNER,
716                              "white", grouping_color)
717
718            self.MarkerDefine(READLINE_BG, stc.STC_MARK_FULLRECT,
719                              wx.Colour(191,191,191), wx.Colour(191,191,191))
720            self.MarkerDefine(INPUT_READLINE, stc.STC_MARK_CHARACTER+ord('<'),
721                              input_color, wx.Colour(191,191,191))
722
723            if enableShellMode:
724                self.mode='ShellMode'
725            else:
726                self.mode='SlicesMode'
727
728            self.execOnNextReturn=False
729            if self.mode=='SlicesMode':
730                self.MarkerDefine(INPUT_START,        stc.STC_MARK_BOXMINUS,
731                                  "white", input_color)
732                self.MarkerDefine(INPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
733                                  "white", input_color)
734                self.MarkerDefine(INPUT_MIDDLE,       stc.STC_MARK_VLINE,
735                                  "white", input_color)
736                self.MarkerDefine(INPUT_END,          stc.STC_MARK_LCORNER,
737                                  "white", input_color)
738            elif self.mode=='ShellMode':
739                self.MarkerDefine(INPUT_START,        stc.STC_MARK_ARROWS,
740                                  input_color, "white")
741                self.MarkerDefine(INPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
742                                  "white", input_color)
743                self.MarkerDefine(INPUT_MIDDLE,       stc.STC_MARK_DOTDOTDOT,
744                                  input_color, "white")
745                self.MarkerDefine(INPUT_END,          stc.STC_MARK_DOTDOTDOT,
746                                  input_color, "white")
747
748            self.MarkerDefine(OUTPUT_START,           stc.STC_MARK_BOXMINUS,
749                              "white", output_color)
750            self.MarkerDefine(OUTPUT_START_FOLDED,    stc.STC_MARK_BOXPLUS,
751                              "white", output_color)
752            self.MarkerDefine(OUTPUT_MIDDLE,          stc.STC_MARK_VLINE,
753                              "white", output_color)
754            self.MarkerDefine(OUTPUT_END,             stc.STC_MARK_LCORNER,
755                              "white", output_color)
756
757            self.MarkerDefine(OUTPUT_BG,             stc.STC_MARK_BACKGROUND,
758                              "white", wx.Colour(242,242,255))
759
760            # Markers for folding margin...
761            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS,
762                              "white", "#808080")
763            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,
764                              "white", "#808080")
765            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,
766                              "white", "#808080")
767            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,
768                              "white", "#808080")
769            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,
770                              "white", "#808080")
771            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED,
772                              "white", "#808080")
773            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,
774                              "white", "#808080")
775
776        # Create the command history.  Commands are added into the
777        # front of the list (ie. at index 0) as they are entered.
778        # self.historyIndex is the current position in the history; it
779        # gets incremented as you retrieve the previous command,
780        # decremented as you retrieve the next, and reset when you hit
781        # Enter.  self.historyIndex == -1 means you're on the current
782        # command, not in the history.
783        self.history = []
784        self.historyIndex = -1
785
786        #DNM -- disable these markers...
787        #seb add mode for "free edit"
788        self.noteMode = 0
789        #self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT)  # marker for hidden
790        self.searchTxt = ""
791
792        # Assign handlers for keyboard events.
793        self.Bind(wx.EVT_CHAR, self.OnChar)
794        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
795
796        self.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
797        # TODO : Add a general functions to handle mouse clicks in the
798        # TODO:  STC window whose sole purpose is to make it so
799        # TODO:  that margin selection becomes unselected...
800
801        # Assign handler for the context menu
802        self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
803        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
804
805        # add the option to not use the stock IDs; otherwise the context menu
806        # may not work on Mac without adding the proper IDs to the menu bar
807        if useStockId:
808            self.ID_CUT = wx.ID_CUT
809            self.ID_COPY = wx.ID_COPY
810            self.ID_PASTE = wx.ID_PASTE
811            self.ID_SELECTALL = wx.ID_SELECTALL
812            self.ID_CLEAR = wx.ID_CLEAR
813            self.ID_UNDO = wx.ID_UNDO
814            self.ID_REDO = wx.ID_REDO
815        else:
816            self.ID_CUT = wx.NewIdRef()
817            self.ID_COPY = wx.NewIdRef()
818            self.ID_PASTE = wx.NewIdRef()
819            self.ID_SELECTALL = wx.NewIdRef()
820            self.ID_CLEAR = wx.NewIdRef()
821            self.ID_UNDO = wx.NewIdRef()
822            self.ID_REDO = wx.NewIdRef()
823
824        # Assign handlers for edit events
825        self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=self.ID_CUT)
826        self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=self.ID_COPY)
827        self.Bind(wx.EVT_MENU, lambda evt: self.CopyWithPrompts(), id=frame.ID_COPY_PLUS)
828        self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=self.ID_PASTE)
829        self.Bind(wx.EVT_MENU, lambda evt: self.PasteAndRun(), id=frame.ID_PASTE_PLUS)
830        self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=self.ID_SELECTALL)
831        self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=self.ID_CLEAR)
832        self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=self.ID_UNDO)
833        self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=self.ID_REDO)
834
835        # Assign handler for idle time.
836        self.waiting = False
837        self.Bind(wx.EVT_IDLE, self.OnIdle)
838
839        # Display the introductory banner information.
840        self.showIntro(introText)
841
842        outStart,outEnd,inStart,inMiddle,inEnd = [[],[],[],[],[]]
843
844        # Make "executed startup script move to the top..."
845        if showPySlicesTutorial:
846            self.write(tutorialText,'Output')
847            tutStart=5
848            testStart=16
849            outStart=[tutStart,testStart+3]
850            outEnd=[tutStart-1,testStart-1]
851            inStart=[testStart]
852            inMiddle=[testStart+1]
853            inEnd=[testStart+2]
854
855        # Assign some pseudo keywords to the interpreter's namespace.
856        self.setBuiltinKeywords()
857
858        # Add 'shell' to the interpreter's local namespace.
859        self.setLocalShell()
860
861        # Do this last so the user has complete control over their
862        # environment.  They can override anything they want.
863        if execStartupScript:
864            if startupScript is None:
865                startupScript = os.environ.get('PYTHONSTARTUP')
866            self.execStartupScript(startupScript)
867        else:
868            self.prompt()
869
870        outStart+=[0]
871        outEnd+=[self.GetLineCount()-2]
872        inStart+=[self.GetLineCount()-1]
873        # Set all the line markers to the proper initial states...
874        for i in range(self.GetLineCount()):
875            self.clearGroupingMarkers(i)
876            self.clearIOMarkers(i)
877            if i in outStart:
878                self.MarkerAdd(i,GROUPING_START)
879                self.MarkerAdd(i,OUTPUT_START)
880                # Background color is confusing for tutorial... skip it!
881                #self.MarkerAdd(i,OUTPUT_BG)
882            elif i in outEnd:
883                self.MarkerAdd(i,GROUPING_END)
884                self.MarkerAdd(i,OUTPUT_END)
885                #self.MarkerAdd(i,OUTPUT_BG)
886            elif i in inStart:
887                self.MarkerAdd(i,GROUPING_START)
888                self.MarkerAdd(i,INPUT_START)
889            elif i in inMiddle:
890                self.MarkerAdd(i,GROUPING_MIDDLE)
891                self.MarkerAdd(i,INPUT_MIDDLE)
892            elif i in inEnd:
893                self.MarkerAdd(i,GROUPING_END)
894                self.MarkerAdd(i,INPUT_END)
895            else:
896                self.MarkerAdd(i,GROUPING_MIDDLE)
897                self.MarkerAdd(i,OUTPUT_MIDDLE)
898                #self.MarkerAdd(i,OUTPUT_BG)
899
900        self.SliceSelection=False
901        self.runningSlice=None
902
903        ## NOTE:  See note at bottom of this file...
904        ## #seb: File drag and drop
905        ## self.SetDropTarget( FileDropTarget(self) )
906
907        #ADD UNDO
908        # Everywhere "ADD UNDO" appears, there is new code to handle markers
909        self.EmptyUndoBuffer()
910
911        wx.CallAfter(self.ScrollToLine, 0)
912
913    def ToggleShellMode(self,enableShellMode=None):
914        if enableShellMode==None:
915            if self.mode=='ShellMode':  self.mode='SlicesMode'
916            elif self.mode=='SlicesMode': self.mode='ShellMode'
917        elif enableShellMode:
918            self.mode='ShellMode'
919        else:
920            self.mode='SlicesMode'
921
922        input_color="red"
923        if self.mode=='SlicesMode':
924            self.MarkerDefine(INPUT_START,           stc.STC_MARK_BOXMINUS,
925                              "white", input_color)
926            self.MarkerDefine(INPUT_START_FOLDED,    stc.STC_MARK_BOXPLUS,
927                              "white", input_color)
928            self.MarkerDefine(INPUT_MIDDLE,          stc.STC_MARK_VLINE,
929                              "white", input_color)
930            self.MarkerDefine(INPUT_END,             stc.STC_MARK_LCORNER,
931                              "white", input_color)
932        elif self.mode=='ShellMode':
933            self.MarkerDefine(INPUT_START,           stc.STC_MARK_ARROWS,
934                              input_color, "white")
935            self.MarkerDefine(INPUT_START_FOLDED,    stc.STC_MARK_BOXPLUS,
936                              "white", input_color)
937            self.MarkerDefine(INPUT_MIDDLE,          stc.STC_MARK_DOTDOTDOT,
938                              input_color, "white")
939            self.MarkerDefine(INPUT_END,             stc.STC_MARK_DOTDOTDOT,
940                              input_color, "white")
941
942    def ToggleFoldingMargin(self,hideFoldingMargin=None):
943        if hideFoldingMargin==None:
944            self.hideFoldingMargin = not self.hideFoldingMargin
945        else:
946            self.hideFoldingMargin = hideFoldingMargin
947
948        if self.hideFoldingMargin:
949            self.SetMarginWidth(4, 0)
950        else:
951            self.SetMarginWidth(4, 12)
952
953    def clearHistory(self):
954        self.history = []
955        self.historyIndex = -1
956        dispatcher.send(signal="SlicesShell.clearHistory")
957
958
959    def destroy(self):
960        del self.interp
961
962    def setFocus(self):
963        """Set focus to the slices shell."""
964        self.SetFocus()
965
966    def OnIdle(self, event):
967        """Free the CPU to do other things."""
968        if self.waiting:
969            time.sleep(0.05)
970        event.Skip()
971
972    def showIntro(self, text=''):
973        """Display introductory text in the slices shell."""
974        if text:
975            self.write(text,type='Output')
976        try:
977            if self.interp.introText:
978                if text and not text.endswith(os.linesep):
979                    self.write(os.linesep,type='Output')
980                self.write(self.interp.introText,type='Output')
981        except AttributeError:
982            pass
983
984    def setBuiltinKeywords(self):
985        """Create pseudo keywords as part of builtins.
986
987        This sets "close", "exit" and "quit" to a helpful string.
988        """
989        from six import PY3
990        if PY3:
991            import builtins
992        else:
993            import __builtin__
994            builtins = __builtin__
995        builtins.close = builtins.exit = builtins.quit = \
996            'Click on the close button to leave the application.'
997        builtins.cd = cd
998        builtins.ls = ls
999        builtins.pwd = pwd
1000        builtins.sx = sx
1001
1002
1003    def quit(self):
1004        """Quit the application."""
1005        # XXX Good enough for now but later we want to send a close event.
1006        # In the close event handler we can make sure they want to
1007        # quit.  Other applications, like PythonCard, may choose to
1008        # hide rather than quit so we should just post the event and
1009        # let the surrounding app decide what it wants to do.
1010        self.write('Click on the close button to leave the application.',
1011                   type='Output')
1012
1013
1014    def setLocalShell(self):
1015        """Add 'slicesshell' to locals as reference to ShellFacade instance."""
1016        self.interp.locals['slicesshell'] = SlicesShellFacade(other=self)
1017
1018
1019    def execStartupScript(self, startupScript):
1020        """Execute the user's PYTHONSTARTUP script if they have one."""
1021        if startupScript and os.path.isfile(startupScript):
1022            text = 'Startup script executed: ' + startupScript
1023            if PY3:
1024                self.push('print(%r)' % text)
1025                self.push('with open(%r, "r") as f:\n'
1026                          '    exec(f.read())\n' % (startupScript))
1027            else:
1028                self.push('print(%r); execfile(%r)' % (text, startupScript))
1029            self.interp.startupScript = startupScript
1030        else:
1031            self.push('')
1032
1033
1034    def about(self):
1035        """Display information about Py."""
1036        text = DISPLAY_TEXT % \
1037        (__author__, VERSION,
1038         sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo),
1039         sys.platform)
1040        self.write(text.strip(),type='Output')
1041
1042    def BreakTextIntoCommands(self,text):
1043        """Turn a text block into multiple multi-line commands."""
1044
1045        #text = text.lstrip() # This should not be done!
1046        text = self.fixLineEndings(text)
1047        text = self.lstripPrompt(text)
1048        text = text.replace(os.linesep, '\n')
1049        lines = text.split('\n')
1050
1051        continuations = testForContinuations(text)
1052
1053        if len(continuations)==2: # Error case...
1054            return None,continuations[1]
1055        elif len(continuations)==4:
1056            stringContinuationList,indentationBlockList, \
1057            lineContinuationList,parentheticalContinuationList = continuations
1058
1059        commands = []
1060        command = ''
1061        for j,line in enumerate(lines):
1062            lstrip = line.lstrip()
1063
1064            # Get the first alnum word:
1065            first_word=[]
1066            for i in lstrip:
1067                if i.isalnum():
1068                    first_word.append(i)
1069                else:
1070                    break
1071            first_word = ''.join(first_word)
1072
1073            # Continue the command if it is blank, has indentation,
1074            # starts with else, elif,except, or finally
1075            # or previous line had a line continuation \
1076
1077            if j==0:
1078                stringCont = False
1079                lineCont=False
1080            else:
1081                stringCont = stringContinuationList[j-1]
1082                lineCont = lineContinuationList[j-1]
1083
1084            if line.strip() == '' or lstrip != line or \
1085               first_word in ['else','elif','except','finally'] or \
1086               stringCont or lineCont:
1087                # Multiline command. Add to the command.
1088                command += '\n'
1089                command += line
1090            else:
1091                # New command.
1092                if command:
1093                    # Add the previous command to the list.
1094                    commands.append(command)
1095                # Start a new command, which may be multiline.
1096                command = line
1097
1098        commands.append(command)
1099
1100        return commands
1101
1102    def MarkerSet(self,line,markerBitsSet):
1103        """MarkerSet is the Set command for MarkerGet"""
1104        markerBits=self.MarkerGet(line)
1105
1106        numMarkers=14
1107        for i in range(numMarkers):
1108            if (markerBitsSet & (1<<i)) and not (markerBits & (1<<i)):
1109                self.MarkerAdd(line,i)
1110            elif not (markerBitsSet & (1<<i)) and (markerBits & (1<<i)):
1111                self.MarkerDelete(line,i)
1112    def GetGroupingSlice(self,line_num=None):
1113        """Get the start/stop lines for the slice based on any line in the slice"""
1114        if line_num==None:
1115            line_num=self.GetCurrentLine()
1116
1117        num_lines=self.GetLineCount()
1118
1119        for i in range(line_num,-1,-1):
1120            if self.MarkerGet(i) & (1<<GROUPING_START | 1<<GROUPING_START_FOLDED):
1121                break
1122        start_line=i
1123
1124        addition=0
1125
1126        for i in range(line_num,num_lines):
1127            if self.MarkerGet(i) & 1<<GROUPING_END:
1128                break
1129            elif (i>line_num) and ( self.MarkerGet(i)
1130                         & (1<<GROUPING_START | 1<<GROUPING_START_FOLDED) ):
1131                addition=-1
1132                break # the solo case...
1133        stop_line=i+addition
1134
1135        return start_line,stop_line
1136
1137    def GetIOSlice(self,line_num=None):
1138        """Get the start/stop lines for the slice based on any line in the slice"""
1139        if line_num==None:
1140            line_num=self.GetCurrentLine()
1141
1142        num_lines=self.GetLineCount()
1143
1144        for i in range(line_num,-1,-1):
1145            if self.MarkerGet(i) & IO_ANY_START_MASK:
1146                break
1147        start_line=i
1148
1149        addition=0
1150
1151        for i in range(line_num,num_lines):
1152            if self.MarkerGet(i) & IO_END_MASK:
1153                break
1154            elif (i>line_num) and (self.MarkerGet(i) & IO_ANY_START_MASK):
1155                addition=-1
1156                break # the solo case...
1157        stop_line=i+addition
1158
1159        return start_line,stop_line
1160
1161    def FoldGroupingSlice(self,line_num=None):
1162        if line_num==None:
1163            line_num=self.GetCurrentLine()
1164
1165        start,end=self.GetGroupingSlice(line_num)
1166        self.HideLines(start+1,end)
1167        marker=self.MarkerGet(start)
1168        self.clearGroupingMarkers(start)
1169        self.MarkerAdd(start,GROUPING_START_FOLDED)
1170        self.clearIOMarkers(start)
1171        if marker & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ):
1172            self.MarkerAdd(start,INPUT_START_FOLDED)
1173        elif marker & ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED ):
1174            self.MarkerAdd(start,OUTPUT_START_FOLDED)
1175            self.MarkerAdd(start,OUTPUT_BG)
1176        else:
1177            pass #print('Bad Markers!!!')
1178    def FoldIOSlice(self,line_num=None):
1179        if line_num==None:
1180            line_num=self.GetCurrentLine()
1181
1182        start,end=self.GetIOSlice(line_num)
1183        self.HideLines(start+1,end)
1184        marker=self.MarkerGet(start)
1185        if (self.MarkerGet(start) & \
1186               (1<<GROUPING_START | 1<<GROUPING_START_FOLDED )) and \
1187               (self.MarkerGet(end) & 1<<GROUPING_END):
1188            self.clearGroupingMarkers(start)
1189            self.MarkerAdd(start,GROUPING_START_FOLDED)
1190        self.clearIOMarkers(start)
1191        if marker & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ):
1192            self.MarkerAdd(start,INPUT_START_FOLDED)
1193        elif marker & ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED ):
1194            self.MarkerAdd(start,OUTPUT_START_FOLDED)
1195            self.MarkerAdd(start,OUTPUT_BG)
1196        else:
1197            pass #print('Bad Markers!!!')
1198    def UnFoldGroupingSlice(self,line_num=None):
1199        if line_num==None:
1200            line_num=self.GetCurrentLine()
1201
1202        start,end=self.GetGroupingSlice(line_num)
1203        self.ShowLines(start+1,end)
1204        self.clearGroupingMarkers(start)
1205        self.MarkerAdd(start,GROUPING_START)
1206        for i in range(start,end):
1207            marker=self.MarkerGet(i)
1208            if marker & (1<<INPUT_START | 1<<INPUT_START_FOLDED):
1209                self.clearIOMarkers(i)
1210                self.MarkerAdd(i,INPUT_START)
1211            elif marker & (1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED):
1212                self.clearIOMarkers(i)
1213                self.MarkerAdd(i,OUTPUT_START)
1214                self.MarkerAdd(i,OUTPUT_BG)
1215
1216    def UnFoldIOSlice(self,line_num=None):
1217        if line_num==None:
1218            line_num=self.GetCurrentLine()
1219
1220        start,end=self.GetIOSlice(line_num)
1221        self.ShowLines(start+1,end)
1222        marker=self.MarkerGet(start)
1223        if (self.MarkerGet(start) & \
1224               (1<<GROUPING_START | 1<<GROUPING_START_FOLDED )) and \
1225               (self.MarkerGet(end) & 1<<GROUPING_END):
1226            self.clearGroupingMarkers(start)
1227            self.MarkerAdd(start,GROUPING_START)
1228        self.clearIOMarkers(start)
1229        if marker & 1<<INPUT_START_FOLDED:
1230            self.MarkerAdd(start,INPUT_START)
1231        elif marker & 1<<OUTPUT_START_FOLDED:
1232            self.MarkerAdd(start,OUTPUT_START)
1233            self.MarkerAdd(start,OUTPUT_BG)
1234
1235    def DeleteOutputSlicesAfter(self,line_num=None):
1236        """Delete all outputs after an input"""
1237        if line_num==None:
1238            line_num=self.GetCurrentLine()
1239
1240        num_lines=self.GetLineCount()
1241
1242        if self.MarkerGet(line_num) & OUTPUT_MASK:
1243            #print('You can only run "DeleteOutputSlicesAfter" from an Input slice!')
1244            return
1245
1246        startIn,endIn=self.GetIOSlice(line_num)
1247        startGrouping,endGrouping=self.GetGroupingSlice(line_num)
1248
1249        if endIn<endGrouping:
1250            self.SetSelection(self.PositionFromLine(endIn+1),
1251                              self.PositionFromLine(endGrouping+1))
1252            self.ReplaceSelection('',sliceDeletion=True)
1253
1254        new_pos=self.GetLineEndPosition(line_num)
1255        self.SetCurrentPos(new_pos)
1256        self.SetSelection(new_pos,new_pos)
1257
1258    def SplitSlice(self,line_num=None):
1259        if line_num==None:
1260            line_num=self.GetCurrentLine()
1261
1262        start_num,end_num=self.GetIOSlice(line_num)
1263
1264        if self.MarkerGet(line_num) & INPUT_MASK:
1265            type='Input'
1266            start=INPUT_START
1267            end=INPUT_END
1268            splitGrouping=True
1269        elif self.MarkerGet(line_num) & OUTPUT_MASK:
1270            type='Output'
1271            start=OUTPUT_START
1272            end=OUTPUT_END
1273            splitGrouping=False
1274
1275        if start_num==end_num:
1276            return # Can't split one line!
1277        elif start_num==line_num:
1278            self.clearIOMarkers(line_num+1)
1279            self.MarkerAdd(line_num+1,start)
1280            if type=='Output': self.MarkerAdd(line_num+1,OUTPUT_BG)
1281            if splitGrouping:
1282                self.clearGroupingMarkers(line_num+1)
1283                self.MarkerAdd(line_num+1,GROUPING_START)
1284        else:
1285            self.clearIOMarkers(line_num)
1286            self.MarkerAdd(line_num,start)
1287            if type=='Output': self.MarkerAdd(line_num,OUTPUT_BG)
1288            if splitGrouping:
1289                self.clearGroupingMarkers(line_num)
1290                self.MarkerAdd(line_num,GROUPING_START)
1291            if line_num-1>start_num:
1292                self.clearIOMarkers(line_num-1)
1293                self.MarkerAdd(line_num-1,end)
1294                if type=='Output': self.MarkerAdd(line_num-1,OUTPUT_BG)
1295                if splitGrouping:
1296                    self.clearGroupingMarkers(line_num-1)
1297                    self.MarkerAdd(line_num-1,GROUPING_END)
1298
1299    def BackspaceWMarkers(self,force=False):
1300        # Warning: This is not good at checking for bad markers!
1301        c_before=self.GetCharAt(self.GetCurrentPos() - 1)
1302        c_after=self.GetCharAt(self.GetCurrentPos())
1303
1304        if c_before==0:
1305            # Disallow deleting the first line or it will destroy the markers...
1306            return False
1307        elif c_before in (ord('\n'),ord('\r')):
1308            line_num=self.GetCurrentLine()
1309
1310            marker=self.MarkerGet(line_num)
1311            marker_before=self.MarkerGet(line_num-1)
1312            marker_after=self.MarkerGet(line_num+1)
1313            if marker_before & ( 1<<GROUPING_END ) :
1314                return False # Disallow deleting lines between slices...
1315            elif marker & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
1316                return False # Disallow deleting lines between slices...
1317            else:
1318                if marker_before & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
1319                    self.clearGroupingMarkers(line_num)
1320                elif marker & ( 1<<GROUPING_END ) :
1321                    self.clearGroupingMarkers(line_num-1)
1322
1323            if (marker_before & 1<<INPUT_END) and force:
1324                # Special case for use in processLine
1325                self.clearIOMarkers(line_num)
1326            elif marker_before & (1<<INPUT_END | 1<<OUTPUT_END):
1327                return False # Disallow deleting lines between slices...
1328            elif marker & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ) :
1329                return False # Disallow deleting lines between slices...
1330            else:
1331                if marker_before & (1<<INPUT_START  | 1<<INPUT_START_FOLDED |
1332                                    1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED):
1333                    self.clearIOMarkers(line_num)
1334                elif marker & ( 1<<INPUT_END | 1<<OUTPUT_END ) :
1335                    self.clearIOMarkers(line_num-1)
1336
1337        return True # If everything went well, return True and do the delete...
1338
1339    def ForwardDeleteWMarkers(self):
1340        c_before=self.GetCharAt(self.GetCurrentPos() - 1)
1341        c_after=self.GetCharAt(self.GetCurrentPos())
1342        if c_after==0:
1343            # Disallow deleting the first line or it will destroy the markers...
1344            return False
1345        elif c_after in (ord('\n'),ord('\r')):
1346            line_num=self.GetCurrentLine()
1347
1348            marker=self.MarkerGet(line_num)
1349            marker_before=self.MarkerGet(line_num-1)
1350            marker_after=self.MarkerGet(line_num+1)
1351            if marker & ( 1<<GROUPING_END ) :
1352                return False # Disallow deleting lines between slices...
1353            elif marker_after & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
1354                return False # Disallow deleting lines between slices...
1355            else:
1356                if marker & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
1357                    self.clearGroupingMarkers(line_num+1)
1358                elif marker_after & ( 1<<GROUPING_END ) :
1359                    self.clearGroupingMarkers(line_num)
1360
1361            if marker & ( 1<<INPUT_END | 1<<OUTPUT_END ) :
1362                return False # Disallow deleting lines between slices...
1363            elif marker_after & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ) :
1364                return False # Disallow deleting lines between slices...
1365            else:
1366                if marker & (1<<INPUT_START  | 1<<INPUT_START_FOLDED |
1367                             1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED) :
1368                    self.clearIOMarkers(line_num+1)
1369                elif marker_after & ( 1<<INPUT_END | 1<<OUTPUT_END ) :
1370                    self.clearIOMarkers(line_num)
1371
1372        return True
1373
1374    def GetIOSelection(self):
1375        started=False
1376        start=0
1377        end=self.GetLineCount()-1
1378        type=None
1379        for i in range(self.GetLineCount()):
1380            if self.MarkerGet(i) & 1<<IO_SELECTING:
1381                if started==False:
1382                    start=i
1383                    if self.MarkerGet(i) & INPUT_MASK:
1384                        type='input'
1385                    elif self.MarkerGet(i) & OUTPUT_MASK:
1386                        type='output'
1387                else:
1388                    if self.MarkerGet(i) & INPUT_MASK:
1389                        if type=='output':
1390                            end=i-1
1391                            break
1392                    elif self.MarkerGet(i) & OUTPUT_MASK:
1393                        if type=='input':
1394                            end=i-1
1395                            break
1396                started=True
1397            elif started==True:
1398                end=i-1
1399                break
1400
1401        if started==False:
1402            #print('No Selection!!')
1403            self.SliceSelection=False
1404
1405        return start,end
1406
1407    def MergeAdjacentSlices(self):
1408        """This function merges all adjacent selected slices.\n""" + \
1409        """Right now, only IO Merging is allowed."""
1410        started=False
1411        start=0
1412        end=self.GetLineCount()-1
1413        type=None
1414        for i in range(self.GetLineCount()):
1415            if self.MarkerGet(i) & 1<<IO_SELECTING:
1416                if started==False:
1417                    start=i
1418                    if self.MarkerGet(i) & INPUT_MASK:
1419                        type='input'
1420                    elif self.MarkerGet(i) & OUTPUT_MASK:
1421                        type='output'
1422                else:
1423                    if self.MarkerGet(i) & INPUT_MASK:
1424                        if type=='output':
1425                            end=i-1
1426                            break
1427                        else:
1428                            self.clearIOMarkers(i)
1429                            self.clearGroupingMarkers(i)
1430                            self.MarkerAdd(i,INPUT_MIDDLE)
1431                            self.MarkerAdd(i,GROUPING_MIDDLE)
1432                    elif self.MarkerGet(i) & OUTPUT_MASK:
1433                        if type=='input':
1434                            end=i-1
1435                            break
1436                        else:
1437                            self.clearIOMarkers(i)
1438                            self.clearGroupingMarkers(i)
1439                            self.MarkerAdd(i,OUTPUT_MIDDLE)
1440                            self.MarkerAdd(i,OUTPUT_BG)
1441                            self.MarkerAdd(i,GROUPING_MIDDLE)
1442                started=True
1443            elif started==True:
1444                end=i-1
1445                break
1446
1447        if started and end!=start:
1448            self.clearIOMarkers(end)
1449            self.clearGroupingMarkers(end)
1450            if type=='input':
1451                self.MarkerAdd(end,INPUT_END)
1452                if end+1<self.GetLineCount():
1453                    if self.MarkerGet(end+1) & OUTPUT_MASK:
1454                        self.MarkerAdd(end,GROUPING_MIDDLE)
1455                    else:
1456                        self.MarkerAdd(end,GROUPING_END)
1457                else:
1458                    self.MarkerAdd(end,GROUPING_END)
1459            else:
1460                if self.MarkerGet(start) & 1<<GROUPING_END:
1461                    self.clearGroupingMarkers(start)
1462                    self.MarkerAdd(start,GROUPING_MIDDLE)
1463                self.MarkerAdd(end,OUTPUT_END)
1464                self.MarkerAdd(end,OUTPUT_BG)
1465                self.MarkerAdd(end,GROUPING_END)
1466
1467
1468    def SliceSelectionDelete(self):
1469        """Deletion of any selected and possibly discontinuous slices."""
1470        if not self.SliceSelection:
1471            return
1472
1473        # collect the line numbers to be deleted...
1474        selectedSlices=[]
1475        start,end=None,None
1476        for i in range(self.GetLineCount()):
1477            if self.MarkerGet(i) & (1<<GROUPING_SELECTING | 1<<IO_SELECTING):
1478                if start==None:
1479                    start=i
1480                end=i
1481            elif start!=None:
1482                selectedSlices.append([start,end])
1483                start,end=None,None
1484        if start!=None:
1485            selectedSlices.append([start,end])
1486
1487        # Unselect everything
1488        self.MarginUnselectAll()
1489        self.SliceSelection=False
1490
1491        # Going in reverse, delete the selections, fixing the markers as we go...
1492        for i in range(len(selectedSlices)-1,-1,-1):
1493            self.SetSelection(self.PositionFromLine(selectedSlices[i][0]),
1494                              self.GetLineEndPosition(selectedSlices[i][1])+1)
1495
1496            markerNext = self.MarkerGet(selectedSlices[i][1]+1)
1497
1498            self.ReplaceSelection('',sliceDeletion=True)
1499
1500            cur_line=self.GetCurrentLine()
1501
1502            # If we've made a mess of the grouping markers, clean it up...
1503            if ((self.MarkerGet(cur_line-1) & 1<<GROUPING_END) and
1504               (self.MarkerGet(cur_line) & ( 1<<GROUPING_MIDDLE | 1<<GROUPING_END ) )):
1505                self.clearGroupingMarkers(cur_line)
1506                self.MarkerAdd(cur_line,GROUPING_START)
1507            elif (( self.MarkerGet(cur_line-1) & 1<<GROUPING_MIDDLE ) and
1508                 ( self.MarkerGet(cur_line) &
1509                      ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) )):
1510                self.clearGroupingMarkers(cur_line-1)
1511                self.MarkerAdd(cur_line-1,GROUPING_END)
1512
1513            if markerNext & 1<<OUTPUT_START:
1514                self.clearIOMarkers(cur_line)
1515                self.MarkerAdd(cur_line,OUTPUT_START)
1516                self.MarkerAdd(cur_line,OUTPUT_BG)
1517            elif markerNext & 1<<OUTPUT_START_FOLDED:
1518                self.clearIOMarkers(cur_line)
1519                self.MarkerAdd(cur_line,OUTPUT_START_FOLDED)
1520                self.MarkerAdd(cur_line,OUTPUT_BG)
1521
1522        return
1523
1524    def OnChar(self, event):
1525        """Keypress event handler.
1526
1527        Only receives an event if OnKeyDown calls event.Skip() for the
1528        corresponding event."""
1529
1530        if self.noteMode:
1531            event.Skip()
1532            return
1533
1534        # Prevent modification of output slices
1535        if not self.CanEdit():
1536            return
1537        key = event.GetKeyCode()
1538        currpos = self.GetCurrentPos()
1539        stoppos = self.PositionFromLine(self.GetCurrentLine())
1540
1541        # Return (Enter) needs to be ignored in this handler.
1542        if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
1543            pass
1544        elif key in self.autoCompleteKeys:
1545            # Usually the dot (period) key activates auto completion.
1546            # Get the command between the prompt and the cursor.  Add
1547            # the autocomplete character to the end of the command.
1548            if self.AutoCompActive():
1549                self.AutoCompCancel()
1550            command = self.GetTextRange(stoppos, currpos) + chr(key)
1551
1552            # write with undo wrapper...
1553            cpos=self.GetCurrentPos()
1554            s=chr(key)
1555            #ADD UNDO
1556            self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s),
1557                                         forceNewAction=False)
1558            self.write(s,type='Input')
1559            self.UpdateUndoHistoryAfter()
1560
1561            if self.autoComplete:
1562                self.autoCompleteShow(command)
1563        elif key == ord('('):
1564            # The left paren activates a call tip and cancels an
1565            # active auto completion.
1566            if self.AutoCompActive():
1567                self.AutoCompCancel()
1568            # Get the command between the prompt and the cursor.  Add
1569            # the '(' to the end of the command.
1570            self.ReplaceSelection('')
1571            command = self.GetTextRange(stoppos, currpos) + '('
1572
1573            # write with undo wrapper...
1574            cpos=self.GetCurrentPos()
1575            s='('
1576            self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s),
1577                                         forceNewAction=True)
1578            self.undoHistory[self.undoIndex]['allowsAppend']=True
1579            self.write(s,type='Input')
1580            self.UpdateUndoHistoryAfter()
1581
1582            self.autoCallTipShow(command,
1583                                 self.GetCurrentPos() == self.GetTextLength())
1584        else:
1585            # Allow the normal event handling to take place.
1586            # Use undo wrapper
1587            cpos=self.GetCurrentPos()
1588            try:
1589                s=chr(key)
1590                self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s))
1591                event.Skip()
1592                self.UpdateUndoHistoryAfter()
1593            except:
1594                event.Skip()
1595
1596    def AutoCompActiveCallback(self):
1597        numChars=self.GetTextLength()-self.TotalLengthForAutoCompActiveCallback
1598        if numChars==0:
1599            self.undoIndex-=1
1600            del(self.undoHistory[-1])
1601        else:
1602            uH=self.undoHistory
1603            uI=self.undoIndex
1604            cpos=uH[uI]['posStart']
1605            s=''.join([chr(self.GetCharAt(cpos+i)) for i in range(numChars)])
1606            s.replace(os.linesep,'\n')
1607            self.undoHistory[self.undoIndex]['charList'] = s
1608            self.undoHistory[self.undoIndex]['posEnd'] = cpos + numChars
1609            self.undoHistory[self.undoIndex]['numLines'] = len(s.split('\n'))
1610            self.UpdateUndoHistoryAfter()
1611
1612    def OnKeyDown(self, event):
1613        """Key down event handler."""
1614
1615        key = event.GetKeyCode()
1616        # If the auto-complete window is up let it do its thing.
1617        if self.AutoCompActive():
1618            if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
1619                cpos=self.GetCurrentPos()
1620                self.UpdateUndoHistoryBefore('insert','dummy',cpos,cpos+5,
1621                                             forceNewAction=True)
1622                self.undoHistory[self.undoIndex]['allowsAppend'] = True
1623                self.TotalLengthForAutoCompActiveCallback=self.GetTextLength()
1624                event.Skip()
1625                wx.CallAfter(self.AutoCompActiveCallback)
1626            if key in [wx.WXK_DELETE,wx.WXK_BACK]:
1627                self.AutoCompCancel()
1628            else:
1629                event.Skip()
1630            return
1631
1632        #DNM
1633        # Prevent modification of output slices
1634        controlDown = event.ControlDown()
1635        altDown = event.AltDown()
1636        shiftDown = event.ShiftDown()
1637        currpos = self.GetCurrentPos()
1638        endpos = self.GetTextLength()
1639        selecting = self.GetSelectionStart() != self.GetSelectionEnd()
1640
1641        if key == wx.WXK_F12: #seb
1642            if self.noteMode:
1643                # self.promptPosStart not used anyway - or ?
1644##                # We don't need to do this any more!
1645##                self.promptPosEnd = self.PositionFromLine(self.GetLineCount()-1 ) +
1646##                                    len(str(sys.ps1))
1647##                self.GotoLine(self.GetLineCount())
1648##                self.GotoPos(self.promptPosEnd)
1649##                self.prompt()  #make sure we have a prompt
1650                self.SetCaretForeground("black")
1651                self.SetCaretWidth(1)    #default
1652                self.SetCaretPeriod(500) #default
1653            else:
1654                self.SetCaretForeground("red")
1655                self.SetCaretWidth(4)
1656                self.SetCaretPeriod(0) #steady
1657
1658            self.noteMode = not self.noteMode
1659            return
1660        if self.noteMode:
1661            event.Skip()
1662            return
1663
1664        doLineBreak=False
1665        doSubmitCommand=False
1666        doPass=False
1667        # Return is used to insert a line break.
1668        # In Shell Mode, hit Return or Enter twice to submit a command
1669        if ((not controlDown and not shiftDown and not altDown) and
1670           key in [wx.WXK_RETURN,]):
1671            if self.mode=='SlicesMode':
1672                doLineBreak=True
1673            elif self.mode=='ShellMode':
1674                startLine,endLine = self.GetIOSlice()
1675                startpos = self.PositionFromLine(startLine)
1676                endpos = self.GetLineEndPosition(endLine)
1677                command = self.GetTextRange(startpos, endpos)
1678                strCont,indentBlock,lineCont,parenCont = testForContinuations(command,ignoreErrors=True)
1679
1680                lastLine = command.split('\n')[-1]
1681                if lastLine.lstrip()=='': # all whitespace...
1682                    stillIndented=False
1683                elif lastLine[0]==' ':
1684                    stillIndented=True
1685                else:
1686                    stillIndented=False
1687
1688                if strCont[-1] or indentBlock[-1] or lineCont[-1] or \
1689                   parenCont[-1]:
1690                    doLineBreak=True
1691                elif stillIndented:
1692                    new_pos=self.GetLineEndPosition(endLine)
1693                    self.SetCurrentPos(new_pos)
1694                    self.SetSelection(new_pos,new_pos)
1695                    doLineBreak=True
1696                elif self.GetCurrentLine()!=endLine:
1697                    new_pos=self.GetLineEndPosition(endLine)
1698                    self.SetCurrentPos(new_pos)
1699                    self.SetSelection(new_pos,new_pos)
1700                    doPass = True
1701                else:
1702                    doSubmitCommand=True
1703        # Enter (Shift/Ctrl + Enter/Return) submits a command to the interpreter.
1704        # In Shell Mode, hit Return or Enter twice to submit a command
1705        elif ( key in [wx.WXK_NUMPAD_ENTER,] or
1706            ( (shiftDown or controlDown) and key in [wx.WXK_RETURN,
1707                                                     wx.WXK_NUMPAD_ENTER] ) ):
1708            if self.mode=='SlicesMode':
1709                doSubmitCommand=True
1710            elif self.mode=='ShellMode':
1711                doLineBreak=True
1712
1713        #Only relevant in ShellMode...
1714
1715        if doPass:
1716            pass
1717        elif doLineBreak or doSubmitCommand:
1718            if self.CallTipActive():
1719                self.CallTipCancel()
1720            elif self.SliceSelection:
1721                for i in range(self.GetLineCount()):
1722                    if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
1723                        self.DoMarginClick(i, 2, shiftDown, controlDown)
1724                        break
1725                    elif self.MarkerGet(i) & 1<<IO_SELECTING:
1726                        self.DoMarginClick(i, 3, shiftDown, controlDown)
1727                        break
1728            elif doLineBreak:
1729                self.insertLineBreak()
1730                #Only relevant in ShellMode...
1731            elif doSubmitCommand:
1732                self.DeleteOutputSlicesAfter()
1733                self.processLine()
1734
1735        # Let Ctrl-Alt-* get handled normally.
1736        elif controlDown and altDown:
1737            event.Skip()
1738
1739        # Clear the current, unexecuted command.
1740        elif key == wx.WXK_ESCAPE:
1741            if self.CallTipActive():
1742                event.Skip()
1743        # Clear the current command
1744        elif key == wx.WXK_BACK and controlDown and shiftDown:
1745            self.clearCommand()
1746
1747        # Increase font size.
1748        elif controlDown and key in (ord(']'), wx.WXK_NUMPAD_ADD):
1749            dispatcher.send(signal='FontIncrease')
1750
1751        # Decrease font size.
1752        elif controlDown and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
1753            dispatcher.send(signal='FontDecrease')
1754
1755        # Default font size.
1756        elif controlDown and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
1757            dispatcher.send(signal='FontDefault')
1758
1759        # Cut to the clipboard.
1760        elif (controlDown and key in (ord('X'), ord('x'))) \
1761                 or (shiftDown and key == wx.WXK_DELETE):
1762            self.Cut()
1763
1764        # Copy to the clipboard.
1765        elif controlDown and not shiftDown \
1766                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
1767            self.Copy()
1768
1769        # Copy to the clipboard, including prompts.
1770        elif controlDown and shiftDown \
1771                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
1772            self.CopyWithPrompts()
1773
1774        # Copy to the clipboard, including prefixed prompts.
1775        elif altDown and not controlDown \
1776                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
1777            self.CopyWithPromptsPrefixed()
1778
1779        # Home needs to be aware of the prompt.
1780        elif controlDown and key == wx.WXK_HOME:
1781            # Go to the beginning of the IO Slice
1782            curLine = self.GetCurrentLine()
1783            IOstart = self.GetIOSlice(curLine)[0]
1784            home = self.PositionFromLine(IOstart)
1785            if currpos == home and \
1786               IOstart > 0:
1787                home = self.PositionFromLine(self.GetIOSlice(curLine-1)[0])
1788            self.SetCurrentPos(home)
1789            if not selecting and not shiftDown:
1790                self.SetAnchor(home)
1791                self.EnsureCaretVisible()
1792
1793        elif controlDown and key == wx.WXK_END:
1794            curLine = self.GetCurrentLine()
1795            IOend = self.GetIOSlice(curLine)[1]
1796            end = self.GetLineEndPosition(IOend)
1797            if currpos == end and \
1798               IOend < self.GetLineCount()-1:
1799                end = self.GetLineEndPosition(self.GetIOSlice(curLine+1)[1])
1800            self.SetCurrentPos(end)
1801            if not selecting and not shiftDown:
1802                self.SetAnchor(end)
1803                self.EnsureCaretVisible()
1804
1805        elif controlDown and key == wx.WXK_PAGEUP:
1806            pos=0
1807            if currpos > pos:
1808                self.SetCurrentPos(pos)
1809                if not selecting and not shiftDown:
1810                    self.SetAnchor(pos)
1811                    self.EnsureCaretVisible()
1812
1813        elif controlDown and key == wx.WXK_PAGEDOWN:
1814            pos = self.GetLineEndPosition(self.GetLineCount()-1)
1815            if currpos < pos:
1816                self.SetCurrentPos(pos)
1817                if not selecting and not shiftDown:
1818                    self.SetAnchor(pos)
1819                    self.EnsureCaretVisible()
1820
1821        elif selecting and key not in NAVKEYS and not self.CanEdit():
1822            pass
1823
1824        # Paste from the clipboard.
1825        elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \
1826                 or (shiftDown and not controlDown and key == wx.WXK_INSERT):
1827            self.Paste()
1828
1829        # Paste from the clipboard, run commands.
1830        elif controlDown and shiftDown and \
1831                             key in (ord('V'), ord('v')) and self.CanEdit():
1832            self.PasteAndRun()
1833
1834        # Replace with the previous command from the history buffer.
1835        elif (controlDown and not shiftDown and key == wx.WXK_UP) \
1836              or (altDown and key in (ord('P'), ord('p'))) and self.CanEdit():
1837            self.OnHistoryReplace(step=+1)
1838
1839        # Replace with the next command from the history buffer.
1840        elif (controlDown and not shiftDown and key == wx.WXK_DOWN) \
1841              or (altDown and key in (ord('N'), ord('n'))) and self.CanEdit():
1842            self.OnHistoryReplace(step=-1)
1843
1844        # Insert the previous command from the history buffer.
1845        elif (controlDown and shiftDown and key == wx.WXK_UP) and \
1846              self.CanEdit():
1847            self.OnHistoryInsert(step=+1)
1848
1849        # Insert the next command from the history buffer.
1850        elif (controlDown and shiftDown and key == wx.WXK_DOWN) and \
1851              self.CanEdit():
1852            self.OnHistoryInsert(step=-1)
1853
1854        # Ctrl-Space shows Auto Completion
1855        # Ctrl-Shift-Space shows CallTips
1856        elif controlDown and key == wx.WXK_SPACE:
1857            self.OnCallTipAutoCompleteManually(shiftDown)
1858
1859        # Ctrl+Shift+H is used to complete Text (from already typed words)
1860        elif controlDown and shiftDown and key in [ord('H')]:
1861            self.OnShowCompHistory()
1862
1863        # Search up the history for the text in front of the cursor.
1864        elif key == wx.WXK_F8:
1865            self.OnHistorySearch()
1866
1867        # Don't backspace over the latest non-continuation prompt.
1868        elif key == wx.WXK_BACK:
1869            if self.SliceSelection:
1870                self.SliceSelectionDelete()
1871                wx.CallAfter(self.RestoreFirstMarker)
1872            elif selecting and self.CanEdit():
1873                self.ReplaceSelection('')
1874                #event.Skip()
1875            elif self.CanEdit():
1876                doDelete=True
1877                cur_line=self.GetCurrentLine()
1878                if not cur_line==0 and \
1879                   self.GetCurrentPos()==self.PositionFromLine(cur_line):
1880                    if self.MarkerGet(cur_line-1) & OUTPUT_MASK:
1881                        doDelete=False
1882
1883                if doDelete:
1884                    cpos=self.GetCurrentPos()
1885                    s=chr(self.GetCharAt(cpos-1))
1886                    self.UpdateUndoHistoryBefore('delete',s,cpos-1,cpos)
1887                    if self.BackspaceWMarkers():
1888                        event.Skip()
1889
1890                wx.CallAfter(self.RestoreFirstMarker)
1891
1892        elif key == wx.WXK_DELETE:
1893            if self.SliceSelection:
1894                self.SliceSelectionDelete()
1895                wx.CallAfter(self.RestoreFirstMarker)
1896            elif selecting and self.CanEdit():
1897                self.ReplaceSelection('')
1898                #event.Skip()
1899            elif self.CanEdit():
1900                doDelete=True
1901                cur_line=self.GetCurrentLine()
1902                if not cur_line==self.GetLineCount()-1 and \
1903                   self.GetCurrentPos()==self.GetLineEndPosition(cur_line):
1904                    if self.MarkerGet(cur_line+1) & OUTPUT_MASK:
1905                        doDelete=False
1906
1907                if doDelete:
1908                    cpos=self.GetCurrentPos()
1909                    s=chr(self.GetCharAt(cpos))
1910                    self.UpdateUndoHistoryBefore('delete',s,cpos,cpos+1)
1911                    if self.ForwardDeleteWMarkers():
1912                        event.Skip()
1913
1914                wx.CallAfter(self.RestoreFirstMarker)
1915
1916        # Only allow these keys after the latest prompt.
1917        elif key == wx.WXK_TAB and self.CanEdit():
1918            # use the same mechanism as with autocmplete...
1919            cpos=self.GetCurrentPos()
1920            self.UpdateUndoHistoryBefore('insert','dummy',cpos,cpos+5,
1921                                         forceNewAction=True)
1922            self.undoHistory[self.undoIndex]['allowsAppend'] = True
1923            self.TotalLengthForAutoCompActiveCallback=self.GetTextLength()
1924            event.Skip()
1925            wx.CallAfter(self.AutoCompActiveCallback)
1926
1927        # Don't toggle between insert mode and overwrite mode.
1928        elif key == wx.WXK_INSERT:
1929            pass
1930
1931        # Don't allow line deletion.
1932        #elif controlDown and key in (ord('L'), ord('l')):
1933            # TODO : Allow line deletion eventually ??
1934            #event.Skip()
1935        #    pass
1936
1937        # Don't allow line transposition.
1938        # Toggle Shell Mode / Slices Mode
1939        elif controlDown and key in (ord('T'), ord('t')):
1940            self.ToggleShellMode()
1941
1942        #Open and Save now work when using CrustSlicesFrames
1943        elif controlDown and key in (ord('L'), ord('l')):
1944            #print('Load it')
1945            file=wx.FileSelector("Load File As New Slice")
1946            if file!=u'':
1947                fid=open(file,'r')
1948                self.LoadPyFileAsSlice(fid)
1949                fid.close()
1950
1951        elif controlDown and key in (ord('D'), ord('d')):
1952            #Disallow line duplication in favor of divide slices
1953            if self.MarkerGet(self.GetCurrentLine()) & INPUT_MASK:
1954                #ADD UNDO
1955                cpos=self.GetCurrentPos()
1956                start,end = map(self.PositionFromLine,
1957                              self.GetGroupingSlice(self.LineFromPosition(cpos)))
1958                self.UpdateUndoHistoryBefore('marker','',start,end,
1959                                             forceNewAction=True)
1960                self.SplitSlice()
1961                # Turn off selecting
1962                self.SetSelection(cpos,cpos)
1963                self.ReplaceSelection('')
1964                self.UpdateUndoHistoryAfter()
1965
1966        elif controlDown and key in (ord('M'), ord('m')):
1967            #ADD UNDO
1968            if self.SliceSelection:
1969                cpos=self.GetCurrentPos()
1970                ioSel=self.GetIOSelection()
1971            if self.SliceSelection:
1972                start,end = map(self.PositionFromLine,ioSel)
1973                self.UpdateUndoHistoryBefore('marker','',start,end,
1974                                             forceNewAction=True)
1975                self.MergeAdjacentSlices()
1976                # Turn off selecting
1977                self.SetSelection(cpos,cpos)
1978                self.ReplaceSelection('')
1979                self.UpdateUndoHistoryAfter()
1980
1981
1982        # Change arrow keys to allow margin behaviors...
1983        elif self.SliceSelection and \
1984             key in [wx.WXK_UP,wx.WXK_DOWN,wx.WXK_RIGHT,wx.WXK_LEFT]:
1985        # TODO : This is useful, but not optimal!
1986            if key==wx.WXK_UP:
1987                for i in range(self.GetLineCount()):
1988                    if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
1989                        if i>0:                     #Grouping
1990                            self.DoMarginClick(i-1, 2, shiftDown, controlDown)
1991                        break
1992                    elif self.MarkerGet(i) & 1<<IO_SELECTING:
1993                        if i>0:                     #IO
1994                            self.DoMarginClick(i-1, 3, shiftDown, controlDown)
1995                        break
1996            elif key==wx.WXK_DOWN:
1997                for i in range(self.GetLineCount()-1,-1,-1):
1998                    if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
1999                        if i<self.GetLineCount()-1: #Grouping
2000                            self.DoMarginClick(i+1, 2, shiftDown, controlDown)
2001                        break
2002                    elif self.MarkerGet(i) & 1<<IO_SELECTING:
2003                        if i<self.GetLineCount()-1: #IO
2004                            self.DoMarginClick(i+1, 3, shiftDown, controlDown)
2005                        break
2006            elif key==wx.WXK_RIGHT:
2007                for i in range(self.GetLineCount()):
2008                    if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
2009                        self.DoMarginClick(i, 3, shiftDown, controlDown)
2010                        break
2011                    elif self.MarkerGet(i) & 1<<IO_SELECTING:
2012                        self.MarginUnselectAll()
2013                        # Go to the beginning of the IO Slice
2014                        self.SetCurrentPos(self.PositionFromLine(i))
2015                        if not selecting and not shiftDown:
2016                            self.SetAnchor(self.PositionFromLine(i))
2017                            self.EnsureCaretVisible()
2018                        break
2019            elif key==wx.WXK_LEFT:
2020                for i in range(self.GetLineCount()):
2021                    if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
2022                        break
2023                    elif self.MarkerGet(i) & 1<<IO_SELECTING:
2024                        self.DoMarginClick(i, 2, shiftDown, controlDown)
2025                        break
2026        # Basic navigation keys should work anywhere.
2027        elif key in NAVKEYS:
2028            event.Skip()
2029        # Protect the readonly portion of the slices shell.
2030        elif not self.CanEdit():
2031            pass
2032        else:
2033            # Check to see if we're selecting
2034            if self.GetSelectionEnd()>self.GetSelectionStart():
2035                # Check to see if a normal input took place
2036                if not controlDown and not altDown and key<256:
2037                    self.ReplaceSelection('') # This seems to work...
2038            event.Skip()
2039
2040        if self.SliceSelection:
2041            if key not in [wx.WXK_UP,wx.WXK_DOWN,wx.WXK_RIGHT,wx.WXK_LEFT,
2042                           wx.WXK_ALT,wx.WXK_COMMAND,wx.WXK_CONTROL,wx.WXK_SHIFT]:
2043                self.MarginUnselectAll()
2044
2045
2046    def MarginSelectAll(self):
2047        num_lines=self.GetLineCount()
2048        for i in range(num_lines):
2049            self.MarkerAdd(i,GROUPING_SELECTING)
2050            self.MarkerDelete(i,IO_SELECTING)
2051
2052    def MarginUnselectAll(self):
2053        num_lines=self.GetLineCount()
2054        for i in range(num_lines):
2055            self.MarkerDelete(i,IO_SELECTING)
2056            self.MarkerDelete(i,GROUPING_SELECTING)
2057        self.SliceSelection=False
2058
2059    def DoMarginClick(self, lineClicked, margin, shiftDown, controlDown):
2060        num_lines=self.GetLineCount()
2061
2062        if margin==1:
2063            pass # these events are not sent right now...
2064        if margin==2:
2065            self.SliceSelection=True
2066            start,end=self.GetGroupingSlice(lineClicked)
2067            startPos=self.PositionFromLine(start)
2068            self.SetCurrentPos(startPos)
2069            self.SetSelection(startPos,startPos)
2070            start_marker=self.MarkerGet(start)
2071            if self.MarkerGet(lineClicked) & 1<<GROUPING_SELECTING:
2072                toggle=self.MarkerDelete
2073                if not shiftDown:
2074                    if start_marker & 1<<GROUPING_START:
2075                        self.FoldGroupingSlice(lineClicked)
2076                    elif start_marker & 1<<GROUPING_START_FOLDED:
2077                        self.UnFoldGroupingSlice(lineClicked)
2078            else:
2079                toggle=self.MarkerAdd
2080
2081            if not shiftDown:
2082                self.MarginUnselectAll()
2083
2084            for i in range(start,end+1):
2085                toggle(i,GROUPING_SELECTING)
2086        elif margin==3:
2087            self.SliceSelection=True
2088            start,end=self.GetIOSlice(lineClicked)
2089            startPos=self.PositionFromLine(start)
2090            self.SetCurrentPos(startPos)
2091            self.SetSelection(startPos,startPos)
2092            start_marker=self.MarkerGet(start)
2093            if self.MarkerGet(lineClicked) & 1<<IO_SELECTING:
2094                toggle=self.MarkerDelete
2095                if not shiftDown:
2096                    if start_marker & IO_START_MASK:
2097                        self.FoldIOSlice(lineClicked)
2098                    elif start_marker & IO_START_FOLDED_MASK:
2099                        self.UnFoldIOSlice(lineClicked)
2100            else:
2101                toggle=self.MarkerAdd
2102
2103            if not shiftDown:
2104                self.MarginUnselectAll()
2105
2106            for i in range(start,end+1):
2107                toggle(i,IO_SELECTING)
2108
2109            #print(start, end)
2110
2111        elif margin==4:
2112            # TODO : Folding ??
2113            if 1:#self.MarkerGet(lineClicked) & ( 1<<7 | 1<<8 ):
2114                if shiftDown:
2115                    self.SetFoldExpanded(lineClicked, True)
2116                    self.Expand(lineClicked, True, True, 1)
2117                elif controlDown:
2118                    if self.GetFoldExpanded(lineClicked):
2119                        self.SetFoldExpanded(lineClicked, False)
2120                        self.Expand(lineClicked, False, True, 0)
2121                    else:
2122                        self.SetFoldExpanded(lineClicked, True)
2123                        self.Expand(lineClicked, True, True, 100)
2124                else:
2125                    self.ToggleFold(lineClicked)
2126        else:
2127            self.MarginUnselectAll()
2128        if margin in [2,3]:
2129            if toggle==self.MarkerDelete and not shiftDown:
2130                self.SliceSelection=False
2131            else:
2132                self.SliceSelection=True
2133
2134    def OnMarginClick(self, evt):
2135
2136        # fold and unfold as neededNAVKEYS
2137        lineClicked = self.LineFromPosition(evt.GetPosition())
2138        self.DoMarginClick(lineClicked,evt.GetMargin(),evt.GetShift(),evt.GetControl())
2139        evt.Skip()
2140
2141    def OnShowCompHistory(self):
2142        """Show possible autocompletion Words from already typed words."""
2143
2144        #copy from history
2145        his = self.history[:]
2146
2147        #put together in one string
2148        joined = " ".join (his)
2149        import re
2150
2151        #sort out only "good" words
2152        newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined)
2153
2154        #length > 1 (mix out "trash")
2155        thlist = []
2156        for i in newlist:
2157            if len (i) > 1:
2158                thlist.append (i)
2159
2160        #unique (no duplicate words
2161        #oneliner from german python forum => unique list
2162        unlist = [thlist[i] for i in xrange(len(thlist)) if thlist[i] not in thlist[:i]]
2163
2164        #sort lowercase
2165        unlist.sort(key=cmp_to_key(lambda a, b: cmp(a.lower(), b.lower())))
2166
2167        #this is more convenient, isn't it?
2168        self.AutoCompSetIgnoreCase(True)
2169
2170        #join again together in a string
2171        stringlist = " ".join(unlist)
2172
2173        #pos von 0 noch ausrechnen
2174
2175        #how big is the offset?
2176        cpos = self.GetCurrentPos() - 1
2177        while chr (self.GetCharAt (cpos)).isalnum():
2178            cpos -= 1
2179
2180        #the most important part
2181        self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist)
2182
2183    def ReplaceSelection(self,text,sliceDeletion=False,*args,**kwds):
2184        startIO,endIO=self.GetIOSlice()
2185        startGrouping,endGrouping=self.GetGroupingSlice()
2186        startSel = self.LineFromPosition(self.GetSelectionStart())
2187        endSel = self.LineFromPosition(self.GetSelectionEnd())
2188
2189        #ADD UNDO
2190        cpos=self.GetSelectionStart()
2191        s=self.GetSelectedText()
2192        if s!='':
2193            self.UpdateUndoHistoryBefore('delete',s,cpos,cpos+len(s),
2194                                         forceNewAction=True)
2195        editwindow.EditWindow.ReplaceSelection(self,'',*args,**kwds)
2196        if s!='' and not sliceDeletion:
2197            self.UpdateUndoHistoryAfter()
2198
2199        if endSel-startSel>0 and not sliceDeletion:
2200            if endSel==endIO and startIO!=self.GetCurrentLine():
2201                self.clearIOMarkers()
2202                self.MarkerAdd(self.GetCurrentLine(),INPUT_END)
2203
2204            if endSel==endGrouping and startGrouping!=self.GetCurrentLine():
2205                self.clearGroupingMarkers()
2206                self.MarkerAdd(self.GetCurrentLine(),GROUPING_END)
2207
2208        cpos=self.GetSelectionStart()
2209        s=text
2210        if s!='':
2211            self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s),
2212                                         forceNewAction=True)
2213            self.write(text)
2214            self.UpdateUndoHistoryAfter()
2215
2216        self.ensureSingleGroupingMarker()
2217        self.ensureSingleIOMarker()
2218
2219
2220    def clearCommand(self):
2221        """Delete the current, unexecuted command."""
2222        if not self.CanEdit():
2223            return
2224        start,end=self.GetIOSlice()
2225        startpos = self.PositionFromLine(start)
2226        endpos = self.GetLineEndPosition(end)
2227        self.SetSelection(startpos, endpos)
2228        self.ReplaceSelection('')
2229        self.more = False
2230
2231    def OnHistoryReplace(self, step):
2232        """Replace with the previous/next command from the history buffer."""
2233        if not self.CanEdit():
2234            return
2235        self.clearCommand()
2236        self.replaceFromHistory(step)
2237
2238    def replaceFromHistory(self, step):
2239        """Replace selection with command from the history buffer."""
2240        if not self.CanEdit():
2241            return
2242        self.ReplaceSelection('')
2243        newindex = self.historyIndex + step
2244        if -1 <= newindex <= len(self.history):
2245            self.historyIndex = newindex
2246        if 0 <= newindex <= len(self.history)-1:
2247            command = self.history[self.historyIndex]
2248            command = command.replace('\n', os.linesep)# + ps2)
2249            self.ReplaceSelection(command)
2250
2251    def OnHistoryInsert(self, step):
2252        """Insert the previous/next command from the history buffer."""
2253        if not self.CanEdit():
2254            return
2255        startpos = self.GetCurrentPos()
2256        self.replaceFromHistory(step)
2257        endpos = self.GetCurrentPos()
2258        self.SetSelection(endpos, startpos)
2259
2260    # TODO: Fix Me!
2261    def OnHistorySearch(self):
2262        """Search up the history buffer for the text in front of the cursor."""
2263        if not self.CanEdit():
2264            return
2265        startpos = self.GetCurrentPos()
2266        # The text up to the cursor is what we search for.
2267        numCharsAfterCursor = self.GetTextLength() - startpos
2268        searchText = self.getCommand(rstrip=False)
2269        #print('history search', startpos, numCharsAfterCursor, searchText)
2270        if numCharsAfterCursor > 0:
2271            searchText = searchText[:-numCharsAfterCursor]
2272        if not searchText:
2273            return
2274        # Search upwards from the current history position and loop
2275        # back to the beginning if we don't find anything.
2276        if (self.historyIndex <= -1) \
2277        or (self.historyIndex >= len(self.history)-2):
2278            searchOrder = range(len(self.history))
2279        else:
2280            searchOrder = range(self.historyIndex+1, len(self.history)) + \
2281                          range(self.historyIndex)
2282        for i in searchOrder:
2283            command = self.history[i]
2284            if command[:len(searchText)] == searchText:
2285                # Replace the current selection with the one we found.
2286                self.ReplaceSelection(command[len(searchText):])
2287                endpos = self.GetCurrentPos()
2288                self.SetSelection(endpos, startpos)
2289                # We've now warped into middle of the history.
2290                self.historyIndex = i
2291                break
2292
2293    def setStatusText(self, text):
2294        """Display status information."""
2295
2296        # This method will likely be replaced by the enclosing app to
2297        # do something more interesting, like write to a status bar.
2298        print(text)
2299
2300    def insertLineBreak(self):
2301        """Insert a new line break."""
2302        if not self.CanEdit():
2303            return
2304        elif self.reader.isreading:
2305            self.processLine()
2306            return
2307
2308
2309        # write with undo wrapper...
2310        cpos=self.GetCurrentPos()
2311        s=os.linesep
2312        self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+1)
2313        self.write(s,type='Input')
2314        self.UpdateUndoHistoryAfter()
2315
2316        self.more = True
2317        self.prompt()
2318
2319    def processLine(self):
2320        """Process the line of text at which the user hit Enter or Shift+RETURN."""
2321        # The user hit ENTER (Shift+RETURN) (Shift+ENTER) and we need to
2322        # decide what to do. They could be sitting on any line in the slices shell.
2323        thepos = self.GetCurrentPos()
2324        cur_line = self.GetCurrentLine()
2325        marker=self.MarkerGet(cur_line)
2326        if marker & INPUT_MASK:
2327            pass
2328        elif marker & OUTPUT_MASK:
2329            return
2330        else:
2331            pass #print('BLANK LINE!!')
2332
2333        startline,endline=self.GetIOSlice(cur_line)
2334
2335        if startline==0:
2336            startpos=0
2337        else:
2338            startpos=self.PositionFromLine(startline)
2339
2340        endpos=self.GetLineEndPosition(endline)
2341
2342        # If they hit ENTER inside the current command, execute the command.
2343        if self.CanEdit():
2344            self.SetCurrentPos(endpos)
2345            self.interp.more = False
2346            command = self.GetTextRange(startpos, endpos)
2347            lines = command.split(os.linesep)
2348            lines = [line.rstrip() for line in lines]
2349            command = '\n'.join(lines)
2350            if self.reader.isreading:
2351                if not command:
2352                    # Match the behavior of the standard Python shell
2353                    # when the user hits return without entering a value.
2354                    command = '\n'
2355                self.reader.input = command
2356                self.write(os.linesep,'Input')
2357                self.MarkerSet(self.GetCurrentLine(),READLINE_BG)
2358                self.MarkerSet(self.GetCurrentLine(),INPUT_READLINE)
2359            else:
2360                self.runningSlice = (startline,endline)
2361                self.push(command,useMultiCommand=True)
2362                #print('command: %s'%command)
2363                wx.CallLater(1, self.EnsureCaretVisible)
2364                self.runningSlice=None
2365
2366        skip=self.BackspaceWMarkers(force=True)
2367        if skip:
2368            self.DeleteBack()
2369
2370        if self.GetCurrentLine()==self.GetLineCount()-1:
2371            self.write(os.linesep,type='Input')
2372            cpos=self.GetCurrentLine()
2373            if self.MarkerGet(cpos-1) & OUTPUT_MASK:
2374                self.MarkerAdd(cpos-1,OUTPUT_BG)
2375            self.SplitSlice()
2376        else:
2377            cur_line=self.GetCurrentLine()
2378            new_pos=self.GetLineEndPosition(cur_line+1)
2379            self.SetSelection(new_pos,new_pos)
2380            self.SetCurrentPos(new_pos)
2381
2382        self.EmptyUndoBuffer()
2383        self.NeedsCheckForSave=True
2384        if self.hasSyntaxError:
2385            pos=self.GetLineEndPosition(self.syntaxErrorRealLine)
2386            self.SetCurrentPos(pos)
2387            self.SetSelection(pos,pos)
2388
2389    # Not Used!!
2390    def getMultilineCommand(self, rstrip=True):
2391        """Extract a multi-line command from the editor.
2392
2393        The command may not necessarily be valid Python syntax."""
2394        # DNM
2395        # XXX Need to extract real prompts here. Need to keep track of
2396        # the prompt every time a command is issued.
2397        text = self.GetCurLine()[0]
2398        line = self.GetCurrentLine()
2399        # Add Marker testing here...
2400        while text == '' and line > 0: # Need to add markers handling...
2401            line -= 1
2402            self.GotoLine(line)
2403            text = self.GetCurLine()[0]
2404        if text=='':
2405            line = self.GetCurrentLine()
2406            self.GotoLine(line)
2407            startpos = self.GetCurrentPos()
2408            line += 1
2409            self.GotoLine(line)
2410            while self.GetCurLine()[0]=='':
2411                line += 1
2412                self.GotoLine(line)
2413            stoppos = self.GetCurrentPos()
2414            command = self.GetTextRange(startpos, stoppos)
2415            command = command.replace(os.linesep, '\n')
2416            command = command.rstrip()
2417            command = command.replace('\n', os.linesep)
2418        else:
2419            command = ''
2420        if rstrip:
2421            command = command.rstrip()
2422        return command
2423
2424    def getCommand(self, text=None, rstrip=True):
2425        """Extract a command from text which may include a shell prompt.
2426
2427        The command may not necessarily be valid Python syntax."""
2428        if not text:
2429            text = self.GetCurLine()[0]
2430        # Strip the prompt off the front leaving just the command.
2431        command = self.lstripPrompt(text)
2432        # Change this -- Nothing has prompts!
2433        #if command == text:
2434        #    command = ''  # Real commands have prompts.
2435        if rstrip:
2436            command = command.rstrip()
2437        return command
2438
2439    def lstripPrompt(self, text):
2440        """Return text without a leading prompt."""
2441        ps1 = str(sys.ps1)
2442        ps1size = len(ps1)
2443        ps2 = str(sys.ps2)
2444        ps2size = len(ps2)
2445        # Strip the prompt off the front of text.
2446        if text[:ps1size] == ps1:
2447            text = text[ps1size:]
2448        elif text[:ps2size] == ps2:
2449            text = text[ps2size:]
2450        return text
2451
2452    def push(self, command, silent = False,useMultiCommand=False):
2453        """Send command to the interpreter for execution."""
2454        if not silent:
2455            self.write(os.linesep,type='Output')
2456        # TODO : What other magic might we insert here?
2457        # TODO : Is there a good reason not to include magic?
2458        if USE_MAGIC:
2459            command=magic(command)
2460
2461        # Allows multi-component commands...
2462        self.hasSyntaxError=False
2463        if useMultiCommand:
2464            result = self.BreakTextIntoCommands(command)
2465            if result[0] == None:
2466                commands=[command]
2467                self.hasSyntaxError=True
2468                syntaxErrorLine=result[1]+1
2469                self.syntaxErrorRealLine = self.GetCurrentLine()+result[1]-len(command.split('\n'))
2470            else:
2471                commands=result
2472        else:
2473            commands=[command]
2474
2475        busy = wx.BusyCursor()
2476        self.waiting = True
2477        self.lastUpdate=None
2478
2479        for i in commands:
2480            if self.hasSyntaxError:
2481                lineno=syntaxErrorLine
2482                offset=0 # not sure how to easily recover this information...
2483                self.write('  File "<input>", line '+str(lineno)+'\n    '+i.split('\n')[lineno-1]+'\n'+' '*offset+'    ^\nSyntaxError: invalid syntax\n','Error')
2484            else:
2485                self.more = self.interp.push(i+'\n')
2486            # (the \n stops many things from bouncing at the interpreter)
2487            # I could do the following, but I don't really like it!
2488            #if useMultiCommand:
2489            #    self.SplitSlice()
2490        self.lastUpdate=None
2491
2492        if not silent:
2493            self.MarkerAdd(self.GetIOSlice()[0],OUTPUT_BG)
2494
2495        self.waiting = False
2496        del busy
2497        if not self.more: # could loop-add to history, too, but I don't like it!
2498            self.addHistory(command.rstrip())
2499
2500        if not silent:
2501            self.prompt()
2502
2503    def addHistory(self, command):
2504        """Add command to the command history."""
2505        # Reset the history position.
2506        self.historyIndex = -1
2507        # Insert this command into the history, unless it's a blank
2508        # line or the same as the last command.
2509        if command!='' and ( len(self.history)==0 or command!=self.history[0] ):
2510            self.history.insert(0, command)
2511            dispatcher.send(signal="SlicesShell.addHistory", command=command)
2512
2513    def clearGroupingMarkers(self,line_num=None):
2514        if line_num==None:
2515            line_num=self.GetCurrentLine()
2516        self.MarkerDelete(line_num,GROUPING_START)
2517        self.MarkerDelete(line_num,GROUPING_START_FOLDED)
2518        self.MarkerDelete(line_num,GROUPING_MIDDLE)
2519        self.MarkerDelete(line_num,GROUPING_END)
2520    def clearIOMarkers(self,line_num=None):
2521        if line_num==None:
2522            line_num=self.GetCurrentLine()
2523        self.MarkerDelete(line_num,INPUT_START)
2524        self.MarkerDelete(line_num,INPUT_START_FOLDED)
2525        self.MarkerDelete(line_num,INPUT_MIDDLE)
2526        self.MarkerDelete(line_num,INPUT_END)
2527        self.MarkerDelete(line_num,OUTPUT_START)
2528        self.MarkerDelete(line_num,OUTPUT_START_FOLDED)
2529        self.MarkerDelete(line_num,OUTPUT_MIDDLE)
2530        self.MarkerDelete(line_num,OUTPUT_END)
2531        self.MarkerDelete(line_num,OUTPUT_BG)
2532        self.MarkerDelete(line_num,READLINE_BG)
2533        self.MarkerDelete(line_num,INPUT_READLINE)
2534    def ensureSingleGroupingMarker(self,line_num=None):
2535        if line_num==None:
2536            line_num=self.GetCurrentLine()
2537        marker=self.MarkerGet(line_num)
2538        if marker & 1<<GROUPING_START:
2539            self.MarkerDelete(line_num,GROUPING_START_FOLDED)
2540            self.MarkerDelete(line_num,GROUPING_MIDDLE)
2541            self.MarkerDelete(line_num,GROUPING_END)
2542        elif marker & 1<<GROUPING_START_FOLDED:
2543            self.MarkerDelete(line_num,GROUPING_MIDDLE)
2544            self.MarkerDelete(line_num,GROUPING_END)
2545        elif marker & 1<<GROUPING_MIDDLE:
2546            self.MarkerDelete(line_num,GROUPING_END)
2547        elif marker & 1<<GROUPING_END:
2548            pass
2549        else:
2550            #print('ERROR! NO GROUPING MARKERS!')
2551            return 1 # Blank marker
2552
2553        return 0
2554
2555    def ensureSingleIOMarker(self,line_num=None):
2556        if line_num==None:
2557            line_num=self.GetCurrentLine()
2558        marker=self.MarkerGet(line_num)
2559        if marker & INPUT_MASK:
2560            self.MarkerDelete(line_num,OUTPUT_START)
2561            self.MarkerDelete(line_num,OUTPUT_START_FOLDED)
2562            self.MarkerDelete(line_num,OUTPUT_MIDDLE)
2563            self.MarkerDelete(line_num,OUTPUT_END)
2564            self.MarkerDelete(line_num,OUTPUT_BG)
2565            [start,start_folded] = [INPUT_START,INPUT_START_FOLDED]
2566            [middle,end] = [INPUT_MIDDLE,INPUT_END]
2567        elif marker & OUTPUT_MASK:
2568            self.MarkerDelete(line_num,INPUT_START)
2569            self.MarkerDelete(line_num,INPUT_START_FOLDED)
2570            self.MarkerDelete(line_num,INPUT_MIDDLE)
2571            self.MarkerDelete(line_num,INPUT_END)
2572            [start,start_folded] = [OUTPUT_START,OUTPUT_START_FOLDED]
2573            [middle,end] = [OUTPUT_MIDDLE,OUTPUT_END]
2574        else:
2575            #print('ERROR! NO IO MARKERS!')
2576            return 1 # Blank marker
2577
2578        if marker & 1<<start:
2579            self.MarkerDelete(line_num,start_folded)
2580            self.MarkerDelete(line_num,middle)
2581            self.MarkerDelete(line_num,end)
2582        elif marker & 1<<start_folded:
2583            self.MarkerDelete(line_num,middle)
2584            self.MarkerDelete(line_num,end)
2585        elif marker & 1<<middle:
2586            self.MarkerDelete(line_num,end)
2587        elif marker & 1<<end:
2588            pass
2589
2590        return 0
2591
2592    def RestoreFirstMarker(self):
2593        first_marker=self.MarkerGet(0)
2594        self.clearGroupingMarkers(0)
2595        self.clearIOMarkers(0)
2596
2597        if first_marker & 1<<GROUPING_START :
2598            self.MarkerAdd(0,GROUPING_START)
2599        elif first_marker & 1<<GROUPING_START_FOLDED :
2600            self.MarkerAdd(0,GROUPING_START_FOLDED)
2601        else:
2602            self.MarkerAdd(0,GROUPING_START)
2603
2604        if first_marker & 1<<INPUT_START :
2605            self.MarkerAdd(0,INPUT_START)
2606        elif first_marker & 1<<INPUT_START_FOLDED :
2607            self.MarkerAdd(0,INPUT_START_FOLDED)
2608        elif first_marker & 1<<OUTPUT_START :
2609            self.MarkerAdd(0,OUTPUT_START)
2610            #self.MarkerAdd(0,OUTPUT_BG) # More harm than good??
2611        elif first_marker & 1<<OUTPUT_START_FOLDED :
2612            self.MarkerAdd(0,OUTPUT_START_FOLDED)
2613            #self.MarkerAdd(0,OUTPUT_BG) # More harm than good??
2614        else:
2615            self.MarkerAdd(0,INPUT_START)
2616
2617        if self.doHistUpdate:
2618            self.UpdateUndoHistoryAfter()
2619
2620    def IsAllowedPair(self,m1,m2):
2621        """This testing function ensures that two adjacent markers are valid"""
2622        i_s = 1<<INPUT_START | 1<<INPUT_START_FOLDED
2623        o_s = 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED
2624        g_s = 1<<GROUPING_START | 1<<GROUPING_START_FOLDED
2625        i_m,o_m,g_m = 1<<INPUT_MIDDLE, 1<<OUTPUT_MIDDLE, 1<<GROUPING_MIDDLE
2626        i_e,o_e,g_e = 1<<INPUT_END, 1<<OUTPUT_END, 1<<GROUPING_END
2627
2628        if (m1 & i_s) and (m1 & g_s):     #1
2629            if   (m2 & i_s) and (m2 & g_s): return True  #1
2630            elif (m2 & i_m) and (m2 & g_m): return True  #2
2631            elif (m2 & i_e) and (m2 & g_m): return True  #3
2632            elif (m2 & i_e) and (m2 & g_e): return True  #4
2633            elif (m2 & o_s) and (m2 & g_s): return False #5
2634            elif (m2 & o_s) and (m2 & g_m): return True  #6
2635            elif (m2 & o_s) and (m2 & g_e): return True  #7
2636            elif (m2 & o_m) and (m2 & g_m): return False #8
2637            elif (m2 & o_e) and (m2 & g_e): return False #9
2638            else: return False
2639        elif (m1 & i_m) and (m1 & g_m):   #2
2640            if   (m2 & i_m) and (m2 & g_m): return True  #2
2641            elif (m2 & i_e) and (m2 & g_m): return True  #3
2642            elif (m2 & i_e) and (m2 & g_e): return True  #4
2643            else: return False
2644        elif (m1 & i_e) and (m1 & g_m):   #3
2645            if   (m2 & o_s) and (m2 & g_m): return True  #6
2646            elif (m2 & o_s) and (m2 & g_e): return True  #7
2647            else: return False
2648        elif (m1 & i_e) and (m1 & g_e):   #4
2649            if   (m2 & i_s) and (m2 & g_s): return True  #1
2650            elif (m2 & o_s) and (m2 & g_s): return True  #5
2651            else: return False
2652        elif (m1 & o_s) and (m1 & g_s):   #5
2653            if   (m2 & i_s) and (m2 & g_s): return True  #1
2654            elif (m2 & i_m) and (m2 & g_m): return False #2
2655            elif (m2 & i_e) and (m2 & g_m): return False #3
2656            elif (m2 & i_e) and (m2 & g_e): return False #4
2657            elif (m2 & o_s) and (m2 & g_s): return True  #5
2658            elif (m2 & o_s) and (m2 & g_m): return False #6
2659            elif (m2 & o_s) and (m2 & g_e): return False #7
2660            elif (m2 & o_m) and (m2 & g_m): return True  #8
2661            elif (m2 & o_e) and (m2 & g_e): return True  #9
2662            else: return False
2663        elif (m1 & o_s) and (m1 & g_m):   #6
2664            if   (m2 & o_m) and (m2 & g_m): return True  #8
2665            elif (m2 & o_e) and (m2 & g_e): return True  #9
2666            else: return False
2667        elif (m1 & o_s) and (m1 & g_e):   #7
2668            if   (m2 & i_s) and (m2 & g_s): return True  #1
2669            elif (m2 & o_s) and (m2 & g_s): return True  #5
2670            else: return False
2671        elif (m1 & o_m) and (m1 & g_m):   #8
2672            if   (m2 & o_m) and (m2 & g_m): return True  #8
2673            elif (m2 & o_e) and (m2 & g_e): return True  #9
2674            else: return False
2675        elif (m1 & o_e) and (m1 & g_e):   #9
2676            if   (m2 & i_s) and (m2 & g_s): return True  #1
2677            elif (m2 & o_s) and (m2 & g_s): return True  #5
2678            else: return False
2679        else:
2680            return False
2681
2682
2683    def CleanAllMarkers(self):
2684        self.RestoreFirstMarker()
2685        first_marker=self.MarkerGet(0)
2686        last_line_num=self.GetLineCount()-1
2687
2688        for i in range(1,last_line_num):
2689            self.ensureSingleGroupingMarker(i)
2690            self.ensureSingleIOMarker(i)
2691
2692            previous_marker=self.MarkerGet(i-1)
2693            marker=self.MarkerGet(i)
2694
2695            if not self.IsAllowedPair(previous_marker,marker):
2696                pass # FIX MARKER!!
2697            # FIX ME
2698
2699    def write(self, text,type='Input',silent=False):
2700        """Display text in the slices shell.
2701
2702        Replace line endings with OS-specific endings."""
2703        text = self.fixLineEndings(text)
2704        split=text.split(os.linesep)
2705        self.AddText(text)
2706
2707        # This part handles all the marker stuff that accompanies
2708        # adding or removing new lines of text...
2709        # Get the total number of lines in the Document == last line number
2710        last_line_num=self.GetLineCount()-1
2711        # Get the line number we ended on in the write
2712        end_line_num=self.GetCurrentLine()
2713        # Get the number of returns we are using == number of lines we pasted -1
2714        num_new_lines=text.count(os.linesep)
2715        # So if num_new_lines==0, start_line_num and end_line_num are the same
2716        start_line_num=end_line_num-num_new_lines+1
2717
2718        # This is a little unnecessary because there will always
2719        # be a line before if we just inserted a newline!
2720        if start_line_num == 0:
2721            previous_line_num=None
2722        else:
2723            previous_line_num=start_line_num-1
2724
2725        #However, this is very important...
2726        if end_line_num == last_line_num:
2727            next_line_num=None
2728        else:
2729            next_line_num=end_line_num+1
2730
2731        if type=='Input':
2732            start = INPUT_START
2733            start_folded = INPUT_START_FOLDED
2734            middle = INPUT_MIDDLE
2735            end = INPUT_END
2736            # preparation for more io types...
2737            opposite_start_mask = 1<<OUTPUT_START
2738            opposite_start_folded_mask = 1<<OUTPUT_START_FOLDED
2739            opposite_middle_mask = 1<<OUTPUT_MIDDLE # To test for bad writes...
2740            opposite_end_mask = 1<<OUTPUT_END # To test for bad writes...
2741        elif type in ['Output','Error']:
2742            #self.MarkerAdd(start_line_num,GROUPING_START_FOLDED)
2743            start=OUTPUT_START
2744            start_folded=OUTPUT_START_FOLDED
2745            middle=OUTPUT_MIDDLE
2746            end=OUTPUT_END
2747            # preparation for more io types...
2748            opposite_start_mask = 1<<INPUT_START
2749            opposite_start_folded_mask = 1<<INPUT_START_FOLDED
2750            opposite_middle_mask = 1<<INPUT_MIDDLE # To test for bad writes...
2751            opposite_end_mask = 1<<INPUT_END # To test for bad writes...
2752
2753        if num_new_lines>0: #Do nothing if typing within a line...
2754            # Update the Grouping Markers
2755            # For the previous line and the start_line
2756            # Test to make sure we can write ... but not here ...
2757            #    test this before we call write or before we add text...
2758            # So we assume it already obeys the rules
2759
2760            badMarkers=False
2761            fixIOEnd=True
2762
2763            if previous_line_num==None:
2764                # This is an impossible case, here just for completeness...
2765                self.clearGroupingMarkers(start_line_num)
2766                self.MarkerAdd(start_line_num,GROUPING_START)
2767
2768                self.clearIOMarkers(start_line_num)
2769                self.MarkerAdd(start_line_num,start)
2770                if type in ['Output','Error']: self.MarkerAdd(start_line_num,OUTPUT_BG)
2771            else:
2772                previous_marker=self.MarkerGet(previous_line_num)
2773                if previous_marker & opposite_middle_mask:
2774                    badMarkers=True
2775
2776            if next_line_num==None:
2777                self.MarkerAdd(end_line_num,GROUPING_END)
2778                self.MarkerAdd(end_line_num,end)
2779                if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
2780                fixEndMarkers=False
2781                # May be overwritten below if start_line_num==end_line_num...
2782            else:
2783                next_marker=self.MarkerGet(next_line_num)
2784                fixEndMarkers=True
2785                if next_marker & ( opposite_middle_mask | opposite_end_mask ):
2786                    badMarkers=True
2787
2788            if not badMarkers:
2789                # ensure previous_line only has one marker & turn end into middle
2790                if previous_line_num!=None:
2791                    # Adjust previous line appropriately, ensure only one marker
2792                    # Only print errors if we are on input!
2793                    blank=False
2794                    blank=blank or self.ensureSingleGroupingMarker(previous_line_num)
2795                    blank=blank or self.ensureSingleIOMarker(previous_line_num)
2796
2797                    if blank:
2798                        #if type=='Input' and not silent: print('BLANK LINE!' # BAD CASE)
2799                        pass
2800
2801                    if previous_marker & 1<<GROUPING_END :
2802                        # Make GROUPING slice continue unless we hit
2803                        #  an output end and are starting a new input...
2804                        if (previous_marker & OUTPUT_MASK) and type=='Input':
2805                            pass
2806                        else:
2807                            self.MarkerDelete(previous_line_num,GROUPING_END)
2808                            # ONLY CHANGING CASE
2809                            self.MarkerAdd(previous_line_num,GROUPING_MIDDLE)
2810
2811                    if previous_marker & 1<<end :
2812                        self.MarkerDelete(previous_line_num,end)
2813                        self.MarkerAdd(previous_line_num,middle) # ONLY CHANGING CASE
2814                        if type in ['Output','Error']: self.MarkerAdd(previous_line_num,OUTPUT_BG)
2815                    elif previous_marker & opposite_middle_mask :
2816                         # BAD CASE
2817                        if type=='Input' and not silent:
2818                            #print('Should have been a bad marker!')
2819                            pass
2820
2821                    # We can only add input to an input slice
2822                    # And can only add output to an output slice
2823
2824                    if previous_marker & ( opposite_start_mask |
2825                                           opposite_start_folded_mask |
2826                                           opposite_end_mask ):
2827                        if type=='Input':
2828                            self.clearGroupingMarkers(start_line_num)
2829                            self.MarkerAdd(start_line_num,GROUPING_START)
2830                            if start_line_num==end_line_num:
2831                                fixEndMarkers=False
2832                        else:
2833                            if start_line_num==end_line_num:
2834                                fixIOEnd=False
2835                        self.clearIOMarkers(start_line_num)
2836                        self.MarkerAdd(start_line_num,start)
2837                        if type in ['Output','Error']: self.MarkerAdd(start_line_num,OUTPUT_BG)
2838                    else:
2839                        if next_line_num!=None:
2840                            self.clearGroupingMarkers(start_line_num)
2841                            self.clearIOMarkers(start_line_num)
2842                            self.MarkerAdd(start_line_num,GROUPING_MIDDLE)
2843                            self.MarkerAdd(start_line_num,middle)
2844                            if type in ['Output','Error']: self.MarkerAdd(start_line_num,OUTPUT_BG)
2845                            # This may be overwritten if start_line_num==end_line_num
2846
2847                # Take care of all the middle lines...
2848                # Does nothing for only one line...
2849                for i in range(start_line_num,end_line_num):
2850                    self.clearGroupingMarkers(i)
2851                    self.MarkerAdd(i,GROUPING_MIDDLE)
2852
2853                    self.clearIOMarkers(i)
2854                    self.MarkerAdd(i,middle)
2855                    if type in ['Output','Error']: self.MarkerAdd(i,OUTPUT_BG)
2856
2857                if fixEndMarkers:
2858                    # Take care of the end_line if we haven't already done so...
2859                    blank=False
2860                    blank=blank or self.ensureSingleGroupingMarker(next_line_num)
2861                    blank=blank or self.ensureSingleIOMarker(next_line_num)
2862
2863                    if blank:
2864                        if type=='Input' and not silent:
2865                            #print('BLANK LINE!' # BAD CASE)
2866                            pass
2867
2868                    self.clearGroupingMarkers(end_line_num)
2869                    if fixIOEnd:
2870                        self.clearIOMarkers(end_line_num)
2871
2872                    if next_marker & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
2873                        self.MarkerAdd(end_line_num,GROUPING_END)
2874                    elif next_marker & ( 1<<GROUPING_MIDDLE | 1<<GROUPING_END ) :
2875                        self.MarkerAdd(end_line_num,GROUPING_MIDDLE)
2876
2877                    if fixIOEnd:
2878                        if next_marker & ( 1<<start | 1<<start_folded ) :
2879                            self.MarkerAdd(end_line_num,end)
2880                            if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
2881                        elif next_marker & ( 1<<middle | 1<<end ) :
2882                            self.MarkerAdd(end_line_num,middle)
2883                            if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
2884                        elif next_marker & ( opposite_start_mask |
2885                                             opposite_start_folded_mask ):
2886                            self.MarkerAdd(end_line_num,end)
2887                            if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
2888                        else:
2889                            self.MarkerAdd(end_line_num,start_folded)
2890                            if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
2891                            if type=='Input' and not silent:
2892                                #print('BAD MARKERS!')
2893                                pass
2894            else:
2895                if type=='Input' and not silent:
2896                    #print('BAD MARKERS!!!')
2897                    pass
2898
2899        self.EnsureCaretVisible()
2900
2901        if self.waiting:
2902            if self.lastUpdate==None:
2903                self.lastUpdate=time.time()
2904            if time.time()-self.lastUpdate > PRINT_UPDATE_MAX_TIME:
2905                self.Update()
2906                self.lastUpdate=time.time()
2907
2908    def fixLineEndings(self, text):
2909        """Return text with line endings replaced by OS-specific endings."""
2910        lines = text.split('\r\n')
2911        for l in range(len(lines)):
2912            chunks = lines[l].split('\r')
2913            for c in range(len(chunks)):
2914                chunks[c] = os.linesep.join(chunks[c].split('\n'))
2915            lines[l] = os.linesep.join(chunks)
2916        text = os.linesep.join(lines)
2917        return text
2918
2919    def prompt(self): # Autoindent added!!!
2920        """Display proper prompt for the context: ps1, ps2 or ps3.
2921
2922        If this is a continuation line, autoindent as necessary."""
2923        # TODO : How much of this can I do away with now without prompts??
2924
2925        isreading = self.reader.isreading
2926
2927        skip = True
2928        if isreading:
2929            prompt = str(sys.ps3)
2930        elif self.more:
2931            prompt = str(sys.ps2)
2932        else:
2933            prompt = str(sys.ps1)
2934        pos = self.GetCurLine()[1]
2935        if pos > 0:
2936            if isreading:
2937                skip = True
2938            else:
2939                self.write(os.linesep,type='Input')
2940        if not self.more:
2941            # Not needed anymore! # self.promptPosStart = self.GetCurrentPos()
2942            pass
2943        if not skip:
2944            self.write(prompt,type='Input')
2945        if not self.more:
2946            # Not needed anymore! # self.promptPosEnd = self.GetCurrentPos()
2947            # Clear the undo history after running a command.
2948            self.EmptyUndoBuffer()
2949
2950        #DNM/CP
2951        # Autoindent magic
2952        # Match the indent of the line above
2953        # UNLESS the line above ends in a colon...then add four spaces
2954        # (after valid keywords (if, else, etc...) only)
2955        if self.more:
2956            line_num=self.GetCurrentLine()
2957            currentLine=self.GetLine(line_num)
2958            previousLine=self.GetLine(line_num-1)
2959            pstrip=previousLine.strip()
2960            lstrip=previousLine.lstrip()
2961
2962            if pstrip == '':
2963                # because it is all whitespace!
2964                indent=previousLine.strip('\n').strip('\r')
2965            else:
2966                indent=previousLine[:(len(previousLine)-len(lstrip))]
2967                if testForContinuations(previousLine,ignoreErrors=True)[1][0]:
2968                    indent+=' '*4
2969
2970            #ADD UNDO
2971            cpos=self.GetCurrentPos()
2972            s=indent
2973            self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s))
2974            self.write(s,type='Input')
2975            self.UpdateUndoHistoryAfter()
2976
2977
2978        self.EnsureCaretVisible()
2979        self.ScrollToColumn(0)
2980
2981    def readline(self):
2982        """Replacement for stdin.readline()."""
2983        input = ''
2984        reader = self.reader
2985        reader.isreading = True
2986        self.prompt()
2987
2988        # Ensure that we get a new line and that it's got an input marker...
2989        # Also need to temporarily block any other action...
2990        cLine = self.GetCurrentLine()
2991        self.clearIOMarkers(cLine)
2992        self.MarkerAdd(cLine,INPUT_START)
2993        self.MarkerAdd(cLine,READLINE_BG)
2994        self.MarkerAdd(cLine,INPUT_READLINE)
2995
2996        try:
2997            while not reader.input:
2998                wx.GetApp().Yield(onlyIfNeeded=True)
2999            input = reader.input
3000        finally:
3001            start,end = self.GetIOSlice()
3002            start = self.runningSlice[1] + 1
3003            for i in range(start,end+1):
3004                self.clearIOMarkers(i)
3005                self.clearGroupingMarkers(i)
3006                self.MarkerAdd(i,OUTPUT_BG)
3007                if i == start:    self.MarkerAdd(i,OUTPUT_START)
3008                elif i==end:      self.MarkerAdd(i,OUTPUT_END)
3009                else:             self.MarkerAdd(i,OUTPUT_MIDDLE)
3010
3011                if i==end:        self.MarkerAdd(i,GROUPING_END)
3012                else:             self.MarkerAdd(i,GROUPING_MIDDLE)
3013            reader.input = ''
3014            reader.isreading = False
3015        input = str(input)  # In case of Unicode.
3016        return input
3017
3018    def readlines(self):
3019        """Replacement for stdin.readlines()."""
3020        lines = []
3021        while lines[-1:] != ['\n']:
3022            lines.append(self.readline())
3023        return lines
3024
3025    def raw_input(self, prompt=''):
3026        """Return string based on user input."""
3027        if prompt:
3028            self.write(prompt,type='Output')
3029        return self.readline()
3030
3031    def ask(self, prompt='Please enter your response:'):
3032        """Get response from the user using a dialog box."""
3033        dialog = wx.TextEntryDialog(None, prompt,
3034                                    'Input Dialog (Raw)', '')
3035        try:
3036            if dialog.ShowModal() == wx.ID_OK:
3037                text = dialog.GetValue()
3038                return text
3039        finally:
3040            dialog.Destroy()
3041        return ''
3042
3043    def pause(self):
3044        """Halt execution pending a response from the user."""
3045        self.ask('Press enter to continue:')
3046
3047    def clear(self):
3048        """Delete all text from the slices shell."""
3049        self.ClearAll()
3050        self.MarkerAdd(0,GROUPING_START)
3051        self.MarkerAdd(0,INPUT_START)
3052
3053    def run(self, command, prompt=True, verbose=True):
3054        """Execute command as if it was typed in directly.
3055        >>> shell.run('print("this")')
3056        >>> print("this")
3057        this
3058        >>>
3059        """
3060        # Go to the very bottom of the text.
3061        endpos = self.GetTextLength()
3062        self.SetCurrentPos(endpos)
3063        command = command.rstrip()
3064        if prompt: self.prompt()
3065        if verbose: self.write(command,type='Input')
3066        self.push(command)
3067
3068    # TODO : Will have to fix this to handle other kinds of errors mentioned before...
3069    def runfile(self, filename):
3070        """Execute all commands in file as if they were typed into the shell."""
3071        file = open(filename)
3072        try:
3073            self.prompt()
3074            for command in file.readlines():
3075                if command[:6] == 'shell.':
3076                    # Run shell methods silently.
3077                    self.run(command, prompt=False, verbose=False)
3078                else:
3079                    self.run(command, prompt=False, verbose=True)
3080        finally:
3081            file.close()
3082
3083    def autoCompleteShow(self, command, offset = 0):
3084        """Display auto-completion popup list."""
3085        self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
3086        self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
3087        list = self.interp.getAutoCompleteList(command,
3088                    includeMagic=self.autoCompleteIncludeMagic,
3089                    includeSingle=self.autoCompleteIncludeSingle,
3090                    includeDouble=self.autoCompleteIncludeDouble)
3091        if list:
3092            options = ' '.join(list)
3093            #offset = 0
3094            self.AutoCompShow(offset, options)
3095
3096    def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False):
3097        """Display argument spec and docstring in a popup window."""
3098        if self.CallTipActive():
3099            self.CallTipCancel()
3100        (name, argspec, tip) = self.interp.getCallTip(command)
3101        if tip:
3102            dispatcher.send(signal='SlicesShell.calltip', sender=self, calltip=tip)
3103        if not self.autoCallTip and not forceCallTip:
3104            return
3105        startpos = self.GetCurrentPos()
3106        if argspec and insertcalltip and self.callTipInsert:
3107            # write with undo history...
3108            cpos=self.GetCurrentPos()
3109            s=argspec + ')'
3110            self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s))
3111            self.write(s,type='Input')
3112            self.UpdateUndoHistoryAfter()
3113
3114            endpos = self.GetCurrentPos()
3115            self.SetSelection(startpos, endpos)
3116        if tip:
3117            tippos = startpos - (len(name) + 1)
3118            fallback = startpos - self.GetColumn(startpos)
3119            # In case there isn't enough room, only go back to the fallback.
3120            tippos = max(tippos, fallback)
3121            self.CallTipShow(tippos, tip)
3122
3123    def OnCallTipAutoCompleteManually (self, shiftDown):
3124        """AutoComplete and Calltips manually."""
3125        if self.AutoCompActive():
3126            self.AutoCompCancel()
3127        currpos = self.GetCurrentPos()
3128        stoppos = self.PositionFromLine(self.GetIOSlice()[0])
3129
3130        cpos = currpos
3131        #go back until '.' is found
3132        pointavailpos = -1
3133        while cpos >= stoppos:
3134            if self.GetCharAt(cpos) == ord ('.'):
3135                pointavailpos = cpos
3136                break
3137            cpos -= 1
3138
3139        #word from non whitespace until '.'
3140        if pointavailpos != -1:
3141            #look backward for first whitespace char
3142            textbehind = self.GetTextRange (pointavailpos + 1, currpos)
3143            pointavailpos += 1
3144
3145            if not shiftDown:
3146                #call AutoComplete
3147                stoppos = self.PositionFromLine(self.GetIOSlice()[0])
3148                textbefore = self.GetTextRange(stoppos, pointavailpos)
3149                self.autoCompleteShow(textbefore, len (textbehind))
3150            else:
3151                #call CallTips
3152                cpos = pointavailpos
3153                begpos = -1
3154                while cpos > stoppos:
3155                    if chr(self.GetCharAt(cpos)).isspace():
3156                        begpos = cpos
3157                        break
3158                    cpos -= 1
3159                if begpos == -1:
3160                    begpos = cpos
3161                ctips = self.GetTextRange (begpos, currpos)
3162                ctindex = ctips.find ('(')
3163                if ctindex != -1 and not self.CallTipActive():
3164                    #insert calltip, if current pos is '(', otherwise show it only
3165                    self.autoCallTipShow( ctips[:ctindex + 1],
3166                          self.GetCharAt(currpos - 1) == ord('(') and
3167                              self.GetCurrentPos() == self.GetTextLength(),
3168                          True )
3169
3170
3171    def writeOut(self, text):
3172        """Replacement for stdout."""
3173        self.write(text,type='Output')
3174        # TODO : FLUSH?? How to make this update real-time...
3175
3176    def writeErr(self, text):
3177        """Replacement for stderr."""
3178        self.write(text,type='Error')
3179
3180    def redirectStdin(self, redirect=True):
3181        """If redirect is true then sys.stdin will come from the shell."""
3182        if redirect:
3183            sys.stdin = self.reader
3184        else:
3185            sys.stdin = self.stdin
3186
3187    def redirectStdout(self, redirect=True):
3188        """If redirect is true then sys.stdout will go to the shell."""
3189        if redirect:
3190            sys.stdout = PseudoFileOut(self.writeOut)
3191        else:
3192            sys.stdout = self.stdout
3193
3194    def redirectStderr(self, redirect=True):
3195        """If redirect is true then sys.stderr will go to the shell."""
3196        if redirect:
3197            sys.stderr = PseudoFileErr(self.writeErr)
3198        else:
3199            sys.stderr = self.stderr
3200
3201    # Take a spashot of the WHOLE grouping slice (or slices)
3202    # The argument s is either what got added or deleted
3203    def UpdateUndoHistoryBefore(self,actionType,s,posStart,posEnd,
3204                                forceNewAction=False):
3205        uH=self.undoHistory
3206        uI=self.undoIndex
3207
3208        s=s.replace(os.linesep,'\n')
3209        startLine=self.LineFromPosition(posStart)
3210
3211        if actionType=='marker':
3212            numLines = self.LineFromPosition(posEnd) - startLine
3213        else:
3214            numLines=s.count('\n')
3215
3216        makeNewAction=forceNewAction
3217
3218        if forceNewAction:
3219            makeNewAction=True
3220        elif self.undoIndex==-1:
3221            makeNewAction=True
3222        elif not uH[uI]['allowsAppend']:
3223            makeNewAction=True
3224        elif actionType!=uH[uI]['actionType']:
3225            makeNewAction=True
3226        elif actionType=='insert':
3227            if posStart!=uH[uI]['posEnd']:
3228                makeNewAction=True
3229            else: # This is a continuation of the previous insert
3230                uH[uI]['charList'] = uH[uI]['charList']+s
3231                uH[uI]['posEnd']   = posEnd # posStart cannot move
3232                uH[uI]['numLines'] = uH[uI]['numLines']+numLines
3233        elif actionType=='delete':
3234            # This is a forward continuation of the previous delete
3235            if posStart==uH[uI]['posStart']:
3236                uH[uI]['charList'] = uH[uI]['charList']+s
3237                uH[uI]['posEnd'] = posEnd
3238                uH[uI]['numLines'] = uH[uI]['numLines']+numLines
3239            # This is a backward continuation of the previous delete
3240            elif posEnd==uH[uI]['posStart']:
3241                uH[uI]['charList'] = s+uH[uI]['charList']
3242                uH[uI]['posStart'] = posStart
3243                uH[uI]['startLine'] = startLine
3244                uH[uI]['numLines'] = uH[uI]['numLines']+numLines
3245            else:
3246                makeNewAction=True
3247
3248        elif actionType=='marker':
3249            makeNewAction=True
3250        else:
3251            pass #print('Unsupported Action Type!!')
3252
3253        if makeNewAction:
3254            del(self.undoHistory[uI+1:]) # remove actions after undoIndex
3255
3256            uH.append({
3257              'actionType' : actionType,    # Action type ('insert','delete','marker')
3258              'allowsAppend': not forceNewAction, # Can action be joined with others?
3259              'charList' : s,        # Character list
3260              'posStart' : posStart, # Cursor poition at the start of the action
3261              'posEnd' : posEnd,     # Cursor position at the end of the action
3262              'startLine' : startLine,  # Start line number,
3263              'numLines' : numLines,  # Number of newlines involved
3264              'mBStart' : None,       # Starting line for markers BEFORE action
3265              'mAStart' : None,       # Starting line for markers AFTER action
3266              'markersBefore' : None, # [markers BEFORE action]
3267              'markersAfter' : None   # [markers AFTER action]
3268             })
3269
3270            self.undoIndex+=1
3271
3272            # Only update the before when starting a new action
3273            start = startLine
3274            if actionType=='insert':
3275                end = start
3276            else:
3277                end = start + numLines
3278
3279            # Update Marker Info
3280            newStart=self.GetGroupingSlice(start)[0]
3281            newEnd=self.GetGroupingSlice(end)[1]
3282            self.undoHistory[self.undoIndex]['markersBefore'] = \
3283                 [self.MarkerGet(i) for i in range(newStart,newEnd+1)]
3284            self.undoHistory[self.undoIndex]['mBStart']=newStart
3285
3286        self.doHistUpdate=True
3287
3288    def UpdateUndoHistoryAfter(self): # s is either what got added or deleted
3289        start = self.undoHistory[self.undoIndex]['startLine']
3290        if self.undoHistory[self.undoIndex]['actionType']=='delete':
3291            end = start
3292        else:
3293            end = start + self.undoHistory[self.undoIndex]['numLines']
3294
3295        newStart=min(self.GetGroupingSlice(start)[0]-1, 0)
3296        newEnd=max(self.GetGroupingSlice(end)[1]+1, self.GetLineCount()-1)
3297        self.undoHistory[self.undoIndex]['markersAfter'] = \
3298             [self.MarkerGet(i) for i in range(newStart,newEnd+1)]
3299        self.undoHistory[self.undoIndex]['mAStart']=newStart
3300
3301        self.doHistUpdate=False
3302
3303    def Undo(self):
3304        #ADD UNDO
3305        #Skip undo if there are no actions...
3306        if self.undoIndex==-1:
3307            return
3308
3309        uHI=self.undoHistory[self.undoIndex]
3310
3311        if uHI['actionType'] in ['insert','delete']:
3312            # This will perform the opposite of the action given
3313            editwindow.EditWindow.Undo(self)
3314        elif uHI['actionType']=='marker': # No text changed, don't pass to STC
3315            pass
3316        else:
3317            #print('Unsupported actionType in undoHistory!!')
3318            return
3319
3320        numLines=len(uHI['markersBefore'])
3321        for i in range(numLines):
3322            self.MarkerSet( uHI['mBStart']+i , uHI['markersBefore'][i] )
3323
3324        self.undoIndex-=1
3325
3326    def Redo(self):
3327        #ADD UNDO
3328        # First check to see if there are any redo operations available
3329        # Note that for redo, undoIndex=-1 is a valid input
3330        if self.undoIndex >= len(self.undoHistory)-1:
3331            return
3332        self.undoIndex+=1
3333        uHI=self.undoHistory[self.undoIndex]
3334
3335        if uHI['actionType'] in ['insert','delete']:
3336            # This will re-perform the given action
3337            editwindow.EditWindow.Redo(self)
3338        elif uHI['actionType']=='marker': # No text changed, don't pass to STC
3339            pass
3340        else:
3341            #print('Unsupported actionType in undoHistory!!')
3342            return
3343
3344        numLines=len(uHI['markersAfter'])
3345        for i in range(numLines):
3346            self.MarkerSet( uHI['mAStart']+i , uHI['markersAfter'][i] )
3347
3348    def EmptyUndoBuffer(self):
3349        editwindow.EditWindow.EmptyUndoBuffer(self)
3350        self.undoIndex=-1
3351        self.undoHistory=[]
3352        self.doHistUpdate=False
3353
3354    def CanCut(self):
3355        return self.CanEdit() and \
3356               (self.GetSelectionStart() != self.GetSelectionEnd())
3357
3358    def CanPaste(self):
3359        """Return true if a paste should succeed."""
3360        if self.CanEdit() and editwindow.EditWindow.CanPaste(self):
3361            return True
3362        else:
3363            return False
3364
3365    def CanEdit(self):
3366        """Return true if editing should succeed."""
3367        marker=self.MarkerGet(self.GetCurrentLine())
3368
3369        if marker & OUTPUT_MASK:
3370            return False
3371        elif marker & INPUT_MASK:
3372            if self.reader.isreading and not \
3373                    (self.MarkerGet(self.GetCurrentLine()) & 1<<INPUT_READLINE ):
3374                return False
3375            start,end=self.GetIOSlice()
3376            sliceStartPos=self.PositionFromLine(start)
3377            sliceEndPos=self.GetLineEndPosition(end)
3378            """Return true if text is selected and can be cut."""
3379            if self.GetSelectionStart() == self.GetSelectionEnd():
3380                return True
3381            elif self.GetSelectionStart() != self.GetSelectionEnd() \
3382                   and self.GetSelectionStart() >= sliceStartPos \
3383                   and self.GetSelectionEnd() >= sliceStartPos \
3384                   and self.GetSelectionStart() <= sliceEndPos \
3385                   and self.GetSelectionEnd() <= sliceEndPos:
3386                return True
3387        return False
3388
3389    def Cut(self):
3390        """Remove selection and place it on the clipboard."""
3391        if self.CanCut() and self.CanCopy():
3392            if self.AutoCompActive():
3393                self.AutoCompCancel()
3394            if self.CallTipActive():
3395                self.CallTipCancel()
3396            self.Copy()
3397            self.ReplaceSelection('')
3398
3399    def Copy(self):
3400        """Copy selection and place it on the clipboard."""
3401        if self.CanCopy():
3402            ps1 = str(sys.ps1)
3403            ps2 = str(sys.ps2)
3404            command = self.GetSelectedText()
3405            command = command.replace(os.linesep + ps2, os.linesep)
3406            command = command.replace(os.linesep + ps1, os.linesep)
3407            command = self.lstripPrompt(text=command)
3408            data = wx.TextDataObject(command)
3409            self._clip(data)
3410
3411    def CopyWithPrompts(self):
3412        """Copy selection, including prompts, and place it on the clipboard."""
3413        if self.CanCopy():
3414            command = self.GetSelectedText()
3415            data = wx.TextDataObject(command)
3416            self._clip(data)
3417
3418    def CopyWithPromptsPrefixed(self):
3419        """Copy selection, including prompts prefixed with four
3420        spaces, and place it on the clipboard."""
3421        if self.CanCopy():
3422            command = self.GetSelectedText()
3423            spaces = ' ' * 4
3424            command = spaces + command.replace(os.linesep,
3425                                               os.linesep + spaces)
3426            data = wx.TextDataObject(command)
3427            self._clip(data)
3428
3429    def _clip(self, data):
3430        if wx.TheClipboard.Open():
3431            wx.TheClipboard.UsePrimarySelection(False)
3432            wx.TheClipboard.SetData(data)
3433            wx.TheClipboard.Flush()
3434            wx.TheClipboard.Close()
3435
3436    def Paste(self):
3437        """Replace selection with clipboard contents."""
3438
3439        #ADD UNDO
3440        if self.CanPaste() and wx.TheClipboard.Open():
3441            ps2 = str(sys.ps2)
3442            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
3443                data = wx.TextDataObject()
3444                if wx.TheClipboard.GetData(data):
3445                    self.ReplaceSelection('')
3446                    command = data.GetText()
3447                    command = command.rstrip()
3448                    command = self.fixLineEndings(command)
3449                    command = self.lstripPrompt(text=command)
3450                    # TODO : This is still useful... Add it back other places?
3451                    command = command.replace(os.linesep + ps2, '\n')
3452                    command = command.replace(os.linesep, '\n')
3453                    #DNM--Don't use '... '
3454                    command = command.replace('\n', os.linesep)# + ps2)
3455
3456                    cpos=self.GetCurrentPos()
3457                    s=command
3458                    self.UpdateUndoHistoryBefore('insert', s, cpos,
3459                                            cpos+len(s), forceNewAction=True)
3460                    self.write(s,type='Input')
3461                    self.UpdateUndoHistoryAfter()
3462
3463                    # Makes paste -> type -> undo consistent with other STC apps
3464                    self.ReplaceSelection('')
3465            wx.TheClipboard.Close()
3466
3467
3468    def PasteAndRun(self):
3469        """Replace selection with clipboard contents, run commands."""
3470        text = ''
3471        if wx.TheClipboard.Open():
3472            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
3473                data = wx.TextDataObject()
3474                if wx.TheClipboard.GetData(data):
3475                    text = data.GetText()
3476            wx.TheClipboard.Close()
3477        if text:
3478            self.Execute(text)
3479
3480
3481    def Execute(self, text):
3482        """Replace selection with text and run commands."""
3483        start,end=self.GetIOSlice()
3484        startpos=self.PositionFromLine(start)
3485        endpos=self.GetLineEndPosition(end)
3486
3487        self.SetCurrentPos(endpos)
3488        self.SetSelection(startpos, endpos)
3489        self.ReplaceSelection('')
3490
3491        hasSyntaxError=False
3492        result = self.BreakTextIntoCommands(command)
3493        if result[0] == None:
3494            commands=[command]
3495            hasSyntaxError=True
3496        else:
3497            commands=result
3498
3499        for command in commands:
3500            command = command.replace('\n', os.linesep)
3501            self.write(command)
3502            self.processLine()
3503
3504    def wrap(self, wrap=True):
3505        """Sets whether text is word wrapped."""
3506        try:
3507            self.SetWrapMode(wrap)
3508        except AttributeError:
3509            return 'Wrapping is not available in this version.'
3510
3511    def zoom(self, points=0):
3512        """Set the zoom level.
3513
3514        This number of points is added to the size of all fonts.  It
3515        may be positive to magnify or negative to reduce."""
3516        self.SetZoom(points)
3517
3518    def LoadSettings(self, config):
3519        self.autoComplete = \
3520                    config.ReadBool('Options/AutoComplete', True)
3521        self.autoCompleteIncludeMagic = \
3522                    config.ReadBool('Options/AutoCompleteIncludeMagic', True)
3523        self.autoCompleteIncludeSingle = \
3524                    config.ReadBool('Options/AutoCompleteIncludeSingle', True)
3525        self.autoCompleteIncludeDouble = \
3526                    config.ReadBool('Options/AutoCompleteIncludeDouble', True)
3527        self.autoCallTip = \
3528                    config.ReadBool('Options/AutoCallTip', True)
3529        self.callTipInsert = \
3530                    config.ReadBool('Options/CallTipInsert', True)
3531
3532        self.SetWrapMode(config.ReadBool('View/WrapMode', True))
3533
3534        self.lineNumbers = \
3535                    config.ReadBool('View/ShowLineNumbers', True)
3536        self.setDisplayLineNumbers (self.lineNumbers)
3537        zoom = config.ReadInt('View/Zoom/Shell', -99)
3538        if zoom != -99:
3539            self.SetZoom(zoom)
3540
3541
3542
3543    def SaveSettings(self, config):
3544        config.WriteBool('Options/AutoComplete', self.autoComplete)
3545        config.WriteBool('Options/AutoCompleteIncludeMagic',
3546                                            self.autoCompleteIncludeMagic)
3547        config.WriteBool('Options/AutoCompleteIncludeSingle',
3548                                            self.autoCompleteIncludeSingle)
3549        config.WriteBool('Options/AutoCompleteIncludeDouble',
3550                                            self.autoCompleteIncludeDouble)
3551        config.WriteBool('Options/AutoCallTip', self.autoCallTip)
3552        config.WriteBool('Options/CallTipInsert', self.callTipInsert)
3553        config.WriteBool('View/WrapMode', self.GetWrapMode())
3554        config.WriteBool('View/ShowLineNumbers', self.lineNumbers)
3555        config.WriteInt('View/Zoom/Shell', self.GetZoom())
3556
3557    def GetContextMenu(self):
3558        """
3559            Create and return a context menu for the slices shell.
3560            This is used instead of the scintilla default menu
3561            in order to correctly respect our immutable buffer.
3562        """
3563        menu = wx.Menu()
3564        menu.Append(self.ID_UNDO, "Undo")
3565        menu.Append(self.ID_REDO, "Redo")
3566
3567        menu.AppendSeparator()
3568
3569        menu.Append(self.ID_CUT, "Cut")
3570        menu.Append(self.ID_COPY, "Copy")
3571        menu.Append(frame.ID_COPY_PLUS, "Copy With Prompts")
3572        menu.Append(self.ID_PASTE, "Paste")
3573        menu.Append(frame.ID_PASTE_PLUS, "Paste And Run")
3574        menu.Append(self.ID_CLEAR, "Clear")
3575
3576        menu.AppendSeparator()
3577
3578        menu.Append(self.ID_SELECTALL, "Select All")
3579        return menu
3580
3581    def OnContextMenu(self, evt):
3582        menu = self.GetContextMenu()
3583        self.PopupMenu(menu)
3584
3585    def OnUpdateUI(self, evt):
3586        id = evt.Id
3587        if id in (self.ID_CUT, self.ID_CLEAR):
3588            evt.Enable(self.CanCut())
3589        elif id in (self.ID_COPY, frame.ID_COPY_PLUS):
3590            evt.Enable(self.CanCopy())
3591        elif id in (self.ID_PASTE, frame.ID_PASTE_PLUS):
3592            evt.Enable(self.CanPaste())
3593        elif id == self.ID_UNDO:
3594            evt.Enable(self.CanUndo())
3595        elif id == self.ID_REDO:
3596            evt.Enable(self.CanRedo())
3597
3598    def LoadPySlicesFile(self,fid):
3599        invalidFileString = 'Not a valid input format'
3600        lineCount=0
3601        groupingStartLines=[0]
3602        ioStartLines=[0]
3603        ioStartTypes=[]
3604        removeComment=False
3605
3606        # Read the initial three (or four) lines that have version and marker information
3607        line=fid.readline()
3608        if line == usrBinEnvPythonText:
3609            line=fid.readline() # Add the option to place #!/usr/bin/env python at the top
3610        if line not in pyslicesFormatHeaderText:  print(invalidFileString); return
3611        line=fid.readline()
3612        if line != groupingStartText:  print(invalidFileString); return
3613        line=fid.readline()
3614        if line == inputStartText:      ioStartTypes.append('input');removeComment=False
3615        elif line == outputStartText:   ioStartTypes.append('output');removeComment=True
3616        else: print(invalidFileString); return
3617
3618        self.ClearAll()
3619
3620        # Write the file's text to the text area
3621        # Capture Marker information to
3622        for i in fid:
3623            if i==groupingStartText:
3624                groupingStartLines.append(lineCount)
3625            elif i==inputStartText:
3626                ioStartLines.append(lineCount)
3627                ioStartTypes.append('input')
3628                removeComment=False
3629            elif i==outputStartText:
3630                ioStartLines.append(lineCount)
3631                ioStartTypes.append('output')
3632                removeComment=True
3633            else:
3634                if removeComment:   w=i[1:].replace(os.linesep,'\n')
3635                else:               w=i.replace(os.linesep,'\n')
3636                self.write(w,'Input',silent=True)
3637                lineCount+=1
3638
3639        if w[-1]=='\n':
3640            lineCount+=1
3641
3642        for i in range(lineCount+1):
3643            self.clearGroupingMarkers(i)
3644            self.clearIOMarkers(i)
3645
3646            doMiddle=False
3647            doEnd=False
3648            if groupingStartLines!=[]:
3649                if i == groupingStartLines[0]:
3650                    self.MarkerAdd(i,GROUPING_START)
3651                    del groupingStartLines[0]
3652                elif i+1 == groupingStartLines[0]:
3653                    doEnd=True
3654                else:
3655                    doMiddle=True
3656            elif i==lineCount-1:
3657                doEnd=True
3658            else:
3659                doMiddle=True
3660
3661            if doMiddle:
3662                self.MarkerAdd(i,GROUPING_MIDDLE)
3663            elif doEnd:
3664                self.MarkerAdd(i,GROUPING_END)
3665
3666            doMiddle=False
3667            doEnd=False
3668            if ioStartLines!=[]:
3669                if i == ioStartLines[0]:
3670                    # Delete the old ioStartTypes (keep the current copy for later use)
3671                    if i>0: del ioStartTypes[0]
3672
3673                    if ioStartTypes[0]=='input':
3674                        self.MarkerAdd(i,INPUT_START)
3675                    elif ioStartTypes[0]=='output':
3676                        self.MarkerAdd(i,OUTPUT_START)
3677                        self.MarkerAdd(i,OUTPUT_BG)
3678                    else:
3679                        #print('Invalid Type!')
3680                        return
3681
3682                    # Only delete markers we are totally finished with...
3683                    # Keep one more "StartTypes" than "StartLines"
3684                    del ioStartLines[0]
3685                elif i+1 == ioStartLines[0]:
3686                    doEnd=True
3687                else:
3688                    doMiddle=True
3689            elif i==lineCount-1:
3690                doEnd=True
3691            else:
3692                doMiddle=True
3693
3694            if doMiddle:
3695                if ioStartTypes[0]=='input':
3696                    self.MarkerAdd(i,INPUT_MIDDLE)
3697                elif ioStartTypes[0]=='output':
3698                    self.MarkerAdd(i,OUTPUT_MIDDLE)
3699                    self.MarkerAdd(i,OUTPUT_BG)
3700                else:
3701                    #print('Invalid Type!')
3702                    return
3703            elif doEnd:
3704                if ioStartTypes[0]=='input':
3705                    self.MarkerAdd(i,INPUT_END)
3706                elif ioStartTypes[0]=='output':
3707                    self.MarkerAdd(i,OUTPUT_END)
3708                    self.MarkerAdd(i,OUTPUT_BG)
3709                else:
3710                    #print('Invalid Type!')
3711                    return
3712
3713        self.EmptyUndoBuffer() # maybe not?
3714
3715
3716    def SavePySlicesFile(self,fid):
3717        addComment=False
3718
3719        def fid_write(s):
3720            fid.write(s.replace('\r\n', '\n')
3721                       .replace('\n', os.linesep)
3722                       .encode('utf-8'))
3723
3724        fid_write(usrBinEnvPythonText)
3725        fid_write(pyslicesFormatHeaderText[-1])
3726        for i in range(self.GetLineCount()):
3727            markers=self.MarkerGet(i)
3728            if markers & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ):
3729                fid_write(groupingStartText)
3730            if markers & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ):
3731                fid_write(inputStartText)
3732                addComment=False
3733            if markers & ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED ):
3734                fid_write(outputStartText)
3735                addComment=True
3736            if addComment:
3737                fid_write(u'#')
3738
3739            fid_write(self.GetLine(i))
3740
3741    # FIX ME!!
3742    def LoadPyFileAsSlice(self,fid):
3743        curpos=self.GetCurrentPos()
3744        start,end = self.GetGroupingSlice()
3745
3746        endpos=self.GetLineEndPosition(end)
3747        self.SetCurrentPos(endpos)
3748        self.SetSelection(endpos, endpos)
3749
3750        text='\n'+fid.read()
3751        self.write(text,'Input')
3752        newpos=self.GetCurrentPos()
3753
3754        self.SetCurrentPos(curpos)
3755        self.SetSelection(curpos,curpos)
3756        self.SplitSlice()
3757        #self.SetCurrentPos(newpos)
3758        #self.SetSelection(newpos,newpos)
3759
3760    def hasChanged(self):
3761        """Return True if contents have changed."""
3762        return self.GetModify() or self.NeedsCheckForSave
3763
3764
3765
3766## NOTE: The DnD of file names is disabled until we can figure out how
3767## best to still allow DnD of text.
3768
3769## #seb : File drag and drop
3770## class FileDropTarget(wx.FileDropTarget):
3771##     def __init__(self, obj):
3772##         wx.FileDropTarget.__init__(self)
3773##         self.obj = obj
3774##     def OnDropFiles(self, x, y, filenames):
3775##         if len(filenames) == 1:
3776##             txt = 'r\"%s\"' % filenames[0]
3777##         else:
3778##             txt = '( '
3779##             for f in filenames:
3780##                 txt += 'r\"%s\" , ' % f
3781##             txt += ')'
3782##         self.obj.AppendText(txt)
3783##         pos = self.obj.GetCurrentPos()
3784##         self.obj.SetCurrentPos( pos )
3785##         self.obj.SetSelection( pos, pos )
3786
3787
3788
3789## class TextAndFileDropTarget(wx.DropTarget):
3790##     def __init__(self, sliceshell):
3791##         wx.DropTarget.__init__(self)
3792##         self.sliceshell = sliceshell
3793##         self.compdo = wx.DataObjectComposite()
3794##         self.textdo = wx.TextDataObject()
3795##         self.filedo = wx.FileDataObject()
3796##         self.compdo.Add(self.textdo)
3797##         self.compdo.Add(self.filedo, True)
3798
3799##         self.SetDataObject(self.compdo)
3800
3801##     def OnDrop(self, x, y):
3802##         return True
3803
3804##     def OnData(self, x, y, result):
3805##         self.GetData()
3806##         if self.textdo.GetTextLength() > 1:
3807##             text = self.textdo.GetText()
3808##             # *** Do somethign with the dragged text here...
3809##             self.textdo.SetText('')
3810##         else:
3811##             filenames = str(self.filename.GetFilenames())
3812##             if len(filenames) == 1:
3813##                 txt = 'r\"%s\"' % filenames[0]
3814##             else:
3815##                 txt = '( '
3816##                 for f in filenames:
3817##                     txt += 'r\"%s\" , ' % f
3818##                 txt += ')'
3819##             self.sliceshell.AppendText(txt)
3820##             pos = self.sliceshell.GetCurrentPos()
3821##             self.sliceshell.SetCurrentPos( pos )
3822##             self.sliceshell.SetSelection( pos, pos )
3823
3824##         return result
3825