wx_frontend.py
202 lines
| 7.5 KiB
| text/x-python
|
PythonLexer
Gael Varoquaux
|
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 | ||||
Gael Varoquaux
|
r1360 | IPython.kernel.core.interpreter. | ||
Gael Varoquaux
|
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 | ||||
Gael Varoquaux
|
r1371 | import re | ||
Gael Varoquaux
|
r1374 | from wx import stc | ||
Gael Varoquaux
|
r1349 | from console_widget import ConsoleWidget | ||
Gael Varoquaux
|
r1375 | import __builtin__ | ||
Gael Varoquaux
|
r1349 | |||
Gael Varoquaux
|
r1360 | from IPython.frontend.prefilterfrontend import PrefilterFrontEnd | ||
Gael Varoquaux
|
r1349 | |||
Gael Varoquaux
|
r1374 | #_COMMAND_BG = '#FAFAF1' # Nice green | ||
Gael Varoquaux
|
r1375 | _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow | ||
Gael Varoquaux
|
r1374 | |||
_RUNNING_BUFFER_MARKER = 31 | ||||
Gael Varoquaux
|
r1349 | #------------------------------------------------------------------------------- | ||
# Classes to implement the Wx frontend | ||||
#------------------------------------------------------------------------------- | ||||
Gael Varoquaux
|
r1360 | class IPythonWxController(PrefilterFrontEnd, ConsoleWidget): | ||
Gael Varoquaux
|
r1349 | |||
output_prompt = \ | ||||
Gael Varoquaux
|
r1362 | '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02' | ||
Gael Varoquaux
|
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) | ||||
Gael Varoquaux
|
r1360 | PrefilterFrontEnd.__init__(self) | ||
Gael Varoquaux
|
r1349 | |||
# Capture Character keys | ||||
self.Bind(wx.EVT_KEY_DOWN, self._on_key_down) | ||||
Gael Varoquaux
|
r1371 | |||
Gael Varoquaux
|
r1374 | # Marker for running buffer. | ||
self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND, | ||||
background=_RUNNING_BUFFER_BG) | ||||
Gael Varoquaux
|
r1371 | |||
Gael Varoquaux
|
r1373 | def do_completion(self, mode=None): | ||
""" Do code completion. | ||||
mode can be 'text', 'popup' or 'none' to use default. | ||||
""" | ||||
Gael Varoquaux
|
r1371 | line = self.get_current_edit_buffer() | ||
Gael Varoquaux
|
r1379 | new_line, completions = self.complete(line) | ||
if len(completions)>1: | ||||
Gael Varoquaux
|
r1374 | self.write_completion(completions, mode=mode) | ||
Gael Varoquaux
|
r1379 | self.replace_current_edit_buffer(new_line) | ||
Gael Varoquaux
|
r1373 | |||
Gael Varoquaux
|
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 | ||||
for name in base_symbol_string.split('.')[1:] + ['__doc__']: | ||||
symbol = getattr(symbol, name) | ||||
self.CallTipShow(self.GetCurrentPos(), symbol) | ||||
Gael Varoquaux
|
r1379 | |||
Gael Varoquaux
|
r1373 | def update_completion(self): | ||
line = self.get_current_edit_buffer() | ||||
if self.AutoCompActive() and not line[-1] == '.': | ||||
line = line[:-1] | ||||
completions = self.complete(line) | ||||
choose_single = self.AutoCompGetChooseSingle() | ||||
self.AutoCompSetChooseSingle(False) | ||||
self.write_completion(completions, mode='popup') | ||||
self.AutoCompSetChooseSingle(choose_single) | ||||
Gael Varoquaux
|
r1371 | |||
Gael Varoquaux
|
r1374 | def execute(self, python_string, raw_string=None): | ||
Gael Varoquaux
|
r1371 | self._cursor = wx.BusyCursor() | ||
Gael Varoquaux
|
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) | ||||
PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string) | ||||
Gael Varoquaux
|
r1371 | |||
def after_execute(self): | ||||
PrefilterFrontEnd.after_execute(self) | ||||
Gael Varoquaux
|
r1373 | if hasattr(self, '_cursor'): | ||
del self._cursor | ||||
Gael Varoquaux
|
r1371 | |||
Gael Varoquaux
|
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() | ||||
Gael Varoquaux
|
r1371 | if self.AutoCompActive(): | ||
event.Skip() | ||||
Gael Varoquaux
|
r1373 | if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE): | ||
wx.CallAfter(self.do_completion) | ||||
elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, | ||||
wx.WXK_RIGHT): | ||||
wx.CallAfter(self.update_completion) | ||||
Gael Varoquaux
|
r1349 | else: | ||
Gael Varoquaux
|
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): | ||||
Gael Varoquaux
|
r1373 | self.do_completion(mode='text') | ||
Gael Varoquaux
|
r1371 | else: | ||
event.Skip() | ||||
Gael Varoquaux
|
r1375 | elif event.KeyCode == ord('('): | ||
event.Skip() | ||||
self.do_calltip() | ||||
Gael Varoquaux
|
r1371 | else: | ||
ConsoleWidget._on_key_down(self, event, skip=skip) | ||||
Gael Varoquaux
|
r1349 | |||
Gael Varoquaux
|
r1373 | def _on_key_up(self, event, skip=True): | ||
if event.KeyCode == 59: | ||||
# Intercepting '.' | ||||
event.Skip() | ||||
Gael Varoquaux
|
r1374 | #self.do_completion(mode='popup') | ||
Gael Varoquaux
|
r1373 | else: | ||
ConsoleWidget._on_key_up(self, event, skip=skip) | ||||
Gael Varoquaux
|
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() | ||||
frame.SetSize((660, 460)) | ||||
self = frame.shell | ||||
app.MainLoop() | ||||