##// END OF EJS Templates
Some code cleanup in javascript and python...
Matthias Bussonnier -
Show More
@@ -1,3396 +1,3394 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Main IPython class."""
2 """Main IPython class."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 from __future__ import absolute_import, print_function
13 from __future__ import absolute_import, print_function
14
14
15 import __future__
15 import __future__
16 import abc
16 import abc
17 import ast
17 import ast
18 import atexit
18 import atexit
19 import functools
19 import functools
20 import os
20 import os
21 import re
21 import re
22 import runpy
22 import runpy
23 import sys
23 import sys
24 import tempfile
24 import tempfile
25 import traceback
25 import traceback
26 import types
26 import types
27 import subprocess
27 import subprocess
28 from io import open as io_open
28 from io import open as io_open
29
29
30 from IPython.config.configurable import SingletonConfigurable
30 from IPython.config.configurable import SingletonConfigurable
31 from IPython.core import debugger, oinspect
31 from IPython.core import debugger, oinspect
32 from IPython.core import magic
32 from IPython.core import magic
33 from IPython.core import page
33 from IPython.core import page
34 from IPython.core import prefilter
34 from IPython.core import prefilter
35 from IPython.core import shadowns
35 from IPython.core import shadowns
36 from IPython.core import ultratb
36 from IPython.core import ultratb
37 from IPython.core.alias import AliasManager, AliasError
37 from IPython.core.alias import AliasManager, AliasError
38 from IPython.core.autocall import ExitAutocall
38 from IPython.core.autocall import ExitAutocall
39 from IPython.core.builtin_trap import BuiltinTrap
39 from IPython.core.builtin_trap import BuiltinTrap
40 from IPython.core.events import EventManager, available_events
40 from IPython.core.events import EventManager, available_events
41 from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
41 from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
42 from IPython.core.display_trap import DisplayTrap
42 from IPython.core.display_trap import DisplayTrap
43 from IPython.core.displayhook import DisplayHook
43 from IPython.core.displayhook import DisplayHook
44 from IPython.core.displaypub import DisplayPublisher
44 from IPython.core.displaypub import DisplayPublisher
45 from IPython.core.error import InputRejected, UsageError
45 from IPython.core.error import InputRejected, UsageError
46 from IPython.core.extensions import ExtensionManager
46 from IPython.core.extensions import ExtensionManager
47 from IPython.core.formatters import DisplayFormatter
47 from IPython.core.formatters import DisplayFormatter
48 from IPython.core.history import HistoryManager
48 from IPython.core.history import HistoryManager
49 from IPython.core.inputsplitter import IPythonInputSplitter, ESC_MAGIC, ESC_MAGIC2
49 from IPython.core.inputsplitter import IPythonInputSplitter, ESC_MAGIC, ESC_MAGIC2
50 from IPython.core.logger import Logger
50 from IPython.core.logger import Logger
51 from IPython.core.macro import Macro
51 from IPython.core.macro import Macro
52 from IPython.core.payload import PayloadManager
52 from IPython.core.payload import PayloadManager
53 from IPython.core.prefilter import PrefilterManager
53 from IPython.core.prefilter import PrefilterManager
54 from IPython.core.profiledir import ProfileDir
54 from IPython.core.profiledir import ProfileDir
55 from IPython.core.prompts import PromptManager
55 from IPython.core.prompts import PromptManager
56 from IPython.core.usage import default_banner
56 from IPython.core.usage import default_banner
57 from IPython.lib.latextools import LaTeXTool
57 from IPython.lib.latextools import LaTeXTool
58 from IPython.testing.skipdoctest import skip_doctest
58 from IPython.testing.skipdoctest import skip_doctest
59 from IPython.utils import PyColorize
59 from IPython.utils import PyColorize
60 from IPython.utils import io
60 from IPython.utils import io
61 from IPython.utils import py3compat
61 from IPython.utils import py3compat
62 from IPython.utils import openpy
62 from IPython.utils import openpy
63 from IPython.utils.decorators import undoc
63 from IPython.utils.decorators import undoc
64 from IPython.utils.io import ask_yes_no
64 from IPython.utils.io import ask_yes_no
65 from IPython.utils.ipstruct import Struct
65 from IPython.utils.ipstruct import Struct
66 from IPython.utils.path import get_home_dir, get_ipython_dir, get_py_filename, unquote_filename, ensure_dir_exists
66 from IPython.utils.path import get_home_dir, get_ipython_dir, get_py_filename, unquote_filename, ensure_dir_exists
67 from IPython.utils.pickleshare import PickleShareDB
67 from IPython.utils.pickleshare import PickleShareDB
68 from IPython.utils.process import system, getoutput
68 from IPython.utils.process import system, getoutput
69 from IPython.utils.py3compat import (builtin_mod, unicode_type, string_types,
69 from IPython.utils.py3compat import (builtin_mod, unicode_type, string_types,
70 with_metaclass, iteritems)
70 with_metaclass, iteritems)
71 from IPython.utils.strdispatch import StrDispatch
71 from IPython.utils.strdispatch import StrDispatch
72 from IPython.utils.syspathcontext import prepended_to_syspath
72 from IPython.utils.syspathcontext import prepended_to_syspath
73 from IPython.utils.text import (format_screen, LSString, SList,
73 from IPython.utils.text import (format_screen, LSString, SList,
74 DollarFormatter)
74 DollarFormatter)
75 from IPython.utils.traitlets import (Integer, Bool, CBool, CaselessStrEnum, Enum,
75 from IPython.utils.traitlets import (Integer, Bool, CBool, CaselessStrEnum, Enum,
76 List, Unicode, Instance, Type)
76 List, Unicode, Instance, Type)
77 from IPython.utils.warn import warn, error
77 from IPython.utils.warn import warn, error
78 import IPython.core.hooks
78 import IPython.core.hooks
79
79
80 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
81 # Globals
81 # Globals
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83
83
84 # compiled regexps for autoindent management
84 # compiled regexps for autoindent management
85 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
85 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
86
86
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88 # Utilities
88 # Utilities
89 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
90
90
91 @undoc
91 @undoc
92 def softspace(file, newvalue):
92 def softspace(file, newvalue):
93 """Copied from code.py, to remove the dependency"""
93 """Copied from code.py, to remove the dependency"""
94
94
95 oldvalue = 0
95 oldvalue = 0
96 try:
96 try:
97 oldvalue = file.softspace
97 oldvalue = file.softspace
98 except AttributeError:
98 except AttributeError:
99 pass
99 pass
100 try:
100 try:
101 file.softspace = newvalue
101 file.softspace = newvalue
102 except (AttributeError, TypeError):
102 except (AttributeError, TypeError):
103 # "attribute-less object" or "read-only attributes"
103 # "attribute-less object" or "read-only attributes"
104 pass
104 pass
105 return oldvalue
105 return oldvalue
106
106
107 @undoc
107 @undoc
108 def no_op(*a, **kw): pass
108 def no_op(*a, **kw): pass
109
109
110 @undoc
110 @undoc
111 class NoOpContext(object):
111 class NoOpContext(object):
112 def __enter__(self): pass
112 def __enter__(self): pass
113 def __exit__(self, type, value, traceback): pass
113 def __exit__(self, type, value, traceback): pass
114 no_op_context = NoOpContext()
114 no_op_context = NoOpContext()
115
115
116 class SpaceInInput(Exception): pass
116 class SpaceInInput(Exception): pass
117
117
118 @undoc
118 @undoc
119 class Bunch: pass
119 class Bunch: pass
120
120
121
121
122 def get_default_colors():
122 def get_default_colors():
123 if sys.platform=='darwin':
123 if sys.platform=='darwin':
124 return "LightBG"
124 return "LightBG"
125 elif os.name=='nt':
125 elif os.name=='nt':
126 return 'Linux'
126 return 'Linux'
127 else:
127 else:
128 return 'Linux'
128 return 'Linux'
129
129
130
130
131 class SeparateUnicode(Unicode):
131 class SeparateUnicode(Unicode):
132 r"""A Unicode subclass to validate separate_in, separate_out, etc.
132 r"""A Unicode subclass to validate separate_in, separate_out, etc.
133
133
134 This is a Unicode based trait that converts '0'->'' and ``'\\n'->'\n'``.
134 This is a Unicode based trait that converts '0'->'' and ``'\\n'->'\n'``.
135 """
135 """
136
136
137 def validate(self, obj, value):
137 def validate(self, obj, value):
138 if value == '0': value = ''
138 if value == '0': value = ''
139 value = value.replace('\\n','\n')
139 value = value.replace('\\n','\n')
140 return super(SeparateUnicode, self).validate(obj, value)
140 return super(SeparateUnicode, self).validate(obj, value)
141
141
142
142
143 class ReadlineNoRecord(object):
143 class ReadlineNoRecord(object):
144 """Context manager to execute some code, then reload readline history
144 """Context manager to execute some code, then reload readline history
145 so that interactive input to the code doesn't appear when pressing up."""
145 so that interactive input to the code doesn't appear when pressing up."""
146 def __init__(self, shell):
146 def __init__(self, shell):
147 self.shell = shell
147 self.shell = shell
148 self._nested_level = 0
148 self._nested_level = 0
149
149
150 def __enter__(self):
150 def __enter__(self):
151 if self._nested_level == 0:
151 if self._nested_level == 0:
152 try:
152 try:
153 self.orig_length = self.current_length()
153 self.orig_length = self.current_length()
154 self.readline_tail = self.get_readline_tail()
154 self.readline_tail = self.get_readline_tail()
155 except (AttributeError, IndexError): # Can fail with pyreadline
155 except (AttributeError, IndexError): # Can fail with pyreadline
156 self.orig_length, self.readline_tail = 999999, []
156 self.orig_length, self.readline_tail = 999999, []
157 self._nested_level += 1
157 self._nested_level += 1
158
158
159 def __exit__(self, type, value, traceback):
159 def __exit__(self, type, value, traceback):
160 self._nested_level -= 1
160 self._nested_level -= 1
161 if self._nested_level == 0:
161 if self._nested_level == 0:
162 # Try clipping the end if it's got longer
162 # Try clipping the end if it's got longer
163 try:
163 try:
164 e = self.current_length() - self.orig_length
164 e = self.current_length() - self.orig_length
165 if e > 0:
165 if e > 0:
166 for _ in range(e):
166 for _ in range(e):
167 self.shell.readline.remove_history_item(self.orig_length)
167 self.shell.readline.remove_history_item(self.orig_length)
168
168
169 # If it still doesn't match, just reload readline history.
169 # If it still doesn't match, just reload readline history.
170 if self.current_length() != self.orig_length \
170 if self.current_length() != self.orig_length \
171 or self.get_readline_tail() != self.readline_tail:
171 or self.get_readline_tail() != self.readline_tail:
172 self.shell.refill_readline_hist()
172 self.shell.refill_readline_hist()
173 except (AttributeError, IndexError):
173 except (AttributeError, IndexError):
174 pass
174 pass
175 # Returning False will cause exceptions to propagate
175 # Returning False will cause exceptions to propagate
176 return False
176 return False
177
177
178 def current_length(self):
178 def current_length(self):
179 return self.shell.readline.get_current_history_length()
179 return self.shell.readline.get_current_history_length()
180
180
181 def get_readline_tail(self, n=10):
181 def get_readline_tail(self, n=10):
182 """Get the last n items in readline history."""
182 """Get the last n items in readline history."""
183 end = self.shell.readline.get_current_history_length() + 1
183 end = self.shell.readline.get_current_history_length() + 1
184 start = max(end-n, 1)
184 start = max(end-n, 1)
185 ghi = self.shell.readline.get_history_item
185 ghi = self.shell.readline.get_history_item
186 return [ghi(x) for x in range(start, end)]
186 return [ghi(x) for x in range(start, end)]
187
187
188
188
189 @undoc
189 @undoc
190 class DummyMod(object):
190 class DummyMod(object):
191 """A dummy module used for IPython's interactive module when
191 """A dummy module used for IPython's interactive module when
192 a namespace must be assigned to the module's __dict__."""
192 a namespace must be assigned to the module's __dict__."""
193 pass
193 pass
194
194
195
195
196 class ExecutionResult(object):
196 class ExecutionResult(object):
197 """The result of a call to :meth:`InteractiveShell.run_cell`
197 """The result of a call to :meth:`InteractiveShell.run_cell`
198
198
199 Stores information about what took place.
199 Stores information about what took place.
200 """
200 """
201 execution_count = None
201 execution_count = None
202 error_before_exec = None
202 error_before_exec = None
203 error_in_exec = None
203 error_in_exec = None
204 result = None
204 result = None
205
205
206 @property
206 @property
207 def success(self):
207 def success(self):
208 return (self.error_before_exec is None) and (self.error_in_exec is None)
208 return (self.error_before_exec is None) and (self.error_in_exec is None)
209
209
210
210
211 class InteractiveShell(SingletonConfigurable):
211 class InteractiveShell(SingletonConfigurable):
212 """An enhanced, interactive shell for Python."""
212 """An enhanced, interactive shell for Python."""
213
213
214 _instance = None
214 _instance = None
215
215
216 ast_transformers = List([], config=True, help=
216 ast_transformers = List([], config=True, help=
217 """
217 """
218 A list of ast.NodeTransformer subclass instances, which will be applied
218 A list of ast.NodeTransformer subclass instances, which will be applied
219 to user input before code is run.
219 to user input before code is run.
220 """
220 """
221 )
221 )
222
222
223 autocall = Enum((0,1,2), default_value=0, config=True, help=
223 autocall = Enum((0,1,2), default_value=0, config=True, help=
224 """
224 """
225 Make IPython automatically call any callable object even if you didn't
225 Make IPython automatically call any callable object even if you didn't
226 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
226 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
227 automatically. The value can be '0' to disable the feature, '1' for
227 automatically. The value can be '0' to disable the feature, '1' for
228 'smart' autocall, where it is not applied if there are no more
228 'smart' autocall, where it is not applied if there are no more
229 arguments on the line, and '2' for 'full' autocall, where all callable
229 arguments on the line, and '2' for 'full' autocall, where all callable
230 objects are automatically called (even if no arguments are present).
230 objects are automatically called (even if no arguments are present).
231 """
231 """
232 )
232 )
233 # TODO: remove all autoindent logic and put into frontends.
233 # TODO: remove all autoindent logic and put into frontends.
234 # We can't do this yet because even runlines uses the autoindent.
234 # We can't do this yet because even runlines uses the autoindent.
235 autoindent = CBool(True, config=True, help=
235 autoindent = CBool(True, config=True, help=
236 """
236 """
237 Autoindent IPython code entered interactively.
237 Autoindent IPython code entered interactively.
238 """
238 """
239 )
239 )
240 automagic = CBool(True, config=True, help=
240 automagic = CBool(True, config=True, help=
241 """
241 """
242 Enable magic commands to be called without the leading %.
242 Enable magic commands to be called without the leading %.
243 """
243 """
244 )
244 )
245
245
246 banner = Unicode('')
247
248 banner1 = Unicode(default_banner, config=True,
246 banner1 = Unicode(default_banner, config=True,
249 help="""The part of the banner to be printed before the profile"""
247 help="""The part of the banner to be printed before the profile"""
250 )
248 )
251 banner2 = Unicode('', config=True,
249 banner2 = Unicode('', config=True,
252 help="""The part of the banner to be printed after the profile"""
250 help="""The part of the banner to be printed after the profile"""
253 )
251 )
254
252
255 cache_size = Integer(1000, config=True, help=
253 cache_size = Integer(1000, config=True, help=
256 """
254 """
257 Set the size of the output cache. The default is 1000, you can
255 Set the size of the output cache. The default is 1000, you can
258 change it permanently in your config file. Setting it to 0 completely
256 change it permanently in your config file. Setting it to 0 completely
259 disables the caching system, and the minimum value accepted is 20 (if
257 disables the caching system, and the minimum value accepted is 20 (if
260 you provide a value less than 20, it is reset to 0 and a warning is
258 you provide a value less than 20, it is reset to 0 and a warning is
261 issued). This limit is defined because otherwise you'll spend more
259 issued). This limit is defined because otherwise you'll spend more
262 time re-flushing a too small cache than working
260 time re-flushing a too small cache than working
263 """
261 """
264 )
262 )
265 color_info = CBool(True, config=True, help=
263 color_info = CBool(True, config=True, help=
266 """
264 """
267 Use colors for displaying information about objects. Because this
265 Use colors for displaying information about objects. Because this
268 information is passed through a pager (like 'less'), and some pagers
266 information is passed through a pager (like 'less'), and some pagers
269 get confused with color codes, this capability can be turned off.
267 get confused with color codes, this capability can be turned off.
270 """
268 """
271 )
269 )
272 colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
270 colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
273 default_value=get_default_colors(), config=True,
271 default_value=get_default_colors(), config=True,
274 help="Set the color scheme (NoColor, Linux, or LightBG)."
272 help="Set the color scheme (NoColor, Linux, or LightBG)."
275 )
273 )
276 colors_force = CBool(False, help=
274 colors_force = CBool(False, help=
277 """
275 """
278 Force use of ANSI color codes, regardless of OS and readline
276 Force use of ANSI color codes, regardless of OS and readline
279 availability.
277 availability.
280 """
278 """
281 # FIXME: This is essentially a hack to allow ZMQShell to show colors
279 # FIXME: This is essentially a hack to allow ZMQShell to show colors
282 # without readline on Win32. When the ZMQ formatting system is
280 # without readline on Win32. When the ZMQ formatting system is
283 # refactored, this should be removed.
281 # refactored, this should be removed.
284 )
282 )
285 debug = CBool(False, config=True)
283 debug = CBool(False, config=True)
286 deep_reload = CBool(False, config=True, help=
284 deep_reload = CBool(False, config=True, help=
287 """
285 """
288 Enable deep (recursive) reloading by default. IPython can use the
286 Enable deep (recursive) reloading by default. IPython can use the
289 deep_reload module which reloads changes in modules recursively (it
287 deep_reload module which reloads changes in modules recursively (it
290 replaces the reload() function, so you don't need to change anything to
288 replaces the reload() function, so you don't need to change anything to
291 use it). deep_reload() forces a full reload of modules whose code may
289 use it). deep_reload() forces a full reload of modules whose code may
292 have changed, which the default reload() function does not. When
290 have changed, which the default reload() function does not. When
293 deep_reload is off, IPython will use the normal reload(), but
291 deep_reload is off, IPython will use the normal reload(), but
294 deep_reload will still be available as dreload().
292 deep_reload will still be available as dreload().
295 """
293 """
296 )
294 )
297 disable_failing_post_execute = CBool(False, config=True,
295 disable_failing_post_execute = CBool(False, config=True,
298 help="Don't call post-execute functions that have failed in the past."
296 help="Don't call post-execute functions that have failed in the past."
299 )
297 )
300 display_formatter = Instance(DisplayFormatter)
298 display_formatter = Instance(DisplayFormatter)
301 displayhook_class = Type(DisplayHook)
299 displayhook_class = Type(DisplayHook)
302 display_pub_class = Type(DisplayPublisher)
300 display_pub_class = Type(DisplayPublisher)
303 data_pub_class = None
301 data_pub_class = None
304
302
305 exit_now = CBool(False)
303 exit_now = CBool(False)
306 exiter = Instance(ExitAutocall)
304 exiter = Instance(ExitAutocall)
307 def _exiter_default(self):
305 def _exiter_default(self):
308 return ExitAutocall(self)
306 return ExitAutocall(self)
309 # Monotonically increasing execution counter
307 # Monotonically increasing execution counter
310 execution_count = Integer(1)
308 execution_count = Integer(1)
311 filename = Unicode("<ipython console>")
309 filename = Unicode("<ipython console>")
312 ipython_dir= Unicode('', config=True) # Set to get_ipython_dir() in __init__
310 ipython_dir= Unicode('', config=True) # Set to get_ipython_dir() in __init__
313
311
314 # Input splitter, to transform input line by line and detect when a block
312 # Input splitter, to transform input line by line and detect when a block
315 # is ready to be executed.
313 # is ready to be executed.
316 input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
314 input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
317 (), {'line_input_checker': True})
315 (), {'line_input_checker': True})
318
316
319 # This InputSplitter instance is used to transform completed cells before
317 # This InputSplitter instance is used to transform completed cells before
320 # running them. It allows cell magics to contain blank lines.
318 # running them. It allows cell magics to contain blank lines.
321 input_transformer_manager = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
319 input_transformer_manager = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
322 (), {'line_input_checker': False})
320 (), {'line_input_checker': False})
323
321
324 logstart = CBool(False, config=True, help=
322 logstart = CBool(False, config=True, help=
325 """
323 """
326 Start logging to the default log file.
324 Start logging to the default log file.
327 """
325 """
328 )
326 )
329 logfile = Unicode('', config=True, help=
327 logfile = Unicode('', config=True, help=
330 """
328 """
331 The name of the logfile to use.
329 The name of the logfile to use.
332 """
330 """
333 )
331 )
334 logappend = Unicode('', config=True, help=
332 logappend = Unicode('', config=True, help=
335 """
333 """
336 Start logging to the given file in append mode.
334 Start logging to the given file in append mode.
337 """
335 """
338 )
336 )
339 object_info_string_level = Enum((0,1,2), default_value=0,
337 object_info_string_level = Enum((0,1,2), default_value=0,
340 config=True)
338 config=True)
341 pdb = CBool(False, config=True, help=
339 pdb = CBool(False, config=True, help=
342 """
340 """
343 Automatically call the pdb debugger after every exception.
341 Automatically call the pdb debugger after every exception.
344 """
342 """
345 )
343 )
346 multiline_history = CBool(sys.platform != 'win32', config=True,
344 multiline_history = CBool(sys.platform != 'win32', config=True,
347 help="Save multi-line entries as one entry in readline history"
345 help="Save multi-line entries as one entry in readline history"
348 )
346 )
349 display_page = Bool(False, config=True,
347 display_page = Bool(False, config=True,
350 help="""If True, anything that would be passed to the pager
348 help="""If True, anything that would be passed to the pager
351 will be displayed as regular output instead."""
349 will be displayed as regular output instead."""
352 )
350 )
353
351
354 # deprecated prompt traits:
352 # deprecated prompt traits:
355
353
356 prompt_in1 = Unicode('In [\\#]: ', config=True,
354 prompt_in1 = Unicode('In [\\#]: ', config=True,
357 help="Deprecated, use PromptManager.in_template")
355 help="Deprecated, use PromptManager.in_template")
358 prompt_in2 = Unicode(' .\\D.: ', config=True,
356 prompt_in2 = Unicode(' .\\D.: ', config=True,
359 help="Deprecated, use PromptManager.in2_template")
357 help="Deprecated, use PromptManager.in2_template")
360 prompt_out = Unicode('Out[\\#]: ', config=True,
358 prompt_out = Unicode('Out[\\#]: ', config=True,
361 help="Deprecated, use PromptManager.out_template")
359 help="Deprecated, use PromptManager.out_template")
362 prompts_pad_left = CBool(True, config=True,
360 prompts_pad_left = CBool(True, config=True,
363 help="Deprecated, use PromptManager.justify")
361 help="Deprecated, use PromptManager.justify")
364
362
365 def _prompt_trait_changed(self, name, old, new):
363 def _prompt_trait_changed(self, name, old, new):
366 table = {
364 table = {
367 'prompt_in1' : 'in_template',
365 'prompt_in1' : 'in_template',
368 'prompt_in2' : 'in2_template',
366 'prompt_in2' : 'in2_template',
369 'prompt_out' : 'out_template',
367 'prompt_out' : 'out_template',
370 'prompts_pad_left' : 'justify',
368 'prompts_pad_left' : 'justify',
371 }
369 }
372 warn("InteractiveShell.{name} is deprecated, use PromptManager.{newname}".format(
370 warn("InteractiveShell.{name} is deprecated, use PromptManager.{newname}".format(
373 name=name, newname=table[name])
371 name=name, newname=table[name])
374 )
372 )
375 # protect against weird cases where self.config may not exist:
373 # protect against weird cases where self.config may not exist:
376 if self.config is not None:
374 if self.config is not None:
377 # propagate to corresponding PromptManager trait
375 # propagate to corresponding PromptManager trait
378 setattr(self.config.PromptManager, table[name], new)
376 setattr(self.config.PromptManager, table[name], new)
379
377
380 _prompt_in1_changed = _prompt_trait_changed
378 _prompt_in1_changed = _prompt_trait_changed
381 _prompt_in2_changed = _prompt_trait_changed
379 _prompt_in2_changed = _prompt_trait_changed
382 _prompt_out_changed = _prompt_trait_changed
380 _prompt_out_changed = _prompt_trait_changed
383 _prompt_pad_left_changed = _prompt_trait_changed
381 _prompt_pad_left_changed = _prompt_trait_changed
384
382
385 show_rewritten_input = CBool(True, config=True,
383 show_rewritten_input = CBool(True, config=True,
386 help="Show rewritten input, e.g. for autocall."
384 help="Show rewritten input, e.g. for autocall."
387 )
385 )
388
386
389 quiet = CBool(False, config=True)
387 quiet = CBool(False, config=True)
390
388
391 history_length = Integer(10000, config=True)
389 history_length = Integer(10000, config=True)
392
390
393 # The readline stuff will eventually be moved to the terminal subclass
391 # The readline stuff will eventually be moved to the terminal subclass
394 # but for now, we can't do that as readline is welded in everywhere.
392 # but for now, we can't do that as readline is welded in everywhere.
395 readline_use = CBool(True, config=True)
393 readline_use = CBool(True, config=True)
396 readline_remove_delims = Unicode('-/~', config=True)
394 readline_remove_delims = Unicode('-/~', config=True)
397 readline_delims = Unicode() # set by init_readline()
395 readline_delims = Unicode() # set by init_readline()
398 # don't use \M- bindings by default, because they
396 # don't use \M- bindings by default, because they
399 # conflict with 8-bit encodings. See gh-58,gh-88
397 # conflict with 8-bit encodings. See gh-58,gh-88
400 readline_parse_and_bind = List([
398 readline_parse_and_bind = List([
401 'tab: complete',
399 'tab: complete',
402 '"\C-l": clear-screen',
400 '"\C-l": clear-screen',
403 'set show-all-if-ambiguous on',
401 'set show-all-if-ambiguous on',
404 '"\C-o": tab-insert',
402 '"\C-o": tab-insert',
405 '"\C-r": reverse-search-history',
403 '"\C-r": reverse-search-history',
406 '"\C-s": forward-search-history',
404 '"\C-s": forward-search-history',
407 '"\C-p": history-search-backward',
405 '"\C-p": history-search-backward',
408 '"\C-n": history-search-forward',
406 '"\C-n": history-search-forward',
409 '"\e[A": history-search-backward',
407 '"\e[A": history-search-backward',
410 '"\e[B": history-search-forward',
408 '"\e[B": history-search-forward',
411 '"\C-k": kill-line',
409 '"\C-k": kill-line',
412 '"\C-u": unix-line-discard',
410 '"\C-u": unix-line-discard',
413 ], config=True)
411 ], config=True)
414
412
415 _custom_readline_config = False
413 _custom_readline_config = False
416
414
417 def _readline_parse_and_bind_changed(self, name, old, new):
415 def _readline_parse_and_bind_changed(self, name, old, new):
418 # notice that readline config is customized
416 # notice that readline config is customized
419 # indicates that it should have higher priority than inputrc
417 # indicates that it should have higher priority than inputrc
420 self._custom_readline_config = True
418 self._custom_readline_config = True
421
419
422 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none'],
420 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none'],
423 default_value='last_expr', config=True,
421 default_value='last_expr', config=True,
424 help="""
422 help="""
425 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
423 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
426 run interactively (displaying output from expressions).""")
424 run interactively (displaying output from expressions).""")
427
425
428 # TODO: this part of prompt management should be moved to the frontends.
426 # TODO: this part of prompt management should be moved to the frontends.
429 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
427 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
430 separate_in = SeparateUnicode('\n', config=True)
428 separate_in = SeparateUnicode('\n', config=True)
431 separate_out = SeparateUnicode('', config=True)
429 separate_out = SeparateUnicode('', config=True)
432 separate_out2 = SeparateUnicode('', config=True)
430 separate_out2 = SeparateUnicode('', config=True)
433 wildcards_case_sensitive = CBool(True, config=True)
431 wildcards_case_sensitive = CBool(True, config=True)
434 xmode = CaselessStrEnum(('Context','Plain', 'Verbose'),
432 xmode = CaselessStrEnum(('Context','Plain', 'Verbose'),
435 default_value='Context', config=True)
433 default_value='Context', config=True)
436
434
437 # Subcomponents of InteractiveShell
435 # Subcomponents of InteractiveShell
438 alias_manager = Instance('IPython.core.alias.AliasManager')
436 alias_manager = Instance('IPython.core.alias.AliasManager')
439 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
437 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
440 builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
438 builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
441 display_trap = Instance('IPython.core.display_trap.DisplayTrap')
439 display_trap = Instance('IPython.core.display_trap.DisplayTrap')
442 extension_manager = Instance('IPython.core.extensions.ExtensionManager')
440 extension_manager = Instance('IPython.core.extensions.ExtensionManager')
443 payload_manager = Instance('IPython.core.payload.PayloadManager')
441 payload_manager = Instance('IPython.core.payload.PayloadManager')
444 history_manager = Instance('IPython.core.history.HistoryAccessorBase')
442 history_manager = Instance('IPython.core.history.HistoryAccessorBase')
445 magics_manager = Instance('IPython.core.magic.MagicsManager')
443 magics_manager = Instance('IPython.core.magic.MagicsManager')
446
444
447 profile_dir = Instance('IPython.core.application.ProfileDir')
445 profile_dir = Instance('IPython.core.application.ProfileDir')
448 @property
446 @property
449 def profile(self):
447 def profile(self):
450 if self.profile_dir is not None:
448 if self.profile_dir is not None:
451 name = os.path.basename(self.profile_dir.location)
449 name = os.path.basename(self.profile_dir.location)
452 return name.replace('profile_','')
450 return name.replace('profile_','')
453
451
454
452
455 # Private interface
453 # Private interface
456 _post_execute = Instance(dict)
454 _post_execute = Instance(dict)
457
455
458 # Tracks any GUI loop loaded for pylab
456 # Tracks any GUI loop loaded for pylab
459 pylab_gui_select = None
457 pylab_gui_select = None
460
458
461 def __init__(self, ipython_dir=None, profile_dir=None,
459 def __init__(self, ipython_dir=None, profile_dir=None,
462 user_module=None, user_ns=None,
460 user_module=None, user_ns=None,
463 custom_exceptions=((), None), **kwargs):
461 custom_exceptions=((), None), **kwargs):
464
462
465 # This is where traits with a config_key argument are updated
463 # This is where traits with a config_key argument are updated
466 # from the values on config.
464 # from the values on config.
467 super(InteractiveShell, self).__init__(**kwargs)
465 super(InteractiveShell, self).__init__(**kwargs)
468 self.configurables = [self]
466 self.configurables = [self]
469
467
470 # These are relatively independent and stateless
468 # These are relatively independent and stateless
471 self.init_ipython_dir(ipython_dir)
469 self.init_ipython_dir(ipython_dir)
472 self.init_profile_dir(profile_dir)
470 self.init_profile_dir(profile_dir)
473 self.init_instance_attrs()
471 self.init_instance_attrs()
474 self.init_environment()
472 self.init_environment()
475
473
476 # Check if we're in a virtualenv, and set up sys.path.
474 # Check if we're in a virtualenv, and set up sys.path.
477 self.init_virtualenv()
475 self.init_virtualenv()
478
476
479 # Create namespaces (user_ns, user_global_ns, etc.)
477 # Create namespaces (user_ns, user_global_ns, etc.)
480 self.init_create_namespaces(user_module, user_ns)
478 self.init_create_namespaces(user_module, user_ns)
481 # This has to be done after init_create_namespaces because it uses
479 # This has to be done after init_create_namespaces because it uses
482 # something in self.user_ns, but before init_sys_modules, which
480 # something in self.user_ns, but before init_sys_modules, which
483 # is the first thing to modify sys.
481 # is the first thing to modify sys.
484 # TODO: When we override sys.stdout and sys.stderr before this class
482 # TODO: When we override sys.stdout and sys.stderr before this class
485 # is created, we are saving the overridden ones here. Not sure if this
483 # is created, we are saving the overridden ones here. Not sure if this
486 # is what we want to do.
484 # is what we want to do.
487 self.save_sys_module_state()
485 self.save_sys_module_state()
488 self.init_sys_modules()
486 self.init_sys_modules()
489
487
490 # While we're trying to have each part of the code directly access what
488 # While we're trying to have each part of the code directly access what
491 # it needs without keeping redundant references to objects, we have too
489 # it needs without keeping redundant references to objects, we have too
492 # much legacy code that expects ip.db to exist.
490 # much legacy code that expects ip.db to exist.
493 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
491 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
494
492
495 self.init_history()
493 self.init_history()
496 self.init_encoding()
494 self.init_encoding()
497 self.init_prefilter()
495 self.init_prefilter()
498
496
499 self.init_syntax_highlighting()
497 self.init_syntax_highlighting()
500 self.init_hooks()
498 self.init_hooks()
501 self.init_events()
499 self.init_events()
502 self.init_pushd_popd_magic()
500 self.init_pushd_popd_magic()
503 # self.init_traceback_handlers use to be here, but we moved it below
501 # self.init_traceback_handlers use to be here, but we moved it below
504 # because it and init_io have to come after init_readline.
502 # because it and init_io have to come after init_readline.
505 self.init_user_ns()
503 self.init_user_ns()
506 self.init_logger()
504 self.init_logger()
507 self.init_builtins()
505 self.init_builtins()
508
506
509 # The following was in post_config_initialization
507 # The following was in post_config_initialization
510 self.init_inspector()
508 self.init_inspector()
511 # init_readline() must come before init_io(), because init_io uses
509 # init_readline() must come before init_io(), because init_io uses
512 # readline related things.
510 # readline related things.
513 self.init_readline()
511 self.init_readline()
514 # We save this here in case user code replaces raw_input, but it needs
512 # We save this here in case user code replaces raw_input, but it needs
515 # to be after init_readline(), because PyPy's readline works by replacing
513 # to be after init_readline(), because PyPy's readline works by replacing
516 # raw_input.
514 # raw_input.
517 if py3compat.PY3:
515 if py3compat.PY3:
518 self.raw_input_original = input
516 self.raw_input_original = input
519 else:
517 else:
520 self.raw_input_original = raw_input
518 self.raw_input_original = raw_input
521 # init_completer must come after init_readline, because it needs to
519 # init_completer must come after init_readline, because it needs to
522 # know whether readline is present or not system-wide to configure the
520 # know whether readline is present or not system-wide to configure the
523 # completers, since the completion machinery can now operate
521 # completers, since the completion machinery can now operate
524 # independently of readline (e.g. over the network)
522 # independently of readline (e.g. over the network)
525 self.init_completer()
523 self.init_completer()
526 # TODO: init_io() needs to happen before init_traceback handlers
524 # TODO: init_io() needs to happen before init_traceback handlers
527 # because the traceback handlers hardcode the stdout/stderr streams.
525 # because the traceback handlers hardcode the stdout/stderr streams.
528 # This logic in in debugger.Pdb and should eventually be changed.
526 # This logic in in debugger.Pdb and should eventually be changed.
529 self.init_io()
527 self.init_io()
530 self.init_traceback_handlers(custom_exceptions)
528 self.init_traceback_handlers(custom_exceptions)
531 self.init_prompts()
529 self.init_prompts()
532 self.init_display_formatter()
530 self.init_display_formatter()
533 self.init_display_pub()
531 self.init_display_pub()
534 self.init_data_pub()
532 self.init_data_pub()
535 self.init_displayhook()
533 self.init_displayhook()
536 self.init_latextool()
534 self.init_latextool()
537 self.init_magics()
535 self.init_magics()
538 self.init_alias()
536 self.init_alias()
539 self.init_logstart()
537 self.init_logstart()
540 self.init_pdb()
538 self.init_pdb()
541 self.init_extension_manager()
539 self.init_extension_manager()
542 self.init_payload()
540 self.init_payload()
543 self.hooks.late_startup_hook()
541 self.hooks.late_startup_hook()
544 self.events.trigger('shell_initialized', self)
542 self.events.trigger('shell_initialized', self)
545 atexit.register(self.atexit_operations)
543 atexit.register(self.atexit_operations)
546
544
547 def get_ipython(self):
545 def get_ipython(self):
548 """Return the currently running IPython instance."""
546 """Return the currently running IPython instance."""
549 return self
547 return self
550
548
551 #-------------------------------------------------------------------------
549 #-------------------------------------------------------------------------
552 # Trait changed handlers
550 # Trait changed handlers
553 #-------------------------------------------------------------------------
551 #-------------------------------------------------------------------------
554
552
555 def _ipython_dir_changed(self, name, new):
553 def _ipython_dir_changed(self, name, new):
556 ensure_dir_exists(new)
554 ensure_dir_exists(new)
557
555
558 def set_autoindent(self,value=None):
556 def set_autoindent(self,value=None):
559 """Set the autoindent flag, checking for readline support.
557 """Set the autoindent flag, checking for readline support.
560
558
561 If called with no arguments, it acts as a toggle."""
559 If called with no arguments, it acts as a toggle."""
562
560
563 if value != 0 and not self.has_readline:
561 if value != 0 and not self.has_readline:
564 if os.name == 'posix':
562 if os.name == 'posix':
565 warn("The auto-indent feature requires the readline library")
563 warn("The auto-indent feature requires the readline library")
566 self.autoindent = 0
564 self.autoindent = 0
567 return
565 return
568 if value is None:
566 if value is None:
569 self.autoindent = not self.autoindent
567 self.autoindent = not self.autoindent
570 else:
568 else:
571 self.autoindent = value
569 self.autoindent = value
572
570
573 #-------------------------------------------------------------------------
571 #-------------------------------------------------------------------------
574 # init_* methods called by __init__
572 # init_* methods called by __init__
575 #-------------------------------------------------------------------------
573 #-------------------------------------------------------------------------
576
574
577 def init_ipython_dir(self, ipython_dir):
575 def init_ipython_dir(self, ipython_dir):
578 if ipython_dir is not None:
576 if ipython_dir is not None:
579 self.ipython_dir = ipython_dir
577 self.ipython_dir = ipython_dir
580 return
578 return
581
579
582 self.ipython_dir = get_ipython_dir()
580 self.ipython_dir = get_ipython_dir()
583
581
584 def init_profile_dir(self, profile_dir):
582 def init_profile_dir(self, profile_dir):
585 if profile_dir is not None:
583 if profile_dir is not None:
586 self.profile_dir = profile_dir
584 self.profile_dir = profile_dir
587 return
585 return
588 self.profile_dir =\
586 self.profile_dir =\
589 ProfileDir.create_profile_dir_by_name(self.ipython_dir, 'default')
587 ProfileDir.create_profile_dir_by_name(self.ipython_dir, 'default')
590
588
591 def init_instance_attrs(self):
589 def init_instance_attrs(self):
592 self.more = False
590 self.more = False
593
591
594 # command compiler
592 # command compiler
595 self.compile = CachingCompiler()
593 self.compile = CachingCompiler()
596
594
597 # Make an empty namespace, which extension writers can rely on both
595 # Make an empty namespace, which extension writers can rely on both
598 # existing and NEVER being used by ipython itself. This gives them a
596 # existing and NEVER being used by ipython itself. This gives them a
599 # convenient location for storing additional information and state
597 # convenient location for storing additional information and state
600 # their extensions may require, without fear of collisions with other
598 # their extensions may require, without fear of collisions with other
601 # ipython names that may develop later.
599 # ipython names that may develop later.
602 self.meta = Struct()
600 self.meta = Struct()
603
601
604 # Temporary files used for various purposes. Deleted at exit.
602 # Temporary files used for various purposes. Deleted at exit.
605 self.tempfiles = []
603 self.tempfiles = []
606 self.tempdirs = []
604 self.tempdirs = []
607
605
608 # Keep track of readline usage (later set by init_readline)
606 # Keep track of readline usage (later set by init_readline)
609 self.has_readline = False
607 self.has_readline = False
610
608
611 # keep track of where we started running (mainly for crash post-mortem)
609 # keep track of where we started running (mainly for crash post-mortem)
612 # This is not being used anywhere currently.
610 # This is not being used anywhere currently.
613 self.starting_dir = py3compat.getcwd()
611 self.starting_dir = py3compat.getcwd()
614
612
615 # Indentation management
613 # Indentation management
616 self.indent_current_nsp = 0
614 self.indent_current_nsp = 0
617
615
618 # Dict to track post-execution functions that have been registered
616 # Dict to track post-execution functions that have been registered
619 self._post_execute = {}
617 self._post_execute = {}
620
618
621 def init_environment(self):
619 def init_environment(self):
622 """Any changes we need to make to the user's environment."""
620 """Any changes we need to make to the user's environment."""
623 pass
621 pass
624
622
625 def init_encoding(self):
623 def init_encoding(self):
626 # Get system encoding at startup time. Certain terminals (like Emacs
624 # Get system encoding at startup time. Certain terminals (like Emacs
627 # under Win32 have it set to None, and we need to have a known valid
625 # under Win32 have it set to None, and we need to have a known valid
628 # encoding to use in the raw_input() method
626 # encoding to use in the raw_input() method
629 try:
627 try:
630 self.stdin_encoding = sys.stdin.encoding or 'ascii'
628 self.stdin_encoding = sys.stdin.encoding or 'ascii'
631 except AttributeError:
629 except AttributeError:
632 self.stdin_encoding = 'ascii'
630 self.stdin_encoding = 'ascii'
633
631
634 def init_syntax_highlighting(self):
632 def init_syntax_highlighting(self):
635 # Python source parser/formatter for syntax highlighting
633 # Python source parser/formatter for syntax highlighting
636 pyformat = PyColorize.Parser().format
634 pyformat = PyColorize.Parser().format
637 self.pycolorize = lambda src: pyformat(src,'str',self.colors)
635 self.pycolorize = lambda src: pyformat(src,'str',self.colors)
638
636
639 def init_pushd_popd_magic(self):
637 def init_pushd_popd_magic(self):
640 # for pushd/popd management
638 # for pushd/popd management
641 self.home_dir = get_home_dir()
639 self.home_dir = get_home_dir()
642
640
643 self.dir_stack = []
641 self.dir_stack = []
644
642
645 def init_logger(self):
643 def init_logger(self):
646 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
644 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
647 logmode='rotate')
645 logmode='rotate')
648
646
649 def init_logstart(self):
647 def init_logstart(self):
650 """Initialize logging in case it was requested at the command line.
648 """Initialize logging in case it was requested at the command line.
651 """
649 """
652 if self.logappend:
650 if self.logappend:
653 self.magic('logstart %s append' % self.logappend)
651 self.magic('logstart %s append' % self.logappend)
654 elif self.logfile:
652 elif self.logfile:
655 self.magic('logstart %s' % self.logfile)
653 self.magic('logstart %s' % self.logfile)
656 elif self.logstart:
654 elif self.logstart:
657 self.magic('logstart')
655 self.magic('logstart')
658
656
659 def init_builtins(self):
657 def init_builtins(self):
660 # A single, static flag that we set to True. Its presence indicates
658 # A single, static flag that we set to True. Its presence indicates
661 # that an IPython shell has been created, and we make no attempts at
659 # that an IPython shell has been created, and we make no attempts at
662 # removing on exit or representing the existence of more than one
660 # removing on exit or representing the existence of more than one
663 # IPython at a time.
661 # IPython at a time.
664 builtin_mod.__dict__['__IPYTHON__'] = True
662 builtin_mod.__dict__['__IPYTHON__'] = True
665
663
666 # In 0.11 we introduced '__IPYTHON__active' as an integer we'd try to
664 # In 0.11 we introduced '__IPYTHON__active' as an integer we'd try to
667 # manage on enter/exit, but with all our shells it's virtually
665 # manage on enter/exit, but with all our shells it's virtually
668 # impossible to get all the cases right. We're leaving the name in for
666 # impossible to get all the cases right. We're leaving the name in for
669 # those who adapted their codes to check for this flag, but will
667 # those who adapted their codes to check for this flag, but will
670 # eventually remove it after a few more releases.
668 # eventually remove it after a few more releases.
671 builtin_mod.__dict__['__IPYTHON__active'] = \
669 builtin_mod.__dict__['__IPYTHON__active'] = \
672 'Deprecated, check for __IPYTHON__'
670 'Deprecated, check for __IPYTHON__'
673
671
674 self.builtin_trap = BuiltinTrap(shell=self)
672 self.builtin_trap = BuiltinTrap(shell=self)
675
673
676 def init_inspector(self):
674 def init_inspector(self):
677 # Object inspector
675 # Object inspector
678 self.inspector = oinspect.Inspector(oinspect.InspectColors,
676 self.inspector = oinspect.Inspector(oinspect.InspectColors,
679 PyColorize.ANSICodeColors,
677 PyColorize.ANSICodeColors,
680 'NoColor',
678 'NoColor',
681 self.object_info_string_level)
679 self.object_info_string_level)
682
680
683 def init_io(self):
681 def init_io(self):
684 # This will just use sys.stdout and sys.stderr. If you want to
682 # This will just use sys.stdout and sys.stderr. If you want to
685 # override sys.stdout and sys.stderr themselves, you need to do that
683 # override sys.stdout and sys.stderr themselves, you need to do that
686 # *before* instantiating this class, because io holds onto
684 # *before* instantiating this class, because io holds onto
687 # references to the underlying streams.
685 # references to the underlying streams.
688 if (sys.platform == 'win32' or sys.platform == 'cli') and self.has_readline:
686 if (sys.platform == 'win32' or sys.platform == 'cli') and self.has_readline:
689 io.stdout = io.stderr = io.IOStream(self.readline._outputfile)
687 io.stdout = io.stderr = io.IOStream(self.readline._outputfile)
690 else:
688 else:
691 io.stdout = io.IOStream(sys.stdout)
689 io.stdout = io.IOStream(sys.stdout)
692 io.stderr = io.IOStream(sys.stderr)
690 io.stderr = io.IOStream(sys.stderr)
693
691
694 def init_prompts(self):
692 def init_prompts(self):
695 self.prompt_manager = PromptManager(shell=self, parent=self)
693 self.prompt_manager = PromptManager(shell=self, parent=self)
696 self.configurables.append(self.prompt_manager)
694 self.configurables.append(self.prompt_manager)
697 # Set system prompts, so that scripts can decide if they are running
695 # Set system prompts, so that scripts can decide if they are running
698 # interactively.
696 # interactively.
699 sys.ps1 = 'In : '
697 sys.ps1 = 'In : '
700 sys.ps2 = '...: '
698 sys.ps2 = '...: '
701 sys.ps3 = 'Out: '
699 sys.ps3 = 'Out: '
702
700
703 def init_display_formatter(self):
701 def init_display_formatter(self):
704 self.display_formatter = DisplayFormatter(parent=self)
702 self.display_formatter = DisplayFormatter(parent=self)
705 self.configurables.append(self.display_formatter)
703 self.configurables.append(self.display_formatter)
706
704
707 def init_display_pub(self):
705 def init_display_pub(self):
708 self.display_pub = self.display_pub_class(parent=self)
706 self.display_pub = self.display_pub_class(parent=self)
709 self.configurables.append(self.display_pub)
707 self.configurables.append(self.display_pub)
710
708
711 def init_data_pub(self):
709 def init_data_pub(self):
712 if not self.data_pub_class:
710 if not self.data_pub_class:
713 self.data_pub = None
711 self.data_pub = None
714 return
712 return
715 self.data_pub = self.data_pub_class(parent=self)
713 self.data_pub = self.data_pub_class(parent=self)
716 self.configurables.append(self.data_pub)
714 self.configurables.append(self.data_pub)
717
715
718 def init_displayhook(self):
716 def init_displayhook(self):
719 # Initialize displayhook, set in/out prompts and printing system
717 # Initialize displayhook, set in/out prompts and printing system
720 self.displayhook = self.displayhook_class(
718 self.displayhook = self.displayhook_class(
721 parent=self,
719 parent=self,
722 shell=self,
720 shell=self,
723 cache_size=self.cache_size,
721 cache_size=self.cache_size,
724 )
722 )
725 self.configurables.append(self.displayhook)
723 self.configurables.append(self.displayhook)
726 # This is a context manager that installs/revmoes the displayhook at
724 # This is a context manager that installs/revmoes the displayhook at
727 # the appropriate time.
725 # the appropriate time.
728 self.display_trap = DisplayTrap(hook=self.displayhook)
726 self.display_trap = DisplayTrap(hook=self.displayhook)
729
727
730 def init_latextool(self):
728 def init_latextool(self):
731 """Configure LaTeXTool."""
729 """Configure LaTeXTool."""
732 cfg = LaTeXTool.instance(parent=self)
730 cfg = LaTeXTool.instance(parent=self)
733 if cfg not in self.configurables:
731 if cfg not in self.configurables:
734 self.configurables.append(cfg)
732 self.configurables.append(cfg)
735
733
736 def init_virtualenv(self):
734 def init_virtualenv(self):
737 """Add a virtualenv to sys.path so the user can import modules from it.
735 """Add a virtualenv to sys.path so the user can import modules from it.
738 This isn't perfect: it doesn't use the Python interpreter with which the
736 This isn't perfect: it doesn't use the Python interpreter with which the
739 virtualenv was built, and it ignores the --no-site-packages option. A
737 virtualenv was built, and it ignores the --no-site-packages option. A
740 warning will appear suggesting the user installs IPython in the
738 warning will appear suggesting the user installs IPython in the
741 virtualenv, but for many cases, it probably works well enough.
739 virtualenv, but for many cases, it probably works well enough.
742
740
743 Adapted from code snippets online.
741 Adapted from code snippets online.
744
742
745 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
743 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
746 """
744 """
747 if 'VIRTUAL_ENV' not in os.environ:
745 if 'VIRTUAL_ENV' not in os.environ:
748 # Not in a virtualenv
746 # Not in a virtualenv
749 return
747 return
750
748
751 # venv detection:
749 # venv detection:
752 # stdlib venv may symlink sys.executable, so we can't use realpath.
750 # stdlib venv may symlink sys.executable, so we can't use realpath.
753 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
751 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
754 # So we just check every item in the symlink tree (generally <= 3)
752 # So we just check every item in the symlink tree (generally <= 3)
755 p = os.path.normcase(sys.executable)
753 p = os.path.normcase(sys.executable)
756 paths = [p]
754 paths = [p]
757 while os.path.islink(p):
755 while os.path.islink(p):
758 p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p)))
756 p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p)))
759 paths.append(p)
757 paths.append(p)
760 p_venv = os.path.normcase(os.environ['VIRTUAL_ENV'])
758 p_venv = os.path.normcase(os.environ['VIRTUAL_ENV'])
761 if any(p.startswith(p_venv) for p in paths):
759 if any(p.startswith(p_venv) for p in paths):
762 # Running properly in the virtualenv, don't need to do anything
760 # Running properly in the virtualenv, don't need to do anything
763 return
761 return
764
762
765 warn("Attempting to work in a virtualenv. If you encounter problems, please "
763 warn("Attempting to work in a virtualenv. If you encounter problems, please "
766 "install IPython inside the virtualenv.")
764 "install IPython inside the virtualenv.")
767 if sys.platform == "win32":
765 if sys.platform == "win32":
768 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'Lib', 'site-packages')
766 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'Lib', 'site-packages')
769 else:
767 else:
770 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'lib',
768 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'lib',
771 'python%d.%d' % sys.version_info[:2], 'site-packages')
769 'python%d.%d' % sys.version_info[:2], 'site-packages')
772
770
773 import site
771 import site
774 sys.path.insert(0, virtual_env)
772 sys.path.insert(0, virtual_env)
775 site.addsitedir(virtual_env)
773 site.addsitedir(virtual_env)
776
774
777 #-------------------------------------------------------------------------
775 #-------------------------------------------------------------------------
778 # Things related to injections into the sys module
776 # Things related to injections into the sys module
779 #-------------------------------------------------------------------------
777 #-------------------------------------------------------------------------
780
778
781 def save_sys_module_state(self):
779 def save_sys_module_state(self):
782 """Save the state of hooks in the sys module.
780 """Save the state of hooks in the sys module.
783
781
784 This has to be called after self.user_module is created.
782 This has to be called after self.user_module is created.
785 """
783 """
786 self._orig_sys_module_state = {}
784 self._orig_sys_module_state = {}
787 self._orig_sys_module_state['stdin'] = sys.stdin
785 self._orig_sys_module_state['stdin'] = sys.stdin
788 self._orig_sys_module_state['stdout'] = sys.stdout
786 self._orig_sys_module_state['stdout'] = sys.stdout
789 self._orig_sys_module_state['stderr'] = sys.stderr
787 self._orig_sys_module_state['stderr'] = sys.stderr
790 self._orig_sys_module_state['excepthook'] = sys.excepthook
788 self._orig_sys_module_state['excepthook'] = sys.excepthook
791 self._orig_sys_modules_main_name = self.user_module.__name__
789 self._orig_sys_modules_main_name = self.user_module.__name__
792 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
790 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
793
791
794 def restore_sys_module_state(self):
792 def restore_sys_module_state(self):
795 """Restore the state of the sys module."""
793 """Restore the state of the sys module."""
796 try:
794 try:
797 for k, v in iteritems(self._orig_sys_module_state):
795 for k, v in iteritems(self._orig_sys_module_state):
798 setattr(sys, k, v)
796 setattr(sys, k, v)
799 except AttributeError:
797 except AttributeError:
800 pass
798 pass
801 # Reset what what done in self.init_sys_modules
799 # Reset what what done in self.init_sys_modules
802 if self._orig_sys_modules_main_mod is not None:
800 if self._orig_sys_modules_main_mod is not None:
803 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
801 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
804
802
805 #-------------------------------------------------------------------------
803 #-------------------------------------------------------------------------
806 # Things related to the banner
804 # Things related to the banner
807 #-------------------------------------------------------------------------
805 #-------------------------------------------------------------------------
808
806
809 @property
807 @property
810 def banner(self):
808 def banner(self):
811 banner = self.banner1
809 banner = self.banner1
812 if self.profile and self.profile != 'default':
810 if self.profile and self.profile != 'default':
813 banner += '\nIPython profile: %s\n' % self.profile
811 banner += '\nIPython profile: %s\n' % self.profile
814 if self.banner2:
812 if self.banner2:
815 banner += '\n' + self.banner2
813 banner += '\n' + self.banner2
816 return banner
814 return banner
817
815
818 def show_banner(self, banner=None):
816 def show_banner(self, banner=None):
819 if banner is None:
817 if banner is None:
820 banner = self.banner
818 banner = self.banner
821 self.write(banner)
819 self.write(banner)
822
820
823 #-------------------------------------------------------------------------
821 #-------------------------------------------------------------------------
824 # Things related to hooks
822 # Things related to hooks
825 #-------------------------------------------------------------------------
823 #-------------------------------------------------------------------------
826
824
827 def init_hooks(self):
825 def init_hooks(self):
828 # hooks holds pointers used for user-side customizations
826 # hooks holds pointers used for user-side customizations
829 self.hooks = Struct()
827 self.hooks = Struct()
830
828
831 self.strdispatchers = {}
829 self.strdispatchers = {}
832
830
833 # Set all default hooks, defined in the IPython.hooks module.
831 # Set all default hooks, defined in the IPython.hooks module.
834 hooks = IPython.core.hooks
832 hooks = IPython.core.hooks
835 for hook_name in hooks.__all__:
833 for hook_name in hooks.__all__:
836 # default hooks have priority 100, i.e. low; user hooks should have
834 # default hooks have priority 100, i.e. low; user hooks should have
837 # 0-100 priority
835 # 0-100 priority
838 self.set_hook(hook_name,getattr(hooks,hook_name), 100, _warn_deprecated=False)
836 self.set_hook(hook_name,getattr(hooks,hook_name), 100, _warn_deprecated=False)
839
837
840 if self.display_page:
838 if self.display_page:
841 self.set_hook('show_in_pager', page.as_hook(page.display_page), 90)
839 self.set_hook('show_in_pager', page.as_hook(page.display_page), 90)
842
840
843 def set_hook(self,name,hook, priority=50, str_key=None, re_key=None,
841 def set_hook(self,name,hook, priority=50, str_key=None, re_key=None,
844 _warn_deprecated=True):
842 _warn_deprecated=True):
845 """set_hook(name,hook) -> sets an internal IPython hook.
843 """set_hook(name,hook) -> sets an internal IPython hook.
846
844
847 IPython exposes some of its internal API as user-modifiable hooks. By
845 IPython exposes some of its internal API as user-modifiable hooks. By
848 adding your function to one of these hooks, you can modify IPython's
846 adding your function to one of these hooks, you can modify IPython's
849 behavior to call at runtime your own routines."""
847 behavior to call at runtime your own routines."""
850
848
851 # At some point in the future, this should validate the hook before it
849 # At some point in the future, this should validate the hook before it
852 # accepts it. Probably at least check that the hook takes the number
850 # accepts it. Probably at least check that the hook takes the number
853 # of args it's supposed to.
851 # of args it's supposed to.
854
852
855 f = types.MethodType(hook,self)
853 f = types.MethodType(hook,self)
856
854
857 # check if the hook is for strdispatcher first
855 # check if the hook is for strdispatcher first
858 if str_key is not None:
856 if str_key is not None:
859 sdp = self.strdispatchers.get(name, StrDispatch())
857 sdp = self.strdispatchers.get(name, StrDispatch())
860 sdp.add_s(str_key, f, priority )
858 sdp.add_s(str_key, f, priority )
861 self.strdispatchers[name] = sdp
859 self.strdispatchers[name] = sdp
862 return
860 return
863 if re_key is not None:
861 if re_key is not None:
864 sdp = self.strdispatchers.get(name, StrDispatch())
862 sdp = self.strdispatchers.get(name, StrDispatch())
865 sdp.add_re(re.compile(re_key), f, priority )
863 sdp.add_re(re.compile(re_key), f, priority )
866 self.strdispatchers[name] = sdp
864 self.strdispatchers[name] = sdp
867 return
865 return
868
866
869 dp = getattr(self.hooks, name, None)
867 dp = getattr(self.hooks, name, None)
870 if name not in IPython.core.hooks.__all__:
868 if name not in IPython.core.hooks.__all__:
871 print("Warning! Hook '%s' is not one of %s" % \
869 print("Warning! Hook '%s' is not one of %s" % \
872 (name, IPython.core.hooks.__all__ ))
870 (name, IPython.core.hooks.__all__ ))
873
871
874 if _warn_deprecated and (name in IPython.core.hooks.deprecated):
872 if _warn_deprecated and (name in IPython.core.hooks.deprecated):
875 alternative = IPython.core.hooks.deprecated[name]
873 alternative = IPython.core.hooks.deprecated[name]
876 warn("Hook {} is deprecated. Use {} instead.".format(name, alternative))
874 warn("Hook {} is deprecated. Use {} instead.".format(name, alternative))
877
875
878 if not dp:
876 if not dp:
879 dp = IPython.core.hooks.CommandChainDispatcher()
877 dp = IPython.core.hooks.CommandChainDispatcher()
880
878
881 try:
879 try:
882 dp.add(f,priority)
880 dp.add(f,priority)
883 except AttributeError:
881 except AttributeError:
884 # it was not commandchain, plain old func - replace
882 # it was not commandchain, plain old func - replace
885 dp = f
883 dp = f
886
884
887 setattr(self.hooks,name, dp)
885 setattr(self.hooks,name, dp)
888
886
889 #-------------------------------------------------------------------------
887 #-------------------------------------------------------------------------
890 # Things related to events
888 # Things related to events
891 #-------------------------------------------------------------------------
889 #-------------------------------------------------------------------------
892
890
893 def init_events(self):
891 def init_events(self):
894 self.events = EventManager(self, available_events)
892 self.events = EventManager(self, available_events)
895
893
896 self.events.register("pre_execute", self._clear_warning_registry)
894 self.events.register("pre_execute", self._clear_warning_registry)
897
895
898 def register_post_execute(self, func):
896 def register_post_execute(self, func):
899 """DEPRECATED: Use ip.events.register('post_run_cell', func)
897 """DEPRECATED: Use ip.events.register('post_run_cell', func)
900
898
901 Register a function for calling after code execution.
899 Register a function for calling after code execution.
902 """
900 """
903 warn("ip.register_post_execute is deprecated, use "
901 warn("ip.register_post_execute is deprecated, use "
904 "ip.events.register('post_run_cell', func) instead.")
902 "ip.events.register('post_run_cell', func) instead.")
905 self.events.register('post_run_cell', func)
903 self.events.register('post_run_cell', func)
906
904
907 def _clear_warning_registry(self):
905 def _clear_warning_registry(self):
908 # clear the warning registry, so that different code blocks with
906 # clear the warning registry, so that different code blocks with
909 # overlapping line number ranges don't cause spurious suppression of
907 # overlapping line number ranges don't cause spurious suppression of
910 # warnings (see gh-6611 for details)
908 # warnings (see gh-6611 for details)
911 if "__warningregistry__" in self.user_global_ns:
909 if "__warningregistry__" in self.user_global_ns:
912 del self.user_global_ns["__warningregistry__"]
910 del self.user_global_ns["__warningregistry__"]
913
911
914 #-------------------------------------------------------------------------
912 #-------------------------------------------------------------------------
915 # Things related to the "main" module
913 # Things related to the "main" module
916 #-------------------------------------------------------------------------
914 #-------------------------------------------------------------------------
917
915
918 def new_main_mod(self, filename, modname):
916 def new_main_mod(self, filename, modname):
919 """Return a new 'main' module object for user code execution.
917 """Return a new 'main' module object for user code execution.
920
918
921 ``filename`` should be the path of the script which will be run in the
919 ``filename`` should be the path of the script which will be run in the
922 module. Requests with the same filename will get the same module, with
920 module. Requests with the same filename will get the same module, with
923 its namespace cleared.
921 its namespace cleared.
924
922
925 ``modname`` should be the module name - normally either '__main__' or
923 ``modname`` should be the module name - normally either '__main__' or
926 the basename of the file without the extension.
924 the basename of the file without the extension.
927
925
928 When scripts are executed via %run, we must keep a reference to their
926 When scripts are executed via %run, we must keep a reference to their
929 __main__ module around so that Python doesn't
927 __main__ module around so that Python doesn't
930 clear it, rendering references to module globals useless.
928 clear it, rendering references to module globals useless.
931
929
932 This method keeps said reference in a private dict, keyed by the
930 This method keeps said reference in a private dict, keyed by the
933 absolute path of the script. This way, for multiple executions of the
931 absolute path of the script. This way, for multiple executions of the
934 same script we only keep one copy of the namespace (the last one),
932 same script we only keep one copy of the namespace (the last one),
935 thus preventing memory leaks from old references while allowing the
933 thus preventing memory leaks from old references while allowing the
936 objects from the last execution to be accessible.
934 objects from the last execution to be accessible.
937 """
935 """
938 filename = os.path.abspath(filename)
936 filename = os.path.abspath(filename)
939 try:
937 try:
940 main_mod = self._main_mod_cache[filename]
938 main_mod = self._main_mod_cache[filename]
941 except KeyError:
939 except KeyError:
942 main_mod = self._main_mod_cache[filename] = types.ModuleType(
940 main_mod = self._main_mod_cache[filename] = types.ModuleType(
943 py3compat.cast_bytes_py2(modname),
941 py3compat.cast_bytes_py2(modname),
944 doc="Module created for script run in IPython")
942 doc="Module created for script run in IPython")
945 else:
943 else:
946 main_mod.__dict__.clear()
944 main_mod.__dict__.clear()
947 main_mod.__name__ = modname
945 main_mod.__name__ = modname
948
946
949 main_mod.__file__ = filename
947 main_mod.__file__ = filename
950 # It seems pydoc (and perhaps others) needs any module instance to
948 # It seems pydoc (and perhaps others) needs any module instance to
951 # implement a __nonzero__ method
949 # implement a __nonzero__ method
952 main_mod.__nonzero__ = lambda : True
950 main_mod.__nonzero__ = lambda : True
953
951
954 return main_mod
952 return main_mod
955
953
956 def clear_main_mod_cache(self):
954 def clear_main_mod_cache(self):
957 """Clear the cache of main modules.
955 """Clear the cache of main modules.
958
956
959 Mainly for use by utilities like %reset.
957 Mainly for use by utilities like %reset.
960
958
961 Examples
959 Examples
962 --------
960 --------
963
961
964 In [15]: import IPython
962 In [15]: import IPython
965
963
966 In [16]: m = _ip.new_main_mod(IPython.__file__, 'IPython')
964 In [16]: m = _ip.new_main_mod(IPython.__file__, 'IPython')
967
965
968 In [17]: len(_ip._main_mod_cache) > 0
966 In [17]: len(_ip._main_mod_cache) > 0
969 Out[17]: True
967 Out[17]: True
970
968
971 In [18]: _ip.clear_main_mod_cache()
969 In [18]: _ip.clear_main_mod_cache()
972
970
973 In [19]: len(_ip._main_mod_cache) == 0
971 In [19]: len(_ip._main_mod_cache) == 0
974 Out[19]: True
972 Out[19]: True
975 """
973 """
976 self._main_mod_cache.clear()
974 self._main_mod_cache.clear()
977
975
978 #-------------------------------------------------------------------------
976 #-------------------------------------------------------------------------
979 # Things related to debugging
977 # Things related to debugging
980 #-------------------------------------------------------------------------
978 #-------------------------------------------------------------------------
981
979
982 def init_pdb(self):
980 def init_pdb(self):
983 # Set calling of pdb on exceptions
981 # Set calling of pdb on exceptions
984 # self.call_pdb is a property
982 # self.call_pdb is a property
985 self.call_pdb = self.pdb
983 self.call_pdb = self.pdb
986
984
987 def _get_call_pdb(self):
985 def _get_call_pdb(self):
988 return self._call_pdb
986 return self._call_pdb
989
987
990 def _set_call_pdb(self,val):
988 def _set_call_pdb(self,val):
991
989
992 if val not in (0,1,False,True):
990 if val not in (0,1,False,True):
993 raise ValueError('new call_pdb value must be boolean')
991 raise ValueError('new call_pdb value must be boolean')
994
992
995 # store value in instance
993 # store value in instance
996 self._call_pdb = val
994 self._call_pdb = val
997
995
998 # notify the actual exception handlers
996 # notify the actual exception handlers
999 self.InteractiveTB.call_pdb = val
997 self.InteractiveTB.call_pdb = val
1000
998
1001 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
999 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
1002 'Control auto-activation of pdb at exceptions')
1000 'Control auto-activation of pdb at exceptions')
1003
1001
1004 def debugger(self,force=False):
1002 def debugger(self,force=False):
1005 """Call the pydb/pdb debugger.
1003 """Call the pydb/pdb debugger.
1006
1004
1007 Keywords:
1005 Keywords:
1008
1006
1009 - force(False): by default, this routine checks the instance call_pdb
1007 - force(False): by default, this routine checks the instance call_pdb
1010 flag and does not actually invoke the debugger if the flag is false.
1008 flag and does not actually invoke the debugger if the flag is false.
1011 The 'force' option forces the debugger to activate even if the flag
1009 The 'force' option forces the debugger to activate even if the flag
1012 is false.
1010 is false.
1013 """
1011 """
1014
1012
1015 if not (force or self.call_pdb):
1013 if not (force or self.call_pdb):
1016 return
1014 return
1017
1015
1018 if not hasattr(sys,'last_traceback'):
1016 if not hasattr(sys,'last_traceback'):
1019 error('No traceback has been produced, nothing to debug.')
1017 error('No traceback has been produced, nothing to debug.')
1020 return
1018 return
1021
1019
1022 # use pydb if available
1020 # use pydb if available
1023 if debugger.has_pydb:
1021 if debugger.has_pydb:
1024 from pydb import pm
1022 from pydb import pm
1025 else:
1023 else:
1026 # fallback to our internal debugger
1024 # fallback to our internal debugger
1027 pm = lambda : self.InteractiveTB.debugger(force=True)
1025 pm = lambda : self.InteractiveTB.debugger(force=True)
1028
1026
1029 with self.readline_no_record:
1027 with self.readline_no_record:
1030 pm()
1028 pm()
1031
1029
1032 #-------------------------------------------------------------------------
1030 #-------------------------------------------------------------------------
1033 # Things related to IPython's various namespaces
1031 # Things related to IPython's various namespaces
1034 #-------------------------------------------------------------------------
1032 #-------------------------------------------------------------------------
1035 default_user_namespaces = True
1033 default_user_namespaces = True
1036
1034
1037 def init_create_namespaces(self, user_module=None, user_ns=None):
1035 def init_create_namespaces(self, user_module=None, user_ns=None):
1038 # Create the namespace where the user will operate. user_ns is
1036 # Create the namespace where the user will operate. user_ns is
1039 # normally the only one used, and it is passed to the exec calls as
1037 # normally the only one used, and it is passed to the exec calls as
1040 # the locals argument. But we do carry a user_global_ns namespace
1038 # the locals argument. But we do carry a user_global_ns namespace
1041 # given as the exec 'globals' argument, This is useful in embedding
1039 # given as the exec 'globals' argument, This is useful in embedding
1042 # situations where the ipython shell opens in a context where the
1040 # situations where the ipython shell opens in a context where the
1043 # distinction between locals and globals is meaningful. For
1041 # distinction between locals and globals is meaningful. For
1044 # non-embedded contexts, it is just the same object as the user_ns dict.
1042 # non-embedded contexts, it is just the same object as the user_ns dict.
1045
1043
1046 # FIXME. For some strange reason, __builtins__ is showing up at user
1044 # FIXME. For some strange reason, __builtins__ is showing up at user
1047 # level as a dict instead of a module. This is a manual fix, but I
1045 # level as a dict instead of a module. This is a manual fix, but I
1048 # should really track down where the problem is coming from. Alex
1046 # should really track down where the problem is coming from. Alex
1049 # Schmolck reported this problem first.
1047 # Schmolck reported this problem first.
1050
1048
1051 # A useful post by Alex Martelli on this topic:
1049 # A useful post by Alex Martelli on this topic:
1052 # Re: inconsistent value from __builtins__
1050 # Re: inconsistent value from __builtins__
1053 # Von: Alex Martelli <aleaxit@yahoo.com>
1051 # Von: Alex Martelli <aleaxit@yahoo.com>
1054 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
1052 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
1055 # Gruppen: comp.lang.python
1053 # Gruppen: comp.lang.python
1056
1054
1057 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
1055 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
1058 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
1056 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
1059 # > <type 'dict'>
1057 # > <type 'dict'>
1060 # > >>> print type(__builtins__)
1058 # > >>> print type(__builtins__)
1061 # > <type 'module'>
1059 # > <type 'module'>
1062 # > Is this difference in return value intentional?
1060 # > Is this difference in return value intentional?
1063
1061
1064 # Well, it's documented that '__builtins__' can be either a dictionary
1062 # Well, it's documented that '__builtins__' can be either a dictionary
1065 # or a module, and it's been that way for a long time. Whether it's
1063 # or a module, and it's been that way for a long time. Whether it's
1066 # intentional (or sensible), I don't know. In any case, the idea is
1064 # intentional (or sensible), I don't know. In any case, the idea is
1067 # that if you need to access the built-in namespace directly, you
1065 # that if you need to access the built-in namespace directly, you
1068 # should start with "import __builtin__" (note, no 's') which will
1066 # should start with "import __builtin__" (note, no 's') which will
1069 # definitely give you a module. Yeah, it's somewhat confusing:-(.
1067 # definitely give you a module. Yeah, it's somewhat confusing:-(.
1070
1068
1071 # These routines return a properly built module and dict as needed by
1069 # These routines return a properly built module and dict as needed by
1072 # the rest of the code, and can also be used by extension writers to
1070 # the rest of the code, and can also be used by extension writers to
1073 # generate properly initialized namespaces.
1071 # generate properly initialized namespaces.
1074 if (user_ns is not None) or (user_module is not None):
1072 if (user_ns is not None) or (user_module is not None):
1075 self.default_user_namespaces = False
1073 self.default_user_namespaces = False
1076 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
1074 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
1077
1075
1078 # A record of hidden variables we have added to the user namespace, so
1076 # A record of hidden variables we have added to the user namespace, so
1079 # we can list later only variables defined in actual interactive use.
1077 # we can list later only variables defined in actual interactive use.
1080 self.user_ns_hidden = {}
1078 self.user_ns_hidden = {}
1081
1079
1082 # Now that FakeModule produces a real module, we've run into a nasty
1080 # Now that FakeModule produces a real module, we've run into a nasty
1083 # problem: after script execution (via %run), the module where the user
1081 # problem: after script execution (via %run), the module where the user
1084 # code ran is deleted. Now that this object is a true module (needed
1082 # code ran is deleted. Now that this object is a true module (needed
1085 # so docetst and other tools work correctly), the Python module
1083 # so docetst and other tools work correctly), the Python module
1086 # teardown mechanism runs over it, and sets to None every variable
1084 # teardown mechanism runs over it, and sets to None every variable
1087 # present in that module. Top-level references to objects from the
1085 # present in that module. Top-level references to objects from the
1088 # script survive, because the user_ns is updated with them. However,
1086 # script survive, because the user_ns is updated with them. However,
1089 # calling functions defined in the script that use other things from
1087 # calling functions defined in the script that use other things from
1090 # the script will fail, because the function's closure had references
1088 # the script will fail, because the function's closure had references
1091 # to the original objects, which are now all None. So we must protect
1089 # to the original objects, which are now all None. So we must protect
1092 # these modules from deletion by keeping a cache.
1090 # these modules from deletion by keeping a cache.
1093 #
1091 #
1094 # To avoid keeping stale modules around (we only need the one from the
1092 # To avoid keeping stale modules around (we only need the one from the
1095 # last run), we use a dict keyed with the full path to the script, so
1093 # last run), we use a dict keyed with the full path to the script, so
1096 # only the last version of the module is held in the cache. Note,
1094 # only the last version of the module is held in the cache. Note,
1097 # however, that we must cache the module *namespace contents* (their
1095 # however, that we must cache the module *namespace contents* (their
1098 # __dict__). Because if we try to cache the actual modules, old ones
1096 # __dict__). Because if we try to cache the actual modules, old ones
1099 # (uncached) could be destroyed while still holding references (such as
1097 # (uncached) could be destroyed while still holding references (such as
1100 # those held by GUI objects that tend to be long-lived)>
1098 # those held by GUI objects that tend to be long-lived)>
1101 #
1099 #
1102 # The %reset command will flush this cache. See the cache_main_mod()
1100 # The %reset command will flush this cache. See the cache_main_mod()
1103 # and clear_main_mod_cache() methods for details on use.
1101 # and clear_main_mod_cache() methods for details on use.
1104
1102
1105 # This is the cache used for 'main' namespaces
1103 # This is the cache used for 'main' namespaces
1106 self._main_mod_cache = {}
1104 self._main_mod_cache = {}
1107
1105
1108 # A table holding all the namespaces IPython deals with, so that
1106 # A table holding all the namespaces IPython deals with, so that
1109 # introspection facilities can search easily.
1107 # introspection facilities can search easily.
1110 self.ns_table = {'user_global':self.user_module.__dict__,
1108 self.ns_table = {'user_global':self.user_module.__dict__,
1111 'user_local':self.user_ns,
1109 'user_local':self.user_ns,
1112 'builtin':builtin_mod.__dict__
1110 'builtin':builtin_mod.__dict__
1113 }
1111 }
1114
1112
1115 @property
1113 @property
1116 def user_global_ns(self):
1114 def user_global_ns(self):
1117 return self.user_module.__dict__
1115 return self.user_module.__dict__
1118
1116
1119 def prepare_user_module(self, user_module=None, user_ns=None):
1117 def prepare_user_module(self, user_module=None, user_ns=None):
1120 """Prepare the module and namespace in which user code will be run.
1118 """Prepare the module and namespace in which user code will be run.
1121
1119
1122 When IPython is started normally, both parameters are None: a new module
1120 When IPython is started normally, both parameters are None: a new module
1123 is created automatically, and its __dict__ used as the namespace.
1121 is created automatically, and its __dict__ used as the namespace.
1124
1122
1125 If only user_module is provided, its __dict__ is used as the namespace.
1123 If only user_module is provided, its __dict__ is used as the namespace.
1126 If only user_ns is provided, a dummy module is created, and user_ns
1124 If only user_ns is provided, a dummy module is created, and user_ns
1127 becomes the global namespace. If both are provided (as they may be
1125 becomes the global namespace. If both are provided (as they may be
1128 when embedding), user_ns is the local namespace, and user_module
1126 when embedding), user_ns is the local namespace, and user_module
1129 provides the global namespace.
1127 provides the global namespace.
1130
1128
1131 Parameters
1129 Parameters
1132 ----------
1130 ----------
1133 user_module : module, optional
1131 user_module : module, optional
1134 The current user module in which IPython is being run. If None,
1132 The current user module in which IPython is being run. If None,
1135 a clean module will be created.
1133 a clean module will be created.
1136 user_ns : dict, optional
1134 user_ns : dict, optional
1137 A namespace in which to run interactive commands.
1135 A namespace in which to run interactive commands.
1138
1136
1139 Returns
1137 Returns
1140 -------
1138 -------
1141 A tuple of user_module and user_ns, each properly initialised.
1139 A tuple of user_module and user_ns, each properly initialised.
1142 """
1140 """
1143 if user_module is None and user_ns is not None:
1141 if user_module is None and user_ns is not None:
1144 user_ns.setdefault("__name__", "__main__")
1142 user_ns.setdefault("__name__", "__main__")
1145 user_module = DummyMod()
1143 user_module = DummyMod()
1146 user_module.__dict__ = user_ns
1144 user_module.__dict__ = user_ns
1147
1145
1148 if user_module is None:
1146 if user_module is None:
1149 user_module = types.ModuleType("__main__",
1147 user_module = types.ModuleType("__main__",
1150 doc="Automatically created module for IPython interactive environment")
1148 doc="Automatically created module for IPython interactive environment")
1151
1149
1152 # We must ensure that __builtin__ (without the final 's') is always
1150 # We must ensure that __builtin__ (without the final 's') is always
1153 # available and pointing to the __builtin__ *module*. For more details:
1151 # available and pointing to the __builtin__ *module*. For more details:
1154 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1152 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1155 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1153 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1156 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1154 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1157
1155
1158 if user_ns is None:
1156 if user_ns is None:
1159 user_ns = user_module.__dict__
1157 user_ns = user_module.__dict__
1160
1158
1161 return user_module, user_ns
1159 return user_module, user_ns
1162
1160
1163 def init_sys_modules(self):
1161 def init_sys_modules(self):
1164 # We need to insert into sys.modules something that looks like a
1162 # We need to insert into sys.modules something that looks like a
1165 # module but which accesses the IPython namespace, for shelve and
1163 # module but which accesses the IPython namespace, for shelve and
1166 # pickle to work interactively. Normally they rely on getting
1164 # pickle to work interactively. Normally they rely on getting
1167 # everything out of __main__, but for embedding purposes each IPython
1165 # everything out of __main__, but for embedding purposes each IPython
1168 # instance has its own private namespace, so we can't go shoving
1166 # instance has its own private namespace, so we can't go shoving
1169 # everything into __main__.
1167 # everything into __main__.
1170
1168
1171 # note, however, that we should only do this for non-embedded
1169 # note, however, that we should only do this for non-embedded
1172 # ipythons, which really mimic the __main__.__dict__ with their own
1170 # ipythons, which really mimic the __main__.__dict__ with their own
1173 # namespace. Embedded instances, on the other hand, should not do
1171 # namespace. Embedded instances, on the other hand, should not do
1174 # this because they need to manage the user local/global namespaces
1172 # this because they need to manage the user local/global namespaces
1175 # only, but they live within a 'normal' __main__ (meaning, they
1173 # only, but they live within a 'normal' __main__ (meaning, they
1176 # shouldn't overtake the execution environment of the script they're
1174 # shouldn't overtake the execution environment of the script they're
1177 # embedded in).
1175 # embedded in).
1178
1176
1179 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1177 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1180 main_name = self.user_module.__name__
1178 main_name = self.user_module.__name__
1181 sys.modules[main_name] = self.user_module
1179 sys.modules[main_name] = self.user_module
1182
1180
1183 def init_user_ns(self):
1181 def init_user_ns(self):
1184 """Initialize all user-visible namespaces to their minimum defaults.
1182 """Initialize all user-visible namespaces to their minimum defaults.
1185
1183
1186 Certain history lists are also initialized here, as they effectively
1184 Certain history lists are also initialized here, as they effectively
1187 act as user namespaces.
1185 act as user namespaces.
1188
1186
1189 Notes
1187 Notes
1190 -----
1188 -----
1191 All data structures here are only filled in, they are NOT reset by this
1189 All data structures here are only filled in, they are NOT reset by this
1192 method. If they were not empty before, data will simply be added to
1190 method. If they were not empty before, data will simply be added to
1193 therm.
1191 therm.
1194 """
1192 """
1195 # This function works in two parts: first we put a few things in
1193 # This function works in two parts: first we put a few things in
1196 # user_ns, and we sync that contents into user_ns_hidden so that these
1194 # user_ns, and we sync that contents into user_ns_hidden so that these
1197 # initial variables aren't shown by %who. After the sync, we add the
1195 # initial variables aren't shown by %who. After the sync, we add the
1198 # rest of what we *do* want the user to see with %who even on a new
1196 # rest of what we *do* want the user to see with %who even on a new
1199 # session (probably nothing, so theye really only see their own stuff)
1197 # session (probably nothing, so theye really only see their own stuff)
1200
1198
1201 # The user dict must *always* have a __builtin__ reference to the
1199 # The user dict must *always* have a __builtin__ reference to the
1202 # Python standard __builtin__ namespace, which must be imported.
1200 # Python standard __builtin__ namespace, which must be imported.
1203 # This is so that certain operations in prompt evaluation can be
1201 # This is so that certain operations in prompt evaluation can be
1204 # reliably executed with builtins. Note that we can NOT use
1202 # reliably executed with builtins. Note that we can NOT use
1205 # __builtins__ (note the 's'), because that can either be a dict or a
1203 # __builtins__ (note the 's'), because that can either be a dict or a
1206 # module, and can even mutate at runtime, depending on the context
1204 # module, and can even mutate at runtime, depending on the context
1207 # (Python makes no guarantees on it). In contrast, __builtin__ is
1205 # (Python makes no guarantees on it). In contrast, __builtin__ is
1208 # always a module object, though it must be explicitly imported.
1206 # always a module object, though it must be explicitly imported.
1209
1207
1210 # For more details:
1208 # For more details:
1211 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1209 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1212 ns = dict()
1210 ns = dict()
1213
1211
1214 # make global variables for user access to the histories
1212 # make global variables for user access to the histories
1215 ns['_ih'] = self.history_manager.input_hist_parsed
1213 ns['_ih'] = self.history_manager.input_hist_parsed
1216 ns['_oh'] = self.history_manager.output_hist
1214 ns['_oh'] = self.history_manager.output_hist
1217 ns['_dh'] = self.history_manager.dir_hist
1215 ns['_dh'] = self.history_manager.dir_hist
1218
1216
1219 ns['_sh'] = shadowns
1217 ns['_sh'] = shadowns
1220
1218
1221 # user aliases to input and output histories. These shouldn't show up
1219 # user aliases to input and output histories. These shouldn't show up
1222 # in %who, as they can have very large reprs.
1220 # in %who, as they can have very large reprs.
1223 ns['In'] = self.history_manager.input_hist_parsed
1221 ns['In'] = self.history_manager.input_hist_parsed
1224 ns['Out'] = self.history_manager.output_hist
1222 ns['Out'] = self.history_manager.output_hist
1225
1223
1226 # Store myself as the public api!!!
1224 # Store myself as the public api!!!
1227 ns['get_ipython'] = self.get_ipython
1225 ns['get_ipython'] = self.get_ipython
1228
1226
1229 ns['exit'] = self.exiter
1227 ns['exit'] = self.exiter
1230 ns['quit'] = self.exiter
1228 ns['quit'] = self.exiter
1231
1229
1232 # Sync what we've added so far to user_ns_hidden so these aren't seen
1230 # Sync what we've added so far to user_ns_hidden so these aren't seen
1233 # by %who
1231 # by %who
1234 self.user_ns_hidden.update(ns)
1232 self.user_ns_hidden.update(ns)
1235
1233
1236 # Anything put into ns now would show up in %who. Think twice before
1234 # Anything put into ns now would show up in %who. Think twice before
1237 # putting anything here, as we really want %who to show the user their
1235 # putting anything here, as we really want %who to show the user their
1238 # stuff, not our variables.
1236 # stuff, not our variables.
1239
1237
1240 # Finally, update the real user's namespace
1238 # Finally, update the real user's namespace
1241 self.user_ns.update(ns)
1239 self.user_ns.update(ns)
1242
1240
1243 @property
1241 @property
1244 def all_ns_refs(self):
1242 def all_ns_refs(self):
1245 """Get a list of references to all the namespace dictionaries in which
1243 """Get a list of references to all the namespace dictionaries in which
1246 IPython might store a user-created object.
1244 IPython might store a user-created object.
1247
1245
1248 Note that this does not include the displayhook, which also caches
1246 Note that this does not include the displayhook, which also caches
1249 objects from the output."""
1247 objects from the output."""
1250 return [self.user_ns, self.user_global_ns, self.user_ns_hidden] + \
1248 return [self.user_ns, self.user_global_ns, self.user_ns_hidden] + \
1251 [m.__dict__ for m in self._main_mod_cache.values()]
1249 [m.__dict__ for m in self._main_mod_cache.values()]
1252
1250
1253 def reset(self, new_session=True):
1251 def reset(self, new_session=True):
1254 """Clear all internal namespaces, and attempt to release references to
1252 """Clear all internal namespaces, and attempt to release references to
1255 user objects.
1253 user objects.
1256
1254
1257 If new_session is True, a new history session will be opened.
1255 If new_session is True, a new history session will be opened.
1258 """
1256 """
1259 # Clear histories
1257 # Clear histories
1260 self.history_manager.reset(new_session)
1258 self.history_manager.reset(new_session)
1261 # Reset counter used to index all histories
1259 # Reset counter used to index all histories
1262 if new_session:
1260 if new_session:
1263 self.execution_count = 1
1261 self.execution_count = 1
1264
1262
1265 # Flush cached output items
1263 # Flush cached output items
1266 if self.displayhook.do_full_cache:
1264 if self.displayhook.do_full_cache:
1267 self.displayhook.flush()
1265 self.displayhook.flush()
1268
1266
1269 # The main execution namespaces must be cleared very carefully,
1267 # The main execution namespaces must be cleared very carefully,
1270 # skipping the deletion of the builtin-related keys, because doing so
1268 # skipping the deletion of the builtin-related keys, because doing so
1271 # would cause errors in many object's __del__ methods.
1269 # would cause errors in many object's __del__ methods.
1272 if self.user_ns is not self.user_global_ns:
1270 if self.user_ns is not self.user_global_ns:
1273 self.user_ns.clear()
1271 self.user_ns.clear()
1274 ns = self.user_global_ns
1272 ns = self.user_global_ns
1275 drop_keys = set(ns.keys())
1273 drop_keys = set(ns.keys())
1276 drop_keys.discard('__builtin__')
1274 drop_keys.discard('__builtin__')
1277 drop_keys.discard('__builtins__')
1275 drop_keys.discard('__builtins__')
1278 drop_keys.discard('__name__')
1276 drop_keys.discard('__name__')
1279 for k in drop_keys:
1277 for k in drop_keys:
1280 del ns[k]
1278 del ns[k]
1281
1279
1282 self.user_ns_hidden.clear()
1280 self.user_ns_hidden.clear()
1283
1281
1284 # Restore the user namespaces to minimal usability
1282 # Restore the user namespaces to minimal usability
1285 self.init_user_ns()
1283 self.init_user_ns()
1286
1284
1287 # Restore the default and user aliases
1285 # Restore the default and user aliases
1288 self.alias_manager.clear_aliases()
1286 self.alias_manager.clear_aliases()
1289 self.alias_manager.init_aliases()
1287 self.alias_manager.init_aliases()
1290
1288
1291 # Flush the private list of module references kept for script
1289 # Flush the private list of module references kept for script
1292 # execution protection
1290 # execution protection
1293 self.clear_main_mod_cache()
1291 self.clear_main_mod_cache()
1294
1292
1295 def del_var(self, varname, by_name=False):
1293 def del_var(self, varname, by_name=False):
1296 """Delete a variable from the various namespaces, so that, as
1294 """Delete a variable from the various namespaces, so that, as
1297 far as possible, we're not keeping any hidden references to it.
1295 far as possible, we're not keeping any hidden references to it.
1298
1296
1299 Parameters
1297 Parameters
1300 ----------
1298 ----------
1301 varname : str
1299 varname : str
1302 The name of the variable to delete.
1300 The name of the variable to delete.
1303 by_name : bool
1301 by_name : bool
1304 If True, delete variables with the given name in each
1302 If True, delete variables with the given name in each
1305 namespace. If False (default), find the variable in the user
1303 namespace. If False (default), find the variable in the user
1306 namespace, and delete references to it.
1304 namespace, and delete references to it.
1307 """
1305 """
1308 if varname in ('__builtin__', '__builtins__'):
1306 if varname in ('__builtin__', '__builtins__'):
1309 raise ValueError("Refusing to delete %s" % varname)
1307 raise ValueError("Refusing to delete %s" % varname)
1310
1308
1311 ns_refs = self.all_ns_refs
1309 ns_refs = self.all_ns_refs
1312
1310
1313 if by_name: # Delete by name
1311 if by_name: # Delete by name
1314 for ns in ns_refs:
1312 for ns in ns_refs:
1315 try:
1313 try:
1316 del ns[varname]
1314 del ns[varname]
1317 except KeyError:
1315 except KeyError:
1318 pass
1316 pass
1319 else: # Delete by object
1317 else: # Delete by object
1320 try:
1318 try:
1321 obj = self.user_ns[varname]
1319 obj = self.user_ns[varname]
1322 except KeyError:
1320 except KeyError:
1323 raise NameError("name '%s' is not defined" % varname)
1321 raise NameError("name '%s' is not defined" % varname)
1324 # Also check in output history
1322 # Also check in output history
1325 ns_refs.append(self.history_manager.output_hist)
1323 ns_refs.append(self.history_manager.output_hist)
1326 for ns in ns_refs:
1324 for ns in ns_refs:
1327 to_delete = [n for n, o in iteritems(ns) if o is obj]
1325 to_delete = [n for n, o in iteritems(ns) if o is obj]
1328 for name in to_delete:
1326 for name in to_delete:
1329 del ns[name]
1327 del ns[name]
1330
1328
1331 # displayhook keeps extra references, but not in a dictionary
1329 # displayhook keeps extra references, but not in a dictionary
1332 for name in ('_', '__', '___'):
1330 for name in ('_', '__', '___'):
1333 if getattr(self.displayhook, name) is obj:
1331 if getattr(self.displayhook, name) is obj:
1334 setattr(self.displayhook, name, None)
1332 setattr(self.displayhook, name, None)
1335
1333
1336 def reset_selective(self, regex=None):
1334 def reset_selective(self, regex=None):
1337 """Clear selective variables from internal namespaces based on a
1335 """Clear selective variables from internal namespaces based on a
1338 specified regular expression.
1336 specified regular expression.
1339
1337
1340 Parameters
1338 Parameters
1341 ----------
1339 ----------
1342 regex : string or compiled pattern, optional
1340 regex : string or compiled pattern, optional
1343 A regular expression pattern that will be used in searching
1341 A regular expression pattern that will be used in searching
1344 variable names in the users namespaces.
1342 variable names in the users namespaces.
1345 """
1343 """
1346 if regex is not None:
1344 if regex is not None:
1347 try:
1345 try:
1348 m = re.compile(regex)
1346 m = re.compile(regex)
1349 except TypeError:
1347 except TypeError:
1350 raise TypeError('regex must be a string or compiled pattern')
1348 raise TypeError('regex must be a string or compiled pattern')
1351 # Search for keys in each namespace that match the given regex
1349 # Search for keys in each namespace that match the given regex
1352 # If a match is found, delete the key/value pair.
1350 # If a match is found, delete the key/value pair.
1353 for ns in self.all_ns_refs:
1351 for ns in self.all_ns_refs:
1354 for var in ns:
1352 for var in ns:
1355 if m.search(var):
1353 if m.search(var):
1356 del ns[var]
1354 del ns[var]
1357
1355
1358 def push(self, variables, interactive=True):
1356 def push(self, variables, interactive=True):
1359 """Inject a group of variables into the IPython user namespace.
1357 """Inject a group of variables into the IPython user namespace.
1360
1358
1361 Parameters
1359 Parameters
1362 ----------
1360 ----------
1363 variables : dict, str or list/tuple of str
1361 variables : dict, str or list/tuple of str
1364 The variables to inject into the user's namespace. If a dict, a
1362 The variables to inject into the user's namespace. If a dict, a
1365 simple update is done. If a str, the string is assumed to have
1363 simple update is done. If a str, the string is assumed to have
1366 variable names separated by spaces. A list/tuple of str can also
1364 variable names separated by spaces. A list/tuple of str can also
1367 be used to give the variable names. If just the variable names are
1365 be used to give the variable names. If just the variable names are
1368 give (list/tuple/str) then the variable values looked up in the
1366 give (list/tuple/str) then the variable values looked up in the
1369 callers frame.
1367 callers frame.
1370 interactive : bool
1368 interactive : bool
1371 If True (default), the variables will be listed with the ``who``
1369 If True (default), the variables will be listed with the ``who``
1372 magic.
1370 magic.
1373 """
1371 """
1374 vdict = None
1372 vdict = None
1375
1373
1376 # We need a dict of name/value pairs to do namespace updates.
1374 # We need a dict of name/value pairs to do namespace updates.
1377 if isinstance(variables, dict):
1375 if isinstance(variables, dict):
1378 vdict = variables
1376 vdict = variables
1379 elif isinstance(variables, string_types+(list, tuple)):
1377 elif isinstance(variables, string_types+(list, tuple)):
1380 if isinstance(variables, string_types):
1378 if isinstance(variables, string_types):
1381 vlist = variables.split()
1379 vlist = variables.split()
1382 else:
1380 else:
1383 vlist = variables
1381 vlist = variables
1384 vdict = {}
1382 vdict = {}
1385 cf = sys._getframe(1)
1383 cf = sys._getframe(1)
1386 for name in vlist:
1384 for name in vlist:
1387 try:
1385 try:
1388 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1386 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1389 except:
1387 except:
1390 print('Could not get variable %s from %s' %
1388 print('Could not get variable %s from %s' %
1391 (name,cf.f_code.co_name))
1389 (name,cf.f_code.co_name))
1392 else:
1390 else:
1393 raise ValueError('variables must be a dict/str/list/tuple')
1391 raise ValueError('variables must be a dict/str/list/tuple')
1394
1392
1395 # Propagate variables to user namespace
1393 # Propagate variables to user namespace
1396 self.user_ns.update(vdict)
1394 self.user_ns.update(vdict)
1397
1395
1398 # And configure interactive visibility
1396 # And configure interactive visibility
1399 user_ns_hidden = self.user_ns_hidden
1397 user_ns_hidden = self.user_ns_hidden
1400 if interactive:
1398 if interactive:
1401 for name in vdict:
1399 for name in vdict:
1402 user_ns_hidden.pop(name, None)
1400 user_ns_hidden.pop(name, None)
1403 else:
1401 else:
1404 user_ns_hidden.update(vdict)
1402 user_ns_hidden.update(vdict)
1405
1403
1406 def drop_by_id(self, variables):
1404 def drop_by_id(self, variables):
1407 """Remove a dict of variables from the user namespace, if they are the
1405 """Remove a dict of variables from the user namespace, if they are the
1408 same as the values in the dictionary.
1406 same as the values in the dictionary.
1409
1407
1410 This is intended for use by extensions: variables that they've added can
1408 This is intended for use by extensions: variables that they've added can
1411 be taken back out if they are unloaded, without removing any that the
1409 be taken back out if they are unloaded, without removing any that the
1412 user has overwritten.
1410 user has overwritten.
1413
1411
1414 Parameters
1412 Parameters
1415 ----------
1413 ----------
1416 variables : dict
1414 variables : dict
1417 A dictionary mapping object names (as strings) to the objects.
1415 A dictionary mapping object names (as strings) to the objects.
1418 """
1416 """
1419 for name, obj in iteritems(variables):
1417 for name, obj in iteritems(variables):
1420 if name in self.user_ns and self.user_ns[name] is obj:
1418 if name in self.user_ns and self.user_ns[name] is obj:
1421 del self.user_ns[name]
1419 del self.user_ns[name]
1422 self.user_ns_hidden.pop(name, None)
1420 self.user_ns_hidden.pop(name, None)
1423
1421
1424 #-------------------------------------------------------------------------
1422 #-------------------------------------------------------------------------
1425 # Things related to object introspection
1423 # Things related to object introspection
1426 #-------------------------------------------------------------------------
1424 #-------------------------------------------------------------------------
1427
1425
1428 def _ofind(self, oname, namespaces=None):
1426 def _ofind(self, oname, namespaces=None):
1429 """Find an object in the available namespaces.
1427 """Find an object in the available namespaces.
1430
1428
1431 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1429 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1432
1430
1433 Has special code to detect magic functions.
1431 Has special code to detect magic functions.
1434 """
1432 """
1435 oname = oname.strip()
1433 oname = oname.strip()
1436 #print '1- oname: <%r>' % oname # dbg
1434 #print '1- oname: <%r>' % oname # dbg
1437 if not oname.startswith(ESC_MAGIC) and \
1435 if not oname.startswith(ESC_MAGIC) and \
1438 not oname.startswith(ESC_MAGIC2) and \
1436 not oname.startswith(ESC_MAGIC2) and \
1439 not py3compat.isidentifier(oname, dotted=True):
1437 not py3compat.isidentifier(oname, dotted=True):
1440 return dict(found=False)
1438 return dict(found=False)
1441
1439
1442 alias_ns = None
1440 alias_ns = None
1443 if namespaces is None:
1441 if namespaces is None:
1444 # Namespaces to search in:
1442 # Namespaces to search in:
1445 # Put them in a list. The order is important so that we
1443 # Put them in a list. The order is important so that we
1446 # find things in the same order that Python finds them.
1444 # find things in the same order that Python finds them.
1447 namespaces = [ ('Interactive', self.user_ns),
1445 namespaces = [ ('Interactive', self.user_ns),
1448 ('Interactive (global)', self.user_global_ns),
1446 ('Interactive (global)', self.user_global_ns),
1449 ('Python builtin', builtin_mod.__dict__),
1447 ('Python builtin', builtin_mod.__dict__),
1450 ]
1448 ]
1451
1449
1452 # initialize results to 'null'
1450 # initialize results to 'null'
1453 found = False; obj = None; ospace = None; ds = None;
1451 found = False; obj = None; ospace = None; ds = None;
1454 ismagic = False; isalias = False; parent = None
1452 ismagic = False; isalias = False; parent = None
1455
1453
1456 # We need to special-case 'print', which as of python2.6 registers as a
1454 # We need to special-case 'print', which as of python2.6 registers as a
1457 # function but should only be treated as one if print_function was
1455 # function but should only be treated as one if print_function was
1458 # loaded with a future import. In this case, just bail.
1456 # loaded with a future import. In this case, just bail.
1459 if (oname == 'print' and not py3compat.PY3 and not \
1457 if (oname == 'print' and not py3compat.PY3 and not \
1460 (self.compile.compiler_flags & __future__.CO_FUTURE_PRINT_FUNCTION)):
1458 (self.compile.compiler_flags & __future__.CO_FUTURE_PRINT_FUNCTION)):
1461 return {'found':found, 'obj':obj, 'namespace':ospace,
1459 return {'found':found, 'obj':obj, 'namespace':ospace,
1462 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1460 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1463
1461
1464 # Look for the given name by splitting it in parts. If the head is
1462 # Look for the given name by splitting it in parts. If the head is
1465 # found, then we look for all the remaining parts as members, and only
1463 # found, then we look for all the remaining parts as members, and only
1466 # declare success if we can find them all.
1464 # declare success if we can find them all.
1467 oname_parts = oname.split('.')
1465 oname_parts = oname.split('.')
1468 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1466 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1469 for nsname,ns in namespaces:
1467 for nsname,ns in namespaces:
1470 try:
1468 try:
1471 obj = ns[oname_head]
1469 obj = ns[oname_head]
1472 except KeyError:
1470 except KeyError:
1473 continue
1471 continue
1474 else:
1472 else:
1475 #print 'oname_rest:', oname_rest # dbg
1473 #print 'oname_rest:', oname_rest # dbg
1476 for idx, part in enumerate(oname_rest):
1474 for idx, part in enumerate(oname_rest):
1477 try:
1475 try:
1478 parent = obj
1476 parent = obj
1479 # The last part is looked up in a special way to avoid
1477 # The last part is looked up in a special way to avoid
1480 # descriptor invocation as it may raise or have side
1478 # descriptor invocation as it may raise or have side
1481 # effects.
1479 # effects.
1482 if idx == len(oname_rest) - 1:
1480 if idx == len(oname_rest) - 1:
1483 obj = self._getattr_property(obj, part)
1481 obj = self._getattr_property(obj, part)
1484 else:
1482 else:
1485 obj = getattr(obj, part)
1483 obj = getattr(obj, part)
1486 except:
1484 except:
1487 # Blanket except b/c some badly implemented objects
1485 # Blanket except b/c some badly implemented objects
1488 # allow __getattr__ to raise exceptions other than
1486 # allow __getattr__ to raise exceptions other than
1489 # AttributeError, which then crashes IPython.
1487 # AttributeError, which then crashes IPython.
1490 break
1488 break
1491 else:
1489 else:
1492 # If we finish the for loop (no break), we got all members
1490 # If we finish the for loop (no break), we got all members
1493 found = True
1491 found = True
1494 ospace = nsname
1492 ospace = nsname
1495 break # namespace loop
1493 break # namespace loop
1496
1494
1497 # Try to see if it's magic
1495 # Try to see if it's magic
1498 if not found:
1496 if not found:
1499 obj = None
1497 obj = None
1500 if oname.startswith(ESC_MAGIC2):
1498 if oname.startswith(ESC_MAGIC2):
1501 oname = oname.lstrip(ESC_MAGIC2)
1499 oname = oname.lstrip(ESC_MAGIC2)
1502 obj = self.find_cell_magic(oname)
1500 obj = self.find_cell_magic(oname)
1503 elif oname.startswith(ESC_MAGIC):
1501 elif oname.startswith(ESC_MAGIC):
1504 oname = oname.lstrip(ESC_MAGIC)
1502 oname = oname.lstrip(ESC_MAGIC)
1505 obj = self.find_line_magic(oname)
1503 obj = self.find_line_magic(oname)
1506 else:
1504 else:
1507 # search without prefix, so run? will find %run?
1505 # search without prefix, so run? will find %run?
1508 obj = self.find_line_magic(oname)
1506 obj = self.find_line_magic(oname)
1509 if obj is None:
1507 if obj is None:
1510 obj = self.find_cell_magic(oname)
1508 obj = self.find_cell_magic(oname)
1511 if obj is not None:
1509 if obj is not None:
1512 found = True
1510 found = True
1513 ospace = 'IPython internal'
1511 ospace = 'IPython internal'
1514 ismagic = True
1512 ismagic = True
1515
1513
1516 # Last try: special-case some literals like '', [], {}, etc:
1514 # Last try: special-case some literals like '', [], {}, etc:
1517 if not found and oname_head in ["''",'""','[]','{}','()']:
1515 if not found and oname_head in ["''",'""','[]','{}','()']:
1518 obj = eval(oname_head)
1516 obj = eval(oname_head)
1519 found = True
1517 found = True
1520 ospace = 'Interactive'
1518 ospace = 'Interactive'
1521
1519
1522 return {'found':found, 'obj':obj, 'namespace':ospace,
1520 return {'found':found, 'obj':obj, 'namespace':ospace,
1523 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1521 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1524
1522
1525 @staticmethod
1523 @staticmethod
1526 def _getattr_property(obj, attrname):
1524 def _getattr_property(obj, attrname):
1527 """Property-aware getattr to use in object finding.
1525 """Property-aware getattr to use in object finding.
1528
1526
1529 If attrname represents a property, return it unevaluated (in case it has
1527 If attrname represents a property, return it unevaluated (in case it has
1530 side effects or raises an error.
1528 side effects or raises an error.
1531
1529
1532 """
1530 """
1533 if not isinstance(obj, type):
1531 if not isinstance(obj, type):
1534 try:
1532 try:
1535 # `getattr(type(obj), attrname)` is not guaranteed to return
1533 # `getattr(type(obj), attrname)` is not guaranteed to return
1536 # `obj`, but does so for property:
1534 # `obj`, but does so for property:
1537 #
1535 #
1538 # property.__get__(self, None, cls) -> self
1536 # property.__get__(self, None, cls) -> self
1539 #
1537 #
1540 # The universal alternative is to traverse the mro manually
1538 # The universal alternative is to traverse the mro manually
1541 # searching for attrname in class dicts.
1539 # searching for attrname in class dicts.
1542 attr = getattr(type(obj), attrname)
1540 attr = getattr(type(obj), attrname)
1543 except AttributeError:
1541 except AttributeError:
1544 pass
1542 pass
1545 else:
1543 else:
1546 # This relies on the fact that data descriptors (with both
1544 # This relies on the fact that data descriptors (with both
1547 # __get__ & __set__ magic methods) take precedence over
1545 # __get__ & __set__ magic methods) take precedence over
1548 # instance-level attributes:
1546 # instance-level attributes:
1549 #
1547 #
1550 # class A(object):
1548 # class A(object):
1551 # @property
1549 # @property
1552 # def foobar(self): return 123
1550 # def foobar(self): return 123
1553 # a = A()
1551 # a = A()
1554 # a.__dict__['foobar'] = 345
1552 # a.__dict__['foobar'] = 345
1555 # a.foobar # == 123
1553 # a.foobar # == 123
1556 #
1554 #
1557 # So, a property may be returned right away.
1555 # So, a property may be returned right away.
1558 if isinstance(attr, property):
1556 if isinstance(attr, property):
1559 return attr
1557 return attr
1560
1558
1561 # Nothing helped, fall back.
1559 # Nothing helped, fall back.
1562 return getattr(obj, attrname)
1560 return getattr(obj, attrname)
1563
1561
1564 def _object_find(self, oname, namespaces=None):
1562 def _object_find(self, oname, namespaces=None):
1565 """Find an object and return a struct with info about it."""
1563 """Find an object and return a struct with info about it."""
1566 return Struct(self._ofind(oname, namespaces))
1564 return Struct(self._ofind(oname, namespaces))
1567
1565
1568 def _inspect(self, meth, oname, namespaces=None, **kw):
1566 def _inspect(self, meth, oname, namespaces=None, **kw):
1569 """Generic interface to the inspector system.
1567 """Generic interface to the inspector system.
1570
1568
1571 This function is meant to be called by pdef, pdoc & friends."""
1569 This function is meant to be called by pdef, pdoc & friends."""
1572 info = self._object_find(oname, namespaces)
1570 info = self._object_find(oname, namespaces)
1573 if info.found:
1571 if info.found:
1574 pmethod = getattr(self.inspector, meth)
1572 pmethod = getattr(self.inspector, meth)
1575 formatter = format_screen if info.ismagic else None
1573 formatter = format_screen if info.ismagic else None
1576 if meth == 'pdoc':
1574 if meth == 'pdoc':
1577 pmethod(info.obj, oname, formatter)
1575 pmethod(info.obj, oname, formatter)
1578 elif meth == 'pinfo':
1576 elif meth == 'pinfo':
1579 pmethod(info.obj, oname, formatter, info, **kw)
1577 pmethod(info.obj, oname, formatter, info, **kw)
1580 else:
1578 else:
1581 pmethod(info.obj, oname)
1579 pmethod(info.obj, oname)
1582 else:
1580 else:
1583 print('Object `%s` not found.' % oname)
1581 print('Object `%s` not found.' % oname)
1584 return 'not found' # so callers can take other action
1582 return 'not found' # so callers can take other action
1585
1583
1586 def object_inspect(self, oname, detail_level=0):
1584 def object_inspect(self, oname, detail_level=0):
1587 """Get object info about oname"""
1585 """Get object info about oname"""
1588 with self.builtin_trap:
1586 with self.builtin_trap:
1589 info = self._object_find(oname)
1587 info = self._object_find(oname)
1590 if info.found:
1588 if info.found:
1591 return self.inspector.info(info.obj, oname, info=info,
1589 return self.inspector.info(info.obj, oname, info=info,
1592 detail_level=detail_level
1590 detail_level=detail_level
1593 )
1591 )
1594 else:
1592 else:
1595 return oinspect.object_info(name=oname, found=False)
1593 return oinspect.object_info(name=oname, found=False)
1596
1594
1597 def object_inspect_text(self, oname, detail_level=0):
1595 def object_inspect_text(self, oname, detail_level=0):
1598 """Get object info as formatted text"""
1596 """Get object info as formatted text"""
1599 with self.builtin_trap:
1597 with self.builtin_trap:
1600 info = self._object_find(oname)
1598 info = self._object_find(oname)
1601 if info.found:
1599 if info.found:
1602 return self.inspector._format_info(info.obj, oname, info=info,
1600 return self.inspector._format_info(info.obj, oname, info=info,
1603 detail_level=detail_level
1601 detail_level=detail_level
1604 )
1602 )
1605 else:
1603 else:
1606 raise KeyError(oname)
1604 raise KeyError(oname)
1607
1605
1608 #-------------------------------------------------------------------------
1606 #-------------------------------------------------------------------------
1609 # Things related to history management
1607 # Things related to history management
1610 #-------------------------------------------------------------------------
1608 #-------------------------------------------------------------------------
1611
1609
1612 def init_history(self):
1610 def init_history(self):
1613 """Sets up the command history, and starts regular autosaves."""
1611 """Sets up the command history, and starts regular autosaves."""
1614 self.history_manager = HistoryManager(shell=self, parent=self)
1612 self.history_manager = HistoryManager(shell=self, parent=self)
1615 self.configurables.append(self.history_manager)
1613 self.configurables.append(self.history_manager)
1616
1614
1617 #-------------------------------------------------------------------------
1615 #-------------------------------------------------------------------------
1618 # Things related to exception handling and tracebacks (not debugging)
1616 # Things related to exception handling and tracebacks (not debugging)
1619 #-------------------------------------------------------------------------
1617 #-------------------------------------------------------------------------
1620
1618
1621 def init_traceback_handlers(self, custom_exceptions):
1619 def init_traceback_handlers(self, custom_exceptions):
1622 # Syntax error handler.
1620 # Syntax error handler.
1623 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor')
1621 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor')
1624
1622
1625 # The interactive one is initialized with an offset, meaning we always
1623 # The interactive one is initialized with an offset, meaning we always
1626 # want to remove the topmost item in the traceback, which is our own
1624 # want to remove the topmost item in the traceback, which is our own
1627 # internal code. Valid modes: ['Plain','Context','Verbose']
1625 # internal code. Valid modes: ['Plain','Context','Verbose']
1628 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1626 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1629 color_scheme='NoColor',
1627 color_scheme='NoColor',
1630 tb_offset = 1,
1628 tb_offset = 1,
1631 check_cache=check_linecache_ipython)
1629 check_cache=check_linecache_ipython)
1632
1630
1633 # The instance will store a pointer to the system-wide exception hook,
1631 # The instance will store a pointer to the system-wide exception hook,
1634 # so that runtime code (such as magics) can access it. This is because
1632 # so that runtime code (such as magics) can access it. This is because
1635 # during the read-eval loop, it may get temporarily overwritten.
1633 # during the read-eval loop, it may get temporarily overwritten.
1636 self.sys_excepthook = sys.excepthook
1634 self.sys_excepthook = sys.excepthook
1637
1635
1638 # and add any custom exception handlers the user may have specified
1636 # and add any custom exception handlers the user may have specified
1639 self.set_custom_exc(*custom_exceptions)
1637 self.set_custom_exc(*custom_exceptions)
1640
1638
1641 # Set the exception mode
1639 # Set the exception mode
1642 self.InteractiveTB.set_mode(mode=self.xmode)
1640 self.InteractiveTB.set_mode(mode=self.xmode)
1643
1641
1644 def set_custom_exc(self, exc_tuple, handler):
1642 def set_custom_exc(self, exc_tuple, handler):
1645 """set_custom_exc(exc_tuple,handler)
1643 """set_custom_exc(exc_tuple,handler)
1646
1644
1647 Set a custom exception handler, which will be called if any of the
1645 Set a custom exception handler, which will be called if any of the
1648 exceptions in exc_tuple occur in the mainloop (specifically, in the
1646 exceptions in exc_tuple occur in the mainloop (specifically, in the
1649 run_code() method).
1647 run_code() method).
1650
1648
1651 Parameters
1649 Parameters
1652 ----------
1650 ----------
1653
1651
1654 exc_tuple : tuple of exception classes
1652 exc_tuple : tuple of exception classes
1655 A *tuple* of exception classes, for which to call the defined
1653 A *tuple* of exception classes, for which to call the defined
1656 handler. It is very important that you use a tuple, and NOT A
1654 handler. It is very important that you use a tuple, and NOT A
1657 LIST here, because of the way Python's except statement works. If
1655 LIST here, because of the way Python's except statement works. If
1658 you only want to trap a single exception, use a singleton tuple::
1656 you only want to trap a single exception, use a singleton tuple::
1659
1657
1660 exc_tuple == (MyCustomException,)
1658 exc_tuple == (MyCustomException,)
1661
1659
1662 handler : callable
1660 handler : callable
1663 handler must have the following signature::
1661 handler must have the following signature::
1664
1662
1665 def my_handler(self, etype, value, tb, tb_offset=None):
1663 def my_handler(self, etype, value, tb, tb_offset=None):
1666 ...
1664 ...
1667 return structured_traceback
1665 return structured_traceback
1668
1666
1669 Your handler must return a structured traceback (a list of strings),
1667 Your handler must return a structured traceback (a list of strings),
1670 or None.
1668 or None.
1671
1669
1672 This will be made into an instance method (via types.MethodType)
1670 This will be made into an instance method (via types.MethodType)
1673 of IPython itself, and it will be called if any of the exceptions
1671 of IPython itself, and it will be called if any of the exceptions
1674 listed in the exc_tuple are caught. If the handler is None, an
1672 listed in the exc_tuple are caught. If the handler is None, an
1675 internal basic one is used, which just prints basic info.
1673 internal basic one is used, which just prints basic info.
1676
1674
1677 To protect IPython from crashes, if your handler ever raises an
1675 To protect IPython from crashes, if your handler ever raises an
1678 exception or returns an invalid result, it will be immediately
1676 exception or returns an invalid result, it will be immediately
1679 disabled.
1677 disabled.
1680
1678
1681 WARNING: by putting in your own exception handler into IPython's main
1679 WARNING: by putting in your own exception handler into IPython's main
1682 execution loop, you run a very good chance of nasty crashes. This
1680 execution loop, you run a very good chance of nasty crashes. This
1683 facility should only be used if you really know what you are doing."""
1681 facility should only be used if you really know what you are doing."""
1684
1682
1685 assert type(exc_tuple)==type(()) , \
1683 assert type(exc_tuple)==type(()) , \
1686 "The custom exceptions must be given AS A TUPLE."
1684 "The custom exceptions must be given AS A TUPLE."
1687
1685
1688 def dummy_handler(self,etype,value,tb,tb_offset=None):
1686 def dummy_handler(self,etype,value,tb,tb_offset=None):
1689 print('*** Simple custom exception handler ***')
1687 print('*** Simple custom exception handler ***')
1690 print('Exception type :',etype)
1688 print('Exception type :',etype)
1691 print('Exception value:',value)
1689 print('Exception value:',value)
1692 print('Traceback :',tb)
1690 print('Traceback :',tb)
1693 #print 'Source code :','\n'.join(self.buffer)
1691 #print 'Source code :','\n'.join(self.buffer)
1694
1692
1695 def validate_stb(stb):
1693 def validate_stb(stb):
1696 """validate structured traceback return type
1694 """validate structured traceback return type
1697
1695
1698 return type of CustomTB *should* be a list of strings, but allow
1696 return type of CustomTB *should* be a list of strings, but allow
1699 single strings or None, which are harmless.
1697 single strings or None, which are harmless.
1700
1698
1701 This function will *always* return a list of strings,
1699 This function will *always* return a list of strings,
1702 and will raise a TypeError if stb is inappropriate.
1700 and will raise a TypeError if stb is inappropriate.
1703 """
1701 """
1704 msg = "CustomTB must return list of strings, not %r" % stb
1702 msg = "CustomTB must return list of strings, not %r" % stb
1705 if stb is None:
1703 if stb is None:
1706 return []
1704 return []
1707 elif isinstance(stb, string_types):
1705 elif isinstance(stb, string_types):
1708 return [stb]
1706 return [stb]
1709 elif not isinstance(stb, list):
1707 elif not isinstance(stb, list):
1710 raise TypeError(msg)
1708 raise TypeError(msg)
1711 # it's a list
1709 # it's a list
1712 for line in stb:
1710 for line in stb:
1713 # check every element
1711 # check every element
1714 if not isinstance(line, string_types):
1712 if not isinstance(line, string_types):
1715 raise TypeError(msg)
1713 raise TypeError(msg)
1716 return stb
1714 return stb
1717
1715
1718 if handler is None:
1716 if handler is None:
1719 wrapped = dummy_handler
1717 wrapped = dummy_handler
1720 else:
1718 else:
1721 def wrapped(self,etype,value,tb,tb_offset=None):
1719 def wrapped(self,etype,value,tb,tb_offset=None):
1722 """wrap CustomTB handler, to protect IPython from user code
1720 """wrap CustomTB handler, to protect IPython from user code
1723
1721
1724 This makes it harder (but not impossible) for custom exception
1722 This makes it harder (but not impossible) for custom exception
1725 handlers to crash IPython.
1723 handlers to crash IPython.
1726 """
1724 """
1727 try:
1725 try:
1728 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
1726 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
1729 return validate_stb(stb)
1727 return validate_stb(stb)
1730 except:
1728 except:
1731 # clear custom handler immediately
1729 # clear custom handler immediately
1732 self.set_custom_exc((), None)
1730 self.set_custom_exc((), None)
1733 print("Custom TB Handler failed, unregistering", file=io.stderr)
1731 print("Custom TB Handler failed, unregistering", file=io.stderr)
1734 # show the exception in handler first
1732 # show the exception in handler first
1735 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
1733 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
1736 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1734 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1737 print("The original exception:", file=io.stdout)
1735 print("The original exception:", file=io.stdout)
1738 stb = self.InteractiveTB.structured_traceback(
1736 stb = self.InteractiveTB.structured_traceback(
1739 (etype,value,tb), tb_offset=tb_offset
1737 (etype,value,tb), tb_offset=tb_offset
1740 )
1738 )
1741 return stb
1739 return stb
1742
1740
1743 self.CustomTB = types.MethodType(wrapped,self)
1741 self.CustomTB = types.MethodType(wrapped,self)
1744 self.custom_exceptions = exc_tuple
1742 self.custom_exceptions = exc_tuple
1745
1743
1746 def excepthook(self, etype, value, tb):
1744 def excepthook(self, etype, value, tb):
1747 """One more defense for GUI apps that call sys.excepthook.
1745 """One more defense for GUI apps that call sys.excepthook.
1748
1746
1749 GUI frameworks like wxPython trap exceptions and call
1747 GUI frameworks like wxPython trap exceptions and call
1750 sys.excepthook themselves. I guess this is a feature that
1748 sys.excepthook themselves. I guess this is a feature that
1751 enables them to keep running after exceptions that would
1749 enables them to keep running after exceptions that would
1752 otherwise kill their mainloop. This is a bother for IPython
1750 otherwise kill their mainloop. This is a bother for IPython
1753 which excepts to catch all of the program exceptions with a try:
1751 which excepts to catch all of the program exceptions with a try:
1754 except: statement.
1752 except: statement.
1755
1753
1756 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
1754 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
1757 any app directly invokes sys.excepthook, it will look to the user like
1755 any app directly invokes sys.excepthook, it will look to the user like
1758 IPython crashed. In order to work around this, we can disable the
1756 IPython crashed. In order to work around this, we can disable the
1759 CrashHandler and replace it with this excepthook instead, which prints a
1757 CrashHandler and replace it with this excepthook instead, which prints a
1760 regular traceback using our InteractiveTB. In this fashion, apps which
1758 regular traceback using our InteractiveTB. In this fashion, apps which
1761 call sys.excepthook will generate a regular-looking exception from
1759 call sys.excepthook will generate a regular-looking exception from
1762 IPython, and the CrashHandler will only be triggered by real IPython
1760 IPython, and the CrashHandler will only be triggered by real IPython
1763 crashes.
1761 crashes.
1764
1762
1765 This hook should be used sparingly, only in places which are not likely
1763 This hook should be used sparingly, only in places which are not likely
1766 to be true IPython errors.
1764 to be true IPython errors.
1767 """
1765 """
1768 self.showtraceback((etype, value, tb), tb_offset=0)
1766 self.showtraceback((etype, value, tb), tb_offset=0)
1769
1767
1770 def _get_exc_info(self, exc_tuple=None):
1768 def _get_exc_info(self, exc_tuple=None):
1771 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1769 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1772
1770
1773 Ensures sys.last_type,value,traceback hold the exc_info we found,
1771 Ensures sys.last_type,value,traceback hold the exc_info we found,
1774 from whichever source.
1772 from whichever source.
1775
1773
1776 raises ValueError if none of these contain any information
1774 raises ValueError if none of these contain any information
1777 """
1775 """
1778 if exc_tuple is None:
1776 if exc_tuple is None:
1779 etype, value, tb = sys.exc_info()
1777 etype, value, tb = sys.exc_info()
1780 else:
1778 else:
1781 etype, value, tb = exc_tuple
1779 etype, value, tb = exc_tuple
1782
1780
1783 if etype is None:
1781 if etype is None:
1784 if hasattr(sys, 'last_type'):
1782 if hasattr(sys, 'last_type'):
1785 etype, value, tb = sys.last_type, sys.last_value, \
1783 etype, value, tb = sys.last_type, sys.last_value, \
1786 sys.last_traceback
1784 sys.last_traceback
1787
1785
1788 if etype is None:
1786 if etype is None:
1789 raise ValueError("No exception to find")
1787 raise ValueError("No exception to find")
1790
1788
1791 # Now store the exception info in sys.last_type etc.
1789 # Now store the exception info in sys.last_type etc.
1792 # WARNING: these variables are somewhat deprecated and not
1790 # WARNING: these variables are somewhat deprecated and not
1793 # necessarily safe to use in a threaded environment, but tools
1791 # necessarily safe to use in a threaded environment, but tools
1794 # like pdb depend on their existence, so let's set them. If we
1792 # like pdb depend on their existence, so let's set them. If we
1795 # find problems in the field, we'll need to revisit their use.
1793 # find problems in the field, we'll need to revisit their use.
1796 sys.last_type = etype
1794 sys.last_type = etype
1797 sys.last_value = value
1795 sys.last_value = value
1798 sys.last_traceback = tb
1796 sys.last_traceback = tb
1799
1797
1800 return etype, value, tb
1798 return etype, value, tb
1801
1799
1802 def show_usage_error(self, exc):
1800 def show_usage_error(self, exc):
1803 """Show a short message for UsageErrors
1801 """Show a short message for UsageErrors
1804
1802
1805 These are special exceptions that shouldn't show a traceback.
1803 These are special exceptions that shouldn't show a traceback.
1806 """
1804 """
1807 self.write_err("UsageError: %s" % exc)
1805 self.write_err("UsageError: %s" % exc)
1808
1806
1809 def get_exception_only(self, exc_tuple=None):
1807 def get_exception_only(self, exc_tuple=None):
1810 """
1808 """
1811 Return as a string (ending with a newline) the exception that
1809 Return as a string (ending with a newline) the exception that
1812 just occurred, without any traceback.
1810 just occurred, without any traceback.
1813 """
1811 """
1814 etype, value, tb = self._get_exc_info(exc_tuple)
1812 etype, value, tb = self._get_exc_info(exc_tuple)
1815 msg = traceback.format_exception_only(etype, value)
1813 msg = traceback.format_exception_only(etype, value)
1816 return ''.join(msg)
1814 return ''.join(msg)
1817
1815
1818 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
1816 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
1819 exception_only=False):
1817 exception_only=False):
1820 """Display the exception that just occurred.
1818 """Display the exception that just occurred.
1821
1819
1822 If nothing is known about the exception, this is the method which
1820 If nothing is known about the exception, this is the method which
1823 should be used throughout the code for presenting user tracebacks,
1821 should be used throughout the code for presenting user tracebacks,
1824 rather than directly invoking the InteractiveTB object.
1822 rather than directly invoking the InteractiveTB object.
1825
1823
1826 A specific showsyntaxerror() also exists, but this method can take
1824 A specific showsyntaxerror() also exists, but this method can take
1827 care of calling it if needed, so unless you are explicitly catching a
1825 care of calling it if needed, so unless you are explicitly catching a
1828 SyntaxError exception, don't try to analyze the stack manually and
1826 SyntaxError exception, don't try to analyze the stack manually and
1829 simply call this method."""
1827 simply call this method."""
1830
1828
1831 try:
1829 try:
1832 try:
1830 try:
1833 etype, value, tb = self._get_exc_info(exc_tuple)
1831 etype, value, tb = self._get_exc_info(exc_tuple)
1834 except ValueError:
1832 except ValueError:
1835 self.write_err('No traceback available to show.\n')
1833 self.write_err('No traceback available to show.\n')
1836 return
1834 return
1837
1835
1838 if issubclass(etype, SyntaxError):
1836 if issubclass(etype, SyntaxError):
1839 # Though this won't be called by syntax errors in the input
1837 # Though this won't be called by syntax errors in the input
1840 # line, there may be SyntaxError cases with imported code.
1838 # line, there may be SyntaxError cases with imported code.
1841 self.showsyntaxerror(filename)
1839 self.showsyntaxerror(filename)
1842 elif etype is UsageError:
1840 elif etype is UsageError:
1843 self.show_usage_error(value)
1841 self.show_usage_error(value)
1844 else:
1842 else:
1845 if exception_only:
1843 if exception_only:
1846 stb = ['An exception has occurred, use %tb to see '
1844 stb = ['An exception has occurred, use %tb to see '
1847 'the full traceback.\n']
1845 'the full traceback.\n']
1848 stb.extend(self.InteractiveTB.get_exception_only(etype,
1846 stb.extend(self.InteractiveTB.get_exception_only(etype,
1849 value))
1847 value))
1850 else:
1848 else:
1851 try:
1849 try:
1852 # Exception classes can customise their traceback - we
1850 # Exception classes can customise their traceback - we
1853 # use this in IPython.parallel for exceptions occurring
1851 # use this in IPython.parallel for exceptions occurring
1854 # in the engines. This should return a list of strings.
1852 # in the engines. This should return a list of strings.
1855 stb = value._render_traceback_()
1853 stb = value._render_traceback_()
1856 except Exception:
1854 except Exception:
1857 stb = self.InteractiveTB.structured_traceback(etype,
1855 stb = self.InteractiveTB.structured_traceback(etype,
1858 value, tb, tb_offset=tb_offset)
1856 value, tb, tb_offset=tb_offset)
1859
1857
1860 self._showtraceback(etype, value, stb)
1858 self._showtraceback(etype, value, stb)
1861 if self.call_pdb:
1859 if self.call_pdb:
1862 # drop into debugger
1860 # drop into debugger
1863 self.debugger(force=True)
1861 self.debugger(force=True)
1864 return
1862 return
1865
1863
1866 # Actually show the traceback
1864 # Actually show the traceback
1867 self._showtraceback(etype, value, stb)
1865 self._showtraceback(etype, value, stb)
1868
1866
1869 except KeyboardInterrupt:
1867 except KeyboardInterrupt:
1870 self.write_err('\n' + self.get_exception_only())
1868 self.write_err('\n' + self.get_exception_only())
1871
1869
1872 def _showtraceback(self, etype, evalue, stb):
1870 def _showtraceback(self, etype, evalue, stb):
1873 """Actually show a traceback.
1871 """Actually show a traceback.
1874
1872
1875 Subclasses may override this method to put the traceback on a different
1873 Subclasses may override this method to put the traceback on a different
1876 place, like a side channel.
1874 place, like a side channel.
1877 """
1875 """
1878 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1876 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1879
1877
1880 def showsyntaxerror(self, filename=None):
1878 def showsyntaxerror(self, filename=None):
1881 """Display the syntax error that just occurred.
1879 """Display the syntax error that just occurred.
1882
1880
1883 This doesn't display a stack trace because there isn't one.
1881 This doesn't display a stack trace because there isn't one.
1884
1882
1885 If a filename is given, it is stuffed in the exception instead
1883 If a filename is given, it is stuffed in the exception instead
1886 of what was there before (because Python's parser always uses
1884 of what was there before (because Python's parser always uses
1887 "<string>" when reading from a string).
1885 "<string>" when reading from a string).
1888 """
1886 """
1889 etype, value, last_traceback = self._get_exc_info()
1887 etype, value, last_traceback = self._get_exc_info()
1890
1888
1891 if filename and issubclass(etype, SyntaxError):
1889 if filename and issubclass(etype, SyntaxError):
1892 try:
1890 try:
1893 value.filename = filename
1891 value.filename = filename
1894 except:
1892 except:
1895 # Not the format we expect; leave it alone
1893 # Not the format we expect; leave it alone
1896 pass
1894 pass
1897
1895
1898 stb = self.SyntaxTB.structured_traceback(etype, value, [])
1896 stb = self.SyntaxTB.structured_traceback(etype, value, [])
1899 self._showtraceback(etype, value, stb)
1897 self._showtraceback(etype, value, stb)
1900
1898
1901 # This is overridden in TerminalInteractiveShell to show a message about
1899 # This is overridden in TerminalInteractiveShell to show a message about
1902 # the %paste magic.
1900 # the %paste magic.
1903 def showindentationerror(self):
1901 def showindentationerror(self):
1904 """Called by run_cell when there's an IndentationError in code entered
1902 """Called by run_cell when there's an IndentationError in code entered
1905 at the prompt.
1903 at the prompt.
1906
1904
1907 This is overridden in TerminalInteractiveShell to show a message about
1905 This is overridden in TerminalInteractiveShell to show a message about
1908 the %paste magic."""
1906 the %paste magic."""
1909 self.showsyntaxerror()
1907 self.showsyntaxerror()
1910
1908
1911 #-------------------------------------------------------------------------
1909 #-------------------------------------------------------------------------
1912 # Things related to readline
1910 # Things related to readline
1913 #-------------------------------------------------------------------------
1911 #-------------------------------------------------------------------------
1914
1912
1915 def init_readline(self):
1913 def init_readline(self):
1916 """Command history completion/saving/reloading."""
1914 """Command history completion/saving/reloading."""
1917
1915
1918 if self.readline_use:
1916 if self.readline_use:
1919 import IPython.utils.rlineimpl as readline
1917 import IPython.utils.rlineimpl as readline
1920
1918
1921 self.rl_next_input = None
1919 self.rl_next_input = None
1922 self.rl_do_indent = False
1920 self.rl_do_indent = False
1923
1921
1924 if not self.readline_use or not readline.have_readline:
1922 if not self.readline_use or not readline.have_readline:
1925 self.has_readline = False
1923 self.has_readline = False
1926 self.readline = None
1924 self.readline = None
1927 # Set a number of methods that depend on readline to be no-op
1925 # Set a number of methods that depend on readline to be no-op
1928 self.readline_no_record = no_op_context
1926 self.readline_no_record = no_op_context
1929 self.set_readline_completer = no_op
1927 self.set_readline_completer = no_op
1930 self.set_custom_completer = no_op
1928 self.set_custom_completer = no_op
1931 if self.readline_use:
1929 if self.readline_use:
1932 warn('Readline services not available or not loaded.')
1930 warn('Readline services not available or not loaded.')
1933 else:
1931 else:
1934 self.has_readline = True
1932 self.has_readline = True
1935 self.readline = readline
1933 self.readline = readline
1936 sys.modules['readline'] = readline
1934 sys.modules['readline'] = readline
1937
1935
1938 # Platform-specific configuration
1936 # Platform-specific configuration
1939 if os.name == 'nt':
1937 if os.name == 'nt':
1940 # FIXME - check with Frederick to see if we can harmonize
1938 # FIXME - check with Frederick to see if we can harmonize
1941 # naming conventions with pyreadline to avoid this
1939 # naming conventions with pyreadline to avoid this
1942 # platform-dependent check
1940 # platform-dependent check
1943 self.readline_startup_hook = readline.set_pre_input_hook
1941 self.readline_startup_hook = readline.set_pre_input_hook
1944 else:
1942 else:
1945 self.readline_startup_hook = readline.set_startup_hook
1943 self.readline_startup_hook = readline.set_startup_hook
1946
1944
1947 # Readline config order:
1945 # Readline config order:
1948 # - IPython config (default value)
1946 # - IPython config (default value)
1949 # - custom inputrc
1947 # - custom inputrc
1950 # - IPython config (user customized)
1948 # - IPython config (user customized)
1951
1949
1952 # load IPython config before inputrc if default
1950 # load IPython config before inputrc if default
1953 # skip if libedit because parse_and_bind syntax is different
1951 # skip if libedit because parse_and_bind syntax is different
1954 if not self._custom_readline_config and not readline.uses_libedit:
1952 if not self._custom_readline_config and not readline.uses_libedit:
1955 for rlcommand in self.readline_parse_and_bind:
1953 for rlcommand in self.readline_parse_and_bind:
1956 readline.parse_and_bind(rlcommand)
1954 readline.parse_and_bind(rlcommand)
1957
1955
1958 # Load user's initrc file (readline config)
1956 # Load user's initrc file (readline config)
1959 # Or if libedit is used, load editrc.
1957 # Or if libedit is used, load editrc.
1960 inputrc_name = os.environ.get('INPUTRC')
1958 inputrc_name = os.environ.get('INPUTRC')
1961 if inputrc_name is None:
1959 if inputrc_name is None:
1962 inputrc_name = '.inputrc'
1960 inputrc_name = '.inputrc'
1963 if readline.uses_libedit:
1961 if readline.uses_libedit:
1964 inputrc_name = '.editrc'
1962 inputrc_name = '.editrc'
1965 inputrc_name = os.path.join(self.home_dir, inputrc_name)
1963 inputrc_name = os.path.join(self.home_dir, inputrc_name)
1966 if os.path.isfile(inputrc_name):
1964 if os.path.isfile(inputrc_name):
1967 try:
1965 try:
1968 readline.read_init_file(inputrc_name)
1966 readline.read_init_file(inputrc_name)
1969 except:
1967 except:
1970 warn('Problems reading readline initialization file <%s>'
1968 warn('Problems reading readline initialization file <%s>'
1971 % inputrc_name)
1969 % inputrc_name)
1972
1970
1973 # load IPython config after inputrc if user has customized
1971 # load IPython config after inputrc if user has customized
1974 if self._custom_readline_config:
1972 if self._custom_readline_config:
1975 for rlcommand in self.readline_parse_and_bind:
1973 for rlcommand in self.readline_parse_and_bind:
1976 readline.parse_and_bind(rlcommand)
1974 readline.parse_and_bind(rlcommand)
1977
1975
1978 # Remove some chars from the delimiters list. If we encounter
1976 # Remove some chars from the delimiters list. If we encounter
1979 # unicode chars, discard them.
1977 # unicode chars, discard them.
1980 delims = readline.get_completer_delims()
1978 delims = readline.get_completer_delims()
1981 if not py3compat.PY3:
1979 if not py3compat.PY3:
1982 delims = delims.encode("ascii", "ignore")
1980 delims = delims.encode("ascii", "ignore")
1983 for d in self.readline_remove_delims:
1981 for d in self.readline_remove_delims:
1984 delims = delims.replace(d, "")
1982 delims = delims.replace(d, "")
1985 delims = delims.replace(ESC_MAGIC, '')
1983 delims = delims.replace(ESC_MAGIC, '')
1986 readline.set_completer_delims(delims)
1984 readline.set_completer_delims(delims)
1987 # Store these so we can restore them if something like rpy2 modifies
1985 # Store these so we can restore them if something like rpy2 modifies
1988 # them.
1986 # them.
1989 self.readline_delims = delims
1987 self.readline_delims = delims
1990 # otherwise we end up with a monster history after a while:
1988 # otherwise we end up with a monster history after a while:
1991 readline.set_history_length(self.history_length)
1989 readline.set_history_length(self.history_length)
1992
1990
1993 self.refill_readline_hist()
1991 self.refill_readline_hist()
1994 self.readline_no_record = ReadlineNoRecord(self)
1992 self.readline_no_record = ReadlineNoRecord(self)
1995
1993
1996 # Configure auto-indent for all platforms
1994 # Configure auto-indent for all platforms
1997 self.set_autoindent(self.autoindent)
1995 self.set_autoindent(self.autoindent)
1998
1996
1999 def refill_readline_hist(self):
1997 def refill_readline_hist(self):
2000 # Load the last 1000 lines from history
1998 # Load the last 1000 lines from history
2001 self.readline.clear_history()
1999 self.readline.clear_history()
2002 stdin_encoding = sys.stdin.encoding or "utf-8"
2000 stdin_encoding = sys.stdin.encoding or "utf-8"
2003 last_cell = u""
2001 last_cell = u""
2004 for _, _, cell in self.history_manager.get_tail(1000,
2002 for _, _, cell in self.history_manager.get_tail(1000,
2005 include_latest=True):
2003 include_latest=True):
2006 # Ignore blank lines and consecutive duplicates
2004 # Ignore blank lines and consecutive duplicates
2007 cell = cell.rstrip()
2005 cell = cell.rstrip()
2008 if cell and (cell != last_cell):
2006 if cell and (cell != last_cell):
2009 try:
2007 try:
2010 if self.multiline_history:
2008 if self.multiline_history:
2011 self.readline.add_history(py3compat.unicode_to_str(cell,
2009 self.readline.add_history(py3compat.unicode_to_str(cell,
2012 stdin_encoding))
2010 stdin_encoding))
2013 else:
2011 else:
2014 for line in cell.splitlines():
2012 for line in cell.splitlines():
2015 self.readline.add_history(py3compat.unicode_to_str(line,
2013 self.readline.add_history(py3compat.unicode_to_str(line,
2016 stdin_encoding))
2014 stdin_encoding))
2017 last_cell = cell
2015 last_cell = cell
2018
2016
2019 except TypeError:
2017 except TypeError:
2020 # The history DB can get corrupted so it returns strings
2018 # The history DB can get corrupted so it returns strings
2021 # containing null bytes, which readline objects to.
2019 # containing null bytes, which readline objects to.
2022 continue
2020 continue
2023
2021
2024 @skip_doctest
2022 @skip_doctest
2025 def set_next_input(self, s, replace=False):
2023 def set_next_input(self, s, replace=False):
2026 """ Sets the 'default' input string for the next command line.
2024 """ Sets the 'default' input string for the next command line.
2027
2025
2028 Requires readline.
2026 Requires readline.
2029
2027
2030 Example::
2028 Example::
2031
2029
2032 In [1]: _ip.set_next_input("Hello Word")
2030 In [1]: _ip.set_next_input("Hello Word")
2033 In [2]: Hello Word_ # cursor is here
2031 In [2]: Hello Word_ # cursor is here
2034 """
2032 """
2035 self.rl_next_input = py3compat.cast_bytes_py2(s)
2033 self.rl_next_input = py3compat.cast_bytes_py2(s)
2036
2034
2037 # Maybe move this to the terminal subclass?
2035 # Maybe move this to the terminal subclass?
2038 def pre_readline(self):
2036 def pre_readline(self):
2039 """readline hook to be used at the start of each line.
2037 """readline hook to be used at the start of each line.
2040
2038
2041 Currently it handles auto-indent only."""
2039 Currently it handles auto-indent only."""
2042
2040
2043 if self.rl_do_indent:
2041 if self.rl_do_indent:
2044 self.readline.insert_text(self._indent_current_str())
2042 self.readline.insert_text(self._indent_current_str())
2045 if self.rl_next_input is not None:
2043 if self.rl_next_input is not None:
2046 self.readline.insert_text(self.rl_next_input)
2044 self.readline.insert_text(self.rl_next_input)
2047 self.rl_next_input = None
2045 self.rl_next_input = None
2048
2046
2049 def _indent_current_str(self):
2047 def _indent_current_str(self):
2050 """return the current level of indentation as a string"""
2048 """return the current level of indentation as a string"""
2051 return self.input_splitter.indent_spaces * ' '
2049 return self.input_splitter.indent_spaces * ' '
2052
2050
2053 #-------------------------------------------------------------------------
2051 #-------------------------------------------------------------------------
2054 # Things related to text completion
2052 # Things related to text completion
2055 #-------------------------------------------------------------------------
2053 #-------------------------------------------------------------------------
2056
2054
2057 def init_completer(self):
2055 def init_completer(self):
2058 """Initialize the completion machinery.
2056 """Initialize the completion machinery.
2059
2057
2060 This creates completion machinery that can be used by client code,
2058 This creates completion machinery that can be used by client code,
2061 either interactively in-process (typically triggered by the readline
2059 either interactively in-process (typically triggered by the readline
2062 library), programatically (such as in test suites) or out-of-prcess
2060 library), programatically (such as in test suites) or out-of-prcess
2063 (typically over the network by remote frontends).
2061 (typically over the network by remote frontends).
2064 """
2062 """
2065 from IPython.core.completer import IPCompleter
2063 from IPython.core.completer import IPCompleter
2066 from IPython.core.completerlib import (module_completer,
2064 from IPython.core.completerlib import (module_completer,
2067 magic_run_completer, cd_completer, reset_completer)
2065 magic_run_completer, cd_completer, reset_completer)
2068
2066
2069 self.Completer = IPCompleter(shell=self,
2067 self.Completer = IPCompleter(shell=self,
2070 namespace=self.user_ns,
2068 namespace=self.user_ns,
2071 global_namespace=self.user_global_ns,
2069 global_namespace=self.user_global_ns,
2072 use_readline=self.has_readline,
2070 use_readline=self.has_readline,
2073 parent=self,
2071 parent=self,
2074 )
2072 )
2075 self.configurables.append(self.Completer)
2073 self.configurables.append(self.Completer)
2076
2074
2077 # Add custom completers to the basic ones built into IPCompleter
2075 # Add custom completers to the basic ones built into IPCompleter
2078 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
2076 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
2079 self.strdispatchers['complete_command'] = sdisp
2077 self.strdispatchers['complete_command'] = sdisp
2080 self.Completer.custom_completers = sdisp
2078 self.Completer.custom_completers = sdisp
2081
2079
2082 self.set_hook('complete_command', module_completer, str_key = 'import')
2080 self.set_hook('complete_command', module_completer, str_key = 'import')
2083 self.set_hook('complete_command', module_completer, str_key = 'from')
2081 self.set_hook('complete_command', module_completer, str_key = 'from')
2084 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
2082 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
2085 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2083 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2086 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2084 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2087
2085
2088 # Only configure readline if we truly are using readline. IPython can
2086 # Only configure readline if we truly are using readline. IPython can
2089 # do tab-completion over the network, in GUIs, etc, where readline
2087 # do tab-completion over the network, in GUIs, etc, where readline
2090 # itself may be absent
2088 # itself may be absent
2091 if self.has_readline:
2089 if self.has_readline:
2092 self.set_readline_completer()
2090 self.set_readline_completer()
2093
2091
2094 def complete(self, text, line=None, cursor_pos=None):
2092 def complete(self, text, line=None, cursor_pos=None):
2095 """Return the completed text and a list of completions.
2093 """Return the completed text and a list of completions.
2096
2094
2097 Parameters
2095 Parameters
2098 ----------
2096 ----------
2099
2097
2100 text : string
2098 text : string
2101 A string of text to be completed on. It can be given as empty and
2099 A string of text to be completed on. It can be given as empty and
2102 instead a line/position pair are given. In this case, the
2100 instead a line/position pair are given. In this case, the
2103 completer itself will split the line like readline does.
2101 completer itself will split the line like readline does.
2104
2102
2105 line : string, optional
2103 line : string, optional
2106 The complete line that text is part of.
2104 The complete line that text is part of.
2107
2105
2108 cursor_pos : int, optional
2106 cursor_pos : int, optional
2109 The position of the cursor on the input line.
2107 The position of the cursor on the input line.
2110
2108
2111 Returns
2109 Returns
2112 -------
2110 -------
2113 text : string
2111 text : string
2114 The actual text that was completed.
2112 The actual text that was completed.
2115
2113
2116 matches : list
2114 matches : list
2117 A sorted list with all possible completions.
2115 A sorted list with all possible completions.
2118
2116
2119 The optional arguments allow the completion to take more context into
2117 The optional arguments allow the completion to take more context into
2120 account, and are part of the low-level completion API.
2118 account, and are part of the low-level completion API.
2121
2119
2122 This is a wrapper around the completion mechanism, similar to what
2120 This is a wrapper around the completion mechanism, similar to what
2123 readline does at the command line when the TAB key is hit. By
2121 readline does at the command line when the TAB key is hit. By
2124 exposing it as a method, it can be used by other non-readline
2122 exposing it as a method, it can be used by other non-readline
2125 environments (such as GUIs) for text completion.
2123 environments (such as GUIs) for text completion.
2126
2124
2127 Simple usage example:
2125 Simple usage example:
2128
2126
2129 In [1]: x = 'hello'
2127 In [1]: x = 'hello'
2130
2128
2131 In [2]: _ip.complete('x.l')
2129 In [2]: _ip.complete('x.l')
2132 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
2130 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
2133 """
2131 """
2134
2132
2135 # Inject names into __builtin__ so we can complete on the added names.
2133 # Inject names into __builtin__ so we can complete on the added names.
2136 with self.builtin_trap:
2134 with self.builtin_trap:
2137 return self.Completer.complete(text, line, cursor_pos)
2135 return self.Completer.complete(text, line, cursor_pos)
2138
2136
2139 def set_custom_completer(self, completer, pos=0):
2137 def set_custom_completer(self, completer, pos=0):
2140 """Adds a new custom completer function.
2138 """Adds a new custom completer function.
2141
2139
2142 The position argument (defaults to 0) is the index in the completers
2140 The position argument (defaults to 0) is the index in the completers
2143 list where you want the completer to be inserted."""
2141 list where you want the completer to be inserted."""
2144
2142
2145 newcomp = types.MethodType(completer,self.Completer)
2143 newcomp = types.MethodType(completer,self.Completer)
2146 self.Completer.matchers.insert(pos,newcomp)
2144 self.Completer.matchers.insert(pos,newcomp)
2147
2145
2148 def set_readline_completer(self):
2146 def set_readline_completer(self):
2149 """Reset readline's completer to be our own."""
2147 """Reset readline's completer to be our own."""
2150 self.readline.set_completer(self.Completer.rlcomplete)
2148 self.readline.set_completer(self.Completer.rlcomplete)
2151
2149
2152 def set_completer_frame(self, frame=None):
2150 def set_completer_frame(self, frame=None):
2153 """Set the frame of the completer."""
2151 """Set the frame of the completer."""
2154 if frame:
2152 if frame:
2155 self.Completer.namespace = frame.f_locals
2153 self.Completer.namespace = frame.f_locals
2156 self.Completer.global_namespace = frame.f_globals
2154 self.Completer.global_namespace = frame.f_globals
2157 else:
2155 else:
2158 self.Completer.namespace = self.user_ns
2156 self.Completer.namespace = self.user_ns
2159 self.Completer.global_namespace = self.user_global_ns
2157 self.Completer.global_namespace = self.user_global_ns
2160
2158
2161 #-------------------------------------------------------------------------
2159 #-------------------------------------------------------------------------
2162 # Things related to magics
2160 # Things related to magics
2163 #-------------------------------------------------------------------------
2161 #-------------------------------------------------------------------------
2164
2162
2165 def init_magics(self):
2163 def init_magics(self):
2166 from IPython.core import magics as m
2164 from IPython.core import magics as m
2167 self.magics_manager = magic.MagicsManager(shell=self,
2165 self.magics_manager = magic.MagicsManager(shell=self,
2168 parent=self,
2166 parent=self,
2169 user_magics=m.UserMagics(self))
2167 user_magics=m.UserMagics(self))
2170 self.configurables.append(self.magics_manager)
2168 self.configurables.append(self.magics_manager)
2171
2169
2172 # Expose as public API from the magics manager
2170 # Expose as public API from the magics manager
2173 self.register_magics = self.magics_manager.register
2171 self.register_magics = self.magics_manager.register
2174 self.define_magic = self.magics_manager.define_magic
2172 self.define_magic = self.magics_manager.define_magic
2175
2173
2176 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2174 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2177 m.ConfigMagics, m.DeprecatedMagics, m.DisplayMagics, m.ExecutionMagics,
2175 m.ConfigMagics, m.DeprecatedMagics, m.DisplayMagics, m.ExecutionMagics,
2178 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2176 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2179 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2177 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2180 )
2178 )
2181
2179
2182 # Register Magic Aliases
2180 # Register Magic Aliases
2183 mman = self.magics_manager
2181 mman = self.magics_manager
2184 # FIXME: magic aliases should be defined by the Magics classes
2182 # FIXME: magic aliases should be defined by the Magics classes
2185 # or in MagicsManager, not here
2183 # or in MagicsManager, not here
2186 mman.register_alias('ed', 'edit')
2184 mman.register_alias('ed', 'edit')
2187 mman.register_alias('hist', 'history')
2185 mman.register_alias('hist', 'history')
2188 mman.register_alias('rep', 'recall')
2186 mman.register_alias('rep', 'recall')
2189 mman.register_alias('SVG', 'svg', 'cell')
2187 mman.register_alias('SVG', 'svg', 'cell')
2190 mman.register_alias('HTML', 'html', 'cell')
2188 mman.register_alias('HTML', 'html', 'cell')
2191 mman.register_alias('file', 'writefile', 'cell')
2189 mman.register_alias('file', 'writefile', 'cell')
2192
2190
2193 # FIXME: Move the color initialization to the DisplayHook, which
2191 # FIXME: Move the color initialization to the DisplayHook, which
2194 # should be split into a prompt manager and displayhook. We probably
2192 # should be split into a prompt manager and displayhook. We probably
2195 # even need a centralize colors management object.
2193 # even need a centralize colors management object.
2196 self.magic('colors %s' % self.colors)
2194 self.magic('colors %s' % self.colors)
2197
2195
2198 # Defined here so that it's included in the documentation
2196 # Defined here so that it's included in the documentation
2199 @functools.wraps(magic.MagicsManager.register_function)
2197 @functools.wraps(magic.MagicsManager.register_function)
2200 def register_magic_function(self, func, magic_kind='line', magic_name=None):
2198 def register_magic_function(self, func, magic_kind='line', magic_name=None):
2201 self.magics_manager.register_function(func,
2199 self.magics_manager.register_function(func,
2202 magic_kind=magic_kind, magic_name=magic_name)
2200 magic_kind=magic_kind, magic_name=magic_name)
2203
2201
2204 def run_line_magic(self, magic_name, line):
2202 def run_line_magic(self, magic_name, line):
2205 """Execute the given line magic.
2203 """Execute the given line magic.
2206
2204
2207 Parameters
2205 Parameters
2208 ----------
2206 ----------
2209 magic_name : str
2207 magic_name : str
2210 Name of the desired magic function, without '%' prefix.
2208 Name of the desired magic function, without '%' prefix.
2211
2209
2212 line : str
2210 line : str
2213 The rest of the input line as a single string.
2211 The rest of the input line as a single string.
2214 """
2212 """
2215 fn = self.find_line_magic(magic_name)
2213 fn = self.find_line_magic(magic_name)
2216 if fn is None:
2214 if fn is None:
2217 cm = self.find_cell_magic(magic_name)
2215 cm = self.find_cell_magic(magic_name)
2218 etpl = "Line magic function `%%%s` not found%s."
2216 etpl = "Line magic function `%%%s` not found%s."
2219 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2217 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2220 'did you mean that instead?)' % magic_name )
2218 'did you mean that instead?)' % magic_name )
2221 error(etpl % (magic_name, extra))
2219 error(etpl % (magic_name, extra))
2222 else:
2220 else:
2223 # Note: this is the distance in the stack to the user's frame.
2221 # Note: this is the distance in the stack to the user's frame.
2224 # This will need to be updated if the internal calling logic gets
2222 # This will need to be updated if the internal calling logic gets
2225 # refactored, or else we'll be expanding the wrong variables.
2223 # refactored, or else we'll be expanding the wrong variables.
2226 stack_depth = 2
2224 stack_depth = 2
2227 magic_arg_s = self.var_expand(line, stack_depth)
2225 magic_arg_s = self.var_expand(line, stack_depth)
2228 # Put magic args in a list so we can call with f(*a) syntax
2226 # Put magic args in a list so we can call with f(*a) syntax
2229 args = [magic_arg_s]
2227 args = [magic_arg_s]
2230 kwargs = {}
2228 kwargs = {}
2231 # Grab local namespace if we need it:
2229 # Grab local namespace if we need it:
2232 if getattr(fn, "needs_local_scope", False):
2230 if getattr(fn, "needs_local_scope", False):
2233 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
2231 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
2234 with self.builtin_trap:
2232 with self.builtin_trap:
2235 result = fn(*args,**kwargs)
2233 result = fn(*args,**kwargs)
2236 return result
2234 return result
2237
2235
2238 def run_cell_magic(self, magic_name, line, cell):
2236 def run_cell_magic(self, magic_name, line, cell):
2239 """Execute the given cell magic.
2237 """Execute the given cell magic.
2240
2238
2241 Parameters
2239 Parameters
2242 ----------
2240 ----------
2243 magic_name : str
2241 magic_name : str
2244 Name of the desired magic function, without '%' prefix.
2242 Name of the desired magic function, without '%' prefix.
2245
2243
2246 line : str
2244 line : str
2247 The rest of the first input line as a single string.
2245 The rest of the first input line as a single string.
2248
2246
2249 cell : str
2247 cell : str
2250 The body of the cell as a (possibly multiline) string.
2248 The body of the cell as a (possibly multiline) string.
2251 """
2249 """
2252 fn = self.find_cell_magic(magic_name)
2250 fn = self.find_cell_magic(magic_name)
2253 if fn is None:
2251 if fn is None:
2254 lm = self.find_line_magic(magic_name)
2252 lm = self.find_line_magic(magic_name)
2255 etpl = "Cell magic `%%{0}` not found{1}."
2253 etpl = "Cell magic `%%{0}` not found{1}."
2256 extra = '' if lm is None else (' (But line magic `%{0}` exists, '
2254 extra = '' if lm is None else (' (But line magic `%{0}` exists, '
2257 'did you mean that instead?)'.format(magic_name))
2255 'did you mean that instead?)'.format(magic_name))
2258 error(etpl.format(magic_name, extra))
2256 error(etpl.format(magic_name, extra))
2259 elif cell == '':
2257 elif cell == '':
2260 message = '%%{0} is a cell magic, but the cell body is empty.'.format(magic_name)
2258 message = '%%{0} is a cell magic, but the cell body is empty.'.format(magic_name)
2261 if self.find_line_magic(magic_name) is not None:
2259 if self.find_line_magic(magic_name) is not None:
2262 message += ' Did you mean the line magic %{0} (single %)?'.format(magic_name)
2260 message += ' Did you mean the line magic %{0} (single %)?'.format(magic_name)
2263 raise UsageError(message)
2261 raise UsageError(message)
2264 else:
2262 else:
2265 # Note: this is the distance in the stack to the user's frame.
2263 # Note: this is the distance in the stack to the user's frame.
2266 # This will need to be updated if the internal calling logic gets
2264 # This will need to be updated if the internal calling logic gets
2267 # refactored, or else we'll be expanding the wrong variables.
2265 # refactored, or else we'll be expanding the wrong variables.
2268 stack_depth = 2
2266 stack_depth = 2
2269 magic_arg_s = self.var_expand(line, stack_depth)
2267 magic_arg_s = self.var_expand(line, stack_depth)
2270 with self.builtin_trap:
2268 with self.builtin_trap:
2271 result = fn(magic_arg_s, cell)
2269 result = fn(magic_arg_s, cell)
2272 return result
2270 return result
2273
2271
2274 def find_line_magic(self, magic_name):
2272 def find_line_magic(self, magic_name):
2275 """Find and return a line magic by name.
2273 """Find and return a line magic by name.
2276
2274
2277 Returns None if the magic isn't found."""
2275 Returns None if the magic isn't found."""
2278 return self.magics_manager.magics['line'].get(magic_name)
2276 return self.magics_manager.magics['line'].get(magic_name)
2279
2277
2280 def find_cell_magic(self, magic_name):
2278 def find_cell_magic(self, magic_name):
2281 """Find and return a cell magic by name.
2279 """Find and return a cell magic by name.
2282
2280
2283 Returns None if the magic isn't found."""
2281 Returns None if the magic isn't found."""
2284 return self.magics_manager.magics['cell'].get(magic_name)
2282 return self.magics_manager.magics['cell'].get(magic_name)
2285
2283
2286 def find_magic(self, magic_name, magic_kind='line'):
2284 def find_magic(self, magic_name, magic_kind='line'):
2287 """Find and return a magic of the given type by name.
2285 """Find and return a magic of the given type by name.
2288
2286
2289 Returns None if the magic isn't found."""
2287 Returns None if the magic isn't found."""
2290 return self.magics_manager.magics[magic_kind].get(magic_name)
2288 return self.magics_manager.magics[magic_kind].get(magic_name)
2291
2289
2292 def magic(self, arg_s):
2290 def magic(self, arg_s):
2293 """DEPRECATED. Use run_line_magic() instead.
2291 """DEPRECATED. Use run_line_magic() instead.
2294
2292
2295 Call a magic function by name.
2293 Call a magic function by name.
2296
2294
2297 Input: a string containing the name of the magic function to call and
2295 Input: a string containing the name of the magic function to call and
2298 any additional arguments to be passed to the magic.
2296 any additional arguments to be passed to the magic.
2299
2297
2300 magic('name -opt foo bar') is equivalent to typing at the ipython
2298 magic('name -opt foo bar') is equivalent to typing at the ipython
2301 prompt:
2299 prompt:
2302
2300
2303 In[1]: %name -opt foo bar
2301 In[1]: %name -opt foo bar
2304
2302
2305 To call a magic without arguments, simply use magic('name').
2303 To call a magic without arguments, simply use magic('name').
2306
2304
2307 This provides a proper Python function to call IPython's magics in any
2305 This provides a proper Python function to call IPython's magics in any
2308 valid Python code you can type at the interpreter, including loops and
2306 valid Python code you can type at the interpreter, including loops and
2309 compound statements.
2307 compound statements.
2310 """
2308 """
2311 # TODO: should we issue a loud deprecation warning here?
2309 # TODO: should we issue a loud deprecation warning here?
2312 magic_name, _, magic_arg_s = arg_s.partition(' ')
2310 magic_name, _, magic_arg_s = arg_s.partition(' ')
2313 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2311 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2314 return self.run_line_magic(magic_name, magic_arg_s)
2312 return self.run_line_magic(magic_name, magic_arg_s)
2315
2313
2316 #-------------------------------------------------------------------------
2314 #-------------------------------------------------------------------------
2317 # Things related to macros
2315 # Things related to macros
2318 #-------------------------------------------------------------------------
2316 #-------------------------------------------------------------------------
2319
2317
2320 def define_macro(self, name, themacro):
2318 def define_macro(self, name, themacro):
2321 """Define a new macro
2319 """Define a new macro
2322
2320
2323 Parameters
2321 Parameters
2324 ----------
2322 ----------
2325 name : str
2323 name : str
2326 The name of the macro.
2324 The name of the macro.
2327 themacro : str or Macro
2325 themacro : str or Macro
2328 The action to do upon invoking the macro. If a string, a new
2326 The action to do upon invoking the macro. If a string, a new
2329 Macro object is created by passing the string to it.
2327 Macro object is created by passing the string to it.
2330 """
2328 """
2331
2329
2332 from IPython.core import macro
2330 from IPython.core import macro
2333
2331
2334 if isinstance(themacro, string_types):
2332 if isinstance(themacro, string_types):
2335 themacro = macro.Macro(themacro)
2333 themacro = macro.Macro(themacro)
2336 if not isinstance(themacro, macro.Macro):
2334 if not isinstance(themacro, macro.Macro):
2337 raise ValueError('A macro must be a string or a Macro instance.')
2335 raise ValueError('A macro must be a string or a Macro instance.')
2338 self.user_ns[name] = themacro
2336 self.user_ns[name] = themacro
2339
2337
2340 #-------------------------------------------------------------------------
2338 #-------------------------------------------------------------------------
2341 # Things related to the running of system commands
2339 # Things related to the running of system commands
2342 #-------------------------------------------------------------------------
2340 #-------------------------------------------------------------------------
2343
2341
2344 def system_piped(self, cmd):
2342 def system_piped(self, cmd):
2345 """Call the given cmd in a subprocess, piping stdout/err
2343 """Call the given cmd in a subprocess, piping stdout/err
2346
2344
2347 Parameters
2345 Parameters
2348 ----------
2346 ----------
2349 cmd : str
2347 cmd : str
2350 Command to execute (can not end in '&', as background processes are
2348 Command to execute (can not end in '&', as background processes are
2351 not supported. Should not be a command that expects input
2349 not supported. Should not be a command that expects input
2352 other than simple text.
2350 other than simple text.
2353 """
2351 """
2354 if cmd.rstrip().endswith('&'):
2352 if cmd.rstrip().endswith('&'):
2355 # this is *far* from a rigorous test
2353 # this is *far* from a rigorous test
2356 # We do not support backgrounding processes because we either use
2354 # We do not support backgrounding processes because we either use
2357 # pexpect or pipes to read from. Users can always just call
2355 # pexpect or pipes to read from. Users can always just call
2358 # os.system() or use ip.system=ip.system_raw
2356 # os.system() or use ip.system=ip.system_raw
2359 # if they really want a background process.
2357 # if they really want a background process.
2360 raise OSError("Background processes not supported.")
2358 raise OSError("Background processes not supported.")
2361
2359
2362 # we explicitly do NOT return the subprocess status code, because
2360 # we explicitly do NOT return the subprocess status code, because
2363 # a non-None value would trigger :func:`sys.displayhook` calls.
2361 # a non-None value would trigger :func:`sys.displayhook` calls.
2364 # Instead, we store the exit_code in user_ns.
2362 # Instead, we store the exit_code in user_ns.
2365 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2363 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2366
2364
2367 def system_raw(self, cmd):
2365 def system_raw(self, cmd):
2368 """Call the given cmd in a subprocess using os.system on Windows or
2366 """Call the given cmd in a subprocess using os.system on Windows or
2369 subprocess.call using the system shell on other platforms.
2367 subprocess.call using the system shell on other platforms.
2370
2368
2371 Parameters
2369 Parameters
2372 ----------
2370 ----------
2373 cmd : str
2371 cmd : str
2374 Command to execute.
2372 Command to execute.
2375 """
2373 """
2376 cmd = self.var_expand(cmd, depth=1)
2374 cmd = self.var_expand(cmd, depth=1)
2377 # protect os.system from UNC paths on Windows, which it can't handle:
2375 # protect os.system from UNC paths on Windows, which it can't handle:
2378 if sys.platform == 'win32':
2376 if sys.platform == 'win32':
2379 from IPython.utils._process_win32 import AvoidUNCPath
2377 from IPython.utils._process_win32 import AvoidUNCPath
2380 with AvoidUNCPath() as path:
2378 with AvoidUNCPath() as path:
2381 if path is not None:
2379 if path is not None:
2382 cmd = '"pushd %s &&"%s' % (path, cmd)
2380 cmd = '"pushd %s &&"%s' % (path, cmd)
2383 cmd = py3compat.unicode_to_str(cmd)
2381 cmd = py3compat.unicode_to_str(cmd)
2384 try:
2382 try:
2385 ec = os.system(cmd)
2383 ec = os.system(cmd)
2386 except KeyboardInterrupt:
2384 except KeyboardInterrupt:
2387 self.write_err('\n' + self.get_exception_only())
2385 self.write_err('\n' + self.get_exception_only())
2388 ec = -2
2386 ec = -2
2389 else:
2387 else:
2390 cmd = py3compat.unicode_to_str(cmd)
2388 cmd = py3compat.unicode_to_str(cmd)
2391 # For posix the result of the subprocess.call() below is an exit
2389 # For posix the result of the subprocess.call() below is an exit
2392 # code, which by convention is zero for success, positive for
2390 # code, which by convention is zero for success, positive for
2393 # program failure. Exit codes above 128 are reserved for signals,
2391 # program failure. Exit codes above 128 are reserved for signals,
2394 # and the formula for converting a signal to an exit code is usually
2392 # and the formula for converting a signal to an exit code is usually
2395 # signal_number+128. To more easily differentiate between exit
2393 # signal_number+128. To more easily differentiate between exit
2396 # codes and signals, ipython uses negative numbers. For instance
2394 # codes and signals, ipython uses negative numbers. For instance
2397 # since control-c is signal 2 but exit code 130, ipython's
2395 # since control-c is signal 2 but exit code 130, ipython's
2398 # _exit_code variable will read -2. Note that some shells like
2396 # _exit_code variable will read -2. Note that some shells like
2399 # csh and fish don't follow sh/bash conventions for exit codes.
2397 # csh and fish don't follow sh/bash conventions for exit codes.
2400 executable = os.environ.get('SHELL', None)
2398 executable = os.environ.get('SHELL', None)
2401 try:
2399 try:
2402 # Use env shell instead of default /bin/sh
2400 # Use env shell instead of default /bin/sh
2403 ec = subprocess.call(cmd, shell=True, executable=executable)
2401 ec = subprocess.call(cmd, shell=True, executable=executable)
2404 except KeyboardInterrupt:
2402 except KeyboardInterrupt:
2405 # intercept control-C; a long traceback is not useful here
2403 # intercept control-C; a long traceback is not useful here
2406 self.write_err('\n' + self.get_exception_only())
2404 self.write_err('\n' + self.get_exception_only())
2407 ec = 130
2405 ec = 130
2408 if ec > 128:
2406 if ec > 128:
2409 ec = -(ec - 128)
2407 ec = -(ec - 128)
2410
2408
2411 # We explicitly do NOT return the subprocess status code, because
2409 # We explicitly do NOT return the subprocess status code, because
2412 # a non-None value would trigger :func:`sys.displayhook` calls.
2410 # a non-None value would trigger :func:`sys.displayhook` calls.
2413 # Instead, we store the exit_code in user_ns. Note the semantics
2411 # Instead, we store the exit_code in user_ns. Note the semantics
2414 # of _exit_code: for control-c, _exit_code == -signal.SIGNIT,
2412 # of _exit_code: for control-c, _exit_code == -signal.SIGNIT,
2415 # but raising SystemExit(_exit_code) will give status 254!
2413 # but raising SystemExit(_exit_code) will give status 254!
2416 self.user_ns['_exit_code'] = ec
2414 self.user_ns['_exit_code'] = ec
2417
2415
2418 # use piped system by default, because it is better behaved
2416 # use piped system by default, because it is better behaved
2419 system = system_piped
2417 system = system_piped
2420
2418
2421 def getoutput(self, cmd, split=True, depth=0):
2419 def getoutput(self, cmd, split=True, depth=0):
2422 """Get output (possibly including stderr) from a subprocess.
2420 """Get output (possibly including stderr) from a subprocess.
2423
2421
2424 Parameters
2422 Parameters
2425 ----------
2423 ----------
2426 cmd : str
2424 cmd : str
2427 Command to execute (can not end in '&', as background processes are
2425 Command to execute (can not end in '&', as background processes are
2428 not supported.
2426 not supported.
2429 split : bool, optional
2427 split : bool, optional
2430 If True, split the output into an IPython SList. Otherwise, an
2428 If True, split the output into an IPython SList. Otherwise, an
2431 IPython LSString is returned. These are objects similar to normal
2429 IPython LSString is returned. These are objects similar to normal
2432 lists and strings, with a few convenience attributes for easier
2430 lists and strings, with a few convenience attributes for easier
2433 manipulation of line-based output. You can use '?' on them for
2431 manipulation of line-based output. You can use '?' on them for
2434 details.
2432 details.
2435 depth : int, optional
2433 depth : int, optional
2436 How many frames above the caller are the local variables which should
2434 How many frames above the caller are the local variables which should
2437 be expanded in the command string? The default (0) assumes that the
2435 be expanded in the command string? The default (0) assumes that the
2438 expansion variables are in the stack frame calling this function.
2436 expansion variables are in the stack frame calling this function.
2439 """
2437 """
2440 if cmd.rstrip().endswith('&'):
2438 if cmd.rstrip().endswith('&'):
2441 # this is *far* from a rigorous test
2439 # this is *far* from a rigorous test
2442 raise OSError("Background processes not supported.")
2440 raise OSError("Background processes not supported.")
2443 out = getoutput(self.var_expand(cmd, depth=depth+1))
2441 out = getoutput(self.var_expand(cmd, depth=depth+1))
2444 if split:
2442 if split:
2445 out = SList(out.splitlines())
2443 out = SList(out.splitlines())
2446 else:
2444 else:
2447 out = LSString(out)
2445 out = LSString(out)
2448 return out
2446 return out
2449
2447
2450 #-------------------------------------------------------------------------
2448 #-------------------------------------------------------------------------
2451 # Things related to aliases
2449 # Things related to aliases
2452 #-------------------------------------------------------------------------
2450 #-------------------------------------------------------------------------
2453
2451
2454 def init_alias(self):
2452 def init_alias(self):
2455 self.alias_manager = AliasManager(shell=self, parent=self)
2453 self.alias_manager = AliasManager(shell=self, parent=self)
2456 self.configurables.append(self.alias_manager)
2454 self.configurables.append(self.alias_manager)
2457
2455
2458 #-------------------------------------------------------------------------
2456 #-------------------------------------------------------------------------
2459 # Things related to extensions
2457 # Things related to extensions
2460 #-------------------------------------------------------------------------
2458 #-------------------------------------------------------------------------
2461
2459
2462 def init_extension_manager(self):
2460 def init_extension_manager(self):
2463 self.extension_manager = ExtensionManager(shell=self, parent=self)
2461 self.extension_manager = ExtensionManager(shell=self, parent=self)
2464 self.configurables.append(self.extension_manager)
2462 self.configurables.append(self.extension_manager)
2465
2463
2466 #-------------------------------------------------------------------------
2464 #-------------------------------------------------------------------------
2467 # Things related to payloads
2465 # Things related to payloads
2468 #-------------------------------------------------------------------------
2466 #-------------------------------------------------------------------------
2469
2467
2470 def init_payload(self):
2468 def init_payload(self):
2471 self.payload_manager = PayloadManager(parent=self)
2469 self.payload_manager = PayloadManager(parent=self)
2472 self.configurables.append(self.payload_manager)
2470 self.configurables.append(self.payload_manager)
2473
2471
2474 #-------------------------------------------------------------------------
2472 #-------------------------------------------------------------------------
2475 # Things related to the prefilter
2473 # Things related to the prefilter
2476 #-------------------------------------------------------------------------
2474 #-------------------------------------------------------------------------
2477
2475
2478 def init_prefilter(self):
2476 def init_prefilter(self):
2479 self.prefilter_manager = PrefilterManager(shell=self, parent=self)
2477 self.prefilter_manager = PrefilterManager(shell=self, parent=self)
2480 self.configurables.append(self.prefilter_manager)
2478 self.configurables.append(self.prefilter_manager)
2481 # Ultimately this will be refactored in the new interpreter code, but
2479 # Ultimately this will be refactored in the new interpreter code, but
2482 # for now, we should expose the main prefilter method (there's legacy
2480 # for now, we should expose the main prefilter method (there's legacy
2483 # code out there that may rely on this).
2481 # code out there that may rely on this).
2484 self.prefilter = self.prefilter_manager.prefilter_lines
2482 self.prefilter = self.prefilter_manager.prefilter_lines
2485
2483
2486 def auto_rewrite_input(self, cmd):
2484 def auto_rewrite_input(self, cmd):
2487 """Print to the screen the rewritten form of the user's command.
2485 """Print to the screen the rewritten form of the user's command.
2488
2486
2489 This shows visual feedback by rewriting input lines that cause
2487 This shows visual feedback by rewriting input lines that cause
2490 automatic calling to kick in, like::
2488 automatic calling to kick in, like::
2491
2489
2492 /f x
2490 /f x
2493
2491
2494 into::
2492 into::
2495
2493
2496 ------> f(x)
2494 ------> f(x)
2497
2495
2498 after the user's input prompt. This helps the user understand that the
2496 after the user's input prompt. This helps the user understand that the
2499 input line was transformed automatically by IPython.
2497 input line was transformed automatically by IPython.
2500 """
2498 """
2501 if not self.show_rewritten_input:
2499 if not self.show_rewritten_input:
2502 return
2500 return
2503
2501
2504 rw = self.prompt_manager.render('rewrite') + cmd
2502 rw = self.prompt_manager.render('rewrite') + cmd
2505
2503
2506 try:
2504 try:
2507 # plain ascii works better w/ pyreadline, on some machines, so
2505 # plain ascii works better w/ pyreadline, on some machines, so
2508 # we use it and only print uncolored rewrite if we have unicode
2506 # we use it and only print uncolored rewrite if we have unicode
2509 rw = str(rw)
2507 rw = str(rw)
2510 print(rw, file=io.stdout)
2508 print(rw, file=io.stdout)
2511 except UnicodeEncodeError:
2509 except UnicodeEncodeError:
2512 print("------> " + cmd)
2510 print("------> " + cmd)
2513
2511
2514 #-------------------------------------------------------------------------
2512 #-------------------------------------------------------------------------
2515 # Things related to extracting values/expressions from kernel and user_ns
2513 # Things related to extracting values/expressions from kernel and user_ns
2516 #-------------------------------------------------------------------------
2514 #-------------------------------------------------------------------------
2517
2515
2518 def _user_obj_error(self):
2516 def _user_obj_error(self):
2519 """return simple exception dict
2517 """return simple exception dict
2520
2518
2521 for use in user_expressions
2519 for use in user_expressions
2522 """
2520 """
2523
2521
2524 etype, evalue, tb = self._get_exc_info()
2522 etype, evalue, tb = self._get_exc_info()
2525 stb = self.InteractiveTB.get_exception_only(etype, evalue)
2523 stb = self.InteractiveTB.get_exception_only(etype, evalue)
2526
2524
2527 exc_info = {
2525 exc_info = {
2528 u'status' : 'error',
2526 u'status' : 'error',
2529 u'traceback' : stb,
2527 u'traceback' : stb,
2530 u'ename' : unicode_type(etype.__name__),
2528 u'ename' : unicode_type(etype.__name__),
2531 u'evalue' : py3compat.safe_unicode(evalue),
2529 u'evalue' : py3compat.safe_unicode(evalue),
2532 }
2530 }
2533
2531
2534 return exc_info
2532 return exc_info
2535
2533
2536 def _format_user_obj(self, obj):
2534 def _format_user_obj(self, obj):
2537 """format a user object to display dict
2535 """format a user object to display dict
2538
2536
2539 for use in user_expressions
2537 for use in user_expressions
2540 """
2538 """
2541
2539
2542 data, md = self.display_formatter.format(obj)
2540 data, md = self.display_formatter.format(obj)
2543 value = {
2541 value = {
2544 'status' : 'ok',
2542 'status' : 'ok',
2545 'data' : data,
2543 'data' : data,
2546 'metadata' : md,
2544 'metadata' : md,
2547 }
2545 }
2548 return value
2546 return value
2549
2547
2550 def user_expressions(self, expressions):
2548 def user_expressions(self, expressions):
2551 """Evaluate a dict of expressions in the user's namespace.
2549 """Evaluate a dict of expressions in the user's namespace.
2552
2550
2553 Parameters
2551 Parameters
2554 ----------
2552 ----------
2555 expressions : dict
2553 expressions : dict
2556 A dict with string keys and string values. The expression values
2554 A dict with string keys and string values. The expression values
2557 should be valid Python expressions, each of which will be evaluated
2555 should be valid Python expressions, each of which will be evaluated
2558 in the user namespace.
2556 in the user namespace.
2559
2557
2560 Returns
2558 Returns
2561 -------
2559 -------
2562 A dict, keyed like the input expressions dict, with the rich mime-typed
2560 A dict, keyed like the input expressions dict, with the rich mime-typed
2563 display_data of each value.
2561 display_data of each value.
2564 """
2562 """
2565 out = {}
2563 out = {}
2566 user_ns = self.user_ns
2564 user_ns = self.user_ns
2567 global_ns = self.user_global_ns
2565 global_ns = self.user_global_ns
2568
2566
2569 for key, expr in iteritems(expressions):
2567 for key, expr in iteritems(expressions):
2570 try:
2568 try:
2571 value = self._format_user_obj(eval(expr, global_ns, user_ns))
2569 value = self._format_user_obj(eval(expr, global_ns, user_ns))
2572 except:
2570 except:
2573 value = self._user_obj_error()
2571 value = self._user_obj_error()
2574 out[key] = value
2572 out[key] = value
2575 return out
2573 return out
2576
2574
2577 #-------------------------------------------------------------------------
2575 #-------------------------------------------------------------------------
2578 # Things related to the running of code
2576 # Things related to the running of code
2579 #-------------------------------------------------------------------------
2577 #-------------------------------------------------------------------------
2580
2578
2581 def ex(self, cmd):
2579 def ex(self, cmd):
2582 """Execute a normal python statement in user namespace."""
2580 """Execute a normal python statement in user namespace."""
2583 with self.builtin_trap:
2581 with self.builtin_trap:
2584 exec(cmd, self.user_global_ns, self.user_ns)
2582 exec(cmd, self.user_global_ns, self.user_ns)
2585
2583
2586 def ev(self, expr):
2584 def ev(self, expr):
2587 """Evaluate python expression expr in user namespace.
2585 """Evaluate python expression expr in user namespace.
2588
2586
2589 Returns the result of evaluation
2587 Returns the result of evaluation
2590 """
2588 """
2591 with self.builtin_trap:
2589 with self.builtin_trap:
2592 return eval(expr, self.user_global_ns, self.user_ns)
2590 return eval(expr, self.user_global_ns, self.user_ns)
2593
2591
2594 def safe_execfile(self, fname, *where, **kw):
2592 def safe_execfile(self, fname, *where, **kw):
2595 """A safe version of the builtin execfile().
2593 """A safe version of the builtin execfile().
2596
2594
2597 This version will never throw an exception, but instead print
2595 This version will never throw an exception, but instead print
2598 helpful error messages to the screen. This only works on pure
2596 helpful error messages to the screen. This only works on pure
2599 Python files with the .py extension.
2597 Python files with the .py extension.
2600
2598
2601 Parameters
2599 Parameters
2602 ----------
2600 ----------
2603 fname : string
2601 fname : string
2604 The name of the file to be executed.
2602 The name of the file to be executed.
2605 where : tuple
2603 where : tuple
2606 One or two namespaces, passed to execfile() as (globals,locals).
2604 One or two namespaces, passed to execfile() as (globals,locals).
2607 If only one is given, it is passed as both.
2605 If only one is given, it is passed as both.
2608 exit_ignore : bool (False)
2606 exit_ignore : bool (False)
2609 If True, then silence SystemExit for non-zero status (it is always
2607 If True, then silence SystemExit for non-zero status (it is always
2610 silenced for zero status, as it is so common).
2608 silenced for zero status, as it is so common).
2611 raise_exceptions : bool (False)
2609 raise_exceptions : bool (False)
2612 If True raise exceptions everywhere. Meant for testing.
2610 If True raise exceptions everywhere. Meant for testing.
2613 shell_futures : bool (False)
2611 shell_futures : bool (False)
2614 If True, the code will share future statements with the interactive
2612 If True, the code will share future statements with the interactive
2615 shell. It will both be affected by previous __future__ imports, and
2613 shell. It will both be affected by previous __future__ imports, and
2616 any __future__ imports in the code will affect the shell. If False,
2614 any __future__ imports in the code will affect the shell. If False,
2617 __future__ imports are not shared in either direction.
2615 __future__ imports are not shared in either direction.
2618
2616
2619 """
2617 """
2620 kw.setdefault('exit_ignore', False)
2618 kw.setdefault('exit_ignore', False)
2621 kw.setdefault('raise_exceptions', False)
2619 kw.setdefault('raise_exceptions', False)
2622 kw.setdefault('shell_futures', False)
2620 kw.setdefault('shell_futures', False)
2623
2621
2624 fname = os.path.abspath(os.path.expanduser(fname))
2622 fname = os.path.abspath(os.path.expanduser(fname))
2625
2623
2626 # Make sure we can open the file
2624 # Make sure we can open the file
2627 try:
2625 try:
2628 with open(fname) as thefile:
2626 with open(fname) as thefile:
2629 pass
2627 pass
2630 except:
2628 except:
2631 warn('Could not open file <%s> for safe execution.' % fname)
2629 warn('Could not open file <%s> for safe execution.' % fname)
2632 return
2630 return
2633
2631
2634 # Find things also in current directory. This is needed to mimic the
2632 # Find things also in current directory. This is needed to mimic the
2635 # behavior of running a script from the system command line, where
2633 # behavior of running a script from the system command line, where
2636 # Python inserts the script's directory into sys.path
2634 # Python inserts the script's directory into sys.path
2637 dname = os.path.dirname(fname)
2635 dname = os.path.dirname(fname)
2638
2636
2639 with prepended_to_syspath(dname):
2637 with prepended_to_syspath(dname):
2640 try:
2638 try:
2641 glob, loc = (where + (None, ))[:2]
2639 glob, loc = (where + (None, ))[:2]
2642 py3compat.execfile(
2640 py3compat.execfile(
2643 fname, glob, loc,
2641 fname, glob, loc,
2644 self.compile if kw['shell_futures'] else None)
2642 self.compile if kw['shell_futures'] else None)
2645 except SystemExit as status:
2643 except SystemExit as status:
2646 # If the call was made with 0 or None exit status (sys.exit(0)
2644 # If the call was made with 0 or None exit status (sys.exit(0)
2647 # or sys.exit() ), don't bother showing a traceback, as both of
2645 # or sys.exit() ), don't bother showing a traceback, as both of
2648 # these are considered normal by the OS:
2646 # these are considered normal by the OS:
2649 # > python -c'import sys;sys.exit(0)'; echo $?
2647 # > python -c'import sys;sys.exit(0)'; echo $?
2650 # 0
2648 # 0
2651 # > python -c'import sys;sys.exit()'; echo $?
2649 # > python -c'import sys;sys.exit()'; echo $?
2652 # 0
2650 # 0
2653 # For other exit status, we show the exception unless
2651 # For other exit status, we show the exception unless
2654 # explicitly silenced, but only in short form.
2652 # explicitly silenced, but only in short form.
2655 if kw['raise_exceptions']:
2653 if kw['raise_exceptions']:
2656 raise
2654 raise
2657 if status.code and not kw['exit_ignore']:
2655 if status.code and not kw['exit_ignore']:
2658 self.showtraceback(exception_only=True)
2656 self.showtraceback(exception_only=True)
2659 except:
2657 except:
2660 if kw['raise_exceptions']:
2658 if kw['raise_exceptions']:
2661 raise
2659 raise
2662 # tb offset is 2 because we wrap execfile
2660 # tb offset is 2 because we wrap execfile
2663 self.showtraceback(tb_offset=2)
2661 self.showtraceback(tb_offset=2)
2664
2662
2665 def safe_execfile_ipy(self, fname, shell_futures=False):
2663 def safe_execfile_ipy(self, fname, shell_futures=False):
2666 """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
2664 """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
2667
2665
2668 Parameters
2666 Parameters
2669 ----------
2667 ----------
2670 fname : str
2668 fname : str
2671 The name of the file to execute. The filename must have a
2669 The name of the file to execute. The filename must have a
2672 .ipy or .ipynb extension.
2670 .ipy or .ipynb extension.
2673 shell_futures : bool (False)
2671 shell_futures : bool (False)
2674 If True, the code will share future statements with the interactive
2672 If True, the code will share future statements with the interactive
2675 shell. It will both be affected by previous __future__ imports, and
2673 shell. It will both be affected by previous __future__ imports, and
2676 any __future__ imports in the code will affect the shell. If False,
2674 any __future__ imports in the code will affect the shell. If False,
2677 __future__ imports are not shared in either direction.
2675 __future__ imports are not shared in either direction.
2678 """
2676 """
2679 fname = os.path.abspath(os.path.expanduser(fname))
2677 fname = os.path.abspath(os.path.expanduser(fname))
2680
2678
2681 # Make sure we can open the file
2679 # Make sure we can open the file
2682 try:
2680 try:
2683 with open(fname) as thefile:
2681 with open(fname) as thefile:
2684 pass
2682 pass
2685 except:
2683 except:
2686 warn('Could not open file <%s> for safe execution.' % fname)
2684 warn('Could not open file <%s> for safe execution.' % fname)
2687 return
2685 return
2688
2686
2689 # Find things also in current directory. This is needed to mimic the
2687 # Find things also in current directory. This is needed to mimic the
2690 # behavior of running a script from the system command line, where
2688 # behavior of running a script from the system command line, where
2691 # Python inserts the script's directory into sys.path
2689 # Python inserts the script's directory into sys.path
2692 dname = os.path.dirname(fname)
2690 dname = os.path.dirname(fname)
2693
2691
2694 def get_cells():
2692 def get_cells():
2695 """generator for sequence of code blocks to run"""
2693 """generator for sequence of code blocks to run"""
2696 if fname.endswith('.ipynb'):
2694 if fname.endswith('.ipynb'):
2697 from IPython.nbformat import read
2695 from IPython.nbformat import read
2698 with io_open(fname) as f:
2696 with io_open(fname) as f:
2699 nb = read(f, as_version=4)
2697 nb = read(f, as_version=4)
2700 if not nb.cells:
2698 if not nb.cells:
2701 return
2699 return
2702 for cell in nb.cells:
2700 for cell in nb.cells:
2703 if cell.cell_type == 'code':
2701 if cell.cell_type == 'code':
2704 yield cell.source
2702 yield cell.source
2705 else:
2703 else:
2706 with open(fname) as f:
2704 with open(fname) as f:
2707 yield f.read()
2705 yield f.read()
2708
2706
2709 with prepended_to_syspath(dname):
2707 with prepended_to_syspath(dname):
2710 try:
2708 try:
2711 for cell in get_cells():
2709 for cell in get_cells():
2712 # self.run_cell currently captures all exceptions
2710 # self.run_cell currently captures all exceptions
2713 # raised in user code. It would be nice if there were
2711 # raised in user code. It would be nice if there were
2714 # versions of run_cell that did raise, so
2712 # versions of run_cell that did raise, so
2715 # we could catch the errors.
2713 # we could catch the errors.
2716 self.run_cell(cell, silent=True, shell_futures=shell_futures)
2714 self.run_cell(cell, silent=True, shell_futures=shell_futures)
2717 except:
2715 except:
2718 self.showtraceback()
2716 self.showtraceback()
2719 warn('Unknown failure executing file: <%s>' % fname)
2717 warn('Unknown failure executing file: <%s>' % fname)
2720
2718
2721 def safe_run_module(self, mod_name, where):
2719 def safe_run_module(self, mod_name, where):
2722 """A safe version of runpy.run_module().
2720 """A safe version of runpy.run_module().
2723
2721
2724 This version will never throw an exception, but instead print
2722 This version will never throw an exception, but instead print
2725 helpful error messages to the screen.
2723 helpful error messages to the screen.
2726
2724
2727 `SystemExit` exceptions with status code 0 or None are ignored.
2725 `SystemExit` exceptions with status code 0 or None are ignored.
2728
2726
2729 Parameters
2727 Parameters
2730 ----------
2728 ----------
2731 mod_name : string
2729 mod_name : string
2732 The name of the module to be executed.
2730 The name of the module to be executed.
2733 where : dict
2731 where : dict
2734 The globals namespace.
2732 The globals namespace.
2735 """
2733 """
2736 try:
2734 try:
2737 try:
2735 try:
2738 where.update(
2736 where.update(
2739 runpy.run_module(str(mod_name), run_name="__main__",
2737 runpy.run_module(str(mod_name), run_name="__main__",
2740 alter_sys=True)
2738 alter_sys=True)
2741 )
2739 )
2742 except SystemExit as status:
2740 except SystemExit as status:
2743 if status.code:
2741 if status.code:
2744 raise
2742 raise
2745 except:
2743 except:
2746 self.showtraceback()
2744 self.showtraceback()
2747 warn('Unknown failure executing module: <%s>' % mod_name)
2745 warn('Unknown failure executing module: <%s>' % mod_name)
2748
2746
2749 def _run_cached_cell_magic(self, magic_name, line):
2747 def _run_cached_cell_magic(self, magic_name, line):
2750 """Special method to call a cell magic with the data stored in self.
2748 """Special method to call a cell magic with the data stored in self.
2751 """
2749 """
2752 cell = self._current_cell_magic_body
2750 cell = self._current_cell_magic_body
2753 self._current_cell_magic_body = None
2751 self._current_cell_magic_body = None
2754 return self.run_cell_magic(magic_name, line, cell)
2752 return self.run_cell_magic(magic_name, line, cell)
2755
2753
2756 def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True):
2754 def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True):
2757 """Run a complete IPython cell.
2755 """Run a complete IPython cell.
2758
2756
2759 Parameters
2757 Parameters
2760 ----------
2758 ----------
2761 raw_cell : str
2759 raw_cell : str
2762 The code (including IPython code such as %magic functions) to run.
2760 The code (including IPython code such as %magic functions) to run.
2763 store_history : bool
2761 store_history : bool
2764 If True, the raw and translated cell will be stored in IPython's
2762 If True, the raw and translated cell will be stored in IPython's
2765 history. For user code calling back into IPython's machinery, this
2763 history. For user code calling back into IPython's machinery, this
2766 should be set to False.
2764 should be set to False.
2767 silent : bool
2765 silent : bool
2768 If True, avoid side-effects, such as implicit displayhooks and
2766 If True, avoid side-effects, such as implicit displayhooks and
2769 and logging. silent=True forces store_history=False.
2767 and logging. silent=True forces store_history=False.
2770 shell_futures : bool
2768 shell_futures : bool
2771 If True, the code will share future statements with the interactive
2769 If True, the code will share future statements with the interactive
2772 shell. It will both be affected by previous __future__ imports, and
2770 shell. It will both be affected by previous __future__ imports, and
2773 any __future__ imports in the code will affect the shell. If False,
2771 any __future__ imports in the code will affect the shell. If False,
2774 __future__ imports are not shared in either direction.
2772 __future__ imports are not shared in either direction.
2775
2773
2776 Returns
2774 Returns
2777 -------
2775 -------
2778 result : :class:`ExecutionResult`
2776 result : :class:`ExecutionResult`
2779 """
2777 """
2780 result = ExecutionResult()
2778 result = ExecutionResult()
2781
2779
2782 if (not raw_cell) or raw_cell.isspace():
2780 if (not raw_cell) or raw_cell.isspace():
2783 return result
2781 return result
2784
2782
2785 if silent:
2783 if silent:
2786 store_history = False
2784 store_history = False
2787
2785
2788 if store_history:
2786 if store_history:
2789 result.execution_count = self.execution_count
2787 result.execution_count = self.execution_count
2790
2788
2791 def error_before_exec(value):
2789 def error_before_exec(value):
2792 result.error_before_exec = value
2790 result.error_before_exec = value
2793 return result
2791 return result
2794
2792
2795 self.events.trigger('pre_execute')
2793 self.events.trigger('pre_execute')
2796 if not silent:
2794 if not silent:
2797 self.events.trigger('pre_run_cell')
2795 self.events.trigger('pre_run_cell')
2798
2796
2799 # If any of our input transformation (input_transformer_manager or
2797 # If any of our input transformation (input_transformer_manager or
2800 # prefilter_manager) raises an exception, we store it in this variable
2798 # prefilter_manager) raises an exception, we store it in this variable
2801 # so that we can display the error after logging the input and storing
2799 # so that we can display the error after logging the input and storing
2802 # it in the history.
2800 # it in the history.
2803 preprocessing_exc_tuple = None
2801 preprocessing_exc_tuple = None
2804 try:
2802 try:
2805 # Static input transformations
2803 # Static input transformations
2806 cell = self.input_transformer_manager.transform_cell(raw_cell)
2804 cell = self.input_transformer_manager.transform_cell(raw_cell)
2807 except SyntaxError:
2805 except SyntaxError:
2808 preprocessing_exc_tuple = sys.exc_info()
2806 preprocessing_exc_tuple = sys.exc_info()
2809 cell = raw_cell # cell has to exist so it can be stored/logged
2807 cell = raw_cell # cell has to exist so it can be stored/logged
2810 else:
2808 else:
2811 if len(cell.splitlines()) == 1:
2809 if len(cell.splitlines()) == 1:
2812 # Dynamic transformations - only applied for single line commands
2810 # Dynamic transformations - only applied for single line commands
2813 with self.builtin_trap:
2811 with self.builtin_trap:
2814 try:
2812 try:
2815 # use prefilter_lines to handle trailing newlines
2813 # use prefilter_lines to handle trailing newlines
2816 # restore trailing newline for ast.parse
2814 # restore trailing newline for ast.parse
2817 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
2815 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
2818 except Exception:
2816 except Exception:
2819 # don't allow prefilter errors to crash IPython
2817 # don't allow prefilter errors to crash IPython
2820 preprocessing_exc_tuple = sys.exc_info()
2818 preprocessing_exc_tuple = sys.exc_info()
2821
2819
2822 # Store raw and processed history
2820 # Store raw and processed history
2823 if store_history:
2821 if store_history:
2824 self.history_manager.store_inputs(self.execution_count,
2822 self.history_manager.store_inputs(self.execution_count,
2825 cell, raw_cell)
2823 cell, raw_cell)
2826 if not silent:
2824 if not silent:
2827 self.logger.log(cell, raw_cell)
2825 self.logger.log(cell, raw_cell)
2828
2826
2829 # Display the exception if input processing failed.
2827 # Display the exception if input processing failed.
2830 if preprocessing_exc_tuple is not None:
2828 if preprocessing_exc_tuple is not None:
2831 self.showtraceback(preprocessing_exc_tuple)
2829 self.showtraceback(preprocessing_exc_tuple)
2832 if store_history:
2830 if store_history:
2833 self.execution_count += 1
2831 self.execution_count += 1
2834 return error_before_exec(preprocessing_exc_tuple[2])
2832 return error_before_exec(preprocessing_exc_tuple[2])
2835
2833
2836 # Our own compiler remembers the __future__ environment. If we want to
2834 # Our own compiler remembers the __future__ environment. If we want to
2837 # run code with a separate __future__ environment, use the default
2835 # run code with a separate __future__ environment, use the default
2838 # compiler
2836 # compiler
2839 compiler = self.compile if shell_futures else CachingCompiler()
2837 compiler = self.compile if shell_futures else CachingCompiler()
2840
2838
2841 with self.builtin_trap:
2839 with self.builtin_trap:
2842 cell_name = self.compile.cache(cell, self.execution_count)
2840 cell_name = self.compile.cache(cell, self.execution_count)
2843
2841
2844 with self.display_trap:
2842 with self.display_trap:
2845 # Compile to bytecode
2843 # Compile to bytecode
2846 try:
2844 try:
2847 code_ast = compiler.ast_parse(cell, filename=cell_name)
2845 code_ast = compiler.ast_parse(cell, filename=cell_name)
2848 except IndentationError as e:
2846 except IndentationError as e:
2849 self.showindentationerror()
2847 self.showindentationerror()
2850 if store_history:
2848 if store_history:
2851 self.execution_count += 1
2849 self.execution_count += 1
2852 return error_before_exec(e)
2850 return error_before_exec(e)
2853 except (OverflowError, SyntaxError, ValueError, TypeError,
2851 except (OverflowError, SyntaxError, ValueError, TypeError,
2854 MemoryError) as e:
2852 MemoryError) as e:
2855 self.showsyntaxerror()
2853 self.showsyntaxerror()
2856 if store_history:
2854 if store_history:
2857 self.execution_count += 1
2855 self.execution_count += 1
2858 return error_before_exec(e)
2856 return error_before_exec(e)
2859
2857
2860 # Apply AST transformations
2858 # Apply AST transformations
2861 try:
2859 try:
2862 code_ast = self.transform_ast(code_ast)
2860 code_ast = self.transform_ast(code_ast)
2863 except InputRejected as e:
2861 except InputRejected as e:
2864 self.showtraceback()
2862 self.showtraceback()
2865 if store_history:
2863 if store_history:
2866 self.execution_count += 1
2864 self.execution_count += 1
2867 return error_before_exec(e)
2865 return error_before_exec(e)
2868
2866
2869 # Give the displayhook a reference to our ExecutionResult so it
2867 # Give the displayhook a reference to our ExecutionResult so it
2870 # can fill in the output value.
2868 # can fill in the output value.
2871 self.displayhook.exec_result = result
2869 self.displayhook.exec_result = result
2872
2870
2873 # Execute the user code
2871 # Execute the user code
2874 interactivity = "none" if silent else self.ast_node_interactivity
2872 interactivity = "none" if silent else self.ast_node_interactivity
2875 self.run_ast_nodes(code_ast.body, cell_name,
2873 self.run_ast_nodes(code_ast.body, cell_name,
2876 interactivity=interactivity, compiler=compiler, result=result)
2874 interactivity=interactivity, compiler=compiler, result=result)
2877
2875
2878 # Reset this so later displayed values do not modify the
2876 # Reset this so later displayed values do not modify the
2879 # ExecutionResult
2877 # ExecutionResult
2880 self.displayhook.exec_result = None
2878 self.displayhook.exec_result = None
2881
2879
2882 self.events.trigger('post_execute')
2880 self.events.trigger('post_execute')
2883 if not silent:
2881 if not silent:
2884 self.events.trigger('post_run_cell')
2882 self.events.trigger('post_run_cell')
2885
2883
2886 if store_history:
2884 if store_history:
2887 # Write output to the database. Does nothing unless
2885 # Write output to the database. Does nothing unless
2888 # history output logging is enabled.
2886 # history output logging is enabled.
2889 self.history_manager.store_output(self.execution_count)
2887 self.history_manager.store_output(self.execution_count)
2890 # Each cell is a *single* input, regardless of how many lines it has
2888 # Each cell is a *single* input, regardless of how many lines it has
2891 self.execution_count += 1
2889 self.execution_count += 1
2892
2890
2893 return result
2891 return result
2894
2892
2895 def transform_ast(self, node):
2893 def transform_ast(self, node):
2896 """Apply the AST transformations from self.ast_transformers
2894 """Apply the AST transformations from self.ast_transformers
2897
2895
2898 Parameters
2896 Parameters
2899 ----------
2897 ----------
2900 node : ast.Node
2898 node : ast.Node
2901 The root node to be transformed. Typically called with the ast.Module
2899 The root node to be transformed. Typically called with the ast.Module
2902 produced by parsing user input.
2900 produced by parsing user input.
2903
2901
2904 Returns
2902 Returns
2905 -------
2903 -------
2906 An ast.Node corresponding to the node it was called with. Note that it
2904 An ast.Node corresponding to the node it was called with. Note that it
2907 may also modify the passed object, so don't rely on references to the
2905 may also modify the passed object, so don't rely on references to the
2908 original AST.
2906 original AST.
2909 """
2907 """
2910 for transformer in self.ast_transformers:
2908 for transformer in self.ast_transformers:
2911 try:
2909 try:
2912 node = transformer.visit(node)
2910 node = transformer.visit(node)
2913 except InputRejected:
2911 except InputRejected:
2914 # User-supplied AST transformers can reject an input by raising
2912 # User-supplied AST transformers can reject an input by raising
2915 # an InputRejected. Short-circuit in this case so that we
2913 # an InputRejected. Short-circuit in this case so that we
2916 # don't unregister the transform.
2914 # don't unregister the transform.
2917 raise
2915 raise
2918 except Exception:
2916 except Exception:
2919 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2917 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2920 self.ast_transformers.remove(transformer)
2918 self.ast_transformers.remove(transformer)
2921
2919
2922 if self.ast_transformers:
2920 if self.ast_transformers:
2923 ast.fix_missing_locations(node)
2921 ast.fix_missing_locations(node)
2924 return node
2922 return node
2925
2923
2926
2924
2927 def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr',
2925 def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr',
2928 compiler=compile, result=None):
2926 compiler=compile, result=None):
2929 """Run a sequence of AST nodes. The execution mode depends on the
2927 """Run a sequence of AST nodes. The execution mode depends on the
2930 interactivity parameter.
2928 interactivity parameter.
2931
2929
2932 Parameters
2930 Parameters
2933 ----------
2931 ----------
2934 nodelist : list
2932 nodelist : list
2935 A sequence of AST nodes to run.
2933 A sequence of AST nodes to run.
2936 cell_name : str
2934 cell_name : str
2937 Will be passed to the compiler as the filename of the cell. Typically
2935 Will be passed to the compiler as the filename of the cell. Typically
2938 the value returned by ip.compile.cache(cell).
2936 the value returned by ip.compile.cache(cell).
2939 interactivity : str
2937 interactivity : str
2940 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
2938 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
2941 run interactively (displaying output from expressions). 'last_expr'
2939 run interactively (displaying output from expressions). 'last_expr'
2942 will run the last node interactively only if it is an expression (i.e.
2940 will run the last node interactively only if it is an expression (i.e.
2943 expressions in loops or other blocks are not displayed. Other values
2941 expressions in loops or other blocks are not displayed. Other values
2944 for this parameter will raise a ValueError.
2942 for this parameter will raise a ValueError.
2945 compiler : callable
2943 compiler : callable
2946 A function with the same interface as the built-in compile(), to turn
2944 A function with the same interface as the built-in compile(), to turn
2947 the AST nodes into code objects. Default is the built-in compile().
2945 the AST nodes into code objects. Default is the built-in compile().
2948 result : ExecutionResult, optional
2946 result : ExecutionResult, optional
2949 An object to store exceptions that occur during execution.
2947 An object to store exceptions that occur during execution.
2950
2948
2951 Returns
2949 Returns
2952 -------
2950 -------
2953 True if an exception occurred while running code, False if it finished
2951 True if an exception occurred while running code, False if it finished
2954 running.
2952 running.
2955 """
2953 """
2956 if not nodelist:
2954 if not nodelist:
2957 return
2955 return
2958
2956
2959 if interactivity == 'last_expr':
2957 if interactivity == 'last_expr':
2960 if isinstance(nodelist[-1], ast.Expr):
2958 if isinstance(nodelist[-1], ast.Expr):
2961 interactivity = "last"
2959 interactivity = "last"
2962 else:
2960 else:
2963 interactivity = "none"
2961 interactivity = "none"
2964
2962
2965 if interactivity == 'none':
2963 if interactivity == 'none':
2966 to_run_exec, to_run_interactive = nodelist, []
2964 to_run_exec, to_run_interactive = nodelist, []
2967 elif interactivity == 'last':
2965 elif interactivity == 'last':
2968 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2966 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2969 elif interactivity == 'all':
2967 elif interactivity == 'all':
2970 to_run_exec, to_run_interactive = [], nodelist
2968 to_run_exec, to_run_interactive = [], nodelist
2971 else:
2969 else:
2972 raise ValueError("Interactivity was %r" % interactivity)
2970 raise ValueError("Interactivity was %r" % interactivity)
2973
2971
2974 exec_count = self.execution_count
2972 exec_count = self.execution_count
2975
2973
2976 try:
2974 try:
2977 for i, node in enumerate(to_run_exec):
2975 for i, node in enumerate(to_run_exec):
2978 mod = ast.Module([node])
2976 mod = ast.Module([node])
2979 code = compiler(mod, cell_name, "exec")
2977 code = compiler(mod, cell_name, "exec")
2980 if self.run_code(code, result):
2978 if self.run_code(code, result):
2981 return True
2979 return True
2982
2980
2983 for i, node in enumerate(to_run_interactive):
2981 for i, node in enumerate(to_run_interactive):
2984 mod = ast.Interactive([node])
2982 mod = ast.Interactive([node])
2985 code = compiler(mod, cell_name, "single")
2983 code = compiler(mod, cell_name, "single")
2986 if self.run_code(code, result):
2984 if self.run_code(code, result):
2987 return True
2985 return True
2988
2986
2989 # Flush softspace
2987 # Flush softspace
2990 if softspace(sys.stdout, 0):
2988 if softspace(sys.stdout, 0):
2991 print()
2989 print()
2992
2990
2993 except:
2991 except:
2994 # It's possible to have exceptions raised here, typically by
2992 # It's possible to have exceptions raised here, typically by
2995 # compilation of odd code (such as a naked 'return' outside a
2993 # compilation of odd code (such as a naked 'return' outside a
2996 # function) that did parse but isn't valid. Typically the exception
2994 # function) that did parse but isn't valid. Typically the exception
2997 # is a SyntaxError, but it's safest just to catch anything and show
2995 # is a SyntaxError, but it's safest just to catch anything and show
2998 # the user a traceback.
2996 # the user a traceback.
2999
2997
3000 # We do only one try/except outside the loop to minimize the impact
2998 # We do only one try/except outside the loop to minimize the impact
3001 # on runtime, and also because if any node in the node list is
2999 # on runtime, and also because if any node in the node list is
3002 # broken, we should stop execution completely.
3000 # broken, we should stop execution completely.
3003 if result:
3001 if result:
3004 result.error_before_exec = sys.exc_info()[1]
3002 result.error_before_exec = sys.exc_info()[1]
3005 self.showtraceback()
3003 self.showtraceback()
3006 return True
3004 return True
3007
3005
3008 return False
3006 return False
3009
3007
3010 def run_code(self, code_obj, result=None):
3008 def run_code(self, code_obj, result=None):
3011 """Execute a code object.
3009 """Execute a code object.
3012
3010
3013 When an exception occurs, self.showtraceback() is called to display a
3011 When an exception occurs, self.showtraceback() is called to display a
3014 traceback.
3012 traceback.
3015
3013
3016 Parameters
3014 Parameters
3017 ----------
3015 ----------
3018 code_obj : code object
3016 code_obj : code object
3019 A compiled code object, to be executed
3017 A compiled code object, to be executed
3020 result : ExecutionResult, optional
3018 result : ExecutionResult, optional
3021 An object to store exceptions that occur during execution.
3019 An object to store exceptions that occur during execution.
3022
3020
3023 Returns
3021 Returns
3024 -------
3022 -------
3025 False : successful execution.
3023 False : successful execution.
3026 True : an error occurred.
3024 True : an error occurred.
3027 """
3025 """
3028 # Set our own excepthook in case the user code tries to call it
3026 # Set our own excepthook in case the user code tries to call it
3029 # directly, so that the IPython crash handler doesn't get triggered
3027 # directly, so that the IPython crash handler doesn't get triggered
3030 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
3028 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
3031
3029
3032 # we save the original sys.excepthook in the instance, in case config
3030 # we save the original sys.excepthook in the instance, in case config
3033 # code (such as magics) needs access to it.
3031 # code (such as magics) needs access to it.
3034 self.sys_excepthook = old_excepthook
3032 self.sys_excepthook = old_excepthook
3035 outflag = 1 # happens in more places, so it's easier as default
3033 outflag = 1 # happens in more places, so it's easier as default
3036 try:
3034 try:
3037 try:
3035 try:
3038 self.hooks.pre_run_code_hook()
3036 self.hooks.pre_run_code_hook()
3039 #rprint('Running code', repr(code_obj)) # dbg
3037 #rprint('Running code', repr(code_obj)) # dbg
3040 exec(code_obj, self.user_global_ns, self.user_ns)
3038 exec(code_obj, self.user_global_ns, self.user_ns)
3041 finally:
3039 finally:
3042 # Reset our crash handler in place
3040 # Reset our crash handler in place
3043 sys.excepthook = old_excepthook
3041 sys.excepthook = old_excepthook
3044 except SystemExit as e:
3042 except SystemExit as e:
3045 if result is not None:
3043 if result is not None:
3046 result.error_in_exec = e
3044 result.error_in_exec = e
3047 self.showtraceback(exception_only=True)
3045 self.showtraceback(exception_only=True)
3048 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
3046 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
3049 except self.custom_exceptions:
3047 except self.custom_exceptions:
3050 etype, value, tb = sys.exc_info()
3048 etype, value, tb = sys.exc_info()
3051 if result is not None:
3049 if result is not None:
3052 result.error_in_exec = value
3050 result.error_in_exec = value
3053 self.CustomTB(etype, value, tb)
3051 self.CustomTB(etype, value, tb)
3054 except:
3052 except:
3055 if result is not None:
3053 if result is not None:
3056 result.error_in_exec = sys.exc_info()[1]
3054 result.error_in_exec = sys.exc_info()[1]
3057 self.showtraceback()
3055 self.showtraceback()
3058 else:
3056 else:
3059 outflag = 0
3057 outflag = 0
3060 return outflag
3058 return outflag
3061
3059
3062 # For backwards compatibility
3060 # For backwards compatibility
3063 runcode = run_code
3061 runcode = run_code
3064
3062
3065 #-------------------------------------------------------------------------
3063 #-------------------------------------------------------------------------
3066 # Things related to GUI support and pylab
3064 # Things related to GUI support and pylab
3067 #-------------------------------------------------------------------------
3065 #-------------------------------------------------------------------------
3068
3066
3069 def enable_gui(self, gui=None):
3067 def enable_gui(self, gui=None):
3070 raise NotImplementedError('Implement enable_gui in a subclass')
3068 raise NotImplementedError('Implement enable_gui in a subclass')
3071
3069
3072 def enable_matplotlib(self, gui=None):
3070 def enable_matplotlib(self, gui=None):
3073 """Enable interactive matplotlib and inline figure support.
3071 """Enable interactive matplotlib and inline figure support.
3074
3072
3075 This takes the following steps:
3073 This takes the following steps:
3076
3074
3077 1. select the appropriate eventloop and matplotlib backend
3075 1. select the appropriate eventloop and matplotlib backend
3078 2. set up matplotlib for interactive use with that backend
3076 2. set up matplotlib for interactive use with that backend
3079 3. configure formatters for inline figure display
3077 3. configure formatters for inline figure display
3080 4. enable the selected gui eventloop
3078 4. enable the selected gui eventloop
3081
3079
3082 Parameters
3080 Parameters
3083 ----------
3081 ----------
3084 gui : optional, string
3082 gui : optional, string
3085 If given, dictates the choice of matplotlib GUI backend to use
3083 If given, dictates the choice of matplotlib GUI backend to use
3086 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3084 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3087 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3085 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3088 matplotlib (as dictated by the matplotlib build-time options plus the
3086 matplotlib (as dictated by the matplotlib build-time options plus the
3089 user's matplotlibrc configuration file). Note that not all backends
3087 user's matplotlibrc configuration file). Note that not all backends
3090 make sense in all contexts, for example a terminal ipython can't
3088 make sense in all contexts, for example a terminal ipython can't
3091 display figures inline.
3089 display figures inline.
3092 """
3090 """
3093 from IPython.core import pylabtools as pt
3091 from IPython.core import pylabtools as pt
3094 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3092 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3095
3093
3096 if gui != 'inline':
3094 if gui != 'inline':
3097 # If we have our first gui selection, store it
3095 # If we have our first gui selection, store it
3098 if self.pylab_gui_select is None:
3096 if self.pylab_gui_select is None:
3099 self.pylab_gui_select = gui
3097 self.pylab_gui_select = gui
3100 # Otherwise if they are different
3098 # Otherwise if they are different
3101 elif gui != self.pylab_gui_select:
3099 elif gui != self.pylab_gui_select:
3102 print ('Warning: Cannot change to a different GUI toolkit: %s.'
3100 print ('Warning: Cannot change to a different GUI toolkit: %s.'
3103 ' Using %s instead.' % (gui, self.pylab_gui_select))
3101 ' Using %s instead.' % (gui, self.pylab_gui_select))
3104 gui, backend = pt.find_gui_and_backend(self.pylab_gui_select)
3102 gui, backend = pt.find_gui_and_backend(self.pylab_gui_select)
3105
3103
3106 pt.activate_matplotlib(backend)
3104 pt.activate_matplotlib(backend)
3107 pt.configure_inline_support(self, backend)
3105 pt.configure_inline_support(self, backend)
3108
3106
3109 # Now we must activate the gui pylab wants to use, and fix %run to take
3107 # Now we must activate the gui pylab wants to use, and fix %run to take
3110 # plot updates into account
3108 # plot updates into account
3111 self.enable_gui(gui)
3109 self.enable_gui(gui)
3112 self.magics_manager.registry['ExecutionMagics'].default_runner = \
3110 self.magics_manager.registry['ExecutionMagics'].default_runner = \
3113 pt.mpl_runner(self.safe_execfile)
3111 pt.mpl_runner(self.safe_execfile)
3114
3112
3115 return gui, backend
3113 return gui, backend
3116
3114
3117 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
3115 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
3118 """Activate pylab support at runtime.
3116 """Activate pylab support at runtime.
3119
3117
3120 This turns on support for matplotlib, preloads into the interactive
3118 This turns on support for matplotlib, preloads into the interactive
3121 namespace all of numpy and pylab, and configures IPython to correctly
3119 namespace all of numpy and pylab, and configures IPython to correctly
3122 interact with the GUI event loop. The GUI backend to be used can be
3120 interact with the GUI event loop. The GUI backend to be used can be
3123 optionally selected with the optional ``gui`` argument.
3121 optionally selected with the optional ``gui`` argument.
3124
3122
3125 This method only adds preloading the namespace to InteractiveShell.enable_matplotlib.
3123 This method only adds preloading the namespace to InteractiveShell.enable_matplotlib.
3126
3124
3127 Parameters
3125 Parameters
3128 ----------
3126 ----------
3129 gui : optional, string
3127 gui : optional, string
3130 If given, dictates the choice of matplotlib GUI backend to use
3128 If given, dictates the choice of matplotlib GUI backend to use
3131 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3129 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3132 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3130 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3133 matplotlib (as dictated by the matplotlib build-time options plus the
3131 matplotlib (as dictated by the matplotlib build-time options plus the
3134 user's matplotlibrc configuration file). Note that not all backends
3132 user's matplotlibrc configuration file). Note that not all backends
3135 make sense in all contexts, for example a terminal ipython can't
3133 make sense in all contexts, for example a terminal ipython can't
3136 display figures inline.
3134 display figures inline.
3137 import_all : optional, bool, default: True
3135 import_all : optional, bool, default: True
3138 Whether to do `from numpy import *` and `from pylab import *`
3136 Whether to do `from numpy import *` and `from pylab import *`
3139 in addition to module imports.
3137 in addition to module imports.
3140 welcome_message : deprecated
3138 welcome_message : deprecated
3141 This argument is ignored, no welcome message will be displayed.
3139 This argument is ignored, no welcome message will be displayed.
3142 """
3140 """
3143 from IPython.core.pylabtools import import_pylab
3141 from IPython.core.pylabtools import import_pylab
3144
3142
3145 gui, backend = self.enable_matplotlib(gui)
3143 gui, backend = self.enable_matplotlib(gui)
3146
3144
3147 # We want to prevent the loading of pylab to pollute the user's
3145 # We want to prevent the loading of pylab to pollute the user's
3148 # namespace as shown by the %who* magics, so we execute the activation
3146 # namespace as shown by the %who* magics, so we execute the activation
3149 # code in an empty namespace, and we update *both* user_ns and
3147 # code in an empty namespace, and we update *both* user_ns and
3150 # user_ns_hidden with this information.
3148 # user_ns_hidden with this information.
3151 ns = {}
3149 ns = {}
3152 import_pylab(ns, import_all)
3150 import_pylab(ns, import_all)
3153 # warn about clobbered names
3151 # warn about clobbered names
3154 ignored = set(["__builtins__"])
3152 ignored = set(["__builtins__"])
3155 both = set(ns).intersection(self.user_ns).difference(ignored)
3153 both = set(ns).intersection(self.user_ns).difference(ignored)
3156 clobbered = [ name for name in both if self.user_ns[name] is not ns[name] ]
3154 clobbered = [ name for name in both if self.user_ns[name] is not ns[name] ]
3157 self.user_ns.update(ns)
3155 self.user_ns.update(ns)
3158 self.user_ns_hidden.update(ns)
3156 self.user_ns_hidden.update(ns)
3159 return gui, backend, clobbered
3157 return gui, backend, clobbered
3160
3158
3161 #-------------------------------------------------------------------------
3159 #-------------------------------------------------------------------------
3162 # Utilities
3160 # Utilities
3163 #-------------------------------------------------------------------------
3161 #-------------------------------------------------------------------------
3164
3162
3165 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
3163 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
3166 """Expand python variables in a string.
3164 """Expand python variables in a string.
3167
3165
3168 The depth argument indicates how many frames above the caller should
3166 The depth argument indicates how many frames above the caller should
3169 be walked to look for the local namespace where to expand variables.
3167 be walked to look for the local namespace where to expand variables.
3170
3168
3171 The global namespace for expansion is always the user's interactive
3169 The global namespace for expansion is always the user's interactive
3172 namespace.
3170 namespace.
3173 """
3171 """
3174 ns = self.user_ns.copy()
3172 ns = self.user_ns.copy()
3175 try:
3173 try:
3176 frame = sys._getframe(depth+1)
3174 frame = sys._getframe(depth+1)
3177 except ValueError:
3175 except ValueError:
3178 # This is thrown if there aren't that many frames on the stack,
3176 # This is thrown if there aren't that many frames on the stack,
3179 # e.g. if a script called run_line_magic() directly.
3177 # e.g. if a script called run_line_magic() directly.
3180 pass
3178 pass
3181 else:
3179 else:
3182 ns.update(frame.f_locals)
3180 ns.update(frame.f_locals)
3183
3181
3184 try:
3182 try:
3185 # We have to use .vformat() here, because 'self' is a valid and common
3183 # We have to use .vformat() here, because 'self' is a valid and common
3186 # name, and expanding **ns for .format() would make it collide with
3184 # name, and expanding **ns for .format() would make it collide with
3187 # the 'self' argument of the method.
3185 # the 'self' argument of the method.
3188 cmd = formatter.vformat(cmd, args=[], kwargs=ns)
3186 cmd = formatter.vformat(cmd, args=[], kwargs=ns)
3189 except Exception:
3187 except Exception:
3190 # if formatter couldn't format, just let it go untransformed
3188 # if formatter couldn't format, just let it go untransformed
3191 pass
3189 pass
3192 return cmd
3190 return cmd
3193
3191
3194 def mktempfile(self, data=None, prefix='ipython_edit_'):
3192 def mktempfile(self, data=None, prefix='ipython_edit_'):
3195 """Make a new tempfile and return its filename.
3193 """Make a new tempfile and return its filename.
3196
3194
3197 This makes a call to tempfile.mkstemp (created in a tempfile.mkdtemp),
3195 This makes a call to tempfile.mkstemp (created in a tempfile.mkdtemp),
3198 but it registers the created filename internally so ipython cleans it up
3196 but it registers the created filename internally so ipython cleans it up
3199 at exit time.
3197 at exit time.
3200
3198
3201 Optional inputs:
3199 Optional inputs:
3202
3200
3203 - data(None): if data is given, it gets written out to the temp file
3201 - data(None): if data is given, it gets written out to the temp file
3204 immediately, and the file is closed again."""
3202 immediately, and the file is closed again."""
3205
3203
3206 dirname = tempfile.mkdtemp(prefix=prefix)
3204 dirname = tempfile.mkdtemp(prefix=prefix)
3207 self.tempdirs.append(dirname)
3205 self.tempdirs.append(dirname)
3208
3206
3209 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3207 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3210 os.close(handle) # On Windows, there can only be one open handle on a file
3208 os.close(handle) # On Windows, there can only be one open handle on a file
3211 self.tempfiles.append(filename)
3209 self.tempfiles.append(filename)
3212
3210
3213 if data:
3211 if data:
3214 tmp_file = open(filename,'w')
3212 tmp_file = open(filename,'w')
3215 tmp_file.write(data)
3213 tmp_file.write(data)
3216 tmp_file.close()
3214 tmp_file.close()
3217 return filename
3215 return filename
3218
3216
3219 # TODO: This should be removed when Term is refactored.
3217 # TODO: This should be removed when Term is refactored.
3220 def write(self,data):
3218 def write(self,data):
3221 """Write a string to the default output"""
3219 """Write a string to the default output"""
3222 io.stdout.write(data)
3220 io.stdout.write(data)
3223
3221
3224 # TODO: This should be removed when Term is refactored.
3222 # TODO: This should be removed when Term is refactored.
3225 def write_err(self,data):
3223 def write_err(self,data):
3226 """Write a string to the default error output"""
3224 """Write a string to the default error output"""
3227 io.stderr.write(data)
3225 io.stderr.write(data)
3228
3226
3229 def ask_yes_no(self, prompt, default=None):
3227 def ask_yes_no(self, prompt, default=None):
3230 if self.quiet:
3228 if self.quiet:
3231 return True
3229 return True
3232 return ask_yes_no(prompt,default)
3230 return ask_yes_no(prompt,default)
3233
3231
3234 def show_usage(self):
3232 def show_usage(self):
3235 """Show a usage message"""
3233 """Show a usage message"""
3236 page.page(IPython.core.usage.interactive_usage)
3234 page.page(IPython.core.usage.interactive_usage)
3237
3235
3238 def extract_input_lines(self, range_str, raw=False):
3236 def extract_input_lines(self, range_str, raw=False):
3239 """Return as a string a set of input history slices.
3237 """Return as a string a set of input history slices.
3240
3238
3241 Parameters
3239 Parameters
3242 ----------
3240 ----------
3243 range_str : string
3241 range_str : string
3244 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
3242 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
3245 since this function is for use by magic functions which get their
3243 since this function is for use by magic functions which get their
3246 arguments as strings. The number before the / is the session
3244 arguments as strings. The number before the / is the session
3247 number: ~n goes n back from the current session.
3245 number: ~n goes n back from the current session.
3248
3246
3249 raw : bool, optional
3247 raw : bool, optional
3250 By default, the processed input is used. If this is true, the raw
3248 By default, the processed input is used. If this is true, the raw
3251 input history is used instead.
3249 input history is used instead.
3252
3250
3253 Notes
3251 Notes
3254 -----
3252 -----
3255
3253
3256 Slices can be described with two notations:
3254 Slices can be described with two notations:
3257
3255
3258 * ``N:M`` -> standard python form, means including items N...(M-1).
3256 * ``N:M`` -> standard python form, means including items N...(M-1).
3259 * ``N-M`` -> include items N..M (closed endpoint).
3257 * ``N-M`` -> include items N..M (closed endpoint).
3260 """
3258 """
3261 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
3259 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
3262 return "\n".join(x for _, _, x in lines)
3260 return "\n".join(x for _, _, x in lines)
3263
3261
3264 def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True, search_ns=False):
3262 def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True, search_ns=False):
3265 """Get a code string from history, file, url, or a string or macro.
3263 """Get a code string from history, file, url, or a string or macro.
3266
3264
3267 This is mainly used by magic functions.
3265 This is mainly used by magic functions.
3268
3266
3269 Parameters
3267 Parameters
3270 ----------
3268 ----------
3271
3269
3272 target : str
3270 target : str
3273
3271
3274 A string specifying code to retrieve. This will be tried respectively
3272 A string specifying code to retrieve. This will be tried respectively
3275 as: ranges of input history (see %history for syntax), url,
3273 as: ranges of input history (see %history for syntax), url,
3276 correspnding .py file, filename, or an expression evaluating to a
3274 correspnding .py file, filename, or an expression evaluating to a
3277 string or Macro in the user namespace.
3275 string or Macro in the user namespace.
3278
3276
3279 raw : bool
3277 raw : bool
3280 If true (default), retrieve raw history. Has no effect on the other
3278 If true (default), retrieve raw history. Has no effect on the other
3281 retrieval mechanisms.
3279 retrieval mechanisms.
3282
3280
3283 py_only : bool (default False)
3281 py_only : bool (default False)
3284 Only try to fetch python code, do not try alternative methods to decode file
3282 Only try to fetch python code, do not try alternative methods to decode file
3285 if unicode fails.
3283 if unicode fails.
3286
3284
3287 Returns
3285 Returns
3288 -------
3286 -------
3289 A string of code.
3287 A string of code.
3290
3288
3291 ValueError is raised if nothing is found, and TypeError if it evaluates
3289 ValueError is raised if nothing is found, and TypeError if it evaluates
3292 to an object of another type. In each case, .args[0] is a printable
3290 to an object of another type. In each case, .args[0] is a printable
3293 message.
3291 message.
3294 """
3292 """
3295 code = self.extract_input_lines(target, raw=raw) # Grab history
3293 code = self.extract_input_lines(target, raw=raw) # Grab history
3296 if code:
3294 if code:
3297 return code
3295 return code
3298 utarget = unquote_filename(target)
3296 utarget = unquote_filename(target)
3299 try:
3297 try:
3300 if utarget.startswith(('http://', 'https://')):
3298 if utarget.startswith(('http://', 'https://')):
3301 return openpy.read_py_url(utarget, skip_encoding_cookie=skip_encoding_cookie)
3299 return openpy.read_py_url(utarget, skip_encoding_cookie=skip_encoding_cookie)
3302 except UnicodeDecodeError:
3300 except UnicodeDecodeError:
3303 if not py_only :
3301 if not py_only :
3304 # Deferred import
3302 # Deferred import
3305 try:
3303 try:
3306 from urllib.request import urlopen # Py3
3304 from urllib.request import urlopen # Py3
3307 except ImportError:
3305 except ImportError:
3308 from urllib import urlopen
3306 from urllib import urlopen
3309 response = urlopen(target)
3307 response = urlopen(target)
3310 return response.read().decode('latin1')
3308 return response.read().decode('latin1')
3311 raise ValueError(("'%s' seem to be unreadable.") % utarget)
3309 raise ValueError(("'%s' seem to be unreadable.") % utarget)
3312
3310
3313 potential_target = [target]
3311 potential_target = [target]
3314 try :
3312 try :
3315 potential_target.insert(0,get_py_filename(target))
3313 potential_target.insert(0,get_py_filename(target))
3316 except IOError:
3314 except IOError:
3317 pass
3315 pass
3318
3316
3319 for tgt in potential_target :
3317 for tgt in potential_target :
3320 if os.path.isfile(tgt): # Read file
3318 if os.path.isfile(tgt): # Read file
3321 try :
3319 try :
3322 return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie)
3320 return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie)
3323 except UnicodeDecodeError :
3321 except UnicodeDecodeError :
3324 if not py_only :
3322 if not py_only :
3325 with io_open(tgt,'r', encoding='latin1') as f :
3323 with io_open(tgt,'r', encoding='latin1') as f :
3326 return f.read()
3324 return f.read()
3327 raise ValueError(("'%s' seem to be unreadable.") % target)
3325 raise ValueError(("'%s' seem to be unreadable.") % target)
3328 elif os.path.isdir(os.path.expanduser(tgt)):
3326 elif os.path.isdir(os.path.expanduser(tgt)):
3329 raise ValueError("'%s' is a directory, not a regular file." % target)
3327 raise ValueError("'%s' is a directory, not a regular file." % target)
3330
3328
3331 if search_ns:
3329 if search_ns:
3332 # Inspect namespace to load object source
3330 # Inspect namespace to load object source
3333 object_info = self.object_inspect(target, detail_level=1)
3331 object_info = self.object_inspect(target, detail_level=1)
3334 if object_info['found'] and object_info['source']:
3332 if object_info['found'] and object_info['source']:
3335 return object_info['source']
3333 return object_info['source']
3336
3334
3337 try: # User namespace
3335 try: # User namespace
3338 codeobj = eval(target, self.user_ns)
3336 codeobj = eval(target, self.user_ns)
3339 except Exception:
3337 except Exception:
3340 raise ValueError(("'%s' was not found in history, as a file, url, "
3338 raise ValueError(("'%s' was not found in history, as a file, url, "
3341 "nor in the user namespace.") % target)
3339 "nor in the user namespace.") % target)
3342
3340
3343 if isinstance(codeobj, string_types):
3341 if isinstance(codeobj, string_types):
3344 return codeobj
3342 return codeobj
3345 elif isinstance(codeobj, Macro):
3343 elif isinstance(codeobj, Macro):
3346 return codeobj.value
3344 return codeobj.value
3347
3345
3348 raise TypeError("%s is neither a string nor a macro." % target,
3346 raise TypeError("%s is neither a string nor a macro." % target,
3349 codeobj)
3347 codeobj)
3350
3348
3351 #-------------------------------------------------------------------------
3349 #-------------------------------------------------------------------------
3352 # Things related to IPython exiting
3350 # Things related to IPython exiting
3353 #-------------------------------------------------------------------------
3351 #-------------------------------------------------------------------------
3354 def atexit_operations(self):
3352 def atexit_operations(self):
3355 """This will be executed at the time of exit.
3353 """This will be executed at the time of exit.
3356
3354
3357 Cleanup operations and saving of persistent data that is done
3355 Cleanup operations and saving of persistent data that is done
3358 unconditionally by IPython should be performed here.
3356 unconditionally by IPython should be performed here.
3359
3357
3360 For things that may depend on startup flags or platform specifics (such
3358 For things that may depend on startup flags or platform specifics (such
3361 as having readline or not), register a separate atexit function in the
3359 as having readline or not), register a separate atexit function in the
3362 code that has the appropriate information, rather than trying to
3360 code that has the appropriate information, rather than trying to
3363 clutter
3361 clutter
3364 """
3362 """
3365 # Close the history session (this stores the end time and line count)
3363 # Close the history session (this stores the end time and line count)
3366 # this must be *before* the tempfile cleanup, in case of temporary
3364 # this must be *before* the tempfile cleanup, in case of temporary
3367 # history db
3365 # history db
3368 self.history_manager.end_session()
3366 self.history_manager.end_session()
3369
3367
3370 # Cleanup all tempfiles and folders left around
3368 # Cleanup all tempfiles and folders left around
3371 for tfile in self.tempfiles:
3369 for tfile in self.tempfiles:
3372 try:
3370 try:
3373 os.unlink(tfile)
3371 os.unlink(tfile)
3374 except OSError:
3372 except OSError:
3375 pass
3373 pass
3376
3374
3377 for tdir in self.tempdirs:
3375 for tdir in self.tempdirs:
3378 try:
3376 try:
3379 os.rmdir(tdir)
3377 os.rmdir(tdir)
3380 except OSError:
3378 except OSError:
3381 pass
3379 pass
3382
3380
3383 # Clear all user namespaces to release all references cleanly.
3381 # Clear all user namespaces to release all references cleanly.
3384 self.reset(new_session=False)
3382 self.reset(new_session=False)
3385
3383
3386 # Run user hooks
3384 # Run user hooks
3387 self.hooks.shutdown_hook()
3385 self.hooks.shutdown_hook()
3388
3386
3389 def cleanup(self):
3387 def cleanup(self):
3390 self.restore_sys_module_state()
3388 self.restore_sys_module_state()
3391
3389
3392
3390
3393 class InteractiveShellABC(with_metaclass(abc.ABCMeta, object)):
3391 class InteractiveShellABC(with_metaclass(abc.ABCMeta, object)):
3394 """An abstract base class for InteractiveShell."""
3392 """An abstract base class for InteractiveShell."""
3395
3393
3396 InteractiveShellABC.register(InteractiveShell)
3394 InteractiveShellABC.register(InteractiveShell)
@@ -1,503 +1,507 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define(['require'
4 define(function(require){
5 ], function(require) {
6 "use strict";
5 "use strict";
7
6
8 var ActionHandler = function (env) {
7 var ActionHandler = function (env) {
9 this.env = env || {};
8 this.env = env || {};
10 Object.seal(this);
9 Object.seal(this);
11 };
10 };
12
11
13 /**
12 /**
14 * A bunch of predefined `Simple Actions` used by IPython.
13 * A bunch of predefined `Simple Actions` used by IPython.
15 * `Simple Actions` have the following keys:
14 * `Simple Actions` have the following keys:
16 * help (optional): a short string the describe the action.
15 * help (optional): a short string the describe the action.
17 * will be used in various context, like as menu name, tool tips on buttons,
16 * will be used in various context, like as menu name, tool tips on buttons,
18 * and short description in help menu.
17 * and short description in help menu.
19 * help_index (optional): a string used to sort action in help menu.
18 * help_index (optional): a string used to sort action in help menu.
20 * icon (optional): a short string that represent the icon that have to be used with this
19 * icon (optional): a short string that represent the icon that have to be used with this
21 * action. this should mainly correspond to a Font_awesome class.
20 * action. this should mainly correspond to a Font_awesome class.
22 * handler : a function which is called when the action is activated. It will receive at first parameter
21 * handler : a function which is called when the action is activated. It will receive at first parameter
23 * a dictionary containing various handle to element of the notebook.
22 * a dictionary containing various handle to element of the notebook.
24 *
23 *
25 * action need to be registered with a **name** that can be use to refer to this action.
24 * action need to be registered with a **name** that can be use to refer to this action.
26 *
25 *
27 *
26 *
28 * if `help` is not provided it will be derived by replacing any dash by space
27 * if `help` is not provided it will be derived by replacing any dash by space
29 * in the **name** of the action. It is advised to provide a prefix to action name to
28 * in the **name** of the action. It is advised to provide a prefix to action name to
30 * avoid conflict the prefix should be all lowercase and end with a dot `.`
29 * avoid conflict the prefix should be all lowercase and end with a dot `.`
31 * in the absence of a prefix the behavior of the action is undefined.
30 * in the absence of a prefix the behavior of the action is undefined.
32 *
31 *
33 * All action provided by IPython are prefixed with `ipython.`.
32 * All action provided by IPython are prefixed with `ipython.`.
34 *
33 *
35 * One can register extra actions or replace an existing action with another one is possible
34 * One can register extra actions or replace an existing action with another one is possible
36 * but is considered undefined behavior.
35 * but is considered undefined behavior.
37 *
36 *
38 **/
37 **/
39 var _action = {
38 var _actions = {
40 'run-select-next': {
39 'run-select-next': {
41 icon: 'fa-play',
40 icon: 'fa-play',
42 help : 'run cell, select below',
41 help : 'run cell, select below',
43 help_index : 'ba',
42 help_index : 'ba',
44 handler : function (env) {
43 handler : function (env) {
45 env.notebook.execute_cell_and_select_below();
44 env.notebook.execute_cell_and_select_below();
46 }
45 }
47 },
46 },
48 'execute-in-place':{
47 'execute-in-place':{
49 help : 'run cell',
48 help : 'run cell',
50 help_index : 'bb',
49 help_index : 'bb',
51 handler : function (env) {
50 handler : function (env) {
52 env.notebook.execute_cell();
51 env.notebook.execute_cell();
53 }
52 }
54 },
53 },
55 'execute-and-insert-after':{
54 'execute-and-insert-after':{
56 help : 'run cell, insert below',
55 help : 'run cell, insert below',
57 help_index : 'bc',
56 help_index : 'bc',
58 handler : function (env) {
57 handler : function (env) {
59 env.notebook.execute_cell_and_insert_below();
58 env.notebook.execute_cell_and_insert_below();
60 }
59 }
61 },
60 },
62 'go-to-command-mode': {
61 'go-to-command-mode': {
63 help : 'command mode',
62 help : 'command mode',
64 help_index : 'aa',
63 help_index : 'aa',
65 handler : function (env) {
64 handler : function (env) {
66 env.notebook.command_mode();
65 env.notebook.command_mode();
67 }
66 }
68 },
67 },
69 'split-cell-at-cursor': {
68 'split-cell-at-cursor': {
70 help : 'split cell',
69 help : 'split cell',
71 help_index : 'ea',
70 help_index : 'ea',
72 handler : function (env) {
71 handler : function (env) {
73 env.notebook.split_cell();
72 env.notebook.split_cell();
74 }
73 }
75 },
74 },
76 'enter-edit-mode' : {
75 'enter-edit-mode' : {
77 help_index : 'aa',
76 help_index : 'aa',
78 handler : function (env) {
77 handler : function (env) {
79 env.notebook.edit_mode();
78 env.notebook.edit_mode();
80 }
79 }
81 },
80 },
82 'select-previous-cell' : {
81 'select-previous-cell' : {
83 help_index : 'da',
82 help_index : 'da',
84 handler : function (env) {
83 handler : function (env) {
85 var index = env.notebook.get_selected_index();
84 var index = env.notebook.get_selected_index();
86 if (index !== 0 && index !== null) {
85 if (index !== 0 && index !== null) {
87 env.notebook.select_prev();
86 env.notebook.select_prev();
88 env.notebook.focus_cell();
87 env.notebook.focus_cell();
89 }
88 }
90 }
89 }
91 },
90 },
92 'select-next-cell' : {
91 'select-next-cell' : {
93 help_index : 'db',
92 help_index : 'db',
94 handler : function (env) {
93 handler : function (env) {
95 var index = env.notebook.get_selected_index();
94 var index = env.notebook.get_selected_index();
96 if (index !== (env.notebook.ncells()-1) && index !== null) {
95 if (index !== (env.notebook.ncells()-1) && index !== null) {
97 env.notebook.select_next();
96 env.notebook.select_next();
98 env.notebook.focus_cell();
97 env.notebook.focus_cell();
99 }
98 }
100 }
99 }
101 },
100 },
102 'cut-selected-cell' : {
101 'cut-selected-cell' : {
103 icon: 'fa-cut',
102 icon: 'fa-cut',
104 help_index : 'ee',
103 help_index : 'ee',
105 handler : function (env) {
104 handler : function (env) {
106 env.notebook.cut_cell();
105 env.notebook.cut_cell();
107 }
106 }
108 },
107 },
109 'copy-selected-cell' : {
108 'copy-selected-cell' : {
110 icon: 'fa-copy',
109 icon: 'fa-copy',
111 help_index : 'ef',
110 help_index : 'ef',
112 handler : function (env) {
111 handler : function (env) {
113 env.notebook.copy_cell();
112 env.notebook.copy_cell();
114 }
113 }
115 },
114 },
116 'paste-cell-before' : {
115 'paste-cell-before' : {
117 help_index : 'eg',
116 help_index : 'eg',
118 handler : function (env) {
117 handler : function (env) {
119 env.notebook.paste_cell_above();
118 env.notebook.paste_cell_above();
120 }
119 }
121 },
120 },
122 'paste-cell-after' : {
121 'paste-cell-after' : {
123 icon: 'fa-paste',
122 icon: 'fa-paste',
124 help_index : 'eh',
123 help_index : 'eh',
125 handler : function (env) {
124 handler : function (env) {
126 env.notebook.paste_cell_below();
125 env.notebook.paste_cell_below();
127 }
126 }
128 },
127 },
129 'insert-cell-before' : {
128 'insert-cell-before' : {
130 help_index : 'ec',
129 help_index : 'ec',
131 handler : function (env) {
130 handler : function (env) {
132 env.notebook.insert_cell_above();
131 env.notebook.insert_cell_above();
133 env.notebook.select_prev();
132 env.notebook.select_prev();
134 env.notebook.focus_cell();
133 env.notebook.focus_cell();
135 }
134 }
136 },
135 },
137 'insert-cell-after' : {
136 'insert-cell-after' : {
138 icon : 'fa-plus',
137 icon : 'fa-plus',
139 help_index : 'ed',
138 help_index : 'ed',
140 handler : function (env) {
139 handler : function (env) {
141 env.notebook.insert_cell_below();
140 env.notebook.insert_cell_below();
142 env.notebook.select_next();
141 env.notebook.select_next();
143 env.notebook.focus_cell();
142 env.notebook.focus_cell();
144 }
143 }
145 },
144 },
146 'change-selected-cell-to-code-cell' : {
145 'change-selected-cell-to-code-cell' : {
147 help : 'to code',
146 help : 'to code',
148 help_index : 'ca',
147 help_index : 'ca',
149 handler : function (env) {
148 handler : function (env) {
150 env.notebook.to_code();
149 env.notebook.to_code();
151 }
150 }
152 },
151 },
153 'change-selected-cell-to-markdown-cell' : {
152 'change-selected-cell-to-markdown-cell' : {
154 help : 'to markdown',
153 help : 'to markdown',
155 help_index : 'cb',
154 help_index : 'cb',
156 handler : function (env) {
155 handler : function (env) {
157 env.notebook.to_markdown();
156 env.notebook.to_markdown();
158 }
157 }
159 },
158 },
160 'change-selected-cell-to-raw-cell' : {
159 'change-selected-cell-to-raw-cell' : {
161 help : 'to raw',
160 help : 'to raw',
162 help_index : 'cc',
161 help_index : 'cc',
163 handler : function (env) {
162 handler : function (env) {
164 env.notebook.to_raw();
163 env.notebook.to_raw();
165 }
164 }
166 },
165 },
167 'change-selected-cell-to-heading-1' : {
166 'change-selected-cell-to-heading-1' : {
168 help : 'to heading 1',
167 help : 'to heading 1',
169 help_index : 'cd',
168 help_index : 'cd',
170 handler : function (env) {
169 handler : function (env) {
171 env.notebook.to_heading(undefined, 1);
170 env.notebook.to_heading(undefined, 1);
172 }
171 }
173 },
172 },
174 'change-selected-cell-to-heading-2' : {
173 'change-selected-cell-to-heading-2' : {
175 help : 'to heading 2',
174 help : 'to heading 2',
176 help_index : 'ce',
175 help_index : 'ce',
177 handler : function (env) {
176 handler : function (env) {
178 env.notebook.to_heading(undefined, 2);
177 env.notebook.to_heading(undefined, 2);
179 }
178 }
180 },
179 },
181 'change-selected-cell-to-heading-3' : {
180 'change-selected-cell-to-heading-3' : {
182 help : 'to heading 3',
181 help : 'to heading 3',
183 help_index : 'cf',
182 help_index : 'cf',
184 handler : function (env) {
183 handler : function (env) {
185 env.notebook.to_heading(undefined, 3);
184 env.notebook.to_heading(undefined, 3);
186 }
185 }
187 },
186 },
188 'change-selected-cell-to-heading-4' : {
187 'change-selected-cell-to-heading-4' : {
189 help : 'to heading 4',
188 help : 'to heading 4',
190 help_index : 'cg',
189 help_index : 'cg',
191 handler : function (env) {
190 handler : function (env) {
192 env.notebook.to_heading(undefined, 4);
191 env.notebook.to_heading(undefined, 4);
193 }
192 }
194 },
193 },
195 'change-selected-cell-to-heading-5' : {
194 'change-selected-cell-to-heading-5' : {
196 help : 'to heading 5',
195 help : 'to heading 5',
197 help_index : 'ch',
196 help_index : 'ch',
198 handler : function (env) {
197 handler : function (env) {
199 env.notebook.to_heading(undefined, 5);
198 env.notebook.to_heading(undefined, 5);
200 }
199 }
201 },
200 },
202 'change-selected-cell-to-heading-6' : {
201 'change-selected-cell-to-heading-6' : {
203 help : 'to heading 6',
202 help : 'to heading 6',
204 help_index : 'ci',
203 help_index : 'ci',
205 handler : function (env) {
204 handler : function (env) {
206 env.notebook.to_heading(undefined, 6);
205 env.notebook.to_heading(undefined, 6);
207 }
206 }
208 },
207 },
209 'toggle-output-visibility-selected-cell' : {
208 'toggle-output-visibility-selected-cell' : {
210 help : 'toggle output',
209 help : 'toggle output',
211 help_index : 'gb',
210 help_index : 'gb',
212 handler : function (env) {
211 handler : function (env) {
213 env.notebook.toggle_output();
212 env.notebook.toggle_output();
214 }
213 }
215 },
214 },
216 'toggle-output-scrolling-selected-cell' : {
215 'toggle-output-scrolling-selected-cell' : {
217 help : 'toggle output scrolling',
216 help : 'toggle output scrolling',
218 help_index : 'gc',
217 help_index : 'gc',
219 handler : function (env) {
218 handler : function (env) {
220 env.notebook.toggle_output_scroll();
219 env.notebook.toggle_output_scroll();
221 }
220 }
222 },
221 },
223 'move-selected-cell-down' : {
222 'move-selected-cell-down' : {
224 icon: 'fa-arrow-down',
223 icon: 'fa-arrow-down',
225 help_index : 'eb',
224 help_index : 'eb',
226 handler : function (env) {
225 handler : function (env) {
227 env.notebook.move_cell_down();
226 env.notebook.move_cell_down();
228 }
227 }
229 },
228 },
230 'move-selected-cell-up' : {
229 'move-selected-cell-up' : {
231 icon: 'fa-arrow-up',
230 icon: 'fa-arrow-up',
232 help_index : 'ea',
231 help_index : 'ea',
233 handler : function (env) {
232 handler : function (env) {
234 env.notebook.move_cell_up();
233 env.notebook.move_cell_up();
235 }
234 }
236 },
235 },
237 'toggle-line-number-selected-cell' : {
236 'toggle-line-number-selected-cell' : {
238 help : 'toggle line numbers',
237 help : 'toggle line numbers',
239 help_index : 'ga',
238 help_index : 'ga',
240 handler : function (env) {
239 handler : function (env) {
241 env.notebook.cell_toggle_line_numbers();
240 env.notebook.cell_toggle_line_numbers();
242 }
241 }
243 },
242 },
244 'show-keyboard-shortcut-help-dialog' : {
243 'show-keyboard-shortcut-help-dialog' : {
245 help_index : 'ge',
244 help_index : 'ge',
246 handler : function (env) {
245 handler : function (env) {
247 env.quick_help.show_keyboard_shortcuts();
246 env.quick_help.show_keyboard_shortcuts();
248 }
247 }
249 },
248 },
250 'delete-cell': {
249 'delete-cell': {
251 help_index : 'ej',
250 help_index : 'ej',
252 handler : function (env) {
251 handler : function (env) {
253 env.notebook.delete_cell();
252 env.notebook.delete_cell();
254 }
253 }
255 },
254 },
256 'interrupt-kernel':{
255 'interrupt-kernel':{
257 icon: 'fa-stop',
256 icon: 'fa-stop',
258 help_index : 'ha',
257 help_index : 'ha',
259 handler : function (env) {
258 handler : function (env) {
260 env.notebook.kernel.interrupt();
259 env.notebook.kernel.interrupt();
261 }
260 }
262 },
261 },
263 'restart-kernel':{
262 'restart-kernel':{
264 icon: 'fa-repeat',
263 icon: 'fa-repeat',
265 help_index : 'hb',
264 help_index : 'hb',
266 handler : function (env) {
265 handler : function (env) {
267 env.notebook.restart_kernel();
266 env.notebook.restart_kernel();
268 }
267 }
269 },
268 },
270 'undo-last-cell-deletion' : {
269 'undo-last-cell-deletion' : {
271 help_index : 'ei',
270 help_index : 'ei',
272 handler : function (env) {
271 handler : function (env) {
273 env.notebook.undelete_cell();
272 env.notebook.undelete_cell();
274 }
273 }
275 },
274 },
276 'merge-selected-cell-with-cell-after' : {
275 'merge-selected-cell-with-cell-after' : {
277 help : 'merge cell below',
276 help : 'merge cell below',
278 help_index : 'ek',
277 help_index : 'ek',
279 handler : function (env) {
278 handler : function (env) {
280 env.notebook.merge_cell_below();
279 env.notebook.merge_cell_below();
281 }
280 }
282 },
281 },
283 'close-pager' : {
282 'close-pager' : {
284 help_index : 'gd',
283 help_index : 'gd',
285 handler : function (env) {
284 handler : function (env) {
286 env.pager.collapse();
285 env.pager.collapse();
287 }
286 }
288 }
287 }
289
288
290 };
289 };
291
290
292 /**
291 /**
293 * A bunch of `Advance actions` for IPython.
292 * A bunch of `Advance actions` for IPython.
294 * Cf `Simple Action` plus the following properties.
293 * Cf `Simple Action` plus the following properties.
295 *
294 *
296 * handler: first argument of the handler is the event that triggerd the action
295 * handler: first argument of the handler is the event that triggerd the action
297 * (typically keypress). The handler is responsible for any modification of the
296 * (typically keypress). The handler is responsible for any modification of the
298 * event and event propagation.
297 * event and event propagation.
299 * Is also responsible for returning false if the event have to be further ignored,
298 * Is also responsible for returning false if the event have to be further ignored,
300 * true, to tell keyboard manager that it ignored the event.
299 * true, to tell keyboard manager that it ignored the event.
301 *
300 *
302 * the second parameter of the handler is the environemnt passed to Simple Actions
301 * the second parameter of the handler is the environemnt passed to Simple Actions
303 *
302 *
304 **/
303 **/
305 var custom_ignore = {
304 var custom_ignore = {
306 'ignore':{
305 'ignore':{
307 handler : function () {
306 handler : function () {
308 return true;
307 return true;
309 }
308 }
310 },
309 },
311 'move-cursor-up-or-previous-cell':{
310 'move-cursor-up-or-previous-cell':{
312 handler : function (env, event) {
311 handler : function (env, event) {
313 var index = env.notebook.get_selected_index();
312 var index = env.notebook.get_selected_index();
314 var cell = env.notebook.get_cell(index);
313 var cell = env.notebook.get_cell(index);
315 var cm = env.notebook.get_selected_cell().code_mirror;
314 var cm = env.notebook.get_selected_cell().code_mirror;
316 var cur = cm.getCursor();
315 var cur = cm.getCursor();
317 if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
316 if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
318 if(event){
317 if(event){
319 event.preventDefault();
318 event.preventDefault();
320 }
319 }
321 env.notebook.command_mode();
320 env.notebook.command_mode();
322 env.notebook.select_prev();
321 env.notebook.select_prev();
323 env.notebook.edit_mode();
322 env.notebook.edit_mode();
324 cm = env.notebook.get_selected_cell().code_mirror;
323 cm = env.notebook.get_selected_cell().code_mirror;
325 cm.setCursor(cm.lastLine(), 0);
324 cm.setCursor(cm.lastLine(), 0);
326 }
325 }
327 return false;
326 return false;
328 }
327 }
329 },
328 },
330 'move-cursor-down-or-next-cell':{
329 'move-cursor-down-or-next-cell':{
331 handler : function (env, event) {
330 handler : function (env, event) {
332 var index = env.notebook.get_selected_index();
331 var index = env.notebook.get_selected_index();
333 var cell = env.notebook.get_cell(index);
332 var cell = env.notebook.get_cell(index);
334 if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) {
333 if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) {
335 if(event){
334 if(event){
336 event.preventDefault();
335 event.preventDefault();
337 }
336 }
338 env.notebook.command_mode();
337 env.notebook.command_mode();
339 env.notebook.select_next();
338 env.notebook.select_next();
340 env.notebook.edit_mode();
339 env.notebook.edit_mode();
341 var cm = env.notebook.get_selected_cell().code_mirror;
340 var cm = env.notebook.get_selected_cell().code_mirror;
342 cm.setCursor(0, 0);
341 cm.setCursor(0, 0);
343 }
342 }
344 return false;
343 return false;
345 }
344 }
346 },
345 },
347 'scroll-down': {
346 'scroll-down': {
348 handler: function(env, event) {
347 handler: function(env, event) {
349 if(event){
348 if(event){
350 event.preventDefault();
349 event.preventDefault();
351 }
350 }
352 return env.notebook.scroll_manager.scroll(1);
351 return env.notebook.scroll_manager.scroll(1);
353 },
352 },
354 },
353 },
355 'scroll-up': {
354 'scroll-up': {
356 handler: function(env, event) {
355 handler: function(env, event) {
357 if(event){
356 if(event){
358 event.preventDefault();
357 event.preventDefault();
359 }
358 }
360 return env.notebook.scroll_manager.scroll(-1);
359 return env.notebook.scroll_manager.scroll(-1);
361 },
360 },
362 },
361 },
363 'save-notebook':{
362 'save-notebook':{
364 help: "Save and Checkpoint",
363 help: "Save and Checkpoint",
365 help_index : 'fb',
364 help_index : 'fb',
366 icon: 'fa-save',
365 icon: 'fa-save',
367 handler : function (env, event) {
366 handler : function (env, event) {
368 env.notebook.save_checkpoint();
367 env.notebook.save_checkpoint();
369 if(event){
368 if(event){
370 event.preventDefault();
369 event.preventDefault();
371 }
370 }
372 return false;
371 return false;
373 }
372 }
374 },
373 },
375 };
374 };
376
375
377 // private stuff that prepend `.ipython` to actions names
376 // private stuff that prepend `.ipython` to actions names
378 // and uniformize/fill in missing pieces in of an action.
377 // and uniformize/fill in missing pieces in of an action.
379 var _prepare_handler = function(registry, subkey, source){
378 var _prepare_handler = function(registry, subkey, source){
380 registry['ipython.'+subkey] = {};
379 registry['ipython.'+subkey] = {};
381 registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
380 registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
382 registry['ipython.'+subkey].help_index = source[subkey].help_index;
381 registry['ipython.'+subkey].help_index = source[subkey].help_index;
383 registry['ipython.'+subkey].icon = source[subkey].icon;
382 registry['ipython.'+subkey].icon = source[subkey].icon;
384 return source[subkey].handler;
383 return source[subkey].handler;
385 };
384 };
386
385
387 // Will actually generate/register all the IPython actions
386 // Will actually generate/register all the IPython actions
388 var fun = function(){
387 var fun = function(){
389 var final_actions = {};
388 var final_actions = {};
390 for(var k in _action){
389 var k;
391 // Js closure are function level not block level need to wrap in a IIFE
390 for(k in _actions){
392 // and append ipython to event name these things do intercept event so are wrapped
391 if(_actions.hasOwnProperty(k)){
393 // in a function that return false.
392 // Js closure are function level not block level need to wrap in a IIFE
394 var handler = _prepare_handler(final_actions, k, _action);
393 // and append ipython to event name these things do intercept event so are wrapped
395 (function(key, handler){
394 // in a function that return false.
396 final_actions['ipython.'+key].handler = function(env, event){
395 var handler = _prepare_handler(final_actions, k, _actions);
397 handler(env);
396 (function(key, handler){
398 if(event){
397 final_actions['ipython.'+key].handler = function(env, event){
399 event.preventDefault();
398 handler(env);
400 }
399 if(event){
401 return false;
400 event.preventDefault();
402 };
401 }
403 })(k, handler);
402 return false;
403 };
404 })(k, handler);
405 }
404 }
406 }
405
407
406 for(var k in custom_ignore){
408 for(k in custom_ignore){
407 // Js closure are function level not block level need to wrap in a IIFE
409 // Js closure are function level not block level need to wrap in a IIFE
408 // same as above, but decide for themselves wether or not they intercept events.
410 // same as above, but decide for themselves wether or not they intercept events.
409 var handler = _prepare_handler(final_actions, k, custom_ignore);
411 if(custom_ignore.hasOwnProperty(k)){
410 (function(key, handler){
412 var handler = _prepare_handler(final_actions, k, custom_ignore);
411 final_actions['ipython.'+key].handler = function(env, event){
413 (function(key, handler){
412 return handler(env, event);
414 final_actions['ipython.'+key].handler = function(env, event){
413 };
415 return handler(env, event);
414 })(k, handler);
416 };
417 })(k, handler);
418 }
415 }
419 }
416
420
417 return final_actions;
421 return final_actions;
418 };
422 };
419 ActionHandler.prototype._actions = fun();
423 ActionHandler.prototype._actions = fun();
420
424
421
425
422 /**
426 /**
423 * extend the environment variable that will be pass to handlers
427 * extend the environment variable that will be pass to handlers
424 **/
428 **/
425 ActionHandler.prototype.extend_env = function(env){
429 ActionHandler.prototype.extend_env = function(env){
426 for(var k in env){
430 for(var k in env){
427 this.env[k] = env[k];
431 this.env[k] = env[k];
428 }
432 }
429 };
433 };
430
434
431 ActionHandler.prototype.register = function(action, name, prefix){
435 ActionHandler.prototype.register = function(action, name, prefix){
432 /**
436 /**
433 * Register an `action` with an optional name and prefix.
437 * Register an `action` with an optional name and prefix.
434 *
438 *
435 * if name and prefix are not given they will be determined automatically.
439 * if name and prefix are not given they will be determined automatically.
436 * if action if just a `function` it will be wrapped in an anonymous action.
440 * if action if just a `function` it will be wrapped in an anonymous action.
437 *
441 *
438 * @return the full name to access this action .
442 * @return the full name to access this action .
439 **/
443 **/
440 action = this.normalise(action);
444 action = this.normalise(action);
441 if( !name ){
445 if( !name ){
442 name = 'autogenerated-'+String(action.handler);
446 name = 'autogenerated-'+String(action.handler);
443 }
447 }
444 prefix = prefix || 'auto';
448 prefix = prefix || 'auto';
445 var full_name = prefix+'.'+name;
449 var full_name = prefix+'.'+name;
446 this._actions[full_name] = action;
450 this._actions[full_name] = action;
447 return full_name;
451 return full_name;
448
452
449 };
453 };
450
454
451
455
452 ActionHandler.prototype.normalise = function(data){
456 ActionHandler.prototype.normalise = function(data){
453 /**
457 /**
454 * given an `action` or `function`, return a normalised `action`
458 * given an `action` or `function`, return a normalised `action`
455 * by setting all known attributes and removing unknown attributes;
459 * by setting all known attributes and removing unknown attributes;
456 **/
460 **/
457 if(typeof(data) === 'function'){
461 if(typeof(data) === 'function'){
458 data = {handler:data};
462 data = {handler:data};
459 }
463 }
460 if(typeof(data.handler) !== 'function'){
464 if(typeof(data.handler) !== 'function'){
461 throw('unknown datatype, cannot register');
465 throw('unknown datatype, cannot register');
462 }
466 }
463 var _data = data;
467 var _data = data;
464 data = {};
468 data = {};
465 data.handler = _data.handler;
469 data.handler = _data.handler;
466 data.help = _data.help || '';
470 data.help = _data.help || '';
467 data.icon = _data.icon || '';
471 data.icon = _data.icon || '';
468 data.help_index = _data.help_index || '';
472 data.help_index = _data.help_index || '';
469 return data;
473 return data;
470 };
474 };
471
475
472 ActionHandler.prototype.get_name = function(name_or_data){
476 ActionHandler.prototype.get_name = function(name_or_data){
473 /**
477 /**
474 * given an `action` or `name` of a action, return the name attached to this action.
478 * given an `action` or `name` of a action, return the name attached to this action.
475 * if given the name of and corresponding actions does not exist in registry, return `null`.
479 * if given the name of and corresponding actions does not exist in registry, return `null`.
476 **/
480 **/
477
481
478 if(typeof(name_or_data) === 'string'){
482 if(typeof(name_or_data) === 'string'){
479 if(this.exists(name_or_data)){
483 if(this.exists(name_or_data)){
480 return name_or_data;
484 return name_or_data;
481 } else {
485 } else {
482 return null;
486 return null;
483 }
487 }
484 } else {
488 } else {
485 return this.register(name_or_data);
489 return this.register(name_or_data);
486 }
490 }
487 };
491 };
488
492
489 ActionHandler.prototype.get = function(name){
493 ActionHandler.prototype.get = function(name){
490 return this._actions[name];
494 return this._actions[name];
491 };
495 };
492
496
493 ActionHandler.prototype.call = function(name, event, env){
497 ActionHandler.prototype.call = function(name, event, env){
494 return this._actions[name].handler(env|| this.env, event);
498 return this._actions[name].handler(env|| this.env, event);
495 };
499 };
496
500
497 ActionHandler.prototype.exists = function(name){
501 ActionHandler.prototype.exists = function(name){
498 return (typeof(this._actions[name]) !== 'undefined');
502 return (typeof(this._actions[name]) !== 'undefined');
499 };
503 };
500
504
501 return {init:ActionHandler};
505 return {init:ActionHandler};
502
506
503 });
507 });
@@ -1,679 +1,679 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3 /**
3 /**
4 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'services/config',
17 'services/config',
18 'notebook/js/cell',
18 'notebook/js/cell',
19 'notebook/js/outputarea',
19 'notebook/js/outputarea',
20 'notebook/js/completer',
20 'notebook/js/completer',
21 'notebook/js/celltoolbar',
21 'notebook/js/celltoolbar',
22 'codemirror/lib/codemirror',
22 'codemirror/lib/codemirror',
23 'codemirror/mode/python/python',
23 'codemirror/mode/python/python',
24 'notebook/js/codemirror-ipython'
24 'notebook/js/codemirror-ipython'
25 ], function(IPython,
25 ], function(IPython,
26 $,
26 $,
27 utils,
27 utils,
28 keyboard,
28 keyboard,
29 configmod,
29 configmod,
30 cell,
30 cell,
31 outputarea,
31 outputarea,
32 completer,
32 completer,
33 celltoolbar,
33 celltoolbar,
34 CodeMirror,
34 CodeMirror,
35 cmpython,
35 cmpython,
36 cmip
36 cmip
37 ) {
37 ) {
38 "use strict";
38 "use strict";
39
39
40 var Cell = cell.Cell;
40 var Cell = cell.Cell;
41
41
42 /* local util for codemirror */
42 /* local util for codemirror */
43 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44
44
45 /**
45 /**
46 *
46 *
47 * function to delete until previous non blanking space character
47 * function to delete until previous non blanking space character
48 * or first multiple of 4 tabstop.
48 * or first multiple of 4 tabstop.
49 * @private
49 * @private
50 */
50 */
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 var tabsize = cm.getOption('tabSize');
55 var tabsize = cm.getOption('tabSize');
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 var select = cm.getRange(from,cur);
58 var select = cm.getRange(from,cur);
59 if( select.match(/^\ +$/) !== null){
59 if( select.match(/^\ +$/) !== null){
60 cm.replaceRange("",from,cur);
60 cm.replaceRange("",from,cur);
61 } else {
61 } else {
62 cm.deleteH(-1,"char");
62 cm.deleteH(-1,"char");
63 }
63 }
64 };
64 };
65
65
66 var keycodes = keyboard.keycodes;
66 var keycodes = keyboard.keycodes;
67
67
68 var CodeCell = function (kernel, options) {
68 var CodeCell = function (kernel, options) {
69 /**
69 /**
70 * Constructor
70 * Constructor
71 *
71 *
72 * A Cell conceived to write code.
72 * A Cell conceived to write code.
73 *
73 *
74 * Parameters:
74 * Parameters:
75 * kernel: Kernel instance
75 * kernel: Kernel instance
76 * The kernel doesn't have to be set at creation time, in that case
76 * The kernel doesn't have to be set at creation time, in that case
77 * it will be null and set_kernel has to be called later.
77 * it will be null and set_kernel has to be called later.
78 * options: dictionary
78 * options: dictionary
79 * Dictionary of keyword arguments.
79 * Dictionary of keyword arguments.
80 * events: $(Events) instance
80 * events: $(Events) instance
81 * config: dictionary
81 * config: dictionary
82 * keyboard_manager: KeyboardManager instance
82 * keyboard_manager: KeyboardManager instance
83 * notebook: Notebook instance
83 * notebook: Notebook instance
84 * tooltip: Tooltip instance
84 * tooltip: Tooltip instance
85 */
85 */
86 this.kernel = kernel || null;
86 this.kernel = kernel || null;
87 this.notebook = options.notebook;
87 this.notebook = options.notebook;
88 this.collapsed = false;
88 this.collapsed = false;
89 this.events = options.events;
89 this.events = options.events;
90 this.tooltip = options.tooltip;
90 this.tooltip = options.tooltip;
91 this.config = options.config;
91 this.config = options.config;
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 CodeCell.config_defaults, 'CodeCell');
93 CodeCell.config_defaults, 'CodeCell');
94
94
95 // create all attributed in constructor function
95 // create all attributed in constructor function
96 // even if null for V8 VM optimisation
96 // even if null for V8 VM optimisation
97 this.input_prompt_number = null;
97 this.input_prompt_number = null;
98 this.celltoolbar = null;
98 this.celltoolbar = null;
99 this.output_area = null;
99 this.output_area = null;
100 // Keep a stack of the 'active' output areas (where active means the
100 // Keep a stack of the 'active' output areas (where active means the
101 // output area that recieves output). When a user activates an output
101 // output area that recieves output). When a user activates an output
102 // area, it gets pushed to the stack. Then, when the output area is
102 // area, it gets pushed to the stack. Then, when the output area is
103 // deactivated, it's popped from the stack. When the stack is empty,
103 // deactivated, it's popped from the stack. When the stack is empty,
104 // the cell's output area is used.
104 // the cell's output area is used.
105 this.active_output_areas = [];
105 this.active_output_areas = [];
106 var that = this;
106 var that = this;
107 Object.defineProperty(this, 'active_output_area', {
107 Object.defineProperty(this, 'active_output_area', {
108 get: function() {
108 get: function() {
109 if (that.active_output_areas && that.active_output_areas.length > 0) {
109 if (that.active_output_areas && that.active_output_areas.length > 0) {
110 return that.active_output_areas[that.active_output_areas.length-1];
110 return that.active_output_areas[that.active_output_areas.length-1];
111 } else {
111 } else {
112 return that.output_area;
112 return that.output_area;
113 }
113 }
114 },
114 },
115 });
115 });
116
116
117 this.last_msg_id = null;
117 this.last_msg_id = null;
118 this.completer = null;
118 this.completer = null;
119 this.widget_views = [];
119 this.widget_views = [];
120 this._widgets_live = true;
120 this._widgets_live = true;
121
121
122 Cell.apply(this,[{
122 Cell.apply(this,[{
123 config: $.extend({}, CodeCell.options_default),
123 config: $.extend({}, CodeCell.options_default),
124 keyboard_manager: options.keyboard_manager,
124 keyboard_manager: options.keyboard_manager,
125 events: this.events}]);
125 events: this.events}]);
126
126
127 // Attributes we want to override in this subclass.
127 // Attributes we want to override in this subclass.
128 this.cell_type = "code";
128 this.cell_type = "code";
129 this.element.focusout(
129 this.element.focusout(
130 function() { that.auto_highlight(); }
130 function() { that.auto_highlight(); }
131 );
131 );
132 };
132 };
133
133
134 CodeCell.options_default = {
134 CodeCell.options_default = {
135 cm_config : {
135 cm_config : {
136 extraKeys: {
136 extraKeys: {
137 "Tab" : "indentMore",
137 "Tab" : "indentMore",
138 "Shift-Tab" : "indentLess",
138 "Shift-Tab" : "indentLess",
139 "Backspace" : "delSpaceToPrevTabStop",
139 "Backspace" : "delSpaceToPrevTabStop",
140 "Cmd-/" : "toggleComment",
140 "Cmd-/" : "toggleComment",
141 "Ctrl-/" : "toggleComment"
141 "Ctrl-/" : "toggleComment"
142 },
142 },
143 mode: 'ipython',
143 mode: 'ipython',
144 theme: 'ipython',
144 theme: 'ipython',
145 matchBrackets: true
145 matchBrackets: true
146 }
146 }
147 };
147 };
148
148
149 CodeCell.config_defaults = {
149 CodeCell.config_defaults = {
150 cell_magic_highlight : {
150 cell_magic_highlight : {
151 'magic_javascript' :{'reg':[/^%%javascript/]},
151 'magic_javascript' :{'reg':[/^%%javascript/]},
152 'magic_perl' :{'reg':[/^%%perl/]},
152 'magic_perl' :{'reg':[/^%%perl/]},
153 'magic_ruby' :{'reg':[/^%%ruby/]},
153 'magic_ruby' :{'reg':[/^%%ruby/]},
154 'magic_python' :{'reg':[/^%%python3?/]},
154 'magic_python' :{'reg':[/^%%python3?/]},
155 'magic_shell' :{'reg':[/^%%bash/]},
155 'magic_shell' :{'reg':[/^%%bash/]},
156 'magic_r' :{'reg':[/^%%R/]},
156 'magic_r' :{'reg':[/^%%R/]},
157 'magic_text/x-cython' :{'reg':[/^%%cython/]},
157 'magic_text/x-cython' :{'reg':[/^%%cython/]},
158 },
158 },
159 };
159 };
160
160
161 CodeCell.msg_cells = {};
161 CodeCell.msg_cells = {};
162
162
163 CodeCell.prototype = Object.create(Cell.prototype);
163 CodeCell.prototype = Object.create(Cell.prototype);
164
164
165 /**
165 /**
166 * @method push_output_area
166 * @method push_output_area
167 */
167 */
168 CodeCell.prototype.push_output_area = function (output_area) {
168 CodeCell.prototype.push_output_area = function (output_area) {
169 this.active_output_areas.push(output_area);
169 this.active_output_areas.push(output_area);
170 };
170 };
171
171
172 /**
172 /**
173 * @method pop_output_area
173 * @method pop_output_area
174 */
174 */
175 CodeCell.prototype.pop_output_area = function (output_area) {
175 CodeCell.prototype.pop_output_area = function (output_area) {
176 var index = this.active_output_areas.lastIndexOf(output_area);
176 var index = this.active_output_areas.lastIndexOf(output_area);
177 if (index > -1) {
177 if (index > -1) {
178 this.active_output_areas.splice(index, 1);
178 this.active_output_areas.splice(index, 1);
179 }
179 }
180 };
180 };
181
181
182 /**
182 /**
183 * @method auto_highlight
183 * @method auto_highlight
184 */
184 */
185 CodeCell.prototype.auto_highlight = function () {
185 CodeCell.prototype.auto_highlight = function () {
186 this._auto_highlight(this.class_config.get_sync('cell_magic_highlight'));
186 this._auto_highlight(this.class_config.get_sync('cell_magic_highlight'));
187 };
187 };
188
188
189 /** @method create_element */
189 /** @method create_element */
190 CodeCell.prototype.create_element = function () {
190 CodeCell.prototype.create_element = function () {
191 Cell.prototype.create_element.apply(this, arguments);
191 Cell.prototype.create_element.apply(this, arguments);
192
192
193 var cell = $('<div></div>').addClass('cell code_cell');
193 var cell = $('<div></div>').addClass('cell code_cell');
194 cell.attr('tabindex','2');
194 cell.attr('tabindex','2');
195
195
196 var input = $('<div></div>').addClass('input');
196 var input = $('<div></div>').addClass('input');
197 var prompt = $('<div/>').addClass('prompt input_prompt');
197 var prompt = $('<div/>').addClass('prompt input_prompt');
198 var inner_cell = $('<div/>').addClass('inner_cell');
198 var inner_cell = $('<div/>').addClass('inner_cell');
199 this.celltoolbar = new celltoolbar.CellToolbar({
199 this.celltoolbar = new celltoolbar.CellToolbar({
200 cell: this,
200 cell: this,
201 notebook: this.notebook});
201 notebook: this.notebook});
202 inner_cell.append(this.celltoolbar.element);
202 inner_cell.append(this.celltoolbar.element);
203 var input_area = $('<div/>').addClass('input_area');
203 var input_area = $('<div/>').addClass('input_area');
204 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
204 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
205 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
205 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
206 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
206 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
207 inner_cell.append(input_area);
207 inner_cell.append(input_area);
208 input.append(prompt).append(inner_cell);
208 input.append(prompt).append(inner_cell);
209
209
210 var widget_area = $('<div/>')
210 var widget_area = $('<div/>')
211 .addClass('widget-area')
211 .addClass('widget-area')
212 .hide();
212 .hide();
213 this.widget_area = widget_area;
213 this.widget_area = widget_area;
214 var widget_prompt = $('<div/>')
214 var widget_prompt = $('<div/>')
215 .addClass('prompt')
215 .addClass('prompt')
216 .appendTo(widget_area);
216 .appendTo(widget_area);
217 var widget_subarea = $('<div/>')
217 var widget_subarea = $('<div/>')
218 .addClass('widget-subarea')
218 .addClass('widget-subarea')
219 .appendTo(widget_area);
219 .appendTo(widget_area);
220 this.widget_subarea = widget_subarea;
220 this.widget_subarea = widget_subarea;
221 var that = this;
221 var that = this;
222 var widget_clear_buton = $('<button />')
222 var widget_clear_buton = $('<button />')
223 .addClass('close')
223 .addClass('close')
224 .html('&times;')
224 .html('&times;')
225 .click(function() {
225 .click(function() {
226 widget_area.slideUp('', function(){
226 widget_area.slideUp('', function(){
227 for (var i = 0; i < that.widget_views.length; i++) {
227 for (var i = 0; i < that.widget_views.length; i++) {
228 var view = that.widget_views[i];
228 var view = that.widget_views[i];
229 view.remove();
229 view.remove();
230
230
231 // Remove widget live events.
231 // Remove widget live events.
232 view.off('comm:live', that._widget_live);
232 view.off('comm:live', that._widget_live);
233 view.off('comm:dead', that._widget_dead);
233 view.off('comm:dead', that._widget_dead);
234 }
234 }
235 that.widget_views = [];
235 that.widget_views = [];
236 widget_subarea.html('');
236 widget_subarea.html('');
237 });
237 });
238 })
238 })
239 .appendTo(widget_prompt);
239 .appendTo(widget_prompt);
240
240
241 var output = $('<div></div>');
241 var output = $('<div></div>');
242 cell.append(input).append(widget_area).append(output);
242 cell.append(input).append(widget_area).append(output);
243 this.element = cell;
243 this.element = cell;
244 this.output_area = new outputarea.OutputArea({
244 this.output_area = new outputarea.OutputArea({
245 selector: output,
245 selector: output,
246 prompt_area: true,
246 prompt_area: true,
247 events: this.events,
247 events: this.events,
248 keyboard_manager: this.keyboard_manager});
248 keyboard_manager: this.keyboard_manager});
249 this.completer = new completer.Completer(this, this.events);
249 this.completer = new completer.Completer(this, this.events);
250 };
250 };
251
251
252 /**
252 /**
253 * Display a widget view in the cell.
253 * Display a widget view in the cell.
254 */
254 */
255 CodeCell.prototype.display_widget_view = function(view_promise) {
255 CodeCell.prototype.display_widget_view = function(view_promise) {
256
256
257 // Display a dummy element
257 // Display a dummy element
258 var dummy = $('<div/>');
258 var dummy = $('<div/>');
259 this.widget_subarea.append(dummy);
259 this.widget_subarea.append(dummy);
260
260
261 // Display the view.
261 // Display the view.
262 var that = this;
262 var that = this;
263 return view_promise.then(function(view) {
263 return view_promise.then(function(view) {
264 that.widget_area.show();
264 that.widget_area.show();
265 dummy.replaceWith(view.$el);
265 dummy.replaceWith(view.$el);
266 that.widget_views.push(view);
266 that.widget_views.push(view);
267
267
268 // Check the live state of the view's model.
268 // Check the live state of the view's model.
269 if (view.model.comm_live) {
269 if (view.model.comm_live) {
270 that._widget_live(view);
270 that._widget_live(view);
271 } else {
271 } else {
272 that._widget_dead(view);
272 that._widget_dead(view);
273 }
273 }
274
274
275 // Listen to comm live events for the view.
275 // Listen to comm live events for the view.
276 view.on('comm:live', that._widget_live, that);
276 view.on('comm:live', that._widget_live, that);
277 view.on('comm:dead', that._widget_dead, that);
277 view.on('comm:dead', that._widget_dead, that);
278 return view;
278 return view;
279 });
279 });
280 };
280 };
281
281
282 /**
282 /**
283 * Handles when a widget loses it's comm connection.
283 * Handles when a widget loses it's comm connection.
284 * @param {WidgetView} view
284 * @param {WidgetView} view
285 */
285 */
286 CodeCell.prototype._widget_dead = function(view) {
286 CodeCell.prototype._widget_dead = function(view) {
287 if (this._widgets_live) {
287 if (this._widgets_live) {
288 this._widgets_live = false;
288 this._widgets_live = false;
289 this.widget_area.addClass('connection-problems');
289 this.widget_area.addClass('connection-problems');
290 }
290 }
291
291
292 };
292 };
293
293
294 /**
294 /**
295 * Handles when a widget is connected to a live comm.
295 * Handles when a widget is connected to a live comm.
296 * @param {WidgetView} view
296 * @param {WidgetView} view
297 */
297 */
298 CodeCell.prototype._widget_live = function(view) {
298 CodeCell.prototype._widget_live = function(view) {
299 if (!this._widgets_live) {
299 if (!this._widgets_live) {
300 // Check that the other widgets are live too. O(N) operation.
300 // Check that the other widgets are live too. O(N) operation.
301 // Abort the function at the first dead widget found.
301 // Abort the function at the first dead widget found.
302 for (var i = 0; i < this.widget_views.length; i++) {
302 for (var i = 0; i < this.widget_views.length; i++) {
303 if (!this.widget_views[i].model.comm_live) return;
303 if (!this.widget_views[i].model.comm_live) return;
304 }
304 }
305 this._widgets_live = true;
305 this._widgets_live = true;
306 this.widget_area.removeClass('connection-problems');
306 this.widget_area.removeClass('connection-problems');
307 }
307 }
308 };
308 };
309
309
310 /** @method bind_events */
310 /** @method bind_events */
311 CodeCell.prototype.bind_events = function () {
311 CodeCell.prototype.bind_events = function () {
312 Cell.prototype.bind_events.apply(this);
312 Cell.prototype.bind_events.apply(this);
313 var that = this;
313 var that = this;
314
314
315 this.element.focusout(
315 this.element.focusout(
316 function() { that.auto_highlight(); }
316 function() { that.auto_highlight(); }
317 );
317 );
318 };
318 };
319
319
320
320
321 /**
321 /**
322 * This method gets called in CodeMirror's onKeyDown/onKeyPress
322 * This method gets called in CodeMirror's onKeyDown/onKeyPress
323 * handlers and is used to provide custom key handling. Its return
323 * handlers and is used to provide custom key handling. Its return
324 * value is used to determine if CodeMirror should ignore the event:
324 * value is used to determine if CodeMirror should ignore the event:
325 * true = ignore, false = don't ignore.
325 * true = ignore, false = don't ignore.
326 * @method handle_codemirror_keyevent
326 * @method handle_codemirror_keyevent
327 */
327 */
328
328
329 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
329 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
330
330
331 var that = this;
331 var that = this;
332 // whatever key is pressed, first, cancel the tooltip request before
332 // whatever key is pressed, first, cancel the tooltip request before
333 // they are sent, and remove tooltip if any, except for tab again
333 // they are sent, and remove tooltip if any, except for tab again
334 var tooltip_closed = null;
334 var tooltip_closed = null;
335 if (event.type === 'keydown' && event.which != keycodes.tab ) {
335 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
336 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
336 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
337 }
337 }
338
338
339 var cur = editor.getCursor();
339 var cur = editor.getCursor();
340 if (event.keyCode === keycodes.enter){
340 if (event.keyCode === keycodes.enter){
341 this.auto_highlight();
341 this.auto_highlight();
342 }
342 }
343
343
344 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
344 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
345 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
345 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
346 // browser and keyboard layout !
346 // browser and keyboard layout !
347 // Pressing '(' , request tooltip, don't forget to reappend it
347 // Pressing '(' , request tooltip, don't forget to reappend it
348 // The second argument says to hide the tooltip if the docstring
348 // The second argument says to hide the tooltip if the docstring
349 // is actually empty
349 // is actually empty
350 this.tooltip.pending(that, true);
350 this.tooltip.pending(that, true);
351 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
351 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
352 // If tooltip is active, cancel it. The call to
352 // If tooltip is active, cancel it. The call to
353 // remove_and_cancel_tooltip above doesn't pass, force=true.
353 // remove_and_cancel_tooltip above doesn't pass, force=true.
354 // Because of this it won't actually close the tooltip
354 // Because of this it won't actually close the tooltip
355 // if it is in sticky mode. Thus, we have to check again if it is open
355 // if it is in sticky mode. Thus, we have to check again if it is open
356 // and close it with force=true.
356 // and close it with force=true.
357 if (!this.tooltip._hidden) {
357 if (!this.tooltip._hidden) {
358 this.tooltip.remove_and_cancel_tooltip(true);
358 this.tooltip.remove_and_cancel_tooltip(true);
359 }
359 }
360 // If we closed the tooltip, don't let CM or the global handlers
360 // If we closed the tooltip, don't let CM or the global handlers
361 // handle this event.
361 // handle this event.
362 event.codemirrorIgnore = true;
362 event.codemirrorIgnore = true;
363 event.preventDefault();
363 event.preventDefault();
364 return true;
364 return true;
365 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
365 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
366 if (editor.somethingSelected() || editor.getSelections().length !== 1){
366 if (editor.somethingSelected() || editor.getSelections().length !== 1){
367 var anchor = editor.getCursor("anchor");
367 var anchor = editor.getCursor("anchor");
368 var head = editor.getCursor("head");
368 var head = editor.getCursor("head");
369 if( anchor.line != head.line){
369 if( anchor.line !== head.line){
370 return false;
370 return false;
371 }
371 }
372 }
372 }
373 this.tooltip.request(that);
373 this.tooltip.request(that);
374 event.codemirrorIgnore = true;
374 event.codemirrorIgnore = true;
375 event.preventDefault();
375 event.preventDefault();
376 return true;
376 return true;
377 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
377 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
378 // Tab completion.
378 // Tab completion.
379 this.tooltip.remove_and_cancel_tooltip();
379 this.tooltip.remove_and_cancel_tooltip();
380
380
381 // completion does not work on multicursor, it might be possible though in some cases
381 // completion does not work on multicursor, it might be possible though in some cases
382 if (editor.somethingSelected() || editor.getSelections().length > 1) {
382 if (editor.somethingSelected() || editor.getSelections().length > 1) {
383 return false;
383 return false;
384 }
384 }
385 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
385 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
386 if (pre_cursor.trim() === "") {
386 if (pre_cursor.trim() === "") {
387 // Don't autocomplete if the part of the line before the cursor
387 // Don't autocomplete if the part of the line before the cursor
388 // is empty. In this case, let CodeMirror handle indentation.
388 // is empty. In this case, let CodeMirror handle indentation.
389 return false;
389 return false;
390 } else {
390 } else {
391 event.codemirrorIgnore = true;
391 event.codemirrorIgnore = true;
392 event.preventDefault();
392 event.preventDefault();
393 this.completer.startCompletion();
393 this.completer.startCompletion();
394 return true;
394 return true;
395 }
395 }
396 }
396 }
397
397
398 // keyboard event wasn't one of those unique to code cells, let's see
398 // keyboard event wasn't one of those unique to code cells, let's see
399 // if it's one of the generic ones (i.e. check edit mode shortcuts)
399 // if it's one of the generic ones (i.e. check edit mode shortcuts)
400 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
400 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
401 };
401 };
402
402
403 // Kernel related calls.
403 // Kernel related calls.
404
404
405 CodeCell.prototype.set_kernel = function (kernel) {
405 CodeCell.prototype.set_kernel = function (kernel) {
406 this.kernel = kernel;
406 this.kernel = kernel;
407 };
407 };
408
408
409 /**
409 /**
410 * Execute current code cell to the kernel
410 * Execute current code cell to the kernel
411 * @method execute
411 * @method execute
412 */
412 */
413 CodeCell.prototype.execute = function () {
413 CodeCell.prototype.execute = function () {
414 if (!this.kernel || !this.kernel.is_connected()) {
414 if (!this.kernel || !this.kernel.is_connected()) {
415 console.log("Can't execute, kernel is not connected.");
415 console.log("Can't execute, kernel is not connected.");
416 return;
416 return;
417 }
417 }
418
418
419 this.active_output_area.clear_output();
419 this.active_output_area.clear_output();
420
420
421 // Clear widget area
421 // Clear widget area
422 for (var i = 0; i < this.widget_views.length; i++) {
422 for (var i = 0; i < this.widget_views.length; i++) {
423 var view = this.widget_views[i];
423 var view = this.widget_views[i];
424 view.remove();
424 view.remove();
425
425
426 // Remove widget live events.
426 // Remove widget live events.
427 view.off('comm:live', this._widget_live);
427 view.off('comm:live', this._widget_live);
428 view.off('comm:dead', this._widget_dead);
428 view.off('comm:dead', this._widget_dead);
429 }
429 }
430 this.widget_views = [];
430 this.widget_views = [];
431 this.widget_subarea.html('');
431 this.widget_subarea.html('');
432 this.widget_subarea.height('');
432 this.widget_subarea.height('');
433 this.widget_area.height('');
433 this.widget_area.height('');
434 this.widget_area.hide();
434 this.widget_area.hide();
435
435
436 this.set_input_prompt('*');
436 this.set_input_prompt('*');
437 this.element.addClass("running");
437 this.element.addClass("running");
438 if (this.last_msg_id) {
438 if (this.last_msg_id) {
439 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
439 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
440 }
440 }
441 var callbacks = this.get_callbacks();
441 var callbacks = this.get_callbacks();
442
442
443 var old_msg_id = this.last_msg_id;
443 var old_msg_id = this.last_msg_id;
444 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
444 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
445 if (old_msg_id) {
445 if (old_msg_id) {
446 delete CodeCell.msg_cells[old_msg_id];
446 delete CodeCell.msg_cells[old_msg_id];
447 }
447 }
448 CodeCell.msg_cells[this.last_msg_id] = this;
448 CodeCell.msg_cells[this.last_msg_id] = this;
449 this.render();
449 this.render();
450 this.events.trigger('execute.CodeCell', {cell: this});
450 this.events.trigger('execute.CodeCell', {cell: this});
451 };
451 };
452
452
453 /**
453 /**
454 * Construct the default callbacks for
454 * Construct the default callbacks for
455 * @method get_callbacks
455 * @method get_callbacks
456 */
456 */
457 CodeCell.prototype.get_callbacks = function () {
457 CodeCell.prototype.get_callbacks = function () {
458 var that = this;
458 var that = this;
459 return {
459 return {
460 shell : {
460 shell : {
461 reply : $.proxy(this._handle_execute_reply, this),
461 reply : $.proxy(this._handle_execute_reply, this),
462 payload : {
462 payload : {
463 set_next_input : $.proxy(this._handle_set_next_input, this),
463 set_next_input : $.proxy(this._handle_set_next_input, this),
464 page : $.proxy(this._open_with_pager, this)
464 page : $.proxy(this._open_with_pager, this)
465 }
465 }
466 },
466 },
467 iopub : {
467 iopub : {
468 output : function() {
468 output : function() {
469 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
469 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
470 },
470 },
471 clear_output : function() {
471 clear_output : function() {
472 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
472 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
473 },
473 },
474 },
474 },
475 input : $.proxy(this._handle_input_request, this)
475 input : $.proxy(this._handle_input_request, this)
476 };
476 };
477 };
477 };
478
478
479 CodeCell.prototype._open_with_pager = function (payload) {
479 CodeCell.prototype._open_with_pager = function (payload) {
480 this.events.trigger('open_with_text.Pager', payload);
480 this.events.trigger('open_with_text.Pager', payload);
481 };
481 };
482
482
483 /**
483 /**
484 * @method _handle_execute_reply
484 * @method _handle_execute_reply
485 * @private
485 * @private
486 */
486 */
487 CodeCell.prototype._handle_execute_reply = function (msg) {
487 CodeCell.prototype._handle_execute_reply = function (msg) {
488 this.set_input_prompt(msg.content.execution_count);
488 this.set_input_prompt(msg.content.execution_count);
489 this.element.removeClass("running");
489 this.element.removeClass("running");
490 this.events.trigger('set_dirty.Notebook', {value: true});
490 this.events.trigger('set_dirty.Notebook', {value: true});
491 };
491 };
492
492
493 /**
493 /**
494 * @method _handle_set_next_input
494 * @method _handle_set_next_input
495 * @private
495 * @private
496 */
496 */
497 CodeCell.prototype._handle_set_next_input = function (payload) {
497 CodeCell.prototype._handle_set_next_input = function (payload) {
498 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
498 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
499 this.events.trigger('set_next_input.Notebook', data);
499 this.events.trigger('set_next_input.Notebook', data);
500 };
500 };
501
501
502 /**
502 /**
503 * @method _handle_input_request
503 * @method _handle_input_request
504 * @private
504 * @private
505 */
505 */
506 CodeCell.prototype._handle_input_request = function (msg) {
506 CodeCell.prototype._handle_input_request = function (msg) {
507 this.active_output_area.append_raw_input(msg);
507 this.active_output_area.append_raw_input(msg);
508 };
508 };
509
509
510
510
511 // Basic cell manipulation.
511 // Basic cell manipulation.
512
512
513 CodeCell.prototype.select = function () {
513 CodeCell.prototype.select = function () {
514 var cont = Cell.prototype.select.apply(this);
514 var cont = Cell.prototype.select.apply(this);
515 if (cont) {
515 if (cont) {
516 this.code_mirror.refresh();
516 this.code_mirror.refresh();
517 this.auto_highlight();
517 this.auto_highlight();
518 }
518 }
519 return cont;
519 return cont;
520 };
520 };
521
521
522 CodeCell.prototype.render = function () {
522 CodeCell.prototype.render = function () {
523 var cont = Cell.prototype.render.apply(this);
523 var cont = Cell.prototype.render.apply(this);
524 // Always execute, even if we are already in the rendered state
524 // Always execute, even if we are already in the rendered state
525 return cont;
525 return cont;
526 };
526 };
527
527
528 CodeCell.prototype.select_all = function () {
528 CodeCell.prototype.select_all = function () {
529 var start = {line: 0, ch: 0};
529 var start = {line: 0, ch: 0};
530 var nlines = this.code_mirror.lineCount();
530 var nlines = this.code_mirror.lineCount();
531 var last_line = this.code_mirror.getLine(nlines-1);
531 var last_line = this.code_mirror.getLine(nlines-1);
532 var end = {line: nlines-1, ch: last_line.length};
532 var end = {line: nlines-1, ch: last_line.length};
533 this.code_mirror.setSelection(start, end);
533 this.code_mirror.setSelection(start, end);
534 };
534 };
535
535
536
536
537 CodeCell.prototype.collapse_output = function () {
537 CodeCell.prototype.collapse_output = function () {
538 this.output_area.collapse();
538 this.output_area.collapse();
539 };
539 };
540
540
541
541
542 CodeCell.prototype.expand_output = function () {
542 CodeCell.prototype.expand_output = function () {
543 this.output_area.expand();
543 this.output_area.expand();
544 this.output_area.unscroll_area();
544 this.output_area.unscroll_area();
545 };
545 };
546
546
547 CodeCell.prototype.scroll_output = function () {
547 CodeCell.prototype.scroll_output = function () {
548 this.output_area.expand();
548 this.output_area.expand();
549 this.output_area.scroll_if_long();
549 this.output_area.scroll_if_long();
550 };
550 };
551
551
552 CodeCell.prototype.toggle_output = function () {
552 CodeCell.prototype.toggle_output = function () {
553 this.output_area.toggle_output();
553 this.output_area.toggle_output();
554 };
554 };
555
555
556 CodeCell.prototype.toggle_output_scroll = function () {
556 CodeCell.prototype.toggle_output_scroll = function () {
557 this.output_area.toggle_scroll();
557 this.output_area.toggle_scroll();
558 };
558 };
559
559
560
560
561 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
561 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
562 var ns;
562 var ns;
563 if (prompt_value === undefined || prompt_value === null) {
563 if (prompt_value === undefined || prompt_value === null) {
564 ns = "&nbsp;";
564 ns = "&nbsp;";
565 } else {
565 } else {
566 ns = encodeURIComponent(prompt_value);
566 ns = encodeURIComponent(prompt_value);
567 }
567 }
568 return 'In&nbsp;[' + ns + ']:';
568 return 'In&nbsp;[' + ns + ']:';
569 };
569 };
570
570
571 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
571 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
572 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
572 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
573 for(var i=1; i < lines_number; i++) {
573 for(var i=1; i < lines_number; i++) {
574 html.push(['...:']);
574 html.push(['...:']);
575 }
575 }
576 return html.join('<br/>');
576 return html.join('<br/>');
577 };
577 };
578
578
579 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
579 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
580
580
581
581
582 CodeCell.prototype.set_input_prompt = function (number) {
582 CodeCell.prototype.set_input_prompt = function (number) {
583 var nline = 1;
583 var nline = 1;
584 if (this.code_mirror !== undefined) {
584 if (this.code_mirror !== undefined) {
585 nline = this.code_mirror.lineCount();
585 nline = this.code_mirror.lineCount();
586 }
586 }
587 this.input_prompt_number = number;
587 this.input_prompt_number = number;
588 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
588 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
589 // This HTML call is okay because the user contents are escaped.
589 // This HTML call is okay because the user contents are escaped.
590 this.element.find('div.input_prompt').html(prompt_html);
590 this.element.find('div.input_prompt').html(prompt_html);
591 };
591 };
592
592
593
593
594 CodeCell.prototype.clear_input = function () {
594 CodeCell.prototype.clear_input = function () {
595 this.code_mirror.setValue('');
595 this.code_mirror.setValue('');
596 };
596 };
597
597
598
598
599 CodeCell.prototype.get_text = function () {
599 CodeCell.prototype.get_text = function () {
600 return this.code_mirror.getValue();
600 return this.code_mirror.getValue();
601 };
601 };
602
602
603
603
604 CodeCell.prototype.set_text = function (code) {
604 CodeCell.prototype.set_text = function (code) {
605 return this.code_mirror.setValue(code);
605 return this.code_mirror.setValue(code);
606 };
606 };
607
607
608
608
609 CodeCell.prototype.clear_output = function (wait) {
609 CodeCell.prototype.clear_output = function (wait) {
610 this.active_output_area.clear_output(wait);
610 this.active_output_area.clear_output(wait);
611 this.set_input_prompt();
611 this.set_input_prompt();
612 };
612 };
613
613
614
614
615 // JSON serialization
615 // JSON serialization
616
616
617 CodeCell.prototype.fromJSON = function (data) {
617 CodeCell.prototype.fromJSON = function (data) {
618 Cell.prototype.fromJSON.apply(this, arguments);
618 Cell.prototype.fromJSON.apply(this, arguments);
619 if (data.cell_type === 'code') {
619 if (data.cell_type === 'code') {
620 if (data.source !== undefined) {
620 if (data.source !== undefined) {
621 this.set_text(data.source);
621 this.set_text(data.source);
622 // make this value the starting point, so that we can only undo
622 // make this value the starting point, so that we can only undo
623 // to this state, instead of a blank cell
623 // to this state, instead of a blank cell
624 this.code_mirror.clearHistory();
624 this.code_mirror.clearHistory();
625 this.auto_highlight();
625 this.auto_highlight();
626 }
626 }
627 this.set_input_prompt(data.execution_count);
627 this.set_input_prompt(data.execution_count);
628 this.output_area.trusted = data.metadata.trusted || false;
628 this.output_area.trusted = data.metadata.trusted || false;
629 this.output_area.fromJSON(data.outputs);
629 this.output_area.fromJSON(data.outputs);
630 if (data.metadata.collapsed !== undefined) {
630 if (data.metadata.collapsed !== undefined) {
631 if (data.metadata.collapsed) {
631 if (data.metadata.collapsed) {
632 this.collapse_output();
632 this.collapse_output();
633 } else {
633 } else {
634 this.expand_output();
634 this.expand_output();
635 }
635 }
636 }
636 }
637 }
637 }
638 };
638 };
639
639
640
640
641 CodeCell.prototype.toJSON = function () {
641 CodeCell.prototype.toJSON = function () {
642 var data = Cell.prototype.toJSON.apply(this);
642 var data = Cell.prototype.toJSON.apply(this);
643 data.source = this.get_text();
643 data.source = this.get_text();
644 // is finite protect against undefined and '*' value
644 // is finite protect against undefined and '*' value
645 if (isFinite(this.input_prompt_number)) {
645 if (isFinite(this.input_prompt_number)) {
646 data.execution_count = this.input_prompt_number;
646 data.execution_count = this.input_prompt_number;
647 } else {
647 } else {
648 data.execution_count = null;
648 data.execution_count = null;
649 }
649 }
650 var outputs = this.output_area.toJSON();
650 var outputs = this.output_area.toJSON();
651 data.outputs = outputs;
651 data.outputs = outputs;
652 data.metadata.trusted = this.output_area.trusted;
652 data.metadata.trusted = this.output_area.trusted;
653 data.metadata.collapsed = this.output_area.collapsed;
653 data.metadata.collapsed = this.output_area.collapsed;
654 return data;
654 return data;
655 };
655 };
656
656
657 /**
657 /**
658 * handle cell level logic when a cell is unselected
658 * handle cell level logic when a cell is unselected
659 * @method unselect
659 * @method unselect
660 * @return is the action being taken
660 * @return is the action being taken
661 */
661 */
662 CodeCell.prototype.unselect = function () {
662 CodeCell.prototype.unselect = function () {
663 var cont = Cell.prototype.unselect.apply(this);
663 var cont = Cell.prototype.unselect.apply(this);
664 if (cont) {
664 if (cont) {
665 // When a code cell is usnelected, make sure that the corresponding
665 // When a code cell is usnelected, make sure that the corresponding
666 // tooltip and completer to that cell is closed.
666 // tooltip and completer to that cell is closed.
667 this.tooltip.remove_and_cancel_tooltip(true);
667 this.tooltip.remove_and_cancel_tooltip(true);
668 if (this.completer !== null) {
668 if (this.completer !== null) {
669 this.completer.close();
669 this.completer.close();
670 }
670 }
671 }
671 }
672 return cont;
672 return cont;
673 };
673 };
674
674
675 // Backwards compatability.
675 // Backwards compatability.
676 IPython.CodeCell = CodeCell;
676 IPython.CodeCell = CodeCell;
677
677
678 return {'CodeCell': CodeCell};
678 return {'CodeCell': CodeCell};
679 });
679 });
@@ -1,386 +1,385 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 'notebook/js/tour',
9 'notebook/js/tour',
10 'bootstrap',
10 'bootstrap',
11 'moment',
11 'moment',
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 "use strict";
13 "use strict";
14
14
15 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
16 /**
16 /**
17 * Constructor
17 * Constructor
18 *
18 *
19 * A MenuBar Class to generate the menubar of IPython notebook
19 * A MenuBar Class to generate the menubar of IPython notebook
20 *
20 *
21 * Parameters:
21 * Parameters:
22 * selector: string
22 * selector: string
23 * options: dictionary
23 * options: dictionary
24 * Dictionary of keyword arguments.
24 * Dictionary of keyword arguments.
25 * notebook: Notebook instance
25 * notebook: Notebook instance
26 * contents: ContentManager instance
26 * contents: ContentManager instance
27 * events: $(Events) instance
27 * events: $(Events) instance
28 * save_widget: SaveWidget instance
28 * save_widget: SaveWidget instance
29 * quick_help: QuickHelp instance
29 * quick_help: QuickHelp instance
30 * base_url : string
30 * base_url : string
31 * notebook_path : string
31 * notebook_path : string
32 * notebook_name : string
32 * notebook_name : string
33 */
33 */
34 options = options || {};
34 options = options || {};
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 this.selector = selector;
36 this.selector = selector;
37 this.notebook = options.notebook;
37 this.notebook = options.notebook;
38 this.contents = options.contents;
38 this.contents = options.contents;
39 this.events = options.events;
39 this.events = options.events;
40 this.save_widget = options.save_widget;
40 this.save_widget = options.save_widget;
41 this.quick_help = options.quick_help;
41 this.quick_help = options.quick_help;
42
42
43 try {
43 try {
44 this.tour = new tour.Tour(this.notebook, this.events);
44 this.tour = new tour.Tour(this.notebook, this.events);
45 } catch (e) {
45 } catch (e) {
46 this.tour = undefined;
46 this.tour = undefined;
47 console.log("Failed to instantiate Notebook Tour", e);
47 console.log("Failed to instantiate Notebook Tour", e);
48 }
48 }
49
49
50 if (this.selector !== undefined) {
50 if (this.selector !== undefined) {
51 this.element = $(selector);
51 this.element = $(selector);
52 this.style();
52 this.style();
53 this.bind_events();
53 this.bind_events();
54 }
54 }
55 };
55 };
56
56
57 // TODO: This has definitively nothing to do with style ...
57 // TODO: This has definitively nothing to do with style ...
58 MenuBar.prototype.style = function () {
58 MenuBar.prototype.style = function () {
59 var that = this;
59 var that = this;
60 this.element.find("li").click(function (event, ui) {
60 this.element.find("li").click(function (event, ui) {
61 // The selected cell loses focus when the menu is entered, so we
61 // The selected cell loses focus when the menu is entered, so we
62 // re-select it upon selection.
62 // re-select it upon selection.
63 var i = that.notebook.get_selected_index();
63 var i = that.notebook.get_selected_index();
64 that.notebook.select(i);
64 that.notebook.select(i);
65 }
65 }
66 );
66 );
67 };
67 };
68
68
69 MenuBar.prototype._nbconvert = function (format, download) {
69 MenuBar.prototype._nbconvert = function (format, download) {
70 download = download || false;
70 download = download || false;
71 var notebook_path = this.notebook.notebook_path;
71 var notebook_path = this.notebook.notebook_path;
72 var url = utils.url_join_encode(
72 var url = utils.url_join_encode(
73 this.base_url,
73 this.base_url,
74 'nbconvert',
74 'nbconvert',
75 format,
75 format,
76 notebook_path
76 notebook_path
77 ) + "?download=" + download.toString();
77 ) + "?download=" + download.toString();
78
78
79 var w = window.open()
79 var w = window.open()
80 if (this.notebook.dirty) {
80 if (this.notebook.dirty) {
81 this.notebook.save_notebook().then(function() {
81 this.notebook.save_notebook().then(function() {
82 w.location = url;
82 w.location = url;
83 });
83 });
84 } else {
84 } else {
85 w.location = url;
85 w.location = url;
86 }
86 }
87 };
87 };
88
88
89 MenuBar.prototype._size_header = function() {
89 MenuBar.prototype._size_header = function() {
90 /**
90 /**
91 * Update header spacer size.
91 * Update header spacer size.
92 */
92 */
93 this.events.trigger('resize-header.Page');
93 this.events.trigger('resize-header.Page');
94 };
94 };
95
95
96 MenuBar.prototype.bind_events = function () {
96 MenuBar.prototype.bind_events = function () {
97 /**
97 /**
98 * File
98 * File
99 */
99 */
100 var that = this;
100 var that = this;
101 this.element.find('#new_notebook').click(function () {
101 this.element.find('#new_notebook').click(function () {
102 var w = window.open();
102 var w = window.open();
103 // Create a new notebook in the same path as the current
103 // Create a new notebook in the same path as the current
104 // notebook's path.
104 // notebook's path.
105 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
105 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
106 that.contents.new_untitled(parent, {type: "notebook"}).then(
106 that.contents.new_untitled(parent, {type: "notebook"}).then(
107 function (data) {
107 function (data) {
108 w.location = utils.url_join_encode(
108 w.location = utils.url_join_encode(
109 that.base_url, 'notebooks', data.path
109 that.base_url, 'notebooks', data.path
110 );
110 );
111 },
111 },
112 function(error) {
112 function(error) {
113 w.close();
113 w.close();
114 dialog.modal({
114 dialog.modal({
115 title : 'Creating Notebook Failed',
115 title : 'Creating Notebook Failed',
116 body : "The error was: " + error.message,
116 body : "The error was: " + error.message,
117 buttons : {'OK' : {'class' : 'btn-primary'}}
117 buttons : {'OK' : {'class' : 'btn-primary'}}
118 });
118 });
119 }
119 }
120 );
120 );
121 });
121 });
122 this.element.find('#open_notebook').click(function () {
122 this.element.find('#open_notebook').click(function () {
123 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
123 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
124 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
124 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
125 });
125 });
126 this.element.find('#copy_notebook').click(function () {
126 this.element.find('#copy_notebook').click(function () {
127 that.notebook.copy_notebook();
127 that.notebook.copy_notebook();
128 return false;
128 return false;
129 });
129 });
130 this.element.find('#download_ipynb').click(function () {
130 this.element.find('#download_ipynb').click(function () {
131 var base_url = that.notebook.base_url;
131 var base_url = that.notebook.base_url;
132 var notebook_path = that.notebook.notebook_path;
132 var notebook_path = that.notebook.notebook_path;
133 if (that.notebook.dirty) {
133 if (that.notebook.dirty) {
134 that.notebook.save_notebook({async : false});
134 that.notebook.save_notebook({async : false});
135 }
135 }
136
136
137 var url = utils.url_join_encode(base_url, 'files', notebook_path);
137 var url = utils.url_join_encode(base_url, 'files', notebook_path);
138 window.open(url + '?download=1');
138 window.open(url + '?download=1');
139 });
139 });
140
140
141 this.element.find('#print_preview').click(function () {
141 this.element.find('#print_preview').click(function () {
142 that._nbconvert('html', false);
142 that._nbconvert('html', false);
143 });
143 });
144
144
145 this.element.find('#download_html').click(function () {
145 this.element.find('#download_html').click(function () {
146 that._nbconvert('html', true);
146 that._nbconvert('html', true);
147 });
147 });
148
148
149 this.element.find('#download_rst').click(function () {
149 this.element.find('#download_rst').click(function () {
150 that._nbconvert('rst', true);
150 that._nbconvert('rst', true);
151 });
151 });
152
152
153 this.element.find('#download_pdf').click(function () {
153 this.element.find('#download_pdf').click(function () {
154 that._nbconvert('pdf', true);
154 that._nbconvert('pdf', true);
155 });
155 });
156
156
157 this.element.find('#download_script').click(function () {
157 this.element.find('#download_script').click(function () {
158 that._nbconvert('script', true);
158 that._nbconvert('script', true);
159 });
159 });
160
160
161 this.element.find('#rename_notebook').click(function () {
161 this.element.find('#rename_notebook').click(function () {
162 that.save_widget.rename_notebook({notebook: that.notebook});
162 that.save_widget.rename_notebook({notebook: that.notebook});
163 });
163 });
164 this.element.find('#save_checkpoint').click(function () {
164 this.element.find('#save_checkpoint').click(function () {
165 that.notebook.save_checkpoint();
165 that.notebook.save_checkpoint();
166 });
166 });
167 this.element.find('#restore_checkpoint').click(function () {
167 this.element.find('#restore_checkpoint').click(function () {
168 });
168 });
169 this.element.find('#trust_notebook').click(function () {
169 this.element.find('#trust_notebook').click(function () {
170 that.notebook.trust_notebook();
170 that.notebook.trust_notebook();
171 });
171 });
172 this.events.on('trust_changed.Notebook', function (event, trusted) {
172 this.events.on('trust_changed.Notebook', function (event, trusted) {
173 if (trusted) {
173 if (trusted) {
174 that.element.find('#trust_notebook')
174 that.element.find('#trust_notebook')
175 .addClass("disabled")
175 .addClass("disabled")
176 .find("a").text("Trusted Notebook");
176 .find("a").text("Trusted Notebook");
177 } else {
177 } else {
178 that.element.find('#trust_notebook')
178 that.element.find('#trust_notebook')
179 .removeClass("disabled")
179 .removeClass("disabled")
180 .find("a").text("Trust Notebook");
180 .find("a").text("Trust Notebook");
181 }
181 }
182 });
182 });
183 this.element.find('#kill_and_exit').click(function () {
183 this.element.find('#kill_and_exit').click(function () {
184 var close_window = function () {
184 var close_window = function () {
185 /**
185 /**
186 * allow closing of new tabs in Chromium, impossible in FF
186 * allow closing of new tabs in Chromium, impossible in FF
187 */
187 */
188 window.open('', '_self', '');
188 window.open('', '_self', '');
189 window.close();
189 window.close();
190 };
190 };
191 // finish with close on success or failure
191 // finish with close on success or failure
192 that.notebook.session.delete(close_window, close_window);
192 that.notebook.session.delete(close_window, close_window);
193 });
193 });
194 // Edit
194 // Edit
195 this.element.find('#cut_cell').click(function () {
195 this.element.find('#cut_cell').click(function () {
196 that.notebook.cut_cell();
196 that.notebook.cut_cell();
197 });
197 });
198 this.element.find('#copy_cell').click(function () {
198 this.element.find('#copy_cell').click(function () {
199 that.notebook.copy_cell();
199 that.notebook.copy_cell();
200 });
200 });
201 this.element.find('#delete_cell').click(function () {
201 this.element.find('#delete_cell').click(function () {
202 that.notebook.delete_cell();
202 that.notebook.delete_cell();
203 });
203 });
204 this.element.find('#undelete_cell').click(function () {
204 this.element.find('#undelete_cell').click(function () {
205 that.notebook.undelete_cell();
205 that.notebook.undelete_cell();
206 });
206 });
207 this.element.find('#split_cell').click(function () {
207 this.element.find('#split_cell').click(function () {
208 that.notebook.split_cell();
208 that.notebook.split_cell();
209 });
209 });
210 this.element.find('#merge_cell_above').click(function () {
210 this.element.find('#merge_cell_above').click(function () {
211 that.notebook.merge_cell_above();
211 that.notebook.merge_cell_above();
212 });
212 });
213 this.element.find('#merge_cell_below').click(function () {
213 this.element.find('#merge_cell_below').click(function () {
214 that.notebook.merge_cell_below();
214 that.notebook.merge_cell_below();
215 });
215 });
216 this.element.find('#move_cell_up').click(function () {
216 this.element.find('#move_cell_up').click(function () {
217 that.notebook.move_cell_up();
217 that.notebook.move_cell_up();
218 });
218 });
219 this.element.find('#move_cell_down').click(function () {
219 this.element.find('#move_cell_down').click(function () {
220 that.notebook.move_cell_down();
220 that.notebook.move_cell_down();
221 });
221 });
222 this.element.find('#edit_nb_metadata').click(function () {
222 this.element.find('#edit_nb_metadata').click(function () {
223 that.notebook.edit_metadata({
223 that.notebook.edit_metadata({
224 notebook: that.notebook,
224 notebook: that.notebook,
225 keyboard_manager: that.notebook.keyboard_manager});
225 keyboard_manager: that.notebook.keyboard_manager});
226 });
226 });
227
227
228 // View
228 // View
229 this.element.find('#toggle_header').click(function () {
229 this.element.find('#toggle_header').click(function () {
230 $('div#header-container').toggle();
230 $('div#header-container').toggle();
231 that._size_header();
231 that._size_header();
232 });
232 });
233 this.element.find('#toggle_toolbar').click(function () {
233 this.element.find('#toggle_toolbar').click(function () {
234 $('div#maintoolbar').toggle();
234 $('div#maintoolbar').toggle();
235 that._size_header();
235 that._size_header();
236 });
236 });
237 // Insert
237 // Insert
238 this.element.find('#insert_cell_above').click(function () {
238 this.element.find('#insert_cell_above').click(function () {
239 that.notebook.insert_cell_above('code');
239 that.notebook.insert_cell_above('code');
240 that.notebook.select_prev();
240 that.notebook.select_prev();
241 });
241 });
242 this.element.find('#insert_cell_below').click(function () {
242 this.element.find('#insert_cell_below').click(function () {
243 that.notebook.insert_cell_below('code');
243 that.notebook.insert_cell_below('code');
244 that.notebook.select_next();
244 that.notebook.select_next();
245 });
245 });
246 // Cell
246 // Cell
247 this.element.find('#run_cell').click(function () {
247 this.element.find('#run_cell').click(function () {
248 that.notebook.execute_cell();
248 that.notebook.execute_cell();
249 });
249 });
250 this.element.find('#run_cell_select_below').click(function () {
250 this.element.find('#run_cell_select_below').click(function () {
251 that.notebook.execute_cell_and_select_below();
251 that.notebook.execute_cell_and_select_below();
252 });
252 });
253 this.element.find('#run_cell_insert_below').click(function () {
253 this.element.find('#run_cell_insert_below').click(function () {
254 that.notebook.execute_cell_and_insert_below();
254 that.notebook.execute_cell_and_insert_below();
255 });
255 });
256 this.element.find('#run_all_cells').click(function () {
256 this.element.find('#run_all_cells').click(function () {
257 that.notebook.execute_all_cells();
257 that.notebook.execute_all_cells();
258 });
258 });
259 this.element.find('#run_all_cells_above').click(function () {
259 this.element.find('#run_all_cells_above').click(function () {
260 that.notebook.execute_cells_above();
260 that.notebook.execute_cells_above();
261 });
261 });
262 this.element.find('#run_all_cells_below').click(function () {
262 this.element.find('#run_all_cells_below').click(function () {
263 that.notebook.execute_cells_below();
263 that.notebook.execute_cells_below();
264 });
264 });
265 this.element.find('#to_code').click(function () {
265 this.element.find('#to_code').click(function () {
266 that.notebook.to_code();
266 that.notebook.to_code();
267 });
267 });
268 this.element.find('#to_markdown').click(function () {
268 this.element.find('#to_markdown').click(function () {
269 that.notebook.to_markdown();
269 that.notebook.to_markdown();
270 });
270 });
271 this.element.find('#to_raw').click(function () {
271 this.element.find('#to_raw').click(function () {
272 that.notebook.to_raw();
272 that.notebook.to_raw();
273 });
273 });
274
274
275 this.element.find('#toggle_current_output').click(function () {
275 this.element.find('#toggle_current_output').click(function () {
276 that.notebook.toggle_output();
276 that.notebook.toggle_output();
277 });
277 });
278 this.element.find('#toggle_current_output_scroll').click(function () {
278 this.element.find('#toggle_current_output_scroll').click(function () {
279 that.notebook.toggle_output_scroll();
279 that.notebook.toggle_output_scroll();
280 });
280 });
281 this.element.find('#clear_current_output').click(function () {
281 this.element.find('#clear_current_output').click(function () {
282 that.notebook.clear_output();
282 that.notebook.clear_output();
283 });
283 });
284
284
285 this.element.find('#toggle_all_output').click(function () {
285 this.element.find('#toggle_all_output').click(function () {
286 that.notebook.toggle_all_output();
286 that.notebook.toggle_all_output();
287 });
287 });
288 this.element.find('#toggle_all_output_scroll').click(function () {
288 this.element.find('#toggle_all_output_scroll').click(function () {
289 that.notebook.toggle_all_output_scroll();
289 that.notebook.toggle_all_output_scroll();
290 });
290 });
291 this.element.find('#clear_all_output').click(function () {
291 this.element.find('#clear_all_output').click(function () {
292 that.notebook.clear_all_output();
292 that.notebook.clear_all_output();
293 });
293 });
294
294
295 // Kernel
295 // Kernel
296 this.element.find('#int_kernel').click(function () {
296 this.element.find('#int_kernel').click(function () {
297 that.notebook.kernel.interrupt();
297 that.notebook.kernel.interrupt();
298 });
298 });
299 this.element.find('#restart_kernel').click(function () {
299 this.element.find('#restart_kernel').click(function () {
300 that.notebook.restart_kernel();
300 that.notebook.restart_kernel();
301 });
301 });
302 this.element.find('#reconnect_kernel').click(function () {
302 this.element.find('#reconnect_kernel').click(function () {
303 that.notebook.kernel.reconnect();
303 that.notebook.kernel.reconnect();
304 });
304 });
305 // Help
305 // Help
306 if (this.tour) {
306 if (this.tour) {
307 this.element.find('#notebook_tour').click(function () {
307 this.element.find('#notebook_tour').click(function () {
308 that.tour.start();
308 that.tour.start();
309 });
309 });
310 } else {
310 } else {
311 this.element.find('#notebook_tour').addClass("disabled");
311 this.element.find('#notebook_tour').addClass("disabled");
312 }
312 }
313 this.element.find('#keyboard_shortcuts').click(function () {
313 this.element.find('#keyboard_shortcuts').click(function () {
314 that.quick_help.show_keyboard_shortcuts();
314 that.quick_help.show_keyboard_shortcuts();
315 });
315 });
316
316
317 this.update_restore_checkpoint(null);
317 this.update_restore_checkpoint(null);
318
318
319 this.events.on('checkpoints_listed.Notebook', function (event, data) {
319 this.events.on('checkpoints_listed.Notebook', function (event, data) {
320 that.update_restore_checkpoint(that.notebook.checkpoints);
320 that.update_restore_checkpoint(that.notebook.checkpoints);
321 });
321 });
322
322
323 this.events.on('checkpoint_created.Notebook', function (event, data) {
323 this.events.on('checkpoint_created.Notebook', function (event, data) {
324 that.update_restore_checkpoint(that.notebook.checkpoints);
324 that.update_restore_checkpoint(that.notebook.checkpoints);
325 });
325 });
326
326
327 this.events.on('notebook_loaded.Notebook', function() {
327 this.events.on('notebook_loaded.Notebook', function() {
328 var langinfo = that.notebook.metadata.language_info || {};
328 var langinfo = that.notebook.metadata.language_info || {};
329 that.update_nbconvert_script(langinfo);
329 that.update_nbconvert_script(langinfo);
330 });
330 });
331
331
332 this.events.on('kernel_ready.Kernel', function(event, data) {
332 this.events.on('kernel_ready.Kernel', function(event, data) {
333 var langinfo = data.kernel.info_reply.language_info || {};
333 var langinfo = data.kernel.info_reply.language_info || {};
334 that.update_nbconvert_script(langinfo);
334 that.update_nbconvert_script(langinfo);
335 });
335 });
336 };
336 };
337
337
338 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
338 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
339 var ul = this.element.find("#restore_checkpoint").find("ul");
339 var ul = this.element.find("#restore_checkpoint").find("ul");
340 ul.empty();
340 ul.empty();
341 if (!checkpoints || checkpoints.length === 0) {
341 if (!checkpoints || checkpoints.length === 0) {
342 ul.append(
342 ul.append(
343 $("<li/>")
343 $("<li/>")
344 .addClass("disabled")
344 .addClass("disabled")
345 .append(
345 .append(
346 $("<a/>")
346 $("<a/>")
347 .text("No checkpoints")
347 .text("No checkpoints")
348 )
348 )
349 );
349 );
350 return;
350 return;
351 }
351 }
352
352
353 var that = this;
353 var that = this;
354 checkpoints.map(function (checkpoint) {
354 checkpoints.map(function (checkpoint) {
355 var d = new Date(checkpoint.last_modified);
355 var d = new Date(checkpoint.last_modified);
356 ul.append(
356 ul.append(
357 $("<li/>").append(
357 $("<li/>").append(
358 $("<a/>")
358 $("<a/>")
359 .attr("href", "#")
359 .attr("href", "#")
360 .text(moment(d).format("LLLL"))
360 .text(moment(d).format("LLLL"))
361 .click(function () {
361 .click(function () {
362 that.notebook.restore_checkpoint_dialog(checkpoint);
362 that.notebook.restore_checkpoint_dialog(checkpoint);
363 })
363 })
364 )
364 )
365 );
365 );
366 });
366 });
367 };
367 };
368
368
369 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
369 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
370 /**
370 /**
371 * Set the 'Download as foo' menu option for the relevant language.
371 * Set the 'Download as foo' menu option for the relevant language.
372 */
372 */
373 var el = this.element.find('#download_script');
373 var el = this.element.find('#download_script');
374 var that = this;
375
374
376 // Set menu entry text to e.g. "Python (.py)"
375 // Set menu entry text to e.g. "Python (.py)"
377 var langname = (langinfo.name || 'Script')
376 var langname = (langinfo.name || 'Script')
378 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
377 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
379 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
378 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
380 };
379 };
381
380
382 // Backwards compatability.
381 // Backwards compatability.
383 IPython.MenuBar = MenuBar;
382 IPython.MenuBar = MenuBar;
384
383
385 return {'MenuBar': MenuBar};
384 return {'MenuBar': MenuBar};
386 });
385 });
@@ -1,2428 +1,2428 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 /**
4 /**
5 * @module notebook
5 * @module notebook
6 */
6 */
7 define([
7 define([
8 'base/js/namespace',
8 'base/js/namespace',
9 'jquery',
9 'jquery',
10 'base/js/utils',
10 'base/js/utils',
11 'base/js/dialog',
11 'base/js/dialog',
12 'notebook/js/cell',
12 'notebook/js/cell',
13 'notebook/js/textcell',
13 'notebook/js/textcell',
14 'notebook/js/codecell',
14 'notebook/js/codecell',
15 'services/config',
15 'services/config',
16 'services/sessions/session',
16 'services/sessions/session',
17 'notebook/js/celltoolbar',
17 'notebook/js/celltoolbar',
18 'components/marked/lib/marked',
18 'components/marked/lib/marked',
19 'codemirror/lib/codemirror',
19 'codemirror/lib/codemirror',
20 'codemirror/addon/runmode/runmode',
20 'codemirror/addon/runmode/runmode',
21 'notebook/js/mathjaxutils',
21 'notebook/js/mathjaxutils',
22 'base/js/keyboard',
22 'base/js/keyboard',
23 'notebook/js/tooltip',
23 'notebook/js/tooltip',
24 'notebook/js/celltoolbarpresets/default',
24 'notebook/js/celltoolbarpresets/default',
25 'notebook/js/celltoolbarpresets/rawcell',
25 'notebook/js/celltoolbarpresets/rawcell',
26 'notebook/js/celltoolbarpresets/slideshow',
26 'notebook/js/celltoolbarpresets/slideshow',
27 'notebook/js/scrollmanager'
27 'notebook/js/scrollmanager'
28 ], function (
28 ], function (
29 IPython,
29 IPython,
30 $,
30 $,
31 utils,
31 utils,
32 dialog,
32 dialog,
33 cellmod,
33 cellmod,
34 textcell,
34 textcell,
35 codecell,
35 codecell,
36 configmod,
36 configmod,
37 session,
37 session,
38 celltoolbar,
38 celltoolbar,
39 marked,
39 marked,
40 CodeMirror,
40 CodeMirror,
41 runMode,
41 runMode,
42 mathjaxutils,
42 mathjaxutils,
43 keyboard,
43 keyboard,
44 tooltip,
44 tooltip,
45 default_celltoolbar,
45 default_celltoolbar,
46 rawcell_celltoolbar,
46 rawcell_celltoolbar,
47 slideshow_celltoolbar,
47 slideshow_celltoolbar,
48 scrollmanager
48 scrollmanager
49 ) {
49 ) {
50 "use strict";
50 "use strict";
51
51
52 /**
52 /**
53 * Contains and manages cells.
53 * Contains and manages cells.
54 *
54 *
55 * @class Notebook
55 * @class Notebook
56 * @param {string} selector
56 * @param {string} selector
57 * @param {object} options - Dictionary of keyword arguments.
57 * @param {object} options - Dictionary of keyword arguments.
58 * @param {jQuery} options.events - selector of Events
58 * @param {jQuery} options.events - selector of Events
59 * @param {KeyboardManager} options.keyboard_manager
59 * @param {KeyboardManager} options.keyboard_manager
60 * @param {Contents} options.contents
60 * @param {Contents} options.contents
61 * @param {SaveWidget} options.save_widget
61 * @param {SaveWidget} options.save_widget
62 * @param {object} options.config
62 * @param {object} options.config
63 * @param {string} options.base_url
63 * @param {string} options.base_url
64 * @param {string} options.notebook_path
64 * @param {string} options.notebook_path
65 * @param {string} options.notebook_name
65 * @param {string} options.notebook_name
66 */
66 */
67 var Notebook = function (selector, options) {
67 var Notebook = function (selector, options) {
68 this.config = options.config;
68 this.config = options.config;
69 this.class_config = new configmod.ConfigWithDefaults(this.config,
69 this.class_config = new configmod.ConfigWithDefaults(this.config,
70 Notebook.options_default, 'Notebook');
70 Notebook.options_default, 'Notebook');
71 this.base_url = options.base_url;
71 this.base_url = options.base_url;
72 this.notebook_path = options.notebook_path;
72 this.notebook_path = options.notebook_path;
73 this.notebook_name = options.notebook_name;
73 this.notebook_name = options.notebook_name;
74 this.events = options.events;
74 this.events = options.events;
75 this.keyboard_manager = options.keyboard_manager;
75 this.keyboard_manager = options.keyboard_manager;
76 this.contents = options.contents;
76 this.contents = options.contents;
77 this.save_widget = options.save_widget;
77 this.save_widget = options.save_widget;
78 this.tooltip = new tooltip.Tooltip(this.events);
78 this.tooltip = new tooltip.Tooltip(this.events);
79 this.ws_url = options.ws_url;
79 this.ws_url = options.ws_url;
80 this._session_starting = false;
80 this._session_starting = false;
81
81
82 // Create default scroll manager.
82 // Create default scroll manager.
83 this.scroll_manager = new scrollmanager.ScrollManager(this);
83 this.scroll_manager = new scrollmanager.ScrollManager(this);
84
84
85 // TODO: This code smells (and the other `= this` line a couple lines down)
85 // TODO: This code smells (and the other `= this` line a couple lines down)
86 // We need a better way to deal with circular instance references.
86 // We need a better way to deal with circular instance references.
87 this.keyboard_manager.notebook = this;
87 this.keyboard_manager.notebook = this;
88 this.save_widget.notebook = this;
88 this.save_widget.notebook = this;
89
89
90 mathjaxutils.init();
90 mathjaxutils.init();
91
91
92 if (marked) {
92 if (marked) {
93 marked.setOptions({
93 marked.setOptions({
94 gfm : true,
94 gfm : true,
95 tables: true,
95 tables: true,
96 // FIXME: probably want central config for CodeMirror theme when we have js config
96 // FIXME: probably want central config for CodeMirror theme when we have js config
97 langPrefix: "cm-s-ipython language-",
97 langPrefix: "cm-s-ipython language-",
98 highlight: function(code, lang, callback) {
98 highlight: function(code, lang, callback) {
99 if (!lang) {
99 if (!lang) {
100 // no language, no highlight
100 // no language, no highlight
101 if (callback) {
101 if (callback) {
102 callback(null, code);
102 callback(null, code);
103 return;
103 return;
104 } else {
104 } else {
105 return code;
105 return code;
106 }
106 }
107 }
107 }
108 utils.requireCodeMirrorMode(lang, function (spec) {
108 utils.requireCodeMirrorMode(lang, function (spec) {
109 var el = document.createElement("div");
109 var el = document.createElement("div");
110 var mode = CodeMirror.getMode({}, spec);
110 var mode = CodeMirror.getMode({}, spec);
111 if (!mode) {
111 if (!mode) {
112 console.log("No CodeMirror mode: " + lang);
112 console.log("No CodeMirror mode: " + lang);
113 callback(null, code);
113 callback(null, code);
114 return;
114 return;
115 }
115 }
116 try {
116 try {
117 CodeMirror.runMode(code, spec, el);
117 CodeMirror.runMode(code, spec, el);
118 callback(null, el.innerHTML);
118 callback(null, el.innerHTML);
119 } catch (err) {
119 } catch (err) {
120 console.log("Failed to highlight " + lang + " code", err);
120 console.log("Failed to highlight " + lang + " code", err);
121 callback(err, code);
121 callback(err, code);
122 }
122 }
123 }, function (err) {
123 }, function (err) {
124 console.log("No CodeMirror mode: " + lang);
124 console.log("No CodeMirror mode: " + lang);
125 callback(err, code);
125 callback(err, code);
126 });
126 });
127 }
127 }
128 });
128 });
129 }
129 }
130
130
131 this.element = $(selector);
131 this.element = $(selector);
132 this.element.scroll();
132 this.element.scroll();
133 this.element.data("notebook", this);
133 this.element.data("notebook", this);
134 this.next_prompt_number = 1;
134 this.next_prompt_number = 1;
135 this.session = null;
135 this.session = null;
136 this.kernel = null;
136 this.kernel = null;
137 this.clipboard = null;
137 this.clipboard = null;
138 this.undelete_backup = null;
138 this.undelete_backup = null;
139 this.undelete_index = null;
139 this.undelete_index = null;
140 this.undelete_below = false;
140 this.undelete_below = false;
141 this.paste_enabled = false;
141 this.paste_enabled = false;
142 this.writable = false;
142 this.writable = false;
143 // It is important to start out in command mode to match the intial mode
143 // It is important to start out in command mode to match the intial mode
144 // of the KeyboardManager.
144 // of the KeyboardManager.
145 this.mode = 'command';
145 this.mode = 'command';
146 this.set_dirty(false);
146 this.set_dirty(false);
147 this.metadata = {};
147 this.metadata = {};
148 this._checkpoint_after_save = false;
148 this._checkpoint_after_save = false;
149 this.last_checkpoint = null;
149 this.last_checkpoint = null;
150 this.checkpoints = [];
150 this.checkpoints = [];
151 this.autosave_interval = 0;
151 this.autosave_interval = 0;
152 this.autosave_timer = null;
152 this.autosave_timer = null;
153 // autosave *at most* every two minutes
153 // autosave *at most* every two minutes
154 this.minimum_autosave_interval = 120000;
154 this.minimum_autosave_interval = 120000;
155 this.notebook_name_blacklist_re = /[\/\\:]/;
155 this.notebook_name_blacklist_re = /[\/\\:]/;
156 this.nbformat = 4; // Increment this when changing the nbformat
156 this.nbformat = 4; // Increment this when changing the nbformat
157 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
157 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
158 this.codemirror_mode = 'ipython';
158 this.codemirror_mode = 'ipython';
159 this.create_elements();
159 this.create_elements();
160 this.bind_events();
160 this.bind_events();
161 this.kernel_selector = null;
161 this.kernel_selector = null;
162 this.dirty = null;
162 this.dirty = null;
163 this.trusted = null;
163 this.trusted = null;
164 this._fully_loaded = false;
164 this._fully_loaded = false;
165
165
166 // Trigger cell toolbar registration.
166 // Trigger cell toolbar registration.
167 default_celltoolbar.register(this);
167 default_celltoolbar.register(this);
168 rawcell_celltoolbar.register(this);
168 rawcell_celltoolbar.register(this);
169 slideshow_celltoolbar.register(this);
169 slideshow_celltoolbar.register(this);
170
170
171 // prevent assign to miss-typed properties.
171 // prevent assign to miss-typed properties.
172 Object.seal(this);
172 Object.seal(this);
173 };
173 };
174
174
175 Notebook.options_default = {
175 Notebook.options_default = {
176 // can be any cell type, or the special values of
176 // can be any cell type, or the special values of
177 // 'above', 'below', or 'selected' to get the value from another cell.
177 // 'above', 'below', or 'selected' to get the value from another cell.
178 default_cell_type: 'code'
178 default_cell_type: 'code'
179 };
179 };
180
180
181 /**
181 /**
182 * Create an HTML and CSS representation of the notebook.
182 * Create an HTML and CSS representation of the notebook.
183 */
183 */
184 Notebook.prototype.create_elements = function () {
184 Notebook.prototype.create_elements = function () {
185 var that = this;
185 var that = this;
186 this.element.attr('tabindex','-1');
186 this.element.attr('tabindex','-1');
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
188 // We add this end_space div to the end of the notebook div to:
188 // We add this end_space div to the end of the notebook div to:
189 // i) provide a margin between the last cell and the end of the notebook
189 // i) provide a margin between the last cell and the end of the notebook
190 // ii) to prevent the div from scrolling up when the last cell is being
190 // ii) to prevent the div from scrolling up when the last cell is being
191 // edited, but is too low on the page, which browsers will do automatically.
191 // edited, but is too low on the page, which browsers will do automatically.
192 var end_space = $('<div/>').addClass('end_space');
192 var end_space = $('<div/>').addClass('end_space');
193 end_space.dblclick(function (e) {
193 end_space.dblclick(function (e) {
194 var ncells = that.ncells();
194 var ncells = that.ncells();
195 that.insert_cell_below('code',ncells-1);
195 that.insert_cell_below('code',ncells-1);
196 });
196 });
197 this.element.append(this.container);
197 this.element.append(this.container);
198 this.container.append(end_space);
198 this.container.append(end_space);
199 };
199 };
200
200
201 /**
201 /**
202 * Bind JavaScript events: key presses and custom IPython events.
202 * Bind JavaScript events: key presses and custom IPython events.
203 */
203 */
204 Notebook.prototype.bind_events = function () {
204 Notebook.prototype.bind_events = function () {
205 var that = this;
205 var that = this;
206
206
207 this.events.on('set_next_input.Notebook', function (event, data) {
207 this.events.on('set_next_input.Notebook', function (event, data) {
208 if (data.replace) {
208 if (data.replace) {
209 data.cell.set_text(data.text);
209 data.cell.set_text(data.text);
210 data.cell.clear_output();
210 data.cell.clear_output();
211 } else {
211 } else {
212 var index = that.find_cell_index(data.cell);
212 var index = that.find_cell_index(data.cell);
213 var new_cell = that.insert_cell_below('code',index);
213 var new_cell = that.insert_cell_below('code',index);
214 new_cell.set_text(data.text);
214 new_cell.set_text(data.text);
215 }
215 }
216 that.dirty = true;
216 that.dirty = true;
217 });
217 });
218
218
219 this.events.on('unrecognized_cell.Cell', function () {
219 this.events.on('unrecognized_cell.Cell', function () {
220 that.warn_nbformat_minor();
220 that.warn_nbformat_minor();
221 });
221 });
222
222
223 this.events.on('unrecognized_output.OutputArea', function () {
223 this.events.on('unrecognized_output.OutputArea', function () {
224 that.warn_nbformat_minor();
224 that.warn_nbformat_minor();
225 });
225 });
226
226
227 this.events.on('set_dirty.Notebook', function (event, data) {
227 this.events.on('set_dirty.Notebook', function (event, data) {
228 that.dirty = data.value;
228 that.dirty = data.value;
229 });
229 });
230
230
231 this.events.on('trust_changed.Notebook', function (event, trusted) {
231 this.events.on('trust_changed.Notebook', function (event, trusted) {
232 that.trusted = trusted;
232 that.trusted = trusted;
233 });
233 });
234
234
235 this.events.on('select.Cell', function (event, data) {
235 this.events.on('select.Cell', function (event, data) {
236 var index = that.find_cell_index(data.cell);
236 var index = that.find_cell_index(data.cell);
237 that.select(index);
237 that.select(index);
238 });
238 });
239
239
240 this.events.on('edit_mode.Cell', function (event, data) {
240 this.events.on('edit_mode.Cell', function (event, data) {
241 that.handle_edit_mode(data.cell);
241 that.handle_edit_mode(data.cell);
242 });
242 });
243
243
244 this.events.on('command_mode.Cell', function (event, data) {
244 this.events.on('command_mode.Cell', function (event, data) {
245 that.handle_command_mode(data.cell);
245 that.handle_command_mode(data.cell);
246 });
246 });
247
247
248 this.events.on('spec_changed.Kernel', function(event, data) {
248 this.events.on('spec_changed.Kernel', function(event, data) {
249 that.metadata.kernelspec =
249 that.metadata.kernelspec =
250 {name: data.name, display_name: data.display_name};
250 {name: data.name, display_name: data.display_name};
251 });
251 });
252
252
253 this.events.on('kernel_ready.Kernel', function(event, data) {
253 this.events.on('kernel_ready.Kernel', function(event, data) {
254 var kinfo = data.kernel.info_reply;
254 var kinfo = data.kernel.info_reply;
255 var langinfo = kinfo.language_info || {};
255 var langinfo = kinfo.language_info || {};
256 that.metadata.language_info = langinfo;
256 that.metadata.language_info = langinfo;
257 // Mode 'null' should be plain, unhighlighted text.
257 // Mode 'null' should be plain, unhighlighted text.
258 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
258 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
259 that.set_codemirror_mode(cm_mode);
259 that.set_codemirror_mode(cm_mode);
260 });
260 });
261
261
262 var collapse_time = function (time) {
262 var collapse_time = function (time) {
263 var app_height = $('#ipython-main-app').height(); // content height
263 var app_height = $('#ipython-main-app').height(); // content height
264 var splitter_height = $('div#pager_splitter').outerHeight(true);
264 var splitter_height = $('div#pager_splitter').outerHeight(true);
265 var new_height = app_height - splitter_height;
265 var new_height = app_height - splitter_height;
266 that.element.animate({height : new_height + 'px'}, time);
266 that.element.animate({height : new_height + 'px'}, time);
267 };
267 };
268
268
269 this.element.bind('collapse_pager', function (event, extrap) {
269 this.element.bind('collapse_pager', function (event, extrap) {
270 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
270 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
271 collapse_time(time);
271 collapse_time(time);
272 });
272 });
273
273
274 var expand_time = function (time) {
274 var expand_time = function (time) {
275 var app_height = $('#ipython-main-app').height(); // content height
275 var app_height = $('#ipython-main-app').height(); // content height
276 var splitter_height = $('div#pager_splitter').outerHeight(true);
276 var splitter_height = $('div#pager_splitter').outerHeight(true);
277 var pager_height = $('div#pager').outerHeight(true);
277 var pager_height = $('div#pager').outerHeight(true);
278 var new_height = app_height - pager_height - splitter_height;
278 var new_height = app_height - pager_height - splitter_height;
279 that.element.animate({height : new_height + 'px'}, time);
279 that.element.animate({height : new_height + 'px'}, time);
280 };
280 };
281
281
282 this.element.bind('expand_pager', function (event, extrap) {
282 this.element.bind('expand_pager', function (event, extrap) {
283 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
283 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
284 expand_time(time);
284 expand_time(time);
285 });
285 });
286
286
287 // Firefox 22 broke $(window).on("beforeunload")
287 // Firefox 22 broke $(window).on("beforeunload")
288 // I'm not sure why or how.
288 // I'm not sure why or how.
289 window.onbeforeunload = function (e) {
289 window.onbeforeunload = function (e) {
290 // TODO: Make killing the kernel configurable.
290 // TODO: Make killing the kernel configurable.
291 var kill_kernel = false;
291 var kill_kernel = false;
292 if (kill_kernel) {
292 if (kill_kernel) {
293 that.session.delete();
293 that.session.delete();
294 }
294 }
295 // if we are autosaving, trigger an autosave on nav-away.
295 // if we are autosaving, trigger an autosave on nav-away.
296 // still warn, because if we don't the autosave may fail.
296 // still warn, because if we don't the autosave may fail.
297 if (that.dirty) {
297 if (that.dirty) {
298 if ( that.autosave_interval ) {
298 if ( that.autosave_interval ) {
299 // schedule autosave in a timeout
299 // schedule autosave in a timeout
300 // this gives you a chance to forcefully discard changes
300 // this gives you a chance to forcefully discard changes
301 // by reloading the page if you *really* want to.
301 // by reloading the page if you *really* want to.
302 // the timer doesn't start until you *dismiss* the dialog.
302 // the timer doesn't start until you *dismiss* the dialog.
303 setTimeout(function () {
303 setTimeout(function () {
304 if (that.dirty) {
304 if (that.dirty) {
305 that.save_notebook();
305 that.save_notebook();
306 }
306 }
307 }, 1000);
307 }, 1000);
308 return "Autosave in progress, latest changes may be lost.";
308 return "Autosave in progress, latest changes may be lost.";
309 } else {
309 } else {
310 return "Unsaved changes will be lost.";
310 return "Unsaved changes will be lost.";
311 }
311 }
312 }
312 }
313 // Null is the *only* return value that will make the browser not
313 // Null is the *only* return value that will make the browser not
314 // pop up the "don't leave" dialog.
314 // pop up the "don't leave" dialog.
315 return null;
315 return null;
316 };
316 };
317 };
317 };
318
318
319 /**
319 /**
320 * Trigger a warning dialog about missing functionality from newer minor versions
320 * Trigger a warning dialog about missing functionality from newer minor versions
321 */
321 */
322 Notebook.prototype.warn_nbformat_minor = function (event) {
322 Notebook.prototype.warn_nbformat_minor = function (event) {
323 var v = 'v' + this.nbformat + '.';
323 var v = 'v' + this.nbformat + '.';
324 var orig_vs = v + this.nbformat_minor;
324 var orig_vs = v + this.nbformat_minor;
325 var this_vs = v + this.current_nbformat_minor;
325 var this_vs = v + this.current_nbformat_minor;
326 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
326 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
327 this_vs + ". You can still work with this notebook, but cell and output types " +
327 this_vs + ". You can still work with this notebook, but cell and output types " +
328 "introduced in later notebook versions will not be available.";
328 "introduced in later notebook versions will not be available.";
329
329
330 dialog.modal({
330 dialog.modal({
331 notebook: this,
331 notebook: this,
332 keyboard_manager: this.keyboard_manager,
332 keyboard_manager: this.keyboard_manager,
333 title : "Newer Notebook",
333 title : "Newer Notebook",
334 body : msg,
334 body : msg,
335 buttons : {
335 buttons : {
336 OK : {
336 OK : {
337 "class" : "btn-danger"
337 "class" : "btn-danger"
338 }
338 }
339 }
339 }
340 });
340 });
341 };
341 };
342
342
343 /**
343 /**
344 * Set the dirty flag, and trigger the set_dirty.Notebook event
344 * Set the dirty flag, and trigger the set_dirty.Notebook event
345 */
345 */
346 Notebook.prototype.set_dirty = function (value) {
346 Notebook.prototype.set_dirty = function (value) {
347 if (value === undefined) {
347 if (value === undefined) {
348 value = true;
348 value = true;
349 }
349 }
350 if (this.dirty == value) {
350 if (this.dirty === value) {
351 return;
351 return;
352 }
352 }
353 this.events.trigger('set_dirty.Notebook', {value: value});
353 this.events.trigger('set_dirty.Notebook', {value: value});
354 };
354 };
355
355
356 /**
356 /**
357 * Scroll the top of the page to a given cell.
357 * Scroll the top of the page to a given cell.
358 *
358 *
359 * @param {integer} index - An index of the cell to view
359 * @param {integer} index - An index of the cell to view
360 * @param {integer} time - Animation time in milliseconds
360 * @param {integer} time - Animation time in milliseconds
361 * @return {integer} Pixel offset from the top of the container
361 * @return {integer} Pixel offset from the top of the container
362 */
362 */
363 Notebook.prototype.scroll_to_cell = function (index, time) {
363 Notebook.prototype.scroll_to_cell = function (index, time) {
364 var cells = this.get_cells();
364 var cells = this.get_cells();
365 time = time || 0;
365 time = time || 0;
366 index = Math.min(cells.length-1,index);
366 index = Math.min(cells.length-1,index);
367 index = Math.max(0 ,index);
367 index = Math.max(0 ,index);
368 var scroll_value = cells[index].element.position().top-cells[0].element.position().top ;
368 var scroll_value = cells[index].element.position().top-cells[0].element.position().top ;
369 this.element.animate({scrollTop:scroll_value}, time);
369 this.element.animate({scrollTop:scroll_value}, time);
370 return scroll_value;
370 return scroll_value;
371 };
371 };
372
372
373 /**
373 /**
374 * Scroll to the bottom of the page.
374 * Scroll to the bottom of the page.
375 */
375 */
376 Notebook.prototype.scroll_to_bottom = function () {
376 Notebook.prototype.scroll_to_bottom = function () {
377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
378 };
378 };
379
379
380 /**
380 /**
381 * Scroll to the top of the page.
381 * Scroll to the top of the page.
382 */
382 */
383 Notebook.prototype.scroll_to_top = function () {
383 Notebook.prototype.scroll_to_top = function () {
384 this.element.animate({scrollTop:0}, 0);
384 this.element.animate({scrollTop:0}, 0);
385 };
385 };
386
386
387 // Edit Notebook metadata
387 // Edit Notebook metadata
388
388
389 /**
389 /**
390 * Display a dialog that allows the user to edit the Notebook's metadata.
390 * Display a dialog that allows the user to edit the Notebook's metadata.
391 */
391 */
392 Notebook.prototype.edit_metadata = function () {
392 Notebook.prototype.edit_metadata = function () {
393 var that = this;
393 var that = this;
394 dialog.edit_metadata({
394 dialog.edit_metadata({
395 md: this.metadata,
395 md: this.metadata,
396 callback: function (md) {
396 callback: function (md) {
397 that.metadata = md;
397 that.metadata = md;
398 },
398 },
399 name: 'Notebook',
399 name: 'Notebook',
400 notebook: this,
400 notebook: this,
401 keyboard_manager: this.keyboard_manager});
401 keyboard_manager: this.keyboard_manager});
402 };
402 };
403
403
404 // Cell indexing, retrieval, etc.
404 // Cell indexing, retrieval, etc.
405
405
406 /**
406 /**
407 * Get all cell elements in the notebook.
407 * Get all cell elements in the notebook.
408 *
408 *
409 * @return {jQuery} A selector of all cell elements
409 * @return {jQuery} A selector of all cell elements
410 */
410 */
411 Notebook.prototype.get_cell_elements = function () {
411 Notebook.prototype.get_cell_elements = function () {
412 return this.container.find(".cell").not('.cell .cell');
412 return this.container.find(".cell").not('.cell .cell');
413 };
413 };
414
414
415 /**
415 /**
416 * Get a particular cell element.
416 * Get a particular cell element.
417 *
417 *
418 * @param {integer} index An index of a cell to select
418 * @param {integer} index An index of a cell to select
419 * @return {jQuery} A selector of the given cell.
419 * @return {jQuery} A selector of the given cell.
420 */
420 */
421 Notebook.prototype.get_cell_element = function (index) {
421 Notebook.prototype.get_cell_element = function (index) {
422 var result = null;
422 var result = null;
423 var e = this.get_cell_elements().eq(index);
423 var e = this.get_cell_elements().eq(index);
424 if (e.length !== 0) {
424 if (e.length !== 0) {
425 result = e;
425 result = e;
426 }
426 }
427 return result;
427 return result;
428 };
428 };
429
429
430 /**
430 /**
431 * Try to get a particular cell by msg_id.
431 * Try to get a particular cell by msg_id.
432 *
432 *
433 * @param {string} msg_id A message UUID
433 * @param {string} msg_id A message UUID
434 * @return {Cell} Cell or null if no cell was found.
434 * @return {Cell} Cell or null if no cell was found.
435 */
435 */
436 Notebook.prototype.get_msg_cell = function (msg_id) {
436 Notebook.prototype.get_msg_cell = function (msg_id) {
437 return codecell.CodeCell.msg_cells[msg_id] || null;
437 return codecell.CodeCell.msg_cells[msg_id] || null;
438 };
438 };
439
439
440 /**
440 /**
441 * Count the cells in this notebook.
441 * Count the cells in this notebook.
442 *
442 *
443 * @return {integer} The number of cells in this notebook
443 * @return {integer} The number of cells in this notebook
444 */
444 */
445 Notebook.prototype.ncells = function () {
445 Notebook.prototype.ncells = function () {
446 return this.get_cell_elements().length;
446 return this.get_cell_elements().length;
447 };
447 };
448
448
449 /**
449 /**
450 * Get all Cell objects in this notebook.
450 * Get all Cell objects in this notebook.
451 *
451 *
452 * @return {Array} This notebook's Cell objects
452 * @return {Array} This notebook's Cell objects
453 */
453 */
454 Notebook.prototype.get_cells = function () {
454 Notebook.prototype.get_cells = function () {
455 // TODO: we are often calling cells as cells()[i], which we should optimize
455 // TODO: we are often calling cells as cells()[i], which we should optimize
456 // to cells(i) or a new method.
456 // to cells(i) or a new method.
457 return this.get_cell_elements().toArray().map(function (e) {
457 return this.get_cell_elements().toArray().map(function (e) {
458 return $(e).data("cell");
458 return $(e).data("cell");
459 });
459 });
460 };
460 };
461
461
462 /**
462 /**
463 * Get a Cell objects from this notebook.
463 * Get a Cell objects from this notebook.
464 *
464 *
465 * @param {integer} index - An index of a cell to retrieve
465 * @param {integer} index - An index of a cell to retrieve
466 * @return {Cell} Cell or null if no cell was found.
466 * @return {Cell} Cell or null if no cell was found.
467 */
467 */
468 Notebook.prototype.get_cell = function (index) {
468 Notebook.prototype.get_cell = function (index) {
469 var result = null;
469 var result = null;
470 var ce = this.get_cell_element(index);
470 var ce = this.get_cell_element(index);
471 if (ce !== null) {
471 if (ce !== null) {
472 result = ce.data('cell');
472 result = ce.data('cell');
473 }
473 }
474 return result;
474 return result;
475 };
475 };
476
476
477 /**
477 /**
478 * Get the cell below a given cell.
478 * Get the cell below a given cell.
479 *
479 *
480 * @param {Cell} cell
480 * @param {Cell} cell
481 * @return {Cell} the next cell or null if no cell was found.
481 * @return {Cell} the next cell or null if no cell was found.
482 */
482 */
483 Notebook.prototype.get_next_cell = function (cell) {
483 Notebook.prototype.get_next_cell = function (cell) {
484 var result = null;
484 var result = null;
485 var index = this.find_cell_index(cell);
485 var index = this.find_cell_index(cell);
486 if (this.is_valid_cell_index(index+1)) {
486 if (this.is_valid_cell_index(index+1)) {
487 result = this.get_cell(index+1);
487 result = this.get_cell(index+1);
488 }
488 }
489 return result;
489 return result;
490 };
490 };
491
491
492 /**
492 /**
493 * Get the cell above a given cell.
493 * Get the cell above a given cell.
494 *
494 *
495 * @param {Cell} cell
495 * @param {Cell} cell
496 * @return {Cell} The previous cell or null if no cell was found.
496 * @return {Cell} The previous cell or null if no cell was found.
497 */
497 */
498 Notebook.prototype.get_prev_cell = function (cell) {
498 Notebook.prototype.get_prev_cell = function (cell) {
499 var result = null;
499 var result = null;
500 var index = this.find_cell_index(cell);
500 var index = this.find_cell_index(cell);
501 if (index !== null && index > 0) {
501 if (index !== null && index > 0) {
502 result = this.get_cell(index-1);
502 result = this.get_cell(index-1);
503 }
503 }
504 return result;
504 return result;
505 };
505 };
506
506
507 /**
507 /**
508 * Get the numeric index of a given cell.
508 * Get the numeric index of a given cell.
509 *
509 *
510 * @param {Cell} cell
510 * @param {Cell} cell
511 * @return {integer} The cell's numeric index or null if no cell was found.
511 * @return {integer} The cell's numeric index or null if no cell was found.
512 */
512 */
513 Notebook.prototype.find_cell_index = function (cell) {
513 Notebook.prototype.find_cell_index = function (cell) {
514 var result = null;
514 var result = null;
515 this.get_cell_elements().filter(function (index) {
515 this.get_cell_elements().filter(function (index) {
516 if ($(this).data("cell") === cell) {
516 if ($(this).data("cell") === cell) {
517 result = index;
517 result = index;
518 }
518 }
519 });
519 });
520 return result;
520 return result;
521 };
521 };
522
522
523 /**
523 /**
524 * Return given index if defined, or the selected index if not.
524 * Return given index if defined, or the selected index if not.
525 *
525 *
526 * @param {integer} [index] - A cell's index
526 * @param {integer} [index] - A cell's index
527 * @return {integer} cell index
527 * @return {integer} cell index
528 */
528 */
529 Notebook.prototype.index_or_selected = function (index) {
529 Notebook.prototype.index_or_selected = function (index) {
530 var i;
530 var i;
531 if (index === undefined || index === null) {
531 if (index === undefined || index === null) {
532 i = this.get_selected_index();
532 i = this.get_selected_index();
533 if (i === null) {
533 if (i === null) {
534 i = 0;
534 i = 0;
535 }
535 }
536 } else {
536 } else {
537 i = index;
537 i = index;
538 }
538 }
539 return i;
539 return i;
540 };
540 };
541
541
542 /**
542 /**
543 * Get the currently selected cell.
543 * Get the currently selected cell.
544 *
544 *
545 * @return {Cell} The selected cell
545 * @return {Cell} The selected cell
546 */
546 */
547 Notebook.prototype.get_selected_cell = function () {
547 Notebook.prototype.get_selected_cell = function () {
548 var index = this.get_selected_index();
548 var index = this.get_selected_index();
549 return this.get_cell(index);
549 return this.get_cell(index);
550 };
550 };
551
551
552 /**
552 /**
553 * Check whether a cell index is valid.
553 * Check whether a cell index is valid.
554 *
554 *
555 * @param {integer} index - A cell index
555 * @param {integer} index - A cell index
556 * @return True if the index is valid, false otherwise
556 * @return True if the index is valid, false otherwise
557 */
557 */
558 Notebook.prototype.is_valid_cell_index = function (index) {
558 Notebook.prototype.is_valid_cell_index = function (index) {
559 if (index !== null && index >= 0 && index < this.ncells()) {
559 if (index !== null && index >= 0 && index < this.ncells()) {
560 return true;
560 return true;
561 } else {
561 } else {
562 return false;
562 return false;
563 }
563 }
564 };
564 };
565
565
566 /**
566 /**
567 * Get the index of the currently selected cell.
567 * Get the index of the currently selected cell.
568 *
568 *
569 * @return {integer} The selected cell's numeric index
569 * @return {integer} The selected cell's numeric index
570 */
570 */
571 Notebook.prototype.get_selected_index = function () {
571 Notebook.prototype.get_selected_index = function () {
572 var result = null;
572 var result = null;
573 this.get_cell_elements().filter(function (index) {
573 this.get_cell_elements().filter(function (index) {
574 if ($(this).data("cell").selected === true) {
574 if ($(this).data("cell").selected === true) {
575 result = index;
575 result = index;
576 }
576 }
577 });
577 });
578 return result;
578 return result;
579 };
579 };
580
580
581
581
582 // Cell selection.
582 // Cell selection.
583
583
584 /**
584 /**
585 * Programmatically select a cell.
585 * Programmatically select a cell.
586 *
586 *
587 * @param {integer} index - A cell's index
587 * @param {integer} index - A cell's index
588 * @return {Notebook} This notebook
588 * @return {Notebook} This notebook
589 */
589 */
590 Notebook.prototype.select = function (index) {
590 Notebook.prototype.select = function (index) {
591 if (this.is_valid_cell_index(index)) {
591 if (this.is_valid_cell_index(index)) {
592 var sindex = this.get_selected_index();
592 var sindex = this.get_selected_index();
593 if (sindex !== null && index !== sindex) {
593 if (sindex !== null && index !== sindex) {
594 // If we are about to select a different cell, make sure we are
594 // If we are about to select a different cell, make sure we are
595 // first in command mode.
595 // first in command mode.
596 if (this.mode !== 'command') {
596 if (this.mode !== 'command') {
597 this.command_mode();
597 this.command_mode();
598 }
598 }
599 this.get_cell(sindex).unselect();
599 this.get_cell(sindex).unselect();
600 }
600 }
601 var cell = this.get_cell(index);
601 var cell = this.get_cell(index);
602 cell.select();
602 cell.select();
603 if (cell.cell_type === 'heading') {
603 if (cell.cell_type === 'heading') {
604 this.events.trigger('selected_cell_type_changed.Notebook',
604 this.events.trigger('selected_cell_type_changed.Notebook',
605 {'cell_type':cell.cell_type,level:cell.level}
605 {'cell_type':cell.cell_type,level:cell.level}
606 );
606 );
607 } else {
607 } else {
608 this.events.trigger('selected_cell_type_changed.Notebook',
608 this.events.trigger('selected_cell_type_changed.Notebook',
609 {'cell_type':cell.cell_type}
609 {'cell_type':cell.cell_type}
610 );
610 );
611 }
611 }
612 }
612 }
613 return this;
613 return this;
614 };
614 };
615
615
616 /**
616 /**
617 * Programmatically select the next cell.
617 * Programmatically select the next cell.
618 *
618 *
619 * @return {Notebook} This notebook
619 * @return {Notebook} This notebook
620 */
620 */
621 Notebook.prototype.select_next = function () {
621 Notebook.prototype.select_next = function () {
622 var index = this.get_selected_index();
622 var index = this.get_selected_index();
623 this.select(index+1);
623 this.select(index+1);
624 return this;
624 return this;
625 };
625 };
626
626
627 /**
627 /**
628 * Programmatically select the previous cell.
628 * Programmatically select the previous cell.
629 *
629 *
630 * @return {Notebook} This notebook
630 * @return {Notebook} This notebook
631 */
631 */
632 Notebook.prototype.select_prev = function () {
632 Notebook.prototype.select_prev = function () {
633 var index = this.get_selected_index();
633 var index = this.get_selected_index();
634 this.select(index-1);
634 this.select(index-1);
635 return this;
635 return this;
636 };
636 };
637
637
638
638
639 // Edit/Command mode
639 // Edit/Command mode
640
640
641 /**
641 /**
642 * Gets the index of the cell that is in edit mode.
642 * Gets the index of the cell that is in edit mode.
643 *
643 *
644 * @return {integer} index
644 * @return {integer} index
645 */
645 */
646 Notebook.prototype.get_edit_index = function () {
646 Notebook.prototype.get_edit_index = function () {
647 var result = null;
647 var result = null;
648 this.get_cell_elements().filter(function (index) {
648 this.get_cell_elements().filter(function (index) {
649 if ($(this).data("cell").mode === 'edit') {
649 if ($(this).data("cell").mode === 'edit') {
650 result = index;
650 result = index;
651 }
651 }
652 });
652 });
653 return result;
653 return result;
654 };
654 };
655
655
656 /**
656 /**
657 * Handle when a a cell blurs and the notebook should enter command mode.
657 * Handle when a a cell blurs and the notebook should enter command mode.
658 *
658 *
659 * @param {Cell} [cell] - Cell to enter command mode on.
659 * @param {Cell} [cell] - Cell to enter command mode on.
660 */
660 */
661 Notebook.prototype.handle_command_mode = function (cell) {
661 Notebook.prototype.handle_command_mode = function (cell) {
662 if (this.mode !== 'command') {
662 if (this.mode !== 'command') {
663 cell.command_mode();
663 cell.command_mode();
664 this.mode = 'command';
664 this.mode = 'command';
665 this.events.trigger('command_mode.Notebook');
665 this.events.trigger('command_mode.Notebook');
666 this.keyboard_manager.command_mode();
666 this.keyboard_manager.command_mode();
667 }
667 }
668 };
668 };
669
669
670 /**
670 /**
671 * Make the notebook enter command mode.
671 * Make the notebook enter command mode.
672 */
672 */
673 Notebook.prototype.command_mode = function () {
673 Notebook.prototype.command_mode = function () {
674 var cell = this.get_cell(this.get_edit_index());
674 var cell = this.get_cell(this.get_edit_index());
675 if (cell && this.mode !== 'command') {
675 if (cell && this.mode !== 'command') {
676 // We don't call cell.command_mode, but rather call cell.focus_cell()
676 // We don't call cell.command_mode, but rather call cell.focus_cell()
677 // which will blur and CM editor and trigger the call to
677 // which will blur and CM editor and trigger the call to
678 // handle_command_mode.
678 // handle_command_mode.
679 cell.focus_cell();
679 cell.focus_cell();
680 }
680 }
681 };
681 };
682
682
683 /**
683 /**
684 * Handle when a cell fires it's edit_mode event.
684 * Handle when a cell fires it's edit_mode event.
685 *
685 *
686 * @param {Cell} [cell] Cell to enter edit mode on.
686 * @param {Cell} [cell] Cell to enter edit mode on.
687 */
687 */
688 Notebook.prototype.handle_edit_mode = function (cell) {
688 Notebook.prototype.handle_edit_mode = function (cell) {
689 if (cell && this.mode !== 'edit') {
689 if (cell && this.mode !== 'edit') {
690 cell.edit_mode();
690 cell.edit_mode();
691 this.mode = 'edit';
691 this.mode = 'edit';
692 this.events.trigger('edit_mode.Notebook');
692 this.events.trigger('edit_mode.Notebook');
693 this.keyboard_manager.edit_mode();
693 this.keyboard_manager.edit_mode();
694 }
694 }
695 };
695 };
696
696
697 /**
697 /**
698 * Make a cell enter edit mode.
698 * Make a cell enter edit mode.
699 */
699 */
700 Notebook.prototype.edit_mode = function () {
700 Notebook.prototype.edit_mode = function () {
701 var cell = this.get_selected_cell();
701 var cell = this.get_selected_cell();
702 if (cell && this.mode !== 'edit') {
702 if (cell && this.mode !== 'edit') {
703 cell.unrender();
703 cell.unrender();
704 cell.focus_editor();
704 cell.focus_editor();
705 }
705 }
706 };
706 };
707
707
708 /**
708 /**
709 * Focus the currently selected cell.
709 * Focus the currently selected cell.
710 */
710 */
711 Notebook.prototype.focus_cell = function () {
711 Notebook.prototype.focus_cell = function () {
712 var cell = this.get_selected_cell();
712 var cell = this.get_selected_cell();
713 if (cell === null) {return;} // No cell is selected
713 if (cell === null) {return;} // No cell is selected
714 cell.focus_cell();
714 cell.focus_cell();
715 };
715 };
716
716
717 // Cell movement
717 // Cell movement
718
718
719 /**
719 /**
720 * Move given (or selected) cell up and select it.
720 * Move given (or selected) cell up and select it.
721 *
721 *
722 * @param {integer} [index] - cell index
722 * @param {integer} [index] - cell index
723 * @return {Notebook} This notebook
723 * @return {Notebook} This notebook
724 */
724 */
725 Notebook.prototype.move_cell_up = function (index) {
725 Notebook.prototype.move_cell_up = function (index) {
726 var i = this.index_or_selected(index);
726 var i = this.index_or_selected(index);
727 if (this.is_valid_cell_index(i) && i > 0) {
727 if (this.is_valid_cell_index(i) && i > 0) {
728 var pivot = this.get_cell_element(i-1);
728 var pivot = this.get_cell_element(i-1);
729 var tomove = this.get_cell_element(i);
729 var tomove = this.get_cell_element(i);
730 if (pivot !== null && tomove !== null) {
730 if (pivot !== null && tomove !== null) {
731 tomove.detach();
731 tomove.detach();
732 pivot.before(tomove);
732 pivot.before(tomove);
733 this.select(i-1);
733 this.select(i-1);
734 var cell = this.get_selected_cell();
734 var cell = this.get_selected_cell();
735 cell.focus_cell();
735 cell.focus_cell();
736 }
736 }
737 this.set_dirty(true);
737 this.set_dirty(true);
738 }
738 }
739 return this;
739 return this;
740 };
740 };
741
741
742
742
743 /**
743 /**
744 * Move given (or selected) cell down and select it.
744 * Move given (or selected) cell down and select it.
745 *
745 *
746 * @param {integer} [index] - cell index
746 * @param {integer} [index] - cell index
747 * @return {Notebook} This notebook
747 * @return {Notebook} This notebook
748 */
748 */
749 Notebook.prototype.move_cell_down = function (index) {
749 Notebook.prototype.move_cell_down = function (index) {
750 var i = this.index_or_selected(index);
750 var i = this.index_or_selected(index);
751 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
751 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
752 var pivot = this.get_cell_element(i+1);
752 var pivot = this.get_cell_element(i+1);
753 var tomove = this.get_cell_element(i);
753 var tomove = this.get_cell_element(i);
754 if (pivot !== null && tomove !== null) {
754 if (pivot !== null && tomove !== null) {
755 tomove.detach();
755 tomove.detach();
756 pivot.after(tomove);
756 pivot.after(tomove);
757 this.select(i+1);
757 this.select(i+1);
758 var cell = this.get_selected_cell();
758 var cell = this.get_selected_cell();
759 cell.focus_cell();
759 cell.focus_cell();
760 }
760 }
761 }
761 }
762 this.set_dirty();
762 this.set_dirty();
763 return this;
763 return this;
764 };
764 };
765
765
766
766
767 // Insertion, deletion.
767 // Insertion, deletion.
768
768
769 /**
769 /**
770 * Delete a cell from the notebook without any precautions
770 * Delete a cell from the notebook without any precautions
771 * Needed to reload checkpoints and other things like that.
771 * Needed to reload checkpoints and other things like that.
772 *
772 *
773 * @param {integer} [index] - cell's numeric index
773 * @param {integer} [index] - cell's numeric index
774 * @return {Notebook} This notebook
774 * @return {Notebook} This notebook
775 */
775 */
776 Notebook.prototype._unsafe_delete_cell = function (index) {
776 Notebook.prototype._unsafe_delete_cell = function (index) {
777 var i = this.index_or_selected(index);
777 var i = this.index_or_selected(index);
778 var cell = this.get_cell(i);
778 var cell = this.get_cell(i);
779
779
780 $('#undelete_cell').addClass('disabled');
780 $('#undelete_cell').addClass('disabled');
781 if (this.is_valid_cell_index(i)) {
781 if (this.is_valid_cell_index(i)) {
782 var old_ncells = this.ncells();
782 var old_ncells = this.ncells();
783 var ce = this.get_cell_element(i);
783 var ce = this.get_cell_element(i);
784 ce.remove();
784 ce.remove();
785 this.set_dirty(true);
785 this.set_dirty(true);
786 }
786 }
787 return this;
787 return this;
788 };
788 };
789
789
790 /**
790 /**
791 * Delete a cell from the notebook.
791 * Delete a cell from the notebook.
792 *
792 *
793 * @param {integer} [index] - cell's numeric index
793 * @param {integer} [index] - cell's numeric index
794 * @return {Notebook} This notebook
794 * @return {Notebook} This notebook
795 */
795 */
796 Notebook.prototype.delete_cell = function (index) {
796 Notebook.prototype.delete_cell = function (index) {
797 var i = this.index_or_selected(index);
797 var i = this.index_or_selected(index);
798 var cell = this.get_cell(i);
798 var cell = this.get_cell(i);
799 if (!cell.is_deletable()) {
799 if (!cell.is_deletable()) {
800 return this;
800 return this;
801 }
801 }
802
802
803 this.undelete_backup = cell.toJSON();
803 this.undelete_backup = cell.toJSON();
804 $('#undelete_cell').removeClass('disabled');
804 $('#undelete_cell').removeClass('disabled');
805 if (this.is_valid_cell_index(i)) {
805 if (this.is_valid_cell_index(i)) {
806 var old_ncells = this.ncells();
806 var old_ncells = this.ncells();
807 var ce = this.get_cell_element(i);
807 var ce = this.get_cell_element(i);
808 ce.remove();
808 ce.remove();
809 if (i === 0) {
809 if (i === 0) {
810 // Always make sure we have at least one cell.
810 // Always make sure we have at least one cell.
811 if (old_ncells === 1) {
811 if (old_ncells === 1) {
812 this.insert_cell_below('code');
812 this.insert_cell_below('code');
813 }
813 }
814 this.select(0);
814 this.select(0);
815 this.undelete_index = 0;
815 this.undelete_index = 0;
816 this.undelete_below = false;
816 this.undelete_below = false;
817 } else if (i === old_ncells-1 && i !== 0) {
817 } else if (i === old_ncells-1 && i !== 0) {
818 this.select(i-1);
818 this.select(i-1);
819 this.undelete_index = i - 1;
819 this.undelete_index = i - 1;
820 this.undelete_below = true;
820 this.undelete_below = true;
821 } else {
821 } else {
822 this.select(i);
822 this.select(i);
823 this.undelete_index = i;
823 this.undelete_index = i;
824 this.undelete_below = false;
824 this.undelete_below = false;
825 }
825 }
826 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
826 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
827 this.set_dirty(true);
827 this.set_dirty(true);
828 }
828 }
829 return this;
829 return this;
830 };
830 };
831
831
832 /**
832 /**
833 * Restore the most recently deleted cell.
833 * Restore the most recently deleted cell.
834 */
834 */
835 Notebook.prototype.undelete_cell = function() {
835 Notebook.prototype.undelete_cell = function() {
836 if (this.undelete_backup !== null && this.undelete_index !== null) {
836 if (this.undelete_backup !== null && this.undelete_index !== null) {
837 var current_index = this.get_selected_index();
837 var current_index = this.get_selected_index();
838 if (this.undelete_index < current_index) {
838 if (this.undelete_index < current_index) {
839 current_index = current_index + 1;
839 current_index = current_index + 1;
840 }
840 }
841 if (this.undelete_index >= this.ncells()) {
841 if (this.undelete_index >= this.ncells()) {
842 this.select(this.ncells() - 1);
842 this.select(this.ncells() - 1);
843 }
843 }
844 else {
844 else {
845 this.select(this.undelete_index);
845 this.select(this.undelete_index);
846 }
846 }
847 var cell_data = this.undelete_backup;
847 var cell_data = this.undelete_backup;
848 var new_cell = null;
848 var new_cell = null;
849 if (this.undelete_below) {
849 if (this.undelete_below) {
850 new_cell = this.insert_cell_below(cell_data.cell_type);
850 new_cell = this.insert_cell_below(cell_data.cell_type);
851 } else {
851 } else {
852 new_cell = this.insert_cell_above(cell_data.cell_type);
852 new_cell = this.insert_cell_above(cell_data.cell_type);
853 }
853 }
854 new_cell.fromJSON(cell_data);
854 new_cell.fromJSON(cell_data);
855 if (this.undelete_below) {
855 if (this.undelete_below) {
856 this.select(current_index+1);
856 this.select(current_index+1);
857 } else {
857 } else {
858 this.select(current_index);
858 this.select(current_index);
859 }
859 }
860 this.undelete_backup = null;
860 this.undelete_backup = null;
861 this.undelete_index = null;
861 this.undelete_index = null;
862 }
862 }
863 $('#undelete_cell').addClass('disabled');
863 $('#undelete_cell').addClass('disabled');
864 };
864 };
865
865
866 /**
866 /**
867 * Insert a cell so that after insertion the cell is at given index.
867 * Insert a cell so that after insertion the cell is at given index.
868 *
868 *
869 * If cell type is not provided, it will default to the type of the
869 * If cell type is not provided, it will default to the type of the
870 * currently active cell.
870 * currently active cell.
871 *
871 *
872 * Similar to insert_above, but index parameter is mandatory.
872 * Similar to insert_above, but index parameter is mandatory.
873 *
873 *
874 * Index will be brought back into the accessible range [0,n].
874 * Index will be brought back into the accessible range [0,n].
875 *
875 *
876 * @param {string} [type] - in ['code','markdown', 'raw'], defaults to 'code'
876 * @param {string} [type] - in ['code','markdown', 'raw'], defaults to 'code'
877 * @param {integer} [index] - a valid index where to insert cell
877 * @param {integer} [index] - a valid index where to insert cell
878 * @return {Cell|null} created cell or null
878 * @return {Cell|null} created cell or null
879 */
879 */
880 Notebook.prototype.insert_cell_at_index = function(type, index){
880 Notebook.prototype.insert_cell_at_index = function(type, index){
881
881
882 var ncells = this.ncells();
882 var ncells = this.ncells();
883 index = Math.min(index, ncells);
883 index = Math.min(index, ncells);
884 index = Math.max(index, 0);
884 index = Math.max(index, 0);
885 var cell = null;
885 var cell = null;
886 type = type || this.class_config.get_sync('default_cell_type');
886 type = type || this.class_config.get_sync('default_cell_type');
887 if (type === 'above') {
887 if (type === 'above') {
888 if (index > 0) {
888 if (index > 0) {
889 type = this.get_cell(index-1).cell_type;
889 type = this.get_cell(index-1).cell_type;
890 } else {
890 } else {
891 type = 'code';
891 type = 'code';
892 }
892 }
893 } else if (type === 'below') {
893 } else if (type === 'below') {
894 if (index < ncells) {
894 if (index < ncells) {
895 type = this.get_cell(index).cell_type;
895 type = this.get_cell(index).cell_type;
896 } else {
896 } else {
897 type = 'code';
897 type = 'code';
898 }
898 }
899 } else if (type === 'selected') {
899 } else if (type === 'selected') {
900 type = this.get_selected_cell().cell_type;
900 type = this.get_selected_cell().cell_type;
901 }
901 }
902
902
903 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
903 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
904 var cell_options = {
904 var cell_options = {
905 events: this.events,
905 events: this.events,
906 config: this.config,
906 config: this.config,
907 keyboard_manager: this.keyboard_manager,
907 keyboard_manager: this.keyboard_manager,
908 notebook: this,
908 notebook: this,
909 tooltip: this.tooltip
909 tooltip: this.tooltip
910 };
910 };
911 switch(type) {
911 switch(type) {
912 case 'code':
912 case 'code':
913 cell = new codecell.CodeCell(this.kernel, cell_options);
913 cell = new codecell.CodeCell(this.kernel, cell_options);
914 cell.set_input_prompt();
914 cell.set_input_prompt();
915 break;
915 break;
916 case 'markdown':
916 case 'markdown':
917 cell = new textcell.MarkdownCell(cell_options);
917 cell = new textcell.MarkdownCell(cell_options);
918 break;
918 break;
919 case 'raw':
919 case 'raw':
920 cell = new textcell.RawCell(cell_options);
920 cell = new textcell.RawCell(cell_options);
921 break;
921 break;
922 default:
922 default:
923 console.log("Unrecognized cell type: ", type, cellmod);
923 console.log("Unrecognized cell type: ", type, cellmod);
924 cell = new cellmod.UnrecognizedCell(cell_options);
924 cell = new cellmod.UnrecognizedCell(cell_options);
925 }
925 }
926
926
927 if(this._insert_element_at_index(cell.element,index)) {
927 if(this._insert_element_at_index(cell.element,index)) {
928 cell.render();
928 cell.render();
929 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
929 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
930 cell.refresh();
930 cell.refresh();
931 // We used to select the cell after we refresh it, but there
931 // We used to select the cell after we refresh it, but there
932 // are now cases were this method is called where select is
932 // are now cases were this method is called where select is
933 // not appropriate. The selection logic should be handled by the
933 // not appropriate. The selection logic should be handled by the
934 // caller of the the top level insert_cell methods.
934 // caller of the the top level insert_cell methods.
935 this.set_dirty(true);
935 this.set_dirty(true);
936 }
936 }
937 }
937 }
938 return cell;
938 return cell;
939
939
940 };
940 };
941
941
942 /**
942 /**
943 * Insert an element at given cell index.
943 * Insert an element at given cell index.
944 *
944 *
945 * @param {HTMLElement} element - a cell element
945 * @param {HTMLElement} element - a cell element
946 * @param {integer} [index] - a valid index where to inser cell
946 * @param {integer} [index] - a valid index where to inser cell
947 * @returns {boolean} success
947 * @returns {boolean} success
948 */
948 */
949 Notebook.prototype._insert_element_at_index = function(element, index){
949 Notebook.prototype._insert_element_at_index = function(element, index){
950 if (element === undefined){
950 if (element === undefined){
951 return false;
951 return false;
952 }
952 }
953
953
954 var ncells = this.ncells();
954 var ncells = this.ncells();
955
955
956 if (ncells === 0) {
956 if (ncells === 0) {
957 // special case append if empty
957 // special case append if empty
958 this.element.find('div.end_space').before(element);
958 this.element.find('div.end_space').before(element);
959 } else if ( ncells === index ) {
959 } else if ( ncells === index ) {
960 // special case append it the end, but not empty
960 // special case append it the end, but not empty
961 this.get_cell_element(index-1).after(element);
961 this.get_cell_element(index-1).after(element);
962 } else if (this.is_valid_cell_index(index)) {
962 } else if (this.is_valid_cell_index(index)) {
963 // otherwise always somewhere to append to
963 // otherwise always somewhere to append to
964 this.get_cell_element(index).before(element);
964 this.get_cell_element(index).before(element);
965 } else {
965 } else {
966 return false;
966 return false;
967 }
967 }
968
968
969 if (this.undelete_index !== null && index <= this.undelete_index) {
969 if (this.undelete_index !== null && index <= this.undelete_index) {
970 this.undelete_index = this.undelete_index + 1;
970 this.undelete_index = this.undelete_index + 1;
971 this.set_dirty(true);
971 this.set_dirty(true);
972 }
972 }
973 return true;
973 return true;
974 };
974 };
975
975
976 /**
976 /**
977 * Insert a cell of given type above given index, or at top
977 * Insert a cell of given type above given index, or at top
978 * of notebook if index smaller than 0.
978 * of notebook if index smaller than 0.
979 *
979 *
980 * @param {string} [type] - cell type
980 * @param {string} [type] - cell type
981 * @param {integer} [index] - defaults to the currently selected cell
981 * @param {integer} [index] - defaults to the currently selected cell
982 * @return {Cell|null} handle to created cell or null
982 * @return {Cell|null} handle to created cell or null
983 */
983 */
984 Notebook.prototype.insert_cell_above = function (type, index) {
984 Notebook.prototype.insert_cell_above = function (type, index) {
985 index = this.index_or_selected(index);
985 index = this.index_or_selected(index);
986 return this.insert_cell_at_index(type, index);
986 return this.insert_cell_at_index(type, index);
987 };
987 };
988
988
989 /**
989 /**
990 * Insert a cell of given type below given index, or at bottom
990 * Insert a cell of given type below given index, or at bottom
991 * of notebook if index greater than number of cells
991 * of notebook if index greater than number of cells
992 *
992 *
993 * @param {string} [type] - cell type
993 * @param {string} [type] - cell type
994 * @param {integer} [index] - defaults to the currently selected cell
994 * @param {integer} [index] - defaults to the currently selected cell
995 * @return {Cell|null} handle to created cell or null
995 * @return {Cell|null} handle to created cell or null
996 */
996 */
997 Notebook.prototype.insert_cell_below = function (type, index) {
997 Notebook.prototype.insert_cell_below = function (type, index) {
998 index = this.index_or_selected(index);
998 index = this.index_or_selected(index);
999 return this.insert_cell_at_index(type, index+1);
999 return this.insert_cell_at_index(type, index+1);
1000 };
1000 };
1001
1001
1002
1002
1003 /**
1003 /**
1004 * Insert cell at end of notebook
1004 * Insert cell at end of notebook
1005 *
1005 *
1006 * @param {string} type - cell type
1006 * @param {string} type - cell type
1007 * @return {Cell|null} handle to created cell or null
1007 * @return {Cell|null} handle to created cell or null
1008 */
1008 */
1009 Notebook.prototype.insert_cell_at_bottom = function (type){
1009 Notebook.prototype.insert_cell_at_bottom = function (type){
1010 var len = this.ncells();
1010 var len = this.ncells();
1011 return this.insert_cell_below(type,len-1);
1011 return this.insert_cell_below(type,len-1);
1012 };
1012 };
1013
1013
1014 /**
1014 /**
1015 * Turn a cell into a code cell.
1015 * Turn a cell into a code cell.
1016 *
1016 *
1017 * @param {integer} [index] - cell index
1017 * @param {integer} [index] - cell index
1018 */
1018 */
1019 Notebook.prototype.to_code = function (index) {
1019 Notebook.prototype.to_code = function (index) {
1020 var i = this.index_or_selected(index);
1020 var i = this.index_or_selected(index);
1021 if (this.is_valid_cell_index(i)) {
1021 if (this.is_valid_cell_index(i)) {
1022 var source_cell = this.get_cell(i);
1022 var source_cell = this.get_cell(i);
1023 if (!(source_cell instanceof codecell.CodeCell)) {
1023 if (!(source_cell instanceof codecell.CodeCell)) {
1024 var target_cell = this.insert_cell_below('code',i);
1024 var target_cell = this.insert_cell_below('code',i);
1025 var text = source_cell.get_text();
1025 var text = source_cell.get_text();
1026 if (text === source_cell.placeholder) {
1026 if (text === source_cell.placeholder) {
1027 text = '';
1027 text = '';
1028 }
1028 }
1029 //metadata
1029 //metadata
1030 target_cell.metadata = source_cell.metadata;
1030 target_cell.metadata = source_cell.metadata;
1031
1031
1032 target_cell.set_text(text);
1032 target_cell.set_text(text);
1033 // make this value the starting point, so that we can only undo
1033 // make this value the starting point, so that we can only undo
1034 // to this state, instead of a blank cell
1034 // to this state, instead of a blank cell
1035 target_cell.code_mirror.clearHistory();
1035 target_cell.code_mirror.clearHistory();
1036 source_cell.element.remove();
1036 source_cell.element.remove();
1037 this.select(i);
1037 this.select(i);
1038 var cursor = source_cell.code_mirror.getCursor();
1038 var cursor = source_cell.code_mirror.getCursor();
1039 target_cell.code_mirror.setCursor(cursor);
1039 target_cell.code_mirror.setCursor(cursor);
1040 this.set_dirty(true);
1040 this.set_dirty(true);
1041 }
1041 }
1042 }
1042 }
1043 };
1043 };
1044
1044
1045 /**
1045 /**
1046 * Turn a cell into a Markdown cell.
1046 * Turn a cell into a Markdown cell.
1047 *
1047 *
1048 * @param {integer} [index] - cell index
1048 * @param {integer} [index] - cell index
1049 */
1049 */
1050 Notebook.prototype.to_markdown = function (index) {
1050 Notebook.prototype.to_markdown = function (index) {
1051 var i = this.index_or_selected(index);
1051 var i = this.index_or_selected(index);
1052 if (this.is_valid_cell_index(i)) {
1052 if (this.is_valid_cell_index(i)) {
1053 var source_cell = this.get_cell(i);
1053 var source_cell = this.get_cell(i);
1054
1054
1055 if (!(source_cell instanceof textcell.MarkdownCell)) {
1055 if (!(source_cell instanceof textcell.MarkdownCell)) {
1056 var target_cell = this.insert_cell_below('markdown',i);
1056 var target_cell = this.insert_cell_below('markdown',i);
1057 var text = source_cell.get_text();
1057 var text = source_cell.get_text();
1058
1058
1059 if (text === source_cell.placeholder) {
1059 if (text === source_cell.placeholder) {
1060 text = '';
1060 text = '';
1061 }
1061 }
1062 // metadata
1062 // metadata
1063 target_cell.metadata = source_cell.metadata;
1063 target_cell.metadata = source_cell.metadata;
1064 // We must show the editor before setting its contents
1064 // We must show the editor before setting its contents
1065 target_cell.unrender();
1065 target_cell.unrender();
1066 target_cell.set_text(text);
1066 target_cell.set_text(text);
1067 // make this value the starting point, so that we can only undo
1067 // make this value the starting point, so that we can only undo
1068 // to this state, instead of a blank cell
1068 // to this state, instead of a blank cell
1069 target_cell.code_mirror.clearHistory();
1069 target_cell.code_mirror.clearHistory();
1070 source_cell.element.remove();
1070 source_cell.element.remove();
1071 this.select(i);
1071 this.select(i);
1072 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1072 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1073 target_cell.render();
1073 target_cell.render();
1074 }
1074 }
1075 var cursor = source_cell.code_mirror.getCursor();
1075 var cursor = source_cell.code_mirror.getCursor();
1076 target_cell.code_mirror.setCursor(cursor);
1076 target_cell.code_mirror.setCursor(cursor);
1077 this.set_dirty(true);
1077 this.set_dirty(true);
1078 }
1078 }
1079 }
1079 }
1080 };
1080 };
1081
1081
1082 /**
1082 /**
1083 * Turn a cell into a raw text cell.
1083 * Turn a cell into a raw text cell.
1084 *
1084 *
1085 * @param {integer} [index] - cell index
1085 * @param {integer} [index] - cell index
1086 */
1086 */
1087 Notebook.prototype.to_raw = function (index) {
1087 Notebook.prototype.to_raw = function (index) {
1088 var i = this.index_or_selected(index);
1088 var i = this.index_or_selected(index);
1089 if (this.is_valid_cell_index(i)) {
1089 if (this.is_valid_cell_index(i)) {
1090 var target_cell = null;
1090 var target_cell = null;
1091 var source_cell = this.get_cell(i);
1091 var source_cell = this.get_cell(i);
1092
1092
1093 if (!(source_cell instanceof textcell.RawCell)) {
1093 if (!(source_cell instanceof textcell.RawCell)) {
1094 target_cell = this.insert_cell_below('raw',i);
1094 target_cell = this.insert_cell_below('raw',i);
1095 var text = source_cell.get_text();
1095 var text = source_cell.get_text();
1096 if (text === source_cell.placeholder) {
1096 if (text === source_cell.placeholder) {
1097 text = '';
1097 text = '';
1098 }
1098 }
1099 //metadata
1099 //metadata
1100 target_cell.metadata = source_cell.metadata;
1100 target_cell.metadata = source_cell.metadata;
1101 // We must show the editor before setting its contents
1101 // We must show the editor before setting its contents
1102 target_cell.unrender();
1102 target_cell.unrender();
1103 target_cell.set_text(text);
1103 target_cell.set_text(text);
1104 // make this value the starting point, so that we can only undo
1104 // make this value the starting point, so that we can only undo
1105 // to this state, instead of a blank cell
1105 // to this state, instead of a blank cell
1106 target_cell.code_mirror.clearHistory();
1106 target_cell.code_mirror.clearHistory();
1107 source_cell.element.remove();
1107 source_cell.element.remove();
1108 this.select(i);
1108 this.select(i);
1109 var cursor = source_cell.code_mirror.getCursor();
1109 var cursor = source_cell.code_mirror.getCursor();
1110 target_cell.code_mirror.setCursor(cursor);
1110 target_cell.code_mirror.setCursor(cursor);
1111 this.set_dirty(true);
1111 this.set_dirty(true);
1112 }
1112 }
1113 }
1113 }
1114 };
1114 };
1115
1115
1116 /**
1116 /**
1117 * Warn about heading cell support removal.
1117 * Warn about heading cell support removal.
1118 */
1118 */
1119 Notebook.prototype._warn_heading = function () {
1119 Notebook.prototype._warn_heading = function () {
1120 dialog.modal({
1120 dialog.modal({
1121 notebook: this,
1121 notebook: this,
1122 keyboard_manager: this.keyboard_manager,
1122 keyboard_manager: this.keyboard_manager,
1123 title : "Use markdown headings",
1123 title : "Use markdown headings",
1124 body : $("<p/>").text(
1124 body : $("<p/>").text(
1125 'IPython no longer uses special heading cells. ' +
1125 'IPython no longer uses special heading cells. ' +
1126 'Instead, write your headings in Markdown cells using # characters:'
1126 'Instead, write your headings in Markdown cells using # characters:'
1127 ).append($('<pre/>').text(
1127 ).append($('<pre/>').text(
1128 '## This is a level 2 heading'
1128 '## This is a level 2 heading'
1129 )),
1129 )),
1130 buttons : {
1130 buttons : {
1131 "OK" : {}
1131 "OK" : {}
1132 }
1132 }
1133 });
1133 });
1134 };
1134 };
1135
1135
1136 /**
1136 /**
1137 * Turn a cell into a heading containing markdown cell.
1137 * Turn a cell into a heading containing markdown cell.
1138 *
1138 *
1139 * @param {integer} [index] - cell index
1139 * @param {integer} [index] - cell index
1140 * @param {integer} [level] - heading level (e.g., 1 for h1)
1140 * @param {integer} [level] - heading level (e.g., 1 for h1)
1141 */
1141 */
1142 Notebook.prototype.to_heading = function (index, level) {
1142 Notebook.prototype.to_heading = function (index, level) {
1143 this.to_markdown(index);
1143 this.to_markdown(index);
1144 level = level || 1;
1144 level = level || 1;
1145 var i = this.index_or_selected(index);
1145 var i = this.index_or_selected(index);
1146 if (this.is_valid_cell_index(i)) {
1146 if (this.is_valid_cell_index(i)) {
1147 var cell = this.get_cell(i);
1147 var cell = this.get_cell(i);
1148 cell.set_heading_level(level);
1148 cell.set_heading_level(level);
1149 this.set_dirty(true);
1149 this.set_dirty(true);
1150 }
1150 }
1151 };
1151 };
1152
1152
1153
1153
1154 // Cut/Copy/Paste
1154 // Cut/Copy/Paste
1155
1155
1156 /**
1156 /**
1157 * Enable the UI elements for pasting cells.
1157 * Enable the UI elements for pasting cells.
1158 */
1158 */
1159 Notebook.prototype.enable_paste = function () {
1159 Notebook.prototype.enable_paste = function () {
1160 var that = this;
1160 var that = this;
1161 if (!this.paste_enabled) {
1161 if (!this.paste_enabled) {
1162 $('#paste_cell_replace').removeClass('disabled')
1162 $('#paste_cell_replace').removeClass('disabled')
1163 .on('click', function () {that.paste_cell_replace();});
1163 .on('click', function () {that.paste_cell_replace();});
1164 $('#paste_cell_above').removeClass('disabled')
1164 $('#paste_cell_above').removeClass('disabled')
1165 .on('click', function () {that.paste_cell_above();});
1165 .on('click', function () {that.paste_cell_above();});
1166 $('#paste_cell_below').removeClass('disabled')
1166 $('#paste_cell_below').removeClass('disabled')
1167 .on('click', function () {that.paste_cell_below();});
1167 .on('click', function () {that.paste_cell_below();});
1168 this.paste_enabled = true;
1168 this.paste_enabled = true;
1169 }
1169 }
1170 };
1170 };
1171
1171
1172 /**
1172 /**
1173 * Disable the UI elements for pasting cells.
1173 * Disable the UI elements for pasting cells.
1174 */
1174 */
1175 Notebook.prototype.disable_paste = function () {
1175 Notebook.prototype.disable_paste = function () {
1176 if (this.paste_enabled) {
1176 if (this.paste_enabled) {
1177 $('#paste_cell_replace').addClass('disabled').off('click');
1177 $('#paste_cell_replace').addClass('disabled').off('click');
1178 $('#paste_cell_above').addClass('disabled').off('click');
1178 $('#paste_cell_above').addClass('disabled').off('click');
1179 $('#paste_cell_below').addClass('disabled').off('click');
1179 $('#paste_cell_below').addClass('disabled').off('click');
1180 this.paste_enabled = false;
1180 this.paste_enabled = false;
1181 }
1181 }
1182 };
1182 };
1183
1183
1184 /**
1184 /**
1185 * Cut a cell.
1185 * Cut a cell.
1186 */
1186 */
1187 Notebook.prototype.cut_cell = function () {
1187 Notebook.prototype.cut_cell = function () {
1188 this.copy_cell();
1188 this.copy_cell();
1189 this.delete_cell();
1189 this.delete_cell();
1190 };
1190 };
1191
1191
1192 /**
1192 /**
1193 * Copy a cell.
1193 * Copy a cell.
1194 */
1194 */
1195 Notebook.prototype.copy_cell = function () {
1195 Notebook.prototype.copy_cell = function () {
1196 var cell = this.get_selected_cell();
1196 var cell = this.get_selected_cell();
1197 this.clipboard = cell.toJSON();
1197 this.clipboard = cell.toJSON();
1198 // remove undeletable status from the copied cell
1198 // remove undeletable status from the copied cell
1199 if (this.clipboard.metadata.deletable !== undefined) {
1199 if (this.clipboard.metadata.deletable !== undefined) {
1200 delete this.clipboard.metadata.deletable;
1200 delete this.clipboard.metadata.deletable;
1201 }
1201 }
1202 this.enable_paste();
1202 this.enable_paste();
1203 };
1203 };
1204
1204
1205 /**
1205 /**
1206 * Replace the selected cell with the cell in the clipboard.
1206 * Replace the selected cell with the cell in the clipboard.
1207 */
1207 */
1208 Notebook.prototype.paste_cell_replace = function () {
1208 Notebook.prototype.paste_cell_replace = function () {
1209 if (this.clipboard !== null && this.paste_enabled) {
1209 if (this.clipboard !== null && this.paste_enabled) {
1210 var cell_data = this.clipboard;
1210 var cell_data = this.clipboard;
1211 var new_cell = this.insert_cell_above(cell_data.cell_type);
1211 var new_cell = this.insert_cell_above(cell_data.cell_type);
1212 new_cell.fromJSON(cell_data);
1212 new_cell.fromJSON(cell_data);
1213 var old_cell = this.get_next_cell(new_cell);
1213 var old_cell = this.get_next_cell(new_cell);
1214 this.delete_cell(this.find_cell_index(old_cell));
1214 this.delete_cell(this.find_cell_index(old_cell));
1215 this.select(this.find_cell_index(new_cell));
1215 this.select(this.find_cell_index(new_cell));
1216 }
1216 }
1217 };
1217 };
1218
1218
1219 /**
1219 /**
1220 * Paste a cell from the clipboard above the selected cell.
1220 * Paste a cell from the clipboard above the selected cell.
1221 */
1221 */
1222 Notebook.prototype.paste_cell_above = function () {
1222 Notebook.prototype.paste_cell_above = function () {
1223 if (this.clipboard !== null && this.paste_enabled) {
1223 if (this.clipboard !== null && this.paste_enabled) {
1224 var cell_data = this.clipboard;
1224 var cell_data = this.clipboard;
1225 var new_cell = this.insert_cell_above(cell_data.cell_type);
1225 var new_cell = this.insert_cell_above(cell_data.cell_type);
1226 new_cell.fromJSON(cell_data);
1226 new_cell.fromJSON(cell_data);
1227 new_cell.focus_cell();
1227 new_cell.focus_cell();
1228 }
1228 }
1229 };
1229 };
1230
1230
1231 /**
1231 /**
1232 * Paste a cell from the clipboard below the selected cell.
1232 * Paste a cell from the clipboard below the selected cell.
1233 */
1233 */
1234 Notebook.prototype.paste_cell_below = function () {
1234 Notebook.prototype.paste_cell_below = function () {
1235 if (this.clipboard !== null && this.paste_enabled) {
1235 if (this.clipboard !== null && this.paste_enabled) {
1236 var cell_data = this.clipboard;
1236 var cell_data = this.clipboard;
1237 var new_cell = this.insert_cell_below(cell_data.cell_type);
1237 var new_cell = this.insert_cell_below(cell_data.cell_type);
1238 new_cell.fromJSON(cell_data);
1238 new_cell.fromJSON(cell_data);
1239 new_cell.focus_cell();
1239 new_cell.focus_cell();
1240 }
1240 }
1241 };
1241 };
1242
1242
1243 // Split/merge
1243 // Split/merge
1244
1244
1245 /**
1245 /**
1246 * Split the selected cell into two cells.
1246 * Split the selected cell into two cells.
1247 */
1247 */
1248 Notebook.prototype.split_cell = function () {
1248 Notebook.prototype.split_cell = function () {
1249 var cell = this.get_selected_cell();
1249 var cell = this.get_selected_cell();
1250 if (cell.is_splittable()) {
1250 if (cell.is_splittable()) {
1251 var texta = cell.get_pre_cursor();
1251 var texta = cell.get_pre_cursor();
1252 var textb = cell.get_post_cursor();
1252 var textb = cell.get_post_cursor();
1253 cell.set_text(textb);
1253 cell.set_text(textb);
1254 var new_cell = this.insert_cell_above(cell.cell_type);
1254 var new_cell = this.insert_cell_above(cell.cell_type);
1255 // Unrender the new cell so we can call set_text.
1255 // Unrender the new cell so we can call set_text.
1256 new_cell.unrender();
1256 new_cell.unrender();
1257 new_cell.set_text(texta);
1257 new_cell.set_text(texta);
1258 }
1258 }
1259 };
1259 };
1260
1260
1261 /**
1261 /**
1262 * Merge the selected cell into the cell above it.
1262 * Merge the selected cell into the cell above it.
1263 */
1263 */
1264 Notebook.prototype.merge_cell_above = function () {
1264 Notebook.prototype.merge_cell_above = function () {
1265 var index = this.get_selected_index();
1265 var index = this.get_selected_index();
1266 var cell = this.get_cell(index);
1266 var cell = this.get_cell(index);
1267 var render = cell.rendered;
1267 var render = cell.rendered;
1268 if (!cell.is_mergeable()) {
1268 if (!cell.is_mergeable()) {
1269 return;
1269 return;
1270 }
1270 }
1271 if (index > 0) {
1271 if (index > 0) {
1272 var upper_cell = this.get_cell(index-1);
1272 var upper_cell = this.get_cell(index-1);
1273 if (!upper_cell.is_mergeable()) {
1273 if (!upper_cell.is_mergeable()) {
1274 return;
1274 return;
1275 }
1275 }
1276 var upper_text = upper_cell.get_text();
1276 var upper_text = upper_cell.get_text();
1277 var text = cell.get_text();
1277 var text = cell.get_text();
1278 if (cell instanceof codecell.CodeCell) {
1278 if (cell instanceof codecell.CodeCell) {
1279 cell.set_text(upper_text+'\n'+text);
1279 cell.set_text(upper_text+'\n'+text);
1280 } else {
1280 } else {
1281 cell.unrender(); // Must unrender before we set_text.
1281 cell.unrender(); // Must unrender before we set_text.
1282 cell.set_text(upper_text+'\n\n'+text);
1282 cell.set_text(upper_text+'\n\n'+text);
1283 if (render) {
1283 if (render) {
1284 // The rendered state of the final cell should match
1284 // The rendered state of the final cell should match
1285 // that of the original selected cell;
1285 // that of the original selected cell;
1286 cell.render();
1286 cell.render();
1287 }
1287 }
1288 }
1288 }
1289 this.delete_cell(index-1);
1289 this.delete_cell(index-1);
1290 this.select(this.find_cell_index(cell));
1290 this.select(this.find_cell_index(cell));
1291 }
1291 }
1292 };
1292 };
1293
1293
1294 /**
1294 /**
1295 * Merge the selected cell into the cell below it.
1295 * Merge the selected cell into the cell below it.
1296 */
1296 */
1297 Notebook.prototype.merge_cell_below = function () {
1297 Notebook.prototype.merge_cell_below = function () {
1298 var index = this.get_selected_index();
1298 var index = this.get_selected_index();
1299 var cell = this.get_cell(index);
1299 var cell = this.get_cell(index);
1300 var render = cell.rendered;
1300 var render = cell.rendered;
1301 if (!cell.is_mergeable()) {
1301 if (!cell.is_mergeable()) {
1302 return;
1302 return;
1303 }
1303 }
1304 if (index < this.ncells()-1) {
1304 if (index < this.ncells()-1) {
1305 var lower_cell = this.get_cell(index+1);
1305 var lower_cell = this.get_cell(index+1);
1306 if (!lower_cell.is_mergeable()) {
1306 if (!lower_cell.is_mergeable()) {
1307 return;
1307 return;
1308 }
1308 }
1309 var lower_text = lower_cell.get_text();
1309 var lower_text = lower_cell.get_text();
1310 var text = cell.get_text();
1310 var text = cell.get_text();
1311 if (cell instanceof codecell.CodeCell) {
1311 if (cell instanceof codecell.CodeCell) {
1312 cell.set_text(text+'\n'+lower_text);
1312 cell.set_text(text+'\n'+lower_text);
1313 } else {
1313 } else {
1314 cell.unrender(); // Must unrender before we set_text.
1314 cell.unrender(); // Must unrender before we set_text.
1315 cell.set_text(text+'\n\n'+lower_text);
1315 cell.set_text(text+'\n\n'+lower_text);
1316 if (render) {
1316 if (render) {
1317 // The rendered state of the final cell should match
1317 // The rendered state of the final cell should match
1318 // that of the original selected cell;
1318 // that of the original selected cell;
1319 cell.render();
1319 cell.render();
1320 }
1320 }
1321 }
1321 }
1322 this.delete_cell(index+1);
1322 this.delete_cell(index+1);
1323 this.select(this.find_cell_index(cell));
1323 this.select(this.find_cell_index(cell));
1324 }
1324 }
1325 };
1325 };
1326
1326
1327
1327
1328 // Cell collapsing and output clearing
1328 // Cell collapsing and output clearing
1329
1329
1330 /**
1330 /**
1331 * Hide a cell's output.
1331 * Hide a cell's output.
1332 *
1332 *
1333 * @param {integer} index - cell index
1333 * @param {integer} index - cell index
1334 */
1334 */
1335 Notebook.prototype.collapse_output = function (index) {
1335 Notebook.prototype.collapse_output = function (index) {
1336 var i = this.index_or_selected(index);
1336 var i = this.index_or_selected(index);
1337 var cell = this.get_cell(i);
1337 var cell = this.get_cell(i);
1338 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1338 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1339 cell.collapse_output();
1339 cell.collapse_output();
1340 this.set_dirty(true);
1340 this.set_dirty(true);
1341 }
1341 }
1342 };
1342 };
1343
1343
1344 /**
1344 /**
1345 * Hide each code cell's output area.
1345 * Hide each code cell's output area.
1346 */
1346 */
1347 Notebook.prototype.collapse_all_output = function () {
1347 Notebook.prototype.collapse_all_output = function () {
1348 this.get_cells().map(function (cell, i) {
1348 this.get_cells().map(function (cell, i) {
1349 if (cell instanceof codecell.CodeCell) {
1349 if (cell instanceof codecell.CodeCell) {
1350 cell.collapse_output();
1350 cell.collapse_output();
1351 }
1351 }
1352 });
1352 });
1353 // this should not be set if the `collapse` key is removed from nbformat
1353 // this should not be set if the `collapse` key is removed from nbformat
1354 this.set_dirty(true);
1354 this.set_dirty(true);
1355 };
1355 };
1356
1356
1357 /**
1357 /**
1358 * Show a cell's output.
1358 * Show a cell's output.
1359 *
1359 *
1360 * @param {integer} index - cell index
1360 * @param {integer} index - cell index
1361 */
1361 */
1362 Notebook.prototype.expand_output = function (index) {
1362 Notebook.prototype.expand_output = function (index) {
1363 var i = this.index_or_selected(index);
1363 var i = this.index_or_selected(index);
1364 var cell = this.get_cell(i);
1364 var cell = this.get_cell(i);
1365 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1365 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1366 cell.expand_output();
1366 cell.expand_output();
1367 this.set_dirty(true);
1367 this.set_dirty(true);
1368 }
1368 }
1369 };
1369 };
1370
1370
1371 /**
1371 /**
1372 * Expand each code cell's output area, and remove scrollbars.
1372 * Expand each code cell's output area, and remove scrollbars.
1373 */
1373 */
1374 Notebook.prototype.expand_all_output = function () {
1374 Notebook.prototype.expand_all_output = function () {
1375 this.get_cells().map(function (cell, i) {
1375 this.get_cells().map(function (cell, i) {
1376 if (cell instanceof codecell.CodeCell) {
1376 if (cell instanceof codecell.CodeCell) {
1377 cell.expand_output();
1377 cell.expand_output();
1378 }
1378 }
1379 });
1379 });
1380 // this should not be set if the `collapse` key is removed from nbformat
1380 // this should not be set if the `collapse` key is removed from nbformat
1381 this.set_dirty(true);
1381 this.set_dirty(true);
1382 };
1382 };
1383
1383
1384 /**
1384 /**
1385 * Clear the selected CodeCell's output area.
1385 * Clear the selected CodeCell's output area.
1386 *
1386 *
1387 * @param {integer} index - cell index
1387 * @param {integer} index - cell index
1388 */
1388 */
1389 Notebook.prototype.clear_output = function (index) {
1389 Notebook.prototype.clear_output = function (index) {
1390 var i = this.index_or_selected(index);
1390 var i = this.index_or_selected(index);
1391 var cell = this.get_cell(i);
1391 var cell = this.get_cell(i);
1392 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1392 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1393 cell.clear_output();
1393 cell.clear_output();
1394 this.set_dirty(true);
1394 this.set_dirty(true);
1395 }
1395 }
1396 };
1396 };
1397
1397
1398 /**
1398 /**
1399 * Clear each code cell's output area.
1399 * Clear each code cell's output area.
1400 */
1400 */
1401 Notebook.prototype.clear_all_output = function () {
1401 Notebook.prototype.clear_all_output = function () {
1402 this.get_cells().map(function (cell, i) {
1402 this.get_cells().map(function (cell, i) {
1403 if (cell instanceof codecell.CodeCell) {
1403 if (cell instanceof codecell.CodeCell) {
1404 cell.clear_output();
1404 cell.clear_output();
1405 }
1405 }
1406 });
1406 });
1407 this.set_dirty(true);
1407 this.set_dirty(true);
1408 };
1408 };
1409
1409
1410 /**
1410 /**
1411 * Scroll the selected CodeCell's output area.
1411 * Scroll the selected CodeCell's output area.
1412 *
1412 *
1413 * @param {integer} index - cell index
1413 * @param {integer} index - cell index
1414 */
1414 */
1415 Notebook.prototype.scroll_output = function (index) {
1415 Notebook.prototype.scroll_output = function (index) {
1416 var i = this.index_or_selected(index);
1416 var i = this.index_or_selected(index);
1417 var cell = this.get_cell(i);
1417 var cell = this.get_cell(i);
1418 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1418 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1419 cell.scroll_output();
1419 cell.scroll_output();
1420 this.set_dirty(true);
1420 this.set_dirty(true);
1421 }
1421 }
1422 };
1422 };
1423
1423
1424 /**
1424 /**
1425 * Expand each code cell's output area and add a scrollbar for long output.
1425 * Expand each code cell's output area and add a scrollbar for long output.
1426 */
1426 */
1427 Notebook.prototype.scroll_all_output = function () {
1427 Notebook.prototype.scroll_all_output = function () {
1428 this.get_cells().map(function (cell, i) {
1428 this.get_cells().map(function (cell, i) {
1429 if (cell instanceof codecell.CodeCell) {
1429 if (cell instanceof codecell.CodeCell) {
1430 cell.scroll_output();
1430 cell.scroll_output();
1431 }
1431 }
1432 });
1432 });
1433 // this should not be set if the `collapse` key is removed from nbformat
1433 // this should not be set if the `collapse` key is removed from nbformat
1434 this.set_dirty(true);
1434 this.set_dirty(true);
1435 };
1435 };
1436
1436
1437 /**
1437 /**
1438 * Toggle whether a cell's output is collapsed or expanded.
1438 * Toggle whether a cell's output is collapsed or expanded.
1439 *
1439 *
1440 * @param {integer} index - cell index
1440 * @param {integer} index - cell index
1441 */
1441 */
1442 Notebook.prototype.toggle_output = function (index) {
1442 Notebook.prototype.toggle_output = function (index) {
1443 var i = this.index_or_selected(index);
1443 var i = this.index_or_selected(index);
1444 var cell = this.get_cell(i);
1444 var cell = this.get_cell(i);
1445 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1445 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1446 cell.toggle_output();
1446 cell.toggle_output();
1447 this.set_dirty(true);
1447 this.set_dirty(true);
1448 }
1448 }
1449 };
1449 };
1450
1450
1451 /**
1451 /**
1452 * Toggle the output of all cells.
1452 * Toggle the output of all cells.
1453 */
1453 */
1454 Notebook.prototype.toggle_all_output = function () {
1454 Notebook.prototype.toggle_all_output = function () {
1455 this.get_cells().map(function (cell, i) {
1455 this.get_cells().map(function (cell, i) {
1456 if (cell instanceof codecell.CodeCell) {
1456 if (cell instanceof codecell.CodeCell) {
1457 cell.toggle_output();
1457 cell.toggle_output();
1458 }
1458 }
1459 });
1459 });
1460 // this should not be set if the `collapse` key is removed from nbformat
1460 // this should not be set if the `collapse` key is removed from nbformat
1461 this.set_dirty(true);
1461 this.set_dirty(true);
1462 };
1462 };
1463
1463
1464 /**
1464 /**
1465 * Toggle a scrollbar for long cell outputs.
1465 * Toggle a scrollbar for long cell outputs.
1466 *
1466 *
1467 * @param {integer} index - cell index
1467 * @param {integer} index - cell index
1468 */
1468 */
1469 Notebook.prototype.toggle_output_scroll = function (index) {
1469 Notebook.prototype.toggle_output_scroll = function (index) {
1470 var i = this.index_or_selected(index);
1470 var i = this.index_or_selected(index);
1471 var cell = this.get_cell(i);
1471 var cell = this.get_cell(i);
1472 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1472 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1473 cell.toggle_output_scroll();
1473 cell.toggle_output_scroll();
1474 this.set_dirty(true);
1474 this.set_dirty(true);
1475 }
1475 }
1476 };
1476 };
1477
1477
1478 /**
1478 /**
1479 * Toggle the scrolling of long output on all cells.
1479 * Toggle the scrolling of long output on all cells.
1480 */
1480 */
1481 Notebook.prototype.toggle_all_output_scroll = function () {
1481 Notebook.prototype.toggle_all_output_scroll = function () {
1482 this.get_cells().map(function (cell, i) {
1482 this.get_cells().map(function (cell, i) {
1483 if (cell instanceof codecell.CodeCell) {
1483 if (cell instanceof codecell.CodeCell) {
1484 cell.toggle_output_scroll();
1484 cell.toggle_output_scroll();
1485 }
1485 }
1486 });
1486 });
1487 // this should not be set if the `collapse` key is removed from nbformat
1487 // this should not be set if the `collapse` key is removed from nbformat
1488 this.set_dirty(true);
1488 this.set_dirty(true);
1489 };
1489 };
1490
1490
1491 // Other cell functions: line numbers, ...
1491 // Other cell functions: line numbers, ...
1492
1492
1493 /**
1493 /**
1494 * Toggle line numbers in the selected cell's input area.
1494 * Toggle line numbers in the selected cell's input area.
1495 */
1495 */
1496 Notebook.prototype.cell_toggle_line_numbers = function() {
1496 Notebook.prototype.cell_toggle_line_numbers = function() {
1497 this.get_selected_cell().toggle_line_numbers();
1497 this.get_selected_cell().toggle_line_numbers();
1498 };
1498 };
1499
1499
1500 /**
1500 /**
1501 * Set the codemirror mode for all code cells, including the default for
1501 * Set the codemirror mode for all code cells, including the default for
1502 * new code cells.
1502 * new code cells.
1503 */
1503 */
1504 Notebook.prototype.set_codemirror_mode = function(newmode){
1504 Notebook.prototype.set_codemirror_mode = function(newmode){
1505 if (newmode === this.codemirror_mode) {
1505 if (newmode === this.codemirror_mode) {
1506 return;
1506 return;
1507 }
1507 }
1508 this.codemirror_mode = newmode;
1508 this.codemirror_mode = newmode;
1509 codecell.CodeCell.options_default.cm_config.mode = newmode;
1509 codecell.CodeCell.options_default.cm_config.mode = newmode;
1510
1510
1511 var that = this;
1511 var that = this;
1512 utils.requireCodeMirrorMode(newmode, function (spec) {
1512 utils.requireCodeMirrorMode(newmode, function (spec) {
1513 that.get_cells().map(function(cell, i) {
1513 that.get_cells().map(function(cell, i) {
1514 if (cell.cell_type === 'code'){
1514 if (cell.cell_type === 'code'){
1515 cell.code_mirror.setOption('mode', spec);
1515 cell.code_mirror.setOption('mode', spec);
1516 // This is currently redundant, because cm_config ends up as
1516 // This is currently redundant, because cm_config ends up as
1517 // codemirror's own .options object, but I don't want to
1517 // codemirror's own .options object, but I don't want to
1518 // rely on that.
1518 // rely on that.
1519 cell.cm_config.mode = spec;
1519 cell.cm_config.mode = spec;
1520 }
1520 }
1521 });
1521 });
1522 });
1522 });
1523 };
1523 };
1524
1524
1525 // Session related things
1525 // Session related things
1526
1526
1527 /**
1527 /**
1528 * Start a new session and set it on each code cell.
1528 * Start a new session and set it on each code cell.
1529 */
1529 */
1530 Notebook.prototype.start_session = function (kernel_name) {
1530 Notebook.prototype.start_session = function (kernel_name) {
1531 if (this._session_starting) {
1531 if (this._session_starting) {
1532 throw new session.SessionAlreadyStarting();
1532 throw new session.SessionAlreadyStarting();
1533 }
1533 }
1534 this._session_starting = true;
1534 this._session_starting = true;
1535
1535
1536 var options = {
1536 var options = {
1537 base_url: this.base_url,
1537 base_url: this.base_url,
1538 ws_url: this.ws_url,
1538 ws_url: this.ws_url,
1539 notebook_path: this.notebook_path,
1539 notebook_path: this.notebook_path,
1540 notebook_name: this.notebook_name,
1540 notebook_name: this.notebook_name,
1541 kernel_name: kernel_name,
1541 kernel_name: kernel_name,
1542 notebook: this
1542 notebook: this
1543 };
1543 };
1544
1544
1545 var success = $.proxy(this._session_started, this);
1545 var success = $.proxy(this._session_started, this);
1546 var failure = $.proxy(this._session_start_failed, this);
1546 var failure = $.proxy(this._session_start_failed, this);
1547
1547
1548 if (this.session !== null) {
1548 if (this.session !== null) {
1549 this.session.restart(options, success, failure);
1549 this.session.restart(options, success, failure);
1550 } else {
1550 } else {
1551 this.session = new session.Session(options);
1551 this.session = new session.Session(options);
1552 this.session.start(success, failure);
1552 this.session.start(success, failure);
1553 }
1553 }
1554 };
1554 };
1555
1555
1556
1556
1557 /**
1557 /**
1558 * Once a session is started, link the code cells to the kernel and pass the
1558 * Once a session is started, link the code cells to the kernel and pass the
1559 * comm manager to the widget manager.
1559 * comm manager to the widget manager.
1560 */
1560 */
1561 Notebook.prototype._session_started = function (){
1561 Notebook.prototype._session_started = function (){
1562 this._session_starting = false;
1562 this._session_starting = false;
1563 this.kernel = this.session.kernel;
1563 this.kernel = this.session.kernel;
1564 var ncells = this.ncells();
1564 var ncells = this.ncells();
1565 for (var i=0; i<ncells; i++) {
1565 for (var i=0; i<ncells; i++) {
1566 var cell = this.get_cell(i);
1566 var cell = this.get_cell(i);
1567 if (cell instanceof codecell.CodeCell) {
1567 if (cell instanceof codecell.CodeCell) {
1568 cell.set_kernel(this.session.kernel);
1568 cell.set_kernel(this.session.kernel);
1569 }
1569 }
1570 }
1570 }
1571 };
1571 };
1572
1572
1573 /**
1573 /**
1574 * Called when the session fails to start.
1574 * Called when the session fails to start.
1575 */
1575 */
1576 Notebook.prototype._session_start_failed = function(jqxhr, status, error){
1576 Notebook.prototype._session_start_failed = function(jqxhr, status, error){
1577 this._session_starting = false;
1577 this._session_starting = false;
1578 utils.log_ajax_error(jqxhr, status, error);
1578 utils.log_ajax_error(jqxhr, status, error);
1579 };
1579 };
1580
1580
1581 /**
1581 /**
1582 * Prompt the user to restart the IPython kernel.
1582 * Prompt the user to restart the IPython kernel.
1583 */
1583 */
1584 Notebook.prototype.restart_kernel = function () {
1584 Notebook.prototype.restart_kernel = function () {
1585 var that = this;
1585 var that = this;
1586 dialog.modal({
1586 dialog.modal({
1587 notebook: this,
1587 notebook: this,
1588 keyboard_manager: this.keyboard_manager,
1588 keyboard_manager: this.keyboard_manager,
1589 title : "Restart kernel or continue running?",
1589 title : "Restart kernel or continue running?",
1590 body : $("<p/>").text(
1590 body : $("<p/>").text(
1591 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1591 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1592 ),
1592 ),
1593 buttons : {
1593 buttons : {
1594 "Continue running" : {},
1594 "Continue running" : {},
1595 "Restart" : {
1595 "Restart" : {
1596 "class" : "btn-danger",
1596 "class" : "btn-danger",
1597 "click" : function() {
1597 "click" : function() {
1598 that.kernel.restart();
1598 that.kernel.restart();
1599 }
1599 }
1600 }
1600 }
1601 }
1601 }
1602 });
1602 });
1603 };
1603 };
1604
1604
1605 /**
1605 /**
1606 * Execute or render cell outputs and go into command mode.
1606 * Execute or render cell outputs and go into command mode.
1607 */
1607 */
1608 Notebook.prototype.execute_cell = function () {
1608 Notebook.prototype.execute_cell = function () {
1609 // mode = shift, ctrl, alt
1609 // mode = shift, ctrl, alt
1610 var cell = this.get_selected_cell();
1610 var cell = this.get_selected_cell();
1611
1611
1612 cell.execute();
1612 cell.execute();
1613 this.command_mode();
1613 this.command_mode();
1614 this.set_dirty(true);
1614 this.set_dirty(true);
1615 };
1615 };
1616
1616
1617 /**
1617 /**
1618 * Execute or render cell outputs and insert a new cell below.
1618 * Execute or render cell outputs and insert a new cell below.
1619 */
1619 */
1620 Notebook.prototype.execute_cell_and_insert_below = function () {
1620 Notebook.prototype.execute_cell_and_insert_below = function () {
1621 var cell = this.get_selected_cell();
1621 var cell = this.get_selected_cell();
1622 var cell_index = this.find_cell_index(cell);
1622 var cell_index = this.find_cell_index(cell);
1623
1623
1624 cell.execute();
1624 cell.execute();
1625
1625
1626 // If we are at the end always insert a new cell and return
1626 // If we are at the end always insert a new cell and return
1627 if (cell_index === (this.ncells()-1)) {
1627 if (cell_index === (this.ncells()-1)) {
1628 this.command_mode();
1628 this.command_mode();
1629 this.insert_cell_below();
1629 this.insert_cell_below();
1630 this.select(cell_index+1);
1630 this.select(cell_index+1);
1631 this.edit_mode();
1631 this.edit_mode();
1632 this.scroll_to_bottom();
1632 this.scroll_to_bottom();
1633 this.set_dirty(true);
1633 this.set_dirty(true);
1634 return;
1634 return;
1635 }
1635 }
1636
1636
1637 this.command_mode();
1637 this.command_mode();
1638 this.insert_cell_below();
1638 this.insert_cell_below();
1639 this.select(cell_index+1);
1639 this.select(cell_index+1);
1640 this.edit_mode();
1640 this.edit_mode();
1641 this.set_dirty(true);
1641 this.set_dirty(true);
1642 };
1642 };
1643
1643
1644 /**
1644 /**
1645 * Execute or render cell outputs and select the next cell.
1645 * Execute or render cell outputs and select the next cell.
1646 */
1646 */
1647 Notebook.prototype.execute_cell_and_select_below = function () {
1647 Notebook.prototype.execute_cell_and_select_below = function () {
1648
1648
1649 var cell = this.get_selected_cell();
1649 var cell = this.get_selected_cell();
1650 var cell_index = this.find_cell_index(cell);
1650 var cell_index = this.find_cell_index(cell);
1651
1651
1652 cell.execute();
1652 cell.execute();
1653
1653
1654 // If we are at the end always insert a new cell and return
1654 // If we are at the end always insert a new cell and return
1655 if (cell_index === (this.ncells()-1)) {
1655 if (cell_index === (this.ncells()-1)) {
1656 this.command_mode();
1656 this.command_mode();
1657 this.insert_cell_below();
1657 this.insert_cell_below();
1658 this.select(cell_index+1);
1658 this.select(cell_index+1);
1659 this.edit_mode();
1659 this.edit_mode();
1660 this.scroll_to_bottom();
1660 this.scroll_to_bottom();
1661 this.set_dirty(true);
1661 this.set_dirty(true);
1662 return;
1662 return;
1663 }
1663 }
1664
1664
1665 this.command_mode();
1665 this.command_mode();
1666 this.select(cell_index+1);
1666 this.select(cell_index+1);
1667 this.focus_cell();
1667 this.focus_cell();
1668 this.set_dirty(true);
1668 this.set_dirty(true);
1669 };
1669 };
1670
1670
1671 /**
1671 /**
1672 * Execute all cells below the selected cell.
1672 * Execute all cells below the selected cell.
1673 */
1673 */
1674 Notebook.prototype.execute_cells_below = function () {
1674 Notebook.prototype.execute_cells_below = function () {
1675 this.execute_cell_range(this.get_selected_index(), this.ncells());
1675 this.execute_cell_range(this.get_selected_index(), this.ncells());
1676 this.scroll_to_bottom();
1676 this.scroll_to_bottom();
1677 };
1677 };
1678
1678
1679 /**
1679 /**
1680 * Execute all cells above the selected cell.
1680 * Execute all cells above the selected cell.
1681 */
1681 */
1682 Notebook.prototype.execute_cells_above = function () {
1682 Notebook.prototype.execute_cells_above = function () {
1683 this.execute_cell_range(0, this.get_selected_index());
1683 this.execute_cell_range(0, this.get_selected_index());
1684 };
1684 };
1685
1685
1686 /**
1686 /**
1687 * Execute all cells.
1687 * Execute all cells.
1688 */
1688 */
1689 Notebook.prototype.execute_all_cells = function () {
1689 Notebook.prototype.execute_all_cells = function () {
1690 this.execute_cell_range(0, this.ncells());
1690 this.execute_cell_range(0, this.ncells());
1691 this.scroll_to_bottom();
1691 this.scroll_to_bottom();
1692 };
1692 };
1693
1693
1694 /**
1694 /**
1695 * Execute a contiguous range of cells.
1695 * Execute a contiguous range of cells.
1696 *
1696 *
1697 * @param {integer} start - index of the first cell to execute (inclusive)
1697 * @param {integer} start - index of the first cell to execute (inclusive)
1698 * @param {integer} end - index of the last cell to execute (exclusive)
1698 * @param {integer} end - index of the last cell to execute (exclusive)
1699 */
1699 */
1700 Notebook.prototype.execute_cell_range = function (start, end) {
1700 Notebook.prototype.execute_cell_range = function (start, end) {
1701 this.command_mode();
1701 this.command_mode();
1702 for (var i=start; i<end; i++) {
1702 for (var i=start; i<end; i++) {
1703 this.select(i);
1703 this.select(i);
1704 this.execute_cell();
1704 this.execute_cell();
1705 }
1705 }
1706 };
1706 };
1707
1707
1708 // Persistance and loading
1708 // Persistance and loading
1709
1709
1710 /**
1710 /**
1711 * Getter method for this notebook's name.
1711 * Getter method for this notebook's name.
1712 *
1712 *
1713 * @return {string} This notebook's name (excluding file extension)
1713 * @return {string} This notebook's name (excluding file extension)
1714 */
1714 */
1715 Notebook.prototype.get_notebook_name = function () {
1715 Notebook.prototype.get_notebook_name = function () {
1716 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1716 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1717 return nbname;
1717 return nbname;
1718 };
1718 };
1719
1719
1720 /**
1720 /**
1721 * Setter method for this notebook's name.
1721 * Setter method for this notebook's name.
1722 *
1722 *
1723 * @param {string} name
1723 * @param {string} name
1724 */
1724 */
1725 Notebook.prototype.set_notebook_name = function (name) {
1725 Notebook.prototype.set_notebook_name = function (name) {
1726 var parent = utils.url_path_split(this.notebook_path)[0];
1726 var parent = utils.url_path_split(this.notebook_path)[0];
1727 this.notebook_name = name;
1727 this.notebook_name = name;
1728 this.notebook_path = utils.url_path_join(parent, name);
1728 this.notebook_path = utils.url_path_join(parent, name);
1729 };
1729 };
1730
1730
1731 /**
1731 /**
1732 * Check that a notebook's name is valid.
1732 * Check that a notebook's name is valid.
1733 *
1733 *
1734 * @param {string} nbname - A name for this notebook
1734 * @param {string} nbname - A name for this notebook
1735 * @return {boolean} True if the name is valid, false if invalid
1735 * @return {boolean} True if the name is valid, false if invalid
1736 */
1736 */
1737 Notebook.prototype.test_notebook_name = function (nbname) {
1737 Notebook.prototype.test_notebook_name = function (nbname) {
1738 nbname = nbname || '';
1738 nbname = nbname || '';
1739 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1739 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1740 return true;
1740 return true;
1741 } else {
1741 } else {
1742 return false;
1742 return false;
1743 }
1743 }
1744 };
1744 };
1745
1745
1746 /**
1746 /**
1747 * Load a notebook from JSON (.ipynb).
1747 * Load a notebook from JSON (.ipynb).
1748 *
1748 *
1749 * @param {object} data - JSON representation of a notebook
1749 * @param {object} data - JSON representation of a notebook
1750 */
1750 */
1751 Notebook.prototype.fromJSON = function (data) {
1751 Notebook.prototype.fromJSON = function (data) {
1752
1752
1753 var content = data.content;
1753 var content = data.content;
1754 var ncells = this.ncells();
1754 var ncells = this.ncells();
1755 var i;
1755 var i;
1756 for (i=0; i<ncells; i++) {
1756 for (i=0; i<ncells; i++) {
1757 // Always delete cell 0 as they get renumbered as they are deleted.
1757 // Always delete cell 0 as they get renumbered as they are deleted.
1758 this._unsafe_delete_cell(0);
1758 this._unsafe_delete_cell(0);
1759 }
1759 }
1760 // Save the metadata and name.
1760 // Save the metadata and name.
1761 this.metadata = content.metadata;
1761 this.metadata = content.metadata;
1762 this.notebook_name = data.name;
1762 this.notebook_name = data.name;
1763 this.notebook_path = data.path;
1763 this.notebook_path = data.path;
1764 var trusted = true;
1764 var trusted = true;
1765
1765
1766 // Trigger an event changing the kernel spec - this will set the default
1766 // Trigger an event changing the kernel spec - this will set the default
1767 // codemirror mode
1767 // codemirror mode
1768 if (this.metadata.kernelspec !== undefined) {
1768 if (this.metadata.kernelspec !== undefined) {
1769 // TODO shoudl probably not trigger here,
1769 // TODO shoudl probably not trigger here,
1770 // should call the kernel selector, or custom.{js|css} not loaded.
1770 // should call the kernel selector, or custom.{js|css} not loaded.
1771 if(this.kernel_selector){
1771 if(this.kernel_selector){
1772 // technically not perfect, we should check that the kernelspec matches
1772 // technically not perfect, we should check that the kernelspec matches
1773 this.kernel_selector.change_kernel(this.metadata.kernelspec.name);
1773 this.kernel_selector.change_kernel(this.metadata.kernelspec.name);
1774 } else {
1774 } else {
1775 console.log('do not have handle on kernel_selector');
1775 console.log('do not have handle on kernel_selector');
1776 }
1776 }
1777 }
1777 }
1778
1778
1779 // Set the codemirror mode from language_info metadata
1779 // Set the codemirror mode from language_info metadata
1780 if (this.metadata.language_info !== undefined) {
1780 if (this.metadata.language_info !== undefined) {
1781 var langinfo = this.metadata.language_info;
1781 var langinfo = this.metadata.language_info;
1782 // Mode 'null' should be plain, unhighlighted text.
1782 // Mode 'null' should be plain, unhighlighted text.
1783 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1783 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1784 this.set_codemirror_mode(cm_mode);
1784 this.set_codemirror_mode(cm_mode);
1785 }
1785 }
1786
1786
1787 var new_cells = content.cells;
1787 var new_cells = content.cells;
1788 ncells = new_cells.length;
1788 ncells = new_cells.length;
1789 var cell_data = null;
1789 var cell_data = null;
1790 var new_cell = null;
1790 var new_cell = null;
1791 for (i=0; i<ncells; i++) {
1791 for (i=0; i<ncells; i++) {
1792 cell_data = new_cells[i];
1792 cell_data = new_cells[i];
1793 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1793 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1794 new_cell.fromJSON(cell_data);
1794 new_cell.fromJSON(cell_data);
1795 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1795 if (new_cell.cell_type === 'code' && !new_cell.output_area.trusted) {
1796 trusted = false;
1796 trusted = false;
1797 }
1797 }
1798 }
1798 }
1799 if (trusted !== this.trusted) {
1799 if (trusted !== this.trusted) {
1800 this.trusted = trusted;
1800 this.trusted = trusted;
1801 this.events.trigger("trust_changed.Notebook", trusted);
1801 this.events.trigger("trust_changed.Notebook", trusted);
1802 }
1802 }
1803 };
1803 };
1804
1804
1805 /**
1805 /**
1806 * Dump this notebook into a JSON-friendly object.
1806 * Dump this notebook into a JSON-friendly object.
1807 *
1807 *
1808 * @return {object} A JSON-friendly representation of this notebook.
1808 * @return {object} A JSON-friendly representation of this notebook.
1809 */
1809 */
1810 Notebook.prototype.toJSON = function () {
1810 Notebook.prototype.toJSON = function () {
1811 // remove the conversion indicator, which only belongs in-memory
1811 // remove the conversion indicator, which only belongs in-memory
1812 delete this.metadata.orig_nbformat;
1812 delete this.metadata.orig_nbformat;
1813 delete this.metadata.orig_nbformat_minor;
1813 delete this.metadata.orig_nbformat_minor;
1814
1814
1815 var cells = this.get_cells();
1815 var cells = this.get_cells();
1816 var ncells = cells.length;
1816 var ncells = cells.length;
1817 var cell_array = new Array(ncells);
1817 var cell_array = new Array(ncells);
1818 var trusted = true;
1818 var trusted = true;
1819 for (var i=0; i<ncells; i++) {
1819 for (var i=0; i<ncells; i++) {
1820 var cell = cells[i];
1820 var cell = cells[i];
1821 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1821 if (cell.cell_type === 'code' && !cell.output_area.trusted) {
1822 trusted = false;
1822 trusted = false;
1823 }
1823 }
1824 cell_array[i] = cell.toJSON();
1824 cell_array[i] = cell.toJSON();
1825 }
1825 }
1826 var data = {
1826 var data = {
1827 cells: cell_array,
1827 cells: cell_array,
1828 metadata: this.metadata,
1828 metadata: this.metadata,
1829 nbformat: this.nbformat,
1829 nbformat: this.nbformat,
1830 nbformat_minor: this.nbformat_minor
1830 nbformat_minor: this.nbformat_minor
1831 };
1831 };
1832 if (trusted != this.trusted) {
1832 if (trusted !== this.trusted) {
1833 this.trusted = trusted;
1833 this.trusted = trusted;
1834 this.events.trigger("trust_changed.Notebook", trusted);
1834 this.events.trigger("trust_changed.Notebook", trusted);
1835 }
1835 }
1836 return data;
1836 return data;
1837 };
1837 };
1838
1838
1839 /**
1839 /**
1840 * Start an autosave timer which periodically saves the notebook.
1840 * Start an autosave timer which periodically saves the notebook.
1841 *
1841 *
1842 * @param {integer} interval - the autosave interval in milliseconds
1842 * @param {integer} interval - the autosave interval in milliseconds
1843 */
1843 */
1844 Notebook.prototype.set_autosave_interval = function (interval) {
1844 Notebook.prototype.set_autosave_interval = function (interval) {
1845 var that = this;
1845 var that = this;
1846 // clear previous interval, so we don't get simultaneous timers
1846 // clear previous interval, so we don't get simultaneous timers
1847 if (this.autosave_timer) {
1847 if (this.autosave_timer) {
1848 clearInterval(this.autosave_timer);
1848 clearInterval(this.autosave_timer);
1849 }
1849 }
1850 if (!this.writable) {
1850 if (!this.writable) {
1851 // disable autosave if not writable
1851 // disable autosave if not writable
1852 interval = 0;
1852 interval = 0;
1853 }
1853 }
1854
1854
1855 this.autosave_interval = this.minimum_autosave_interval = interval;
1855 this.autosave_interval = this.minimum_autosave_interval = interval;
1856 if (interval) {
1856 if (interval) {
1857 this.autosave_timer = setInterval(function() {
1857 this.autosave_timer = setInterval(function() {
1858 if (that.dirty) {
1858 if (that.dirty) {
1859 that.save_notebook();
1859 that.save_notebook();
1860 }
1860 }
1861 }, interval);
1861 }, interval);
1862 this.events.trigger("autosave_enabled.Notebook", interval);
1862 this.events.trigger("autosave_enabled.Notebook", interval);
1863 } else {
1863 } else {
1864 this.autosave_timer = null;
1864 this.autosave_timer = null;
1865 this.events.trigger("autosave_disabled.Notebook");
1865 this.events.trigger("autosave_disabled.Notebook");
1866 }
1866 }
1867 };
1867 };
1868
1868
1869 /**
1869 /**
1870 * Save this notebook on the server. This becomes a notebook instance's
1870 * Save this notebook on the server. This becomes a notebook instance's
1871 * .save_notebook method *after* the entire notebook has been loaded.
1871 * .save_notebook method *after* the entire notebook has been loaded.
1872 */
1872 */
1873 Notebook.prototype.save_notebook = function () {
1873 Notebook.prototype.save_notebook = function () {
1874 if (!this._fully_loaded) {
1874 if (!this._fully_loaded) {
1875 this.events.trigger('notebook_save_failed.Notebook',
1875 this.events.trigger('notebook_save_failed.Notebook',
1876 new Error("Load failed, save is disabled")
1876 new Error("Load failed, save is disabled")
1877 );
1877 );
1878 return;
1878 return;
1879 } else if (!this.writable) {
1879 } else if (!this.writable) {
1880 this.events.trigger('notebook_save_failed.Notebook',
1880 this.events.trigger('notebook_save_failed.Notebook',
1881 new Error("Notebook is read-only")
1881 new Error("Notebook is read-only")
1882 );
1882 );
1883 return;
1883 return;
1884 }
1884 }
1885
1885
1886 // Trigger an event before save, which allows listeners to modify
1886 // Trigger an event before save, which allows listeners to modify
1887 // the notebook as needed.
1887 // the notebook as needed.
1888 this.events.trigger('before_save.Notebook');
1888 this.events.trigger('before_save.Notebook');
1889
1889
1890 // Create a JSON model to be sent to the server.
1890 // Create a JSON model to be sent to the server.
1891 var model = {
1891 var model = {
1892 type : "notebook",
1892 type : "notebook",
1893 content : this.toJSON()
1893 content : this.toJSON()
1894 };
1894 };
1895 // time the ajax call for autosave tuning purposes.
1895 // time the ajax call for autosave tuning purposes.
1896 var start = new Date().getTime();
1896 var start = new Date().getTime();
1897
1897
1898 var that = this;
1898 var that = this;
1899 return this.contents.save(this.notebook_path, model).then(
1899 return this.contents.save(this.notebook_path, model).then(
1900 $.proxy(this.save_notebook_success, this, start),
1900 $.proxy(this.save_notebook_success, this, start),
1901 function (error) {
1901 function (error) {
1902 that.events.trigger('notebook_save_failed.Notebook', error);
1902 that.events.trigger('notebook_save_failed.Notebook', error);
1903 }
1903 }
1904 );
1904 );
1905 };
1905 };
1906
1906
1907 /**
1907 /**
1908 * Success callback for saving a notebook.
1908 * Success callback for saving a notebook.
1909 *
1909 *
1910 * @param {integer} start - Time when the save request start
1910 * @param {integer} start - Time when the save request start
1911 * @param {object} data - JSON representation of a notebook
1911 * @param {object} data - JSON representation of a notebook
1912 */
1912 */
1913 Notebook.prototype.save_notebook_success = function (start, data) {
1913 Notebook.prototype.save_notebook_success = function (start, data) {
1914 this.set_dirty(false);
1914 this.set_dirty(false);
1915 if (data.message) {
1915 if (data.message) {
1916 // save succeeded, but validation failed.
1916 // save succeeded, but validation failed.
1917 var body = $("<div>");
1917 var body = $("<div>");
1918 var title = "Notebook validation failed";
1918 var title = "Notebook validation failed";
1919
1919
1920 body.append($("<p>").text(
1920 body.append($("<p>").text(
1921 "The save operation succeeded," +
1921 "The save operation succeeded," +
1922 " but the notebook does not appear to be valid." +
1922 " but the notebook does not appear to be valid." +
1923 " The validation error was:"
1923 " The validation error was:"
1924 )).append($("<div>").addClass("validation-error").append(
1924 )).append($("<div>").addClass("validation-error").append(
1925 $("<pre>").text(data.message)
1925 $("<pre>").text(data.message)
1926 ));
1926 ));
1927 dialog.modal({
1927 dialog.modal({
1928 notebook: this,
1928 notebook: this,
1929 keyboard_manager: this.keyboard_manager,
1929 keyboard_manager: this.keyboard_manager,
1930 title: title,
1930 title: title,
1931 body: body,
1931 body: body,
1932 buttons : {
1932 buttons : {
1933 OK : {
1933 OK : {
1934 "class" : "btn-primary"
1934 "class" : "btn-primary"
1935 }
1935 }
1936 }
1936 }
1937 });
1937 });
1938 }
1938 }
1939 this.events.trigger('notebook_saved.Notebook');
1939 this.events.trigger('notebook_saved.Notebook');
1940 this._update_autosave_interval(start);
1940 this._update_autosave_interval(start);
1941 if (this._checkpoint_after_save) {
1941 if (this._checkpoint_after_save) {
1942 this.create_checkpoint();
1942 this.create_checkpoint();
1943 this._checkpoint_after_save = false;
1943 this._checkpoint_after_save = false;
1944 }
1944 }
1945 };
1945 };
1946
1946
1947 /**
1947 /**
1948 * Update the autosave interval based on the duration of the last save.
1948 * Update the autosave interval based on the duration of the last save.
1949 *
1949 *
1950 * @param {integer} timestamp - when the save request started
1950 * @param {integer} timestamp - when the save request started
1951 */
1951 */
1952 Notebook.prototype._update_autosave_interval = function (start) {
1952 Notebook.prototype._update_autosave_interval = function (start) {
1953 var duration = (new Date().getTime() - start);
1953 var duration = (new Date().getTime() - start);
1954 if (this.autosave_interval) {
1954 if (this.autosave_interval) {
1955 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1955 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1956 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1956 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1957 // round to 10 seconds, otherwise we will be setting a new interval too often
1957 // round to 10 seconds, otherwise we will be setting a new interval too often
1958 interval = 10000 * Math.round(interval / 10000);
1958 interval = 10000 * Math.round(interval / 10000);
1959 // set new interval, if it's changed
1959 // set new interval, if it's changed
1960 if (interval != this.autosave_interval) {
1960 if (interval !== this.autosave_interval) {
1961 this.set_autosave_interval(interval);
1961 this.set_autosave_interval(interval);
1962 }
1962 }
1963 }
1963 }
1964 };
1964 };
1965
1965
1966 /**
1966 /**
1967 * Explicitly trust the output of this notebook.
1967 * Explicitly trust the output of this notebook.
1968 */
1968 */
1969 Notebook.prototype.trust_notebook = function () {
1969 Notebook.prototype.trust_notebook = function () {
1970 var body = $("<div>").append($("<p>")
1970 var body = $("<div>").append($("<p>")
1971 .text("A trusted IPython notebook may execute hidden malicious code ")
1971 .text("A trusted IPython notebook may execute hidden malicious code ")
1972 .append($("<strong>")
1972 .append($("<strong>")
1973 .append(
1973 .append(
1974 $("<em>").text("when you open it")
1974 $("<em>").text("when you open it")
1975 )
1975 )
1976 ).append(".").append(
1976 ).append(".").append(
1977 " Selecting trust will immediately reload this notebook in a trusted state."
1977 " Selecting trust will immediately reload this notebook in a trusted state."
1978 ).append(
1978 ).append(
1979 " For more information, see the "
1979 " For more information, see the "
1980 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1980 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1981 .text("IPython security documentation")
1981 .text("IPython security documentation")
1982 ).append(".")
1982 ).append(".")
1983 );
1983 );
1984
1984
1985 var nb = this;
1985 var nb = this;
1986 dialog.modal({
1986 dialog.modal({
1987 notebook: this,
1987 notebook: this,
1988 keyboard_manager: this.keyboard_manager,
1988 keyboard_manager: this.keyboard_manager,
1989 title: "Trust this notebook?",
1989 title: "Trust this notebook?",
1990 body: body,
1990 body: body,
1991
1991
1992 buttons: {
1992 buttons: {
1993 Cancel : {},
1993 Cancel : {},
1994 Trust : {
1994 Trust : {
1995 class : "btn-danger",
1995 class : "btn-danger",
1996 click : function () {
1996 click : function () {
1997 var cells = nb.get_cells();
1997 var cells = nb.get_cells();
1998 for (var i = 0; i < cells.length; i++) {
1998 for (var i = 0; i < cells.length; i++) {
1999 var cell = cells[i];
1999 var cell = cells[i];
2000 if (cell.cell_type == 'code') {
2000 if (cell.cell_type === 'code') {
2001 cell.output_area.trusted = true;
2001 cell.output_area.trusted = true;
2002 }
2002 }
2003 }
2003 }
2004 nb.events.on('notebook_saved.Notebook', function () {
2004 nb.events.on('notebook_saved.Notebook', function () {
2005 window.location.reload();
2005 window.location.reload();
2006 });
2006 });
2007 nb.save_notebook();
2007 nb.save_notebook();
2008 }
2008 }
2009 }
2009 }
2010 }
2010 }
2011 });
2011 });
2012 };
2012 };
2013
2013
2014 /**
2014 /**
2015 * Make a copy of the current notebook.
2015 * Make a copy of the current notebook.
2016 */
2016 */
2017 Notebook.prototype.copy_notebook = function () {
2017 Notebook.prototype.copy_notebook = function () {
2018 var that = this;
2018 var that = this;
2019 var base_url = this.base_url;
2019 var base_url = this.base_url;
2020 var w = window.open();
2020 var w = window.open();
2021 var parent = utils.url_path_split(this.notebook_path)[0];
2021 var parent = utils.url_path_split(this.notebook_path)[0];
2022 this.contents.copy(this.notebook_path, parent).then(
2022 this.contents.copy(this.notebook_path, parent).then(
2023 function (data) {
2023 function (data) {
2024 w.location = utils.url_join_encode(
2024 w.location = utils.url_join_encode(
2025 base_url, 'notebooks', data.path
2025 base_url, 'notebooks', data.path
2026 );
2026 );
2027 },
2027 },
2028 function(error) {
2028 function(error) {
2029 w.close();
2029 w.close();
2030 that.events.trigger('notebook_copy_failed', error);
2030 that.events.trigger('notebook_copy_failed', error);
2031 }
2031 }
2032 );
2032 );
2033 };
2033 };
2034
2034
2035 /**
2035 /**
2036 * Rename the notebook.
2036 * Rename the notebook.
2037 * @param {string} new_name
2037 * @param {string} new_name
2038 * @return {Promise} promise that resolves when the notebook is renamed.
2038 * @return {Promise} promise that resolves when the notebook is renamed.
2039 */
2039 */
2040 Notebook.prototype.rename = function (new_name) {
2040 Notebook.prototype.rename = function (new_name) {
2041 if (!new_name.match(/\.ipynb$/)) {
2041 if (!new_name.match(/\.ipynb$/)) {
2042 new_name = new_name + ".ipynb";
2042 new_name = new_name + ".ipynb";
2043 }
2043 }
2044
2044
2045 var that = this;
2045 var that = this;
2046 var parent = utils.url_path_split(this.notebook_path)[0];
2046 var parent = utils.url_path_split(this.notebook_path)[0];
2047 var new_path = utils.url_path_join(parent, new_name);
2047 var new_path = utils.url_path_join(parent, new_name);
2048 return this.contents.rename(this.notebook_path, new_path).then(
2048 return this.contents.rename(this.notebook_path, new_path).then(
2049 function (json) {
2049 function (json) {
2050 that.notebook_name = json.name;
2050 that.notebook_name = json.name;
2051 that.notebook_path = json.path;
2051 that.notebook_path = json.path;
2052 that.session.rename_notebook(json.path);
2052 that.session.rename_notebook(json.path);
2053 that.events.trigger('notebook_renamed.Notebook', json);
2053 that.events.trigger('notebook_renamed.Notebook', json);
2054 }
2054 }
2055 );
2055 );
2056 };
2056 };
2057
2057
2058 /**
2058 /**
2059 * Delete this notebook
2059 * Delete this notebook
2060 */
2060 */
2061 Notebook.prototype.delete = function () {
2061 Notebook.prototype.delete = function () {
2062 this.contents.delete(this.notebook_path);
2062 this.contents.delete(this.notebook_path);
2063 };
2063 };
2064
2064
2065 /**
2065 /**
2066 * Request a notebook's data from the server.
2066 * Request a notebook's data from the server.
2067 *
2067 *
2068 * @param {string} notebook_path - A notebook to load
2068 * @param {string} notebook_path - A notebook to load
2069 */
2069 */
2070 Notebook.prototype.load_notebook = function (notebook_path) {
2070 Notebook.prototype.load_notebook = function (notebook_path) {
2071 this.notebook_path = notebook_path;
2071 this.notebook_path = notebook_path;
2072 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2072 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2073 this.events.trigger('notebook_loading.Notebook');
2073 this.events.trigger('notebook_loading.Notebook');
2074 this.contents.get(notebook_path, {type: 'notebook'}).then(
2074 this.contents.get(notebook_path, {type: 'notebook'}).then(
2075 $.proxy(this.load_notebook_success, this),
2075 $.proxy(this.load_notebook_success, this),
2076 $.proxy(this.load_notebook_error, this)
2076 $.proxy(this.load_notebook_error, this)
2077 );
2077 );
2078 };
2078 };
2079
2079
2080 /**
2080 /**
2081 * Success callback for loading a notebook from the server.
2081 * Success callback for loading a notebook from the server.
2082 *
2082 *
2083 * Load notebook data from the JSON response.
2083 * Load notebook data from the JSON response.
2084 *
2084 *
2085 * @param {object} data JSON representation of a notebook
2085 * @param {object} data JSON representation of a notebook
2086 */
2086 */
2087 Notebook.prototype.load_notebook_success = function (data) {
2087 Notebook.prototype.load_notebook_success = function (data) {
2088 var failed, msg;
2088 var failed, msg;
2089 try {
2089 try {
2090 this.fromJSON(data);
2090 this.fromJSON(data);
2091 } catch (e) {
2091 } catch (e) {
2092 failed = e;
2092 failed = e;
2093 console.log("Notebook failed to load from JSON:", e);
2093 console.log("Notebook failed to load from JSON:", e);
2094 }
2094 }
2095 if (failed || data.message) {
2095 if (failed || data.message) {
2096 // *either* fromJSON failed or validation failed
2096 // *either* fromJSON failed or validation failed
2097 var body = $("<div>");
2097 var body = $("<div>");
2098 var title;
2098 var title;
2099 if (failed) {
2099 if (failed) {
2100 title = "Notebook failed to load";
2100 title = "Notebook failed to load";
2101 body.append($("<p>").text(
2101 body.append($("<p>").text(
2102 "The error was: "
2102 "The error was: "
2103 )).append($("<div>").addClass("js-error").text(
2103 )).append($("<div>").addClass("js-error").text(
2104 failed.toString()
2104 failed.toString()
2105 )).append($("<p>").text(
2105 )).append($("<p>").text(
2106 "See the error console for details."
2106 "See the error console for details."
2107 ));
2107 ));
2108 } else {
2108 } else {
2109 title = "Notebook validation failed";
2109 title = "Notebook validation failed";
2110 }
2110 }
2111
2111
2112 if (data.message) {
2112 if (data.message) {
2113 if (failed) {
2113 if (failed) {
2114 msg = "The notebook also failed validation:";
2114 msg = "The notebook also failed validation:";
2115 } else {
2115 } else {
2116 msg = "An invalid notebook may not function properly." +
2116 msg = "An invalid notebook may not function properly." +
2117 " The validation error was:";
2117 " The validation error was:";
2118 }
2118 }
2119 body.append($("<p>").text(
2119 body.append($("<p>").text(
2120 msg
2120 msg
2121 )).append($("<div>").addClass("validation-error").append(
2121 )).append($("<div>").addClass("validation-error").append(
2122 $("<pre>").text(data.message)
2122 $("<pre>").text(data.message)
2123 ));
2123 ));
2124 }
2124 }
2125
2125
2126 dialog.modal({
2126 dialog.modal({
2127 notebook: this,
2127 notebook: this,
2128 keyboard_manager: this.keyboard_manager,
2128 keyboard_manager: this.keyboard_manager,
2129 title: title,
2129 title: title,
2130 body: body,
2130 body: body,
2131 buttons : {
2131 buttons : {
2132 OK : {
2132 OK : {
2133 "class" : "btn-primary"
2133 "class" : "btn-primary"
2134 }
2134 }
2135 }
2135 }
2136 });
2136 });
2137 }
2137 }
2138 if (this.ncells() === 0) {
2138 if (this.ncells() === 0) {
2139 this.insert_cell_below('code');
2139 this.insert_cell_below('code');
2140 this.edit_mode(0);
2140 this.edit_mode(0);
2141 } else {
2141 } else {
2142 this.select(0);
2142 this.select(0);
2143 this.handle_command_mode(this.get_cell(0));
2143 this.handle_command_mode(this.get_cell(0));
2144 }
2144 }
2145 this.set_dirty(false);
2145 this.set_dirty(false);
2146 this.scroll_to_top();
2146 this.scroll_to_top();
2147 this.writable = data.writable || false;
2147 this.writable = data.writable || false;
2148 var nbmodel = data.content;
2148 var nbmodel = data.content;
2149 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2149 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2150 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2150 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2151 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2151 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2152 var src;
2152 var src;
2153 if (nbmodel.nbformat > orig_nbformat) {
2153 if (nbmodel.nbformat > orig_nbformat) {
2154 src = " an older notebook format ";
2154 src = " an older notebook format ";
2155 } else {
2155 } else {
2156 src = " a newer notebook format ";
2156 src = " a newer notebook format ";
2157 }
2157 }
2158
2158
2159 msg = "This notebook has been converted from" + src +
2159 msg = "This notebook has been converted from" + src +
2160 "(v"+orig_nbformat+") to the current notebook " +
2160 "(v"+orig_nbformat+") to the current notebook " +
2161 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2161 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2162 "current notebook format will be used.";
2162 "current notebook format will be used.";
2163
2163
2164 if (nbmodel.nbformat > orig_nbformat) {
2164 if (nbmodel.nbformat > orig_nbformat) {
2165 msg += " Older versions of IPython may not be able to read the new format.";
2165 msg += " Older versions of IPython may not be able to read the new format.";
2166 } else {
2166 } else {
2167 msg += " Some features of the original notebook may not be available.";
2167 msg += " Some features of the original notebook may not be available.";
2168 }
2168 }
2169 msg += " To preserve the original version, close the " +
2169 msg += " To preserve the original version, close the " +
2170 "notebook without saving it.";
2170 "notebook without saving it.";
2171 dialog.modal({
2171 dialog.modal({
2172 notebook: this,
2172 notebook: this,
2173 keyboard_manager: this.keyboard_manager,
2173 keyboard_manager: this.keyboard_manager,
2174 title : "Notebook converted",
2174 title : "Notebook converted",
2175 body : msg,
2175 body : msg,
2176 buttons : {
2176 buttons : {
2177 OK : {
2177 OK : {
2178 class : "btn-primary"
2178 class : "btn-primary"
2179 }
2179 }
2180 }
2180 }
2181 });
2181 });
2182 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2182 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2183 this.nbformat_minor = nbmodel.nbformat_minor;
2183 this.nbformat_minor = nbmodel.nbformat_minor;
2184 }
2184 }
2185
2185
2186 // Create the session after the notebook is completely loaded to prevent
2186 // Create the session after the notebook is completely loaded to prevent
2187 // code execution upon loading, which is a security risk.
2187 // code execution upon loading, which is a security risk.
2188 if (this.session === null) {
2188 if (this.session === null) {
2189 var kernel_name;
2189 var kernel_name;
2190 if (this.metadata.kernelspec) {
2190 if (this.metadata.kernelspec) {
2191 var kernelspec = this.metadata.kernelspec || {};
2191 var kernelspec = this.metadata.kernelspec || {};
2192 kernel_name = kernelspec.name;
2192 kernel_name = kernelspec.name;
2193 } else {
2193 } else {
2194 kernel_name = utils.get_url_param('kernel_name');
2194 kernel_name = utils.get_url_param('kernel_name');
2195 }
2195 }
2196 this.start_session(kernel_name);
2196 this.start_session(kernel_name);
2197 }
2197 }
2198 // load our checkpoint list
2198 // load our checkpoint list
2199 this.list_checkpoints();
2199 this.list_checkpoints();
2200
2200
2201 // load toolbar state
2201 // load toolbar state
2202 if (this.metadata.celltoolbar) {
2202 if (this.metadata.celltoolbar) {
2203 celltoolbar.CellToolbar.global_show();
2203 celltoolbar.CellToolbar.global_show();
2204 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2204 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2205 } else {
2205 } else {
2206 celltoolbar.CellToolbar.global_hide();
2206 celltoolbar.CellToolbar.global_hide();
2207 }
2207 }
2208
2208
2209 if (!this.writable) {
2209 if (!this.writable) {
2210 this.set_autosave_interval(0);
2210 this.set_autosave_interval(0);
2211 this.events.trigger('notebook_read_only.Notebook');
2211 this.events.trigger('notebook_read_only.Notebook');
2212 }
2212 }
2213
2213
2214 // now that we're fully loaded, it is safe to restore save functionality
2214 // now that we're fully loaded, it is safe to restore save functionality
2215 this._fully_loaded = true;
2215 this._fully_loaded = true;
2216 this.events.trigger('notebook_loaded.Notebook');
2216 this.events.trigger('notebook_loaded.Notebook');
2217 };
2217 };
2218
2218
2219 Notebook.prototype.set_kernelselector = function(k_selector){
2219 Notebook.prototype.set_kernelselector = function(k_selector){
2220 this.kernel_selector = k_selector;
2220 this.kernel_selector = k_selector;
2221 };
2221 };
2222
2222
2223 /**
2223 /**
2224 * Failure callback for loading a notebook from the server.
2224 * Failure callback for loading a notebook from the server.
2225 *
2225 *
2226 * @param {Error} error
2226 * @param {Error} error
2227 */
2227 */
2228 Notebook.prototype.load_notebook_error = function (error) {
2228 Notebook.prototype.load_notebook_error = function (error) {
2229 this.events.trigger('notebook_load_failed.Notebook', error);
2229 this.events.trigger('notebook_load_failed.Notebook', error);
2230 var msg;
2230 var msg;
2231 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2231 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2232 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2232 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2233 msg = "An unknown error occurred while loading this notebook. " +
2233 msg = "An unknown error occurred while loading this notebook. " +
2234 "This version can load notebook formats " +
2234 "This version can load notebook formats " +
2235 "v" + this.nbformat + " or earlier. See the server log for details.";
2235 "v" + this.nbformat + " or earlier. See the server log for details.";
2236 } else {
2236 } else {
2237 msg = error.message;
2237 msg = error.message;
2238 }
2238 }
2239 dialog.modal({
2239 dialog.modal({
2240 notebook: this,
2240 notebook: this,
2241 keyboard_manager: this.keyboard_manager,
2241 keyboard_manager: this.keyboard_manager,
2242 title: "Error loading notebook",
2242 title: "Error loading notebook",
2243 body : msg,
2243 body : msg,
2244 buttons : {
2244 buttons : {
2245 "OK": {}
2245 "OK": {}
2246 }
2246 }
2247 });
2247 });
2248 };
2248 };
2249
2249
2250 /********************* checkpoint-related ********************/
2250 /********************* checkpoint-related ********************/
2251
2251
2252 /**
2252 /**
2253 * Save the notebook then immediately create a checkpoint.
2253 * Save the notebook then immediately create a checkpoint.
2254 */
2254 */
2255 Notebook.prototype.save_checkpoint = function () {
2255 Notebook.prototype.save_checkpoint = function () {
2256 this._checkpoint_after_save = true;
2256 this._checkpoint_after_save = true;
2257 this.save_notebook();
2257 this.save_notebook();
2258 };
2258 };
2259
2259
2260 /**
2260 /**
2261 * Add a checkpoint for this notebook.
2261 * Add a checkpoint for this notebook.
2262 */
2262 */
2263 Notebook.prototype.add_checkpoint = function (checkpoint) {
2263 Notebook.prototype.add_checkpoint = function (checkpoint) {
2264 var found = false;
2264 var found = false;
2265 for (var i = 0; i < this.checkpoints.length; i++) {
2265 for (var i = 0; i < this.checkpoints.length; i++) {
2266 var existing = this.checkpoints[i];
2266 var existing = this.checkpoints[i];
2267 if (existing.id == checkpoint.id) {
2267 if (existing.id === checkpoint.id) {
2268 found = true;
2268 found = true;
2269 this.checkpoints[i] = checkpoint;
2269 this.checkpoints[i] = checkpoint;
2270 break;
2270 break;
2271 }
2271 }
2272 }
2272 }
2273 if (!found) {
2273 if (!found) {
2274 this.checkpoints.push(checkpoint);
2274 this.checkpoints.push(checkpoint);
2275 }
2275 }
2276 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2276 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2277 };
2277 };
2278
2278
2279 /**
2279 /**
2280 * List checkpoints for this notebook.
2280 * List checkpoints for this notebook.
2281 */
2281 */
2282 Notebook.prototype.list_checkpoints = function () {
2282 Notebook.prototype.list_checkpoints = function () {
2283 var that = this;
2283 var that = this;
2284 this.contents.list_checkpoints(this.notebook_path).then(
2284 this.contents.list_checkpoints(this.notebook_path).then(
2285 $.proxy(this.list_checkpoints_success, this),
2285 $.proxy(this.list_checkpoints_success, this),
2286 function(error) {
2286 function(error) {
2287 that.events.trigger('list_checkpoints_failed.Notebook', error);
2287 that.events.trigger('list_checkpoints_failed.Notebook', error);
2288 }
2288 }
2289 );
2289 );
2290 };
2290 };
2291
2291
2292 /**
2292 /**
2293 * Success callback for listing checkpoints.
2293 * Success callback for listing checkpoints.
2294 *
2294 *
2295 * @param {object} data - JSON representation of a checkpoint
2295 * @param {object} data - JSON representation of a checkpoint
2296 */
2296 */
2297 Notebook.prototype.list_checkpoints_success = function (data) {
2297 Notebook.prototype.list_checkpoints_success = function (data) {
2298 this.checkpoints = data;
2298 this.checkpoints = data;
2299 if (data.length) {
2299 if (data.length) {
2300 this.last_checkpoint = data[data.length - 1];
2300 this.last_checkpoint = data[data.length - 1];
2301 } else {
2301 } else {
2302 this.last_checkpoint = null;
2302 this.last_checkpoint = null;
2303 }
2303 }
2304 this.events.trigger('checkpoints_listed.Notebook', [data]);
2304 this.events.trigger('checkpoints_listed.Notebook', [data]);
2305 };
2305 };
2306
2306
2307 /**
2307 /**
2308 * Create a checkpoint of this notebook on the server from the most recent save.
2308 * Create a checkpoint of this notebook on the server from the most recent save.
2309 */
2309 */
2310 Notebook.prototype.create_checkpoint = function () {
2310 Notebook.prototype.create_checkpoint = function () {
2311 var that = this;
2311 var that = this;
2312 this.contents.create_checkpoint(this.notebook_path).then(
2312 this.contents.create_checkpoint(this.notebook_path).then(
2313 $.proxy(this.create_checkpoint_success, this),
2313 $.proxy(this.create_checkpoint_success, this),
2314 function (error) {
2314 function (error) {
2315 that.events.trigger('checkpoint_failed.Notebook', error);
2315 that.events.trigger('checkpoint_failed.Notebook', error);
2316 }
2316 }
2317 );
2317 );
2318 };
2318 };
2319
2319
2320 /**
2320 /**
2321 * Success callback for creating a checkpoint.
2321 * Success callback for creating a checkpoint.
2322 *
2322 *
2323 * @param {object} data - JSON representation of a checkpoint
2323 * @param {object} data - JSON representation of a checkpoint
2324 */
2324 */
2325 Notebook.prototype.create_checkpoint_success = function (data) {
2325 Notebook.prototype.create_checkpoint_success = function (data) {
2326 this.add_checkpoint(data);
2326 this.add_checkpoint(data);
2327 this.events.trigger('checkpoint_created.Notebook', data);
2327 this.events.trigger('checkpoint_created.Notebook', data);
2328 };
2328 };
2329
2329
2330 /**
2330 /**
2331 * Display the restore checkpoint dialog
2331 * Display the restore checkpoint dialog
2332 * @param {string} checkpoint ID
2332 * @param {string} checkpoint ID
2333 */
2333 */
2334 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2334 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2335 var that = this;
2335 var that = this;
2336 checkpoint = checkpoint || this.last_checkpoint;
2336 checkpoint = checkpoint || this.last_checkpoint;
2337 if ( ! checkpoint ) {
2337 if ( ! checkpoint ) {
2338 console.log("restore dialog, but no checkpoint to restore to!");
2338 console.log("restore dialog, but no checkpoint to restore to!");
2339 return;
2339 return;
2340 }
2340 }
2341 var body = $('<div/>').append(
2341 var body = $('<div/>').append(
2342 $('<p/>').addClass("p-space").text(
2342 $('<p/>').addClass("p-space").text(
2343 "Are you sure you want to revert the notebook to " +
2343 "Are you sure you want to revert the notebook to " +
2344 "the latest checkpoint?"
2344 "the latest checkpoint?"
2345 ).append(
2345 ).append(
2346 $("<strong/>").text(
2346 $("<strong/>").text(
2347 " This cannot be undone."
2347 " This cannot be undone."
2348 )
2348 )
2349 )
2349 )
2350 ).append(
2350 ).append(
2351 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2351 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2352 ).append(
2352 ).append(
2353 $('<p/>').addClass("p-space").text(
2353 $('<p/>').addClass("p-space").text(
2354 Date(checkpoint.last_modified)
2354 Date(checkpoint.last_modified)
2355 ).css("text-align", "center")
2355 ).css("text-align", "center")
2356 );
2356 );
2357
2357
2358 dialog.modal({
2358 dialog.modal({
2359 notebook: this,
2359 notebook: this,
2360 keyboard_manager: this.keyboard_manager,
2360 keyboard_manager: this.keyboard_manager,
2361 title : "Revert notebook to checkpoint",
2361 title : "Revert notebook to checkpoint",
2362 body : body,
2362 body : body,
2363 buttons : {
2363 buttons : {
2364 Revert : {
2364 Revert : {
2365 class : "btn-danger",
2365 class : "btn-danger",
2366 click : function () {
2366 click : function () {
2367 that.restore_checkpoint(checkpoint.id);
2367 that.restore_checkpoint(checkpoint.id);
2368 }
2368 }
2369 },
2369 },
2370 Cancel : {}
2370 Cancel : {}
2371 }
2371 }
2372 });
2372 });
2373 };
2373 };
2374
2374
2375 /**
2375 /**
2376 * Restore the notebook to a checkpoint state.
2376 * Restore the notebook to a checkpoint state.
2377 *
2377 *
2378 * @param {string} checkpoint ID
2378 * @param {string} checkpoint ID
2379 */
2379 */
2380 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2380 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2381 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2381 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2382 var that = this;
2382 var that = this;
2383 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2383 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2384 $.proxy(this.restore_checkpoint_success, this),
2384 $.proxy(this.restore_checkpoint_success, this),
2385 function (error) {
2385 function (error) {
2386 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2386 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2387 }
2387 }
2388 );
2388 );
2389 };
2389 };
2390
2390
2391 /**
2391 /**
2392 * Success callback for restoring a notebook to a checkpoint.
2392 * Success callback for restoring a notebook to a checkpoint.
2393 */
2393 */
2394 Notebook.prototype.restore_checkpoint_success = function () {
2394 Notebook.prototype.restore_checkpoint_success = function () {
2395 this.events.trigger('checkpoint_restored.Notebook');
2395 this.events.trigger('checkpoint_restored.Notebook');
2396 this.load_notebook(this.notebook_path);
2396 this.load_notebook(this.notebook_path);
2397 };
2397 };
2398
2398
2399 /**
2399 /**
2400 * Delete a notebook checkpoint.
2400 * Delete a notebook checkpoint.
2401 *
2401 *
2402 * @param {string} checkpoint ID
2402 * @param {string} checkpoint ID
2403 */
2403 */
2404 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2404 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2405 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2405 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2406 var that = this;
2406 var that = this;
2407 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2407 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2408 $.proxy(this.delete_checkpoint_success, this),
2408 $.proxy(this.delete_checkpoint_success, this),
2409 function (error) {
2409 function (error) {
2410 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2410 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2411 }
2411 }
2412 );
2412 );
2413 };
2413 };
2414
2414
2415 /**
2415 /**
2416 * Success callback for deleting a notebook checkpoint.
2416 * Success callback for deleting a notebook checkpoint.
2417 */
2417 */
2418 Notebook.prototype.delete_checkpoint_success = function () {
2418 Notebook.prototype.delete_checkpoint_success = function () {
2419 this.events.trigger('checkpoint_deleted.Notebook');
2419 this.events.trigger('checkpoint_deleted.Notebook');
2420 this.load_notebook(this.notebook_path);
2420 this.load_notebook(this.notebook_path);
2421 };
2421 };
2422
2422
2423
2423
2424 // For backwards compatability.
2424 // For backwards compatability.
2425 IPython.Notebook = Notebook;
2425 IPython.Notebook = Notebook;
2426
2426
2427 return {'Notebook': Notebook};
2427 return {'Notebook': Notebook};
2428 });
2428 });
@@ -1,375 +1,374 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 'notebook/js/cell',
8 'notebook/js/cell',
9 'base/js/security',
9 'base/js/security',
10 'services/config',
10 'services/config',
11 'notebook/js/mathjaxutils',
11 'notebook/js/mathjaxutils',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'codemirror/lib/codemirror',
14 'codemirror/lib/codemirror',
15 'codemirror/mode/gfm/gfm',
15 'codemirror/mode/gfm/gfm',
16 'notebook/js/codemirror-ipythongfm'
16 'notebook/js/codemirror-ipythongfm'
17 ], function(IPython,
17 ], function(IPython,
18 utils,
18 utils,
19 $,
19 $,
20 cell,
20 cell,
21 security,
21 security,
22 configmod,
22 configmod,
23 mathjaxutils,
23 mathjaxutils,
24 celltoolbar,
24 celltoolbar,
25 marked,
25 marked,
26 CodeMirror,
26 CodeMirror,
27 gfm,
27 gfm,
28 ipgfm
28 ipgfm
29 ) {
29 ) {
30 "use strict";
30 "use strict";
31 var Cell = cell.Cell;
31 var Cell = cell.Cell;
32
32
33 var TextCell = function (options) {
33 var TextCell = function (options) {
34 /**
34 /**
35 * Constructor
35 * Constructor
36 *
36 *
37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
38 * and cell type is 'text' cell start as not redered.
38 * and cell type is 'text' cell start as not redered.
39 *
39 *
40 * Parameters:
40 * Parameters:
41 * options: dictionary
41 * options: dictionary
42 * Dictionary of keyword arguments.
42 * Dictionary of keyword arguments.
43 * events: $(Events) instance
43 * events: $(Events) instance
44 * config: dictionary
44 * config: dictionary
45 * keyboard_manager: KeyboardManager instance
45 * keyboard_manager: KeyboardManager instance
46 * notebook: Notebook instance
46 * notebook: Notebook instance
47 */
47 */
48 options = options || {};
48 options = options || {};
49
49
50 // in all TextCell/Cell subclasses
50 // in all TextCell/Cell subclasses
51 // do not assign most of members here, just pass it down
51 // do not assign most of members here, just pass it down
52 // in the options dict potentially overwriting what you wish.
52 // in the options dict potentially overwriting what you wish.
53 // they will be assigned in the base class.
53 // they will be assigned in the base class.
54 this.notebook = options.notebook;
54 this.notebook = options.notebook;
55 this.events = options.events;
55 this.events = options.events;
56 this.config = options.config;
56 this.config = options.config;
57
57
58 // we cannot put this as a class key as it has handle to "this".
58 // we cannot put this as a class key as it has handle to "this".
59 var config = utils.mergeopt(TextCell, this.config);
59 var config = utils.mergeopt(TextCell, this.config);
60 Cell.apply(this, [{
60 Cell.apply(this, [{
61 config: config,
61 config: config,
62 keyboard_manager: options.keyboard_manager,
62 keyboard_manager: options.keyboard_manager,
63 events: this.events}]);
63 events: this.events}]);
64
64
65 this.cell_type = this.cell_type || 'text';
65 this.cell_type = this.cell_type || 'text';
66 mathjaxutils = mathjaxutils;
66 mathjaxutils = mathjaxutils;
67 this.rendered = false;
67 this.rendered = false;
68 };
68 };
69
69
70 TextCell.prototype = Object.create(Cell.prototype);
70 TextCell.prototype = Object.create(Cell.prototype);
71
71
72 TextCell.options_default = {
72 TextCell.options_default = {
73 cm_config : {
73 cm_config : {
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
75 mode: 'htmlmixed',
75 mode: 'htmlmixed',
76 lineWrapping : true,
76 lineWrapping : true,
77 }
77 }
78 };
78 };
79
79
80
80
81 /**
81 /**
82 * Create the DOM element of the TextCell
82 * Create the DOM element of the TextCell
83 * @method create_element
83 * @method create_element
84 * @private
84 * @private
85 */
85 */
86 TextCell.prototype.create_element = function () {
86 TextCell.prototype.create_element = function () {
87 Cell.prototype.create_element.apply(this, arguments);
87 Cell.prototype.create_element.apply(this, arguments);
88
88
89 var cell = $("<div>").addClass('cell text_cell');
89 var cell = $("<div>").addClass('cell text_cell');
90 cell.attr('tabindex','2');
90 cell.attr('tabindex','2');
91
91
92 var prompt = $('<div/>').addClass('prompt input_prompt');
92 var prompt = $('<div/>').addClass('prompt input_prompt');
93 cell.append(prompt);
93 cell.append(prompt);
94 var inner_cell = $('<div/>').addClass('inner_cell');
94 var inner_cell = $('<div/>').addClass('inner_cell');
95 this.celltoolbar = new celltoolbar.CellToolbar({
95 this.celltoolbar = new celltoolbar.CellToolbar({
96 cell: this,
96 cell: this,
97 notebook: this.notebook});
97 notebook: this.notebook});
98 inner_cell.append(this.celltoolbar.element);
98 inner_cell.append(this.celltoolbar.element);
99 var input_area = $('<div/>').addClass('input_area');
99 var input_area = $('<div/>').addClass('input_area');
100 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
100 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
101 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
101 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
102 // The tabindex=-1 makes this div focusable.
102 // The tabindex=-1 makes this div focusable.
103 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
103 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
104 .attr('tabindex','-1');
104 .attr('tabindex','-1');
105 inner_cell.append(input_area).append(render_area);
105 inner_cell.append(input_area).append(render_area);
106 cell.append(inner_cell);
106 cell.append(inner_cell);
107 this.element = cell;
107 this.element = cell;
108 };
108 };
109
109
110
110
111 // Cell level actions
111 // Cell level actions
112
112
113 TextCell.prototype.select = function () {
113 TextCell.prototype.select = function () {
114 var cont = Cell.prototype.select.apply(this);
114 var cont = Cell.prototype.select.apply(this);
115 if (cont) {
115 if (cont) {
116 if (this.mode === 'edit') {
116 if (this.mode === 'edit') {
117 this.code_mirror.refresh();
117 this.code_mirror.refresh();
118 }
118 }
119 }
119 }
120 return cont;
120 return cont;
121 };
121 };
122
122
123 TextCell.prototype.unrender = function () {
123 TextCell.prototype.unrender = function () {
124 if (this.read_only) return;
124 if (this.read_only) return;
125 var cont = Cell.prototype.unrender.apply(this);
125 var cont = Cell.prototype.unrender.apply(this);
126 if (cont) {
126 if (cont) {
127 var text_cell = this.element;
127 var text_cell = this.element;
128 var output = text_cell.find("div.text_cell_render");
129 if (this.get_text() === this.placeholder) {
128 if (this.get_text() === this.placeholder) {
130 this.set_text('');
129 this.set_text('');
131 }
130 }
132 this.refresh();
131 this.refresh();
133 }
132 }
134 return cont;
133 return cont;
135 };
134 };
136
135
137 TextCell.prototype.execute = function () {
136 TextCell.prototype.execute = function () {
138 this.render();
137 this.render();
139 };
138 };
140
139
141 /**
140 /**
142 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
141 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
143 * @method get_text
142 * @method get_text
144 * @retrun {string} CodeMirror current text value
143 * @retrun {string} CodeMirror current text value
145 */
144 */
146 TextCell.prototype.get_text = function() {
145 TextCell.prototype.get_text = function() {
147 return this.code_mirror.getValue();
146 return this.code_mirror.getValue();
148 };
147 };
149
148
150 /**
149 /**
151 * @param {string} text - Codemiror text value
150 * @param {string} text - Codemiror text value
152 * @see TextCell#get_text
151 * @see TextCell#get_text
153 * @method set_text
152 * @method set_text
154 * */
153 * */
155 TextCell.prototype.set_text = function(text) {
154 TextCell.prototype.set_text = function(text) {
156 this.code_mirror.setValue(text);
155 this.code_mirror.setValue(text);
157 this.unrender();
156 this.unrender();
158 this.code_mirror.refresh();
157 this.code_mirror.refresh();
159 };
158 };
160
159
161 /**
160 /**
162 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
161 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
163 * @method get_rendered
162 * @method get_rendered
164 * */
163 * */
165 TextCell.prototype.get_rendered = function() {
164 TextCell.prototype.get_rendered = function() {
166 return this.element.find('div.text_cell_render').html();
165 return this.element.find('div.text_cell_render').html();
167 };
166 };
168
167
169 /**
168 /**
170 * @method set_rendered
169 * @method set_rendered
171 */
170 */
172 TextCell.prototype.set_rendered = function(text) {
171 TextCell.prototype.set_rendered = function(text) {
173 this.element.find('div.text_cell_render').html(text);
172 this.element.find('div.text_cell_render').html(text);
174 };
173 };
175
174
176
175
177 /**
176 /**
178 * Create Text cell from JSON
177 * Create Text cell from JSON
179 * @param {json} data - JSON serialized text-cell
178 * @param {json} data - JSON serialized text-cell
180 * @method fromJSON
179 * @method fromJSON
181 */
180 */
182 TextCell.prototype.fromJSON = function (data) {
181 TextCell.prototype.fromJSON = function (data) {
183 Cell.prototype.fromJSON.apply(this, arguments);
182 Cell.prototype.fromJSON.apply(this, arguments);
184 if (data.cell_type === this.cell_type) {
183 if (data.cell_type === this.cell_type) {
185 if (data.source !== undefined) {
184 if (data.source !== undefined) {
186 this.set_text(data.source);
185 this.set_text(data.source);
187 // make this value the starting point, so that we can only undo
186 // make this value the starting point, so that we can only undo
188 // to this state, instead of a blank cell
187 // to this state, instead of a blank cell
189 this.code_mirror.clearHistory();
188 this.code_mirror.clearHistory();
190 // TODO: This HTML needs to be treated as potentially dangerous
189 // TODO: This HTML needs to be treated as potentially dangerous
191 // user input and should be handled before set_rendered.
190 // user input and should be handled before set_rendered.
192 this.set_rendered(data.rendered || '');
191 this.set_rendered(data.rendered || '');
193 this.rendered = false;
192 this.rendered = false;
194 this.render();
193 this.render();
195 }
194 }
196 }
195 }
197 };
196 };
198
197
199 /** Generate JSON from cell
198 /** Generate JSON from cell
200 * @return {object} cell data serialised to json
199 * @return {object} cell data serialised to json
201 */
200 */
202 TextCell.prototype.toJSON = function () {
201 TextCell.prototype.toJSON = function () {
203 var data = Cell.prototype.toJSON.apply(this);
202 var data = Cell.prototype.toJSON.apply(this);
204 data.source = this.get_text();
203 data.source = this.get_text();
205 if (data.source == this.placeholder) {
204 if (data.source == this.placeholder) {
206 data.source = "";
205 data.source = "";
207 }
206 }
208 return data;
207 return data;
209 };
208 };
210
209
211
210
212 var MarkdownCell = function (options) {
211 var MarkdownCell = function (options) {
213 /**
212 /**
214 * Constructor
213 * Constructor
215 *
214 *
216 * Parameters:
215 * Parameters:
217 * options: dictionary
216 * options: dictionary
218 * Dictionary of keyword arguments.
217 * Dictionary of keyword arguments.
219 * events: $(Events) instance
218 * events: $(Events) instance
220 * config: ConfigSection instance
219 * config: ConfigSection instance
221 * keyboard_manager: KeyboardManager instance
220 * keyboard_manager: KeyboardManager instance
222 * notebook: Notebook instance
221 * notebook: Notebook instance
223 */
222 */
224 options = options || {};
223 options = options || {};
225 var config = utils.mergeopt(MarkdownCell, {});
224 var config = utils.mergeopt(MarkdownCell, {});
226 TextCell.apply(this, [$.extend({}, options, {config: config})]);
225 TextCell.apply(this, [$.extend({}, options, {config: config})]);
227
226
228 this.class_config = new configmod.ConfigWithDefaults(options.config,
227 this.class_config = new configmod.ConfigWithDefaults(options.config,
229 {}, 'MarkdownCell');
228 {}, 'MarkdownCell');
230 this.cell_type = 'markdown';
229 this.cell_type = 'markdown';
231 };
230 };
232
231
233 MarkdownCell.options_default = {
232 MarkdownCell.options_default = {
234 cm_config: {
233 cm_config: {
235 mode: 'ipythongfm'
234 mode: 'ipythongfm'
236 },
235 },
237 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
236 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
238 };
237 };
239
238
240 MarkdownCell.prototype = Object.create(TextCell.prototype);
239 MarkdownCell.prototype = Object.create(TextCell.prototype);
241
240
242 MarkdownCell.prototype.set_heading_level = function (level) {
241 MarkdownCell.prototype.set_heading_level = function (level) {
243 /**
242 /**
244 * make a markdown cell a heading
243 * make a markdown cell a heading
245 */
244 */
246 level = level || 1;
245 level = level || 1;
247 var source = this.get_text();
246 var source = this.get_text();
248 source = source.replace(/^(#*)\s?/,
247 source = source.replace(/^(#*)\s?/,
249 new Array(level + 1).join('#') + ' ');
248 new Array(level + 1).join('#') + ' ');
250 this.set_text(source);
249 this.set_text(source);
251 this.refresh();
250 this.refresh();
252 if (this.rendered) {
251 if (this.rendered) {
253 this.render();
252 this.render();
254 }
253 }
255 };
254 };
256
255
257 /**
256 /**
258 * @method render
257 * @method render
259 */
258 */
260 MarkdownCell.prototype.render = function () {
259 MarkdownCell.prototype.render = function () {
261 var cont = TextCell.prototype.render.apply(this);
260 var cont = TextCell.prototype.render.apply(this);
262 if (cont) {
261 if (cont) {
263 var that = this;
262 var that = this;
264 var text = this.get_text();
263 var text = this.get_text();
265 var math = null;
264 var math = null;
266 if (text === "") { text = this.placeholder; }
265 if (text === "") { text = this.placeholder; }
267 var text_and_math = mathjaxutils.remove_math(text);
266 var text_and_math = mathjaxutils.remove_math(text);
268 text = text_and_math[0];
267 text = text_and_math[0];
269 math = text_and_math[1];
268 math = text_and_math[1];
270 marked(text, function (err, html) {
269 marked(text, function (err, html) {
271 html = mathjaxutils.replace_math(html, math);
270 html = mathjaxutils.replace_math(html, math);
272 html = security.sanitize_html(html);
271 html = security.sanitize_html(html);
273 html = $($.parseHTML(html));
272 html = $($.parseHTML(html));
274 // add anchors to headings
273 // add anchors to headings
275 html.find(":header").addBack(":header").each(function (i, h) {
274 html.find(":header").addBack(":header").each(function (i, h) {
276 h = $(h);
275 h = $(h);
277 var hash = h.text().replace(/ /g, '-');
276 var hash = h.text().replace(/ /g, '-');
278 h.attr('id', hash);
277 h.attr('id', hash);
279 h.append(
278 h.append(
280 $('<a/>')
279 $('<a/>')
281 .addClass('anchor-link')
280 .addClass('anchor-link')
282 .attr('href', '#' + hash)
281 .attr('href', '#' + hash)
283 .text('ΒΆ')
282 .text('ΒΆ')
284 );
283 );
285 });
284 });
286 // links in markdown cells should open in new tabs
285 // links in markdown cells should open in new tabs
287 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
286 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
288 that.set_rendered(html);
287 that.set_rendered(html);
289 that.typeset();
288 that.typeset();
290 that.events.trigger("rendered.MarkdownCell", {cell: that});
289 that.events.trigger("rendered.MarkdownCell", {cell: that});
291 });
290 });
292 }
291 }
293 return cont;
292 return cont;
294 };
293 };
295
294
296
295
297 var RawCell = function (options) {
296 var RawCell = function (options) {
298 /**
297 /**
299 * Constructor
298 * Constructor
300 *
299 *
301 * Parameters:
300 * Parameters:
302 * options: dictionary
301 * options: dictionary
303 * Dictionary of keyword arguments.
302 * Dictionary of keyword arguments.
304 * events: $(Events) instance
303 * events: $(Events) instance
305 * config: ConfigSection instance
304 * config: ConfigSection instance
306 * keyboard_manager: KeyboardManager instance
305 * keyboard_manager: KeyboardManager instance
307 * notebook: Notebook instance
306 * notebook: Notebook instance
308 */
307 */
309 options = options || {};
308 options = options || {};
310 var config = utils.mergeopt(RawCell, {});
309 var config = utils.mergeopt(RawCell, {});
311 TextCell.apply(this, [$.extend({}, options, {config: config})]);
310 TextCell.apply(this, [$.extend({}, options, {config: config})]);
312
311
313 this.class_config = new configmod.ConfigWithDefaults(options.config,
312 this.class_config = new configmod.ConfigWithDefaults(options.config,
314 RawCell.config_defaults, 'RawCell');
313 RawCell.config_defaults, 'RawCell');
315 this.cell_type = 'raw';
314 this.cell_type = 'raw';
316 };
315 };
317
316
318 RawCell.options_default = {
317 RawCell.options_default = {
319 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
318 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
320 "It will not be rendered in the notebook. " +
319 "It will not be rendered in the notebook. " +
321 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
320 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
322 };
321 };
323
322
324 RawCell.config_defaults = {
323 RawCell.config_defaults = {
325 highlight_modes : {
324 highlight_modes : {
326 'diff' :{'reg':[/^diff/]}
325 'diff' :{'reg':[/^diff/]}
327 },
326 },
328 };
327 };
329
328
330 RawCell.prototype = Object.create(TextCell.prototype);
329 RawCell.prototype = Object.create(TextCell.prototype);
331
330
332 /** @method bind_events **/
331 /** @method bind_events **/
333 RawCell.prototype.bind_events = function () {
332 RawCell.prototype.bind_events = function () {
334 TextCell.prototype.bind_events.apply(this);
333 TextCell.prototype.bind_events.apply(this);
335 var that = this;
334 var that = this;
336 this.element.focusout(function() {
335 this.element.focusout(function() {
337 that.auto_highlight();
336 that.auto_highlight();
338 that.render();
337 that.render();
339 });
338 });
340
339
341 this.code_mirror.on('focus', function() { that.unrender(); });
340 this.code_mirror.on('focus', function() { that.unrender(); });
342 };
341 };
343
342
344 /**
343 /**
345 * Trigger autodetection of highlight scheme for current cell
344 * Trigger autodetection of highlight scheme for current cell
346 * @method auto_highlight
345 * @method auto_highlight
347 */
346 */
348 RawCell.prototype.auto_highlight = function () {
347 RawCell.prototype.auto_highlight = function () {
349 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
348 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
350 };
349 };
351
350
352 /** @method render **/
351 /** @method render **/
353 RawCell.prototype.render = function () {
352 RawCell.prototype.render = function () {
354 var cont = TextCell.prototype.render.apply(this);
353 var cont = TextCell.prototype.render.apply(this);
355 if (cont){
354 if (cont){
356 var text = this.get_text();
355 var text = this.get_text();
357 if (text === "") { text = this.placeholder; }
356 if (text === "") { text = this.placeholder; }
358 this.set_text(text);
357 this.set_text(text);
359 this.element.removeClass('rendered');
358 this.element.removeClass('rendered');
360 }
359 }
361 return cont;
360 return cont;
362 };
361 };
363
362
364 // Backwards compatability.
363 // Backwards compatability.
365 IPython.TextCell = TextCell;
364 IPython.TextCell = TextCell;
366 IPython.MarkdownCell = MarkdownCell;
365 IPython.MarkdownCell = MarkdownCell;
367 IPython.RawCell = RawCell;
366 IPython.RawCell = RawCell;
368
367
369 var textcell = {
368 var textcell = {
370 TextCell: TextCell,
369 TextCell: TextCell,
371 MarkdownCell: MarkdownCell,
370 MarkdownCell: MarkdownCell,
372 RawCell: RawCell
371 RawCell: RawCell
373 };
372 };
374 return textcell;
373 return textcell;
375 });
374 });
@@ -1,53 +1,54 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'jquery',
5 'jquery',
6 'termjs',
6 'termjs',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/page',
8 'base/js/page',
9 'terminal/js/terminado',
9 'terminal/js/terminado',
10 'custom/custom',
10 'custom/custom',
11 ], function(
11 ], function(
12 $,
12 $,
13 termjs,
13 termjs,
14 utils,
14 utils,
15 page,
15 page,
16 terminado
16 terminado
17 ){
17 ){
18 "use strict";
18 page = new page.Page();
19 page = new page.Page();
19 // Test size: 25x80
20 // Test size: 25x80
20 var termRowHeight = function(){ return 1.00 * $("#dummy-screen")[0].offsetHeight / 25;};
21 var termRowHeight = function(){ return 1.00 * $("#dummy-screen")[0].offsetHeight / 25;};
21 // 1.02 here arrived at by trial and error to make the spacing look right
22 // 1.02 here arrived at by trial and error to make the spacing look right
22 var termColWidth = function() { return 1.02 * $("#dummy-screen-rows")[0].offsetWidth / 80;};
23 var termColWidth = function() { return 1.02 * $("#dummy-screen-rows")[0].offsetWidth / 80;};
23
24
24 var base_url = utils.get_body_data('baseUrl');
25 var base_url = utils.get_body_data('baseUrl');
25 var ws_path = utils.get_body_data('wsPath');
26 var ws_path = utils.get_body_data('wsPath');
26 var ws_url = location.protocol.replace('http', 'ws') + "//" + location.host
27 var ws_url = location.protocol.replace('http', 'ws') + "//" + location.host
27 + base_url + ws_path;
28 + base_url + ws_path;
28
29
29 var header = $("#header")[0]
30 var header = $("#header")[0]
30 function calculate_size() {
31 function calculate_size() {
31 height = window.innerHeight - header.offsetHeight;
32 var height = window.innerHeight - header.offsetHeight;
32 width = $('#terminado-container').width();
33 var width = $('#terminado-container').width();
33 var rows = Math.min(1000, Math.max(20, Math.floor(height/termRowHeight())-1));
34 var rows = Math.min(1000, Math.max(20, Math.floor(height/termRowHeight())-1));
34 var cols = Math.min(1000, Math.max(40, Math.floor(width/termColWidth())-1));
35 var cols = Math.min(1000, Math.max(40, Math.floor(width/termColWidth())-1));
35 console.log("resize to :", rows , 'rows by ', cols, 'columns');
36 console.log("resize to :", rows , 'rows by ', cols, 'columns');
36 return {rows: rows, cols: cols};
37 return {rows: rows, cols: cols};
37 }
38 }
38
39
39 page.show_header();
40 page.show_header();
40
41
41 size = calculate_size();
42 var size = calculate_size();
42 var terminal = terminado.make_terminal($("#terminado-container")[0], size, ws_url);
43 var terminal = terminado.make_terminal($("#terminado-container")[0], size, ws_url);
43
44
44 page.show_site();
45 page.show_site();
45
46
46 window.onresize = function() {
47 window.onresize = function() {
47 var geom = calculate_size();
48 var geom = calculate_size();
48 terminal.term.resize(geom.cols, geom.rows);
49 terminal.term.resize(geom.cols, geom.rows);
49 terminal.socket.send(JSON.stringify(["set_size", geom.rows, geom.cols,
50 terminal.socket.send(JSON.stringify(["set_size", geom.rows, geom.cols,
50 window.innerHeight, window.innerWidth]));
51 window.innerHeight, window.innerWidth]));
51 };
52 };
52
53
53 });
54 });
@@ -1,39 +1,40 b''
1 define ([], function() {
1 define ([], function() {
2 "use strict";
2 function make_terminal(element, size, ws_url) {
3 function make_terminal(element, size, ws_url) {
3 var ws = new WebSocket(ws_url);
4 var ws = new WebSocket(ws_url);
4 var term = new Terminal({
5 var term = new Terminal({
5 cols: size.cols,
6 cols: size.cols,
6 rows: size.rows,
7 rows: size.rows,
7 screenKeys: true,
8 screenKeys: true,
8 useStyle: false
9 useStyle: false
9 });
10 });
10 ws.onopen = function(event) {
11 ws.onopen = function(event) {
11 ws.send(JSON.stringify(["set_size", size.rows, size.cols,
12 ws.send(JSON.stringify(["set_size", size.rows, size.cols,
12 window.innerHeight, window.innerWidth]));
13 window.innerHeight, window.innerWidth]));
13 term.on('data', function(data) {
14 term.on('data', function(data) {
14 ws.send(JSON.stringify(['stdin', data]));
15 ws.send(JSON.stringify(['stdin', data]));
15 });
16 });
16
17
17 term.on('title', function(title) {
18 term.on('title', function(title) {
18 document.title = title;
19 document.title = title;
19 });
20 });
20
21
21 term.open(element);
22 term.open(element);
22
23
23 ws.onmessage = function(event) {
24 ws.onmessage = function(event) {
24 json_msg = JSON.parse(event.data);
25 var json_msg = JSON.parse(event.data);
25 switch(json_msg[0]) {
26 switch(json_msg[0]) {
26 case "stdout":
27 case "stdout":
27 term.write(json_msg[1]);
28 term.write(json_msg[1]);
28 break;
29 break;
29 case "disconnect":
30 case "disconnect":
30 term.write("\r\n\r\n[CLOSED]\r\n");
31 term.write("\r\n\r\n[CLOSED]\r\n");
31 break;
32 break;
32 }
33 }
33 };
34 };
34 };
35 };
35 return {socket: ws, term: term};
36 return {socket: ws, term: term};
36 }
37 }
37
38
38 return {make_terminal: make_terminal};
39 return {make_terminal: make_terminal};
39 });
40 });
@@ -1,122 +1,121 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 'tree/js/notebooklist',
8 'tree/js/notebooklist',
9 ], function(IPython, utils, $, notebooklist) {
9 ], function(IPython, utils, $, notebooklist) {
10 "use strict";
10 "use strict";
11
11
12 var TerminalList = function (selector, options) {
12 var TerminalList = function (selector, options) {
13 /**
13 /**
14 * Constructor
14 * Constructor
15 *
15 *
16 * Parameters:
16 * Parameters:
17 * selector: string
17 * selector: string
18 * options: dictionary
18 * options: dictionary
19 * Dictionary of keyword arguments.
19 * Dictionary of keyword arguments.
20 * base_url: string
20 * base_url: string
21 */
21 */
22 this.base_url = options.base_url || utils.get_body_data("baseUrl");
22 this.base_url = options.base_url || utils.get_body_data("baseUrl");
23 this.element_name = options.element_name || 'terminal';
23 this.element_name = options.element_name || 'terminal';
24 this.selector = selector;
24 this.selector = selector;
25 this.terminals = [];
25 this.terminals = [];
26 if (this.selector !== undefined) {
26 if (this.selector !== undefined) {
27 this.element = $(selector);
27 this.element = $(selector);
28 this.style();
28 this.style();
29 this.bind_events();
29 this.bind_events();
30 this.load_terminals();
30 this.load_terminals();
31 }
31 }
32 };
32 };
33
33
34 TerminalList.prototype = Object.create(notebooklist.NotebookList.prototype);
34 TerminalList.prototype = Object.create(notebooklist.NotebookList.prototype);
35
35
36 TerminalList.prototype.bind_events = function () {
36 TerminalList.prototype.bind_events = function () {
37 var that = this;
37 var that = this;
38 $('#refresh_' + this.element_name + '_list').click(function () {
38 $('#refresh_' + this.element_name + '_list').click(function () {
39 that.load_terminals();
39 that.load_terminals();
40 });
40 });
41 $('#new_terminal').click($.proxy(this.new_terminal, this));
41 $('#new_terminal').click($.proxy(this.new_terminal, this));
42 };
42 };
43
43
44 TerminalList.prototype.new_terminal = function () {
44 TerminalList.prototype.new_terminal = function () {
45 var w = window.open();
45 var w = window.open();
46 var base_url = this.base_url;
46 var base_url = this.base_url;
47 var settings = {
47 var settings = {
48 type : "POST",
48 type : "POST",
49 dataType: "json",
49 dataType: "json",
50 success : function (data, status, xhr) {
50 success : function (data, status, xhr) {
51 var name = data.name;
51 var name = data.name;
52 w.location = utils.url_join_encode(base_url, 'terminals', name);
52 w.location = utils.url_join_encode(base_url, 'terminals', name);
53 },
53 },
54 error : function(jqXHR, status, error){
54 error : function(jqXHR, status, error){
55 w.close();
55 w.close();
56 utils.log_ajax_error(jqXHR, status, error);
56 utils.log_ajax_error(jqXHR, status, error);
57 },
57 },
58 };
58 };
59 var url = utils.url_join_encode(
59 var url = utils.url_join_encode(
60 this.base_url,
60 this.base_url,
61 'api/terminals'
61 'api/terminals'
62 );
62 );
63 $.ajax(url, settings);
63 $.ajax(url, settings);
64 };
64 };
65
65
66 TerminalList.prototype.load_terminals = function() {
66 TerminalList.prototype.load_terminals = function() {
67 var that = this;
68 var url = utils.url_join_encode(this.base_url, 'api/terminals');
67 var url = utils.url_join_encode(this.base_url, 'api/terminals');
69 $.ajax(url, {
68 $.ajax(url, {
70 type: "GET",
69 type: "GET",
71 cache: false,
70 cache: false,
72 dataType: "json",
71 dataType: "json",
73 success: $.proxy(this.terminals_loaded, this),
72 success: $.proxy(this.terminals_loaded, this),
74 error : utils.log_ajax_error
73 error : utils.log_ajax_error
75 });
74 });
76 };
75 };
77
76
78 TerminalList.prototype.terminals_loaded = function (data) {
77 TerminalList.prototype.terminals_loaded = function (data) {
79 this.terminals = data;
78 this.terminals = data;
80 this.clear_list();
79 this.clear_list();
81 var item, path_name, term;
80 var item, term;
82 for (var i=0; i < this.terminals.length; i++) {
81 for (var i=0; i < this.terminals.length; i++) {
83 term = this.terminals[i];
82 term = this.terminals[i];
84 item = this.new_item(-1);
83 item = this.new_item(-1);
85 this.add_link(term.name, item);
84 this.add_link(term.name, item);
86 this.add_shutdown_button(term.name, item);
85 this.add_shutdown_button(term.name, item);
87 }
86 }
88 $('#terminal_list_header').toggle(data.length === 0);
87 $('#terminal_list_header').toggle(data.length === 0);
89 };
88 };
90
89
91 TerminalList.prototype.add_link = function(name, item) {
90 TerminalList.prototype.add_link = function(name, item) {
92 item.data('term-name', name);
91 item.data('term-name', name);
93 item.find(".item_name").text("terminals/" + name);
92 item.find(".item_name").text("terminals/" + name);
94 item.find(".item_icon").addClass("fa fa-terminal");
93 item.find(".item_icon").addClass("fa fa-terminal");
95 var link = item.find("a.item_link")
94 var link = item.find("a.item_link")
96 .attr('href', utils.url_join_encode(this.base_url, "terminals", name));
95 .attr('href', utils.url_join_encode(this.base_url, "terminals", name));
97 link.attr('target', '_blank');
96 link.attr('target', '_blank');
98 this.add_shutdown_button(name, item);
97 this.add_shutdown_button(name, item);
99 };
98 };
100
99
101 TerminalList.prototype.add_shutdown_button = function(name, item) {
100 TerminalList.prototype.add_shutdown_button = function(name, item) {
102 var that = this;
101 var that = this;
103 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-warning").
102 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-warning").
104 click(function (e) {
103 click(function (e) {
105 var settings = {
104 var settings = {
106 processData : false,
105 processData : false,
107 type : "DELETE",
106 type : "DELETE",
108 dataType : "json",
107 dataType : "json",
109 success : function () {
108 success : function () {
110 that.load_terminals();
109 that.load_terminals();
111 },
110 },
112 error : utils.log_ajax_error,
111 error : utils.log_ajax_error,
113 };
112 };
114 var url = utils.url_join_encode(that.base_url, 'api/terminals', name);
113 var url = utils.url_join_encode(that.base_url, 'api/terminals', name);
115 $.ajax(url, settings);
114 $.ajax(url, settings);
116 return false;
115 return false;
117 });
116 });
118 item.find(".item_buttons").text("").append(shutdown_button);
117 item.find(".item_buttons").text("").append(shutdown_button);
119 };
118 };
120
119
121 return {TerminalList: TerminalList};
120 return {TerminalList: TerminalList};
122 });
121 });
@@ -1,762 +1,762 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define(["widgets/js/manager",
4 define(["widgets/js/manager",
5 "underscore",
5 "underscore",
6 "backbone",
6 "backbone",
7 "jquery",
7 "jquery",
8 "base/js/utils",
8 "base/js/utils",
9 "base/js/namespace",
9 "base/js/namespace",
10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
11 "use strict";
11
12
12 var WidgetModel = Backbone.Model.extend({
13 var WidgetModel = Backbone.Model.extend({
13 constructor: function (widget_manager, model_id, comm) {
14 constructor: function (widget_manager, model_id, comm) {
14 /**
15 /**
15 * Constructor
16 * Constructor
16 *
17 *
17 * Creates a WidgetModel instance.
18 * Creates a WidgetModel instance.
18 *
19 *
19 * Parameters
20 * Parameters
20 * ----------
21 * ----------
21 * widget_manager : WidgetManager instance
22 * widget_manager : WidgetManager instance
22 * model_id : string
23 * model_id : string
23 * An ID unique to this model.
24 * An ID unique to this model.
24 * comm : Comm instance (optional)
25 * comm : Comm instance (optional)
25 */
26 */
26 this.widget_manager = widget_manager;
27 this.widget_manager = widget_manager;
27 this.state_change = Promise.resolve();
28 this.state_change = Promise.resolve();
28 this._buffered_state_diff = {};
29 this._buffered_state_diff = {};
29 this.pending_msgs = 0;
30 this.pending_msgs = 0;
30 this.msg_buffer = null;
31 this.msg_buffer = null;
31 this.state_lock = null;
32 this.state_lock = null;
32 this.id = model_id;
33 this.id = model_id;
33 this.views = {};
34 this.views = {};
34 this._resolve_received_state = {};
35 this._resolve_received_state = {};
35
36
36 if (comm !== undefined) {
37 if (comm !== undefined) {
37 // Remember comm associated with the model.
38 // Remember comm associated with the model.
38 this.comm = comm;
39 this.comm = comm;
39 comm.model = this;
40 comm.model = this;
40
41
41 // Hook comm messages up to model.
42 // Hook comm messages up to model.
42 comm.on_close($.proxy(this._handle_comm_closed, this));
43 comm.on_close($.proxy(this._handle_comm_closed, this));
43 comm.on_msg($.proxy(this._handle_comm_msg, this));
44 comm.on_msg($.proxy(this._handle_comm_msg, this));
44
45
45 // Assume the comm is alive.
46 // Assume the comm is alive.
46 this.set_comm_live(true);
47 this.set_comm_live(true);
47 } else {
48 } else {
48 this.set_comm_live(false);
49 this.set_comm_live(false);
49 }
50 }
50
51
51 // Listen for the events that lead to the websocket being terminated.
52 // Listen for the events that lead to the websocket being terminated.
52 var that = this;
53 var that = this;
53 var died = function() {
54 var died = function() {
54 that.set_comm_live(false);
55 that.set_comm_live(false);
55 };
56 };
56 widget_manager.notebook.events.on('kernel_disconnected.Kernel', died);
57 widget_manager.notebook.events.on('kernel_disconnected.Kernel', died);
57 widget_manager.notebook.events.on('kernel_killed.Kernel', died);
58 widget_manager.notebook.events.on('kernel_killed.Kernel', died);
58 widget_manager.notebook.events.on('kernel_restarting.Kernel', died);
59 widget_manager.notebook.events.on('kernel_restarting.Kernel', died);
59 widget_manager.notebook.events.on('kernel_dead.Kernel', died);
60 widget_manager.notebook.events.on('kernel_dead.Kernel', died);
60
61
61 return Backbone.Model.apply(this);
62 return Backbone.Model.apply(this);
62 },
63 },
63
64
64 send: function (content, callbacks) {
65 send: function (content, callbacks) {
65 /**
66 /**
66 * Send a custom msg over the comm.
67 * Send a custom msg over the comm.
67 */
68 */
68 if (this.comm !== undefined) {
69 if (this.comm !== undefined) {
69 var data = {method: 'custom', content: content};
70 var data = {method: 'custom', content: content};
70 this.comm.send(data, callbacks);
71 this.comm.send(data, callbacks);
71 this.pending_msgs++;
72 this.pending_msgs++;
72 }
73 }
73 },
74 },
74
75
75 request_state: function(callbacks) {
76 request_state: function(callbacks) {
76 /**
77 /**
77 * Request a state push from the back-end.
78 * Request a state push from the back-end.
78 */
79 */
79 if (!this.comm) {
80 if (!this.comm) {
80 console.error("Could not request_state because comm doesn't exist!");
81 console.error("Could not request_state because comm doesn't exist!");
81 return;
82 return;
82 }
83 }
83
84
84 var msg_id = this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks());
85 var msg_id = this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks());
85
86
86 // Promise that is resolved when a state is received
87 // Promise that is resolved when a state is received
87 // from the back-end.
88 // from the back-end.
88 var that = this;
89 var that = this;
89 var received_state = new Promise(function(resolve) {
90 var received_state = new Promise(function(resolve) {
90 that._resolve_received_state[msg_id] = resolve;
91 that._resolve_received_state[msg_id] = resolve;
91 });
92 });
92 return received_state;
93 return received_state;
93 },
94 },
94
95
95 set_comm_live: function(live) {
96 set_comm_live: function(live) {
96 /**
97 /**
97 * Change the comm_live state of the model.
98 * Change the comm_live state of the model.
98 */
99 */
99 if (this.comm_live === undefined || this.comm_live != live) {
100 if (this.comm_live === undefined || this.comm_live != live) {
100 this.comm_live = live;
101 this.comm_live = live;
101 this.trigger(live ? 'comm:live' : 'comm:dead', {model: this});
102 this.trigger(live ? 'comm:live' : 'comm:dead', {model: this});
102 }
103 }
103 },
104 },
104
105
105 close: function(comm_closed) {
106 close: function(comm_closed) {
106 /**
107 /**
107 * Close model
108 * Close model
108 */
109 */
109 if (this.comm && !comm_closed) {
110 if (this.comm && !comm_closed) {
110 this.comm.close();
111 this.comm.close();
111 }
112 }
112 this.stopListening();
113 this.stopListening();
113 this.trigger('destroy', this);
114 this.trigger('destroy', this);
114 delete this.comm.model; // Delete ref so GC will collect widget model.
115 delete this.comm.model; // Delete ref so GC will collect widget model.
115 delete this.comm;
116 delete this.comm;
116 delete this.model_id; // Delete id from model so widget manager cleans up.
117 delete this.model_id; // Delete id from model so widget manager cleans up.
117 _.each(this.views, function(v, id, views) {
118 _.each(this.views, function(v, id, views) {
118 v.then(function(view) {
119 v.then(function(view) {
119 view.remove();
120 view.remove();
120 delete views[id];
121 delete views[id];
121 });
122 });
122 });
123 });
123 },
124 },
124
125
125 _handle_comm_closed: function (msg) {
126 _handle_comm_closed: function (msg) {
126 /**
127 /**
127 * Handle when a widget is closed.
128 * Handle when a widget is closed.
128 */
129 */
129 this.trigger('comm:close');
130 this.trigger('comm:close');
130 this.close(true);
131 this.close(true);
131 },
132 },
132
133
133 _handle_comm_msg: function (msg) {
134 _handle_comm_msg: function (msg) {
134 /**
135 /**
135 * Handle incoming comm msg.
136 * Handle incoming comm msg.
136 */
137 */
137 var method = msg.content.data.method;
138 var method = msg.content.data.method;
138 var that = this;
139 var that = this;
139 switch (method) {
140 switch (method) {
140 case 'update':
141 case 'update':
141 this.state_change = this.state_change
142 this.state_change = this.state_change
142 .then(function() {
143 .then(function() {
143 return that.set_state(msg.content.data.state);
144 return that.set_state(msg.content.data.state);
144 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true))
145 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true))
145 .then(function() {
146 .then(function() {
146 var parent_id = msg.parent_header.msg_id;
147 var parent_id = msg.parent_header.msg_id;
147 if (that._resolve_received_state[parent_id] !== undefined) {
148 if (that._resolve_received_state[parent_id] !== undefined) {
148 that._resolve_received_state[parent_id].call();
149 that._resolve_received_state[parent_id].call();
149 delete that._resolve_received_state[parent_id];
150 delete that._resolve_received_state[parent_id];
150 }
151 }
151 }).catch(utils.reject("Couldn't resolve state request promise", true));
152 }).catch(utils.reject("Couldn't resolve state request promise", true));
152 break;
153 break;
153 case 'custom':
154 case 'custom':
154 this.trigger('msg:custom', msg.content.data.content);
155 this.trigger('msg:custom', msg.content.data.content);
155 break;
156 break;
156 case 'display':
157 case 'display':
157 this.widget_manager.display_view(msg, this)
158 this.widget_manager.display_view(msg, this)
158 .catch(utils.reject('Could not process display view msg', true));
159 .catch(utils.reject('Could not process display view msg', true));
159 break;
160 break;
160 }
161 }
161 },
162 },
162
163
163 set_state: function (state) {
164 set_state: function (state) {
164 var that = this;
165 var that = this;
165 // Handle when a widget is updated via the python side.
166 // Handle when a widget is updated via the python side.
166 return this._unpack_models(state).then(function(state) {
167 return this._unpack_models(state).then(function(state) {
167 that.state_lock = state;
168 that.state_lock = state;
168 try {
169 try {
169 WidgetModel.__super__.set.call(that, state);
170 WidgetModel.__super__.set.call(that, state);
170 } finally {
171 } finally {
171 that.state_lock = null;
172 that.state_lock = null;
172 }
173 }
173 }).catch(utils.reject("Couldn't set model state", true));
174 }).catch(utils.reject("Couldn't set model state", true));
174 },
175 },
175
176
176 get_state: function() {
177 get_state: function() {
177 // Get the serializable state of the model.
178 // Get the serializable state of the model.
178 state = this.toJSON();
179 var state = this.toJSON();
179 for (var key in state) {
180 for (var key in state) {
180 if (state.hasOwnProperty(key)) {
181 if (state.hasOwnProperty(key)) {
181 state[key] = this._pack_models(state[key]);
182 state[key] = this._pack_models(state[key]);
182 }
183 }
183 }
184 }
184 return state;
185 return state;
185 },
186 },
186
187
187 _handle_status: function (msg, callbacks) {
188 _handle_status: function (msg, callbacks) {
188 /**
189 /**
189 * Handle status msgs.
190 * Handle status msgs.
190 *
191 *
191 * execution_state : ('busy', 'idle', 'starting')
192 * execution_state : ('busy', 'idle', 'starting')
192 */
193 */
193 if (this.comm !== undefined) {
194 if (this.comm !== undefined) {
194 if (msg.content.execution_state ==='idle') {
195 if (msg.content.execution_state ==='idle') {
195 // Send buffer if this message caused another message to be
196 // Send buffer if this message caused another message to be
196 // throttled.
197 // throttled.
197 if (this.msg_buffer !== null &&
198 if (this.msg_buffer !== null &&
198 (this.get('msg_throttle') || 3) === this.pending_msgs) {
199 (this.get('msg_throttle') || 3) === this.pending_msgs) {
199 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
200 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
200 this.comm.send(data, callbacks);
201 this.comm.send(data, callbacks);
201 this.msg_buffer = null;
202 this.msg_buffer = null;
202 } else {
203 } else {
203 --this.pending_msgs;
204 --this.pending_msgs;
204 }
205 }
205 }
206 }
206 }
207 }
207 },
208 },
208
209
209 callbacks: function(view) {
210 callbacks: function(view) {
210 /**
211 /**
211 * Create msg callbacks for a comm msg.
212 * Create msg callbacks for a comm msg.
212 */
213 */
213 var callbacks = this.widget_manager.callbacks(view);
214 var callbacks = this.widget_manager.callbacks(view);
214
215
215 if (callbacks.iopub === undefined) {
216 if (callbacks.iopub === undefined) {
216 callbacks.iopub = {};
217 callbacks.iopub = {};
217 }
218 }
218
219
219 var that = this;
220 var that = this;
220 callbacks.iopub.status = function (msg) {
221 callbacks.iopub.status = function (msg) {
221 that._handle_status(msg, callbacks);
222 that._handle_status(msg, callbacks);
222 };
223 };
223 return callbacks;
224 return callbacks;
224 },
225 },
225
226
226 set: function(key, val, options) {
227 set: function(key, val, options) {
227 /**
228 /**
228 * Set a value.
229 * Set a value.
229 */
230 */
230 var return_value = WidgetModel.__super__.set.apply(this, arguments);
231 var return_value = WidgetModel.__super__.set.apply(this, arguments);
231
232
232 // Backbone only remembers the diff of the most recent set()
233 // Backbone only remembers the diff of the most recent set()
233 // operation. Calling set multiple times in a row results in a
234 // operation. Calling set multiple times in a row results in a
234 // loss of diff information. Here we keep our own running diff.
235 // loss of diff information. Here we keep our own running diff.
235 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
236 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
236 return return_value;
237 return return_value;
237 },
238 },
238
239
239 sync: function (method, model, options) {
240 sync: function (method, model, options) {
240 /**
241 /**
241 * Handle sync to the back-end. Called when a model.save() is called.
242 * Handle sync to the back-end. Called when a model.save() is called.
242 *
243 *
243 * Make sure a comm exists.
244 * Make sure a comm exists.
244 */
245 */
245 var error = options.error || function() {
246 var error = options.error || function() {
246 console.error('Backbone sync error:', arguments);
247 console.error('Backbone sync error:', arguments);
247 };
248 };
248 if (this.comm === undefined) {
249 if (this.comm === undefined) {
249 error();
250 error();
250 return false;
251 return false;
251 }
252 }
252
253
253 // Delete any key value pairs that the back-end already knows about.
254 // Delete any key value pairs that the back-end already knows about.
254 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
255 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
255 if (this.state_lock !== null) {
256 if (this.state_lock !== null) {
256 var keys = Object.keys(this.state_lock);
257 var keys = Object.keys(this.state_lock);
257 for (var i=0; i<keys.length; i++) {
258 for (var i=0; i<keys.length; i++) {
258 var key = keys[i];
259 var key = keys[i];
259 if (attrs[key] === this.state_lock[key]) {
260 if (attrs[key] === this.state_lock[key]) {
260 delete attrs[key];
261 delete attrs[key];
261 }
262 }
262 }
263 }
263 }
264 }
264
265
265 // Only sync if there are attributes to send to the back-end.
266 // Only sync if there are attributes to send to the back-end.
266 attrs = this._pack_models(attrs);
267 attrs = this._pack_models(attrs);
267 if (_.size(attrs) > 0) {
268 if (_.size(attrs) > 0) {
268
269
269 // If this message was sent via backbone itself, it will not
270 // If this message was sent via backbone itself, it will not
270 // have any callbacks. It's important that we create callbacks
271 // have any callbacks. It's important that we create callbacks
271 // so we can listen for status messages, etc...
272 // so we can listen for status messages, etc...
272 var callbacks = options.callbacks || this.callbacks();
273 var callbacks = options.callbacks || this.callbacks();
273
274
274 // Check throttle.
275 // Check throttle.
275 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
276 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
276 // The throttle has been exceeded, buffer the current msg so
277 // The throttle has been exceeded, buffer the current msg so
277 // it can be sent once the kernel has finished processing
278 // it can be sent once the kernel has finished processing
278 // some of the existing messages.
279 // some of the existing messages.
279
280
280 // Combine updates if it is a 'patch' sync, otherwise replace updates
281 // Combine updates if it is a 'patch' sync, otherwise replace updates
281 switch (method) {
282 switch (method) {
282 case 'patch':
283 case 'patch':
283 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
284 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
284 break;
285 break;
285 case 'update':
286 case 'update':
286 case 'create':
287 case 'create':
287 this.msg_buffer = attrs;
288 this.msg_buffer = attrs;
288 break;
289 break;
289 default:
290 default:
290 error();
291 error();
291 return false;
292 return false;
292 }
293 }
293 this.msg_buffer_callbacks = callbacks;
294 this.msg_buffer_callbacks = callbacks;
294
295
295 } else {
296 } else {
296 // We haven't exceeded the throttle, send the message like
297 // We haven't exceeded the throttle, send the message like
297 // normal.
298 // normal.
298 var data = {method: 'backbone', sync_data: attrs};
299 var data = {method: 'backbone', sync_data: attrs};
299 this.comm.send(data, callbacks);
300 this.comm.send(data, callbacks);
300 this.pending_msgs++;
301 this.pending_msgs++;
301 }
302 }
302 }
303 }
303 // Since the comm is a one-way communication, assume the message
304 // Since the comm is a one-way communication, assume the message
304 // arrived. Don't call success since we don't have a model back from the server
305 // arrived. Don't call success since we don't have a model back from the server
305 // this means we miss out on the 'sync' event.
306 // this means we miss out on the 'sync' event.
306 this._buffered_state_diff = {};
307 this._buffered_state_diff = {};
307 },
308 },
308
309
309 save_changes: function(callbacks) {
310 save_changes: function(callbacks) {
310 /**
311 /**
311 * Push this model's state to the back-end
312 * Push this model's state to the back-end
312 *
313 *
313 * This invokes a Backbone.Sync.
314 * This invokes a Backbone.Sync.
314 */
315 */
315 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
316 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
316 },
317 },
317
318
318 _pack_models: function(value) {
319 _pack_models: function(value) {
319 /**
320 /**
320 * Replace models with model ids recursively.
321 * Replace models with model ids recursively.
321 */
322 */
322 var that = this;
323 var that = this;
323 var packed;
324 var packed;
324 if (value instanceof Backbone.Model) {
325 if (value instanceof Backbone.Model) {
325 return "IPY_MODEL_" + value.id;
326 return "IPY_MODEL_" + value.id;
326
327
327 } else if ($.isArray(value)) {
328 } else if ($.isArray(value)) {
328 packed = [];
329 packed = [];
329 _.each(value, function(sub_value, key) {
330 _.each(value, function(sub_value, key) {
330 packed.push(that._pack_models(sub_value));
331 packed.push(that._pack_models(sub_value));
331 });
332 });
332 return packed;
333 return packed;
333 } else if (value instanceof Date || value instanceof String) {
334 } else if (value instanceof Date || value instanceof String) {
334 return value;
335 return value;
335 } else if (value instanceof Object) {
336 } else if (value instanceof Object) {
336 packed = {};
337 packed = {};
337 _.each(value, function(sub_value, key) {
338 _.each(value, function(sub_value, key) {
338 packed[key] = that._pack_models(sub_value);
339 packed[key] = that._pack_models(sub_value);
339 });
340 });
340 return packed;
341 return packed;
341
342
342 } else {
343 } else {
343 return value;
344 return value;
344 }
345 }
345 },
346 },
346
347
347 _unpack_models: function(value) {
348 _unpack_models: function(value) {
348 /**
349 /**
349 * Replace model ids with models recursively.
350 * Replace model ids with models recursively.
350 */
351 */
351 var that = this;
352 var that = this;
352 var unpacked;
353 var unpacked;
353 if ($.isArray(value)) {
354 if ($.isArray(value)) {
354 unpacked = [];
355 unpacked = [];
355 _.each(value, function(sub_value, key) {
356 _.each(value, function(sub_value, key) {
356 unpacked.push(that._unpack_models(sub_value));
357 unpacked.push(that._unpack_models(sub_value));
357 });
358 });
358 return Promise.all(unpacked);
359 return Promise.all(unpacked);
359 } else if (value instanceof Object) {
360 } else if (value instanceof Object) {
360 unpacked = {};
361 unpacked = {};
361 _.each(value, function(sub_value, key) {
362 _.each(value, function(sub_value, key) {
362 unpacked[key] = that._unpack_models(sub_value);
363 unpacked[key] = that._unpack_models(sub_value);
363 });
364 });
364 return utils.resolve_promises_dict(unpacked);
365 return utils.resolve_promises_dict(unpacked);
365 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
366 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
366 // get_model returns a promise already
367 // get_model returns a promise already
367 return this.widget_manager.get_model(value.slice(10, value.length));
368 return this.widget_manager.get_model(value.slice(10, value.length));
368 } else {
369 } else {
369 return Promise.resolve(value);
370 return Promise.resolve(value);
370 }
371 }
371 },
372 },
372
373
373 on_some_change: function(keys, callback, context) {
374 on_some_change: function(keys, callback, context) {
374 /**
375 /**
375 * on_some_change(["key1", "key2"], foo, context) differs from
376 * on_some_change(["key1", "key2"], foo, context) differs from
376 * on("change:key1 change:key2", foo, context).
377 * on("change:key1 change:key2", foo, context).
377 * If the widget attributes key1 and key2 are both modified,
378 * If the widget attributes key1 and key2 are both modified,
378 * the second form will result in foo being called twice
379 * the second form will result in foo being called twice
379 * while the first will call foo only once.
380 * while the first will call foo only once.
380 */
381 */
381 this.on('change', function() {
382 this.on('change', function() {
382 if (keys.some(this.hasChanged, this)) {
383 if (keys.some(this.hasChanged, this)) {
383 callback.apply(context);
384 callback.apply(context);
384 }
385 }
385 }, this);
386 }, this);
386
387
387 },
388 },
388 });
389 });
389 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
390 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
390
391
391
392
392 var WidgetView = Backbone.View.extend({
393 var WidgetView = Backbone.View.extend({
393 initialize: function(parameters) {
394 initialize: function(parameters) {
394 /**
395 /**
395 * Public constructor.
396 * Public constructor.
396 */
397 */
397 this.model.on('change',this.update,this);
398 this.model.on('change',this.update,this);
398
399
399 // Bubble the comm live events.
400 // Bubble the comm live events.
400 this.model.on('comm:live', function() {
401 this.model.on('comm:live', function() {
401 this.trigger('comm:live', this);
402 this.trigger('comm:live', this);
402 }, this);
403 }, this);
403 this.model.on('comm:dead', function() {
404 this.model.on('comm:dead', function() {
404 this.trigger('comm:dead', this);
405 this.trigger('comm:dead', this);
405 }, this);
406 }, this);
406
407
407 this.options = parameters.options;
408 this.options = parameters.options;
408 this.on('displayed', function() {
409 this.on('displayed', function() {
409 this.is_displayed = true;
410 this.is_displayed = true;
410 }, this);
411 }, this);
411 },
412 },
412
413
413 update: function(){
414 update: function(){
414 /**
415 /**
415 * Triggered on model change.
416 * Triggered on model change.
416 *
417 *
417 * Update view to be consistent with this.model
418 * Update view to be consistent with this.model
418 */
419 */
419 },
420 },
420
421
421 create_child_view: function(child_model, options) {
422 create_child_view: function(child_model, options) {
422 /**
423 /**
423 * Create and promise that resolves to a child view of a given model
424 * Create and promise that resolves to a child view of a given model
424 */
425 */
425 var that = this;
426 var that = this;
426 options = $.extend({ parent: this }, options || {});
427 options = $.extend({ parent: this }, options || {});
427 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
428 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
428 },
429 },
429
430
430 callbacks: function(){
431 callbacks: function(){
431 /**
432 /**
432 * Create msg callbacks for a comm msg.
433 * Create msg callbacks for a comm msg.
433 */
434 */
434 return this.model.callbacks(this);
435 return this.model.callbacks(this);
435 },
436 },
436
437
437 render: function(){
438 render: function(){
438 /**
439 /**
439 * Render the view.
440 * Render the view.
440 *
441 *
441 * By default, this is only called the first time the view is created
442 * By default, this is only called the first time the view is created
442 */
443 */
443 },
444 },
444
445
445 send: function (content) {
446 send: function (content) {
446 /**
447 /**
447 * Send a custom msg associated with this view.
448 * Send a custom msg associated with this view.
448 */
449 */
449 this.model.send(content, this.callbacks());
450 this.model.send(content, this.callbacks());
450 },
451 },
451
452
452 touch: function () {
453 touch: function () {
453 this.model.save_changes(this.callbacks());
454 this.model.save_changes(this.callbacks());
454 },
455 },
455
456
456 after_displayed: function (callback, context) {
457 after_displayed: function (callback, context) {
457 /**
458 /**
458 * Calls the callback right away is the view is already displayed
459 * Calls the callback right away is the view is already displayed
459 * otherwise, register the callback to the 'displayed' event.
460 * otherwise, register the callback to the 'displayed' event.
460 */
461 */
461 if (this.is_displayed) {
462 if (this.is_displayed) {
462 callback.apply(context);
463 callback.apply(context);
463 } else {
464 } else {
464 this.on('displayed', callback, context);
465 this.on('displayed', callback, context);
465 }
466 }
466 },
467 },
467
468
468 remove: function () {
469 remove: function () {
469 // Raise a remove event when the view is removed.
470 // Raise a remove event when the view is removed.
470 WidgetView.__super__.remove.apply(this, arguments);
471 WidgetView.__super__.remove.apply(this, arguments);
471 this.trigger('remove');
472 this.trigger('remove');
472 }
473 }
473 });
474 });
474
475
475
476
476 var DOMWidgetView = WidgetView.extend({
477 var DOMWidgetView = WidgetView.extend({
477 initialize: function (parameters) {
478 initialize: function (parameters) {
478 /**
479 /**
479 * Public constructor
480 * Public constructor
480 */
481 */
481 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
482 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
482 this.model.on('change:visible', this.update_visible, this);
483 this.model.on('change:visible', this.update_visible, this);
483 this.model.on('change:_css', this.update_css, this);
484 this.model.on('change:_css', this.update_css, this);
484
485
485 this.model.on('change:_dom_classes', function(model, new_classes) {
486 this.model.on('change:_dom_classes', function(model, new_classes) {
486 var old_classes = model.previous('_dom_classes');
487 var old_classes = model.previous('_dom_classes');
487 this.update_classes(old_classes, new_classes);
488 this.update_classes(old_classes, new_classes);
488 }, this);
489 }, this);
489
490
490 this.model.on('change:color', function (model, value) {
491 this.model.on('change:color', function (model, value) {
491 this.update_attr('color', value); }, this);
492 this.update_attr('color', value); }, this);
492
493
493 this.model.on('change:background_color', function (model, value) {
494 this.model.on('change:background_color', function (model, value) {
494 this.update_attr('background', value); }, this);
495 this.update_attr('background', value); }, this);
495
496
496 this.model.on('change:width', function (model, value) {
497 this.model.on('change:width', function (model, value) {
497 this.update_attr('width', value); }, this);
498 this.update_attr('width', value); }, this);
498
499
499 this.model.on('change:height', function (model, value) {
500 this.model.on('change:height', function (model, value) {
500 this.update_attr('height', value); }, this);
501 this.update_attr('height', value); }, this);
501
502
502 this.model.on('change:border_color', function (model, value) {
503 this.model.on('change:border_color', function (model, value) {
503 this.update_attr('border-color', value); }, this);
504 this.update_attr('border-color', value); }, this);
504
505
505 this.model.on('change:border_width', function (model, value) {
506 this.model.on('change:border_width', function (model, value) {
506 this.update_attr('border-width', value); }, this);
507 this.update_attr('border-width', value); }, this);
507
508
508 this.model.on('change:border_style', function (model, value) {
509 this.model.on('change:border_style', function (model, value) {
509 this.update_attr('border-style', value); }, this);
510 this.update_attr('border-style', value); }, this);
510
511
511 this.model.on('change:font_style', function (model, value) {
512 this.model.on('change:font_style', function (model, value) {
512 this.update_attr('font-style', value); }, this);
513 this.update_attr('font-style', value); }, this);
513
514
514 this.model.on('change:font_weight', function (model, value) {
515 this.model.on('change:font_weight', function (model, value) {
515 this.update_attr('font-weight', value); }, this);
516 this.update_attr('font-weight', value); }, this);
516
517
517 this.model.on('change:font_size', function (model, value) {
518 this.model.on('change:font_size', function (model, value) {
518 this.update_attr('font-size', this._default_px(value)); }, this);
519 this.update_attr('font-size', this._default_px(value)); }, this);
519
520
520 this.model.on('change:font_family', function (model, value) {
521 this.model.on('change:font_family', function (model, value) {
521 this.update_attr('font-family', value); }, this);
522 this.update_attr('font-family', value); }, this);
522
523
523 this.model.on('change:padding', function (model, value) {
524 this.model.on('change:padding', function (model, value) {
524 this.update_attr('padding', value); }, this);
525 this.update_attr('padding', value); }, this);
525
526
526 this.model.on('change:margin', function (model, value) {
527 this.model.on('change:margin', function (model, value) {
527 this.update_attr('margin', this._default_px(value)); }, this);
528 this.update_attr('margin', this._default_px(value)); }, this);
528
529
529 this.model.on('change:border_radius', function (model, value) {
530 this.model.on('change:border_radius', function (model, value) {
530 this.update_attr('border-radius', this._default_px(value)); }, this);
531 this.update_attr('border-radius', this._default_px(value)); }, this);
531
532
532 this.after_displayed(function() {
533 this.after_displayed(function() {
533 this.update_visible(this.model, this.model.get("visible"));
534 this.update_visible(this.model, this.model.get("visible"));
534 this.update_classes([], this.model.get('_dom_classes'));
535 this.update_classes([], this.model.get('_dom_classes'));
535
536
536 this.update_attr('color', this.model.get('color'));
537 this.update_attr('color', this.model.get('color'));
537 this.update_attr('background', this.model.get('background_color'));
538 this.update_attr('background', this.model.get('background_color'));
538 this.update_attr('width', this.model.get('width'));
539 this.update_attr('width', this.model.get('width'));
539 this.update_attr('height', this.model.get('height'));
540 this.update_attr('height', this.model.get('height'));
540 this.update_attr('border-color', this.model.get('border_color'));
541 this.update_attr('border-color', this.model.get('border_color'));
541 this.update_attr('border-width', this.model.get('border_width'));
542 this.update_attr('border-width', this.model.get('border_width'));
542 this.update_attr('border-style', this.model.get('border_style'));
543 this.update_attr('border-style', this.model.get('border_style'));
543 this.update_attr('font-style', this.model.get('font_style'));
544 this.update_attr('font-style', this.model.get('font_style'));
544 this.update_attr('font-weight', this.model.get('font_weight'));
545 this.update_attr('font-weight', this.model.get('font_weight'));
545 this.update_attr('font-size', this.model.get('font_size'));
546 this.update_attr('font-size', this.model.get('font_size'));
546 this.update_attr('font-family', this.model.get('font_family'));
547 this.update_attr('font-family', this.model.get('font_family'));
547 this.update_attr('padding', this.model.get('padding'));
548 this.update_attr('padding', this.model.get('padding'));
548 this.update_attr('margin', this.model.get('margin'));
549 this.update_attr('margin', this.model.get('margin'));
549 this.update_attr('border-radius', this.model.get('border_radius'));
550 this.update_attr('border-radius', this.model.get('border_radius'));
550
551
551 this.update_css(this.model, this.model.get("_css"));
552 this.update_css(this.model, this.model.get("_css"));
552 }, this);
553 }, this);
553 },
554 },
554
555
555 _default_px: function(value) {
556 _default_px: function(value) {
556 /**
557 /**
557 * Makes browser interpret a numerical string as a pixel value.
558 * Makes browser interpret a numerical string as a pixel value.
558 */
559 */
559 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
560 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
560 return value.trim() + 'px';
561 return value.trim() + 'px';
561 }
562 }
562 return value;
563 return value;
563 },
564 },
564
565
565 update_attr: function(name, value) {
566 update_attr: function(name, value) {
566 /**
567 /**
567 * Set a css attr of the widget view.
568 * Set a css attr of the widget view.
568 */
569 */
569 this.$el.css(name, value);
570 this.$el.css(name, value);
570 },
571 },
571
572
572 update_visible: function(model, value) {
573 update_visible: function(model, value) {
573 /**
574 /**
574 * Update visibility
575 * Update visibility
575 */
576 */
576 switch(value) {
577 switch(value) {
577 case null: // python None
578 case null: // python None
578 this.$el.show().css('visibility', 'hidden'); break;
579 this.$el.show().css('visibility', 'hidden'); break;
579 case false:
580 case false:
580 this.$el.hide(); break;
581 this.$el.hide(); break;
581 case true:
582 case true:
582 this.$el.show().css('visibility', ''); break;
583 this.$el.show().css('visibility', ''); break;
583 }
584 }
584 },
585 },
585
586
586 update_css: function (model, css) {
587 update_css: function (model, css) {
587 /**
588 /**
588 * Update the css styling of this view.
589 * Update the css styling of this view.
589 */
590 */
590 var e = this.$el;
591 if (css === undefined) {return;}
591 if (css === undefined) {return;}
592 for (var i = 0; i < css.length; i++) {
592 for (var i = 0; i < css.length; i++) {
593 // Apply the css traits to all elements that match the selector.
593 // Apply the css traits to all elements that match the selector.
594 var selector = css[i][0];
594 var selector = css[i][0];
595 var elements = this._get_selector_element(selector);
595 var elements = this._get_selector_element(selector);
596 if (elements.length > 0) {
596 if (elements.length > 0) {
597 var trait_key = css[i][1];
597 var trait_key = css[i][1];
598 var trait_value = css[i][2];
598 var trait_value = css[i][2];
599 elements.css(trait_key ,trait_value);
599 elements.css(trait_key ,trait_value);
600 }
600 }
601 }
601 }
602 },
602 },
603
603
604 update_classes: function (old_classes, new_classes, $el) {
604 update_classes: function (old_classes, new_classes, $el) {
605 /**
605 /**
606 * Update the DOM classes applied to an element, default to this.$el.
606 * Update the DOM classes applied to an element, default to this.$el.
607 */
607 */
608 if ($el===undefined) {
608 if ($el===undefined) {
609 $el = this.$el;
609 $el = this.$el;
610 }
610 }
611 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
611 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
612 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
612 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
613 },
613 },
614
614
615 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
615 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
616 /**
616 /**
617 * Update the DOM classes applied to the widget based on a single
617 * Update the DOM classes applied to the widget based on a single
618 * trait's value.
618 * trait's value.
619 *
619 *
620 * Given a trait value classes map, this function automatically
620 * Given a trait value classes map, this function automatically
621 * handles applying the appropriate classes to the widget element
621 * handles applying the appropriate classes to the widget element
622 * and removing classes that are no longer valid.
622 * and removing classes that are no longer valid.
623 *
623 *
624 * Parameters
624 * Parameters
625 * ----------
625 * ----------
626 * class_map: dictionary
626 * class_map: dictionary
627 * Dictionary of trait values to class lists.
627 * Dictionary of trait values to class lists.
628 * Example:
628 * Example:
629 * {
629 * {
630 * success: ['alert', 'alert-success'],
630 * success: ['alert', 'alert-success'],
631 * info: ['alert', 'alert-info'],
631 * info: ['alert', 'alert-info'],
632 * warning: ['alert', 'alert-warning'],
632 * warning: ['alert', 'alert-warning'],
633 * danger: ['alert', 'alert-danger']
633 * danger: ['alert', 'alert-danger']
634 * };
634 * };
635 * trait_name: string
635 * trait_name: string
636 * Name of the trait to check the value of.
636 * Name of the trait to check the value of.
637 * previous_trait_value: optional string, default ''
637 * previous_trait_value: optional string, default ''
638 * Last trait value
638 * Last trait value
639 * $el: optional jQuery element handle, defaults to this.$el
639 * $el: optional jQuery element handle, defaults to this.$el
640 * Element that the classes are applied to.
640 * Element that the classes are applied to.
641 */
641 */
642 var key = previous_trait_value;
642 var key = previous_trait_value;
643 if (key === undefined) {
643 if (key === undefined) {
644 key = this.model.previous(trait_name);
644 key = this.model.previous(trait_name);
645 }
645 }
646 var old_classes = class_map[key] ? class_map[key] : [];
646 var old_classes = class_map[key] ? class_map[key] : [];
647 key = this.model.get(trait_name);
647 key = this.model.get(trait_name);
648 var new_classes = class_map[key] ? class_map[key] : [];
648 var new_classes = class_map[key] ? class_map[key] : [];
649
649
650 this.update_classes(old_classes, new_classes, $el || this.$el);
650 this.update_classes(old_classes, new_classes, $el || this.$el);
651 },
651 },
652
652
653 _get_selector_element: function (selector) {
653 _get_selector_element: function (selector) {
654 /**
654 /**
655 * Get the elements via the css selector.
655 * Get the elements via the css selector.
656 */
656 */
657 var elements;
657 var elements;
658 if (!selector) {
658 if (!selector) {
659 elements = this.$el;
659 elements = this.$el;
660 } else {
660 } else {
661 elements = this.$el.find(selector).addBack(selector);
661 elements = this.$el.find(selector).addBack(selector);
662 }
662 }
663 return elements;
663 return elements;
664 },
664 },
665
665
666 typeset: function(element, text){
666 typeset: function(element, text){
667 utils.typeset.apply(null, arguments);
667 utils.typeset.apply(null, arguments);
668 },
668 },
669 });
669 });
670
670
671
671
672 var ViewList = function(create_view, remove_view, context) {
672 var ViewList = function(create_view, remove_view, context) {
673 /**
673 /**
674 * - create_view and remove_view are default functions called when adding or removing views
674 * - create_view and remove_view are default functions called when adding or removing views
675 * - create_view takes a model and returns a view or a promise for a view for that model
675 * - create_view takes a model and returns a view or a promise for a view for that model
676 * - remove_view takes a view and destroys it (including calling `view.remove()`)
676 * - remove_view takes a view and destroys it (including calling `view.remove()`)
677 * - each time the update() function is called with a new list, the create and remove
677 * - each time the update() function is called with a new list, the create and remove
678 * callbacks will be called in an order so that if you append the views created in the
678 * callbacks will be called in an order so that if you append the views created in the
679 * create callback and remove the views in the remove callback, you will duplicate
679 * create callback and remove the views in the remove callback, you will duplicate
680 * the order of the list.
680 * the order of the list.
681 * - the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
681 * - the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
682 * - the context defaults to the created ViewList. If you pass another context, the create and remove
682 * - the context defaults to the created ViewList. If you pass another context, the create and remove
683 * will be called in that context.
683 * will be called in that context.
684 */
684 */
685
685
686 this.initialize.apply(this, arguments);
686 this.initialize.apply(this, arguments);
687 };
687 };
688
688
689 _.extend(ViewList.prototype, {
689 _.extend(ViewList.prototype, {
690 initialize: function(create_view, remove_view, context) {
690 initialize: function(create_view, remove_view, context) {
691 this._handler_context = context || this;
691 this._handler_context = context || this;
692 this._models = [];
692 this._models = [];
693 this.views = []; // list of promises for views
693 this.views = []; // list of promises for views
694 this._create_view = create_view;
694 this._create_view = create_view;
695 this._remove_view = remove_view || function(view) {view.remove();};
695 this._remove_view = remove_view || function(view) {view.remove();};
696 },
696 },
697
697
698 update: function(new_models, create_view, remove_view, context) {
698 update: function(new_models, create_view, remove_view, context) {
699 /**
699 /**
700 * the create_view, remove_view, and context arguments override the defaults
700 * the create_view, remove_view, and context arguments override the defaults
701 * specified when the list is created.
701 * specified when the list is created.
702 * after this function, the .views attribute is a list of promises for views
702 * after this function, the .views attribute is a list of promises for views
703 * if you want to perform some action on the list of views, do something like
703 * if you want to perform some action on the list of views, do something like
704 * `Promise.all(myviewlist.views).then(function(views) {...});`
704 * `Promise.all(myviewlist.views).then(function(views) {...});`
705 */
705 */
706 var remove = remove_view || this._remove_view;
706 var remove = remove_view || this._remove_view;
707 var create = create_view || this._create_view;
707 var create = create_view || this._create_view;
708 var context = context || this._handler_context;
708 context = context || this._handler_context;
709 var i = 0;
709 var i = 0;
710 // first, skip past the beginning of the lists if they are identical
710 // first, skip past the beginning of the lists if they are identical
711 for (; i < new_models.length; i++) {
711 for (; i < new_models.length; i++) {
712 if (i >= this._models.length || new_models[i] !== this._models[i]) {
712 if (i >= this._models.length || new_models[i] !== this._models[i]) {
713 break;
713 break;
714 }
714 }
715 }
715 }
716
716
717 var first_removed = i;
717 var first_removed = i;
718 // Remove the non-matching items from the old list.
718 // Remove the non-matching items from the old list.
719 var removed = this.views.splice(first_removed, this.views.length-first_removed);
719 var removed = this.views.splice(first_removed, this.views.length-first_removed);
720 for (var j = 0; j < removed.length; j++) {
720 for (var j = 0; j < removed.length; j++) {
721 removed[j].then(function(view) {
721 removed[j].then(function(view) {
722 remove.call(context, view)
722 remove.call(context, view)
723 });
723 });
724 }
724 }
725
725
726 // Add the rest of the new list items.
726 // Add the rest of the new list items.
727 for (; i < new_models.length; i++) {
727 for (; i < new_models.length; i++) {
728 this.views.push(Promise.resolve(create.call(context, new_models[i])));
728 this.views.push(Promise.resolve(create.call(context, new_models[i])));
729 }
729 }
730 // make a copy of the input array
730 // make a copy of the input array
731 this._models = new_models.slice();
731 this._models = new_models.slice();
732 },
732 },
733
733
734 remove: function() {
734 remove: function() {
735 /**
735 /**
736 * removes every view in the list; convenience function for `.update([])`
736 * removes every view in the list; convenience function for `.update([])`
737 * that should be faster
737 * that should be faster
738 * returns a promise that resolves after this removal is done
738 * returns a promise that resolves after this removal is done
739 */
739 */
740 var that = this;
740 var that = this;
741 return Promise.all(this.views).then(function(views) {
741 return Promise.all(this.views).then(function(views) {
742 for (var i = 0; i < that.views.length; i++) {
742 for (var i = 0; i < that.views.length; i++) {
743 that._remove_view.call(that._handler_context, views[i]);
743 that._remove_view.call(that._handler_context, views[i]);
744 }
744 }
745 that.views = [];
745 that.views = [];
746 that._models = [];
746 that._models = [];
747 });
747 });
748 },
748 },
749 });
749 });
750
750
751 var widget = {
751 var widget = {
752 'WidgetModel': WidgetModel,
752 'WidgetModel': WidgetModel,
753 'WidgetView': WidgetView,
753 'WidgetView': WidgetView,
754 'DOMWidgetView': DOMWidgetView,
754 'DOMWidgetView': DOMWidgetView,
755 'ViewList': ViewList,
755 'ViewList': ViewList,
756 };
756 };
757
757
758 // For backwards compatability.
758 // For backwards compatability.
759 $.extend(IPython, widget);
759 $.extend(IPython, widget);
760
760
761 return widget;
761 return widget;
762 });
762 });
@@ -1,369 +1,370 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "jqueryui",
6 "jqueryui",
7 "base/js/utils",
7 "base/js/utils",
8 "bootstrap",
8 "bootstrap",
9 ], function(widget, $, utils){
9 ], function(widget, $, utils){
10 "use strict";
10
11
11 var BoxView = widget.DOMWidgetView.extend({
12 var BoxView = widget.DOMWidgetView.extend({
12 initialize: function(){
13 initialize: function(){
13 /**
14 /**
14 * Public constructor
15 * Public constructor
15 */
16 */
16 BoxView.__super__.initialize.apply(this, arguments);
17 BoxView.__super__.initialize.apply(this, arguments);
17 this.children_views = new widget.ViewList(this.add_child_model, null, this);
18 this.children_views = new widget.ViewList(this.add_child_model, null, this);
18 this.listenTo(this.model, 'change:children', function(model, value) {
19 this.listenTo(this.model, 'change:children', function(model, value) {
19 this.children_views.update(value);
20 this.children_views.update(value);
20 }, this);
21 }, this);
21 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
22 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
22 this.update_overflow_x();
23 this.update_overflow_x();
23 }, this);
24 }, this);
24 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
25 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
25 this.update_overflow_y();
26 this.update_overflow_y();
26 }, this);
27 }, this);
27 this.listenTo(this.model, 'change:box_style', function(model, value) {
28 this.listenTo(this.model, 'change:box_style', function(model, value) {
28 this.update_box_style();
29 this.update_box_style();
29 }, this);
30 }, this);
30 },
31 },
31
32
32 update_attr: function(name, value) {
33 update_attr: function(name, value) {
33 /**
34 /**
34 * Set a css attr of the widget view.
35 * Set a css attr of the widget view.
35 */
36 */
36 this.$box.css(name, value);
37 this.$box.css(name, value);
37 },
38 },
38
39
39 render: function(){
40 render: function(){
40 /**
41 /**
41 * Called when view is rendered.
42 * Called when view is rendered.
42 */
43 */
43 this.$box = this.$el;
44 this.$box = this.$el;
44 this.$box.addClass('widget-box');
45 this.$box.addClass('widget-box');
45 this.children_views.update(this.model.get('children'));
46 this.children_views.update(this.model.get('children'));
46 this.update_overflow_x();
47 this.update_overflow_x();
47 this.update_overflow_y();
48 this.update_overflow_y();
48 this.update_box_style('');
49 this.update_box_style('');
49 },
50 },
50
51
51 update_overflow_x: function() {
52 update_overflow_x: function() {
52 /**
53 /**
53 * Called when the x-axis overflow setting is changed.
54 * Called when the x-axis overflow setting is changed.
54 */
55 */
55 this.$box.css('overflow-x', this.model.get('overflow_x'));
56 this.$box.css('overflow-x', this.model.get('overflow_x'));
56 },
57 },
57
58
58 update_overflow_y: function() {
59 update_overflow_y: function() {
59 /**
60 /**
60 * Called when the y-axis overflow setting is changed.
61 * Called when the y-axis overflow setting is changed.
61 */
62 */
62 this.$box.css('overflow-y', this.model.get('overflow_y'));
63 this.$box.css('overflow-y', this.model.get('overflow_y'));
63 },
64 },
64
65
65 update_box_style: function(previous_trait_value) {
66 update_box_style: function(previous_trait_value) {
66 var class_map = {
67 var class_map = {
67 success: ['alert', 'alert-success'],
68 success: ['alert', 'alert-success'],
68 info: ['alert', 'alert-info'],
69 info: ['alert', 'alert-info'],
69 warning: ['alert', 'alert-warning'],
70 warning: ['alert', 'alert-warning'],
70 danger: ['alert', 'alert-danger']
71 danger: ['alert', 'alert-danger']
71 };
72 };
72 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
73 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
73 },
74 },
74
75
75 add_child_model: function(model) {
76 add_child_model: function(model) {
76 /**
77 /**
77 * Called when a model is added to the children list.
78 * Called when a model is added to the children list.
78 */
79 */
79 var that = this;
80 var that = this;
80 var dummy = $('<div/>');
81 var dummy = $('<div/>');
81 that.$box.append(dummy);
82 that.$box.append(dummy);
82 return this.create_child_view(model).then(function(view) {
83 return this.create_child_view(model).then(function(view) {
83 dummy.replaceWith(view.el);
84 dummy.replaceWith(view.el);
84
85
85 // Trigger the displayed event of the child view.
86 // Trigger the displayed event of the child view.
86 that.after_displayed(function() {
87 that.after_displayed(function() {
87 view.trigger('displayed');
88 view.trigger('displayed');
88 });
89 });
89 return view;
90 return view;
90 }).catch(utils.reject("Couldn't add child view to box", true));
91 }).catch(utils.reject("Couldn't add child view to box", true));
91 },
92 },
92
93
93 remove: function() {
94 remove: function() {
94 /**
95 /**
95 * We remove this widget before removing the children as an optimization
96 * We remove this widget before removing the children as an optimization
96 * we want to remove the entire container from the DOM first before
97 * we want to remove the entire container from the DOM first before
97 * removing each individual child separately.
98 * removing each individual child separately.
98 */
99 */
99 BoxView.__super__.remove.apply(this, arguments);
100 BoxView.__super__.remove.apply(this, arguments);
100 this.children_views.remove();
101 this.children_views.remove();
101 },
102 },
102 });
103 });
103
104
104
105
105 var FlexBoxView = BoxView.extend({
106 var FlexBoxView = BoxView.extend({
106 render: function(){
107 render: function(){
107 FlexBoxView.__super__.render.apply(this);
108 FlexBoxView.__super__.render.apply(this);
108 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
109 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
109 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
110 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
110 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
111 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
111 this.listenTo(this.model, 'change:align', this._align_changed, this);
112 this.listenTo(this.model, 'change:align', this._align_changed, this);
112 this._flex_changed();
113 this._flex_changed();
113 this._pack_changed();
114 this._pack_changed();
114 this._align_changed();
115 this._align_changed();
115 this.update_orientation();
116 this.update_orientation();
116 },
117 },
117
118
118 update_orientation: function(){
119 update_orientation: function(){
119 var orientation = this.model.get("orientation");
120 var orientation = this.model.get("orientation");
120 if (orientation == "vertical") {
121 if (orientation == "vertical") {
121 this.$box.removeClass("hbox").addClass("vbox");
122 this.$box.removeClass("hbox").addClass("vbox");
122 } else {
123 } else {
123 this.$box.removeClass("vbox").addClass("hbox");
124 this.$box.removeClass("vbox").addClass("hbox");
124 }
125 }
125 },
126 },
126
127
127 _flex_changed: function(){
128 _flex_changed: function(){
128 if (this.model.previous('flex')) {
129 if (this.model.previous('flex')) {
129 this.$box.removeClass('box-flex' + this.model.previous('flex'));
130 this.$box.removeClass('box-flex' + this.model.previous('flex'));
130 }
131 }
131 this.$box.addClass('box-flex' + this.model.get('flex'));
132 this.$box.addClass('box-flex' + this.model.get('flex'));
132 },
133 },
133
134
134 _pack_changed: function(){
135 _pack_changed: function(){
135 if (this.model.previous('pack')) {
136 if (this.model.previous('pack')) {
136 this.$box.removeClass(this.model.previous('pack'));
137 this.$box.removeClass(this.model.previous('pack'));
137 }
138 }
138 this.$box.addClass(this.model.get('pack'));
139 this.$box.addClass(this.model.get('pack'));
139 },
140 },
140
141
141 _align_changed: function(){
142 _align_changed: function(){
142 if (this.model.previous('align')) {
143 if (this.model.previous('align')) {
143 this.$box.removeClass('align-' + this.model.previous('align'));
144 this.$box.removeClass('align-' + this.model.previous('align'));
144 }
145 }
145 this.$box.addClass('align-' + this.model.get('align'));
146 this.$box.addClass('align-' + this.model.get('align'));
146 },
147 },
147 });
148 });
148
149
149 var PopupView = BoxView.extend({
150 var PopupView = BoxView.extend({
150
151
151 render: function(){
152 render: function(){
152 /**
153 /**
153 * Called when view is rendered.
154 * Called when view is rendered.
154 */
155 */
155 var that = this;
156 var that = this;
156
157
157 this.$el.on("remove", function(){
158 this.$el.on("remove", function(){
158 that.$backdrop.remove();
159 that.$backdrop.remove();
159 });
160 });
160 this.$backdrop = $('<div />')
161 this.$backdrop = $('<div />')
161 .appendTo($('#notebook-container'))
162 .appendTo($('#notebook-container'))
162 .addClass('modal-dialog')
163 .addClass('modal-dialog')
163 .css('position', 'absolute')
164 .css('position', 'absolute')
164 .css('left', '0px')
165 .css('left', '0px')
165 .css('top', '0px');
166 .css('top', '0px');
166 this.$window = $('<div />')
167 this.$window = $('<div />')
167 .appendTo(this.$backdrop)
168 .appendTo(this.$backdrop)
168 .addClass('modal-content widget-modal')
169 .addClass('modal-content widget-modal')
169 .mousedown(function(){
170 .mousedown(function(){
170 that.bring_to_front();
171 that.bring_to_front();
171 });
172 });
172
173
173 // Set the elements array since the this.$window element is not child
174 // Set the elements array since the this.$window element is not child
174 // of this.$el and the parent widget manager or other widgets may
175 // of this.$el and the parent widget manager or other widgets may
175 // need to know about all of the top-level widgets. The IPython
176 // need to know about all of the top-level widgets. The IPython
176 // widget manager uses this to register the elements with the
177 // widget manager uses this to register the elements with the
177 // keyboard manager.
178 // keyboard manager.
178 this.additional_elements = [this.$window];
179 this.additional_elements = [this.$window];
179
180
180 this.$title_bar = $('<div />')
181 this.$title_bar = $('<div />')
181 .addClass('popover-title')
182 .addClass('popover-title')
182 .appendTo(this.$window)
183 .appendTo(this.$window)
183 .mousedown(function(){
184 .mousedown(function(){
184 that.bring_to_front();
185 that.bring_to_front();
185 });
186 });
186 this.$close = $('<button />')
187 this.$close = $('<button />')
187 .addClass('close fa fa-remove')
188 .addClass('close fa fa-remove')
188 .css('margin-left', '5px')
189 .css('margin-left', '5px')
189 .appendTo(this.$title_bar)
190 .appendTo(this.$title_bar)
190 .click(function(){
191 .click(function(){
191 that.hide();
192 that.hide();
192 event.stopPropagation();
193 event.stopPropagation();
193 });
194 });
194 this.$minimize = $('<button />')
195 this.$minimize = $('<button />')
195 .addClass('close fa fa-arrow-down')
196 .addClass('close fa fa-arrow-down')
196 .appendTo(this.$title_bar)
197 .appendTo(this.$title_bar)
197 .click(function(){
198 .click(function(){
198 that.popped_out = !that.popped_out;
199 that.popped_out = !that.popped_out;
199 if (!that.popped_out) {
200 if (!that.popped_out) {
200 that.$minimize
201 that.$minimize
201 .removeClass('fa-arrow-down')
202 .removeClass('fa-arrow-down')
202 .addClass('fa-arrow-up');
203 .addClass('fa-arrow-up');
203
204
204 that.$window
205 that.$window
205 .draggable('destroy')
206 .draggable('destroy')
206 .resizable('destroy')
207 .resizable('destroy')
207 .removeClass('widget-modal modal-content')
208 .removeClass('widget-modal modal-content')
208 .addClass('docked-widget-modal')
209 .addClass('docked-widget-modal')
209 .detach()
210 .detach()
210 .insertBefore(that.$show_button);
211 .insertBefore(that.$show_button);
211 that.$show_button.hide();
212 that.$show_button.hide();
212 that.$close.hide();
213 that.$close.hide();
213 } else {
214 } else {
214 that.$minimize
215 that.$minimize
215 .addClass('fa-arrow-down')
216 .addClass('fa-arrow-down')
216 .removeClass('fa-arrow-up');
217 .removeClass('fa-arrow-up');
217
218
218 that.$window
219 that.$window
219 .removeClass('docked-widget-modal')
220 .removeClass('docked-widget-modal')
220 .addClass('widget-modal modal-content')
221 .addClass('widget-modal modal-content')
221 .detach()
222 .detach()
222 .appendTo(that.$backdrop)
223 .appendTo(that.$backdrop)
223 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
224 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
224 .resizable()
225 .resizable()
225 .children('.ui-resizable-handle').show();
226 .children('.ui-resizable-handle').show();
226 that.show();
227 that.show();
227 that.$show_button.show();
228 that.$show_button.show();
228 that.$close.show();
229 that.$close.show();
229 }
230 }
230 event.stopPropagation();
231 event.stopPropagation();
231 });
232 });
232 this.$title = $('<div />')
233 this.$title = $('<div />')
233 .addClass('widget-modal-title')
234 .addClass('widget-modal-title')
234 .html("&nbsp;")
235 .html("&nbsp;")
235 .appendTo(this.$title_bar);
236 .appendTo(this.$title_bar);
236 this.$box = $('<div />')
237 this.$box = $('<div />')
237 .addClass('modal-body')
238 .addClass('modal-body')
238 .addClass('widget-modal-body')
239 .addClass('widget-modal-body')
239 .addClass('widget-box')
240 .addClass('widget-box')
240 .addClass('vbox')
241 .addClass('vbox')
241 .appendTo(this.$window);
242 .appendTo(this.$window);
242
243
243 this.$show_button = $('<button />')
244 this.$show_button = $('<button />')
244 .html("&nbsp;")
245 .html("&nbsp;")
245 .addClass('btn btn-info widget-modal-show')
246 .addClass('btn btn-info widget-modal-show')
246 .appendTo(this.$el)
247 .appendTo(this.$el)
247 .click(function(){
248 .click(function(){
248 that.show();
249 that.show();
249 });
250 });
250
251
251 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
252 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
252 this.$window.resizable();
253 this.$window.resizable();
253 this.$window.on('resize', function(){
254 this.$window.on('resize', function(){
254 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
255 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
255 });
256 });
256
257
257 this._shown_once = false;
258 this._shown_once = false;
258 this.popped_out = true;
259 this.popped_out = true;
259
260
260 this.children_views.update(this.model.get('children'))
261 this.children_views.update(this.model.get('children'))
261 },
262 },
262
263
263 hide: function() {
264 hide: function() {
264 /**
265 /**
265 * Called when the modal hide button is clicked.
266 * Called when the modal hide button is clicked.
266 */
267 */
267 this.$window.hide();
268 this.$window.hide();
268 this.$show_button.removeClass('btn-info');
269 this.$show_button.removeClass('btn-info');
269 },
270 },
270
271
271 show: function() {
272 show: function() {
272 /**
273 /**
273 * Called when the modal show button is clicked.
274 * Called when the modal show button is clicked.
274 */
275 */
275 this.$show_button.addClass('btn-info');
276 this.$show_button.addClass('btn-info');
276 this.$window.show();
277 this.$window.show();
277 if (this.popped_out) {
278 if (this.popped_out) {
278 this.$window.css("positon", "absolute");
279 this.$window.css("positon", "absolute");
279 this.$window.css("top", "0px");
280 this.$window.css("top", "0px");
280 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
281 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
281 $(window).scrollLeft()) + "px");
282 $(window).scrollLeft()) + "px");
282 this.bring_to_front();
283 this.bring_to_front();
283 }
284 }
284 },
285 },
285
286
286 bring_to_front: function() {
287 bring_to_front: function() {
287 /**
288 /**
288 * Make the modal top-most, z-ordered about the other modals.
289 * Make the modal top-most, z-ordered about the other modals.
289 */
290 */
290 var $widget_modals = $(".widget-modal");
291 var $widget_modals = $(".widget-modal");
291 var max_zindex = 0;
292 var max_zindex = 0;
292 $widget_modals.each(function (index, el){
293 $widget_modals.each(function (index, el){
293 var zindex = parseInt($(el).css('z-index'));
294 var zindex = parseInt($(el).css('z-index'));
294 if (!isNaN(zindex)) {
295 if (!isNaN(zindex)) {
295 max_zindex = Math.max(max_zindex, zindex);
296 max_zindex = Math.max(max_zindex, zindex);
296 }
297 }
297 });
298 });
298
299
299 // Start z-index of widget modals at 2000
300 // Start z-index of widget modals at 2000
300 max_zindex = Math.max(max_zindex, 2000);
301 max_zindex = Math.max(max_zindex, 2000);
301
302
302 $widget_modals.each(function (index, el){
303 $widget_modals.each(function (index, el){
303 $el = $(el);
304 $el = $(el);
304 if (max_zindex == parseInt($el.css('z-index'))) {
305 if (max_zindex == parseInt($el.css('z-index'))) {
305 $el.css('z-index', max_zindex - 1);
306 $el.css('z-index', max_zindex - 1);
306 }
307 }
307 });
308 });
308 this.$window.css('z-index', max_zindex);
309 this.$window.css('z-index', max_zindex);
309 },
310 },
310
311
311 update: function(){
312 update: function(){
312 /**
313 /**
313 * Update the contents of this view
314 * Update the contents of this view
314 *
315 *
315 * Called when the model is changed. The model may have been
316 * Called when the model is changed. The model may have been
316 * changed by another view or by a state update from the back-end.
317 * changed by another view or by a state update from the back-end.
317 */
318 */
318 var description = this.model.get('description');
319 var description = this.model.get('description');
319 if (description.trim().length === 0) {
320 if (description.trim().length === 0) {
320 this.$title.html("&nbsp;"); // Preserve title height
321 this.$title.html("&nbsp;"); // Preserve title height
321 } else {
322 } else {
322 this.typeset(this.$title, description);
323 this.typeset(this.$title, description);
323 }
324 }
324
325
325 var button_text = this.model.get('button_text');
326 var button_text = this.model.get('button_text');
326 if (button_text.trim().length === 0) {
327 if (button_text.trim().length === 0) {
327 this.$show_button.html("&nbsp;"); // Preserve button height
328 this.$show_button.html("&nbsp;"); // Preserve button height
328 } else {
329 } else {
329 this.$show_button.text(button_text);
330 this.$show_button.text(button_text);
330 }
331 }
331
332
332 if (!this._shown_once) {
333 if (!this._shown_once) {
333 this._shown_once = true;
334 this._shown_once = true;
334 this.show();
335 this.show();
335 }
336 }
336
337
337 return PopupView.__super__.update.apply(this);
338 return PopupView.__super__.update.apply(this);
338 },
339 },
339
340
340 _get_selector_element: function(selector) {
341 _get_selector_element: function(selector) {
341 /**
342 /**
342 * Get an element view a 'special' jquery selector. (see widget.js)
343 * Get an element view a 'special' jquery selector. (see widget.js)
343 *
344 *
344 * Since the modal actually isn't within the $el in the DOM, we need to extend
345 * Since the modal actually isn't within the $el in the DOM, we need to extend
345 * the selector logic to allow the user to set css on the modal if need be.
346 * the selector logic to allow the user to set css on the modal if need be.
346 * The convention used is:
347 * The convention used is:
347 * "modal" - select the modal div
348 * "modal" - select the modal div
348 * "modal [selector]" - select element(s) within the modal div.
349 * "modal [selector]" - select element(s) within the modal div.
349 * "[selector]" - select elements within $el
350 * "[selector]" - select elements within $el
350 * "" - select the $el
351 * "" - select the $el
351 */
352 */
352 if (selector.substring(0, 5) == 'modal') {
353 if (selector.substring(0, 5) == 'modal') {
353 if (selector == 'modal') {
354 if (selector == 'modal') {
354 return this.$window;
355 return this.$window;
355 } else {
356 } else {
356 return this.$window.find(selector.substring(6));
357 return this.$window.find(selector.substring(6));
357 }
358 }
358 } else {
359 } else {
359 return PopupView.__super__._get_selector_element.apply(this, [selector]);
360 return PopupView.__super__._get_selector_element.apply(this, [selector]);
360 }
361 }
361 },
362 },
362 });
363 });
363
364
364 return {
365 return {
365 'BoxView': BoxView,
366 'BoxView': BoxView,
366 'PopupView': PopupView,
367 'PopupView': PopupView,
367 'FlexBoxView': FlexBoxView,
368 'FlexBoxView': FlexBoxView,
368 };
369 };
369 });
370 });
@@ -1,490 +1,491 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "jqueryui",
6 "jqueryui",
7 "base/js/keyboard",
7 "base/js/keyboard",
8 "bootstrap"
8 "bootstrap"
9 ], function(widget, $, keyboard){
9 ], function(widget, $, keyboard){
10
10
11 var IntSliderView = widget.DOMWidgetView.extend({
11 var IntSliderView = widget.DOMWidgetView.extend({
12 render : function(){
12 render : function(){
13 /**
13 /**
14 * Called when view is rendered.
14 * Called when view is rendered.
15 */
15 */
16 this.$el
16 this.$el
17 .addClass('widget-hbox widget-slider');
17 .addClass('widget-hbox widget-slider');
18 this.$label = $('<div />')
18 this.$label = $('<div />')
19 .appendTo(this.$el)
19 .appendTo(this.$el)
20 .addClass('widget-label')
20 .addClass('widget-label')
21 .hide();
21 .hide();
22
22
23 this.$slider = $('<div />')
23 this.$slider = $('<div />')
24 .slider({})
24 .slider({})
25 .addClass('slider');
25 .addClass('slider');
26 // Put the slider in a container
26 // Put the slider in a container
27 this.$slider_container = $('<div />')
27 this.$slider_container = $('<div />')
28 .addClass('widget-hslider')
28 .addClass('widget-hslider')
29 .append(this.$slider);
29 .append(this.$slider);
30 this.$el.append(this.$slider_container);
30 this.$el.append(this.$slider_container);
31
31
32 this.$readout = $('<div/>')
32 this.$readout = $('<div/>')
33 .appendTo(this.$el)
33 .appendTo(this.$el)
34 .addClass('widget-readout')
34 .addClass('widget-readout')
35 .attr('contentEditable', true)
35 .attr('contentEditable', true)
36 .hide();
36 .hide();
37
37
38 this.model.on('change:slider_color', function(sender, value) {
38 this.model.on('change:slider_color', function(sender, value) {
39 this.$slider.find('a').css('background', value);
39 this.$slider.find('a').css('background', value);
40 }, this);
40 }, this);
41 this.$slider.find('a').css('background', this.model.get('slider_color'));
41 this.$slider.find('a').css('background', this.model.get('slider_color'));
42
42
43 // Set defaults.
43 // Set defaults.
44 this.update();
44 this.update();
45 },
45 },
46
46
47 update_attr: function(name, value) {
47 update_attr: function(name, value) {
48 /**
48 /**
49 * Set a css attr of the widget view.
49 * Set a css attr of the widget view.
50 */
50 */
51 if (name == 'color') {
51 if (name == 'color') {
52 this.$readout.css(name, value);
52 this.$readout.css(name, value);
53 } else if (name.substring(0, 4) == 'font') {
53 } else if (name.substring(0, 4) == 'font') {
54 this.$readout.css(name, value);
54 this.$readout.css(name, value);
55 } else if (name.substring(0, 6) == 'border') {
55 } else if (name.substring(0, 6) == 'border') {
56 this.$slider.find('a').css(name, value);
56 this.$slider.find('a').css(name, value);
57 this.$slider_container.css(name, value);
57 this.$slider_container.css(name, value);
58 } else if (name == 'width' || name == 'height' || name == 'background') {
58 } else if (name == 'width' || name == 'height' || name == 'background') {
59 this.$slider_container.css(name, value);
59 this.$slider_container.css(name, value);
60 } else if (name == 'padding' || name == 'margin') {
60 } else if (name == 'padding' || name == 'margin') {
61 this.$el.css(name, value);
61 this.$el.css(name, value);
62 } else {
62 } else {
63 this.$slider.css(name, value);
63 this.$slider.css(name, value);
64 }
64 }
65 },
65 },
66
66
67 update : function(options){
67 update : function(options){
68 /**
68 /**
69 * Update the contents of this view
69 * Update the contents of this view
70 *
70 *
71 * Called when the model is changed. The model may have been
71 * Called when the model is changed. The model may have been
72 * changed by another view or by a state update from the back-end.
72 * changed by another view or by a state update from the back-end.
73 */
73 */
74 if (options === undefined || options.updated_view != this) {
74 if (options === undefined || options.updated_view != this) {
75 // JQuery slider option keys. These keys happen to have a
75 // JQuery slider option keys. These keys happen to have a
76 // one-to-one mapping with the corrosponding keys of the model.
76 // one-to-one mapping with the corrosponding keys of the model.
77 var jquery_slider_keys = ['step', 'disabled'];
77 var jquery_slider_keys = ['step', 'disabled'];
78 var that = this;
78 var that = this;
79 that.$slider.slider({});
79 that.$slider.slider({});
80 _.each(jquery_slider_keys, function(key, i) {
80 _.each(jquery_slider_keys, function(key, i) {
81 var model_value = that.model.get(key);
81 var model_value = that.model.get(key);
82 if (model_value !== undefined) {
82 if (model_value !== undefined) {
83 that.$slider.slider("option", key, model_value);
83 that.$slider.slider("option", key, model_value);
84 }
84 }
85 });
85 });
86
86
87 var max = this.model.get('max');
87 var max = this.model.get('max');
88 var min = this.model.get('min');
88 var min = this.model.get('min');
89 if (min <= max) {
89 if (min <= max) {
90 if (max !== undefined) this.$slider.slider('option', 'max', max);
90 if (max !== undefined) this.$slider.slider('option', 'max', max);
91 if (min !== undefined) this.$slider.slider('option', 'min', min);
91 if (min !== undefined) this.$slider.slider('option', 'min', min);
92 }
92 }
93
93
94 var range_value = this.model.get("_range");
94 var range_value = this.model.get("_range");
95 if (range_value !== undefined) {
95 if (range_value !== undefined) {
96 this.$slider.slider("option", "range", range_value);
96 this.$slider.slider("option", "range", range_value);
97 }
97 }
98
98
99 // WORKAROUND FOR JQUERY SLIDER BUG.
99 // WORKAROUND FOR JQUERY SLIDER BUG.
100 // The horizontal position of the slider handle
100 // The horizontal position of the slider handle
101 // depends on the value of the slider at the time
101 // depends on the value of the slider at the time
102 // of orientation change. Before applying the new
102 // of orientation change. Before applying the new
103 // workaround, we set the value to the minimum to
103 // workaround, we set the value to the minimum to
104 // make sure that the horizontal placement of the
104 // make sure that the horizontal placement of the
105 // handle in the vertical slider is always
105 // handle in the vertical slider is always
106 // consistent.
106 // consistent.
107 var orientation = this.model.get('orientation');
107 var orientation = this.model.get('orientation');
108 var min = this.model.get('min');
108 var min = this.model.get('min');
109 var max = this.model.get('max');
109 var max = this.model.get('max');
110 if (this.model.get('_range')) {
110 if (this.model.get('_range')) {
111 this.$slider.slider('option', 'values', [min, min]);
111 this.$slider.slider('option', 'values', [min, min]);
112 } else {
112 } else {
113 this.$slider.slider('option', 'value', min);
113 this.$slider.slider('option', 'value', min);
114 }
114 }
115 this.$slider.slider('option', 'orientation', orientation);
115 this.$slider.slider('option', 'orientation', orientation);
116 var value = this.model.get('value');
116 var value = this.model.get('value');
117 if (this.model.get('_range')) {
117 if (this.model.get('_range')) {
118 // values for the range case are validated python-side in
118 // values for the range case are validated python-side in
119 // _Bounded{Int,Float}RangeWidget._validate
119 // _Bounded{Int,Float}RangeWidget._validate
120 this.$slider.slider('option', 'values', value);
120 this.$slider.slider('option', 'values', value);
121 this.$readout.text(value.join("-"));
121 this.$readout.text(value.join("-"));
122 } else {
122 } else {
123 if(value > max) {
123 if(value > max) {
124 value = max;
124 value = max;
125 }
125 }
126 else if(value < min){
126 else if(value < min){
127 value = min;
127 value = min;
128 }
128 }
129 this.$slider.slider('option', 'value', value);
129 this.$slider.slider('option', 'value', value);
130 this.$readout.text(value);
130 this.$readout.text(value);
131 }
131 }
132
132
133 if(this.model.get('value')!=value) {
133 if(this.model.get('value')!=value) {
134 this.model.set('value', value, {updated_view: this});
134 this.model.set('value', value, {updated_view: this});
135 this.touch();
135 this.touch();
136 }
136 }
137
137
138 // Use the right CSS classes for vertical & horizontal sliders
138 // Use the right CSS classes for vertical & horizontal sliders
139 if (orientation=='vertical') {
139 if (orientation=='vertical') {
140 this.$slider_container
140 this.$slider_container
141 .removeClass('widget-hslider')
141 .removeClass('widget-hslider')
142 .addClass('widget-vslider');
142 .addClass('widget-vslider');
143 this.$el
143 this.$el
144 .removeClass('widget-hbox')
144 .removeClass('widget-hbox')
145 .addClass('widget-vbox');
145 .addClass('widget-vbox');
146
146
147 } else {
147 } else {
148 this.$slider_container
148 this.$slider_container
149 .removeClass('widget-vslider')
149 .removeClass('widget-vslider')
150 .addClass('widget-hslider');
150 .addClass('widget-hslider');
151 this.$el
151 this.$el
152 .removeClass('widget-vbox')
152 .removeClass('widget-vbox')
153 .addClass('widget-hbox');
153 .addClass('widget-hbox');
154 }
154 }
155
155
156 var description = this.model.get('description');
156 var description = this.model.get('description');
157 if (description.length === 0) {
157 if (description.length === 0) {
158 this.$label.hide();
158 this.$label.hide();
159 } else {
159 } else {
160 this.typeset(this.$label, description);
160 this.typeset(this.$label, description);
161 this.$label.show();
161 this.$label.show();
162 }
162 }
163
163
164 var readout = this.model.get('readout');
164 var readout = this.model.get('readout');
165 if (readout) {
165 if (readout) {
166 this.$readout.show();
166 this.$readout.show();
167 } else {
167 } else {
168 this.$readout.hide();
168 this.$readout.hide();
169 }
169 }
170 }
170 }
171 return IntSliderView.__super__.update.apply(this);
171 return IntSliderView.__super__.update.apply(this);
172 },
172 },
173
173
174 events: {
174 events: {
175 // Dictionary of events and their handlers.
175 // Dictionary of events and their handlers.
176 "slide" : "handleSliderChange",
176 "slide" : "handleSliderChange",
177 "blur [contentEditable=true]": "handleTextChange",
177 "blur [contentEditable=true]": "handleTextChange",
178 "keydown [contentEditable=true]": "handleKeyDown"
178 "keydown [contentEditable=true]": "handleKeyDown"
179 },
179 },
180
180
181 handleKeyDown: function(e) {
181 handleKeyDown: function(e) {
182 if (e.keyCode == keyboard.keycodes.enter) {
182 if (e.keyCode == keyboard.keycodes.enter) {
183 e.preventDefault();
183 e.preventDefault();
184 this.handleTextChange();
184 this.handleTextChange();
185 }
185 }
186 },
186 },
187
187
188 handleTextChange: function() {
188 handleTextChange: function() {
189 /**
189 /**
190 * this handles the entry of text into the contentEditable label
190 * this handles the entry of text into the contentEditable label
191 * first, the value is checked if it contains a parseable number
191 * first, the value is checked if it contains a parseable number
192 * (or pair of numbers, for the _range case)
192 * (or pair of numbers, for the _range case)
193 * then it is clamped within the min-max range of the slider
193 * then it is clamped within the min-max range of the slider
194 * finally, the model is updated if the value is to be changed
194 * finally, the model is updated if the value is to be changed
195 *
195 *
196 * if any of these conditions are not met, the text is reset
196 * if any of these conditions are not met, the text is reset
197 *
197 *
198 * the step size is not enforced
198 * the step size is not enforced
199 */
199 */
200
200
201 var text = this.$readout.text();
201 var text = this.$readout.text();
202 var vmin = this.model.get('min');
202 var vmin = this.model.get('min');
203 var vmax = this.model.get('max');
203 var vmax = this.model.get('max');
204 if (this.model.get("_range")) {
204 if (this.model.get("_range")) {
205 // range case
205 // range case
206 // ranges can be expressed either "val-val" or "val:val" (+spaces)
206 // ranges can be expressed either "val-val" or "val:val" (+spaces)
207 var match = this._range_regex.exec(text);
207 var match = this._range_regex.exec(text);
208 if (match) {
208 if (match) {
209 var values = [this._parse_value(match[1]),
209 var values = [this._parse_value(match[1]),
210 this._parse_value(match[2])];
210 this._parse_value(match[2])];
211 // reject input where NaN or lower > upper
211 // reject input where NaN or lower > upper
212 if (isNaN(values[0]) ||
212 if (isNaN(values[0]) ||
213 isNaN(values[1]) ||
213 isNaN(values[1]) ||
214 (values[0] > values[1])) {
214 (values[0] > values[1])) {
215 this.$readout.text(this.model.get('value').join('-'));
215 this.$readout.text(this.model.get('value').join('-'));
216 } else {
216 } else {
217 // clamp to range
217 // clamp to range
218 values = [Math.max(Math.min(values[0], vmax), vmin),
218 values = [Math.max(Math.min(values[0], vmax), vmin),
219 Math.max(Math.min(values[1], vmax), vmin)];
219 Math.max(Math.min(values[1], vmax), vmin)];
220
220
221 if ((values[0] != this.model.get('value')[0]) ||
221 if ((values[0] != this.model.get('value')[0]) ||
222 (values[1] != this.model.get('value')[1])) {
222 (values[1] != this.model.get('value')[1])) {
223 this.$readout.text(values.join('-'));
223 this.$readout.text(values.join('-'));
224 this.model.set('value', values, {updated_view: this});
224 this.model.set('value', values, {updated_view: this});
225 this.touch();
225 this.touch();
226 } else {
226 } else {
227 this.$readout.text(this.model.get('value').join('-'));
227 this.$readout.text(this.model.get('value').join('-'));
228 }
228 }
229 }
229 }
230 } else {
230 } else {
231 this.$readout.text(this.model.get('value').join('-'));
231 this.$readout.text(this.model.get('value').join('-'));
232 }
232 }
233 } else {
233 } else {
234 // single value case
234 // single value case
235 var value = this._parse_value(text);
235 var value = this._parse_value(text);
236 if (isNaN(value)) {
236 if (isNaN(value)) {
237 this.$readout.text(this.model.get('value'));
237 this.$readout.text(this.model.get('value'));
238 } else {
238 } else {
239 value = Math.max(Math.min(value, vmax), vmin);
239 value = Math.max(Math.min(value, vmax), vmin);
240
240
241 if (value != this.model.get('value')) {
241 if (value != this.model.get('value')) {
242 this.$readout.text(value);
242 this.$readout.text(value);
243 this.model.set('value', value, {updated_view: this});
243 this.model.set('value', value, {updated_view: this});
244 this.touch();
244 this.touch();
245 } else {
245 } else {
246 this.$readout.text(this.model.get('value'));
246 this.$readout.text(this.model.get('value'));
247 }
247 }
248 }
248 }
249 }
249 }
250 },
250 },
251
251
252 _parse_value: parseInt,
252 _parse_value: parseInt,
253
253
254 _range_regex: /^\s*([+-]?\d+)\s*[-:]\s*([+-]?\d+)/,
254 _range_regex: /^\s*([+-]?\d+)\s*[-:]\s*([+-]?\d+)/,
255
255
256 handleSliderChange: function(e, ui) {
256 handleSliderChange: function(e, ui) {
257 /**
257 /**
258 * Called when the slider value is changed.
258 * Called when the slider value is changed.
259 *
259 *
260 * Calling model.set will trigger all of the other views of the
260 * Calling model.set will trigger all of the other views of the
261 * model to update.
261 * model to update.
262 */
262 */
263 var actual_value;
263 if (this.model.get("_range")) {
264 if (this.model.get("_range")) {
264 var actual_value = ui.values.map(this._validate_slide_value);
265 actual_value = ui.values.map(this._validate_slide_value);
265 this.$readout.text(actual_value.join("-"));
266 this.$readout.text(actual_value.join("-"));
266 } else {
267 } else {
267 var actual_value = this._validate_slide_value(ui.value);
268 actual_value = this._validate_slide_value(ui.value);
268 this.$readout.text(actual_value);
269 this.$readout.text(actual_value);
269 }
270 }
270 this.model.set('value', actual_value, {updated_view: this});
271 this.model.set('value', actual_value, {updated_view: this});
271 this.touch();
272 this.touch();
272 },
273 },
273
274
274 _validate_slide_value: function(x) {
275 _validate_slide_value: function(x) {
275 /**
276 /**
276 * Validate the value of the slider before sending it to the back-end
277 * Validate the value of the slider before sending it to the back-end
277 * and applying it to the other views on the page.
278 * and applying it to the other views on the page.
278 *
279 *
279 * Double bit-wise not truncates the decimel (int cast).
280 * Double bit-wise not truncates the decimel (int cast).
280 */
281 */
281 return ~~x;
282 return ~~x;
282 },
283 },
283 });
284 });
284
285
285
286
286 var IntTextView = widget.DOMWidgetView.extend({
287 var IntTextView = widget.DOMWidgetView.extend({
287 render : function(){
288 render : function(){
288 /**
289 /**
289 * Called when view is rendered.
290 * Called when view is rendered.
290 */
291 */
291 this.$el
292 this.$el
292 .addClass('widget-hbox widget-text');
293 .addClass('widget-hbox widget-text');
293 this.$label = $('<div />')
294 this.$label = $('<div />')
294 .appendTo(this.$el)
295 .appendTo(this.$el)
295 .addClass('widget-label')
296 .addClass('widget-label')
296 .hide();
297 .hide();
297 this.$textbox = $('<input type="text" />')
298 this.$textbox = $('<input type="text" />')
298 .addClass('form-control')
299 .addClass('form-control')
299 .addClass('widget-numeric-text')
300 .addClass('widget-numeric-text')
300 .appendTo(this.$el);
301 .appendTo(this.$el);
301 this.update(); // Set defaults.
302 this.update(); // Set defaults.
302 },
303 },
303
304
304 update : function(options){
305 update : function(options){
305 /**
306 /**
306 * Update the contents of this view
307 * Update the contents of this view
307 *
308 *
308 * Called when the model is changed. The model may have been
309 * Called when the model is changed. The model may have been
309 * changed by another view or by a state update from the back-end.
310 * changed by another view or by a state update from the back-end.
310 */
311 */
311 if (options === undefined || options.updated_view != this) {
312 if (options === undefined || options.updated_view != this) {
312 var value = this.model.get('value');
313 var value = this.model.get('value');
313 if (this._parse_value(this.$textbox.val()) != value) {
314 if (this._parse_value(this.$textbox.val()) != value) {
314 this.$textbox.val(value);
315 this.$textbox.val(value);
315 }
316 }
316
317
317 if (this.model.get('disabled')) {
318 if (this.model.get('disabled')) {
318 this.$textbox.attr('disabled','disabled');
319 this.$textbox.attr('disabled','disabled');
319 } else {
320 } else {
320 this.$textbox.removeAttr('disabled');
321 this.$textbox.removeAttr('disabled');
321 }
322 }
322
323
323 var description = this.model.get('description');
324 var description = this.model.get('description');
324 if (description.length === 0) {
325 if (description.length === 0) {
325 this.$label.hide();
326 this.$label.hide();
326 } else {
327 } else {
327 this.typeset(this.$label, description);
328 this.typeset(this.$label, description);
328 this.$label.show();
329 this.$label.show();
329 }
330 }
330 }
331 }
331 return IntTextView.__super__.update.apply(this);
332 return IntTextView.__super__.update.apply(this);
332 },
333 },
333
334
334 update_attr: function(name, value) {
335 update_attr: function(name, value) {
335 /**
336 /**
336 * Set a css attr of the widget view.
337 * Set a css attr of the widget view.
337 */
338 */
338 if (name == 'padding' || name == 'margin') {
339 if (name == 'padding' || name == 'margin') {
339 this.$el.css(name, value);
340 this.$el.css(name, value);
340 } else {
341 } else {
341 this.$textbox.css(name, value);
342 this.$textbox.css(name, value);
342 }
343 }
343 },
344 },
344
345
345 events: {
346 events: {
346 // Dictionary of events and their handlers.
347 // Dictionary of events and their handlers.
347 "keyup input" : "handleChanging",
348 "keyup input" : "handleChanging",
348 "paste input" : "handleChanging",
349 "paste input" : "handleChanging",
349 "cut input" : "handleChanging",
350 "cut input" : "handleChanging",
350
351
351 // Fires only when control is validated or looses focus.
352 // Fires only when control is validated or looses focus.
352 "change input" : "handleChanged"
353 "change input" : "handleChanged"
353 },
354 },
354
355
355 handleChanging: function(e) {
356 handleChanging: function(e) {
356 /**
357 /**
357 * Handles and validates user input.
358 * Handles and validates user input.
358 *
359 *
359 * Try to parse value as a int.
360 * Try to parse value as a int.
360 */
361 */
361 var numericalValue = 0;
362 var numericalValue = 0;
362 var trimmed = e.target.value.trim();
363 var trimmed = e.target.value.trim();
363 if (trimmed === '') {
364 if (trimmed === '') {
364 return;
365 return;
365 } else {
366 } else {
366 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
367 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
367 numericalValue = this._parse_value(e.target.value);
368 numericalValue = this._parse_value(e.target.value);
368 }
369 }
369 }
370 }
370
371
371 // If parse failed, reset value to value stored in model.
372 // If parse failed, reset value to value stored in model.
372 if (isNaN(numericalValue)) {
373 if (isNaN(numericalValue)) {
373 e.target.value = this.model.get('value');
374 e.target.value = this.model.get('value');
374 } else if (!isNaN(numericalValue)) {
375 } else if (!isNaN(numericalValue)) {
375 if (this.model.get('max') !== undefined) {
376 if (this.model.get('max') !== undefined) {
376 numericalValue = Math.min(this.model.get('max'), numericalValue);
377 numericalValue = Math.min(this.model.get('max'), numericalValue);
377 }
378 }
378 if (this.model.get('min') !== undefined) {
379 if (this.model.get('min') !== undefined) {
379 numericalValue = Math.max(this.model.get('min'), numericalValue);
380 numericalValue = Math.max(this.model.get('min'), numericalValue);
380 }
381 }
381
382
382 // Apply the value if it has changed.
383 // Apply the value if it has changed.
383 if (numericalValue != this.model.get('value')) {
384 if (numericalValue != this.model.get('value')) {
384
385
385 // Calling model.set will trigger all of the other views of the
386 // Calling model.set will trigger all of the other views of the
386 // model to update.
387 // model to update.
387 this.model.set('value', numericalValue, {updated_view: this});
388 this.model.set('value', numericalValue, {updated_view: this});
388 this.touch();
389 this.touch();
389 }
390 }
390 }
391 }
391 },
392 },
392
393
393 handleChanged: function(e) {
394 handleChanged: function(e) {
394 /**
395 /**
395 * Applies validated input.
396 * Applies validated input.
396 */
397 */
397 if (e.target.value.trim() === '' || e.target.value !== this.model.get('value')) {
398 if (e.target.value.trim() === '' || e.target.value !== this.model.get('value')) {
398 e.target.value = this.model.get('value');
399 e.target.value = this.model.get('value');
399 }
400 }
400 },
401 },
401
402
402 _parse_value: parseInt
403 _parse_value: parseInt
403 });
404 });
404
405
405
406
406 var ProgressView = widget.DOMWidgetView.extend({
407 var ProgressView = widget.DOMWidgetView.extend({
407 render : function(){
408 render : function(){
408 /**
409 /**
409 * Called when view is rendered.
410 * Called when view is rendered.
410 */
411 */
411 this.$el
412 this.$el
412 .addClass('widget-hbox widget-progress');
413 .addClass('widget-hbox widget-progress');
413 this.$label = $('<div />')
414 this.$label = $('<div />')
414 .appendTo(this.$el)
415 .appendTo(this.$el)
415 .addClass('widget-label')
416 .addClass('widget-label')
416 .hide();
417 .hide();
417 this.$progress = $('<div />')
418 this.$progress = $('<div />')
418 .addClass('progress')
419 .addClass('progress')
419 .addClass('widget-progress')
420 .addClass('widget-progress')
420 .appendTo(this.$el);
421 .appendTo(this.$el);
421 this.$bar = $('<div />')
422 this.$bar = $('<div />')
422 .addClass('progress-bar')
423 .addClass('progress-bar')
423 .css('width', '50%')
424 .css('width', '50%')
424 .appendTo(this.$progress);
425 .appendTo(this.$progress);
425 this.update(); // Set defaults.
426 this.update(); // Set defaults.
426
427
427 this.model.on('change:bar_style', function(model, value) {
428 this.model.on('change:bar_style', function(model, value) {
428 this.update_bar_style();
429 this.update_bar_style();
429 }, this);
430 }, this);
430 this.update_bar_style('');
431 this.update_bar_style('');
431 },
432 },
432
433
433 update : function(){
434 update : function(){
434 /**
435 /**
435 * Update the contents of this view
436 * Update the contents of this view
436 *
437 *
437 * Called when the model is changed. The model may have been
438 * Called when the model is changed. The model may have been
438 * changed by another view or by a state update from the back-end.
439 * changed by another view or by a state update from the back-end.
439 */
440 */
440 var value = this.model.get('value');
441 var value = this.model.get('value');
441 var max = this.model.get('max');
442 var max = this.model.get('max');
442 var min = this.model.get('min');
443 var min = this.model.get('min');
443 var percent = 100.0 * (value - min) / (max - min);
444 var percent = 100.0 * (value - min) / (max - min);
444 this.$bar.css('width', percent + '%');
445 this.$bar.css('width', percent + '%');
445
446
446 var description = this.model.get('description');
447 var description = this.model.get('description');
447 if (description.length === 0) {
448 if (description.length === 0) {
448 this.$label.hide();
449 this.$label.hide();
449 } else {
450 } else {
450 this.typeset(this.$label, description);
451 this.typeset(this.$label, description);
451 this.$label.show();
452 this.$label.show();
452 }
453 }
453 return ProgressView.__super__.update.apply(this);
454 return ProgressView.__super__.update.apply(this);
454 },
455 },
455
456
456 update_bar_style: function(previous_trait_value) {
457 update_bar_style: function(previous_trait_value) {
457 var class_map = {
458 var class_map = {
458 success: ['progress-bar-success'],
459 success: ['progress-bar-success'],
459 info: ['progress-bar-info'],
460 info: ['progress-bar-info'],
460 warning: ['progress-bar-warning'],
461 warning: ['progress-bar-warning'],
461 danger: ['progress-bar-danger']
462 danger: ['progress-bar-danger']
462 };
463 };
463 this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar);
464 this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar);
464 },
465 },
465
466
466 update_attr: function(name, value) {
467 update_attr: function(name, value) {
467 /**
468 /**
468 * Set a css attr of the widget view.
469 * Set a css attr of the widget view.
469 */
470 */
470 if (name.substring(0, 6) == 'border' || name == 'width' ||
471 if (name.substring(0, 6) == 'border' || name == 'width' ||
471 name == 'height' || name == 'background' || name == 'margin' ||
472 name == 'height' || name == 'background' || name == 'margin' ||
472 name == 'padding') {
473 name == 'padding') {
473
474
474 this.$progress.css(name, value);
475 this.$progress.css(name, value);
475 } else if (name == 'color') {
476 } else if (name == 'color') {
476 this.$bar.css('background', value);
477 this.$bar.css('background', value);
477 } else if (name == 'padding' || name == 'margin') {
478 } else if (name == 'padding' || name == 'margin') {
478 this.$el.css(name, value);
479 this.$el.css(name, value);
479 } else {
480 } else {
480 this.$bar.css(name, value);
481 this.$bar.css(name, value);
481 }
482 }
482 },
483 },
483 });
484 });
484
485
485 return {
486 return {
486 'IntSliderView': IntSliderView,
487 'IntSliderView': IntSliderView,
487 'IntTextView': IntTextView,
488 'IntTextView': IntTextView,
488 'ProgressView': ProgressView,
489 'ProgressView': ProgressView,
489 };
490 };
490 });
491 });
@@ -1,301 +1,300 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "base/js/utils",
6 "base/js/utils",
7 "jquery",
7 "jquery",
8 "bootstrap",
8 "bootstrap",
9 ], function(widget, utils, $){
9 ], function(widget, utils, $){
10
10
11 var AccordionView = widget.DOMWidgetView.extend({
11 var AccordionView = widget.DOMWidgetView.extend({
12 initialize: function(){
12 initialize: function(){
13 AccordionView.__super__.initialize.apply(this, arguments);
13 AccordionView.__super__.initialize.apply(this, arguments);
14
14
15 this.containers = [];
15 this.containers = [];
16 this.model_containers = {};
16 this.model_containers = {};
17 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
17 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
18 this.listenTo(this.model, 'change:children', function(model, value) {
18 this.listenTo(this.model, 'change:children', function(model, value) {
19 this.children_views.update(value);
19 this.children_views.update(value);
20 }, this);
20 }, this);
21 },
21 },
22
22
23 render: function(){
23 render: function(){
24 /**
24 /**
25 * Called when view is rendered.
25 * Called when view is rendered.
26 */
26 */
27 var guid = 'panel-group' + utils.uuid();
27 var guid = 'panel-group' + utils.uuid();
28 this.$el
28 this.$el
29 .attr('id', guid)
29 .attr('id', guid)
30 .addClass('panel-group');
30 .addClass('panel-group');
31 this.model.on('change:selected_index', function(model, value, options) {
31 this.model.on('change:selected_index', function(model, value, options) {
32 this.update_selected_index(model.previous('selected_index'), value, options);
32 this.update_selected_index(model.previous('selected_index'), value, options);
33 }, this);
33 }, this);
34 this.model.on('change:_titles', function(model, value, options) {
34 this.model.on('change:_titles', function(model, value, options) {
35 this.update_titles(value);
35 this.update_titles(value);
36 }, this);
36 }, this);
37 this.on('displayed', function() {
37 this.on('displayed', function() {
38 this.update_titles();
38 this.update_titles();
39 }, this);
39 }, this);
40 this.children_views.update(this.model.get('children'));
40 this.children_views.update(this.model.get('children'));
41 },
41 },
42
42
43 update_titles: function(titles) {
43 update_titles: function(titles) {
44 /**
44 /**
45 * Set tab titles
45 * Set tab titles
46 */
46 */
47 if (!titles) {
47 if (!titles) {
48 titles = this.model.get('_titles');
48 titles = this.model.get('_titles');
49 }
49 }
50
50
51 var that = this;
51 var that = this;
52 _.each(titles, function(title, page_index) {
52 _.each(titles, function(title, page_index) {
53 var accordian = that.containers[page_index];
53 var accordian = that.containers[page_index];
54 if (accordian !== undefined) {
54 if (accordian !== undefined) {
55 accordian
55 accordian
56 .find('.panel-heading')
56 .find('.panel-heading')
57 .find('.accordion-toggle')
57 .find('.accordion-toggle')
58 .text(title);
58 .text(title);
59 }
59 }
60 });
60 });
61 },
61 },
62
62
63 update_selected_index: function(old_index, new_index, options) {
63 update_selected_index: function(old_index, new_index, options) {
64 /**
64 /**
65 * Only update the selection if the selection wasn't triggered
65 * Only update the selection if the selection wasn't triggered
66 * by the front-end. It must be triggered by the back-end.
66 * by the front-end. It must be triggered by the back-end.
67 */
67 */
68 if (options === undefined || options.updated_view != this) {
68 if (options === undefined || options.updated_view != this) {
69 this.containers[old_index].find('.panel-collapse').collapse('hide');
69 this.containers[old_index].find('.panel-collapse').collapse('hide');
70 if (0 <= new_index && new_index < this.containers.length) {
70 if (0 <= new_index && new_index < this.containers.length) {
71 this.containers[new_index].find('.panel-collapse').collapse('show');
71 this.containers[new_index].find('.panel-collapse').collapse('show');
72 }
72 }
73 }
73 }
74 },
74 },
75
75
76 remove_child_view: function(view) {
76 remove_child_view: function(view) {
77 /**
77 /**
78 * Called when a child is removed from children list.
78 * Called when a child is removed from children list.
79 * TODO: does this handle two different views of the same model as children?
79 * TODO: does this handle two different views of the same model as children?
80 */
80 */
81 var model = view.model;
81 var model = view.model;
82 var accordion_group = this.model_containers[model.id];
82 var accordion_group = this.model_containers[model.id];
83 this.containers.splice(accordion_group.container_index, 1);
83 this.containers.splice(accordion_group.container_index, 1);
84 delete this.model_containers[model.id];
84 delete this.model_containers[model.id];
85 accordion_group.remove();
85 accordion_group.remove();
86 },
86 },
87
87
88 add_child_view: function(model) {
88 add_child_view: function(model) {
89 /**
89 /**
90 * Called when a child is added to children list.
90 * Called when a child is added to children list.
91 */
91 */
92 var index = this.containers.length;
92 var index = this.containers.length;
93 var uuid = utils.uuid();
93 var uuid = utils.uuid();
94 var accordion_group = $('<div />')
94 var accordion_group = $('<div />')
95 .addClass('panel panel-default')
95 .addClass('panel panel-default')
96 .appendTo(this.$el);
96 .appendTo(this.$el);
97 var accordion_heading = $('<div />')
97 var accordion_heading = $('<div />')
98 .addClass('panel-heading')
98 .addClass('panel-heading')
99 .appendTo(accordion_group);
99 .appendTo(accordion_group);
100 var that = this;
100 var that = this;
101 var accordion_toggle = $('<a />')
101 var accordion_toggle = $('<a />')
102 .addClass('accordion-toggle')
102 .addClass('accordion-toggle')
103 .attr('data-toggle', 'collapse')
103 .attr('data-toggle', 'collapse')
104 .attr('data-parent', '#' + this.$el.attr('id'))
104 .attr('data-parent', '#' + this.$el.attr('id'))
105 .attr('href', '#' + uuid)
105 .attr('href', '#' + uuid)
106 .click(function(evt){
106 .click(function(evt){
107
107
108 // Calling model.set will trigger all of the other views of the
108 // Calling model.set will trigger all of the other views of the
109 // model to update.
109 // model to update.
110 that.model.set("selected_index", index, {updated_view: that});
110 that.model.set("selected_index", index, {updated_view: that});
111 that.touch();
111 that.touch();
112 })
112 })
113 .text('Page ' + index)
113 .text('Page ' + index)
114 .appendTo(accordion_heading);
114 .appendTo(accordion_heading);
115 var accordion_body = $('<div />', {id: uuid})
115 var accordion_body = $('<div />', {id: uuid})
116 .addClass('panel-collapse collapse')
116 .addClass('panel-collapse collapse')
117 .appendTo(accordion_group);
117 .appendTo(accordion_group);
118 var accordion_inner = $('<div />')
118 var accordion_inner = $('<div />')
119 .addClass('panel-body')
119 .addClass('panel-body')
120 .appendTo(accordion_body);
120 .appendTo(accordion_body);
121 var container_index = this.containers.push(accordion_group) - 1;
121 var container_index = this.containers.push(accordion_group) - 1;
122 accordion_group.container_index = container_index;
122 accordion_group.container_index = container_index;
123 this.model_containers[model.id] = accordion_group;
123 this.model_containers[model.id] = accordion_group;
124
124
125 var dummy = $('<div/>');
125 var dummy = $('<div/>');
126 accordion_inner.append(dummy);
126 accordion_inner.append(dummy);
127 return this.create_child_view(model).then(function(view) {
127 return this.create_child_view(model).then(function(view) {
128 dummy.replaceWith(view.$el);
128 dummy.replaceWith(view.$el);
129 that.update();
129 that.update();
130 that.update_titles();
130 that.update_titles();
131
131
132 // Trigger the displayed event of the child view.
132 // Trigger the displayed event of the child view.
133 that.after_displayed(function() {
133 that.after_displayed(function() {
134 view.trigger('displayed');
134 view.trigger('displayed');
135 });
135 });
136 return view;
136 return view;
137 }).catch(utils.reject("Couldn't add child view to box", true));
137 }).catch(utils.reject("Couldn't add child view to box", true));
138 },
138 },
139
139
140 remove: function() {
140 remove: function() {
141 /**
141 /**
142 * We remove this widget before removing the children as an optimization
142 * We remove this widget before removing the children as an optimization
143 * we want to remove the entire container from the DOM first before
143 * we want to remove the entire container from the DOM first before
144 * removing each individual child separately.
144 * removing each individual child separately.
145 */
145 */
146 AccordionView.__super__.remove.apply(this, arguments);
146 AccordionView.__super__.remove.apply(this, arguments);
147 this.children_views.remove();
147 this.children_views.remove();
148 },
148 },
149 });
149 });
150
150
151
151
152 var TabView = widget.DOMWidgetView.extend({
152 var TabView = widget.DOMWidgetView.extend({
153 initialize: function() {
153 initialize: function() {
154 /**
154 /**
155 * Public constructor.
155 * Public constructor.
156 */
156 */
157 TabView.__super__.initialize.apply(this, arguments);
157 TabView.__super__.initialize.apply(this, arguments);
158
158
159 this.containers = [];
159 this.containers = [];
160 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
160 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
161 this.listenTo(this.model, 'change:children', function(model, value) {
161 this.listenTo(this.model, 'change:children', function(model, value) {
162 this.children_views.update(value);
162 this.children_views.update(value);
163 }, this);
163 }, this);
164 },
164 },
165
165
166 render: function(){
166 render: function(){
167 /**
167 /**
168 * Called when view is rendered.
168 * Called when view is rendered.
169 */
169 */
170 var uuid = 'tabs'+utils.uuid();
170 var uuid = 'tabs'+utils.uuid();
171 var that = this;
172 this.$tabs = $('<div />', {id: uuid})
171 this.$tabs = $('<div />', {id: uuid})
173 .addClass('nav')
172 .addClass('nav')
174 .addClass('nav-tabs')
173 .addClass('nav-tabs')
175 .appendTo(this.$el);
174 .appendTo(this.$el);
176 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
175 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
177 .addClass('tab-content')
176 .addClass('tab-content')
178 .appendTo(this.$el);
177 .appendTo(this.$el);
179 this.children_views.update(this.model.get('children'));
178 this.children_views.update(this.model.get('children'));
180 },
179 },
181
180
182 update_attr: function(name, value) {
181 update_attr: function(name, value) {
183 /**
182 /**
184 * Set a css attr of the widget view.
183 * Set a css attr of the widget view.
185 */
184 */
186 if (name == 'padding' || name == 'margin') {
185 if (name == 'padding' || name == 'margin') {
187 this.$el.css(name, value);
186 this.$el.css(name, value);
188 } else {
187 } else {
189 this.$tabs.css(name, value);
188 this.$tabs.css(name, value);
190 }
189 }
191 },
190 },
192
191
193 remove_child_view: function(view) {
192 remove_child_view: function(view) {
194 /**
193 /**
195 * Called when a child is removed from children list.
194 * Called when a child is removed from children list.
196 */
195 */
197 this.containers.splice(view.parent_tab.tab_text_index, 1);
196 this.containers.splice(view.parent_tab.tab_text_index, 1);
198 view.parent_tab.remove();
197 view.parent_tab.remove();
199 view.parent_container.remove();
198 view.parent_container.remove();
200 view.remove();
199 view.remove();
201 },
200 },
202
201
203 add_child_view: function(model) {
202 add_child_view: function(model) {
204 /**
203 /**
205 * Called when a child is added to children list.
204 * Called when a child is added to children list.
206 */
205 */
207 var index = this.containers.length;
206 var index = this.containers.length;
208 var uuid = utils.uuid();
207 var uuid = utils.uuid();
209
208
210 var that = this;
209 var that = this;
211 var tab = $('<li />')
210 var tab = $('<li />')
212 .css('list-style-type', 'none')
211 .css('list-style-type', 'none')
213 .appendTo(this.$tabs);
212 .appendTo(this.$tabs);
214
213
215
214
216 var tab_text = $('<a />')
215 var tab_text = $('<a />')
217 .attr('href', '#' + uuid)
216 .attr('href', '#' + uuid)
218 .attr('data-toggle', 'tab')
217 .attr('data-toggle', 'tab')
219 .text('Page ' + index)
218 .text('Page ' + index)
220 .appendTo(tab)
219 .appendTo(tab)
221 .click(function (e) {
220 .click(function (e) {
222
221
223 // Calling model.set will trigger all of the other views of the
222 // Calling model.set will trigger all of the other views of the
224 // model to update.
223 // model to update.
225 that.model.set("selected_index", index, {updated_view: that});
224 that.model.set("selected_index", index, {updated_view: that});
226 that.touch();
225 that.touch();
227 that.select_page(index);
226 that.select_page(index);
228 });
227 });
229 tab.tab_text_index = that.containers.push(tab_text) - 1;
228 tab.tab_text_index = that.containers.push(tab_text) - 1;
230
229
231 var dummy = $('<div />');
230 var dummy = $('<div />');
232 var contents_div = $('<div />', {id: uuid})
231 var contents_div = $('<div />', {id: uuid})
233 .addClass('tab-pane')
232 .addClass('tab-pane')
234 .addClass('fade')
233 .addClass('fade')
235 .append(dummy)
234 .append(dummy)
236 .appendTo(that.$tab_contents);
235 .appendTo(that.$tab_contents);
237
236
238 return this.create_child_view(model).then(function(view) {
237 return this.create_child_view(model).then(function(view) {
239 dummy.replaceWith(view.$el);
238 dummy.replaceWith(view.$el);
240 view.parent_tab = tab;
239 view.parent_tab = tab;
241 view.parent_container = contents_div;
240 view.parent_container = contents_div;
242
241
243 // Trigger the displayed event of the child view.
242 // Trigger the displayed event of the child view.
244 that.after_displayed(function() {
243 that.after_displayed(function() {
245 view.trigger('displayed');
244 view.trigger('displayed');
246 });
245 });
247 return view;
246 return view;
248 }).catch(utils.reject("Couldn't add child view to box", true));
247 }).catch(utils.reject("Couldn't add child view to box", true));
249 },
248 },
250
249
251 update: function(options) {
250 update: function(options) {
252 /**
251 /**
253 * Update the contents of this view
252 * Update the contents of this view
254 *
253 *
255 * Called when the model is changed. The model may have been
254 * Called when the model is changed. The model may have been
256 * changed by another view or by a state update from the back-end.
255 * changed by another view or by a state update from the back-end.
257 */
256 */
258 if (options === undefined || options.updated_view != this) {
257 if (options === undefined || options.updated_view != this) {
259 // Set tab titles
258 // Set tab titles
260 var titles = this.model.get('_titles');
259 var titles = this.model.get('_titles');
261 var that = this;
260 var that = this;
262 _.each(titles, function(title, page_index) {
261 _.each(titles, function(title, page_index) {
263 var tab_text = that.containers[page_index];
262 var tab_text = that.containers[page_index];
264 if (tab_text !== undefined) {
263 if (tab_text !== undefined) {
265 tab_text.text(title);
264 tab_text.text(title);
266 }
265 }
267 });
266 });
268
267
269 var selected_index = this.model.get('selected_index');
268 var selected_index = this.model.get('selected_index');
270 if (0 <= selected_index && selected_index < this.containers.length) {
269 if (0 <= selected_index && selected_index < this.containers.length) {
271 this.select_page(selected_index);
270 this.select_page(selected_index);
272 }
271 }
273 }
272 }
274 return TabView.__super__.update.apply(this);
273 return TabView.__super__.update.apply(this);
275 },
274 },
276
275
277 select_page: function(index) {
276 select_page: function(index) {
278 /**
277 /**
279 * Select a page.
278 * Select a page.
280 */
279 */
281 this.$tabs.find('li')
280 this.$tabs.find('li')
282 .removeClass('active');
281 .removeClass('active');
283 this.containers[index].tab('show');
282 this.containers[index].tab('show');
284 },
283 },
285
284
286 remove: function() {
285 remove: function() {
287 /**
286 /**
288 * We remove this widget before removing the children as an optimization
287 * We remove this widget before removing the children as an optimization
289 * we want to remove the entire container from the DOM first before
288 * we want to remove the entire container from the DOM first before
290 * removing each individual child separately.
289 * removing each individual child separately.
291 */
290 */
292 TabView.__super__.remove.apply(this, arguments);
291 TabView.__super__.remove.apply(this, arguments);
293 this.children_views.remove();
292 this.children_views.remove();
294 },
293 },
295 });
294 });
296
295
297 return {
296 return {
298 'AccordionView': AccordionView,
297 'AccordionView': AccordionView,
299 'TabView': TabView,
298 'TabView': TabView,
300 };
299 };
301 });
300 });
@@ -1,62 +1,63 b''
1 //
1 //
2 // Test that a Markdown cell is rendered to HTML.
2 // Test that a Markdown cell is rendered to HTML.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 "use strict";
5 // Test JavaScript models.
6 // Test JavaScript models.
6 var output = this.evaluate(function () {
7 var output = this.evaluate(function () {
7 IPython.notebook.to_markdown();
8 IPython.notebook.to_markdown();
8 var cell = IPython.notebook.get_selected_cell();
9 var cell = IPython.notebook.get_selected_cell();
9 cell.set_text('# Foo');
10 cell.set_text('# Foo');
10 cell.render();
11 cell.render();
11 return cell.get_rendered();
12 return cell.get_rendered();
12 });
13 });
13 this.test.assertEquals(output.trim(), '<h1 id=\"Foo\">Foo<a class=\"anchor-link\" href=\"#Foo\">ΒΆ</a></h1>', 'Markdown JS API works.');
14 this.test.assertEquals(output.trim(), '<h1 id=\"Foo\">Foo<a class=\"anchor-link\" href=\"#Foo\">ΒΆ</a></h1>', 'Markdown JS API works.');
14
15
15 // Test menubar entries.
16 // Test menubar entries.
16 output = this.evaluate(function () {
17 output = this.evaluate(function () {
17 $('#to_code').mouseenter().click();
18 $('#to_code').mouseenter().click();
18 $('#to_markdown').mouseenter().click();
19 $('#to_markdown').mouseenter().click();
19 var cell = IPython.notebook.get_selected_cell();
20 var cell = IPython.notebook.get_selected_cell();
20 cell.set_text('**Bar**');
21 cell.set_text('**Bar**');
21 $('#run_cell').mouseenter().click();
22 $('#run_cell').mouseenter().click();
22 return cell.get_rendered();
23 return cell.get_rendered();
23 });
24 });
24 this.test.assertEquals(output.trim(), '<p><strong>Bar</strong></p>', 'Markdown menubar items work.');
25 this.test.assertEquals(output.trim(), '<p><strong>Bar</strong></p>', 'Markdown menubar items work.');
25
26
26 // Test toolbar buttons.
27 // Test toolbar buttons.
27 output = this.evaluate(function () {
28 output = this.evaluate(function () {
28 $('#cell_type').val('code').change();
29 $('#cell_type').val('code').change();
29 $('#cell_type').val('markdown').change();
30 $('#cell_type').val('markdown').change();
30 var cell = IPython.notebook.get_selected_cell();
31 var cell = IPython.notebook.get_selected_cell();
31 cell.set_text('*Baz*');
32 cell.set_text('*Baz*');
32 $("button[data-jupyter-action='ipython.run-select-next']")[0].click();
33 $("button[data-jupyter-action='ipython.run-select-next']")[0].click();
33 return cell.get_rendered();
34 return cell.get_rendered();
34 });
35 });
35 this.test.assertEquals(output.trim(), '<p><em>Baz</em></p>', 'Markdown toolbar items work.');
36 this.test.assertEquals(output.trim(), '<p><em>Baz</em></p>', 'Markdown toolbar items work.');
36
37
37 // Test markdown headings
38 // Test markdown headings
38
39
39 var text = 'multi\nline';
40 var text = 'multi\nline';
40
41
41 this.evaluate(function (text) {
42 this.evaluate(function (text) {
42 var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
43 var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
43 cell.set_text(text);
44 cell.set_text(text);
44 }, {text: text});
45 }, {text: text});
45
46
46 var set_level = function (level) {
47 var set_level = function (level) {
47 return casper.evaluate(function (level) {
48 return casper.evaluate(function (level) {
48 var cell = IPython.notebook.get_cell(0);
49 var cell = IPython.notebook.get_cell(0);
49 cell.set_heading_level(level);
50 cell.set_heading_level(level);
50 return cell.get_text();
51 return cell.get_text();
51 }, {level: level});
52 }, {level: level});
52 };
53 };
53
54
54 var level_text;
55 var level_text;
55 var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ];
56 var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ];
56 for (var idx=0; idx < levels.length; idx++) {
57 for (var idx=0; idx < levels.length; idx++) {
57 var level = levels[idx];
58 var level = levels[idx];
58 level_text = set_level(level);
59 level_text = set_level(level);
59 hashes = new Array(level + 1).join('#');
60 var hashes = new Array(level + 1).join('#');
60 this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
61 this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
61 }
62 }
62 });
63 });
@@ -1,247 +1,247 b''
1 // Test opening a rich notebook, saving it, and reopening it again.
1 // Test opening a rich notebook, saving it, and reopening it again.
2 //
2 //
3 //toJSON fromJSON toJSON and do a string comparison
3 //toJSON fromJSON toJSON and do a string comparison
4
4
5
5
6 // this is just a copy of OutputArea.mime_mape_r in IPython/html/static/notebook/js/outputarea.js
6 // this is just a copy of OutputArea.mime_mape_r in IPython/html/static/notebook/js/outputarea.js
7 mime = {
7 mime = {
8 "text" : "text/plain",
8 "text" : "text/plain",
9 "html" : "text/html",
9 "html" : "text/html",
10 "svg" : "image/svg+xml",
10 "svg" : "image/svg+xml",
11 "png" : "image/png",
11 "png" : "image/png",
12 "jpeg" : "image/jpeg",
12 "jpeg" : "image/jpeg",
13 "latex" : "text/latex",
13 "latex" : "text/latex",
14 "json" : "application/json",
14 "json" : "application/json",
15 "javascript" : "application/javascript",
15 "javascript" : "application/javascript",
16 };
16 };
17
17
18 var black_dot_jpeg="u\"\"\"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDACodICUgGiolIiUvLSoyP2lEPzo6P4FcYUxpmYagnpaG\nk5GovfLNqLPltZGT0v/V5fr/////o8v///////L/////2wBDAS0vLz83P3xERHz/rpOu////////\n////////////////////////////////////////////////////////////wgARCAABAAEDAREA\nAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAABP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEA\nAhADEAAAARn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAA\nAAAAAAAA/9oACAEDAQE/AX//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/AX//xAAUEAEA\nAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nIX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEA\nAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nEH//2Q==\"\"\"";
18 var black_dot_jpeg="u\"\"\"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDACodICUgGiolIiUvLSoyP2lEPzo6P4FcYUxpmYagnpaG\nk5GovfLNqLPltZGT0v/V5fr/////o8v///////L/////2wBDAS0vLz83P3xERHz/rpOu////////\n////////////////////////////////////////////////////////////wgARCAABAAEDAREA\nAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAABP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEA\nAhADEAAAARn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAA\nAAAAAAAA/9oACAEDAQE/AX//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/AX//xAAUEAEA\nAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nIX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEA\nAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nEH//2Q==\"\"\"";
19 var black_dot_png = 'u\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QA\\niAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AA\\nAAACAAHiIbwzAAAAAElFTkSuQmCC\"';
19 var black_dot_png = 'u\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QA\\niAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AA\\nAAACAAHiIbwzAAAAAElFTkSuQmCC\"';
20 var svg = "\"<svg width='1cm' height='1cm' viewBox='0 0 1000 500'><defs><style>rect {fill:red;}; </style></defs><rect id='r1' x='200' y='100' width='600' height='300' /></svg>\"";
20 var svg = "\"<svg width='1cm' height='1cm' viewBox='0 0 1000 500'><defs><style>rect {fill:red;}; </style></defs><rect id='r1' x='200' y='100' width='600' height='300' /></svg>\"";
21
21
22 // helper function to ensure that the short_name is found in the toJSON
22 // helper function to ensure that the short_name is found in the toJSON
23 // represetnation, while the original in-memory cell retains its long mimetype
23 // represetnation, while the original in-memory cell retains its long mimetype
24 // name, and that fromJSON also gets its long mimetype name
24 // name, and that fromJSON also gets its long mimetype name
25 function assert_has(short_name, json, result, result2) {
25 function assert_has(short_name, json, result, result2) {
26 long_name = mime[short_name];
26 var long_name = mime[short_name];
27 this.test.assertFalse(json[0].data.hasOwnProperty(short_name),
27 this.test.assertFalse(json[0].data.hasOwnProperty(short_name),
28 "toJSON() representation doesn't use " + short_name);
28 "toJSON() representation doesn't use " + short_name);
29 this.test.assertTrue(json[0].data.hasOwnProperty(long_name),
29 this.test.assertTrue(json[0].data.hasOwnProperty(long_name),
30 'toJSON() representation uses ' + long_name);
30 'toJSON() representation uses ' + long_name);
31 this.test.assertTrue(result.data.hasOwnProperty(long_name),
31 this.test.assertTrue(result.data.hasOwnProperty(long_name),
32 'toJSON() original embedded JSON keeps ' + long_name);
32 'toJSON() original embedded JSON keeps ' + long_name);
33 this.test.assertTrue(result2.data.hasOwnProperty(long_name),
33 this.test.assertTrue(result2.data.hasOwnProperty(long_name),
34 'fromJSON() embedded ' + short_name + ' gets mime key ' + long_name);
34 'fromJSON() embedded ' + short_name + ' gets mime key ' + long_name);
35 }
35 }
36
36
37 // helper function for checkout that the first two cells have a particular
37 // helper function for checkout that the first two cells have a particular
38 // output_type (either 'execute_result' or 'display_data'), and checks the to/fromJSON
38 // output_type (either 'execute_result' or 'display_data'), and checks the to/fromJSON
39 // for a set of mimetype keys, ensuring the old short names ('javascript', 'text',
39 // for a set of mimetype keys, ensuring the old short names ('javascript', 'text',
40 // 'png', etc) are not used.
40 // 'png', etc) are not used.
41 function check_output_area(output_type, keys) {
41 function check_output_area(output_type, keys) {
42 this.wait_for_output(0);
42 this.wait_for_output(0);
43 json = this.evaluate(function() {
43 var json = this.evaluate(function() {
44 var json = IPython.notebook.get_cell(0).output_area.toJSON();
44 var json = IPython.notebook.get_cell(0).output_area.toJSON();
45 // appended cell will initially be empty, let's add some output
45 // appended cell will initially be empty, let's add some output
46 IPython.notebook.get_cell(1).output_area.fromJSON(json);
46 IPython.notebook.get_cell(1).output_area.fromJSON(json);
47 return json;
47 return json;
48 });
48 });
49 // The evaluate call above happens asynchronously: wait for cell[1] to have output
49 // The evaluate call above happens asynchronously: wait for cell[1] to have output
50 this.wait_for_output(1);
50 this.wait_for_output(1);
51 var result = this.get_output_cell(0);
51 var result = this.get_output_cell(0);
52 var result2 = this.get_output_cell(1);
52 var result2 = this.get_output_cell(1);
53 this.test.assertEquals(result.output_type, output_type,
53 this.test.assertEquals(result.output_type, output_type,
54 'testing ' + output_type + ' for ' + keys.join(' and '));
54 'testing ' + output_type + ' for ' + keys.join(' and '));
55
55
56 for (var idx in keys) {
56 for (var idx in keys) {
57 assert_has.apply(this, [keys[idx], json, result, result2]);
57 assert_has.apply(this, [keys[idx], json, result, result2]);
58 }
58 }
59 }
59 }
60
60
61
61
62 // helper function to clear the first two cells, set the text of and execute
62 // helper function to clear the first two cells, set the text of and execute
63 // the first one
63 // the first one
64 function clear_and_execute(that, code) {
64 function clear_and_execute(that, code) {
65 that.evaluate(function() {
65 that.evaluate(function() {
66 IPython.notebook.get_cell(0).clear_output();
66 IPython.notebook.get_cell(0).clear_output();
67 IPython.notebook.get_cell(1).clear_output();
67 IPython.notebook.get_cell(1).clear_output();
68 });
68 });
69 that.then(function () {
69 that.then(function () {
70 that.set_cell_text(0, code);
70 that.set_cell_text(0, code);
71 that.execute_cell(0);
71 that.execute_cell(0);
72 that.wait_for_idle();
72 that.wait_for_idle();
73 });
73 });
74 }
74 }
75
75
76 casper.notebook_test(function () {
76 casper.notebook_test(function () {
77 this.evaluate(function () {
77 this.evaluate(function () {
78 var cell = IPython.notebook.get_cell(0);
78 var cell = IPython.notebook.get_cell(0);
79 // "we have to make messes to find out who we are"
79 // "we have to make messes to find out who we are"
80 cell.set_text([
80 cell.set_text([
81 "%%javascript",
81 "%%javascript",
82 "IPython.notebook.insert_cell_below('code')"
82 "IPython.notebook.insert_cell_below('code')"
83 ].join('\n')
83 ].join('\n')
84 );
84 );
85 });
85 });
86
86
87 this.execute_cell_then(0, function () {
87 this.execute_cell_then(0, function () {
88 var result = this.get_output_cell(0);
88 var result = this.get_output_cell(0);
89 var num_cells = this.get_cells_length();
89 var num_cells = this.get_cells_length();
90 this.test.assertEquals(num_cells, 2, '%%javascript magic works');
90 this.test.assertEquals(num_cells, 2, '%%javascript magic works');
91 this.test.assertTrue(result.data.hasOwnProperty('application/javascript'),
91 this.test.assertTrue(result.data.hasOwnProperty('application/javascript'),
92 'testing JS embedded with mime key');
92 'testing JS embedded with mime key');
93 });
93 });
94
94
95 //this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
95 //this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
96 this.then(function () {
96 this.then(function () {
97 clear_and_execute(this, [
97 clear_and_execute(this, [
98 "%%javascript",
98 "%%javascript",
99 "var a=5;"
99 "var a=5;"
100 ].join('\n'));
100 ].join('\n'));
101 });
101 });
102
102
103
103
104 this.then(function () {
104 this.then(function () {
105 check_output_area.apply(this, ['display_data', ['javascript']]);
105 check_output_area.apply(this, ['display_data', ['javascript']]);
106
106
107 });
107 });
108
108
109 this.then(function() {
109 this.then(function() {
110 clear_and_execute(this, '%lsmagic');
110 clear_and_execute(this, '%lsmagic');
111 });
111 });
112
112
113 this.then(function () {
113 this.then(function () {
114 check_output_area.apply(this, ['execute_result', ['text', 'json']]);
114 check_output_area.apply(this, ['execute_result', ['text', 'json']]);
115 });
115 });
116
116
117 this.then(function() {
117 this.then(function() {
118 clear_and_execute(this,
118 clear_and_execute(this,
119 "x = %lsmagic\nfrom IPython.display import display; display(x)");
119 "x = %lsmagic\nfrom IPython.display import display; display(x)");
120 });
120 });
121
121
122 this.then(function ( ) {
122 this.then(function ( ) {
123 check_output_area.apply(this, ['display_data', ['text', 'json']]);
123 check_output_area.apply(this, ['display_data', ['text', 'json']]);
124 });
124 });
125
125
126 this.then(function() {
126 this.then(function() {
127 clear_and_execute(this,
127 clear_and_execute(this,
128 "from IPython.display import Latex; Latex('$X^2$')");
128 "from IPython.display import Latex; Latex('$X^2$')");
129 });
129 });
130
130
131 this.then(function ( ) {
131 this.then(function ( ) {
132 check_output_area.apply(this, ['execute_result', ['text', 'latex']]);
132 check_output_area.apply(this, ['execute_result', ['text', 'latex']]);
133 });
133 });
134
134
135 this.then(function() {
135 this.then(function() {
136 clear_and_execute(this,
136 clear_and_execute(this,
137 "from IPython.display import Latex, display; display(Latex('$X^2$'))");
137 "from IPython.display import Latex, display; display(Latex('$X^2$'))");
138 });
138 });
139
139
140 this.then(function ( ) {
140 this.then(function ( ) {
141 check_output_area.apply(this, ['display_data', ['text', 'latex']]);
141 check_output_area.apply(this, ['display_data', ['text', 'latex']]);
142 });
142 });
143
143
144 this.then(function() {
144 this.then(function() {
145 clear_and_execute(this,
145 clear_and_execute(this,
146 "from IPython.display import HTML; HTML('<b>it works!</b>')");
146 "from IPython.display import HTML; HTML('<b>it works!</b>')");
147 });
147 });
148
148
149 this.then(function ( ) {
149 this.then(function ( ) {
150 check_output_area.apply(this, ['execute_result', ['text', 'html']]);
150 check_output_area.apply(this, ['execute_result', ['text', 'html']]);
151 });
151 });
152
152
153 this.then(function() {
153 this.then(function() {
154 clear_and_execute(this,
154 clear_and_execute(this,
155 "from IPython.display import HTML, display; display(HTML('<b>it works!</b>'))");
155 "from IPython.display import HTML, display; display(HTML('<b>it works!</b>'))");
156 });
156 });
157
157
158 this.then(function ( ) {
158 this.then(function ( ) {
159 check_output_area.apply(this, ['display_data', ['text', 'html']]);
159 check_output_area.apply(this, ['display_data', ['text', 'html']]);
160 });
160 });
161
161
162
162
163 this.then(function() {
163 this.then(function() {
164 clear_and_execute(this,
164 clear_and_execute(this,
165 "from IPython.display import Image; Image(" + black_dot_png + ")");
165 "from IPython.display import Image; Image(" + black_dot_png + ")");
166 });
166 });
167 this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
167 this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
168
168
169 this.then(function ( ) {
169 this.then(function ( ) {
170 check_output_area.apply(this, ['execute_result', ['text', 'png']]);
170 check_output_area.apply(this, ['execute_result', ['text', 'png']]);
171 });
171 });
172
172
173 this.then(function() {
173 this.then(function() {
174 clear_and_execute(this,
174 clear_and_execute(this,
175 "from IPython.display import Image, display; display(Image(" + black_dot_png + "))");
175 "from IPython.display import Image, display; display(Image(" + black_dot_png + "))");
176 });
176 });
177
177
178 this.then(function ( ) {
178 this.then(function ( ) {
179 check_output_area.apply(this, ['display_data', ['text', 'png']]);
179 check_output_area.apply(this, ['display_data', ['text', 'png']]);
180 });
180 });
181
181
182
182
183 this.then(function() {
183 this.then(function() {
184 clear_and_execute(this,
184 clear_and_execute(this,
185 "from IPython.display import Image; Image(" + black_dot_jpeg + ", format='jpeg')");
185 "from IPython.display import Image; Image(" + black_dot_jpeg + ", format='jpeg')");
186 });
186 });
187
187
188 this.then(function ( ) {
188 this.then(function ( ) {
189 check_output_area.apply(this, ['execute_result', ['text', 'jpeg']]);
189 check_output_area.apply(this, ['execute_result', ['text', 'jpeg']]);
190 });
190 });
191
191
192 this.then(function() {
192 this.then(function() {
193 clear_and_execute(this,
193 clear_and_execute(this,
194 "from IPython.display import Image, display; display(Image(" + black_dot_jpeg + ", format='jpeg'))");
194 "from IPython.display import Image, display; display(Image(" + black_dot_jpeg + ", format='jpeg'))");
195 });
195 });
196
196
197 this.then(function ( ) {
197 this.then(function ( ) {
198 check_output_area.apply(this, ['display_data', ['text', 'jpeg']]);
198 check_output_area.apply(this, ['display_data', ['text', 'jpeg']]);
199 });
199 });
200
200
201 this.then(function() {
201 this.then(function() {
202 clear_and_execute(this,
202 clear_and_execute(this,
203 "from IPython.core.display import SVG; SVG(" + svg + ")");
203 "from IPython.core.display import SVG; SVG(" + svg + ")");
204 });
204 });
205
205
206 this.then(function ( ) {
206 this.then(function ( ) {
207 check_output_area.apply(this, ['execute_result', ['text', 'svg']]);
207 check_output_area.apply(this, ['execute_result', ['text', 'svg']]);
208 });
208 });
209
209
210 this.then(function() {
210 this.then(function() {
211 clear_and_execute(this,
211 clear_and_execute(this,
212 "from IPython.core.display import SVG, display; display(SVG(" + svg + "))");
212 "from IPython.core.display import SVG, display; display(SVG(" + svg + "))");
213 });
213 });
214
214
215 this.then(function ( ) {
215 this.then(function ( ) {
216 check_output_area.apply(this, ['display_data', ['text', 'svg']]);
216 check_output_area.apply(this, ['display_data', ['text', 'svg']]);
217 });
217 });
218
218
219 this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
219 this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
220
220
221 this.then(function() {
221 this.then(function() {
222 clear_and_execute(this, [
222 clear_and_execute(this, [
223 "from IPython.core.formatters import HTMLFormatter",
223 "from IPython.core.formatters import HTMLFormatter",
224 "x = HTMLFormatter()",
224 "x = HTMLFormatter()",
225 "x.format_type = 'text/superfancymimetype'",
225 "x.format_type = 'text/superfancymimetype'",
226 "get_ipython().display_formatter.formatters['text/superfancymimetype'] = x",
226 "get_ipython().display_formatter.formatters['text/superfancymimetype'] = x",
227 "from IPython.display import HTML, display",
227 "from IPython.display import HTML, display",
228 'display(HTML("yo"))',
228 'display(HTML("yo"))',
229 "HTML('hello')"].join('\n')
229 "HTML('hello')"].join('\n')
230 );
230 );
231
231
232 });
232 });
233
233
234 this.wait_for_output(0, 1);
234 this.wait_for_output(0, 1);
235
235
236 this.then(function () {
236 this.then(function () {
237 var long_name = 'text/superfancymimetype';
237 var long_name = 'text/superfancymimetype';
238 var result = this.get_output_cell(0);
238 var result = this.get_output_cell(0);
239 this.test.assertTrue(result.data.hasOwnProperty(long_name),
239 this.test.assertTrue(result.data.hasOwnProperty(long_name),
240 'display_data custom mimetype ' + long_name);
240 'display_data custom mimetype ' + long_name);
241 result = this.get_output_cell(0, 1);
241 result = this.get_output_cell(0, 1);
242 this.test.assertTrue(result.data.hasOwnProperty(long_name),
242 this.test.assertTrue(result.data.hasOwnProperty(long_name),
243 'execute_result custom mimetype ' + long_name);
243 'execute_result custom mimetype ' + long_name);
244
244
245 });
245 });
246
246
247 });
247 });
@@ -1,830 +1,831 b''
1 //
1 //
2 // Utility functions for the HTML notebook's CasperJS tests.
2 // Utility functions for the HTML notebook's CasperJS tests.
3 //
3 //
4 casper.get_notebook_server = function () {
4 casper.get_notebook_server = function () {
5 // Get the URL of a notebook server on which to run tests.
5 // Get the URL of a notebook server on which to run tests.
6 var port = casper.cli.get("port");
6 var port = casper.cli.get("port");
7 port = (typeof port === 'undefined') ? '8888' : port;
7 port = (typeof port === 'undefined') ? '8888' : port;
8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
9 };
9 };
10
10
11 casper.open_new_notebook = function () {
11 casper.open_new_notebook = function () {
12 // Create and open a new notebook.
12 // Create and open a new notebook.
13 var baseUrl = this.get_notebook_server();
13 var baseUrl = this.get_notebook_server();
14 this.start(baseUrl);
14 this.start(baseUrl);
15 this.waitFor(this.page_loaded);
15 this.waitFor(this.page_loaded);
16 this.thenClick('#kernel-python2 a, #kernel-python3 a');
16 this.thenClick('#kernel-python2 a, #kernel-python3 a');
17
17
18 this.waitForPopup('');
18 this.waitForPopup('');
19
19
20 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
20 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
21 this.then(function () {
21 this.then(function () {
22 this.open(this.popups[0].url);
22 this.open(this.popups[0].url);
23 });
23 });
24 this.waitFor(this.page_loaded);
24 this.waitFor(this.page_loaded);
25
25
26 // Hook the log and error methods of the console, forcing them to
26 // Hook the log and error methods of the console, forcing them to
27 // serialize their arguments before printing. This allows the
27 // serialize their arguments before printing. This allows the
28 // Objects to cross into the phantom/slimer regime for display.
28 // Objects to cross into the phantom/slimer regime for display.
29 this.thenEvaluate(function(){
29 this.thenEvaluate(function(){
30 var serialize_arguments = function(f, context) {
30 var serialize_arguments = function(f, context) {
31 return function() {
31 return function() {
32 var pretty_arguments = [];
32 var pretty_arguments = [];
33 for (var i = 0; i < arguments.length; i++) {
33 for (var i = 0; i < arguments.length; i++) {
34 var value = arguments[i];
34 var value = arguments[i];
35 if (value instanceof Object) {
35 if (value instanceof Object) {
36 var name = value.name || 'Object';
36 var name = value.name || 'Object';
37 // Print a JSON string representation of the object.
37 // Print a JSON string representation of the object.
38 // If we don't do this, [Object object] gets printed
38 // If we don't do this, [Object object] gets printed
39 // by casper, which is useless. The long regular
39 // by casper, which is useless. The long regular
40 // expression reduces the verbosity of the JSON.
40 // expression reduces the verbosity of the JSON.
41 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
41 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
42 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
42 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
43 .replace(/\n(\s+)?\n/g, '\n'));
43 .replace(/\n(\s+)?\n/g, '\n'));
44 } else {
44 } else {
45 pretty_arguments.push(value);
45 pretty_arguments.push(value);
46 }
46 }
47 }
47 }
48 f.apply(context, pretty_arguments);
48 f.apply(context, pretty_arguments);
49 };
49 };
50 };
50 };
51 console.log = serialize_arguments(console.log, console);
51 console.log = serialize_arguments(console.log, console);
52 console.error = serialize_arguments(console.error, console);
52 console.error = serialize_arguments(console.error, console);
53 });
53 });
54
54
55 // Make sure the kernel has started
55 // Make sure the kernel has started
56 this.waitFor(this.kernel_running);
56 this.waitFor(this.kernel_running);
57 // track the IPython busy/idle state
57 // track the IPython busy/idle state
58 this.thenEvaluate(function () {
58 this.thenEvaluate(function () {
59 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
59 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
60
60
61 events.on('kernel_idle.Kernel',function () {
61 events.on('kernel_idle.Kernel',function () {
62 IPython._status = 'idle';
62 IPython._status = 'idle';
63 });
63 });
64 events.on('kernel_busy.Kernel',function () {
64 events.on('kernel_busy.Kernel',function () {
65 IPython._status = 'busy';
65 IPython._status = 'busy';
66 });
66 });
67 });
67 });
68 });
68 });
69
69
70 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
70 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
71 // sure the notebook has actually been loaded into the IPython namespace
71 // sure the notebook has actually been loaded into the IPython namespace
72 // before running any tests.
72 // before running any tests.
73 this.waitFor(function() {
73 this.waitFor(function() {
74 return this.evaluate(function () {
74 return this.evaluate(function () {
75 return IPython.notebook;
75 return IPython.notebook;
76 });
76 });
77 });
77 });
78 };
78 };
79
79
80 casper.page_loaded = function() {
80 casper.page_loaded = function() {
81 // Return whether or not the kernel is running.
81 // Return whether or not the kernel is running.
82 return this.evaluate(function() {
82 return this.evaluate(function() {
83 return typeof IPython !== "undefined" &&
83 return typeof IPython !== "undefined" &&
84 IPython.page !== undefined;
84 IPython.page !== undefined;
85 });
85 });
86 };
86 };
87
87
88 casper.kernel_running = function() {
88 casper.kernel_running = function() {
89 // Return whether or not the kernel is running.
89 // Return whether or not the kernel is running.
90 return this.evaluate(function() {
90 return this.evaluate(function() {
91 return IPython &&
91 return IPython &&
92 IPython.notebook &&
92 IPython.notebook &&
93 IPython.notebook.kernel &&
93 IPython.notebook.kernel &&
94 IPython.notebook.kernel.is_connected();
94 IPython.notebook.kernel.is_connected();
95 });
95 });
96 };
96 };
97
97
98 casper.kernel_disconnected = function() {
98 casper.kernel_disconnected = function() {
99 return this.evaluate(function() {
99 return this.evaluate(function() {
100 return IPython.notebook.kernel.is_fully_disconnected();
100 return IPython.notebook.kernel.is_fully_disconnected();
101 });
101 });
102 };
102 };
103
103
104 casper.wait_for_kernel_ready = function () {
104 casper.wait_for_kernel_ready = function () {
105 this.waitFor(this.kernel_running);
105 this.waitFor(this.kernel_running);
106 this.thenEvaluate(function () {
106 this.thenEvaluate(function () {
107 IPython._kernel_ready = false;
107 IPython._kernel_ready = false;
108 IPython.notebook.kernel.kernel_info(
108 IPython.notebook.kernel.kernel_info(
109 function () {
109 function () {
110 IPython._kernel_ready = true;
110 IPython._kernel_ready = true;
111 });
111 });
112 });
112 });
113 this.waitFor(function () {
113 this.waitFor(function () {
114 return this.evaluate(function () {
114 return this.evaluate(function () {
115 return IPython._kernel_ready;
115 return IPython._kernel_ready;
116 });
116 });
117 });
117 });
118 };
118 };
119
119
120 casper.shutdown_current_kernel = function () {
120 casper.shutdown_current_kernel = function () {
121 // Shut down the current notebook's kernel.
121 // Shut down the current notebook's kernel.
122 this.thenEvaluate(function() {
122 this.thenEvaluate(function() {
123 IPython.notebook.session.delete();
123 IPython.notebook.session.delete();
124 });
124 });
125 // We close the page right after this so we need to give it time to complete.
125 // We close the page right after this so we need to give it time to complete.
126 this.wait(1000);
126 this.wait(1000);
127 };
127 };
128
128
129 casper.delete_current_notebook = function () {
129 casper.delete_current_notebook = function () {
130 // Delete created notebook.
130 // Delete created notebook.
131
131
132 // For some unknown reason, this doesn't work?!?
132 // For some unknown reason, this doesn't work?!?
133 this.thenEvaluate(function() {
133 this.thenEvaluate(function() {
134 IPython.notebook.delete();
134 IPython.notebook.delete();
135 });
135 });
136 };
136 };
137
137
138 casper.wait_for_busy = function () {
138 casper.wait_for_busy = function () {
139 // Waits for the notebook to enter a busy state.
139 // Waits for the notebook to enter a busy state.
140 this.waitFor(function () {
140 this.waitFor(function () {
141 return this.evaluate(function () {
141 return this.evaluate(function () {
142 return IPython._status == 'busy';
142 return IPython._status == 'busy';
143 });
143 });
144 });
144 });
145 };
145 };
146
146
147 casper.wait_for_idle = function () {
147 casper.wait_for_idle = function () {
148 // Waits for the notebook to idle.
148 // Waits for the notebook to idle.
149 this.waitFor(function () {
149 this.waitFor(function () {
150 return this.evaluate(function () {
150 return this.evaluate(function () {
151 return IPython._status == 'idle';
151 return IPython._status == 'idle';
152 });
152 });
153 });
153 });
154 };
154 };
155
155
156 casper.wait_for_output = function (cell_num, out_num) {
156 casper.wait_for_output = function (cell_num, out_num) {
157 // wait for the nth output in a given cell
157 // wait for the nth output in a given cell
158 this.wait_for_idle();
158 this.wait_for_idle();
159 out_num = out_num || 0;
159 out_num = out_num || 0;
160 this.then(function() {
160 this.then(function() {
161 this.waitFor(function (c, o) {
161 this.waitFor(function (c, o) {
162 return this.evaluate(function get_output(c, o) {
162 return this.evaluate(function get_output(c, o) {
163 var cell = IPython.notebook.get_cell(c);
163 var cell = IPython.notebook.get_cell(c);
164 return cell.output_area.outputs.length > o;
164 return cell.output_area.outputs.length > o;
165 },
165 },
166 // pass parameter from the test suite js to the browser code js
166 // pass parameter from the test suite js to the browser code js
167 {c : cell_num, o : out_num});
167 {c : cell_num, o : out_num});
168 });
168 });
169 },
169 },
170 function then() { },
170 function then() { },
171 function timeout() {
171 function timeout() {
172 this.echo("wait_for_output timed out!");
172 this.echo("wait_for_output timed out!");
173 });
173 });
174 };
174 };
175
175
176 casper.wait_for_widget = function (widget_info) {
176 casper.wait_for_widget = function (widget_info) {
177 // wait for a widget msg que to reach 0
177 // wait for a widget msg que to reach 0
178 //
178 //
179 // Parameters
179 // Parameters
180 // ----------
180 // ----------
181 // widget_info : object
181 // widget_info : object
182 // Object which contains info related to the widget. The model_id property
182 // Object which contains info related to the widget. The model_id property
183 // is used to identify the widget.
183 // is used to identify the widget.
184
184
185 // Clear the results of a previous query, if they exist. Make sure a
185 // Clear the results of a previous query, if they exist. Make sure a
186 // dictionary exists to store the async results in.
186 // dictionary exists to store the async results in.
187 this.thenEvaluate(function(model_id) {
187 this.thenEvaluate(function(model_id) {
188 if (window.pending_msgs === undefined) {
188 if (window.pending_msgs === undefined) {
189 window.pending_msgs = {};
189 window.pending_msgs = {};
190 } else {
190 } else {
191 window.pending_msgs[model_id] = -1;
191 window.pending_msgs[model_id] = -1;
192 }
192 }
193 }, {model_id: widget_info.model_id});
193 }, {model_id: widget_info.model_id});
194
194
195 // Wait for the pending messages to be 0.
195 // Wait for the pending messages to be 0.
196 this.waitFor(function () {
196 this.waitFor(function () {
197 var pending = this.evaluate(function (model_id) {
197 var pending = this.evaluate(function (model_id) {
198
198
199 // Get the model. Once the model is had, store it's pending_msgs
199 // Get the model. Once the model is had, store it's pending_msgs
200 // count in the window's dictionary.
200 // count in the window's dictionary.
201 IPython.notebook.kernel.widget_manager.get_model(model_id)
201 IPython.notebook.kernel.widget_manager.get_model(model_id)
202 .then(function(model) {
202 .then(function(model) {
203 window.pending_msgs[model_id] = model.pending_msgs;
203 window.pending_msgs[model_id] = model.pending_msgs;
204 });
204 });
205
205
206 // Return the pending_msgs result.
206 // Return the pending_msgs result.
207 return window.pending_msgs[model_id];
207 return window.pending_msgs[model_id];
208 }, {model_id: widget_info.model_id});
208 }, {model_id: widget_info.model_id});
209
209
210 if (pending === 0) {
210 if (pending === 0) {
211 return true;
211 return true;
212 } else {
212 } else {
213 return false;
213 return false;
214 }
214 }
215 });
215 });
216 };
216 };
217
217
218 casper.get_output_cell = function (cell_num, out_num) {
218 casper.get_output_cell = function (cell_num, out_num) {
219 // return an output of a given cell
219 // return an output of a given cell
220 out_num = out_num || 0;
220 out_num = out_num || 0;
221 var result = casper.evaluate(function (c, o) {
221 var result = casper.evaluate(function (c, o) {
222 var cell = IPython.notebook.get_cell(c);
222 var cell = IPython.notebook.get_cell(c);
223 return cell.output_area.outputs[o];
223 return cell.output_area.outputs[o];
224 },
224 },
225 {c : cell_num, o : out_num});
225 {c : cell_num, o : out_num});
226 if (!result) {
226 if (!result) {
227 var num_outputs = casper.evaluate(function (c) {
227 var num_outputs = casper.evaluate(function (c) {
228 var cell = IPython.notebook.get_cell(c);
228 var cell = IPython.notebook.get_cell(c);
229 return cell.output_area.outputs.length;
229 return cell.output_area.outputs.length;
230 },
230 },
231 {c : cell_num});
231 {c : cell_num});
232 this.test.assertTrue(false,
232 this.test.assertTrue(false,
233 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
233 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
234 );
234 );
235 } else {
235 } else {
236 return result;
236 return result;
237 }
237 }
238 };
238 };
239
239
240 casper.get_cells_length = function () {
240 casper.get_cells_length = function () {
241 // return the number of cells in the notebook
241 // return the number of cells in the notebook
242 var result = casper.evaluate(function () {
242 var result = casper.evaluate(function () {
243 return IPython.notebook.get_cells().length;
243 return IPython.notebook.get_cells().length;
244 });
244 });
245 return result;
245 return result;
246 };
246 };
247
247
248 casper.set_cell_text = function(index, text){
248 casper.set_cell_text = function(index, text){
249 // Set the text content of a cell.
249 // Set the text content of a cell.
250 this.evaluate(function (index, text) {
250 this.evaluate(function (index, text) {
251 var cell = IPython.notebook.get_cell(index);
251 var cell = IPython.notebook.get_cell(index);
252 cell.set_text(text);
252 cell.set_text(text);
253 }, index, text);
253 }, index, text);
254 };
254 };
255
255
256 casper.get_cell_text = function(index){
256 casper.get_cell_text = function(index){
257 // Get the text content of a cell.
257 // Get the text content of a cell.
258 return this.evaluate(function (index) {
258 return this.evaluate(function (index) {
259 var cell = IPython.notebook.get_cell(index);
259 var cell = IPython.notebook.get_cell(index);
260 return cell.get_text();
260 return cell.get_text();
261 }, index);
261 }, index);
262 };
262 };
263
263
264 casper.insert_cell_at_bottom = function(cell_type){
264 casper.insert_cell_at_bottom = function(cell_type){
265 // Inserts a cell at the bottom of the notebook
265 // Inserts a cell at the bottom of the notebook
266 // Returns the new cell's index.
266 // Returns the new cell's index.
267 return this.evaluate(function (cell_type) {
267 return this.evaluate(function (cell_type) {
268 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
268 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
269 return IPython.notebook.find_cell_index(cell);
269 return IPython.notebook.find_cell_index(cell);
270 }, cell_type);
270 }, cell_type);
271 };
271 };
272
272
273 casper.append_cell = function(text, cell_type) {
273 casper.append_cell = function(text, cell_type) {
274 // Insert a cell at the bottom of the notebook and set the cells text.
274 // Insert a cell at the bottom of the notebook and set the cells text.
275 // Returns the new cell's index.
275 // Returns the new cell's index.
276 var index = this.insert_cell_at_bottom(cell_type);
276 var index = this.insert_cell_at_bottom(cell_type);
277 if (text !== undefined) {
277 if (text !== undefined) {
278 this.set_cell_text(index, text);
278 this.set_cell_text(index, text);
279 }
279 }
280 return index;
280 return index;
281 };
281 };
282
282
283 casper.execute_cell = function(index, expect_failure){
283 casper.execute_cell = function(index, expect_failure){
284 // Asynchronously executes a cell by index.
284 // Asynchronously executes a cell by index.
285 // Returns the cell's index.
285 // Returns the cell's index.
286
286
287 if (expect_failure === undefined) expect_failure = false;
287 if (expect_failure === undefined) expect_failure = false;
288 var that = this;
288 var that = this;
289 this.then(function(){
289 this.then(function(){
290 that.evaluate(function (index) {
290 that.evaluate(function (index) {
291 var cell = IPython.notebook.get_cell(index);
291 var cell = IPython.notebook.get_cell(index);
292 cell.execute();
292 cell.execute();
293 }, index);
293 }, index);
294 });
294 });
295 this.wait_for_idle();
295 this.wait_for_idle();
296
296
297 this.then(function () {
297 this.then(function () {
298 var error = that.evaluate(function (index) {
298 var error = that.evaluate(function (index) {
299 var cell = IPython.notebook.get_cell(index);
299 var cell = IPython.notebook.get_cell(index);
300 var outputs = cell.output_area.outputs;
300 var outputs = cell.output_area.outputs;
301 for (var i = 0; i < outputs.length; i++) {
301 for (var i = 0; i < outputs.length; i++) {
302 if (outputs[i].output_type == 'error') {
302 if (outputs[i].output_type == 'error') {
303 return outputs[i];
303 return outputs[i];
304 }
304 }
305 }
305 }
306 return false;
306 return false;
307 }, index);
307 }, index);
308 if (error === null) {
308 if (error === null) {
309 this.test.fail("Failed to check for error output");
309 this.test.fail("Failed to check for error output");
310 }
310 }
311 if (expect_failure && error === false) {
311 if (expect_failure && error === false) {
312 this.test.fail("Expected error while running cell");
312 this.test.fail("Expected error while running cell");
313 } else if (!expect_failure && error !== false) {
313 } else if (!expect_failure && error !== false) {
314 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
314 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
315 }
315 }
316 });
316 });
317 return index;
317 return index;
318 };
318 };
319
319
320 casper.execute_cell_then = function(index, then_callback, expect_failure) {
320 casper.execute_cell_then = function(index, then_callback, expect_failure) {
321 // Synchronously executes a cell by index.
321 // Synchronously executes a cell by index.
322 // Optionally accepts a then_callback parameter. then_callback will get called
322 // Optionally accepts a then_callback parameter. then_callback will get called
323 // when the cell has finished executing.
323 // when the cell has finished executing.
324 // Returns the cell's index.
324 // Returns the cell's index.
325 var return_val = this.execute_cell(index, expect_failure);
325 var return_val = this.execute_cell(index, expect_failure);
326
326
327 this.wait_for_idle();
327 this.wait_for_idle();
328
328
329 var that = this;
329 var that = this;
330 this.then(function(){
330 this.then(function(){
331 if (then_callback!==undefined) {
331 if (then_callback!==undefined) {
332 then_callback.apply(that, [index]);
332 then_callback.apply(that, [index]);
333 }
333 }
334 });
334 });
335
335
336 return return_val;
336 return return_val;
337 };
337 };
338
338
339 casper.wait_for_element = function(index, selector){
339 casper.wait_for_element = function(index, selector){
340 // Utility function that allows us to easily wait for an element
340 // Utility function that allows us to easily wait for an element
341 // within a cell. Uses JQuery selector to look for the element.
341 // within a cell. Uses JQuery selector to look for the element.
342 var that = this;
342 var that = this;
343 this.waitFor(function() {
343 this.waitFor(function() {
344 return that.cell_element_exists(index, selector);
344 return that.cell_element_exists(index, selector);
345 });
345 });
346 };
346 };
347
347
348 casper.cell_element_exists = function(index, selector){
348 casper.cell_element_exists = function(index, selector){
349 // Utility function that allows us to easily check if an element exists
349 // Utility function that allows us to easily check if an element exists
350 // within a cell. Uses JQuery selector to look for the element.
350 // within a cell. Uses JQuery selector to look for the element.
351 return casper.evaluate(function (index, selector) {
351 return casper.evaluate(function (index, selector) {
352 var $cell = IPython.notebook.get_cell(index).element;
352 var $cell = IPython.notebook.get_cell(index).element;
353 return $cell.find(selector).length > 0;
353 return $cell.find(selector).length > 0;
354 }, index, selector);
354 }, index, selector);
355 };
355 };
356
356
357 casper.cell_element_function = function(index, selector, function_name, function_args){
357 casper.cell_element_function = function(index, selector, function_name, function_args){
358 // Utility function that allows us to execute a jQuery function on an
358 // Utility function that allows us to execute a jQuery function on an
359 // element within a cell.
359 // element within a cell.
360 return casper.evaluate(function (index, selector, function_name, function_args) {
360 return casper.evaluate(function (index, selector, function_name, function_args) {
361 var $cell = IPython.notebook.get_cell(index).element;
361 var $cell = IPython.notebook.get_cell(index).element;
362 var $el = $cell.find(selector);
362 var $el = $cell.find(selector);
363 return $el[function_name].apply($el, function_args);
363 return $el[function_name].apply($el, function_args);
364 }, index, selector, function_name, function_args);
364 }, index, selector, function_name, function_args);
365 };
365 };
366
366
367 casper.validate_notebook_state = function(message, mode, cell_index) {
367 casper.validate_notebook_state = function(message, mode, cell_index) {
368 // Validate the entire dual mode state of the notebook. Make sure no more than
368 // Validate the entire dual mode state of the notebook. Make sure no more than
369 // one cell is selected, focused, in edit mode, etc...
369 // one cell is selected, focused, in edit mode, etc...
370
370
371 // General tests.
371 // General tests.
372 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
372 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
373 message + '; keyboard and notebook modes match');
373 message + '; keyboard and notebook modes match');
374 // Is the selected cell the only cell that is selected?
374 // Is the selected cell the only cell that is selected?
375 if (cell_index!==undefined) {
375 if (cell_index!==undefined) {
376 this.test.assert(this.is_only_cell_selected(cell_index),
376 this.test.assert(this.is_only_cell_selected(cell_index),
377 message + '; cell ' + cell_index + ' is the only cell selected');
377 message + '; cell ' + cell_index + ' is the only cell selected');
378 }
378 }
379
379
380 // Mode specific tests.
380 // Mode specific tests.
381 if (mode==='command') {
381 if (mode==='command') {
382 // Are the notebook and keyboard manager in command mode?
382 // Are the notebook and keyboard manager in command mode?
383 this.test.assertEquals(this.get_keyboard_mode(), 'command',
383 this.test.assertEquals(this.get_keyboard_mode(), 'command',
384 message + '; in command mode');
384 message + '; in command mode');
385 // Make sure there isn't a single cell in edit mode.
385 // Make sure there isn't a single cell in edit mode.
386 this.test.assert(this.is_only_cell_edit(null),
386 this.test.assert(this.is_only_cell_edit(null),
387 message + '; all cells in command mode');
387 message + '; all cells in command mode');
388 this.test.assert(this.is_cell_editor_focused(null),
388 this.test.assert(this.is_cell_editor_focused(null),
389 message + '; no cell editors are focused while in command mode');
389 message + '; no cell editors are focused while in command mode');
390
390
391 } else if (mode==='edit') {
391 } else if (mode==='edit') {
392 // Are the notebook and keyboard manager in edit mode?
392 // Are the notebook and keyboard manager in edit mode?
393 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
393 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
394 message + '; in edit mode');
394 message + '; in edit mode');
395 if (cell_index!==undefined) {
395 if (cell_index!==undefined) {
396 // Is the specified cell the only cell in edit mode?
396 // Is the specified cell the only cell in edit mode?
397 this.test.assert(this.is_only_cell_edit(cell_index),
397 this.test.assert(this.is_only_cell_edit(cell_index),
398 message + '; cell ' + cell_index + ' is the only cell in edit mode '+ this.cells_modes());
398 message + '; cell ' + cell_index + ' is the only cell in edit mode '+ this.cells_modes());
399 // Is the specified cell the only cell with a focused code mirror?
399 // Is the specified cell the only cell with a focused code mirror?
400 this.test.assert(this.is_cell_editor_focused(cell_index),
400 this.test.assert(this.is_cell_editor_focused(cell_index),
401 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
401 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
402 }
402 }
403
403
404 } else {
404 } else {
405 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
405 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
406 }
406 }
407 };
407 };
408
408
409 casper.select_cell = function(index) {
409 casper.select_cell = function(index) {
410 // Select a cell in the notebook.
410 // Select a cell in the notebook.
411 this.evaluate(function (i) {
411 this.evaluate(function (i) {
412 IPython.notebook.select(i);
412 IPython.notebook.select(i);
413 }, {i: index});
413 }, {i: index});
414 };
414 };
415
415
416 casper.click_cell_editor = function(index) {
416 casper.click_cell_editor = function(index) {
417 // Emulate a click on a cell's editor.
417 // Emulate a click on a cell's editor.
418
418
419 // Code Mirror does not play nicely with emulated brower events.
419 // Code Mirror does not play nicely with emulated brower events.
420 // Instead of trying to emulate a click, here we run code similar to
420 // Instead of trying to emulate a click, here we run code similar to
421 // the code used in Code Mirror that handles the mousedown event on a
421 // the code used in Code Mirror that handles the mousedown event on a
422 // region of codemirror that the user can focus.
422 // region of codemirror that the user can focus.
423 this.evaluate(function (i) {
423 this.evaluate(function (i) {
424 var cm = IPython.notebook.get_cell(i).code_mirror;
424 var cm = IPython.notebook.get_cell(i).code_mirror;
425 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
425 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input)){
426 cm.display.input.focus();
426 cm.display.input.focus();
427 }
427 }, {i: index});
428 }, {i: index});
428 };
429 };
429
430
430 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
431 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
431 // Set the Code Mirror instance cursor's location.
432 // Set the Code Mirror instance cursor's location.
432 this.evaluate(function (i, l, c) {
433 this.evaluate(function (i, l, c) {
433 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
434 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
434 }, {i: index, l: line_index, c: char_index});
435 }, {i: index, l: line_index, c: char_index});
435 };
436 };
436
437
437 casper.focus_notebook = function() {
438 casper.focus_notebook = function() {
438 // Focus the notebook div.
439 // Focus the notebook div.
439 this.evaluate(function (){
440 this.evaluate(function (){
440 $('#notebook').focus();
441 $('#notebook').focus();
441 }, {});
442 }, {});
442 };
443 };
443
444
444 casper.trigger_keydown = function() {
445 casper.trigger_keydown = function() {
445 // Emulate a keydown in the notebook.
446 // Emulate a keydown in the notebook.
446 for (var i = 0; i < arguments.length; i++) {
447 for (var i = 0; i < arguments.length; i++) {
447 this.evaluate(function (k) {
448 this.evaluate(function (k) {
448 var element = $(document);
449 var element = $(document);
449 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
450 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
450 element.trigger(event);
451 element.trigger(event);
451 }, {k: arguments[i]});
452 }, {k: arguments[i]});
452 }
453 }
453 };
454 };
454
455
455 casper.get_keyboard_mode = function() {
456 casper.get_keyboard_mode = function() {
456 // Get the mode of the keyboard manager.
457 // Get the mode of the keyboard manager.
457 return this.evaluate(function() {
458 return this.evaluate(function() {
458 return IPython.keyboard_manager.mode;
459 return IPython.keyboard_manager.mode;
459 }, {});
460 }, {});
460 };
461 };
461
462
462 casper.get_notebook_mode = function() {
463 casper.get_notebook_mode = function() {
463 // Get the mode of the notebook.
464 // Get the mode of the notebook.
464 return this.evaluate(function() {
465 return this.evaluate(function() {
465 return IPython.notebook.mode;
466 return IPython.notebook.mode;
466 }, {});
467 }, {});
467 };
468 };
468
469
469 casper.get_cell = function(index) {
470 casper.get_cell = function(index) {
470 // Get a single cell.
471 // Get a single cell.
471 //
472 //
472 // Note: Handles to DOM elements stored in the cell will be useless once in
473 // Note: Handles to DOM elements stored in the cell will be useless once in
473 // CasperJS context.
474 // CasperJS context.
474 return this.evaluate(function(i) {
475 return this.evaluate(function(i) {
475 var cell = IPython.notebook.get_cell(i);
476 var cell = IPython.notebook.get_cell(i);
476 if (cell) {
477 if (cell) {
477 return cell;
478 return cell;
478 }
479 }
479 return null;
480 return null;
480 }, {i : index});
481 }, {i : index});
481 };
482 };
482
483
483 casper.is_cell_editor_focused = function(index) {
484 casper.is_cell_editor_focused = function(index) {
484 // Make sure a cell's editor is the only editor focused on the page.
485 // Make sure a cell's editor is the only editor focused on the page.
485 return this.evaluate(function(i) {
486 return this.evaluate(function(i) {
486 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
487 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
487 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
488 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
488 if (i === null) {
489 if (i === null) {
489 return focused_textarea.length === 0;
490 return focused_textarea.length === 0;
490 } else {
491 } else {
491 var cell = IPython.notebook.get_cell(i);
492 var cell = IPython.notebook.get_cell(i);
492 if (cell) {
493 if (cell) {
493 return cell.code_mirror.getInputField() == focused_textarea[0];
494 return cell.code_mirror.getInputField() == focused_textarea[0];
494 }
495 }
495 }
496 }
496 return false;
497 return false;
497 }, {i : index});
498 }, {i : index});
498 };
499 };
499
500
500 casper.is_only_cell_selected = function(index) {
501 casper.is_only_cell_selected = function(index) {
501 // Check if a cell is the only cell selected.
502 // Check if a cell is the only cell selected.
502 // Pass null as the index to check if no cells are selected.
503 // Pass null as the index to check if no cells are selected.
503 return this.is_only_cell_on(index, 'selected', 'unselected');
504 return this.is_only_cell_on(index, 'selected', 'unselected');
504 };
505 };
505
506
506 casper.is_only_cell_edit = function(index) {
507 casper.is_only_cell_edit = function(index) {
507 // Check if a cell is the only cell in edit mode.
508 // Check if a cell is the only cell in edit mode.
508 // Pass null as the index to check if all of the cells are in command mode.
509 // Pass null as the index to check if all of the cells are in command mode.
509 var cells_length = this.get_cells_length();
510 var cells_length = this.get_cells_length();
510 for (var j = 0; j < cells_length; j++) {
511 for (var j = 0; j < cells_length; j++) {
511 if (j === index) {
512 if (j === index) {
512 if (!this.cell_mode_is(j, 'edit')) {
513 if (!this.cell_mode_is(j, 'edit')) {
513 return false;
514 return false;
514 }
515 }
515 } else {
516 } else {
516 if (this.cell_mode_is(j, 'edit')) {
517 if (this.cell_mode_is(j, 'edit')) {
517 return false;
518 return false;
518 }
519 }
519 }
520 }
520 }
521 }
521 return true;
522 return true;
522 };
523 };
523
524
524 casper.is_only_cell_on = function(i, on_class, off_class) {
525 casper.is_only_cell_on = function(i, on_class, off_class) {
525 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
526 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
526 // All of the other cells are checked for the `off_class` DOM class.
527 // All of the other cells are checked for the `off_class` DOM class.
527 // Pass null as the index to check if all of the cells have the `off_class`.
528 // Pass null as the index to check if all of the cells have the `off_class`.
528 var cells_length = this.get_cells_length();
529 var cells_length = this.get_cells_length();
529 for (var j = 0; j < cells_length; j++) {
530 for (var j = 0; j < cells_length; j++) {
530 if (j === i) {
531 if (j === i) {
531 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
532 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
532 return false;
533 return false;
533 }
534 }
534 } else {
535 } else {
535 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
536 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
536 return false;
537 return false;
537 }
538 }
538 }
539 }
539 }
540 }
540 return true;
541 return true;
541 };
542 };
542
543
543 casper.cells_modes = function(){
544 casper.cells_modes = function(){
544 return this.evaluate(function(){
545 return this.evaluate(function(){
545 return IPython.notebook.get_cells().map(function(x,c){return x.mode})
546 return IPython.notebook.get_cells().map(function(x,c){return x.mode})
546 }, {});
547 }, {});
547 };
548 };
548
549
549 casper.cell_mode_is = function(index, mode) {
550 casper.cell_mode_is = function(index, mode) {
550 // Check if a cell is in a specific mode
551 // Check if a cell is in a specific mode
551 return this.evaluate(function(i, m) {
552 return this.evaluate(function(i, m) {
552 var cell = IPython.notebook.get_cell(i);
553 var cell = IPython.notebook.get_cell(i);
553 if (cell) {
554 if (cell) {
554 return cell.mode === m;
555 return cell.mode === m;
555 }
556 }
556 return false;
557 return false;
557 }, {i : index, m: mode});
558 }, {i : index, m: mode});
558 };
559 };
559
560
560
561
561 casper.cell_has_class = function(index, classes) {
562 casper.cell_has_class = function(index, classes) {
562 // Check if a cell has a class.
563 // Check if a cell has a class.
563 return this.evaluate(function(i, c) {
564 return this.evaluate(function(i, c) {
564 var cell = IPython.notebook.get_cell(i);
565 var cell = IPython.notebook.get_cell(i);
565 if (cell) {
566 if (cell) {
566 return cell.element.hasClass(c);
567 return cell.element.hasClass(c);
567 }
568 }
568 return false;
569 return false;
569 }, {i : index, c: classes});
570 }, {i : index, c: classes});
570 };
571 };
571
572
572 casper.is_cell_rendered = function (index) {
573 casper.is_cell_rendered = function (index) {
573 return this.evaluate(function(i) {
574 return this.evaluate(function(i) {
574 return !!IPython.notebook.get_cell(i).rendered;
575 return !!IPython.notebook.get_cell(i).rendered;
575 }, {i:index});
576 }, {i:index});
576 };
577 };
577
578
578 casper.assert_colors_equal = function (hex_color, local_color, msg) {
579 casper.assert_colors_equal = function (hex_color, local_color, msg) {
579 // Tests to see if two colors are equal.
580 // Tests to see if two colors are equal.
580 //
581 //
581 // Parameters
582 // Parameters
582 // hex_color: string
583 // hex_color: string
583 // Hexadecimal color code, with or without preceeding hash character.
584 // Hexadecimal color code, with or without preceeding hash character.
584 // local_color: string
585 // local_color: string
585 // Local color representation. Can either be hexadecimal (default for
586 // Local color representation. Can either be hexadecimal (default for
586 // phantom) or rgb (default for slimer).
587 // phantom) or rgb (default for slimer).
587
588
588 // Remove parentheses, hashes, semi-colons, and space characters.
589 // Remove parentheses, hashes, semi-colons, and space characters.
589 hex_color = hex_color.replace(/[\(\); #]/, '');
590 hex_color = hex_color.replace(/[\(\); #]/, '');
590 local_color = local_color.replace(/[\(\); #]/, '');
591 local_color = local_color.replace(/[\(\); #]/, '');
591
592
592 // If the local color is rgb, clean it up and replace
593 // If the local color is rgb, clean it up and replace
593 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
594 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
594 components = local_color.substr(3).split(',');
595 var components = local_color.substr(3).split(',');
595 local_color = '';
596 local_color = '';
596 for (var i = 0; i < components.length; i++) {
597 for (var i = 0; i < components.length; i++) {
597 var part = parseInt(components[i]).toString(16);
598 var part = parseInt(components[i]).toString(16);
598 while (part.length < 2) part = '0' + part;
599 while (part.length < 2) part = '0' + part;
599 local_color += part;
600 local_color += part;
600 }
601 }
601 }
602 }
602
603
603 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
604 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
604 };
605 };
605
606
606 casper.notebook_test = function(test) {
607 casper.notebook_test = function(test) {
607 // Wrap a notebook test to reduce boilerplate.
608 // Wrap a notebook test to reduce boilerplate.
608 this.open_new_notebook();
609 this.open_new_notebook();
609
610
610 // Echo whether or not we are running this test using SlimerJS
611 // Echo whether or not we are running this test using SlimerJS
611 if (this.evaluate(function(){
612 if (this.evaluate(function(){
612 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
613 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
613 })) {
614 })) {
614 console.log('This test is running in SlimerJS.');
615 console.log('This test is running in SlimerJS.');
615 this.slimerjs = true;
616 this.slimerjs = true;
616 }
617 }
617
618
618 // Make sure to remove the onbeforeunload callback. This callback is
619 // Make sure to remove the onbeforeunload callback. This callback is
619 // responsible for the "Are you sure you want to quit?" type messages.
620 // responsible for the "Are you sure you want to quit?" type messages.
620 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
621 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
621 this.then(function(){
622 this.then(function(){
622 this.evaluate(function(){
623 this.evaluate(function(){
623 window.onbeforeunload = function(){};
624 window.onbeforeunload = function(){};
624 });
625 });
625 });
626 });
626
627
627 this.then(test);
628 this.then(test);
628
629
629 // Kill the kernel and delete the notebook.
630 // Kill the kernel and delete the notebook.
630 this.shutdown_current_kernel();
631 this.shutdown_current_kernel();
631 // This is still broken but shouldn't be a problem for now.
632 // This is still broken but shouldn't be a problem for now.
632 // this.delete_current_notebook();
633 // this.delete_current_notebook();
633
634
634 // This is required to clean up the page we just finished with. If we don't call this
635 // This is required to clean up the page we just finished with. If we don't call this
635 // casperjs will leak file descriptors of all the open WebSockets in that page. We
636 // casperjs will leak file descriptors of all the open WebSockets in that page. We
636 // have to set this.page=null so that next time casper.start runs, it will create a
637 // have to set this.page=null so that next time casper.start runs, it will create a
637 // new page from scratch.
638 // new page from scratch.
638 this.then(function () {
639 this.then(function () {
639 this.page.close();
640 this.page.close();
640 this.page = null;
641 this.page = null;
641 });
642 });
642
643
643 // Run the browser automation.
644 // Run the browser automation.
644 this.run(function() {
645 this.run(function() {
645 this.test.done();
646 this.test.done();
646 });
647 });
647 };
648 };
648
649
649 casper.wait_for_dashboard = function () {
650 casper.wait_for_dashboard = function () {
650 // Wait for the dashboard list to load.
651 // Wait for the dashboard list to load.
651 casper.waitForSelector('.list_item');
652 casper.waitForSelector('.list_item');
652 };
653 };
653
654
654 casper.open_dashboard = function () {
655 casper.open_dashboard = function () {
655 // Start casper by opening the dashboard page.
656 // Start casper by opening the dashboard page.
656 var baseUrl = this.get_notebook_server();
657 var baseUrl = this.get_notebook_server();
657 this.start(baseUrl);
658 this.start(baseUrl);
658 this.waitFor(this.page_loaded);
659 this.waitFor(this.page_loaded);
659 this.wait_for_dashboard();
660 this.wait_for_dashboard();
660 };
661 };
661
662
662 casper.dashboard_test = function (test) {
663 casper.dashboard_test = function (test) {
663 // Open the dashboard page and run a test.
664 // Open the dashboard page and run a test.
664 this.open_dashboard();
665 this.open_dashboard();
665 this.then(test);
666 this.then(test);
666
667
667 this.then(function () {
668 this.then(function () {
668 this.page.close();
669 this.page.close();
669 this.page = null;
670 this.page = null;
670 });
671 });
671
672
672 // Run the browser automation.
673 // Run the browser automation.
673 this.run(function() {
674 this.run(function() {
674 this.test.done();
675 this.test.done();
675 });
676 });
676 };
677 };
677
678
678 // note that this will only work for UNIQUE events -- if you want to
679 // note that this will only work for UNIQUE events -- if you want to
679 // listen for the same event twice, this will not work!
680 // listen for the same event twice, this will not work!
680 casper.event_test = function (name, events, action, timeout) {
681 casper.event_test = function (name, events, action, timeout) {
681
682
682 // set up handlers to listen for each of the events
683 // set up handlers to listen for each of the events
683 this.thenEvaluate(function (events) {
684 this.thenEvaluate(function (events) {
684 var make_handler = function (event) {
685 var make_handler = function (event) {
685 return function () {
686 return function () {
686 IPython._events_triggered.push(event);
687 IPython._events_triggered.push(event);
687 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
688 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
688 delete IPython._event_handlers[event];
689 delete IPython._event_handlers[event];
689 };
690 };
690 };
691 };
691 IPython._event_handlers = {};
692 IPython._event_handlers = {};
692 IPython._events_triggered = [];
693 IPython._events_triggered = [];
693 for (var i=0; i < events.length; i++) {
694 for (var i=0; i < events.length; i++) {
694 IPython._event_handlers[events[i]] = make_handler(events[i]);
695 IPython._event_handlers[events[i]] = make_handler(events[i]);
695 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
696 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
696 }
697 }
697 }, [events]);
698 }, [events]);
698
699
699 // execute the requested action
700 // execute the requested action
700 this.then(action);
701 this.then(action);
701
702
702 // wait for all the events to be triggered
703 // wait for all the events to be triggered
703 this.waitFor(function () {
704 this.waitFor(function () {
704 return this.evaluate(function (events) {
705 return this.evaluate(function (events) {
705 return IPython._events_triggered.length >= events.length;
706 return IPython._events_triggered.length >= events.length;
706 }, [events]);
707 }, [events]);
707 }, undefined, undefined, timeout);
708 }, undefined, undefined, timeout);
708
709
709 // test that the events were triggered in the proper order
710 // test that the events were triggered in the proper order
710 this.then(function () {
711 this.then(function () {
711 var triggered = this.evaluate(function () {
712 var triggered = this.evaluate(function () {
712 return IPython._events_triggered;
713 return IPython._events_triggered;
713 });
714 });
714 var handlers = this.evaluate(function () {
715 var handlers = this.evaluate(function () {
715 return Object.keys(IPython._event_handlers);
716 return Object.keys(IPython._event_handlers);
716 });
717 });
717 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
718 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
718 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
719 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
719 for (var i=0; i < events.length; i++) {
720 for (var i=0; i < events.length; i++) {
720 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
721 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
721 }
722 }
722 });
723 });
723
724
724 // turn off any remaining event listeners
725 // turn off any remaining event listeners
725 this.thenEvaluate(function () {
726 this.thenEvaluate(function () {
726 for (var event in IPython._event_handlers) {
727 for (var event in IPython._event_handlers) {
727 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
728 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
728 delete IPython._event_handlers[event];
729 delete IPython._event_handlers[event];
729 }
730 }
730 });
731 });
731 };
732 };
732
733
733 casper.options.waitTimeout=10000;
734 casper.options.waitTimeout=10000;
734 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
735 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
735 this.echo("Timeout for " + casper.get_notebook_server());
736 this.echo("Timeout for " + casper.get_notebook_server());
736 this.echo("Is the notebook server running?");
737 this.echo("Is the notebook server running?");
737 });
738 });
738
739
739 casper.print_log = function () {
740 casper.print_log = function () {
740 // Pass `console.log` calls from page JS to casper.
741 // Pass `console.log` calls from page JS to casper.
741 this.on('remote.message', function(msg) {
742 this.on('remote.message', function(msg) {
742 this.echo('Remote message caught: ' + msg);
743 this.echo('Remote message caught: ' + msg);
743 });
744 });
744 };
745 };
745
746
746 casper.on("page.error", function onError(msg, trace) {
747 casper.on("page.error", function onError(msg, trace) {
747 // show errors in the browser
748 // show errors in the browser
748 this.echo("Page Error");
749 this.echo("Page Error");
749 this.echo(" Message: " + msg.split('\n').join('\n '));
750 this.echo(" Message: " + msg.split('\n').join('\n '));
750 this.echo(" Call stack:");
751 this.echo(" Call stack:");
751 var local_path = this.get_notebook_server();
752 var local_path = this.get_notebook_server();
752 for (var i = 0; i < trace.length; i++) {
753 for (var i = 0; i < trace.length; i++) {
753 var frame = trace[i];
754 var frame = trace[i];
754 var file = frame.file;
755 var file = frame.file;
755 // shorten common phantomjs evaluate url
756 // shorten common phantomjs evaluate url
756 // this will have a different value on slimerjs
757 // this will have a different value on slimerjs
757 if (file === "phantomjs://webpage.evaluate()") {
758 if (file === "phantomjs://webpage.evaluate()") {
758 file = "evaluate";
759 file = "evaluate";
759 }
760 }
760 // remove the version tag from the path
761 // remove the version tag from the path
761 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
762 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
762 // remove the local address from the beginning of the path
763 // remove the local address from the beginning of the path
763 if (file.indexOf(local_path) === 0) {
764 if (file.indexOf(local_path) === 0) {
764 file = file.substr(local_path.length);
765 file = file.substr(local_path.length);
765 }
766 }
766 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
767 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
767 this.echo(" line " + frame.line + " of " + file + frame_text);
768 this.echo(" line " + frame.line + " of " + file + frame_text);
768 }
769 }
769 });
770 });
770
771
771
772
772 casper.capture_log = function () {
773 casper.capture_log = function () {
773 // show captured errors
774 // show captured errors
774 var captured_log = [];
775 var captured_log = [];
775 var seen_errors = 0;
776 var seen_errors = 0;
776 this.on('remote.message', function(msg) {
777 this.on('remote.message', function(msg) {
777 captured_log.push(msg);
778 captured_log.push(msg);
778 });
779 });
779
780
780 var that = this;
781 var that = this;
781 this.test.on("test.done", function (result) {
782 this.test.on("test.done", function (result) {
782 // test.done runs per-file,
783 // test.done runs per-file,
783 // but suiteResults is per-suite (directory)
784 // but suiteResults is per-suite (directory)
784 var current_errors;
785 var current_errors;
785 if (this.suiteResults) {
786 if (this.suiteResults) {
786 // casper 1.1 has suiteResults
787 // casper 1.1 has suiteResults
787 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
788 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
788 } else {
789 } else {
789 // casper 1.0 has testResults instead
790 // casper 1.0 has testResults instead
790 current_errors = this.testResults.failed;
791 current_errors = this.testResults.failed;
791 }
792 }
792
793
793 if (current_errors > seen_errors && captured_log.length > 0) {
794 if (current_errors > seen_errors && captured_log.length > 0) {
794 casper.echo("\nCaptured console.log:");
795 casper.echo("\nCaptured console.log:");
795 for (var i = 0; i < captured_log.length; i++) {
796 for (var i = 0; i < captured_log.length; i++) {
796 var output = String(captured_log[i]).split('\n');
797 var output = String(captured_log[i]).split('\n');
797 for (var j = 0; j < output.length; j++) {
798 for (var j = 0; j < output.length; j++) {
798 casper.echo(" " + output[j]);
799 casper.echo(" " + output[j]);
799 }
800 }
800 }
801 }
801 }
802 }
802
803
803 seen_errors = current_errors;
804 seen_errors = current_errors;
804 captured_log = [];
805 captured_log = [];
805 });
806 });
806 };
807 };
807
808
808 casper.interact = function() {
809 casper.interact = function() {
809 // Start an interactive Javascript console.
810 // Start an interactive Javascript console.
810 var system = require('system');
811 var system = require('system');
811 system.stdout.writeLine('JS interactive console.');
812 system.stdout.writeLine('JS interactive console.');
812 system.stdout.writeLine('Type `exit` to quit.');
813 system.stdout.writeLine('Type `exit` to quit.');
813
814
814 function read_line() {
815 function read_line() {
815 system.stdout.writeLine('JS: ');
816 system.stdout.writeLine('JS: ');
816 var line = system.stdin.readLine();
817 var line = system.stdin.readLine();
817 return line;
818 return line;
818 }
819 }
819
820
820 var input = read_line();
821 var input = read_line();
821 while (input.trim() != 'exit') {
822 while (input.trim() != 'exit') {
822 var output = this.evaluate(function(code) {
823 var output = this.evaluate(function(code) {
823 return String(eval(code));
824 return String(eval(code));
824 }, {code: input});
825 }, {code: input});
825 system.stdout.writeLine('\nOut: ' + output);
826 system.stdout.writeLine('\nOut: ' + output);
826 input = read_line();
827 input = read_line();
827 }
828 }
828 };
829 };
829
830
830 casper.capture_log();
831 casper.capture_log();
@@ -1,91 +1,92 b''
1 // Test widget bool class
1 // Test widget bool class
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 "use strict";
3
4
4 // Create a checkbox and togglebutton.
5 // Create a checkbox and togglebutton.
5 var bool_index = this.append_cell(
6 var bool_index = this.append_cell(
6 'from IPython.html import widgets\n' +
7 'from IPython.html import widgets\n' +
7 'from IPython.display import display, clear_output\n' +
8 'from IPython.display import display, clear_output\n' +
8 'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' +
9 'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' +
9 ' widgets.ToggleButton(description="Title", value=True)]\n' +
10 ' widgets.ToggleButton(description="Title", value=True)]\n' +
10 'display(bool_widgets[0])\n' +
11 'display(bool_widgets[0])\n' +
11 'display(bool_widgets[1])\n' +
12 'display(bool_widgets[1])\n' +
12 'print("Success")');
13 'print("Success")');
13 this.execute_cell_then(bool_index, function(index){
14 this.execute_cell_then(bool_index, function(index){
14 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
15 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
15 'Create bool widget cell executed with correct output.');
16 'Create bool widget cell executed with correct output.');
16 });
17 });
17
18
18 // Wait for the widgets to actually display.
19 // Wait for the widgets to actually display.
19 var widget_checkbox_selector = '.widget-area .widget-subarea .widget-hbox input';
20 var widget_checkbox_selector = '.widget-area .widget-subarea .widget-hbox input';
20 var widget_togglebutton_selector = '.widget-area .widget-subarea button';
21 var widget_togglebutton_selector = '.widget-area .widget-subarea button';
21 this.wait_for_element(bool_index, widget_checkbox_selector);
22 this.wait_for_element(bool_index, widget_checkbox_selector);
22 this.wait_for_element(bool_index, widget_togglebutton_selector);
23 this.wait_for_element(bool_index, widget_togglebutton_selector);
23
24
24 // Continue the tests.
25 // Continue the tests.
25 this.then(function() {
26 this.then(function() {
26 this.test.assert(this.cell_element_exists(bool_index,
27 this.test.assert(this.cell_element_exists(bool_index,
27 '.widget-area .widget-subarea'),
28 '.widget-area .widget-subarea'),
28 'Widget subarea exists.');
29 'Widget subarea exists.');
29
30
30 this.test.assert(this.cell_element_exists(bool_index,
31 this.test.assert(this.cell_element_exists(bool_index,
31 widget_checkbox_selector),
32 widget_checkbox_selector),
32 'Checkbox exists.');
33 'Checkbox exists.');
33
34
34 this.test.assert(this.cell_element_function(bool_index,
35 this.test.assert(this.cell_element_function(bool_index,
35 widget_checkbox_selector, 'prop', ['checked']),
36 widget_checkbox_selector, 'prop', ['checked']),
36 'Checkbox is checked.');
37 'Checkbox is checked.');
37
38
38 this.test.assert(this.cell_element_exists(bool_index,
39 this.test.assert(this.cell_element_exists(bool_index,
39 '.widget-area .widget-subarea .widget-hbox .widget-label'),
40 '.widget-area .widget-subarea .widget-hbox .widget-label'),
40 'Checkbox label exists.');
41 'Checkbox label exists.');
41
42
42 this.test.assert(this.cell_element_function(bool_index,
43 this.test.assert(this.cell_element_function(bool_index,
43 '.widget-area .widget-subarea .widget-hbox .widget-label', 'html')=="Title",
44 '.widget-area .widget-subarea .widget-hbox .widget-label', 'html')=="Title",
44 'Checkbox labeled correctly.');
45 'Checkbox labeled correctly.');
45
46
46 this.test.assert(this.cell_element_exists(bool_index,
47 this.test.assert(this.cell_element_exists(bool_index,
47 widget_togglebutton_selector),
48 widget_togglebutton_selector),
48 'Toggle button exists.');
49 'Toggle button exists.');
49
50
50 this.test.assert(this.cell_element_function(bool_index,
51 this.test.assert(this.cell_element_function(bool_index,
51 widget_togglebutton_selector, 'html')=="Title",
52 widget_togglebutton_selector, 'html')=="Title",
52 'Toggle button labeled correctly.');
53 'Toggle button labeled correctly.');
53
54
54 this.test.assert(this.cell_element_function(bool_index,
55 this.test.assert(this.cell_element_function(bool_index,
55 widget_togglebutton_selector, 'hasClass', ['active']),
56 widget_togglebutton_selector, 'hasClass', ['active']),
56 'Toggle button is toggled.');
57 'Toggle button is toggled.');
57 });
58 });
58
59
59 // Try changing the state of the widgets programatically.
60 // Try changing the state of the widgets programatically.
60 index = this.append_cell(
61 var index = this.append_cell(
61 'bool_widgets[0].value = False\n' +
62 'bool_widgets[0].value = False\n' +
62 'bool_widgets[1].value = False\n' +
63 'bool_widgets[1].value = False\n' +
63 'print("Success")');
64 'print("Success")');
64 this.execute_cell_then(index, function(index){
65 this.execute_cell_then(index, function(index){
65 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
66 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
66 'Change bool widget value cell executed with correct output.');
67 'Change bool widget value cell executed with correct output.');
67
68
68 this.test.assert(! this.cell_element_function(bool_index,
69 this.test.assert(! this.cell_element_function(bool_index,
69 widget_checkbox_selector, 'prop', ['checked']),
70 widget_checkbox_selector, 'prop', ['checked']),
70 'Checkbox is not checked. (1)');
71 'Checkbox is not checked. (1)');
71
72
72 this.test.assert(! this.cell_element_function(bool_index,
73 this.test.assert(! this.cell_element_function(bool_index,
73 widget_togglebutton_selector, 'hasClass', ['active']),
74 widget_togglebutton_selector, 'hasClass', ['active']),
74 'Toggle button is not toggled. (1)');
75 'Toggle button is not toggled. (1)');
75
76
76 // Try toggling the bool by clicking on the checkbox.
77 // Try toggling the bool by clicking on the checkbox.
77 this.cell_element_function(bool_index, widget_checkbox_selector, 'click');
78 this.cell_element_function(bool_index, widget_checkbox_selector, 'click');
78
79
79 this.test.assert(this.cell_element_function(bool_index,
80 this.test.assert(this.cell_element_function(bool_index,
80 widget_checkbox_selector, 'prop', ['checked']),
81 widget_checkbox_selector, 'prop', ['checked']),
81 'Checkbox is checked. (2)');
82 'Checkbox is checked. (2)');
82
83
83 // Try toggling the bool by clicking on the toggle button.
84 // Try toggling the bool by clicking on the toggle button.
84 this.cell_element_function(bool_index, widget_togglebutton_selector, 'click');
85 this.cell_element_function(bool_index, widget_togglebutton_selector, 'click');
85
86
86 this.test.assert(this.cell_element_function(bool_index,
87 this.test.assert(this.cell_element_function(bool_index,
87 widget_togglebutton_selector, 'hasClass', ['active']),
88 widget_togglebutton_selector, 'hasClass', ['active']),
88 'Toggle button is toggled. (3)');
89 'Toggle button is toggled. (3)');
89
90
90 });
91 });
91 }); No newline at end of file
92 });
@@ -1,48 +1,49 b''
1 // Test image class
1 // Test image class
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 index = this.append_cell(
3 "use strict";
4 var index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 'print("Success")');
7 this.execute_cell_then(index);
8 this.execute_cell_then(index);
8
9
9 // Get the temporary directory that the test server is running in.
10 // Get the temporary directory that the test server is running in.
10 var cwd = '';
11 var cwd = '';
11 index = this.append_cell('!echo $(pwd)');
12 index = this.append_cell('!echo $(pwd)');
12 this.execute_cell_then(index, function(index){
13 this.execute_cell_then(index, function(index){
13 cwd = this.get_output_cell(index).text.trim();
14 cwd = this.get_output_cell(index).text.trim();
14 });
15 });
15
16
16 var test_jpg = '/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABUBAQEAAAAAAAAAAAAAAAAAAAME/9oADAMBAAIQAxAAAAECv//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z';
17 var test_jpg = '/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABUBAQEAAAAAAAAAAAAAAAAAAAME/9oADAMBAAIQAxAAAAECv//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z';
17
18
18 var image_index = this.append_cell(
19 var image_index = this.append_cell(
19 'import base64\n' +
20 'import base64\n' +
20 'data = base64.b64decode("' + test_jpg + '")\n' +
21 'data = base64.b64decode("' + test_jpg + '")\n' +
21 'image = widgets.Image()\n' +
22 'image = widgets.Image()\n' +
22 'image.format = "jpeg"\n' +
23 'image.format = "jpeg"\n' +
23 'image.value = data\n' +
24 'image.value = data\n' +
24 'image.width = "50px"\n' +
25 'image.width = "50px"\n' +
25 'image.height = "50px"\n' +
26 'image.height = "50px"\n' +
26 'display(image)\n' +
27 'display(image)\n' +
27 'print("Success")\n');
28 'print("Success")\n');
28 this.execute_cell_then(image_index, function(index){
29 this.execute_cell_then(image_index, function(index){
29 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
30 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
30 'Create image executed with correct output.');
31 'Create image executed with correct output.');
31 });
32 });
32
33
33 // Wait for the widget to actually display.
34 // Wait for the widget to actually display.
34 var img_selector = '.widget-area .widget-subarea img';
35 var img_selector = '.widget-area .widget-subarea img';
35 this.wait_for_element(image_index, img_selector);
36 this.wait_for_element(image_index, img_selector);
36
37
37 this.then(function(){
38 this.then(function(){
38 this.test.assert(this.cell_element_exists(image_index,
39 this.test.assert(this.cell_element_exists(image_index,
39 '.widget-area .widget-subarea'),
40 '.widget-area .widget-subarea'),
40 'Widget subarea exists.');
41 'Widget subarea exists.');
41
42
42 this.test.assert(this.cell_element_exists(image_index, img_selector), 'Image exists.');
43 this.test.assert(this.cell_element_exists(image_index, img_selector), 'Image exists.');
43
44
44 // Verify that the image's base64 data has made it into the DOM.
45 // Verify that the image's base64 data has made it into the DOM.
45 var img_src = this.cell_element_function(image_index, img_selector, 'attr', ['src']);
46 var img_src = this.cell_element_function(image_index, img_selector, 'attr', ['src']);
46 this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.');
47 this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.');
47 });
48 });
48 }); No newline at end of file
49 });
@@ -1,586 +1,587 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import tempfile
14 import tempfile
15 import glob
15 import glob
16 from warnings import warn
16 from warnings import warn
17 from hashlib import md5
17 from hashlib import md5
18
18
19 import IPython
19 import IPython
20 from IPython.testing.skipdoctest import skip_doctest
20 from IPython.testing.skipdoctest import skip_doctest
21 from IPython.utils.process import system
21 from IPython.utils.process import system
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23 from IPython.utils import py3compat
23 from IPython.utils import py3compat
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Code
25 # Code
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 fs_encoding = sys.getfilesystemencoding()
28 fs_encoding = sys.getfilesystemencoding()
29
29
30 def _get_long_path_name(path):
31 """Dummy no-op."""
32 return path
33
34 def _writable_dir(path):
30 def _writable_dir(path):
35 """Whether `path` is a directory, to which the user has write access."""
31 """Whether `path` is a directory, to which the user has write access."""
36 return os.path.isdir(path) and os.access(path, os.W_OK)
32 return os.path.isdir(path) and os.access(path, os.W_OK)
37
33
38 if sys.platform == 'win32':
34 if sys.platform == 'win32':
39 @skip_doctest
35 @skip_doctest
40 def _get_long_path_name(path):
36 def _get_long_path_name(path):
41 """Get a long path name (expand ~) on Windows using ctypes.
37 """Get a long path name (expand ~) on Windows using ctypes.
42
38
43 Examples
39 Examples
44 --------
40 --------
45
41
46 >>> get_long_path_name('c:\\docume~1')
42 >>> get_long_path_name('c:\\docume~1')
47 u'c:\\\\Documents and Settings'
43 u'c:\\\\Documents and Settings'
48
44
49 """
45 """
50 try:
46 try:
51 import ctypes
47 import ctypes
52 except ImportError:
48 except ImportError:
53 raise ImportError('you need to have ctypes installed for this to work')
49 raise ImportError('you need to have ctypes installed for this to work')
54 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
50 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
55 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
51 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
56 ctypes.c_uint ]
52 ctypes.c_uint ]
57
53
58 buf = ctypes.create_unicode_buffer(260)
54 buf = ctypes.create_unicode_buffer(260)
59 rv = _GetLongPathName(path, buf, 260)
55 rv = _GetLongPathName(path, buf, 260)
60 if rv == 0 or rv > 260:
56 if rv == 0 or rv > 260:
61 return path
57 return path
62 else:
58 else:
63 return buf.value
59 return buf.value
60 else:
61 def _get_long_path_name(path):
62 """Dummy no-op."""
63 return path
64
64
65
65
66
66 def get_long_path_name(path):
67 def get_long_path_name(path):
67 """Expand a path into its long form.
68 """Expand a path into its long form.
68
69
69 On Windows this expands any ~ in the paths. On other platforms, it is
70 On Windows this expands any ~ in the paths. On other platforms, it is
70 a null operation.
71 a null operation.
71 """
72 """
72 return _get_long_path_name(path)
73 return _get_long_path_name(path)
73
74
74
75
75 def unquote_filename(name, win32=(sys.platform=='win32')):
76 def unquote_filename(name, win32=(sys.platform=='win32')):
76 """ On Windows, remove leading and trailing quotes from filenames.
77 """ On Windows, remove leading and trailing quotes from filenames.
77 """
78 """
78 if win32:
79 if win32:
79 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
80 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
80 name = name[1:-1]
81 name = name[1:-1]
81 return name
82 return name
82
83
83 def compress_user(path):
84 def compress_user(path):
84 """Reverse of :func:`os.path.expanduser`
85 """Reverse of :func:`os.path.expanduser`
85 """
86 """
86 home = os.path.expanduser('~')
87 home = os.path.expanduser('~')
87 if path.startswith(home):
88 if path.startswith(home):
88 path = "~" + path[len(home):]
89 path = "~" + path[len(home):]
89 return path
90 return path
90
91
91 def get_py_filename(name, force_win32=None):
92 def get_py_filename(name, force_win32=None):
92 """Return a valid python filename in the current directory.
93 """Return a valid python filename in the current directory.
93
94
94 If the given name is not a file, it adds '.py' and searches again.
95 If the given name is not a file, it adds '.py' and searches again.
95 Raises IOError with an informative message if the file isn't found.
96 Raises IOError with an informative message if the file isn't found.
96
97
97 On Windows, apply Windows semantics to the filename. In particular, remove
98 On Windows, apply Windows semantics to the filename. In particular, remove
98 any quoting that has been applied to it. This option can be forced for
99 any quoting that has been applied to it. This option can be forced for
99 testing purposes.
100 testing purposes.
100 """
101 """
101
102
102 name = os.path.expanduser(name)
103 name = os.path.expanduser(name)
103 if force_win32 is None:
104 if force_win32 is None:
104 win32 = (sys.platform == 'win32')
105 win32 = (sys.platform == 'win32')
105 else:
106 else:
106 win32 = force_win32
107 win32 = force_win32
107 name = unquote_filename(name, win32=win32)
108 name = unquote_filename(name, win32=win32)
108 if not os.path.isfile(name) and not name.endswith('.py'):
109 if not os.path.isfile(name) and not name.endswith('.py'):
109 name += '.py'
110 name += '.py'
110 if os.path.isfile(name):
111 if os.path.isfile(name):
111 return name
112 return name
112 else:
113 else:
113 raise IOError('File `%r` not found.' % name)
114 raise IOError('File `%r` not found.' % name)
114
115
115
116
116 def filefind(filename, path_dirs=None):
117 def filefind(filename, path_dirs=None):
117 """Find a file by looking through a sequence of paths.
118 """Find a file by looking through a sequence of paths.
118
119
119 This iterates through a sequence of paths looking for a file and returns
120 This iterates through a sequence of paths looking for a file and returns
120 the full, absolute path of the first occurence of the file. If no set of
121 the full, absolute path of the first occurence of the file. If no set of
121 path dirs is given, the filename is tested as is, after running through
122 path dirs is given, the filename is tested as is, after running through
122 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
123 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
123
124
124 filefind('myfile.txt')
125 filefind('myfile.txt')
125
126
126 will find the file in the current working dir, but::
127 will find the file in the current working dir, but::
127
128
128 filefind('~/myfile.txt')
129 filefind('~/myfile.txt')
129
130
130 Will find the file in the users home directory. This function does not
131 Will find the file in the users home directory. This function does not
131 automatically try any paths, such as the cwd or the user's home directory.
132 automatically try any paths, such as the cwd or the user's home directory.
132
133
133 Parameters
134 Parameters
134 ----------
135 ----------
135 filename : str
136 filename : str
136 The filename to look for.
137 The filename to look for.
137 path_dirs : str, None or sequence of str
138 path_dirs : str, None or sequence of str
138 The sequence of paths to look for the file in. If None, the filename
139 The sequence of paths to look for the file in. If None, the filename
139 need to be absolute or be in the cwd. If a string, the string is
140 need to be absolute or be in the cwd. If a string, the string is
140 put into a sequence and the searched. If a sequence, walk through
141 put into a sequence and the searched. If a sequence, walk through
141 each element and join with ``filename``, calling :func:`expandvars`
142 each element and join with ``filename``, calling :func:`expandvars`
142 and :func:`expanduser` before testing for existence.
143 and :func:`expanduser` before testing for existence.
143
144
144 Returns
145 Returns
145 -------
146 -------
146 Raises :exc:`IOError` or returns absolute path to file.
147 Raises :exc:`IOError` or returns absolute path to file.
147 """
148 """
148
149
149 # If paths are quoted, abspath gets confused, strip them...
150 # If paths are quoted, abspath gets confused, strip them...
150 filename = filename.strip('"').strip("'")
151 filename = filename.strip('"').strip("'")
151 # If the input is an absolute path, just check it exists
152 # If the input is an absolute path, just check it exists
152 if os.path.isabs(filename) and os.path.isfile(filename):
153 if os.path.isabs(filename) and os.path.isfile(filename):
153 return filename
154 return filename
154
155
155 if path_dirs is None:
156 if path_dirs is None:
156 path_dirs = ("",)
157 path_dirs = ("",)
157 elif isinstance(path_dirs, py3compat.string_types):
158 elif isinstance(path_dirs, py3compat.string_types):
158 path_dirs = (path_dirs,)
159 path_dirs = (path_dirs,)
159
160
160 for path in path_dirs:
161 for path in path_dirs:
161 if path == '.': path = py3compat.getcwd()
162 if path == '.': path = py3compat.getcwd()
162 testname = expand_path(os.path.join(path, filename))
163 testname = expand_path(os.path.join(path, filename))
163 if os.path.isfile(testname):
164 if os.path.isfile(testname):
164 return os.path.abspath(testname)
165 return os.path.abspath(testname)
165
166
166 raise IOError("File %r does not exist in any of the search paths: %r" %
167 raise IOError("File %r does not exist in any of the search paths: %r" %
167 (filename, path_dirs) )
168 (filename, path_dirs) )
168
169
169
170
170 class HomeDirError(Exception):
171 class HomeDirError(Exception):
171 pass
172 pass
172
173
173
174
174 def get_home_dir(require_writable=False):
175 def get_home_dir(require_writable=False):
175 """Return the 'home' directory, as a unicode string.
176 """Return the 'home' directory, as a unicode string.
176
177
177 Uses os.path.expanduser('~'), and checks for writability.
178 Uses os.path.expanduser('~'), and checks for writability.
178
179
179 See stdlib docs for how this is determined.
180 See stdlib docs for how this is determined.
180 $HOME is first priority on *ALL* platforms.
181 $HOME is first priority on *ALL* platforms.
181
182
182 Parameters
183 Parameters
183 ----------
184 ----------
184
185
185 require_writable : bool [default: False]
186 require_writable : bool [default: False]
186 if True:
187 if True:
187 guarantees the return value is a writable directory, otherwise
188 guarantees the return value is a writable directory, otherwise
188 raises HomeDirError
189 raises HomeDirError
189 if False:
190 if False:
190 The path is resolved, but it is not guaranteed to exist or be writable.
191 The path is resolved, but it is not guaranteed to exist or be writable.
191 """
192 """
192
193
193 homedir = os.path.expanduser('~')
194 homedir = os.path.expanduser('~')
194 # Next line will make things work even when /home/ is a symlink to
195 # Next line will make things work even when /home/ is a symlink to
195 # /usr/home as it is on FreeBSD, for example
196 # /usr/home as it is on FreeBSD, for example
196 homedir = os.path.realpath(homedir)
197 homedir = os.path.realpath(homedir)
197
198
198 if not _writable_dir(homedir) and os.name == 'nt':
199 if not _writable_dir(homedir) and os.name == 'nt':
199 # expanduser failed, use the registry to get the 'My Documents' folder.
200 # expanduser failed, use the registry to get the 'My Documents' folder.
200 try:
201 try:
201 try:
202 try:
202 import winreg as wreg # Py 3
203 import winreg as wreg # Py 3
203 except ImportError:
204 except ImportError:
204 import _winreg as wreg # Py 2
205 import _winreg as wreg # Py 2
205 key = wreg.OpenKey(
206 key = wreg.OpenKey(
206 wreg.HKEY_CURRENT_USER,
207 wreg.HKEY_CURRENT_USER,
207 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
208 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
208 )
209 )
209 homedir = wreg.QueryValueEx(key,'Personal')[0]
210 homedir = wreg.QueryValueEx(key,'Personal')[0]
210 key.Close()
211 key.Close()
211 except:
212 except:
212 pass
213 pass
213
214
214 if (not require_writable) or _writable_dir(homedir):
215 if (not require_writable) or _writable_dir(homedir):
215 return py3compat.cast_unicode(homedir, fs_encoding)
216 return py3compat.cast_unicode(homedir, fs_encoding)
216 else:
217 else:
217 raise HomeDirError('%s is not a writable dir, '
218 raise HomeDirError('%s is not a writable dir, '
218 'set $HOME environment variable to override' % homedir)
219 'set $HOME environment variable to override' % homedir)
219
220
220 def get_xdg_dir():
221 def get_xdg_dir():
221 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
222 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
222
223
223 This is only for non-OS X posix (Linux,Unix,etc.) systems.
224 This is only for non-OS X posix (Linux,Unix,etc.) systems.
224 """
225 """
225
226
226 env = os.environ
227 env = os.environ
227
228
228 if os.name == 'posix' and sys.platform != 'darwin':
229 if os.name == 'posix' and sys.platform != 'darwin':
229 # Linux, Unix, AIX, etc.
230 # Linux, Unix, AIX, etc.
230 # use ~/.config if empty OR not set
231 # use ~/.config if empty OR not set
231 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
232 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
232 if xdg and _writable_dir(xdg):
233 if xdg and _writable_dir(xdg):
233 return py3compat.cast_unicode(xdg, fs_encoding)
234 return py3compat.cast_unicode(xdg, fs_encoding)
234
235
235 return None
236 return None
236
237
237
238
238 def get_xdg_cache_dir():
239 def get_xdg_cache_dir():
239 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
240 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
240
241
241 This is only for non-OS X posix (Linux,Unix,etc.) systems.
242 This is only for non-OS X posix (Linux,Unix,etc.) systems.
242 """
243 """
243
244
244 env = os.environ
245 env = os.environ
245
246
246 if os.name == 'posix' and sys.platform != 'darwin':
247 if os.name == 'posix' and sys.platform != 'darwin':
247 # Linux, Unix, AIX, etc.
248 # Linux, Unix, AIX, etc.
248 # use ~/.cache if empty OR not set
249 # use ~/.cache if empty OR not set
249 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
250 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
250 if xdg and _writable_dir(xdg):
251 if xdg and _writable_dir(xdg):
251 return py3compat.cast_unicode(xdg, fs_encoding)
252 return py3compat.cast_unicode(xdg, fs_encoding)
252
253
253 return None
254 return None
254
255
255
256
256 def get_ipython_dir():
257 def get_ipython_dir():
257 """Get the IPython directory for this platform and user.
258 """Get the IPython directory for this platform and user.
258
259
259 This uses the logic in `get_home_dir` to find the home directory
260 This uses the logic in `get_home_dir` to find the home directory
260 and then adds .ipython to the end of the path.
261 and then adds .ipython to the end of the path.
261 """
262 """
262
263
263 env = os.environ
264 env = os.environ
264 pjoin = os.path.join
265 pjoin = os.path.join
265
266
266
267
267 ipdir_def = '.ipython'
268 ipdir_def = '.ipython'
268
269
269 home_dir = get_home_dir()
270 home_dir = get_home_dir()
270 xdg_dir = get_xdg_dir()
271 xdg_dir = get_xdg_dir()
271
272
272 # import pdb; pdb.set_trace() # dbg
273 # import pdb; pdb.set_trace() # dbg
273 if 'IPYTHON_DIR' in env:
274 if 'IPYTHON_DIR' in env:
274 warn('The environment variable IPYTHON_DIR is deprecated. '
275 warn('The environment variable IPYTHON_DIR is deprecated. '
275 'Please use IPYTHONDIR instead.')
276 'Please use IPYTHONDIR instead.')
276 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
277 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
277 if ipdir is None:
278 if ipdir is None:
278 # not set explicitly, use ~/.ipython
279 # not set explicitly, use ~/.ipython
279 ipdir = pjoin(home_dir, ipdir_def)
280 ipdir = pjoin(home_dir, ipdir_def)
280 if xdg_dir:
281 if xdg_dir:
281 # Several IPython versions (up to 1.x) defaulted to .config/ipython
282 # Several IPython versions (up to 1.x) defaulted to .config/ipython
282 # on Linux. We have decided to go back to using .ipython everywhere
283 # on Linux. We have decided to go back to using .ipython everywhere
283 xdg_ipdir = pjoin(xdg_dir, 'ipython')
284 xdg_ipdir = pjoin(xdg_dir, 'ipython')
284
285
285 if _writable_dir(xdg_ipdir):
286 if _writable_dir(xdg_ipdir):
286 cu = compress_user
287 cu = compress_user
287 if os.path.exists(ipdir):
288 if os.path.exists(ipdir):
288 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
289 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
289 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
290 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
290 elif os.path.islink(xdg_ipdir):
291 elif os.path.islink(xdg_ipdir):
291 warn(('{0} is deprecated. Move link to {1} to '
292 warn(('{0} is deprecated. Move link to {1} to '
292 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
293 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
293 else:
294 else:
294 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
295 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
295 shutil.move(xdg_ipdir, ipdir)
296 shutil.move(xdg_ipdir, ipdir)
296
297
297 ipdir = os.path.normpath(os.path.expanduser(ipdir))
298 ipdir = os.path.normpath(os.path.expanduser(ipdir))
298
299
299 if os.path.exists(ipdir) and not _writable_dir(ipdir):
300 if os.path.exists(ipdir) and not _writable_dir(ipdir):
300 # ipdir exists, but is not writable
301 # ipdir exists, but is not writable
301 warn("IPython dir '{0}' is not a writable location,"
302 warn("IPython dir '{0}' is not a writable location,"
302 " using a temp directory.".format(ipdir))
303 " using a temp directory.".format(ipdir))
303 ipdir = tempfile.mkdtemp()
304 ipdir = tempfile.mkdtemp()
304 elif not os.path.exists(ipdir):
305 elif not os.path.exists(ipdir):
305 parent = os.path.dirname(ipdir)
306 parent = os.path.dirname(ipdir)
306 if not _writable_dir(parent):
307 if not _writable_dir(parent):
307 # ipdir does not exist and parent isn't writable
308 # ipdir does not exist and parent isn't writable
308 warn("IPython parent '{0}' is not a writable location,"
309 warn("IPython parent '{0}' is not a writable location,"
309 " using a temp directory.".format(parent))
310 " using a temp directory.".format(parent))
310 ipdir = tempfile.mkdtemp()
311 ipdir = tempfile.mkdtemp()
311
312
312 return py3compat.cast_unicode(ipdir, fs_encoding)
313 return py3compat.cast_unicode(ipdir, fs_encoding)
313
314
314
315
315 def get_ipython_cache_dir():
316 def get_ipython_cache_dir():
316 """Get the cache directory it is created if it does not exist."""
317 """Get the cache directory it is created if it does not exist."""
317 xdgdir = get_xdg_cache_dir()
318 xdgdir = get_xdg_cache_dir()
318 if xdgdir is None:
319 if xdgdir is None:
319 return get_ipython_dir()
320 return get_ipython_dir()
320 ipdir = os.path.join(xdgdir, "ipython")
321 ipdir = os.path.join(xdgdir, "ipython")
321 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
322 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
322 ensure_dir_exists(ipdir)
323 ensure_dir_exists(ipdir)
323 elif not _writable_dir(xdgdir):
324 elif not _writable_dir(xdgdir):
324 return get_ipython_dir()
325 return get_ipython_dir()
325
326
326 return py3compat.cast_unicode(ipdir, fs_encoding)
327 return py3compat.cast_unicode(ipdir, fs_encoding)
327
328
328
329
329 def get_ipython_package_dir():
330 def get_ipython_package_dir():
330 """Get the base directory where IPython itself is installed."""
331 """Get the base directory where IPython itself is installed."""
331 ipdir = os.path.dirname(IPython.__file__)
332 ipdir = os.path.dirname(IPython.__file__)
332 return py3compat.cast_unicode(ipdir, fs_encoding)
333 return py3compat.cast_unicode(ipdir, fs_encoding)
333
334
334
335
335 def get_ipython_module_path(module_str):
336 def get_ipython_module_path(module_str):
336 """Find the path to an IPython module in this version of IPython.
337 """Find the path to an IPython module in this version of IPython.
337
338
338 This will always find the version of the module that is in this importable
339 This will always find the version of the module that is in this importable
339 IPython package. This will always return the path to the ``.py``
340 IPython package. This will always return the path to the ``.py``
340 version of the module.
341 version of the module.
341 """
342 """
342 if module_str == 'IPython':
343 if module_str == 'IPython':
343 return os.path.join(get_ipython_package_dir(), '__init__.py')
344 return os.path.join(get_ipython_package_dir(), '__init__.py')
344 mod = import_item(module_str)
345 mod = import_item(module_str)
345 the_path = mod.__file__.replace('.pyc', '.py')
346 the_path = mod.__file__.replace('.pyc', '.py')
346 the_path = the_path.replace('.pyo', '.py')
347 the_path = the_path.replace('.pyo', '.py')
347 return py3compat.cast_unicode(the_path, fs_encoding)
348 return py3compat.cast_unicode(the_path, fs_encoding)
348
349
349 def locate_profile(profile='default'):
350 def locate_profile(profile='default'):
350 """Find the path to the folder associated with a given profile.
351 """Find the path to the folder associated with a given profile.
351
352
352 I.e. find $IPYTHONDIR/profile_whatever.
353 I.e. find $IPYTHONDIR/profile_whatever.
353 """
354 """
354 from IPython.core.profiledir import ProfileDir, ProfileDirError
355 from IPython.core.profiledir import ProfileDir, ProfileDirError
355 try:
356 try:
356 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
357 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
357 except ProfileDirError:
358 except ProfileDirError:
358 # IOError makes more sense when people are expecting a path
359 # IOError makes more sense when people are expecting a path
359 raise IOError("Couldn't find profile %r" % profile)
360 raise IOError("Couldn't find profile %r" % profile)
360 return pd.location
361 return pd.location
361
362
362 def expand_path(s):
363 def expand_path(s):
363 """Expand $VARS and ~names in a string, like a shell
364 """Expand $VARS and ~names in a string, like a shell
364
365
365 :Examples:
366 :Examples:
366
367
367 In [2]: os.environ['FOO']='test'
368 In [2]: os.environ['FOO']='test'
368
369
369 In [3]: expand_path('variable FOO is $FOO')
370 In [3]: expand_path('variable FOO is $FOO')
370 Out[3]: 'variable FOO is test'
371 Out[3]: 'variable FOO is test'
371 """
372 """
372 # This is a pretty subtle hack. When expand user is given a UNC path
373 # This is a pretty subtle hack. When expand user is given a UNC path
373 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
374 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
374 # the $ to get (\\server\share\%username%). I think it considered $
375 # the $ to get (\\server\share\%username%). I think it considered $
375 # alone an empty var. But, we need the $ to remains there (it indicates
376 # alone an empty var. But, we need the $ to remains there (it indicates
376 # a hidden share).
377 # a hidden share).
377 if os.name=='nt':
378 if os.name=='nt':
378 s = s.replace('$\\', 'IPYTHON_TEMP')
379 s = s.replace('$\\', 'IPYTHON_TEMP')
379 s = os.path.expandvars(os.path.expanduser(s))
380 s = os.path.expandvars(os.path.expanduser(s))
380 if os.name=='nt':
381 if os.name=='nt':
381 s = s.replace('IPYTHON_TEMP', '$\\')
382 s = s.replace('IPYTHON_TEMP', '$\\')
382 return s
383 return s
383
384
384
385
385 def unescape_glob(string):
386 def unescape_glob(string):
386 """Unescape glob pattern in `string`."""
387 """Unescape glob pattern in `string`."""
387 def unescape(s):
388 def unescape(s):
388 for pattern in '*[]!?':
389 for pattern in '*[]!?':
389 s = s.replace(r'\{0}'.format(pattern), pattern)
390 s = s.replace(r'\{0}'.format(pattern), pattern)
390 return s
391 return s
391 return '\\'.join(map(unescape, string.split('\\\\')))
392 return '\\'.join(map(unescape, string.split('\\\\')))
392
393
393
394
394 def shellglob(args):
395 def shellglob(args):
395 """
396 """
396 Do glob expansion for each element in `args` and return a flattened list.
397 Do glob expansion for each element in `args` and return a flattened list.
397
398
398 Unmatched glob pattern will remain as-is in the returned list.
399 Unmatched glob pattern will remain as-is in the returned list.
399
400
400 """
401 """
401 expanded = []
402 expanded = []
402 # Do not unescape backslash in Windows as it is interpreted as
403 # Do not unescape backslash in Windows as it is interpreted as
403 # path separator:
404 # path separator:
404 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
405 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
405 for a in args:
406 for a in args:
406 expanded.extend(glob.glob(a) or [unescape(a)])
407 expanded.extend(glob.glob(a) or [unescape(a)])
407 return expanded
408 return expanded
408
409
409
410
410 def target_outdated(target,deps):
411 def target_outdated(target,deps):
411 """Determine whether a target is out of date.
412 """Determine whether a target is out of date.
412
413
413 target_outdated(target,deps) -> 1/0
414 target_outdated(target,deps) -> 1/0
414
415
415 deps: list of filenames which MUST exist.
416 deps: list of filenames which MUST exist.
416 target: single filename which may or may not exist.
417 target: single filename which may or may not exist.
417
418
418 If target doesn't exist or is older than any file listed in deps, return
419 If target doesn't exist or is older than any file listed in deps, return
419 true, otherwise return false.
420 true, otherwise return false.
420 """
421 """
421 try:
422 try:
422 target_time = os.path.getmtime(target)
423 target_time = os.path.getmtime(target)
423 except os.error:
424 except os.error:
424 return 1
425 return 1
425 for dep in deps:
426 for dep in deps:
426 dep_time = os.path.getmtime(dep)
427 dep_time = os.path.getmtime(dep)
427 if dep_time > target_time:
428 if dep_time > target_time:
428 #print "For target",target,"Dep failed:",dep # dbg
429 #print "For target",target,"Dep failed:",dep # dbg
429 #print "times (dep,tar):",dep_time,target_time # dbg
430 #print "times (dep,tar):",dep_time,target_time # dbg
430 return 1
431 return 1
431 return 0
432 return 0
432
433
433
434
434 def target_update(target,deps,cmd):
435 def target_update(target,deps,cmd):
435 """Update a target with a given command given a list of dependencies.
436 """Update a target with a given command given a list of dependencies.
436
437
437 target_update(target,deps,cmd) -> runs cmd if target is outdated.
438 target_update(target,deps,cmd) -> runs cmd if target is outdated.
438
439
439 This is just a wrapper around target_outdated() which calls the given
440 This is just a wrapper around target_outdated() which calls the given
440 command if target is outdated."""
441 command if target is outdated."""
441
442
442 if target_outdated(target,deps):
443 if target_outdated(target,deps):
443 system(cmd)
444 system(cmd)
444
445
445 def filehash(path):
446 def filehash(path):
446 """Make an MD5 hash of a file, ignoring any differences in line
447 """Make an MD5 hash of a file, ignoring any differences in line
447 ending characters."""
448 ending characters."""
448 with open(path, "rU") as f:
449 with open(path, "rU") as f:
449 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
450 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
450
451
451 # If the config is unmodified from the default, we'll just delete it.
452 # If the config is unmodified from the default, we'll just delete it.
452 # These are consistent for 0.10.x, thankfully. We're not going to worry about
453 # These are consistent for 0.10.x, thankfully. We're not going to worry about
453 # older versions.
454 # older versions.
454 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
455 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
455 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
456 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
456
457
457 def check_for_old_config(ipython_dir=None):
458 def check_for_old_config(ipython_dir=None):
458 """Check for old config files, and present a warning if they exist.
459 """Check for old config files, and present a warning if they exist.
459
460
460 A link to the docs of the new config is included in the message.
461 A link to the docs of the new config is included in the message.
461
462
462 This should mitigate confusion with the transition to the new
463 This should mitigate confusion with the transition to the new
463 config system in 0.11.
464 config system in 0.11.
464 """
465 """
465 if ipython_dir is None:
466 if ipython_dir is None:
466 ipython_dir = get_ipython_dir()
467 ipython_dir = get_ipython_dir()
467
468
468 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
469 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
469 warned = False
470 warned = False
470 for cfg in old_configs:
471 for cfg in old_configs:
471 f = os.path.join(ipython_dir, cfg)
472 f = os.path.join(ipython_dir, cfg)
472 if os.path.exists(f):
473 if os.path.exists(f):
473 if filehash(f) == old_config_md5.get(cfg, ''):
474 if filehash(f) == old_config_md5.get(cfg, ''):
474 os.unlink(f)
475 os.unlink(f)
475 else:
476 else:
476 warn("Found old IPython config file {!r} (modified by user)".format(f))
477 warn("Found old IPython config file {!r} (modified by user)".format(f))
477 warned = True
478 warned = True
478
479
479 if warned:
480 if warned:
480 warn("""
481 warn("""
481 The IPython configuration system has changed as of 0.11, and these files will
482 The IPython configuration system has changed as of 0.11, and these files will
482 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
483 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
483 of the new config system.
484 of the new config system.
484 To start configuring IPython, do `ipython profile create`, and edit
485 To start configuring IPython, do `ipython profile create`, and edit
485 `ipython_config.py` in <ipython_dir>/profile_default.
486 `ipython_config.py` in <ipython_dir>/profile_default.
486 If you need to leave the old config files in place for an older version of
487 If you need to leave the old config files in place for an older version of
487 IPython and want to suppress this warning message, set
488 IPython and want to suppress this warning message, set
488 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
489 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
489
490
490 def get_security_file(filename, profile='default'):
491 def get_security_file(filename, profile='default'):
491 """Return the absolute path of a security file given by filename and profile
492 """Return the absolute path of a security file given by filename and profile
492
493
493 This allows users and developers to find security files without
494 This allows users and developers to find security files without
494 knowledge of the IPython directory structure. The search path
495 knowledge of the IPython directory structure. The search path
495 will be ['.', profile.security_dir]
496 will be ['.', profile.security_dir]
496
497
497 Parameters
498 Parameters
498 ----------
499 ----------
499
500
500 filename : str
501 filename : str
501 The file to be found. If it is passed as an absolute path, it will
502 The file to be found. If it is passed as an absolute path, it will
502 simply be returned.
503 simply be returned.
503 profile : str [default: 'default']
504 profile : str [default: 'default']
504 The name of the profile to search. Leaving this unspecified
505 The name of the profile to search. Leaving this unspecified
505 The file to be found. If it is passed as an absolute path, fname will
506 The file to be found. If it is passed as an absolute path, fname will
506 simply be returned.
507 simply be returned.
507
508
508 Returns
509 Returns
509 -------
510 -------
510 Raises :exc:`IOError` if file not found or returns absolute path to file.
511 Raises :exc:`IOError` if file not found or returns absolute path to file.
511 """
512 """
512 # import here, because profiledir also imports from utils.path
513 # import here, because profiledir also imports from utils.path
513 from IPython.core.profiledir import ProfileDir
514 from IPython.core.profiledir import ProfileDir
514 try:
515 try:
515 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
516 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
516 except Exception:
517 except Exception:
517 # will raise ProfileDirError if no such profile
518 # will raise ProfileDirError if no such profile
518 raise IOError("Profile %r not found")
519 raise IOError("Profile %r not found")
519 return filefind(filename, ['.', pd.security_dir])
520 return filefind(filename, ['.', pd.security_dir])
520
521
521
522
522 ENOLINK = 1998
523 ENOLINK = 1998
523
524
524 def link(src, dst):
525 def link(src, dst):
525 """Hard links ``src`` to ``dst``, returning 0 or errno.
526 """Hard links ``src`` to ``dst``, returning 0 or errno.
526
527
527 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
528 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
528 supported by the operating system.
529 supported by the operating system.
529 """
530 """
530
531
531 if not hasattr(os, "link"):
532 if not hasattr(os, "link"):
532 return ENOLINK
533 return ENOLINK
533 link_errno = 0
534 link_errno = 0
534 try:
535 try:
535 os.link(src, dst)
536 os.link(src, dst)
536 except OSError as e:
537 except OSError as e:
537 link_errno = e.errno
538 link_errno = e.errno
538 return link_errno
539 return link_errno
539
540
540
541
541 def link_or_copy(src, dst):
542 def link_or_copy(src, dst):
542 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
543 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
543
544
544 Attempts to maintain the semantics of ``shutil.copy``.
545 Attempts to maintain the semantics of ``shutil.copy``.
545
546
546 Because ``os.link`` does not overwrite files, a unique temporary file
547 Because ``os.link`` does not overwrite files, a unique temporary file
547 will be used if the target already exists, then that file will be moved
548 will be used if the target already exists, then that file will be moved
548 into place.
549 into place.
549 """
550 """
550
551
551 if os.path.isdir(dst):
552 if os.path.isdir(dst):
552 dst = os.path.join(dst, os.path.basename(src))
553 dst = os.path.join(dst, os.path.basename(src))
553
554
554 link_errno = link(src, dst)
555 link_errno = link(src, dst)
555 if link_errno == errno.EEXIST:
556 if link_errno == errno.EEXIST:
556 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
557 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
557 try:
558 try:
558 link_or_copy(src, new_dst)
559 link_or_copy(src, new_dst)
559 except:
560 except:
560 try:
561 try:
561 os.remove(new_dst)
562 os.remove(new_dst)
562 except OSError:
563 except OSError:
563 pass
564 pass
564 raise
565 raise
565 os.rename(new_dst, dst)
566 os.rename(new_dst, dst)
566 elif link_errno != 0:
567 elif link_errno != 0:
567 # Either link isn't supported, or the filesystem doesn't support
568 # Either link isn't supported, or the filesystem doesn't support
568 # linking, or 'src' and 'dst' are on different filesystems.
569 # linking, or 'src' and 'dst' are on different filesystems.
569 shutil.copy(src, dst)
570 shutil.copy(src, dst)
570
571
571 def ensure_dir_exists(path, mode=0o755):
572 def ensure_dir_exists(path, mode=0o755):
572 """ensure that a directory exists
573 """ensure that a directory exists
573
574
574 If it doesn't exist, try to create it and protect against a race condition
575 If it doesn't exist, try to create it and protect against a race condition
575 if another process is doing the same.
576 if another process is doing the same.
576
577
577 The default permissions are 755, which differ from os.makedirs default of 777.
578 The default permissions are 755, which differ from os.makedirs default of 777.
578 """
579 """
579 if not os.path.exists(path):
580 if not os.path.exists(path):
580 try:
581 try:
581 os.makedirs(path, mode=mode)
582 os.makedirs(path, mode=mode)
582 except OSError as e:
583 except OSError as e:
583 if e.errno != errno.EEXIST:
584 if e.errno != errno.EEXIST:
584 raise
585 raise
585 elif not os.path.isdir(path):
586 elif not os.path.isdir(path):
586 raise IOError("%r exists but is not a directory" % path)
587 raise IOError("%r exists but is not a directory" % path)
@@ -1,164 +1,156 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with terminals.
3 Utilities for working with terminals.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian E. Granger
7 * Brian E. Granger
8 * Fernando Perez
8 * Fernando Perez
9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import struct
24 import struct
25 import sys
25 import sys
26 import warnings
26 import warnings
27
27
28 from . import py3compat
28 from . import py3compat
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Code
31 # Code
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 # This variable is part of the expected API of the module:
34 # This variable is part of the expected API of the module:
35 ignore_termtitle = True
35 ignore_termtitle = True
36
36
37
37
38 def _term_clear():
39 pass
40
41
38
42 if os.name == 'posix':
39 if os.name == 'posix':
43 def _term_clear():
40 def _term_clear():
44 os.system('clear')
41 os.system('clear')
45
42 elif sys.platform == 'win32':
46
47 if sys.platform == 'win32':
48 def _term_clear():
43 def _term_clear():
49 os.system('cls')
44 os.system('cls')
45 else:
46 def _term_clear():
47 pass
50
48
51
49
52 def term_clear():
53 _term_clear()
54
55
50
56 def toggle_set_term_title(val):
51 def toggle_set_term_title(val):
57 """Control whether set_term_title is active or not.
52 """Control whether set_term_title is active or not.
58
53
59 set_term_title() allows writing to the console titlebar. In embedded
54 set_term_title() allows writing to the console titlebar. In embedded
60 widgets this can cause problems, so this call can be used to toggle it on
55 widgets this can cause problems, so this call can be used to toggle it on
61 or off as needed.
56 or off as needed.
62
57
63 The default state of the module is for the function to be disabled.
58 The default state of the module is for the function to be disabled.
64
59
65 Parameters
60 Parameters
66 ----------
61 ----------
67 val : bool
62 val : bool
68 If True, set_term_title() actually writes to the terminal (using the
63 If True, set_term_title() actually writes to the terminal (using the
69 appropriate platform-specific module). If False, it is a no-op.
64 appropriate platform-specific module). If False, it is a no-op.
70 """
65 """
71 global ignore_termtitle
66 global ignore_termtitle
72 ignore_termtitle = not(val)
67 ignore_termtitle = not(val)
73
68
74
69
75 def _set_term_title(*args,**kw):
70 def _set_term_title(*args,**kw):
76 """Dummy no-op."""
71 """Dummy no-op."""
77 pass
72 pass
78
73
79
74
80 def _set_term_title_xterm(title):
75 def _set_term_title_xterm(title):
81 """ Change virtual terminal title in xterm-workalikes """
76 """ Change virtual terminal title in xterm-workalikes """
82 sys.stdout.write('\033]0;%s\007' % title)
77 sys.stdout.write('\033]0;%s\007' % title)
83
78
84 if os.name == 'posix':
79 if os.name == 'posix':
85 TERM = os.environ.get('TERM','')
80 TERM = os.environ.get('TERM','')
86 if TERM.startswith('xterm'):
81 if TERM.startswith('xterm'):
87 _set_term_title = _set_term_title_xterm
82 _set_term_title = _set_term_title_xterm
88
83 elif sys.platform == 'win32':
89
90 if sys.platform == 'win32':
91 try:
84 try:
92 import ctypes
85 import ctypes
93
86
94 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
87 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
95 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
88 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
96
89
97 def _set_term_title(title):
90 def _set_term_title(title):
98 """Set terminal title using ctypes to access the Win32 APIs."""
91 """Set terminal title using ctypes to access the Win32 APIs."""
99 SetConsoleTitleW(title)
92 SetConsoleTitleW(title)
100 except ImportError:
93 except ImportError:
101 def _set_term_title(title):
94 def _set_term_title(title):
102 """Set terminal title using the 'title' command."""
95 """Set terminal title using the 'title' command."""
103 global ignore_termtitle
96 global ignore_termtitle
104
97
105 try:
98 try:
106 # Cannot be on network share when issuing system commands
99 # Cannot be on network share when issuing system commands
107 curr = py3compat.getcwd()
100 curr = py3compat.getcwd()
108 os.chdir("C:")
101 os.chdir("C:")
109 ret = os.system("title " + title)
102 ret = os.system("title " + title)
110 finally:
103 finally:
111 os.chdir(curr)
104 os.chdir(curr)
112 if ret:
105 if ret:
113 # non-zero return code signals error, don't try again
106 # non-zero return code signals error, don't try again
114 ignore_termtitle = True
107 ignore_termtitle = True
115
108
116
109
117 def set_term_title(title):
110 def set_term_title(title):
118 """Set terminal title using the necessary platform-dependent calls."""
111 """Set terminal title using the necessary platform-dependent calls."""
119 if ignore_termtitle:
112 if ignore_termtitle:
120 return
113 return
121 _set_term_title(title)
114 _set_term_title(title)
122
115
123
116
124 def freeze_term_title():
117 def freeze_term_title():
125 warnings.warn("This function is deprecated, use toggle_set_term_title()")
118 warnings.warn("This function is deprecated, use toggle_set_term_title()")
126 global ignore_termtitle
119 global ignore_termtitle
127 ignore_termtitle = True
120 ignore_termtitle = True
128
121
129
122
130 def get_terminal_size(defaultx=80, defaulty=25):
131 return defaultx, defaulty
132
133
134 if sys.platform == 'win32':
123 if sys.platform == 'win32':
135 def get_terminal_size(defaultx=80, defaulty=25):
124 def get_terminal_size(defaultx=80, defaulty=25):
136 """Return size of current terminal console.
125 """Return size of current terminal console.
137
126
138 This function try to determine actual size of current working
127 This function try to determine actual size of current working
139 console window and return tuple (sizex, sizey) if success,
128 console window and return tuple (sizex, sizey) if success,
140 or default size (defaultx, defaulty) otherwise.
129 or default size (defaultx, defaulty) otherwise.
141
130
142 Dependencies: ctypes should be installed.
131 Dependencies: ctypes should be installed.
143
132
144 Author: Alexander Belchenko (e-mail: bialix AT ukr.net)
133 Author: Alexander Belchenko (e-mail: bialix AT ukr.net)
145 """
134 """
146 try:
135 try:
147 import ctypes
136 import ctypes
148 except ImportError:
137 except ImportError:
149 return defaultx, defaulty
138 return defaultx, defaulty
150
139
151 h = ctypes.windll.kernel32.GetStdHandle(-11)
140 h = ctypes.windll.kernel32.GetStdHandle(-11)
152 csbi = ctypes.create_string_buffer(22)
141 csbi = ctypes.create_string_buffer(22)
153 res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
142 res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
154
143
155 if res:
144 if res:
156 (bufx, bufy, curx, cury, wattr,
145 (bufx, bufy, curx, cury, wattr,
157 left, top, right, bottom, maxx, maxy) = struct.unpack(
146 left, top, right, bottom, maxx, maxy) = struct.unpack(
158 "hhhhHhhhhhh", csbi.raw)
147 "hhhhHhhhhhh", csbi.raw)
159 sizex = right - left + 1
148 sizex = right - left + 1
160 sizey = bottom - top + 1
149 sizey = bottom - top + 1
161 return (sizex, sizey)
150 return (sizex, sizey)
162 else:
151 else:
163 return (defaultx, defaulty)
152 return (defaultx, defaulty)
153 else:
154 def get_terminal_size(defaultx=80, defaulty=25):
155 return defaultx, defaulty
164
156
@@ -1,76 +1,75 b''
1 """Tests for tokenutil"""
1 """Tests for tokenutil"""
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 import nose.tools as nt
5 import nose.tools as nt
6
6
7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
8
8
9 def expect_token(expected, cell, cursor_pos):
9 def expect_token(expected, cell, cursor_pos):
10 token = token_at_cursor(cell, cursor_pos)
10 token = token_at_cursor(cell, cursor_pos)
11 offset = 0
11 offset = 0
12 for line in cell.splitlines():
12 for line in cell.splitlines():
13 if offset + len(line) >= cursor_pos:
13 if offset + len(line) >= cursor_pos:
14 break
14 break
15 else:
15 else:
16 offset += len(line)
16 offset += len(line)
17 column = cursor_pos - offset
17 column = cursor_pos - offset
18 line_with_cursor = '%s|%s' % (line[:column], line[column:])
18 line_with_cursor = '%s|%s' % (line[:column], line[column:])
19 line
20 nt.assert_equal(token, expected,
19 nt.assert_equal(token, expected,
21 "Expected %r, got %r in: %r (pos %i)" % (
20 "Expected %r, got %r in: %r (pos %i)" % (
22 expected, token, line_with_cursor, cursor_pos)
21 expected, token, line_with_cursor, cursor_pos)
23 )
22 )
24
23
25 def test_simple():
24 def test_simple():
26 cell = "foo"
25 cell = "foo"
27 for i in range(len(cell)):
26 for i in range(len(cell)):
28 expect_token("foo", cell, i)
27 expect_token("foo", cell, i)
29
28
30 def test_function():
29 def test_function():
31 cell = "foo(a=5, b='10')"
30 cell = "foo(a=5, b='10')"
32 expected = 'foo'
31 expected = 'foo'
33 # up to `foo(|a=`
32 # up to `foo(|a=`
34 for i in range(cell.find('a=') + 1):
33 for i in range(cell.find('a=') + 1):
35 expect_token("foo", cell, i)
34 expect_token("foo", cell, i)
36 # find foo after `=`
35 # find foo after `=`
37 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
36 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
38 expect_token("foo", cell, i)
37 expect_token("foo", cell, i)
39 # in between `5,|` and `|b=`
38 # in between `5,|` and `|b=`
40 for i in range(cell.find(','), cell.find('b=')):
39 for i in range(cell.find(','), cell.find('b=')):
41 expect_token("foo", cell, i)
40 expect_token("foo", cell, i)
42
41
43 def test_multiline():
42 def test_multiline():
44 cell = '\n'.join([
43 cell = '\n'.join([
45 'a = 5',
44 'a = 5',
46 'b = hello("string", there)'
45 'b = hello("string", there)'
47 ])
46 ])
48 expected = 'hello'
47 expected = 'hello'
49 start = cell.index(expected) + 1
48 start = cell.index(expected) + 1
50 for i in range(start, start + len(expected)):
49 for i in range(start, start + len(expected)):
51 expect_token(expected, cell, i)
50 expect_token(expected, cell, i)
52 expected = 'there'
51 expected = 'there'
53 start = cell.index(expected) + 1
52 start = cell.index(expected) + 1
54 for i in range(start, start + len(expected)):
53 for i in range(start, start + len(expected)):
55 expect_token(expected, cell, i)
54 expect_token(expected, cell, i)
56
55
57 def test_attrs():
56 def test_attrs():
58 cell = "foo(a=obj.attr.subattr)"
57 cell = "foo(a=obj.attr.subattr)"
59 expected = 'obj'
58 expected = 'obj'
60 idx = cell.find('obj') + 1
59 idx = cell.find('obj') + 1
61 for i in range(idx, idx + 3):
60 for i in range(idx, idx + 3):
62 expect_token(expected, cell, i)
61 expect_token(expected, cell, i)
63 idx = cell.find('.attr') + 2
62 idx = cell.find('.attr') + 2
64 expected = 'obj.attr'
63 expected = 'obj.attr'
65 for i in range(idx, idx + 4):
64 for i in range(idx, idx + 4):
66 expect_token(expected, cell, i)
65 expect_token(expected, cell, i)
67 idx = cell.find('.subattr') + 2
66 idx = cell.find('.subattr') + 2
68 expected = 'obj.attr.subattr'
67 expected = 'obj.attr.subattr'
69 for i in range(idx, len(cell)):
68 for i in range(idx, len(cell)):
70 expect_token(expected, cell, i)
69 expect_token(expected, cell, i)
71
70
72 def test_line_at_cursor():
71 def test_line_at_cursor():
73 cell = ""
72 cell = ""
74 (line, offset) = line_at_cursor(cell, cursor_pos=11)
73 (line, offset) = line_at_cursor(cell, cursor_pos=11)
75 assert line == "", ("Expected '', got %r" % line)
74 assert line == "", ("Expected '', got %r" % line)
76 assert offset == 0, ("Expected '', got %r" % line)
75 assert offset == 0, ("Expected '', got %r" % line)
@@ -1,333 +1,333 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.rst, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Minimal Python version sanity check
21 # Minimal Python version sanity check
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
23 from __future__ import print_function
24
24
25 import sys
25 import sys
26
26
27 # This check is also made in IPython/__init__, don't forget to update both when
27 # This check is also made in IPython/__init__, don't forget to update both when
28 # changing Python version requirements.
28 # changing Python version requirements.
29 v = sys.version_info
29 v = sys.version_info
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
32 print(error, file=sys.stderr)
32 print(error, file=sys.stderr)
33 sys.exit(1)
33 sys.exit(1)
34
34
35 PY3 = (sys.version_info[0] >= 3)
35 PY3 = (sys.version_info[0] >= 3)
36
36
37 # At least we're on the python version we need, move on.
37 # At least we're on the python version we need, move on.
38
38
39 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
40 # Imports
40 # Imports
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42
42
43 # Stdlib imports
43 # Stdlib imports
44 import os
44 import os
45 import shutil
45 import shutil
46
46
47 from glob import glob
47 from glob import glob
48
48
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
50 # update it when the contents of directories change.
50 # update it when the contents of directories change.
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
52
52
53 from distutils.core import setup
53 from distutils.core import setup
54
54
55 # Our own imports
55 # Our own imports
56 from setupbase import target_update
56 from setupbase import target_update
57
57
58 from setupbase import (
58 from setupbase import (
59 setup_args,
59 setup_args,
60 find_packages,
60 find_packages,
61 find_package_data,
61 find_package_data,
62 check_package_data_first,
62 check_package_data_first,
63 find_entry_points,
63 find_entry_points,
64 build_scripts_entrypt,
64 build_scripts_entrypt,
65 find_data_files,
65 find_data_files,
66 check_for_dependencies,
66 check_for_dependencies,
67 git_prebuild,
67 git_prebuild,
68 check_submodule_status,
68 check_submodule_status,
69 update_submodules,
69 update_submodules,
70 require_submodules,
70 require_submodules,
71 UpdateSubmodules,
71 UpdateSubmodules,
72 get_bdist_wheel,
72 get_bdist_wheel,
73 CompileCSS,
73 CompileCSS,
74 JavascriptVersion,
74 JavascriptVersion,
75 css_js_prerelease,
75 css_js_prerelease,
76 install_symlinked,
76 install_symlinked,
77 install_lib_symlink,
77 install_lib_symlink,
78 install_scripts_for_symlink,
78 install_scripts_for_symlink,
79 unsymlink,
79 unsymlink,
80 )
80 )
81 from setupext import setupext
81 from setupext import setupext
82
82
83 isfile = os.path.isfile
83 isfile = os.path.isfile
84 pjoin = os.path.join
84 pjoin = os.path.join
85
85
86 #-------------------------------------------------------------------------------
86 #-------------------------------------------------------------------------------
87 # Handle OS specific things
87 # Handle OS specific things
88 #-------------------------------------------------------------------------------
88 #-------------------------------------------------------------------------------
89
89
90 if os.name in ('nt','dos'):
90 if os.name in ('nt','dos'):
91 os_name = 'windows'
91 os_name = 'windows'
92 else:
92 else:
93 os_name = os.name
93 os_name = os.name
94
94
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
97 # actually works.
97 # actually works.
98 if os_name == 'windows' and 'sdist' in sys.argv:
98 if os_name == 'windows' and 'sdist' in sys.argv:
99 print('The sdist command is not available under Windows. Exiting.')
99 print('The sdist command is not available under Windows. Exiting.')
100 sys.exit(1)
100 sys.exit(1)
101
101
102 #-------------------------------------------------------------------------------
102 #-------------------------------------------------------------------------------
103 # Make sure we aren't trying to run without submodules
103 # Make sure we aren't trying to run without submodules
104 #-------------------------------------------------------------------------------
104 #-------------------------------------------------------------------------------
105 here = os.path.abspath(os.path.dirname(__file__))
105 here = os.path.abspath(os.path.dirname(__file__))
106
106
107 def require_clean_submodules():
107 def require_clean_submodules():
108 """Check on git submodules before distutils can do anything
108 """Check on git submodules before distutils can do anything
109
109
110 Since distutils cannot be trusted to update the tree
110 Since distutils cannot be trusted to update the tree
111 after everything has been set in motion,
111 after everything has been set in motion,
112 this is not a distutils command.
112 this is not a distutils command.
113 """
113 """
114 # PACKAGERS: Add a return here to skip checks for git submodules
114 # PACKAGERS: Add a return here to skip checks for git submodules
115
115
116 # don't do anything if nothing is actually supposed to happen
116 # don't do anything if nothing is actually supposed to happen
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
118 if do_nothing in sys.argv:
118 if do_nothing in sys.argv:
119 return
119 return
120
120
121 status = check_submodule_status(here)
121 status = check_submodule_status(here)
122
122
123 if status == "missing":
123 if status == "missing":
124 print("checking out submodules for the first time")
124 print("checking out submodules for the first time")
125 update_submodules(here)
125 update_submodules(here)
126 elif status == "unclean":
126 elif status == "unclean":
127 print('\n'.join([
127 print('\n'.join([
128 "Cannot build / install IPython with unclean submodules",
128 "Cannot build / install IPython with unclean submodules",
129 "Please update submodules with",
129 "Please update submodules with",
130 " python setup.py submodule",
130 " python setup.py submodule",
131 "or",
131 "or",
132 " git submodule update",
132 " git submodule update",
133 "or commit any submodule changes you have made."
133 "or commit any submodule changes you have made."
134 ]))
134 ]))
135 sys.exit(1)
135 sys.exit(1)
136
136
137 require_clean_submodules()
137 require_clean_submodules()
138
138
139 #-------------------------------------------------------------------------------
139 #-------------------------------------------------------------------------------
140 # Things related to the IPython documentation
140 # Things related to the IPython documentation
141 #-------------------------------------------------------------------------------
141 #-------------------------------------------------------------------------------
142
142
143 # update the manuals when building a source dist
143 # update the manuals when building a source dist
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
145
145
146 # List of things to be updated. Each entry is a triplet of args for
146 # List of things to be updated. Each entry is a triplet of args for
147 # target_update()
147 # target_update()
148 to_update = [
148 to_update = [
149 # FIXME - Disabled for now: we need to redo an automatic way
149 # FIXME - Disabled for now: we need to redo an automatic way
150 # of generating the magic info inside the rst.
150 # of generating the magic info inside the rst.
151 #('docs/magic.tex',
151 #('docs/magic.tex',
152 #['IPython/Magic.py'],
152 #['IPython/Magic.py'],
153 #"cd doc && ./update_magic.sh" ),
153 #"cd doc && ./update_magic.sh" ),
154
154
155 ('docs/man/ipcluster.1.gz',
155 ('docs/man/ipcluster.1.gz',
156 ['docs/man/ipcluster.1'],
156 ['docs/man/ipcluster.1'],
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
158
158
159 ('docs/man/ipcontroller.1.gz',
159 ('docs/man/ipcontroller.1.gz',
160 ['docs/man/ipcontroller.1'],
160 ['docs/man/ipcontroller.1'],
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
162
162
163 ('docs/man/ipengine.1.gz',
163 ('docs/man/ipengine.1.gz',
164 ['docs/man/ipengine.1'],
164 ['docs/man/ipengine.1'],
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
166
166
167 ('docs/man/ipython.1.gz',
167 ('docs/man/ipython.1.gz',
168 ['docs/man/ipython.1'],
168 ['docs/man/ipython.1'],
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
170
170
171 ]
171 ]
172
172
173
173
174 [ target_update(*t) for t in to_update ]
174 [ target_update(*t) for t in to_update ]
175
175
176 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
177 # Find all the packages, package data, and data_files
177 # Find all the packages, package data, and data_files
178 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
179
179
180 packages = find_packages()
180 packages = find_packages()
181 package_data = find_package_data()
181 package_data = find_package_data()
182
182
183 data_files = find_data_files()
183 data_files = find_data_files()
184
184
185 setup_args['packages'] = packages
185 setup_args['packages'] = packages
186 setup_args['package_data'] = package_data
186 setup_args['package_data'] = package_data
187 setup_args['data_files'] = data_files
187 setup_args['data_files'] = data_files
188
188
189 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
190 # custom distutils commands
190 # custom distutils commands
191 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
192 # imports here, so they are after setuptools import if there was one
192 # imports here, so they are after setuptools import if there was one
193 from distutils.command.sdist import sdist
193 from distutils.command.sdist import sdist
194 from distutils.command.upload import upload
194 from distutils.command.upload import upload
195
195
196 class UploadWindowsInstallers(upload):
196 class UploadWindowsInstallers(upload):
197
197
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
199 user_options = upload.user_options + [
199 user_options = upload.user_options + [
200 ('files=', 'f', 'exe file (or glob) to upload')
200 ('files=', 'f', 'exe file (or glob) to upload')
201 ]
201 ]
202 def initialize_options(self):
202 def initialize_options(self):
203 upload.initialize_options(self)
203 upload.initialize_options(self)
204 meta = self.distribution.metadata
204 meta = self.distribution.metadata
205 base = '{name}-{version}'.format(
205 base = '{name}-{version}'.format(
206 name=meta.get_name(),
206 name=meta.get_name(),
207 version=meta.get_version()
207 version=meta.get_version()
208 )
208 )
209 self.files = os.path.join('dist', '%s.*.exe' % base)
209 self.files = os.path.join('dist', '%s.*.exe' % base)
210
210
211 def run(self):
211 def run(self):
212 for dist_file in glob(self.files):
212 for dist_file in glob(self.files):
213 self.upload_file('bdist_wininst', 'any', dist_file)
213 self.upload_file('bdist_wininst', 'any', dist_file)
214
214
215 setup_args['cmdclass'] = {
215 setup_args['cmdclass'] = {
216 'build_py': css_js_prerelease(
216 'build_py': css_js_prerelease(
217 check_package_data_first(git_prebuild('IPython')),
217 check_package_data_first(git_prebuild('IPython')),
218 strict=False),
218 strict=False),
219 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
219 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
220 'upload_wininst' : UploadWindowsInstallers,
220 'upload_wininst' : UploadWindowsInstallers,
221 'submodule' : UpdateSubmodules,
221 'submodule' : UpdateSubmodules,
222 'css' : CompileCSS,
222 'css' : CompileCSS,
223 'symlink': install_symlinked,
223 'symlink': install_symlinked,
224 'install_lib_symlink': install_lib_symlink,
224 'install_lib_symlink': install_lib_symlink,
225 'install_scripts_sym': install_scripts_for_symlink,
225 'install_scripts_sym': install_scripts_for_symlink,
226 'unsymlink': unsymlink,
226 'unsymlink': unsymlink,
227 'jsversion' : JavascriptVersion,
227 'jsversion' : JavascriptVersion,
228 }
228 }
229
229
230 #---------------------------------------------------------------------------
230 #---------------------------------------------------------------------------
231 # Handle scripts, dependencies, and setuptools specific things
231 # Handle scripts, dependencies, and setuptools specific things
232 #---------------------------------------------------------------------------
232 #---------------------------------------------------------------------------
233
233
234 # For some commands, use setuptools. Note that we do NOT list install here!
234 # For some commands, use setuptools. Note that we do NOT list install here!
235 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
235 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
236 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
236 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
237 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
237 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
238 'egg_info', 'easy_install', 'upload', 'install_egg_info',
238 'egg_info', 'easy_install', 'upload', 'install_egg_info',
239 ))
239 ))
240
240
241 if len(needs_setuptools.intersection(sys.argv)) > 0:
241 if len(needs_setuptools.intersection(sys.argv)) > 0:
242 import setuptools
242 import setuptools
243
243
244 # This dict is used for passing extra arguments that are setuptools
244 # This dict is used for passing extra arguments that are setuptools
245 # specific to setup
245 # specific to setup
246 setuptools_extra_args = {}
246 setuptools_extra_args = {}
247
247
248 # setuptools requirements
248 # setuptools requirements
249
249
250 extras_require = dict(
250 extras_require = dict(
251 parallel = ['pyzmq>=2.1.11'],
251 parallel = ['pyzmq>=2.1.11'],
252 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
252 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
253 zmq = ['pyzmq>=2.1.11'],
253 zmq = ['pyzmq>=2.1.11'],
254 doc = ['Sphinx>=1.1', 'numpydoc'],
254 doc = ['Sphinx>=1.1', 'numpydoc'],
255 test = ['nose>=0.10.1', 'requests'],
255 test = ['nose>=0.10.1', 'requests'],
256 terminal = [],
256 terminal = [],
257 nbformat = ['jsonschema>=2.0'],
257 nbformat = ['jsonschema>=2.0'],
258 notebook = ['tornado>=4.0', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.5'],
258 notebook = ['tornado>=4.0', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.5'],
259 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
259 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
260 )
260 )
261
261
262 if sys.version_info < (3, 3):
262 if sys.version_info < (3, 3):
263 extras_require['test'].append('mock')
263 extras_require['test'].append('mock')
264
264
265 extras_require['notebook'].extend(extras_require['nbformat'])
265 extras_require['notebook'].extend(extras_require['nbformat'])
266 extras_require['nbconvert'].extend(extras_require['nbformat'])
266 extras_require['nbconvert'].extend(extras_require['nbformat'])
267
267
268 everything = set()
268 everything = set()
269 for deps in extras_require.values():
269 for deps in extras_require.values():
270 everything.update(deps)
270 everything.update(deps)
271 extras_require['all'] = everything
271 extras_require['all'] = everything
272
272
273 install_requires = []
273 install_requires = []
274
274
275 # add readline
275 # add readline
276 if sys.platform == 'darwin':
276 if sys.platform == 'darwin':
277 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
277 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
278 install_requires.append('gnureadline')
278 install_requires.append('gnureadline')
279 elif sys.platform.startswith('win'):
279 elif sys.platform.startswith('win'):
280 extras_require['terminal'].append('pyreadline>=2.0')
280 extras_require['terminal'].append('pyreadline>=2.0')
281
281
282
282
283 if 'setuptools' in sys.modules:
283 if 'setuptools' in sys.modules:
284 # setup.py develop should check for submodules
284 # setup.py develop should check for submodules
285 from setuptools.command.develop import develop
285 from setuptools.command.develop import develop
286 setup_args['cmdclass']['develop'] = require_submodules(develop)
286 setup_args['cmdclass']['develop'] = require_submodules(develop)
287 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
287 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
288
288
289 setuptools_extra_args['zip_safe'] = False
289 setuptools_extra_args['zip_safe'] = False
290 setuptools_extra_args['entry_points'] = {'console_scripts':find_entry_points()}
290 setuptools_extra_args['entry_points'] = {'console_scripts':find_entry_points()}
291 setup_args['extras_require'] = extras_require
291 setup_args['extras_require'] = extras_require
292 requires = setup_args['install_requires'] = install_requires
292 requires = setup_args['install_requires'] = install_requires
293
293
294 # Script to be run by the windows binary installer after the default setup
294 # Script to be run by the windows binary installer after the default setup
295 # routine, to add shortcuts and similar windows-only things. Windows
295 # routine, to add shortcuts and similar windows-only things. Windows
296 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
296 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
297 # doesn't find them.
297 # doesn't find them.
298 if 'bdist_wininst' in sys.argv:
298 if 'bdist_wininst' in sys.argv:
299 if len(sys.argv) > 2 and \
299 if len(sys.argv) > 2 and \
300 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
300 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
301 print >> sys.stderr, "ERROR: bdist_wininst must be run alone. Exiting."
301 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
302 sys.exit(1)
302 sys.exit(1)
303 setup_args['data_files'].append(
303 setup_args['data_files'].append(
304 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
304 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
305 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
305 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
306 setup_args['options'] = {"bdist_wininst":
306 setup_args['options'] = {"bdist_wininst":
307 {"install_script":
307 {"install_script":
308 "ipython_win_post_install.py"}}
308 "ipython_win_post_install.py"}}
309
309
310 else:
310 else:
311 # If we are installing without setuptools, call this function which will
311 # If we are installing without setuptools, call this function which will
312 # check for dependencies an inform the user what is needed. This is
312 # check for dependencies an inform the user what is needed. This is
313 # just to make life easy for users.
313 # just to make life easy for users.
314 for install_cmd in ('install', 'symlink'):
314 for install_cmd in ('install', 'symlink'):
315 if install_cmd in sys.argv:
315 if install_cmd in sys.argv:
316 check_for_dependencies()
316 check_for_dependencies()
317 break
317 break
318 # scripts has to be a non-empty list, or install_scripts isn't called
318 # scripts has to be a non-empty list, or install_scripts isn't called
319 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
319 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
320
320
321 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
321 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
322
322
323 #---------------------------------------------------------------------------
323 #---------------------------------------------------------------------------
324 # Do the actual setup now
324 # Do the actual setup now
325 #---------------------------------------------------------------------------
325 #---------------------------------------------------------------------------
326
326
327 setup_args.update(setuptools_extra_args)
327 setup_args.update(setuptools_extra_args)
328
328
329 def main():
329 def main():
330 setup(**setup_args)
330 setup(**setup_args)
331
331
332 if __name__ == '__main__':
332 if __name__ == '__main__':
333 main()
333 main()
General Comments 0
You need to be logged in to leave comments. Login now