diff --git a/IPython/__main__.py b/IPython/__main__.py new file mode 100644 index 0000000..66af32a --- /dev/null +++ b/IPython/__main__.py @@ -0,0 +1,14 @@ +# encoding: utf-8 +"""Terminal-based IPython entry point. +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2012, IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +from IPython.frontend.terminal.ipapp import launch_new_instance + +launch_new_instance() diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index d733b30..b173a5a 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -477,23 +477,51 @@ class Pdb(OldPdb): do_l = do_list def do_pdef(self, arg): - """The debugger interface to magic_pdef""" + """Print the call signature for any callable object. + + The debugger interface to %pdef""" namespaces = [('Locals', self.curframe.f_locals), ('Globals', self.curframe.f_globals)] self.shell.find_line_magic('pdef')(arg, namespaces=namespaces) def do_pdoc(self, arg): - """The debugger interface to magic_pdoc""" + """Print the docstring for an object. + + The debugger interface to %pdoc.""" namespaces = [('Locals', self.curframe.f_locals), ('Globals', self.curframe.f_globals)] self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces) + def do_pfile(self, arg): + """Print (or run through pager) the file where an object is defined. + + The debugger interface to %pfile. + """ + namespaces = [('Locals', self.curframe.f_locals), + ('Globals', self.curframe.f_globals)] + self.shell.find_line_magic('pfile')(arg, namespaces=namespaces) + def do_pinfo(self, arg): - """The debugger equivalant of ?obj""" + """Provide detailed information about an object. + + The debugger interface to %pinfo, i.e., obj?.""" + namespaces = [('Locals', self.curframe.f_locals), + ('Globals', self.curframe.f_globals)] + self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces) + + def do_pinfo2(self, arg): + """Provide extra detailed information about an object. + + The debugger interface to %pinfo2, i.e., obj??.""" + namespaces = [('Locals', self.curframe.f_locals), + ('Globals', self.curframe.f_globals)] + self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces) + + def do_psource(self, arg): + """Print (or run through pager) the source code for an object.""" namespaces = [('Locals', self.curframe.f_locals), ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('pinfo')("pinfo %s" % arg, - namespaces=namespaces) + self.shell.find_line_magic('psource')(arg, namespaces=namespaces) def checkline(self, filename, lineno): """Check whether specified line seems to be executable. diff --git a/IPython/core/extensions.py b/IPython/core/extensions.py index 594a344..6ee6bcb 100644 --- a/IPython/core/extensions.py +++ b/IPython/core/extensions.py @@ -23,8 +23,12 @@ import sys from urllib import urlretrieve from urlparse import urlparse +from IPython.core.error import UsageError from IPython.config.configurable import Configurable from IPython.utils.traitlets import Instance +from IPython.utils.py3compat import PY3 +if PY3: + from imp import reload #----------------------------------------------------------------------------- # Main class @@ -44,10 +48,11 @@ class ExtensionManager(Configurable): the only argument. You can do anything you want with IPython at that point, including defining new magic and aliases, adding new components, etc. - - The :func:`load_ipython_extension` will be called again is you - load or reload the extension again. It is up to the extension - author to add code to manage that. + + You can also optionaly define an :func:`unload_ipython_extension(ipython)` + function, which will be called if the user unloads or reloads the extension. + The extension manager will only call :func:`load_ipython_extension` again + if the extension is reloaded. You can put your extension modules anywhere you want, as long as they can be imported by Python's standard import mechanism. However, @@ -63,6 +68,7 @@ class ExtensionManager(Configurable): self.shell.on_trait_change( self._on_ipython_dir_changed, 'ipython_dir' ) + self.loaded = set() def __del__(self): self.shell.on_trait_change( @@ -80,26 +86,43 @@ class ExtensionManager(Configurable): def load_extension(self, module_str): """Load an IPython extension by its module name. - If :func:`load_ipython_extension` returns anything, this function - will return that object. + Returns the string "already loaded" if the extension is already loaded, + "no load function" if the module doesn't have a load_ipython_extension + function, or None if it succeeded. """ + if module_str in self.loaded: + return "already loaded" + from IPython.utils.syspathcontext import prepended_to_syspath if module_str not in sys.modules: with prepended_to_syspath(self.ipython_extension_dir): __import__(module_str) mod = sys.modules[module_str] - return self._call_load_ipython_extension(mod) + if self._call_load_ipython_extension(mod): + self.loaded.add(module_str) + else: + return "no load function" def unload_extension(self, module_str): """Unload an IPython extension by its module name. This function looks up the extension's name in ``sys.modules`` and simply calls ``mod.unload_ipython_extension(self)``. + + Returns the string "no unload function" if the extension doesn't define + a function to unload itself, "not loaded" if the extension isn't loaded, + otherwise None. """ + if module_str not in self.loaded: + return "not loaded" + if module_str in sys.modules: mod = sys.modules[module_str] - self._call_unload_ipython_extension(mod) + if self._call_unload_ipython_extension(mod): + self.loaded.discard(module_str) + else: + return "no unload function" def reload_extension(self, module_str): """Reload an IPython extension by calling reload. @@ -111,21 +134,25 @@ class ExtensionManager(Configurable): """ from IPython.utils.syspathcontext import prepended_to_syspath - with prepended_to_syspath(self.ipython_extension_dir): - if module_str in sys.modules: - mod = sys.modules[module_str] + if (module_str in self.loaded) and (module_str in sys.modules): + self.unload_extension(module_str) + mod = sys.modules[module_str] + with prepended_to_syspath(self.ipython_extension_dir): reload(mod) - self._call_load_ipython_extension(mod) - else: - self.load_extension(module_str) + if self._call_load_ipython_extension(mod): + self.loaded.add(module_str) + else: + self.load_extension(module_str) def _call_load_ipython_extension(self, mod): if hasattr(mod, 'load_ipython_extension'): - return mod.load_ipython_extension(self.shell) + mod.load_ipython_extension(self.shell) + return True def _call_unload_ipython_extension(self, mod): if hasattr(mod, 'unload_ipython_extension'): - return mod.unload_ipython_extension(self.shell) + mod.unload_ipython_extension(self.shell) + return True def install_extension(self, url, filename=None): """Download and install an IPython extension. diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 2f6c22e..4d7796a 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -629,7 +629,7 @@ class InteractiveShell(SingletonConfigurable): # override sys.stdout and sys.stderr themselves, you need to do that # *before* instantiating this class, because io holds onto # references to the underlying streams. - if sys.platform == 'win32' and self.has_readline: + if (sys.platform == 'win32' or sys.platform == 'cli') and self.has_readline: io.stdout = io.stderr = io.IOStream(self.readline._outputfile) else: io.stdout = io.IOStream(sys.stdout) diff --git a/IPython/core/magic_arguments.py b/IPython/core/magic_arguments.py index 3a9cca9..15e0ebb 100644 --- a/IPython/core/magic_arguments.py +++ b/IPython/core/magic_arguments.py @@ -173,11 +173,17 @@ class magic_arguments(ArgDecorator): return func -class argument(ArgDecorator): - """ Store arguments and keywords to pass to add_argument(). +class ArgMethodWrapper(ArgDecorator): + + """ + Base class to define a wrapper for ArgumentParser method. + + Child class must define either `_method_name` or `add_to_parser`. - Instances also serve to decorate command methods. """ + + _method_name = None + def __init__(self, *args, **kwds): self.args = args self.kwds = kwds @@ -187,18 +193,31 @@ class argument(ArgDecorator): """ if group is not None: parser = group - parser.add_argument(*self.args, **self.kwds) + getattr(parser, self._method_name)(*self.args, **self.kwds) return None -class argument_group(ArgDecorator): +class argument(ArgMethodWrapper): + """ Store arguments and keywords to pass to add_argument(). + + Instances also serve to decorate command methods. + """ + _method_name = 'add_argument' + + +class defaults(ArgMethodWrapper): + """ Store arguments and keywords to pass to set_defaults(). + + Instances also serve to decorate command methods. + """ + _method_name = 'set_defaults' + + +class argument_group(ArgMethodWrapper): """ Store arguments and keywords to pass to add_argument_group(). Instances also serve to decorate command methods. """ - def __init__(self, *args, **kwds): - self.args = args - self.kwds = kwds def add_to_parser(self, parser, group): """ Add this object's information to the parser. diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 5825640..980a0a6 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -313,7 +313,8 @@ Currently the magic system has the following functions:""", import IPython.utils.rlineimpl as readline if not shell.colors_force and \ - not readline.have_readline and sys.platform == "win32": + not readline.have_readline and \ + (sys.platform == "win32" or sys.platform == "cli"): msg = """\ Proper color support under MS Windows requires the pyreadline library. You can find it at: diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 3669ef2..1b916b7 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -563,6 +563,11 @@ python-profiler package from non-free.""") return # if we find a good linenumber, set the breakpoint deb.do_break('%s:%s' % (filename, bp)) + + # Mimic Pdb._runscript(...) + deb._wait_for_mainpyfile = True + deb.mainpyfile = deb.canonic(filename) + # Start file run print "NOTE: Enter 'c' at the", print "%s prompt to start your script." % deb.prompt diff --git a/IPython/core/magics/extension.py b/IPython/core/magics/extension.py index 93a8bc1..31356b0 100644 --- a/IPython/core/magics/extension.py +++ b/IPython/core/magics/extension.py @@ -59,14 +59,30 @@ class ExtensionMagics(Magics): """Load an IPython extension by its module name.""" if not module_str: raise UsageError('Missing module name.') - return self.shell.extension_manager.load_extension(module_str) + res = self.shell.extension_manager.load_extension(module_str) + + if res == 'already loaded': + print "The %s extension is already loaded. To reload it, use:" % module_str + print " %reload_ext", module_str + elif res == 'no load function': + print "The %s module is not an IPython extension." % module_str @line_magic def unload_ext(self, module_str): - """Unload an IPython extension by its module name.""" + """Unload an IPython extension by its module name. + + Not all extensions can be unloaded, only those which define an + ``unload_ipython_extension`` function. + """ if not module_str: raise UsageError('Missing module name.') - self.shell.extension_manager.unload_extension(module_str) + + res = self.shell.extension_manager.unload_extension(module_str) + + if res == 'no unload function': + print "The %s extension doesn't define how to unload it." % module_str + elif res == "not loaded": + print "The %s extension is not loaded." % module_str @line_magic def reload_ext(self, module_str): diff --git a/IPython/core/magics/history.py b/IPython/core/magics/history.py index dbfa1c8..3d9884d 100644 --- a/IPython/core/magics/history.py +++ b/IPython/core/magics/history.py @@ -16,10 +16,13 @@ from __future__ import print_function # Stdlib import os from io import open as io_open +from IPython.external.argparse import Action # Our own packages from IPython.core.error import StdinNotImplementedError from IPython.core.magic import Magics, magics_class, line_magic +from IPython.core.magic_arguments import (argument, magic_arguments, + parse_argstring) from IPython.testing.skipdoctest import skip_doctest from IPython.utils import io @@ -27,16 +30,71 @@ from IPython.utils import io # Magics class implementation #----------------------------------------------------------------------------- + +_unspecified = object() + + @magics_class class HistoryMagics(Magics): + @magic_arguments() + @argument( + '-n', dest='print_nums', action='store_true', default=False, + help=""" + print line numbers for each input. + This feature is only available if numbered prompts are in use. + """) + @argument( + '-o', dest='get_output', action='store_true', default=False, + help="also print outputs for each input.") + @argument( + '-p', dest='pyprompts', action='store_true', default=False, + help=""" + print classic '>>>' python prompts before each input. + This is useful for making documentation, and in conjunction + with -o, for producing doctest-ready output. + """) + @argument( + '-t', dest='raw', action='store_false', default=True, + help=""" + print the 'translated' history, as IPython understands it. + IPython filters your input and converts it all into valid Python + source before executing it (things like magics or aliases are turned + into function calls, for example). With this option, you'll see the + native history instead of the user-entered version: '%%cd /' will be + seen as 'get_ipython().magic("%%cd /")' instead of '%%cd /'. + """) + @argument( + '-f', dest='filename', + help=""" + FILENAME: instead of printing the output to the screen, redirect + it to the given file. The file is always overwritten, though *when + it can*, IPython asks for confirmation first. In particular, running + the command 'history -f FILENAME' from the IPython Notebook + interface will replace FILENAME even if it already exists *without* + confirmation. + """) + @argument( + '-g', dest='pattern', nargs='*', default=None, + help=""" + treat the arg as a glob pattern to search for in (full) history. + This includes the saved history (almost all commands ever written). + The pattern may contain '?' to match one unknown character and '*' + to match any number of unknown characters. Use '%%hist -g' to show + full saved history (may be very long). + """) + @argument( + '-l', dest='limit', type=int, nargs='?', default=_unspecified, + help=""" + get the last n lines from all sessions. Specify n as a single + arg, or the default is the last 10 lines. + """) + @argument('range', nargs='*') @skip_doctest @line_magic def history(self, parameter_s = ''): """Print input history (_i variables), with most recent last. - %history [-o -p -t -n] [-f filename] [range | -g pattern | -l number] - By default, input history is printed without line numbers so it can be directly pasted into an editor. Use -n to show them. @@ -52,43 +110,6 @@ class HistoryMagics(Magics): The same syntax is used by %macro, %save, %edit, %rerun - Options: - - -n: print line numbers for each input. - This feature is only available if numbered prompts are in use. - - -o: also print outputs for each input. - - -p: print classic '>>>' python prompts before each input. This is - useful for making documentation, and in conjunction with -o, for - producing doctest-ready output. - - -r: (default) print the 'raw' history, i.e. the actual commands you - typed. - - -t: print the 'translated' history, as IPython understands it. - IPython filters your input and converts it all into valid Python - source before executing it (things like magics or aliases are turned - into function calls, for example). With this option, you'll see the - native history instead of the user-entered version: '%cd /' will be - seen as 'get_ipython().magic("%cd /")' instead of '%cd /'. - - -g: treat the arg as a pattern to grep for in (full) history. - This includes the saved history (almost all commands ever written). - The pattern may contain '?' to match one unknown character and '*' - to match any number of unknown characters. Use '%hist -g' to show - full saved history (may be very long). - - -l: get the last n lines from all sessions. Specify n as a single - arg, or the default is the last 10 lines. - - -f FILENAME: instead of printing the output to the screen, redirect - it to the given file. The file is always overwritten, though *when - it can*, IPython asks for confirmation first. In particular, running - the command 'history -f FILENAME' from the IPython Notebook - interface will replace FILENAME even if it already exists *without* - confirmation. - Examples -------- :: @@ -100,11 +121,7 @@ class HistoryMagics(Magics): """ - if not self.shell.displayhook.do_full_cache: - print('This feature is only available if numbered prompts ' - 'are in use.') - return - opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string') + args = parse_argstring(self.history, parameter_s) # For brevity history_manager = self.shell.history_manager @@ -116,9 +133,8 @@ class HistoryMagics(Magics): return "%s/%s" % (session, line) # Check if output to specific file was requested. - try: - outfname = opts['f'] - except KeyError: + outfname = args.filename + if not outfname: outfile = io.stdout # default # We don't want to close stdout at the end! close_at_end = False @@ -135,27 +151,29 @@ class HistoryMagics(Magics): outfile = io_open(outfname, 'w', encoding='utf-8') close_at_end = True - print_nums = 'n' in opts - get_output = 'o' in opts - pyprompts = 'p' in opts - # Raw history is the default - raw = not('t' in opts) + print_nums = args.print_nums + get_output = args.get_output + pyprompts = args.pyprompts + raw = args.raw pattern = None + limit = None if args.limit is _unspecified else args.limit - if 'g' in opts: # Glob search - pattern = "*" + args + "*" if args else "*" - hist = history_manager.search(pattern, raw=raw, output=get_output) + if args.pattern is not None: + if args.pattern: + pattern = "*" + " ".join(args.pattern) + "*" + else: + pattern = "*" + hist = history_manager.search(pattern, raw=raw, output=get_output, + n=limit) print_nums = True - elif 'l' in opts: # Get 'tail' - try: - n = int(args) - except (ValueError, IndexError): - n = 10 + elif args.limit is not _unspecified: + n = 10 if limit is None else limit hist = history_manager.get_tail(n, raw=raw, output=get_output) else: - if args: # Get history by ranges - hist = history_manager.get_range_by_str(args, raw, get_output) + if args.range: # Get history by ranges + hist = history_manager.get_range_by_str(" ".join(args.range), + raw, get_output) else: # Just get history for the current session hist = history_manager.get_range(raw=raw, output=get_output) diff --git a/IPython/core/magics/namespace.py b/IPython/core/magics/namespace.py index 8c16a3e..36f2d02 100644 --- a/IPython/core/magics/namespace.py +++ b/IPython/core/magics/namespace.py @@ -69,7 +69,7 @@ class NamespaceMagics(Magics): @skip_doctest @line_magic def pdef(self, parameter_s='', namespaces=None): - """Print the definition header for any callable object. + """Print the call signature for any callable object. If the object is a class, print the constructor information. @@ -98,7 +98,7 @@ class NamespaceMagics(Magics): self.shell._inspect('psource',parameter_s, namespaces) @line_magic - def pfile(self, parameter_s=''): + def pfile(self, parameter_s='', namespaces=None): """Print (or run through pager) the file where an object is defined. The file opens at the line where the object definition begins. IPython @@ -111,7 +111,7 @@ class NamespaceMagics(Magics): viewer.""" # first interpret argument as an object name - out = self.shell._inspect('pfile',parameter_s) + out = self.shell._inspect('pfile',parameter_s, namespaces) # if not, try the input as a filename if out == 'not found': try: diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index dc4ab87..ba5f935 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -344,7 +344,7 @@ class Inspector: self.set_active_scheme(scheme) def _getdef(self,obj,oname=''): - """Return the definition header for any callable object. + """Return the call signature for any callable object. If any exception is generated, None is returned instead and the exception is suppressed.""" @@ -373,7 +373,7 @@ class Inspector: print() def pdef(self, obj, oname=''): - """Print the definition header for any callable object. + """Print the call signature for any callable object. If the object is a class, print the constructor information.""" diff --git a/IPython/core/tests/test_extension.py b/IPython/core/tests/test_extension.py new file mode 100644 index 0000000..43446ba --- /dev/null +++ b/IPython/core/tests/test_extension.py @@ -0,0 +1,73 @@ +import os.path + +import nose.tools as nt + +import IPython.testing.tools as tt +from IPython.utils.syspathcontext import prepended_to_syspath +from IPython.utils.tempdir import TemporaryDirectory + +ext1_content = """ +def load_ipython_extension(ip): + print("Running ext1 load") + +def unload_ipython_extension(ip): + print("Running ext1 unload") +""" + +ext2_content = """ +def load_ipython_extension(ip): + print("Running ext2 load") +""" + +def test_extension_loading(): + em = get_ipython().extension_manager + with TemporaryDirectory() as td: + ext1 = os.path.join(td, 'ext1.py') + with open(ext1, 'w') as f: + f.write(ext1_content) + + ext2 = os.path.join(td, 'ext2.py') + with open(ext2, 'w') as f: + f.write(ext2_content) + + with prepended_to_syspath(td): + assert 'ext1' not in em.loaded + assert 'ext2' not in em.loaded + + # Load extension + with tt.AssertPrints("Running ext1 load"): + assert em.load_extension('ext1') is None + assert 'ext1' in em.loaded + + # Should refuse to load it again + with tt.AssertNotPrints("Running ext1 load"): + assert em.load_extension('ext1') == 'already loaded' + + # Reload + with tt.AssertPrints("Running ext1 unload"): + with tt.AssertPrints("Running ext1 load", suppress=False): + em.reload_extension('ext1') + + # Unload + with tt.AssertPrints("Running ext1 unload"): + assert em.unload_extension('ext1') is None + + # Can't unload again + with tt.AssertNotPrints("Running ext1 unload"): + assert em.unload_extension('ext1') == 'not loaded' + assert em.unload_extension('ext2') == 'not loaded' + + # Load extension 2 + with tt.AssertPrints("Running ext2 load"): + assert em.load_extension('ext2') is None + + # Can't unload this + assert em.unload_extension('ext2') == 'no unload function' + + # But can reload it + with tt.AssertPrints("Running ext2 load"): + em.reload_extension('ext2') + +def test_non_extension(): + em = get_ipython().extension_manager + nt.assert_equal(em.load_extension('sys'), "no load function") diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index dfc85d7..74e1744 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -96,13 +96,41 @@ def doctest_run_option_parser(): In [2]: %run print_argv.py print*.py ['print_argv.py'] - In [3]: %run print_argv.py print\\*.py + In [3]: %run -G print_argv.py print*.py ['print*.py'] - In [4]: %run print_argv.py 'print*.py' + """ + + +@dec.skip_win32 +def doctest_run_option_parser_for_posix(): + r"""Test option parser in %run (Linux/OSX specific). + + You need double quote to escape glob in POSIX systems: + + In [1]: %run print_argv.py print\\*.py + ['print*.py'] + + You can't use quote to escape glob in POSIX systems: + + In [2]: %run print_argv.py 'print*.py' ['print_argv.py'] - In [5]: %run -G print_argv.py print*.py + """ + + +@dec.skip_if_not_win32 +def doctest_run_option_parser_for_windows(): + r"""Test option parser in %run (Windows specific). + + In Windows, you can't escape ``*` `by backslash: + + In [1]: %run print_argv.py print\\*.py + ['print\\*.py'] + + You can use quote to escape glob: + + In [2]: %run print_argv.py 'print*.py' ['print*.py'] """ diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 27334ed..40ae40d 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -514,14 +514,8 @@ class AutoreloadMagics(Magics): pass -_loaded = False - - def load_ipython_extension(ip): """Load the extension in IPython.""" - global _loaded - if not _loaded: - auto_reload = AutoreloadMagics(ip) - ip.register_magics(auto_reload) - ip.set_hook('pre_run_code_hook', auto_reload.pre_run_code_hook) - _loaded = True + auto_reload = AutoreloadMagics(ip) + ip.register_magics(auto_reload) + ip.set_hook('pre_run_code_hook', auto_reload.pre_run_code_hook) diff --git a/IPython/extensions/cythonmagic.py b/IPython/extensions/cythonmagic.py index ae640fe..8edf5d5 100644 --- a/IPython/extensions/cythonmagic.py +++ b/IPython/extensions/cythonmagic.py @@ -273,11 +273,7 @@ class CythonMagics(Magics): html = '\n'.join(l for l in html.splitlines() if not r.match(l)) return html -_loaded = False def load_ipython_extension(ip): """Load the extension in IPython.""" - global _loaded - if not _loaded: - ip.register_magics(CythonMagics) - _loaded = True + ip.register_magics(CythonMagics) diff --git a/IPython/extensions/octavemagic.py b/IPython/extensions/octavemagic.py index edba5c5..d8798cb 100644 --- a/IPython/extensions/octavemagic.py +++ b/IPython/extensions/octavemagic.py @@ -362,10 +362,6 @@ __doc__ = __doc__.format( ) -_loaded = False def load_ipython_extension(ip): """Load the extension in IPython.""" - global _loaded - if not _loaded: - ip.register_magics(OctaveMagics) - _loaded = True + ip.register_magics(OctaveMagics) diff --git a/IPython/extensions/rmagic.py b/IPython/extensions/rmagic.py index f9cf9f7..1fdfa95 100644 --- a/IPython/extensions/rmagic.py +++ b/IPython/extensions/rmagic.py @@ -588,10 +588,6 @@ __doc__ = __doc__.format( ) -_loaded = False def load_ipython_extension(ip): """Load the extension in IPython.""" - global _loaded - if not _loaded: - ip.register_magics(RMagics) - _loaded = True + ip.register_magics(RMagics) diff --git a/IPython/extensions/storemagic.py b/IPython/extensions/storemagic.py index b02457b..ca9376b 100644 --- a/IPython/extensions/storemagic.py +++ b/IPython/extensions/storemagic.py @@ -209,12 +209,6 @@ class StoreMagics(Magics): print "Stored '%s' (%s)" % (args[0], obj.__class__.__name__) -_loaded = False - - def load_ipython_extension(ip): """Load the extension in IPython.""" - global _loaded - if not _loaded: - ip.register_magics(StoreMagics) - _loaded = True + ip.register_magics(StoreMagics) diff --git a/IPython/external/ssh/tunnel.py b/IPython/external/ssh/tunnel.py index 9ae2311..c63f0ce 100644 --- a/IPython/external/ssh/tunnel.py +++ b/IPython/external/ssh/tunnel.py @@ -22,6 +22,7 @@ Authors from __future__ import print_function import os,sys, atexit +import signal import socket from multiprocessing import Process from getpass import getpass, getuser @@ -331,9 +332,10 @@ def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None except Exception as e: print ('*** Failed to connect to %s:%d: %r' % (server, port, e)) sys.exit(1) - - # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport)) - + + # Don't let SIGINT kill the tunnel subprocess + signal.signal(signal.SIGINT, signal.SIG_IGN) + try: forward_tunnel(lport, remoteip, rport, client.get_transport()) except KeyboardInterrupt: diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py index fe1b3dc..8ddd9a0 100644 --- a/IPython/frontend/html/notebook/notebookapp.py +++ b/IPython/frontend/html/notebook/notebookapp.py @@ -470,11 +470,14 @@ class NotebookApp(BaseIPythonApplication): ssl_options = None self.web_app.password = self.password self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options) - if ssl_options is None and not self.ip and not (self.read_only and not self.password): - self.log.critical('WARNING: the notebook server is listening on all IP addresses ' - 'but not using any encryption or authentication. This is highly ' - 'insecure and not recommended.') - + if not self.ip: + warning = "WARNING: The notebook server is listening on all IP addresses" + if ssl_options is None: + self.log.critical(warning + " and not using encryption. This" + "is not recommended.") + if not self.password and not self.read_only: + self.log.critical(warning + "and not using authentication." + "This is highly insecure and not recommended.") success = None for port in random_ports(self.port, self.port_retries+1): try: diff --git a/IPython/frontend/html/notebook/static/js/cell.js b/IPython/frontend/html/notebook/static/js/cell.js index 4dbb9e6..be63097 100644 --- a/IPython/frontend/html/notebook/static/js/cell.js +++ b/IPython/frontend/html/notebook/static/js/cell.js @@ -50,15 +50,13 @@ var IPython = (function (IPython) { }); }; - - // typeset with MathJax if MathJax is available Cell.prototype.typeset = function () { if (window.MathJax){ - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + var cell_math = this.element.get(0); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,cell_math]); } }; - Cell.prototype.select = function () { this.element.addClass('ui-widget-content ui-corner-all'); this.selected = true; diff --git a/IPython/frontend/html/notebook/static/js/codecell.js b/IPython/frontend/html/notebook/static/js/codecell.js index 857e8f5..bdba51c 100644 --- a/IPython/frontend/html/notebook/static/js/codecell.js +++ b/IPython/frontend/html/notebook/static/js/codecell.js @@ -123,6 +123,7 @@ var IPython = (function (IPython) { } else if (event.keyCode === key.TAB && event.type == 'keydown') { // Tab completion. //Do not trim here because of tooltip + if (editor.somethingSelected()){return false} var pre_cursor = editor.getRange({line:cur.line,ch:0},cur); if (pre_cursor.trim() === "") { // Don't autocomplete if the part of the line before the cursor diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index dea7711..14f382f 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -64,7 +64,7 @@ var IPython = (function (IPython) { Kernel.prototype.restart = function () { - $([IPython.events]).trigger({type: 'status_restarting.Kernel', kernel: this}); + $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); var that = this; if (this.running) { this.stop_channels(); @@ -86,6 +86,7 @@ var IPython = (function (IPython) { this.start_channels(); this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this); this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this); + $([IPython.events]).trigger('status_started.Kernel', {kernel: this}); }; @@ -245,7 +246,8 @@ var IPython = (function (IPython) { user_expressions : {}, allow_stdin : false }; - $.extend(true, content, options) + $.extend(true, content, options) + $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); var msg = this._get_msg("execute_request", content); this.shell_channel.send(JSON.stringify(msg)); this.set_callbacks_for_msg(msg.header.msg_id, callbacks); @@ -279,7 +281,7 @@ var IPython = (function (IPython) { Kernel.prototype.interrupt = function () { if (this.running) { - $([IPython.events]).trigger({type: 'status_interrupting.Kernel', kernel: this}); + $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this}); $.post(this.kernel_url + "/interrupt"); }; }; @@ -312,6 +314,7 @@ var IPython = (function (IPython) { Kernel.prototype._handle_shell_reply = function (e) { reply = $.parseJSON(e.data); + $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply}); var header = reply.header; var content = reply.content; var metadata = reply.metadata; @@ -367,12 +370,12 @@ var IPython = (function (IPython) { } } else if (msg_type === 'status') { if (content.execution_state === 'busy') { - $([IPython.events]).trigger({type: 'status_busy.Kernel', kernel: this}); + $([IPython.events]).trigger('status_busy.Kernel', {kernel: this}); } else if (content.execution_state === 'idle') { - $([IPython.events]).trigger({type: 'status_idle.Kernel', kernel: this}); + $([IPython.events]).trigger('status_idle.Kernel', {kernel: this}); } else if (content.execution_state === 'dead') { this.stop_channels(); - $([IPython.events]).trigger({type: 'status_dead.Kernel', kernel: this}); + $([IPython.events]).trigger('status_dead.Kernel', {kernel: this}); }; } else if (msg_type === 'clear_output') { var cb = callbacks['clear_output']; diff --git a/IPython/frontend/html/notebook/static/js/maintoolbar.js b/IPython/frontend/html/notebook/static/js/maintoolbar.js index 328fca1..0ac6b36 100644 --- a/IPython/frontend/html/notebook/static/js/maintoolbar.js +++ b/IPython/frontend/html/notebook/static/js/maintoolbar.js @@ -51,10 +51,10 @@ var IPython = (function (IPython) { }, { id : 'paste_b', - label : 'Paste Cell', + label : 'Paste Cell Below', icon : 'ui-icon-clipboard', callback : function () { - IPython.notebook.paste_cell(); + IPython.notebook.paste_cell_below(); } } ],'cut_copy_paste'); diff --git a/IPython/frontend/html/notebook/static/js/mathjaxutils.js b/IPython/frontend/html/notebook/static/js/mathjaxutils.js index 2dea85e..7c7cb1a 100644 --- a/IPython/frontend/html/notebook/static/js/mathjaxutils.js +++ b/IPython/frontend/html/notebook/static/js/mathjaxutils.js @@ -14,10 +14,9 @@ IPython.namespace('IPython.mathjaxutils'); IPython.mathjaxutils = (function (IPython) { var init = function () { - if (window.MathJax) { + if (window.MathJax) { // MathJax loaded MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS", useLabelIds: true } }, tex2jax: { inlineMath: [ ['$','$'], ["\\(","\\)"] ], displayMath: [ ['$$','$$'], ["\\[","\\]"] ], @@ -28,6 +27,7 @@ IPython.mathjaxutils = (function (IPython) { styles: {'.MathJax_Display': {"margin": 0}} } }); + MathJax.Hub.Configured(); } else if (window.mathjax_url != "") { // Don't have MathJax, but should. Show dialog. var dialog = $('
') @@ -88,7 +88,6 @@ IPython.mathjaxutils = (function (IPython) { var inline = "$"; // the inline math delimiter var blocks, start, end, last, braces; // used in searching for math var math; // stores math until pagedown (Markdown parser) is done - var HUB = MathJax.Hub; // MATHSPLIT contains the pattern for math delimiters and special symbols // needed for searching for math in the text input. @@ -102,11 +101,12 @@ IPython.mathjaxutils = (function (IPython) { // math, then push the math string onto the storage array. // The preProcess function is called on all blocks if it has been passed in var process_math = function (i, j, pre_process) { + var hub = MathJax.Hub; var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&") // use HTML entity for & .replace(//g, ">") // use HTML entity for > ; - if (HUB.Browser.isMSIE) { + if (hub.Browser.isMSIE) { block = block.replace(/(%[^\n]*)\n/g, "$1
\n") } while (j > i) { @@ -127,6 +127,10 @@ IPython.mathjaxutils = (function (IPython) { // (which will be a paragraph). // var remove_math = function (text) { + if (!window.MathJax) { + return text; + } + start = end = last = null; // for tracking math delimiters math = []; // stores math strings for later @@ -216,6 +220,10 @@ IPython.mathjaxutils = (function (IPython) { // and clear the math array (no need to keep it around). // var replace_math = function (text) { + if (!window.MathJax) { + return text; + } + text = text.replace(/@@(\d+)@@/g, function (match, n) { return math[n] }); @@ -223,21 +231,11 @@ IPython.mathjaxutils = (function (IPython) { return text; } - var queue_render = function () { - // see https://groups.google.com/forum/?fromgroups=#!topic/mathjax-users/cpwy5eCH1ZQ - MathJax.Hub.Queue( - ["resetEquationNumbers",MathJax.InputJax.TeX], - ["PreProcess",MathJax.Hub], - ["Reprocess",MathJax.Hub] - ); - } - return { init : init, process_math : process_math, remove_math : remove_math, - replace_math : replace_math, - queue_render : queue_render + replace_math : replace_math }; }(IPython)); \ No newline at end of file diff --git a/IPython/frontend/html/notebook/static/js/menubar.js b/IPython/frontend/html/notebook/static/js/menubar.js index fc0db88..dc894df 100644 --- a/IPython/frontend/html/notebook/static/js/menubar.js +++ b/IPython/frontend/html/notebook/static/js/menubar.js @@ -128,7 +128,13 @@ var IPython = (function (IPython) { }); this.element.find('#run_all_cells').click(function () { IPython.notebook.execute_all_cells(); - }); + }).attr('title', 'Run all cells in the notebook'); + this.element.find('#run_all_cells_above').click(function () { + IPython.notebook.execute_cells_above(); + }).attr('title', 'Run all cells above (but not including) this cell'); + this.element.find('#run_all_cells_below').click(function () { + IPython.notebook.execute_cells_below(); + }).attr('title', 'Run this cell and all cells below it'); this.element.find('#to_code').click(function () { IPython.notebook.to_code(); }); diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index db7cae5..e7b9286 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -22,6 +22,9 @@ var IPython = (function (IPython) { this.next_prompt_number = 1; this.kernel = null; this.clipboard = null; + this.undelete_backup = null; + this.undelete_index = null; + this.undelete_below = false; this.paste_enabled = false; this.dirty = false; this.metadata = {}; @@ -139,8 +142,8 @@ var IPython = (function (IPython) { that.control_key_active = false; return false; } else if (event.which === 86 && that.control_key_active) { - // Paste selected cell = v - that.paste_cell(); + // Paste below selected cell = v + that.paste_cell_below(); that.control_key_active = false; return false; } else if (event.which === 68 && that.control_key_active) { @@ -257,6 +260,11 @@ var IPython = (function (IPython) { IPython.quick_help.show_keyboard_shortcuts(); that.control_key_active = false; return false; + } else if (event.which === 90 && that.control_key_active) { + // Undo last cell delete = z + that.undelete(); + that.control_key_active = false; + return false; } else if (that.control_key_active) { that.control_key_active = false; return true; @@ -536,13 +544,19 @@ var IPython = (function (IPython) { Notebook.prototype.delete_cell = function (index) { var i = this.index_or_selected(index); + var cell = this.get_selected_cell(); + this.undelete_backup = cell.toJSON(); if (this.is_valid_cell_index(i)) { var ce = this.get_cell_element(i); ce.remove(); if (i === (this.ncells())) { this.select(i-1); + this.undelete_index = i - 1; + this.undelete_below = true; } else { this.select(i); + this.undelete_index = i; + this.undelete_below = false; }; this.dirty = true; }; @@ -560,6 +574,11 @@ var IPython = (function (IPython) { // index = cell index or undefined to insert below selected index = this.index_or_selected(index); var cell = null; + // This is intentionally < rather than <= for the sake of more + // sensible behavior in some cases. + if (this.undelete_index !== null && index < this.undelete_index) { + this.undelete_index = this.undelete_index + 1; + } if (this.ncells() === 0 || this.is_valid_cell_index(index)) { if (type === 'code') { cell = new IPython.CodeCell(this.kernel); @@ -594,6 +613,9 @@ var IPython = (function (IPython) { // index = cell index or undefined to insert above selected index = this.index_or_selected(index); var cell = null; + if (this.undelete_index !== null && index <= this.undelete_index) { + this.undelete_index = this.undelete_index + 1; + } if (this.ncells() === 0 || this.is_valid_cell_index(index)) { if (type === 'code') { cell = new IPython.CodeCell(this.kernel); @@ -756,8 +778,8 @@ var IPython = (function (IPython) { Notebook.prototype.enable_paste = function () { var that = this; if (!this.paste_enabled) { - $('#paste_cell').removeClass('ui-state-disabled') - .on('click', function () {that.paste_cell();}); + $('#paste_cell_replace').removeClass('ui-state-disabled') + .on('click', function () {that.paste_cell_replace();}); $('#paste_cell_above').removeClass('ui-state-disabled') .on('click', function () {that.paste_cell_above();}); $('#paste_cell_below').removeClass('ui-state-disabled') @@ -769,7 +791,7 @@ var IPython = (function (IPython) { Notebook.prototype.disable_paste = function () { if (this.paste_enabled) { - $('#paste_cell').addClass('ui-state-disabled').off('click'); + $('#paste_cell_replace').addClass('ui-state-disabled').off('click'); $('#paste_cell_above').addClass('ui-state-disabled').off('click'); $('#paste_cell_below').addClass('ui-state-disabled').off('click'); this.paste_enabled = false; @@ -789,7 +811,7 @@ var IPython = (function (IPython) { }; - Notebook.prototype.paste_cell = function () { + Notebook.prototype.paste_cell_replace = function () { if (this.clipboard !== null && this.paste_enabled) { var cell_data = this.clipboard; var new_cell = this.insert_cell_above(cell_data.cell_type); @@ -818,6 +840,33 @@ var IPython = (function (IPython) { }; }; + // Cell undelete + + Notebook.prototype.undelete = function() { + if (this.undelete_backup !== null && this.undelete_index !== null) { + var current_index = this.get_selected_index(); + if (this.undelete_index < current_index) { + current_index = current_index + 1; + } + if (this.undelete_index >= this.ncells()) { + this.select(this.ncells() - 1); + } + else { + this.select(this.undelete_index); + } + var cell_data = this.undelete_backup; + var new_cell = null; + if (this.undelete_below) { + new_cell = this.insert_cell_below(cell_data.cell_type); + } else { + new_cell = this.insert_cell_above(cell_data.cell_type); + } + new_cell.fromJSON(cell_data); + this.select(current_index); + this.undelete_backup = null; + this.undelete_index = null; + } + } // Split/merge @@ -1049,13 +1098,25 @@ var IPython = (function (IPython) { }; + Notebook.prototype.execute_cells_below = function () { + this.execute_cell_range(this.get_selected_index(), this.ncells()); + that.scroll_to_bottom(); + }; + + Notebook.prototype.execute_cells_above = function () { + this.execute_cell_range(0, this.get_selected_index()); + }; + Notebook.prototype.execute_all_cells = function () { - var ncells = this.ncells(); - for (var i=0; i").addClass("box-flex1 output_subarea output_text"); // escape ANSI & HTML specials in plaintext: + data = utils.wrapUrls(data); data = utils.fixConsole(data); data = utils.fixCarriageReturn(data); + data = utils.autoLinkUrls(data); if (extra_class){ toinsert.addClass(extra_class); } diff --git a/IPython/frontend/html/notebook/static/js/quickhelp.js b/IPython/frontend/html/notebook/static/js/quickhelp.js index e88df2f..5bf0b19 100644 --- a/IPython/frontend/html/notebook/static/js/quickhelp.js +++ b/IPython/frontend/html/notebook/static/js/quickhelp.js @@ -33,6 +33,7 @@ var IPython = (function (IPython) { {key: 'Ctrl-m c', help: 'copy cell'}, {key: 'Ctrl-m v', help: 'paste cell'}, {key: 'Ctrl-m d', help: 'delete cell'}, + {key: 'Ctrl-m z', help: 'undo last cell deletion'}, {key: 'Ctrl-m a', help: 'insert cell above'}, {key: 'Ctrl-m b', help: 'insert cell below'}, {key: 'Ctrl-m o', help: 'toggle output'}, diff --git a/IPython/frontend/html/notebook/static/js/textcell.js b/IPython/frontend/html/notebook/static/js/textcell.js index 8b46bbe..bab05de 100644 --- a/IPython/frontend/html/notebook/static/js/textcell.js +++ b/IPython/frontend/html/notebook/static/js/textcell.js @@ -221,11 +221,9 @@ var IPython = (function (IPython) { if (this.rendered === false) { var text = this.get_text(); if (text === "") { text = this.placeholder; } - text = IPython.mathjaxutils.remove_math(text) var html = IPython.markdown_converter.makeHtml(text); html = IPython.mathjaxutils.replace_math(html) - try { this.set_rendered(html); } catch (e) { @@ -235,7 +233,6 @@ var IPython = (function (IPython) { "Error rendering Markdown!
" + e.toString()) ); } - this.typeset() this.element.find('div.text_cell_input').hide(); this.element.find("div.text_cell_render").show(); var code_snippets = this.element.find("pre > code"); @@ -250,8 +247,7 @@ var IPython = (function (IPython) { return '' + code + ''; }); - - IPython.mathjaxutils.queue_render() + this.typeset() this.rendered = true; } }; diff --git a/IPython/frontend/html/notebook/static/js/utils.js b/IPython/frontend/html/notebook/static/js/utils.js index d38d6da..559f4ea 100644 --- a/IPython/frontend/html/notebook/static/js/utils.js +++ b/IPython/frontend/html/notebook/static/js/utils.js @@ -195,11 +195,30 @@ IPython.utils = (function (IPython) { tmp = txt; do { txt = tmp; - tmp = txt.replace(/^.*\r(?!\n)/gm, ''); + tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline + tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line } while (tmp.length < txt.length); return txt; } + // Locate URLs in plain text and wrap them in spaces so that they can be + // better picked out by autoLinkUrls even after the text has been + // converted to HTML + function wrapUrls(txt) { + // Note this regexp is a modified version of one from + // Markdown.Converter For now it only supports http(s) and ftp URLs, + // but could easily support others (though file:// should maybe be + // avoided) + var url_re = /(^|\W)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi; + return txt.replace(url_re, "$1 $2$3 $4"); + } + + // Locate a URL with spaces around it and convert that to a anchor tag + function autoLinkUrls(txt) { + return txt.replace(/ ((https?|ftp):[^'">\s]+) /gi, + "$1"); + } + grow = function(element) { // Grow the cell by hand. This is used upon reloading from JSON, when the // autogrow handler is not called. @@ -261,6 +280,8 @@ IPython.utils = (function (IPython) { keycodes : keycodes, grow : grow, fixCarriageReturn : fixCarriageReturn, + wrapUrls : wrapUrls, + autoLinkUrls : autoLinkUrls, points_to_pixels : points_to_pixels }; diff --git a/IPython/frontend/html/notebook/templates/notebook.html b/IPython/frontend/html/notebook/templates/notebook.html index ef991d4..b809b09 100644 --- a/IPython/frontend/html/notebook/templates/notebook.html +++ b/IPython/frontend/html/notebook/templates/notebook.html @@ -2,7 +2,7 @@ {% block stylesheet %} {% if mathjax_url %} - + {% end %} + {% end %}