diff --git a/IPython/frontend/_process/__init__.py b/IPython/frontend/_process/__init__.py new file mode 100644 index 0000000..af9be72 --- /dev/null +++ b/IPython/frontend/_process/__init__.py @@ -0,0 +1,19 @@ +""" +Package for dealing for process execution in a callback environment, in a +portable way. + +killable_process.py is a wrapper of subprocess.Popen that allows the +subprocess and its children to be killed in a reliable way, including +under windows. + +winprocess.py is required by killable_process.py to kill processes under +windows. + +piped_process.py wraps process execution with callbacks to print output, +in a non-blocking way. It can be used to interact with a subprocess in eg +a GUI event loop. +""" + +from pipedprocess import PipedProcess + + diff --git a/IPython/frontend/killable_process.py b/IPython/frontend/_process/killableprocess.py similarity index 100% rename from IPython/frontend/killable_process.py rename to IPython/frontend/_process/killableprocess.py diff --git a/IPython/frontend/piped_process.py b/IPython/frontend/_process/pipedprocess.py similarity index 97% rename from IPython/frontend/piped_process.py rename to IPython/frontend/_process/pipedprocess.py index 5339fdd..ee60ab4 100644 --- a/IPython/frontend/piped_process.py +++ b/IPython/frontend/_process/pipedprocess.py @@ -15,7 +15,7 @@ __docformat__ = "restructuredtext en" #------------------------------------------------------------------------------- # Imports #------------------------------------------------------------------------------- -from killable_process import Popen, PIPE +from killableprocess import Popen, PIPE from threading import Thread from time import sleep diff --git a/IPython/frontend/winprocess.py b/IPython/frontend/_process/winprocess.py similarity index 100% rename from IPython/frontend/winprocess.py rename to IPython/frontend/_process/winprocess.py diff --git a/IPython/frontend/asyncfrontendbase.py b/IPython/frontend/asyncfrontendbase.py index c3614b7..3662351 100644 --- a/IPython/frontend/asyncfrontendbase.py +++ b/IPython/frontend/asyncfrontendbase.py @@ -18,14 +18,11 @@ import uuid try: from zope.interface import Interface, Attribute, implements, classProvides -except ImportError: - #zope.interface is not available - Interface = object - def Attribute(name, doc): pass - def implements(interface): pass - def classProvides(interface): pass - - +except ImportError, e: + e.message = """%s +________________________________________________________________________________ +zope.interface is required to run asynchronous frontends.""" % e.message + e.args = (e.message, ) + e.args[1:] from frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory @@ -34,9 +31,11 @@ from IPython.kernel.core.history import FrontEndHistory try: from twisted.python.failure import Failure -except ImportError: - #Twisted not available - Failure = Exception +except ImportError, e: + e.message = """%s +________________________________________________________________________________ +twisted is required to run asynchronous frontends.""" % e.message + e.args = (e.message, ) + e.args[1:] diff --git a/IPython/frontend/frontendbase.py b/IPython/frontend/frontendbase.py index 9d0ecfa..4fa4ad8 100644 --- a/IPython/frontend/frontendbase.py +++ b/IPython/frontend/frontendbase.py @@ -24,14 +24,7 @@ import string import uuid import _ast -try: - from zope.interface import Interface, Attribute, implements, classProvides -except ImportError: - #zope.interface is not available - Interface = object - def Attribute(name, doc): pass - def implements(interface): pass - def classProvides(interface): pass +from zopeinterface import Interface, Attribute, implements, classProvides from IPython.kernel.core.history import FrontEndHistory from IPython.kernel.core.util import Bunch @@ -46,6 +39,8 @@ rc.prompt_in2 = r'...' rc.prompt_out = r'Out [$number]: ' ############################################################################## +# Interface definitions +############################################################################## class IFrontEndFactory(Interface): """Factory interface for frontends.""" @@ -59,7 +54,6 @@ class IFrontEndFactory(Interface): pass - class IFrontEnd(Interface): """Interface for frontends. All methods return t.i.d.Deferred""" @@ -72,9 +66,10 @@ class IFrontEnd(Interface): def update_cell_prompt(result, blockID=None): """Subclass may override to update the input prompt for a block. - Since this method will be called as a - twisted.internet.defer.Deferred's callback/errback, - implementations should return result when finished. + + In asynchronous frontends, this method will be called as a + twisted.internet.defer.Deferred's callback/errback. + Implementations should thus return result when finished. Result is a result dict in case of success, and a twisted.python.util.failure.Failure in case of an error @@ -82,7 +77,6 @@ class IFrontEnd(Interface): pass - def render_result(result): """Render the result of an execute call. Implementors may choose the method of rendering. @@ -100,16 +94,17 @@ class IFrontEnd(Interface): pass def render_error(failure): - """Subclasses must override to render the failure. Since this method - will be called as a twisted.internet.defer.Deferred's callback, - implementations should return result when finished. + """Subclasses must override to render the failure. + + In asynchronous frontend, since this method will be called as a + twisted.internet.defer.Deferred's callback. Implementations + should thus return result when finished. blockID = failure.blockID """ pass - def input_prompt(number=''): """Returns the input prompt by subsituting into self.input_prompt_template @@ -140,7 +135,6 @@ class IFrontEnd(Interface): pass - def get_history_previous(current_block): """Returns the block previous in the history. Saves currentBlock if the history_cursor is currently at the end of the input history""" @@ -151,6 +145,20 @@ class IFrontEnd(Interface): pass + def complete(self, line): + """Returns the list of possible completions, and the completed + line. + + The input argument is the full line to be completed. This method + returns both the line completed as much as possible, and the list + of further possible completions (full words). + """ + pass + + +############################################################################## +# Base class for all the frontends. +############################################################################## class FrontEndBase(object): """ @@ -321,6 +329,8 @@ class FrontEndBase(object): def update_cell_prompt(self, result, blockID=None): """Subclass may override to update the input prompt for a block. + + This method only really makes sens in asyncrhonous frontend. Since this method will be called as a twisted.internet.defer.Deferred's callback, implementations should return result when finished. @@ -330,18 +340,22 @@ class FrontEndBase(object): def render_result(self, result): - """Subclasses must override to render result. Since this method will - be called as a twisted.internet.defer.Deferred's callback, - implementations should return result when finished. + """Subclasses must override to render result. + + In asynchronous frontends, this method will be called as a + twisted.internet.defer.Deferred's callback. Implementations + should thus return result when finished. """ return result def render_error(self, failure): - """Subclasses must override to render the failure. Since this method - will be called as a twisted.internet.defer.Deferred's callback, - implementations should return result when finished. + """Subclasses must override to render the failure. + + In asynchronous frontends, this method will be called as a + twisted.internet.defer.Deferred's callback. Implementations + should thus return result when finished. """ return failure diff --git a/IPython/frontend/linefrontendbase.py b/IPython/frontend/linefrontendbase.py index b89d46d..e9d76cd 100644 --- a/IPython/frontend/linefrontendbase.py +++ b/IPython/frontend/linefrontendbase.py @@ -1,5 +1,6 @@ """ -Base front end class for all line-oriented frontends. +Base front end class for all line-oriented frontends, rather than +block-oriented. Currently this focuses on synchronous frontends. """ @@ -23,6 +24,9 @@ from frontendbase import FrontEndBase from IPython.kernel.core.interpreter import Interpreter def common_prefix(strings): + """ Given a list of strings, return the common prefix between all + these strings. + """ ref = strings[0] prefix = '' for size in range(len(ref)): @@ -38,6 +42,10 @@ def common_prefix(strings): # Base class for the line-oriented front ends #------------------------------------------------------------------------------- class LineFrontEndBase(FrontEndBase): + """ Concrete implementation of the FrontEndBase class. This is meant + to be the base class behind all the frontend that are line-oriented, + rather than block-oriented. + """ # We need to keep the prompt number, to be able to increment # it when there is an exception. @@ -47,7 +55,7 @@ class LineFrontEndBase(FrontEndBase): last_result = dict(number=0) #-------------------------------------------------------------------------- - # Public API + # FrontEndBase interface #-------------------------------------------------------------------------- def __init__(self, shell=None, history=None): @@ -81,6 +89,9 @@ class LineFrontEndBase(FrontEndBase): def render_result(self, result): + """ Frontend-specific rendering of the result of a calculation + that has been sent to an engine. + """ if 'stdout' in result and result['stdout']: self.write('\n' + result['stdout']) if 'display' in result and result['display']: @@ -91,20 +102,25 @@ class LineFrontEndBase(FrontEndBase): def render_error(self, failure): + """ Frontend-specific rendering of error. + """ self.insert_text('\n\n'+str(failure)+'\n\n') return failure - def prefilter_input(self, string): - string = string.replace('\r\n', '\n') - string = string.replace('\t', 4*' ') - # Clean the trailing whitespace - string = '\n'.join(l.rstrip() for l in string.split('\n')) - return string - - def is_complete(self, string): + """ Check if a string forms a complete, executable set of + commands. + + For the line-oriented frontend, multi-line code is not executed + as soon as it is complete: the users has to enter two line + returns. + """ if string in ('', '\n'): + # Prefiltering, eg through ipython0, may return an empty + # string although some operations have been accomplished. We + # thus want to consider an empty string as a complete + # statement. return True elif ( len(self.get_current_edit_buffer().split('\n'))>2 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)): @@ -116,8 +132,8 @@ class LineFrontEndBase(FrontEndBase): def execute(self, python_string, raw_string=None): - """ Send the python_string to the interpreter, stores the - raw_string in the history and starts a new prompt. + """ Stores the raw_string in the history, and sends the + python string to the interpreter. """ if raw_string is None: raw_string = python_string @@ -133,6 +149,18 @@ class LineFrontEndBase(FrontEndBase): finally: self.after_execute() + #-------------------------------------------------------------------------- + # LineFrontEndBase interface + #-------------------------------------------------------------------------- + + def prefilter_input(self, string): + """ Priflter the input to turn it in valid python. + """ + string = string.replace('\r\n', '\n') + string = string.replace('\t', 4*' ') + # Clean the trailing whitespace + string = '\n'.join(l.rstrip() for l in string.split('\n')) + return string def after_execute(self): """ All the operations required after an execution to put the @@ -145,6 +173,10 @@ class LineFrontEndBase(FrontEndBase): self.history_cursor = len(self.history.input_cache) - 1 + #-------------------------------------------------------------------------- + # Private API + #-------------------------------------------------------------------------- + def _on_enter(self): """ Called when the return key is pressed in a line editing buffer. @@ -160,11 +192,10 @@ class LineFrontEndBase(FrontEndBase): self.write('\t') - #-------------------------------------------------------------------------- - # Private API - #-------------------------------------------------------------------------- - def _get_indent_string(self, string): + """ Return the string of whitespace that prefixes a line. Used to + add the right amount of indendation when creating a new line. + """ string = string.replace('\t', ' '*4) string = string.split('\n')[-1] indent_chars = len(string) - len(string.lstrip()) diff --git a/IPython/frontend/prefilterfrontend.py b/IPython/frontend/prefilterfrontend.py index d22bb40..f84c4c0 100644 --- a/IPython/frontend/prefilterfrontend.py +++ b/IPython/frontend/prefilterfrontend.py @@ -2,6 +2,12 @@ Frontend class that uses IPython0 to prefilter the inputs. Using the IPython0 mechanism gives us access to the magics. + +This is a transitory class, used here to do the transition between +ipython0 and ipython1. This class is meant to be short-lived as more +functionnality is abstracted out of ipython0 in reusable functions and +is added on the interpreter. This class can be a used to guide this +refactoring. """ __docformat__ = "restructuredtext en" @@ -27,6 +33,8 @@ from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap from IPython.genutils import Term import pydoc +import os + def mk_system_call(system_call_function, command): """ given a os.system replacement, and a leading string command, @@ -41,6 +49,14 @@ def mk_system_call(system_call_function, command): # Frontend class using ipython0 to do the prefiltering. #------------------------------------------------------------------------------- class PrefilterFrontEnd(LineFrontEndBase): + """ Class that uses ipython0 to do prefilter the input, do the + completion and the magics. + + The core trick is to use an ipython0 instance to prefilter the + input, and share the namespace between the interpreter instance used + to execute the statements and the ipython0 used for code + completion... + """ def __init__(self, *args, **kwargs): LineFrontEndBase.__init__(self, *args, **kwargs) @@ -67,38 +83,20 @@ class PrefilterFrontEnd(LineFrontEndBase): err_callback=self.write, ) self.shell.traceback_trap = SyncTracebackTrap( - formatters=self.shell.traceback_trap.formatters + formatters=self.shell.traceback_trap.formatters, ) # Capture and release the outputs, to make sure all the # shadow variables are set self.capture_output() self.release_output() - - def prefilter_input(self, input_string): - """ Using IPython0 to prefilter the commands. - """ - input_string = LineFrontEndBase.prefilter_input(self, input_string) - filtered_lines = [] - # The IPython0 prefilters sometime produce output. We need to - # capture it. - self.capture_output() - self.last_result = dict(number=self.prompt_number) - try: - for line in input_string.split('\n'): - filtered_lines.append(self.ipython0.prefilter(line, False)) - except: - # XXX: probably not the right thing to do. - self.ipython0.showsyntaxerror() - self.after_execute() - finally: - self.release_output() - - filtered_string = '\n'.join(filtered_lines) - return filtered_string - + #-------------------------------------------------------------------------- + # FrontEndBase interface + #-------------------------------------------------------------------------- def show_traceback(self): + """ Use ipython0 to capture the last traceback and display it. + """ self.capture_output() self.ipython0.showtraceback() self.release_output() @@ -111,13 +109,6 @@ class PrefilterFrontEnd(LineFrontEndBase): self.release_output() - def system_call(self, command): - """ Allows for frontend to define their own system call, to be - able capture output and redirect input. - """ - return os.system(command, args) - - def capture_output(self): """ Capture all the output mechanisms we can think of. """ @@ -154,6 +145,45 @@ class PrefilterFrontEnd(LineFrontEndBase): line = line[:-len(word)] + prefix return line, completions + + #-------------------------------------------------------------------------- + # LineFrontEndBase interface + #-------------------------------------------------------------------------- + + def prefilter_input(self, input_string): + """ Using IPython0 to prefilter the commands to turn them + in executable statements that are valid Python strings. + """ + input_string = LineFrontEndBase.prefilter_input(self, input_string) + filtered_lines = [] + # The IPython0 prefilters sometime produce output. We need to + # capture it. + self.capture_output() + self.last_result = dict(number=self.prompt_number) + try: + for line in input_string.split('\n'): + filtered_lines.append(self.ipython0.prefilter(line, False)) + except: + # XXX: probably not the right thing to do. + self.ipython0.showsyntaxerror() + self.after_execute() + finally: + self.release_output() + + filtered_string = '\n'.join(filtered_lines) + return filtered_string + + + #-------------------------------------------------------------------------- + # PrefilterLineFrontEnd interface + #-------------------------------------------------------------------------- + + def system_call(self, command_string): + """ Allows for frontend to define their own system call, to be + able capture output and redirect input. + """ + return os.system(command_string) + def do_exit(self): """ Exit the shell, cleanup and save the history. diff --git a/IPython/frontend/wx/console_widget.py b/IPython/frontend/wx/console_widget.py index da91206..1091658 100644 --- a/IPython/frontend/wx/console_widget.py +++ b/IPython/frontend/wx/console_widget.py @@ -76,6 +76,8 @@ class ConsoleWidget(editwindow.EditWindow): keeping the cursor inside the editing line. """ + # This is where the title captured from the ANSI escape sequences are + # stored. title = 'Console' style = _DEFAULT_STYLE.copy() @@ -102,7 +104,7 @@ class ConsoleWidget(editwindow.EditWindow): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, ): editwindow.EditWindow.__init__(self, parent, id, pos, size, style) - self.configure_scintilla() + self._configure_scintilla() # FIXME: we need to retrieve this from the interpreter. self.prompt = \ @@ -113,87 +115,6 @@ class ConsoleWidget(editwindow.EditWindow): self.Bind(wx.EVT_KEY_UP, self._on_key_up) - def configure_scintilla(self): - self.SetEOLMode(stc.STC_EOL_LF) - - # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside - # the widget - self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) - self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) - # Also allow Ctrl Shift "=" for poor non US keyboard users. - self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT, - stc.STC_CMD_ZOOMIN) - - #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT, - # stc.STC_CMD_PAGEUP) - - #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT, - # stc.STC_CMD_PAGEDOWN) - - # Keys: we need to clear some of the keys the that don't play - # well with a console. - self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL) - self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL) - self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL) - - - self.SetEOLMode(stc.STC_EOL_CRLF) - self.SetWrapMode(stc.STC_WRAP_CHAR) - self.SetWrapMode(stc.STC_WRAP_WORD) - self.SetBufferedDraw(True) - self.SetUseAntiAliasing(True) - self.SetLayoutCache(stc.STC_CACHE_PAGE) - self.SetUndoCollection(False) - self.SetUseTabs(True) - self.SetIndent(4) - self.SetTabWidth(4) - - self.EnsureCaretVisible() - # we don't want scintilla's autocompletion to choose - # automaticaly out of a single choice list, as we pop it up - # automaticaly - self.AutoCompSetChooseSingle(False) - self.AutoCompSetMaxHeight(10) - - self.SetMargins(3, 3) #text is moved away from border with 3px - # Suppressing Scintilla margins - self.SetMarginWidth(0, 0) - self.SetMarginWidth(1, 0) - self.SetMarginWidth(2, 0) - - self._apply_style() - - # Xterm escape sequences - self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') - self.title_pat = re.compile('\x1b]0;(.*?)\x07') - - #self.SetEdgeMode(stc.STC_EDGE_LINE) - #self.SetEdgeColumn(80) - - # styles - p = self.style - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default']) - self.StyleClearAll() - self.StyleSetSpec(_STDOUT_STYLE, p['stdout']) - self.StyleSetSpec(_STDERR_STYLE, p['stderr']) - self.StyleSetSpec(_TRACE_STYLE, p['trace']) - - self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood']) - self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad']) - self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment']) - self.StyleSetSpec(stc.STC_P_NUMBER, p['number']) - self.StyleSetSpec(stc.STC_P_STRING, p['string']) - self.StyleSetSpec(stc.STC_P_CHARACTER, p['char']) - self.StyleSetSpec(stc.STC_P_WORD, p['keyword']) - self.StyleSetSpec(stc.STC_P_WORD2, p['keyword']) - self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple']) - self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble']) - self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class']) - self.StyleSetSpec(stc.STC_P_DEFNAME, p['def']) - self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator']) - self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment']) - - def write(self, text, refresh=True): """ Write given text to buffer, while translating the ansi escape sequences. @@ -270,24 +191,20 @@ class ConsoleWidget(editwindow.EditWindow): return current_edit_buffer - #-------------------------------------------------------------------------- - # Private API - #-------------------------------------------------------------------------- - - def _apply_style(self): - """ Applies the colors for the different text elements and the - carret. - """ - self.SetCaretForeground(self.carret_color) + def scroll_to_bottom(self): + maxrange = self.GetScrollRange(wx.VERTICAL) + self.ScrollLines(maxrange) - #self.StyleClearAll() - self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, - "fore:#FF0000,back:#0000FF,bold") - self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, - "fore:#000000,back:#FF0000,bold") - for style in self.ANSI_STYLES.values(): - self.StyleSetSpec(style[0], "bold,fore:%s" % style[1]) + def pop_completion(self, possibilities, offset=0): + """ Pops up an autocompletion menu. Offset is the offset + in characters of the position at which the menu should + appear, relativ to the cursor. + """ + self.AutoCompSetIgnoreCase(False) + self.AutoCompSetAutoHide(False) + self.AutoCompSetMaxHeight(len(possibilities)) + self.AutoCompShow(offset, " ".join(possibilities)) def write_completion(self, possibilities): @@ -315,21 +232,105 @@ class ConsoleWidget(editwindow.EditWindow): self.new_prompt(self.prompt % (self.last_result['number'] + 1)) self.replace_current_edit_buffer(current_buffer) - - def pop_completion(self, possibilities, offset=0): - """ Pops up an autocompletion menu. Offset is the offset - in characters of the position at which the menu should - appear, relativ to the cursor. + #-------------------------------------------------------------------------- + # Private API + #-------------------------------------------------------------------------- + + def _apply_style(self): + """ Applies the colors for the different text elements and the + carret. """ - self.AutoCompSetIgnoreCase(False) - self.AutoCompSetAutoHide(False) - self.AutoCompSetMaxHeight(len(possibilities)) - self.AutoCompShow(offset, " ".join(possibilities)) + self.SetCaretForeground(self.carret_color) - - def scroll_to_bottom(self): - maxrange = self.GetScrollRange(wx.VERTICAL) - self.ScrollLines(maxrange) + #self.StyleClearAll() + self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, + "fore:#FF0000,back:#0000FF,bold") + self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, + "fore:#000000,back:#FF0000,bold") + + for style in self.ANSI_STYLES.values(): + self.StyleSetSpec(style[0], "bold,fore:%s" % style[1]) + + + def _configure_scintilla(self): + self.SetEOLMode(stc.STC_EOL_LF) + + # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside + # the widget + self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) + self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) + # Also allow Ctrl Shift "=" for poor non US keyboard users. + self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT, + stc.STC_CMD_ZOOMIN) + + #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT, + # stc.STC_CMD_PAGEUP) + + #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT, + # stc.STC_CMD_PAGEDOWN) + + # Keys: we need to clear some of the keys the that don't play + # well with a console. + self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL) + self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL) + self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL) + + + self.SetEOLMode(stc.STC_EOL_CRLF) + self.SetWrapMode(stc.STC_WRAP_CHAR) + self.SetWrapMode(stc.STC_WRAP_WORD) + self.SetBufferedDraw(True) + self.SetUseAntiAliasing(True) + self.SetLayoutCache(stc.STC_CACHE_PAGE) + self.SetUndoCollection(False) + self.SetUseTabs(True) + self.SetIndent(4) + self.SetTabWidth(4) + + self.EnsureCaretVisible() + # we don't want scintilla's autocompletion to choose + # automaticaly out of a single choice list, as we pop it up + # automaticaly + self.AutoCompSetChooseSingle(False) + self.AutoCompSetMaxHeight(10) + + self.SetMargins(3, 3) #text is moved away from border with 3px + # Suppressing Scintilla margins + self.SetMarginWidth(0, 0) + self.SetMarginWidth(1, 0) + self.SetMarginWidth(2, 0) + + self._apply_style() + + # Xterm escape sequences + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + self.title_pat = re.compile('\x1b]0;(.*?)\x07') + + #self.SetEdgeMode(stc.STC_EDGE_LINE) + #self.SetEdgeColumn(80) + + # styles + p = self.style + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default']) + self.StyleClearAll() + self.StyleSetSpec(_STDOUT_STYLE, p['stdout']) + self.StyleSetSpec(_STDERR_STYLE, p['stderr']) + self.StyleSetSpec(_TRACE_STYLE, p['trace']) + + self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood']) + self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad']) + self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment']) + self.StyleSetSpec(stc.STC_P_NUMBER, p['number']) + self.StyleSetSpec(stc.STC_P_STRING, p['string']) + self.StyleSetSpec(stc.STC_P_CHARACTER, p['char']) + self.StyleSetSpec(stc.STC_P_WORD, p['keyword']) + self.StyleSetSpec(stc.STC_P_WORD2, p['keyword']) + self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple']) + self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble']) + self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class']) + self.StyleSetSpec(stc.STC_P_DEFNAME, p['def']) + self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator']) + self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment']) def _on_key_down(self, event, skip=True): @@ -418,7 +419,6 @@ class ConsoleWidget(editwindow.EditWindow): - if __name__ == '__main__': # Some simple code to test the console widget. class MainWindow(wx.Frame): diff --git a/IPython/frontend/wx/wx_frontend.py b/IPython/frontend/wx/wx_frontend.py index 1ba94f1..6a5f377 100644 --- a/IPython/frontend/wx/wx_frontend.py +++ b/IPython/frontend/wx/wx_frontend.py @@ -5,6 +5,8 @@ """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. """ __docformat__ = "restructuredtext en" @@ -20,21 +22,25 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- - -import wx +# Major library imports import re -from wx import stc -from console_widget import ConsoleWidget import __builtin__ from time import sleep import sys -import signal - from threading import Lock -from IPython.frontend.piped_process import PipedProcess +import wx +from wx import stc + +# Ipython-specific imports. +from IPython.frontend._process import PipedProcess +from console_widget import ConsoleWidget from IPython.frontend.prefilterfrontend import PrefilterFrontEnd +#------------------------------------------------------------------------------- +# Constants +#------------------------------------------------------------------------------- + #_COMMAND_BG = '#FAFAF1' # Nice green _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow _ERROR_BG = '#FFF1F1' # Nice red @@ -46,7 +52,14 @@ _ERROR_MARKER = 30 # Classes to implement the Wx frontend #------------------------------------------------------------------------------- class WxController(PrefilterFrontEnd, ConsoleWidget): + """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. + """ + # FIXME: this shouldn't be there. output_prompt = \ '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02' @@ -55,7 +68,6 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): # The title of the terminal, as captured through the ANSI escape # sequences. - def _set_title(self, title): return self.Parent.SetTitle(title) @@ -114,8 +126,43 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID) wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush) + + def raw_input(self, prompt): + """ A replacement from python's raw_input. + """ + self.new_prompt(prompt) + self.waiting = True + self.__old_on_enter = self._on_enter + def my_on_enter(): + self.waiting = False + self._on_enter = my_on_enter + # XXX: Busy waiting, ugly. + while self.waiting: + wx.Yield() + sleep(0.1) + self._on_enter = self.__old_on_enter + self._input_state = 'buffering' + return self.get_current_edit_buffer().rstrip('\n') + + + def system_call(self, command_string): + self._input_state = 'subprocess' + self._running_process = PipedProcess(command_string, + out_callback=self.buffered_write, + end_callback = self._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) + # Be sure to flush the buffer. + self._buffer_flush(event=None) + + def do_completion(self): - """ Do code completion. + """ Do code completion on current line. """ if self.debug: print >>sys.__stdout__, "do_completion", @@ -129,6 +176,8 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): def do_calltip(self): + """ Analyse current and displays useful calltip for it. + """ if self.debug: print >>sys.__stdout__, "do_calltip" separators = re.compile('[\s\{\}\[\]\(\)\= ,:]') @@ -154,12 +203,12 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): pass - def popup_completion(self, create=False): + def _popup_completion(self, create=False): """ Updates the popup completion menu if it exists. If create is true, open the menu. """ if self.debug: - print >>sys.__stdout__, "popup_completion", + print >>sys.__stdout__, "_popup_completion", line = self.get_current_edit_buffer() if (self.AutoCompActive() and not line[-1] == '.') \ or create==True: @@ -174,28 +223,23 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): print >>sys.__stdout__, completions - def new_prompt(self, prompt): - self._input_state = 'readline' - ConsoleWidget.new_prompt(self, prompt) - + def buffered_write(self, text): + """ A write method for streams, that caches the stream in order + to avoid flooding the event loop. - def raw_input(self, prompt): - """ A replacement from python's raw_input. + This can be called outside of the main loop, in separate + threads. """ - self.new_prompt(prompt) - self.waiting = True - self.__old_on_enter = self._on_enter - def my_on_enter(): - self.waiting = False - self._on_enter = my_on_enter - # XXX: Busy waiting, ugly. - while self.waiting: - wx.Yield() - sleep(0.1) - self._on_enter = self.__old_on_enter - self._input_state = 'buffering' - return self.get_current_edit_buffer().rstrip('\n') - + self._out_buffer_lock.acquire() + self._out_buffer.append(text) + self._out_buffer_lock.release() + if not self._buffer_flush_timer.IsRunning(): + wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds + + + #-------------------------------------------------------------------------- + # LineFrontEnd interface + #-------------------------------------------------------------------------- def execute(self, python_string, raw_string=None): self._input_state = 'buffering' @@ -226,39 +270,10 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): def after_execute(self): PrefilterFrontEnd.after_execute(self) + # Clear the wait cursor if hasattr(self, '_cursor'): del self._cursor - - def system_call(self, command_string): - self._input_state = 'subprocess' - self._running_process = PipedProcess(command_string, - out_callback=self.buffered_write, - end_callback = self._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) - # Be sure to flush the buffer. - self._buffer_flush(event=None) - - - def buffered_write(self, text): - """ A write method for streams, that caches the stream in order - to avoid flooding the event loop. - - This can be called outside of the main loop, in separate - threads. - """ - self._out_buffer_lock.acquire() - self._out_buffer.append(text) - self._out_buffer_lock.release() - if not self._buffer_flush_timer.IsRunning(): - wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds - def show_traceback(self): start_line = self.GetCurrentLine() @@ -266,12 +281,19 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): wx.Yield() for i in range(start_line, self.GetCurrentLine()): self.MarkerAdd(i, _ERROR_MARKER) - + #-------------------------------------------------------------------------- - # Private API + # ConsoleWidget interface #-------------------------------------------------------------------------- - + + def new_prompt(self, prompt): + """ Display a new prompt, and start a new input buffer. + """ + self._input_state = 'readline' + ConsoleWidget.new_prompt(self, prompt) + + def _on_key_down(self, event, skip=True): """ Capture the character events, let the parent widget handle them, and put our logic afterward. @@ -286,8 +308,8 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): self._running_process.process.kill() elif self._input_state == 'buffering': if self.debug: - print >>sys.__stderr__, 'Raising KeyboardException' - raise KeyboardException + print >>sys.__stderr__, 'Raising KeyboardInterrupt' + raise KeyboardInterrupt # XXX: We need to make really sure we # get back to a prompt. elif self._input_state == 'subprocess' and ( @@ -315,10 +337,10 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): elif self.AutoCompActive(): event.Skip() if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE): - wx.CallAfter(self.popup_completion, create=True) + 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) + wx.CallAfter(self._popup_completion) else: # Up history if event.KeyCode == wx.WXK_UP and ( @@ -352,20 +374,28 @@ class WxController(PrefilterFrontEnd, ConsoleWidget): def _on_key_up(self, event, skip=True): + """ Called when any key is released. + """ if event.KeyCode in (59, ord('.')): # Intercepting '.' event.Skip() - self.popup_completion(create=True) + self._popup_completion(create=True) else: ConsoleWidget._on_key_up(self, event, skip=skip) def _on_enter(self): + """ Called on return key down, in readline input_state. + """ if self.debug: print >>sys.__stdout__, repr(self.get_current_edit_buffer()) PrefilterFrontEnd._on_enter(self) + #-------------------------------------------------------------------------- + # Private API + #-------------------------------------------------------------------------- + def _end_system_call(self): """ Called at the end of a system call. """ diff --git a/IPython/frontend/zopeinterface.py b/IPython/frontend/zopeinterface.py new file mode 100644 index 0000000..fd37101 --- /dev/null +++ b/IPython/frontend/zopeinterface.py @@ -0,0 +1,34 @@ +# encoding: utf-8 +# -*- test-case-name: IPython.frontend.tests.test_frontendbase -*- +""" +zope.interface mock. If zope is installed, this module provides a zope +interface classes, if not it provides mocks for them. + +Classes provided: +Interface, Attribute, implements, classProvides +""" +__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 string +import uuid +import _ast + +try: + from zope.interface import Interface, Attribute, implements, classProvides +except ImportError: + #zope.interface is not available + Interface = object + def Attribute(name, doc): pass + def implements(interface): pass + def classProvides(interface): pass + diff --git a/IPython/kernel/core/sync_traceback_trap.py b/IPython/kernel/core/sync_traceback_trap.py index 439d2b8..df9b0e5 100644 --- a/IPython/kernel/core/sync_traceback_trap.py +++ b/IPython/kernel/core/sync_traceback_trap.py @@ -22,19 +22,23 @@ from IPython.ultraTB import ColorTB class SyncTracebackTrap(TracebackTrap): - def __init__(self, sync_formatter=None, formatters=None): + def __init__(self, sync_formatter=None, formatters=None, + raiseException=True): TracebackTrap.__init__(self, formatters=formatters) if sync_formatter is None: sync_formatter = ColorTB(color_scheme='LightBG') self.sync_formatter = sync_formatter + self.raiseException = raiseException def hook(self, *args): """ This method actually implements the hook. """ self.args = args - - print self.sync_formatter(*self.args) + if not self.raiseException: + print self.sync_formatter(*self.args) + else: + raise