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