Show More
@@ -0,0 +1,14 b'' | |||||
|
1 | # encoding: utf-8 | |||
|
2 | """Terminal-based IPython entry point. | |||
|
3 | """ | |||
|
4 | #----------------------------------------------------------------------------- | |||
|
5 | # Copyright (c) 2012, IPython Development Team. | |||
|
6 | # | |||
|
7 | # Distributed under the terms of the Modified BSD License. | |||
|
8 | # | |||
|
9 | # The full license is in the file COPYING.txt, distributed with this software. | |||
|
10 | #----------------------------------------------------------------------------- | |||
|
11 | ||||
|
12 | from IPython.frontend.terminal.ipapp import launch_new_instance | |||
|
13 | ||||
|
14 | launch_new_instance() |
@@ -0,0 +1,73 b'' | |||||
|
1 | import os.path | |||
|
2 | ||||
|
3 | import nose.tools as nt | |||
|
4 | ||||
|
5 | import IPython.testing.tools as tt | |||
|
6 | from IPython.utils.syspathcontext import prepended_to_syspath | |||
|
7 | from IPython.utils.tempdir import TemporaryDirectory | |||
|
8 | ||||
|
9 | ext1_content = """ | |||
|
10 | def load_ipython_extension(ip): | |||
|
11 | print("Running ext1 load") | |||
|
12 | ||||
|
13 | def unload_ipython_extension(ip): | |||
|
14 | print("Running ext1 unload") | |||
|
15 | """ | |||
|
16 | ||||
|
17 | ext2_content = """ | |||
|
18 | def load_ipython_extension(ip): | |||
|
19 | print("Running ext2 load") | |||
|
20 | """ | |||
|
21 | ||||
|
22 | def test_extension_loading(): | |||
|
23 | em = get_ipython().extension_manager | |||
|
24 | with TemporaryDirectory() as td: | |||
|
25 | ext1 = os.path.join(td, 'ext1.py') | |||
|
26 | with open(ext1, 'w') as f: | |||
|
27 | f.write(ext1_content) | |||
|
28 | ||||
|
29 | ext2 = os.path.join(td, 'ext2.py') | |||
|
30 | with open(ext2, 'w') as f: | |||
|
31 | f.write(ext2_content) | |||
|
32 | ||||
|
33 | with prepended_to_syspath(td): | |||
|
34 | assert 'ext1' not in em.loaded | |||
|
35 | assert 'ext2' not in em.loaded | |||
|
36 | ||||
|
37 | # Load extension | |||
|
38 | with tt.AssertPrints("Running ext1 load"): | |||
|
39 | assert em.load_extension('ext1') is None | |||
|
40 | assert 'ext1' in em.loaded | |||
|
41 | ||||
|
42 | # Should refuse to load it again | |||
|
43 | with tt.AssertNotPrints("Running ext1 load"): | |||
|
44 | assert em.load_extension('ext1') == 'already loaded' | |||
|
45 | ||||
|
46 | # Reload | |||
|
47 | with tt.AssertPrints("Running ext1 unload"): | |||
|
48 | with tt.AssertPrints("Running ext1 load", suppress=False): | |||
|
49 | em.reload_extension('ext1') | |||
|
50 | ||||
|
51 | # Unload | |||
|
52 | with tt.AssertPrints("Running ext1 unload"): | |||
|
53 | assert em.unload_extension('ext1') is None | |||
|
54 | ||||
|
55 | # Can't unload again | |||
|
56 | with tt.AssertNotPrints("Running ext1 unload"): | |||
|
57 | assert em.unload_extension('ext1') == 'not loaded' | |||
|
58 | assert em.unload_extension('ext2') == 'not loaded' | |||
|
59 | ||||
|
60 | # Load extension 2 | |||
|
61 | with tt.AssertPrints("Running ext2 load"): | |||
|
62 | assert em.load_extension('ext2') is None | |||
|
63 | ||||
|
64 | # Can't unload this | |||
|
65 | assert em.unload_extension('ext2') == 'no unload function' | |||
|
66 | ||||
|
67 | # But can reload it | |||
|
68 | with tt.AssertPrints("Running ext2 load"): | |||
|
69 | em.reload_extension('ext2') | |||
|
70 | ||||
|
71 | def test_non_extension(): | |||
|
72 | em = get_ipython().extension_manager | |||
|
73 | nt.assert_equal(em.load_extension('sys'), "no load function") |
@@ -477,23 +477,51 b' class Pdb(OldPdb):' | |||||
477 | do_l = do_list |
|
477 | do_l = do_list | |
478 |
|
478 | |||
479 | def do_pdef(self, arg): |
|
479 | def do_pdef(self, arg): | |
480 | """The debugger interface to magic_pdef""" |
|
480 | """Print the call signature for any callable object. | |
|
481 | ||||
|
482 | The debugger interface to %pdef""" | |||
481 | namespaces = [('Locals', self.curframe.f_locals), |
|
483 | namespaces = [('Locals', self.curframe.f_locals), | |
482 | ('Globals', self.curframe.f_globals)] |
|
484 | ('Globals', self.curframe.f_globals)] | |
483 | self.shell.find_line_magic('pdef')(arg, namespaces=namespaces) |
|
485 | self.shell.find_line_magic('pdef')(arg, namespaces=namespaces) | |
484 |
|
486 | |||
485 | def do_pdoc(self, arg): |
|
487 | def do_pdoc(self, arg): | |
486 | """The debugger interface to magic_pdoc""" |
|
488 | """Print the docstring for an object. | |
|
489 | ||||
|
490 | The debugger interface to %pdoc.""" | |||
487 | namespaces = [('Locals', self.curframe.f_locals), |
|
491 | namespaces = [('Locals', self.curframe.f_locals), | |
488 | ('Globals', self.curframe.f_globals)] |
|
492 | ('Globals', self.curframe.f_globals)] | |
489 | self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces) |
|
493 | self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces) | |
490 |
|
494 | |||
|
495 | def do_pfile(self, arg): | |||
|
496 | """Print (or run through pager) the file where an object is defined. | |||
|
497 | ||||
|
498 | The debugger interface to %pfile. | |||
|
499 | """ | |||
|
500 | namespaces = [('Locals', self.curframe.f_locals), | |||
|
501 | ('Globals', self.curframe.f_globals)] | |||
|
502 | self.shell.find_line_magic('pfile')(arg, namespaces=namespaces) | |||
|
503 | ||||
491 | def do_pinfo(self, arg): |
|
504 | def do_pinfo(self, arg): | |
492 | """The debugger equivalant of ?obj""" |
|
505 | """Provide detailed information about an object. | |
|
506 | ||||
|
507 | The debugger interface to %pinfo, i.e., obj?.""" | |||
|
508 | namespaces = [('Locals', self.curframe.f_locals), | |||
|
509 | ('Globals', self.curframe.f_globals)] | |||
|
510 | self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces) | |||
|
511 | ||||
|
512 | def do_pinfo2(self, arg): | |||
|
513 | """Provide extra detailed information about an object. | |||
|
514 | ||||
|
515 | The debugger interface to %pinfo2, i.e., obj??.""" | |||
|
516 | namespaces = [('Locals', self.curframe.f_locals), | |||
|
517 | ('Globals', self.curframe.f_globals)] | |||
|
518 | self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces) | |||
|
519 | ||||
|
520 | def do_psource(self, arg): | |||
|
521 | """Print (or run through pager) the source code for an object.""" | |||
493 | namespaces = [('Locals', self.curframe.f_locals), |
|
522 | namespaces = [('Locals', self.curframe.f_locals), | |
494 | ('Globals', self.curframe.f_globals)] |
|
523 | ('Globals', self.curframe.f_globals)] | |
495 |
self.shell.find_line_magic('p |
|
524 | self.shell.find_line_magic('psource')(arg, namespaces=namespaces) | |
496 | namespaces=namespaces) |
|
|||
497 |
|
525 | |||
498 | def checkline(self, filename, lineno): |
|
526 | def checkline(self, filename, lineno): | |
499 | """Check whether specified line seems to be executable. |
|
527 | """Check whether specified line seems to be executable. |
@@ -23,8 +23,12 b' import sys' | |||||
23 | from urllib import urlretrieve |
|
23 | from urllib import urlretrieve | |
24 | from urlparse import urlparse |
|
24 | from urlparse import urlparse | |
25 |
|
25 | |||
|
26 | from IPython.core.error import UsageError | |||
26 | from IPython.config.configurable import Configurable |
|
27 | from IPython.config.configurable import Configurable | |
27 | from IPython.utils.traitlets import Instance |
|
28 | from IPython.utils.traitlets import Instance | |
|
29 | from IPython.utils.py3compat import PY3 | |||
|
30 | if PY3: | |||
|
31 | from imp import reload | |||
28 |
|
32 | |||
29 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
30 | # Main class |
|
34 | # Main class | |
@@ -44,10 +48,11 b' class ExtensionManager(Configurable):' | |||||
44 | the only argument. You can do anything you want with IPython at |
|
48 | the only argument. You can do anything you want with IPython at | |
45 | that point, including defining new magic and aliases, adding new |
|
49 | that point, including defining new magic and aliases, adding new | |
46 | components, etc. |
|
50 | components, etc. | |
47 |
|
51 | |||
48 | The :func:`load_ipython_extension` will be called again is you |
|
52 | You can also optionaly define an :func:`unload_ipython_extension(ipython)` | |
49 | load or reload the extension again. It is up to the extension |
|
53 | function, which will be called if the user unloads or reloads the extension. | |
50 | author to add code to manage that. |
|
54 | The extension manager will only call :func:`load_ipython_extension` again | |
|
55 | if the extension is reloaded. | |||
51 |
|
56 | |||
52 | You can put your extension modules anywhere you want, as long as |
|
57 | You can put your extension modules anywhere you want, as long as | |
53 | they can be imported by Python's standard import mechanism. However, |
|
58 | they can be imported by Python's standard import mechanism. However, | |
@@ -63,6 +68,7 b' class ExtensionManager(Configurable):' | |||||
63 | self.shell.on_trait_change( |
|
68 | self.shell.on_trait_change( | |
64 | self._on_ipython_dir_changed, 'ipython_dir' |
|
69 | self._on_ipython_dir_changed, 'ipython_dir' | |
65 | ) |
|
70 | ) | |
|
71 | self.loaded = set() | |||
66 |
|
72 | |||
67 | def __del__(self): |
|
73 | def __del__(self): | |
68 | self.shell.on_trait_change( |
|
74 | self.shell.on_trait_change( | |
@@ -80,26 +86,43 b' class ExtensionManager(Configurable):' | |||||
80 | def load_extension(self, module_str): |
|
86 | def load_extension(self, module_str): | |
81 | """Load an IPython extension by its module name. |
|
87 | """Load an IPython extension by its module name. | |
82 |
|
88 | |||
83 | If :func:`load_ipython_extension` returns anything, this function |
|
89 | Returns the string "already loaded" if the extension is already loaded, | |
84 | will return that object. |
|
90 | "no load function" if the module doesn't have a load_ipython_extension | |
|
91 | function, or None if it succeeded. | |||
85 | """ |
|
92 | """ | |
|
93 | if module_str in self.loaded: | |||
|
94 | return "already loaded" | |||
|
95 | ||||
86 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
96 | from IPython.utils.syspathcontext import prepended_to_syspath | |
87 |
|
97 | |||
88 | if module_str not in sys.modules: |
|
98 | if module_str not in sys.modules: | |
89 | with prepended_to_syspath(self.ipython_extension_dir): |
|
99 | with prepended_to_syspath(self.ipython_extension_dir): | |
90 | __import__(module_str) |
|
100 | __import__(module_str) | |
91 | mod = sys.modules[module_str] |
|
101 | mod = sys.modules[module_str] | |
92 |
|
|
102 | if self._call_load_ipython_extension(mod): | |
|
103 | self.loaded.add(module_str) | |||
|
104 | else: | |||
|
105 | return "no load function" | |||
93 |
|
106 | |||
94 | def unload_extension(self, module_str): |
|
107 | def unload_extension(self, module_str): | |
95 | """Unload an IPython extension by its module name. |
|
108 | """Unload an IPython extension by its module name. | |
96 |
|
109 | |||
97 | This function looks up the extension's name in ``sys.modules`` and |
|
110 | This function looks up the extension's name in ``sys.modules`` and | |
98 | simply calls ``mod.unload_ipython_extension(self)``. |
|
111 | simply calls ``mod.unload_ipython_extension(self)``. | |
|
112 | ||||
|
113 | Returns the string "no unload function" if the extension doesn't define | |||
|
114 | a function to unload itself, "not loaded" if the extension isn't loaded, | |||
|
115 | otherwise None. | |||
99 | """ |
|
116 | """ | |
|
117 | if module_str not in self.loaded: | |||
|
118 | return "not loaded" | |||
|
119 | ||||
100 | if module_str in sys.modules: |
|
120 | if module_str in sys.modules: | |
101 | mod = sys.modules[module_str] |
|
121 | mod = sys.modules[module_str] | |
102 | self._call_unload_ipython_extension(mod) |
|
122 | if self._call_unload_ipython_extension(mod): | |
|
123 | self.loaded.discard(module_str) | |||
|
124 | else: | |||
|
125 | return "no unload function" | |||
103 |
|
126 | |||
104 | def reload_extension(self, module_str): |
|
127 | def reload_extension(self, module_str): | |
105 | """Reload an IPython extension by calling reload. |
|
128 | """Reload an IPython extension by calling reload. | |
@@ -111,21 +134,25 b' class ExtensionManager(Configurable):' | |||||
111 | """ |
|
134 | """ | |
112 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
135 | from IPython.utils.syspathcontext import prepended_to_syspath | |
113 |
|
136 | |||
114 | with prepended_to_syspath(self.ipython_extension_dir): |
|
137 | if (module_str in self.loaded) and (module_str in sys.modules): | |
115 | if module_str in sys.modules: |
|
138 | self.unload_extension(module_str) | |
116 |
|
|
139 | mod = sys.modules[module_str] | |
|
140 | with prepended_to_syspath(self.ipython_extension_dir): | |||
117 | reload(mod) |
|
141 | reload(mod) | |
118 |
|
|
142 | if self._call_load_ipython_extension(mod): | |
119 | else: |
|
143 | self.loaded.add(module_str) | |
120 | self.load_extension(module_str) |
|
144 | else: | |
|
145 | self.load_extension(module_str) | |||
121 |
|
146 | |||
122 | def _call_load_ipython_extension(self, mod): |
|
147 | def _call_load_ipython_extension(self, mod): | |
123 | if hasattr(mod, 'load_ipython_extension'): |
|
148 | if hasattr(mod, 'load_ipython_extension'): | |
124 |
|
|
149 | mod.load_ipython_extension(self.shell) | |
|
150 | return True | |||
125 |
|
151 | |||
126 | def _call_unload_ipython_extension(self, mod): |
|
152 | def _call_unload_ipython_extension(self, mod): | |
127 | if hasattr(mod, 'unload_ipython_extension'): |
|
153 | if hasattr(mod, 'unload_ipython_extension'): | |
128 |
|
|
154 | mod.unload_ipython_extension(self.shell) | |
|
155 | return True | |||
129 |
|
156 | |||
130 | def install_extension(self, url, filename=None): |
|
157 | def install_extension(self, url, filename=None): | |
131 | """Download and install an IPython extension. |
|
158 | """Download and install an IPython extension. |
@@ -629,7 +629,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
629 | # override sys.stdout and sys.stderr themselves, you need to do that |
|
629 | # override sys.stdout and sys.stderr themselves, you need to do that | |
630 | # *before* instantiating this class, because io holds onto |
|
630 | # *before* instantiating this class, because io holds onto | |
631 | # references to the underlying streams. |
|
631 | # references to the underlying streams. | |
632 | if sys.platform == 'win32' and self.has_readline: |
|
632 | if (sys.platform == 'win32' or sys.platform == 'cli') and self.has_readline: | |
633 | io.stdout = io.stderr = io.IOStream(self.readline._outputfile) |
|
633 | io.stdout = io.stderr = io.IOStream(self.readline._outputfile) | |
634 | else: |
|
634 | else: | |
635 | io.stdout = io.IOStream(sys.stdout) |
|
635 | io.stdout = io.IOStream(sys.stdout) |
@@ -173,11 +173,17 b' class magic_arguments(ArgDecorator):' | |||||
173 | return func |
|
173 | return func | |
174 |
|
174 | |||
175 |
|
175 | |||
176 |
class |
|
176 | class ArgMethodWrapper(ArgDecorator): | |
177 | """ Store arguments and keywords to pass to add_argument(). |
|
177 | ||
|
178 | """ | |||
|
179 | Base class to define a wrapper for ArgumentParser method. | |||
|
180 | ||||
|
181 | Child class must define either `_method_name` or `add_to_parser`. | |||
178 |
|
182 | |||
179 | Instances also serve to decorate command methods. |
|
|||
180 | """ |
|
183 | """ | |
|
184 | ||||
|
185 | _method_name = None | |||
|
186 | ||||
181 | def __init__(self, *args, **kwds): |
|
187 | def __init__(self, *args, **kwds): | |
182 | self.args = args |
|
188 | self.args = args | |
183 | self.kwds = kwds |
|
189 | self.kwds = kwds | |
@@ -187,18 +193,31 b' class argument(ArgDecorator):' | |||||
187 | """ |
|
193 | """ | |
188 | if group is not None: |
|
194 | if group is not None: | |
189 | parser = group |
|
195 | parser = group | |
190 |
parser. |
|
196 | getattr(parser, self._method_name)(*self.args, **self.kwds) | |
191 | return None |
|
197 | return None | |
192 |
|
198 | |||
193 |
|
199 | |||
194 |
class argument |
|
200 | class argument(ArgMethodWrapper): | |
|
201 | """ Store arguments and keywords to pass to add_argument(). | |||
|
202 | ||||
|
203 | Instances also serve to decorate command methods. | |||
|
204 | """ | |||
|
205 | _method_name = 'add_argument' | |||
|
206 | ||||
|
207 | ||||
|
208 | class defaults(ArgMethodWrapper): | |||
|
209 | """ Store arguments and keywords to pass to set_defaults(). | |||
|
210 | ||||
|
211 | Instances also serve to decorate command methods. | |||
|
212 | """ | |||
|
213 | _method_name = 'set_defaults' | |||
|
214 | ||||
|
215 | ||||
|
216 | class argument_group(ArgMethodWrapper): | |||
195 | """ Store arguments and keywords to pass to add_argument_group(). |
|
217 | """ Store arguments and keywords to pass to add_argument_group(). | |
196 |
|
218 | |||
197 | Instances also serve to decorate command methods. |
|
219 | Instances also serve to decorate command methods. | |
198 | """ |
|
220 | """ | |
199 | def __init__(self, *args, **kwds): |
|
|||
200 | self.args = args |
|
|||
201 | self.kwds = kwds |
|
|||
202 |
|
221 | |||
203 | def add_to_parser(self, parser, group): |
|
222 | def add_to_parser(self, parser, group): | |
204 | """ Add this object's information to the parser. |
|
223 | """ Add this object's information to the parser. |
@@ -313,7 +313,8 b' Currently the magic system has the following functions:""",' | |||||
313 | import IPython.utils.rlineimpl as readline |
|
313 | import IPython.utils.rlineimpl as readline | |
314 |
|
314 | |||
315 | if not shell.colors_force and \ |
|
315 | if not shell.colors_force and \ | |
316 |
not readline.have_readline and |
|
316 | not readline.have_readline and \ | |
|
317 | (sys.platform == "win32" or sys.platform == "cli"): | |||
317 | msg = """\ |
|
318 | msg = """\ | |
318 | Proper color support under MS Windows requires the pyreadline library. |
|
319 | Proper color support under MS Windows requires the pyreadline library. | |
319 | You can find it at: |
|
320 | You can find it at: |
@@ -563,6 +563,11 b' python-profiler package from non-free.""")' | |||||
563 | return |
|
563 | return | |
564 | # if we find a good linenumber, set the breakpoint |
|
564 | # if we find a good linenumber, set the breakpoint | |
565 | deb.do_break('%s:%s' % (filename, bp)) |
|
565 | deb.do_break('%s:%s' % (filename, bp)) | |
|
566 | ||||
|
567 | # Mimic Pdb._runscript(...) | |||
|
568 | deb._wait_for_mainpyfile = True | |||
|
569 | deb.mainpyfile = deb.canonic(filename) | |||
|
570 | ||||
566 | # Start file run |
|
571 | # Start file run | |
567 | print "NOTE: Enter 'c' at the", |
|
572 | print "NOTE: Enter 'c' at the", | |
568 | print "%s prompt to start your script." % deb.prompt |
|
573 | print "%s prompt to start your script." % deb.prompt |
@@ -59,14 +59,30 b' class ExtensionMagics(Magics):' | |||||
59 | """Load an IPython extension by its module name.""" |
|
59 | """Load an IPython extension by its module name.""" | |
60 | if not module_str: |
|
60 | if not module_str: | |
61 | raise UsageError('Missing module name.') |
|
61 | raise UsageError('Missing module name.') | |
62 |
re |
|
62 | res = self.shell.extension_manager.load_extension(module_str) | |
|
63 | ||||
|
64 | if res == 'already loaded': | |||
|
65 | print "The %s extension is already loaded. To reload it, use:" % module_str | |||
|
66 | print " %reload_ext", module_str | |||
|
67 | elif res == 'no load function': | |||
|
68 | print "The %s module is not an IPython extension." % module_str | |||
63 |
|
69 | |||
64 | @line_magic |
|
70 | @line_magic | |
65 | def unload_ext(self, module_str): |
|
71 | def unload_ext(self, module_str): | |
66 |
"""Unload an IPython extension by its module name. |
|
72 | """Unload an IPython extension by its module name. | |
|
73 | ||||
|
74 | Not all extensions can be unloaded, only those which define an | |||
|
75 | ``unload_ipython_extension`` function. | |||
|
76 | """ | |||
67 | if not module_str: |
|
77 | if not module_str: | |
68 | raise UsageError('Missing module name.') |
|
78 | raise UsageError('Missing module name.') | |
69 | self.shell.extension_manager.unload_extension(module_str) |
|
79 | ||
|
80 | res = self.shell.extension_manager.unload_extension(module_str) | |||
|
81 | ||||
|
82 | if res == 'no unload function': | |||
|
83 | print "The %s extension doesn't define how to unload it." % module_str | |||
|
84 | elif res == "not loaded": | |||
|
85 | print "The %s extension is not loaded." % module_str | |||
70 |
|
86 | |||
71 | @line_magic |
|
87 | @line_magic | |
72 | def reload_ext(self, module_str): |
|
88 | def reload_ext(self, module_str): |
@@ -16,10 +16,13 b' from __future__ import print_function' | |||||
16 | # Stdlib |
|
16 | # Stdlib | |
17 | import os |
|
17 | import os | |
18 | from io import open as io_open |
|
18 | from io import open as io_open | |
|
19 | from IPython.external.argparse import Action | |||
19 |
|
20 | |||
20 | # Our own packages |
|
21 | # Our own packages | |
21 | from IPython.core.error import StdinNotImplementedError |
|
22 | from IPython.core.error import StdinNotImplementedError | |
22 | from IPython.core.magic import Magics, magics_class, line_magic |
|
23 | from IPython.core.magic import Magics, magics_class, line_magic | |
|
24 | from IPython.core.magic_arguments import (argument, magic_arguments, | |||
|
25 | parse_argstring) | |||
23 | from IPython.testing.skipdoctest import skip_doctest |
|
26 | from IPython.testing.skipdoctest import skip_doctest | |
24 | from IPython.utils import io |
|
27 | from IPython.utils import io | |
25 |
|
28 | |||
@@ -27,16 +30,71 b' from IPython.utils import io' | |||||
27 | # Magics class implementation |
|
30 | # Magics class implementation | |
28 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
29 |
|
32 | |||
|
33 | ||||
|
34 | _unspecified = object() | |||
|
35 | ||||
|
36 | ||||
30 | @magics_class |
|
37 | @magics_class | |
31 | class HistoryMagics(Magics): |
|
38 | class HistoryMagics(Magics): | |
32 |
|
39 | |||
|
40 | @magic_arguments() | |||
|
41 | @argument( | |||
|
42 | '-n', dest='print_nums', action='store_true', default=False, | |||
|
43 | help=""" | |||
|
44 | print line numbers for each input. | |||
|
45 | This feature is only available if numbered prompts are in use. | |||
|
46 | """) | |||
|
47 | @argument( | |||
|
48 | '-o', dest='get_output', action='store_true', default=False, | |||
|
49 | help="also print outputs for each input.") | |||
|
50 | @argument( | |||
|
51 | '-p', dest='pyprompts', action='store_true', default=False, | |||
|
52 | help=""" | |||
|
53 | print classic '>>>' python prompts before each input. | |||
|
54 | This is useful for making documentation, and in conjunction | |||
|
55 | with -o, for producing doctest-ready output. | |||
|
56 | """) | |||
|
57 | @argument( | |||
|
58 | '-t', dest='raw', action='store_false', default=True, | |||
|
59 | help=""" | |||
|
60 | print the 'translated' history, as IPython understands it. | |||
|
61 | IPython filters your input and converts it all into valid Python | |||
|
62 | source before executing it (things like magics or aliases are turned | |||
|
63 | into function calls, for example). With this option, you'll see the | |||
|
64 | native history instead of the user-entered version: '%%cd /' will be | |||
|
65 | seen as 'get_ipython().magic("%%cd /")' instead of '%%cd /'. | |||
|
66 | """) | |||
|
67 | @argument( | |||
|
68 | '-f', dest='filename', | |||
|
69 | help=""" | |||
|
70 | FILENAME: instead of printing the output to the screen, redirect | |||
|
71 | it to the given file. The file is always overwritten, though *when | |||
|
72 | it can*, IPython asks for confirmation first. In particular, running | |||
|
73 | the command 'history -f FILENAME' from the IPython Notebook | |||
|
74 | interface will replace FILENAME even if it already exists *without* | |||
|
75 | confirmation. | |||
|
76 | """) | |||
|
77 | @argument( | |||
|
78 | '-g', dest='pattern', nargs='*', default=None, | |||
|
79 | help=""" | |||
|
80 | treat the arg as a glob pattern to search for in (full) history. | |||
|
81 | This includes the saved history (almost all commands ever written). | |||
|
82 | The pattern may contain '?' to match one unknown character and '*' | |||
|
83 | to match any number of unknown characters. Use '%%hist -g' to show | |||
|
84 | full saved history (may be very long). | |||
|
85 | """) | |||
|
86 | @argument( | |||
|
87 | '-l', dest='limit', type=int, nargs='?', default=_unspecified, | |||
|
88 | help=""" | |||
|
89 | get the last n lines from all sessions. Specify n as a single | |||
|
90 | arg, or the default is the last 10 lines. | |||
|
91 | """) | |||
|
92 | @argument('range', nargs='*') | |||
33 | @skip_doctest |
|
93 | @skip_doctest | |
34 | @line_magic |
|
94 | @line_magic | |
35 | def history(self, parameter_s = ''): |
|
95 | def history(self, parameter_s = ''): | |
36 | """Print input history (_i<n> variables), with most recent last. |
|
96 | """Print input history (_i<n> variables), with most recent last. | |
37 |
|
97 | |||
38 | %history [-o -p -t -n] [-f filename] [range | -g pattern | -l number] |
|
|||
39 |
|
||||
40 | By default, input history is printed without line numbers so it can be |
|
98 | By default, input history is printed without line numbers so it can be | |
41 | directly pasted into an editor. Use -n to show them. |
|
99 | directly pasted into an editor. Use -n to show them. | |
42 |
|
100 | |||
@@ -52,43 +110,6 b' class HistoryMagics(Magics):' | |||||
52 |
|
110 | |||
53 | The same syntax is used by %macro, %save, %edit, %rerun |
|
111 | The same syntax is used by %macro, %save, %edit, %rerun | |
54 |
|
112 | |||
55 | Options: |
|
|||
56 |
|
||||
57 | -n: print line numbers for each input. |
|
|||
58 | This feature is only available if numbered prompts are in use. |
|
|||
59 |
|
||||
60 | -o: also print outputs for each input. |
|
|||
61 |
|
||||
62 | -p: print classic '>>>' python prompts before each input. This is |
|
|||
63 | useful for making documentation, and in conjunction with -o, for |
|
|||
64 | producing doctest-ready output. |
|
|||
65 |
|
||||
66 | -r: (default) print the 'raw' history, i.e. the actual commands you |
|
|||
67 | typed. |
|
|||
68 |
|
||||
69 | -t: print the 'translated' history, as IPython understands it. |
|
|||
70 | IPython filters your input and converts it all into valid Python |
|
|||
71 | source before executing it (things like magics or aliases are turned |
|
|||
72 | into function calls, for example). With this option, you'll see the |
|
|||
73 | native history instead of the user-entered version: '%cd /' will be |
|
|||
74 | seen as 'get_ipython().magic("%cd /")' instead of '%cd /'. |
|
|||
75 |
|
||||
76 | -g: treat the arg as a pattern to grep for in (full) history. |
|
|||
77 | This includes the saved history (almost all commands ever written). |
|
|||
78 | The pattern may contain '?' to match one unknown character and '*' |
|
|||
79 | to match any number of unknown characters. Use '%hist -g' to show |
|
|||
80 | full saved history (may be very long). |
|
|||
81 |
|
||||
82 | -l: get the last n lines from all sessions. Specify n as a single |
|
|||
83 | arg, or the default is the last 10 lines. |
|
|||
84 |
|
||||
85 | -f FILENAME: instead of printing the output to the screen, redirect |
|
|||
86 | it to the given file. The file is always overwritten, though *when |
|
|||
87 | it can*, IPython asks for confirmation first. In particular, running |
|
|||
88 | the command 'history -f FILENAME' from the IPython Notebook |
|
|||
89 | interface will replace FILENAME even if it already exists *without* |
|
|||
90 | confirmation. |
|
|||
91 |
|
||||
92 | Examples |
|
113 | Examples | |
93 | -------- |
|
114 | -------- | |
94 | :: |
|
115 | :: | |
@@ -100,11 +121,7 b' class HistoryMagics(Magics):' | |||||
100 |
|
121 | |||
101 | """ |
|
122 | """ | |
102 |
|
123 | |||
103 | if not self.shell.displayhook.do_full_cache: |
|
124 | args = parse_argstring(self.history, parameter_s) | |
104 | print('This feature is only available if numbered prompts ' |
|
|||
105 | 'are in use.') |
|
|||
106 | return |
|
|||
107 | opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string') |
|
|||
108 |
|
125 | |||
109 | # For brevity |
|
126 | # For brevity | |
110 | history_manager = self.shell.history_manager |
|
127 | history_manager = self.shell.history_manager | |
@@ -116,9 +133,8 b' class HistoryMagics(Magics):' | |||||
116 | return "%s/%s" % (session, line) |
|
133 | return "%s/%s" % (session, line) | |
117 |
|
134 | |||
118 | # Check if output to specific file was requested. |
|
135 | # Check if output to specific file was requested. | |
119 | try: |
|
136 | outfname = args.filename | |
120 |
|
|
137 | if not outfname: | |
121 | except KeyError: |
|
|||
122 | outfile = io.stdout # default |
|
138 | outfile = io.stdout # default | |
123 | # We don't want to close stdout at the end! |
|
139 | # We don't want to close stdout at the end! | |
124 | close_at_end = False |
|
140 | close_at_end = False | |
@@ -135,27 +151,29 b' class HistoryMagics(Magics):' | |||||
135 | outfile = io_open(outfname, 'w', encoding='utf-8') |
|
151 | outfile = io_open(outfname, 'w', encoding='utf-8') | |
136 | close_at_end = True |
|
152 | close_at_end = True | |
137 |
|
153 | |||
138 |
print_nums = |
|
154 | print_nums = args.print_nums | |
139 |
get_output = |
|
155 | get_output = args.get_output | |
140 |
pyprompts = |
|
156 | pyprompts = args.pyprompts | |
141 | # Raw history is the default |
|
157 | raw = args.raw | |
142 | raw = not('t' in opts) |
|
|||
143 |
|
158 | |||
144 | pattern = None |
|
159 | pattern = None | |
|
160 | limit = None if args.limit is _unspecified else args.limit | |||
145 |
|
161 | |||
146 | if 'g' in opts: # Glob search |
|
162 | if args.pattern is not None: | |
147 | pattern = "*" + args + "*" if args else "*" |
|
163 | if args.pattern: | |
148 | hist = history_manager.search(pattern, raw=raw, output=get_output) |
|
164 | pattern = "*" + " ".join(args.pattern) + "*" | |
|
165 | else: | |||
|
166 | pattern = "*" | |||
|
167 | hist = history_manager.search(pattern, raw=raw, output=get_output, | |||
|
168 | n=limit) | |||
149 | print_nums = True |
|
169 | print_nums = True | |
150 | elif 'l' in opts: # Get 'tail' |
|
170 | elif args.limit is not _unspecified: | |
151 | try: |
|
171 | n = 10 if limit is None else limit | |
152 | n = int(args) |
|
|||
153 | except (ValueError, IndexError): |
|
|||
154 | n = 10 |
|
|||
155 | hist = history_manager.get_tail(n, raw=raw, output=get_output) |
|
172 | hist = history_manager.get_tail(n, raw=raw, output=get_output) | |
156 | else: |
|
173 | else: | |
157 |
if args: |
|
174 | if args.range: # Get history by ranges | |
158 |
hist = history_manager.get_range_by_str(args, |
|
175 | hist = history_manager.get_range_by_str(" ".join(args.range), | |
|
176 | raw, get_output) | |||
159 | else: # Just get history for the current session |
|
177 | else: # Just get history for the current session | |
160 | hist = history_manager.get_range(raw=raw, output=get_output) |
|
178 | hist = history_manager.get_range(raw=raw, output=get_output) | |
161 |
|
179 |
@@ -69,7 +69,7 b' class NamespaceMagics(Magics):' | |||||
69 | @skip_doctest |
|
69 | @skip_doctest | |
70 | @line_magic |
|
70 | @line_magic | |
71 | def pdef(self, parameter_s='', namespaces=None): |
|
71 | def pdef(self, parameter_s='', namespaces=None): | |
72 |
"""Print the |
|
72 | """Print the call signature for any callable object. | |
73 |
|
73 | |||
74 | If the object is a class, print the constructor information. |
|
74 | If the object is a class, print the constructor information. | |
75 |
|
75 | |||
@@ -98,7 +98,7 b' class NamespaceMagics(Magics):' | |||||
98 | self.shell._inspect('psource',parameter_s, namespaces) |
|
98 | self.shell._inspect('psource',parameter_s, namespaces) | |
99 |
|
99 | |||
100 | @line_magic |
|
100 | @line_magic | |
101 | def pfile(self, parameter_s=''): |
|
101 | def pfile(self, parameter_s='', namespaces=None): | |
102 | """Print (or run through pager) the file where an object is defined. |
|
102 | """Print (or run through pager) the file where an object is defined. | |
103 |
|
103 | |||
104 | The file opens at the line where the object definition begins. IPython |
|
104 | The file opens at the line where the object definition begins. IPython | |
@@ -111,7 +111,7 b' class NamespaceMagics(Magics):' | |||||
111 | viewer.""" |
|
111 | viewer.""" | |
112 |
|
112 | |||
113 | # first interpret argument as an object name |
|
113 | # first interpret argument as an object name | |
114 | out = self.shell._inspect('pfile',parameter_s) |
|
114 | out = self.shell._inspect('pfile',parameter_s, namespaces) | |
115 | # if not, try the input as a filename |
|
115 | # if not, try the input as a filename | |
116 | if out == 'not found': |
|
116 | if out == 'not found': | |
117 | try: |
|
117 | try: |
@@ -344,7 +344,7 b' class Inspector:' | |||||
344 | self.set_active_scheme(scheme) |
|
344 | self.set_active_scheme(scheme) | |
345 |
|
345 | |||
346 | def _getdef(self,obj,oname=''): |
|
346 | def _getdef(self,obj,oname=''): | |
347 |
"""Return the |
|
347 | """Return the call signature for any callable object. | |
348 |
|
348 | |||
349 | If any exception is generated, None is returned instead and the |
|
349 | If any exception is generated, None is returned instead and the | |
350 | exception is suppressed.""" |
|
350 | exception is suppressed.""" | |
@@ -373,7 +373,7 b' class Inspector:' | |||||
373 | print() |
|
373 | print() | |
374 |
|
374 | |||
375 | def pdef(self, obj, oname=''): |
|
375 | def pdef(self, obj, oname=''): | |
376 |
"""Print the |
|
376 | """Print the call signature for any callable object. | |
377 |
|
377 | |||
378 | If the object is a class, print the constructor information.""" |
|
378 | If the object is a class, print the constructor information.""" | |
379 |
|
379 |
@@ -96,13 +96,41 b' def doctest_run_option_parser():' | |||||
96 | In [2]: %run print_argv.py print*.py |
|
96 | In [2]: %run print_argv.py print*.py | |
97 | ['print_argv.py'] |
|
97 | ['print_argv.py'] | |
98 |
|
98 | |||
99 |
In [3]: %run print_argv.py print |
|
99 | In [3]: %run -G print_argv.py print*.py | |
100 | ['print*.py'] |
|
100 | ['print*.py'] | |
101 |
|
101 | |||
102 | In [4]: %run print_argv.py 'print*.py' |
|
102 | """ | |
|
103 | ||||
|
104 | ||||
|
105 | @dec.skip_win32 | |||
|
106 | def doctest_run_option_parser_for_posix(): | |||
|
107 | r"""Test option parser in %run (Linux/OSX specific). | |||
|
108 | ||||
|
109 | You need double quote to escape glob in POSIX systems: | |||
|
110 | ||||
|
111 | In [1]: %run print_argv.py print\\*.py | |||
|
112 | ['print*.py'] | |||
|
113 | ||||
|
114 | You can't use quote to escape glob in POSIX systems: | |||
|
115 | ||||
|
116 | In [2]: %run print_argv.py 'print*.py' | |||
103 | ['print_argv.py'] |
|
117 | ['print_argv.py'] | |
104 |
|
118 | |||
105 | In [5]: %run -G print_argv.py print*.py |
|
119 | """ | |
|
120 | ||||
|
121 | ||||
|
122 | @dec.skip_if_not_win32 | |||
|
123 | def doctest_run_option_parser_for_windows(): | |||
|
124 | r"""Test option parser in %run (Windows specific). | |||
|
125 | ||||
|
126 | In Windows, you can't escape ``*` `by backslash: | |||
|
127 | ||||
|
128 | In [1]: %run print_argv.py print\\*.py | |||
|
129 | ['print\\*.py'] | |||
|
130 | ||||
|
131 | You can use quote to escape glob: | |||
|
132 | ||||
|
133 | In [2]: %run print_argv.py 'print*.py' | |||
106 | ['print*.py'] |
|
134 | ['print*.py'] | |
107 |
|
135 | |||
108 | """ |
|
136 | """ |
@@ -514,14 +514,8 b' class AutoreloadMagics(Magics):' | |||||
514 | pass |
|
514 | pass | |
515 |
|
515 | |||
516 |
|
516 | |||
517 | _loaded = False |
|
|||
518 |
|
||||
519 |
|
||||
520 | def load_ipython_extension(ip): |
|
517 | def load_ipython_extension(ip): | |
521 | """Load the extension in IPython.""" |
|
518 | """Load the extension in IPython.""" | |
522 | global _loaded |
|
519 | auto_reload = AutoreloadMagics(ip) | |
523 | if not _loaded: |
|
520 | ip.register_magics(auto_reload) | |
524 | auto_reload = AutoreloadMagics(ip) |
|
521 | ip.set_hook('pre_run_code_hook', auto_reload.pre_run_code_hook) | |
525 | ip.register_magics(auto_reload) |
|
|||
526 | ip.set_hook('pre_run_code_hook', auto_reload.pre_run_code_hook) |
|
|||
527 | _loaded = True |
|
@@ -273,11 +273,7 b' class CythonMagics(Magics):' | |||||
273 | html = '\n'.join(l for l in html.splitlines() if not r.match(l)) |
|
273 | html = '\n'.join(l for l in html.splitlines() if not r.match(l)) | |
274 | return html |
|
274 | return html | |
275 |
|
275 | |||
276 | _loaded = False |
|
|||
277 |
|
276 | |||
278 | def load_ipython_extension(ip): |
|
277 | def load_ipython_extension(ip): | |
279 | """Load the extension in IPython.""" |
|
278 | """Load the extension in IPython.""" | |
280 | global _loaded |
|
279 | ip.register_magics(CythonMagics) | |
281 | if not _loaded: |
|
|||
282 | ip.register_magics(CythonMagics) |
|
|||
283 | _loaded = True |
|
@@ -362,10 +362,6 b' __doc__ = __doc__.format(' | |||||
362 | ) |
|
362 | ) | |
363 |
|
363 | |||
364 |
|
364 | |||
365 | _loaded = False |
|
|||
366 | def load_ipython_extension(ip): |
|
365 | def load_ipython_extension(ip): | |
367 | """Load the extension in IPython.""" |
|
366 | """Load the extension in IPython.""" | |
368 | global _loaded |
|
367 | ip.register_magics(OctaveMagics) | |
369 | if not _loaded: |
|
|||
370 | ip.register_magics(OctaveMagics) |
|
|||
371 | _loaded = True |
|
@@ -588,10 +588,6 b' __doc__ = __doc__.format(' | |||||
588 | ) |
|
588 | ) | |
589 |
|
589 | |||
590 |
|
590 | |||
591 | _loaded = False |
|
|||
592 | def load_ipython_extension(ip): |
|
591 | def load_ipython_extension(ip): | |
593 | """Load the extension in IPython.""" |
|
592 | """Load the extension in IPython.""" | |
594 | global _loaded |
|
593 | ip.register_magics(RMagics) | |
595 | if not _loaded: |
|
|||
596 | ip.register_magics(RMagics) |
|
|||
597 | _loaded = True |
|
@@ -209,12 +209,6 b' class StoreMagics(Magics):' | |||||
209 | print "Stored '%s' (%s)" % (args[0], obj.__class__.__name__) |
|
209 | print "Stored '%s' (%s)" % (args[0], obj.__class__.__name__) | |
210 |
|
210 | |||
211 |
|
211 | |||
212 | _loaded = False |
|
|||
213 |
|
||||
214 |
|
||||
215 | def load_ipython_extension(ip): |
|
212 | def load_ipython_extension(ip): | |
216 | """Load the extension in IPython.""" |
|
213 | """Load the extension in IPython.""" | |
217 | global _loaded |
|
214 | ip.register_magics(StoreMagics) | |
218 | if not _loaded: |
|
|||
219 | ip.register_magics(StoreMagics) |
|
|||
220 | _loaded = True |
|
@@ -22,6 +22,7 b' Authors' | |||||
22 | from __future__ import print_function |
|
22 | from __future__ import print_function | |
23 |
|
23 | |||
24 | import os,sys, atexit |
|
24 | import os,sys, atexit | |
|
25 | import signal | |||
25 | import socket |
|
26 | import socket | |
26 | from multiprocessing import Process |
|
27 | from multiprocessing import Process | |
27 | from getpass import getpass, getuser |
|
28 | from getpass import getpass, getuser | |
@@ -331,9 +332,10 b' def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None' | |||||
331 | except Exception as e: |
|
332 | except Exception as e: | |
332 | print ('*** Failed to connect to %s:%d: %r' % (server, port, e)) |
|
333 | print ('*** Failed to connect to %s:%d: %r' % (server, port, e)) | |
333 | sys.exit(1) |
|
334 | sys.exit(1) | |
334 |
|
335 | |||
335 | # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport)) |
|
336 | # Don't let SIGINT kill the tunnel subprocess | |
336 |
|
337 | signal.signal(signal.SIGINT, signal.SIG_IGN) | ||
|
338 | ||||
337 | try: |
|
339 | try: | |
338 | forward_tunnel(lport, remoteip, rport, client.get_transport()) |
|
340 | forward_tunnel(lport, remoteip, rport, client.get_transport()) | |
339 | except KeyboardInterrupt: |
|
341 | except KeyboardInterrupt: |
@@ -470,11 +470,14 b' class NotebookApp(BaseIPythonApplication):' | |||||
470 | ssl_options = None |
|
470 | ssl_options = None | |
471 | self.web_app.password = self.password |
|
471 | self.web_app.password = self.password | |
472 | self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options) |
|
472 | self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options) | |
473 | if ssl_options is None and not self.ip and not (self.read_only and not self.password): |
|
473 | if not self.ip: | |
474 |
|
|
474 | warning = "WARNING: The notebook server is listening on all IP addresses" | |
475 | 'but not using any encryption or authentication. This is highly ' |
|
475 | if ssl_options is None: | |
476 | 'insecure and not recommended.') |
|
476 | self.log.critical(warning + " and not using encryption. This" | |
477 |
|
477 | "is not recommended.") | ||
|
478 | if not self.password and not self.read_only: | |||
|
479 | self.log.critical(warning + "and not using authentication." | |||
|
480 | "This is highly insecure and not recommended.") | |||
478 | success = None |
|
481 | success = None | |
479 | for port in random_ports(self.port, self.port_retries+1): |
|
482 | for port in random_ports(self.port, self.port_retries+1): | |
480 | try: |
|
483 | try: |
@@ -50,15 +50,13 b' var IPython = (function (IPython) {' | |||||
50 | }); |
|
50 | }); | |
51 | }; |
|
51 | }; | |
52 |
|
52 | |||
53 |
|
||||
54 | // typeset with MathJax if MathJax is available |
|
|||
55 | Cell.prototype.typeset = function () { |
|
53 | Cell.prototype.typeset = function () { | |
56 | if (window.MathJax){ |
|
54 | if (window.MathJax){ | |
57 | MathJax.Hub.Queue(["Typeset",MathJax.Hub]); |
|
55 | var cell_math = this.element.get(0); | |
|
56 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,cell_math]); | |||
58 | } |
|
57 | } | |
59 | }; |
|
58 | }; | |
60 |
|
59 | |||
61 |
|
||||
62 | Cell.prototype.select = function () { |
|
60 | Cell.prototype.select = function () { | |
63 | this.element.addClass('ui-widget-content ui-corner-all'); |
|
61 | this.element.addClass('ui-widget-content ui-corner-all'); | |
64 | this.selected = true; |
|
62 | this.selected = true; |
@@ -123,6 +123,7 b' var IPython = (function (IPython) {' | |||||
123 | } else if (event.keyCode === key.TAB && event.type == 'keydown') { |
|
123 | } else if (event.keyCode === key.TAB && event.type == 'keydown') { | |
124 | // Tab completion. |
|
124 | // Tab completion. | |
125 | //Do not trim here because of tooltip |
|
125 | //Do not trim here because of tooltip | |
|
126 | if (editor.somethingSelected()){return false} | |||
126 | var pre_cursor = editor.getRange({line:cur.line,ch:0},cur); |
|
127 | var pre_cursor = editor.getRange({line:cur.line,ch:0},cur); | |
127 | if (pre_cursor.trim() === "") { |
|
128 | if (pre_cursor.trim() === "") { | |
128 | // Don't autocomplete if the part of the line before the cursor |
|
129 | // Don't autocomplete if the part of the line before the cursor |
@@ -64,7 +64,7 b' var IPython = (function (IPython) {' | |||||
64 |
|
64 | |||
65 |
|
65 | |||
66 | Kernel.prototype.restart = function () { |
|
66 | Kernel.prototype.restart = function () { | |
67 |
$([IPython.events]).trigger( |
|
67 | $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); | |
68 | var that = this; |
|
68 | var that = this; | |
69 | if (this.running) { |
|
69 | if (this.running) { | |
70 | this.stop_channels(); |
|
70 | this.stop_channels(); | |
@@ -86,6 +86,7 b' var IPython = (function (IPython) {' | |||||
86 | this.start_channels(); |
|
86 | this.start_channels(); | |
87 | this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this); |
|
87 | this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this); | |
88 | this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this); |
|
88 | this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this); | |
|
89 | $([IPython.events]).trigger('status_started.Kernel', {kernel: this}); | |||
89 | }; |
|
90 | }; | |
90 |
|
91 | |||
91 |
|
92 | |||
@@ -245,7 +246,8 b' var IPython = (function (IPython) {' | |||||
245 | user_expressions : {}, |
|
246 | user_expressions : {}, | |
246 | allow_stdin : false |
|
247 | allow_stdin : false | |
247 | }; |
|
248 | }; | |
248 |
|
|
249 | $.extend(true, content, options) | |
|
250 | $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); | |||
249 | var msg = this._get_msg("execute_request", content); |
|
251 | var msg = this._get_msg("execute_request", content); | |
250 | this.shell_channel.send(JSON.stringify(msg)); |
|
252 | this.shell_channel.send(JSON.stringify(msg)); | |
251 | this.set_callbacks_for_msg(msg.header.msg_id, callbacks); |
|
253 | this.set_callbacks_for_msg(msg.header.msg_id, callbacks); | |
@@ -279,7 +281,7 b' var IPython = (function (IPython) {' | |||||
279 |
|
281 | |||
280 | Kernel.prototype.interrupt = function () { |
|
282 | Kernel.prototype.interrupt = function () { | |
281 | if (this.running) { |
|
283 | if (this.running) { | |
282 |
$([IPython.events]).trigger( |
|
284 | $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this}); | |
283 | $.post(this.kernel_url + "/interrupt"); |
|
285 | $.post(this.kernel_url + "/interrupt"); | |
284 | }; |
|
286 | }; | |
285 | }; |
|
287 | }; | |
@@ -312,6 +314,7 b' var IPython = (function (IPython) {' | |||||
312 |
|
314 | |||
313 | Kernel.prototype._handle_shell_reply = function (e) { |
|
315 | Kernel.prototype._handle_shell_reply = function (e) { | |
314 | reply = $.parseJSON(e.data); |
|
316 | reply = $.parseJSON(e.data); | |
|
317 | $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply}); | |||
315 | var header = reply.header; |
|
318 | var header = reply.header; | |
316 | var content = reply.content; |
|
319 | var content = reply.content; | |
317 | var metadata = reply.metadata; |
|
320 | var metadata = reply.metadata; | |
@@ -367,12 +370,12 b' var IPython = (function (IPython) {' | |||||
367 | } |
|
370 | } | |
368 | } else if (msg_type === 'status') { |
|
371 | } else if (msg_type === 'status') { | |
369 | if (content.execution_state === 'busy') { |
|
372 | if (content.execution_state === 'busy') { | |
370 |
$([IPython.events]).trigger( |
|
373 | $([IPython.events]).trigger('status_busy.Kernel', {kernel: this}); | |
371 | } else if (content.execution_state === 'idle') { |
|
374 | } else if (content.execution_state === 'idle') { | |
372 |
$([IPython.events]).trigger( |
|
375 | $([IPython.events]).trigger('status_idle.Kernel', {kernel: this}); | |
373 | } else if (content.execution_state === 'dead') { |
|
376 | } else if (content.execution_state === 'dead') { | |
374 | this.stop_channels(); |
|
377 | this.stop_channels(); | |
375 |
$([IPython.events]).trigger( |
|
378 | $([IPython.events]).trigger('status_dead.Kernel', {kernel: this}); | |
376 | }; |
|
379 | }; | |
377 | } else if (msg_type === 'clear_output') { |
|
380 | } else if (msg_type === 'clear_output') { | |
378 | var cb = callbacks['clear_output']; |
|
381 | var cb = callbacks['clear_output']; |
@@ -51,10 +51,10 b' var IPython = (function (IPython) {' | |||||
51 | }, |
|
51 | }, | |
52 | { |
|
52 | { | |
53 | id : 'paste_b', |
|
53 | id : 'paste_b', | |
54 | label : 'Paste Cell', |
|
54 | label : 'Paste Cell Below', | |
55 | icon : 'ui-icon-clipboard', |
|
55 | icon : 'ui-icon-clipboard', | |
56 | callback : function () { |
|
56 | callback : function () { | |
57 | IPython.notebook.paste_cell(); |
|
57 | IPython.notebook.paste_cell_below(); | |
58 | } |
|
58 | } | |
59 | } |
|
59 | } | |
60 | ],'cut_copy_paste'); |
|
60 | ],'cut_copy_paste'); |
@@ -14,10 +14,9 b" IPython.namespace('IPython.mathjaxutils');" | |||||
14 | IPython.mathjaxutils = (function (IPython) { |
|
14 | IPython.mathjaxutils = (function (IPython) { | |
15 |
|
15 | |||
16 | var init = function () { |
|
16 | var init = function () { | |
17 |
if (window.MathJax) { |
|
17 | if (window.MathJax) { | |
18 | // MathJax loaded |
|
18 | // MathJax loaded | |
19 | MathJax.Hub.Config({ |
|
19 | MathJax.Hub.Config({ | |
20 | TeX: { equationNumbers: { autoNumber: "AMS", useLabelIds: true } }, |
|
|||
21 | tex2jax: { |
|
20 | tex2jax: { | |
22 | inlineMath: [ ['$','$'], ["\\(","\\)"] ], |
|
21 | inlineMath: [ ['$','$'], ["\\(","\\)"] ], | |
23 | displayMath: [ ['$$','$$'], ["\\[","\\]"] ], |
|
22 | displayMath: [ ['$$','$$'], ["\\[","\\]"] ], | |
@@ -28,6 +27,7 b' IPython.mathjaxutils = (function (IPython) {' | |||||
28 | styles: {'.MathJax_Display': {"margin": 0}} |
|
27 | styles: {'.MathJax_Display': {"margin": 0}} | |
29 | } |
|
28 | } | |
30 | }); |
|
29 | }); | |
|
30 | MathJax.Hub.Configured(); | |||
31 | } else if (window.mathjax_url != "") { |
|
31 | } else if (window.mathjax_url != "") { | |
32 | // Don't have MathJax, but should. Show dialog. |
|
32 | // Don't have MathJax, but should. Show dialog. | |
33 | var dialog = $('<div></div>') |
|
33 | var dialog = $('<div></div>') | |
@@ -88,7 +88,6 b' IPython.mathjaxutils = (function (IPython) {' | |||||
88 | var inline = "$"; // the inline math delimiter |
|
88 | var inline = "$"; // the inline math delimiter | |
89 | var blocks, start, end, last, braces; // used in searching for math |
|
89 | var blocks, start, end, last, braces; // used in searching for math | |
90 | var math; // stores math until pagedown (Markdown parser) is done |
|
90 | var math; // stores math until pagedown (Markdown parser) is done | |
91 | var HUB = MathJax.Hub; |
|
|||
92 |
|
91 | |||
93 | // MATHSPLIT contains the pattern for math delimiters and special symbols |
|
92 | // MATHSPLIT contains the pattern for math delimiters and special symbols | |
94 | // needed for searching for math in the text input. |
|
93 | // needed for searching for math in the text input. | |
@@ -102,11 +101,12 b' IPython.mathjaxutils = (function (IPython) {' | |||||
102 | // math, then push the math string onto the storage array. |
|
101 | // math, then push the math string onto the storage array. | |
103 | // The preProcess function is called on all blocks if it has been passed in |
|
102 | // The preProcess function is called on all blocks if it has been passed in | |
104 | var process_math = function (i, j, pre_process) { |
|
103 | var process_math = function (i, j, pre_process) { | |
|
104 | var hub = MathJax.Hub; | |||
105 | var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&") // use HTML entity for & |
|
105 | var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&") // use HTML entity for & | |
106 | .replace(/</g, "<") // use HTML entity for < |
|
106 | .replace(/</g, "<") // use HTML entity for < | |
107 | .replace(/>/g, ">") // use HTML entity for > |
|
107 | .replace(/>/g, ">") // use HTML entity for > | |
108 | ; |
|
108 | ; | |
109 |
if ( |
|
109 | if (hub.Browser.isMSIE) { | |
110 | block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n") |
|
110 | block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n") | |
111 | } |
|
111 | } | |
112 | while (j > i) { |
|
112 | while (j > i) { | |
@@ -127,6 +127,10 b' IPython.mathjaxutils = (function (IPython) {' | |||||
127 | // (which will be a paragraph). |
|
127 | // (which will be a paragraph). | |
128 | // |
|
128 | // | |
129 | var remove_math = function (text) { |
|
129 | var remove_math = function (text) { | |
|
130 | if (!window.MathJax) { | |||
|
131 | return text; | |||
|
132 | } | |||
|
133 | ||||
130 | start = end = last = null; // for tracking math delimiters |
|
134 | start = end = last = null; // for tracking math delimiters | |
131 | math = []; // stores math strings for later |
|
135 | math = []; // stores math strings for later | |
132 |
|
136 | |||
@@ -216,6 +220,10 b' IPython.mathjaxutils = (function (IPython) {' | |||||
216 | // and clear the math array (no need to keep it around). |
|
220 | // and clear the math array (no need to keep it around). | |
217 | // |
|
221 | // | |
218 | var replace_math = function (text) { |
|
222 | var replace_math = function (text) { | |
|
223 | if (!window.MathJax) { | |||
|
224 | return text; | |||
|
225 | } | |||
|
226 | ||||
219 | text = text.replace(/@@(\d+)@@/g, function (match, n) { |
|
227 | text = text.replace(/@@(\d+)@@/g, function (match, n) { | |
220 | return math[n] |
|
228 | return math[n] | |
221 | }); |
|
229 | }); | |
@@ -223,21 +231,11 b' IPython.mathjaxutils = (function (IPython) {' | |||||
223 | return text; |
|
231 | return text; | |
224 | } |
|
232 | } | |
225 |
|
233 | |||
226 | var queue_render = function () { |
|
|||
227 | // see https://groups.google.com/forum/?fromgroups=#!topic/mathjax-users/cpwy5eCH1ZQ |
|
|||
228 | MathJax.Hub.Queue( |
|
|||
229 | ["resetEquationNumbers",MathJax.InputJax.TeX], |
|
|||
230 | ["PreProcess",MathJax.Hub], |
|
|||
231 | ["Reprocess",MathJax.Hub] |
|
|||
232 | ); |
|
|||
233 | } |
|
|||
234 |
|
||||
235 | return { |
|
234 | return { | |
236 | init : init, |
|
235 | init : init, | |
237 | process_math : process_math, |
|
236 | process_math : process_math, | |
238 | remove_math : remove_math, |
|
237 | remove_math : remove_math, | |
239 |
replace_math : replace_math |
|
238 | replace_math : replace_math | |
240 | queue_render : queue_render |
|
|||
241 | }; |
|
239 | }; | |
242 |
|
240 | |||
243 | }(IPython)); No newline at end of file |
|
241 | }(IPython)); |
@@ -128,7 +128,13 b' var IPython = (function (IPython) {' | |||||
128 | }); |
|
128 | }); | |
129 | this.element.find('#run_all_cells').click(function () { |
|
129 | this.element.find('#run_all_cells').click(function () { | |
130 | IPython.notebook.execute_all_cells(); |
|
130 | IPython.notebook.execute_all_cells(); | |
131 | }); |
|
131 | }).attr('title', 'Run all cells in the notebook'); | |
|
132 | this.element.find('#run_all_cells_above').click(function () { | |||
|
133 | IPython.notebook.execute_cells_above(); | |||
|
134 | }).attr('title', 'Run all cells above (but not including) this cell'); | |||
|
135 | this.element.find('#run_all_cells_below').click(function () { | |||
|
136 | IPython.notebook.execute_cells_below(); | |||
|
137 | }).attr('title', 'Run this cell and all cells below it'); | |||
132 | this.element.find('#to_code').click(function () { |
|
138 | this.element.find('#to_code').click(function () { | |
133 | IPython.notebook.to_code(); |
|
139 | IPython.notebook.to_code(); | |
134 | }); |
|
140 | }); |
@@ -22,6 +22,9 b' var IPython = (function (IPython) {' | |||||
22 | this.next_prompt_number = 1; |
|
22 | this.next_prompt_number = 1; | |
23 | this.kernel = null; |
|
23 | this.kernel = null; | |
24 | this.clipboard = null; |
|
24 | this.clipboard = null; | |
|
25 | this.undelete_backup = null; | |||
|
26 | this.undelete_index = null; | |||
|
27 | this.undelete_below = false; | |||
25 | this.paste_enabled = false; |
|
28 | this.paste_enabled = false; | |
26 | this.dirty = false; |
|
29 | this.dirty = false; | |
27 | this.metadata = {}; |
|
30 | this.metadata = {}; | |
@@ -139,8 +142,8 b' var IPython = (function (IPython) {' | |||||
139 | that.control_key_active = false; |
|
142 | that.control_key_active = false; | |
140 | return false; |
|
143 | return false; | |
141 | } else if (event.which === 86 && that.control_key_active) { |
|
144 | } else if (event.which === 86 && that.control_key_active) { | |
142 | // Paste selected cell = v |
|
145 | // Paste below selected cell = v | |
143 | that.paste_cell(); |
|
146 | that.paste_cell_below(); | |
144 | that.control_key_active = false; |
|
147 | that.control_key_active = false; | |
145 | return false; |
|
148 | return false; | |
146 | } else if (event.which === 68 && that.control_key_active) { |
|
149 | } else if (event.which === 68 && that.control_key_active) { | |
@@ -257,6 +260,11 b' var IPython = (function (IPython) {' | |||||
257 | IPython.quick_help.show_keyboard_shortcuts(); |
|
260 | IPython.quick_help.show_keyboard_shortcuts(); | |
258 | that.control_key_active = false; |
|
261 | that.control_key_active = false; | |
259 | return false; |
|
262 | return false; | |
|
263 | } else if (event.which === 90 && that.control_key_active) { | |||
|
264 | // Undo last cell delete = z | |||
|
265 | that.undelete(); | |||
|
266 | that.control_key_active = false; | |||
|
267 | return false; | |||
260 | } else if (that.control_key_active) { |
|
268 | } else if (that.control_key_active) { | |
261 | that.control_key_active = false; |
|
269 | that.control_key_active = false; | |
262 | return true; |
|
270 | return true; | |
@@ -536,13 +544,19 b' var IPython = (function (IPython) {' | |||||
536 |
|
544 | |||
537 | Notebook.prototype.delete_cell = function (index) { |
|
545 | Notebook.prototype.delete_cell = function (index) { | |
538 | var i = this.index_or_selected(index); |
|
546 | var i = this.index_or_selected(index); | |
|
547 | var cell = this.get_selected_cell(); | |||
|
548 | this.undelete_backup = cell.toJSON(); | |||
539 | if (this.is_valid_cell_index(i)) { |
|
549 | if (this.is_valid_cell_index(i)) { | |
540 | var ce = this.get_cell_element(i); |
|
550 | var ce = this.get_cell_element(i); | |
541 | ce.remove(); |
|
551 | ce.remove(); | |
542 | if (i === (this.ncells())) { |
|
552 | if (i === (this.ncells())) { | |
543 | this.select(i-1); |
|
553 | this.select(i-1); | |
|
554 | this.undelete_index = i - 1; | |||
|
555 | this.undelete_below = true; | |||
544 | } else { |
|
556 | } else { | |
545 | this.select(i); |
|
557 | this.select(i); | |
|
558 | this.undelete_index = i; | |||
|
559 | this.undelete_below = false; | |||
546 | }; |
|
560 | }; | |
547 | this.dirty = true; |
|
561 | this.dirty = true; | |
548 | }; |
|
562 | }; | |
@@ -560,6 +574,11 b' var IPython = (function (IPython) {' | |||||
560 | // index = cell index or undefined to insert below selected |
|
574 | // index = cell index or undefined to insert below selected | |
561 | index = this.index_or_selected(index); |
|
575 | index = this.index_or_selected(index); | |
562 | var cell = null; |
|
576 | var cell = null; | |
|
577 | // This is intentionally < rather than <= for the sake of more | |||
|
578 | // sensible behavior in some cases. | |||
|
579 | if (this.undelete_index !== null && index < this.undelete_index) { | |||
|
580 | this.undelete_index = this.undelete_index + 1; | |||
|
581 | } | |||
563 | if (this.ncells() === 0 || this.is_valid_cell_index(index)) { |
|
582 | if (this.ncells() === 0 || this.is_valid_cell_index(index)) { | |
564 | if (type === 'code') { |
|
583 | if (type === 'code') { | |
565 | cell = new IPython.CodeCell(this.kernel); |
|
584 | cell = new IPython.CodeCell(this.kernel); | |
@@ -594,6 +613,9 b' var IPython = (function (IPython) {' | |||||
594 | // index = cell index or undefined to insert above selected |
|
613 | // index = cell index or undefined to insert above selected | |
595 | index = this.index_or_selected(index); |
|
614 | index = this.index_or_selected(index); | |
596 | var cell = null; |
|
615 | var cell = null; | |
|
616 | if (this.undelete_index !== null && index <= this.undelete_index) { | |||
|
617 | this.undelete_index = this.undelete_index + 1; | |||
|
618 | } | |||
597 | if (this.ncells() === 0 || this.is_valid_cell_index(index)) { |
|
619 | if (this.ncells() === 0 || this.is_valid_cell_index(index)) { | |
598 | if (type === 'code') { |
|
620 | if (type === 'code') { | |
599 | cell = new IPython.CodeCell(this.kernel); |
|
621 | cell = new IPython.CodeCell(this.kernel); | |
@@ -756,8 +778,8 b' var IPython = (function (IPython) {' | |||||
756 | Notebook.prototype.enable_paste = function () { |
|
778 | Notebook.prototype.enable_paste = function () { | |
757 | var that = this; |
|
779 | var that = this; | |
758 | if (!this.paste_enabled) { |
|
780 | if (!this.paste_enabled) { | |
759 | $('#paste_cell').removeClass('ui-state-disabled') |
|
781 | $('#paste_cell_replace').removeClass('ui-state-disabled') | |
760 | .on('click', function () {that.paste_cell();}); |
|
782 | .on('click', function () {that.paste_cell_replace();}); | |
761 | $('#paste_cell_above').removeClass('ui-state-disabled') |
|
783 | $('#paste_cell_above').removeClass('ui-state-disabled') | |
762 | .on('click', function () {that.paste_cell_above();}); |
|
784 | .on('click', function () {that.paste_cell_above();}); | |
763 | $('#paste_cell_below').removeClass('ui-state-disabled') |
|
785 | $('#paste_cell_below').removeClass('ui-state-disabled') | |
@@ -769,7 +791,7 b' var IPython = (function (IPython) {' | |||||
769 |
|
791 | |||
770 | Notebook.prototype.disable_paste = function () { |
|
792 | Notebook.prototype.disable_paste = function () { | |
771 | if (this.paste_enabled) { |
|
793 | if (this.paste_enabled) { | |
772 | $('#paste_cell').addClass('ui-state-disabled').off('click'); |
|
794 | $('#paste_cell_replace').addClass('ui-state-disabled').off('click'); | |
773 | $('#paste_cell_above').addClass('ui-state-disabled').off('click'); |
|
795 | $('#paste_cell_above').addClass('ui-state-disabled').off('click'); | |
774 | $('#paste_cell_below').addClass('ui-state-disabled').off('click'); |
|
796 | $('#paste_cell_below').addClass('ui-state-disabled').off('click'); | |
775 | this.paste_enabled = false; |
|
797 | this.paste_enabled = false; | |
@@ -789,7 +811,7 b' var IPython = (function (IPython) {' | |||||
789 | }; |
|
811 | }; | |
790 |
|
812 | |||
791 |
|
813 | |||
792 | Notebook.prototype.paste_cell = function () { |
|
814 | Notebook.prototype.paste_cell_replace = function () { | |
793 | if (this.clipboard !== null && this.paste_enabled) { |
|
815 | if (this.clipboard !== null && this.paste_enabled) { | |
794 | var cell_data = this.clipboard; |
|
816 | var cell_data = this.clipboard; | |
795 | var new_cell = this.insert_cell_above(cell_data.cell_type); |
|
817 | var new_cell = this.insert_cell_above(cell_data.cell_type); | |
@@ -818,6 +840,33 b' var IPython = (function (IPython) {' | |||||
818 | }; |
|
840 | }; | |
819 | }; |
|
841 | }; | |
820 |
|
842 | |||
|
843 | // Cell undelete | |||
|
844 | ||||
|
845 | Notebook.prototype.undelete = function() { | |||
|
846 | if (this.undelete_backup !== null && this.undelete_index !== null) { | |||
|
847 | var current_index = this.get_selected_index(); | |||
|
848 | if (this.undelete_index < current_index) { | |||
|
849 | current_index = current_index + 1; | |||
|
850 | } | |||
|
851 | if (this.undelete_index >= this.ncells()) { | |||
|
852 | this.select(this.ncells() - 1); | |||
|
853 | } | |||
|
854 | else { | |||
|
855 | this.select(this.undelete_index); | |||
|
856 | } | |||
|
857 | var cell_data = this.undelete_backup; | |||
|
858 | var new_cell = null; | |||
|
859 | if (this.undelete_below) { | |||
|
860 | new_cell = this.insert_cell_below(cell_data.cell_type); | |||
|
861 | } else { | |||
|
862 | new_cell = this.insert_cell_above(cell_data.cell_type); | |||
|
863 | } | |||
|
864 | new_cell.fromJSON(cell_data); | |||
|
865 | this.select(current_index); | |||
|
866 | this.undelete_backup = null; | |||
|
867 | this.undelete_index = null; | |||
|
868 | } | |||
|
869 | } | |||
821 |
|
870 | |||
822 | // Split/merge |
|
871 | // Split/merge | |
823 |
|
872 | |||
@@ -1049,13 +1098,25 b' var IPython = (function (IPython) {' | |||||
1049 | }; |
|
1098 | }; | |
1050 |
|
1099 | |||
1051 |
|
1100 | |||
|
1101 | Notebook.prototype.execute_cells_below = function () { | |||
|
1102 | this.execute_cell_range(this.get_selected_index(), this.ncells()); | |||
|
1103 | that.scroll_to_bottom(); | |||
|
1104 | }; | |||
|
1105 | ||||
|
1106 | Notebook.prototype.execute_cells_above = function () { | |||
|
1107 | this.execute_cell_range(0, this.get_selected_index()); | |||
|
1108 | }; | |||
|
1109 | ||||
1052 | Notebook.prototype.execute_all_cells = function () { |
|
1110 | Notebook.prototype.execute_all_cells = function () { | |
1053 |
|
|
1111 | this.execute_cell_range(0, this.ncells()); | |
1054 | for (var i=0; i<ncells; i++) { |
|
1112 | that.scroll_to_bottom(); | |
|
1113 | }; | |||
|
1114 | ||||
|
1115 | Notebook.prototype.execute_cell_range = function (start, end) { | |||
|
1116 | for (var i=start; i<end; i++) { | |||
1055 | this.select(i); |
|
1117 | this.select(i); | |
1056 | this.execute_selected_cell({add_new:false}); |
|
1118 | this.execute_selected_cell({add_new:false}); | |
1057 | }; |
|
1119 | }; | |
1058 | this.scroll_to_bottom(); |
|
|||
1059 | }; |
|
1120 | }; | |
1060 |
|
1121 | |||
1061 | // Persistance and loading |
|
1122 | // Persistance and loading |
@@ -379,8 +379,10 b' var IPython = (function (IPython) {' | |||||
379 | OutputArea.prototype.append_text = function (data, element, extra_class) { |
|
379 | OutputArea.prototype.append_text = function (data, element, extra_class) { | |
380 | var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_text"); |
|
380 | var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_text"); | |
381 | // escape ANSI & HTML specials in plaintext: |
|
381 | // escape ANSI & HTML specials in plaintext: | |
|
382 | data = utils.wrapUrls(data); | |||
382 | data = utils.fixConsole(data); |
|
383 | data = utils.fixConsole(data); | |
383 | data = utils.fixCarriageReturn(data); |
|
384 | data = utils.fixCarriageReturn(data); | |
|
385 | data = utils.autoLinkUrls(data); | |||
384 | if (extra_class){ |
|
386 | if (extra_class){ | |
385 | toinsert.addClass(extra_class); |
|
387 | toinsert.addClass(extra_class); | |
386 | } |
|
388 | } |
@@ -33,6 +33,7 b' var IPython = (function (IPython) {' | |||||
33 | {key: 'Ctrl-m c', help: 'copy cell'}, |
|
33 | {key: 'Ctrl-m c', help: 'copy cell'}, | |
34 | {key: 'Ctrl-m v', help: 'paste cell'}, |
|
34 | {key: 'Ctrl-m v', help: 'paste cell'}, | |
35 | {key: 'Ctrl-m d', help: 'delete cell'}, |
|
35 | {key: 'Ctrl-m d', help: 'delete cell'}, | |
|
36 | {key: 'Ctrl-m z', help: 'undo last cell deletion'}, | |||
36 | {key: 'Ctrl-m a', help: 'insert cell above'}, |
|
37 | {key: 'Ctrl-m a', help: 'insert cell above'}, | |
37 | {key: 'Ctrl-m b', help: 'insert cell below'}, |
|
38 | {key: 'Ctrl-m b', help: 'insert cell below'}, | |
38 | {key: 'Ctrl-m o', help: 'toggle output'}, |
|
39 | {key: 'Ctrl-m o', help: 'toggle output'}, |
@@ -221,11 +221,9 b' var IPython = (function (IPython) {' | |||||
221 | if (this.rendered === false) { |
|
221 | if (this.rendered === false) { | |
222 | var text = this.get_text(); |
|
222 | var text = this.get_text(); | |
223 | if (text === "") { text = this.placeholder; } |
|
223 | if (text === "") { text = this.placeholder; } | |
224 |
|
||||
225 | text = IPython.mathjaxutils.remove_math(text) |
|
224 | text = IPython.mathjaxutils.remove_math(text) | |
226 | var html = IPython.markdown_converter.makeHtml(text); |
|
225 | var html = IPython.markdown_converter.makeHtml(text); | |
227 | html = IPython.mathjaxutils.replace_math(html) |
|
226 | html = IPython.mathjaxutils.replace_math(html) | |
228 |
|
||||
229 | try { |
|
227 | try { | |
230 | this.set_rendered(html); |
|
228 | this.set_rendered(html); | |
231 | } catch (e) { |
|
229 | } catch (e) { | |
@@ -235,7 +233,6 b' var IPython = (function (IPython) {' | |||||
235 | "Error rendering Markdown!<br/>" + e.toString()) |
|
233 | "Error rendering Markdown!<br/>" + e.toString()) | |
236 | ); |
|
234 | ); | |
237 | } |
|
235 | } | |
238 | this.typeset() |
|
|||
239 | this.element.find('div.text_cell_input').hide(); |
|
236 | this.element.find('div.text_cell_input').hide(); | |
240 | this.element.find("div.text_cell_render").show(); |
|
237 | this.element.find("div.text_cell_render").show(); | |
241 | var code_snippets = this.element.find("pre > code"); |
|
238 | var code_snippets = this.element.find("pre > code"); | |
@@ -250,8 +247,7 b' var IPython = (function (IPython) {' | |||||
250 |
|
247 | |||
251 | return '<code class="prettyprint">' + code + '</code>'; |
|
248 | return '<code class="prettyprint">' + code + '</code>'; | |
252 | }); |
|
249 | }); | |
253 |
|
250 | this.typeset() | ||
254 | IPython.mathjaxutils.queue_render() |
|
|||
255 | this.rendered = true; |
|
251 | this.rendered = true; | |
256 | } |
|
252 | } | |
257 | }; |
|
253 | }; |
@@ -195,11 +195,30 b' IPython.utils = (function (IPython) {' | |||||
195 | tmp = txt; |
|
195 | tmp = txt; | |
196 | do { |
|
196 | do { | |
197 | txt = tmp; |
|
197 | txt = tmp; | |
198 |
tmp = txt.replace(/ |
|
198 | tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline | |
|
199 | tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line | |||
199 | } while (tmp.length < txt.length); |
|
200 | } while (tmp.length < txt.length); | |
200 | return txt; |
|
201 | return txt; | |
201 | } |
|
202 | } | |
202 |
|
203 | |||
|
204 | // Locate URLs in plain text and wrap them in spaces so that they can be | |||
|
205 | // better picked out by autoLinkUrls even after the text has been | |||
|
206 | // converted to HTML | |||
|
207 | function wrapUrls(txt) { | |||
|
208 | // Note this regexp is a modified version of one from | |||
|
209 | // Markdown.Converter For now it only supports http(s) and ftp URLs, | |||
|
210 | // but could easily support others (though file:// should maybe be | |||
|
211 | // avoided) | |||
|
212 | var url_re = /(^|\W)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi; | |||
|
213 | return txt.replace(url_re, "$1 $2$3 $4"); | |||
|
214 | } | |||
|
215 | ||||
|
216 | // Locate a URL with spaces around it and convert that to a anchor tag | |||
|
217 | function autoLinkUrls(txt) { | |||
|
218 | return txt.replace(/ ((https?|ftp):[^'">\s]+) /gi, | |||
|
219 | "<a target=\"_blank\" href=\"$1\">$1</a>"); | |||
|
220 | } | |||
|
221 | ||||
203 | grow = function(element) { |
|
222 | grow = function(element) { | |
204 | // Grow the cell by hand. This is used upon reloading from JSON, when the |
|
223 | // Grow the cell by hand. This is used upon reloading from JSON, when the | |
205 | // autogrow handler is not called. |
|
224 | // autogrow handler is not called. | |
@@ -261,6 +280,8 b' IPython.utils = (function (IPython) {' | |||||
261 | keycodes : keycodes, |
|
280 | keycodes : keycodes, | |
262 | grow : grow, |
|
281 | grow : grow, | |
263 | fixCarriageReturn : fixCarriageReturn, |
|
282 | fixCarriageReturn : fixCarriageReturn, | |
|
283 | wrapUrls : wrapUrls, | |||
|
284 | autoLinkUrls : autoLinkUrls, | |||
264 | points_to_pixels : points_to_pixels |
|
285 | points_to_pixels : points_to_pixels | |
265 | }; |
|
286 | }; | |
266 |
|
287 |
@@ -2,7 +2,7 b'' | |||||
2 | {% block stylesheet %} |
|
2 | {% block stylesheet %} | |
3 |
|
3 | |||
4 | {% if mathjax_url %} |
|
4 | {% if mathjax_url %} | |
5 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script> |
|
5 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script> | |
6 | {% end %} |
|
6 | {% end %} | |
7 | <script type="text/javascript"> |
|
7 | <script type="text/javascript"> | |
8 | // MathJax disabled, set as null to distingish from *missing* MathJax, |
|
8 | // MathJax disabled, set as null to distingish from *missing* MathJax, | |
@@ -75,9 +75,9 b' data-notebook-id={{notebook_id}}' | |||||
75 | <ul> |
|
75 | <ul> | |
76 | <li id="cut_cell"><a href="#">Cut Cell</a></li> |
|
76 | <li id="cut_cell"><a href="#">Cut Cell</a></li> | |
77 | <li id="copy_cell"><a href="#">Copy Cell</a></li> |
|
77 | <li id="copy_cell"><a href="#">Copy Cell</a></li> | |
78 | <li id="paste_cell" class="ui-state-disabled"><a href="#">Paste Cell</a></li> |
|
|||
79 | <li id="paste_cell_above" class="ui-state-disabled"><a href="#">Paste Cell Above</a></li> |
|
78 | <li id="paste_cell_above" class="ui-state-disabled"><a href="#">Paste Cell Above</a></li> | |
80 | <li id="paste_cell_below" class="ui-state-disabled"><a href="#">Paste Cell Below</a></li> |
|
79 | <li id="paste_cell_below" class="ui-state-disabled"><a href="#">Paste Cell Below</a></li> | |
|
80 | <li id="paste_cell_replace" class="ui-state-disabled"><a href="#">Paste Cell & Replace</a></li> | |||
81 | <li id="delete_cell"><a href="#">Delete</a></li> |
|
81 | <li id="delete_cell"><a href="#">Delete</a></li> | |
82 | <hr/> |
|
82 | <hr/> | |
83 | <li id="split_cell"><a href="#">Split Cell</a></li> |
|
83 | <li id="split_cell"><a href="#">Split Cell</a></li> | |
@@ -108,6 +108,8 b' data-notebook-id={{notebook_id}}' | |||||
108 | <li id="run_cell"><a href="#">Run</a></li> |
|
108 | <li id="run_cell"><a href="#">Run</a></li> | |
109 | <li id="run_cell_in_place"><a href="#">Run in Place</a></li> |
|
109 | <li id="run_cell_in_place"><a href="#">Run in Place</a></li> | |
110 | <li id="run_all_cells"><a href="#">Run All</a></li> |
|
110 | <li id="run_all_cells"><a href="#">Run All</a></li> | |
|
111 | <li id="run_all_cells_above"><a href="#">Run All Above</a></li> | |||
|
112 | <li id="run_all_cells_below"><a href="#">Run All Below</a></li> | |||
111 | <hr/> |
|
113 | <hr/> | |
112 | <li id="to_code"><a href="#">Code</a></li> |
|
114 | <li id="to_code"><a href="#">Code</a></li> | |
113 | <li id="to_markdown"><a href="#">Markdown </a></li> |
|
115 | <li id="to_markdown"><a href="#">Markdown </a></li> |
@@ -3,7 +3,7 b'' | |||||
3 | {% block stylesheet %} |
|
3 | {% block stylesheet %} | |
4 |
|
4 | |||
5 | {% if mathjax_url %} |
|
5 | {% if mathjax_url %} | |
6 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script> |
|
6 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script> | |
7 | {% end %} |
|
7 | {% end %} | |
8 | <script type="text/javascript"> |
|
8 | <script type="text/javascript"> | |
9 | // MathJax disabled, set as null to distingish from *missing* MathJax, |
|
9 | // MathJax disabled, set as null to distingish from *missing* MathJax, |
@@ -268,7 +268,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):' | |||||
268 | self._continuation_prompt = '> ' |
|
268 | self._continuation_prompt = '> ' | |
269 | self._continuation_prompt_html = None |
|
269 | self._continuation_prompt_html = None | |
270 | self._executing = False |
|
270 | self._executing = False | |
271 | self._filter_drag = False |
|
|||
272 | self._filter_resize = False |
|
271 | self._filter_resize = False | |
273 | self._html_exporter = HtmlExporter(self._control) |
|
272 | self._html_exporter = HtmlExporter(self._control) | |
274 | self._input_buffer_executing = '' |
|
273 | self._input_buffer_executing = '' | |
@@ -343,7 +342,51 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):' | |||||
343 | triggered=self.reset_font) |
|
342 | triggered=self.reset_font) | |
344 | self.addAction(self.reset_font_size) |
|
343 | self.addAction(self.reset_font_size) | |
345 |
|
344 | |||
|
345 | # Accept drag and drop events here. Drops were already turned off | |||
|
346 | # in self._control when that widget was created. | |||
|
347 | self.setAcceptDrops(True) | |||
346 |
|
348 | |||
|
349 | #--------------------------------------------------------------------------- | |||
|
350 | # Drag and drop support | |||
|
351 | #--------------------------------------------------------------------------- | |||
|
352 | ||||
|
353 | def dragEnterEvent(self, e): | |||
|
354 | if e.mimeData().hasUrls(): | |||
|
355 | # The link action should indicate to that the drop will insert | |||
|
356 | # the file anme. | |||
|
357 | e.setDropAction(QtCore.Qt.LinkAction) | |||
|
358 | e.accept() | |||
|
359 | elif e.mimeData().hasText(): | |||
|
360 | # By changing the action to copy we don't need to worry about | |||
|
361 | # the user accidentally moving text around in the widget. | |||
|
362 | e.setDropAction(QtCore.Qt.CopyAction) | |||
|
363 | e.accept() | |||
|
364 | ||||
|
365 | def dragMoveEvent(self, e): | |||
|
366 | if e.mimeData().hasUrls(): | |||
|
367 | pass | |||
|
368 | elif e.mimeData().hasText(): | |||
|
369 | cursor = self._control.cursorForPosition(e.pos()) | |||
|
370 | if self._in_buffer(cursor.position()): | |||
|
371 | e.setDropAction(QtCore.Qt.CopyAction) | |||
|
372 | self._control.setTextCursor(cursor) | |||
|
373 | else: | |||
|
374 | e.setDropAction(QtCore.Qt.IgnoreAction) | |||
|
375 | e.accept() | |||
|
376 | ||||
|
377 | def dropEvent(self, e): | |||
|
378 | if e.mimeData().hasUrls(): | |||
|
379 | self._keep_cursor_in_buffer() | |||
|
380 | cursor = self._control.textCursor() | |||
|
381 | filenames = [url.toLocalFile() for url in e.mimeData().urls()] | |||
|
382 | text = ', '.join("'" + f.replace("'", "'\"'\"'") + "'" | |||
|
383 | for f in filenames) | |||
|
384 | self._insert_plain_text_into_buffer(cursor, text) | |||
|
385 | elif e.mimeData().hasText(): | |||
|
386 | cursor = self._control.cursorForPosition(e.pos()) | |||
|
387 | if self._in_buffer(cursor.position()): | |||
|
388 | text = e.mimeData().text() | |||
|
389 | self._insert_plain_text_into_buffer(cursor, text) | |||
347 |
|
390 | |||
348 | def eventFilter(self, obj, event): |
|
391 | def eventFilter(self, obj, event): | |
349 | """ Reimplemented to ensure a console-like behavior in the underlying |
|
392 | """ Reimplemented to ensure a console-like behavior in the underlying | |
@@ -392,39 +435,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):' | |||||
392 | event.key() in self._shortcuts: |
|
435 | event.key() in self._shortcuts: | |
393 | event.accept() |
|
436 | event.accept() | |
394 |
|
437 | |||
395 | # Ensure that drags are safe. The problem is that the drag starting |
|
|||
396 | # logic, which determines whether the drag is a Copy or Move, is locked |
|
|||
397 | # down in QTextControl. If the widget is editable, which it must be if |
|
|||
398 | # we're not executing, the drag will be a Move. The following hack |
|
|||
399 | # prevents QTextControl from deleting the text by clearing the selection |
|
|||
400 | # when a drag leave event originating from this widget is dispatched. |
|
|||
401 | # The fact that we have to clear the user's selection is unfortunate, |
|
|||
402 | # but the alternative--trying to prevent Qt from using its hardwired |
|
|||
403 | # drag logic and writing our own--is worse. |
|
|||
404 | elif etype == QtCore.QEvent.DragEnter and \ |
|
|||
405 | obj == self._control.viewport() and \ |
|
|||
406 | event.source() == self._control.viewport(): |
|
|||
407 | self._filter_drag = True |
|
|||
408 | elif etype == QtCore.QEvent.DragLeave and \ |
|
|||
409 | obj == self._control.viewport() and \ |
|
|||
410 | self._filter_drag: |
|
|||
411 | cursor = self._control.textCursor() |
|
|||
412 | cursor.clearSelection() |
|
|||
413 | self._control.setTextCursor(cursor) |
|
|||
414 | self._filter_drag = False |
|
|||
415 |
|
||||
416 | # Ensure that drops are safe. |
|
|||
417 | elif etype == QtCore.QEvent.Drop and obj == self._control.viewport(): |
|
|||
418 | cursor = self._control.cursorForPosition(event.pos()) |
|
|||
419 | if self._in_buffer(cursor.position()): |
|
|||
420 | text = event.mimeData().text() |
|
|||
421 | self._insert_plain_text_into_buffer(cursor, text) |
|
|||
422 |
|
||||
423 | # Qt is expecting to get something here--drag and drop occurs in its |
|
|||
424 | # own event loop. Send a DragLeave event to end it. |
|
|||
425 | QtGui.qApp.sendEvent(obj, QtGui.QDragLeaveEvent()) |
|
|||
426 | return True |
|
|||
427 |
|
||||
428 | # Handle scrolling of the vsplit pager. This hack attempts to solve |
|
438 | # Handle scrolling of the vsplit pager. This hack attempts to solve | |
429 | # problems with tearing of the help text inside the pager window. This |
|
439 | # problems with tearing of the help text inside the pager window. This | |
430 | # happens only on Mac OS X with both PySide and PyQt. This fix isn't |
|
440 | # happens only on Mac OS X with both PySide and PyQt. This fix isn't | |
@@ -1035,8 +1045,12 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):' | |||||
1035 | control.setAcceptRichText(False) |
|
1045 | control.setAcceptRichText(False) | |
1036 | control.setMouseTracking(True) |
|
1046 | control.setMouseTracking(True) | |
1037 |
|
1047 | |||
|
1048 | # Prevent the widget from handling drops, as we already provide | |||
|
1049 | # the logic in this class. | |||
|
1050 | control.setAcceptDrops(False) | |||
|
1051 | ||||
1038 | # Install event filters. The filter on the viewport is needed for |
|
1052 | # Install event filters. The filter on the viewport is needed for | |
1039 |
# mouse events |
|
1053 | # mouse events. | |
1040 | control.installEventFilter(self) |
|
1054 | control.installEventFilter(self) | |
1041 | control.viewport().installEventFilter(self) |
|
1055 | control.viewport().installEventFilter(self) | |
1042 |
|
1056 | |||
@@ -1773,6 +1787,32 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):' | |||||
1773 | else: |
|
1787 | else: | |
1774 | self._append_plain_text(text) |
|
1788 | self._append_plain_text(text) | |
1775 |
|
1789 | |||
|
1790 | def _set_paging(self, paging): | |||
|
1791 | """ | |||
|
1792 | Change the pager to `paging` style. | |||
|
1793 | ||||
|
1794 | XXX: currently, this is limited to switching between 'hsplit' and | |||
|
1795 | 'vsplit'. | |||
|
1796 | ||||
|
1797 | Parameters: | |||
|
1798 | ----------- | |||
|
1799 | paging : string | |||
|
1800 | Either "hsplit", "vsplit", or "inside" | |||
|
1801 | """ | |||
|
1802 | if self._splitter is None: | |||
|
1803 | raise NotImplementedError("""can only switch if --paging=hsplit or | |||
|
1804 | --paging=vsplit is used.""") | |||
|
1805 | if paging == 'hsplit': | |||
|
1806 | self._splitter.setOrientation(QtCore.Qt.Horizontal) | |||
|
1807 | elif paging == 'vsplit': | |||
|
1808 | self._splitter.setOrientation(QtCore.Qt.Vertical) | |||
|
1809 | elif paging == 'inside': | |||
|
1810 | raise NotImplementedError("""switching to 'inside' paging not | |||
|
1811 | supported yet.""") | |||
|
1812 | else: | |||
|
1813 | raise ValueError("unknown paging method '%s'" % paging) | |||
|
1814 | self.paging = paging | |||
|
1815 | ||||
1776 | def _prompt_finished(self): |
|
1816 | def _prompt_finished(self): | |
1777 | """ Called immediately after a prompt is finished, i.e. when some input |
|
1817 | """ Called immediately after a prompt is finished, i.e. when some input | |
1778 | will be processed and a new prompt displayed. |
|
1818 | will be processed and a new prompt displayed. |
@@ -528,7 +528,25 b' class MainWindow(QtGui.QMainWindow):' | |||||
528 | statusTip="Clear the console", |
|
528 | statusTip="Clear the console", | |
529 | triggered=self.clear_magic_active_frontend) |
|
529 | triggered=self.clear_magic_active_frontend) | |
530 | self.add_menu_action(self.view_menu, self.clear_action) |
|
530 | self.add_menu_action(self.view_menu, self.clear_action) | |
531 |
|
531 | |||
|
532 | self.pager_menu = self.view_menu.addMenu("&Pager") | |||
|
533 | ||||
|
534 | hsplit_action = QtGui.QAction(".. &Horizontal Split", | |||
|
535 | self, | |||
|
536 | triggered=lambda: self.set_paging_active_frontend('hsplit')) | |||
|
537 | ||||
|
538 | vsplit_action = QtGui.QAction(" : &Vertical Split", | |||
|
539 | self, | |||
|
540 | triggered=lambda: self.set_paging_active_frontend('vsplit')) | |||
|
541 | ||||
|
542 | inside_action = QtGui.QAction(" &Inside Pager", | |||
|
543 | self, | |||
|
544 | triggered=lambda: self.set_paging_active_frontend('inside')) | |||
|
545 | ||||
|
546 | self.pager_menu.addAction(hsplit_action) | |||
|
547 | self.pager_menu.addAction(vsplit_action) | |||
|
548 | self.pager_menu.addAction(inside_action) | |||
|
549 | ||||
532 | def init_kernel_menu(self): |
|
550 | def init_kernel_menu(self): | |
533 | self.kernel_menu = self.menuBar().addMenu("&Kernel") |
|
551 | self.kernel_menu = self.menuBar().addMenu("&Kernel") | |
534 | # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl |
|
552 | # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl | |
@@ -829,6 +847,9 b' class MainWindow(QtGui.QMainWindow):' | |||||
829 | self.maximizeAct.setEnabled(True) |
|
847 | self.maximizeAct.setEnabled(True) | |
830 | self.minimizeAct.setEnabled(True) |
|
848 | self.minimizeAct.setEnabled(True) | |
831 |
|
849 | |||
|
850 | def set_paging_active_frontend(self, paging): | |||
|
851 | self.active_frontend._set_paging(paging) | |||
|
852 | ||||
832 | def close_active_frontend(self): |
|
853 | def close_active_frontend(self): | |
833 | self.close_tab(self.active_frontend) |
|
854 | self.close_tab(self.active_frontend) | |
834 |
|
855 |
@@ -1,7 +1,8 b'' | |||||
1 | """Various display related classes. |
|
1 | """Various display related classes. | |
2 |
|
2 | |||
3 | Authors : MinRK, gregcaporaso |
|
3 | Authors : MinRK, gregcaporaso, dannystaple | |
4 | """ |
|
4 | """ | |
|
5 | import urllib | |||
5 |
|
6 | |||
6 | from os.path import exists, isfile, splitext, abspath, join, isdir, walk |
|
7 | from os.path import exists, isfile, splitext, abspath, join, isdir, walk | |
7 |
|
8 | |||
@@ -17,24 +18,40 b' class YouTubeVideo(object):' | |||||
17 |
|
18 | |||
18 | vid = YouTubeVideo("foo") |
|
19 | vid = YouTubeVideo("foo") | |
19 | display(vid) |
|
20 | display(vid) | |
|
21 | ||||
|
22 | To start from 30 seconds: | |||
|
23 | ||||
|
24 | vid = YouTubeVideo("abc", start=30) | |||
|
25 | display(vid) | |||
|
26 | ||||
|
27 | To calculate seconds from time as hours, minutes, seconds use: | |||
|
28 | start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds()) | |||
|
29 | ||||
|
30 | Other parameters can be provided as documented at | |||
|
31 | https://developers.google.com/youtube/player_parameters#parameter-subheader | |||
20 | """ |
|
32 | """ | |
21 |
|
33 | |||
22 | def __init__(self, id, width=400, height=300): |
|
34 | def __init__(self, id, width=400, height=300, **kwargs): | |
23 | self.id = id |
|
35 | self.id = id | |
24 | self.width = width |
|
36 | self.width = width | |
25 | self.height = height |
|
37 | self.height = height | |
|
38 | self.params = kwargs | |||
26 |
|
39 | |||
27 | def _repr_html_(self): |
|
40 | def _repr_html_(self): | |
28 | """return YouTube embed iframe for this video id""" |
|
41 | """return YouTube embed iframe for this video id""" | |
|
42 | if self.params: | |||
|
43 | params = "?" + urllib.urlencode(self.params) | |||
|
44 | else: | |||
|
45 | params = "" | |||
29 | return """ |
|
46 | return """ | |
30 | <iframe |
|
47 | <iframe | |
31 | width="%i" |
|
48 | width="%i" | |
32 | height="%i" |
|
49 | height="%i" | |
33 | src="http://www.youtube.com/embed/%s" |
|
50 | src="http://www.youtube.com/embed/%s%s" | |
34 | frameborder="0" |
|
51 | frameborder="0" | |
35 | allowfullscreen |
|
52 | allowfullscreen | |
36 | ></iframe> |
|
53 | ></iframe> | |
37 | """%(self.width, self.height, self.id) |
|
54 | """ % (self.width, self.height, self.id, params) | |
38 |
|
55 | |||
39 | class FileLink(object): |
|
56 | class FileLink(object): | |
40 | """Class for embedding a local file link in an IPython session, based on path |
|
57 | """Class for embedding a local file link in an IPython session, based on path | |
@@ -240,4 +257,4 b' class FileLinks(FileLink):' | |||||
240 | """ |
|
257 | """ | |
241 | result_lines = [] |
|
258 | result_lines = [] | |
242 | walk(self.path, self.terminal_display_formatter, result_lines) |
|
259 | walk(self.path, self.terminal_display_formatter, result_lines) | |
243 | return '\n'.join(result_lines) No newline at end of file |
|
260 | return '\n'.join(result_lines) |
@@ -85,11 +85,33 b' def create_inputhook_qt4(mgr, app=None):' | |||||
85 | return 0 |
|
85 | return 0 | |
86 | app.processEvents(QtCore.QEventLoop.AllEvents, 300) |
|
86 | app.processEvents(QtCore.QEventLoop.AllEvents, 300) | |
87 | if not stdin_ready(): |
|
87 | if not stdin_ready(): | |
|
88 | # Generally a program would run QCoreApplication::exec() | |||
|
89 | # from main() to enter and process the Qt event loop until | |||
|
90 | # quit() or exit() is called and the program terminates. | |||
|
91 | # | |||
|
92 | # For our input hook integration, we need to repeatedly | |||
|
93 | # enter and process the Qt event loop for only a short | |||
|
94 | # amount of time (say 50ms) to ensure that Python stays | |||
|
95 | # responsive to other user inputs. | |||
|
96 | # | |||
|
97 | # A naive approach would be to repeatedly call | |||
|
98 | # QCoreApplication::exec(), using a timer to quit after a | |||
|
99 | # short amount of time. Unfortunately, QCoreApplication | |||
|
100 | # emits an aboutToQuit signal before stopping, which has | |||
|
101 | # the undesirable effect of closing all modal windows. | |||
|
102 | # | |||
|
103 | # To work around this problem, we instead create a | |||
|
104 | # QEventLoop and call QEventLoop::exec(). Other than | |||
|
105 | # setting some state variables which do not seem to be | |||
|
106 | # used anywhere, the only thing QCoreApplication adds is | |||
|
107 | # the aboutToQuit signal which is precisely what we are | |||
|
108 | # trying to avoid. | |||
88 | timer = QtCore.QTimer() |
|
109 | timer = QtCore.QTimer() | |
89 | timer.timeout.connect(app.quit) |
|
110 | event_loop = QtCore.QEventLoop() | |
|
111 | timer.timeout.connect(event_loop.quit) | |||
90 | while not stdin_ready(): |
|
112 | while not stdin_ready(): | |
91 | timer.start(50) |
|
113 | timer.start(50) | |
92 |
|
|
114 | event_loop.exec_() | |
93 | timer.stop() |
|
115 | timer.stop() | |
94 | except KeyboardInterrupt: |
|
116 | except KeyboardInterrupt: | |
95 | ignore_CTRL_C() |
|
117 | ignore_CTRL_C() |
@@ -41,6 +41,7 b' from IPython.zmq.ipkernel import Kernel, IPKernelApp' | |||||
41 | from IPython.zmq.session import ( |
|
41 | from IPython.zmq.session import ( | |
42 | Session, session_aliases, session_flags |
|
42 | Session, session_aliases, session_flags | |
43 | ) |
|
43 | ) | |
|
44 | from IPython.zmq.zmqshell import ZMQInteractiveShell | |||
44 |
|
45 | |||
45 | from IPython.config.configurable import Configurable |
|
46 | from IPython.config.configurable import Configurable | |
46 |
|
47 | |||
@@ -143,7 +144,7 b' class IPEngineApp(BaseParallelApplication):' | |||||
143 | description = _description |
|
144 | description = _description | |
144 | examples = _examples |
|
145 | examples = _examples | |
145 | config_file_name = Unicode(default_config_file_name) |
|
146 | config_file_name = Unicode(default_config_file_name) | |
146 | classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI]) |
|
147 | classes = List([ZMQInteractiveShell, ProfileDir, Session, EngineFactory, Kernel, MPI]) | |
147 |
|
148 | |||
148 | startup_script = Unicode(u'', config=True, |
|
149 | startup_script = Unicode(u'', config=True, | |
149 | help='specify a script to be run at startup') |
|
150 | help='specify a script to be run at startup') |
@@ -650,18 +650,27 b' class SSHClusterLauncher(SSHLauncher):' | |||||
650 |
|
650 | |||
651 | If not specified, use calling profile, stripping out possible leading homedir. |
|
651 | If not specified, use calling profile, stripping out possible leading homedir. | |
652 | """) |
|
652 | """) | |
653 |
|
653 | |||
654 |
def |
|
654 | def _profile_dir_changed(self, name, old, new): | |
655 | """turns /home/you/.ipython/profile_foo into .ipython/profile_foo |
|
655 | if not self.remote_profile_dir: | |
656 | """ |
|
656 | # trigger remote_profile_dir_default logic again, | |
|
657 | # in case it was already triggered before profile_dir was set | |||
|
658 | self.remote_profile_dir = self._strip_home(new) | |||
|
659 | ||||
|
660 | @staticmethod | |||
|
661 | def _strip_home(path): | |||
|
662 | """turns /home/you/.ipython/profile_foo into .ipython/profile_foo""" | |||
657 | home = get_home_dir() |
|
663 | home = get_home_dir() | |
658 | if not home.endswith('/'): |
|
664 | if not home.endswith('/'): | |
659 | home = home+'/' |
|
665 | home = home+'/' | |
660 |
|
666 | |||
661 |
if |
|
667 | if path.startswith(home): | |
662 |
return |
|
668 | return path[len(home):] | |
663 | else: |
|
669 | else: | |
664 |
return |
|
670 | return path | |
|
671 | ||||
|
672 | def _remote_profile_dir_default(self): | |||
|
673 | return self._strip_home(self.profile_dir) | |||
665 |
|
674 | |||
666 | def _cluster_id_changed(self, name, old, new): |
|
675 | def _cluster_id_changed(self, name, old, new): | |
667 | if new: |
|
676 | if new: |
@@ -373,8 +373,11 b' def shellglob(args):' | |||||
373 |
|
373 | |||
374 | """ |
|
374 | """ | |
375 | expanded = [] |
|
375 | expanded = [] | |
|
376 | # Do not unescape backslash in Windows as it is interpreted as | |||
|
377 | # path separator: | |||
|
378 | unescape = unescape_glob if sys.platform != 'win32' else lambda x: x | |||
376 | for a in args: |
|
379 | for a in args: | |
377 |
expanded.extend(glob.glob(a) or [unescape |
|
380 | expanded.extend(glob.glob(a) or [unescape(a)]) | |
378 | return expanded |
|
381 | return expanded | |
379 |
|
382 | |||
380 |
|
383 |
@@ -73,7 +73,7 b" if have_readline and hasattr(_rl, 'rlmain'):" | |||||
73 | line = lineobj.TextLine(line) |
|
73 | line = lineobj.TextLine(line) | |
74 | return _rl.add_history(line) |
|
74 | return _rl.add_history(line) | |
75 |
|
75 | |||
76 | if sys.platform == 'win32' and have_readline: |
|
76 | if (sys.platform == 'win32' or sys.platform == 'cli') and have_readline: | |
77 | try: |
|
77 | try: | |
78 | _outputfile=_rl.GetOutputFile() |
|
78 | _outputfile=_rl.GetOutputFile() | |
79 | except AttributeError: |
|
79 | except AttributeError: |
@@ -19,6 +19,7 b' import shutil' | |||||
19 | import sys |
|
19 | import sys | |
20 | import tempfile |
|
20 | import tempfile | |
21 | from io import StringIO |
|
21 | from io import StringIO | |
|
22 | from contextlib import contextmanager | |||
22 |
|
23 | |||
23 | from os.path import join, abspath, split |
|
24 | from os.path import join, abspath, split | |
24 |
|
25 | |||
@@ -447,42 +448,73 b' def test_unicode_in_filename():' | |||||
447 | str(ex) |
|
448 | str(ex) | |
448 |
|
449 | |||
449 |
|
450 | |||
450 | def test_shellglob(): |
|
451 | class TestShellGlob(object): | |
451 | """Test glob expansion for %run magic.""" |
|
|||
452 | filenames_start_with_a = map('a{0}'.format, range(3)) |
|
|||
453 | filenames_end_with_b = map('{0}b'.format, range(3)) |
|
|||
454 | filenames = filenames_start_with_a + filenames_end_with_b |
|
|||
455 |
|
452 | |||
456 | with TemporaryDirectory() as td: |
|
453 | @classmethod | |
457 | save = os.getcwdu() |
|
454 | def setUpClass(cls): | |
458 | try: |
|
455 | cls.filenames_start_with_a = map('a{0}'.format, range(3)) | |
459 | os.chdir(td) |
|
456 | cls.filenames_end_with_b = map('{0}b'.format, range(3)) | |
|
457 | cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b | |||
|
458 | cls.tempdir = TemporaryDirectory() | |||
|
459 | td = cls.tempdir.name | |||
460 |
|
460 | |||
|
461 | with cls.in_tempdir(): | |||
461 | # Create empty files |
|
462 | # Create empty files | |
462 | for fname in filenames: |
|
463 | for fname in cls.filenames: | |
463 | open(os.path.join(td, fname), 'w').close() |
|
464 | open(os.path.join(td, fname), 'w').close() | |
464 |
|
465 | |||
465 | def assert_match(patterns, matches): |
|
466 | @classmethod | |
466 | # glob returns unordered list. that's why sorted is required. |
|
467 | def tearDownClass(cls): | |
467 | nt.assert_equals(sorted(path.shellglob(patterns)), |
|
468 | cls.tempdir.cleanup() | |
468 | sorted(matches)) |
|
469 | ||
469 |
|
470 | @classmethod | ||
470 | assert_match(['*'], filenames) |
|
471 | @contextmanager | |
471 | assert_match(['a*'], filenames_start_with_a) |
|
472 | def in_tempdir(cls): | |
472 | assert_match(['*c'], ['*c']) |
|
473 | save = os.getcwdu() | |
473 | assert_match(['*', 'a*', '*b', '*c'], |
|
474 | try: | |
474 | filenames |
|
475 | os.chdir(cls.tempdir.name) | |
475 | + filenames_start_with_a |
|
476 | yield | |
476 | + filenames_end_with_b |
|
|||
477 | + ['*c']) |
|
|||
478 |
|
||||
479 | assert_match([r'\*'], ['*']) |
|
|||
480 | assert_match([r'a\*', 'a*'], ['a*'] + filenames_start_with_a) |
|
|||
481 | assert_match(['a[012]'], filenames_start_with_a) |
|
|||
482 | assert_match([r'a\[012]'], ['a[012]']) |
|
|||
483 | finally: |
|
477 | finally: | |
484 | os.chdir(save) |
|
478 | os.chdir(save) | |
485 |
|
479 | |||
|
480 | def check_match(self, patterns, matches): | |||
|
481 | with self.in_tempdir(): | |||
|
482 | # glob returns unordered list. that's why sorted is required. | |||
|
483 | nt.assert_equals(sorted(path.shellglob(patterns)), | |||
|
484 | sorted(matches)) | |||
|
485 | ||||
|
486 | def common_cases(self): | |||
|
487 | return [ | |||
|
488 | (['*'], self.filenames), | |||
|
489 | (['a*'], self.filenames_start_with_a), | |||
|
490 | (['*c'], ['*c']), | |||
|
491 | (['*', 'a*', '*b', '*c'], self.filenames | |||
|
492 | + self.filenames_start_with_a | |||
|
493 | + self.filenames_end_with_b | |||
|
494 | + ['*c']), | |||
|
495 | (['a[012]'], self.filenames_start_with_a), | |||
|
496 | ] | |||
|
497 | ||||
|
498 | @skip_win32 | |||
|
499 | def test_match_posix(self): | |||
|
500 | for (patterns, matches) in self.common_cases() + [ | |||
|
501 | ([r'\*'], ['*']), | |||
|
502 | ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a), | |||
|
503 | ([r'a\[012]'], ['a[012]']), | |||
|
504 | ]: | |||
|
505 | yield (self.check_match, patterns, matches) | |||
|
506 | ||||
|
507 | @skip_if_not_win32 | |||
|
508 | def test_match_windows(self): | |||
|
509 | for (patterns, matches) in self.common_cases() + [ | |||
|
510 | # In windows, backslash is interpreted as path | |||
|
511 | # separator. Therefore, you can't escape glob | |||
|
512 | # using it. | |||
|
513 | ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), | |||
|
514 | ([r'a\[012]'], [r'a\[012]']), | |||
|
515 | ]: | |||
|
516 | yield (self.check_match, patterns, matches) | |||
|
517 | ||||
486 |
|
518 | |||
487 | def test_unescape_glob(): |
|
519 | def test_unescape_glob(): | |
488 | nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?') |
|
520 | nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?') |
@@ -1,4 +1,5 b'' | |||||
1 | include README.rst |
|
1 | include README.rst | |
|
2 | include COPYING.txt | |||
2 | include ipython.py |
|
3 | include ipython.py | |
3 | include setupbase.py |
|
4 | include setupbase.py | |
4 | include setupegg.py |
|
5 | include setupegg.py |
@@ -162,87 +162,7 b'' | |||||
162 | "\n", |
|
162 | "\n", | |
163 | "---\n", |
|
163 | "---\n", | |
164 | "\n", |
|
164 | "\n", | |
165 | "These equation reference examples are adapted from an [example page in the MathJax documentation](http://cdn.mathjax.org/mathjax/latest/test/sample-eqrefs.html). Note that it's okay to reference equations across cells. Click inside this cell to see the source.\n", |
|
165 | "Equation numbering and referencing will be available in a future version of IPython." | |
166 | "\n", |
|
|||
167 | "## Labeled equations and references\n", |
|
|||
168 | "\n", |
|
|||
169 | "Here is a labeled equation:\n", |
|
|||
170 | "\\begin{equation}\n", |
|
|||
171 | "x+1\\over\\sqrt{1-x^2}\\label{ref1}\n", |
|
|||
172 | "\\end{equation}\n", |
|
|||
173 | "\n", |
|
|||
174 | "with a reference to ref1: \\ref{ref1},\n", |
|
|||
175 | "and another numbered one with no label:\n", |
|
|||
176 | "\\begin{equation}\n", |
|
|||
177 | "x+1\\over\\sqrt{1-x^2}\n", |
|
|||
178 | "\\end{equation}" |
|
|||
179 | ] |
|
|||
180 | }, |
|
|||
181 | { |
|
|||
182 | "cell_type": "markdown", |
|
|||
183 | "metadata": {}, |
|
|||
184 | "source": [ |
|
|||
185 | "## \\nonumber and equation*\n", |
|
|||
186 | "\n", |
|
|||
187 | "This one uses \\nonumber:\n", |
|
|||
188 | "\\begin{equation}\n", |
|
|||
189 | "x+1\\over\\sqrt{1-x^2}\\nonumber\n", |
|
|||
190 | "\\end{equation}\n", |
|
|||
191 | "\n", |
|
|||
192 | "Here's one with the equation* environment:\n", |
|
|||
193 | "\\begin{equation*}\n", |
|
|||
194 | "x+1\\over\\sqrt{1-x^2}\n", |
|
|||
195 | "\\end{equation*}" |
|
|||
196 | ] |
|
|||
197 | }, |
|
|||
198 | { |
|
|||
199 | "cell_type": "markdown", |
|
|||
200 | "metadata": {}, |
|
|||
201 | "source": [ |
|
|||
202 | "## Forward references\n", |
|
|||
203 | "\n", |
|
|||
204 | "This is a forward reference [\\ref{ref2}] and another \\eqref{ref2} for the \n", |
|
|||
205 | "following equation:\n", |
|
|||
206 | "\n", |
|
|||
207 | "\\begin{equation}\n", |
|
|||
208 | "x+1\\over\\sqrt{1-x^2}\\label{ref2}\n", |
|
|||
209 | "\\end{equation}\n", |
|
|||
210 | "\n", |
|
|||
211 | "More math:\n", |
|
|||
212 | "\\begin{equation}\n", |
|
|||
213 | "x+1\\over\\sqrt{1-x^2}\n", |
|
|||
214 | "\\end{equation}" |
|
|||
215 | ] |
|
|||
216 | }, |
|
|||
217 | { |
|
|||
218 | "cell_type": "markdown", |
|
|||
219 | "metadata": {}, |
|
|||
220 | "source": [ |
|
|||
221 | "### References inline and in environments\n", |
|
|||
222 | "\n", |
|
|||
223 | "Here is a ref inside math: $\\ref{ref2}+1$ and text after it.\n", |
|
|||
224 | "\n", |
|
|||
225 | "\\begin{align} \n", |
|
|||
226 | "x& = y_1-y_2+y_3-y_5+y_8-\\dots \n", |
|
|||
227 | "&& \\text{by \\eqref{ref1}}\\\\ \n", |
|
|||
228 | "& = y'\\circ y^* && \\text{(by \\eqref{ref3})}\\\\ \n", |
|
|||
229 | "& = y(0) y' && \\text {by Axiom 1.} \n", |
|
|||
230 | "\\end{align} \n", |
|
|||
231 | "\n", |
|
|||
232 | "### Missing references\n", |
|
|||
233 | "Here's a bad ref [\\ref{ref4}] to a nonexistent label.\n", |
|
|||
234 | "\n", |
|
|||
235 | "### Numbering align environments\n", |
|
|||
236 | "An alignment:\n", |
|
|||
237 | "\\begin{align}\n", |
|
|||
238 | "a&=b\\label{ref3}\\cr\n", |
|
|||
239 | "&=c+d\n", |
|
|||
240 | "\\end{align}\n", |
|
|||
241 | "and a starred one:\n", |
|
|||
242 | "\\begin{align*}\n", |
|
|||
243 | "a&=b\\cr\n", |
|
|||
244 | "&=c+d\n", |
|
|||
245 | "\\end{align*}" |
|
|||
246 | ] |
|
166 | ] | |
247 | }, |
|
167 | }, | |
248 | { |
|
168 | { | |
@@ -331,14 +251,6 b'' | |||||
331 | "x=4\n", |
|
251 | "x=4\n", | |
332 | "$$" |
|
252 | "$$" | |
333 | ] |
|
253 | ] | |
334 | }, |
|
|||
335 | { |
|
|||
336 | "cell_type": "code", |
|
|||
337 | "collapsed": false, |
|
|||
338 | "input": [], |
|
|||
339 | "language": "python", |
|
|||
340 | "metadata": {}, |
|
|||
341 | "outputs": [] |
|
|||
342 | } |
|
254 | } | |
343 | ], |
|
255 | ], | |
344 | "metadata": {} |
|
256 | "metadata": {} |
@@ -337,7 +337,7 b' this is just a summary:' | |||||
337 | * **%pdoc <object>**: Print (or run through a pager if too long) the |
|
337 | * **%pdoc <object>**: Print (or run through a pager if too long) the | |
338 | docstring for an object. If the given object is a class, it will |
|
338 | docstring for an object. If the given object is a class, it will | |
339 | print both the class and the constructor docstrings. |
|
339 | print both the class and the constructor docstrings. | |
340 |
* **%pdef <object>**: Print the |
|
340 | * **%pdef <object>**: Print the call signature for any callable | |
341 | object. If the object is a class, print the constructor information. |
|
341 | object. If the object is a class, print the constructor information. | |
342 | * **%psource <object>**: Print (or run through a pager if too long) |
|
342 | * **%psource <object>**: Print (or run through a pager if too long) | |
343 | the source code for an object. |
|
343 | the source code for an object. |
@@ -6,6 +6,7 b' This should ONLY be run at real release time.' | |||||
6 | from __future__ import print_function |
|
6 | from __future__ import print_function | |
7 |
|
7 | |||
8 | from toollib import * |
|
8 | from toollib import * | |
|
9 | from gh_api import post_download | |||
9 |
|
10 | |||
10 | # Get main ipython dir, this will raise if it doesn't pass some checks |
|
11 | # Get main ipython dir, this will raise if it doesn't pass some checks | |
11 | ipdir = get_ipdir() |
|
12 | ipdir = get_ipdir() | |
@@ -53,6 +54,11 b" sh(sdists + ' upload')" | |||||
53 | cd(distdir) |
|
54 | cd(distdir) | |
54 | print( 'Uploading distribution files...') |
|
55 | print( 'Uploading distribution files...') | |
55 |
|
56 | |||
|
57 | for fname in os.listdir('.'): | |||
|
58 | print('uploading %s to GitHub' % fname) | |||
|
59 | desc = "IPython %s source distribution" % version | |||
|
60 | post_download("ipython/ipython", fname, description=desc) | |||
|
61 | ||||
56 | # Make target dir if it doesn't exist |
|
62 | # Make target dir if it doesn't exist | |
57 | sh('ssh %s "mkdir -p %s/release/%s" ' % (archive_user, archive_dir, version)) |
|
63 | sh('ssh %s "mkdir -p %s/release/%s" ' % (archive_user, archive_dir, version)) | |
58 | sh('scp * %s' % release_site) |
|
64 | sh('scp * %s' % release_site) |
@@ -23,20 +23,34 b' except ImportError:' | |||||
23 |
|
23 | |||
24 | github = '--github' in sys.argv |
|
24 | github = '--github' in sys.argv | |
25 |
|
25 | |||
26 |
cmd_t = "{py} setup.py bdist_wininst |
|
26 | cmd_t = "{py} setup.py bdist_wininst" | |
27 |
|
27 | |||
28 | pypi = '--pypi' in sys.argv |
|
28 | pypi = '--pypi' in sys.argv | |
29 | pypi_cmd_t = "python setup.py upload_wininst -f {fname}" |
|
29 | pypi_cmd_t = "python setup.py upload_wininst -f {fname}" | |
30 |
|
30 | |||
31 | for py in ['python', 'python3']: |
|
31 | # Windows Python cannot normally cross-compile, | |
|
32 | # so you must have 4 Pythons to make 4 installers: | |||
|
33 | # http://docs.python.org/2/distutils/builtdist.html#cross-compiling-on-windows | |||
|
34 | ||||
|
35 | pythons = { | |||
|
36 | 2: { | |||
|
37 | 'win32' : r'C:\\Python27\Python.exe', | |||
|
38 | 'win-amd64': r'C:\\Python27_64\Python.exe', | |||
|
39 | }, | |||
|
40 | 3: { | |||
|
41 | 'win32' : r'C:\\Python33\Python.exe', | |||
|
42 | 'win-amd64': r'C:\\Python33_64\Python.exe', | |||
|
43 | }, | |||
|
44 | } | |||
|
45 | ||||
|
46 | for v,plat_py in pythons.items(): | |||
32 | # deliberately mangle the name, |
|
47 | # deliberately mangle the name, | |
33 | # so easy_install doesn't find these and do horrible wrong things |
|
48 | # so easy_install doesn't find these and do horrible wrong things | |
34 | v = 3 if py.endswith('3') else 2 |
|
|||
35 | try: |
|
49 | try: | |
36 | shutil.rmtree('build') |
|
50 | shutil.rmtree('build') | |
37 | except OSError: |
|
51 | except OSError: | |
38 | pass |
|
52 | pass | |
39 | for plat in ['win32', 'win-amd64']: |
|
53 | for plat,py in plat_py.items(): | |
40 | cmd = cmd_t.format(**locals()) |
|
54 | cmd = cmd_t.format(**locals()) | |
41 | sh(cmd) |
|
55 | sh(cmd) | |
42 | orig = glob.glob(os.path.join('dist', 'ipython-*.{plat}.exe'.format(**locals())))[0] |
|
56 | orig = glob.glob(os.path.join('dist', 'ipython-*.{plat}.exe'.format(**locals())))[0] |
General Comments 0
You need to be logged in to leave comments.
Login now