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