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