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