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