]>
Commit | Line | Data |
---|---|---|
d14a1e28 RD |
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. | |
1fded56b | 4 | |
d14a1e28 | 5 | Sponsored by Orbtech - Your source for Python programming expertise.""" |
1fded56b | 6 | |
d14a1e28 RD |
7 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" |
8 | __cvsid__ = "$Id$" | |
9 | __revision__ = "$Revision$"[11:-2] | |
10 | ||
d14a1e28 RD |
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 | ||
d14a1e28 RD |
28 | sys.ps3 = '<-- ' # Input prompt. |
29 | ||
30 | NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT, | |
31 | wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PRIOR, wx.WXK_NEXT) | |
32 | ||
33 | ||
02b800ce | 34 | class ShellFrame(frame.Frame, frame.ShellFrameMixin): |
d14a1e28 RD |
35 | """Frame containing the shell component.""" |
36 | ||
37 | name = 'Shell Frame' | |
38 | revision = __revision__ | |
39 | ||
40 | def __init__(self, parent=None, id=-1, title='PyShell', | |
41 | pos=wx.DefaultPosition, size=wx.DefaultSize, | |
42 | style=wx.DEFAULT_FRAME_STYLE, locals=None, | |
02b800ce RD |
43 | InterpClass=None, |
44 | config=None, dataDir=None, | |
45 | *args, **kwds): | |
d14a1e28 RD |
46 | """Create ShellFrame instance.""" |
47 | frame.Frame.__init__(self, parent, id, title, pos, size, style) | |
02b800ce RD |
48 | frame.ShellFrameMixin.__init__(self, config, dataDir) |
49 | ||
50 | if size == wx.DefaultSize: | |
51 | self.SetSize((750, 525)) | |
52 | ||
d14a1e28 | 53 | intro = 'PyShell %s - The Flakiest Python Shell' % VERSION |
d14a1e28 RD |
54 | self.SetStatusText(intro.replace('\n', ', ')) |
55 | self.shell = Shell(parent=self, id=-1, introText=intro, | |
56 | locals=locals, InterpClass=InterpClass, | |
02b800ce RD |
57 | startupScript=self.startupScript, |
58 | execStartupScript=self.execStartupScript, | |
d14a1e28 | 59 | *args, **kwds) |
02b800ce | 60 | |
d14a1e28 RD |
61 | # Override the shell so that status messages go to the status bar. |
62 | self.shell.setStatusText = self.SetStatusText | |
63 | ||
02b800ce RD |
64 | self.shell.SetFocus() |
65 | self.LoadSettings() | |
66 | ||
67 | ||
d14a1e28 RD |
68 | def OnClose(self, event): |
69 | """Event handler for closing.""" | |
70 | # This isn't working the way I want, but I'll leave it for now. | |
71 | if self.shell.waiting: | |
72 | if event.CanVeto(): | |
73 | event.Veto(True) | |
74 | else: | |
02b800ce | 75 | self.SaveSettings() |
d14a1e28 RD |
76 | self.shell.destroy() |
77 | self.Destroy() | |
78 | ||
79 | def OnAbout(self, event): | |
80 | """Display an About window.""" | |
81 | title = 'About PyShell' | |
82 | text = 'PyShell %s\n\n' % VERSION + \ | |
83 | 'Yet another Python shell, only flakier.\n\n' + \ | |
84 | 'Half-baked by Patrick K. O\'Brien,\n' + \ | |
85 | 'the other half is still in the oven.\n\n' + \ | |
86 | 'Shell Revision: %s\n' % self.shell.revision + \ | |
87 | 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \ | |
f2f8a5fc | 88 | 'Platform: %s\n' % sys.platform + \ |
d14a1e28 RD |
89 | 'Python Version: %s\n' % sys.version.split()[0] + \ |
90 | 'wxPython Version: %s\n' % wx.VERSION_STRING + \ | |
f2f8a5fc | 91 | ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:])) |
d14a1e28 RD |
92 | dialog = wx.MessageDialog(self, text, title, |
93 | wx.OK | wx.ICON_INFORMATION) | |
94 | dialog.ShowModal() | |
95 | dialog.Destroy() | |
96 | ||
97 | ||
4617be08 RD |
98 | def OnHelp(self, event): |
99 | """Show a help dialog.""" | |
100 | frame.ShellFrameMixin.OnHelp(self, event) | |
101 | ||
102 | ||
02b800ce RD |
103 | def LoadSettings(self): |
104 | if self.config is not None: | |
105 | frame.ShellFrameMixin.LoadSettings(self) | |
106 | frame.Frame.LoadSettings(self, self.config) | |
107 | self.shell.LoadSettings(self.config) | |
d14a1e28 | 108 | |
c0ab3f7f | 109 | def SaveSettings(self, force=False): |
02b800ce RD |
110 | if self.config is not None: |
111 | frame.ShellFrameMixin.SaveSettings(self) | |
c0ab3f7f | 112 | if self.autoSaveSettings or force: |
02b800ce RD |
113 | frame.Frame.SaveSettings(self, self.config) |
114 | self.shell.SaveSettings(self.config) | |
d14a1e28 | 115 | |
02b800ce RD |
116 | def DoSaveSettings(self): |
117 | if self.config is not None: | |
c0ab3f7f | 118 | self.SaveSettings(force=True) |
02b800ce RD |
119 | self.config.Flush() |
120 | ||
d14a1e28 | 121 | |
02b800ce RD |
122 | |
123 | ||
124 | HELP_TEXT = """\ | |
d14a1e28 RD |
125 | * Key bindings: |
126 | Home Go to the beginning of the command or line. | |
127 | Shift+Home Select to the beginning of the command or line. | |
128 | Shift+End Select to the end of the line. | |
129 | End Go to the end of the line. | |
130 | Ctrl+C Copy selected text, removing prompts. | |
131 | Ctrl+Shift+C Copy selected text, retaining prompts. | |
02b800ce | 132 | Alt+C Copy to the clipboard, including prefixed prompts. |
d14a1e28 RD |
133 | Ctrl+X Cut selected text. |
134 | Ctrl+V Paste from clipboard. | |
135 | Ctrl+Shift+V Paste and run multiple commands from clipboard. | |
136 | Ctrl+Up Arrow Retrieve Previous History item. | |
137 | Alt+P Retrieve Previous History item. | |
138 | Ctrl+Down Arrow Retrieve Next History item. | |
139 | Alt+N Retrieve Next History item. | |
140 | Shift+Up Arrow Insert Previous History item. | |
141 | Shift+Down Arrow Insert Next History item. | |
142 | F8 Command-completion of History item. | |
143 | (Type a few characters of a previous command and press F8.) | |
144 | Ctrl+Enter Insert new line into multiline command. | |
145 | Ctrl+] Increase font size. | |
146 | Ctrl+[ Decrease font size. | |
147 | Ctrl+= Default font size. | |
02b800ce RD |
148 | Ctrl-Space Show Auto Completion. |
149 | Ctrl-Alt-Space Show Call Tip. | |
02b800ce | 150 | Shift+Enter Complete Text from History. |
e773f79b RD |
151 | Ctrl+F Search |
152 | F3 Search next | |
02b800ce RD |
153 | Ctrl+H "hide" lines containing selection / "unhide" |
154 | F12 on/off "free-edit" mode | |
d14a1e28 RD |
155 | """ |
156 | ||
02b800ce RD |
157 | class ShellFacade: |
158 | """Simplified interface to all shell-related functionality. | |
159 | ||
160 | This is a semi-transparent facade, in that all attributes of other | |
161 | are accessible, even though only some are visible to the user.""" | |
162 | ||
163 | name = 'Shell Interface' | |
164 | revision = __revision__ | |
165 | ||
166 | def __init__(self, other): | |
167 | """Create a ShellFacade instance.""" | |
168 | d = self.__dict__ | |
169 | d['other'] = other | |
170 | d['helpText'] = HELP_TEXT | |
171 | ||
d14a1e28 RD |
172 | def help(self): |
173 | """Display some useful information about how to use the shell.""" | |
174 | self.write(self.helpText) | |
175 | ||
176 | def __getattr__(self, name): | |
177 | if hasattr(self.other, name): | |
178 | return getattr(self.other, name) | |
179 | else: | |
180 | raise AttributeError, name | |
181 | ||
182 | def __setattr__(self, name, value): | |
183 | if self.__dict__.has_key(name): | |
184 | self.__dict__[name] = value | |
185 | elif hasattr(self.other, name): | |
186 | setattr(self.other, name, value) | |
187 | else: | |
188 | raise AttributeError, name | |
189 | ||
190 | def _getAttributeNames(self): | |
191 | """Return list of magic attributes to extend introspection.""" | |
192 | list = [ | |
193 | 'about', | |
194 | 'ask', | |
195 | 'autoCallTip', | |
196 | 'autoComplete', | |
d351525a | 197 | 'autoCompleteAutoHide', |
d14a1e28 RD |
198 | 'autoCompleteCaseInsensitive', |
199 | 'autoCompleteIncludeDouble', | |
200 | 'autoCompleteIncludeMagic', | |
201 | 'autoCompleteIncludeSingle', | |
02b800ce | 202 | 'callTipInsert', |
d14a1e28 RD |
203 | 'clear', |
204 | 'pause', | |
205 | 'prompt', | |
206 | 'quit', | |
207 | 'redirectStderr', | |
208 | 'redirectStdin', | |
209 | 'redirectStdout', | |
210 | 'run', | |
211 | 'runfile', | |
212 | 'wrap', | |
213 | 'zoom', | |
214 | ] | |
215 | list.sort() | |
216 | return list | |
217 | ||
218 | ||
02b800ce | 219 | |
d14a1e28 RD |
220 | class Shell(editwindow.EditWindow): |
221 | """Shell based on StyledTextCtrl.""" | |
222 | ||
223 | name = 'Shell' | |
224 | revision = __revision__ | |
225 | ||
226 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, | |
227 | size=wx.DefaultSize, style=wx.CLIP_CHILDREN, | |
02b800ce RD |
228 | introText='', locals=None, InterpClass=None, |
229 | startupScript=None, execStartupScript=True, | |
230 | *args, **kwds): | |
d14a1e28 RD |
231 | """Create Shell instance.""" |
232 | editwindow.EditWindow.__init__(self, parent, id, pos, size, style) | |
233 | self.wrap() | |
234 | if locals is None: | |
a47c63ba PB |
235 | import __main__ |
236 | locals = __main__.__dict__ | |
02b800ce | 237 | |
d14a1e28 RD |
238 | # Grab these so they can be restored by self.redirect* methods. |
239 | self.stdin = sys.stdin | |
240 | self.stdout = sys.stdout | |
241 | self.stderr = sys.stderr | |
02b800ce | 242 | |
d14a1e28 RD |
243 | # Import a default interpreter class if one isn't provided. |
244 | if InterpClass == None: | |
245 | from interpreter import Interpreter | |
246 | else: | |
247 | Interpreter = InterpClass | |
02b800ce | 248 | |
d14a1e28 RD |
249 | # Create a replacement for stdin. |
250 | self.reader = PseudoFileIn(self.readline, self.readlines) | |
251 | self.reader.input = '' | |
252 | self.reader.isreading = False | |
02b800ce | 253 | |
d14a1e28 RD |
254 | # Set up the interpreter. |
255 | self.interp = Interpreter(locals=locals, | |
256 | rawin=self.raw_input, | |
257 | stdin=self.reader, | |
258 | stdout=PseudoFileOut(self.writeOut), | |
259 | stderr=PseudoFileErr(self.writeErr), | |
260 | *args, **kwds) | |
02b800ce | 261 | |
d14a1e28 RD |
262 | # Set up the buffer. |
263 | self.buffer = Buffer() | |
02b800ce | 264 | |
d14a1e28 RD |
265 | # Find out for which keycodes the interpreter will autocomplete. |
266 | self.autoCompleteKeys = self.interp.getAutoCompleteKeys() | |
02b800ce | 267 | |
d14a1e28 RD |
268 | # Keep track of the last non-continuation prompt positions. |
269 | self.promptPosStart = 0 | |
270 | self.promptPosEnd = 0 | |
02b800ce | 271 | |
d14a1e28 RD |
272 | # Keep track of multi-line commands. |
273 | self.more = False | |
02b800ce | 274 | |
d14a1e28 RD |
275 | # Create the command history. Commands are added into the |
276 | # front of the list (ie. at index 0) as they are entered. | |
277 | # self.historyIndex is the current position in the history; it | |
278 | # gets incremented as you retrieve the previous command, | |
279 | # decremented as you retrieve the next, and reset when you hit | |
280 | # Enter. self.historyIndex == -1 means you're on the current | |
281 | # command, not in the history. | |
282 | self.history = [] | |
283 | self.historyIndex = -1 | |
02b800ce RD |
284 | |
285 | #seb add mode for "free edit" | |
286 | self.noteMode = 0 | |
287 | self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT) # marker for hidden | |
288 | self.searchTxt = "" | |
289 | ||
d14a1e28 | 290 | # Assign handlers for keyboard events. |
02b800ce RD |
291 | self.Bind(wx.EVT_CHAR, self.OnChar) |
292 | self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) | |
293 | ||
d14a1e28 RD |
294 | # Assign handler for idle time. |
295 | self.waiting = False | |
02b800ce RD |
296 | self.Bind(wx.EVT_IDLE, self.OnIdle) |
297 | ||
d14a1e28 RD |
298 | # Display the introductory banner information. |
299 | self.showIntro(introText) | |
02b800ce | 300 | |
d14a1e28 RD |
301 | # Assign some pseudo keywords to the interpreter's namespace. |
302 | self.setBuiltinKeywords() | |
02b800ce | 303 | |
d14a1e28 RD |
304 | # Add 'shell' to the interpreter's local namespace. |
305 | self.setLocalShell() | |
02b800ce RD |
306 | |
307 | ## NOTE: See note at bottom of this file... | |
308 | ## #seb: File drag and drop | |
309 | ## self.SetDropTarget( FileDropTarget(self) ) | |
310 | ||
d14a1e28 RD |
311 | # Do this last so the user has complete control over their |
312 | # environment. They can override anything they want. | |
02b800ce RD |
313 | if execStartupScript: |
314 | if startupScript is None: | |
315 | startupScript = os.environ.get('PYTHONSTARTUP') | |
316 | self.execStartupScript(startupScript) | |
317 | else: | |
318 | self.prompt() | |
319 | ||
d14a1e28 RD |
320 | wx.CallAfter(self.ScrollToLine, 0) |
321 | ||
02b800ce | 322 | |
e773f79b RD |
323 | def clearHistory(self): |
324 | self.history = [] | |
325 | self.historyIndex = -1 | |
326 | dispatcher.send(signal="Shell.clearHistory") | |
327 | ||
02b800ce | 328 | |
d14a1e28 RD |
329 | def destroy(self): |
330 | del self.interp | |
331 | ||
332 | def setFocus(self): | |
333 | """Set focus to the shell.""" | |
334 | self.SetFocus() | |
335 | ||
336 | def OnIdle(self, event): | |
337 | """Free the CPU to do other things.""" | |
338 | if self.waiting: | |
339 | time.sleep(0.05) | |
340 | event.Skip() | |
341 | ||
342 | def showIntro(self, text=''): | |
343 | """Display introductory text in the shell.""" | |
344 | if text: | |
345 | if not text.endswith(os.linesep): | |
346 | text += os.linesep | |
347 | self.write(text) | |
348 | try: | |
349 | self.write(self.interp.introText) | |
350 | except AttributeError: | |
351 | pass | |
352 | ||
353 | def setBuiltinKeywords(self): | |
354 | """Create pseudo keywords as part of builtins. | |
355 | ||
356 | This sets `close`, `exit` and `quit` to a helpful string. | |
357 | """ | |
358 | import __builtin__ | |
359 | __builtin__.close = __builtin__.exit = __builtin__.quit = \ | |
360 | 'Click on the close button to leave the application.' | |
361 | ||
02b800ce | 362 | |
d14a1e28 RD |
363 | def quit(self): |
364 | """Quit the application.""" | |
d14a1e28 | 365 | # XXX Good enough for now but later we want to send a close event. |
d14a1e28 RD |
366 | # In the close event handler we can make sure they want to |
367 | # quit. Other applications, like PythonCard, may choose to | |
368 | # hide rather than quit so we should just post the event and | |
369 | # let the surrounding app decide what it wants to do. | |
370 | self.write('Click on the close button to leave the application.') | |
371 | ||
02b800ce | 372 | |
d14a1e28 RD |
373 | def setLocalShell(self): |
374 | """Add 'shell' to locals as reference to ShellFacade instance.""" | |
375 | self.interp.locals['shell'] = ShellFacade(other=self) | |
376 | ||
02b800ce | 377 | |
d14a1e28 RD |
378 | def execStartupScript(self, startupScript): |
379 | """Execute the user's PYTHONSTARTUP script if they have one.""" | |
380 | if startupScript and os.path.isfile(startupScript): | |
381 | text = 'Startup script executed: ' + startupScript | |
382 | self.push('print %r; execfile(%r)' % (text, startupScript)) | |
02b800ce | 383 | self.interp.startupScript = startupScript |
d14a1e28 RD |
384 | else: |
385 | self.push('') | |
386 | ||
02b800ce | 387 | |
d14a1e28 RD |
388 | def about(self): |
389 | """Display information about Py.""" | |
390 | text = """ | |
391 | Author: %r | |
392 | Py Version: %s | |
393 | Py Shell Revision: %s | |
394 | Py Interpreter Revision: %s | |
395 | Python Version: %s | |
396 | wxPython Version: %s | |
02b800ce | 397 | wxPython PlatformInfo: %s |
d14a1e28 RD |
398 | Platform: %s""" % \ |
399 | (__author__, VERSION, self.revision, self.interp.revision, | |
02b800ce RD |
400 | sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo), |
401 | sys.platform) | |
d14a1e28 RD |
402 | self.write(text.strip()) |
403 | ||
02b800ce | 404 | |
d14a1e28 RD |
405 | def OnChar(self, event): |
406 | """Keypress event handler. | |
407 | ||
408 | Only receives an event if OnKeyDown calls event.Skip() for the | |
409 | corresponding event.""" | |
410 | ||
02b800ce RD |
411 | if self.noteMode: |
412 | event.Skip() | |
413 | return | |
414 | ||
d14a1e28 RD |
415 | # Prevent modification of previously submitted |
416 | # commands/responses. | |
417 | if not self.CanEdit(): | |
418 | return | |
419 | key = event.KeyCode() | |
420 | currpos = self.GetCurrentPos() | |
421 | stoppos = self.promptPosEnd | |
422 | # Return (Enter) needs to be ignored in this handler. | |
e773f79b | 423 | if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: |
d14a1e28 RD |
424 | pass |
425 | elif key in self.autoCompleteKeys: | |
426 | # Usually the dot (period) key activates auto completion. | |
427 | # Get the command between the prompt and the cursor. Add | |
428 | # the autocomplete character to the end of the command. | |
429 | if self.AutoCompActive(): | |
430 | self.AutoCompCancel() | |
431 | command = self.GetTextRange(stoppos, currpos) + chr(key) | |
432 | self.write(chr(key)) | |
433 | if self.autoComplete: | |
434 | self.autoCompleteShow(command) | |
435 | elif key == ord('('): | |
436 | # The left paren activates a call tip and cancels an | |
437 | # active auto completion. | |
438 | if self.AutoCompActive(): | |
439 | self.AutoCompCancel() | |
440 | # Get the command between the prompt and the cursor. Add | |
441 | # the '(' to the end of the command. | |
442 | self.ReplaceSelection('') | |
443 | command = self.GetTextRange(stoppos, currpos) + '(' | |
444 | self.write('(') | |
02b800ce | 445 | self.autoCallTipShow(command, self.GetCurrentPos() == self.GetTextLength()) |
d14a1e28 RD |
446 | else: |
447 | # Allow the normal event handling to take place. | |
448 | event.Skip() | |
449 | ||
02b800ce | 450 | |
d14a1e28 RD |
451 | def OnKeyDown(self, event): |
452 | """Key down event handler.""" | |
453 | ||
454 | key = event.KeyCode() | |
455 | # If the auto-complete window is up let it do its thing. | |
456 | if self.AutoCompActive(): | |
457 | event.Skip() | |
458 | return | |
486afba9 | 459 | |
d14a1e28 RD |
460 | # Prevent modification of previously submitted |
461 | # commands/responses. | |
462 | controlDown = event.ControlDown() | |
463 | altDown = event.AltDown() | |
464 | shiftDown = event.ShiftDown() | |
465 | currpos = self.GetCurrentPos() | |
466 | endpos = self.GetTextLength() | |
467 | selecting = self.GetSelectionStart() != self.GetSelectionEnd() | |
02b800ce | 468 | |
486afba9 | 469 | if controlDown and shiftDown and key in (ord('F'), ord('f')): |
02b800ce RD |
470 | li = self.GetCurrentLine() |
471 | m = self.MarkerGet(li) | |
472 | if m & 1<<0: | |
473 | startP = self.PositionFromLine(li) | |
474 | self.MarkerDelete(li, 0) | |
475 | maxli = self.GetLineCount() | |
476 | li += 1 # li stayed visible as header-line | |
477 | li0 = li | |
478 | while li<maxli and self.GetLineVisible(li) == 0: | |
479 | li += 1 | |
480 | endP = self.GetLineEndPosition(li-1) | |
481 | self.ShowLines(li0, li-1) | |
482 | self.SetSelection( startP, endP ) # select reappearing text to allow "hide again" | |
483 | return | |
484 | startP,endP = self.GetSelection() | |
485 | endP-=1 | |
486 | startL,endL = self.LineFromPosition(startP), self.LineFromPosition(endP) | |
487 | ||
488 | if endL == self.LineFromPosition(self.promptPosEnd): # never hide last prompt | |
489 | endL -= 1 | |
490 | ||
491 | m = self.MarkerGet(startL) | |
492 | self.MarkerAdd(startL, 0) | |
493 | self.HideLines(startL+1,endL) | |
494 | self.SetCurrentPos( startP ) # to ensure caret stays visible ! | |
495 | ||
496 | if key == wx.WXK_F12: #seb | |
497 | if self.noteMode: | |
498 | # self.promptPosStart not used anyway - or ? | |
499 | self.promptPosEnd = self.PositionFromLine( self.GetLineCount()-1 ) + len(str(sys.ps1)) | |
500 | self.GotoLine(self.GetLineCount()) | |
501 | self.GotoPos(self.promptPosEnd) | |
502 | self.prompt() #make sure we have a prompt | |
503 | self.SetCaretForeground("black") | |
504 | self.SetCaretWidth(1) #default | |
505 | self.SetCaretPeriod(500) #default | |
506 | else: | |
507 | self.SetCaretForeground("red") | |
508 | self.SetCaretWidth(4) | |
509 | self.SetCaretPeriod(0) #steady | |
510 | ||
511 | self.noteMode = not self.noteMode | |
512 | return | |
513 | if self.noteMode: | |
514 | event.Skip() | |
515 | return | |
516 | ||
d14a1e28 RD |
517 | # Return (Enter) is used to submit a command to the |
518 | # interpreter. | |
e773f79b | 519 | if (not controlDown and not shiftDown and not altDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: |
d14a1e28 RD |
520 | if self.CallTipActive(): |
521 | self.CallTipCancel() | |
522 | self.processLine() | |
486afba9 RD |
523 | |
524 | # Complete Text (from already typed words) | |
e773f79b | 525 | elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: |
02b800ce | 526 | self.OnShowCompHistory() |
486afba9 RD |
527 | |
528 | # Ctrl+Return (Ctrl+Enter) is used to insert a line break. | |
e773f79b | 529 | elif controlDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: |
d14a1e28 RD |
530 | if self.CallTipActive(): |
531 | self.CallTipCancel() | |
532 | if currpos == endpos: | |
533 | self.processLine() | |
534 | else: | |
535 | self.insertLineBreak() | |
486afba9 | 536 | |
d14a1e28 RD |
537 | # Let Ctrl-Alt-* get handled normally. |
538 | elif controlDown and altDown: | |
539 | event.Skip() | |
486afba9 | 540 | |
d14a1e28 RD |
541 | # Clear the current, unexecuted command. |
542 | elif key == wx.WXK_ESCAPE: | |
543 | if self.CallTipActive(): | |
544 | event.Skip() | |
545 | else: | |
546 | self.clearCommand() | |
486afba9 | 547 | |
1ad0de84 RD |
548 | # Clear the current command |
549 | elif key == wx.WXK_BACK and controlDown and shiftDown: | |
550 | self.clearCommand() | |
551 | ||
d14a1e28 | 552 | # Increase font size. |
486afba9 | 553 | elif controlDown and key in (ord(']'), wx.WXK_NUMPAD_ADD): |
d14a1e28 | 554 | dispatcher.send(signal='FontIncrease') |
486afba9 | 555 | |
d14a1e28 | 556 | # Decrease font size. |
486afba9 | 557 | elif controlDown and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT): |
d14a1e28 | 558 | dispatcher.send(signal='FontDecrease') |
486afba9 | 559 | |
d14a1e28 | 560 | # Default font size. |
486afba9 | 561 | elif controlDown and key in (ord('='), wx.WXK_NUMPAD_DIVIDE): |
d14a1e28 | 562 | dispatcher.send(signal='FontDefault') |
486afba9 | 563 | |
d14a1e28 RD |
564 | # Cut to the clipboard. |
565 | elif (controlDown and key in (ord('X'), ord('x'))) \ | |
486afba9 | 566 | or (shiftDown and key == wx.WXK_DELETE): |
d14a1e28 | 567 | self.Cut() |
486afba9 | 568 | |
d14a1e28 RD |
569 | # Copy to the clipboard. |
570 | elif controlDown and not shiftDown \ | |
486afba9 | 571 | and key in (ord('C'), ord('c'), wx.WXK_INSERT): |
d14a1e28 | 572 | self.Copy() |
486afba9 | 573 | |
d14a1e28 RD |
574 | # Copy to the clipboard, including prompts. |
575 | elif controlDown and shiftDown \ | |
486afba9 | 576 | and key in (ord('C'), ord('c'), wx.WXK_INSERT): |
d14a1e28 | 577 | self.CopyWithPrompts() |
486afba9 | 578 | |
d14a1e28 RD |
579 | # Copy to the clipboard, including prefixed prompts. |
580 | elif altDown and not controlDown \ | |
486afba9 | 581 | and key in (ord('C'), ord('c'), wx.WXK_INSERT): |
d14a1e28 | 582 | self.CopyWithPromptsPrefixed() |
486afba9 | 583 | |
d14a1e28 RD |
584 | # Home needs to be aware of the prompt. |
585 | elif key == wx.WXK_HOME: | |
586 | home = self.promptPosEnd | |
587 | if currpos > home: | |
588 | self.SetCurrentPos(home) | |
589 | if not selecting and not shiftDown: | |
590 | self.SetAnchor(home) | |
591 | self.EnsureCaretVisible() | |
592 | else: | |
593 | event.Skip() | |
486afba9 | 594 | |
d14a1e28 RD |
595 | # |
596 | # The following handlers modify text, so we need to see if | |
597 | # there is a selection that includes text prior to the prompt. | |
598 | # | |
599 | # Don't modify a selection with text prior to the prompt. | |
600 | elif selecting and key not in NAVKEYS and not self.CanEdit(): | |
601 | pass | |
486afba9 | 602 | |
d14a1e28 RD |
603 | # Paste from the clipboard. |
604 | elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \ | |
605 | or (shiftDown and not controlDown and key == wx.WXK_INSERT): | |
606 | self.Paste() | |
486afba9 RD |
607 | |
608 | # manually invoke AutoComplete and Calltips | |
02b800ce | 609 | elif controlDown and key == wx.WXK_SPACE: |
486afba9 RD |
610 | self.OnCallTipAutoCompleteManually(shiftDown) |
611 | ||
d14a1e28 RD |
612 | # Paste from the clipboard, run commands. |
613 | elif controlDown and shiftDown and key in (ord('V'), ord('v')): | |
614 | self.PasteAndRun() | |
486afba9 | 615 | |
d14a1e28 RD |
616 | # Replace with the previous command from the history buffer. |
617 | elif (controlDown and key == wx.WXK_UP) \ | |
618 | or (altDown and key in (ord('P'), ord('p'))): | |
619 | self.OnHistoryReplace(step=+1) | |
486afba9 | 620 | |
d14a1e28 RD |
621 | # Replace with the next command from the history buffer. |
622 | elif (controlDown and key == wx.WXK_DOWN) \ | |
623 | or (altDown and key in (ord('N'), ord('n'))): | |
624 | self.OnHistoryReplace(step=-1) | |
486afba9 | 625 | |
d14a1e28 RD |
626 | # Insert the previous command from the history buffer. |
627 | elif (shiftDown and key == wx.WXK_UP) and self.CanEdit(): | |
628 | self.OnHistoryInsert(step=+1) | |
486afba9 | 629 | |
d14a1e28 RD |
630 | # Insert the next command from the history buffer. |
631 | elif (shiftDown and key == wx.WXK_DOWN) and self.CanEdit(): | |
632 | self.OnHistoryInsert(step=-1) | |
486afba9 | 633 | |
d14a1e28 RD |
634 | # Search up the history for the text in front of the cursor. |
635 | elif key == wx.WXK_F8: | |
636 | self.OnHistorySearch() | |
486afba9 | 637 | |
d14a1e28 RD |
638 | # Don't backspace over the latest non-continuation prompt. |
639 | elif key == wx.WXK_BACK: | |
640 | if selecting and self.CanEdit(): | |
641 | event.Skip() | |
642 | elif currpos > self.promptPosEnd: | |
643 | event.Skip() | |
486afba9 | 644 | |
d14a1e28 RD |
645 | # Only allow these keys after the latest prompt. |
646 | elif key in (wx.WXK_TAB, wx.WXK_DELETE): | |
647 | if self.CanEdit(): | |
648 | event.Skip() | |
486afba9 | 649 | |
d14a1e28 RD |
650 | # Don't toggle between insert mode and overwrite mode. |
651 | elif key == wx.WXK_INSERT: | |
652 | pass | |
486afba9 | 653 | |
d14a1e28 RD |
654 | # Don't allow line deletion. |
655 | elif controlDown and key in (ord('L'), ord('l')): | |
656 | pass | |
486afba9 | 657 | |
d14a1e28 RD |
658 | # Don't allow line transposition. |
659 | elif controlDown and key in (ord('T'), ord('t')): | |
660 | pass | |
486afba9 | 661 | |
d14a1e28 RD |
662 | # Basic navigation keys should work anywhere. |
663 | elif key in NAVKEYS: | |
664 | event.Skip() | |
486afba9 | 665 | |
d14a1e28 RD |
666 | # Protect the readonly portion of the shell. |
667 | elif not self.CanEdit(): | |
668 | pass | |
486afba9 | 669 | |
d14a1e28 RD |
670 | else: |
671 | event.Skip() | |
672 | ||
486afba9 | 673 | |
02b800ce RD |
674 | def OnShowCompHistory(self): |
675 | """Show possible autocompletion Words from already typed words.""" | |
676 | ||
677 | #copy from history | |
678 | his = self.history[:] | |
679 | ||
680 | #put together in one string | |
681 | joined = " ".join (his) | |
682 | import re | |
683 | ||
684 | #sort out only "good" words | |
685 | newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined) | |
686 | ||
687 | #length > 1 (mix out "trash") | |
688 | thlist = [] | |
689 | for i in newlist: | |
690 | if len (i) > 1: | |
691 | thlist.append (i) | |
692 | ||
693 | #unique (no duplicate words | |
694 | #oneliner from german python forum => unique list | |
695 | unlist = [thlist[i] for i in xrange(len(thlist)) if thlist[i] not in thlist[:i]] | |
696 | ||
697 | #sort lowercase | |
698 | unlist.sort(lambda a, b: cmp(a.lower(), b.lower())) | |
699 | ||
700 | #this is more convenient, isn't it? | |
701 | self.AutoCompSetIgnoreCase(True) | |
702 | ||
703 | #join again together in a string | |
704 | stringlist = " ".join(unlist) | |
705 | ||
706 | #pos von 0 noch ausrechnen | |
707 | ||
708 | #how big is the offset? | |
709 | cpos = self.GetCurrentPos() - 1 | |
710 | while chr (self.GetCharAt (cpos)).isalnum(): | |
711 | cpos -= 1 | |
712 | ||
713 | #the most important part | |
714 | self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist) | |
715 | ||
716 | ||
d14a1e28 RD |
717 | def clearCommand(self): |
718 | """Delete the current, unexecuted command.""" | |
719 | startpos = self.promptPosEnd | |
720 | endpos = self.GetTextLength() | |
721 | self.SetSelection(startpos, endpos) | |
722 | self.ReplaceSelection('') | |
723 | self.more = False | |
724 | ||
725 | def OnHistoryReplace(self, step): | |
726 | """Replace with the previous/next command from the history buffer.""" | |
727 | self.clearCommand() | |
728 | self.replaceFromHistory(step) | |
729 | ||
730 | def replaceFromHistory(self, step): | |
731 | """Replace selection with command from the history buffer.""" | |
732 | ps2 = str(sys.ps2) | |
733 | self.ReplaceSelection('') | |
734 | newindex = self.historyIndex + step | |
735 | if -1 <= newindex <= len(self.history): | |
736 | self.historyIndex = newindex | |
737 | if 0 <= newindex <= len(self.history)-1: | |
738 | command = self.history[self.historyIndex] | |
739 | command = command.replace('\n', os.linesep + ps2) | |
740 | self.ReplaceSelection(command) | |
741 | ||
742 | def OnHistoryInsert(self, step): | |
743 | """Insert the previous/next command from the history buffer.""" | |
744 | if not self.CanEdit(): | |
745 | return | |
746 | startpos = self.GetCurrentPos() | |
747 | self.replaceFromHistory(step) | |
748 | endpos = self.GetCurrentPos() | |
749 | self.SetSelection(endpos, startpos) | |
750 | ||
751 | def OnHistorySearch(self): | |
752 | """Search up the history buffer for the text in front of the cursor.""" | |
753 | if not self.CanEdit(): | |
754 | return | |
755 | startpos = self.GetCurrentPos() | |
756 | # The text up to the cursor is what we search for. | |
757 | numCharsAfterCursor = self.GetTextLength() - startpos | |
758 | searchText = self.getCommand(rstrip=False) | |
759 | if numCharsAfterCursor > 0: | |
760 | searchText = searchText[:-numCharsAfterCursor] | |
761 | if not searchText: | |
762 | return | |
763 | # Search upwards from the current history position and loop | |
764 | # back to the beginning if we don't find anything. | |
765 | if (self.historyIndex <= -1) \ | |
766 | or (self.historyIndex >= len(self.history)-2): | |
767 | searchOrder = range(len(self.history)) | |
768 | else: | |
769 | searchOrder = range(self.historyIndex+1, len(self.history)) + \ | |
770 | range(self.historyIndex) | |
771 | for i in searchOrder: | |
772 | command = self.history[i] | |
773 | if command[:len(searchText)] == searchText: | |
774 | # Replace the current selection with the one we found. | |
775 | self.ReplaceSelection(command[len(searchText):]) | |
776 | endpos = self.GetCurrentPos() | |
777 | self.SetSelection(endpos, startpos) | |
778 | # We've now warped into middle of the history. | |
779 | self.historyIndex = i | |
780 | break | |
781 | ||
782 | def setStatusText(self, text): | |
783 | """Display status information.""" | |
784 | ||
785 | # This method will likely be replaced by the enclosing app to | |
786 | # do something more interesting, like write to a status bar. | |
787 | print text | |
788 | ||
789 | def insertLineBreak(self): | |
790 | """Insert a new line break.""" | |
791 | if self.CanEdit(): | |
792 | self.write(os.linesep) | |
793 | self.more = True | |
794 | self.prompt() | |
795 | ||
796 | def processLine(self): | |
797 | """Process the line of text at which the user hit Enter.""" | |
798 | ||
799 | # The user hit ENTER and we need to decide what to do. They | |
800 | # could be sitting on any line in the shell. | |
801 | ||
802 | thepos = self.GetCurrentPos() | |
803 | startpos = self.promptPosEnd | |
804 | endpos = self.GetTextLength() | |
805 | ps2 = str(sys.ps2) | |
806 | # If they hit RETURN inside the current command, execute the | |
807 | # command. | |
808 | if self.CanEdit(): | |
809 | self.SetCurrentPos(endpos) | |
810 | self.interp.more = False | |
811 | command = self.GetTextRange(startpos, endpos) | |
812 | lines = command.split(os.linesep + ps2) | |
813 | lines = [line.rstrip() for line in lines] | |
814 | command = '\n'.join(lines) | |
815 | if self.reader.isreading: | |
816 | if not command: | |
817 | # Match the behavior of the standard Python shell | |
818 | # when the user hits return without entering a | |
819 | # value. | |
820 | command = '\n' | |
821 | self.reader.input = command | |
822 | self.write(os.linesep) | |
823 | else: | |
824 | self.push(command) | |
02b800ce | 825 | wx.FutureCall(1, self.EnsureCaretVisible) |
d14a1e28 RD |
826 | # Or replace the current command with the other command. |
827 | else: | |
828 | # If the line contains a command (even an invalid one). | |
829 | if self.getCommand(rstrip=False): | |
830 | command = self.getMultilineCommand() | |
831 | self.clearCommand() | |
832 | self.write(command) | |
833 | # Otherwise, put the cursor back where we started. | |
834 | else: | |
835 | self.SetCurrentPos(thepos) | |
836 | self.SetAnchor(thepos) | |
837 | ||
838 | def getMultilineCommand(self, rstrip=True): | |
839 | """Extract a multi-line command from the editor. | |
840 | ||
841 | The command may not necessarily be valid Python syntax.""" | |
842 | # XXX Need to extract real prompts here. Need to keep track of | |
843 | # the prompt every time a command is issued. | |
844 | ps1 = str(sys.ps1) | |
845 | ps1size = len(ps1) | |
846 | ps2 = str(sys.ps2) | |
847 | ps2size = len(ps2) | |
848 | # This is a total hack job, but it works. | |
849 | text = self.GetCurLine()[0] | |
850 | line = self.GetCurrentLine() | |
851 | while text[:ps2size] == ps2 and line > 0: | |
852 | line -= 1 | |
853 | self.GotoLine(line) | |
854 | text = self.GetCurLine()[0] | |
855 | if text[:ps1size] == ps1: | |
856 | line = self.GetCurrentLine() | |
857 | self.GotoLine(line) | |
858 | startpos = self.GetCurrentPos() + ps1size | |
859 | line += 1 | |
860 | self.GotoLine(line) | |
861 | while self.GetCurLine()[0][:ps2size] == ps2: | |
862 | line += 1 | |
863 | self.GotoLine(line) | |
864 | stoppos = self.GetCurrentPos() | |
865 | command = self.GetTextRange(startpos, stoppos) | |
866 | command = command.replace(os.linesep + ps2, '\n') | |
867 | command = command.rstrip() | |
868 | command = command.replace('\n', os.linesep + ps2) | |
869 | else: | |
870 | command = '' | |
871 | if rstrip: | |
872 | command = command.rstrip() | |
873 | return command | |
874 | ||
875 | def getCommand(self, text=None, rstrip=True): | |
876 | """Extract a command from text which may include a shell prompt. | |
877 | ||
878 | The command may not necessarily be valid Python syntax.""" | |
879 | if not text: | |
880 | text = self.GetCurLine()[0] | |
881 | # Strip the prompt off the front leaving just the command. | |
882 | command = self.lstripPrompt(text) | |
883 | if command == text: | |
884 | command = '' # Real commands have prompts. | |
885 | if rstrip: | |
886 | command = command.rstrip() | |
887 | return command | |
888 | ||
889 | def lstripPrompt(self, text): | |
890 | """Return text without a leading prompt.""" | |
891 | ps1 = str(sys.ps1) | |
892 | ps1size = len(ps1) | |
893 | ps2 = str(sys.ps2) | |
894 | ps2size = len(ps2) | |
895 | # Strip the prompt off the front of text. | |
896 | if text[:ps1size] == ps1: | |
897 | text = text[ps1size:] | |
898 | elif text[:ps2size] == ps2: | |
899 | text = text[ps2size:] | |
900 | return text | |
901 | ||
02b800ce | 902 | def push(self, command, silent = False): |
d14a1e28 | 903 | """Send command to the interpreter for execution.""" |
02b800ce RD |
904 | if not silent: |
905 | self.write(os.linesep) | |
d14a1e28 RD |
906 | busy = wx.BusyCursor() |
907 | self.waiting = True | |
908 | self.more = self.interp.push(command) | |
909 | self.waiting = False | |
910 | del busy | |
911 | if not self.more: | |
912 | self.addHistory(command.rstrip()) | |
02b800ce RD |
913 | if not silent: |
914 | self.prompt() | |
d14a1e28 RD |
915 | |
916 | def addHistory(self, command): | |
917 | """Add command to the command history.""" | |
918 | # Reset the history position. | |
919 | self.historyIndex = -1 | |
920 | # Insert this command into the history, unless it's a blank | |
921 | # line or the same as the last command. | |
922 | if command != '' \ | |
923 | and (len(self.history) == 0 or command != self.history[0]): | |
924 | self.history.insert(0, command) | |
e773f79b | 925 | dispatcher.send(signal="Shell.addHistory", command=command) |
d14a1e28 RD |
926 | |
927 | def write(self, text): | |
928 | """Display text in the shell. | |
929 | ||
930 | Replace line endings with OS-specific endings.""" | |
931 | text = self.fixLineEndings(text) | |
932 | self.AddText(text) | |
933 | self.EnsureCaretVisible() | |
934 | ||
935 | def fixLineEndings(self, text): | |
936 | """Return text with line endings replaced by OS-specific endings.""" | |
937 | lines = text.split('\r\n') | |
938 | for l in range(len(lines)): | |
939 | chunks = lines[l].split('\r') | |
940 | for c in range(len(chunks)): | |
941 | chunks[c] = os.linesep.join(chunks[c].split('\n')) | |
942 | lines[l] = os.linesep.join(chunks) | |
943 | text = os.linesep.join(lines) | |
944 | return text | |
945 | ||
946 | def prompt(self): | |
947 | """Display proper prompt for the context: ps1, ps2 or ps3. | |
948 | ||
949 | If this is a continuation line, autoindent as necessary.""" | |
950 | isreading = self.reader.isreading | |
951 | skip = False | |
952 | if isreading: | |
953 | prompt = str(sys.ps3) | |
954 | elif self.more: | |
955 | prompt = str(sys.ps2) | |
956 | else: | |
957 | prompt = str(sys.ps1) | |
958 | pos = self.GetCurLine()[1] | |
959 | if pos > 0: | |
960 | if isreading: | |
961 | skip = True | |
962 | else: | |
963 | self.write(os.linesep) | |
964 | if not self.more: | |
965 | self.promptPosStart = self.GetCurrentPos() | |
966 | if not skip: | |
967 | self.write(prompt) | |
968 | if not self.more: | |
969 | self.promptPosEnd = self.GetCurrentPos() | |
970 | # Keep the undo feature from undoing previous responses. | |
971 | self.EmptyUndoBuffer() | |
972 | # XXX Add some autoindent magic here if more. | |
973 | if self.more: | |
974 | self.write(' '*4) # Temporary hack indentation. | |
975 | self.EnsureCaretVisible() | |
976 | self.ScrollToColumn(0) | |
977 | ||
978 | def readline(self): | |
979 | """Replacement for stdin.readline().""" | |
980 | input = '' | |
981 | reader = self.reader | |
982 | reader.isreading = True | |
983 | self.prompt() | |
984 | try: | |
985 | while not reader.input: | |
986 | wx.YieldIfNeeded() | |
987 | input = reader.input | |
988 | finally: | |
989 | reader.input = '' | |
990 | reader.isreading = False | |
991 | input = str(input) # In case of Unicode. | |
992 | return input | |
993 | ||
994 | def readlines(self): | |
995 | """Replacement for stdin.readlines().""" | |
996 | lines = [] | |
997 | while lines[-1:] != ['\n']: | |
998 | lines.append(self.readline()) | |
999 | return lines | |
1000 | ||
1001 | def raw_input(self, prompt=''): | |
1002 | """Return string based on user input.""" | |
1003 | if prompt: | |
1004 | self.write(prompt) | |
1005 | return self.readline() | |
1006 | ||
1007 | def ask(self, prompt='Please enter your response:'): | |
1008 | """Get response from the user using a dialog box.""" | |
1009 | dialog = wx.TextEntryDialog(None, prompt, | |
1010 | 'Input Dialog (Raw)', '') | |
1011 | try: | |
1012 | if dialog.ShowModal() == wx.ID_OK: | |
1013 | text = dialog.GetValue() | |
1014 | return text | |
1015 | finally: | |
1016 | dialog.Destroy() | |
1017 | return '' | |
1018 | ||
1019 | def pause(self): | |
1020 | """Halt execution pending a response from the user.""" | |
1021 | self.ask('Press enter to continue:') | |
1022 | ||
1023 | def clear(self): | |
1024 | """Delete all text from the shell.""" | |
1025 | self.ClearAll() | |
1026 | ||
1027 | def run(self, command, prompt=True, verbose=True): | |
1028 | """Execute command as if it was typed in directly. | |
1029 | >>> shell.run('print "this"') | |
1030 | >>> print "this" | |
1031 | this | |
1032 | >>> | |
1033 | """ | |
1034 | # Go to the very bottom of the text. | |
1035 | endpos = self.GetTextLength() | |
1036 | self.SetCurrentPos(endpos) | |
1037 | command = command.rstrip() | |
1038 | if prompt: self.prompt() | |
1039 | if verbose: self.write(command) | |
1040 | self.push(command) | |
1041 | ||
1042 | def runfile(self, filename): | |
1043 | """Execute all commands in file as if they were typed into the | |
1044 | shell.""" | |
1045 | file = open(filename) | |
1046 | try: | |
1047 | self.prompt() | |
1048 | for command in file.readlines(): | |
1049 | if command[:6] == 'shell.': | |
1050 | # Run shell methods silently. | |
1051 | self.run(command, prompt=False, verbose=False) | |
1052 | else: | |
1053 | self.run(command, prompt=False, verbose=True) | |
1054 | finally: | |
1055 | file.close() | |
1056 | ||
02b800ce | 1057 | def autoCompleteShow(self, command, offset = 0): |
d14a1e28 | 1058 | """Display auto-completion popup list.""" |
d351525a PB |
1059 | self.AutoCompSetAutoHide(self.autoCompleteAutoHide) |
1060 | self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive) | |
d14a1e28 RD |
1061 | list = self.interp.getAutoCompleteList(command, |
1062 | includeMagic=self.autoCompleteIncludeMagic, | |
1063 | includeSingle=self.autoCompleteIncludeSingle, | |
1064 | includeDouble=self.autoCompleteIncludeDouble) | |
1065 | if list: | |
1066 | options = ' '.join(list) | |
02b800ce | 1067 | #offset = 0 |
d14a1e28 RD |
1068 | self.AutoCompShow(offset, options) |
1069 | ||
02b800ce | 1070 | def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False): |
d14a1e28 RD |
1071 | """Display argument spec and docstring in a popup window.""" |
1072 | if self.CallTipActive(): | |
1073 | self.CallTipCancel() | |
1074 | (name, argspec, tip) = self.interp.getCallTip(command) | |
1075 | if tip: | |
1076 | dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip) | |
02b800ce | 1077 | if not self.autoCallTip and not forceCallTip: |
d14a1e28 | 1078 | return |
02b800ce | 1079 | if argspec and insertcalltip and self.callTipInsert: |
d14a1e28 RD |
1080 | startpos = self.GetCurrentPos() |
1081 | self.write(argspec + ')') | |
1082 | endpos = self.GetCurrentPos() | |
1083 | self.SetSelection(endpos, startpos) | |
1084 | if tip: | |
1085 | curpos = self.GetCurrentPos() | |
1086 | tippos = curpos - (len(name) + 1) | |
1087 | fallback = curpos - self.GetColumn(curpos) | |
1088 | # In case there isn't enough room, only go back to the | |
1089 | # fallback. | |
1090 | tippos = max(tippos, fallback) | |
1091 | self.CallTipShow(tippos, tip) | |
02b800ce RD |
1092 | |
1093 | def OnCallTipAutoCompleteManually (self, shiftDown): | |
1094 | """AutoComplete and Calltips manually.""" | |
1095 | if self.AutoCompActive(): | |
1096 | self.AutoCompCancel() | |
1097 | currpos = self.GetCurrentPos() | |
1098 | stoppos = self.promptPosEnd | |
1099 | ||
1100 | cpos = currpos | |
1101 | #go back until '.' is found | |
1102 | pointavailpos = -1 | |
1103 | while cpos >= stoppos: | |
1104 | if self.GetCharAt(cpos) == ord ('.'): | |
1105 | pointavailpos = cpos | |
1106 | break | |
1107 | cpos -= 1 | |
1108 | ||
1109 | #word from non whitespace until '.' | |
1110 | if pointavailpos != -1: | |
1111 | #look backward for first whitespace char | |
1112 | textbehind = self.GetTextRange (pointavailpos + 1, currpos) | |
1113 | pointavailpos += 1 | |
1114 | ||
1115 | if not shiftDown: | |
1116 | #call AutoComplete | |
1117 | stoppos = self.promptPosEnd | |
1118 | textbefore = self.GetTextRange(stoppos, pointavailpos) | |
1119 | self.autoCompleteShow(textbefore, len (textbehind)) | |
1120 | else: | |
1121 | #call CallTips | |
1122 | cpos = pointavailpos | |
1123 | begpos = -1 | |
1124 | while cpos > stoppos: | |
1125 | if chr(self.GetCharAt(cpos)).isspace(): | |
1126 | begpos = cpos | |
1127 | break | |
1128 | cpos -= 1 | |
1129 | if begpos == -1: | |
1130 | begpos = cpos | |
1131 | ctips = self.GetTextRange (begpos, currpos) | |
1132 | ctindex = ctips.find ('(') | |
1133 | if ctindex != -1 and not self.CallTipActive(): | |
1134 | #insert calltip, if current pos is '(', otherwise show it only | |
095315e2 RD |
1135 | self.autoCallTipShow(ctips[:ctindex + 1], |
1136 | self.GetCharAt(currpos - 1) == ord('(') and self.GetCurrentPos() == self.GetTextLength(), | |
02b800ce RD |
1137 | True) |
1138 | ||
d14a1e28 RD |
1139 | |
1140 | def writeOut(self, text): | |
1141 | """Replacement for stdout.""" | |
1142 | self.write(text) | |
1143 | ||
1144 | def writeErr(self, text): | |
1145 | """Replacement for stderr.""" | |
1146 | self.write(text) | |
1147 | ||
1148 | def redirectStdin(self, redirect=True): | |
1149 | """If redirect is true then sys.stdin will come from the shell.""" | |
1150 | if redirect: | |
1151 | sys.stdin = self.reader | |
1152 | else: | |
1153 | sys.stdin = self.stdin | |
1154 | ||
1155 | def redirectStdout(self, redirect=True): | |
1156 | """If redirect is true then sys.stdout will go to the shell.""" | |
1157 | if redirect: | |
1158 | sys.stdout = PseudoFileOut(self.writeOut) | |
1159 | else: | |
1160 | sys.stdout = self.stdout | |
1161 | ||
1162 | def redirectStderr(self, redirect=True): | |
1163 | """If redirect is true then sys.stderr will go to the shell.""" | |
1164 | if redirect: | |
1165 | sys.stderr = PseudoFileErr(self.writeErr) | |
1166 | else: | |
1167 | sys.stderr = self.stderr | |
1168 | ||
1169 | def CanCut(self): | |
1170 | """Return true if text is selected and can be cut.""" | |
1171 | if self.GetSelectionStart() != self.GetSelectionEnd() \ | |
1172 | and self.GetSelectionStart() >= self.promptPosEnd \ | |
1173 | and self.GetSelectionEnd() >= self.promptPosEnd: | |
1174 | return True | |
1175 | else: | |
1176 | return False | |
1177 | ||
1178 | def CanPaste(self): | |
1179 | """Return true if a paste should succeed.""" | |
1180 | if self.CanEdit() and editwindow.EditWindow.CanPaste(self): | |
1181 | return True | |
1182 | else: | |
1183 | return False | |
1184 | ||
1185 | def CanEdit(self): | |
1186 | """Return true if editing should succeed.""" | |
1187 | if self.GetSelectionStart() != self.GetSelectionEnd(): | |
1188 | if self.GetSelectionStart() >= self.promptPosEnd \ | |
1189 | and self.GetSelectionEnd() >= self.promptPosEnd: | |
1190 | return True | |
1191 | else: | |
1192 | return False | |
1193 | else: | |
1194 | return self.GetCurrentPos() >= self.promptPosEnd | |
1195 | ||
1196 | def Cut(self): | |
1197 | """Remove selection and place it on the clipboard.""" | |
1198 | if self.CanCut() and self.CanCopy(): | |
1199 | if self.AutoCompActive(): | |
1200 | self.AutoCompCancel() | |
1201 | if self.CallTipActive(): | |
1202 | self.CallTipCancel() | |
1203 | self.Copy() | |
1204 | self.ReplaceSelection('') | |
1205 | ||
1206 | def Copy(self): | |
1207 | """Copy selection and place it on the clipboard.""" | |
1208 | if self.CanCopy(): | |
1209 | ps1 = str(sys.ps1) | |
1210 | ps2 = str(sys.ps2) | |
1211 | command = self.GetSelectedText() | |
1212 | command = command.replace(os.linesep + ps2, os.linesep) | |
1213 | command = command.replace(os.linesep + ps1, os.linesep) | |
1214 | command = self.lstripPrompt(text=command) | |
1215 | data = wx.TextDataObject(command) | |
1216 | self._clip(data) | |
1217 | ||
1218 | def CopyWithPrompts(self): | |
1219 | """Copy selection, including prompts, and place it on the clipboard.""" | |
1220 | if self.CanCopy(): | |
1221 | command = self.GetSelectedText() | |
1222 | data = wx.TextDataObject(command) | |
1223 | self._clip(data) | |
1224 | ||
1225 | def CopyWithPromptsPrefixed(self): | |
1226 | """Copy selection, including prompts prefixed with four | |
1227 | spaces, and place it on the clipboard.""" | |
1228 | if self.CanCopy(): | |
1229 | command = self.GetSelectedText() | |
1230 | spaces = ' ' * 4 | |
1231 | command = spaces + command.replace(os.linesep, | |
1232 | os.linesep + spaces) | |
1233 | data = wx.TextDataObject(command) | |
1234 | self._clip(data) | |
1235 | ||
1236 | def _clip(self, data): | |
1237 | if wx.TheClipboard.Open(): | |
1238 | wx.TheClipboard.UsePrimarySelection(False) | |
1239 | wx.TheClipboard.SetData(data) | |
1240 | wx.TheClipboard.Flush() | |
1241 | wx.TheClipboard.Close() | |
1242 | ||
1243 | def Paste(self): | |
1244 | """Replace selection with clipboard contents.""" | |
1245 | if self.CanPaste() and wx.TheClipboard.Open(): | |
1246 | ps2 = str(sys.ps2) | |
1247 | if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): | |
1248 | data = wx.TextDataObject() | |
1249 | if wx.TheClipboard.GetData(data): | |
1250 | self.ReplaceSelection('') | |
1251 | command = data.GetText() | |
1252 | command = command.rstrip() | |
1253 | command = self.fixLineEndings(command) | |
1254 | command = self.lstripPrompt(text=command) | |
1255 | command = command.replace(os.linesep + ps2, '\n') | |
1256 | command = command.replace(os.linesep, '\n') | |
1257 | command = command.replace('\n', os.linesep + ps2) | |
1258 | self.write(command) | |
1259 | wx.TheClipboard.Close() | |
1260 | ||
095315e2 | 1261 | |
d14a1e28 RD |
1262 | def PasteAndRun(self): |
1263 | """Replace selection with clipboard contents, run commands.""" | |
095315e2 | 1264 | text = '' |
d14a1e28 | 1265 | if wx.TheClipboard.Open(): |
d14a1e28 RD |
1266 | if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): |
1267 | data = wx.TextDataObject() | |
1268 | if wx.TheClipboard.GetData(data): | |
d14a1e28 | 1269 | text = data.GetText() |
d14a1e28 | 1270 | wx.TheClipboard.Close() |
095315e2 RD |
1271 | if text: |
1272 | self.Execute(text) | |
1273 | ||
1274 | ||
1275 | def Execute(self, text): | |
1276 | """Replace selection with text and run commands.""" | |
1277 | ps1 = str(sys.ps1) | |
1278 | ps2 = str(sys.ps2) | |
1279 | endpos = self.GetTextLength() | |
1280 | self.SetCurrentPos(endpos) | |
1281 | startpos = self.promptPosEnd | |
1282 | self.SetSelection(startpos, endpos) | |
1283 | self.ReplaceSelection('') | |
1284 | text = text.lstrip() | |
1285 | text = self.fixLineEndings(text) | |
1286 | text = self.lstripPrompt(text) | |
1287 | text = text.replace(os.linesep + ps1, '\n') | |
1288 | text = text.replace(os.linesep + ps2, '\n') | |
1289 | text = text.replace(os.linesep, '\n') | |
1290 | lines = text.split('\n') | |
1291 | commands = [] | |
1292 | command = '' | |
1293 | for line in lines: | |
1294 | if line.strip() == ps2.strip(): | |
1295 | # If we are pasting from something like a | |
1296 | # web page that drops the trailing space | |
1297 | # from the ps2 prompt of a blank line. | |
1298 | line = '' | |
1299 | lstrip = line.lstrip() | |
1300 | if line.strip() != '' and lstrip == line and \ | |
1301 | lstrip[:4] not in ['else','elif'] and \ | |
1302 | lstrip[:6] != 'except': | |
1303 | # New command. | |
1304 | if command: | |
1305 | # Add the previous command to the list. | |
1306 | commands.append(command) | |
1307 | # Start a new command, which may be multiline. | |
1308 | command = line | |
1309 | else: | |
1310 | # Multiline command. Add to the command. | |
1311 | command += '\n' | |
1312 | command += line | |
1313 | commands.append(command) | |
1314 | for command in commands: | |
1315 | command = command.replace('\n', os.linesep + ps2) | |
1316 | self.write(command) | |
1317 | self.processLine() | |
1318 | ||
d14a1e28 RD |
1319 | |
1320 | def wrap(self, wrap=True): | |
1321 | """Sets whether text is word wrapped.""" | |
1322 | try: | |
1323 | self.SetWrapMode(wrap) | |
1324 | except AttributeError: | |
1325 | return 'Wrapping is not available in this version.' | |
1326 | ||
1327 | def zoom(self, points=0): | |
1328 | """Set the zoom level. | |
1329 | ||
1330 | This number of points is added to the size of all fonts. It | |
1331 | may be positive to magnify or negative to reduce.""" | |
1332 | self.SetZoom(points) | |
02b800ce RD |
1333 | |
1334 | ||
1335 | ||
1336 | def LoadSettings(self, config): | |
1337 | self.autoComplete = config.ReadBool('Options/AutoComplete', True) | |
1338 | self.autoCompleteIncludeMagic = config.ReadBool('Options/AutoCompleteIncludeMagic', True) | |
1339 | self.autoCompleteIncludeSingle = config.ReadBool('Options/AutoCompleteIncludeSingle', True) | |
1340 | self.autoCompleteIncludeDouble = config.ReadBool('Options/AutoCompleteIncludeDouble', True) | |
1341 | ||
1342 | self.autoCallTip = config.ReadBool('Options/AutoCallTip', True) | |
1343 | self.callTipInsert = config.ReadBool('Options/CallTipInsert', True) | |
1344 | self.SetWrapMode(config.ReadBool('View/WrapMode', True)) | |
1345 | ||
1346 | useAA = config.ReadBool('Options/UseAntiAliasing', self.GetUseAntiAliasing()) | |
1347 | self.SetUseAntiAliasing(useAA) | |
1348 | self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True) | |
1349 | self.setDisplayLineNumbers (self.lineNumbers) | |
1350 | zoom = config.ReadInt('View/Zoom/Shell', -99) | |
1351 | if zoom != -99: | |
1352 | self.SetZoom(zoom) | |
1353 | ||
1354 | ||
1355 | ||
1356 | def SaveSettings(self, config): | |
1357 | config.WriteBool('Options/AutoComplete', self.autoComplete) | |
1358 | config.WriteBool('Options/AutoCompleteIncludeMagic', self.autoCompleteIncludeMagic) | |
1359 | config.WriteBool('Options/AutoCompleteIncludeSingle', self.autoCompleteIncludeSingle) | |
1360 | config.WriteBool('Options/AutoCompleteIncludeDouble', self.autoCompleteIncludeDouble) | |
1361 | config.WriteBool('Options/AutoCallTip', self.autoCallTip) | |
1362 | config.WriteBool('Options/CallTipInsert', self.callTipInsert) | |
1363 | config.WriteBool('Options/UseAntiAliasing', self.GetUseAntiAliasing()) | |
1364 | config.WriteBool('View/WrapMode', self.GetWrapMode()) | |
1365 | config.WriteBool('View/ShowLineNumbers', self.lineNumbers) | |
1366 | config.WriteInt('View/Zoom/Shell', self.GetZoom()) | |
1367 | ||
1368 | ||
1369 | ||
1370 | ## NOTE: The DnD of file names is disabled until I can figure out how | |
1371 | ## best to still allow DnD of text. | |
1372 | ||
1373 | ||
1374 | ## #seb : File drag and drop | |
1375 | ## class FileDropTarget(wx.FileDropTarget): | |
1376 | ## def __init__(self, obj): | |
1377 | ## wx.FileDropTarget.__init__(self) | |
1378 | ## self.obj = obj | |
1379 | ## def OnDropFiles(self, x, y, filenames): | |
1380 | ## if len(filenames) == 1: | |
1381 | ## txt = 'r\"%s\"' % filenames[0] | |
1382 | ## else: | |
1383 | ## txt = '( ' | |
1384 | ## for f in filenames: | |
1385 | ## txt += 'r\"%s\" , ' % f | |
1386 | ## txt += ')' | |
1387 | ## self.obj.AppendText(txt) | |
1388 | ## pos = self.obj.GetCurrentPos() | |
1389 | ## self.obj.SetCurrentPos( pos ) | |
1390 | ## self.obj.SetSelection( pos, pos ) | |
1391 | ||
1392 | ||
1393 | ||
1394 | ## class TextAndFileDropTarget(wx.DropTarget): | |
1395 | ## def __init__(self, shell): | |
1396 | ## wx.DropTarget.__init__(self) | |
1397 | ## self.shell = shell | |
1398 | ## self.compdo = wx.DataObjectComposite() | |
1399 | ## self.textdo = wx.TextDataObject() | |
1400 | ## self.filedo = wx.FileDataObject() | |
1401 | ## self.compdo.Add(self.textdo) | |
1402 | ## self.compdo.Add(self.filedo, True) | |
1403 | ||
1404 | ## self.SetDataObject(self.compdo) | |
1405 | ||
1406 | ## def OnDrop(self, x, y): | |
1407 | ## return True | |
1408 | ||
1409 | ## def OnData(self, x, y, result): | |
1410 | ## self.GetData() | |
1411 | ## if self.textdo.GetTextLength() > 1: | |
1412 | ## text = self.textdo.GetText() | |
1413 | ## # *** Do somethign with the dragged text here... | |
1414 | ## self.textdo.SetText('') | |
1415 | ## else: | |
1416 | ## filenames = str(self.filename.GetFilenames()) | |
1417 | ## if len(filenames) == 1: | |
1418 | ## txt = 'r\"%s\"' % filenames[0] | |
1419 | ## else: | |
1420 | ## txt = '( ' | |
1421 | ## for f in filenames: | |
1422 | ## txt += 'r\"%s\" , ' % f | |
1423 | ## txt += ')' | |
1424 | ## self.shell.AppendText(txt) | |
1425 | ## pos = self.shell.GetCurrentPos() | |
1426 | ## self.shell.SetCurrentPos( pos ) | |
1427 | ## self.shell.SetSelection( pos, pos ) | |
1428 | ||
1429 | ## return result | |
1430 |