diff --git a/IPython/frontend/_process/killableprocess.py b/IPython/frontend/_process/killableprocess.py index e845686..955482c 100644 --- a/IPython/frontend/_process/killableprocess.py +++ b/IPython/frontend/_process/killableprocess.py @@ -50,7 +50,6 @@ import subprocess from subprocess import PIPE import sys import os -import time import types try: @@ -69,8 +68,16 @@ except ImportError: mswindows = (sys.platform == "win32") +skip = False + if mswindows: - import winprocess + import platform + if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000': + # Killable process does not work under vista when starting for + # something else than cmd. + skip = True + else: + import winprocess else: import signal @@ -78,7 +85,11 @@ if not mswindows: def DoNothing(*args): pass -class Popen(subprocess.Popen): + +if skip: + Popen = subprocess.Popen +else: + class Popen(subprocess.Popen): if not mswindows: # Override __init__ to set a preexec_fn def __init__(self, *args, **kwargs): diff --git a/IPython/frontend/_process/pipedprocess.py b/IPython/frontend/_process/pipedprocess.py index 2cda128..1b145af 100644 --- a/IPython/frontend/_process/pipedprocess.py +++ b/IPython/frontend/_process/pipedprocess.py @@ -50,7 +50,7 @@ class PipedProcess(Thread): """ env = os.environ env['TERM'] = 'xterm' - process = Popen((self.command_string + ' 2>&1', ), shell=True, + process = Popen(self.command_string + ' 2>&1', shell=True, env=env, universal_newlines=True, stdout=PIPE, stdin=PIPE, ) diff --git a/IPython/frontend/linefrontendbase.py b/IPython/frontend/linefrontendbase.py index 6f60d75..494fc71 100644 --- a/IPython/frontend/linefrontendbase.py +++ b/IPython/frontend/linefrontendbase.py @@ -20,6 +20,8 @@ import re import IPython import sys +import codeop +import traceback from frontendbase import FrontEndBase from IPython.kernel.core.interpreter import Interpreter @@ -76,6 +78,11 @@ class LineFrontEndBase(FrontEndBase): if banner is not None: self.banner = banner + + def start(self): + """ Put the frontend in a state where it is ready for user + interaction. + """ if self.banner is not None: self.write(self.banner, refresh=False) @@ -141,9 +148,18 @@ class LineFrontEndBase(FrontEndBase): and not re.findall(r"\n[\t ]*\n[\t ]*$", string)): return False else: - # Add line returns here, to make sure that the statement is - # complete. - return FrontEndBase.is_complete(self, string.rstrip() + '\n\n') + self.capture_output() + try: + # Add line returns here, to make sure that the statement is + # complete. + is_complete = codeop.compile_command(string.rstrip() + '\n\n', + "", "exec") + self.release_output() + except Exception, e: + # XXX: Hack: return True so that the + # code gets executed and the error captured. + is_complete = True + return is_complete def write(self, string, refresh=True): @@ -181,7 +197,7 @@ class LineFrontEndBase(FrontEndBase): #-------------------------------------------------------------------------- def prefilter_input(self, string): - """ Priflter the input to turn it in valid python. + """ Prefilter the input to turn it in valid python. """ string = string.replace('\r\n', '\n') string = string.replace('\t', 4*' ') @@ -210,9 +226,12 @@ class LineFrontEndBase(FrontEndBase): line = self.input_buffer new_line, completions = self.complete(line) if len(completions)>1: - self.write_completion(completions) - self.input_buffer = new_line + self.write_completion(completions, new_line=new_line) + elif not line == new_line: + self.input_buffer = new_line if self.debug: + print >>sys.__stdout__, 'line', line + print >>sys.__stdout__, 'new_line', new_line print >>sys.__stdout__, completions @@ -222,10 +241,15 @@ class LineFrontEndBase(FrontEndBase): return 80 - def write_completion(self, possibilities): + def write_completion(self, possibilities, new_line=None): """ Write the list of possible completions. + + new_line is the completed input line that should be displayed + after the completion are writen. If None, the input_buffer + before the completion is used. """ - current_buffer = self.input_buffer + if new_line is None: + new_line = self.input_buffer self.write('\n') max_len = len(max(possibilities, key=len)) + 1 @@ -246,7 +270,7 @@ class LineFrontEndBase(FrontEndBase): self.write(''.join(buf)) self.new_prompt(self.input_prompt_template.substitute( number=self.last_result['number'] + 1)) - self.input_buffer = current_buffer + self.input_buffer = new_line def new_prompt(self, prompt): @@ -275,6 +299,8 @@ class LineFrontEndBase(FrontEndBase): else: self.input_buffer += self._get_indent_string( current_buffer[:-1]) + if len(current_buffer.split('\n')) == 2: + self.input_buffer += '\t\t' if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'): self.input_buffer += '\t' diff --git a/IPython/frontend/prefilterfrontend.py b/IPython/frontend/prefilterfrontend.py index 8479132..ad6ce13 100644 --- a/IPython/frontend/prefilterfrontend.py +++ b/IPython/frontend/prefilterfrontend.py @@ -24,6 +24,7 @@ __docformat__ = "restructuredtext en" import sys from linefrontendbase import LineFrontEndBase, common_prefix +from frontendbase import FrontEndBase from IPython.ipmaker import make_IPython from IPython.ipapi import IPApi @@ -34,6 +35,7 @@ from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap from IPython.genutils import Term import pydoc import os +import sys def mk_system_call(system_call_function, command): @@ -57,6 +59,8 @@ class PrefilterFrontEnd(LineFrontEndBase): to execute the statements and the ipython0 used for code completion... """ + + debug = False def __init__(self, ipython0=None, *args, **kwargs): """ Parameters: @@ -65,12 +69,24 @@ class PrefilterFrontEnd(LineFrontEndBase): ipython0: an optional ipython0 instance to use for command prefiltering and completion. """ + LineFrontEndBase.__init__(self, *args, **kwargs) + self.shell.output_trap = RedirectorOutputTrap( + out_callback=self.write, + err_callback=self.write, + ) + self.shell.traceback_trap = SyncTracebackTrap( + formatters=self.shell.traceback_trap.formatters, + ) + + # Start the ipython0 instance: self.save_output_hooks() if ipython0 is None: # Instanciate an IPython0 interpreter to be able to use the # prefiltering. # XXX: argv=[] is a bit bold. - ipython0 = make_IPython(argv=[]) + ipython0 = make_IPython(argv=[], + user_ns=self.shell.user_ns, + user_global_ns=self.shell.user_global_ns) self.ipython0 = ipython0 # Set the pager: self.ipython0.set_hook('show_in_pager', @@ -86,24 +102,13 @@ class PrefilterFrontEnd(LineFrontEndBase): 'ls -CF') # And now clean up the mess created by ipython0 self.release_output() + + if not 'banner' in kwargs and self.banner is None: - kwargs['banner'] = self.ipython0.BANNER + """ + self.banner = self.ipython0.BANNER + """ This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" - LineFrontEndBase.__init__(self, *args, **kwargs) - # XXX: Hack: mix the two namespaces - self.shell.user_ns.update(self.ipython0.user_ns) - self.ipython0.user_ns = self.shell.user_ns - self.shell.user_global_ns.update(self.ipython0.user_global_ns) - self.ipython0.user_global_ns = self.shell.user_global_ns - - self.shell.output_trap = RedirectorOutputTrap( - out_callback=self.write, - err_callback=self.write, - ) - self.shell.traceback_trap = SyncTracebackTrap( - formatters=self.shell.traceback_trap.formatters, - ) + self.start() #-------------------------------------------------------------------------- # FrontEndBase interface @@ -113,7 +118,7 @@ This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" """ Use ipython0 to capture the last traceback and display it. """ self.capture_output() - self.ipython0.showtraceback() + self.ipython0.showtraceback(tb_offset=-1) self.release_output() @@ -164,6 +169,8 @@ This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" def complete(self, line): + # FIXME: This should be factored out in the linefrontendbase + # method. word = line.split('\n')[-1].split(' ')[-1] completions = self.ipython0.complete(word) # FIXME: The proper sort should be done in the complete method. diff --git a/IPython/frontend/tests/test_process.py b/IPython/frontend/tests/test_process.py index 8f6784f..dc8db5f 100644 --- a/IPython/frontend/tests/test_process.py +++ b/IPython/frontend/tests/test_process.py @@ -20,8 +20,6 @@ from IPython.frontend._process import PipedProcess from IPython.testing import decorators as testdec -# FIXME -@testdec.skip("This doesn't work under Windows") def test_capture_out(): """ A simple test to see if we can execute a process and get the output. """ @@ -33,8 +31,6 @@ def test_capture_out(): assert result == '1' -# FIXME -@testdec.skip("This doesn't work under Windows") def test_io(): """ Checks that we can send characters on stdin to the process. """ @@ -51,8 +47,6 @@ def test_io(): assert result == test_string -# FIXME -@testdec.skip("This doesn't work under Windows") def test_kill(): """ Check that we can kill a process, and its subprocess. """ diff --git a/IPython/frontend/wx/console_widget.py b/IPython/frontend/wx/console_widget.py index 3afda8a..d61e84a 100644 --- a/IPython/frontend/wx/console_widget.py +++ b/IPython/frontend/wx/console_widget.py @@ -23,6 +23,7 @@ import wx import wx.stc as stc from wx.py import editwindow +import time import sys LINESEP = '\n' if sys.platform == 'win32': @@ -115,12 +116,15 @@ class ConsoleWidget(editwindow.EditWindow): # The color of the carret (call _apply_style() after setting) carret_color = 'BLACK' + # Store the last time a refresh was done + _last_refresh_time = 0 + #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, - size=wx.DefaultSize, style=0, ): + size=wx.DefaultSize, style=wx.WANTS_CHARS, ): editwindow.EditWindow.__init__(self, parent, id, pos, size, style) self._configure_scintilla() @@ -168,9 +172,14 @@ class ConsoleWidget(editwindow.EditWindow): self.GotoPos(self.GetLength()) if refresh: - # Maybe this is faster than wx.Yield() - self.ProcessEvent(wx.PaintEvent()) - #wx.Yield() + current_time = time.time() + if current_time - self._last_refresh_time > 0.03: + if sys.platform == 'win32': + wx.SafeYield() + else: + wx.Yield() + # self.ProcessEvent(wx.PaintEvent()) + self._last_refresh_time = current_time def new_prompt(self, prompt): @@ -183,7 +192,6 @@ class ConsoleWidget(editwindow.EditWindow): # now we update our cursor giving end of prompt self.current_prompt_pos = self.GetLength() self.current_prompt_line = self.GetCurrentLine() - wx.Yield() self.EnsureCaretVisible() diff --git a/IPython/frontend/wx/wx_frontend.py b/IPython/frontend/wx/wx_frontend.py index b695897..d4182cc 100644 --- a/IPython/frontend/wx/wx_frontend.py +++ b/IPython/frontend/wx/wx_frontend.py @@ -128,6 +128,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): # while it is being swapped _out_buffer_lock = Lock() + # The different line markers used to higlight the prompts. _markers = dict() #-------------------------------------------------------------------------- @@ -135,12 +136,16 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): #-------------------------------------------------------------------------- def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, - size=wx.DefaultSize, style=wx.CLIP_CHILDREN, + size=wx.DefaultSize, + style=wx.CLIP_CHILDREN|wx.WANTS_CHARS, *args, **kwds): """ Create Shell instance. """ ConsoleWidget.__init__(self, parent, id, pos, size, style) PrefilterFrontEnd.__init__(self, **kwds) + + # Stick in our own raw_input: + self.ipython0.raw_input = self.raw_input # Marker for complete buffer. self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND, @@ -164,9 +169,11 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): # Inject self in namespace, for debug if self.debug: self.shell.user_ns['self'] = self + # Inject our own raw_input in namespace + self.shell.user_ns['raw_input'] = self.raw_input - def raw_input(self, prompt): + def raw_input(self, prompt=''): """ A replacement from python's raw_input. """ self.new_prompt(prompt) @@ -174,15 +181,13 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): if hasattr(self, '_cursor'): del self._cursor self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS)) - self.waiting = True self.__old_on_enter = self._on_enter + event_loop = wx.EventLoop() def my_on_enter(): - self.waiting = False + event_loop.Exit() self._on_enter = my_on_enter - # XXX: Busy waiting, ugly. - while self.waiting: - wx.Yield() - sleep(0.1) + # XXX: Running a separate event_loop. Ugly. + event_loop.Run() self._on_enter = self.__old_on_enter self._input_state = 'buffering' self._cursor = wx.BusyCursor() @@ -191,16 +196,18 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): def system_call(self, command_string): self._input_state = 'subprocess' + event_loop = wx.EventLoop() + def _end_system_call(): + self._input_state = 'buffering' + self._running_process = False + event_loop.Exit() + self._running_process = PipedProcess(command_string, out_callback=self.buffered_write, - end_callback = self._end_system_call) + end_callback = _end_system_call) self._running_process.start() - # XXX: another one of these polling loops to have a blocking - # call - wx.Yield() - while self._running_process: - wx.Yield() - sleep(0.1) + # XXX: Running a separate event_loop. Ugly. + event_loop.Run() # Be sure to flush the buffer. self._buffer_flush(event=None) @@ -226,8 +233,9 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): for name in symbol_string.split('.')[1:] + ['__doc__']: symbol = getattr(symbol, name) self.AutoCompCancel() - wx.Yield() - self.CallTipShow(self.GetCurrentPos(), symbol) + # Check that the symbol can indeed be converted to a string: + symbol += '' + wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol) except: # The retrieve symbol couldn't be converted to a string pass @@ -238,9 +246,9 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): true, open the menu. """ if self.debug: - print >>sys.__stdout__, "_popup_completion", + print >>sys.__stdout__, "_popup_completion" line = self.input_buffer - if (self.AutoCompActive() and not line[-1] == '.') \ + if (self.AutoCompActive() and line and not line[-1] == '.') \ or create==True: suggestion, completions = self.complete(line) offset=0 @@ -284,19 +292,21 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): if i in self._markers: self.MarkerDeleteHandle(self._markers[i]) self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER) - # Update the display: - wx.Yield() - self.GotoPos(self.GetLength()) - PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string) + # 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) def save_output_hooks(self): self.__old_raw_input = __builtin__.raw_input PrefilterFrontEnd.save_output_hooks(self) def capture_output(self): - __builtin__.raw_input = self.raw_input self.SetLexer(stc.STC_LEX_NULL) PrefilterFrontEnd.capture_output(self) + __builtin__.raw_input = self.raw_input def release_output(self): @@ -316,12 +326,24 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): def show_traceback(self): start_line = self.GetCurrentLine() PrefilterFrontEnd.show_traceback(self) - wx.Yield() + self.ProcessEvent(wx.PaintEvent()) + #wx.Yield() for i in range(start_line, self.GetCurrentLine()): self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER) #-------------------------------------------------------------------------- + # 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) + + + #-------------------------------------------------------------------------- # ConsoleWidget interface #-------------------------------------------------------------------------- @@ -351,7 +373,8 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): if self._input_state == 'subprocess': if self.debug: print >>sys.__stderr__, 'Killing running process' - self._running_process.process.kill() + if hasattr(self._running_process, 'process'): + self._running_process.process.kill() elif self._input_state == 'buffering': if self.debug: print >>sys.__stderr__, 'Raising KeyboardInterrupt' @@ -376,7 +399,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): char = '\04' self._running_process.process.stdin.write(char) self._running_process.process.stdin.flush() - elif event.KeyCode in (ord('('), 57): + elif event.KeyCode in (ord('('), 57, 53): # Calltips event.Skip() self.do_calltip() @@ -410,8 +433,8 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): self.input_buffer = new_buffer # Tab-completion elif event.KeyCode == ord('\t'): - last_line = self.input_buffer.split('\n')[-1] - if not re.match(r'^\s*$', last_line): + current_line, current_line_number = self.CurLine + if not re.match(r'^\s*$', current_line): self.complete_current_input() if self.AutoCompActive(): wx.CallAfter(self._popup_completion, create=True) @@ -427,7 +450,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): if event.KeyCode in (59, ord('.')): # Intercepting '.' event.Skip() - self._popup_completion(create=True) + wx.CallAfter(self._popup_completion, create=True) else: ConsoleWidget._on_key_up(self, event, skip=skip) @@ -456,13 +479,6 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): # Private API #-------------------------------------------------------------------------- - def _end_system_call(self): - """ Called at the end of a system call. - """ - self._input_state = 'buffering' - self._running_process = False - - def _buffer_flush(self, event): """ Called by the timer to flush the write buffer. diff --git a/IPython/kernel/core/interpreter.py b/IPython/kernel/core/interpreter.py index f7d906c..bca170b 100644 --- a/IPython/kernel/core/interpreter.py +++ b/IPython/kernel/core/interpreter.py @@ -680,6 +680,13 @@ class Interpreter(object): # how trailing whitespace is handled, but this seems to work. python = python.strip() + # The compiler module does not like unicode. We need to convert + # it encode it: + if isinstance(python, unicode): + # Use the utf-8-sig BOM so the compiler detects this a UTF-8 + # encode string. + python = '\xef\xbb\xbf' + python.encode('utf-8') + # The compiler module will parse the code into an abstract syntax tree. ast = compiler.parse(python) diff --git a/IPython/kernel/core/tests/test_interpreter.py b/IPython/kernel/core/tests/test_interpreter.py new file mode 100644 index 0000000..3efc4f2 --- /dev/null +++ b/IPython/kernel/core/tests/test_interpreter.py @@ -0,0 +1,26 @@ +# encoding: utf-8 + +"""This file contains unittests for the interpreter.py module.""" + +__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 +#----------------------------------------------------------------------------- + +from IPython.kernel.core.interpreter import Interpreter + +def test_unicode(): + """ Test unicode handling with the interpreter. + """ + i = Interpreter() + i.execute_python(u'print "ù"') + i.execute_python('print "ù"') + diff --git a/IPython/testing/plugin/Makefile b/IPython/testing/plugin/Makefile index 878946c..9d8157a 100644 --- a/IPython/testing/plugin/Makefile +++ b/IPython/testing/plugin/Makefile @@ -1,6 +1,5 @@ # Set this prefix to where you want to install the plugin -PREFIX=~/usr/local -PREFIX=~/tmp/local +PREFIX=/usr/local NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \ diff --git a/setupegg.py b/setupegg.py index c59ea94..0924b19 100755 --- a/setupegg.py +++ b/setupegg.py @@ -1,14 +1,8 @@ #!/usr/bin/env python """Wrapper to run setup.py using setuptools.""" -import os import sys -# Add my local path to sys.path -home = os.environ['HOME'] -sys.path.insert(0,'%s/usr/local/lib/python%s/site-packages' % - (home,sys.version[:3])) - # now, import setuptools and call the actual setup import setuptools # print sys.argv