|
|
# 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
|
|
|
IPython.kernel.core.interpreter.
|
|
|
|
|
|
"""
|
|
|
|
|
|
__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
|
|
|
import re
|
|
|
from wx import stc
|
|
|
from console_widget import ConsoleWidget
|
|
|
import __builtin__
|
|
|
|
|
|
from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
|
|
|
|
|
|
#_COMMAND_BG = '#FAFAF1' # Nice green
|
|
|
_RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
|
|
|
|
|
|
_RUNNING_BUFFER_MARKER = 31
|
|
|
|
|
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
# Classes to implement the Wx frontend
|
|
|
#-------------------------------------------------------------------------------
|
|
|
class IPythonWxController(PrefilterFrontEnd, ConsoleWidget):
|
|
|
|
|
|
output_prompt = \
|
|
|
'\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
|
|
|
|
|
|
#--------------------------------------------------------------------------
|
|
|
# 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)
|
|
|
PrefilterFrontEnd.__init__(self)
|
|
|
|
|
|
# Capture Character keys
|
|
|
self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
|
|
|
|
|
|
# Marker for running buffer.
|
|
|
self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
|
|
|
background=_RUNNING_BUFFER_BG)
|
|
|
|
|
|
|
|
|
|
|
|
def do_completion(self):
|
|
|
""" Do code completion.
|
|
|
"""
|
|
|
line = self.get_current_edit_buffer()
|
|
|
new_line, completions = self.complete(line)
|
|
|
if len(completions)>1:
|
|
|
self.write_completion(completions)
|
|
|
self.replace_current_edit_buffer(new_line)
|
|
|
|
|
|
|
|
|
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 symbol_string.split('.')[1:] + ['__doc__']:
|
|
|
symbol = getattr(symbol, name)
|
|
|
try:
|
|
|
self.AutoCompCancel()
|
|
|
wx.Yield()
|
|
|
self.CallTipShow(self.GetCurrentPos(), symbol)
|
|
|
except TypeError:
|
|
|
# The retrieve symbol couldn't be converted to a string
|
|
|
pass
|
|
|
|
|
|
|
|
|
def popup_completion(self, create=False):
|
|
|
""" Updates the popup completion menu if it exists. If create is
|
|
|
true, open the menu.
|
|
|
"""
|
|
|
line = self.get_current_edit_buffer()
|
|
|
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)
|
|
|
self.pop_completion(completions, offset=offset)
|
|
|
|
|
|
|
|
|
def execute(self, python_string, raw_string=None):
|
|
|
self.CallTipCancel()
|
|
|
self._cursor = wx.BusyCursor()
|
|
|
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)
|
|
|
# Update the display:
|
|
|
wx.Yield()
|
|
|
# Remove the trailing "\n" for cleaner display
|
|
|
self.SetSelection(self.GetLength()-1, self.GetLength())
|
|
|
self.ReplaceSelection('')
|
|
|
self.GotoPos(self.GetLength())
|
|
|
PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
|
|
|
|
|
|
|
|
|
def after_execute(self):
|
|
|
PrefilterFrontEnd.after_execute(self)
|
|
|
if hasattr(self, '_cursor'):
|
|
|
del self._cursor
|
|
|
|
|
|
#--------------------------------------------------------------------------
|
|
|
# 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()
|
|
|
if event.KeyCode == ord('('):
|
|
|
event.Skip()
|
|
|
self.do_calltip()
|
|
|
elif self.AutoCompActive():
|
|
|
event.Skip()
|
|
|
if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
|
|
|
wx.CallAfter(self.popup_completion, create=True)
|
|
|
elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
|
|
|
wx.WXK_RIGHT):
|
|
|
wx.CallAfter(self.popup_completion)
|
|
|
else:
|
|
|
# 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):
|
|
|
self.do_completion()
|
|
|
else:
|
|
|
event.Skip()
|
|
|
else:
|
|
|
ConsoleWidget._on_key_down(self, event, skip=skip)
|
|
|
|
|
|
|
|
|
def _on_key_up(self, event, skip=True):
|
|
|
if event.KeyCode == 59:
|
|
|
# Intercepting '.'
|
|
|
event.Skip()
|
|
|
self.popup_completion(create=True)
|
|
|
else:
|
|
|
ConsoleWidget._on_key_up(self, event, skip=skip)
|
|
|
|
|
|
|
|
|
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((680, 460))
|
|
|
self = frame.shell
|
|
|
|
|
|
app.MainLoop()
|
|
|
|
|
|
|