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