Show More
wx_frontend.py
219 lines
| 8.1 KiB
| text/x-python
|
PythonLexer
|
r1349 | # encoding: utf-8 -*- test-case-name: | ||
# FIXME: Need to add tests. | ||||
# ipython1.frontend.cocoa.tests.test_cocoa_frontend -*- | ||||
"""Classes to provide a Wx frontend to the | ||||
|
r1360 | IPython.kernel.core.interpreter. | ||
|
r1349 | |||
""" | ||||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
# Copyright (C) 2008 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
import wx | ||||
|
r1371 | import re | ||
|
r1374 | from wx import stc | ||
|
r1349 | from console_widget import ConsoleWidget | ||
|
r1375 | import __builtin__ | ||
|
r1349 | |||
|
r1360 | from IPython.frontend.prefilterfrontend import PrefilterFrontEnd | ||
|
r1349 | |||
|
r1374 | #_COMMAND_BG = '#FAFAF1' # Nice green | ||
|
r1375 | _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow | ||
|
r1374 | |||
_RUNNING_BUFFER_MARKER = 31 | ||||
|
r1349 | #------------------------------------------------------------------------------- | ||
# Classes to implement the Wx frontend | ||||
#------------------------------------------------------------------------------- | ||||
|
r1360 | class IPythonWxController(PrefilterFrontEnd, ConsoleWidget): | ||
|
r1349 | |||
output_prompt = \ | ||||
|
r1380 | '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02' | ||
|
r1349 | |||
#-------------------------------------------------------------------------- | ||||
# Public API | ||||
#-------------------------------------------------------------------------- | ||||
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, | ||||
size=wx.DefaultSize, style=wx.CLIP_CHILDREN, | ||||
*args, **kwds): | ||||
""" Create Shell instance. | ||||
""" | ||||
ConsoleWidget.__init__(self, parent, id, pos, size, style) | ||||
|
r1360 | PrefilterFrontEnd.__init__(self) | ||
|
r1349 | |||
# Capture Character keys | ||||
self.Bind(wx.EVT_KEY_DOWN, self._on_key_down) | ||||
|
r1371 | |||
|
r1374 | # Marker for running buffer. | ||
self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND, | ||||
background=_RUNNING_BUFFER_BG) | ||||
|
r1371 | |||
|
r1380 | def do_completion(self): | ||
|
r1373 | """ Do code completion. | ||
""" | ||||
|
r1371 | line = self.get_current_edit_buffer() | ||
|
r1379 | new_line, completions = self.complete(line) | ||
if len(completions)>1: | ||||
|
r1380 | self.write_completion(completions) | ||
|
r1379 | self.replace_current_edit_buffer(new_line) | ||
|
r1373 | |||
|
r1375 | def do_calltip(self): | ||
separators = [' ', '(', '[', '{', '\n', '\t'] | ||||
symbol = self.get_current_edit_buffer() | ||||
for separator in separators: | ||||
symbol_string = symbol.split(separator)[-1] | ||||
base_symbol_string = symbol_string.split('.')[0] | ||||
if base_symbol_string in self.shell.user_ns: | ||||
symbol = self.shell.user_ns[base_symbol_string] | ||||
elif base_symbol_string in self.shell.user_global_ns: | ||||
symbol = self.shell.user_global_ns[base_symbol_string] | ||||
elif base_symbol_string in __builtin__.__dict__: | ||||
symbol = __builtin__.__dict__[base_symbol_string] | ||||
else: | ||||
return False | ||||
|
r1381 | for name in symbol_string.split('.')[1:] + ['__doc__']: | ||
|
r1375 | symbol = getattr(symbol, name) | ||
|
r1380 | try: | ||
|
r1381 | self.AutoCompCancel() | ||
wx.Yield() | ||||
|
r1380 | self.CallTipShow(self.GetCurrentPos(), symbol) | ||
except TypeError: | ||||
# The retrieve symbol couldn't be converted to a string | ||||
pass | ||||
|
r1375 | |||
|
r1379 | |||
|
r1380 | def popup_completion(self, create=False): | ||
""" Updates the popup completion menu if it exists. If create is | ||||
true, open the menu. | ||||
""" | ||||
|
r1373 | line = self.get_current_edit_buffer() | ||
|
r1380 | if (self.AutoCompActive() and not line[-1] == '.') \ | ||
or create==True: | ||||
suggestion, completions = self.complete(line) | ||||
offset=0 | ||||
if completions: | ||||
complete_sep = re.compile('[\s\{\}\[\]\(\)\= ]') | ||||
residual = complete_sep.split(line)[-1] | ||||
offset = len(residual) | ||||
|
r1381 | self.pop_completion(completions, offset=offset) | ||
|
r1371 | |||
|
r1374 | def execute(self, python_string, raw_string=None): | ||
|
r1380 | self.CallTipCancel() | ||
|
r1371 | self._cursor = wx.BusyCursor() | ||
|
r1374 | if raw_string is None: | ||
raw_string = python_string | ||||
end_line = self.current_prompt_line \ | ||||
+ max(1, len(raw_string.split('\n'))-1) | ||||
for i in range(self.current_prompt_line, end_line): | ||||
self.MarkerAdd(i, 31) | ||||
|
r1381 | # Update the display: | ||
wx.Yield() | ||||
|
r1380 | # Remove the trailing "\n" for cleaner display | ||
self.SetSelection(self.GetLength()-1, self.GetLength()) | ||||
self.ReplaceSelection('') | ||||
self.GotoPos(self.GetLength()) | ||||
|
r1374 | PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string) | ||
|
r1371 | |||
def after_execute(self): | ||||
PrefilterFrontEnd.after_execute(self) | ||||
|
r1373 | if hasattr(self, '_cursor'): | ||
del self._cursor | ||||
|
r1371 | |||
|
r1349 | #-------------------------------------------------------------------------- | ||
# Private API | ||||
#-------------------------------------------------------------------------- | ||||
def _on_key_down(self, event, skip=True): | ||||
""" Capture the character events, let the parent | ||||
widget handle them, and put our logic afterward. | ||||
""" | ||||
current_line_number = self.GetCurrentLine() | ||||
|
r1381 | if event.KeyCode == ord('('): | ||
event.Skip() | ||||
self.do_calltip() | ||||
elif self.AutoCompActive(): | ||||
|
r1371 | event.Skip() | ||
|
r1373 | if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE): | ||
|
r1381 | wx.CallAfter(self.popup_completion, create=True) | ||
|
r1373 | elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, | ||
wx.WXK_RIGHT): | ||||
|
r1380 | wx.CallAfter(self.popup_completion) | ||
|
r1349 | else: | ||
|
r1371 | # Up history | ||
if event.KeyCode == wx.WXK_UP and ( | ||||
( current_line_number == self.current_prompt_line and | ||||
event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) ) | ||||
or event.ControlDown() ): | ||||
new_buffer = self.get_history_previous( | ||||
self.get_current_edit_buffer()) | ||||
if new_buffer is not None: | ||||
self.replace_current_edit_buffer(new_buffer) | ||||
if self.GetCurrentLine() > self.current_prompt_line: | ||||
# Go to first line, for seemless history up. | ||||
self.GotoPos(self.current_prompt_pos) | ||||
# Down history | ||||
elif event.KeyCode == wx.WXK_DOWN and ( | ||||
( current_line_number == self.LineCount -1 and | ||||
event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) ) | ||||
or event.ControlDown() ): | ||||
new_buffer = self.get_history_next() | ||||
if new_buffer is not None: | ||||
self.replace_current_edit_buffer(new_buffer) | ||||
elif event.KeyCode == ord('\t'): | ||||
last_line = self.get_current_edit_buffer().split('\n')[-1] | ||||
if not re.match(r'^\s*$', last_line): | ||||
|
r1380 | self.do_completion() | ||
|
r1371 | else: | ||
event.Skip() | ||||
else: | ||||
ConsoleWidget._on_key_down(self, event, skip=skip) | ||||
|
r1349 | |||
|
r1373 | def _on_key_up(self, event, skip=True): | ||
if event.KeyCode == 59: | ||||
# Intercepting '.' | ||||
event.Skip() | ||||
|
r1380 | self.popup_completion(create=True) | ||
|
r1373 | else: | ||
ConsoleWidget._on_key_up(self, event, skip=skip) | ||||
|
r1349 | |||
if __name__ == '__main__': | ||||
class MainWindow(wx.Frame): | ||||
def __init__(self, parent, id, title): | ||||
wx.Frame.__init__(self, parent, id, title, size=(300,250)) | ||||
self._sizer = wx.BoxSizer(wx.VERTICAL) | ||||
self.shell = IPythonWxController(self) | ||||
self._sizer.Add(self.shell, 1, wx.EXPAND) | ||||
self.SetSizer(self._sizer) | ||||
self.SetAutoLayout(1) | ||||
self.Show(True) | ||||
app = wx.PySimpleApp() | ||||
frame = MainWindow(None, wx.ID_ANY, 'Ipython') | ||||
frame.shell.SetFocus() | ||||
|
r1380 | frame.SetSize((680, 460)) | ||
|
r1349 | self = frame.shell | ||
app.MainLoop() | ||||