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