From 321357e1da5dbadfea7757922dff44bdf73c1cc5 2009-03-15 01:09:07 From: Fernando Perez Date: 2009-03-15 01:09:07 Subject: [PATCH] Merging Laurent's WX branch, reviewed by Gael. The next commit will fix line-ending problems, before pushing to Launchpad. --- diff --git a/IPython/gui/wx/ipshell_nonblocking.py b/IPython/gui/wx/ipshell_nonblocking.py index be9d47b..def47c5 100644 --- a/IPython/gui/wx/ipshell_nonblocking.py +++ b/IPython/gui/wx/ipshell_nonblocking.py @@ -62,11 +62,10 @@ class _Helper(object): ############################################################################## class _CodeExecutor(ThreadEx): ''' Thread that execute ipython code ''' - def __init__(self, instance, after): + def __init__(self, instance): ThreadEx.__init__(self) self.instance = instance - self._afterExecute = after - + def run(self): '''Thread main loop''' try: @@ -74,7 +73,7 @@ class _CodeExecutor(ThreadEx): self.instance._help_text = None self.instance._execute() # used for uper class to generate event after execution - self._afterExecute() + self.instance._after_execute() except KeyboardInterrupt: pass @@ -114,8 +113,7 @@ class NonBlockingIPShell(object): ''' #ipython0 initialisation self._IP = None - self._term = None - self.initIpython0(argv, user_ns, user_global_ns, + self.init_ipython0(argv, user_ns, user_global_ns, cin, cout, cerr, ask_exit_handler) @@ -127,33 +125,32 @@ class NonBlockingIPShell(object): #thread working vars self._line_to_execute = '' - + self._threading = True + #vars that will be checked by GUI loop to handle thread states... #will be replaced later by PostEvent GUI funtions... self._doc_text = None self._help_text = None self._add_button = None - def initIpython0(self, argv=[], user_ns={}, user_global_ns=None, + def init_ipython0(self, argv=[], user_ns={}, user_global_ns=None, cin=None, cout=None, cerr=None, ask_exit_handler=None): - ''' Initialize an ithon0 instance ''' + ''' Initialize an ipython0 instance ''' - #first we redefine in/out/error functions of IPython + #first we redefine in/out/error functions of IPython + #BUG: we've got a limitation form ipython0 there + #only one instance can be instanciated else tehre will be + #cin/cout/cerr clash... if cin: - IPython.Shell.Term.cin = cin + IPython.genutils.Term.cin = cin if cout: - IPython.Shell.Term.cout = cout + IPython.genutils.Term.cout = cout if cerr: - IPython.Shell.Term.cerr = cerr + IPython.genutils.Term.cerr = cerr - # This is to get rid of the blockage that accurs during - # IPython.Shell.InteractiveShell.user_setup() - IPython.iplib.raw_input = lambda x: None - - self._term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) - excepthook = sys.excepthook + #Hack to save sys.displayhook, because ipython seems to overwrite it... self.sys_displayhook_ori = sys.displayhook @@ -163,7 +160,8 @@ class NonBlockingIPShell(object): embedded=True, shell_class=IPython.Shell.InteractiveShell) - #we restore sys.displayhook + #we save ipython0 displayhook and we restore sys.displayhook + self.displayhook = sys.displayhook sys.displayhook = self.sys_displayhook_ori #we replace IPython default encoding by wx locale encoding @@ -173,11 +171,12 @@ class NonBlockingIPShell(object): #we replace the ipython default pager by our pager self._IP.set_hook('show_in_pager', self._pager) - #we replace the ipython default shell command caller by our shell handler + #we replace the ipython default shell command caller + #by our shell handler self._IP.set_hook('shell_hook', self._shell) #we replace the ipython default input command caller by our method - IPython.iplib.raw_input_original = self._raw_input + IPython.iplib.raw_input_original = self._raw_input_original #we replace the ipython default exit command by our method self._IP.exit = ask_exit_handler #we replace the help command @@ -186,26 +185,68 @@ class NonBlockingIPShell(object): #we disable cpase magic... until we found a way to use it properly. #import IPython.ipapi ip = IPython.ipapi.get() - def bypassMagic(self, arg): + def bypass_magic(self, arg): print '%this magic is currently disabled.' - ip.expose_magic('cpaste', bypassMagic) + ip.expose_magic('cpaste', bypass_magic) + + import __builtin__ + __builtin__.raw_input = self._raw_input sys.excepthook = excepthook - #----------------------- Thread management section ---------------------- - def doExecute(self, line): + #----------------------- Thread management section ---------------------- + def do_execute(self, line): """ Tell the thread to process the 'line' command """ self._line_to_execute = line - #we launch the ipython line execution in a thread to make it interruptible - #with include it in self namespace to be able to call ce.raise_exc(KeyboardInterrupt) - self.ce = _CodeExecutor(self, self._afterExecute) - self.ce.start() - - #----------------------- IPython management section ---------------------- - def getDocText(self): + + if self._threading: + #we launch the ipython line execution in a thread to make it + #interruptible with include it in self namespace to be able + #to call ce.raise_exc(KeyboardInterrupt) + self.ce = _CodeExecutor(self) + self.ce.start() + else: + try: + self._doc_text = None + self._help_text = None + self._execute() + # used for uper class to generate event after execution + self._after_execute() + + except KeyboardInterrupt: + pass + + #----------------------- IPython management section ---------------------- + def get_threading(self): + """ + Returns threading status, is set to True, then each command sent to + the interpreter will be executed in a separated thread allowing, + for example, breaking a long running commands. + Disallowing it, permits better compatibilty with instance that is embedding + IPython instance. + + @return: Execution method + @rtype: bool + """ + return self._threading + + def set_threading(self, state): + """ + Sets threading state, if set to True, then each command sent to + the interpreter will be executed in a separated thread allowing, + for example, breaking a long running commands. + Disallowing it, permits better compatibilty with instance that is embedding + IPython instance. + + @param state: Sets threading state + @type bool + """ + self._threading = state + + def get_doc_text(self): """ Returns the output of the processing that need to be paged (if any) @@ -214,7 +255,7 @@ class NonBlockingIPShell(object): """ return self._doc_text - def getHelpText(self): + def get_help_text(self): """ Returns the output of the processing that need to be paged via help pager(if any) @@ -223,7 +264,7 @@ class NonBlockingIPShell(object): """ return self._help_text - def getBanner(self): + def get_banner(self): """ Returns the IPython banner for useful info on IPython instance @@ -232,7 +273,7 @@ class NonBlockingIPShell(object): """ return self._IP.BANNER - def getPromptCount(self): + def get_prompt_count(self): """ Returns the prompt number. Each time a user execute a line in the IPython shell the prompt count is increased @@ -242,7 +283,7 @@ class NonBlockingIPShell(object): """ return self._IP.outputcache.prompt_count - def getPrompt(self): + def get_prompt(self): """ Returns current prompt inside IPython instance (Can be In [...]: ot ...:) @@ -252,7 +293,7 @@ class NonBlockingIPShell(object): """ return self._prompt - def getIndentation(self): + def get_indentation(self): """ Returns the current indentation level Usefull to put the caret at the good start position if we want to do autoindentation. @@ -262,7 +303,7 @@ class NonBlockingIPShell(object): """ return self._IP.indent_current_nsp - def updateNamespace(self, ns_dict): + def update_namespace(self, ns_dict): ''' Add the current dictionary to the shell namespace. @@ -286,7 +327,7 @@ class NonBlockingIPShell(object): possibilities = self._IP.complete(split_line[-1]) if possibilities: - def _commonPrefix(str1, str2): + def _common_prefix(str1, str2): ''' Reduction function. returns common prefix of two given strings. @@ -302,13 +343,13 @@ class NonBlockingIPShell(object): if not str2.startswith(str1[:i+1]): return str1[:i] return str1 - common_prefix = reduce(_commonPrefix, possibilities) + common_prefix = reduce(_common_prefix, possibilities) completed = line[:-len(split_line[-1])]+common_prefix else: completed = line return completed, possibilities - def historyBack(self): + def history_back(self): ''' Provides one history command back. @@ -320,10 +361,10 @@ class NonBlockingIPShell(object): while((history == '' or history == '\n') and self._history_level >0): if self._history_level >= 1: self._history_level -= 1 - history = self._getHistory() + history = self._get_history() return history - def historyForward(self): + def history_forward(self): ''' Provides one history command forward. @@ -333,38 +374,38 @@ class NonBlockingIPShell(object): history = '' #the below while loop is used to suppress empty history lines while((history == '' or history == '\n') \ - and self._history_level <= self._getHistoryMaxIndex()): - if self._history_level < self._getHistoryMaxIndex(): + and self._history_level <= self._get_history_max_index()): + if self._history_level < self._get_history_max_index(): self._history_level += 1 - history = self._getHistory() + history = self._get_history() else: - if self._history_level == self._getHistoryMaxIndex(): - history = self._getHistory() + if self._history_level == self._get_history_max_index(): + history = self._get_history() self._history_level += 1 else: history = '' return history - def initHistoryIndex(self): + def init_history_index(self): ''' set history to last command entered ''' - self._history_level = self._getHistoryMaxIndex()+1 + self._history_level = self._get_history_max_index()+1 #----------------------- IPython PRIVATE management section -------------- - def _afterExecute(self): + def _after_execute(self): ''' Can be redefined to generate post event after excution is done ''' pass - #def _askExit(self): - # ''' - # Can be redefined to generate post event to exit the Ipython shell - # ''' - # pass + def _ask_exit(self): + ''' + Can be redefined to generate post event to exit the Ipython shell + ''' + pass - def _getHistoryMaxIndex(self): + def _get_history_max_index(self): ''' returns the max length of the history buffer @@ -373,7 +414,7 @@ class NonBlockingIPShell(object): ''' return len(self._IP.input_hist_raw)-1 - def _getHistory(self): + def _get_history(self): ''' Get's the command string of the current history level. @@ -388,7 +429,7 @@ class NonBlockingIPShell(object): This function is used as a callback replacment to IPython help pager function It puts the 'text' value inside the self._help_text string that can be retrived via - getHelpText function. + get_help_text function. ''' if self._help_text == None: self._help_text = text @@ -400,11 +441,11 @@ class NonBlockingIPShell(object): This function is used as a callback replacment to IPython pager function It puts the 'text' value inside the self._doc_text string that can be retrived via - getDocText function. + get_doc_text function. ''' self._doc_text = text - def _raw_input(self, prompt=''): + def _raw_input_original(self, prompt=''): ''' Custom raw_input() replacement. Get's current line from console buffer. @@ -416,13 +457,21 @@ class NonBlockingIPShell(object): ''' return self._line_to_execute + def _raw_input(self, prompt=''): + """ A replacement from python's raw_input. + """ + raise NotImplementedError + def _execute(self): ''' Executes the current line provided by the shell object. ''' + orig_stdout = sys.stdout sys.stdout = IPython.Shell.Term.cout - + #self.sys_displayhook_ori = sys.displayhook + #sys.displayhook = self.displayhook + try: line = self._IP.raw_input(None, self._iter_more) if self._IP.autoindent: @@ -440,8 +489,10 @@ class NonBlockingIPShell(object): except: self._IP.showtraceback() else: + self._IP.write(str(self._IP.outputcache.prompt_out).strip()) self._iter_more = self._IP.push(line) - if (self._IP.SyntaxTB.last_syntax_error and self._IP.rc.autoedit_syntax): + if (self._IP.SyntaxTB.last_syntax_error and \ + self._IP.rc.autoedit_syntax): self._IP.edit_syntax_error() if self._iter_more: self._prompt = str(self._IP.outputcache.prompt2).strip() @@ -450,8 +501,10 @@ class NonBlockingIPShell(object): else: self._prompt = str(self._IP.outputcache.prompt1).strip() self._IP.indent_current_nsp = 0 #we set indentation to 0 + sys.stdout = orig_stdout - + #sys.displayhook = self.sys_displayhook_ori + def _shell(self, ip, cmd): ''' Replacement method to allow shell commands without them blocking. @@ -462,7 +515,8 @@ class NonBlockingIPShell(object): @type cmd: string ''' stdin, stdout = os.popen4(cmd) - result = stdout.read().decode('cp437').encode(locale.getpreferredencoding()) + result = stdout.read().decode('cp437').\ + encode(locale.getpreferredencoding()) #we use print command because the shell command is called #inside IPython instance and thus is redirected to thread cout #"\x01\x1b[1;36m\x02" <-- add colour to the text... diff --git a/IPython/gui/wx/ipython_history.py b/IPython/gui/wx/ipython_history.py index 5053886..91f8769 100644 --- a/IPython/gui/wx/ipython_history.py +++ b/IPython/gui/wx/ipython_history.py @@ -29,17 +29,21 @@ class IPythonHistoryPanel(wx.Panel): self.filter_magic = wx.CheckBox(self, -1, "%: Magic keys") self.options={'filter_empty':{'value':'True', - 'checkbox':self.filter_empty,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_empty, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, 'filter_doc':{'value':'True', - 'checkbox':self.filter_doc,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_doc, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, 'filter_cmd':{'value':'True', - 'checkbox':self.filter_cmd,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_cmd, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, 'filter_magic':{'value':'True', - 'checkbox':self.filter_magic,'True':True,'False':False, - 'setfunc':lambda x:None}, + 'checkbox':self.filter_magic, \ + 'True':True,'False':False, + 'setfunc':lambda x:None}, } self.reloadOptions(self.options) @@ -199,51 +203,81 @@ class PythonSTC(stc.StyledTextCtrl): self.SetLayoutCache(stc.STC_CACHE_PAGE) # Setup a margin to hold fold markers - #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? + #self.SetFoldFlags(16) + ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) self.SetMarginMask(2, stc.STC_MASK_FOLDERS) self.SetMarginSensitive(2, True) self.SetMarginWidth(2, 12) if self.fold_symbols == 0: - # Arrow pointing right for contracted folders, arrow pointing down for expanded - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") + # Arrow pointing right for contracted folders, + # arrow pointing down for expanded + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_ARROWDOWN, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_ARROW, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_EMPTY, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_EMPTY, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_EMPTY, "white", "black") elif self.fold_symbols == 1: # Plus for contracted folders, minus for expanded - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_MINUS, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_PLUS, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_EMPTY, "white", "black") elif self.fold_symbols == 2: # Like a flattened tree control using circular headers and curved joins - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_CIRCLEMINUS, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_CIRCLEPLUS, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_VLINE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_LCORNERCURVE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_TCORNERCURVE, "white", "#404040") elif self.fold_symbols == 3: # Like a flattened tree control using square headers - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \ + stc.STC_MARK_BOXMINUS, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \ + stc.STC_MARK_BOXPLUS, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \ + stc.STC_MARK_VLINE, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \ + stc.STC_MARK_LCORNER, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \ + stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \ + stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \ + stc.STC_MARK_TCORNER, "white", "#808080") self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI) @@ -363,7 +397,7 @@ class PythonSTC(stc.StyledTextCtrl): if braceAtCaret < 0: charAfter = self.GetCharAt(caretPos) styleAfter = self.GetStyleAt(caretPos) - + if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: braceAtCaret = caretPos diff --git a/IPython/gui/wx/ipython_view.py b/IPython/gui/wx/ipython_view.py index c189780..c9c5f4b 100644 --- a/IPython/gui/wx/ipython_view.py +++ b/IPython/gui/wx/ipython_view.py @@ -1,885 +1,944 @@ -#!/usr/bin/python -# -*- coding: iso-8859-15 -*- -''' -Provides IPython WX console widgets. - -@author: Laurent Dufrechou -laurent.dufrechou _at_ gmail.com -This WX widget is based on the original work of Eitan Isaacson -that provided the console for the GTK toolkit. - -Original work from: -@author: Eitan Isaacson -@organization: IBM Corporation -@copyright: Copyright (c) 2007 IBM Corporation -@license: BSD - -All rights reserved. This program and the accompanying materials are made -available under the terms of the BSD which accompanies this distribution, and -is available at U{http://www.opensource.org/licenses/bsd-license.php} -''' - -__version__ = 0.8 -__author__ = "Laurent Dufrechou" -__email__ = "laurent.dufrechou _at_ gmail.com" -__license__ = "BSD" - -import wx -import wx.stc as stc - -import re -from StringIO import StringIO - -import sys -import codecs -import locale -for enc in (locale.getpreferredencoding(), - sys.getfilesystemencoding(), - sys.getdefaultencoding()): - try: - codecs.lookup(enc) - ENCODING = enc - break - except LookupError: - pass -else: - ENCODING = 'utf-8' - -from ipshell_nonblocking import NonBlockingIPShell - -class WxNonBlockingIPShell(NonBlockingIPShell): - ''' - An NonBlockingIPShell Thread that is WX dependent. - ''' - def __init__(self, parent, - argv=[],user_ns={},user_global_ns=None, - cin=None, cout=None, cerr=None, - ask_exit_handler=None): - - NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns, - cin, cout, cerr, - ask_exit_handler) - - self.parent = parent - - self.ask_exit_callback = ask_exit_handler - self._IP.exit = self._askExit - - def addGUIShortcut(self, text, func): - wx.CallAfter(self.parent.add_button_handler, - button_info={ 'text':text, - 'func':self.parent.doExecuteLine(func)}) - - def _askExit(self): - wx.CallAfter(self.ask_exit_callback, ()) - - def _afterExecute(self): - wx.CallAfter(self.parent.evtStateExecuteDone, ()) - - -class WxConsoleView(stc.StyledTextCtrl): - ''' - Specialized styled text control view for console-like workflow. - We use here a scintilla frontend thus it can be reused in any GUI that - supports scintilla with less work. - - @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names. - (with Black background) - @type ANSI_COLORS_BLACK: dictionary - - @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names. - (with White background) - @type ANSI_COLORS_WHITE: dictionary - - @ivar color_pat: Regex of terminal color pattern - @type color_pat: _sre.SRE_Pattern - ''' - ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'], - '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], - '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], - '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], - '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], - '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], - '1;34': [12, 'LIGHT BLUE'], '1;35': - [13, 'MEDIUM VIOLET RED'], - '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} - - ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'], - '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], - '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], - '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], - '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], - '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], - '1;34': [12, 'LIGHT BLUE'], '1;35': - [13, 'MEDIUM VIOLET RED'], - '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} - - def __init__(self, parent, prompt, intro="", background_color="BLACK", - pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize, - style=0, autocomplete_mode = 'IPYTHON'): - ''' - Initialize console view. - - @param parent: Parent widget - @param prompt: User specified prompt - @type intro: string - @param intro: User specified startup introduction string - @type intro: string - @param background_color: Can be BLACK or WHITE - @type background_color: string - @param other: init param of styledTextControl (can be used as-is) - @param autocomplete_mode: Can be 'IPYTHON' or 'STC' - 'IPYTHON' show autocompletion the ipython way - 'STC" show it scintilla text control way - ''' - stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style) - - ####### Scintilla configuration ################################### - - # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside - # the widget - self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) - self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) - - #We draw a line at position 80 - self.SetEdgeMode(stc.STC_EDGE_LINE) - self.SetEdgeColumn(80) - self.SetEdgeColour(wx.LIGHT_GREY) - - #self.SetViewWhiteSpace(True) - #self.SetViewEOL(True) - 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() - - 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.background_color = background_color - self.buildStyles() - - self.indent = 0 - self.prompt_count = 0 - self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') - - self.write(intro) - self.setPrompt(prompt) - self.showPrompt() - - self.autocomplete_mode = autocomplete_mode - - self.Bind(wx.EVT_KEY_DOWN, self._onKeypress) - - def buildStyles(self): - #we define platform specific fonts - if wx.Platform == '__WXMSW__': - faces = { 'times': 'Times New Roman', - 'mono' : 'Courier New', - 'helv' : 'Arial', - 'other': 'Comic Sans MS', - 'size' : 10, - 'size2': 8, - } - elif wx.Platform == '__WXMAC__': - faces = { 'times': 'Times New Roman', - 'mono' : 'Monaco', - 'helv' : 'Arial', - 'other': 'Comic Sans MS', - 'size' : 10, - 'size2': 8, - } - else: - faces = { 'times': 'Times', - 'mono' : 'Courier', - 'helv' : 'Helvetica', - 'other': 'new century schoolbook', - 'size' : 10, - 'size2': 8, - } - - # make some styles - if self.background_color != "BLACK": - self.background_color = "WHITE" - self.SetCaretForeground("BLACK") - self.ANSI_STYLES = self.ANSI_STYLES_WHITE - else: - self.SetCaretForeground("WHITE") - self.ANSI_STYLES = self.ANSI_STYLES_BLACK - - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, - "fore:%s,back:%s,size:%d,face:%s" - % (self.ANSI_STYLES['0;30'][1], - self.background_color, - faces['size'], faces['mono'])) - 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 setBackgroundColor(self, color): - self.background_color = color - self.buildStyles() - - def getBackgroundColor(self, color): - return self.background_color - - def asyncWrite(self, text): - ''' - Write given text to buffer in an asynchroneous way. - It is used from another thread to be able to acces the GUI. - @param text: Text to append - @type text: string - ''' - try: - #print >>sys.__stdout__,'entering' - wx.MutexGuiEnter() - #print >>sys.__stdout__,'locking the GUI' - - #be sure not to be interrutpted before the MutexGuiLeave! - self.write(text) - - #print >>sys.__stdout__,'done' - - except KeyboardInterrupt: - #print >>sys.__stdout__,'got keyboard interrupt' - wx.MutexGuiLeave() - #print >>sys.__stdout__,'interrupt unlock the GUI' - raise KeyboardInterrupt - wx.MutexGuiLeave() - #print >>sys.__stdout__,'normal unlock the GUI' - - - def write(self, text): - ''' - Write given text to buffer. - - @param text: Text to append. - @type text: string - ''' - segments = self.color_pat.split(text) - segment = segments.pop(0) - self.StartStyling(self.getCurrentLineEnd(), 0xFF) - self.AppendText(segment) - - if segments: - ansi_tags = self.color_pat.findall(text) - - for tag in ansi_tags: - i = segments.index(tag) - self.StartStyling(self.getCurrentLineEnd(), 0xFF) - self.AppendText(segments[i+1]) - - if tag != '0': - self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0]) - - segments.pop(i) - - self.moveCursor(self.getCurrentLineEnd()) - - def getPromptLen(self): - ''' - Return the length of current prompt - ''' - return len(str(self.prompt_count)) + 7 - - def setPrompt(self, prompt): - self.prompt = prompt - - def setIndentation(self, indentation): - self.indent = indentation - - def setPromptCount(self, count): - self.prompt_count = count - - def showPrompt(self): - ''' - Prints prompt at start of line. - - @param prompt: Prompt to print. - @type prompt: string - ''' - self.write(self.prompt) - #now we update the position of end of prompt - self.current_start = self.getCurrentLineEnd() - - autoindent = self.indent*' ' - autoindent = autoindent.replace(' ','\t') - self.write(autoindent) - - def changeLine(self, text): - ''' - Replace currently entered command line with given text. - - @param text: Text to use as replacement. - @type text: string - ''' - self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd()) - self.ReplaceSelection(text) - self.moveCursor(self.getCurrentLineEnd()) - - def getCurrentPromptStart(self): - return self.current_start - - def getCurrentLineStart(self): - return self.GotoLine(self.LineFromPosition(self.GetCurrentPos())) - - def getCurrentLineEnd(self): - return self.GetLength() - - def getCurrentLine(self): - ''' - Get text in current command line. - - @return: Text of current command line. - @rtype: string - ''' - return self.GetTextRange(self.getCurrentPromptStart(), - self.getCurrentLineEnd()) - - def moveCursorOnNewValidKey(self): - #If cursor is at wrong position put it at last line... - if self.GetCurrentPos() < self.getCurrentPromptStart(): - self.GotoPos(self.getCurrentPromptStart()) - - def removeFromTo(self, from_pos, to_pos): - if from_pos < to_pos: - self.SetSelection(from_pos, to_pos) - self.DeleteBack() - - def removeCurrentLine(self): - self.LineDelete() - - def moveCursor(self, position): - self.GotoPos(position) - - def getCursorPos(self): - return self.GetCurrentPos() - - def selectFromTo(self, from_pos, to_pos): - self.SetSelectionStart(from_pos) - self.SetSelectionEnd(to_pos) - - def writeHistory(self, history): - self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd()) - self.changeLine(history) - - def setCompletionMethod(self, completion): - if completion in ['IPYTHON', 'STC']: - self.autocomplete_mode = completion - else: - raise AttributeError - - def getCompletionMethod(self, completion): - return self.autocomplete_mode - - def writeCompletion(self, possibilities): - if self.autocomplete_mode == 'IPYTHON': - max_len = len(max(possibilities, key=len)) - max_symbol = ' '*max_len - - #now we check how much symbol we can put on a line... - test_buffer = max_symbol + ' '*4 - - allowed_symbols = 80/len(test_buffer) - if allowed_symbols == 0: - allowed_symbols = 1 - - pos = 1 - buf = '' - for symbol in possibilities: - #buf += symbol+'\n'#*spaces) - if pos < allowed_symbols: - spaces = max_len - len(symbol) + 4 - buf += symbol+' '*spaces - pos += 1 - else: - buf += symbol+'\n' - pos = 1 - self.write(buf) - else: - possibilities.sort() # Python sorts are case sensitive - self.AutoCompSetIgnoreCase(False) - self.AutoCompSetAutoHide(False) - #let compute the length ot last word - splitter = [' ', '(', '[', '{'] - last_word = self.getCurrentLine() - for breaker in splitter: - last_word = last_word.split(breaker)[-1] - self.AutoCompShow(len(last_word), " ".join(possibilities)) - - def _onKeypress(self, event, skip=True): - ''' - Key press callback used for correcting behavior for console-like - interfaces. For example 'home' should go to prompt, not to begining of - line. - - @param widget: Widget that key press accored in. - @type widget: gtk.Widget - @param event: Event object - @type event: gtk.gdk.Event - - @return: Return True if event as been catched. - @rtype: boolean - ''' - - if not self.AutoCompActive(): - if event.GetKeyCode() == wx.WXK_HOME: - if event.Modifiers == wx.MOD_NONE: - self.moveCursorOnNewValidKey() - self.moveCursor(self.getCurrentPromptStart()) - return True - elif event.Modifiers == wx.MOD_SHIFT: - self.moveCursorOnNewValidKey() - self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos()) - return True - else: - return False - - elif event.GetKeyCode() == wx.WXK_LEFT: - if event.Modifiers == wx.MOD_NONE: - self.moveCursorOnNewValidKey() - - self.moveCursor(self.getCursorPos()-1) - if self.getCursorPos() < self.getCurrentPromptStart(): - self.moveCursor(self.getCurrentPromptStart()) - return True - - elif event.GetKeyCode() == wx.WXK_BACK: - self.moveCursorOnNewValidKey() - if self.getCursorPos() > self.getCurrentPromptStart(): - event.Skip() - return True - - if skip: - if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\ - and event.Modifiers == wx.MOD_NONE: - self.moveCursorOnNewValidKey() - - event.Skip() - return True - return False - else: - event.Skip() - - def OnUpdateUI(self, evt): - # check for matching braces - braceAtCaret = -1 - braceOpposite = -1 - charBefore = None - caretPos = self.GetCurrentPos() - - if caretPos > 0: - charBefore = self.GetCharAt(caretPos - 1) - styleBefore = self.GetStyleAt(caretPos - 1) - - # check before - if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR: - braceAtCaret = caretPos - 1 - - # check after - if braceAtCaret < 0: - charAfter = self.GetCharAt(caretPos) - styleAfter = self.GetStyleAt(caretPos) - - if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: - braceAtCaret = caretPos - - if braceAtCaret >= 0: - braceOpposite = self.BraceMatch(braceAtCaret) - - if braceAtCaret != -1 and braceOpposite == -1: - self.BraceBadLight(braceAtCaret) - else: - self.BraceHighlight(braceAtCaret, braceOpposite) - #pt = self.PointFromPosition(braceOpposite) - #self.Refresh(True, wxRect(pt.x, pt.y, 5,5)) - #print pt - #self.Refresh(False) - -class IPShellWidget(wx.Panel): - ''' - This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl - If you want to port this to any other GUI toolkit, just replace the - WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate - from whatever container you want. I've choosed to derivate from a wx.Panel - because it seems to be more useful - Any idea to make it more 'generic' welcomed. - ''' - - def __init__(self, parent, intro=None, - background_color="BLACK", add_button_handler=None, - wx_ip_shell=None, user_ns={},user_global_ns=None, - ): - ''' - Initialize. - Instanciate an IPython thread. - Instanciate a WxConsoleView. - Redirect I/O to console. - ''' - wx.Panel.__init__(self,parent,wx.ID_ANY) - - self.parent = parent - ### IPython non blocking shell instanciation ### - self.cout = StringIO() - self.add_button_handler = add_button_handler - - if wx_ip_shell is not None: - self.IP = wx_ip_shell - else: - self.IP = WxNonBlockingIPShell(self, - cout = self.cout, cerr = self.cout, - ask_exit_handler = self.askExitCallback) - - ### IPython wx console view instanciation ### - #If user didn't defined an intro text, we create one for him - #If you really wnat an empty intro just call wxIPythonViewPanel - #with intro='' - if intro is None: - welcome_text = "Welcome to WxIPython Shell.\n\n" - welcome_text+= self.IP.getBanner() - welcome_text+= "!command -> Execute command in shell\n" - welcome_text+= "TAB -> Autocompletion\n" - else: - welcome_text = intro - - self.text_ctrl = WxConsoleView(self, - self.IP.getPrompt(), - intro=welcome_text, - background_color=background_color) - - self.cout.write = self.text_ctrl.asyncWrite - - option_text = wx.StaticText(self, -1, "Options:") - self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion") - #self.completion_option.SetValue(False) - self.background_option = wx.CheckBox(self, -1, "White Background") - #self.background_option.SetValue(False) - - self.options={'completion':{'value':'IPYTHON', - 'checkbox':self.completion_option,'STC':True,'IPYTHON':False, - 'setfunc':self.text_ctrl.setCompletionMethod}, - 'background_color':{'value':'BLACK', - 'checkbox':self.background_option,'WHITE':True,'BLACK':False, - 'setfunc':self.text_ctrl.setBackgroundColor}, - } - self.reloadOptions(self.options) - - self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress) - self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion) - self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor) - - ### making the layout of the panel ### - sizer = wx.BoxSizer(wx.VERTICAL) - sizer.Add(self.text_ctrl, 1, wx.EXPAND) - option_sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(option_sizer, 0) - option_sizer.AddMany([(10, 20), - (option_text, 0, wx.ALIGN_CENTER_VERTICAL), - (5, 5), - (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL), - (8, 8), - (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL) - ]) - self.SetAutoLayout(True) - sizer.Fit(self) - sizer.SetSizeHints(self) - self.SetSizer(sizer) - #and we focus on the widget :) - self.SetFocus() - - #widget state management (for key handling different cases) - self.setCurrentState('IDLE') - self.pager_state = 'DONE' - self.raw_input_current_line = 0 - - def askExitCallback(self, event): - self.askExitHandler(event) - - #---------------------- IPython Thread Management ------------------------ - def stateDoExecuteLine(self): - lines=self.text_ctrl.getCurrentLine() - self.text_ctrl.write('\n') - lines_to_execute = lines.replace('\t',' '*4) - lines_to_execute = lines_to_execute.replace('\r','') - self.IP.doExecute(lines_to_execute.encode(ENCODING)) - self.updateHistoryTracker(lines) - self.setCurrentState('WAIT_END_OF_EXECUTION') - - def evtStateExecuteDone(self,evt): - self.doc = self.IP.getDocText() - self.help = self.IP.getHelpText() - if self.doc: - self.pager_lines = self.doc[7:].split('\n') - self.pager_state = 'INIT' - self.setCurrentState('SHOW_DOC') - self.pager(self.doc) - elif self.help: - self.pager_lines = self.help.split('\n') - self.pager_state = 'INIT' - self.setCurrentState('SHOW_DOC') - self.pager(self.help) - else: - self.stateShowPrompt() - - def stateShowPrompt(self): - self.setCurrentState('SHOW_PROMPT') - self.text_ctrl.setPrompt(self.IP.getPrompt()) - self.text_ctrl.setIndentation(self.IP.getIndentation()) - self.text_ctrl.setPromptCount(self.IP.getPromptCount()) - self.text_ctrl.showPrompt() - self.IP.initHistoryIndex() - self.setCurrentState('IDLE') - - def setCurrentState(self, state): - self.cur_state = state - self.updateStatusTracker(self.cur_state) - - def pager(self,text): - - if self.pager_state == 'INIT': - #print >>sys.__stdout__,"PAGER state:",self.pager_state - self.pager_nb_lines = len(self.pager_lines) - self.pager_index = 0 - self.pager_do_remove = False - self.text_ctrl.write('\n') - self.pager_state = 'PROCESS_LINES' - - if self.pager_state == 'PROCESS_LINES': - #print >>sys.__stdout__,"PAGER state:",self.pager_state - if self.pager_do_remove == True: - self.text_ctrl.removeCurrentLine() - self.pager_do_remove = False - - if self.pager_nb_lines > 10: - #print >>sys.__stdout__,"PAGER processing 10 lines" - if self.pager_index > 0: - self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n') - else: - self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n') - - for line in self.pager_lines[self.pager_index+1:self.pager_index+9]: - self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n') - self.pager_index += 10 - self.pager_nb_lines -= 10 - self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---") - self.pager_do_remove = True - self.pager_state = 'WAITING' - return - else: - #print >>sys.__stdout__,"PAGER processing last lines" - if self.pager_nb_lines > 0: - if self.pager_index > 0: - self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n') - else: - self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n') - - self.pager_index += 1 - self.pager_nb_lines -= 1 - if self.pager_nb_lines > 0: - for line in self.pager_lines[self.pager_index:]: - self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n') - self.pager_nb_lines = 0 - self.pager_state = 'DONE' - self.stateShowPrompt() - - #------------------------ Key Handler ------------------------------------ - def keyPress(self, event): - ''' - Key press callback with plenty of shell goodness, like history, - autocompletions, etc. - ''' - if event.GetKeyCode() == ord('C'): - if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT: - if self.cur_state == 'WAIT_END_OF_EXECUTION': - #we raise an exception inside the IPython thread container - self.IP.ce.raise_exc(KeyboardInterrupt) - return - - #let this before 'wx.WXK_RETURN' because we have to put 'IDLE' - #mode if AutoComp has been set as inactive - if self.cur_state == 'COMPLETING': - if not self.text_ctrl.AutoCompActive(): - self.cur_state = 'IDLE' - else: - event.Skip() - - if event.KeyCode == wx.WXK_RETURN: - if self.cur_state == 'IDLE': - #we change the state ot the state machine - self.setCurrentState('DO_EXECUTE_LINE') - self.stateDoExecuteLine() - return - - if self.pager_state == 'WAITING': - self.pager_state = 'PROCESS_LINES' - self.pager(self.doc) - return - - if self.cur_state == 'WAITING_USER_INPUT': - line=self.text_ctrl.getCurrentLine() - self.text_ctrl.write('\n') - self.setCurrentState('WAIT_END_OF_EXECUTION') - return - - if event.GetKeyCode() in [ord('q'),ord('Q')]: - if self.pager_state == 'WAITING': - self.pager_state = 'DONE' - self.text_ctrl.write('\n') - self.stateShowPrompt() - return - - if self.cur_state == 'WAITING_USER_INPUT': - event.Skip() - - if self.cur_state == 'IDLE': - if event.KeyCode == wx.WXK_UP: - history = self.IP.historyBack() - self.text_ctrl.writeHistory(history) - return - if event.KeyCode == wx.WXK_DOWN: - history = self.IP.historyForward() - self.text_ctrl.writeHistory(history) - return - if event.KeyCode == wx.WXK_TAB: - #if line empty we disable tab completion - if not self.text_ctrl.getCurrentLine().strip(): - self.text_ctrl.write('\t') - return - completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine()) - if len(possibilities) > 1: - if self.text_ctrl.autocomplete_mode == 'IPYTHON': - cur_slice = self.text_ctrl.getCurrentLine() - self.text_ctrl.write('\n') - self.text_ctrl.writeCompletion(possibilities) - self.text_ctrl.write('\n') - - self.text_ctrl.showPrompt() - self.text_ctrl.write(cur_slice) - self.text_ctrl.changeLine(completed or cur_slice) - else: - self.cur_state = 'COMPLETING' - self.text_ctrl.writeCompletion(possibilities) - else: - self.text_ctrl.changeLine(completed or cur_slice) - return - event.Skip() - - #------------------------ Option Section --------------------------------- - def evtCheckOptionCompletion(self, event): - if event.IsChecked(): - self.options['completion']['value']='STC' - else: - self.options['completion']['value']='IPYTHON' - self.text_ctrl.setCompletionMethod(self.options['completion']['value']) - self.updateOptionTracker('completion', - self.options['completion']['value']) - self.text_ctrl.SetFocus() - - def evtCheckOptionBackgroundColor(self, event): - if event.IsChecked(): - self.options['background_color']['value']='WHITE' - else: - self.options['background_color']['value']='BLACK' - self.text_ctrl.setBackgroundColor(self.options['background_color']['value']) - self.updateOptionTracker('background_color', - self.options['background_color']['value']) - self.text_ctrl.SetFocus() - - def getOptions(self): - return self.options - - def reloadOptions(self,options): - self.options = options - for key in self.options.keys(): - value = self.options[key]['value'] - self.options[key]['checkbox'].SetValue(self.options[key][value]) - self.options[key]['setfunc'](value) - - - #------------------------ Hook Section ----------------------------------- - def updateOptionTracker(self,name,value): - ''' - Default history tracker (does nothing) - ''' - pass - - def setOptionTrackerHook(self,func): - ''' - Define a new history tracker - ''' - self.updateOptionTracker = func - - def updateHistoryTracker(self,command_line): - ''' - Default history tracker (does nothing) - ''' - pass - - def setHistoryTrackerHook(self,func): - ''' - Define a new history tracker - ''' - self.updateHistoryTracker = func - - def updateStatusTracker(self,status): - ''' - Default status tracker (does nothing) - ''' - pass - - def setStatusTrackerHook(self,func): - ''' - Define a new status tracker - ''' - self.updateStatusTracker = func - - def askExitHandler(self, event): - ''' - Default exit handler - ''' - self.text_ctrl.write('\nExit callback has not been set.') - - def setAskExitHandler(self, func): - ''' - Define an exit handler - ''' - self.askExitHandler = func - -if __name__ == '__main__': - # Some simple code to test the shell widget. - 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 = IPShellWidget(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.SetSize((780, 460)) - shell = frame.shell - - app.MainLoop() - - +#!/usr/bin/python +# -*- coding: iso-8859-15 -*- +''' +Provides IPython WX console widgets. + +@author: Laurent Dufrechou +laurent.dufrechou _at_ gmail.com +This WX widget is based on the original work of Eitan Isaacson +that provided the console for the GTK toolkit. + +Original work from: +@author: Eitan Isaacson +@organization: IBM Corporation +@copyright: Copyright (c) 2007 IBM Corporation +@license: BSD + +All rights reserved. This program and the accompanying materials are made +available under the terms of the BSD which accompanies this distribution, and +is available at U{http://www.opensource.org/licenses/bsd-license.php} +''' + +__version__ = 0.9 +__author__ = "Laurent Dufrechou" +__email__ = "laurent.dufrechou _at_ gmail.com" +__license__ = "BSD" + +import wx +import wx.stc as stc + +import re +from StringIO import StringIO + +import sys +import codecs +import locale +import time + +for enc in (locale.getpreferredencoding(), + sys.getfilesystemencoding(), + sys.getdefaultencoding()): + try: + codecs.lookup(enc) + ENCODING = enc + break + except LookupError: + pass +else: + ENCODING = 'utf-8' + +from ipshell_nonblocking import NonBlockingIPShell + +class WxNonBlockingIPShell(NonBlockingIPShell): + ''' + An NonBlockingIPShell Thread that is WX dependent. + ''' + def __init__(self, parent, + argv=[],user_ns={},user_global_ns=None, + cin=None, cout=None, cerr=None, + ask_exit_handler=None): + + NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns, + cin, cout, cerr, + ask_exit_handler) + + self.parent = parent + + self.ask_exit_callback = ask_exit_handler + self._IP.exit = self._ask_exit + + def addGUIShortcut(self, text, func): + wx.CallAfter(self.parent.add_button_handler, + button_info={ 'text':text, + 'func':self.parent.doExecuteLine(func)}) + + def _raw_input(self, prompt=''): + """ A replacement from python's raw_input. + """ + self.answer = None + if(self._threading == True): + wx.CallAfter(self._yesNoBox, prompt) + while self.answer is None: + time.sleep(.1) + else: + self._yesNoBox(prompt) + return self.answer + + def _yesNoBox(self, prompt): + """ yes/no box managed with wx.CallAfter jsut in case caler is executed in a thread""" + dlg = wx.TextEntryDialog( + self.parent, prompt, + 'Input requested', 'Python') + dlg.SetValue("") + + answer = '' + if dlg.ShowModal() == wx.ID_OK: + answer = dlg.GetValue() + + dlg.Destroy() + self.answer = answer + + def _ask_exit(self): + wx.CallAfter(self.ask_exit_callback, ()) + + def _after_execute(self): + wx.CallAfter(self.parent.evtStateExecuteDone, ()) + + +class WxConsoleView(stc.StyledTextCtrl): + ''' + Specialized styled text control view for console-like workflow. + We use here a scintilla frontend thus it can be reused in any GUI that + supports scintilla with less work. + + @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names. + (with Black background) + @type ANSI_COLORS_BLACK: dictionary + + @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names. + (with White background) + @type ANSI_COLORS_WHITE: dictionary + + @ivar color_pat: Regex of terminal color pattern + @type color_pat: _sre.SRE_Pattern + ''' + ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'], + '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], + '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], + '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], + '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], + '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], + '1;34': [12, 'LIGHT BLUE'], '1;35': + [13, 'MEDIUM VIOLET RED'], + '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} + + ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'], + '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], + '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], + '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], + '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], + '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], + '1;34': [12, 'LIGHT BLUE'], '1;35': + [13, 'MEDIUM VIOLET RED'], + '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} + + def __init__(self, parent, prompt, intro="", background_color="BLACK", + pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize, + style=0, autocomplete_mode = 'IPYTHON'): + ''' + Initialize console view. + + @param parent: Parent widget + @param prompt: User specified prompt + @type intro: string + @param intro: User specified startup introduction string + @type intro: string + @param background_color: Can be BLACK or WHITE + @type background_color: string + @param other: init param of styledTextControl (can be used as-is) + @param autocomplete_mode: Can be 'IPYTHON' or 'STC' + 'IPYTHON' show autocompletion the ipython way + 'STC" show it scintilla text control way + ''' + stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style) + + ####### Scintilla configuration ################################### + + # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside + # the widget + self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) + self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) + + #We draw a line at position 80 + self.SetEdgeMode(stc.STC_EDGE_LINE) + self.SetEdgeColumn(80) + self.SetEdgeColour(wx.LIGHT_GREY) + + #self.SetViewWhiteSpace(True) + #self.SetViewEOL(True) + 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() + + 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.background_color = background_color + self.buildStyles() + + self.indent = 0 + self.prompt_count = 0 + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + + self.write(intro) + self.setPrompt(prompt) + self.showPrompt() + + self.autocomplete_mode = autocomplete_mode + + self.Bind(wx.EVT_KEY_DOWN, self._onKeypress) + + def buildStyles(self): + #we define platform specific fonts + if wx.Platform == '__WXMSW__': + faces = { 'times': 'Times New Roman', + 'mono' : 'Courier New', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 10, + 'size2': 8, + } + elif wx.Platform == '__WXMAC__': + faces = { 'times': 'Times New Roman', + 'mono' : 'Monaco', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 10, + 'size2': 8, + } + else: + faces = { 'times': 'Times', + 'mono' : 'Courier', + 'helv' : 'Helvetica', + 'other': 'new century schoolbook', + 'size' : 10, + 'size2': 8, + } + + # make some styles + if self.background_color != "BLACK": + self.background_color = "WHITE" + self.SetCaretForeground("BLACK") + self.ANSI_STYLES = self.ANSI_STYLES_WHITE + else: + self.SetCaretForeground("WHITE") + self.ANSI_STYLES = self.ANSI_STYLES_BLACK + + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, + "fore:%s,back:%s,size:%d,face:%s" + % (self.ANSI_STYLES['0;30'][1], + self.background_color, + faces['size'], faces['mono'])) + 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 setBackgroundColor(self, color): + self.background_color = color + self.buildStyles() + + def getBackgroundColor(self, color): + return self.background_color + + def asyncWrite(self, text): + ''' + Write given text to buffer in an asynchroneous way. + It is used from another thread to be able to acces the GUI. + @param text: Text to append + @type text: string + ''' + try: + wx.MutexGuiEnter() + + #be sure not to be interrutpted before the MutexGuiLeave! + self.write(text) + + except KeyboardInterrupt: + wx.MutexGuiLeave() + raise KeyboardInterrupt + wx.MutexGuiLeave() + + + def write(self, text): + ''' + Write given text to buffer. + + @param text: Text to append. + @type text: string + ''' + segments = self.color_pat.split(text) + segment = segments.pop(0) + self.StartStyling(self.getCurrentLineEnd(), 0xFF) + self.AppendText(segment) + + if segments: + ansi_tags = self.color_pat.findall(text) + + for tag in ansi_tags: + i = segments.index(tag) + self.StartStyling(self.getCurrentLineEnd(), 0xFF) + self.AppendText(segments[i+1]) + + if tag != '0': + self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0]) + + segments.pop(i) + + self.moveCursor(self.getCurrentLineEnd()) + + def getPromptLen(self): + ''' + Return the length of current prompt + ''' + return len(str(self.prompt_count)) + 7 + + def setPrompt(self, prompt): + self.prompt = prompt + + def setIndentation(self, indentation): + self.indent = indentation + + def setPromptCount(self, count): + self.prompt_count = count + + def showPrompt(self): + ''' + Prints prompt at start of line. + + @param prompt: Prompt to print. + @type prompt: string + ''' + self.write(self.prompt) + #now we update the position of end of prompt + self.current_start = self.getCurrentLineEnd() + + autoindent = self.indent*' ' + autoindent = autoindent.replace(' ','\t') + self.write(autoindent) + + def changeLine(self, text): + ''' + Replace currently entered command line with given text. + + @param text: Text to use as replacement. + @type text: string + ''' + self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd()) + self.ReplaceSelection(text) + self.moveCursor(self.getCurrentLineEnd()) + + def getCurrentPromptStart(self): + return self.current_start + + def getCurrentLineStart(self): + return self.GotoLine(self.LineFromPosition(self.GetCurrentPos())) + + def getCurrentLineEnd(self): + return self.GetLength() + + def getCurrentLine(self): + ''' + Get text in current command line. + + @return: Text of current command line. + @rtype: string + ''' + return self.GetTextRange(self.getCurrentPromptStart(), + self.getCurrentLineEnd()) + + def moveCursorOnNewValidKey(self): + #If cursor is at wrong position put it at last line... + if self.GetCurrentPos() < self.getCurrentPromptStart(): + self.GotoPos(self.getCurrentPromptStart()) + + def removeFromTo(self, from_pos, to_pos): + if from_pos < to_pos: + self.SetSelection(from_pos, to_pos) + self.DeleteBack() + + def removeCurrentLine(self): + self.LineDelete() + + def moveCursor(self, position): + self.GotoPos(position) + + def getCursorPos(self): + return self.GetCurrentPos() + + def selectFromTo(self, from_pos, to_pos): + self.SetSelectionStart(from_pos) + self.SetSelectionEnd(to_pos) + + def writeHistory(self, history): + self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd()) + self.changeLine(history) + + def setCompletionMethod(self, completion): + if completion in ['IPYTHON', 'STC']: + self.autocomplete_mode = completion + else: + raise AttributeError + + def getCompletionMethod(self, completion): + return self.autocomplete_mode + + def writeCompletion(self, possibilities): + if self.autocomplete_mode == 'IPYTHON': + max_len = len(max(possibilities, key=len)) + max_symbol = ' '*max_len + + #now we check how much symbol we can put on a line... + test_buffer = max_symbol + ' '*4 + + allowed_symbols = 80/len(test_buffer) + if allowed_symbols == 0: + allowed_symbols = 1 + + pos = 1 + buf = '' + for symbol in possibilities: + #buf += symbol+'\n'#*spaces) + if pos < allowed_symbols: + spaces = max_len - len(symbol) + 4 + buf += symbol+' '*spaces + pos += 1 + else: + buf += symbol+'\n' + pos = 1 + self.write(buf) + else: + possibilities.sort() # Python sorts are case sensitive + self.AutoCompSetIgnoreCase(False) + self.AutoCompSetAutoHide(False) + #let compute the length ot last word + splitter = [' ', '(', '[', '{','='] + last_word = self.getCurrentLine() + for breaker in splitter: + last_word = last_word.split(breaker)[-1] + self.AutoCompShow(len(last_word), " ".join(possibilities)) + + def _onKeypress(self, event, skip=True): + ''' + Key press callback used for correcting behavior for console-like + interfaces. For example 'home' should go to prompt, not to begining of + line. + + @param widget: Widget that key press accored in. + @type widget: gtk.Widget + @param event: Event object + @type event: gtk.gdk.Event + + @return: Return True if event as been catched. + @rtype: boolean + ''' + if not self.AutoCompActive(): + if event.GetKeyCode() == wx.WXK_HOME: + if event.Modifiers == wx.MOD_NONE: + self.moveCursorOnNewValidKey() + self.moveCursor(self.getCurrentPromptStart()) + return True + elif event.Modifiers == wx.MOD_SHIFT: + self.moveCursorOnNewValidKey() + self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos()) + return True + else: + return False + + elif event.GetKeyCode() == wx.WXK_LEFT: + if event.Modifiers == wx.MOD_NONE: + self.moveCursorOnNewValidKey() + + self.moveCursor(self.getCursorPos()-1) + if self.getCursorPos() < self.getCurrentPromptStart(): + self.moveCursor(self.getCurrentPromptStart()) + return True + + elif event.GetKeyCode() == wx.WXK_BACK: + self.moveCursorOnNewValidKey() + if self.getCursorPos() > self.getCurrentPromptStart(): + event.Skip() + return True + + if skip: + if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\ + and event.Modifiers == wx.MOD_NONE: + self.moveCursorOnNewValidKey() + + event.Skip() + return True + return False + else: + event.Skip() + + def OnUpdateUI(self, evt): + # check for matching braces + braceAtCaret = -1 + braceOpposite = -1 + charBefore = None + caretPos = self.GetCurrentPos() + + if caretPos > 0: + charBefore = self.GetCharAt(caretPos - 1) + styleBefore = self.GetStyleAt(caretPos - 1) + + # check before + if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR: + braceAtCaret = caretPos - 1 + + # check after + if braceAtCaret < 0: + charAfter = self.GetCharAt(caretPos) + styleAfter = self.GetStyleAt(caretPos) + + if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: + braceAtCaret = caretPos + + if braceAtCaret >= 0: + braceOpposite = self.BraceMatch(braceAtCaret) + + if braceAtCaret != -1 and braceOpposite == -1: + self.BraceBadLight(braceAtCaret) + else: + self.BraceHighlight(braceAtCaret, braceOpposite) + #pt = self.PointFromPosition(braceOpposite) + #self.Refresh(True, wxRect(pt.x, pt.y, 5,5)) + #print pt + #self.Refresh(False) + +class IPShellWidget(wx.Panel): + ''' + This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl + If you want to port this to any other GUI toolkit, just replace the + WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate + from whatever container you want. I've choosed to derivate from a wx.Panel + because it seems to be more useful + Any idea to make it more 'generic' welcomed. + ''' + + def __init__(self, parent, intro=None, + background_color="BLACK", add_button_handler=None, + wx_ip_shell=None, user_ns={},user_global_ns=None, + ): + ''' + Initialize. + Instanciate an IPython thread. + Instanciate a WxConsoleView. + Redirect I/O to console. + ''' + wx.Panel.__init__(self,parent,wx.ID_ANY) + + self.parent = parent + ### IPython non blocking shell instanciation ### + self.cout = StringIO() + self.add_button_handler = add_button_handler + + if wx_ip_shell is not None: + self.IP = wx_ip_shell + else: + self.IP = WxNonBlockingIPShell(self, + cout = self.cout, cerr = self.cout, + ask_exit_handler = self.askExitCallback) + + ### IPython wx console view instanciation ### + #If user didn't defined an intro text, we create one for him + #If you really wnat an empty intro just call wxIPythonViewPanel + #with intro='' + if intro is None: + welcome_text = "Welcome to WxIPython Shell.\n\n" + welcome_text+= self.IP.get_banner() + welcome_text+= "!command -> Execute command in shell\n" + welcome_text+= "TAB -> Autocompletion\n" + else: + welcome_text = intro + + self.text_ctrl = WxConsoleView(self, + self.IP.get_prompt(), + intro=welcome_text, + background_color=background_color) + + option_text = wx.StaticText(self, -1, "Options:") + self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion") + self.completion_option.SetToolTip(wx.ToolTip( + "Selects the completion type:\nEither Ipython default style or Scintilla one")) + #self.completion_option.SetValue(False) + self.background_option = wx.CheckBox(self, -1, "White Background") + self.background_option.SetToolTip(wx.ToolTip( + "Selects the back ground color: BLACK or WHITE")) + #self.background_option.SetValue(False) + self.threading_option = wx.CheckBox(self, -1, "Execute in thread") + self.threading_option.SetToolTip(wx.ToolTip( + "Use threading: infinite loop don't freeze the GUI and commands can be breaked\nNo threading: maximum compatibility")) + #self.threading_option.SetValue(False) + + self.options={'completion':{'value':'IPYTHON', + 'checkbox':self.completion_option,'STC':True,'IPYTHON':False, + 'setfunc':self.text_ctrl.setCompletionMethod}, + 'background_color':{'value':'BLACK', + 'checkbox':self.background_option,'WHITE':True,'BLACK':False, + 'setfunc':self.text_ctrl.setBackgroundColor}, + 'threading':{'value':'True', + 'checkbox':self.threading_option,'True':True,'False':False, + 'setfunc':self.IP.set_threading}, + } + + #self.cout.write dEfault option is asynchroneous because default sate is threading ON + self.cout.write = self.text_ctrl.asyncWrite + #we reloard options + self.reloadOptions(self.options) + + self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress) + self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion) + self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor) + self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading) + + ### making the layout of the panel ### + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.text_ctrl, 1, wx.EXPAND) + option_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(option_sizer, 0) + option_sizer.AddMany([(10, 20), + (option_text, 0, wx.ALIGN_CENTER_VERTICAL), + (5, 5), + (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL), + (8, 8), + (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL), + (8, 8), + (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL) + ]) + self.SetAutoLayout(True) + sizer.Fit(self) + sizer.SetSizeHints(self) + self.SetSizer(sizer) + #and we focus on the widget :) + self.SetFocus() + + #widget state management (for key handling different cases) + self.setCurrentState('IDLE') + self.pager_state = 'DONE' + self.raw_input_current_line = 0 + + def askExitCallback(self, event): + self.askExitHandler(event) + + #---------------------- IPython Thread Management ------------------------ + def stateDoExecuteLine(self): + lines=self.text_ctrl.getCurrentLine() + self.text_ctrl.write('\n') + lines_to_execute = lines.replace('\t',' '*4) + lines_to_execute = lines_to_execute.replace('\r','') + self.IP.do_execute(lines_to_execute.encode(ENCODING)) + self.updateHistoryTracker(lines) + if(self.text_ctrl.getCursorPos()!=0): + self.text_ctrl.removeCurrentLine() + self.setCurrentState('WAIT_END_OF_EXECUTION') + + def evtStateExecuteDone(self,evt): + self.doc = self.IP.get_doc_text() + self.help = self.IP.get_help_text() + if self.doc: + self.pager_lines = self.doc[7:].split('\n') + self.pager_state = 'INIT' + self.setCurrentState('SHOW_DOC') + self.pager(self.doc) + elif self.help: + self.pager_lines = self.help.split('\n') + self.pager_state = 'INIT' + self.setCurrentState('SHOW_DOC') + self.pager(self.help) + else: + if(self.text_ctrl.getCursorPos()!=0): + self.text_ctrl.removeCurrentLine() + self.stateShowPrompt() + + def stateShowPrompt(self): + self.setCurrentState('SHOW_PROMPT') + self.text_ctrl.setPrompt(self.IP.get_prompt()) + self.text_ctrl.setIndentation(self.IP.get_indentation()) + self.text_ctrl.setPromptCount(self.IP.get_prompt_count()) + self.text_ctrl.showPrompt() + self.IP.init_history_index() + self.setCurrentState('IDLE') + + def setCurrentState(self, state): + self.cur_state = state + self.updateStatusTracker(self.cur_state) + + def pager(self,text): + + if self.pager_state == 'INIT': + #print >>sys.__stdout__,"PAGER state:",self.pager_state + self.pager_nb_lines = len(self.pager_lines) + self.pager_index = 0 + self.pager_do_remove = False + self.text_ctrl.write('\n') + self.pager_state = 'PROCESS_LINES' + + if self.pager_state == 'PROCESS_LINES': + #print >>sys.__stdout__,"PAGER state:",self.pager_state + if self.pager_do_remove == True: + self.text_ctrl.removeCurrentLine() + self.pager_do_remove = False + + if self.pager_nb_lines > 10: + #print >>sys.__stdout__,"PAGER processing 10 lines" + if self.pager_index > 0: + self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n') + else: + self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n') + + for line in self.pager_lines[self.pager_index+1:self.pager_index+9]: + self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n') + self.pager_index += 10 + self.pager_nb_lines -= 10 + self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---") + self.pager_do_remove = True + self.pager_state = 'WAITING' + return + else: + #print >>sys.__stdout__,"PAGER processing last lines" + if self.pager_nb_lines > 0: + if self.pager_index > 0: + self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n') + else: + self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n') + + self.pager_index += 1 + self.pager_nb_lines -= 1 + if self.pager_nb_lines > 0: + for line in self.pager_lines[self.pager_index:]: + self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n') + self.pager_nb_lines = 0 + self.pager_state = 'DONE' + self.stateShowPrompt() + + #------------------------ Key Handler ------------------------------------ + def keyPress(self, event): + ''' + Key press callback with plenty of shell goodness, like history, + autocompletions, etc. + ''' + if event.GetKeyCode() == ord('C'): + if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT: + if self.cur_state == 'WAIT_END_OF_EXECUTION': + #we raise an exception inside the IPython thread container + self.IP.ce.raise_exc(KeyboardInterrupt) + return + + #let this before 'wx.WXK_RETURN' because we have to put 'IDLE' + #mode if AutoComp has been set as inactive + if self.cur_state == 'COMPLETING': + if not self.text_ctrl.AutoCompActive(): + self.cur_state = 'IDLE' + else: + event.Skip() + + if event.KeyCode == wx.WXK_RETURN: + if self.cur_state == 'IDLE': + #we change the state ot the state machine + self.setCurrentState('DO_EXECUTE_LINE') + self.stateDoExecuteLine() + return + + if self.pager_state == 'WAITING': + self.pager_state = 'PROCESS_LINES' + self.pager(self.doc) + return + + if self.cur_state == 'WAITING_USER_INPUT': + line=self.text_ctrl.getCurrentLine() + self.text_ctrl.write('\n') + self.setCurrentState('WAIT_END_OF_EXECUTION') + return + + if event.GetKeyCode() in [ord('q'),ord('Q')]: + if self.pager_state == 'WAITING': + self.pager_state = 'DONE' + self.text_ctrl.write('\n') + self.stateShowPrompt() + return + + if self.cur_state == 'WAITING_USER_INPUT': + event.Skip() + + if self.cur_state == 'IDLE': + if event.KeyCode == wx.WXK_UP: + history = self.IP.history_back() + self.text_ctrl.writeHistory(history) + return + if event.KeyCode == wx.WXK_DOWN: + history = self.IP.history_forward() + self.text_ctrl.writeHistory(history) + return + if event.KeyCode == wx.WXK_TAB: + #if line empty we disable tab completion + if not self.text_ctrl.getCurrentLine().strip(): + self.text_ctrl.write('\t') + return + completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine()) + if len(possibilities) > 1: + if self.text_ctrl.autocomplete_mode == 'IPYTHON': + cur_slice = self.text_ctrl.getCurrentLine() + self.text_ctrl.write('\n') + self.text_ctrl.writeCompletion(possibilities) + self.text_ctrl.write('\n') + + self.text_ctrl.showPrompt() + self.text_ctrl.write(cur_slice) + self.text_ctrl.changeLine(completed or cur_slice) + else: + self.cur_state = 'COMPLETING' + self.text_ctrl.writeCompletion(possibilities) + else: + self.text_ctrl.changeLine(completed or cur_slice) + return + event.Skip() + + #------------------------ Option Section --------------------------------- + def evtCheckOptionCompletion(self, event): + if event.IsChecked(): + self.options['completion']['value']='STC' + else: + self.options['completion']['value']='IPYTHON' + self.text_ctrl.setCompletionMethod(self.options['completion']['value']) + self.updateOptionTracker('completion', + self.options['completion']['value']) + self.text_ctrl.SetFocus() + + def evtCheckOptionBackgroundColor(self, event): + if event.IsChecked(): + self.options['background_color']['value']='WHITE' + else: + self.options['background_color']['value']='BLACK' + self.text_ctrl.setBackgroundColor(self.options['background_color']['value']) + self.updateOptionTracker('background_color', + self.options['background_color']['value']) + self.text_ctrl.SetFocus() + + def evtCheckOptionThreading(self, event): + if event.IsChecked(): + self.options['threading']['value']='True' + self.IP.set_threading(True) + self.cout.write = self.text_ctrl.asyncWrite + else: + self.options['threading']['value']='False' + self.IP.set_threading(False) + self.cout.write = self.text_ctrl.write + self.updateOptionTracker('threading', + self.options['threading']['value']) + self.text_ctrl.SetFocus() + + def getOptions(self): + return self.options + + def reloadOptions(self,options): + self.options = options + for key in self.options.keys(): + value = self.options[key]['value'] + self.options[key]['checkbox'].SetValue(self.options[key][value]) + self.options[key]['setfunc'](value) + + if self.options['threading']['value']=='True': + self.IP.set_threading(True) + self.cout.write = self.text_ctrl.asyncWrite + else: + self.IP.set_threading(False) + self.cout.write = self.text_ctrl.write + + #------------------------ Hook Section ----------------------------------- + def updateOptionTracker(self,name,value): + ''' + Default history tracker (does nothing) + ''' + pass + + def setOptionTrackerHook(self,func): + ''' + Define a new history tracker + ''' + self.updateOptionTracker = func + + def updateHistoryTracker(self,command_line): + ''' + Default history tracker (does nothing) + ''' + pass + + def setHistoryTrackerHook(self,func): + ''' + Define a new history tracker + ''' + self.updateHistoryTracker = func + + def updateStatusTracker(self,status): + ''' + Default status tracker (does nothing) + ''' + pass + + def setStatusTrackerHook(self,func): + ''' + Define a new status tracker + ''' + self.updateStatusTracker = func + + def askExitHandler(self, event): + ''' + Default exit handler + ''' + self.text_ctrl.write('\nExit callback has not been set.') + + def setAskExitHandler(self, func): + ''' + Define an exit handler + ''' + self.askExitHandler = func + +if __name__ == '__main__': + # Some simple code to test the shell widget. + 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 = IPShellWidget(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.SetSize((780, 460)) + shell = frame.shell + + app.MainLoop() + + diff --git a/IPython/gui/wx/wxIPython.py b/IPython/gui/wx/wxIPython.py index 0d0d249..b724e53 100644 --- a/IPython/gui/wx/wxIPython.py +++ b/IPython/gui/wx/wxIPython.py @@ -10,10 +10,18 @@ from wx.lib.wordwrap import wordwrap from IPython.gui.wx.ipython_view import IPShellWidget from IPython.gui.wx.ipython_history import IPythonHistoryPanel +#used to invoke ipython1 wx implementation +### FIXME ### temporary disabled due to interference with 'show_in_pager' hook +is_sync_frontend_ok = False +try: + from IPython.frontend.wx.ipythonx import IPythonXController +except ImportError: + is_sync_frontend_ok = False + #used to create options.conf file in user directory from IPython.ipapi import get -__version__ = 0.8 +__version__ = 0.91 __author__ = "Laurent Dufrechou" __email__ = "laurent.dufrechou _at_ gmail.com" __license__ = "BSD" @@ -27,7 +35,7 @@ class MyFrame(wx.Frame): application with movables windows""" def __init__(self, parent=None, id=-1, title="WxIPython", pos=wx.DefaultPosition, - size=(800, 600), style=wx.DEFAULT_FRAME_STYLE): + size=(800, 600), style=wx.DEFAULT_FRAME_STYLE, sync_ok=False): wx.Frame.__init__(self, parent, id, title, pos, size, style) self._mgr = wx.aui.AuiManager() @@ -41,12 +49,18 @@ class MyFrame(wx.Frame): self.ipython_panel = IPShellWidget(self,background_color = "BLACK") #self.ipython_panel = IPShellWidget(self,background_color = "WHITE") - + if(sync_ok): + self.ipython_panel2 = IPythonXController(self) + else: + self.ipython_panel2 = None self.ipython_panel.setHistoryTrackerHook(self.history_panel.write) self.ipython_panel.setStatusTrackerHook(self.updateStatus) self.ipython_panel.setAskExitHandler(self.OnExitDlg) self.ipython_panel.setOptionTrackerHook(self.optionSave) + #Create a notebook to display different IPython shell implementations + self.nb = wx.aui.AuiNotebook(self) + self.optionLoad() self.statusbar = self.createStatus() @@ -55,7 +69,11 @@ class MyFrame(wx.Frame): ######################################################################## ### add the panes to the manager # main panels - self._mgr.AddPane(self.ipython_panel , wx.CENTER, "IPython Shell") + self._mgr.AddPane(self.nb , wx.CENTER, "IPython Shells") + self.nb.AddPage(self.ipython_panel , "IPython0 Shell") + if(sync_ok): + self.nb.AddPage(self.ipython_panel2, "IPython1 Synchroneous Shell") + self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history") # now we specify some panel characteristics @@ -77,7 +95,10 @@ class MyFrame(wx.Frame): warn_text = 'Hello from IPython and wxPython.\n' warn_text +='Please Note that this work is still EXPERIMENTAL\n' warn_text +='It does NOT emulate currently all the IPython functions.\n' - + warn_text +="\nIf you use MATPLOTLIB with show() you'll need to deactivate the THREADING option.\n" + if(not sync_ok): + warn_text +="\n->No twisted package detected, IPython1 example deactivated." + dlg = wx.MessageDialog(self, warn_text, 'Warning Box', @@ -146,13 +167,6 @@ class MyFrame(wx.Frame): about_menu = wx.Menu() about_menu.Append(wx.ID_HIGHEST+3, "About") - #view_menu.AppendSeparator() - #options_menu = wx.Menu() - #options_menu.AppendCheckItem(wx.ID_HIGHEST+7, "Allow Floating") - #options_menu.AppendCheckItem(wx.ID_HIGHEST+8, "Transparent Hint") - #options_menu.AppendCheckItem(wx.ID_HIGHEST+9, "Transparent Hint Fade-in") - - mb.Append(file_menu, "File") mb.Append(view_menu, "View") mb.Append(about_menu, "About") @@ -233,17 +247,17 @@ class MyFrame(wx.Frame): #----------------------------------------- class MyApp(wx.PySimpleApp): """Creating our application""" - def __init__(self): + def __init__(self, sync_ok=False): wx.PySimpleApp.__init__(self) - self.frame = MyFrame() + self.frame = MyFrame(sync_ok=sync_ok) self.frame.Show() #----------------------------------------- #Main loop #----------------------------------------- def main(): - app = MyApp() + app = MyApp(is_sync_frontend_ok) app.SetTopWindow(app.frame) app.MainLoop()