1"""Shell is an interactive text control in which a user types in 2commands to be sent to the interpreter. This particular shell is 3based on wxPython's wxStyledTextCtrl. 4 5Sponsored by Orbtech - Your source for Python programming expertise.""" 6 7__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" 8 9import wx 10from wx import stc 11from six import PY3 12 13import keyword 14import os 15import sys 16import time 17from functools import cmp_to_key 18 19from .buffer import Buffer 20from . import dispatcher 21from . import editwindow 22from . import frame 23from .pseudo import PseudoFileIn 24from .pseudo import PseudoFileOut 25from .pseudo import PseudoFileErr 26from .version import VERSION 27from .magic import magic 28from .path import ls,cd,pwd,sx 29 30sys.ps3 = '<-- ' # Input prompt. 31USE_MAGIC=True 32# Force updates from long-running commands after this many seconds 33PRINT_UPDATE_MAX_TIME=2 34 35NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT, 36 wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN) 37 38 39class ShellFrame(frame.Frame, frame.ShellFrameMixin): 40 """Frame containing the shell component.""" 41 42 name = 'Shell Frame' 43 44 def __init__(self, parent=None, id=-1, title='PyShell', 45 pos=wx.DefaultPosition, size=wx.DefaultSize, 46 style=wx.DEFAULT_FRAME_STYLE, locals=None, 47 InterpClass=None, 48 config=None, dataDir=None, 49 *args, **kwds): 50 """Create ShellFrame instance.""" 51 frame.Frame.__init__(self, parent, id, title, pos, size, style) 52 frame.ShellFrameMixin.__init__(self, config, dataDir) 53 54 if size == wx.DefaultSize: 55 self.SetSize((750, 525)) 56 57 intro = 'PyShell %s - The Flakiest Python Shell' % VERSION 58 self.SetStatusText(intro.replace('\n', ', ')) 59 self.shell = Shell(parent=self, id=-1, introText=intro, 60 locals=locals, InterpClass=InterpClass, 61 startupScript=self.startupScript, 62 execStartupScript=self.execStartupScript, 63 *args, **kwds) 64 65 # Override the shell so that status messages go to the status bar. 66 self.shell.setStatusText = self.SetStatusText 67 68 self.shell.SetFocus() 69 self.LoadSettings() 70 71 72 def OnClose(self, event): 73 """Event handler for closing.""" 74 # This isn't working the way I want, but I'll leave it for now. 75 if self.shell.waiting: 76 if event.CanVeto(): 77 event.Veto(True) 78 else: 79 self.SaveSettings() 80 self.shell.destroy() 81 self.Destroy() 82 83 def OnAbout(self, event): 84 """Display an About window.""" 85 title = 'About PyShell' 86 text = 'PyShell %s\n\n' % VERSION + \ 87 'Yet another Python shell, only flakier.\n\n' + \ 88 'Half-baked by Patrick K. O\'Brien,\n' + \ 89 'the other half is still in the oven.\n\n' + \ 90 'Platform: %s\n' % sys.platform + \ 91 'Python Version: %s\n' % sys.version.split()[0] + \ 92 'wxPython Version: %s\n' % wx.VERSION_STRING + \ 93 ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:])) 94 dialog = wx.MessageDialog(self, text, title, 95 wx.OK | wx.ICON_INFORMATION) 96 dialog.ShowModal() 97 dialog.Destroy() 98 99 100 def OnHelp(self, event): 101 """Show a help dialog.""" 102 frame.ShellFrameMixin.OnHelp(self, event) 103 104 105 def LoadSettings(self): 106 if self.config is not None: 107 frame.ShellFrameMixin.LoadSettings(self) 108 frame.Frame.LoadSettings(self, self.config) 109 self.shell.LoadSettings(self.config) 110 111 def SaveSettings(self, force=False): 112 if self.config is not None: 113 frame.ShellFrameMixin.SaveSettings(self) 114 if self.autoSaveSettings or force: 115 frame.Frame.SaveSettings(self, self.config) 116 self.shell.SaveSettings(self.config) 117 118 def DoSaveSettings(self): 119 if self.config is not None: 120 self.SaveSettings(force=True) 121 self.config.Flush() 122 123 124 125 126HELP_TEXT = """\ 127* Key bindings: 128Home Go to the beginning of the command or line. 129Shift+Home Select to the beginning of the command or line. 130Shift+End Select to the end of the line. 131End Go to the end of the line. 132Ctrl+C Copy selected text, removing prompts. 133Ctrl+Shift+C Copy selected text, retaining prompts. 134Alt+C Copy to the clipboard, including prefixed prompts. 135Ctrl+X Cut selected text. 136Ctrl+V Paste from clipboard. 137Ctrl+Shift+V Paste and run multiple commands from clipboard. 138Ctrl+Up Arrow Retrieve Previous History item. 139Alt+P Retrieve Previous History item. 140Ctrl+Down Arrow Retrieve Next History item. 141Alt+N Retrieve Next History item. 142Shift+Up Arrow Insert Previous History item. 143Shift+Down Arrow Insert Next History item. 144F8 Command-completion of History item. 145 (Type a few characters of a previous command and press F8.) 146Ctrl+Enter Insert new line into multiline command. 147Ctrl+] Increase font size. 148Ctrl+[ Decrease font size. 149Ctrl+= Default font size. 150Ctrl-Space Show Auto Completion. 151Ctrl-Alt-Space Show Call Tip. 152Shift+Enter Complete Text from History. 153Ctrl+F Search 154F3 Search next 155Ctrl+H "hide" lines containing selection / "unhide" 156F12 on/off "free-edit" mode 157""" 158 159class ShellFacade: 160 """Simplified interface to all shell-related functionality. 161 162 This is a semi-transparent facade, in that all attributes of other 163 are accessible, even though only some are visible to the user.""" 164 165 name = 'Shell Interface' 166 167 def __init__(self, other): 168 """Create a ShellFacade instance.""" 169 d = self.__dict__ 170 d['other'] = other 171 d['helpText'] = HELP_TEXT 172 173 def help(self): 174 """Display some useful information about how to use the shell.""" 175 self.write(self.helpText) 176 177 def __getattr__(self, name): 178 if hasattr(self.other, name): 179 return getattr(self.other, name) 180 else: 181 raise AttributeError(name) 182 183 def __setattr__(self, name, value): 184 if name in self.__dict__: 185 self.__dict__[name] = value 186 elif hasattr(self.other, name): 187 setattr(self.other, name, value) 188 else: 189 raise AttributeError(name) 190 191 def _getAttributeNames(self): 192 """Return list of magic attributes to extend introspection.""" 193 list = [ 194 'about', 195 'ask', 196 'autoCallTip', 197 'autoComplete', 198 'autoCompleteAutoHide', 199 'autoCompleteCaseInsensitive', 200 'autoCompleteIncludeDouble', 201 'autoCompleteIncludeMagic', 202 'autoCompleteIncludeSingle', 203 'callTipInsert', 204 'clear', 205 'pause', 206 'prompt', 207 'quit', 208 'redirectStderr', 209 'redirectStdin', 210 'redirectStdout', 211 'run', 212 'runfile', 213 'wrap', 214 'zoom', 215 ] 216 list.sort() 217 return list 218 219 220#DNM 221DISPLAY_TEXT=""" 222Author: %r 223Py Version: %s 224Python Version: %s 225wxPython Version: %s 226wxPython PlatformInfo: %s 227Platform: %s""" 228 229class Shell(editwindow.EditWindow): 230 """Shell based on StyledTextCtrl.""" 231 232 name = 'Shell' 233 234 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, 235 size=wx.DefaultSize, style=wx.CLIP_CHILDREN, 236 introText='', locals=None, InterpClass=None, 237 startupScript=None, execStartupScript=True, 238 useStockId=True, 239 *args, **kwds): 240 """Create Shell instance.""" 241 editwindow.EditWindow.__init__(self, parent, id, pos, size, style) 242 self.wrap() 243 if locals is None: 244 import __main__ 245 locals = __main__.__dict__ 246 247 # Grab these so they can be restored by self.redirect* methods. 248 self.stdin = sys.stdin 249 self.stdout = sys.stdout 250 self.stderr = sys.stderr 251 252 # Import a default interpreter class if one isn't provided. 253 if InterpClass == None: 254 from .interpreter import Interpreter 255 else: 256 Interpreter = InterpClass 257 258 # Create a replacement for stdin. 259 self.reader = PseudoFileIn(self.readline, self.readlines) 260 self.reader.input = '' 261 self.reader.isreading = False 262 263 # Set up the interpreter. 264 self.interp = Interpreter(locals=locals, 265 rawin=self.raw_input, 266 stdin=self.reader, 267 stdout=PseudoFileOut(self.writeOut), 268 stderr=PseudoFileErr(self.writeErr), 269 *args, **kwds) 270 271 # Set up the buffer. 272 self.buffer = Buffer() 273 274 # Find out for which keycodes the interpreter will autocomplete. 275 self.autoCompleteKeys = self.interp.getAutoCompleteKeys() 276 277 # Keep track of the last non-continuation prompt positions. 278 self.promptPosStart = 0 279 self.promptPosEnd = 0 280 281 # Keep track of multi-line commands. 282 self.more = False 283 284 # For use with forced updates during long-running scripts 285 self.lastUpdate=None 286 287 # Create the command history. Commands are added into the 288 # front of the list (ie. at index 0) as they are entered. 289 # self.historyIndex is the current position in the history; it 290 # gets incremented as you retrieve the previous command, 291 # decremented as you retrieve the next, and reset when you hit 292 # Enter. self.historyIndex == -1 means you're on the current 293 # command, not in the history. 294 self.history = [] 295 self.historyIndex = -1 296 297 #seb add mode for "free edit" 298 self.noteMode = 0 299 self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT) # marker for hidden 300 self.searchTxt = "" 301 302 # Assign handlers for keyboard events. 303 self.Bind(wx.EVT_CHAR, self.OnChar) 304 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 305 306 # Assign handler for the context menu 307 self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) 308 self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI) 309 310 # add the option to not use the stock IDs; otherwise the context menu 311 # may not work on Mac without adding the proper IDs to the menu bar 312 if useStockId: 313 self.ID_CUT = wx.ID_CUT 314 self.ID_COPY = wx.ID_COPY 315 self.ID_PASTE = wx.ID_PASTE 316 self.ID_SELECTALL = wx.ID_SELECTALL 317 self.ID_CLEAR = wx.ID_CLEAR 318 self.ID_UNDO = wx.ID_UNDO 319 self.ID_REDO = wx.ID_REDO 320 else: 321 self.ID_CUT = wx.NewIdRef() 322 self.ID_COPY = wx.NewIdRef() 323 self.ID_PASTE = wx.NewIdRef() 324 self.ID_SELECTALL = wx.NewIdRef() 325 self.ID_CLEAR = wx.NewIdRef() 326 self.ID_UNDO = wx.NewIdRef() 327 self.ID_REDO = wx.NewIdRef() 328 329 # Assign handlers for edit events 330 self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=self.ID_CUT) 331 self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=self.ID_COPY) 332 self.Bind(wx.EVT_MENU, lambda evt: self.CopyWithPrompts(), id=frame.ID_COPY_PLUS) 333 self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=self.ID_PASTE) 334 self.Bind(wx.EVT_MENU, lambda evt: self.PasteAndRun(), id=frame.ID_PASTE_PLUS) 335 self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=self.ID_SELECTALL) 336 self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=self.ID_CLEAR) 337 self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=self.ID_UNDO) 338 self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=self.ID_REDO) 339 340 341 # Assign handler for idle time. 342 self.waiting = False 343 self.Bind(wx.EVT_IDLE, self.OnIdle) 344 345 # Display the introductory banner information. 346 self.showIntro(introText) 347 348 # Assign some pseudo keywords to the interpreter's namespace. 349 self.setBuiltinKeywords() 350 351 # Add 'shell' to the interpreter's local namespace. 352 self.setLocalShell() 353 354 ## NOTE: See note at bottom of this file... 355 ## #seb: File drag and drop 356 ## self.SetDropTarget( FileDropTarget(self) ) 357 358 # Do this last so the user has complete control over their 359 # environment. They can override anything they want. 360 if execStartupScript: 361 if startupScript is None: 362 startupScript = os.environ.get('PYTHONSTARTUP') 363 self.execStartupScript(startupScript) 364 else: 365 self.prompt() 366 367 wx.CallAfter(self.ScrollToLine, 0) 368 369 370 def clearHistory(self): 371 self.history = [] 372 self.historyIndex = -1 373 dispatcher.send(signal="Shell.clearHistory") 374 375 376 def destroy(self): 377 del self.interp 378 379 def setFocus(self): 380 """Set focus to the shell.""" 381 self.SetFocus() 382 383 def OnIdle(self, event): 384 """Free the CPU to do other things.""" 385 if self.waiting: 386 time.sleep(0.05) 387 event.Skip() 388 389 def showIntro(self, text=''): 390 """Display introductory text in the shell.""" 391 if text: 392 self.write(text) 393 try: 394 if self.interp.introText: 395 if text and not text.endswith(os.linesep): 396 self.write(os.linesep) 397 self.write(self.interp.introText) 398 except AttributeError: 399 pass 400 401 def setBuiltinKeywords(self): 402 """Create pseudo keywords as part of builtins. 403 404 This sets "close", "exit" and "quit" to a helpful string. 405 """ 406 from six.moves import builtins 407 builtins.close = builtins.exit = builtins.quit = \ 408 'Click on the close button to leave the application.' 409 builtins.cd = cd 410 builtins.ls = ls 411 builtins.pwd = pwd 412 builtins.sx = sx 413 414 415 def quit(self): 416 """Quit the application.""" 417 # XXX Good enough for now but later we want to send a close event. 418 # In the close event handler we can make sure they want to 419 # quit. Other applications, like PythonCard, may choose to 420 # hide rather than quit so we should just post the event and 421 # let the surrounding app decide what it wants to do. 422 self.write('Click on the close button to leave the application.') 423 424 425 def setLocalShell(self): 426 """Add 'shell' to locals as reference to ShellFacade instance.""" 427 self.interp.locals['shell'] = ShellFacade(other=self) 428 429 430 def execStartupScript(self, startupScript): 431 """Execute the user's PYTHONSTARTUP script if they have one.""" 432 if startupScript and os.path.isfile(startupScript): 433 text = 'Startup script executed: ' + startupScript 434 if PY3: 435 self.push('print(%r)' % text) 436 self.push('with open(%r, "r") as f:\n' 437 ' exec(f.read())\n' % (startupScript)) 438 else: 439 self.push('print(%r); execfile(%r)' % (text, startupScript)) 440 self.interp.startupScript = startupScript 441 else: 442 self.push('') 443 444 445 def about(self): 446 """Display information about Py.""" 447 #DNM 448 text = DISPLAY_TEXT % \ 449 (__author__, VERSION, 450 sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo), 451 sys.platform) 452 self.write(text.strip()) 453 454 455 def OnChar(self, event): 456 """Keypress event handler. 457 458 Only receives an event if OnKeyDown calls event.Skip() for the 459 corresponding event.""" 460 461 if self.noteMode: 462 event.Skip() 463 return 464 465 # Prevent modification of previously submitted 466 # commands/responses. 467 if not self.CanEdit(): 468 return 469 key = event.GetKeyCode() 470 currpos = self.GetCurrentPos() 471 stoppos = self.promptPosEnd 472 # Return (Enter) needs to be ignored in this handler. 473 if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 474 pass 475 elif key in self.autoCompleteKeys: 476 # Usually the dot (period) key activates auto completion. 477 # Get the command between the prompt and the cursor. Add 478 # the autocomplete character to the end of the command. 479 if self.AutoCompActive(): 480 self.AutoCompCancel() 481 command = self.GetTextRange(stoppos, currpos) + chr(key) 482 self.write(chr(key)) 483 if self.autoComplete: 484 self.autoCompleteShow(command) 485 elif key == ord('('): 486 # The left paren activates a call tip and cancels an 487 # active auto completion. 488 if self.AutoCompActive(): 489 self.AutoCompCancel() 490 # Get the command between the prompt and the cursor. Add 491 # the '(' to the end of the command. 492 self.ReplaceSelection('') 493 command = self.GetTextRange(stoppos, currpos) + '(' 494 self.write('(') 495 self.autoCallTipShow(command, self.GetCurrentPos() == self.GetTextLength()) 496 else: 497 # Allow the normal event handling to take place. 498 event.Skip() 499 500 501 def OnKeyDown(self, event): 502 """Key down event handler.""" 503 504 key = event.GetKeyCode() 505 # If the auto-complete window is up let it do its thing. 506 if self.AutoCompActive(): 507 event.Skip() 508 return 509 510 # Prevent modification of previously submitted 511 # commands/responses. 512 controlDown = event.ControlDown() 513 rawControlDown = event.RawControlDown() 514 altDown = event.AltDown() 515 shiftDown = event.ShiftDown() 516 currpos = self.GetCurrentPos() 517 endpos = self.GetTextLength() 518 selecting = self.GetSelectionStart() != self.GetSelectionEnd() 519 520 if (rawControlDown or controlDown) and shiftDown and key in (ord('F'), ord('f')): 521 li = self.GetCurrentLine() 522 m = self.MarkerGet(li) 523 if m & 1<<0: 524 startP = self.PositionFromLine(li) 525 self.MarkerDelete(li, 0) 526 maxli = self.GetLineCount() 527 li += 1 # li stayed visible as header-line 528 li0 = li 529 while li<maxli and self.GetLineVisible(li) == 0: 530 li += 1 531 endP = self.GetLineEndPosition(li-1) 532 self.ShowLines(li0, li-1) 533 # select reappearing text to allow "hide again" 534 self.SetSelection( startP, endP ) 535 return 536 startP,endP = self.GetSelection() 537 endP-=1 538 startL = self.LineFromPosition(startP) 539 endL = self.LineFromPosition(endP) 540 541 # never hide last prompt 542 if endL == self.LineFromPosition(self.promptPosEnd): 543 endL -= 1 544 545 m = self.MarkerGet(startL) 546 self.MarkerAdd(startL, 0) 547 self.HideLines(startL+1,endL) 548 self.SetCurrentPos( startP ) # to ensure caret stays visible ! 549 550 if key == wx.WXK_F12: #seb 551 if self.noteMode: 552 # self.promptPosStart not used anyway - or ? 553 self.promptPosEnd = \ 554 self.PositionFromLine( self.GetLineCount()-1 ) + \ 555 len(str(sys.ps1)) 556 self.GotoLine(self.GetLineCount()) 557 self.GotoPos(self.promptPosEnd) 558 self.prompt() #make sure we have a prompt 559 self.SetCaretForeground("black") 560 self.SetCaretWidth(1) #default 561 self.SetCaretPeriod(500) #default 562 else: 563 self.SetCaretForeground("red") 564 self.SetCaretWidth(4) 565 self.SetCaretPeriod(0) #steady 566 567 self.noteMode = not self.noteMode 568 return 569 if self.noteMode: 570 event.Skip() 571 return 572 573 # Return (Enter) is used to submit a command to the 574 # interpreter. 575 if (not (rawControlDown or controlDown) and not shiftDown and not altDown) and \ 576 key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 577 if self.CallTipActive(): 578 self.CallTipCancel() 579 self.processLine() 580 581 # Complete Text (from already typed words) 582 elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 583 self.OnShowCompHistory() 584 585 # Ctrl+Return (Ctrl+Enter) is used to insert a line break. 586 elif (rawControlDown or controlDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 587 if self.CallTipActive(): 588 self.CallTipCancel() 589 if currpos == endpos: 590 self.processLine() 591 else: 592 self.insertLineBreak() 593 594 # Let Ctrl-Alt-* get handled normally. 595 elif (rawControlDown or controlDown) and altDown: 596 event.Skip() 597 598 # Clear the current, unexecuted command. 599 elif key == wx.WXK_ESCAPE: 600 if self.CallTipActive(): 601 event.Skip() 602 else: 603 self.clearCommand() 604 605 # Clear the current command 606 elif key == wx.WXK_BACK and (rawControlDown or controlDown) and shiftDown: 607 self.clearCommand() 608 609 # Increase font size. 610 elif (rawControlDown or controlDown) and key in (ord(']'), wx.WXK_NUMPAD_ADD): 611 dispatcher.send(signal='FontIncrease') 612 613 # Decrease font size. 614 elif (rawControlDown or controlDown) and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT): 615 dispatcher.send(signal='FontDecrease') 616 617 # Default font size. 618 elif (rawControlDown or controlDown) and key in (ord('='), wx.WXK_NUMPAD_DIVIDE): 619 dispatcher.send(signal='FontDefault') 620 621 # Cut to the clipboard. 622 elif ((rawControlDown or controlDown) and key in (ord('X'), ord('x'))) \ 623 or (shiftDown and key == wx.WXK_DELETE): 624 self.Cut() 625 626 # Copy to the clipboard. 627 elif (rawControlDown or controlDown) and not shiftDown \ 628 and key in (ord('C'), ord('c'), wx.WXK_INSERT): 629 self.Copy() 630 631 # Copy to the clipboard, including prompts. 632 elif (rawControlDown or controlDown) and shiftDown \ 633 and key in (ord('C'), ord('c'), wx.WXK_INSERT): 634 self.CopyWithPrompts() 635 636 # Copy to the clipboard, including prefixed prompts. 637 elif altDown and not controlDown \ 638 and key in (ord('C'), ord('c'), wx.WXK_INSERT): 639 self.CopyWithPromptsPrefixed() 640 641 # Home needs to be aware of the prompt. 642 elif (rawControlDown or controlDown) and key == wx.WXK_HOME: 643 home = self.promptPosEnd 644 if currpos > home: 645 self.SetCurrentPos(home) 646 if not selecting and not shiftDown: 647 self.SetAnchor(home) 648 self.EnsureCaretVisible() 649 else: 650 event.Skip() 651 652 # Home needs to be aware of the prompt. 653 elif key == wx.WXK_HOME: 654 home = self.promptPosEnd 655 if currpos > home: 656 [line_str,line_len] = self.GetCurLine() 657 pos=self.GetCurrentPos() 658 if line_str[:4] in [sys.ps1,sys.ps2,sys.ps3]: 659 self.SetCurrentPos(pos+4-line_len) 660 #self.SetCurrentPos(home) 661 if not selecting and not shiftDown: 662 self.SetAnchor(pos+4-line_len) 663 self.EnsureCaretVisible() 664 else: 665 event.Skip() 666 else: 667 event.Skip() 668 669 # 670 # The following handlers modify text, so we need to see if 671 # there is a selection that includes text prior to the prompt. 672 # 673 # Don't modify a selection with text prior to the prompt. 674 elif selecting and key not in NAVKEYS and not self.CanEdit(): 675 pass 676 677 # Paste from the clipboard. 678 elif ((rawControlDown or controlDown) and not shiftDown and key in (ord('V'), ord('v'))) \ 679 or (shiftDown and not controlDown and key == wx.WXK_INSERT): 680 self.Paste() 681 682 # manually invoke AutoComplete and Calltips 683 elif (rawControlDown or controlDown) and key == wx.WXK_SPACE: 684 self.OnCallTipAutoCompleteManually(shiftDown) 685 686 # Paste from the clipboard, run commands. 687 elif (rawControlDown or controlDown) and shiftDown and key in (ord('V'), ord('v')): 688 self.PasteAndRun() 689 690 # Replace with the previous command from the history buffer. 691 elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_UP) \ 692 or (altDown and key in (ord('P'), ord('p'))): 693 self.OnHistoryReplace(step=+1) 694 695 # Replace with the next command from the history buffer. 696 elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_DOWN) \ 697 or (altDown and key in (ord('N'), ord('n'))): 698 self.OnHistoryReplace(step=-1) 699 700 # Insert the previous command from the history buffer. 701 elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_UP) and self.CanEdit(): 702 self.OnHistoryInsert(step=+1) 703 704 # Insert the next command from the history buffer. 705 elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_DOWN) and self.CanEdit(): 706 self.OnHistoryInsert(step=-1) 707 708 # Search up the history for the text in front of the cursor. 709 elif key == wx.WXK_F8: 710 self.OnHistorySearch() 711 712 # Don't backspace over the latest non-continuation prompt. 713 elif key == wx.WXK_BACK: 714 if selecting and self.CanEdit(): 715 event.Skip() 716 elif currpos > self.promptPosEnd: 717 event.Skip() 718 719 # Only allow these keys after the latest prompt. 720 elif key in (wx.WXK_TAB, wx.WXK_DELETE): 721 if self.CanEdit(): 722 event.Skip() 723 724 # Don't toggle between insert mode and overwrite mode. 725 elif key == wx.WXK_INSERT: 726 pass 727 728 # Don't allow line deletion. 729 elif controlDown and key in (ord('L'), ord('l')): 730 # TODO : Allow line deletion eventually... 731 #event.Skip() 732 pass 733 734 # Don't allow line transposition. 735 elif controlDown and key in (ord('T'), ord('t')): 736 # TODO : Allow line transposition eventually... 737 # TODO : Will have to adjust markers accordingly and test if allowed... 738 #event.Skip() 739 pass 740 741 # Basic navigation keys should work anywhere. 742 elif key in NAVKEYS: 743 event.Skip() 744 745 # Protect the readonly portion of the shell. 746 elif not self.CanEdit(): 747 pass 748 749 else: 750 event.Skip() 751 752 753 def OnShowCompHistory(self): 754 """Show possible autocompletion Words from already typed words.""" 755 756 #copy from history 757 his = self.history[:] 758 759 #put together in one string 760 joined = " ".join (his) 761 import re 762 763 #sort out only "good" words 764 newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined) 765 766 #length > 1 (mix out "trash") 767 thlist = [] 768 for i in newlist: 769 if len (i) > 1: 770 thlist.append (i) 771 772 #unique (no duplicate words 773 #oneliner from german python forum => unique list 774 unlist = [thlist[i] for i in range(len(thlist)) if thlist[i] not in thlist[:i]] 775 776 #sort lowercase 777 def _cmp(a,b): 778 return ((a > b) - (a < b)) 779 unlist.sort(key=cmp_to_key(lambda a, b: _cmp(a.lower(), b.lower()))) 780 781 #this is more convenient, isn't it? 782 self.AutoCompSetIgnoreCase(True) 783 784 #join again together in a string 785 stringlist = " ".join(unlist) 786 787 #pos von 0 noch ausrechnen 788 789 #how big is the offset? 790 cpos = self.GetCurrentPos() - 1 791 while chr (self.GetCharAt (cpos)).isalnum(): 792 cpos -= 1 793 794 #the most important part 795 self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist) 796 797 798 def clearCommand(self): 799 """Delete the current, unexecuted command.""" 800 startpos = self.promptPosEnd 801 endpos = self.GetTextLength() 802 self.SetSelection(startpos, endpos) 803 self.ReplaceSelection('') 804 self.more = False 805 806 def OnHistoryReplace(self, step): 807 """Replace with the previous/next command from the history buffer.""" 808 self.clearCommand() 809 self.replaceFromHistory(step) 810 811 def replaceFromHistory(self, step): 812 """Replace selection with command from the history buffer.""" 813 ps2 = str(sys.ps2) 814 self.ReplaceSelection('') 815 newindex = self.historyIndex + step 816 if -1 <= newindex <= len(self.history): 817 self.historyIndex = newindex 818 if 0 <= newindex <= len(self.history)-1: 819 command = self.history[self.historyIndex] 820 command = command.replace('\n', os.linesep + ps2) 821 self.ReplaceSelection(command) 822 823 def OnHistoryInsert(self, step): 824 """Insert the previous/next command from the history buffer.""" 825 if not self.CanEdit(): 826 return 827 startpos = self.GetCurrentPos() 828 self.replaceFromHistory(step) 829 endpos = self.GetCurrentPos() 830 self.SetSelection(endpos, startpos) 831 832 def OnHistorySearch(self): 833 """Search up the history buffer for the text in front of the cursor.""" 834 if not self.CanEdit(): 835 return 836 startpos = self.GetCurrentPos() 837 # The text up to the cursor is what we search for. 838 numCharsAfterCursor = self.GetTextLength() - startpos 839 searchText = self.getCommand(rstrip=False) 840 if numCharsAfterCursor > 0: 841 searchText = searchText[:-numCharsAfterCursor] 842 if not searchText: 843 return 844 # Search upwards from the current history position and loop 845 # back to the beginning if we don't find anything. 846 if (self.historyIndex <= -1) \ 847 or (self.historyIndex >= len(self.history)-2): 848 searchOrder = range(len(self.history)) 849 else: 850 searchOrder = range(self.historyIndex+1, len(self.history)) + \ 851 range(self.historyIndex) 852 for i in searchOrder: 853 command = self.history[i] 854 if command[:len(searchText)] == searchText: 855 # Replace the current selection with the one we found. 856 self.ReplaceSelection(command[len(searchText):]) 857 endpos = self.GetCurrentPos() 858 self.SetSelection(endpos, startpos) 859 # We've now warped into middle of the history. 860 self.historyIndex = i 861 break 862 863 def setStatusText(self, text): 864 """Display status information.""" 865 866 # This method will likely be replaced by the enclosing app to 867 # do something more interesting, like write to a status bar. 868 print(text) 869 870 def insertLineBreak(self): 871 """Insert a new line break.""" 872 if self.CanEdit(): 873 self.write(os.linesep) 874 self.more = True 875 self.prompt() 876 877 def processLine(self): 878 """Process the line of text at which the user hit Enter.""" 879 880 # The user hit ENTER and we need to decide what to do. They 881 # could be sitting on any line in the shell. 882 883 thepos = self.GetCurrentPos() 884 startpos = self.promptPosEnd 885 endpos = self.GetTextLength() 886 ps2 = str(sys.ps2) 887 # If they hit RETURN inside the current command, execute the 888 # command. 889 if self.CanEdit(): 890 self.SetCurrentPos(endpos) 891 self.interp.more = False 892 command = self.GetTextRange(startpos, endpos) 893 lines = command.split(os.linesep + ps2) 894 lines = [line.rstrip() for line in lines] 895 command = '\n'.join(lines) 896 if self.reader.isreading: 897 if not command: 898 # Match the behavior of the standard Python shell 899 # when the user hits return without entering a 900 # value. 901 command = '\n' 902 self.reader.input = command 903 self.write(os.linesep) 904 else: 905 self.push(command) 906 wx.CallLater(1, self.EnsureCaretVisible) 907 # Or replace the current command with the other command. 908 else: 909 # If the line contains a command (even an invalid one). 910 if self.getCommand(rstrip=False): 911 command = self.getMultilineCommand() 912 self.clearCommand() 913 self.write(command) 914 # Otherwise, put the cursor back where we started. 915 else: 916 self.SetCurrentPos(thepos) 917 self.SetAnchor(thepos) 918 919 def getMultilineCommand(self, rstrip=True): 920 """Extract a multi-line command from the editor. 921 922 The command may not necessarily be valid Python syntax.""" 923 # XXX Need to extract real prompts here. Need to keep track of 924 # the prompt every time a command is issued. 925 ps1 = str(sys.ps1) 926 ps1size = len(ps1) 927 ps2 = str(sys.ps2) 928 ps2size = len(ps2) 929 # This is a total hack job, but it works. 930 text = self.GetCurLine()[0] 931 line = self.GetCurrentLine() 932 while text[:ps2size] == ps2 and line > 0: 933 line -= 1 934 self.GotoLine(line) 935 text = self.GetCurLine()[0] 936 if text[:ps1size] == ps1: 937 line = self.GetCurrentLine() 938 self.GotoLine(line) 939 startpos = self.GetCurrentPos() + ps1size 940 line += 1 941 self.GotoLine(line) 942 while self.GetCurLine()[0][:ps2size] == ps2: 943 line += 1 944 self.GotoLine(line) 945 stoppos = self.GetCurrentPos() 946 command = self.GetTextRange(startpos, stoppos) 947 command = command.replace(os.linesep + ps2, '\n') 948 command = command.rstrip() 949 command = command.replace('\n', os.linesep + ps2) 950 else: 951 command = '' 952 if rstrip: 953 command = command.rstrip() 954 return command 955 956 def getCommand(self, text=None, rstrip=True): 957 """Extract a command from text which may include a shell prompt. 958 959 The command may not necessarily be valid Python syntax.""" 960 if not text: 961 text = self.GetCurLine()[0] 962 # Strip the prompt off the front leaving just the command. 963 command = self.lstripPrompt(text) 964 if command == text: 965 command = '' # Real commands have prompts. 966 if rstrip: 967 command = command.rstrip() 968 return command 969 970 def lstripPrompt(self, text): 971 """Return text without a leading prompt.""" 972 ps1 = str(sys.ps1) 973 ps1size = len(ps1) 974 ps2 = str(sys.ps2) 975 ps2size = len(ps2) 976 # Strip the prompt off the front of text. 977 if text[:ps1size] == ps1: 978 text = text[ps1size:] 979 elif text[:ps2size] == ps2: 980 text = text[ps2size:] 981 return text 982 983 def push(self, command, silent = False): 984 """Send command to the interpreter for execution.""" 985 if not silent: 986 self.write(os.linesep) 987 988 #DNM 989 if USE_MAGIC: 990 command=magic(command) 991 992 busy = wx.BusyCursor() 993 self.waiting = True 994 self.lastUpdate=None 995 self.more = self.interp.push(command) 996 self.lastUpdate=None 997 self.waiting = False 998 del busy 999 if not self.more: 1000 self.addHistory(command.rstrip()) 1001 if not silent: 1002 self.prompt() 1003 1004 def addHistory(self, command): 1005 """Add command to the command history.""" 1006 # Reset the history position. 1007 self.historyIndex = -1 1008 # Insert this command into the history, unless it's a blank 1009 # line or the same as the last command. 1010 if command != '' \ 1011 and (len(self.history) == 0 or command != self.history[0]): 1012 self.history.insert(0, command) 1013 dispatcher.send(signal="Shell.addHistory", command=command) 1014 1015 def write(self, text): 1016 """Display text in the shell. 1017 1018 Replace line endings with OS-specific endings.""" 1019 text = self.fixLineEndings(text) 1020 self.AddText(text) 1021 self.EnsureCaretVisible() 1022 1023 if self.waiting: 1024 if self.lastUpdate==None: 1025 self.lastUpdate=time.time() 1026 if time.time()-self.lastUpdate > PRINT_UPDATE_MAX_TIME: 1027 self.Update() 1028 self.lastUpdate=time.time() 1029 1030 def fixLineEndings(self, text): 1031 """Return text with line endings replaced by OS-specific endings.""" 1032 lines = text.split('\r\n') 1033 for l in range(len(lines)): 1034 chunks = lines[l].split('\r') 1035 for c in range(len(chunks)): 1036 chunks[c] = os.linesep.join(chunks[c].split('\n')) 1037 lines[l] = os.linesep.join(chunks) 1038 text = os.linesep.join(lines) 1039 return text 1040 1041 def prompt(self): 1042 """Display proper prompt for the context: ps1, ps2 or ps3. 1043 1044 If this is a continuation line, autoindent as necessary.""" 1045 isreading = self.reader.isreading 1046 skip = False 1047 if isreading: 1048 prompt = str(sys.ps3) 1049 elif self.more: 1050 prompt = str(sys.ps2) 1051 else: 1052 prompt = str(sys.ps1) 1053 pos = self.GetCurLine()[1] 1054 if pos > 0: 1055 if isreading: 1056 skip = True 1057 else: 1058 self.write(os.linesep) 1059 if not self.more: 1060 self.promptPosStart = self.GetCurrentPos() 1061 if not skip: 1062 self.write(prompt) 1063 if not self.more: 1064 self.promptPosEnd = self.GetCurrentPos() 1065 # Keep the undo feature from undoing previous responses. 1066 self.EmptyUndoBuffer() 1067 1068 if self.more: 1069 line_num=self.GetCurrentLine() 1070 currentLine=self.GetLine(line_num) 1071 previousLine=self.GetLine(line_num-1)[len(prompt):] 1072 pstrip=previousLine.strip() 1073 lstrip=previousLine.lstrip() 1074 1075 # Get the first alnum word: 1076 first_word=[] 1077 for i in pstrip: 1078 if i.isalnum(): 1079 first_word.append(i) 1080 else: 1081 break 1082 first_word = ''.join(first_word) 1083 1084 if pstrip == '': 1085 # because it is all whitespace! 1086 indent=previousLine.strip('\n').strip('\r') 1087 else: 1088 indent=previousLine[:(len(previousLine)-len(lstrip))] 1089 if pstrip[-1]==':' and \ 1090 first_word in ['if','else','elif','for','while', 1091 'def','class','try','except','finally']: 1092 indent+=' '*4 1093 1094 self.write(indent) 1095 self.EnsureCaretVisible() 1096 self.ScrollToColumn(0) 1097 1098 def readline(self): 1099 """Replacement for stdin.readline().""" 1100 input = '' 1101 reader = self.reader 1102 reader.isreading = True 1103 self.prompt() 1104 try: 1105 while not reader.input: 1106 wx.GetApp().Yield(onlyIfNeeded=True) 1107 input = reader.input 1108 finally: 1109 reader.input = '' 1110 reader.isreading = False 1111 input = str(input) # In case of Unicode. 1112 return input 1113 1114 def readlines(self): 1115 """Replacement for stdin.readlines().""" 1116 lines = [] 1117 while lines[-1:] != ['\n']: 1118 lines.append(self.readline()) 1119 return lines 1120 1121 def raw_input(self, prompt=''): 1122 """Return string based on user input.""" 1123 if prompt: 1124 self.write(prompt) 1125 return self.readline() 1126 1127 def ask(self, prompt='Please enter your response:'): 1128 """Get response from the user using a dialog box.""" 1129 dialog = wx.TextEntryDialog(None, prompt, 1130 'Input Dialog (Raw)', '') 1131 try: 1132 if dialog.ShowModal() == wx.ID_OK: 1133 text = dialog.GetValue() 1134 return text 1135 finally: 1136 dialog.Destroy() 1137 return '' 1138 1139 def pause(self): 1140 """Halt execution pending a response from the user.""" 1141 self.ask('Press enter to continue:') 1142 1143 def clear(self): 1144 """Delete all text from the shell.""" 1145 self.ClearAll() 1146 1147 def run(self, command, prompt=True, verbose=True): 1148 """Execute command as if it was typed in directly. 1149 >>> shell.run('print("this")') 1150 >>> print("this") 1151 this 1152 >>> 1153 """ 1154 # Go to the very bottom of the text. 1155 endpos = self.GetTextLength() 1156 self.SetCurrentPos(endpos) 1157 command = command.rstrip() 1158 if prompt: self.prompt() 1159 if verbose: self.write(command) 1160 self.push(command) 1161 1162 def runfile(self, filename): 1163 """Execute all commands in file as if they were typed into the 1164 shell.""" 1165 file = open(filename) 1166 try: 1167 self.prompt() 1168 for command in file.readlines(): 1169 if command[:6] == 'shell.': 1170 # Run shell methods silently. 1171 self.run(command, prompt=False, verbose=False) 1172 else: 1173 self.run(command, prompt=False, verbose=True) 1174 finally: 1175 file.close() 1176 1177 def autoCompleteShow(self, command, offset = 0): 1178 """Display auto-completion popup list.""" 1179 self.AutoCompSetAutoHide(self.autoCompleteAutoHide) 1180 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive) 1181 list = self.interp.getAutoCompleteList(command, 1182 includeMagic=self.autoCompleteIncludeMagic, 1183 includeSingle=self.autoCompleteIncludeSingle, 1184 includeDouble=self.autoCompleteIncludeDouble) 1185 if list: 1186 options = ' '.join(list) 1187 #offset = 0 1188 self.AutoCompShow(offset, options) 1189 1190 def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False): 1191 """Display argument spec and docstring in a popup window.""" 1192 if self.CallTipActive(): 1193 self.CallTipCancel() 1194 (name, argspec, tip) = self.interp.getCallTip(command) 1195 if tip: 1196 dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip) 1197 if not self.autoCallTip and not forceCallTip: 1198 return 1199 startpos = self.GetCurrentPos() 1200 if argspec and insertcalltip and self.callTipInsert: 1201 self.write(argspec + ')') 1202 endpos = self.GetCurrentPos() 1203 self.SetSelection(startpos, endpos) 1204 if tip: 1205 tippos = startpos - (len(name) + 1) 1206 fallback = startpos - self.GetColumn(startpos) 1207 # In case there isn't enough room, only go back to the 1208 # fallback. 1209 tippos = max(tippos, fallback) 1210 self.CallTipShow(tippos, tip) 1211 1212 def OnCallTipAutoCompleteManually (self, shiftDown): 1213 """AutoComplete and Calltips manually.""" 1214 if self.AutoCompActive(): 1215 self.AutoCompCancel() 1216 currpos = self.GetCurrentPos() 1217 stoppos = self.promptPosEnd 1218 1219 cpos = currpos 1220 #go back until '.' is found 1221 pointavailpos = -1 1222 while cpos >= stoppos: 1223 if self.GetCharAt(cpos) == ord ('.'): 1224 pointavailpos = cpos 1225 break 1226 cpos -= 1 1227 1228 #word from non whitespace until '.' 1229 if pointavailpos != -1: 1230 #look backward for first whitespace char 1231 textbehind = self.GetTextRange (pointavailpos + 1, currpos) 1232 pointavailpos += 1 1233 1234 if not shiftDown: 1235 #call AutoComplete 1236 stoppos = self.promptPosEnd 1237 textbefore = self.GetTextRange(stoppos, pointavailpos) 1238 self.autoCompleteShow(textbefore, len (textbehind)) 1239 else: 1240 #call CallTips 1241 cpos = pointavailpos 1242 begpos = -1 1243 while cpos > stoppos: 1244 if chr(self.GetCharAt(cpos)).isspace(): 1245 begpos = cpos 1246 break 1247 cpos -= 1 1248 if begpos == -1: 1249 begpos = cpos 1250 ctips = self.GetTextRange (begpos, currpos) 1251 ctindex = ctips.find ('(') 1252 if ctindex != -1 and not self.CallTipActive(): 1253 #insert calltip, if current pos is '(', otherwise show it only 1254 self.autoCallTipShow(ctips[:ctindex + 1], 1255 self.GetCharAt(currpos - 1) == ord('(') and \ 1256 self.GetCurrentPos() == self.GetTextLength(), 1257 True) 1258 1259 1260 def writeOut(self, text): 1261 """Replacement for stdout.""" 1262 self.write(text) 1263 1264 def writeErr(self, text): 1265 """Replacement for stderr.""" 1266 self.write(text) 1267 1268 def redirectStdin(self, redirect=True): 1269 """If redirect is true then sys.stdin will come from the shell.""" 1270 if redirect: 1271 sys.stdin = self.reader 1272 else: 1273 sys.stdin = self.stdin 1274 1275 def redirectStdout(self, redirect=True): 1276 """If redirect is true then sys.stdout will go to the shell.""" 1277 if redirect: 1278 sys.stdout = PseudoFileOut(self.writeOut) 1279 else: 1280 sys.stdout = self.stdout 1281 1282 def redirectStderr(self, redirect=True): 1283 """If redirect is true then sys.stderr will go to the shell.""" 1284 if redirect: 1285 sys.stderr = PseudoFileErr(self.writeErr) 1286 else: 1287 sys.stderr = self.stderr 1288 1289 def CanCut(self): 1290 """Return true if text is selected and can be cut.""" 1291 if self.GetSelectionStart() != self.GetSelectionEnd() \ 1292 and self.GetSelectionStart() >= self.promptPosEnd \ 1293 and self.GetSelectionEnd() >= self.promptPosEnd: 1294 return True 1295 else: 1296 return False 1297 1298 def CanPaste(self): 1299 """Return true if a paste should succeed.""" 1300 if self.CanEdit() and editwindow.EditWindow.CanPaste(self): 1301 return True 1302 else: 1303 return False 1304 1305 def CanEdit(self): 1306 """Return true if editing should succeed.""" 1307 if self.GetSelectionStart() != self.GetSelectionEnd(): 1308 if self.GetSelectionStart() >= self.promptPosEnd \ 1309 and self.GetSelectionEnd() >= self.promptPosEnd: 1310 return True 1311 else: 1312 return False 1313 else: 1314 return self.GetCurrentPos() >= self.promptPosEnd 1315 1316 def Cut(self): 1317 """Remove selection and place it on the clipboard.""" 1318 if self.CanCut() and self.CanCopy(): 1319 if self.AutoCompActive(): 1320 self.AutoCompCancel() 1321 if self.CallTipActive(): 1322 self.CallTipCancel() 1323 self.Copy() 1324 self.ReplaceSelection('') 1325 1326 def Copy(self): 1327 """Copy selection and place it on the clipboard.""" 1328 if self.CanCopy(): 1329 ps1 = str(sys.ps1) 1330 ps2 = str(sys.ps2) 1331 command = self.GetSelectedText() 1332 command = command.replace(os.linesep + ps2, os.linesep) 1333 command = command.replace(os.linesep + ps1, os.linesep) 1334 command = self.lstripPrompt(text=command) 1335 data = wx.TextDataObject(command) 1336 self._clip(data) 1337 1338 def CopyWithPrompts(self): 1339 """Copy selection, including prompts, and place it on the clipboard.""" 1340 if self.CanCopy(): 1341 command = self.GetSelectedText() 1342 data = wx.TextDataObject(command) 1343 self._clip(data) 1344 1345 def CopyWithPromptsPrefixed(self): 1346 """Copy selection, including prompts prefixed with four 1347 spaces, and place it on the clipboard.""" 1348 if self.CanCopy(): 1349 command = self.GetSelectedText() 1350 spaces = ' ' * 4 1351 command = spaces + command.replace(os.linesep, 1352 os.linesep + spaces) 1353 data = wx.TextDataObject(command) 1354 self._clip(data) 1355 1356 def _clip(self, data): 1357 if wx.TheClipboard.Open(): 1358 wx.TheClipboard.UsePrimarySelection(False) 1359 wx.TheClipboard.SetData(data) 1360 wx.TheClipboard.Flush() 1361 wx.TheClipboard.Close() 1362 1363 def Paste(self): 1364 """Replace selection with clipboard contents.""" 1365 if self.CanPaste() and wx.TheClipboard.Open(): 1366 ps2 = str(sys.ps2) 1367 if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): 1368 data = wx.TextDataObject() 1369 if wx.TheClipboard.GetData(data): 1370 self.ReplaceSelection('') 1371 command = data.GetText() 1372 command = command.rstrip() 1373 command = self.fixLineEndings(command) 1374 command = self.lstripPrompt(text=command) 1375 command = command.replace(os.linesep + ps2, '\n') 1376 command = command.replace(os.linesep, '\n') 1377 command = command.replace('\n', os.linesep + ps2) 1378 self.write(command) 1379 wx.TheClipboard.Close() 1380 1381 1382 def PasteAndRun(self): 1383 """Replace selection with clipboard contents, run commands.""" 1384 text = '' 1385 if wx.TheClipboard.Open(): 1386 if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): 1387 data = wx.TextDataObject() 1388 if wx.TheClipboard.GetData(data): 1389 text = data.GetText() 1390 wx.TheClipboard.Close() 1391 if text: 1392 self.Execute(text) 1393 1394 1395 def Execute(self, text): 1396 """Replace selection with text and run commands.""" 1397 ps1 = str(sys.ps1) 1398 ps2 = str(sys.ps2) 1399 endpos = self.GetTextLength() 1400 self.SetCurrentPos(endpos) 1401 startpos = self.promptPosEnd 1402 self.SetSelection(startpos, endpos) 1403 self.ReplaceSelection('') 1404 text = text.lstrip() 1405 text = self.fixLineEndings(text) 1406 text = self.lstripPrompt(text) 1407 text = text.replace(os.linesep + ps1, '\n') 1408 text = text.replace(os.linesep + ps2, '\n') 1409 text = text.replace(os.linesep, '\n') 1410 lines = text.split('\n') 1411 commands = [] 1412 command = '' 1413 for line in lines: 1414 if line.strip() == ps2.strip(): 1415 # If we are pasting from something like a 1416 # web page that drops the trailing space 1417 # from the ps2 prompt of a blank line. 1418 line = '' 1419 lstrip = line.lstrip() 1420 if line.strip() != '' and lstrip == line and \ 1421 lstrip[:4] not in ['else','elif'] and \ 1422 lstrip[:6] != 'except': 1423 # New command. 1424 if command: 1425 # Add the previous command to the list. 1426 commands.append(command) 1427 # Start a new command, which may be multiline. 1428 command = line 1429 else: 1430 # Multiline command. Add to the command. 1431 command += '\n' 1432 command += line 1433 commands.append(command) 1434 for command in commands: 1435 command = command.replace('\n', os.linesep + ps2) 1436 self.write(command) 1437 self.processLine() 1438 1439 1440 def wrap(self, wrap=True): 1441 """Sets whether text is word wrapped.""" 1442 try: 1443 self.SetWrapMode(wrap) 1444 except AttributeError: 1445 return 'Wrapping is not available in this version.' 1446 1447 def zoom(self, points=0): 1448 """Set the zoom level. 1449 1450 This number of points is added to the size of all fonts. It 1451 may be positive to magnify or negative to reduce.""" 1452 self.SetZoom(points) 1453 1454 1455 1456 def LoadSettings(self, config): 1457 self.autoComplete = \ 1458 config.ReadBool('Options/AutoComplete', True) 1459 self.autoCompleteIncludeMagic = \ 1460 config.ReadBool('Options/AutoCompleteIncludeMagic', True) 1461 self.autoCompleteIncludeSingle = \ 1462 config.ReadBool('Options/AutoCompleteIncludeSingle', True) 1463 self.autoCompleteIncludeDouble = \ 1464 config.ReadBool('Options/AutoCompleteIncludeDouble', True) 1465 1466 self.autoCallTip = config.ReadBool('Options/AutoCallTip', True) 1467 self.callTipInsert = config.ReadBool('Options/CallTipInsert', True) 1468 self.SetWrapMode(config.ReadBool('View/WrapMode', True)) 1469 1470 self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True) 1471 self.setDisplayLineNumbers (self.lineNumbers) 1472 zoom = config.ReadInt('View/Zoom/Shell', -99) 1473 if zoom != -99: 1474 self.SetZoom(zoom) 1475 1476 1477 def SaveSettings(self, config): 1478 config.WriteBool('Options/AutoComplete', self.autoComplete) 1479 config.WriteBool('Options/AutoCompleteIncludeMagic', 1480 self.autoCompleteIncludeMagic) 1481 config.WriteBool('Options/AutoCompleteIncludeSingle', 1482 self.autoCompleteIncludeSingle) 1483 config.WriteBool('Options/AutoCompleteIncludeDouble', 1484 self.autoCompleteIncludeDouble) 1485 config.WriteBool('Options/AutoCallTip', self.autoCallTip) 1486 config.WriteBool('Options/CallTipInsert', self.callTipInsert) 1487 config.WriteBool('View/WrapMode', self.GetWrapMode()) 1488 config.WriteBool('View/ShowLineNumbers', self.lineNumbers) 1489 config.WriteInt('View/Zoom/Shell', self.GetZoom()) 1490 1491 def GetContextMenu(self): 1492 """ 1493 Create and return a context menu for the shell. 1494 This is used instead of the scintilla default menu 1495 in order to correctly respect our immutable buffer. 1496 """ 1497 menu = wx.Menu() 1498 menu.Append(self.ID_UNDO, "Undo") 1499 menu.Append(self.ID_REDO, "Redo") 1500 1501 menu.AppendSeparator() 1502 1503 menu.Append(self.ID_CUT, "Cut") 1504 menu.Append(self.ID_COPY, "Copy") 1505 menu.Append(frame.ID_COPY_PLUS, "Copy With Prompts") 1506 menu.Append(self.ID_PASTE, "Paste") 1507 menu.Append(frame.ID_PASTE_PLUS, "Paste And Run") 1508 menu.Append(self.ID_CLEAR, "Clear") 1509 1510 menu.AppendSeparator() 1511 1512 menu.Append(self.ID_SELECTALL, "Select All") 1513 return menu 1514 1515 def OnContextMenu(self, evt): 1516 menu = self.GetContextMenu() 1517 self.PopupMenu(menu) 1518 1519 def OnUpdateUI(self, evt): 1520 id = evt.Id 1521 if id in (self.ID_CUT, self.ID_CLEAR): 1522 evt.Enable(self.CanCut()) 1523 elif id in (self.ID_COPY, frame.ID_COPY_PLUS): 1524 evt.Enable(self.CanCopy()) 1525 elif id in (self.ID_PASTE, frame.ID_PASTE_PLUS): 1526 evt.Enable(self.CanPaste()) 1527 elif id == self.ID_UNDO: 1528 evt.Enable(self.CanUndo()) 1529 elif id == self.ID_REDO: 1530 evt.Enable(self.CanRedo()) 1531 1532 1533 1534 1535## NOTE: The DnD of file names is disabled until we can figure out how 1536## best to still allow DnD of text. 1537 1538 1539## #seb : File drag and drop 1540## class FileDropTarget(wx.FileDropTarget): 1541## def __init__(self, obj): 1542## wx.FileDropTarget.__init__(self) 1543## self.obj = obj 1544## def OnDropFiles(self, x, y, filenames): 1545## if len(filenames) == 1: 1546## txt = 'r\"%s\"' % filenames[0] 1547## else: 1548## txt = '( ' 1549## for f in filenames: 1550## txt += 'r\"%s\" , ' % f 1551## txt += ')' 1552## self.obj.AppendText(txt) 1553## pos = self.obj.GetCurrentPos() 1554## self.obj.SetCurrentPos( pos ) 1555## self.obj.SetSelection( pos, pos ) 1556 1557 1558 1559## class TextAndFileDropTarget(wx.DropTarget): 1560## def __init__(self, shell): 1561## wx.DropTarget.__init__(self) 1562## self.shell = shell 1563## self.compdo = wx.DataObjectComposite() 1564## self.textdo = wx.TextDataObject() 1565## self.filedo = wx.FileDataObject() 1566## self.compdo.Add(self.textdo) 1567## self.compdo.Add(self.filedo, True) 1568 1569## self.SetDataObject(self.compdo) 1570 1571## def OnDrop(self, x, y): 1572## return True 1573 1574## def OnData(self, x, y, result): 1575## self.GetData() 1576## if self.textdo.GetTextLength() > 1: 1577## text = self.textdo.GetText() 1578## # *** Do somethign with the dragged text here... 1579## self.textdo.SetText('') 1580## else: 1581## filenames = str(self.filename.GetFilenames()) 1582## if len(filenames) == 1: 1583## txt = 'r\"%s\"' % filenames[0] 1584## else: 1585## txt = '( ' 1586## for f in filenames: 1587## txt += 'r\"%s\" , ' % f 1588## txt += ')' 1589## self.shell.AppendText(txt) 1590## pos = self.shell.GetCurrentPos() 1591## self.shell.SetCurrentPos( pos ) 1592## self.shell.SetSelection( pos, pos ) 1593 1594## return result 1595