wx_frontend.py
601 lines
| 23.3 KiB
| text/x-python
|
PythonLexer
Gael Varoquaux
|
r1349 | # encoding: utf-8 -*- test-case-name: | ||
# FIXME: Need to add tests. | ||||
gvaroquaux
|
r1460 | # ipython1.frontend.wx.tests.test_wx_frontend -*- | ||
Gael Varoquaux
|
r1349 | |||
"""Classes to provide a Wx frontend to the | ||||
Gael Varoquaux
|
r1360 | IPython.kernel.core.interpreter. | ||
Gael Varoquaux
|
r1349 | |||
gvaroquaux
|
r1455 | This class inherits from ConsoleWidget, that provides a console-like | ||
widget to provide a text-rendering widget suitable for a terminal. | ||||
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 | ||||
#------------------------------------------------------------------------------- | ||||
gvaroquaux
|
r1455 | # Major library imports | ||
Gael Varoquaux
|
r1371 | import re | ||
Gael Varoquaux
|
r1375 | import __builtin__ | ||
Gael Varoquaux
|
r1437 | import sys | ||
from threading import Lock | ||||
gvaroquaux
|
r1455 | import wx | ||
from wx import stc | ||||
# Ipython-specific imports. | ||||
Gael Varoquaux
|
r1947 | from IPython.frontend.process import PipedProcess | ||
Gael Varoquaux
|
r1893 | from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \ | ||
_ERROR_MARKER, _INPUT_MARKER | ||||
Gael Varoquaux
|
r1360 | from IPython.frontend.prefilterfrontend import PrefilterFrontEnd | ||
Gael Varoquaux
|
r1349 | |||
gvaroquaux
|
r1455 | #------------------------------------------------------------------------------- | ||
Gael Varoquaux
|
r1349 | # Classes to implement the Wx frontend | ||
#------------------------------------------------------------------------------- | ||||
gvaroquaux
|
r1462 | class WxController(ConsoleWidget, PrefilterFrontEnd): | ||
gvaroquaux
|
r1455 | """Classes to provide a Wx frontend to the | ||
IPython.kernel.core.interpreter. | ||||
This class inherits from ConsoleWidget, that provides a console-like | ||||
widget to provide a text-rendering widget suitable for a terminal. | ||||
""" | ||||
Laurent Dufrechou
|
r1835 | |||
gvaroquaux
|
r1447 | # Print debug info on what is happening to the console. | ||
gvaroquaux
|
r1475 | debug = False | ||
Gael Varoquaux
|
r1437 | |||
gvaroquaux
|
r1447 | # The title of the terminal, as captured through the ANSI escape | ||
# sequences. | ||||
def _set_title(self, title): | ||||
return self.Parent.SetTitle(title) | ||||
def _get_title(self): | ||||
return self.Parent.GetTitle() | ||||
title = property(_get_title, _set_title) | ||||
gvaroquaux
|
r1462 | |||
# The buffer being edited. | ||||
Gael Varoquaux
|
r1473 | # We are duplicating the definition here because of multiple | ||
gvaroquaux
|
r1462 | # inheritence | ||
def _set_input_buffer(self, string): | ||||
Gael Varoquaux
|
r1473 | ConsoleWidget._set_input_buffer(self, string) | ||
self._colorize_input_buffer() | ||||
gvaroquaux
|
r1462 | |||
def _get_input_buffer(self): | ||||
""" Returns the text in current edit buffer. | ||||
""" | ||||
return ConsoleWidget._get_input_buffer(self) | ||||
input_buffer = property(_get_input_buffer, _set_input_buffer) | ||||
gvaroquaux
|
r1447 | #-------------------------------------------------------------------------- | ||
# Private Attributes | ||||
#-------------------------------------------------------------------------- | ||||
# A flag governing the behavior of the input. Can be: | ||||
# | ||||
# 'readline' for readline-like behavior with a prompt | ||||
# and an edit buffer. | ||||
gvaroquaux
|
r1480 | # 'raw_input' similar to readline, but triggered by a raw-input | ||
# call. Can be used by subclasses to act differently. | ||||
gvaroquaux
|
r1447 | # 'subprocess' for sending the raw input directly to a | ||
# subprocess. | ||||
# 'buffering' for buffering of the input, that will be used | ||||
# when the input state switches back to another state. | ||||
_input_state = 'readline' | ||||
Gael Varoquaux
|
r1437 | # Attribute to store reference to the pipes of a subprocess, if we | ||
# are running any. | ||||
gvaroquaux
|
r1447 | _running_process = False | ||
Gael Varoquaux
|
r1437 | |||
# A queue for writing fast streams to the screen without flooding the | ||||
# event loop | ||||
gvaroquaux
|
r1447 | _out_buffer = [] | ||
Gael Varoquaux
|
r1437 | |||
gvaroquaux
|
r1447 | # A lock to lock the _out_buffer to make sure we don't empty it | ||
Gael Varoquaux
|
r1437 | # while it is being swapped | ||
gvaroquaux
|
r1447 | _out_buffer_lock = Lock() | ||
Gael Varoquaux
|
r1437 | |||
Gael Varoquaux
|
r1624 | # The different line markers used to higlight the prompts. | ||
Gael Varoquaux
|
r1472 | _markers = dict() | ||
Gael Varoquaux
|
r1349 | #-------------------------------------------------------------------------- | ||
# Public API | ||||
#-------------------------------------------------------------------------- | ||||
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, | ||||
gvaroquaux
|
r1630 | size=wx.DefaultSize, | ||
style=wx.CLIP_CHILDREN|wx.WANTS_CHARS, | ||||
Gael Varoquaux
|
r1893 | styledef=None, | ||
Gael Varoquaux
|
r1349 | *args, **kwds): | ||
""" Create Shell instance. | ||||
Gael Varoquaux
|
r1893 | |||
Parameters | ||||
----------- | ||||
styledef : dict, optional | ||||
styledef is the dictionary of options used to define the | ||||
style. | ||||
Gael Varoquaux
|
r1349 | """ | ||
Gael Varoquaux
|
r1893 | if styledef is not None: | ||
self.style = styledef | ||||
Gael Varoquaux
|
r1349 | ConsoleWidget.__init__(self, parent, id, pos, size, style) | ||
gvaroquaux
|
r1484 | PrefilterFrontEnd.__init__(self, **kwds) | ||
gvaroquaux
|
r1654 | |||
# Stick in our own raw_input: | ||||
self.ipython0.raw_input = self.raw_input | ||||
Gael Varoquaux
|
r1349 | |||
Gael Varoquaux
|
r1437 | # A time for flushing the write buffer | ||
BUFFER_FLUSH_TIMER_ID = 100 | ||||
self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID) | ||||
wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush) | ||||
Gael Varoquaux
|
r1374 | |||
gvaroquaux
|
r1484 | if 'debug' in kwds: | ||
self.debug = kwds['debug'] | ||||
kwds.pop('debug') | ||||
Gael Varoquaux
|
r1472 | # Inject self in namespace, for debug | ||
if self.debug: | ||||
self.shell.user_ns['self'] = self | ||||
gvaroquaux
|
r1654 | # Inject our own raw_input in namespace | ||
self.shell.user_ns['raw_input'] = self.raw_input | ||||
Laurent Dufrechou
|
r1834 | |||
gvaroquaux
|
r1660 | def raw_input(self, prompt=''): | ||
gvaroquaux
|
r1455 | """ A replacement from python's raw_input. | ||
""" | ||||
self.new_prompt(prompt) | ||||
gvaroquaux
|
r1480 | self._input_state = 'raw_input' | ||
Gael Varoquaux
|
r1489 | if hasattr(self, '_cursor'): | ||
del self._cursor | ||||
gvaroquaux
|
r1484 | self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS)) | ||
gvaroquaux
|
r1455 | self.__old_on_enter = self._on_enter | ||
gvaroquaux
|
r1655 | event_loop = wx.EventLoop() | ||
gvaroquaux
|
r1455 | def my_on_enter(): | ||
gvaroquaux
|
r1655 | event_loop.Exit() | ||
gvaroquaux
|
r1455 | self._on_enter = my_on_enter | ||
gvaroquaux
|
r1655 | # XXX: Running a separate event_loop. Ugly. | ||
event_loop.Run() | ||||
gvaroquaux
|
r1455 | self._on_enter = self.__old_on_enter | ||
self._input_state = 'buffering' | ||||
gvaroquaux
|
r1484 | self._cursor = wx.BusyCursor() | ||
gvaroquaux
|
r1462 | return self.input_buffer.rstrip('\n') | ||
gvaroquaux
|
r1455 | |||
def system_call(self, command_string): | ||||
self._input_state = 'subprocess' | ||||
gvaroquaux
|
r1656 | event_loop = wx.EventLoop() | ||
def _end_system_call(): | ||||
self._input_state = 'buffering' | ||||
self._running_process = False | ||||
event_loop.Exit() | ||||
gvaroquaux
|
r1455 | self._running_process = PipedProcess(command_string, | ||
out_callback=self.buffered_write, | ||||
gvaroquaux
|
r1656 | end_callback = _end_system_call) | ||
gvaroquaux
|
r1455 | self._running_process.start() | ||
gvaroquaux
|
r1656 | # XXX: Running a separate event_loop. Ugly. | ||
event_loop.Run() | ||||
gvaroquaux
|
r1455 | # Be sure to flush the buffer. | ||
self._buffer_flush(event=None) | ||||
Gael Varoquaux
|
r1375 | def do_calltip(self): | ||
gvaroquaux
|
r1455 | """ Analyse current and displays useful calltip for it. | ||
""" | ||||
Gael Varoquaux
|
r1437 | if self.debug: | ||
print >>sys.__stdout__, "do_calltip" | ||||
Gael Varoquaux
|
r1382 | separators = re.compile('[\s\{\}\[\]\(\)\= ,:]') | ||
gvaroquaux
|
r1462 | symbol = self.input_buffer | ||
Gael Varoquaux
|
r1382 | symbol_string = separators.split(symbol)[-1] | ||
Gael Varoquaux
|
r1375 | 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 | ||||
Gael Varoquaux
|
r1380 | try: | ||
gvaroquaux
|
r1484 | for name in symbol_string.split('.')[1:] + ['__doc__']: | ||
symbol = getattr(symbol, name) | ||||
Gael Varoquaux
|
r1381 | self.AutoCompCancel() | ||
gvaroquaux
|
r1659 | # Check that the symbol can indeed be converted to a string: | ||
symbol += '' | ||||
gvaroquaux
|
r1657 | wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol) | ||
Gael Varoquaux
|
r1382 | except: | ||
Gael Varoquaux
|
r1380 | # The retrieve symbol couldn't be converted to a string | ||
pass | ||||
Gael Varoquaux
|
r1375 | |||
Gael Varoquaux
|
r1379 | |||
gvaroquaux
|
r1455 | def _popup_completion(self, create=False): | ||
Gael Varoquaux
|
r1380 | """ Updates the popup completion menu if it exists. If create is | ||
true, open the menu. | ||||
""" | ||||
Gael Varoquaux
|
r1437 | if self.debug: | ||
Gael Varoquaux
|
r1624 | print >>sys.__stdout__, "_popup_completion" | ||
gvaroquaux
|
r1462 | line = self.input_buffer | ||
Gael Varoquaux
|
r1624 | if (self.AutoCompActive() and line and not line[-1] == '.') \ | ||
Gael Varoquaux
|
r1380 | or create==True: | ||
suggestion, completions = self.complete(line) | ||||
if completions: | ||||
Gael Varoquaux
|
r1887 | offset = len(self._get_completion_text(line)) | ||
Gael Varoquaux
|
r1381 | self.pop_completion(completions, offset=offset) | ||
Gael Varoquaux
|
r1437 | if self.debug: | ||
print >>sys.__stdout__, completions | ||||
Gael Varoquaux
|
r1371 | |||
gvaroquaux
|
r1455 | def buffered_write(self, text): | ||
""" A write method for streams, that caches the stream in order | ||||
to avoid flooding the event loop. | ||||
gvaroquaux
|
r1447 | |||
gvaroquaux
|
r1455 | This can be called outside of the main loop, in separate | ||
threads. | ||||
Gael Varoquaux
|
r1388 | """ | ||
gvaroquaux
|
r1455 | self._out_buffer_lock.acquire() | ||
self._out_buffer.append(text) | ||||
self._out_buffer_lock.release() | ||||
if not self._buffer_flush_timer.IsRunning(): | ||||
gvaroquaux
|
r1503 | wx.CallAfter(self._buffer_flush_timer.Start, | ||
milliseconds=100, oneShot=True) | ||||
gvaroquaux
|
r1455 | |||
Gael Varoquaux
|
r1893 | def clear_screen(self): | ||
""" Empty completely the widget. | ||||
""" | ||||
self.ClearAll() | ||||
self.new_prompt(self.input_prompt_template.substitute( | ||||
number=(self.last_result['number'] + 1))) | ||||
Gael Varoquaux
|
r1732 | #-------------------------------------------------------------------------- | ||
# LineFrontEnd interface | ||||
#-------------------------------------------------------------------------- | ||||
def execute(self, python_string, raw_string=None): | ||||
self._input_state = 'buffering' | ||||
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): | ||||
if i in self._markers: | ||||
self.MarkerDeleteHandle(self._markers[i]) | ||||
self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER) | ||||
# Use a callafter to update the display robustly under windows | ||||
def callback(): | ||||
self.GotoPos(self.GetLength()) | ||||
PrefilterFrontEnd.execute(self, python_string, | ||||
raw_string=raw_string) | ||||
wx.CallAfter(callback) | ||||
Gael Varoquaux
|
r1712 | def execute_command(self, command, hidden=False): | ||
""" Execute a command, not only in the model, but also in the | ||||
view. | ||||
""" | ||||
Gael Varoquaux
|
r1896 | # XXX: This method needs to be integrated in the base fronted | ||
# interface | ||||
Gael Varoquaux
|
r1712 | if hidden: | ||
return self.shell.execute(command) | ||||
else: | ||||
# XXX: we are not storing the input buffer previous to the | ||||
# execution, as this forces us to run the execution | ||||
# input_buffer a yield, which is not good. | ||||
##current_buffer = self.shell.control.input_buffer | ||||
command = command.rstrip() | ||||
if len(command.split('\n')) > 1: | ||||
# The input command is several lines long, we need to | ||||
# force the execution to happen | ||||
command += '\n' | ||||
cleaned_command = self.prefilter_input(command) | ||||
self.input_buffer = command | ||||
# Do not use wx.Yield() (aka GUI.process_events()) to avoid | ||||
# recursive yields. | ||||
self.ProcessEvent(wx.PaintEvent()) | ||||
self.write('\n') | ||||
if not self.is_complete(cleaned_command + '\n'): | ||||
self._colorize_input_buffer() | ||||
self.render_error('Incomplete or invalid input') | ||||
self.new_prompt(self.input_prompt_template.substitute( | ||||
number=(self.last_result['number'] + 1))) | ||||
return False | ||||
self._on_enter() | ||||
return True | ||||
Gael Varoquaux
|
r1473 | def save_output_hooks(self): | ||
self.__old_raw_input = __builtin__.raw_input | ||||
PrefilterFrontEnd.save_output_hooks(self) | ||||
Gael Varoquaux
|
r1391 | |||
def capture_output(self): | ||||
gvaroquaux
|
r1503 | self.SetLexer(stc.STC_LEX_NULL) | ||
Gael Varoquaux
|
r1391 | PrefilterFrontEnd.capture_output(self) | ||
gvaroquaux
|
r1654 | __builtin__.raw_input = self.raw_input | ||
Gael Varoquaux
|
r1391 | |||
def release_output(self): | ||||
Gael Varoquaux
|
r1388 | __builtin__.raw_input = self.__old_raw_input | ||
Gael Varoquaux
|
r1472 | PrefilterFrontEnd.release_output(self) | ||
gvaroquaux
|
r1503 | self.SetLexer(stc.STC_LEX_PYTHON) | ||
Gael Varoquaux
|
r1374 | |||
Gael Varoquaux
|
r1371 | |||
def after_execute(self): | ||||
PrefilterFrontEnd.after_execute(self) | ||||
gvaroquaux
|
r1455 | # Clear the wait cursor | ||
Gael Varoquaux
|
r1373 | if hasattr(self, '_cursor'): | ||
del self._cursor | ||||
gvaroquaux
|
r1484 | self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR)) | ||
Gael Varoquaux
|
r1371 | |||
Gael Varoquaux
|
r1437 | |||
Gael Varoquaux
|
r1384 | def show_traceback(self): | ||
start_line = self.GetCurrentLine() | ||||
PrefilterFrontEnd.show_traceback(self) | ||||
gvaroquaux
|
r1651 | self.ProcessEvent(wx.PaintEvent()) | ||
#wx.Yield() | ||||
Gael Varoquaux
|
r1384 | for i in range(start_line, self.GetCurrentLine()): | ||
Gael Varoquaux
|
r1472 | self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER) | ||
Gael Varoquaux
|
r1384 | |||
gvaroquaux
|
r1455 | |||
Gael Varoquaux
|
r1349 | #-------------------------------------------------------------------------- | ||
gvaroquaux
|
r1639 | # FrontEndBase interface | ||
#-------------------------------------------------------------------------- | ||||
def render_error(self, e): | ||||
start_line = self.GetCurrentLine() | ||||
self.write('\n' + e + '\n') | ||||
for i in range(start_line, self.GetCurrentLine()): | ||||
self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER) | ||||
#-------------------------------------------------------------------------- | ||||
gvaroquaux
|
r1455 | # ConsoleWidget interface | ||
Gael Varoquaux
|
r1349 | #-------------------------------------------------------------------------- | ||
gvaroquaux
|
r1455 | |||
def new_prompt(self, prompt): | ||||
""" Display a new prompt, and start a new input buffer. | ||||
""" | ||||
self._input_state = 'readline' | ||||
ConsoleWidget.new_prompt(self, prompt) | ||||
Gael Varoquaux
|
r1473 | i = self.current_prompt_line | ||
self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER) | ||||
gvaroquaux
|
r1455 | |||
Gael Varoquaux
|
r1893 | def continuation_prompt(self, *args, **kwargs): | ||
# Avoid multiple inheritence, be explicit about which | ||||
# parent method class gets called | ||||
return ConsoleWidget.continuation_prompt(self, *args, **kwargs) | ||||
Gael Varoquaux
|
r1458 | def write(self, *args, **kwargs): | ||
# Avoid multiple inheritence, be explicit about which | ||||
# parent method class gets called | ||||
Gael Varoquaux
|
r1893 | return ConsoleWidget.write(self, *args, **kwargs) | ||
Gael Varoquaux
|
r1458 | |||
Gael Varoquaux
|
r1349 | def _on_key_down(self, event, skip=True): | ||
""" Capture the character events, let the parent | ||||
widget handle them, and put our logic afterward. | ||||
""" | ||||
Gael Varoquaux
|
r1451 | # FIXME: This method needs to be broken down in smaller ones. | ||
Gael Varoquaux
|
r1896 | current_line_num = self.GetCurrentLine() | ||
gvaroquaux
|
r1447 | if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown(): | ||
# Capture Control-C | ||||
if self._input_state == 'subprocess': | ||||
if self.debug: | ||||
print >>sys.__stderr__, 'Killing running process' | ||||
gvaroquaux
|
r1648 | if hasattr(self._running_process, 'process'): | ||
self._running_process.process.kill() | ||||
gvaroquaux
|
r1447 | elif self._input_state == 'buffering': | ||
if self.debug: | ||||
gvaroquaux
|
r1455 | print >>sys.__stderr__, 'Raising KeyboardInterrupt' | ||
raise KeyboardInterrupt | ||||
gvaroquaux
|
r1447 | # XXX: We need to make really sure we | ||
# get back to a prompt. | ||||
Gael Varoquaux
|
r1450 | elif self._input_state == 'subprocess' and ( | ||
( event.KeyCode<256 and | ||||
gvaroquaux
|
r1460 | not event.ControlDown() ) | ||
Gael Varoquaux
|
r1450 | or | ||
( event.KeyCode in (ord('d'), ord('D')) and | ||||
event.ControlDown())): | ||||
gvaroquaux
|
r1447 | # We are running a process, we redirect keys. | ||
Gael Varoquaux
|
r1437 | ConsoleWidget._on_key_down(self, event, skip=skip) | ||
Gael Varoquaux
|
r1450 | char = chr(event.KeyCode) | ||
# Deal with some inconsistency in wx keycodes: | ||||
if char == '\r': | ||||
char = '\n' | ||||
elif not event.ShiftDown(): | ||||
char = char.lower() | ||||
if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')): | ||||
char = '\04' | ||||
self._running_process.process.stdin.write(char) | ||||
gvaroquaux
|
r1449 | self._running_process.process.stdin.flush() | ||
gvaroquaux
|
r1658 | elif event.KeyCode in (ord('('), 57, 53): | ||
Gael Varoquaux
|
r1437 | # Calltips | ||
Gael Varoquaux
|
r1381 | event.Skip() | ||
self.do_calltip() | ||||
Gael Varoquaux
|
r1493 | elif self.AutoCompActive() and not event.KeyCode == ord('\t'): | ||
Gael Varoquaux
|
r1371 | event.Skip() | ||
Gael Varoquaux
|
r1373 | if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE): | ||
gvaroquaux
|
r1455 | wx.CallAfter(self._popup_completion, create=True) | ||
Gael Varoquaux
|
r1373 | elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, | ||
Gael Varoquaux
|
r1493 | wx.WXK_RIGHT, wx.WXK_ESCAPE): | ||
gvaroquaux
|
r1455 | wx.CallAfter(self._popup_completion) | ||
Gael Varoquaux
|
r1349 | else: | ||
Gael Varoquaux
|
r1371 | # Up history | ||
if event.KeyCode == wx.WXK_UP and ( | ||||
Gael Varoquaux
|
r1896 | ( current_line_num == self.current_prompt_line and | ||
Gael Varoquaux
|
r1371 | event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) ) | ||
or event.ControlDown() ): | ||||
new_buffer = self.get_history_previous( | ||||
gvaroquaux
|
r1462 | self.input_buffer) | ||
Gael Varoquaux
|
r1371 | if new_buffer is not None: | ||
gvaroquaux
|
r1462 | self.input_buffer = new_buffer | ||
Gael Varoquaux
|
r1371 | 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 ( | ||||
Gael Varoquaux
|
r1896 | ( current_line_num == self.LineCount -1 and | ||
Gael Varoquaux
|
r1371 | event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) ) | ||
or event.ControlDown() ): | ||||
new_buffer = self.get_history_next() | ||||
if new_buffer is not None: | ||||
gvaroquaux
|
r1462 | self.input_buffer = new_buffer | ||
Gael Varoquaux
|
r1390 | # Tab-completion | ||
Gael Varoquaux
|
r1371 | elif event.KeyCode == ord('\t'): | ||
Gael Varoquaux
|
r1896 | current_line, current_line_num = self.CurLine | ||
gvaroquaux
|
r1652 | if not re.match(r'^\s*$', current_line): | ||
gvaroquaux
|
r1463 | self.complete_current_input() | ||
Gael Varoquaux
|
r1493 | if self.AutoCompActive(): | ||
wx.CallAfter(self._popup_completion, create=True) | ||||
Gael Varoquaux
|
r1371 | else: | ||
event.Skip() | ||||
Gael Varoquaux
|
r1894 | elif event.KeyCode == wx.WXK_BACK: | ||
# If characters where erased, check if we have to | ||||
# remove a line. | ||||
# XXX: What about DEL? | ||||
Gael Varoquaux
|
r1895 | # FIXME: This logics should be in ConsoleWidget, as it is | ||
# independant of IPython | ||||
Gael Varoquaux
|
r1894 | current_line, _ = self.CurLine | ||
current_pos = self.GetCurrentPos() | ||||
Gael Varoquaux
|
r1896 | current_line_num = self.LineFromPosition(current_pos) | ||
Gael Varoquaux
|
r1894 | current_col = self.GetColumn(current_pos) | ||
len_prompt = len(self.continuation_prompt()) | ||||
if ( current_line.startswith(self.continuation_prompt()) | ||||
Gael Varoquaux
|
r1896 | and current_col == len_prompt): | ||
Gael Varoquaux
|
r1894 | new_lines = [] | ||
for line_num, line in enumerate( | ||||
self.input_buffer.split('\n')): | ||||
if (line_num + self.current_prompt_line == | ||||
Gael Varoquaux
|
r1896 | current_line_num): | ||
Gael Varoquaux
|
r1894 | new_lines.append(line[len_prompt:]) | ||
else: | ||||
new_lines.append('\n'+line) | ||||
# The first character is '\n', due to the above | ||||
# code: | ||||
self.input_buffer = ''.join(new_lines)[1:] | ||||
self.GotoPos(current_pos - 1 - len_prompt) | ||||
else: | ||||
ConsoleWidget._on_key_down(self, event, skip=skip) | ||||
Gael Varoquaux
|
r1371 | else: | ||
ConsoleWidget._on_key_down(self, event, skip=skip) | ||||
Gael Varoquaux
|
r1894 | |||
Gael Varoquaux
|
r1349 | |||
Gael Varoquaux
|
r1373 | def _on_key_up(self, event, skip=True): | ||
gvaroquaux
|
r1455 | """ Called when any key is released. | ||
""" | ||||
Gael Varoquaux
|
r1437 | if event.KeyCode in (59, ord('.')): | ||
Gael Varoquaux
|
r1373 | # Intercepting '.' | ||
event.Skip() | ||||
Gael Varoquaux
|
r1624 | wx.CallAfter(self._popup_completion, create=True) | ||
Gael Varoquaux
|
r1373 | else: | ||
ConsoleWidget._on_key_up(self, event, skip=skip) | ||||
Gael Varoquaux
|
r1894 | # Make sure the continuation_prompts are always followed by a | ||
# whitespace | ||||
new_lines = [] | ||||
if self._input_state == 'readline': | ||||
Gael Varoquaux
|
r1884 | position = self.GetCurrentPos() | ||
Gael Varoquaux
|
r1947 | continuation_prompt = self.continuation_prompt()[:-1] | ||
Gael Varoquaux
|
r1894 | for line in self.input_buffer.split('\n'): | ||
Gael Varoquaux
|
r1947 | if not line == continuation_prompt: | ||
Gael Varoquaux
|
r1894 | new_lines.append(line) | ||
self.input_buffer = '\n'.join(new_lines) | ||||
Gael Varoquaux
|
r1884 | self.GotoPos(position) | ||
Gael Varoquaux
|
r1373 | |||
gvaroquaux
|
r1447 | |||
gvaroquaux
|
r1436 | def _on_enter(self): | ||
gvaroquaux
|
r1455 | """ Called on return key down, in readline input_state. | ||
""" | ||||
Gael Varoquaux
|
r1896 | last_line_num = self.LineFromPosition(self.GetLength()) | ||
current_line_num = self.LineFromPosition(self.GetCurrentPos()) | ||||
new_line_pos = (last_line_num - current_line_num) | ||||
gvaroquaux
|
r1436 | if self.debug: | ||
gvaroquaux
|
r1462 | print >>sys.__stdout__, repr(self.input_buffer) | ||
Gael Varoquaux
|
r1896 | self.write('\n', refresh=False) | ||
# Under windows scintilla seems to be doing funny | ||||
# stuff to the line returns here, but the getter for | ||||
# input_buffer filters this out. | ||||
if sys.platform == 'win32': | ||||
self.input_buffer = self.input_buffer | ||||
Gael Varoquaux
|
r1947 | old_prompt_num = self.current_prompt_pos | ||
Gael Varoquaux
|
r1896 | has_executed = PrefilterFrontEnd._on_enter(self, | ||
new_line_pos=new_line_pos) | ||||
Gael Varoquaux
|
r1947 | if old_prompt_num == self.current_prompt_pos: | ||
# No execution has happened | ||||
Gael Varoquaux
|
r1896 | self.GotoPos(self.GetLineEndPosition(current_line_num + 1)) | ||
return has_executed | ||||
Gael Varoquaux
|
r1349 | |||
Gael Varoquaux
|
r1393 | |||
gvaroquaux
|
r1455 | #-------------------------------------------------------------------------- | ||
gvaroquaux
|
r1503 | # EditWindow API | ||
#-------------------------------------------------------------------------- | ||||
def OnUpdateUI(self, event): | ||||
""" Override the OnUpdateUI of the EditWindow class, to prevent | ||||
syntax highlighting both for faster redraw, and for more | ||||
consistent look and feel. | ||||
""" | ||||
if not self._input_state == 'readline': | ||||
ConsoleWidget.OnUpdateUI(self, event) | ||||
#-------------------------------------------------------------------------- | ||||
gvaroquaux
|
r1455 | # Private API | ||
#-------------------------------------------------------------------------- | ||||
gvaroquaux
|
r1447 | def _buffer_flush(self, event): | ||
""" Called by the timer to flush the write buffer. | ||||
This is always called in the mainloop, by the wx timer. | ||||
""" | ||||
self._out_buffer_lock.acquire() | ||||
_out_buffer = self._out_buffer | ||||
self._out_buffer = [] | ||||
self._out_buffer_lock.release() | ||||
gvaroquaux
|
r1449 | self.write(''.join(_out_buffer), refresh=False) | ||
gvaroquaux
|
r1503 | |||
Gael Varoquaux
|
r1393 | |||
Gael Varoquaux
|
r1473 | def _colorize_input_buffer(self): | ||
""" Keep the input buffer lines at a bright color. | ||||
""" | ||||
gvaroquaux
|
r1480 | if not self._input_state in ('readline', 'raw_input'): | ||
gvaroquaux
|
r1475 | return | ||
gvaroquaux
|
r1478 | end_line = self.GetCurrentLine() | ||
if not sys.platform == 'win32': | ||||
end_line += 1 | ||||
Gael Varoquaux
|
r1473 | for i in range(self.current_prompt_line, end_line): | ||
if i in self._markers: | ||||
self.MarkerDeleteHandle(self._markers[i]) | ||||
self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER) | ||||
Gael Varoquaux
|
r1393 | |||
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) | ||||
Gael Varoquaux
|
r1389 | self.shell = WxController(self) | ||
Gael Varoquaux
|
r1349 | 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() | ||||
Gael Varoquaux
|
r1380 | frame.SetSize((680, 460)) | ||
Gael Varoquaux
|
r1349 | self = frame.shell | ||
app.MainLoop() | ||||