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