]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/py/shell.py
Oops
[wxWidgets.git] / wxPython / wx / py / shell.py
1 """Shell is an interactive text control in which a user types in
2 commands to be sent to the interpreter. This particular shell is
3 based on wxPython's wxStyledTextCtrl.
4
5 Sponsored by Orbtech - Your source for Python programming expertise."""
6
7 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
8 __cvsid__ = "$Id$"
9 __revision__ = "$Revision$"[11:-2]
10
11 import wx
12 from wx import stc
13
14 import keyword
15 import os
16 import sys
17 import time
18
19 from buffer import Buffer
20 import dispatcher
21 import editwindow
22 import frame
23 from pseudo import PseudoFileIn
24 from pseudo import PseudoFileOut
25 from pseudo import PseudoFileErr
26 from version import VERSION
27
28 sys.ps3 = '<-- ' # Input prompt.
29
30 NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
31 wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PRIOR, wx.WXK_NEXT)
32
33
34 class ShellFrame(frame.Frame):
35 """Frame containing the shell component."""
36
37 name = 'Shell Frame'
38 revision = __revision__
39
40 def __init__(self, parent=None, id=-1, title='PyShell',
41 pos=wx.DefaultPosition, size=wx.DefaultSize,
42 style=wx.DEFAULT_FRAME_STYLE, locals=None,
43 InterpClass=None, *args, **kwds):
44 """Create ShellFrame instance."""
45 frame.Frame.__init__(self, parent, id, title, pos, size, style)
46 intro = 'PyShell %s - The Flakiest Python Shell' % VERSION
47 intro += '\nSponsored by Orbtech - ' + \
48 'Your source for Python programming expertise.'
49 self.SetStatusText(intro.replace('\n', ', '))
50 self.shell = Shell(parent=self, id=-1, introText=intro,
51 locals=locals, InterpClass=InterpClass,
52 *args, **kwds)
53 # Override the shell so that status messages go to the status bar.
54 self.shell.setStatusText = self.SetStatusText
55
56 def OnClose(self, event):
57 """Event handler for closing."""
58 # This isn't working the way I want, but I'll leave it for now.
59 if self.shell.waiting:
60 if event.CanVeto():
61 event.Veto(True)
62 else:
63 self.shell.destroy()
64 self.Destroy()
65
66 def OnAbout(self, event):
67 """Display an About window."""
68 title = 'About PyShell'
69 text = 'PyShell %s\n\n' % VERSION + \
70 'Yet another Python shell, only flakier.\n\n' + \
71 'Half-baked by Patrick K. O\'Brien,\n' + \
72 'the other half is still in the oven.\n\n' + \
73 'Shell Revision: %s\n' % self.shell.revision + \
74 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
75 'Python Version: %s\n' % sys.version.split()[0] + \
76 'wxPython Version: %s\n' % wx.VERSION_STRING + \
77 'Platform: %s\n' % sys.platform
78 dialog = wx.MessageDialog(self, text, title,
79 wx.OK | wx.ICON_INFORMATION)
80 dialog.ShowModal()
81 dialog.Destroy()
82
83
84 class ShellFacade:
85 """Simplified interface to all shell-related functionality.
86
87 This is a semi-transparent facade, in that all attributes of other
88 are accessible, even though only some are visible to the user."""
89
90 name = 'Shell Interface'
91 revision = __revision__
92
93 def __init__(self, other):
94 """Create a ShellFacade instance."""
95 d = self.__dict__
96 d['other'] = other
97 d['helpText'] = \
98 """
99 * Key bindings:
100 Home Go to the beginning of the command or line.
101 Shift+Home Select to the beginning of the command or line.
102 Shift+End Select to the end of the line.
103 End Go to the end of the line.
104 Ctrl+C Copy selected text, removing prompts.
105 Ctrl+Shift+C Copy selected text, retaining prompts.
106 Ctrl+X Cut selected text.
107 Ctrl+V Paste from clipboard.
108 Ctrl+Shift+V Paste and run multiple commands from clipboard.
109 Ctrl+Up Arrow Retrieve Previous History item.
110 Alt+P Retrieve Previous History item.
111 Ctrl+Down Arrow Retrieve Next History item.
112 Alt+N Retrieve Next History item.
113 Shift+Up Arrow Insert Previous History item.
114 Shift+Down Arrow Insert Next History item.
115 F8 Command-completion of History item.
116 (Type a few characters of a previous command and press F8.)
117 Ctrl+Enter Insert new line into multiline command.
118 Ctrl+] Increase font size.
119 Ctrl+[ Decrease font size.
120 Ctrl+= Default font size.
121 """
122
123 def help(self):
124 """Display some useful information about how to use the shell."""
125 self.write(self.helpText)
126
127 def __getattr__(self, name):
128 if hasattr(self.other, name):
129 return getattr(self.other, name)
130 else:
131 raise AttributeError, name
132
133 def __setattr__(self, name, value):
134 if self.__dict__.has_key(name):
135 self.__dict__[name] = value
136 elif hasattr(self.other, name):
137 setattr(self.other, name, value)
138 else:
139 raise AttributeError, name
140
141 def _getAttributeNames(self):
142 """Return list of magic attributes to extend introspection."""
143 list = [
144 'about',
145 'ask',
146 'autoCallTip',
147 'autoComplete',
148 'autoCompleteAutoHide',
149 'autoCompleteCaseInsensitive',
150 'autoCompleteIncludeDouble',
151 'autoCompleteIncludeMagic',
152 'autoCompleteIncludeSingle',
153 'clear',
154 'pause',
155 'prompt',
156 'quit',
157 'redirectStderr',
158 'redirectStdin',
159 'redirectStdout',
160 'run',
161 'runfile',
162 'wrap',
163 'zoom',
164 ]
165 list.sort()
166 return list
167
168
169 class Shell(editwindow.EditWindow):
170 """Shell based on StyledTextCtrl."""
171
172 name = 'Shell'
173 revision = __revision__
174
175 def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
176 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
177 introText='', locals=None, InterpClass=None, *args, **kwds):
178 """Create Shell instance."""
179 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
180 self.wrap()
181 if locals is None:
182 import __main__
183 locals = __main__.__dict__
184 # Grab these so they can be restored by self.redirect* methods.
185 self.stdin = sys.stdin
186 self.stdout = sys.stdout
187 self.stderr = sys.stderr
188 # Import a default interpreter class if one isn't provided.
189 if InterpClass == None:
190 from interpreter import Interpreter
191 else:
192 Interpreter = InterpClass
193 # Create a replacement for stdin.
194 self.reader = PseudoFileIn(self.readline, self.readlines)
195 self.reader.input = ''
196 self.reader.isreading = False
197 # Set up the interpreter.
198 self.interp = Interpreter(locals=locals,
199 rawin=self.raw_input,
200 stdin=self.reader,
201 stdout=PseudoFileOut(self.writeOut),
202 stderr=PseudoFileErr(self.writeErr),
203 *args, **kwds)
204 # Set up the buffer.
205 self.buffer = Buffer()
206 # Find out for which keycodes the interpreter will autocomplete.
207 self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
208 # Keep track of the last non-continuation prompt positions.
209 self.promptPosStart = 0
210 self.promptPosEnd = 0
211 # Keep track of multi-line commands.
212 self.more = False
213 # Create the command history. Commands are added into the
214 # front of the list (ie. at index 0) as they are entered.
215 # self.historyIndex is the current position in the history; it
216 # gets incremented as you retrieve the previous command,
217 # decremented as you retrieve the next, and reset when you hit
218 # Enter. self.historyIndex == -1 means you're on the current
219 # command, not in the history.
220 self.history = []
221 self.historyIndex = -1
222 # Assign handlers for keyboard events.
223 wx.EVT_CHAR(self, self.OnChar)
224 wx.EVT_KEY_DOWN(self, self.OnKeyDown)
225 # Assign handler for idle time.
226 self.waiting = False
227 wx.EVT_IDLE(self, self.OnIdle)
228 # Display the introductory banner information.
229 self.showIntro(introText)
230 # Assign some pseudo keywords to the interpreter's namespace.
231 self.setBuiltinKeywords()
232 # Add 'shell' to the interpreter's local namespace.
233 self.setLocalShell()
234 # Do this last so the user has complete control over their
235 # environment. They can override anything they want.
236 self.execStartupScript(self.interp.startupScript)
237 wx.CallAfter(self.ScrollToLine, 0)
238
239 def destroy(self):
240 del self.interp
241
242 def setFocus(self):
243 """Set focus to the shell."""
244 self.SetFocus()
245
246 def OnIdle(self, event):
247 """Free the CPU to do other things."""
248 if self.waiting:
249 time.sleep(0.05)
250 event.Skip()
251
252 def showIntro(self, text=''):
253 """Display introductory text in the shell."""
254 if text:
255 if not text.endswith(os.linesep):
256 text += os.linesep
257 self.write(text)
258 try:
259 self.write(self.interp.introText)
260 except AttributeError:
261 pass
262
263 def setBuiltinKeywords(self):
264 """Create pseudo keywords as part of builtins.
265
266 This sets `close`, `exit` and `quit` to a helpful string.
267 """
268 import __builtin__
269 __builtin__.close = __builtin__.exit = __builtin__.quit = \
270 'Click on the close button to leave the application.'
271
272 def quit(self):
273 """Quit the application."""
274
275 # XXX Good enough for now but later we want to send a close event.
276
277 # In the close event handler we can make sure they want to
278 # quit. Other applications, like PythonCard, may choose to
279 # hide rather than quit so we should just post the event and
280 # let the surrounding app decide what it wants to do.
281 self.write('Click on the close button to leave the application.')
282
283 def setLocalShell(self):
284 """Add 'shell' to locals as reference to ShellFacade instance."""
285 self.interp.locals['shell'] = ShellFacade(other=self)
286
287 def execStartupScript(self, startupScript):
288 """Execute the user's PYTHONSTARTUP script if they have one."""
289 if startupScript and os.path.isfile(startupScript):
290 text = 'Startup script executed: ' + startupScript
291 self.push('print %r; execfile(%r)' % (text, startupScript))
292 else:
293 self.push('')
294
295 def about(self):
296 """Display information about Py."""
297 text = """
298 Author: %r
299 Py Version: %s
300 Py Shell Revision: %s
301 Py Interpreter Revision: %s
302 Python Version: %s
303 wxPython Version: %s
304 Platform: %s""" % \
305 (__author__, VERSION, self.revision, self.interp.revision,
306 sys.version.split()[0], wx.VERSION_STRING, sys.platform)
307 self.write(text.strip())
308
309 def OnChar(self, event):
310 """Keypress event handler.
311
312 Only receives an event if OnKeyDown calls event.Skip() for the
313 corresponding event."""
314
315 # Prevent modification of previously submitted
316 # commands/responses.
317 if not self.CanEdit():
318 return
319 key = event.KeyCode()
320 currpos = self.GetCurrentPos()
321 stoppos = self.promptPosEnd
322 # Return (Enter) needs to be ignored in this handler.
323 if key == wx.WXK_RETURN:
324 pass
325 elif key in self.autoCompleteKeys:
326 # Usually the dot (period) key activates auto completion.
327 # Get the command between the prompt and the cursor. Add
328 # the autocomplete character to the end of the command.
329 if self.AutoCompActive():
330 self.AutoCompCancel()
331 command = self.GetTextRange(stoppos, currpos) + chr(key)
332 self.write(chr(key))
333 if self.autoComplete:
334 self.autoCompleteShow(command)
335 elif key == ord('('):
336 # The left paren activates a call tip and cancels an
337 # active auto completion.
338 if self.AutoCompActive():
339 self.AutoCompCancel()
340 # Get the command between the prompt and the cursor. Add
341 # the '(' to the end of the command.
342 self.ReplaceSelection('')
343 command = self.GetTextRange(stoppos, currpos) + '('
344 self.write('(')
345 self.autoCallTipShow(command)
346 else:
347 # Allow the normal event handling to take place.
348 event.Skip()
349
350 def OnKeyDown(self, event):
351 """Key down event handler."""
352
353 key = event.KeyCode()
354 # If the auto-complete window is up let it do its thing.
355 if self.AutoCompActive():
356 event.Skip()
357 return
358 # Prevent modification of previously submitted
359 # commands/responses.
360 controlDown = event.ControlDown()
361 altDown = event.AltDown()
362 shiftDown = event.ShiftDown()
363 currpos = self.GetCurrentPos()
364 endpos = self.GetTextLength()
365 selecting = self.GetSelectionStart() != self.GetSelectionEnd()
366 # Return (Enter) is used to submit a command to the
367 # interpreter.
368 if not controlDown and key == wx.WXK_RETURN:
369 if self.CallTipActive():
370 self.CallTipCancel()
371 self.processLine()
372 # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
373 elif controlDown and key == wx.WXK_RETURN:
374 if self.CallTipActive():
375 self.CallTipCancel()
376 if currpos == endpos:
377 self.processLine()
378 else:
379 self.insertLineBreak()
380 # Let Ctrl-Alt-* get handled normally.
381 elif controlDown and altDown:
382 event.Skip()
383 # Clear the current, unexecuted command.
384 elif key == wx.WXK_ESCAPE:
385 if self.CallTipActive():
386 event.Skip()
387 else:
388 self.clearCommand()
389 # Increase font size.
390 elif controlDown and key in (ord(']'),):
391 dispatcher.send(signal='FontIncrease')
392 # Decrease font size.
393 elif controlDown and key in (ord('['),):
394 dispatcher.send(signal='FontDecrease')
395 # Default font size.
396 elif controlDown and key in (ord('='),):
397 dispatcher.send(signal='FontDefault')
398 # Cut to the clipboard.
399 elif (controlDown and key in (ord('X'), ord('x'))) \
400 or (shiftDown and key == wx.WXK_DELETE):
401 self.Cut()
402 # Copy to the clipboard.
403 elif controlDown and not shiftDown \
404 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
405 self.Copy()
406 # Copy to the clipboard, including prompts.
407 elif controlDown and shiftDown \
408 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
409 self.CopyWithPrompts()
410 # Copy to the clipboard, including prefixed prompts.
411 elif altDown and not controlDown \
412 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
413 self.CopyWithPromptsPrefixed()
414 # Home needs to be aware of the prompt.
415 elif key == wx.WXK_HOME:
416 home = self.promptPosEnd
417 if currpos > home:
418 self.SetCurrentPos(home)
419 if not selecting and not shiftDown:
420 self.SetAnchor(home)
421 self.EnsureCaretVisible()
422 else:
423 event.Skip()
424 #
425 # The following handlers modify text, so we need to see if
426 # there is a selection that includes text prior to the prompt.
427 #
428 # Don't modify a selection with text prior to the prompt.
429 elif selecting and key not in NAVKEYS and not self.CanEdit():
430 pass
431 # Paste from the clipboard.
432 elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \
433 or (shiftDown and not controlDown and key == wx.WXK_INSERT):
434 self.Paste()
435 # Paste from the clipboard, run commands.
436 elif controlDown and shiftDown and key in (ord('V'), ord('v')):
437 self.PasteAndRun()
438 # Replace with the previous command from the history buffer.
439 elif (controlDown and key == wx.WXK_UP) \
440 or (altDown and key in (ord('P'), ord('p'))):
441 self.OnHistoryReplace(step=+1)
442 # Replace with the next command from the history buffer.
443 elif (controlDown and key == wx.WXK_DOWN) \
444 or (altDown and key in (ord('N'), ord('n'))):
445 self.OnHistoryReplace(step=-1)
446 # Insert the previous command from the history buffer.
447 elif (shiftDown and key == wx.WXK_UP) and self.CanEdit():
448 self.OnHistoryInsert(step=+1)
449 # Insert the next command from the history buffer.
450 elif (shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
451 self.OnHistoryInsert(step=-1)
452 # Search up the history for the text in front of the cursor.
453 elif key == wx.WXK_F8:
454 self.OnHistorySearch()
455 # Don't backspace over the latest non-continuation prompt.
456 elif key == wx.WXK_BACK:
457 if selecting and self.CanEdit():
458 event.Skip()
459 elif currpos > self.promptPosEnd:
460 event.Skip()
461 # Only allow these keys after the latest prompt.
462 elif key in (wx.WXK_TAB, wx.WXK_DELETE):
463 if self.CanEdit():
464 event.Skip()
465 # Don't toggle between insert mode and overwrite mode.
466 elif key == wx.WXK_INSERT:
467 pass
468 # Don't allow line deletion.
469 elif controlDown and key in (ord('L'), ord('l')):
470 pass
471 # Don't allow line transposition.
472 elif controlDown and key in (ord('T'), ord('t')):
473 pass
474 # Basic navigation keys should work anywhere.
475 elif key in NAVKEYS:
476 event.Skip()
477 # Protect the readonly portion of the shell.
478 elif not self.CanEdit():
479 pass
480 else:
481 event.Skip()
482
483 def clearCommand(self):
484 """Delete the current, unexecuted command."""
485 startpos = self.promptPosEnd
486 endpos = self.GetTextLength()
487 self.SetSelection(startpos, endpos)
488 self.ReplaceSelection('')
489 self.more = False
490
491 def OnHistoryReplace(self, step):
492 """Replace with the previous/next command from the history buffer."""
493 self.clearCommand()
494 self.replaceFromHistory(step)
495
496 def replaceFromHistory(self, step):
497 """Replace selection with command from the history buffer."""
498 ps2 = str(sys.ps2)
499 self.ReplaceSelection('')
500 newindex = self.historyIndex + step
501 if -1 <= newindex <= len(self.history):
502 self.historyIndex = newindex
503 if 0 <= newindex <= len(self.history)-1:
504 command = self.history[self.historyIndex]
505 command = command.replace('\n', os.linesep + ps2)
506 self.ReplaceSelection(command)
507
508 def OnHistoryInsert(self, step):
509 """Insert the previous/next command from the history buffer."""
510 if not self.CanEdit():
511 return
512 startpos = self.GetCurrentPos()
513 self.replaceFromHistory(step)
514 endpos = self.GetCurrentPos()
515 self.SetSelection(endpos, startpos)
516
517 def OnHistorySearch(self):
518 """Search up the history buffer for the text in front of the cursor."""
519 if not self.CanEdit():
520 return
521 startpos = self.GetCurrentPos()
522 # The text up to the cursor is what we search for.
523 numCharsAfterCursor = self.GetTextLength() - startpos
524 searchText = self.getCommand(rstrip=False)
525 if numCharsAfterCursor > 0:
526 searchText = searchText[:-numCharsAfterCursor]
527 if not searchText:
528 return
529 # Search upwards from the current history position and loop
530 # back to the beginning if we don't find anything.
531 if (self.historyIndex <= -1) \
532 or (self.historyIndex >= len(self.history)-2):
533 searchOrder = range(len(self.history))
534 else:
535 searchOrder = range(self.historyIndex+1, len(self.history)) + \
536 range(self.historyIndex)
537 for i in searchOrder:
538 command = self.history[i]
539 if command[:len(searchText)] == searchText:
540 # Replace the current selection with the one we found.
541 self.ReplaceSelection(command[len(searchText):])
542 endpos = self.GetCurrentPos()
543 self.SetSelection(endpos, startpos)
544 # We've now warped into middle of the history.
545 self.historyIndex = i
546 break
547
548 def setStatusText(self, text):
549 """Display status information."""
550
551 # This method will likely be replaced by the enclosing app to
552 # do something more interesting, like write to a status bar.
553 print text
554
555 def insertLineBreak(self):
556 """Insert a new line break."""
557 if self.CanEdit():
558 self.write(os.linesep)
559 self.more = True
560 self.prompt()
561
562 def processLine(self):
563 """Process the line of text at which the user hit Enter."""
564
565 # The user hit ENTER and we need to decide what to do. They
566 # could be sitting on any line in the shell.
567
568 thepos = self.GetCurrentPos()
569 startpos = self.promptPosEnd
570 endpos = self.GetTextLength()
571 ps2 = str(sys.ps2)
572 # If they hit RETURN inside the current command, execute the
573 # command.
574 if self.CanEdit():
575 self.SetCurrentPos(endpos)
576 self.interp.more = False
577 command = self.GetTextRange(startpos, endpos)
578 lines = command.split(os.linesep + ps2)
579 lines = [line.rstrip() for line in lines]
580 command = '\n'.join(lines)
581 if self.reader.isreading:
582 if not command:
583 # Match the behavior of the standard Python shell
584 # when the user hits return without entering a
585 # value.
586 command = '\n'
587 self.reader.input = command
588 self.write(os.linesep)
589 else:
590 self.push(command)
591 # Or replace the current command with the other command.
592 else:
593 # If the line contains a command (even an invalid one).
594 if self.getCommand(rstrip=False):
595 command = self.getMultilineCommand()
596 self.clearCommand()
597 self.write(command)
598 # Otherwise, put the cursor back where we started.
599 else:
600 self.SetCurrentPos(thepos)
601 self.SetAnchor(thepos)
602
603 def getMultilineCommand(self, rstrip=True):
604 """Extract a multi-line command from the editor.
605
606 The command may not necessarily be valid Python syntax."""
607 # XXX Need to extract real prompts here. Need to keep track of
608 # the prompt every time a command is issued.
609 ps1 = str(sys.ps1)
610 ps1size = len(ps1)
611 ps2 = str(sys.ps2)
612 ps2size = len(ps2)
613 # This is a total hack job, but it works.
614 text = self.GetCurLine()[0]
615 line = self.GetCurrentLine()
616 while text[:ps2size] == ps2 and line > 0:
617 line -= 1
618 self.GotoLine(line)
619 text = self.GetCurLine()[0]
620 if text[:ps1size] == ps1:
621 line = self.GetCurrentLine()
622 self.GotoLine(line)
623 startpos = self.GetCurrentPos() + ps1size
624 line += 1
625 self.GotoLine(line)
626 while self.GetCurLine()[0][:ps2size] == ps2:
627 line += 1
628 self.GotoLine(line)
629 stoppos = self.GetCurrentPos()
630 command = self.GetTextRange(startpos, stoppos)
631 command = command.replace(os.linesep + ps2, '\n')
632 command = command.rstrip()
633 command = command.replace('\n', os.linesep + ps2)
634 else:
635 command = ''
636 if rstrip:
637 command = command.rstrip()
638 return command
639
640 def getCommand(self, text=None, rstrip=True):
641 """Extract a command from text which may include a shell prompt.
642
643 The command may not necessarily be valid Python syntax."""
644 if not text:
645 text = self.GetCurLine()[0]
646 # Strip the prompt off the front leaving just the command.
647 command = self.lstripPrompt(text)
648 if command == text:
649 command = '' # Real commands have prompts.
650 if rstrip:
651 command = command.rstrip()
652 return command
653
654 def lstripPrompt(self, text):
655 """Return text without a leading prompt."""
656 ps1 = str(sys.ps1)
657 ps1size = len(ps1)
658 ps2 = str(sys.ps2)
659 ps2size = len(ps2)
660 # Strip the prompt off the front of text.
661 if text[:ps1size] == ps1:
662 text = text[ps1size:]
663 elif text[:ps2size] == ps2:
664 text = text[ps2size:]
665 return text
666
667 def push(self, command):
668 """Send command to the interpreter for execution."""
669 self.write(os.linesep)
670 busy = wx.BusyCursor()
671 self.waiting = True
672 self.more = self.interp.push(command)
673 self.waiting = False
674 del busy
675 if not self.more:
676 self.addHistory(command.rstrip())
677 self.prompt()
678
679 def addHistory(self, command):
680 """Add command to the command history."""
681 # Reset the history position.
682 self.historyIndex = -1
683 # Insert this command into the history, unless it's a blank
684 # line or the same as the last command.
685 if command != '' \
686 and (len(self.history) == 0 or command != self.history[0]):
687 self.history.insert(0, command)
688
689 def write(self, text):
690 """Display text in the shell.
691
692 Replace line endings with OS-specific endings."""
693 text = self.fixLineEndings(text)
694 self.AddText(text)
695 self.EnsureCaretVisible()
696
697 def fixLineEndings(self, text):
698 """Return text with line endings replaced by OS-specific endings."""
699 lines = text.split('\r\n')
700 for l in range(len(lines)):
701 chunks = lines[l].split('\r')
702 for c in range(len(chunks)):
703 chunks[c] = os.linesep.join(chunks[c].split('\n'))
704 lines[l] = os.linesep.join(chunks)
705 text = os.linesep.join(lines)
706 return text
707
708 def prompt(self):
709 """Display proper prompt for the context: ps1, ps2 or ps3.
710
711 If this is a continuation line, autoindent as necessary."""
712 isreading = self.reader.isreading
713 skip = False
714 if isreading:
715 prompt = str(sys.ps3)
716 elif self.more:
717 prompt = str(sys.ps2)
718 else:
719 prompt = str(sys.ps1)
720 pos = self.GetCurLine()[1]
721 if pos > 0:
722 if isreading:
723 skip = True
724 else:
725 self.write(os.linesep)
726 if not self.more:
727 self.promptPosStart = self.GetCurrentPos()
728 if not skip:
729 self.write(prompt)
730 if not self.more:
731 self.promptPosEnd = self.GetCurrentPos()
732 # Keep the undo feature from undoing previous responses.
733 self.EmptyUndoBuffer()
734 # XXX Add some autoindent magic here if more.
735 if self.more:
736 self.write(' '*4) # Temporary hack indentation.
737 self.EnsureCaretVisible()
738 self.ScrollToColumn(0)
739
740 def readline(self):
741 """Replacement for stdin.readline()."""
742 input = ''
743 reader = self.reader
744 reader.isreading = True
745 self.prompt()
746 try:
747 while not reader.input:
748 wx.YieldIfNeeded()
749 input = reader.input
750 finally:
751 reader.input = ''
752 reader.isreading = False
753 input = str(input) # In case of Unicode.
754 return input
755
756 def readlines(self):
757 """Replacement for stdin.readlines()."""
758 lines = []
759 while lines[-1:] != ['\n']:
760 lines.append(self.readline())
761 return lines
762
763 def raw_input(self, prompt=''):
764 """Return string based on user input."""
765 if prompt:
766 self.write(prompt)
767 return self.readline()
768
769 def ask(self, prompt='Please enter your response:'):
770 """Get response from the user using a dialog box."""
771 dialog = wx.TextEntryDialog(None, prompt,
772 'Input Dialog (Raw)', '')
773 try:
774 if dialog.ShowModal() == wx.ID_OK:
775 text = dialog.GetValue()
776 return text
777 finally:
778 dialog.Destroy()
779 return ''
780
781 def pause(self):
782 """Halt execution pending a response from the user."""
783 self.ask('Press enter to continue:')
784
785 def clear(self):
786 """Delete all text from the shell."""
787 self.ClearAll()
788
789 def run(self, command, prompt=True, verbose=True):
790 """Execute command as if it was typed in directly.
791 >>> shell.run('print "this"')
792 >>> print "this"
793 this
794 >>>
795 """
796 # Go to the very bottom of the text.
797 endpos = self.GetTextLength()
798 self.SetCurrentPos(endpos)
799 command = command.rstrip()
800 if prompt: self.prompt()
801 if verbose: self.write(command)
802 self.push(command)
803
804 def runfile(self, filename):
805 """Execute all commands in file as if they were typed into the
806 shell."""
807 file = open(filename)
808 try:
809 self.prompt()
810 for command in file.readlines():
811 if command[:6] == 'shell.':
812 # Run shell methods silently.
813 self.run(command, prompt=False, verbose=False)
814 else:
815 self.run(command, prompt=False, verbose=True)
816 finally:
817 file.close()
818
819 def autoCompleteShow(self, command):
820 """Display auto-completion popup list."""
821 self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
822 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
823 list = self.interp.getAutoCompleteList(command,
824 includeMagic=self.autoCompleteIncludeMagic,
825 includeSingle=self.autoCompleteIncludeSingle,
826 includeDouble=self.autoCompleteIncludeDouble)
827 if list:
828 options = ' '.join(list)
829 offset = 0
830 self.AutoCompShow(offset, options)
831
832 def autoCallTipShow(self, command):
833 """Display argument spec and docstring in a popup window."""
834 if self.CallTipActive():
835 self.CallTipCancel()
836 (name, argspec, tip) = self.interp.getCallTip(command)
837 if tip:
838 dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
839 if not self.autoCallTip:
840 return
841 if argspec:
842 startpos = self.GetCurrentPos()
843 self.write(argspec + ')')
844 endpos = self.GetCurrentPos()
845 self.SetSelection(endpos, startpos)
846 if tip:
847 curpos = self.GetCurrentPos()
848 tippos = curpos - (len(name) + 1)
849 fallback = curpos - self.GetColumn(curpos)
850 # In case there isn't enough room, only go back to the
851 # fallback.
852 tippos = max(tippos, fallback)
853 self.CallTipShow(tippos, tip)
854
855 def writeOut(self, text):
856 """Replacement for stdout."""
857 self.write(text)
858
859 def writeErr(self, text):
860 """Replacement for stderr."""
861 self.write(text)
862
863 def redirectStdin(self, redirect=True):
864 """If redirect is true then sys.stdin will come from the shell."""
865 if redirect:
866 sys.stdin = self.reader
867 else:
868 sys.stdin = self.stdin
869
870 def redirectStdout(self, redirect=True):
871 """If redirect is true then sys.stdout will go to the shell."""
872 if redirect:
873 sys.stdout = PseudoFileOut(self.writeOut)
874 else:
875 sys.stdout = self.stdout
876
877 def redirectStderr(self, redirect=True):
878 """If redirect is true then sys.stderr will go to the shell."""
879 if redirect:
880 sys.stderr = PseudoFileErr(self.writeErr)
881 else:
882 sys.stderr = self.stderr
883
884 def CanCut(self):
885 """Return true if text is selected and can be cut."""
886 if self.GetSelectionStart() != self.GetSelectionEnd() \
887 and self.GetSelectionStart() >= self.promptPosEnd \
888 and self.GetSelectionEnd() >= self.promptPosEnd:
889 return True
890 else:
891 return False
892
893 def CanPaste(self):
894 """Return true if a paste should succeed."""
895 if self.CanEdit() and editwindow.EditWindow.CanPaste(self):
896 return True
897 else:
898 return False
899
900 def CanEdit(self):
901 """Return true if editing should succeed."""
902 if self.GetSelectionStart() != self.GetSelectionEnd():
903 if self.GetSelectionStart() >= self.promptPosEnd \
904 and self.GetSelectionEnd() >= self.promptPosEnd:
905 return True
906 else:
907 return False
908 else:
909 return self.GetCurrentPos() >= self.promptPosEnd
910
911 def Cut(self):
912 """Remove selection and place it on the clipboard."""
913 if self.CanCut() and self.CanCopy():
914 if self.AutoCompActive():
915 self.AutoCompCancel()
916 if self.CallTipActive():
917 self.CallTipCancel()
918 self.Copy()
919 self.ReplaceSelection('')
920
921 def Copy(self):
922 """Copy selection and place it on the clipboard."""
923 if self.CanCopy():
924 ps1 = str(sys.ps1)
925 ps2 = str(sys.ps2)
926 command = self.GetSelectedText()
927 command = command.replace(os.linesep + ps2, os.linesep)
928 command = command.replace(os.linesep + ps1, os.linesep)
929 command = self.lstripPrompt(text=command)
930 data = wx.TextDataObject(command)
931 self._clip(data)
932
933 def CopyWithPrompts(self):
934 """Copy selection, including prompts, and place it on the clipboard."""
935 if self.CanCopy():
936 command = self.GetSelectedText()
937 data = wx.TextDataObject(command)
938 self._clip(data)
939
940 def CopyWithPromptsPrefixed(self):
941 """Copy selection, including prompts prefixed with four
942 spaces, and place it on the clipboard."""
943 if self.CanCopy():
944 command = self.GetSelectedText()
945 spaces = ' ' * 4
946 command = spaces + command.replace(os.linesep,
947 os.linesep + spaces)
948 data = wx.TextDataObject(command)
949 self._clip(data)
950
951 def _clip(self, data):
952 if wx.TheClipboard.Open():
953 wx.TheClipboard.UsePrimarySelection(False)
954 wx.TheClipboard.SetData(data)
955 wx.TheClipboard.Flush()
956 wx.TheClipboard.Close()
957
958 def Paste(self):
959 """Replace selection with clipboard contents."""
960 if self.CanPaste() and wx.TheClipboard.Open():
961 ps2 = str(sys.ps2)
962 if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
963 data = wx.TextDataObject()
964 if wx.TheClipboard.GetData(data):
965 self.ReplaceSelection('')
966 command = data.GetText()
967 command = command.rstrip()
968 command = self.fixLineEndings(command)
969 command = self.lstripPrompt(text=command)
970 command = command.replace(os.linesep + ps2, '\n')
971 command = command.replace(os.linesep, '\n')
972 command = command.replace('\n', os.linesep + ps2)
973 self.write(command)
974 wx.TheClipboard.Close()
975
976 def PasteAndRun(self):
977 """Replace selection with clipboard contents, run commands."""
978 if wx.TheClipboard.Open():
979 ps1 = str(sys.ps1)
980 ps2 = str(sys.ps2)
981 if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
982 data = wx.TextDataObject()
983 if wx.TheClipboard.GetData(data):
984 endpos = self.GetTextLength()
985 self.SetCurrentPos(endpos)
986 startpos = self.promptPosEnd
987 self.SetSelection(startpos, endpos)
988 self.ReplaceSelection('')
989 text = data.GetText()
990 text = text.lstrip()
991 text = self.fixLineEndings(text)
992 text = self.lstripPrompt(text)
993 text = text.replace(os.linesep + ps1, '\n')
994 text = text.replace(os.linesep + ps2, '\n')
995 text = text.replace(os.linesep, '\n')
996 lines = text.split('\n')
997 commands = []
998 command = ''
999 for line in lines:
1000 if line.strip() == ps2.strip():
1001 # If we are pasting from something like a
1002 # web page that drops the trailing space
1003 # from the ps2 prompt of a blank line.
1004 line = ''
1005 if line.strip() != '' and line.lstrip() == line:
1006 # New command.
1007 if command:
1008 # Add the previous command to the list.
1009 commands.append(command)
1010 # Start a new command, which may be multiline.
1011 command = line
1012 else:
1013 # Multiline command. Add to the command.
1014 command += '\n'
1015 command += line
1016 commands.append(command)
1017 for command in commands:
1018 command = command.replace('\n', os.linesep + ps2)
1019 self.write(command)
1020 self.processLine()
1021 wx.TheClipboard.Close()
1022
1023 def wrap(self, wrap=True):
1024 """Sets whether text is word wrapped."""
1025 try:
1026 self.SetWrapMode(wrap)
1027 except AttributeError:
1028 return 'Wrapping is not available in this version.'
1029
1030 def zoom(self, points=0):
1031 """Set the zoom level.
1032
1033 This number of points is added to the size of all fonts. It
1034 may be positive to magnify or negative to reduce."""
1035 self.SetZoom(points)