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