##// END OF EJS Templates
Some code cleanup in javascript and python...
Matthias Bussonnier -
Show More
@@ -1,3396 +1,3394 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Main IPython class."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 from __future__ import absolute_import, print_function
14 14
15 15 import __future__
16 16 import abc
17 17 import ast
18 18 import atexit
19 19 import functools
20 20 import os
21 21 import re
22 22 import runpy
23 23 import sys
24 24 import tempfile
25 25 import traceback
26 26 import types
27 27 import subprocess
28 28 from io import open as io_open
29 29
30 30 from IPython.config.configurable import SingletonConfigurable
31 31 from IPython.core import debugger, oinspect
32 32 from IPython.core import magic
33 33 from IPython.core import page
34 34 from IPython.core import prefilter
35 35 from IPython.core import shadowns
36 36 from IPython.core import ultratb
37 37 from IPython.core.alias import AliasManager, AliasError
38 38 from IPython.core.autocall import ExitAutocall
39 39 from IPython.core.builtin_trap import BuiltinTrap
40 40 from IPython.core.events import EventManager, available_events
41 41 from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
42 42 from IPython.core.display_trap import DisplayTrap
43 43 from IPython.core.displayhook import DisplayHook
44 44 from IPython.core.displaypub import DisplayPublisher
45 45 from IPython.core.error import InputRejected, UsageError
46 46 from IPython.core.extensions import ExtensionManager
47 47 from IPython.core.formatters import DisplayFormatter
48 48 from IPython.core.history import HistoryManager
49 49 from IPython.core.inputsplitter import IPythonInputSplitter, ESC_MAGIC, ESC_MAGIC2
50 50 from IPython.core.logger import Logger
51 51 from IPython.core.macro import Macro
52 52 from IPython.core.payload import PayloadManager
53 53 from IPython.core.prefilter import PrefilterManager
54 54 from IPython.core.profiledir import ProfileDir
55 55 from IPython.core.prompts import PromptManager
56 56 from IPython.core.usage import default_banner
57 57 from IPython.lib.latextools import LaTeXTool
58 58 from IPython.testing.skipdoctest import skip_doctest
59 59 from IPython.utils import PyColorize
60 60 from IPython.utils import io
61 61 from IPython.utils import py3compat
62 62 from IPython.utils import openpy
63 63 from IPython.utils.decorators import undoc
64 64 from IPython.utils.io import ask_yes_no
65 65 from IPython.utils.ipstruct import Struct
66 66 from IPython.utils.path import get_home_dir, get_ipython_dir, get_py_filename, unquote_filename, ensure_dir_exists
67 67 from IPython.utils.pickleshare import PickleShareDB
68 68 from IPython.utils.process import system, getoutput
69 69 from IPython.utils.py3compat import (builtin_mod, unicode_type, string_types,
70 70 with_metaclass, iteritems)
71 71 from IPython.utils.strdispatch import StrDispatch
72 72 from IPython.utils.syspathcontext import prepended_to_syspath
73 73 from IPython.utils.text import (format_screen, LSString, SList,
74 74 DollarFormatter)
75 75 from IPython.utils.traitlets import (Integer, Bool, CBool, CaselessStrEnum, Enum,
76 76 List, Unicode, Instance, Type)
77 77 from IPython.utils.warn import warn, error
78 78 import IPython.core.hooks
79 79
80 80 #-----------------------------------------------------------------------------
81 81 # Globals
82 82 #-----------------------------------------------------------------------------
83 83
84 84 # compiled regexps for autoindent management
85 85 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
86 86
87 87 #-----------------------------------------------------------------------------
88 88 # Utilities
89 89 #-----------------------------------------------------------------------------
90 90
91 91 @undoc
92 92 def softspace(file, newvalue):
93 93 """Copied from code.py, to remove the dependency"""
94 94
95 95 oldvalue = 0
96 96 try:
97 97 oldvalue = file.softspace
98 98 except AttributeError:
99 99 pass
100 100 try:
101 101 file.softspace = newvalue
102 102 except (AttributeError, TypeError):
103 103 # "attribute-less object" or "read-only attributes"
104 104 pass
105 105 return oldvalue
106 106
107 107 @undoc
108 108 def no_op(*a, **kw): pass
109 109
110 110 @undoc
111 111 class NoOpContext(object):
112 112 def __enter__(self): pass
113 113 def __exit__(self, type, value, traceback): pass
114 114 no_op_context = NoOpContext()
115 115
116 116 class SpaceInInput(Exception): pass
117 117
118 118 @undoc
119 119 class Bunch: pass
120 120
121 121
122 122 def get_default_colors():
123 123 if sys.platform=='darwin':
124 124 return "LightBG"
125 125 elif os.name=='nt':
126 126 return 'Linux'
127 127 else:
128 128 return 'Linux'
129 129
130 130
131 131 class SeparateUnicode(Unicode):
132 132 r"""A Unicode subclass to validate separate_in, separate_out, etc.
133 133
134 134 This is a Unicode based trait that converts '0'->'' and ``'\\n'->'\n'``.
135 135 """
136 136
137 137 def validate(self, obj, value):
138 138 if value == '0': value = ''
139 139 value = value.replace('\\n','\n')
140 140 return super(SeparateUnicode, self).validate(obj, value)
141 141
142 142
143 143 class ReadlineNoRecord(object):
144 144 """Context manager to execute some code, then reload readline history
145 145 so that interactive input to the code doesn't appear when pressing up."""
146 146 def __init__(self, shell):
147 147 self.shell = shell
148 148 self._nested_level = 0
149 149
150 150 def __enter__(self):
151 151 if self._nested_level == 0:
152 152 try:
153 153 self.orig_length = self.current_length()
154 154 self.readline_tail = self.get_readline_tail()
155 155 except (AttributeError, IndexError): # Can fail with pyreadline
156 156 self.orig_length, self.readline_tail = 999999, []
157 157 self._nested_level += 1
158 158
159 159 def __exit__(self, type, value, traceback):
160 160 self._nested_level -= 1
161 161 if self._nested_level == 0:
162 162 # Try clipping the end if it's got longer
163 163 try:
164 164 e = self.current_length() - self.orig_length
165 165 if e > 0:
166 166 for _ in range(e):
167 167 self.shell.readline.remove_history_item(self.orig_length)
168 168
169 169 # If it still doesn't match, just reload readline history.
170 170 if self.current_length() != self.orig_length \
171 171 or self.get_readline_tail() != self.readline_tail:
172 172 self.shell.refill_readline_hist()
173 173 except (AttributeError, IndexError):
174 174 pass
175 175 # Returning False will cause exceptions to propagate
176 176 return False
177 177
178 178 def current_length(self):
179 179 return self.shell.readline.get_current_history_length()
180 180
181 181 def get_readline_tail(self, n=10):
182 182 """Get the last n items in readline history."""
183 183 end = self.shell.readline.get_current_history_length() + 1
184 184 start = max(end-n, 1)
185 185 ghi = self.shell.readline.get_history_item
186 186 return [ghi(x) for x in range(start, end)]
187 187
188 188
189 189 @undoc
190 190 class DummyMod(object):
191 191 """A dummy module used for IPython's interactive module when
192 192 a namespace must be assigned to the module's __dict__."""
193 193 pass
194 194
195 195
196 196 class ExecutionResult(object):
197 197 """The result of a call to :meth:`InteractiveShell.run_cell`
198 198
199 199 Stores information about what took place.
200 200 """
201 201 execution_count = None
202 202 error_before_exec = None
203 203 error_in_exec = None
204 204 result = None
205 205
206 206 @property
207 207 def success(self):
208 208 return (self.error_before_exec is None) and (self.error_in_exec is None)
209 209
210 210
211 211 class InteractiveShell(SingletonConfigurable):
212 212 """An enhanced, interactive shell for Python."""
213 213
214 214 _instance = None
215 215
216 216 ast_transformers = List([], config=True, help=
217 217 """
218 218 A list of ast.NodeTransformer subclass instances, which will be applied
219 219 to user input before code is run.
220 220 """
221 221 )
222 222
223 223 autocall = Enum((0,1,2), default_value=0, config=True, help=
224 224 """
225 225 Make IPython automatically call any callable object even if you didn't
226 226 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
227 227 automatically. The value can be '0' to disable the feature, '1' for
228 228 'smart' autocall, where it is not applied if there are no more
229 229 arguments on the line, and '2' for 'full' autocall, where all callable
230 230 objects are automatically called (even if no arguments are present).
231 231 """
232 232 )
233 233 # TODO: remove all autoindent logic and put into frontends.
234 234 # We can't do this yet because even runlines uses the autoindent.
235 235 autoindent = CBool(True, config=True, help=
236 236 """
237 237 Autoindent IPython code entered interactively.
238 238 """
239 239 )
240 240 automagic = CBool(True, config=True, help=
241 241 """
242 242 Enable magic commands to be called without the leading %.
243 243 """
244 244 )
245 245
246 banner = Unicode('')
247
248 246 banner1 = Unicode(default_banner, config=True,
249 247 help="""The part of the banner to be printed before the profile"""
250 248 )
251 249 banner2 = Unicode('', config=True,
252 250 help="""The part of the banner to be printed after the profile"""
253 251 )
254 252
255 253 cache_size = Integer(1000, config=True, help=
256 254 """
257 255 Set the size of the output cache. The default is 1000, you can
258 256 change it permanently in your config file. Setting it to 0 completely
259 257 disables the caching system, and the minimum value accepted is 20 (if
260 258 you provide a value less than 20, it is reset to 0 and a warning is
261 259 issued). This limit is defined because otherwise you'll spend more
262 260 time re-flushing a too small cache than working
263 261 """
264 262 )
265 263 color_info = CBool(True, config=True, help=
266 264 """
267 265 Use colors for displaying information about objects. Because this
268 266 information is passed through a pager (like 'less'), and some pagers
269 267 get confused with color codes, this capability can be turned off.
270 268 """
271 269 )
272 270 colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
273 271 default_value=get_default_colors(), config=True,
274 272 help="Set the color scheme (NoColor, Linux, or LightBG)."
275 273 )
276 274 colors_force = CBool(False, help=
277 275 """
278 276 Force use of ANSI color codes, regardless of OS and readline
279 277 availability.
280 278 """
281 279 # FIXME: This is essentially a hack to allow ZMQShell to show colors
282 280 # without readline on Win32. When the ZMQ formatting system is
283 281 # refactored, this should be removed.
284 282 )
285 283 debug = CBool(False, config=True)
286 284 deep_reload = CBool(False, config=True, help=
287 285 """
288 286 Enable deep (recursive) reloading by default. IPython can use the
289 287 deep_reload module which reloads changes in modules recursively (it
290 288 replaces the reload() function, so you don't need to change anything to
291 289 use it). deep_reload() forces a full reload of modules whose code may
292 290 have changed, which the default reload() function does not. When
293 291 deep_reload is off, IPython will use the normal reload(), but
294 292 deep_reload will still be available as dreload().
295 293 """
296 294 )
297 295 disable_failing_post_execute = CBool(False, config=True,
298 296 help="Don't call post-execute functions that have failed in the past."
299 297 )
300 298 display_formatter = Instance(DisplayFormatter)
301 299 displayhook_class = Type(DisplayHook)
302 300 display_pub_class = Type(DisplayPublisher)
303 301 data_pub_class = None
304 302
305 303 exit_now = CBool(False)
306 304 exiter = Instance(ExitAutocall)
307 305 def _exiter_default(self):
308 306 return ExitAutocall(self)
309 307 # Monotonically increasing execution counter
310 308 execution_count = Integer(1)
311 309 filename = Unicode("<ipython console>")
312 310 ipython_dir= Unicode('', config=True) # Set to get_ipython_dir() in __init__
313 311
314 312 # Input splitter, to transform input line by line and detect when a block
315 313 # is ready to be executed.
316 314 input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
317 315 (), {'line_input_checker': True})
318 316
319 317 # This InputSplitter instance is used to transform completed cells before
320 318 # running them. It allows cell magics to contain blank lines.
321 319 input_transformer_manager = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
322 320 (), {'line_input_checker': False})
323 321
324 322 logstart = CBool(False, config=True, help=
325 323 """
326 324 Start logging to the default log file.
327 325 """
328 326 )
329 327 logfile = Unicode('', config=True, help=
330 328 """
331 329 The name of the logfile to use.
332 330 """
333 331 )
334 332 logappend = Unicode('', config=True, help=
335 333 """
336 334 Start logging to the given file in append mode.
337 335 """
338 336 )
339 337 object_info_string_level = Enum((0,1,2), default_value=0,
340 338 config=True)
341 339 pdb = CBool(False, config=True, help=
342 340 """
343 341 Automatically call the pdb debugger after every exception.
344 342 """
345 343 )
346 344 multiline_history = CBool(sys.platform != 'win32', config=True,
347 345 help="Save multi-line entries as one entry in readline history"
348 346 )
349 347 display_page = Bool(False, config=True,
350 348 help="""If True, anything that would be passed to the pager
351 349 will be displayed as regular output instead."""
352 350 )
353 351
354 352 # deprecated prompt traits:
355 353
356 354 prompt_in1 = Unicode('In [\\#]: ', config=True,
357 355 help="Deprecated, use PromptManager.in_template")
358 356 prompt_in2 = Unicode(' .\\D.: ', config=True,
359 357 help="Deprecated, use PromptManager.in2_template")
360 358 prompt_out = Unicode('Out[\\#]: ', config=True,
361 359 help="Deprecated, use PromptManager.out_template")
362 360 prompts_pad_left = CBool(True, config=True,
363 361 help="Deprecated, use PromptManager.justify")
364 362
365 363 def _prompt_trait_changed(self, name, old, new):
366 364 table = {
367 365 'prompt_in1' : 'in_template',
368 366 'prompt_in2' : 'in2_template',
369 367 'prompt_out' : 'out_template',
370 368 'prompts_pad_left' : 'justify',
371 369 }
372 370 warn("InteractiveShell.{name} is deprecated, use PromptManager.{newname}".format(
373 371 name=name, newname=table[name])
374 372 )
375 373 # protect against weird cases where self.config may not exist:
376 374 if self.config is not None:
377 375 # propagate to corresponding PromptManager trait
378 376 setattr(self.config.PromptManager, table[name], new)
379 377
380 378 _prompt_in1_changed = _prompt_trait_changed
381 379 _prompt_in2_changed = _prompt_trait_changed
382 380 _prompt_out_changed = _prompt_trait_changed
383 381 _prompt_pad_left_changed = _prompt_trait_changed
384 382
385 383 show_rewritten_input = CBool(True, config=True,
386 384 help="Show rewritten input, e.g. for autocall."
387 385 )
388 386
389 387 quiet = CBool(False, config=True)
390 388
391 389 history_length = Integer(10000, config=True)
392 390
393 391 # The readline stuff will eventually be moved to the terminal subclass
394 392 # but for now, we can't do that as readline is welded in everywhere.
395 393 readline_use = CBool(True, config=True)
396 394 readline_remove_delims = Unicode('-/~', config=True)
397 395 readline_delims = Unicode() # set by init_readline()
398 396 # don't use \M- bindings by default, because they
399 397 # conflict with 8-bit encodings. See gh-58,gh-88
400 398 readline_parse_and_bind = List([
401 399 'tab: complete',
402 400 '"\C-l": clear-screen',
403 401 'set show-all-if-ambiguous on',
404 402 '"\C-o": tab-insert',
405 403 '"\C-r": reverse-search-history',
406 404 '"\C-s": forward-search-history',
407 405 '"\C-p": history-search-backward',
408 406 '"\C-n": history-search-forward',
409 407 '"\e[A": history-search-backward',
410 408 '"\e[B": history-search-forward',
411 409 '"\C-k": kill-line',
412 410 '"\C-u": unix-line-discard',
413 411 ], config=True)
414 412
415 413 _custom_readline_config = False
416 414
417 415 def _readline_parse_and_bind_changed(self, name, old, new):
418 416 # notice that readline config is customized
419 417 # indicates that it should have higher priority than inputrc
420 418 self._custom_readline_config = True
421 419
422 420 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none'],
423 421 default_value='last_expr', config=True,
424 422 help="""
425 423 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
426 424 run interactively (displaying output from expressions).""")
427 425
428 426 # TODO: this part of prompt management should be moved to the frontends.
429 427 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
430 428 separate_in = SeparateUnicode('\n', config=True)
431 429 separate_out = SeparateUnicode('', config=True)
432 430 separate_out2 = SeparateUnicode('', config=True)
433 431 wildcards_case_sensitive = CBool(True, config=True)
434 432 xmode = CaselessStrEnum(('Context','Plain', 'Verbose'),
435 433 default_value='Context', config=True)
436 434
437 435 # Subcomponents of InteractiveShell
438 436 alias_manager = Instance('IPython.core.alias.AliasManager')
439 437 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
440 438 builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
441 439 display_trap = Instance('IPython.core.display_trap.DisplayTrap')
442 440 extension_manager = Instance('IPython.core.extensions.ExtensionManager')
443 441 payload_manager = Instance('IPython.core.payload.PayloadManager')
444 442 history_manager = Instance('IPython.core.history.HistoryAccessorBase')
445 443 magics_manager = Instance('IPython.core.magic.MagicsManager')
446 444
447 445 profile_dir = Instance('IPython.core.application.ProfileDir')
448 446 @property
449 447 def profile(self):
450 448 if self.profile_dir is not None:
451 449 name = os.path.basename(self.profile_dir.location)
452 450 return name.replace('profile_','')
453 451
454 452
455 453 # Private interface
456 454 _post_execute = Instance(dict)
457 455
458 456 # Tracks any GUI loop loaded for pylab
459 457 pylab_gui_select = None
460 458
461 459 def __init__(self, ipython_dir=None, profile_dir=None,
462 460 user_module=None, user_ns=None,
463 461 custom_exceptions=((), None), **kwargs):
464 462
465 463 # This is where traits with a config_key argument are updated
466 464 # from the values on config.
467 465 super(InteractiveShell, self).__init__(**kwargs)
468 466 self.configurables = [self]
469 467
470 468 # These are relatively independent and stateless
471 469 self.init_ipython_dir(ipython_dir)
472 470 self.init_profile_dir(profile_dir)
473 471 self.init_instance_attrs()
474 472 self.init_environment()
475 473
476 474 # Check if we're in a virtualenv, and set up sys.path.
477 475 self.init_virtualenv()
478 476
479 477 # Create namespaces (user_ns, user_global_ns, etc.)
480 478 self.init_create_namespaces(user_module, user_ns)
481 479 # This has to be done after init_create_namespaces because it uses
482 480 # something in self.user_ns, but before init_sys_modules, which
483 481 # is the first thing to modify sys.
484 482 # TODO: When we override sys.stdout and sys.stderr before this class
485 483 # is created, we are saving the overridden ones here. Not sure if this
486 484 # is what we want to do.
487 485 self.save_sys_module_state()
488 486 self.init_sys_modules()
489 487
490 488 # While we're trying to have each part of the code directly access what
491 489 # it needs without keeping redundant references to objects, we have too
492 490 # much legacy code that expects ip.db to exist.
493 491 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
494 492
495 493 self.init_history()
496 494 self.init_encoding()
497 495 self.init_prefilter()
498 496
499 497 self.init_syntax_highlighting()
500 498 self.init_hooks()
501 499 self.init_events()
502 500 self.init_pushd_popd_magic()
503 501 # self.init_traceback_handlers use to be here, but we moved it below
504 502 # because it and init_io have to come after init_readline.
505 503 self.init_user_ns()
506 504 self.init_logger()
507 505 self.init_builtins()
508 506
509 507 # The following was in post_config_initialization
510 508 self.init_inspector()
511 509 # init_readline() must come before init_io(), because init_io uses
512 510 # readline related things.
513 511 self.init_readline()
514 512 # We save this here in case user code replaces raw_input, but it needs
515 513 # to be after init_readline(), because PyPy's readline works by replacing
516 514 # raw_input.
517 515 if py3compat.PY3:
518 516 self.raw_input_original = input
519 517 else:
520 518 self.raw_input_original = raw_input
521 519 # init_completer must come after init_readline, because it needs to
522 520 # know whether readline is present or not system-wide to configure the
523 521 # completers, since the completion machinery can now operate
524 522 # independently of readline (e.g. over the network)
525 523 self.init_completer()
526 524 # TODO: init_io() needs to happen before init_traceback handlers
527 525 # because the traceback handlers hardcode the stdout/stderr streams.
528 526 # This logic in in debugger.Pdb and should eventually be changed.
529 527 self.init_io()
530 528 self.init_traceback_handlers(custom_exceptions)
531 529 self.init_prompts()
532 530 self.init_display_formatter()
533 531 self.init_display_pub()
534 532 self.init_data_pub()
535 533 self.init_displayhook()
536 534 self.init_latextool()
537 535 self.init_magics()
538 536 self.init_alias()
539 537 self.init_logstart()
540 538 self.init_pdb()
541 539 self.init_extension_manager()
542 540 self.init_payload()
543 541 self.hooks.late_startup_hook()
544 542 self.events.trigger('shell_initialized', self)
545 543 atexit.register(self.atexit_operations)
546 544
547 545 def get_ipython(self):
548 546 """Return the currently running IPython instance."""
549 547 return self
550 548
551 549 #-------------------------------------------------------------------------
552 550 # Trait changed handlers
553 551 #-------------------------------------------------------------------------
554 552
555 553 def _ipython_dir_changed(self, name, new):
556 554 ensure_dir_exists(new)
557 555
558 556 def set_autoindent(self,value=None):
559 557 """Set the autoindent flag, checking for readline support.
560 558
561 559 If called with no arguments, it acts as a toggle."""
562 560
563 561 if value != 0 and not self.has_readline:
564 562 if os.name == 'posix':
565 563 warn("The auto-indent feature requires the readline library")
566 564 self.autoindent = 0
567 565 return
568 566 if value is None:
569 567 self.autoindent = not self.autoindent
570 568 else:
571 569 self.autoindent = value
572 570
573 571 #-------------------------------------------------------------------------
574 572 # init_* methods called by __init__
575 573 #-------------------------------------------------------------------------
576 574
577 575 def init_ipython_dir(self, ipython_dir):
578 576 if ipython_dir is not None:
579 577 self.ipython_dir = ipython_dir
580 578 return
581 579
582 580 self.ipython_dir = get_ipython_dir()
583 581
584 582 def init_profile_dir(self, profile_dir):
585 583 if profile_dir is not None:
586 584 self.profile_dir = profile_dir
587 585 return
588 586 self.profile_dir =\
589 587 ProfileDir.create_profile_dir_by_name(self.ipython_dir, 'default')
590 588
591 589 def init_instance_attrs(self):
592 590 self.more = False
593 591
594 592 # command compiler
595 593 self.compile = CachingCompiler()
596 594
597 595 # Make an empty namespace, which extension writers can rely on both
598 596 # existing and NEVER being used by ipython itself. This gives them a
599 597 # convenient location for storing additional information and state
600 598 # their extensions may require, without fear of collisions with other
601 599 # ipython names that may develop later.
602 600 self.meta = Struct()
603 601
604 602 # Temporary files used for various purposes. Deleted at exit.
605 603 self.tempfiles = []
606 604 self.tempdirs = []
607 605
608 606 # Keep track of readline usage (later set by init_readline)
609 607 self.has_readline = False
610 608
611 609 # keep track of where we started running (mainly for crash post-mortem)
612 610 # This is not being used anywhere currently.
613 611 self.starting_dir = py3compat.getcwd()
614 612
615 613 # Indentation management
616 614 self.indent_current_nsp = 0
617 615
618 616 # Dict to track post-execution functions that have been registered
619 617 self._post_execute = {}
620 618
621 619 def init_environment(self):
622 620 """Any changes we need to make to the user's environment."""
623 621 pass
624 622
625 623 def init_encoding(self):
626 624 # Get system encoding at startup time. Certain terminals (like Emacs
627 625 # under Win32 have it set to None, and we need to have a known valid
628 626 # encoding to use in the raw_input() method
629 627 try:
630 628 self.stdin_encoding = sys.stdin.encoding or 'ascii'
631 629 except AttributeError:
632 630 self.stdin_encoding = 'ascii'
633 631
634 632 def init_syntax_highlighting(self):
635 633 # Python source parser/formatter for syntax highlighting
636 634 pyformat = PyColorize.Parser().format
637 635 self.pycolorize = lambda src: pyformat(src,'str',self.colors)
638 636
639 637 def init_pushd_popd_magic(self):
640 638 # for pushd/popd management
641 639 self.home_dir = get_home_dir()
642 640
643 641 self.dir_stack = []
644 642
645 643 def init_logger(self):
646 644 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
647 645 logmode='rotate')
648 646
649 647 def init_logstart(self):
650 648 """Initialize logging in case it was requested at the command line.
651 649 """
652 650 if self.logappend:
653 651 self.magic('logstart %s append' % self.logappend)
654 652 elif self.logfile:
655 653 self.magic('logstart %s' % self.logfile)
656 654 elif self.logstart:
657 655 self.magic('logstart')
658 656
659 657 def init_builtins(self):
660 658 # A single, static flag that we set to True. Its presence indicates
661 659 # that an IPython shell has been created, and we make no attempts at
662 660 # removing on exit or representing the existence of more than one
663 661 # IPython at a time.
664 662 builtin_mod.__dict__['__IPYTHON__'] = True
665 663
666 664 # In 0.11 we introduced '__IPYTHON__active' as an integer we'd try to
667 665 # manage on enter/exit, but with all our shells it's virtually
668 666 # impossible to get all the cases right. We're leaving the name in for
669 667 # those who adapted their codes to check for this flag, but will
670 668 # eventually remove it after a few more releases.
671 669 builtin_mod.__dict__['__IPYTHON__active'] = \
672 670 'Deprecated, check for __IPYTHON__'
673 671
674 672 self.builtin_trap = BuiltinTrap(shell=self)
675 673
676 674 def init_inspector(self):
677 675 # Object inspector
678 676 self.inspector = oinspect.Inspector(oinspect.InspectColors,
679 677 PyColorize.ANSICodeColors,
680 678 'NoColor',
681 679 self.object_info_string_level)
682 680
683 681 def init_io(self):
684 682 # This will just use sys.stdout and sys.stderr. If you want to
685 683 # override sys.stdout and sys.stderr themselves, you need to do that
686 684 # *before* instantiating this class, because io holds onto
687 685 # references to the underlying streams.
688 686 if (sys.platform == 'win32' or sys.platform == 'cli') and self.has_readline:
689 687 io.stdout = io.stderr = io.IOStream(self.readline._outputfile)
690 688 else:
691 689 io.stdout = io.IOStream(sys.stdout)
692 690 io.stderr = io.IOStream(sys.stderr)
693 691
694 692 def init_prompts(self):
695 693 self.prompt_manager = PromptManager(shell=self, parent=self)
696 694 self.configurables.append(self.prompt_manager)
697 695 # Set system prompts, so that scripts can decide if they are running
698 696 # interactively.
699 697 sys.ps1 = 'In : '
700 698 sys.ps2 = '...: '
701 699 sys.ps3 = 'Out: '
702 700
703 701 def init_display_formatter(self):
704 702 self.display_formatter = DisplayFormatter(parent=self)
705 703 self.configurables.append(self.display_formatter)
706 704
707 705 def init_display_pub(self):
708 706 self.display_pub = self.display_pub_class(parent=self)
709 707 self.configurables.append(self.display_pub)
710 708
711 709 def init_data_pub(self):
712 710 if not self.data_pub_class:
713 711 self.data_pub = None
714 712 return
715 713 self.data_pub = self.data_pub_class(parent=self)
716 714 self.configurables.append(self.data_pub)
717 715
718 716 def init_displayhook(self):
719 717 # Initialize displayhook, set in/out prompts and printing system
720 718 self.displayhook = self.displayhook_class(
721 719 parent=self,
722 720 shell=self,
723 721 cache_size=self.cache_size,
724 722 )
725 723 self.configurables.append(self.displayhook)
726 724 # This is a context manager that installs/revmoes the displayhook at
727 725 # the appropriate time.
728 726 self.display_trap = DisplayTrap(hook=self.displayhook)
729 727
730 728 def init_latextool(self):
731 729 """Configure LaTeXTool."""
732 730 cfg = LaTeXTool.instance(parent=self)
733 731 if cfg not in self.configurables:
734 732 self.configurables.append(cfg)
735 733
736 734 def init_virtualenv(self):
737 735 """Add a virtualenv to sys.path so the user can import modules from it.
738 736 This isn't perfect: it doesn't use the Python interpreter with which the
739 737 virtualenv was built, and it ignores the --no-site-packages option. A
740 738 warning will appear suggesting the user installs IPython in the
741 739 virtualenv, but for many cases, it probably works well enough.
742 740
743 741 Adapted from code snippets online.
744 742
745 743 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
746 744 """
747 745 if 'VIRTUAL_ENV' not in os.environ:
748 746 # Not in a virtualenv
749 747 return
750 748
751 749 # venv detection:
752 750 # stdlib venv may symlink sys.executable, so we can't use realpath.
753 751 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
754 752 # So we just check every item in the symlink tree (generally <= 3)
755 753 p = os.path.normcase(sys.executable)
756 754 paths = [p]
757 755 while os.path.islink(p):
758 756 p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p)))
759 757 paths.append(p)
760 758 p_venv = os.path.normcase(os.environ['VIRTUAL_ENV'])
761 759 if any(p.startswith(p_venv) for p in paths):
762 760 # Running properly in the virtualenv, don't need to do anything
763 761 return
764 762
765 763 warn("Attempting to work in a virtualenv. If you encounter problems, please "
766 764 "install IPython inside the virtualenv.")
767 765 if sys.platform == "win32":
768 766 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'Lib', 'site-packages')
769 767 else:
770 768 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'lib',
771 769 'python%d.%d' % sys.version_info[:2], 'site-packages')
772 770
773 771 import site
774 772 sys.path.insert(0, virtual_env)
775 773 site.addsitedir(virtual_env)
776 774
777 775 #-------------------------------------------------------------------------
778 776 # Things related to injections into the sys module
779 777 #-------------------------------------------------------------------------
780 778
781 779 def save_sys_module_state(self):
782 780 """Save the state of hooks in the sys module.
783 781
784 782 This has to be called after self.user_module is created.
785 783 """
786 784 self._orig_sys_module_state = {}
787 785 self._orig_sys_module_state['stdin'] = sys.stdin
788 786 self._orig_sys_module_state['stdout'] = sys.stdout
789 787 self._orig_sys_module_state['stderr'] = sys.stderr
790 788 self._orig_sys_module_state['excepthook'] = sys.excepthook
791 789 self._orig_sys_modules_main_name = self.user_module.__name__
792 790 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
793 791
794 792 def restore_sys_module_state(self):
795 793 """Restore the state of the sys module."""
796 794 try:
797 795 for k, v in iteritems(self._orig_sys_module_state):
798 796 setattr(sys, k, v)
799 797 except AttributeError:
800 798 pass
801 799 # Reset what what done in self.init_sys_modules
802 800 if self._orig_sys_modules_main_mod is not None:
803 801 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
804 802
805 803 #-------------------------------------------------------------------------
806 804 # Things related to the banner
807 805 #-------------------------------------------------------------------------
808 806
809 807 @property
810 808 def banner(self):
811 809 banner = self.banner1
812 810 if self.profile and self.profile != 'default':
813 811 banner += '\nIPython profile: %s\n' % self.profile
814 812 if self.banner2:
815 813 banner += '\n' + self.banner2
816 814 return banner
817 815
818 816 def show_banner(self, banner=None):
819 817 if banner is None:
820 818 banner = self.banner
821 819 self.write(banner)
822 820
823 821 #-------------------------------------------------------------------------
824 822 # Things related to hooks
825 823 #-------------------------------------------------------------------------
826 824
827 825 def init_hooks(self):
828 826 # hooks holds pointers used for user-side customizations
829 827 self.hooks = Struct()
830 828
831 829 self.strdispatchers = {}
832 830
833 831 # Set all default hooks, defined in the IPython.hooks module.
834 832 hooks = IPython.core.hooks
835 833 for hook_name in hooks.__all__:
836 834 # default hooks have priority 100, i.e. low; user hooks should have
837 835 # 0-100 priority
838 836 self.set_hook(hook_name,getattr(hooks,hook_name), 100, _warn_deprecated=False)
839 837
840 838 if self.display_page:
841 839 self.set_hook('show_in_pager', page.as_hook(page.display_page), 90)
842 840
843 841 def set_hook(self,name,hook, priority=50, str_key=None, re_key=None,
844 842 _warn_deprecated=True):
845 843 """set_hook(name,hook) -> sets an internal IPython hook.
846 844
847 845 IPython exposes some of its internal API as user-modifiable hooks. By
848 846 adding your function to one of these hooks, you can modify IPython's
849 847 behavior to call at runtime your own routines."""
850 848
851 849 # At some point in the future, this should validate the hook before it
852 850 # accepts it. Probably at least check that the hook takes the number
853 851 # of args it's supposed to.
854 852
855 853 f = types.MethodType(hook,self)
856 854
857 855 # check if the hook is for strdispatcher first
858 856 if str_key is not None:
859 857 sdp = self.strdispatchers.get(name, StrDispatch())
860 858 sdp.add_s(str_key, f, priority )
861 859 self.strdispatchers[name] = sdp
862 860 return
863 861 if re_key is not None:
864 862 sdp = self.strdispatchers.get(name, StrDispatch())
865 863 sdp.add_re(re.compile(re_key), f, priority )
866 864 self.strdispatchers[name] = sdp
867 865 return
868 866
869 867 dp = getattr(self.hooks, name, None)
870 868 if name not in IPython.core.hooks.__all__:
871 869 print("Warning! Hook '%s' is not one of %s" % \
872 870 (name, IPython.core.hooks.__all__ ))
873 871
874 872 if _warn_deprecated and (name in IPython.core.hooks.deprecated):
875 873 alternative = IPython.core.hooks.deprecated[name]
876 874 warn("Hook {} is deprecated. Use {} instead.".format(name, alternative))
877 875
878 876 if not dp:
879 877 dp = IPython.core.hooks.CommandChainDispatcher()
880 878
881 879 try:
882 880 dp.add(f,priority)
883 881 except AttributeError:
884 882 # it was not commandchain, plain old func - replace
885 883 dp = f
886 884
887 885 setattr(self.hooks,name, dp)
888 886
889 887 #-------------------------------------------------------------------------
890 888 # Things related to events
891 889 #-------------------------------------------------------------------------
892 890
893 891 def init_events(self):
894 892 self.events = EventManager(self, available_events)
895 893
896 894 self.events.register("pre_execute", self._clear_warning_registry)
897 895
898 896 def register_post_execute(self, func):
899 897 """DEPRECATED: Use ip.events.register('post_run_cell', func)
900 898
901 899 Register a function for calling after code execution.
902 900 """
903 901 warn("ip.register_post_execute is deprecated, use "
904 902 "ip.events.register('post_run_cell', func) instead.")
905 903 self.events.register('post_run_cell', func)
906 904
907 905 def _clear_warning_registry(self):
908 906 # clear the warning registry, so that different code blocks with
909 907 # overlapping line number ranges don't cause spurious suppression of
910 908 # warnings (see gh-6611 for details)
911 909 if "__warningregistry__" in self.user_global_ns:
912 910 del self.user_global_ns["__warningregistry__"]
913 911
914 912 #-------------------------------------------------------------------------
915 913 # Things related to the "main" module
916 914 #-------------------------------------------------------------------------
917 915
918 916 def new_main_mod(self, filename, modname):
919 917 """Return a new 'main' module object for user code execution.
920 918
921 919 ``filename`` should be the path of the script which will be run in the
922 920 module. Requests with the same filename will get the same module, with
923 921 its namespace cleared.
924 922
925 923 ``modname`` should be the module name - normally either '__main__' or
926 924 the basename of the file without the extension.
927 925
928 926 When scripts are executed via %run, we must keep a reference to their
929 927 __main__ module around so that Python doesn't
930 928 clear it, rendering references to module globals useless.
931 929
932 930 This method keeps said reference in a private dict, keyed by the
933 931 absolute path of the script. This way, for multiple executions of the
934 932 same script we only keep one copy of the namespace (the last one),
935 933 thus preventing memory leaks from old references while allowing the
936 934 objects from the last execution to be accessible.
937 935 """
938 936 filename = os.path.abspath(filename)
939 937 try:
940 938 main_mod = self._main_mod_cache[filename]
941 939 except KeyError:
942 940 main_mod = self._main_mod_cache[filename] = types.ModuleType(
943 941 py3compat.cast_bytes_py2(modname),
944 942 doc="Module created for script run in IPython")
945 943 else:
946 944 main_mod.__dict__.clear()
947 945 main_mod.__name__ = modname
948 946
949 947 main_mod.__file__ = filename
950 948 # It seems pydoc (and perhaps others) needs any module instance to
951 949 # implement a __nonzero__ method
952 950 main_mod.__nonzero__ = lambda : True
953 951
954 952 return main_mod
955 953
956 954 def clear_main_mod_cache(self):
957 955 """Clear the cache of main modules.
958 956
959 957 Mainly for use by utilities like %reset.
960 958
961 959 Examples
962 960 --------
963 961
964 962 In [15]: import IPython
965 963
966 964 In [16]: m = _ip.new_main_mod(IPython.__file__, 'IPython')
967 965
968 966 In [17]: len(_ip._main_mod_cache) > 0
969 967 Out[17]: True
970 968
971 969 In [18]: _ip.clear_main_mod_cache()
972 970
973 971 In [19]: len(_ip._main_mod_cache) == 0
974 972 Out[19]: True
975 973 """
976 974 self._main_mod_cache.clear()
977 975
978 976 #-------------------------------------------------------------------------
979 977 # Things related to debugging
980 978 #-------------------------------------------------------------------------
981 979
982 980 def init_pdb(self):
983 981 # Set calling of pdb on exceptions
984 982 # self.call_pdb is a property
985 983 self.call_pdb = self.pdb
986 984
987 985 def _get_call_pdb(self):
988 986 return self._call_pdb
989 987
990 988 def _set_call_pdb(self,val):
991 989
992 990 if val not in (0,1,False,True):
993 991 raise ValueError('new call_pdb value must be boolean')
994 992
995 993 # store value in instance
996 994 self._call_pdb = val
997 995
998 996 # notify the actual exception handlers
999 997 self.InteractiveTB.call_pdb = val
1000 998
1001 999 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
1002 1000 'Control auto-activation of pdb at exceptions')
1003 1001
1004 1002 def debugger(self,force=False):
1005 1003 """Call the pydb/pdb debugger.
1006 1004
1007 1005 Keywords:
1008 1006
1009 1007 - force(False): by default, this routine checks the instance call_pdb
1010 1008 flag and does not actually invoke the debugger if the flag is false.
1011 1009 The 'force' option forces the debugger to activate even if the flag
1012 1010 is false.
1013 1011 """
1014 1012
1015 1013 if not (force or self.call_pdb):
1016 1014 return
1017 1015
1018 1016 if not hasattr(sys,'last_traceback'):
1019 1017 error('No traceback has been produced, nothing to debug.')
1020 1018 return
1021 1019
1022 1020 # use pydb if available
1023 1021 if debugger.has_pydb:
1024 1022 from pydb import pm
1025 1023 else:
1026 1024 # fallback to our internal debugger
1027 1025 pm = lambda : self.InteractiveTB.debugger(force=True)
1028 1026
1029 1027 with self.readline_no_record:
1030 1028 pm()
1031 1029
1032 1030 #-------------------------------------------------------------------------
1033 1031 # Things related to IPython's various namespaces
1034 1032 #-------------------------------------------------------------------------
1035 1033 default_user_namespaces = True
1036 1034
1037 1035 def init_create_namespaces(self, user_module=None, user_ns=None):
1038 1036 # Create the namespace where the user will operate. user_ns is
1039 1037 # normally the only one used, and it is passed to the exec calls as
1040 1038 # the locals argument. But we do carry a user_global_ns namespace
1041 1039 # given as the exec 'globals' argument, This is useful in embedding
1042 1040 # situations where the ipython shell opens in a context where the
1043 1041 # distinction between locals and globals is meaningful. For
1044 1042 # non-embedded contexts, it is just the same object as the user_ns dict.
1045 1043
1046 1044 # FIXME. For some strange reason, __builtins__ is showing up at user
1047 1045 # level as a dict instead of a module. This is a manual fix, but I
1048 1046 # should really track down where the problem is coming from. Alex
1049 1047 # Schmolck reported this problem first.
1050 1048
1051 1049 # A useful post by Alex Martelli on this topic:
1052 1050 # Re: inconsistent value from __builtins__
1053 1051 # Von: Alex Martelli <aleaxit@yahoo.com>
1054 1052 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
1055 1053 # Gruppen: comp.lang.python
1056 1054
1057 1055 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
1058 1056 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
1059 1057 # > <type 'dict'>
1060 1058 # > >>> print type(__builtins__)
1061 1059 # > <type 'module'>
1062 1060 # > Is this difference in return value intentional?
1063 1061
1064 1062 # Well, it's documented that '__builtins__' can be either a dictionary
1065 1063 # or a module, and it's been that way for a long time. Whether it's
1066 1064 # intentional (or sensible), I don't know. In any case, the idea is
1067 1065 # that if you need to access the built-in namespace directly, you
1068 1066 # should start with "import __builtin__" (note, no 's') which will
1069 1067 # definitely give you a module. Yeah, it's somewhat confusing:-(.
1070 1068
1071 1069 # These routines return a properly built module and dict as needed by
1072 1070 # the rest of the code, and can also be used by extension writers to
1073 1071 # generate properly initialized namespaces.
1074 1072 if (user_ns is not None) or (user_module is not None):
1075 1073 self.default_user_namespaces = False
1076 1074 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
1077 1075
1078 1076 # A record of hidden variables we have added to the user namespace, so
1079 1077 # we can list later only variables defined in actual interactive use.
1080 1078 self.user_ns_hidden = {}
1081 1079
1082 1080 # Now that FakeModule produces a real module, we've run into a nasty
1083 1081 # problem: after script execution (via %run), the module where the user
1084 1082 # code ran is deleted. Now that this object is a true module (needed
1085 1083 # so docetst and other tools work correctly), the Python module
1086 1084 # teardown mechanism runs over it, and sets to None every variable
1087 1085 # present in that module. Top-level references to objects from the
1088 1086 # script survive, because the user_ns is updated with them. However,
1089 1087 # calling functions defined in the script that use other things from
1090 1088 # the script will fail, because the function's closure had references
1091 1089 # to the original objects, which are now all None. So we must protect
1092 1090 # these modules from deletion by keeping a cache.
1093 1091 #
1094 1092 # To avoid keeping stale modules around (we only need the one from the
1095 1093 # last run), we use a dict keyed with the full path to the script, so
1096 1094 # only the last version of the module is held in the cache. Note,
1097 1095 # however, that we must cache the module *namespace contents* (their
1098 1096 # __dict__). Because if we try to cache the actual modules, old ones
1099 1097 # (uncached) could be destroyed while still holding references (such as
1100 1098 # those held by GUI objects that tend to be long-lived)>
1101 1099 #
1102 1100 # The %reset command will flush this cache. See the cache_main_mod()
1103 1101 # and clear_main_mod_cache() methods for details on use.
1104 1102
1105 1103 # This is the cache used for 'main' namespaces
1106 1104 self._main_mod_cache = {}
1107 1105
1108 1106 # A table holding all the namespaces IPython deals with, so that
1109 1107 # introspection facilities can search easily.
1110 1108 self.ns_table = {'user_global':self.user_module.__dict__,
1111 1109 'user_local':self.user_ns,
1112 1110 'builtin':builtin_mod.__dict__
1113 1111 }
1114 1112
1115 1113 @property
1116 1114 def user_global_ns(self):
1117 1115 return self.user_module.__dict__
1118 1116
1119 1117 def prepare_user_module(self, user_module=None, user_ns=None):
1120 1118 """Prepare the module and namespace in which user code will be run.
1121 1119
1122 1120 When IPython is started normally, both parameters are None: a new module
1123 1121 is created automatically, and its __dict__ used as the namespace.
1124 1122
1125 1123 If only user_module is provided, its __dict__ is used as the namespace.
1126 1124 If only user_ns is provided, a dummy module is created, and user_ns
1127 1125 becomes the global namespace. If both are provided (as they may be
1128 1126 when embedding), user_ns is the local namespace, and user_module
1129 1127 provides the global namespace.
1130 1128
1131 1129 Parameters
1132 1130 ----------
1133 1131 user_module : module, optional
1134 1132 The current user module in which IPython is being run. If None,
1135 1133 a clean module will be created.
1136 1134 user_ns : dict, optional
1137 1135 A namespace in which to run interactive commands.
1138 1136
1139 1137 Returns
1140 1138 -------
1141 1139 A tuple of user_module and user_ns, each properly initialised.
1142 1140 """
1143 1141 if user_module is None and user_ns is not None:
1144 1142 user_ns.setdefault("__name__", "__main__")
1145 1143 user_module = DummyMod()
1146 1144 user_module.__dict__ = user_ns
1147 1145
1148 1146 if user_module is None:
1149 1147 user_module = types.ModuleType("__main__",
1150 1148 doc="Automatically created module for IPython interactive environment")
1151 1149
1152 1150 # We must ensure that __builtin__ (without the final 's') is always
1153 1151 # available and pointing to the __builtin__ *module*. For more details:
1154 1152 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1155 1153 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1156 1154 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1157 1155
1158 1156 if user_ns is None:
1159 1157 user_ns = user_module.__dict__
1160 1158
1161 1159 return user_module, user_ns
1162 1160
1163 1161 def init_sys_modules(self):
1164 1162 # We need to insert into sys.modules something that looks like a
1165 1163 # module but which accesses the IPython namespace, for shelve and
1166 1164 # pickle to work interactively. Normally they rely on getting
1167 1165 # everything out of __main__, but for embedding purposes each IPython
1168 1166 # instance has its own private namespace, so we can't go shoving
1169 1167 # everything into __main__.
1170 1168
1171 1169 # note, however, that we should only do this for non-embedded
1172 1170 # ipythons, which really mimic the __main__.__dict__ with their own
1173 1171 # namespace. Embedded instances, on the other hand, should not do
1174 1172 # this because they need to manage the user local/global namespaces
1175 1173 # only, but they live within a 'normal' __main__ (meaning, they
1176 1174 # shouldn't overtake the execution environment of the script they're
1177 1175 # embedded in).
1178 1176
1179 1177 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1180 1178 main_name = self.user_module.__name__
1181 1179 sys.modules[main_name] = self.user_module
1182 1180
1183 1181 def init_user_ns(self):
1184 1182 """Initialize all user-visible namespaces to their minimum defaults.
1185 1183
1186 1184 Certain history lists are also initialized here, as they effectively
1187 1185 act as user namespaces.
1188 1186
1189 1187 Notes
1190 1188 -----
1191 1189 All data structures here are only filled in, they are NOT reset by this
1192 1190 method. If they were not empty before, data will simply be added to
1193 1191 therm.
1194 1192 """
1195 1193 # This function works in two parts: first we put a few things in
1196 1194 # user_ns, and we sync that contents into user_ns_hidden so that these
1197 1195 # initial variables aren't shown by %who. After the sync, we add the
1198 1196 # rest of what we *do* want the user to see with %who even on a new
1199 1197 # session (probably nothing, so theye really only see their own stuff)
1200 1198
1201 1199 # The user dict must *always* have a __builtin__ reference to the
1202 1200 # Python standard __builtin__ namespace, which must be imported.
1203 1201 # This is so that certain operations in prompt evaluation can be
1204 1202 # reliably executed with builtins. Note that we can NOT use
1205 1203 # __builtins__ (note the 's'), because that can either be a dict or a
1206 1204 # module, and can even mutate at runtime, depending on the context
1207 1205 # (Python makes no guarantees on it). In contrast, __builtin__ is
1208 1206 # always a module object, though it must be explicitly imported.
1209 1207
1210 1208 # For more details:
1211 1209 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1212 1210 ns = dict()
1213 1211
1214 1212 # make global variables for user access to the histories
1215 1213 ns['_ih'] = self.history_manager.input_hist_parsed
1216 1214 ns['_oh'] = self.history_manager.output_hist
1217 1215 ns['_dh'] = self.history_manager.dir_hist
1218 1216
1219 1217 ns['_sh'] = shadowns
1220 1218
1221 1219 # user aliases to input and output histories. These shouldn't show up
1222 1220 # in %who, as they can have very large reprs.
1223 1221 ns['In'] = self.history_manager.input_hist_parsed
1224 1222 ns['Out'] = self.history_manager.output_hist
1225 1223
1226 1224 # Store myself as the public api!!!
1227 1225 ns['get_ipython'] = self.get_ipython
1228 1226
1229 1227 ns['exit'] = self.exiter
1230 1228 ns['quit'] = self.exiter
1231 1229
1232 1230 # Sync what we've added so far to user_ns_hidden so these aren't seen
1233 1231 # by %who
1234 1232 self.user_ns_hidden.update(ns)
1235 1233
1236 1234 # Anything put into ns now would show up in %who. Think twice before
1237 1235 # putting anything here, as we really want %who to show the user their
1238 1236 # stuff, not our variables.
1239 1237
1240 1238 # Finally, update the real user's namespace
1241 1239 self.user_ns.update(ns)
1242 1240
1243 1241 @property
1244 1242 def all_ns_refs(self):
1245 1243 """Get a list of references to all the namespace dictionaries in which
1246 1244 IPython might store a user-created object.
1247 1245
1248 1246 Note that this does not include the displayhook, which also caches
1249 1247 objects from the output."""
1250 1248 return [self.user_ns, self.user_global_ns, self.user_ns_hidden] + \
1251 1249 [m.__dict__ for m in self._main_mod_cache.values()]
1252 1250
1253 1251 def reset(self, new_session=True):
1254 1252 """Clear all internal namespaces, and attempt to release references to
1255 1253 user objects.
1256 1254
1257 1255 If new_session is True, a new history session will be opened.
1258 1256 """
1259 1257 # Clear histories
1260 1258 self.history_manager.reset(new_session)
1261 1259 # Reset counter used to index all histories
1262 1260 if new_session:
1263 1261 self.execution_count = 1
1264 1262
1265 1263 # Flush cached output items
1266 1264 if self.displayhook.do_full_cache:
1267 1265 self.displayhook.flush()
1268 1266
1269 1267 # The main execution namespaces must be cleared very carefully,
1270 1268 # skipping the deletion of the builtin-related keys, because doing so
1271 1269 # would cause errors in many object's __del__ methods.
1272 1270 if self.user_ns is not self.user_global_ns:
1273 1271 self.user_ns.clear()
1274 1272 ns = self.user_global_ns
1275 1273 drop_keys = set(ns.keys())
1276 1274 drop_keys.discard('__builtin__')
1277 1275 drop_keys.discard('__builtins__')
1278 1276 drop_keys.discard('__name__')
1279 1277 for k in drop_keys:
1280 1278 del ns[k]
1281 1279
1282 1280 self.user_ns_hidden.clear()
1283 1281
1284 1282 # Restore the user namespaces to minimal usability
1285 1283 self.init_user_ns()
1286 1284
1287 1285 # Restore the default and user aliases
1288 1286 self.alias_manager.clear_aliases()
1289 1287 self.alias_manager.init_aliases()
1290 1288
1291 1289 # Flush the private list of module references kept for script
1292 1290 # execution protection
1293 1291 self.clear_main_mod_cache()
1294 1292
1295 1293 def del_var(self, varname, by_name=False):
1296 1294 """Delete a variable from the various namespaces, so that, as
1297 1295 far as possible, we're not keeping any hidden references to it.
1298 1296
1299 1297 Parameters
1300 1298 ----------
1301 1299 varname : str
1302 1300 The name of the variable to delete.
1303 1301 by_name : bool
1304 1302 If True, delete variables with the given name in each
1305 1303 namespace. If False (default), find the variable in the user
1306 1304 namespace, and delete references to it.
1307 1305 """
1308 1306 if varname in ('__builtin__', '__builtins__'):
1309 1307 raise ValueError("Refusing to delete %s" % varname)
1310 1308
1311 1309 ns_refs = self.all_ns_refs
1312 1310
1313 1311 if by_name: # Delete by name
1314 1312 for ns in ns_refs:
1315 1313 try:
1316 1314 del ns[varname]
1317 1315 except KeyError:
1318 1316 pass
1319 1317 else: # Delete by object
1320 1318 try:
1321 1319 obj = self.user_ns[varname]
1322 1320 except KeyError:
1323 1321 raise NameError("name '%s' is not defined" % varname)
1324 1322 # Also check in output history
1325 1323 ns_refs.append(self.history_manager.output_hist)
1326 1324 for ns in ns_refs:
1327 1325 to_delete = [n for n, o in iteritems(ns) if o is obj]
1328 1326 for name in to_delete:
1329 1327 del ns[name]
1330 1328
1331 1329 # displayhook keeps extra references, but not in a dictionary
1332 1330 for name in ('_', '__', '___'):
1333 1331 if getattr(self.displayhook, name) is obj:
1334 1332 setattr(self.displayhook, name, None)
1335 1333
1336 1334 def reset_selective(self, regex=None):
1337 1335 """Clear selective variables from internal namespaces based on a
1338 1336 specified regular expression.
1339 1337
1340 1338 Parameters
1341 1339 ----------
1342 1340 regex : string or compiled pattern, optional
1343 1341 A regular expression pattern that will be used in searching
1344 1342 variable names in the users namespaces.
1345 1343 """
1346 1344 if regex is not None:
1347 1345 try:
1348 1346 m = re.compile(regex)
1349 1347 except TypeError:
1350 1348 raise TypeError('regex must be a string or compiled pattern')
1351 1349 # Search for keys in each namespace that match the given regex
1352 1350 # If a match is found, delete the key/value pair.
1353 1351 for ns in self.all_ns_refs:
1354 1352 for var in ns:
1355 1353 if m.search(var):
1356 1354 del ns[var]
1357 1355
1358 1356 def push(self, variables, interactive=True):
1359 1357 """Inject a group of variables into the IPython user namespace.
1360 1358
1361 1359 Parameters
1362 1360 ----------
1363 1361 variables : dict, str or list/tuple of str
1364 1362 The variables to inject into the user's namespace. If a dict, a
1365 1363 simple update is done. If a str, the string is assumed to have
1366 1364 variable names separated by spaces. A list/tuple of str can also
1367 1365 be used to give the variable names. If just the variable names are
1368 1366 give (list/tuple/str) then the variable values looked up in the
1369 1367 callers frame.
1370 1368 interactive : bool
1371 1369 If True (default), the variables will be listed with the ``who``
1372 1370 magic.
1373 1371 """
1374 1372 vdict = None
1375 1373
1376 1374 # We need a dict of name/value pairs to do namespace updates.
1377 1375 if isinstance(variables, dict):
1378 1376 vdict = variables
1379 1377 elif isinstance(variables, string_types+(list, tuple)):
1380 1378 if isinstance(variables, string_types):
1381 1379 vlist = variables.split()
1382 1380 else:
1383 1381 vlist = variables
1384 1382 vdict = {}
1385 1383 cf = sys._getframe(1)
1386 1384 for name in vlist:
1387 1385 try:
1388 1386 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1389 1387 except:
1390 1388 print('Could not get variable %s from %s' %
1391 1389 (name,cf.f_code.co_name))
1392 1390 else:
1393 1391 raise ValueError('variables must be a dict/str/list/tuple')
1394 1392
1395 1393 # Propagate variables to user namespace
1396 1394 self.user_ns.update(vdict)
1397 1395
1398 1396 # And configure interactive visibility
1399 1397 user_ns_hidden = self.user_ns_hidden
1400 1398 if interactive:
1401 1399 for name in vdict:
1402 1400 user_ns_hidden.pop(name, None)
1403 1401 else:
1404 1402 user_ns_hidden.update(vdict)
1405 1403
1406 1404 def drop_by_id(self, variables):
1407 1405 """Remove a dict of variables from the user namespace, if they are the
1408 1406 same as the values in the dictionary.
1409 1407
1410 1408 This is intended for use by extensions: variables that they've added can
1411 1409 be taken back out if they are unloaded, without removing any that the
1412 1410 user has overwritten.
1413 1411
1414 1412 Parameters
1415 1413 ----------
1416 1414 variables : dict
1417 1415 A dictionary mapping object names (as strings) to the objects.
1418 1416 """
1419 1417 for name, obj in iteritems(variables):
1420 1418 if name in self.user_ns and self.user_ns[name] is obj:
1421 1419 del self.user_ns[name]
1422 1420 self.user_ns_hidden.pop(name, None)
1423 1421
1424 1422 #-------------------------------------------------------------------------
1425 1423 # Things related to object introspection
1426 1424 #-------------------------------------------------------------------------
1427 1425
1428 1426 def _ofind(self, oname, namespaces=None):
1429 1427 """Find an object in the available namespaces.
1430 1428
1431 1429 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1432 1430
1433 1431 Has special code to detect magic functions.
1434 1432 """
1435 1433 oname = oname.strip()
1436 1434 #print '1- oname: <%r>' % oname # dbg
1437 1435 if not oname.startswith(ESC_MAGIC) and \
1438 1436 not oname.startswith(ESC_MAGIC2) and \
1439 1437 not py3compat.isidentifier(oname, dotted=True):
1440 1438 return dict(found=False)
1441 1439
1442 1440 alias_ns = None
1443 1441 if namespaces is None:
1444 1442 # Namespaces to search in:
1445 1443 # Put them in a list. The order is important so that we
1446 1444 # find things in the same order that Python finds them.
1447 1445 namespaces = [ ('Interactive', self.user_ns),
1448 1446 ('Interactive (global)', self.user_global_ns),
1449 1447 ('Python builtin', builtin_mod.__dict__),
1450 1448 ]
1451 1449
1452 1450 # initialize results to 'null'
1453 1451 found = False; obj = None; ospace = None; ds = None;
1454 1452 ismagic = False; isalias = False; parent = None
1455 1453
1456 1454 # We need to special-case 'print', which as of python2.6 registers as a
1457 1455 # function but should only be treated as one if print_function was
1458 1456 # loaded with a future import. In this case, just bail.
1459 1457 if (oname == 'print' and not py3compat.PY3 and not \
1460 1458 (self.compile.compiler_flags & __future__.CO_FUTURE_PRINT_FUNCTION)):
1461 1459 return {'found':found, 'obj':obj, 'namespace':ospace,
1462 1460 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1463 1461
1464 1462 # Look for the given name by splitting it in parts. If the head is
1465 1463 # found, then we look for all the remaining parts as members, and only
1466 1464 # declare success if we can find them all.
1467 1465 oname_parts = oname.split('.')
1468 1466 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1469 1467 for nsname,ns in namespaces:
1470 1468 try:
1471 1469 obj = ns[oname_head]
1472 1470 except KeyError:
1473 1471 continue
1474 1472 else:
1475 1473 #print 'oname_rest:', oname_rest # dbg
1476 1474 for idx, part in enumerate(oname_rest):
1477 1475 try:
1478 1476 parent = obj
1479 1477 # The last part is looked up in a special way to avoid
1480 1478 # descriptor invocation as it may raise or have side
1481 1479 # effects.
1482 1480 if idx == len(oname_rest) - 1:
1483 1481 obj = self._getattr_property(obj, part)
1484 1482 else:
1485 1483 obj = getattr(obj, part)
1486 1484 except:
1487 1485 # Blanket except b/c some badly implemented objects
1488 1486 # allow __getattr__ to raise exceptions other than
1489 1487 # AttributeError, which then crashes IPython.
1490 1488 break
1491 1489 else:
1492 1490 # If we finish the for loop (no break), we got all members
1493 1491 found = True
1494 1492 ospace = nsname
1495 1493 break # namespace loop
1496 1494
1497 1495 # Try to see if it's magic
1498 1496 if not found:
1499 1497 obj = None
1500 1498 if oname.startswith(ESC_MAGIC2):
1501 1499 oname = oname.lstrip(ESC_MAGIC2)
1502 1500 obj = self.find_cell_magic(oname)
1503 1501 elif oname.startswith(ESC_MAGIC):
1504 1502 oname = oname.lstrip(ESC_MAGIC)
1505 1503 obj = self.find_line_magic(oname)
1506 1504 else:
1507 1505 # search without prefix, so run? will find %run?
1508 1506 obj = self.find_line_magic(oname)
1509 1507 if obj is None:
1510 1508 obj = self.find_cell_magic(oname)
1511 1509 if obj is not None:
1512 1510 found = True
1513 1511 ospace = 'IPython internal'
1514 1512 ismagic = True
1515 1513
1516 1514 # Last try: special-case some literals like '', [], {}, etc:
1517 1515 if not found and oname_head in ["''",'""','[]','{}','()']:
1518 1516 obj = eval(oname_head)
1519 1517 found = True
1520 1518 ospace = 'Interactive'
1521 1519
1522 1520 return {'found':found, 'obj':obj, 'namespace':ospace,
1523 1521 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1524 1522
1525 1523 @staticmethod
1526 1524 def _getattr_property(obj, attrname):
1527 1525 """Property-aware getattr to use in object finding.
1528 1526
1529 1527 If attrname represents a property, return it unevaluated (in case it has
1530 1528 side effects or raises an error.
1531 1529
1532 1530 """
1533 1531 if not isinstance(obj, type):
1534 1532 try:
1535 1533 # `getattr(type(obj), attrname)` is not guaranteed to return
1536 1534 # `obj`, but does so for property:
1537 1535 #
1538 1536 # property.__get__(self, None, cls) -> self
1539 1537 #
1540 1538 # The universal alternative is to traverse the mro manually
1541 1539 # searching for attrname in class dicts.
1542 1540 attr = getattr(type(obj), attrname)
1543 1541 except AttributeError:
1544 1542 pass
1545 1543 else:
1546 1544 # This relies on the fact that data descriptors (with both
1547 1545 # __get__ & __set__ magic methods) take precedence over
1548 1546 # instance-level attributes:
1549 1547 #
1550 1548 # class A(object):
1551 1549 # @property
1552 1550 # def foobar(self): return 123
1553 1551 # a = A()
1554 1552 # a.__dict__['foobar'] = 345
1555 1553 # a.foobar # == 123
1556 1554 #
1557 1555 # So, a property may be returned right away.
1558 1556 if isinstance(attr, property):
1559 1557 return attr
1560 1558
1561 1559 # Nothing helped, fall back.
1562 1560 return getattr(obj, attrname)
1563 1561
1564 1562 def _object_find(self, oname, namespaces=None):
1565 1563 """Find an object and return a struct with info about it."""
1566 1564 return Struct(self._ofind(oname, namespaces))
1567 1565
1568 1566 def _inspect(self, meth, oname, namespaces=None, **kw):
1569 1567 """Generic interface to the inspector system.
1570 1568
1571 1569 This function is meant to be called by pdef, pdoc & friends."""
1572 1570 info = self._object_find(oname, namespaces)
1573 1571 if info.found:
1574 1572 pmethod = getattr(self.inspector, meth)
1575 1573 formatter = format_screen if info.ismagic else None
1576 1574 if meth == 'pdoc':
1577 1575 pmethod(info.obj, oname, formatter)
1578 1576 elif meth == 'pinfo':
1579 1577 pmethod(info.obj, oname, formatter, info, **kw)
1580 1578 else:
1581 1579 pmethod(info.obj, oname)
1582 1580 else:
1583 1581 print('Object `%s` not found.' % oname)
1584 1582 return 'not found' # so callers can take other action
1585 1583
1586 1584 def object_inspect(self, oname, detail_level=0):
1587 1585 """Get object info about oname"""
1588 1586 with self.builtin_trap:
1589 1587 info = self._object_find(oname)
1590 1588 if info.found:
1591 1589 return self.inspector.info(info.obj, oname, info=info,
1592 1590 detail_level=detail_level
1593 1591 )
1594 1592 else:
1595 1593 return oinspect.object_info(name=oname, found=False)
1596 1594
1597 1595 def object_inspect_text(self, oname, detail_level=0):
1598 1596 """Get object info as formatted text"""
1599 1597 with self.builtin_trap:
1600 1598 info = self._object_find(oname)
1601 1599 if info.found:
1602 1600 return self.inspector._format_info(info.obj, oname, info=info,
1603 1601 detail_level=detail_level
1604 1602 )
1605 1603 else:
1606 1604 raise KeyError(oname)
1607 1605
1608 1606 #-------------------------------------------------------------------------
1609 1607 # Things related to history management
1610 1608 #-------------------------------------------------------------------------
1611 1609
1612 1610 def init_history(self):
1613 1611 """Sets up the command history, and starts regular autosaves."""
1614 1612 self.history_manager = HistoryManager(shell=self, parent=self)
1615 1613 self.configurables.append(self.history_manager)
1616 1614
1617 1615 #-------------------------------------------------------------------------
1618 1616 # Things related to exception handling and tracebacks (not debugging)
1619 1617 #-------------------------------------------------------------------------
1620 1618
1621 1619 def init_traceback_handlers(self, custom_exceptions):
1622 1620 # Syntax error handler.
1623 1621 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor')
1624 1622
1625 1623 # The interactive one is initialized with an offset, meaning we always
1626 1624 # want to remove the topmost item in the traceback, which is our own
1627 1625 # internal code. Valid modes: ['Plain','Context','Verbose']
1628 1626 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1629 1627 color_scheme='NoColor',
1630 1628 tb_offset = 1,
1631 1629 check_cache=check_linecache_ipython)
1632 1630
1633 1631 # The instance will store a pointer to the system-wide exception hook,
1634 1632 # so that runtime code (such as magics) can access it. This is because
1635 1633 # during the read-eval loop, it may get temporarily overwritten.
1636 1634 self.sys_excepthook = sys.excepthook
1637 1635
1638 1636 # and add any custom exception handlers the user may have specified
1639 1637 self.set_custom_exc(*custom_exceptions)
1640 1638
1641 1639 # Set the exception mode
1642 1640 self.InteractiveTB.set_mode(mode=self.xmode)
1643 1641
1644 1642 def set_custom_exc(self, exc_tuple, handler):
1645 1643 """set_custom_exc(exc_tuple,handler)
1646 1644
1647 1645 Set a custom exception handler, which will be called if any of the
1648 1646 exceptions in exc_tuple occur in the mainloop (specifically, in the
1649 1647 run_code() method).
1650 1648
1651 1649 Parameters
1652 1650 ----------
1653 1651
1654 1652 exc_tuple : tuple of exception classes
1655 1653 A *tuple* of exception classes, for which to call the defined
1656 1654 handler. It is very important that you use a tuple, and NOT A
1657 1655 LIST here, because of the way Python's except statement works. If
1658 1656 you only want to trap a single exception, use a singleton tuple::
1659 1657
1660 1658 exc_tuple == (MyCustomException,)
1661 1659
1662 1660 handler : callable
1663 1661 handler must have the following signature::
1664 1662
1665 1663 def my_handler(self, etype, value, tb, tb_offset=None):
1666 1664 ...
1667 1665 return structured_traceback
1668 1666
1669 1667 Your handler must return a structured traceback (a list of strings),
1670 1668 or None.
1671 1669
1672 1670 This will be made into an instance method (via types.MethodType)
1673 1671 of IPython itself, and it will be called if any of the exceptions
1674 1672 listed in the exc_tuple are caught. If the handler is None, an
1675 1673 internal basic one is used, which just prints basic info.
1676 1674
1677 1675 To protect IPython from crashes, if your handler ever raises an
1678 1676 exception or returns an invalid result, it will be immediately
1679 1677 disabled.
1680 1678
1681 1679 WARNING: by putting in your own exception handler into IPython's main
1682 1680 execution loop, you run a very good chance of nasty crashes. This
1683 1681 facility should only be used if you really know what you are doing."""
1684 1682
1685 1683 assert type(exc_tuple)==type(()) , \
1686 1684 "The custom exceptions must be given AS A TUPLE."
1687 1685
1688 1686 def dummy_handler(self,etype,value,tb,tb_offset=None):
1689 1687 print('*** Simple custom exception handler ***')
1690 1688 print('Exception type :',etype)
1691 1689 print('Exception value:',value)
1692 1690 print('Traceback :',tb)
1693 1691 #print 'Source code :','\n'.join(self.buffer)
1694 1692
1695 1693 def validate_stb(stb):
1696 1694 """validate structured traceback return type
1697 1695
1698 1696 return type of CustomTB *should* be a list of strings, but allow
1699 1697 single strings or None, which are harmless.
1700 1698
1701 1699 This function will *always* return a list of strings,
1702 1700 and will raise a TypeError if stb is inappropriate.
1703 1701 """
1704 1702 msg = "CustomTB must return list of strings, not %r" % stb
1705 1703 if stb is None:
1706 1704 return []
1707 1705 elif isinstance(stb, string_types):
1708 1706 return [stb]
1709 1707 elif not isinstance(stb, list):
1710 1708 raise TypeError(msg)
1711 1709 # it's a list
1712 1710 for line in stb:
1713 1711 # check every element
1714 1712 if not isinstance(line, string_types):
1715 1713 raise TypeError(msg)
1716 1714 return stb
1717 1715
1718 1716 if handler is None:
1719 1717 wrapped = dummy_handler
1720 1718 else:
1721 1719 def wrapped(self,etype,value,tb,tb_offset=None):
1722 1720 """wrap CustomTB handler, to protect IPython from user code
1723 1721
1724 1722 This makes it harder (but not impossible) for custom exception
1725 1723 handlers to crash IPython.
1726 1724 """
1727 1725 try:
1728 1726 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
1729 1727 return validate_stb(stb)
1730 1728 except:
1731 1729 # clear custom handler immediately
1732 1730 self.set_custom_exc((), None)
1733 1731 print("Custom TB Handler failed, unregistering", file=io.stderr)
1734 1732 # show the exception in handler first
1735 1733 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
1736 1734 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1737 1735 print("The original exception:", file=io.stdout)
1738 1736 stb = self.InteractiveTB.structured_traceback(
1739 1737 (etype,value,tb), tb_offset=tb_offset
1740 1738 )
1741 1739 return stb
1742 1740
1743 1741 self.CustomTB = types.MethodType(wrapped,self)
1744 1742 self.custom_exceptions = exc_tuple
1745 1743
1746 1744 def excepthook(self, etype, value, tb):
1747 1745 """One more defense for GUI apps that call sys.excepthook.
1748 1746
1749 1747 GUI frameworks like wxPython trap exceptions and call
1750 1748 sys.excepthook themselves. I guess this is a feature that
1751 1749 enables them to keep running after exceptions that would
1752 1750 otherwise kill their mainloop. This is a bother for IPython
1753 1751 which excepts to catch all of the program exceptions with a try:
1754 1752 except: statement.
1755 1753
1756 1754 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
1757 1755 any app directly invokes sys.excepthook, it will look to the user like
1758 1756 IPython crashed. In order to work around this, we can disable the
1759 1757 CrashHandler and replace it with this excepthook instead, which prints a
1760 1758 regular traceback using our InteractiveTB. In this fashion, apps which
1761 1759 call sys.excepthook will generate a regular-looking exception from
1762 1760 IPython, and the CrashHandler will only be triggered by real IPython
1763 1761 crashes.
1764 1762
1765 1763 This hook should be used sparingly, only in places which are not likely
1766 1764 to be true IPython errors.
1767 1765 """
1768 1766 self.showtraceback((etype, value, tb), tb_offset=0)
1769 1767
1770 1768 def _get_exc_info(self, exc_tuple=None):
1771 1769 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1772 1770
1773 1771 Ensures sys.last_type,value,traceback hold the exc_info we found,
1774 1772 from whichever source.
1775 1773
1776 1774 raises ValueError if none of these contain any information
1777 1775 """
1778 1776 if exc_tuple is None:
1779 1777 etype, value, tb = sys.exc_info()
1780 1778 else:
1781 1779 etype, value, tb = exc_tuple
1782 1780
1783 1781 if etype is None:
1784 1782 if hasattr(sys, 'last_type'):
1785 1783 etype, value, tb = sys.last_type, sys.last_value, \
1786 1784 sys.last_traceback
1787 1785
1788 1786 if etype is None:
1789 1787 raise ValueError("No exception to find")
1790 1788
1791 1789 # Now store the exception info in sys.last_type etc.
1792 1790 # WARNING: these variables are somewhat deprecated and not
1793 1791 # necessarily safe to use in a threaded environment, but tools
1794 1792 # like pdb depend on their existence, so let's set them. If we
1795 1793 # find problems in the field, we'll need to revisit their use.
1796 1794 sys.last_type = etype
1797 1795 sys.last_value = value
1798 1796 sys.last_traceback = tb
1799 1797
1800 1798 return etype, value, tb
1801 1799
1802 1800 def show_usage_error(self, exc):
1803 1801 """Show a short message for UsageErrors
1804 1802
1805 1803 These are special exceptions that shouldn't show a traceback.
1806 1804 """
1807 1805 self.write_err("UsageError: %s" % exc)
1808 1806
1809 1807 def get_exception_only(self, exc_tuple=None):
1810 1808 """
1811 1809 Return as a string (ending with a newline) the exception that
1812 1810 just occurred, without any traceback.
1813 1811 """
1814 1812 etype, value, tb = self._get_exc_info(exc_tuple)
1815 1813 msg = traceback.format_exception_only(etype, value)
1816 1814 return ''.join(msg)
1817 1815
1818 1816 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
1819 1817 exception_only=False):
1820 1818 """Display the exception that just occurred.
1821 1819
1822 1820 If nothing is known about the exception, this is the method which
1823 1821 should be used throughout the code for presenting user tracebacks,
1824 1822 rather than directly invoking the InteractiveTB object.
1825 1823
1826 1824 A specific showsyntaxerror() also exists, but this method can take
1827 1825 care of calling it if needed, so unless you are explicitly catching a
1828 1826 SyntaxError exception, don't try to analyze the stack manually and
1829 1827 simply call this method."""
1830 1828
1831 1829 try:
1832 1830 try:
1833 1831 etype, value, tb = self._get_exc_info(exc_tuple)
1834 1832 except ValueError:
1835 1833 self.write_err('No traceback available to show.\n')
1836 1834 return
1837 1835
1838 1836 if issubclass(etype, SyntaxError):
1839 1837 # Though this won't be called by syntax errors in the input
1840 1838 # line, there may be SyntaxError cases with imported code.
1841 1839 self.showsyntaxerror(filename)
1842 1840 elif etype is UsageError:
1843 1841 self.show_usage_error(value)
1844 1842 else:
1845 1843 if exception_only:
1846 1844 stb = ['An exception has occurred, use %tb to see '
1847 1845 'the full traceback.\n']
1848 1846 stb.extend(self.InteractiveTB.get_exception_only(etype,
1849 1847 value))
1850 1848 else:
1851 1849 try:
1852 1850 # Exception classes can customise their traceback - we
1853 1851 # use this in IPython.parallel for exceptions occurring
1854 1852 # in the engines. This should return a list of strings.
1855 1853 stb = value._render_traceback_()
1856 1854 except Exception:
1857 1855 stb = self.InteractiveTB.structured_traceback(etype,
1858 1856 value, tb, tb_offset=tb_offset)
1859 1857
1860 1858 self._showtraceback(etype, value, stb)
1861 1859 if self.call_pdb:
1862 1860 # drop into debugger
1863 1861 self.debugger(force=True)
1864 1862 return
1865 1863
1866 1864 # Actually show the traceback
1867 1865 self._showtraceback(etype, value, stb)
1868 1866
1869 1867 except KeyboardInterrupt:
1870 1868 self.write_err('\n' + self.get_exception_only())
1871 1869
1872 1870 def _showtraceback(self, etype, evalue, stb):
1873 1871 """Actually show a traceback.
1874 1872
1875 1873 Subclasses may override this method to put the traceback on a different
1876 1874 place, like a side channel.
1877 1875 """
1878 1876 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1879 1877
1880 1878 def showsyntaxerror(self, filename=None):
1881 1879 """Display the syntax error that just occurred.
1882 1880
1883 1881 This doesn't display a stack trace because there isn't one.
1884 1882
1885 1883 If a filename is given, it is stuffed in the exception instead
1886 1884 of what was there before (because Python's parser always uses
1887 1885 "<string>" when reading from a string).
1888 1886 """
1889 1887 etype, value, last_traceback = self._get_exc_info()
1890 1888
1891 1889 if filename and issubclass(etype, SyntaxError):
1892 1890 try:
1893 1891 value.filename = filename
1894 1892 except:
1895 1893 # Not the format we expect; leave it alone
1896 1894 pass
1897 1895
1898 1896 stb = self.SyntaxTB.structured_traceback(etype, value, [])
1899 1897 self._showtraceback(etype, value, stb)
1900 1898
1901 1899 # This is overridden in TerminalInteractiveShell to show a message about
1902 1900 # the %paste magic.
1903 1901 def showindentationerror(self):
1904 1902 """Called by run_cell when there's an IndentationError in code entered
1905 1903 at the prompt.
1906 1904
1907 1905 This is overridden in TerminalInteractiveShell to show a message about
1908 1906 the %paste magic."""
1909 1907 self.showsyntaxerror()
1910 1908
1911 1909 #-------------------------------------------------------------------------
1912 1910 # Things related to readline
1913 1911 #-------------------------------------------------------------------------
1914 1912
1915 1913 def init_readline(self):
1916 1914 """Command history completion/saving/reloading."""
1917 1915
1918 1916 if self.readline_use:
1919 1917 import IPython.utils.rlineimpl as readline
1920 1918
1921 1919 self.rl_next_input = None
1922 1920 self.rl_do_indent = False
1923 1921
1924 1922 if not self.readline_use or not readline.have_readline:
1925 1923 self.has_readline = False
1926 1924 self.readline = None
1927 1925 # Set a number of methods that depend on readline to be no-op
1928 1926 self.readline_no_record = no_op_context
1929 1927 self.set_readline_completer = no_op
1930 1928 self.set_custom_completer = no_op
1931 1929 if self.readline_use:
1932 1930 warn('Readline services not available or not loaded.')
1933 1931 else:
1934 1932 self.has_readline = True
1935 1933 self.readline = readline
1936 1934 sys.modules['readline'] = readline
1937 1935
1938 1936 # Platform-specific configuration
1939 1937 if os.name == 'nt':
1940 1938 # FIXME - check with Frederick to see if we can harmonize
1941 1939 # naming conventions with pyreadline to avoid this
1942 1940 # platform-dependent check
1943 1941 self.readline_startup_hook = readline.set_pre_input_hook
1944 1942 else:
1945 1943 self.readline_startup_hook = readline.set_startup_hook
1946 1944
1947 1945 # Readline config order:
1948 1946 # - IPython config (default value)
1949 1947 # - custom inputrc
1950 1948 # - IPython config (user customized)
1951 1949
1952 1950 # load IPython config before inputrc if default
1953 1951 # skip if libedit because parse_and_bind syntax is different
1954 1952 if not self._custom_readline_config and not readline.uses_libedit:
1955 1953 for rlcommand in self.readline_parse_and_bind:
1956 1954 readline.parse_and_bind(rlcommand)
1957 1955
1958 1956 # Load user's initrc file (readline config)
1959 1957 # Or if libedit is used, load editrc.
1960 1958 inputrc_name = os.environ.get('INPUTRC')
1961 1959 if inputrc_name is None:
1962 1960 inputrc_name = '.inputrc'
1963 1961 if readline.uses_libedit:
1964 1962 inputrc_name = '.editrc'
1965 1963 inputrc_name = os.path.join(self.home_dir, inputrc_name)
1966 1964 if os.path.isfile(inputrc_name):
1967 1965 try:
1968 1966 readline.read_init_file(inputrc_name)
1969 1967 except:
1970 1968 warn('Problems reading readline initialization file <%s>'
1971 1969 % inputrc_name)
1972 1970
1973 1971 # load IPython config after inputrc if user has customized
1974 1972 if self._custom_readline_config:
1975 1973 for rlcommand in self.readline_parse_and_bind:
1976 1974 readline.parse_and_bind(rlcommand)
1977 1975
1978 1976 # Remove some chars from the delimiters list. If we encounter
1979 1977 # unicode chars, discard them.
1980 1978 delims = readline.get_completer_delims()
1981 1979 if not py3compat.PY3:
1982 1980 delims = delims.encode("ascii", "ignore")
1983 1981 for d in self.readline_remove_delims:
1984 1982 delims = delims.replace(d, "")
1985 1983 delims = delims.replace(ESC_MAGIC, '')
1986 1984 readline.set_completer_delims(delims)
1987 1985 # Store these so we can restore them if something like rpy2 modifies
1988 1986 # them.
1989 1987 self.readline_delims = delims
1990 1988 # otherwise we end up with a monster history after a while:
1991 1989 readline.set_history_length(self.history_length)
1992 1990
1993 1991 self.refill_readline_hist()
1994 1992 self.readline_no_record = ReadlineNoRecord(self)
1995 1993
1996 1994 # Configure auto-indent for all platforms
1997 1995 self.set_autoindent(self.autoindent)
1998 1996
1999 1997 def refill_readline_hist(self):
2000 1998 # Load the last 1000 lines from history
2001 1999 self.readline.clear_history()
2002 2000 stdin_encoding = sys.stdin.encoding or "utf-8"
2003 2001 last_cell = u""
2004 2002 for _, _, cell in self.history_manager.get_tail(1000,
2005 2003 include_latest=True):
2006 2004 # Ignore blank lines and consecutive duplicates
2007 2005 cell = cell.rstrip()
2008 2006 if cell and (cell != last_cell):
2009 2007 try:
2010 2008 if self.multiline_history:
2011 2009 self.readline.add_history(py3compat.unicode_to_str(cell,
2012 2010 stdin_encoding))
2013 2011 else:
2014 2012 for line in cell.splitlines():
2015 2013 self.readline.add_history(py3compat.unicode_to_str(line,
2016 2014 stdin_encoding))
2017 2015 last_cell = cell
2018 2016
2019 2017 except TypeError:
2020 2018 # The history DB can get corrupted so it returns strings
2021 2019 # containing null bytes, which readline objects to.
2022 2020 continue
2023 2021
2024 2022 @skip_doctest
2025 2023 def set_next_input(self, s, replace=False):
2026 2024 """ Sets the 'default' input string for the next command line.
2027 2025
2028 2026 Requires readline.
2029 2027
2030 2028 Example::
2031 2029
2032 2030 In [1]: _ip.set_next_input("Hello Word")
2033 2031 In [2]: Hello Word_ # cursor is here
2034 2032 """
2035 2033 self.rl_next_input = py3compat.cast_bytes_py2(s)
2036 2034
2037 2035 # Maybe move this to the terminal subclass?
2038 2036 def pre_readline(self):
2039 2037 """readline hook to be used at the start of each line.
2040 2038
2041 2039 Currently it handles auto-indent only."""
2042 2040
2043 2041 if self.rl_do_indent:
2044 2042 self.readline.insert_text(self._indent_current_str())
2045 2043 if self.rl_next_input is not None:
2046 2044 self.readline.insert_text(self.rl_next_input)
2047 2045 self.rl_next_input = None
2048 2046
2049 2047 def _indent_current_str(self):
2050 2048 """return the current level of indentation as a string"""
2051 2049 return self.input_splitter.indent_spaces * ' '
2052 2050
2053 2051 #-------------------------------------------------------------------------
2054 2052 # Things related to text completion
2055 2053 #-------------------------------------------------------------------------
2056 2054
2057 2055 def init_completer(self):
2058 2056 """Initialize the completion machinery.
2059 2057
2060 2058 This creates completion machinery that can be used by client code,
2061 2059 either interactively in-process (typically triggered by the readline
2062 2060 library), programatically (such as in test suites) or out-of-prcess
2063 2061 (typically over the network by remote frontends).
2064 2062 """
2065 2063 from IPython.core.completer import IPCompleter
2066 2064 from IPython.core.completerlib import (module_completer,
2067 2065 magic_run_completer, cd_completer, reset_completer)
2068 2066
2069 2067 self.Completer = IPCompleter(shell=self,
2070 2068 namespace=self.user_ns,
2071 2069 global_namespace=self.user_global_ns,
2072 2070 use_readline=self.has_readline,
2073 2071 parent=self,
2074 2072 )
2075 2073 self.configurables.append(self.Completer)
2076 2074
2077 2075 # Add custom completers to the basic ones built into IPCompleter
2078 2076 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
2079 2077 self.strdispatchers['complete_command'] = sdisp
2080 2078 self.Completer.custom_completers = sdisp
2081 2079
2082 2080 self.set_hook('complete_command', module_completer, str_key = 'import')
2083 2081 self.set_hook('complete_command', module_completer, str_key = 'from')
2084 2082 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
2085 2083 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2086 2084 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2087 2085
2088 2086 # Only configure readline if we truly are using readline. IPython can
2089 2087 # do tab-completion over the network, in GUIs, etc, where readline
2090 2088 # itself may be absent
2091 2089 if self.has_readline:
2092 2090 self.set_readline_completer()
2093 2091
2094 2092 def complete(self, text, line=None, cursor_pos=None):
2095 2093 """Return the completed text and a list of completions.
2096 2094
2097 2095 Parameters
2098 2096 ----------
2099 2097
2100 2098 text : string
2101 2099 A string of text to be completed on. It can be given as empty and
2102 2100 instead a line/position pair are given. In this case, the
2103 2101 completer itself will split the line like readline does.
2104 2102
2105 2103 line : string, optional
2106 2104 The complete line that text is part of.
2107 2105
2108 2106 cursor_pos : int, optional
2109 2107 The position of the cursor on the input line.
2110 2108
2111 2109 Returns
2112 2110 -------
2113 2111 text : string
2114 2112 The actual text that was completed.
2115 2113
2116 2114 matches : list
2117 2115 A sorted list with all possible completions.
2118 2116
2119 2117 The optional arguments allow the completion to take more context into
2120 2118 account, and are part of the low-level completion API.
2121 2119
2122 2120 This is a wrapper around the completion mechanism, similar to what
2123 2121 readline does at the command line when the TAB key is hit. By
2124 2122 exposing it as a method, it can be used by other non-readline
2125 2123 environments (such as GUIs) for text completion.
2126 2124
2127 2125 Simple usage example:
2128 2126
2129 2127 In [1]: x = 'hello'
2130 2128
2131 2129 In [2]: _ip.complete('x.l')
2132 2130 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
2133 2131 """
2134 2132
2135 2133 # Inject names into __builtin__ so we can complete on the added names.
2136 2134 with self.builtin_trap:
2137 2135 return self.Completer.complete(text, line, cursor_pos)
2138 2136
2139 2137 def set_custom_completer(self, completer, pos=0):
2140 2138 """Adds a new custom completer function.
2141 2139
2142 2140 The position argument (defaults to 0) is the index in the completers
2143 2141 list where you want the completer to be inserted."""
2144 2142
2145 2143 newcomp = types.MethodType(completer,self.Completer)
2146 2144 self.Completer.matchers.insert(pos,newcomp)
2147 2145
2148 2146 def set_readline_completer(self):
2149 2147 """Reset readline's completer to be our own."""
2150 2148 self.readline.set_completer(self.Completer.rlcomplete)
2151 2149
2152 2150 def set_completer_frame(self, frame=None):
2153 2151 """Set the frame of the completer."""
2154 2152 if frame:
2155 2153 self.Completer.namespace = frame.f_locals
2156 2154 self.Completer.global_namespace = frame.f_globals
2157 2155 else:
2158 2156 self.Completer.namespace = self.user_ns
2159 2157 self.Completer.global_namespace = self.user_global_ns
2160 2158
2161 2159 #-------------------------------------------------------------------------
2162 2160 # Things related to magics
2163 2161 #-------------------------------------------------------------------------
2164 2162
2165 2163 def init_magics(self):
2166 2164 from IPython.core import magics as m
2167 2165 self.magics_manager = magic.MagicsManager(shell=self,
2168 2166 parent=self,
2169 2167 user_magics=m.UserMagics(self))
2170 2168 self.configurables.append(self.magics_manager)
2171 2169
2172 2170 # Expose as public API from the magics manager
2173 2171 self.register_magics = self.magics_manager.register
2174 2172 self.define_magic = self.magics_manager.define_magic
2175 2173
2176 2174 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2177 2175 m.ConfigMagics, m.DeprecatedMagics, m.DisplayMagics, m.ExecutionMagics,
2178 2176 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2179 2177 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2180 2178 )
2181 2179
2182 2180 # Register Magic Aliases
2183 2181 mman = self.magics_manager
2184 2182 # FIXME: magic aliases should be defined by the Magics classes
2185 2183 # or in MagicsManager, not here
2186 2184 mman.register_alias('ed', 'edit')
2187 2185 mman.register_alias('hist', 'history')
2188 2186 mman.register_alias('rep', 'recall')
2189 2187 mman.register_alias('SVG', 'svg', 'cell')
2190 2188 mman.register_alias('HTML', 'html', 'cell')
2191 2189 mman.register_alias('file', 'writefile', 'cell')
2192 2190
2193 2191 # FIXME: Move the color initialization to the DisplayHook, which
2194 2192 # should be split into a prompt manager and displayhook. We probably
2195 2193 # even need a centralize colors management object.
2196 2194 self.magic('colors %s' % self.colors)
2197 2195
2198 2196 # Defined here so that it's included in the documentation
2199 2197 @functools.wraps(magic.MagicsManager.register_function)
2200 2198 def register_magic_function(self, func, magic_kind='line', magic_name=None):
2201 2199 self.magics_manager.register_function(func,
2202 2200 magic_kind=magic_kind, magic_name=magic_name)
2203 2201
2204 2202 def run_line_magic(self, magic_name, line):
2205 2203 """Execute the given line magic.
2206 2204
2207 2205 Parameters
2208 2206 ----------
2209 2207 magic_name : str
2210 2208 Name of the desired magic function, without '%' prefix.
2211 2209
2212 2210 line : str
2213 2211 The rest of the input line as a single string.
2214 2212 """
2215 2213 fn = self.find_line_magic(magic_name)
2216 2214 if fn is None:
2217 2215 cm = self.find_cell_magic(magic_name)
2218 2216 etpl = "Line magic function `%%%s` not found%s."
2219 2217 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2220 2218 'did you mean that instead?)' % magic_name )
2221 2219 error(etpl % (magic_name, extra))
2222 2220 else:
2223 2221 # Note: this is the distance in the stack to the user's frame.
2224 2222 # This will need to be updated if the internal calling logic gets
2225 2223 # refactored, or else we'll be expanding the wrong variables.
2226 2224 stack_depth = 2
2227 2225 magic_arg_s = self.var_expand(line, stack_depth)
2228 2226 # Put magic args in a list so we can call with f(*a) syntax
2229 2227 args = [magic_arg_s]
2230 2228 kwargs = {}
2231 2229 # Grab local namespace if we need it:
2232 2230 if getattr(fn, "needs_local_scope", False):
2233 2231 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
2234 2232 with self.builtin_trap:
2235 2233 result = fn(*args,**kwargs)
2236 2234 return result
2237 2235
2238 2236 def run_cell_magic(self, magic_name, line, cell):
2239 2237 """Execute the given cell magic.
2240 2238
2241 2239 Parameters
2242 2240 ----------
2243 2241 magic_name : str
2244 2242 Name of the desired magic function, without '%' prefix.
2245 2243
2246 2244 line : str
2247 2245 The rest of the first input line as a single string.
2248 2246
2249 2247 cell : str
2250 2248 The body of the cell as a (possibly multiline) string.
2251 2249 """
2252 2250 fn = self.find_cell_magic(magic_name)
2253 2251 if fn is None:
2254 2252 lm = self.find_line_magic(magic_name)
2255 2253 etpl = "Cell magic `%%{0}` not found{1}."
2256 2254 extra = '' if lm is None else (' (But line magic `%{0}` exists, '
2257 2255 'did you mean that instead?)'.format(magic_name))
2258 2256 error(etpl.format(magic_name, extra))
2259 2257 elif cell == '':
2260 2258 message = '%%{0} is a cell magic, but the cell body is empty.'.format(magic_name)
2261 2259 if self.find_line_magic(magic_name) is not None:
2262 2260 message += ' Did you mean the line magic %{0} (single %)?'.format(magic_name)
2263 2261 raise UsageError(message)
2264 2262 else:
2265 2263 # Note: this is the distance in the stack to the user's frame.
2266 2264 # This will need to be updated if the internal calling logic gets
2267 2265 # refactored, or else we'll be expanding the wrong variables.
2268 2266 stack_depth = 2
2269 2267 magic_arg_s = self.var_expand(line, stack_depth)
2270 2268 with self.builtin_trap:
2271 2269 result = fn(magic_arg_s, cell)
2272 2270 return result
2273 2271
2274 2272 def find_line_magic(self, magic_name):
2275 2273 """Find and return a line magic by name.
2276 2274
2277 2275 Returns None if the magic isn't found."""
2278 2276 return self.magics_manager.magics['line'].get(magic_name)
2279 2277
2280 2278 def find_cell_magic(self, magic_name):
2281 2279 """Find and return a cell magic by name.
2282 2280
2283 2281 Returns None if the magic isn't found."""
2284 2282 return self.magics_manager.magics['cell'].get(magic_name)
2285 2283
2286 2284 def find_magic(self, magic_name, magic_kind='line'):
2287 2285 """Find and return a magic of the given type by name.
2288 2286
2289 2287 Returns None if the magic isn't found."""
2290 2288 return self.magics_manager.magics[magic_kind].get(magic_name)
2291 2289
2292 2290 def magic(self, arg_s):
2293 2291 """DEPRECATED. Use run_line_magic() instead.
2294 2292
2295 2293 Call a magic function by name.
2296 2294
2297 2295 Input: a string containing the name of the magic function to call and
2298 2296 any additional arguments to be passed to the magic.
2299 2297
2300 2298 magic('name -opt foo bar') is equivalent to typing at the ipython
2301 2299 prompt:
2302 2300
2303 2301 In[1]: %name -opt foo bar
2304 2302
2305 2303 To call a magic without arguments, simply use magic('name').
2306 2304
2307 2305 This provides a proper Python function to call IPython's magics in any
2308 2306 valid Python code you can type at the interpreter, including loops and
2309 2307 compound statements.
2310 2308 """
2311 2309 # TODO: should we issue a loud deprecation warning here?
2312 2310 magic_name, _, magic_arg_s = arg_s.partition(' ')
2313 2311 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2314 2312 return self.run_line_magic(magic_name, magic_arg_s)
2315 2313
2316 2314 #-------------------------------------------------------------------------
2317 2315 # Things related to macros
2318 2316 #-------------------------------------------------------------------------
2319 2317
2320 2318 def define_macro(self, name, themacro):
2321 2319 """Define a new macro
2322 2320
2323 2321 Parameters
2324 2322 ----------
2325 2323 name : str
2326 2324 The name of the macro.
2327 2325 themacro : str or Macro
2328 2326 The action to do upon invoking the macro. If a string, a new
2329 2327 Macro object is created by passing the string to it.
2330 2328 """
2331 2329
2332 2330 from IPython.core import macro
2333 2331
2334 2332 if isinstance(themacro, string_types):
2335 2333 themacro = macro.Macro(themacro)
2336 2334 if not isinstance(themacro, macro.Macro):
2337 2335 raise ValueError('A macro must be a string or a Macro instance.')
2338 2336 self.user_ns[name] = themacro
2339 2337
2340 2338 #-------------------------------------------------------------------------
2341 2339 # Things related to the running of system commands
2342 2340 #-------------------------------------------------------------------------
2343 2341
2344 2342 def system_piped(self, cmd):
2345 2343 """Call the given cmd in a subprocess, piping stdout/err
2346 2344
2347 2345 Parameters
2348 2346 ----------
2349 2347 cmd : str
2350 2348 Command to execute (can not end in '&', as background processes are
2351 2349 not supported. Should not be a command that expects input
2352 2350 other than simple text.
2353 2351 """
2354 2352 if cmd.rstrip().endswith('&'):
2355 2353 # this is *far* from a rigorous test
2356 2354 # We do not support backgrounding processes because we either use
2357 2355 # pexpect or pipes to read from. Users can always just call
2358 2356 # os.system() or use ip.system=ip.system_raw
2359 2357 # if they really want a background process.
2360 2358 raise OSError("Background processes not supported.")
2361 2359
2362 2360 # we explicitly do NOT return the subprocess status code, because
2363 2361 # a non-None value would trigger :func:`sys.displayhook` calls.
2364 2362 # Instead, we store the exit_code in user_ns.
2365 2363 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2366 2364
2367 2365 def system_raw(self, cmd):
2368 2366 """Call the given cmd in a subprocess using os.system on Windows or
2369 2367 subprocess.call using the system shell on other platforms.
2370 2368
2371 2369 Parameters
2372 2370 ----------
2373 2371 cmd : str
2374 2372 Command to execute.
2375 2373 """
2376 2374 cmd = self.var_expand(cmd, depth=1)
2377 2375 # protect os.system from UNC paths on Windows, which it can't handle:
2378 2376 if sys.platform == 'win32':
2379 2377 from IPython.utils._process_win32 import AvoidUNCPath
2380 2378 with AvoidUNCPath() as path:
2381 2379 if path is not None:
2382 2380 cmd = '"pushd %s &&"%s' % (path, cmd)
2383 2381 cmd = py3compat.unicode_to_str(cmd)
2384 2382 try:
2385 2383 ec = os.system(cmd)
2386 2384 except KeyboardInterrupt:
2387 2385 self.write_err('\n' + self.get_exception_only())
2388 2386 ec = -2
2389 2387 else:
2390 2388 cmd = py3compat.unicode_to_str(cmd)
2391 2389 # For posix the result of the subprocess.call() below is an exit
2392 2390 # code, which by convention is zero for success, positive for
2393 2391 # program failure. Exit codes above 128 are reserved for signals,
2394 2392 # and the formula for converting a signal to an exit code is usually
2395 2393 # signal_number+128. To more easily differentiate between exit
2396 2394 # codes and signals, ipython uses negative numbers. For instance
2397 2395 # since control-c is signal 2 but exit code 130, ipython's
2398 2396 # _exit_code variable will read -2. Note that some shells like
2399 2397 # csh and fish don't follow sh/bash conventions for exit codes.
2400 2398 executable = os.environ.get('SHELL', None)
2401 2399 try:
2402 2400 # Use env shell instead of default /bin/sh
2403 2401 ec = subprocess.call(cmd, shell=True, executable=executable)
2404 2402 except KeyboardInterrupt:
2405 2403 # intercept control-C; a long traceback is not useful here
2406 2404 self.write_err('\n' + self.get_exception_only())
2407 2405 ec = 130
2408 2406 if ec > 128:
2409 2407 ec = -(ec - 128)
2410 2408
2411 2409 # We explicitly do NOT return the subprocess status code, because
2412 2410 # a non-None value would trigger :func:`sys.displayhook` calls.
2413 2411 # Instead, we store the exit_code in user_ns. Note the semantics
2414 2412 # of _exit_code: for control-c, _exit_code == -signal.SIGNIT,
2415 2413 # but raising SystemExit(_exit_code) will give status 254!
2416 2414 self.user_ns['_exit_code'] = ec
2417 2415
2418 2416 # use piped system by default, because it is better behaved
2419 2417 system = system_piped
2420 2418
2421 2419 def getoutput(self, cmd, split=True, depth=0):
2422 2420 """Get output (possibly including stderr) from a subprocess.
2423 2421
2424 2422 Parameters
2425 2423 ----------
2426 2424 cmd : str
2427 2425 Command to execute (can not end in '&', as background processes are
2428 2426 not supported.
2429 2427 split : bool, optional
2430 2428 If True, split the output into an IPython SList. Otherwise, an
2431 2429 IPython LSString is returned. These are objects similar to normal
2432 2430 lists and strings, with a few convenience attributes for easier
2433 2431 manipulation of line-based output. You can use '?' on them for
2434 2432 details.
2435 2433 depth : int, optional
2436 2434 How many frames above the caller are the local variables which should
2437 2435 be expanded in the command string? The default (0) assumes that the
2438 2436 expansion variables are in the stack frame calling this function.
2439 2437 """
2440 2438 if cmd.rstrip().endswith('&'):
2441 2439 # this is *far* from a rigorous test
2442 2440 raise OSError("Background processes not supported.")
2443 2441 out = getoutput(self.var_expand(cmd, depth=depth+1))
2444 2442 if split:
2445 2443 out = SList(out.splitlines())
2446 2444 else:
2447 2445 out = LSString(out)
2448 2446 return out
2449 2447
2450 2448 #-------------------------------------------------------------------------
2451 2449 # Things related to aliases
2452 2450 #-------------------------------------------------------------------------
2453 2451
2454 2452 def init_alias(self):
2455 2453 self.alias_manager = AliasManager(shell=self, parent=self)
2456 2454 self.configurables.append(self.alias_manager)
2457 2455
2458 2456 #-------------------------------------------------------------------------
2459 2457 # Things related to extensions
2460 2458 #-------------------------------------------------------------------------
2461 2459
2462 2460 def init_extension_manager(self):
2463 2461 self.extension_manager = ExtensionManager(shell=self, parent=self)
2464 2462 self.configurables.append(self.extension_manager)
2465 2463
2466 2464 #-------------------------------------------------------------------------
2467 2465 # Things related to payloads
2468 2466 #-------------------------------------------------------------------------
2469 2467
2470 2468 def init_payload(self):
2471 2469 self.payload_manager = PayloadManager(parent=self)
2472 2470 self.configurables.append(self.payload_manager)
2473 2471
2474 2472 #-------------------------------------------------------------------------
2475 2473 # Things related to the prefilter
2476 2474 #-------------------------------------------------------------------------
2477 2475
2478 2476 def init_prefilter(self):
2479 2477 self.prefilter_manager = PrefilterManager(shell=self, parent=self)
2480 2478 self.configurables.append(self.prefilter_manager)
2481 2479 # Ultimately this will be refactored in the new interpreter code, but
2482 2480 # for now, we should expose the main prefilter method (there's legacy
2483 2481 # code out there that may rely on this).
2484 2482 self.prefilter = self.prefilter_manager.prefilter_lines
2485 2483
2486 2484 def auto_rewrite_input(self, cmd):
2487 2485 """Print to the screen the rewritten form of the user's command.
2488 2486
2489 2487 This shows visual feedback by rewriting input lines that cause
2490 2488 automatic calling to kick in, like::
2491 2489
2492 2490 /f x
2493 2491
2494 2492 into::
2495 2493
2496 2494 ------> f(x)
2497 2495
2498 2496 after the user's input prompt. This helps the user understand that the
2499 2497 input line was transformed automatically by IPython.
2500 2498 """
2501 2499 if not self.show_rewritten_input:
2502 2500 return
2503 2501
2504 2502 rw = self.prompt_manager.render('rewrite') + cmd
2505 2503
2506 2504 try:
2507 2505 # plain ascii works better w/ pyreadline, on some machines, so
2508 2506 # we use it and only print uncolored rewrite if we have unicode
2509 2507 rw = str(rw)
2510 2508 print(rw, file=io.stdout)
2511 2509 except UnicodeEncodeError:
2512 2510 print("------> " + cmd)
2513 2511
2514 2512 #-------------------------------------------------------------------------
2515 2513 # Things related to extracting values/expressions from kernel and user_ns
2516 2514 #-------------------------------------------------------------------------
2517 2515
2518 2516 def _user_obj_error(self):
2519 2517 """return simple exception dict
2520 2518
2521 2519 for use in user_expressions
2522 2520 """
2523 2521
2524 2522 etype, evalue, tb = self._get_exc_info()
2525 2523 stb = self.InteractiveTB.get_exception_only(etype, evalue)
2526 2524
2527 2525 exc_info = {
2528 2526 u'status' : 'error',
2529 2527 u'traceback' : stb,
2530 2528 u'ename' : unicode_type(etype.__name__),
2531 2529 u'evalue' : py3compat.safe_unicode(evalue),
2532 2530 }
2533 2531
2534 2532 return exc_info
2535 2533
2536 2534 def _format_user_obj(self, obj):
2537 2535 """format a user object to display dict
2538 2536
2539 2537 for use in user_expressions
2540 2538 """
2541 2539
2542 2540 data, md = self.display_formatter.format(obj)
2543 2541 value = {
2544 2542 'status' : 'ok',
2545 2543 'data' : data,
2546 2544 'metadata' : md,
2547 2545 }
2548 2546 return value
2549 2547
2550 2548 def user_expressions(self, expressions):
2551 2549 """Evaluate a dict of expressions in the user's namespace.
2552 2550
2553 2551 Parameters
2554 2552 ----------
2555 2553 expressions : dict
2556 2554 A dict with string keys and string values. The expression values
2557 2555 should be valid Python expressions, each of which will be evaluated
2558 2556 in the user namespace.
2559 2557
2560 2558 Returns
2561 2559 -------
2562 2560 A dict, keyed like the input expressions dict, with the rich mime-typed
2563 2561 display_data of each value.
2564 2562 """
2565 2563 out = {}
2566 2564 user_ns = self.user_ns
2567 2565 global_ns = self.user_global_ns
2568 2566
2569 2567 for key, expr in iteritems(expressions):
2570 2568 try:
2571 2569 value = self._format_user_obj(eval(expr, global_ns, user_ns))
2572 2570 except:
2573 2571 value = self._user_obj_error()
2574 2572 out[key] = value
2575 2573 return out
2576 2574
2577 2575 #-------------------------------------------------------------------------
2578 2576 # Things related to the running of code
2579 2577 #-------------------------------------------------------------------------
2580 2578
2581 2579 def ex(self, cmd):
2582 2580 """Execute a normal python statement in user namespace."""
2583 2581 with self.builtin_trap:
2584 2582 exec(cmd, self.user_global_ns, self.user_ns)
2585 2583
2586 2584 def ev(self, expr):
2587 2585 """Evaluate python expression expr in user namespace.
2588 2586
2589 2587 Returns the result of evaluation
2590 2588 """
2591 2589 with self.builtin_trap:
2592 2590 return eval(expr, self.user_global_ns, self.user_ns)
2593 2591
2594 2592 def safe_execfile(self, fname, *where, **kw):
2595 2593 """A safe version of the builtin execfile().
2596 2594
2597 2595 This version will never throw an exception, but instead print
2598 2596 helpful error messages to the screen. This only works on pure
2599 2597 Python files with the .py extension.
2600 2598
2601 2599 Parameters
2602 2600 ----------
2603 2601 fname : string
2604 2602 The name of the file to be executed.
2605 2603 where : tuple
2606 2604 One or two namespaces, passed to execfile() as (globals,locals).
2607 2605 If only one is given, it is passed as both.
2608 2606 exit_ignore : bool (False)
2609 2607 If True, then silence SystemExit for non-zero status (it is always
2610 2608 silenced for zero status, as it is so common).
2611 2609 raise_exceptions : bool (False)
2612 2610 If True raise exceptions everywhere. Meant for testing.
2613 2611 shell_futures : bool (False)
2614 2612 If True, the code will share future statements with the interactive
2615 2613 shell. It will both be affected by previous __future__ imports, and
2616 2614 any __future__ imports in the code will affect the shell. If False,
2617 2615 __future__ imports are not shared in either direction.
2618 2616
2619 2617 """
2620 2618 kw.setdefault('exit_ignore', False)
2621 2619 kw.setdefault('raise_exceptions', False)
2622 2620 kw.setdefault('shell_futures', False)
2623 2621
2624 2622 fname = os.path.abspath(os.path.expanduser(fname))
2625 2623
2626 2624 # Make sure we can open the file
2627 2625 try:
2628 2626 with open(fname) as thefile:
2629 2627 pass
2630 2628 except:
2631 2629 warn('Could not open file <%s> for safe execution.' % fname)
2632 2630 return
2633 2631
2634 2632 # Find things also in current directory. This is needed to mimic the
2635 2633 # behavior of running a script from the system command line, where
2636 2634 # Python inserts the script's directory into sys.path
2637 2635 dname = os.path.dirname(fname)
2638 2636
2639 2637 with prepended_to_syspath(dname):
2640 2638 try:
2641 2639 glob, loc = (where + (None, ))[:2]
2642 2640 py3compat.execfile(
2643 2641 fname, glob, loc,
2644 2642 self.compile if kw['shell_futures'] else None)
2645 2643 except SystemExit as status:
2646 2644 # If the call was made with 0 or None exit status (sys.exit(0)
2647 2645 # or sys.exit() ), don't bother showing a traceback, as both of
2648 2646 # these are considered normal by the OS:
2649 2647 # > python -c'import sys;sys.exit(0)'; echo $?
2650 2648 # 0
2651 2649 # > python -c'import sys;sys.exit()'; echo $?
2652 2650 # 0
2653 2651 # For other exit status, we show the exception unless
2654 2652 # explicitly silenced, but only in short form.
2655 2653 if kw['raise_exceptions']:
2656 2654 raise
2657 2655 if status.code and not kw['exit_ignore']:
2658 2656 self.showtraceback(exception_only=True)
2659 2657 except:
2660 2658 if kw['raise_exceptions']:
2661 2659 raise
2662 2660 # tb offset is 2 because we wrap execfile
2663 2661 self.showtraceback(tb_offset=2)
2664 2662
2665 2663 def safe_execfile_ipy(self, fname, shell_futures=False):
2666 2664 """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
2667 2665
2668 2666 Parameters
2669 2667 ----------
2670 2668 fname : str
2671 2669 The name of the file to execute. The filename must have a
2672 2670 .ipy or .ipynb extension.
2673 2671 shell_futures : bool (False)
2674 2672 If True, the code will share future statements with the interactive
2675 2673 shell. It will both be affected by previous __future__ imports, and
2676 2674 any __future__ imports in the code will affect the shell. If False,
2677 2675 __future__ imports are not shared in either direction.
2678 2676 """
2679 2677 fname = os.path.abspath(os.path.expanduser(fname))
2680 2678
2681 2679 # Make sure we can open the file
2682 2680 try:
2683 2681 with open(fname) as thefile:
2684 2682 pass
2685 2683 except:
2686 2684 warn('Could not open file <%s> for safe execution.' % fname)
2687 2685 return
2688 2686
2689 2687 # Find things also in current directory. This is needed to mimic the
2690 2688 # behavior of running a script from the system command line, where
2691 2689 # Python inserts the script's directory into sys.path
2692 2690 dname = os.path.dirname(fname)
2693 2691
2694 2692 def get_cells():
2695 2693 """generator for sequence of code blocks to run"""
2696 2694 if fname.endswith('.ipynb'):
2697 2695 from IPython.nbformat import read
2698 2696 with io_open(fname) as f:
2699 2697 nb = read(f, as_version=4)
2700 2698 if not nb.cells:
2701 2699 return
2702 2700 for cell in nb.cells:
2703 2701 if cell.cell_type == 'code':
2704 2702 yield cell.source
2705 2703 else:
2706 2704 with open(fname) as f:
2707 2705 yield f.read()
2708 2706
2709 2707 with prepended_to_syspath(dname):
2710 2708 try:
2711 2709 for cell in get_cells():
2712 2710 # self.run_cell currently captures all exceptions
2713 2711 # raised in user code. It would be nice if there were
2714 2712 # versions of run_cell that did raise, so
2715 2713 # we could catch the errors.
2716 2714 self.run_cell(cell, silent=True, shell_futures=shell_futures)
2717 2715 except:
2718 2716 self.showtraceback()
2719 2717 warn('Unknown failure executing file: <%s>' % fname)
2720 2718
2721 2719 def safe_run_module(self, mod_name, where):
2722 2720 """A safe version of runpy.run_module().
2723 2721
2724 2722 This version will never throw an exception, but instead print
2725 2723 helpful error messages to the screen.
2726 2724
2727 2725 `SystemExit` exceptions with status code 0 or None are ignored.
2728 2726
2729 2727 Parameters
2730 2728 ----------
2731 2729 mod_name : string
2732 2730 The name of the module to be executed.
2733 2731 where : dict
2734 2732 The globals namespace.
2735 2733 """
2736 2734 try:
2737 2735 try:
2738 2736 where.update(
2739 2737 runpy.run_module(str(mod_name), run_name="__main__",
2740 2738 alter_sys=True)
2741 2739 )
2742 2740 except SystemExit as status:
2743 2741 if status.code:
2744 2742 raise
2745 2743 except:
2746 2744 self.showtraceback()
2747 2745 warn('Unknown failure executing module: <%s>' % mod_name)
2748 2746
2749 2747 def _run_cached_cell_magic(self, magic_name, line):
2750 2748 """Special method to call a cell magic with the data stored in self.
2751 2749 """
2752 2750 cell = self._current_cell_magic_body
2753 2751 self._current_cell_magic_body = None
2754 2752 return self.run_cell_magic(magic_name, line, cell)
2755 2753
2756 2754 def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True):
2757 2755 """Run a complete IPython cell.
2758 2756
2759 2757 Parameters
2760 2758 ----------
2761 2759 raw_cell : str
2762 2760 The code (including IPython code such as %magic functions) to run.
2763 2761 store_history : bool
2764 2762 If True, the raw and translated cell will be stored in IPython's
2765 2763 history. For user code calling back into IPython's machinery, this
2766 2764 should be set to False.
2767 2765 silent : bool
2768 2766 If True, avoid side-effects, such as implicit displayhooks and
2769 2767 and logging. silent=True forces store_history=False.
2770 2768 shell_futures : bool
2771 2769 If True, the code will share future statements with the interactive
2772 2770 shell. It will both be affected by previous __future__ imports, and
2773 2771 any __future__ imports in the code will affect the shell. If False,
2774 2772 __future__ imports are not shared in either direction.
2775 2773
2776 2774 Returns
2777 2775 -------
2778 2776 result : :class:`ExecutionResult`
2779 2777 """
2780 2778 result = ExecutionResult()
2781 2779
2782 2780 if (not raw_cell) or raw_cell.isspace():
2783 2781 return result
2784 2782
2785 2783 if silent:
2786 2784 store_history = False
2787 2785
2788 2786 if store_history:
2789 2787 result.execution_count = self.execution_count
2790 2788
2791 2789 def error_before_exec(value):
2792 2790 result.error_before_exec = value
2793 2791 return result
2794 2792
2795 2793 self.events.trigger('pre_execute')
2796 2794 if not silent:
2797 2795 self.events.trigger('pre_run_cell')
2798 2796
2799 2797 # If any of our input transformation (input_transformer_manager or
2800 2798 # prefilter_manager) raises an exception, we store it in this variable
2801 2799 # so that we can display the error after logging the input and storing
2802 2800 # it in the history.
2803 2801 preprocessing_exc_tuple = None
2804 2802 try:
2805 2803 # Static input transformations
2806 2804 cell = self.input_transformer_manager.transform_cell(raw_cell)
2807 2805 except SyntaxError:
2808 2806 preprocessing_exc_tuple = sys.exc_info()
2809 2807 cell = raw_cell # cell has to exist so it can be stored/logged
2810 2808 else:
2811 2809 if len(cell.splitlines()) == 1:
2812 2810 # Dynamic transformations - only applied for single line commands
2813 2811 with self.builtin_trap:
2814 2812 try:
2815 2813 # use prefilter_lines to handle trailing newlines
2816 2814 # restore trailing newline for ast.parse
2817 2815 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
2818 2816 except Exception:
2819 2817 # don't allow prefilter errors to crash IPython
2820 2818 preprocessing_exc_tuple = sys.exc_info()
2821 2819
2822 2820 # Store raw and processed history
2823 2821 if store_history:
2824 2822 self.history_manager.store_inputs(self.execution_count,
2825 2823 cell, raw_cell)
2826 2824 if not silent:
2827 2825 self.logger.log(cell, raw_cell)
2828 2826
2829 2827 # Display the exception if input processing failed.
2830 2828 if preprocessing_exc_tuple is not None:
2831 2829 self.showtraceback(preprocessing_exc_tuple)
2832 2830 if store_history:
2833 2831 self.execution_count += 1
2834 2832 return error_before_exec(preprocessing_exc_tuple[2])
2835 2833
2836 2834 # Our own compiler remembers the __future__ environment. If we want to
2837 2835 # run code with a separate __future__ environment, use the default
2838 2836 # compiler
2839 2837 compiler = self.compile if shell_futures else CachingCompiler()
2840 2838
2841 2839 with self.builtin_trap:
2842 2840 cell_name = self.compile.cache(cell, self.execution_count)
2843 2841
2844 2842 with self.display_trap:
2845 2843 # Compile to bytecode
2846 2844 try:
2847 2845 code_ast = compiler.ast_parse(cell, filename=cell_name)
2848 2846 except IndentationError as e:
2849 2847 self.showindentationerror()
2850 2848 if store_history:
2851 2849 self.execution_count += 1
2852 2850 return error_before_exec(e)
2853 2851 except (OverflowError, SyntaxError, ValueError, TypeError,
2854 2852 MemoryError) as e:
2855 2853 self.showsyntaxerror()
2856 2854 if store_history:
2857 2855 self.execution_count += 1
2858 2856 return error_before_exec(e)
2859 2857
2860 2858 # Apply AST transformations
2861 2859 try:
2862 2860 code_ast = self.transform_ast(code_ast)
2863 2861 except InputRejected as e:
2864 2862 self.showtraceback()
2865 2863 if store_history:
2866 2864 self.execution_count += 1
2867 2865 return error_before_exec(e)
2868 2866
2869 2867 # Give the displayhook a reference to our ExecutionResult so it
2870 2868 # can fill in the output value.
2871 2869 self.displayhook.exec_result = result
2872 2870
2873 2871 # Execute the user code
2874 2872 interactivity = "none" if silent else self.ast_node_interactivity
2875 2873 self.run_ast_nodes(code_ast.body, cell_name,
2876 2874 interactivity=interactivity, compiler=compiler, result=result)
2877 2875
2878 2876 # Reset this so later displayed values do not modify the
2879 2877 # ExecutionResult
2880 2878 self.displayhook.exec_result = None
2881 2879
2882 2880 self.events.trigger('post_execute')
2883 2881 if not silent:
2884 2882 self.events.trigger('post_run_cell')
2885 2883
2886 2884 if store_history:
2887 2885 # Write output to the database. Does nothing unless
2888 2886 # history output logging is enabled.
2889 2887 self.history_manager.store_output(self.execution_count)
2890 2888 # Each cell is a *single* input, regardless of how many lines it has
2891 2889 self.execution_count += 1
2892 2890
2893 2891 return result
2894 2892
2895 2893 def transform_ast(self, node):
2896 2894 """Apply the AST transformations from self.ast_transformers
2897 2895
2898 2896 Parameters
2899 2897 ----------
2900 2898 node : ast.Node
2901 2899 The root node to be transformed. Typically called with the ast.Module
2902 2900 produced by parsing user input.
2903 2901
2904 2902 Returns
2905 2903 -------
2906 2904 An ast.Node corresponding to the node it was called with. Note that it
2907 2905 may also modify the passed object, so don't rely on references to the
2908 2906 original AST.
2909 2907 """
2910 2908 for transformer in self.ast_transformers:
2911 2909 try:
2912 2910 node = transformer.visit(node)
2913 2911 except InputRejected:
2914 2912 # User-supplied AST transformers can reject an input by raising
2915 2913 # an InputRejected. Short-circuit in this case so that we
2916 2914 # don't unregister the transform.
2917 2915 raise
2918 2916 except Exception:
2919 2917 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2920 2918 self.ast_transformers.remove(transformer)
2921 2919
2922 2920 if self.ast_transformers:
2923 2921 ast.fix_missing_locations(node)
2924 2922 return node
2925 2923
2926 2924
2927 2925 def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr',
2928 2926 compiler=compile, result=None):
2929 2927 """Run a sequence of AST nodes. The execution mode depends on the
2930 2928 interactivity parameter.
2931 2929
2932 2930 Parameters
2933 2931 ----------
2934 2932 nodelist : list
2935 2933 A sequence of AST nodes to run.
2936 2934 cell_name : str
2937 2935 Will be passed to the compiler as the filename of the cell. Typically
2938 2936 the value returned by ip.compile.cache(cell).
2939 2937 interactivity : str
2940 2938 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
2941 2939 run interactively (displaying output from expressions). 'last_expr'
2942 2940 will run the last node interactively only if it is an expression (i.e.
2943 2941 expressions in loops or other blocks are not displayed. Other values
2944 2942 for this parameter will raise a ValueError.
2945 2943 compiler : callable
2946 2944 A function with the same interface as the built-in compile(), to turn
2947 2945 the AST nodes into code objects. Default is the built-in compile().
2948 2946 result : ExecutionResult, optional
2949 2947 An object to store exceptions that occur during execution.
2950 2948
2951 2949 Returns
2952 2950 -------
2953 2951 True if an exception occurred while running code, False if it finished
2954 2952 running.
2955 2953 """
2956 2954 if not nodelist:
2957 2955 return
2958 2956
2959 2957 if interactivity == 'last_expr':
2960 2958 if isinstance(nodelist[-1], ast.Expr):
2961 2959 interactivity = "last"
2962 2960 else:
2963 2961 interactivity = "none"
2964 2962
2965 2963 if interactivity == 'none':
2966 2964 to_run_exec, to_run_interactive = nodelist, []
2967 2965 elif interactivity == 'last':
2968 2966 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2969 2967 elif interactivity == 'all':
2970 2968 to_run_exec, to_run_interactive = [], nodelist
2971 2969 else:
2972 2970 raise ValueError("Interactivity was %r" % interactivity)
2973 2971
2974 2972 exec_count = self.execution_count
2975 2973
2976 2974 try:
2977 2975 for i, node in enumerate(to_run_exec):
2978 2976 mod = ast.Module([node])
2979 2977 code = compiler(mod, cell_name, "exec")
2980 2978 if self.run_code(code, result):
2981 2979 return True
2982 2980
2983 2981 for i, node in enumerate(to_run_interactive):
2984 2982 mod = ast.Interactive([node])
2985 2983 code = compiler(mod, cell_name, "single")
2986 2984 if self.run_code(code, result):
2987 2985 return True
2988 2986
2989 2987 # Flush softspace
2990 2988 if softspace(sys.stdout, 0):
2991 2989 print()
2992 2990
2993 2991 except:
2994 2992 # It's possible to have exceptions raised here, typically by
2995 2993 # compilation of odd code (such as a naked 'return' outside a
2996 2994 # function) that did parse but isn't valid. Typically the exception
2997 2995 # is a SyntaxError, but it's safest just to catch anything and show
2998 2996 # the user a traceback.
2999 2997
3000 2998 # We do only one try/except outside the loop to minimize the impact
3001 2999 # on runtime, and also because if any node in the node list is
3002 3000 # broken, we should stop execution completely.
3003 3001 if result:
3004 3002 result.error_before_exec = sys.exc_info()[1]
3005 3003 self.showtraceback()
3006 3004 return True
3007 3005
3008 3006 return False
3009 3007
3010 3008 def run_code(self, code_obj, result=None):
3011 3009 """Execute a code object.
3012 3010
3013 3011 When an exception occurs, self.showtraceback() is called to display a
3014 3012 traceback.
3015 3013
3016 3014 Parameters
3017 3015 ----------
3018 3016 code_obj : code object
3019 3017 A compiled code object, to be executed
3020 3018 result : ExecutionResult, optional
3021 3019 An object to store exceptions that occur during execution.
3022 3020
3023 3021 Returns
3024 3022 -------
3025 3023 False : successful execution.
3026 3024 True : an error occurred.
3027 3025 """
3028 3026 # Set our own excepthook in case the user code tries to call it
3029 3027 # directly, so that the IPython crash handler doesn't get triggered
3030 3028 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
3031 3029
3032 3030 # we save the original sys.excepthook in the instance, in case config
3033 3031 # code (such as magics) needs access to it.
3034 3032 self.sys_excepthook = old_excepthook
3035 3033 outflag = 1 # happens in more places, so it's easier as default
3036 3034 try:
3037 3035 try:
3038 3036 self.hooks.pre_run_code_hook()
3039 3037 #rprint('Running code', repr(code_obj)) # dbg
3040 3038 exec(code_obj, self.user_global_ns, self.user_ns)
3041 3039 finally:
3042 3040 # Reset our crash handler in place
3043 3041 sys.excepthook = old_excepthook
3044 3042 except SystemExit as e:
3045 3043 if result is not None:
3046 3044 result.error_in_exec = e
3047 3045 self.showtraceback(exception_only=True)
3048 3046 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
3049 3047 except self.custom_exceptions:
3050 3048 etype, value, tb = sys.exc_info()
3051 3049 if result is not None:
3052 3050 result.error_in_exec = value
3053 3051 self.CustomTB(etype, value, tb)
3054 3052 except:
3055 3053 if result is not None:
3056 3054 result.error_in_exec = sys.exc_info()[1]
3057 3055 self.showtraceback()
3058 3056 else:
3059 3057 outflag = 0
3060 3058 return outflag
3061 3059
3062 3060 # For backwards compatibility
3063 3061 runcode = run_code
3064 3062
3065 3063 #-------------------------------------------------------------------------
3066 3064 # Things related to GUI support and pylab
3067 3065 #-------------------------------------------------------------------------
3068 3066
3069 3067 def enable_gui(self, gui=None):
3070 3068 raise NotImplementedError('Implement enable_gui in a subclass')
3071 3069
3072 3070 def enable_matplotlib(self, gui=None):
3073 3071 """Enable interactive matplotlib and inline figure support.
3074 3072
3075 3073 This takes the following steps:
3076 3074
3077 3075 1. select the appropriate eventloop and matplotlib backend
3078 3076 2. set up matplotlib for interactive use with that backend
3079 3077 3. configure formatters for inline figure display
3080 3078 4. enable the selected gui eventloop
3081 3079
3082 3080 Parameters
3083 3081 ----------
3084 3082 gui : optional, string
3085 3083 If given, dictates the choice of matplotlib GUI backend to use
3086 3084 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3087 3085 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3088 3086 matplotlib (as dictated by the matplotlib build-time options plus the
3089 3087 user's matplotlibrc configuration file). Note that not all backends
3090 3088 make sense in all contexts, for example a terminal ipython can't
3091 3089 display figures inline.
3092 3090 """
3093 3091 from IPython.core import pylabtools as pt
3094 3092 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3095 3093
3096 3094 if gui != 'inline':
3097 3095 # If we have our first gui selection, store it
3098 3096 if self.pylab_gui_select is None:
3099 3097 self.pylab_gui_select = gui
3100 3098 # Otherwise if they are different
3101 3099 elif gui != self.pylab_gui_select:
3102 3100 print ('Warning: Cannot change to a different GUI toolkit: %s.'
3103 3101 ' Using %s instead.' % (gui, self.pylab_gui_select))
3104 3102 gui, backend = pt.find_gui_and_backend(self.pylab_gui_select)
3105 3103
3106 3104 pt.activate_matplotlib(backend)
3107 3105 pt.configure_inline_support(self, backend)
3108 3106
3109 3107 # Now we must activate the gui pylab wants to use, and fix %run to take
3110 3108 # plot updates into account
3111 3109 self.enable_gui(gui)
3112 3110 self.magics_manager.registry['ExecutionMagics'].default_runner = \
3113 3111 pt.mpl_runner(self.safe_execfile)
3114 3112
3115 3113 return gui, backend
3116 3114
3117 3115 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
3118 3116 """Activate pylab support at runtime.
3119 3117
3120 3118 This turns on support for matplotlib, preloads into the interactive
3121 3119 namespace all of numpy and pylab, and configures IPython to correctly
3122 3120 interact with the GUI event loop. The GUI backend to be used can be
3123 3121 optionally selected with the optional ``gui`` argument.
3124 3122
3125 3123 This method only adds preloading the namespace to InteractiveShell.enable_matplotlib.
3126 3124
3127 3125 Parameters
3128 3126 ----------
3129 3127 gui : optional, string
3130 3128 If given, dictates the choice of matplotlib GUI backend to use
3131 3129 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3132 3130 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3133 3131 matplotlib (as dictated by the matplotlib build-time options plus the
3134 3132 user's matplotlibrc configuration file). Note that not all backends
3135 3133 make sense in all contexts, for example a terminal ipython can't
3136 3134 display figures inline.
3137 3135 import_all : optional, bool, default: True
3138 3136 Whether to do `from numpy import *` and `from pylab import *`
3139 3137 in addition to module imports.
3140 3138 welcome_message : deprecated
3141 3139 This argument is ignored, no welcome message will be displayed.
3142 3140 """
3143 3141 from IPython.core.pylabtools import import_pylab
3144 3142
3145 3143 gui, backend = self.enable_matplotlib(gui)
3146 3144
3147 3145 # We want to prevent the loading of pylab to pollute the user's
3148 3146 # namespace as shown by the %who* magics, so we execute the activation
3149 3147 # code in an empty namespace, and we update *both* user_ns and
3150 3148 # user_ns_hidden with this information.
3151 3149 ns = {}
3152 3150 import_pylab(ns, import_all)
3153 3151 # warn about clobbered names
3154 3152 ignored = set(["__builtins__"])
3155 3153 both = set(ns).intersection(self.user_ns).difference(ignored)
3156 3154 clobbered = [ name for name in both if self.user_ns[name] is not ns[name] ]
3157 3155 self.user_ns.update(ns)
3158 3156 self.user_ns_hidden.update(ns)
3159 3157 return gui, backend, clobbered
3160 3158
3161 3159 #-------------------------------------------------------------------------
3162 3160 # Utilities
3163 3161 #-------------------------------------------------------------------------
3164 3162
3165 3163 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
3166 3164 """Expand python variables in a string.
3167 3165
3168 3166 The depth argument indicates how many frames above the caller should
3169 3167 be walked to look for the local namespace where to expand variables.
3170 3168
3171 3169 The global namespace for expansion is always the user's interactive
3172 3170 namespace.
3173 3171 """
3174 3172 ns = self.user_ns.copy()
3175 3173 try:
3176 3174 frame = sys._getframe(depth+1)
3177 3175 except ValueError:
3178 3176 # This is thrown if there aren't that many frames on the stack,
3179 3177 # e.g. if a script called run_line_magic() directly.
3180 3178 pass
3181 3179 else:
3182 3180 ns.update(frame.f_locals)
3183 3181
3184 3182 try:
3185 3183 # We have to use .vformat() here, because 'self' is a valid and common
3186 3184 # name, and expanding **ns for .format() would make it collide with
3187 3185 # the 'self' argument of the method.
3188 3186 cmd = formatter.vformat(cmd, args=[], kwargs=ns)
3189 3187 except Exception:
3190 3188 # if formatter couldn't format, just let it go untransformed
3191 3189 pass
3192 3190 return cmd
3193 3191
3194 3192 def mktempfile(self, data=None, prefix='ipython_edit_'):
3195 3193 """Make a new tempfile and return its filename.
3196 3194
3197 3195 This makes a call to tempfile.mkstemp (created in a tempfile.mkdtemp),
3198 3196 but it registers the created filename internally so ipython cleans it up
3199 3197 at exit time.
3200 3198
3201 3199 Optional inputs:
3202 3200
3203 3201 - data(None): if data is given, it gets written out to the temp file
3204 3202 immediately, and the file is closed again."""
3205 3203
3206 3204 dirname = tempfile.mkdtemp(prefix=prefix)
3207 3205 self.tempdirs.append(dirname)
3208 3206
3209 3207 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3210 3208 os.close(handle) # On Windows, there can only be one open handle on a file
3211 3209 self.tempfiles.append(filename)
3212 3210
3213 3211 if data:
3214 3212 tmp_file = open(filename,'w')
3215 3213 tmp_file.write(data)
3216 3214 tmp_file.close()
3217 3215 return filename
3218 3216
3219 3217 # TODO: This should be removed when Term is refactored.
3220 3218 def write(self,data):
3221 3219 """Write a string to the default output"""
3222 3220 io.stdout.write(data)
3223 3221
3224 3222 # TODO: This should be removed when Term is refactored.
3225 3223 def write_err(self,data):
3226 3224 """Write a string to the default error output"""
3227 3225 io.stderr.write(data)
3228 3226
3229 3227 def ask_yes_no(self, prompt, default=None):
3230 3228 if self.quiet:
3231 3229 return True
3232 3230 return ask_yes_no(prompt,default)
3233 3231
3234 3232 def show_usage(self):
3235 3233 """Show a usage message"""
3236 3234 page.page(IPython.core.usage.interactive_usage)
3237 3235
3238 3236 def extract_input_lines(self, range_str, raw=False):
3239 3237 """Return as a string a set of input history slices.
3240 3238
3241 3239 Parameters
3242 3240 ----------
3243 3241 range_str : string
3244 3242 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
3245 3243 since this function is for use by magic functions which get their
3246 3244 arguments as strings. The number before the / is the session
3247 3245 number: ~n goes n back from the current session.
3248 3246
3249 3247 raw : bool, optional
3250 3248 By default, the processed input is used. If this is true, the raw
3251 3249 input history is used instead.
3252 3250
3253 3251 Notes
3254 3252 -----
3255 3253
3256 3254 Slices can be described with two notations:
3257 3255
3258 3256 * ``N:M`` -> standard python form, means including items N...(M-1).
3259 3257 * ``N-M`` -> include items N..M (closed endpoint).
3260 3258 """
3261 3259 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
3262 3260 return "\n".join(x for _, _, x in lines)
3263 3261
3264 3262 def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True, search_ns=False):
3265 3263 """Get a code string from history, file, url, or a string or macro.
3266 3264
3267 3265 This is mainly used by magic functions.
3268 3266
3269 3267 Parameters
3270 3268 ----------
3271 3269
3272 3270 target : str
3273 3271
3274 3272 A string specifying code to retrieve. This will be tried respectively
3275 3273 as: ranges of input history (see %history for syntax), url,
3276 3274 correspnding .py file, filename, or an expression evaluating to a
3277 3275 string or Macro in the user namespace.
3278 3276
3279 3277 raw : bool
3280 3278 If true (default), retrieve raw history. Has no effect on the other
3281 3279 retrieval mechanisms.
3282 3280
3283 3281 py_only : bool (default False)
3284 3282 Only try to fetch python code, do not try alternative methods to decode file
3285 3283 if unicode fails.
3286 3284
3287 3285 Returns
3288 3286 -------
3289 3287 A string of code.
3290 3288
3291 3289 ValueError is raised if nothing is found, and TypeError if it evaluates
3292 3290 to an object of another type. In each case, .args[0] is a printable
3293 3291 message.
3294 3292 """
3295 3293 code = self.extract_input_lines(target, raw=raw) # Grab history
3296 3294 if code:
3297 3295 return code
3298 3296 utarget = unquote_filename(target)
3299 3297 try:
3300 3298 if utarget.startswith(('http://', 'https://')):
3301 3299 return openpy.read_py_url(utarget, skip_encoding_cookie=skip_encoding_cookie)
3302 3300 except UnicodeDecodeError:
3303 3301 if not py_only :
3304 3302 # Deferred import
3305 3303 try:
3306 3304 from urllib.request import urlopen # Py3
3307 3305 except ImportError:
3308 3306 from urllib import urlopen
3309 3307 response = urlopen(target)
3310 3308 return response.read().decode('latin1')
3311 3309 raise ValueError(("'%s' seem to be unreadable.") % utarget)
3312 3310
3313 3311 potential_target = [target]
3314 3312 try :
3315 3313 potential_target.insert(0,get_py_filename(target))
3316 3314 except IOError:
3317 3315 pass
3318 3316
3319 3317 for tgt in potential_target :
3320 3318 if os.path.isfile(tgt): # Read file
3321 3319 try :
3322 3320 return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie)
3323 3321 except UnicodeDecodeError :
3324 3322 if not py_only :
3325 3323 with io_open(tgt,'r', encoding='latin1') as f :
3326 3324 return f.read()
3327 3325 raise ValueError(("'%s' seem to be unreadable.") % target)
3328 3326 elif os.path.isdir(os.path.expanduser(tgt)):
3329 3327 raise ValueError("'%s' is a directory, not a regular file." % target)
3330 3328
3331 3329 if search_ns:
3332 3330 # Inspect namespace to load object source
3333 3331 object_info = self.object_inspect(target, detail_level=1)
3334 3332 if object_info['found'] and object_info['source']:
3335 3333 return object_info['source']
3336 3334
3337 3335 try: # User namespace
3338 3336 codeobj = eval(target, self.user_ns)
3339 3337 except Exception:
3340 3338 raise ValueError(("'%s' was not found in history, as a file, url, "
3341 3339 "nor in the user namespace.") % target)
3342 3340
3343 3341 if isinstance(codeobj, string_types):
3344 3342 return codeobj
3345 3343 elif isinstance(codeobj, Macro):
3346 3344 return codeobj.value
3347 3345
3348 3346 raise TypeError("%s is neither a string nor a macro." % target,
3349 3347 codeobj)
3350 3348
3351 3349 #-------------------------------------------------------------------------
3352 3350 # Things related to IPython exiting
3353 3351 #-------------------------------------------------------------------------
3354 3352 def atexit_operations(self):
3355 3353 """This will be executed at the time of exit.
3356 3354
3357 3355 Cleanup operations and saving of persistent data that is done
3358 3356 unconditionally by IPython should be performed here.
3359 3357
3360 3358 For things that may depend on startup flags or platform specifics (such
3361 3359 as having readline or not), register a separate atexit function in the
3362 3360 code that has the appropriate information, rather than trying to
3363 3361 clutter
3364 3362 """
3365 3363 # Close the history session (this stores the end time and line count)
3366 3364 # this must be *before* the tempfile cleanup, in case of temporary
3367 3365 # history db
3368 3366 self.history_manager.end_session()
3369 3367
3370 3368 # Cleanup all tempfiles and folders left around
3371 3369 for tfile in self.tempfiles:
3372 3370 try:
3373 3371 os.unlink(tfile)
3374 3372 except OSError:
3375 3373 pass
3376 3374
3377 3375 for tdir in self.tempdirs:
3378 3376 try:
3379 3377 os.rmdir(tdir)
3380 3378 except OSError:
3381 3379 pass
3382 3380
3383 3381 # Clear all user namespaces to release all references cleanly.
3384 3382 self.reset(new_session=False)
3385 3383
3386 3384 # Run user hooks
3387 3385 self.hooks.shutdown_hook()
3388 3386
3389 3387 def cleanup(self):
3390 3388 self.restore_sys_module_state()
3391 3389
3392 3390
3393 3391 class InteractiveShellABC(with_metaclass(abc.ABCMeta, object)):
3394 3392 """An abstract base class for InteractiveShell."""
3395 3393
3396 3394 InteractiveShellABC.register(InteractiveShell)
@@ -1,503 +1,507 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 define(['require'
5 ], function(require) {
4 define(function(require){
6 5 "use strict";
7 6
8 7 var ActionHandler = function (env) {
9 8 this.env = env || {};
10 9 Object.seal(this);
11 10 };
12 11
13 12 /**
14 13 * A bunch of predefined `Simple Actions` used by IPython.
15 14 * `Simple Actions` have the following keys:
16 15 * help (optional): a short string the describe the action.
17 16 * will be used in various context, like as menu name, tool tips on buttons,
18 17 * and short description in help menu.
19 18 * help_index (optional): a string used to sort action in help menu.
20 19 * icon (optional): a short string that represent the icon that have to be used with this
21 20 * action. this should mainly correspond to a Font_awesome class.
22 21 * handler : a function which is called when the action is activated. It will receive at first parameter
23 22 * a dictionary containing various handle to element of the notebook.
24 23 *
25 24 * action need to be registered with a **name** that can be use to refer to this action.
26 25 *
27 26 *
28 27 * if `help` is not provided it will be derived by replacing any dash by space
29 28 * in the **name** of the action. It is advised to provide a prefix to action name to
30 29 * avoid conflict the prefix should be all lowercase and end with a dot `.`
31 30 * in the absence of a prefix the behavior of the action is undefined.
32 31 *
33 32 * All action provided by IPython are prefixed with `ipython.`.
34 33 *
35 34 * One can register extra actions or replace an existing action with another one is possible
36 35 * but is considered undefined behavior.
37 36 *
38 37 **/
39 var _action = {
38 var _actions = {
40 39 'run-select-next': {
41 40 icon: 'fa-play',
42 41 help : 'run cell, select below',
43 42 help_index : 'ba',
44 43 handler : function (env) {
45 44 env.notebook.execute_cell_and_select_below();
46 45 }
47 46 },
48 47 'execute-in-place':{
49 48 help : 'run cell',
50 49 help_index : 'bb',
51 50 handler : function (env) {
52 51 env.notebook.execute_cell();
53 52 }
54 53 },
55 54 'execute-and-insert-after':{
56 55 help : 'run cell, insert below',
57 56 help_index : 'bc',
58 57 handler : function (env) {
59 58 env.notebook.execute_cell_and_insert_below();
60 59 }
61 60 },
62 61 'go-to-command-mode': {
63 62 help : 'command mode',
64 63 help_index : 'aa',
65 64 handler : function (env) {
66 65 env.notebook.command_mode();
67 66 }
68 67 },
69 68 'split-cell-at-cursor': {
70 69 help : 'split cell',
71 70 help_index : 'ea',
72 71 handler : function (env) {
73 72 env.notebook.split_cell();
74 73 }
75 74 },
76 75 'enter-edit-mode' : {
77 76 help_index : 'aa',
78 77 handler : function (env) {
79 78 env.notebook.edit_mode();
80 79 }
81 80 },
82 81 'select-previous-cell' : {
83 82 help_index : 'da',
84 83 handler : function (env) {
85 84 var index = env.notebook.get_selected_index();
86 85 if (index !== 0 && index !== null) {
87 86 env.notebook.select_prev();
88 87 env.notebook.focus_cell();
89 88 }
90 89 }
91 90 },
92 91 'select-next-cell' : {
93 92 help_index : 'db',
94 93 handler : function (env) {
95 94 var index = env.notebook.get_selected_index();
96 95 if (index !== (env.notebook.ncells()-1) && index !== null) {
97 96 env.notebook.select_next();
98 97 env.notebook.focus_cell();
99 98 }
100 99 }
101 100 },
102 101 'cut-selected-cell' : {
103 102 icon: 'fa-cut',
104 103 help_index : 'ee',
105 104 handler : function (env) {
106 105 env.notebook.cut_cell();
107 106 }
108 107 },
109 108 'copy-selected-cell' : {
110 109 icon: 'fa-copy',
111 110 help_index : 'ef',
112 111 handler : function (env) {
113 112 env.notebook.copy_cell();
114 113 }
115 114 },
116 115 'paste-cell-before' : {
117 116 help_index : 'eg',
118 117 handler : function (env) {
119 118 env.notebook.paste_cell_above();
120 119 }
121 120 },
122 121 'paste-cell-after' : {
123 122 icon: 'fa-paste',
124 123 help_index : 'eh',
125 124 handler : function (env) {
126 125 env.notebook.paste_cell_below();
127 126 }
128 127 },
129 128 'insert-cell-before' : {
130 129 help_index : 'ec',
131 130 handler : function (env) {
132 131 env.notebook.insert_cell_above();
133 132 env.notebook.select_prev();
134 133 env.notebook.focus_cell();
135 134 }
136 135 },
137 136 'insert-cell-after' : {
138 137 icon : 'fa-plus',
139 138 help_index : 'ed',
140 139 handler : function (env) {
141 140 env.notebook.insert_cell_below();
142 141 env.notebook.select_next();
143 142 env.notebook.focus_cell();
144 143 }
145 144 },
146 145 'change-selected-cell-to-code-cell' : {
147 146 help : 'to code',
148 147 help_index : 'ca',
149 148 handler : function (env) {
150 149 env.notebook.to_code();
151 150 }
152 151 },
153 152 'change-selected-cell-to-markdown-cell' : {
154 153 help : 'to markdown',
155 154 help_index : 'cb',
156 155 handler : function (env) {
157 156 env.notebook.to_markdown();
158 157 }
159 158 },
160 159 'change-selected-cell-to-raw-cell' : {
161 160 help : 'to raw',
162 161 help_index : 'cc',
163 162 handler : function (env) {
164 163 env.notebook.to_raw();
165 164 }
166 165 },
167 166 'change-selected-cell-to-heading-1' : {
168 167 help : 'to heading 1',
169 168 help_index : 'cd',
170 169 handler : function (env) {
171 170 env.notebook.to_heading(undefined, 1);
172 171 }
173 172 },
174 173 'change-selected-cell-to-heading-2' : {
175 174 help : 'to heading 2',
176 175 help_index : 'ce',
177 176 handler : function (env) {
178 177 env.notebook.to_heading(undefined, 2);
179 178 }
180 179 },
181 180 'change-selected-cell-to-heading-3' : {
182 181 help : 'to heading 3',
183 182 help_index : 'cf',
184 183 handler : function (env) {
185 184 env.notebook.to_heading(undefined, 3);
186 185 }
187 186 },
188 187 'change-selected-cell-to-heading-4' : {
189 188 help : 'to heading 4',
190 189 help_index : 'cg',
191 190 handler : function (env) {
192 191 env.notebook.to_heading(undefined, 4);
193 192 }
194 193 },
195 194 'change-selected-cell-to-heading-5' : {
196 195 help : 'to heading 5',
197 196 help_index : 'ch',
198 197 handler : function (env) {
199 198 env.notebook.to_heading(undefined, 5);
200 199 }
201 200 },
202 201 'change-selected-cell-to-heading-6' : {
203 202 help : 'to heading 6',
204 203 help_index : 'ci',
205 204 handler : function (env) {
206 205 env.notebook.to_heading(undefined, 6);
207 206 }
208 207 },
209 208 'toggle-output-visibility-selected-cell' : {
210 209 help : 'toggle output',
211 210 help_index : 'gb',
212 211 handler : function (env) {
213 212 env.notebook.toggle_output();
214 213 }
215 214 },
216 215 'toggle-output-scrolling-selected-cell' : {
217 216 help : 'toggle output scrolling',
218 217 help_index : 'gc',
219 218 handler : function (env) {
220 219 env.notebook.toggle_output_scroll();
221 220 }
222 221 },
223 222 'move-selected-cell-down' : {
224 223 icon: 'fa-arrow-down',
225 224 help_index : 'eb',
226 225 handler : function (env) {
227 226 env.notebook.move_cell_down();
228 227 }
229 228 },
230 229 'move-selected-cell-up' : {
231 230 icon: 'fa-arrow-up',
232 231 help_index : 'ea',
233 232 handler : function (env) {
234 233 env.notebook.move_cell_up();
235 234 }
236 235 },
237 236 'toggle-line-number-selected-cell' : {
238 237 help : 'toggle line numbers',
239 238 help_index : 'ga',
240 239 handler : function (env) {
241 240 env.notebook.cell_toggle_line_numbers();
242 241 }
243 242 },
244 243 'show-keyboard-shortcut-help-dialog' : {
245 244 help_index : 'ge',
246 245 handler : function (env) {
247 246 env.quick_help.show_keyboard_shortcuts();
248 247 }
249 248 },
250 249 'delete-cell': {
251 250 help_index : 'ej',
252 251 handler : function (env) {
253 252 env.notebook.delete_cell();
254 253 }
255 254 },
256 255 'interrupt-kernel':{
257 256 icon: 'fa-stop',
258 257 help_index : 'ha',
259 258 handler : function (env) {
260 259 env.notebook.kernel.interrupt();
261 260 }
262 261 },
263 262 'restart-kernel':{
264 263 icon: 'fa-repeat',
265 264 help_index : 'hb',
266 265 handler : function (env) {
267 266 env.notebook.restart_kernel();
268 267 }
269 268 },
270 269 'undo-last-cell-deletion' : {
271 270 help_index : 'ei',
272 271 handler : function (env) {
273 272 env.notebook.undelete_cell();
274 273 }
275 274 },
276 275 'merge-selected-cell-with-cell-after' : {
277 276 help : 'merge cell below',
278 277 help_index : 'ek',
279 278 handler : function (env) {
280 279 env.notebook.merge_cell_below();
281 280 }
282 281 },
283 282 'close-pager' : {
284 283 help_index : 'gd',
285 284 handler : function (env) {
286 285 env.pager.collapse();
287 286 }
288 287 }
289 288
290 289 };
291 290
292 291 /**
293 292 * A bunch of `Advance actions` for IPython.
294 293 * Cf `Simple Action` plus the following properties.
295 294 *
296 295 * handler: first argument of the handler is the event that triggerd the action
297 296 * (typically keypress). The handler is responsible for any modification of the
298 297 * event and event propagation.
299 298 * Is also responsible for returning false if the event have to be further ignored,
300 299 * true, to tell keyboard manager that it ignored the event.
301 300 *
302 301 * the second parameter of the handler is the environemnt passed to Simple Actions
303 302 *
304 303 **/
305 304 var custom_ignore = {
306 305 'ignore':{
307 306 handler : function () {
308 307 return true;
309 308 }
310 309 },
311 310 'move-cursor-up-or-previous-cell':{
312 311 handler : function (env, event) {
313 312 var index = env.notebook.get_selected_index();
314 313 var cell = env.notebook.get_cell(index);
315 314 var cm = env.notebook.get_selected_cell().code_mirror;
316 315 var cur = cm.getCursor();
317 316 if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
318 317 if(event){
319 318 event.preventDefault();
320 319 }
321 320 env.notebook.command_mode();
322 321 env.notebook.select_prev();
323 322 env.notebook.edit_mode();
324 323 cm = env.notebook.get_selected_cell().code_mirror;
325 324 cm.setCursor(cm.lastLine(), 0);
326 325 }
327 326 return false;
328 327 }
329 328 },
330 329 'move-cursor-down-or-next-cell':{
331 330 handler : function (env, event) {
332 331 var index = env.notebook.get_selected_index();
333 332 var cell = env.notebook.get_cell(index);
334 333 if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) {
335 334 if(event){
336 335 event.preventDefault();
337 336 }
338 337 env.notebook.command_mode();
339 338 env.notebook.select_next();
340 339 env.notebook.edit_mode();
341 340 var cm = env.notebook.get_selected_cell().code_mirror;
342 341 cm.setCursor(0, 0);
343 342 }
344 343 return false;
345 344 }
346 345 },
347 346 'scroll-down': {
348 347 handler: function(env, event) {
349 348 if(event){
350 349 event.preventDefault();
351 350 }
352 351 return env.notebook.scroll_manager.scroll(1);
353 352 },
354 353 },
355 354 'scroll-up': {
356 355 handler: function(env, event) {
357 356 if(event){
358 357 event.preventDefault();
359 358 }
360 359 return env.notebook.scroll_manager.scroll(-1);
361 360 },
362 361 },
363 362 'save-notebook':{
364 363 help: "Save and Checkpoint",
365 364 help_index : 'fb',
366 365 icon: 'fa-save',
367 366 handler : function (env, event) {
368 367 env.notebook.save_checkpoint();
369 368 if(event){
370 369 event.preventDefault();
371 370 }
372 371 return false;
373 372 }
374 373 },
375 374 };
376 375
377 376 // private stuff that prepend `.ipython` to actions names
378 377 // and uniformize/fill in missing pieces in of an action.
379 378 var _prepare_handler = function(registry, subkey, source){
380 379 registry['ipython.'+subkey] = {};
381 380 registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
382 381 registry['ipython.'+subkey].help_index = source[subkey].help_index;
383 382 registry['ipython.'+subkey].icon = source[subkey].icon;
384 383 return source[subkey].handler;
385 384 };
386 385
387 386 // Will actually generate/register all the IPython actions
388 387 var fun = function(){
389 388 var final_actions = {};
390 for(var k in _action){
389 var k;
390 for(k in _actions){
391 if(_actions.hasOwnProperty(k)){
391 392 // Js closure are function level not block level need to wrap in a IIFE
392 393 // and append ipython to event name these things do intercept event so are wrapped
393 394 // in a function that return false.
394 var handler = _prepare_handler(final_actions, k, _action);
395 var handler = _prepare_handler(final_actions, k, _actions);
395 396 (function(key, handler){
396 397 final_actions['ipython.'+key].handler = function(env, event){
397 398 handler(env);
398 399 if(event){
399 400 event.preventDefault();
400 401 }
401 402 return false;
402 403 };
403 404 })(k, handler);
404 405 }
406 }
405 407
406 for(var k in custom_ignore){
408 for(k in custom_ignore){
407 409 // Js closure are function level not block level need to wrap in a IIFE
408 410 // same as above, but decide for themselves wether or not they intercept events.
411 if(custom_ignore.hasOwnProperty(k)){
409 412 var handler = _prepare_handler(final_actions, k, custom_ignore);
410 413 (function(key, handler){
411 414 final_actions['ipython.'+key].handler = function(env, event){
412 415 return handler(env, event);
413 416 };
414 417 })(k, handler);
415 418 }
419 }
416 420
417 421 return final_actions;
418 422 };
419 423 ActionHandler.prototype._actions = fun();
420 424
421 425
422 426 /**
423 427 * extend the environment variable that will be pass to handlers
424 428 **/
425 429 ActionHandler.prototype.extend_env = function(env){
426 430 for(var k in env){
427 431 this.env[k] = env[k];
428 432 }
429 433 };
430 434
431 435 ActionHandler.prototype.register = function(action, name, prefix){
432 436 /**
433 437 * Register an `action` with an optional name and prefix.
434 438 *
435 439 * if name and prefix are not given they will be determined automatically.
436 440 * if action if just a `function` it will be wrapped in an anonymous action.
437 441 *
438 442 * @return the full name to access this action .
439 443 **/
440 444 action = this.normalise(action);
441 445 if( !name ){
442 446 name = 'autogenerated-'+String(action.handler);
443 447 }
444 448 prefix = prefix || 'auto';
445 449 var full_name = prefix+'.'+name;
446 450 this._actions[full_name] = action;
447 451 return full_name;
448 452
449 453 };
450 454
451 455
452 456 ActionHandler.prototype.normalise = function(data){
453 457 /**
454 458 * given an `action` or `function`, return a normalised `action`
455 459 * by setting all known attributes and removing unknown attributes;
456 460 **/
457 461 if(typeof(data) === 'function'){
458 462 data = {handler:data};
459 463 }
460 464 if(typeof(data.handler) !== 'function'){
461 465 throw('unknown datatype, cannot register');
462 466 }
463 467 var _data = data;
464 468 data = {};
465 469 data.handler = _data.handler;
466 470 data.help = _data.help || '';
467 471 data.icon = _data.icon || '';
468 472 data.help_index = _data.help_index || '';
469 473 return data;
470 474 };
471 475
472 476 ActionHandler.prototype.get_name = function(name_or_data){
473 477 /**
474 478 * given an `action` or `name` of a action, return the name attached to this action.
475 479 * if given the name of and corresponding actions does not exist in registry, return `null`.
476 480 **/
477 481
478 482 if(typeof(name_or_data) === 'string'){
479 483 if(this.exists(name_or_data)){
480 484 return name_or_data;
481 485 } else {
482 486 return null;
483 487 }
484 488 } else {
485 489 return this.register(name_or_data);
486 490 }
487 491 };
488 492
489 493 ActionHandler.prototype.get = function(name){
490 494 return this._actions[name];
491 495 };
492 496
493 497 ActionHandler.prototype.call = function(name, event, env){
494 498 return this._actions[name].handler(env|| this.env, event);
495 499 };
496 500
497 501 ActionHandler.prototype.exists = function(name){
498 502 return (typeof(this._actions[name]) !== 'undefined');
499 503 };
500 504
501 505 return {init:ActionHandler};
502 506
503 507 });
@@ -1,679 +1,679 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3 /**
4 4 *
5 5 *
6 6 * @module codecell
7 7 * @namespace codecell
8 8 * @class CodeCell
9 9 */
10 10
11 11
12 12 define([
13 13 'base/js/namespace',
14 14 'jquery',
15 15 'base/js/utils',
16 16 'base/js/keyboard',
17 17 'services/config',
18 18 'notebook/js/cell',
19 19 'notebook/js/outputarea',
20 20 'notebook/js/completer',
21 21 'notebook/js/celltoolbar',
22 22 'codemirror/lib/codemirror',
23 23 'codemirror/mode/python/python',
24 24 'notebook/js/codemirror-ipython'
25 25 ], function(IPython,
26 26 $,
27 27 utils,
28 28 keyboard,
29 29 configmod,
30 30 cell,
31 31 outputarea,
32 32 completer,
33 33 celltoolbar,
34 34 CodeMirror,
35 35 cmpython,
36 36 cmip
37 37 ) {
38 38 "use strict";
39 39
40 40 var Cell = cell.Cell;
41 41
42 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 47 * function to delete until previous non blanking space character
48 48 * or first multiple of 4 tabstop.
49 49 * @private
50 50 */
51 51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 55 var tabsize = cm.getOption('tabSize');
56 56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 58 var select = cm.getRange(from,cur);
59 59 if( select.match(/^\ +$/) !== null){
60 60 cm.replaceRange("",from,cur);
61 61 } else {
62 62 cm.deleteH(-1,"char");
63 63 }
64 64 };
65 65
66 66 var keycodes = keyboard.keycodes;
67 67
68 68 var CodeCell = function (kernel, options) {
69 69 /**
70 70 * Constructor
71 71 *
72 72 * A Cell conceived to write code.
73 73 *
74 74 * Parameters:
75 75 * kernel: Kernel instance
76 76 * The kernel doesn't have to be set at creation time, in that case
77 77 * it will be null and set_kernel has to be called later.
78 78 * options: dictionary
79 79 * Dictionary of keyword arguments.
80 80 * events: $(Events) instance
81 81 * config: dictionary
82 82 * keyboard_manager: KeyboardManager instance
83 83 * notebook: Notebook instance
84 84 * tooltip: Tooltip instance
85 85 */
86 86 this.kernel = kernel || null;
87 87 this.notebook = options.notebook;
88 88 this.collapsed = false;
89 89 this.events = options.events;
90 90 this.tooltip = options.tooltip;
91 91 this.config = options.config;
92 92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 93 CodeCell.config_defaults, 'CodeCell');
94 94
95 95 // create all attributed in constructor function
96 96 // even if null for V8 VM optimisation
97 97 this.input_prompt_number = null;
98 98 this.celltoolbar = null;
99 99 this.output_area = null;
100 100 // Keep a stack of the 'active' output areas (where active means the
101 101 // output area that recieves output). When a user activates an output
102 102 // area, it gets pushed to the stack. Then, when the output area is
103 103 // deactivated, it's popped from the stack. When the stack is empty,
104 104 // the cell's output area is used.
105 105 this.active_output_areas = [];
106 106 var that = this;
107 107 Object.defineProperty(this, 'active_output_area', {
108 108 get: function() {
109 109 if (that.active_output_areas && that.active_output_areas.length > 0) {
110 110 return that.active_output_areas[that.active_output_areas.length-1];
111 111 } else {
112 112 return that.output_area;
113 113 }
114 114 },
115 115 });
116 116
117 117 this.last_msg_id = null;
118 118 this.completer = null;
119 119 this.widget_views = [];
120 120 this._widgets_live = true;
121 121
122 122 Cell.apply(this,[{
123 123 config: $.extend({}, CodeCell.options_default),
124 124 keyboard_manager: options.keyboard_manager,
125 125 events: this.events}]);
126 126
127 127 // Attributes we want to override in this subclass.
128 128 this.cell_type = "code";
129 129 this.element.focusout(
130 130 function() { that.auto_highlight(); }
131 131 );
132 132 };
133 133
134 134 CodeCell.options_default = {
135 135 cm_config : {
136 136 extraKeys: {
137 137 "Tab" : "indentMore",
138 138 "Shift-Tab" : "indentLess",
139 139 "Backspace" : "delSpaceToPrevTabStop",
140 140 "Cmd-/" : "toggleComment",
141 141 "Ctrl-/" : "toggleComment"
142 142 },
143 143 mode: 'ipython',
144 144 theme: 'ipython',
145 145 matchBrackets: true
146 146 }
147 147 };
148 148
149 149 CodeCell.config_defaults = {
150 150 cell_magic_highlight : {
151 151 'magic_javascript' :{'reg':[/^%%javascript/]},
152 152 'magic_perl' :{'reg':[/^%%perl/]},
153 153 'magic_ruby' :{'reg':[/^%%ruby/]},
154 154 'magic_python' :{'reg':[/^%%python3?/]},
155 155 'magic_shell' :{'reg':[/^%%bash/]},
156 156 'magic_r' :{'reg':[/^%%R/]},
157 157 'magic_text/x-cython' :{'reg':[/^%%cython/]},
158 158 },
159 159 };
160 160
161 161 CodeCell.msg_cells = {};
162 162
163 163 CodeCell.prototype = Object.create(Cell.prototype);
164 164
165 165 /**
166 166 * @method push_output_area
167 167 */
168 168 CodeCell.prototype.push_output_area = function (output_area) {
169 169 this.active_output_areas.push(output_area);
170 170 };
171 171
172 172 /**
173 173 * @method pop_output_area
174 174 */
175 175 CodeCell.prototype.pop_output_area = function (output_area) {
176 176 var index = this.active_output_areas.lastIndexOf(output_area);
177 177 if (index > -1) {
178 178 this.active_output_areas.splice(index, 1);
179 179 }
180 180 };
181 181
182 182 /**
183 183 * @method auto_highlight
184 184 */
185 185 CodeCell.prototype.auto_highlight = function () {
186 186 this._auto_highlight(this.class_config.get_sync('cell_magic_highlight'));
187 187 };
188 188
189 189 /** @method create_element */
190 190 CodeCell.prototype.create_element = function () {
191 191 Cell.prototype.create_element.apply(this, arguments);
192 192
193 193 var cell = $('<div></div>').addClass('cell code_cell');
194 194 cell.attr('tabindex','2');
195 195
196 196 var input = $('<div></div>').addClass('input');
197 197 var prompt = $('<div/>').addClass('prompt input_prompt');
198 198 var inner_cell = $('<div/>').addClass('inner_cell');
199 199 this.celltoolbar = new celltoolbar.CellToolbar({
200 200 cell: this,
201 201 notebook: this.notebook});
202 202 inner_cell.append(this.celltoolbar.element);
203 203 var input_area = $('<div/>').addClass('input_area');
204 204 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
205 205 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
206 206 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
207 207 inner_cell.append(input_area);
208 208 input.append(prompt).append(inner_cell);
209 209
210 210 var widget_area = $('<div/>')
211 211 .addClass('widget-area')
212 212 .hide();
213 213 this.widget_area = widget_area;
214 214 var widget_prompt = $('<div/>')
215 215 .addClass('prompt')
216 216 .appendTo(widget_area);
217 217 var widget_subarea = $('<div/>')
218 218 .addClass('widget-subarea')
219 219 .appendTo(widget_area);
220 220 this.widget_subarea = widget_subarea;
221 221 var that = this;
222 222 var widget_clear_buton = $('<button />')
223 223 .addClass('close')
224 224 .html('&times;')
225 225 .click(function() {
226 226 widget_area.slideUp('', function(){
227 227 for (var i = 0; i < that.widget_views.length; i++) {
228 228 var view = that.widget_views[i];
229 229 view.remove();
230 230
231 231 // Remove widget live events.
232 232 view.off('comm:live', that._widget_live);
233 233 view.off('comm:dead', that._widget_dead);
234 234 }
235 235 that.widget_views = [];
236 236 widget_subarea.html('');
237 237 });
238 238 })
239 239 .appendTo(widget_prompt);
240 240
241 241 var output = $('<div></div>');
242 242 cell.append(input).append(widget_area).append(output);
243 243 this.element = cell;
244 244 this.output_area = new outputarea.OutputArea({
245 245 selector: output,
246 246 prompt_area: true,
247 247 events: this.events,
248 248 keyboard_manager: this.keyboard_manager});
249 249 this.completer = new completer.Completer(this, this.events);
250 250 };
251 251
252 252 /**
253 253 * Display a widget view in the cell.
254 254 */
255 255 CodeCell.prototype.display_widget_view = function(view_promise) {
256 256
257 257 // Display a dummy element
258 258 var dummy = $('<div/>');
259 259 this.widget_subarea.append(dummy);
260 260
261 261 // Display the view.
262 262 var that = this;
263 263 return view_promise.then(function(view) {
264 264 that.widget_area.show();
265 265 dummy.replaceWith(view.$el);
266 266 that.widget_views.push(view);
267 267
268 268 // Check the live state of the view's model.
269 269 if (view.model.comm_live) {
270 270 that._widget_live(view);
271 271 } else {
272 272 that._widget_dead(view);
273 273 }
274 274
275 275 // Listen to comm live events for the view.
276 276 view.on('comm:live', that._widget_live, that);
277 277 view.on('comm:dead', that._widget_dead, that);
278 278 return view;
279 279 });
280 280 };
281 281
282 282 /**
283 283 * Handles when a widget loses it's comm connection.
284 284 * @param {WidgetView} view
285 285 */
286 286 CodeCell.prototype._widget_dead = function(view) {
287 287 if (this._widgets_live) {
288 288 this._widgets_live = false;
289 289 this.widget_area.addClass('connection-problems');
290 290 }
291 291
292 292 };
293 293
294 294 /**
295 295 * Handles when a widget is connected to a live comm.
296 296 * @param {WidgetView} view
297 297 */
298 298 CodeCell.prototype._widget_live = function(view) {
299 299 if (!this._widgets_live) {
300 300 // Check that the other widgets are live too. O(N) operation.
301 301 // Abort the function at the first dead widget found.
302 302 for (var i = 0; i < this.widget_views.length; i++) {
303 303 if (!this.widget_views[i].model.comm_live) return;
304 304 }
305 305 this._widgets_live = true;
306 306 this.widget_area.removeClass('connection-problems');
307 307 }
308 308 };
309 309
310 310 /** @method bind_events */
311 311 CodeCell.prototype.bind_events = function () {
312 312 Cell.prototype.bind_events.apply(this);
313 313 var that = this;
314 314
315 315 this.element.focusout(
316 316 function() { that.auto_highlight(); }
317 317 );
318 318 };
319 319
320 320
321 321 /**
322 322 * This method gets called in CodeMirror's onKeyDown/onKeyPress
323 323 * handlers and is used to provide custom key handling. Its return
324 324 * value is used to determine if CodeMirror should ignore the event:
325 325 * true = ignore, false = don't ignore.
326 326 * @method handle_codemirror_keyevent
327 327 */
328 328
329 329 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
330 330
331 331 var that = this;
332 332 // whatever key is pressed, first, cancel the tooltip request before
333 333 // they are sent, and remove tooltip if any, except for tab again
334 334 var tooltip_closed = null;
335 if (event.type === 'keydown' && event.which != keycodes.tab ) {
335 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
336 336 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
337 337 }
338 338
339 339 var cur = editor.getCursor();
340 340 if (event.keyCode === keycodes.enter){
341 341 this.auto_highlight();
342 342 }
343 343
344 344 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
345 345 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
346 346 // browser and keyboard layout !
347 347 // Pressing '(' , request tooltip, don't forget to reappend it
348 348 // The second argument says to hide the tooltip if the docstring
349 349 // is actually empty
350 350 this.tooltip.pending(that, true);
351 351 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
352 352 // If tooltip is active, cancel it. The call to
353 353 // remove_and_cancel_tooltip above doesn't pass, force=true.
354 354 // Because of this it won't actually close the tooltip
355 355 // if it is in sticky mode. Thus, we have to check again if it is open
356 356 // and close it with force=true.
357 357 if (!this.tooltip._hidden) {
358 358 this.tooltip.remove_and_cancel_tooltip(true);
359 359 }
360 360 // If we closed the tooltip, don't let CM or the global handlers
361 361 // handle this event.
362 362 event.codemirrorIgnore = true;
363 363 event.preventDefault();
364 364 return true;
365 365 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
366 366 if (editor.somethingSelected() || editor.getSelections().length !== 1){
367 367 var anchor = editor.getCursor("anchor");
368 368 var head = editor.getCursor("head");
369 if( anchor.line != head.line){
369 if( anchor.line !== head.line){
370 370 return false;
371 371 }
372 372 }
373 373 this.tooltip.request(that);
374 374 event.codemirrorIgnore = true;
375 375 event.preventDefault();
376 376 return true;
377 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
377 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
378 378 // Tab completion.
379 379 this.tooltip.remove_and_cancel_tooltip();
380 380
381 381 // completion does not work on multicursor, it might be possible though in some cases
382 382 if (editor.somethingSelected() || editor.getSelections().length > 1) {
383 383 return false;
384 384 }
385 385 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
386 386 if (pre_cursor.trim() === "") {
387 387 // Don't autocomplete if the part of the line before the cursor
388 388 // is empty. In this case, let CodeMirror handle indentation.
389 389 return false;
390 390 } else {
391 391 event.codemirrorIgnore = true;
392 392 event.preventDefault();
393 393 this.completer.startCompletion();
394 394 return true;
395 395 }
396 396 }
397 397
398 398 // keyboard event wasn't one of those unique to code cells, let's see
399 399 // if it's one of the generic ones (i.e. check edit mode shortcuts)
400 400 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
401 401 };
402 402
403 403 // Kernel related calls.
404 404
405 405 CodeCell.prototype.set_kernel = function (kernel) {
406 406 this.kernel = kernel;
407 407 };
408 408
409 409 /**
410 410 * Execute current code cell to the kernel
411 411 * @method execute
412 412 */
413 413 CodeCell.prototype.execute = function () {
414 414 if (!this.kernel || !this.kernel.is_connected()) {
415 415 console.log("Can't execute, kernel is not connected.");
416 416 return;
417 417 }
418 418
419 419 this.active_output_area.clear_output();
420 420
421 421 // Clear widget area
422 422 for (var i = 0; i < this.widget_views.length; i++) {
423 423 var view = this.widget_views[i];
424 424 view.remove();
425 425
426 426 // Remove widget live events.
427 427 view.off('comm:live', this._widget_live);
428 428 view.off('comm:dead', this._widget_dead);
429 429 }
430 430 this.widget_views = [];
431 431 this.widget_subarea.html('');
432 432 this.widget_subarea.height('');
433 433 this.widget_area.height('');
434 434 this.widget_area.hide();
435 435
436 436 this.set_input_prompt('*');
437 437 this.element.addClass("running");
438 438 if (this.last_msg_id) {
439 439 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
440 440 }
441 441 var callbacks = this.get_callbacks();
442 442
443 443 var old_msg_id = this.last_msg_id;
444 444 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
445 445 if (old_msg_id) {
446 446 delete CodeCell.msg_cells[old_msg_id];
447 447 }
448 448 CodeCell.msg_cells[this.last_msg_id] = this;
449 449 this.render();
450 450 this.events.trigger('execute.CodeCell', {cell: this});
451 451 };
452 452
453 453 /**
454 454 * Construct the default callbacks for
455 455 * @method get_callbacks
456 456 */
457 457 CodeCell.prototype.get_callbacks = function () {
458 458 var that = this;
459 459 return {
460 460 shell : {
461 461 reply : $.proxy(this._handle_execute_reply, this),
462 462 payload : {
463 463 set_next_input : $.proxy(this._handle_set_next_input, this),
464 464 page : $.proxy(this._open_with_pager, this)
465 465 }
466 466 },
467 467 iopub : {
468 468 output : function() {
469 469 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
470 470 },
471 471 clear_output : function() {
472 472 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
473 473 },
474 474 },
475 475 input : $.proxy(this._handle_input_request, this)
476 476 };
477 477 };
478 478
479 479 CodeCell.prototype._open_with_pager = function (payload) {
480 480 this.events.trigger('open_with_text.Pager', payload);
481 481 };
482 482
483 483 /**
484 484 * @method _handle_execute_reply
485 485 * @private
486 486 */
487 487 CodeCell.prototype._handle_execute_reply = function (msg) {
488 488 this.set_input_prompt(msg.content.execution_count);
489 489 this.element.removeClass("running");
490 490 this.events.trigger('set_dirty.Notebook', {value: true});
491 491 };
492 492
493 493 /**
494 494 * @method _handle_set_next_input
495 495 * @private
496 496 */
497 497 CodeCell.prototype._handle_set_next_input = function (payload) {
498 498 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
499 499 this.events.trigger('set_next_input.Notebook', data);
500 500 };
501 501
502 502 /**
503 503 * @method _handle_input_request
504 504 * @private
505 505 */
506 506 CodeCell.prototype._handle_input_request = function (msg) {
507 507 this.active_output_area.append_raw_input(msg);
508 508 };
509 509
510 510
511 511 // Basic cell manipulation.
512 512
513 513 CodeCell.prototype.select = function () {
514 514 var cont = Cell.prototype.select.apply(this);
515 515 if (cont) {
516 516 this.code_mirror.refresh();
517 517 this.auto_highlight();
518 518 }
519 519 return cont;
520 520 };
521 521
522 522 CodeCell.prototype.render = function () {
523 523 var cont = Cell.prototype.render.apply(this);
524 524 // Always execute, even if we are already in the rendered state
525 525 return cont;
526 526 };
527 527
528 528 CodeCell.prototype.select_all = function () {
529 529 var start = {line: 0, ch: 0};
530 530 var nlines = this.code_mirror.lineCount();
531 531 var last_line = this.code_mirror.getLine(nlines-1);
532 532 var end = {line: nlines-1, ch: last_line.length};
533 533 this.code_mirror.setSelection(start, end);
534 534 };
535 535
536 536
537 537 CodeCell.prototype.collapse_output = function () {
538 538 this.output_area.collapse();
539 539 };
540 540
541 541
542 542 CodeCell.prototype.expand_output = function () {
543 543 this.output_area.expand();
544 544 this.output_area.unscroll_area();
545 545 };
546 546
547 547 CodeCell.prototype.scroll_output = function () {
548 548 this.output_area.expand();
549 549 this.output_area.scroll_if_long();
550 550 };
551 551
552 552 CodeCell.prototype.toggle_output = function () {
553 553 this.output_area.toggle_output();
554 554 };
555 555
556 556 CodeCell.prototype.toggle_output_scroll = function () {
557 557 this.output_area.toggle_scroll();
558 558 };
559 559
560 560
561 561 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
562 562 var ns;
563 563 if (prompt_value === undefined || prompt_value === null) {
564 564 ns = "&nbsp;";
565 565 } else {
566 566 ns = encodeURIComponent(prompt_value);
567 567 }
568 568 return 'In&nbsp;[' + ns + ']:';
569 569 };
570 570
571 571 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
572 572 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
573 573 for(var i=1; i < lines_number; i++) {
574 574 html.push(['...:']);
575 575 }
576 576 return html.join('<br/>');
577 577 };
578 578
579 579 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
580 580
581 581
582 582 CodeCell.prototype.set_input_prompt = function (number) {
583 583 var nline = 1;
584 584 if (this.code_mirror !== undefined) {
585 585 nline = this.code_mirror.lineCount();
586 586 }
587 587 this.input_prompt_number = number;
588 588 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
589 589 // This HTML call is okay because the user contents are escaped.
590 590 this.element.find('div.input_prompt').html(prompt_html);
591 591 };
592 592
593 593
594 594 CodeCell.prototype.clear_input = function () {
595 595 this.code_mirror.setValue('');
596 596 };
597 597
598 598
599 599 CodeCell.prototype.get_text = function () {
600 600 return this.code_mirror.getValue();
601 601 };
602 602
603 603
604 604 CodeCell.prototype.set_text = function (code) {
605 605 return this.code_mirror.setValue(code);
606 606 };
607 607
608 608
609 609 CodeCell.prototype.clear_output = function (wait) {
610 610 this.active_output_area.clear_output(wait);
611 611 this.set_input_prompt();
612 612 };
613 613
614 614
615 615 // JSON serialization
616 616
617 617 CodeCell.prototype.fromJSON = function (data) {
618 618 Cell.prototype.fromJSON.apply(this, arguments);
619 619 if (data.cell_type === 'code') {
620 620 if (data.source !== undefined) {
621 621 this.set_text(data.source);
622 622 // make this value the starting point, so that we can only undo
623 623 // to this state, instead of a blank cell
624 624 this.code_mirror.clearHistory();
625 625 this.auto_highlight();
626 626 }
627 627 this.set_input_prompt(data.execution_count);
628 628 this.output_area.trusted = data.metadata.trusted || false;
629 629 this.output_area.fromJSON(data.outputs);
630 630 if (data.metadata.collapsed !== undefined) {
631 631 if (data.metadata.collapsed) {
632 632 this.collapse_output();
633 633 } else {
634 634 this.expand_output();
635 635 }
636 636 }
637 637 }
638 638 };
639 639
640 640
641 641 CodeCell.prototype.toJSON = function () {
642 642 var data = Cell.prototype.toJSON.apply(this);
643 643 data.source = this.get_text();
644 644 // is finite protect against undefined and '*' value
645 645 if (isFinite(this.input_prompt_number)) {
646 646 data.execution_count = this.input_prompt_number;
647 647 } else {
648 648 data.execution_count = null;
649 649 }
650 650 var outputs = this.output_area.toJSON();
651 651 data.outputs = outputs;
652 652 data.metadata.trusted = this.output_area.trusted;
653 653 data.metadata.collapsed = this.output_area.collapsed;
654 654 return data;
655 655 };
656 656
657 657 /**
658 658 * handle cell level logic when a cell is unselected
659 659 * @method unselect
660 660 * @return is the action being taken
661 661 */
662 662 CodeCell.prototype.unselect = function () {
663 663 var cont = Cell.prototype.unselect.apply(this);
664 664 if (cont) {
665 665 // When a code cell is usnelected, make sure that the corresponding
666 666 // tooltip and completer to that cell is closed.
667 667 this.tooltip.remove_and_cancel_tooltip(true);
668 668 if (this.completer !== null) {
669 669 this.completer.close();
670 670 }
671 671 }
672 672 return cont;
673 673 };
674 674
675 675 // Backwards compatability.
676 676 IPython.CodeCell = CodeCell;
677 677
678 678 return {'CodeCell': CodeCell};
679 679 });
@@ -1,386 +1,385 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'jquery',
6 6 'base/js/namespace',
7 7 'base/js/dialog',
8 8 'base/js/utils',
9 9 'notebook/js/tour',
10 10 'bootstrap',
11 11 'moment',
12 12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 13 "use strict";
14 14
15 15 var MenuBar = function (selector, options) {
16 16 /**
17 17 * Constructor
18 18 *
19 19 * A MenuBar Class to generate the menubar of IPython notebook
20 20 *
21 21 * Parameters:
22 22 * selector: string
23 23 * options: dictionary
24 24 * Dictionary of keyword arguments.
25 25 * notebook: Notebook instance
26 26 * contents: ContentManager instance
27 27 * events: $(Events) instance
28 28 * save_widget: SaveWidget instance
29 29 * quick_help: QuickHelp instance
30 30 * base_url : string
31 31 * notebook_path : string
32 32 * notebook_name : string
33 33 */
34 34 options = options || {};
35 35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 36 this.selector = selector;
37 37 this.notebook = options.notebook;
38 38 this.contents = options.contents;
39 39 this.events = options.events;
40 40 this.save_widget = options.save_widget;
41 41 this.quick_help = options.quick_help;
42 42
43 43 try {
44 44 this.tour = new tour.Tour(this.notebook, this.events);
45 45 } catch (e) {
46 46 this.tour = undefined;
47 47 console.log("Failed to instantiate Notebook Tour", e);
48 48 }
49 49
50 50 if (this.selector !== undefined) {
51 51 this.element = $(selector);
52 52 this.style();
53 53 this.bind_events();
54 54 }
55 55 };
56 56
57 57 // TODO: This has definitively nothing to do with style ...
58 58 MenuBar.prototype.style = function () {
59 59 var that = this;
60 60 this.element.find("li").click(function (event, ui) {
61 61 // The selected cell loses focus when the menu is entered, so we
62 62 // re-select it upon selection.
63 63 var i = that.notebook.get_selected_index();
64 64 that.notebook.select(i);
65 65 }
66 66 );
67 67 };
68 68
69 69 MenuBar.prototype._nbconvert = function (format, download) {
70 70 download = download || false;
71 71 var notebook_path = this.notebook.notebook_path;
72 72 var url = utils.url_join_encode(
73 73 this.base_url,
74 74 'nbconvert',
75 75 format,
76 76 notebook_path
77 77 ) + "?download=" + download.toString();
78 78
79 79 var w = window.open()
80 80 if (this.notebook.dirty) {
81 81 this.notebook.save_notebook().then(function() {
82 82 w.location = url;
83 83 });
84 84 } else {
85 85 w.location = url;
86 86 }
87 87 };
88 88
89 89 MenuBar.prototype._size_header = function() {
90 90 /**
91 91 * Update header spacer size.
92 92 */
93 93 this.events.trigger('resize-header.Page');
94 94 };
95 95
96 96 MenuBar.prototype.bind_events = function () {
97 97 /**
98 98 * File
99 99 */
100 100 var that = this;
101 101 this.element.find('#new_notebook').click(function () {
102 102 var w = window.open();
103 103 // Create a new notebook in the same path as the current
104 104 // notebook's path.
105 105 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
106 106 that.contents.new_untitled(parent, {type: "notebook"}).then(
107 107 function (data) {
108 108 w.location = utils.url_join_encode(
109 109 that.base_url, 'notebooks', data.path
110 110 );
111 111 },
112 112 function(error) {
113 113 w.close();
114 114 dialog.modal({
115 115 title : 'Creating Notebook Failed',
116 116 body : "The error was: " + error.message,
117 117 buttons : {'OK' : {'class' : 'btn-primary'}}
118 118 });
119 119 }
120 120 );
121 121 });
122 122 this.element.find('#open_notebook').click(function () {
123 123 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
124 124 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
125 125 });
126 126 this.element.find('#copy_notebook').click(function () {
127 127 that.notebook.copy_notebook();
128 128 return false;
129 129 });
130 130 this.element.find('#download_ipynb').click(function () {
131 131 var base_url = that.notebook.base_url;
132 132 var notebook_path = that.notebook.notebook_path;
133 133 if (that.notebook.dirty) {
134 134 that.notebook.save_notebook({async : false});
135 135 }
136 136
137 137 var url = utils.url_join_encode(base_url, 'files', notebook_path);
138 138 window.open(url + '?download=1');
139 139 });
140 140
141 141 this.element.find('#print_preview').click(function () {
142 142 that._nbconvert('html', false);
143 143 });
144 144
145 145 this.element.find('#download_html').click(function () {
146 146 that._nbconvert('html', true);
147 147 });
148 148
149 149 this.element.find('#download_rst').click(function () {
150 150 that._nbconvert('rst', true);
151 151 });
152 152
153 153 this.element.find('#download_pdf').click(function () {
154 154 that._nbconvert('pdf', true);
155 155 });
156 156
157 157 this.element.find('#download_script').click(function () {
158 158 that._nbconvert('script', true);
159 159 });
160 160
161 161 this.element.find('#rename_notebook').click(function () {
162 162 that.save_widget.rename_notebook({notebook: that.notebook});
163 163 });
164 164 this.element.find('#save_checkpoint').click(function () {
165 165 that.notebook.save_checkpoint();
166 166 });
167 167 this.element.find('#restore_checkpoint').click(function () {
168 168 });
169 169 this.element.find('#trust_notebook').click(function () {
170 170 that.notebook.trust_notebook();
171 171 });
172 172 this.events.on('trust_changed.Notebook', function (event, trusted) {
173 173 if (trusted) {
174 174 that.element.find('#trust_notebook')
175 175 .addClass("disabled")
176 176 .find("a").text("Trusted Notebook");
177 177 } else {
178 178 that.element.find('#trust_notebook')
179 179 .removeClass("disabled")
180 180 .find("a").text("Trust Notebook");
181 181 }
182 182 });
183 183 this.element.find('#kill_and_exit').click(function () {
184 184 var close_window = function () {
185 185 /**
186 186 * allow closing of new tabs in Chromium, impossible in FF
187 187 */
188 188 window.open('', '_self', '');
189 189 window.close();
190 190 };
191 191 // finish with close on success or failure
192 192 that.notebook.session.delete(close_window, close_window);
193 193 });
194 194 // Edit
195 195 this.element.find('#cut_cell').click(function () {
196 196 that.notebook.cut_cell();
197 197 });
198 198 this.element.find('#copy_cell').click(function () {
199 199 that.notebook.copy_cell();
200 200 });
201 201 this.element.find('#delete_cell').click(function () {
202 202 that.notebook.delete_cell();
203 203 });
204 204 this.element.find('#undelete_cell').click(function () {
205 205 that.notebook.undelete_cell();
206 206 });
207 207 this.element.find('#split_cell').click(function () {
208 208 that.notebook.split_cell();
209 209 });
210 210 this.element.find('#merge_cell_above').click(function () {
211 211 that.notebook.merge_cell_above();
212 212 });
213 213 this.element.find('#merge_cell_below').click(function () {
214 214 that.notebook.merge_cell_below();
215 215 });
216 216 this.element.find('#move_cell_up').click(function () {
217 217 that.notebook.move_cell_up();
218 218 });
219 219 this.element.find('#move_cell_down').click(function () {
220 220 that.notebook.move_cell_down();
221 221 });
222 222 this.element.find('#edit_nb_metadata').click(function () {
223 223 that.notebook.edit_metadata({
224 224 notebook: that.notebook,
225 225 keyboard_manager: that.notebook.keyboard_manager});
226 226 });
227 227
228 228 // View
229 229 this.element.find('#toggle_header').click(function () {
230 230 $('div#header-container').toggle();
231 231 that._size_header();
232 232 });
233 233 this.element.find('#toggle_toolbar').click(function () {
234 234 $('div#maintoolbar').toggle();
235 235 that._size_header();
236 236 });
237 237 // Insert
238 238 this.element.find('#insert_cell_above').click(function () {
239 239 that.notebook.insert_cell_above('code');
240 240 that.notebook.select_prev();
241 241 });
242 242 this.element.find('#insert_cell_below').click(function () {
243 243 that.notebook.insert_cell_below('code');
244 244 that.notebook.select_next();
245 245 });
246 246 // Cell
247 247 this.element.find('#run_cell').click(function () {
248 248 that.notebook.execute_cell();
249 249 });
250 250 this.element.find('#run_cell_select_below').click(function () {
251 251 that.notebook.execute_cell_and_select_below();
252 252 });
253 253 this.element.find('#run_cell_insert_below').click(function () {
254 254 that.notebook.execute_cell_and_insert_below();
255 255 });
256 256 this.element.find('#run_all_cells').click(function () {
257 257 that.notebook.execute_all_cells();
258 258 });
259 259 this.element.find('#run_all_cells_above').click(function () {
260 260 that.notebook.execute_cells_above();
261 261 });
262 262 this.element.find('#run_all_cells_below').click(function () {
263 263 that.notebook.execute_cells_below();
264 264 });
265 265 this.element.find('#to_code').click(function () {
266 266 that.notebook.to_code();
267 267 });
268 268 this.element.find('#to_markdown').click(function () {
269 269 that.notebook.to_markdown();
270 270 });
271 271 this.element.find('#to_raw').click(function () {
272 272 that.notebook.to_raw();
273 273 });
274 274
275 275 this.element.find('#toggle_current_output').click(function () {
276 276 that.notebook.toggle_output();
277 277 });
278 278 this.element.find('#toggle_current_output_scroll').click(function () {
279 279 that.notebook.toggle_output_scroll();
280 280 });
281 281 this.element.find('#clear_current_output').click(function () {
282 282 that.notebook.clear_output();
283 283 });
284 284
285 285 this.element.find('#toggle_all_output').click(function () {
286 286 that.notebook.toggle_all_output();
287 287 });
288 288 this.element.find('#toggle_all_output_scroll').click(function () {
289 289 that.notebook.toggle_all_output_scroll();
290 290 });
291 291 this.element.find('#clear_all_output').click(function () {
292 292 that.notebook.clear_all_output();
293 293 });
294 294
295 295 // Kernel
296 296 this.element.find('#int_kernel').click(function () {
297 297 that.notebook.kernel.interrupt();
298 298 });
299 299 this.element.find('#restart_kernel').click(function () {
300 300 that.notebook.restart_kernel();
301 301 });
302 302 this.element.find('#reconnect_kernel').click(function () {
303 303 that.notebook.kernel.reconnect();
304 304 });
305 305 // Help
306 306 if (this.tour) {
307 307 this.element.find('#notebook_tour').click(function () {
308 308 that.tour.start();
309 309 });
310 310 } else {
311 311 this.element.find('#notebook_tour').addClass("disabled");
312 312 }
313 313 this.element.find('#keyboard_shortcuts').click(function () {
314 314 that.quick_help.show_keyboard_shortcuts();
315 315 });
316 316
317 317 this.update_restore_checkpoint(null);
318 318
319 319 this.events.on('checkpoints_listed.Notebook', function (event, data) {
320 320 that.update_restore_checkpoint(that.notebook.checkpoints);
321 321 });
322 322
323 323 this.events.on('checkpoint_created.Notebook', function (event, data) {
324 324 that.update_restore_checkpoint(that.notebook.checkpoints);
325 325 });
326 326
327 327 this.events.on('notebook_loaded.Notebook', function() {
328 328 var langinfo = that.notebook.metadata.language_info || {};
329 329 that.update_nbconvert_script(langinfo);
330 330 });
331 331
332 332 this.events.on('kernel_ready.Kernel', function(event, data) {
333 333 var langinfo = data.kernel.info_reply.language_info || {};
334 334 that.update_nbconvert_script(langinfo);
335 335 });
336 336 };
337 337
338 338 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
339 339 var ul = this.element.find("#restore_checkpoint").find("ul");
340 340 ul.empty();
341 341 if (!checkpoints || checkpoints.length === 0) {
342 342 ul.append(
343 343 $("<li/>")
344 344 .addClass("disabled")
345 345 .append(
346 346 $("<a/>")
347 347 .text("No checkpoints")
348 348 )
349 349 );
350 350 return;
351 351 }
352 352
353 353 var that = this;
354 354 checkpoints.map(function (checkpoint) {
355 355 var d = new Date(checkpoint.last_modified);
356 356 ul.append(
357 357 $("<li/>").append(
358 358 $("<a/>")
359 359 .attr("href", "#")
360 360 .text(moment(d).format("LLLL"))
361 361 .click(function () {
362 362 that.notebook.restore_checkpoint_dialog(checkpoint);
363 363 })
364 364 )
365 365 );
366 366 });
367 367 };
368 368
369 369 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
370 370 /**
371 371 * Set the 'Download as foo' menu option for the relevant language.
372 372 */
373 373 var el = this.element.find('#download_script');
374 var that = this;
375 374
376 375 // Set menu entry text to e.g. "Python (.py)"
377 376 var langname = (langinfo.name || 'Script')
378 377 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
379 378 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
380 379 };
381 380
382 381 // Backwards compatability.
383 382 IPython.MenuBar = MenuBar;
384 383
385 384 return {'MenuBar': MenuBar};
386 385 });
@@ -1,2428 +1,2428 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 /**
5 5 * @module notebook
6 6 */
7 7 define([
8 8 'base/js/namespace',
9 9 'jquery',
10 10 'base/js/utils',
11 11 'base/js/dialog',
12 12 'notebook/js/cell',
13 13 'notebook/js/textcell',
14 14 'notebook/js/codecell',
15 15 'services/config',
16 16 'services/sessions/session',
17 17 'notebook/js/celltoolbar',
18 18 'components/marked/lib/marked',
19 19 'codemirror/lib/codemirror',
20 20 'codemirror/addon/runmode/runmode',
21 21 'notebook/js/mathjaxutils',
22 22 'base/js/keyboard',
23 23 'notebook/js/tooltip',
24 24 'notebook/js/celltoolbarpresets/default',
25 25 'notebook/js/celltoolbarpresets/rawcell',
26 26 'notebook/js/celltoolbarpresets/slideshow',
27 27 'notebook/js/scrollmanager'
28 28 ], function (
29 29 IPython,
30 30 $,
31 31 utils,
32 32 dialog,
33 33 cellmod,
34 34 textcell,
35 35 codecell,
36 36 configmod,
37 37 session,
38 38 celltoolbar,
39 39 marked,
40 40 CodeMirror,
41 41 runMode,
42 42 mathjaxutils,
43 43 keyboard,
44 44 tooltip,
45 45 default_celltoolbar,
46 46 rawcell_celltoolbar,
47 47 slideshow_celltoolbar,
48 48 scrollmanager
49 49 ) {
50 50 "use strict";
51 51
52 52 /**
53 53 * Contains and manages cells.
54 54 *
55 55 * @class Notebook
56 56 * @param {string} selector
57 57 * @param {object} options - Dictionary of keyword arguments.
58 58 * @param {jQuery} options.events - selector of Events
59 59 * @param {KeyboardManager} options.keyboard_manager
60 60 * @param {Contents} options.contents
61 61 * @param {SaveWidget} options.save_widget
62 62 * @param {object} options.config
63 63 * @param {string} options.base_url
64 64 * @param {string} options.notebook_path
65 65 * @param {string} options.notebook_name
66 66 */
67 67 var Notebook = function (selector, options) {
68 68 this.config = options.config;
69 69 this.class_config = new configmod.ConfigWithDefaults(this.config,
70 70 Notebook.options_default, 'Notebook');
71 71 this.base_url = options.base_url;
72 72 this.notebook_path = options.notebook_path;
73 73 this.notebook_name = options.notebook_name;
74 74 this.events = options.events;
75 75 this.keyboard_manager = options.keyboard_manager;
76 76 this.contents = options.contents;
77 77 this.save_widget = options.save_widget;
78 78 this.tooltip = new tooltip.Tooltip(this.events);
79 79 this.ws_url = options.ws_url;
80 80 this._session_starting = false;
81 81
82 82 // Create default scroll manager.
83 83 this.scroll_manager = new scrollmanager.ScrollManager(this);
84 84
85 85 // TODO: This code smells (and the other `= this` line a couple lines down)
86 86 // We need a better way to deal with circular instance references.
87 87 this.keyboard_manager.notebook = this;
88 88 this.save_widget.notebook = this;
89 89
90 90 mathjaxutils.init();
91 91
92 92 if (marked) {
93 93 marked.setOptions({
94 94 gfm : true,
95 95 tables: true,
96 96 // FIXME: probably want central config for CodeMirror theme when we have js config
97 97 langPrefix: "cm-s-ipython language-",
98 98 highlight: function(code, lang, callback) {
99 99 if (!lang) {
100 100 // no language, no highlight
101 101 if (callback) {
102 102 callback(null, code);
103 103 return;
104 104 } else {
105 105 return code;
106 106 }
107 107 }
108 108 utils.requireCodeMirrorMode(lang, function (spec) {
109 109 var el = document.createElement("div");
110 110 var mode = CodeMirror.getMode({}, spec);
111 111 if (!mode) {
112 112 console.log("No CodeMirror mode: " + lang);
113 113 callback(null, code);
114 114 return;
115 115 }
116 116 try {
117 117 CodeMirror.runMode(code, spec, el);
118 118 callback(null, el.innerHTML);
119 119 } catch (err) {
120 120 console.log("Failed to highlight " + lang + " code", err);
121 121 callback(err, code);
122 122 }
123 123 }, function (err) {
124 124 console.log("No CodeMirror mode: " + lang);
125 125 callback(err, code);
126 126 });
127 127 }
128 128 });
129 129 }
130 130
131 131 this.element = $(selector);
132 132 this.element.scroll();
133 133 this.element.data("notebook", this);
134 134 this.next_prompt_number = 1;
135 135 this.session = null;
136 136 this.kernel = null;
137 137 this.clipboard = null;
138 138 this.undelete_backup = null;
139 139 this.undelete_index = null;
140 140 this.undelete_below = false;
141 141 this.paste_enabled = false;
142 142 this.writable = false;
143 143 // It is important to start out in command mode to match the intial mode
144 144 // of the KeyboardManager.
145 145 this.mode = 'command';
146 146 this.set_dirty(false);
147 147 this.metadata = {};
148 148 this._checkpoint_after_save = false;
149 149 this.last_checkpoint = null;
150 150 this.checkpoints = [];
151 151 this.autosave_interval = 0;
152 152 this.autosave_timer = null;
153 153 // autosave *at most* every two minutes
154 154 this.minimum_autosave_interval = 120000;
155 155 this.notebook_name_blacklist_re = /[\/\\:]/;
156 156 this.nbformat = 4; // Increment this when changing the nbformat
157 157 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
158 158 this.codemirror_mode = 'ipython';
159 159 this.create_elements();
160 160 this.bind_events();
161 161 this.kernel_selector = null;
162 162 this.dirty = null;
163 163 this.trusted = null;
164 164 this._fully_loaded = false;
165 165
166 166 // Trigger cell toolbar registration.
167 167 default_celltoolbar.register(this);
168 168 rawcell_celltoolbar.register(this);
169 169 slideshow_celltoolbar.register(this);
170 170
171 171 // prevent assign to miss-typed properties.
172 172 Object.seal(this);
173 173 };
174 174
175 175 Notebook.options_default = {
176 176 // can be any cell type, or the special values of
177 177 // 'above', 'below', or 'selected' to get the value from another cell.
178 178 default_cell_type: 'code'
179 179 };
180 180
181 181 /**
182 182 * Create an HTML and CSS representation of the notebook.
183 183 */
184 184 Notebook.prototype.create_elements = function () {
185 185 var that = this;
186 186 this.element.attr('tabindex','-1');
187 187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
188 188 // We add this end_space div to the end of the notebook div to:
189 189 // i) provide a margin between the last cell and the end of the notebook
190 190 // ii) to prevent the div from scrolling up when the last cell is being
191 191 // edited, but is too low on the page, which browsers will do automatically.
192 192 var end_space = $('<div/>').addClass('end_space');
193 193 end_space.dblclick(function (e) {
194 194 var ncells = that.ncells();
195 195 that.insert_cell_below('code',ncells-1);
196 196 });
197 197 this.element.append(this.container);
198 198 this.container.append(end_space);
199 199 };
200 200
201 201 /**
202 202 * Bind JavaScript events: key presses and custom IPython events.
203 203 */
204 204 Notebook.prototype.bind_events = function () {
205 205 var that = this;
206 206
207 207 this.events.on('set_next_input.Notebook', function (event, data) {
208 208 if (data.replace) {
209 209 data.cell.set_text(data.text);
210 210 data.cell.clear_output();
211 211 } else {
212 212 var index = that.find_cell_index(data.cell);
213 213 var new_cell = that.insert_cell_below('code',index);
214 214 new_cell.set_text(data.text);
215 215 }
216 216 that.dirty = true;
217 217 });
218 218
219 219 this.events.on('unrecognized_cell.Cell', function () {
220 220 that.warn_nbformat_minor();
221 221 });
222 222
223 223 this.events.on('unrecognized_output.OutputArea', function () {
224 224 that.warn_nbformat_minor();
225 225 });
226 226
227 227 this.events.on('set_dirty.Notebook', function (event, data) {
228 228 that.dirty = data.value;
229 229 });
230 230
231 231 this.events.on('trust_changed.Notebook', function (event, trusted) {
232 232 that.trusted = trusted;
233 233 });
234 234
235 235 this.events.on('select.Cell', function (event, data) {
236 236 var index = that.find_cell_index(data.cell);
237 237 that.select(index);
238 238 });
239 239
240 240 this.events.on('edit_mode.Cell', function (event, data) {
241 241 that.handle_edit_mode(data.cell);
242 242 });
243 243
244 244 this.events.on('command_mode.Cell', function (event, data) {
245 245 that.handle_command_mode(data.cell);
246 246 });
247 247
248 248 this.events.on('spec_changed.Kernel', function(event, data) {
249 249 that.metadata.kernelspec =
250 250 {name: data.name, display_name: data.display_name};
251 251 });
252 252
253 253 this.events.on('kernel_ready.Kernel', function(event, data) {
254 254 var kinfo = data.kernel.info_reply;
255 255 var langinfo = kinfo.language_info || {};
256 256 that.metadata.language_info = langinfo;
257 257 // Mode 'null' should be plain, unhighlighted text.
258 258 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
259 259 that.set_codemirror_mode(cm_mode);
260 260 });
261 261
262 262 var collapse_time = function (time) {
263 263 var app_height = $('#ipython-main-app').height(); // content height
264 264 var splitter_height = $('div#pager_splitter').outerHeight(true);
265 265 var new_height = app_height - splitter_height;
266 266 that.element.animate({height : new_height + 'px'}, time);
267 267 };
268 268
269 269 this.element.bind('collapse_pager', function (event, extrap) {
270 270 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
271 271 collapse_time(time);
272 272 });
273 273
274 274 var expand_time = function (time) {
275 275 var app_height = $('#ipython-main-app').height(); // content height
276 276 var splitter_height = $('div#pager_splitter').outerHeight(true);
277 277 var pager_height = $('div#pager').outerHeight(true);
278 278 var new_height = app_height - pager_height - splitter_height;
279 279 that.element.animate({height : new_height + 'px'}, time);
280 280 };
281 281
282 282 this.element.bind('expand_pager', function (event, extrap) {
283 283 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
284 284 expand_time(time);
285 285 });
286 286
287 287 // Firefox 22 broke $(window).on("beforeunload")
288 288 // I'm not sure why or how.
289 289 window.onbeforeunload = function (e) {
290 290 // TODO: Make killing the kernel configurable.
291 291 var kill_kernel = false;
292 292 if (kill_kernel) {
293 293 that.session.delete();
294 294 }
295 295 // if we are autosaving, trigger an autosave on nav-away.
296 296 // still warn, because if we don't the autosave may fail.
297 297 if (that.dirty) {
298 298 if ( that.autosave_interval ) {
299 299 // schedule autosave in a timeout
300 300 // this gives you a chance to forcefully discard changes
301 301 // by reloading the page if you *really* want to.
302 302 // the timer doesn't start until you *dismiss* the dialog.
303 303 setTimeout(function () {
304 304 if (that.dirty) {
305 305 that.save_notebook();
306 306 }
307 307 }, 1000);
308 308 return "Autosave in progress, latest changes may be lost.";
309 309 } else {
310 310 return "Unsaved changes will be lost.";
311 311 }
312 312 }
313 313 // Null is the *only* return value that will make the browser not
314 314 // pop up the "don't leave" dialog.
315 315 return null;
316 316 };
317 317 };
318 318
319 319 /**
320 320 * Trigger a warning dialog about missing functionality from newer minor versions
321 321 */
322 322 Notebook.prototype.warn_nbformat_minor = function (event) {
323 323 var v = 'v' + this.nbformat + '.';
324 324 var orig_vs = v + this.nbformat_minor;
325 325 var this_vs = v + this.current_nbformat_minor;
326 326 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
327 327 this_vs + ". You can still work with this notebook, but cell and output types " +
328 328 "introduced in later notebook versions will not be available.";
329 329
330 330 dialog.modal({
331 331 notebook: this,
332 332 keyboard_manager: this.keyboard_manager,
333 333 title : "Newer Notebook",
334 334 body : msg,
335 335 buttons : {
336 336 OK : {
337 337 "class" : "btn-danger"
338 338 }
339 339 }
340 340 });
341 341 };
342 342
343 343 /**
344 344 * Set the dirty flag, and trigger the set_dirty.Notebook event
345 345 */
346 346 Notebook.prototype.set_dirty = function (value) {
347 347 if (value === undefined) {
348 348 value = true;
349 349 }
350 if (this.dirty == value) {
350 if (this.dirty === value) {
351 351 return;
352 352 }
353 353 this.events.trigger('set_dirty.Notebook', {value: value});
354 354 };
355 355
356 356 /**
357 357 * Scroll the top of the page to a given cell.
358 358 *
359 359 * @param {integer} index - An index of the cell to view
360 360 * @param {integer} time - Animation time in milliseconds
361 361 * @return {integer} Pixel offset from the top of the container
362 362 */
363 363 Notebook.prototype.scroll_to_cell = function (index, time) {
364 364 var cells = this.get_cells();
365 365 time = time || 0;
366 366 index = Math.min(cells.length-1,index);
367 367 index = Math.max(0 ,index);
368 368 var scroll_value = cells[index].element.position().top-cells[0].element.position().top ;
369 369 this.element.animate({scrollTop:scroll_value}, time);
370 370 return scroll_value;
371 371 };
372 372
373 373 /**
374 374 * Scroll to the bottom of the page.
375 375 */
376 376 Notebook.prototype.scroll_to_bottom = function () {
377 377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
378 378 };
379 379
380 380 /**
381 381 * Scroll to the top of the page.
382 382 */
383 383 Notebook.prototype.scroll_to_top = function () {
384 384 this.element.animate({scrollTop:0}, 0);
385 385 };
386 386
387 387 // Edit Notebook metadata
388 388
389 389 /**
390 390 * Display a dialog that allows the user to edit the Notebook's metadata.
391 391 */
392 392 Notebook.prototype.edit_metadata = function () {
393 393 var that = this;
394 394 dialog.edit_metadata({
395 395 md: this.metadata,
396 396 callback: function (md) {
397 397 that.metadata = md;
398 398 },
399 399 name: 'Notebook',
400 400 notebook: this,
401 401 keyboard_manager: this.keyboard_manager});
402 402 };
403 403
404 404 // Cell indexing, retrieval, etc.
405 405
406 406 /**
407 407 * Get all cell elements in the notebook.
408 408 *
409 409 * @return {jQuery} A selector of all cell elements
410 410 */
411 411 Notebook.prototype.get_cell_elements = function () {
412 412 return this.container.find(".cell").not('.cell .cell');
413 413 };
414 414
415 415 /**
416 416 * Get a particular cell element.
417 417 *
418 418 * @param {integer} index An index of a cell to select
419 419 * @return {jQuery} A selector of the given cell.
420 420 */
421 421 Notebook.prototype.get_cell_element = function (index) {
422 422 var result = null;
423 423 var e = this.get_cell_elements().eq(index);
424 424 if (e.length !== 0) {
425 425 result = e;
426 426 }
427 427 return result;
428 428 };
429 429
430 430 /**
431 431 * Try to get a particular cell by msg_id.
432 432 *
433 433 * @param {string} msg_id A message UUID
434 434 * @return {Cell} Cell or null if no cell was found.
435 435 */
436 436 Notebook.prototype.get_msg_cell = function (msg_id) {
437 437 return codecell.CodeCell.msg_cells[msg_id] || null;
438 438 };
439 439
440 440 /**
441 441 * Count the cells in this notebook.
442 442 *
443 443 * @return {integer} The number of cells in this notebook
444 444 */
445 445 Notebook.prototype.ncells = function () {
446 446 return this.get_cell_elements().length;
447 447 };
448 448
449 449 /**
450 450 * Get all Cell objects in this notebook.
451 451 *
452 452 * @return {Array} This notebook's Cell objects
453 453 */
454 454 Notebook.prototype.get_cells = function () {
455 455 // TODO: we are often calling cells as cells()[i], which we should optimize
456 456 // to cells(i) or a new method.
457 457 return this.get_cell_elements().toArray().map(function (e) {
458 458 return $(e).data("cell");
459 459 });
460 460 };
461 461
462 462 /**
463 463 * Get a Cell objects from this notebook.
464 464 *
465 465 * @param {integer} index - An index of a cell to retrieve
466 466 * @return {Cell} Cell or null if no cell was found.
467 467 */
468 468 Notebook.prototype.get_cell = function (index) {
469 469 var result = null;
470 470 var ce = this.get_cell_element(index);
471 471 if (ce !== null) {
472 472 result = ce.data('cell');
473 473 }
474 474 return result;
475 475 };
476 476
477 477 /**
478 478 * Get the cell below a given cell.
479 479 *
480 480 * @param {Cell} cell
481 481 * @return {Cell} the next cell or null if no cell was found.
482 482 */
483 483 Notebook.prototype.get_next_cell = function (cell) {
484 484 var result = null;
485 485 var index = this.find_cell_index(cell);
486 486 if (this.is_valid_cell_index(index+1)) {
487 487 result = this.get_cell(index+1);
488 488 }
489 489 return result;
490 490 };
491 491
492 492 /**
493 493 * Get the cell above a given cell.
494 494 *
495 495 * @param {Cell} cell
496 496 * @return {Cell} The previous cell or null if no cell was found.
497 497 */
498 498 Notebook.prototype.get_prev_cell = function (cell) {
499 499 var result = null;
500 500 var index = this.find_cell_index(cell);
501 501 if (index !== null && index > 0) {
502 502 result = this.get_cell(index-1);
503 503 }
504 504 return result;
505 505 };
506 506
507 507 /**
508 508 * Get the numeric index of a given cell.
509 509 *
510 510 * @param {Cell} cell
511 511 * @return {integer} The cell's numeric index or null if no cell was found.
512 512 */
513 513 Notebook.prototype.find_cell_index = function (cell) {
514 514 var result = null;
515 515 this.get_cell_elements().filter(function (index) {
516 516 if ($(this).data("cell") === cell) {
517 517 result = index;
518 518 }
519 519 });
520 520 return result;
521 521 };
522 522
523 523 /**
524 524 * Return given index if defined, or the selected index if not.
525 525 *
526 526 * @param {integer} [index] - A cell's index
527 527 * @return {integer} cell index
528 528 */
529 529 Notebook.prototype.index_or_selected = function (index) {
530 530 var i;
531 531 if (index === undefined || index === null) {
532 532 i = this.get_selected_index();
533 533 if (i === null) {
534 534 i = 0;
535 535 }
536 536 } else {
537 537 i = index;
538 538 }
539 539 return i;
540 540 };
541 541
542 542 /**
543 543 * Get the currently selected cell.
544 544 *
545 545 * @return {Cell} The selected cell
546 546 */
547 547 Notebook.prototype.get_selected_cell = function () {
548 548 var index = this.get_selected_index();
549 549 return this.get_cell(index);
550 550 };
551 551
552 552 /**
553 553 * Check whether a cell index is valid.
554 554 *
555 555 * @param {integer} index - A cell index
556 556 * @return True if the index is valid, false otherwise
557 557 */
558 558 Notebook.prototype.is_valid_cell_index = function (index) {
559 559 if (index !== null && index >= 0 && index < this.ncells()) {
560 560 return true;
561 561 } else {
562 562 return false;
563 563 }
564 564 };
565 565
566 566 /**
567 567 * Get the index of the currently selected cell.
568 568 *
569 569 * @return {integer} The selected cell's numeric index
570 570 */
571 571 Notebook.prototype.get_selected_index = function () {
572 572 var result = null;
573 573 this.get_cell_elements().filter(function (index) {
574 574 if ($(this).data("cell").selected === true) {
575 575 result = index;
576 576 }
577 577 });
578 578 return result;
579 579 };
580 580
581 581
582 582 // Cell selection.
583 583
584 584 /**
585 585 * Programmatically select a cell.
586 586 *
587 587 * @param {integer} index - A cell's index
588 588 * @return {Notebook} This notebook
589 589 */
590 590 Notebook.prototype.select = function (index) {
591 591 if (this.is_valid_cell_index(index)) {
592 592 var sindex = this.get_selected_index();
593 593 if (sindex !== null && index !== sindex) {
594 594 // If we are about to select a different cell, make sure we are
595 595 // first in command mode.
596 596 if (this.mode !== 'command') {
597 597 this.command_mode();
598 598 }
599 599 this.get_cell(sindex).unselect();
600 600 }
601 601 var cell = this.get_cell(index);
602 602 cell.select();
603 603 if (cell.cell_type === 'heading') {
604 604 this.events.trigger('selected_cell_type_changed.Notebook',
605 605 {'cell_type':cell.cell_type,level:cell.level}
606 606 );
607 607 } else {
608 608 this.events.trigger('selected_cell_type_changed.Notebook',
609 609 {'cell_type':cell.cell_type}
610 610 );
611 611 }
612 612 }
613 613 return this;
614 614 };
615 615
616 616 /**
617 617 * Programmatically select the next cell.
618 618 *
619 619 * @return {Notebook} This notebook
620 620 */
621 621 Notebook.prototype.select_next = function () {
622 622 var index = this.get_selected_index();
623 623 this.select(index+1);
624 624 return this;
625 625 };
626 626
627 627 /**
628 628 * Programmatically select the previous cell.
629 629 *
630 630 * @return {Notebook} This notebook
631 631 */
632 632 Notebook.prototype.select_prev = function () {
633 633 var index = this.get_selected_index();
634 634 this.select(index-1);
635 635 return this;
636 636 };
637 637
638 638
639 639 // Edit/Command mode
640 640
641 641 /**
642 642 * Gets the index of the cell that is in edit mode.
643 643 *
644 644 * @return {integer} index
645 645 */
646 646 Notebook.prototype.get_edit_index = function () {
647 647 var result = null;
648 648 this.get_cell_elements().filter(function (index) {
649 649 if ($(this).data("cell").mode === 'edit') {
650 650 result = index;
651 651 }
652 652 });
653 653 return result;
654 654 };
655 655
656 656 /**
657 657 * Handle when a a cell blurs and the notebook should enter command mode.
658 658 *
659 659 * @param {Cell} [cell] - Cell to enter command mode on.
660 660 */
661 661 Notebook.prototype.handle_command_mode = function (cell) {
662 662 if (this.mode !== 'command') {
663 663 cell.command_mode();
664 664 this.mode = 'command';
665 665 this.events.trigger('command_mode.Notebook');
666 666 this.keyboard_manager.command_mode();
667 667 }
668 668 };
669 669
670 670 /**
671 671 * Make the notebook enter command mode.
672 672 */
673 673 Notebook.prototype.command_mode = function () {
674 674 var cell = this.get_cell(this.get_edit_index());
675 675 if (cell && this.mode !== 'command') {
676 676 // We don't call cell.command_mode, but rather call cell.focus_cell()
677 677 // which will blur and CM editor and trigger the call to
678 678 // handle_command_mode.
679 679 cell.focus_cell();
680 680 }
681 681 };
682 682
683 683 /**
684 684 * Handle when a cell fires it's edit_mode event.
685 685 *
686 686 * @param {Cell} [cell] Cell to enter edit mode on.
687 687 */
688 688 Notebook.prototype.handle_edit_mode = function (cell) {
689 689 if (cell && this.mode !== 'edit') {
690 690 cell.edit_mode();
691 691 this.mode = 'edit';
692 692 this.events.trigger('edit_mode.Notebook');
693 693 this.keyboard_manager.edit_mode();
694 694 }
695 695 };
696 696
697 697 /**
698 698 * Make a cell enter edit mode.
699 699 */
700 700 Notebook.prototype.edit_mode = function () {
701 701 var cell = this.get_selected_cell();
702 702 if (cell && this.mode !== 'edit') {
703 703 cell.unrender();
704 704 cell.focus_editor();
705 705 }
706 706 };
707 707
708 708 /**
709 709 * Focus the currently selected cell.
710 710 */
711 711 Notebook.prototype.focus_cell = function () {
712 712 var cell = this.get_selected_cell();
713 713 if (cell === null) {return;} // No cell is selected
714 714 cell.focus_cell();
715 715 };
716 716
717 717 // Cell movement
718 718
719 719 /**
720 720 * Move given (or selected) cell up and select it.
721 721 *
722 722 * @param {integer} [index] - cell index
723 723 * @return {Notebook} This notebook
724 724 */
725 725 Notebook.prototype.move_cell_up = function (index) {
726 726 var i = this.index_or_selected(index);
727 727 if (this.is_valid_cell_index(i) && i > 0) {
728 728 var pivot = this.get_cell_element(i-1);
729 729 var tomove = this.get_cell_element(i);
730 730 if (pivot !== null && tomove !== null) {
731 731 tomove.detach();
732 732 pivot.before(tomove);
733 733 this.select(i-1);
734 734 var cell = this.get_selected_cell();
735 735 cell.focus_cell();
736 736 }
737 737 this.set_dirty(true);
738 738 }
739 739 return this;
740 740 };
741 741
742 742
743 743 /**
744 744 * Move given (or selected) cell down and select it.
745 745 *
746 746 * @param {integer} [index] - cell index
747 747 * @return {Notebook} This notebook
748 748 */
749 749 Notebook.prototype.move_cell_down = function (index) {
750 750 var i = this.index_or_selected(index);
751 751 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
752 752 var pivot = this.get_cell_element(i+1);
753 753 var tomove = this.get_cell_element(i);
754 754 if (pivot !== null && tomove !== null) {
755 755 tomove.detach();
756 756 pivot.after(tomove);
757 757 this.select(i+1);
758 758 var cell = this.get_selected_cell();
759 759 cell.focus_cell();
760 760 }
761 761 }
762 762 this.set_dirty();
763 763 return this;
764 764 };
765 765
766 766
767 767 // Insertion, deletion.
768 768
769 769 /**
770 770 * Delete a cell from the notebook without any precautions
771 771 * Needed to reload checkpoints and other things like that.
772 772 *
773 773 * @param {integer} [index] - cell's numeric index
774 774 * @return {Notebook} This notebook
775 775 */
776 776 Notebook.prototype._unsafe_delete_cell = function (index) {
777 777 var i = this.index_or_selected(index);
778 778 var cell = this.get_cell(i);
779 779
780 780 $('#undelete_cell').addClass('disabled');
781 781 if (this.is_valid_cell_index(i)) {
782 782 var old_ncells = this.ncells();
783 783 var ce = this.get_cell_element(i);
784 784 ce.remove();
785 785 this.set_dirty(true);
786 786 }
787 787 return this;
788 788 };
789 789
790 790 /**
791 791 * Delete a cell from the notebook.
792 792 *
793 793 * @param {integer} [index] - cell's numeric index
794 794 * @return {Notebook} This notebook
795 795 */
796 796 Notebook.prototype.delete_cell = function (index) {
797 797 var i = this.index_or_selected(index);
798 798 var cell = this.get_cell(i);
799 799 if (!cell.is_deletable()) {
800 800 return this;
801 801 }
802 802
803 803 this.undelete_backup = cell.toJSON();
804 804 $('#undelete_cell').removeClass('disabled');
805 805 if (this.is_valid_cell_index(i)) {
806 806 var old_ncells = this.ncells();
807 807 var ce = this.get_cell_element(i);
808 808 ce.remove();
809 809 if (i === 0) {
810 810 // Always make sure we have at least one cell.
811 811 if (old_ncells === 1) {
812 812 this.insert_cell_below('code');
813 813 }
814 814 this.select(0);
815 815 this.undelete_index = 0;
816 816 this.undelete_below = false;
817 817 } else if (i === old_ncells-1 && i !== 0) {
818 818 this.select(i-1);
819 819 this.undelete_index = i - 1;
820 820 this.undelete_below = true;
821 821 } else {
822 822 this.select(i);
823 823 this.undelete_index = i;
824 824 this.undelete_below = false;
825 825 }
826 826 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
827 827 this.set_dirty(true);
828 828 }
829 829 return this;
830 830 };
831 831
832 832 /**
833 833 * Restore the most recently deleted cell.
834 834 */
835 835 Notebook.prototype.undelete_cell = function() {
836 836 if (this.undelete_backup !== null && this.undelete_index !== null) {
837 837 var current_index = this.get_selected_index();
838 838 if (this.undelete_index < current_index) {
839 839 current_index = current_index + 1;
840 840 }
841 841 if (this.undelete_index >= this.ncells()) {
842 842 this.select(this.ncells() - 1);
843 843 }
844 844 else {
845 845 this.select(this.undelete_index);
846 846 }
847 847 var cell_data = this.undelete_backup;
848 848 var new_cell = null;
849 849 if (this.undelete_below) {
850 850 new_cell = this.insert_cell_below(cell_data.cell_type);
851 851 } else {
852 852 new_cell = this.insert_cell_above(cell_data.cell_type);
853 853 }
854 854 new_cell.fromJSON(cell_data);
855 855 if (this.undelete_below) {
856 856 this.select(current_index+1);
857 857 } else {
858 858 this.select(current_index);
859 859 }
860 860 this.undelete_backup = null;
861 861 this.undelete_index = null;
862 862 }
863 863 $('#undelete_cell').addClass('disabled');
864 864 };
865 865
866 866 /**
867 867 * Insert a cell so that after insertion the cell is at given index.
868 868 *
869 869 * If cell type is not provided, it will default to the type of the
870 870 * currently active cell.
871 871 *
872 872 * Similar to insert_above, but index parameter is mandatory.
873 873 *
874 874 * Index will be brought back into the accessible range [0,n].
875 875 *
876 876 * @param {string} [type] - in ['code','markdown', 'raw'], defaults to 'code'
877 877 * @param {integer} [index] - a valid index where to insert cell
878 878 * @return {Cell|null} created cell or null
879 879 */
880 880 Notebook.prototype.insert_cell_at_index = function(type, index){
881 881
882 882 var ncells = this.ncells();
883 883 index = Math.min(index, ncells);
884 884 index = Math.max(index, 0);
885 885 var cell = null;
886 886 type = type || this.class_config.get_sync('default_cell_type');
887 887 if (type === 'above') {
888 888 if (index > 0) {
889 889 type = this.get_cell(index-1).cell_type;
890 890 } else {
891 891 type = 'code';
892 892 }
893 893 } else if (type === 'below') {
894 894 if (index < ncells) {
895 895 type = this.get_cell(index).cell_type;
896 896 } else {
897 897 type = 'code';
898 898 }
899 899 } else if (type === 'selected') {
900 900 type = this.get_selected_cell().cell_type;
901 901 }
902 902
903 903 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
904 904 var cell_options = {
905 905 events: this.events,
906 906 config: this.config,
907 907 keyboard_manager: this.keyboard_manager,
908 908 notebook: this,
909 909 tooltip: this.tooltip
910 910 };
911 911 switch(type) {
912 912 case 'code':
913 913 cell = new codecell.CodeCell(this.kernel, cell_options);
914 914 cell.set_input_prompt();
915 915 break;
916 916 case 'markdown':
917 917 cell = new textcell.MarkdownCell(cell_options);
918 918 break;
919 919 case 'raw':
920 920 cell = new textcell.RawCell(cell_options);
921 921 break;
922 922 default:
923 923 console.log("Unrecognized cell type: ", type, cellmod);
924 924 cell = new cellmod.UnrecognizedCell(cell_options);
925 925 }
926 926
927 927 if(this._insert_element_at_index(cell.element,index)) {
928 928 cell.render();
929 929 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
930 930 cell.refresh();
931 931 // We used to select the cell after we refresh it, but there
932 932 // are now cases were this method is called where select is
933 933 // not appropriate. The selection logic should be handled by the
934 934 // caller of the the top level insert_cell methods.
935 935 this.set_dirty(true);
936 936 }
937 937 }
938 938 return cell;
939 939
940 940 };
941 941
942 942 /**
943 943 * Insert an element at given cell index.
944 944 *
945 945 * @param {HTMLElement} element - a cell element
946 946 * @param {integer} [index] - a valid index where to inser cell
947 947 * @returns {boolean} success
948 948 */
949 949 Notebook.prototype._insert_element_at_index = function(element, index){
950 950 if (element === undefined){
951 951 return false;
952 952 }
953 953
954 954 var ncells = this.ncells();
955 955
956 956 if (ncells === 0) {
957 957 // special case append if empty
958 958 this.element.find('div.end_space').before(element);
959 959 } else if ( ncells === index ) {
960 960 // special case append it the end, but not empty
961 961 this.get_cell_element(index-1).after(element);
962 962 } else if (this.is_valid_cell_index(index)) {
963 963 // otherwise always somewhere to append to
964 964 this.get_cell_element(index).before(element);
965 965 } else {
966 966 return false;
967 967 }
968 968
969 969 if (this.undelete_index !== null && index <= this.undelete_index) {
970 970 this.undelete_index = this.undelete_index + 1;
971 971 this.set_dirty(true);
972 972 }
973 973 return true;
974 974 };
975 975
976 976 /**
977 977 * Insert a cell of given type above given index, or at top
978 978 * of notebook if index smaller than 0.
979 979 *
980 980 * @param {string} [type] - cell type
981 981 * @param {integer} [index] - defaults to the currently selected cell
982 982 * @return {Cell|null} handle to created cell or null
983 983 */
984 984 Notebook.prototype.insert_cell_above = function (type, index) {
985 985 index = this.index_or_selected(index);
986 986 return this.insert_cell_at_index(type, index);
987 987 };
988 988
989 989 /**
990 990 * Insert a cell of given type below given index, or at bottom
991 991 * of notebook if index greater than number of cells
992 992 *
993 993 * @param {string} [type] - cell type
994 994 * @param {integer} [index] - defaults to the currently selected cell
995 995 * @return {Cell|null} handle to created cell or null
996 996 */
997 997 Notebook.prototype.insert_cell_below = function (type, index) {
998 998 index = this.index_or_selected(index);
999 999 return this.insert_cell_at_index(type, index+1);
1000 1000 };
1001 1001
1002 1002
1003 1003 /**
1004 1004 * Insert cell at end of notebook
1005 1005 *
1006 1006 * @param {string} type - cell type
1007 1007 * @return {Cell|null} handle to created cell or null
1008 1008 */
1009 1009 Notebook.prototype.insert_cell_at_bottom = function (type){
1010 1010 var len = this.ncells();
1011 1011 return this.insert_cell_below(type,len-1);
1012 1012 };
1013 1013
1014 1014 /**
1015 1015 * Turn a cell into a code cell.
1016 1016 *
1017 1017 * @param {integer} [index] - cell index
1018 1018 */
1019 1019 Notebook.prototype.to_code = function (index) {
1020 1020 var i = this.index_or_selected(index);
1021 1021 if (this.is_valid_cell_index(i)) {
1022 1022 var source_cell = this.get_cell(i);
1023 1023 if (!(source_cell instanceof codecell.CodeCell)) {
1024 1024 var target_cell = this.insert_cell_below('code',i);
1025 1025 var text = source_cell.get_text();
1026 1026 if (text === source_cell.placeholder) {
1027 1027 text = '';
1028 1028 }
1029 1029 //metadata
1030 1030 target_cell.metadata = source_cell.metadata;
1031 1031
1032 1032 target_cell.set_text(text);
1033 1033 // make this value the starting point, so that we can only undo
1034 1034 // to this state, instead of a blank cell
1035 1035 target_cell.code_mirror.clearHistory();
1036 1036 source_cell.element.remove();
1037 1037 this.select(i);
1038 1038 var cursor = source_cell.code_mirror.getCursor();
1039 1039 target_cell.code_mirror.setCursor(cursor);
1040 1040 this.set_dirty(true);
1041 1041 }
1042 1042 }
1043 1043 };
1044 1044
1045 1045 /**
1046 1046 * Turn a cell into a Markdown cell.
1047 1047 *
1048 1048 * @param {integer} [index] - cell index
1049 1049 */
1050 1050 Notebook.prototype.to_markdown = function (index) {
1051 1051 var i = this.index_or_selected(index);
1052 1052 if (this.is_valid_cell_index(i)) {
1053 1053 var source_cell = this.get_cell(i);
1054 1054
1055 1055 if (!(source_cell instanceof textcell.MarkdownCell)) {
1056 1056 var target_cell = this.insert_cell_below('markdown',i);
1057 1057 var text = source_cell.get_text();
1058 1058
1059 1059 if (text === source_cell.placeholder) {
1060 1060 text = '';
1061 1061 }
1062 1062 // metadata
1063 1063 target_cell.metadata = source_cell.metadata;
1064 1064 // We must show the editor before setting its contents
1065 1065 target_cell.unrender();
1066 1066 target_cell.set_text(text);
1067 1067 // make this value the starting point, so that we can only undo
1068 1068 // to this state, instead of a blank cell
1069 1069 target_cell.code_mirror.clearHistory();
1070 1070 source_cell.element.remove();
1071 1071 this.select(i);
1072 1072 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1073 1073 target_cell.render();
1074 1074 }
1075 1075 var cursor = source_cell.code_mirror.getCursor();
1076 1076 target_cell.code_mirror.setCursor(cursor);
1077 1077 this.set_dirty(true);
1078 1078 }
1079 1079 }
1080 1080 };
1081 1081
1082 1082 /**
1083 1083 * Turn a cell into a raw text cell.
1084 1084 *
1085 1085 * @param {integer} [index] - cell index
1086 1086 */
1087 1087 Notebook.prototype.to_raw = function (index) {
1088 1088 var i = this.index_or_selected(index);
1089 1089 if (this.is_valid_cell_index(i)) {
1090 1090 var target_cell = null;
1091 1091 var source_cell = this.get_cell(i);
1092 1092
1093 1093 if (!(source_cell instanceof textcell.RawCell)) {
1094 1094 target_cell = this.insert_cell_below('raw',i);
1095 1095 var text = source_cell.get_text();
1096 1096 if (text === source_cell.placeholder) {
1097 1097 text = '';
1098 1098 }
1099 1099 //metadata
1100 1100 target_cell.metadata = source_cell.metadata;
1101 1101 // We must show the editor before setting its contents
1102 1102 target_cell.unrender();
1103 1103 target_cell.set_text(text);
1104 1104 // make this value the starting point, so that we can only undo
1105 1105 // to this state, instead of a blank cell
1106 1106 target_cell.code_mirror.clearHistory();
1107 1107 source_cell.element.remove();
1108 1108 this.select(i);
1109 1109 var cursor = source_cell.code_mirror.getCursor();
1110 1110 target_cell.code_mirror.setCursor(cursor);
1111 1111 this.set_dirty(true);
1112 1112 }
1113 1113 }
1114 1114 };
1115 1115
1116 1116 /**
1117 1117 * Warn about heading cell support removal.
1118 1118 */
1119 1119 Notebook.prototype._warn_heading = function () {
1120 1120 dialog.modal({
1121 1121 notebook: this,
1122 1122 keyboard_manager: this.keyboard_manager,
1123 1123 title : "Use markdown headings",
1124 1124 body : $("<p/>").text(
1125 1125 'IPython no longer uses special heading cells. ' +
1126 1126 'Instead, write your headings in Markdown cells using # characters:'
1127 1127 ).append($('<pre/>').text(
1128 1128 '## This is a level 2 heading'
1129 1129 )),
1130 1130 buttons : {
1131 1131 "OK" : {}
1132 1132 }
1133 1133 });
1134 1134 };
1135 1135
1136 1136 /**
1137 1137 * Turn a cell into a heading containing markdown cell.
1138 1138 *
1139 1139 * @param {integer} [index] - cell index
1140 1140 * @param {integer} [level] - heading level (e.g., 1 for h1)
1141 1141 */
1142 1142 Notebook.prototype.to_heading = function (index, level) {
1143 1143 this.to_markdown(index);
1144 1144 level = level || 1;
1145 1145 var i = this.index_or_selected(index);
1146 1146 if (this.is_valid_cell_index(i)) {
1147 1147 var cell = this.get_cell(i);
1148 1148 cell.set_heading_level(level);
1149 1149 this.set_dirty(true);
1150 1150 }
1151 1151 };
1152 1152
1153 1153
1154 1154 // Cut/Copy/Paste
1155 1155
1156 1156 /**
1157 1157 * Enable the UI elements for pasting cells.
1158 1158 */
1159 1159 Notebook.prototype.enable_paste = function () {
1160 1160 var that = this;
1161 1161 if (!this.paste_enabled) {
1162 1162 $('#paste_cell_replace').removeClass('disabled')
1163 1163 .on('click', function () {that.paste_cell_replace();});
1164 1164 $('#paste_cell_above').removeClass('disabled')
1165 1165 .on('click', function () {that.paste_cell_above();});
1166 1166 $('#paste_cell_below').removeClass('disabled')
1167 1167 .on('click', function () {that.paste_cell_below();});
1168 1168 this.paste_enabled = true;
1169 1169 }
1170 1170 };
1171 1171
1172 1172 /**
1173 1173 * Disable the UI elements for pasting cells.
1174 1174 */
1175 1175 Notebook.prototype.disable_paste = function () {
1176 1176 if (this.paste_enabled) {
1177 1177 $('#paste_cell_replace').addClass('disabled').off('click');
1178 1178 $('#paste_cell_above').addClass('disabled').off('click');
1179 1179 $('#paste_cell_below').addClass('disabled').off('click');
1180 1180 this.paste_enabled = false;
1181 1181 }
1182 1182 };
1183 1183
1184 1184 /**
1185 1185 * Cut a cell.
1186 1186 */
1187 1187 Notebook.prototype.cut_cell = function () {
1188 1188 this.copy_cell();
1189 1189 this.delete_cell();
1190 1190 };
1191 1191
1192 1192 /**
1193 1193 * Copy a cell.
1194 1194 */
1195 1195 Notebook.prototype.copy_cell = function () {
1196 1196 var cell = this.get_selected_cell();
1197 1197 this.clipboard = cell.toJSON();
1198 1198 // remove undeletable status from the copied cell
1199 1199 if (this.clipboard.metadata.deletable !== undefined) {
1200 1200 delete this.clipboard.metadata.deletable;
1201 1201 }
1202 1202 this.enable_paste();
1203 1203 };
1204 1204
1205 1205 /**
1206 1206 * Replace the selected cell with the cell in the clipboard.
1207 1207 */
1208 1208 Notebook.prototype.paste_cell_replace = function () {
1209 1209 if (this.clipboard !== null && this.paste_enabled) {
1210 1210 var cell_data = this.clipboard;
1211 1211 var new_cell = this.insert_cell_above(cell_data.cell_type);
1212 1212 new_cell.fromJSON(cell_data);
1213 1213 var old_cell = this.get_next_cell(new_cell);
1214 1214 this.delete_cell(this.find_cell_index(old_cell));
1215 1215 this.select(this.find_cell_index(new_cell));
1216 1216 }
1217 1217 };
1218 1218
1219 1219 /**
1220 1220 * Paste a cell from the clipboard above the selected cell.
1221 1221 */
1222 1222 Notebook.prototype.paste_cell_above = function () {
1223 1223 if (this.clipboard !== null && this.paste_enabled) {
1224 1224 var cell_data = this.clipboard;
1225 1225 var new_cell = this.insert_cell_above(cell_data.cell_type);
1226 1226 new_cell.fromJSON(cell_data);
1227 1227 new_cell.focus_cell();
1228 1228 }
1229 1229 };
1230 1230
1231 1231 /**
1232 1232 * Paste a cell from the clipboard below the selected cell.
1233 1233 */
1234 1234 Notebook.prototype.paste_cell_below = function () {
1235 1235 if (this.clipboard !== null && this.paste_enabled) {
1236 1236 var cell_data = this.clipboard;
1237 1237 var new_cell = this.insert_cell_below(cell_data.cell_type);
1238 1238 new_cell.fromJSON(cell_data);
1239 1239 new_cell.focus_cell();
1240 1240 }
1241 1241 };
1242 1242
1243 1243 // Split/merge
1244 1244
1245 1245 /**
1246 1246 * Split the selected cell into two cells.
1247 1247 */
1248 1248 Notebook.prototype.split_cell = function () {
1249 1249 var cell = this.get_selected_cell();
1250 1250 if (cell.is_splittable()) {
1251 1251 var texta = cell.get_pre_cursor();
1252 1252 var textb = cell.get_post_cursor();
1253 1253 cell.set_text(textb);
1254 1254 var new_cell = this.insert_cell_above(cell.cell_type);
1255 1255 // Unrender the new cell so we can call set_text.
1256 1256 new_cell.unrender();
1257 1257 new_cell.set_text(texta);
1258 1258 }
1259 1259 };
1260 1260
1261 1261 /**
1262 1262 * Merge the selected cell into the cell above it.
1263 1263 */
1264 1264 Notebook.prototype.merge_cell_above = function () {
1265 1265 var index = this.get_selected_index();
1266 1266 var cell = this.get_cell(index);
1267 1267 var render = cell.rendered;
1268 1268 if (!cell.is_mergeable()) {
1269 1269 return;
1270 1270 }
1271 1271 if (index > 0) {
1272 1272 var upper_cell = this.get_cell(index-1);
1273 1273 if (!upper_cell.is_mergeable()) {
1274 1274 return;
1275 1275 }
1276 1276 var upper_text = upper_cell.get_text();
1277 1277 var text = cell.get_text();
1278 1278 if (cell instanceof codecell.CodeCell) {
1279 1279 cell.set_text(upper_text+'\n'+text);
1280 1280 } else {
1281 1281 cell.unrender(); // Must unrender before we set_text.
1282 1282 cell.set_text(upper_text+'\n\n'+text);
1283 1283 if (render) {
1284 1284 // The rendered state of the final cell should match
1285 1285 // that of the original selected cell;
1286 1286 cell.render();
1287 1287 }
1288 1288 }
1289 1289 this.delete_cell(index-1);
1290 1290 this.select(this.find_cell_index(cell));
1291 1291 }
1292 1292 };
1293 1293
1294 1294 /**
1295 1295 * Merge the selected cell into the cell below it.
1296 1296 */
1297 1297 Notebook.prototype.merge_cell_below = function () {
1298 1298 var index = this.get_selected_index();
1299 1299 var cell = this.get_cell(index);
1300 1300 var render = cell.rendered;
1301 1301 if (!cell.is_mergeable()) {
1302 1302 return;
1303 1303 }
1304 1304 if (index < this.ncells()-1) {
1305 1305 var lower_cell = this.get_cell(index+1);
1306 1306 if (!lower_cell.is_mergeable()) {
1307 1307 return;
1308 1308 }
1309 1309 var lower_text = lower_cell.get_text();
1310 1310 var text = cell.get_text();
1311 1311 if (cell instanceof codecell.CodeCell) {
1312 1312 cell.set_text(text+'\n'+lower_text);
1313 1313 } else {
1314 1314 cell.unrender(); // Must unrender before we set_text.
1315 1315 cell.set_text(text+'\n\n'+lower_text);
1316 1316 if (render) {
1317 1317 // The rendered state of the final cell should match
1318 1318 // that of the original selected cell;
1319 1319 cell.render();
1320 1320 }
1321 1321 }
1322 1322 this.delete_cell(index+1);
1323 1323 this.select(this.find_cell_index(cell));
1324 1324 }
1325 1325 };
1326 1326
1327 1327
1328 1328 // Cell collapsing and output clearing
1329 1329
1330 1330 /**
1331 1331 * Hide a cell's output.
1332 1332 *
1333 1333 * @param {integer} index - cell index
1334 1334 */
1335 1335 Notebook.prototype.collapse_output = function (index) {
1336 1336 var i = this.index_or_selected(index);
1337 1337 var cell = this.get_cell(i);
1338 1338 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1339 1339 cell.collapse_output();
1340 1340 this.set_dirty(true);
1341 1341 }
1342 1342 };
1343 1343
1344 1344 /**
1345 1345 * Hide each code cell's output area.
1346 1346 */
1347 1347 Notebook.prototype.collapse_all_output = function () {
1348 1348 this.get_cells().map(function (cell, i) {
1349 1349 if (cell instanceof codecell.CodeCell) {
1350 1350 cell.collapse_output();
1351 1351 }
1352 1352 });
1353 1353 // this should not be set if the `collapse` key is removed from nbformat
1354 1354 this.set_dirty(true);
1355 1355 };
1356 1356
1357 1357 /**
1358 1358 * Show a cell's output.
1359 1359 *
1360 1360 * @param {integer} index - cell index
1361 1361 */
1362 1362 Notebook.prototype.expand_output = function (index) {
1363 1363 var i = this.index_or_selected(index);
1364 1364 var cell = this.get_cell(i);
1365 1365 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1366 1366 cell.expand_output();
1367 1367 this.set_dirty(true);
1368 1368 }
1369 1369 };
1370 1370
1371 1371 /**
1372 1372 * Expand each code cell's output area, and remove scrollbars.
1373 1373 */
1374 1374 Notebook.prototype.expand_all_output = function () {
1375 1375 this.get_cells().map(function (cell, i) {
1376 1376 if (cell instanceof codecell.CodeCell) {
1377 1377 cell.expand_output();
1378 1378 }
1379 1379 });
1380 1380 // this should not be set if the `collapse` key is removed from nbformat
1381 1381 this.set_dirty(true);
1382 1382 };
1383 1383
1384 1384 /**
1385 1385 * Clear the selected CodeCell's output area.
1386 1386 *
1387 1387 * @param {integer} index - cell index
1388 1388 */
1389 1389 Notebook.prototype.clear_output = function (index) {
1390 1390 var i = this.index_or_selected(index);
1391 1391 var cell = this.get_cell(i);
1392 1392 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1393 1393 cell.clear_output();
1394 1394 this.set_dirty(true);
1395 1395 }
1396 1396 };
1397 1397
1398 1398 /**
1399 1399 * Clear each code cell's output area.
1400 1400 */
1401 1401 Notebook.prototype.clear_all_output = function () {
1402 1402 this.get_cells().map(function (cell, i) {
1403 1403 if (cell instanceof codecell.CodeCell) {
1404 1404 cell.clear_output();
1405 1405 }
1406 1406 });
1407 1407 this.set_dirty(true);
1408 1408 };
1409 1409
1410 1410 /**
1411 1411 * Scroll the selected CodeCell's output area.
1412 1412 *
1413 1413 * @param {integer} index - cell index
1414 1414 */
1415 1415 Notebook.prototype.scroll_output = function (index) {
1416 1416 var i = this.index_or_selected(index);
1417 1417 var cell = this.get_cell(i);
1418 1418 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1419 1419 cell.scroll_output();
1420 1420 this.set_dirty(true);
1421 1421 }
1422 1422 };
1423 1423
1424 1424 /**
1425 1425 * Expand each code cell's output area and add a scrollbar for long output.
1426 1426 */
1427 1427 Notebook.prototype.scroll_all_output = function () {
1428 1428 this.get_cells().map(function (cell, i) {
1429 1429 if (cell instanceof codecell.CodeCell) {
1430 1430 cell.scroll_output();
1431 1431 }
1432 1432 });
1433 1433 // this should not be set if the `collapse` key is removed from nbformat
1434 1434 this.set_dirty(true);
1435 1435 };
1436 1436
1437 1437 /**
1438 1438 * Toggle whether a cell's output is collapsed or expanded.
1439 1439 *
1440 1440 * @param {integer} index - cell index
1441 1441 */
1442 1442 Notebook.prototype.toggle_output = function (index) {
1443 1443 var i = this.index_or_selected(index);
1444 1444 var cell = this.get_cell(i);
1445 1445 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1446 1446 cell.toggle_output();
1447 1447 this.set_dirty(true);
1448 1448 }
1449 1449 };
1450 1450
1451 1451 /**
1452 1452 * Toggle the output of all cells.
1453 1453 */
1454 1454 Notebook.prototype.toggle_all_output = function () {
1455 1455 this.get_cells().map(function (cell, i) {
1456 1456 if (cell instanceof codecell.CodeCell) {
1457 1457 cell.toggle_output();
1458 1458 }
1459 1459 });
1460 1460 // this should not be set if the `collapse` key is removed from nbformat
1461 1461 this.set_dirty(true);
1462 1462 };
1463 1463
1464 1464 /**
1465 1465 * Toggle a scrollbar for long cell outputs.
1466 1466 *
1467 1467 * @param {integer} index - cell index
1468 1468 */
1469 1469 Notebook.prototype.toggle_output_scroll = function (index) {
1470 1470 var i = this.index_or_selected(index);
1471 1471 var cell = this.get_cell(i);
1472 1472 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1473 1473 cell.toggle_output_scroll();
1474 1474 this.set_dirty(true);
1475 1475 }
1476 1476 };
1477 1477
1478 1478 /**
1479 1479 * Toggle the scrolling of long output on all cells.
1480 1480 */
1481 1481 Notebook.prototype.toggle_all_output_scroll = function () {
1482 1482 this.get_cells().map(function (cell, i) {
1483 1483 if (cell instanceof codecell.CodeCell) {
1484 1484 cell.toggle_output_scroll();
1485 1485 }
1486 1486 });
1487 1487 // this should not be set if the `collapse` key is removed from nbformat
1488 1488 this.set_dirty(true);
1489 1489 };
1490 1490
1491 1491 // Other cell functions: line numbers, ...
1492 1492
1493 1493 /**
1494 1494 * Toggle line numbers in the selected cell's input area.
1495 1495 */
1496 1496 Notebook.prototype.cell_toggle_line_numbers = function() {
1497 1497 this.get_selected_cell().toggle_line_numbers();
1498 1498 };
1499 1499
1500 1500 /**
1501 1501 * Set the codemirror mode for all code cells, including the default for
1502 1502 * new code cells.
1503 1503 */
1504 1504 Notebook.prototype.set_codemirror_mode = function(newmode){
1505 1505 if (newmode === this.codemirror_mode) {
1506 1506 return;
1507 1507 }
1508 1508 this.codemirror_mode = newmode;
1509 1509 codecell.CodeCell.options_default.cm_config.mode = newmode;
1510 1510
1511 1511 var that = this;
1512 1512 utils.requireCodeMirrorMode(newmode, function (spec) {
1513 1513 that.get_cells().map(function(cell, i) {
1514 1514 if (cell.cell_type === 'code'){
1515 1515 cell.code_mirror.setOption('mode', spec);
1516 1516 // This is currently redundant, because cm_config ends up as
1517 1517 // codemirror's own .options object, but I don't want to
1518 1518 // rely on that.
1519 1519 cell.cm_config.mode = spec;
1520 1520 }
1521 1521 });
1522 1522 });
1523 1523 };
1524 1524
1525 1525 // Session related things
1526 1526
1527 1527 /**
1528 1528 * Start a new session and set it on each code cell.
1529 1529 */
1530 1530 Notebook.prototype.start_session = function (kernel_name) {
1531 1531 if (this._session_starting) {
1532 1532 throw new session.SessionAlreadyStarting();
1533 1533 }
1534 1534 this._session_starting = true;
1535 1535
1536 1536 var options = {
1537 1537 base_url: this.base_url,
1538 1538 ws_url: this.ws_url,
1539 1539 notebook_path: this.notebook_path,
1540 1540 notebook_name: this.notebook_name,
1541 1541 kernel_name: kernel_name,
1542 1542 notebook: this
1543 1543 };
1544 1544
1545 1545 var success = $.proxy(this._session_started, this);
1546 1546 var failure = $.proxy(this._session_start_failed, this);
1547 1547
1548 1548 if (this.session !== null) {
1549 1549 this.session.restart(options, success, failure);
1550 1550 } else {
1551 1551 this.session = new session.Session(options);
1552 1552 this.session.start(success, failure);
1553 1553 }
1554 1554 };
1555 1555
1556 1556
1557 1557 /**
1558 1558 * Once a session is started, link the code cells to the kernel and pass the
1559 1559 * comm manager to the widget manager.
1560 1560 */
1561 1561 Notebook.prototype._session_started = function (){
1562 1562 this._session_starting = false;
1563 1563 this.kernel = this.session.kernel;
1564 1564 var ncells = this.ncells();
1565 1565 for (var i=0; i<ncells; i++) {
1566 1566 var cell = this.get_cell(i);
1567 1567 if (cell instanceof codecell.CodeCell) {
1568 1568 cell.set_kernel(this.session.kernel);
1569 1569 }
1570 1570 }
1571 1571 };
1572 1572
1573 1573 /**
1574 1574 * Called when the session fails to start.
1575 1575 */
1576 1576 Notebook.prototype._session_start_failed = function(jqxhr, status, error){
1577 1577 this._session_starting = false;
1578 1578 utils.log_ajax_error(jqxhr, status, error);
1579 1579 };
1580 1580
1581 1581 /**
1582 1582 * Prompt the user to restart the IPython kernel.
1583 1583 */
1584 1584 Notebook.prototype.restart_kernel = function () {
1585 1585 var that = this;
1586 1586 dialog.modal({
1587 1587 notebook: this,
1588 1588 keyboard_manager: this.keyboard_manager,
1589 1589 title : "Restart kernel or continue running?",
1590 1590 body : $("<p/>").text(
1591 1591 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1592 1592 ),
1593 1593 buttons : {
1594 1594 "Continue running" : {},
1595 1595 "Restart" : {
1596 1596 "class" : "btn-danger",
1597 1597 "click" : function() {
1598 1598 that.kernel.restart();
1599 1599 }
1600 1600 }
1601 1601 }
1602 1602 });
1603 1603 };
1604 1604
1605 1605 /**
1606 1606 * Execute or render cell outputs and go into command mode.
1607 1607 */
1608 1608 Notebook.prototype.execute_cell = function () {
1609 1609 // mode = shift, ctrl, alt
1610 1610 var cell = this.get_selected_cell();
1611 1611
1612 1612 cell.execute();
1613 1613 this.command_mode();
1614 1614 this.set_dirty(true);
1615 1615 };
1616 1616
1617 1617 /**
1618 1618 * Execute or render cell outputs and insert a new cell below.
1619 1619 */
1620 1620 Notebook.prototype.execute_cell_and_insert_below = function () {
1621 1621 var cell = this.get_selected_cell();
1622 1622 var cell_index = this.find_cell_index(cell);
1623 1623
1624 1624 cell.execute();
1625 1625
1626 1626 // If we are at the end always insert a new cell and return
1627 1627 if (cell_index === (this.ncells()-1)) {
1628 1628 this.command_mode();
1629 1629 this.insert_cell_below();
1630 1630 this.select(cell_index+1);
1631 1631 this.edit_mode();
1632 1632 this.scroll_to_bottom();
1633 1633 this.set_dirty(true);
1634 1634 return;
1635 1635 }
1636 1636
1637 1637 this.command_mode();
1638 1638 this.insert_cell_below();
1639 1639 this.select(cell_index+1);
1640 1640 this.edit_mode();
1641 1641 this.set_dirty(true);
1642 1642 };
1643 1643
1644 1644 /**
1645 1645 * Execute or render cell outputs and select the next cell.
1646 1646 */
1647 1647 Notebook.prototype.execute_cell_and_select_below = function () {
1648 1648
1649 1649 var cell = this.get_selected_cell();
1650 1650 var cell_index = this.find_cell_index(cell);
1651 1651
1652 1652 cell.execute();
1653 1653
1654 1654 // If we are at the end always insert a new cell and return
1655 1655 if (cell_index === (this.ncells()-1)) {
1656 1656 this.command_mode();
1657 1657 this.insert_cell_below();
1658 1658 this.select(cell_index+1);
1659 1659 this.edit_mode();
1660 1660 this.scroll_to_bottom();
1661 1661 this.set_dirty(true);
1662 1662 return;
1663 1663 }
1664 1664
1665 1665 this.command_mode();
1666 1666 this.select(cell_index+1);
1667 1667 this.focus_cell();
1668 1668 this.set_dirty(true);
1669 1669 };
1670 1670
1671 1671 /**
1672 1672 * Execute all cells below the selected cell.
1673 1673 */
1674 1674 Notebook.prototype.execute_cells_below = function () {
1675 1675 this.execute_cell_range(this.get_selected_index(), this.ncells());
1676 1676 this.scroll_to_bottom();
1677 1677 };
1678 1678
1679 1679 /**
1680 1680 * Execute all cells above the selected cell.
1681 1681 */
1682 1682 Notebook.prototype.execute_cells_above = function () {
1683 1683 this.execute_cell_range(0, this.get_selected_index());
1684 1684 };
1685 1685
1686 1686 /**
1687 1687 * Execute all cells.
1688 1688 */
1689 1689 Notebook.prototype.execute_all_cells = function () {
1690 1690 this.execute_cell_range(0, this.ncells());
1691 1691 this.scroll_to_bottom();
1692 1692 };
1693 1693
1694 1694 /**
1695 1695 * Execute a contiguous range of cells.
1696 1696 *
1697 1697 * @param {integer} start - index of the first cell to execute (inclusive)
1698 1698 * @param {integer} end - index of the last cell to execute (exclusive)
1699 1699 */
1700 1700 Notebook.prototype.execute_cell_range = function (start, end) {
1701 1701 this.command_mode();
1702 1702 for (var i=start; i<end; i++) {
1703 1703 this.select(i);
1704 1704 this.execute_cell();
1705 1705 }
1706 1706 };
1707 1707
1708 1708 // Persistance and loading
1709 1709
1710 1710 /**
1711 1711 * Getter method for this notebook's name.
1712 1712 *
1713 1713 * @return {string} This notebook's name (excluding file extension)
1714 1714 */
1715 1715 Notebook.prototype.get_notebook_name = function () {
1716 1716 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1717 1717 return nbname;
1718 1718 };
1719 1719
1720 1720 /**
1721 1721 * Setter method for this notebook's name.
1722 1722 *
1723 1723 * @param {string} name
1724 1724 */
1725 1725 Notebook.prototype.set_notebook_name = function (name) {
1726 1726 var parent = utils.url_path_split(this.notebook_path)[0];
1727 1727 this.notebook_name = name;
1728 1728 this.notebook_path = utils.url_path_join(parent, name);
1729 1729 };
1730 1730
1731 1731 /**
1732 1732 * Check that a notebook's name is valid.
1733 1733 *
1734 1734 * @param {string} nbname - A name for this notebook
1735 1735 * @return {boolean} True if the name is valid, false if invalid
1736 1736 */
1737 1737 Notebook.prototype.test_notebook_name = function (nbname) {
1738 1738 nbname = nbname || '';
1739 1739 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1740 1740 return true;
1741 1741 } else {
1742 1742 return false;
1743 1743 }
1744 1744 };
1745 1745
1746 1746 /**
1747 1747 * Load a notebook from JSON (.ipynb).
1748 1748 *
1749 1749 * @param {object} data - JSON representation of a notebook
1750 1750 */
1751 1751 Notebook.prototype.fromJSON = function (data) {
1752 1752
1753 1753 var content = data.content;
1754 1754 var ncells = this.ncells();
1755 1755 var i;
1756 1756 for (i=0; i<ncells; i++) {
1757 1757 // Always delete cell 0 as they get renumbered as they are deleted.
1758 1758 this._unsafe_delete_cell(0);
1759 1759 }
1760 1760 // Save the metadata and name.
1761 1761 this.metadata = content.metadata;
1762 1762 this.notebook_name = data.name;
1763 1763 this.notebook_path = data.path;
1764 1764 var trusted = true;
1765 1765
1766 1766 // Trigger an event changing the kernel spec - this will set the default
1767 1767 // codemirror mode
1768 1768 if (this.metadata.kernelspec !== undefined) {
1769 1769 // TODO shoudl probably not trigger here,
1770 1770 // should call the kernel selector, or custom.{js|css} not loaded.
1771 1771 if(this.kernel_selector){
1772 1772 // technically not perfect, we should check that the kernelspec matches
1773 1773 this.kernel_selector.change_kernel(this.metadata.kernelspec.name);
1774 1774 } else {
1775 1775 console.log('do not have handle on kernel_selector');
1776 1776 }
1777 1777 }
1778 1778
1779 1779 // Set the codemirror mode from language_info metadata
1780 1780 if (this.metadata.language_info !== undefined) {
1781 1781 var langinfo = this.metadata.language_info;
1782 1782 // Mode 'null' should be plain, unhighlighted text.
1783 1783 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1784 1784 this.set_codemirror_mode(cm_mode);
1785 1785 }
1786 1786
1787 1787 var new_cells = content.cells;
1788 1788 ncells = new_cells.length;
1789 1789 var cell_data = null;
1790 1790 var new_cell = null;
1791 1791 for (i=0; i<ncells; i++) {
1792 1792 cell_data = new_cells[i];
1793 1793 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1794 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 1796 trusted = false;
1797 1797 }
1798 1798 }
1799 1799 if (trusted !== this.trusted) {
1800 1800 this.trusted = trusted;
1801 1801 this.events.trigger("trust_changed.Notebook", trusted);
1802 1802 }
1803 1803 };
1804 1804
1805 1805 /**
1806 1806 * Dump this notebook into a JSON-friendly object.
1807 1807 *
1808 1808 * @return {object} A JSON-friendly representation of this notebook.
1809 1809 */
1810 1810 Notebook.prototype.toJSON = function () {
1811 1811 // remove the conversion indicator, which only belongs in-memory
1812 1812 delete this.metadata.orig_nbformat;
1813 1813 delete this.metadata.orig_nbformat_minor;
1814 1814
1815 1815 var cells = this.get_cells();
1816 1816 var ncells = cells.length;
1817 1817 var cell_array = new Array(ncells);
1818 1818 var trusted = true;
1819 1819 for (var i=0; i<ncells; i++) {
1820 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 1822 trusted = false;
1823 1823 }
1824 1824 cell_array[i] = cell.toJSON();
1825 1825 }
1826 1826 var data = {
1827 1827 cells: cell_array,
1828 1828 metadata: this.metadata,
1829 1829 nbformat: this.nbformat,
1830 1830 nbformat_minor: this.nbformat_minor
1831 1831 };
1832 if (trusted != this.trusted) {
1832 if (trusted !== this.trusted) {
1833 1833 this.trusted = trusted;
1834 1834 this.events.trigger("trust_changed.Notebook", trusted);
1835 1835 }
1836 1836 return data;
1837 1837 };
1838 1838
1839 1839 /**
1840 1840 * Start an autosave timer which periodically saves the notebook.
1841 1841 *
1842 1842 * @param {integer} interval - the autosave interval in milliseconds
1843 1843 */
1844 1844 Notebook.prototype.set_autosave_interval = function (interval) {
1845 1845 var that = this;
1846 1846 // clear previous interval, so we don't get simultaneous timers
1847 1847 if (this.autosave_timer) {
1848 1848 clearInterval(this.autosave_timer);
1849 1849 }
1850 1850 if (!this.writable) {
1851 1851 // disable autosave if not writable
1852 1852 interval = 0;
1853 1853 }
1854 1854
1855 1855 this.autosave_interval = this.minimum_autosave_interval = interval;
1856 1856 if (interval) {
1857 1857 this.autosave_timer = setInterval(function() {
1858 1858 if (that.dirty) {
1859 1859 that.save_notebook();
1860 1860 }
1861 1861 }, interval);
1862 1862 this.events.trigger("autosave_enabled.Notebook", interval);
1863 1863 } else {
1864 1864 this.autosave_timer = null;
1865 1865 this.events.trigger("autosave_disabled.Notebook");
1866 1866 }
1867 1867 };
1868 1868
1869 1869 /**
1870 1870 * Save this notebook on the server. This becomes a notebook instance's
1871 1871 * .save_notebook method *after* the entire notebook has been loaded.
1872 1872 */
1873 1873 Notebook.prototype.save_notebook = function () {
1874 1874 if (!this._fully_loaded) {
1875 1875 this.events.trigger('notebook_save_failed.Notebook',
1876 1876 new Error("Load failed, save is disabled")
1877 1877 );
1878 1878 return;
1879 1879 } else if (!this.writable) {
1880 1880 this.events.trigger('notebook_save_failed.Notebook',
1881 1881 new Error("Notebook is read-only")
1882 1882 );
1883 1883 return;
1884 1884 }
1885 1885
1886 1886 // Trigger an event before save, which allows listeners to modify
1887 1887 // the notebook as needed.
1888 1888 this.events.trigger('before_save.Notebook');
1889 1889
1890 1890 // Create a JSON model to be sent to the server.
1891 1891 var model = {
1892 1892 type : "notebook",
1893 1893 content : this.toJSON()
1894 1894 };
1895 1895 // time the ajax call for autosave tuning purposes.
1896 1896 var start = new Date().getTime();
1897 1897
1898 1898 var that = this;
1899 1899 return this.contents.save(this.notebook_path, model).then(
1900 1900 $.proxy(this.save_notebook_success, this, start),
1901 1901 function (error) {
1902 1902 that.events.trigger('notebook_save_failed.Notebook', error);
1903 1903 }
1904 1904 );
1905 1905 };
1906 1906
1907 1907 /**
1908 1908 * Success callback for saving a notebook.
1909 1909 *
1910 1910 * @param {integer} start - Time when the save request start
1911 1911 * @param {object} data - JSON representation of a notebook
1912 1912 */
1913 1913 Notebook.prototype.save_notebook_success = function (start, data) {
1914 1914 this.set_dirty(false);
1915 1915 if (data.message) {
1916 1916 // save succeeded, but validation failed.
1917 1917 var body = $("<div>");
1918 1918 var title = "Notebook validation failed";
1919 1919
1920 1920 body.append($("<p>").text(
1921 1921 "The save operation succeeded," +
1922 1922 " but the notebook does not appear to be valid." +
1923 1923 " The validation error was:"
1924 1924 )).append($("<div>").addClass("validation-error").append(
1925 1925 $("<pre>").text(data.message)
1926 1926 ));
1927 1927 dialog.modal({
1928 1928 notebook: this,
1929 1929 keyboard_manager: this.keyboard_manager,
1930 1930 title: title,
1931 1931 body: body,
1932 1932 buttons : {
1933 1933 OK : {
1934 1934 "class" : "btn-primary"
1935 1935 }
1936 1936 }
1937 1937 });
1938 1938 }
1939 1939 this.events.trigger('notebook_saved.Notebook');
1940 1940 this._update_autosave_interval(start);
1941 1941 if (this._checkpoint_after_save) {
1942 1942 this.create_checkpoint();
1943 1943 this._checkpoint_after_save = false;
1944 1944 }
1945 1945 };
1946 1946
1947 1947 /**
1948 1948 * Update the autosave interval based on the duration of the last save.
1949 1949 *
1950 1950 * @param {integer} timestamp - when the save request started
1951 1951 */
1952 1952 Notebook.prototype._update_autosave_interval = function (start) {
1953 1953 var duration = (new Date().getTime() - start);
1954 1954 if (this.autosave_interval) {
1955 1955 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1956 1956 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1957 1957 // round to 10 seconds, otherwise we will be setting a new interval too often
1958 1958 interval = 10000 * Math.round(interval / 10000);
1959 1959 // set new interval, if it's changed
1960 if (interval != this.autosave_interval) {
1960 if (interval !== this.autosave_interval) {
1961 1961 this.set_autosave_interval(interval);
1962 1962 }
1963 1963 }
1964 1964 };
1965 1965
1966 1966 /**
1967 1967 * Explicitly trust the output of this notebook.
1968 1968 */
1969 1969 Notebook.prototype.trust_notebook = function () {
1970 1970 var body = $("<div>").append($("<p>")
1971 1971 .text("A trusted IPython notebook may execute hidden malicious code ")
1972 1972 .append($("<strong>")
1973 1973 .append(
1974 1974 $("<em>").text("when you open it")
1975 1975 )
1976 1976 ).append(".").append(
1977 1977 " Selecting trust will immediately reload this notebook in a trusted state."
1978 1978 ).append(
1979 1979 " For more information, see the "
1980 1980 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1981 1981 .text("IPython security documentation")
1982 1982 ).append(".")
1983 1983 );
1984 1984
1985 1985 var nb = this;
1986 1986 dialog.modal({
1987 1987 notebook: this,
1988 1988 keyboard_manager: this.keyboard_manager,
1989 1989 title: "Trust this notebook?",
1990 1990 body: body,
1991 1991
1992 1992 buttons: {
1993 1993 Cancel : {},
1994 1994 Trust : {
1995 1995 class : "btn-danger",
1996 1996 click : function () {
1997 1997 var cells = nb.get_cells();
1998 1998 for (var i = 0; i < cells.length; i++) {
1999 1999 var cell = cells[i];
2000 if (cell.cell_type == 'code') {
2000 if (cell.cell_type === 'code') {
2001 2001 cell.output_area.trusted = true;
2002 2002 }
2003 2003 }
2004 2004 nb.events.on('notebook_saved.Notebook', function () {
2005 2005 window.location.reload();
2006 2006 });
2007 2007 nb.save_notebook();
2008 2008 }
2009 2009 }
2010 2010 }
2011 2011 });
2012 2012 };
2013 2013
2014 2014 /**
2015 2015 * Make a copy of the current notebook.
2016 2016 */
2017 2017 Notebook.prototype.copy_notebook = function () {
2018 2018 var that = this;
2019 2019 var base_url = this.base_url;
2020 2020 var w = window.open();
2021 2021 var parent = utils.url_path_split(this.notebook_path)[0];
2022 2022 this.contents.copy(this.notebook_path, parent).then(
2023 2023 function (data) {
2024 2024 w.location = utils.url_join_encode(
2025 2025 base_url, 'notebooks', data.path
2026 2026 );
2027 2027 },
2028 2028 function(error) {
2029 2029 w.close();
2030 2030 that.events.trigger('notebook_copy_failed', error);
2031 2031 }
2032 2032 );
2033 2033 };
2034 2034
2035 2035 /**
2036 2036 * Rename the notebook.
2037 2037 * @param {string} new_name
2038 2038 * @return {Promise} promise that resolves when the notebook is renamed.
2039 2039 */
2040 2040 Notebook.prototype.rename = function (new_name) {
2041 2041 if (!new_name.match(/\.ipynb$/)) {
2042 2042 new_name = new_name + ".ipynb";
2043 2043 }
2044 2044
2045 2045 var that = this;
2046 2046 var parent = utils.url_path_split(this.notebook_path)[0];
2047 2047 var new_path = utils.url_path_join(parent, new_name);
2048 2048 return this.contents.rename(this.notebook_path, new_path).then(
2049 2049 function (json) {
2050 2050 that.notebook_name = json.name;
2051 2051 that.notebook_path = json.path;
2052 2052 that.session.rename_notebook(json.path);
2053 2053 that.events.trigger('notebook_renamed.Notebook', json);
2054 2054 }
2055 2055 );
2056 2056 };
2057 2057
2058 2058 /**
2059 2059 * Delete this notebook
2060 2060 */
2061 2061 Notebook.prototype.delete = function () {
2062 2062 this.contents.delete(this.notebook_path);
2063 2063 };
2064 2064
2065 2065 /**
2066 2066 * Request a notebook's data from the server.
2067 2067 *
2068 2068 * @param {string} notebook_path - A notebook to load
2069 2069 */
2070 2070 Notebook.prototype.load_notebook = function (notebook_path) {
2071 2071 this.notebook_path = notebook_path;
2072 2072 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2073 2073 this.events.trigger('notebook_loading.Notebook');
2074 2074 this.contents.get(notebook_path, {type: 'notebook'}).then(
2075 2075 $.proxy(this.load_notebook_success, this),
2076 2076 $.proxy(this.load_notebook_error, this)
2077 2077 );
2078 2078 };
2079 2079
2080 2080 /**
2081 2081 * Success callback for loading a notebook from the server.
2082 2082 *
2083 2083 * Load notebook data from the JSON response.
2084 2084 *
2085 2085 * @param {object} data JSON representation of a notebook
2086 2086 */
2087 2087 Notebook.prototype.load_notebook_success = function (data) {
2088 2088 var failed, msg;
2089 2089 try {
2090 2090 this.fromJSON(data);
2091 2091 } catch (e) {
2092 2092 failed = e;
2093 2093 console.log("Notebook failed to load from JSON:", e);
2094 2094 }
2095 2095 if (failed || data.message) {
2096 2096 // *either* fromJSON failed or validation failed
2097 2097 var body = $("<div>");
2098 2098 var title;
2099 2099 if (failed) {
2100 2100 title = "Notebook failed to load";
2101 2101 body.append($("<p>").text(
2102 2102 "The error was: "
2103 2103 )).append($("<div>").addClass("js-error").text(
2104 2104 failed.toString()
2105 2105 )).append($("<p>").text(
2106 2106 "See the error console for details."
2107 2107 ));
2108 2108 } else {
2109 2109 title = "Notebook validation failed";
2110 2110 }
2111 2111
2112 2112 if (data.message) {
2113 2113 if (failed) {
2114 2114 msg = "The notebook also failed validation:";
2115 2115 } else {
2116 2116 msg = "An invalid notebook may not function properly." +
2117 2117 " The validation error was:";
2118 2118 }
2119 2119 body.append($("<p>").text(
2120 2120 msg
2121 2121 )).append($("<div>").addClass("validation-error").append(
2122 2122 $("<pre>").text(data.message)
2123 2123 ));
2124 2124 }
2125 2125
2126 2126 dialog.modal({
2127 2127 notebook: this,
2128 2128 keyboard_manager: this.keyboard_manager,
2129 2129 title: title,
2130 2130 body: body,
2131 2131 buttons : {
2132 2132 OK : {
2133 2133 "class" : "btn-primary"
2134 2134 }
2135 2135 }
2136 2136 });
2137 2137 }
2138 2138 if (this.ncells() === 0) {
2139 2139 this.insert_cell_below('code');
2140 2140 this.edit_mode(0);
2141 2141 } else {
2142 2142 this.select(0);
2143 2143 this.handle_command_mode(this.get_cell(0));
2144 2144 }
2145 2145 this.set_dirty(false);
2146 2146 this.scroll_to_top();
2147 2147 this.writable = data.writable || false;
2148 2148 var nbmodel = data.content;
2149 2149 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2150 2150 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2151 2151 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2152 2152 var src;
2153 2153 if (nbmodel.nbformat > orig_nbformat) {
2154 2154 src = " an older notebook format ";
2155 2155 } else {
2156 2156 src = " a newer notebook format ";
2157 2157 }
2158 2158
2159 2159 msg = "This notebook has been converted from" + src +
2160 2160 "(v"+orig_nbformat+") to the current notebook " +
2161 2161 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2162 2162 "current notebook format will be used.";
2163 2163
2164 2164 if (nbmodel.nbformat > orig_nbformat) {
2165 2165 msg += " Older versions of IPython may not be able to read the new format.";
2166 2166 } else {
2167 2167 msg += " Some features of the original notebook may not be available.";
2168 2168 }
2169 2169 msg += " To preserve the original version, close the " +
2170 2170 "notebook without saving it.";
2171 2171 dialog.modal({
2172 2172 notebook: this,
2173 2173 keyboard_manager: this.keyboard_manager,
2174 2174 title : "Notebook converted",
2175 2175 body : msg,
2176 2176 buttons : {
2177 2177 OK : {
2178 2178 class : "btn-primary"
2179 2179 }
2180 2180 }
2181 2181 });
2182 2182 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2183 2183 this.nbformat_minor = nbmodel.nbformat_minor;
2184 2184 }
2185 2185
2186 2186 // Create the session after the notebook is completely loaded to prevent
2187 2187 // code execution upon loading, which is a security risk.
2188 2188 if (this.session === null) {
2189 2189 var kernel_name;
2190 2190 if (this.metadata.kernelspec) {
2191 2191 var kernelspec = this.metadata.kernelspec || {};
2192 2192 kernel_name = kernelspec.name;
2193 2193 } else {
2194 2194 kernel_name = utils.get_url_param('kernel_name');
2195 2195 }
2196 2196 this.start_session(kernel_name);
2197 2197 }
2198 2198 // load our checkpoint list
2199 2199 this.list_checkpoints();
2200 2200
2201 2201 // load toolbar state
2202 2202 if (this.metadata.celltoolbar) {
2203 2203 celltoolbar.CellToolbar.global_show();
2204 2204 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2205 2205 } else {
2206 2206 celltoolbar.CellToolbar.global_hide();
2207 2207 }
2208 2208
2209 2209 if (!this.writable) {
2210 2210 this.set_autosave_interval(0);
2211 2211 this.events.trigger('notebook_read_only.Notebook');
2212 2212 }
2213 2213
2214 2214 // now that we're fully loaded, it is safe to restore save functionality
2215 2215 this._fully_loaded = true;
2216 2216 this.events.trigger('notebook_loaded.Notebook');
2217 2217 };
2218 2218
2219 2219 Notebook.prototype.set_kernelselector = function(k_selector){
2220 2220 this.kernel_selector = k_selector;
2221 2221 };
2222 2222
2223 2223 /**
2224 2224 * Failure callback for loading a notebook from the server.
2225 2225 *
2226 2226 * @param {Error} error
2227 2227 */
2228 2228 Notebook.prototype.load_notebook_error = function (error) {
2229 2229 this.events.trigger('notebook_load_failed.Notebook', error);
2230 2230 var msg;
2231 2231 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2232 2232 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2233 2233 msg = "An unknown error occurred while loading this notebook. " +
2234 2234 "This version can load notebook formats " +
2235 2235 "v" + this.nbformat + " or earlier. See the server log for details.";
2236 2236 } else {
2237 2237 msg = error.message;
2238 2238 }
2239 2239 dialog.modal({
2240 2240 notebook: this,
2241 2241 keyboard_manager: this.keyboard_manager,
2242 2242 title: "Error loading notebook",
2243 2243 body : msg,
2244 2244 buttons : {
2245 2245 "OK": {}
2246 2246 }
2247 2247 });
2248 2248 };
2249 2249
2250 2250 /********************* checkpoint-related ********************/
2251 2251
2252 2252 /**
2253 2253 * Save the notebook then immediately create a checkpoint.
2254 2254 */
2255 2255 Notebook.prototype.save_checkpoint = function () {
2256 2256 this._checkpoint_after_save = true;
2257 2257 this.save_notebook();
2258 2258 };
2259 2259
2260 2260 /**
2261 2261 * Add a checkpoint for this notebook.
2262 2262 */
2263 2263 Notebook.prototype.add_checkpoint = function (checkpoint) {
2264 2264 var found = false;
2265 2265 for (var i = 0; i < this.checkpoints.length; i++) {
2266 2266 var existing = this.checkpoints[i];
2267 if (existing.id == checkpoint.id) {
2267 if (existing.id === checkpoint.id) {
2268 2268 found = true;
2269 2269 this.checkpoints[i] = checkpoint;
2270 2270 break;
2271 2271 }
2272 2272 }
2273 2273 if (!found) {
2274 2274 this.checkpoints.push(checkpoint);
2275 2275 }
2276 2276 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2277 2277 };
2278 2278
2279 2279 /**
2280 2280 * List checkpoints for this notebook.
2281 2281 */
2282 2282 Notebook.prototype.list_checkpoints = function () {
2283 2283 var that = this;
2284 2284 this.contents.list_checkpoints(this.notebook_path).then(
2285 2285 $.proxy(this.list_checkpoints_success, this),
2286 2286 function(error) {
2287 2287 that.events.trigger('list_checkpoints_failed.Notebook', error);
2288 2288 }
2289 2289 );
2290 2290 };
2291 2291
2292 2292 /**
2293 2293 * Success callback for listing checkpoints.
2294 2294 *
2295 2295 * @param {object} data - JSON representation of a checkpoint
2296 2296 */
2297 2297 Notebook.prototype.list_checkpoints_success = function (data) {
2298 2298 this.checkpoints = data;
2299 2299 if (data.length) {
2300 2300 this.last_checkpoint = data[data.length - 1];
2301 2301 } else {
2302 2302 this.last_checkpoint = null;
2303 2303 }
2304 2304 this.events.trigger('checkpoints_listed.Notebook', [data]);
2305 2305 };
2306 2306
2307 2307 /**
2308 2308 * Create a checkpoint of this notebook on the server from the most recent save.
2309 2309 */
2310 2310 Notebook.prototype.create_checkpoint = function () {
2311 2311 var that = this;
2312 2312 this.contents.create_checkpoint(this.notebook_path).then(
2313 2313 $.proxy(this.create_checkpoint_success, this),
2314 2314 function (error) {
2315 2315 that.events.trigger('checkpoint_failed.Notebook', error);
2316 2316 }
2317 2317 );
2318 2318 };
2319 2319
2320 2320 /**
2321 2321 * Success callback for creating a checkpoint.
2322 2322 *
2323 2323 * @param {object} data - JSON representation of a checkpoint
2324 2324 */
2325 2325 Notebook.prototype.create_checkpoint_success = function (data) {
2326 2326 this.add_checkpoint(data);
2327 2327 this.events.trigger('checkpoint_created.Notebook', data);
2328 2328 };
2329 2329
2330 2330 /**
2331 2331 * Display the restore checkpoint dialog
2332 2332 * @param {string} checkpoint ID
2333 2333 */
2334 2334 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2335 2335 var that = this;
2336 2336 checkpoint = checkpoint || this.last_checkpoint;
2337 2337 if ( ! checkpoint ) {
2338 2338 console.log("restore dialog, but no checkpoint to restore to!");
2339 2339 return;
2340 2340 }
2341 2341 var body = $('<div/>').append(
2342 2342 $('<p/>').addClass("p-space").text(
2343 2343 "Are you sure you want to revert the notebook to " +
2344 2344 "the latest checkpoint?"
2345 2345 ).append(
2346 2346 $("<strong/>").text(
2347 2347 " This cannot be undone."
2348 2348 )
2349 2349 )
2350 2350 ).append(
2351 2351 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2352 2352 ).append(
2353 2353 $('<p/>').addClass("p-space").text(
2354 2354 Date(checkpoint.last_modified)
2355 2355 ).css("text-align", "center")
2356 2356 );
2357 2357
2358 2358 dialog.modal({
2359 2359 notebook: this,
2360 2360 keyboard_manager: this.keyboard_manager,
2361 2361 title : "Revert notebook to checkpoint",
2362 2362 body : body,
2363 2363 buttons : {
2364 2364 Revert : {
2365 2365 class : "btn-danger",
2366 2366 click : function () {
2367 2367 that.restore_checkpoint(checkpoint.id);
2368 2368 }
2369 2369 },
2370 2370 Cancel : {}
2371 2371 }
2372 2372 });
2373 2373 };
2374 2374
2375 2375 /**
2376 2376 * Restore the notebook to a checkpoint state.
2377 2377 *
2378 2378 * @param {string} checkpoint ID
2379 2379 */
2380 2380 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2381 2381 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2382 2382 var that = this;
2383 2383 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2384 2384 $.proxy(this.restore_checkpoint_success, this),
2385 2385 function (error) {
2386 2386 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2387 2387 }
2388 2388 );
2389 2389 };
2390 2390
2391 2391 /**
2392 2392 * Success callback for restoring a notebook to a checkpoint.
2393 2393 */
2394 2394 Notebook.prototype.restore_checkpoint_success = function () {
2395 2395 this.events.trigger('checkpoint_restored.Notebook');
2396 2396 this.load_notebook(this.notebook_path);
2397 2397 };
2398 2398
2399 2399 /**
2400 2400 * Delete a notebook checkpoint.
2401 2401 *
2402 2402 * @param {string} checkpoint ID
2403 2403 */
2404 2404 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2405 2405 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2406 2406 var that = this;
2407 2407 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2408 2408 $.proxy(this.delete_checkpoint_success, this),
2409 2409 function (error) {
2410 2410 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2411 2411 }
2412 2412 );
2413 2413 };
2414 2414
2415 2415 /**
2416 2416 * Success callback for deleting a notebook checkpoint.
2417 2417 */
2418 2418 Notebook.prototype.delete_checkpoint_success = function () {
2419 2419 this.events.trigger('checkpoint_deleted.Notebook');
2420 2420 this.load_notebook(this.notebook_path);
2421 2421 };
2422 2422
2423 2423
2424 2424 // For backwards compatability.
2425 2425 IPython.Notebook = Notebook;
2426 2426
2427 2427 return {'Notebook': Notebook};
2428 2428 });
@@ -1,375 +1,374 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'base/js/utils',
7 7 'jquery',
8 8 'notebook/js/cell',
9 9 'base/js/security',
10 10 'services/config',
11 11 'notebook/js/mathjaxutils',
12 12 'notebook/js/celltoolbar',
13 13 'components/marked/lib/marked',
14 14 'codemirror/lib/codemirror',
15 15 'codemirror/mode/gfm/gfm',
16 16 'notebook/js/codemirror-ipythongfm'
17 17 ], function(IPython,
18 18 utils,
19 19 $,
20 20 cell,
21 21 security,
22 22 configmod,
23 23 mathjaxutils,
24 24 celltoolbar,
25 25 marked,
26 26 CodeMirror,
27 27 gfm,
28 28 ipgfm
29 29 ) {
30 30 "use strict";
31 31 var Cell = cell.Cell;
32 32
33 33 var TextCell = function (options) {
34 34 /**
35 35 * Constructor
36 36 *
37 37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
38 38 * and cell type is 'text' cell start as not redered.
39 39 *
40 40 * Parameters:
41 41 * options: dictionary
42 42 * Dictionary of keyword arguments.
43 43 * events: $(Events) instance
44 44 * config: dictionary
45 45 * keyboard_manager: KeyboardManager instance
46 46 * notebook: Notebook instance
47 47 */
48 48 options = options || {};
49 49
50 50 // in all TextCell/Cell subclasses
51 51 // do not assign most of members here, just pass it down
52 52 // in the options dict potentially overwriting what you wish.
53 53 // they will be assigned in the base class.
54 54 this.notebook = options.notebook;
55 55 this.events = options.events;
56 56 this.config = options.config;
57 57
58 58 // we cannot put this as a class key as it has handle to "this".
59 59 var config = utils.mergeopt(TextCell, this.config);
60 60 Cell.apply(this, [{
61 61 config: config,
62 62 keyboard_manager: options.keyboard_manager,
63 63 events: this.events}]);
64 64
65 65 this.cell_type = this.cell_type || 'text';
66 66 mathjaxutils = mathjaxutils;
67 67 this.rendered = false;
68 68 };
69 69
70 70 TextCell.prototype = Object.create(Cell.prototype);
71 71
72 72 TextCell.options_default = {
73 73 cm_config : {
74 74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
75 75 mode: 'htmlmixed',
76 76 lineWrapping : true,
77 77 }
78 78 };
79 79
80 80
81 81 /**
82 82 * Create the DOM element of the TextCell
83 83 * @method create_element
84 84 * @private
85 85 */
86 86 TextCell.prototype.create_element = function () {
87 87 Cell.prototype.create_element.apply(this, arguments);
88 88
89 89 var cell = $("<div>").addClass('cell text_cell');
90 90 cell.attr('tabindex','2');
91 91
92 92 var prompt = $('<div/>').addClass('prompt input_prompt');
93 93 cell.append(prompt);
94 94 var inner_cell = $('<div/>').addClass('inner_cell');
95 95 this.celltoolbar = new celltoolbar.CellToolbar({
96 96 cell: this,
97 97 notebook: this.notebook});
98 98 inner_cell.append(this.celltoolbar.element);
99 99 var input_area = $('<div/>').addClass('input_area');
100 100 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
101 101 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
102 102 // The tabindex=-1 makes this div focusable.
103 103 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
104 104 .attr('tabindex','-1');
105 105 inner_cell.append(input_area).append(render_area);
106 106 cell.append(inner_cell);
107 107 this.element = cell;
108 108 };
109 109
110 110
111 111 // Cell level actions
112 112
113 113 TextCell.prototype.select = function () {
114 114 var cont = Cell.prototype.select.apply(this);
115 115 if (cont) {
116 116 if (this.mode === 'edit') {
117 117 this.code_mirror.refresh();
118 118 }
119 119 }
120 120 return cont;
121 121 };
122 122
123 123 TextCell.prototype.unrender = function () {
124 124 if (this.read_only) return;
125 125 var cont = Cell.prototype.unrender.apply(this);
126 126 if (cont) {
127 127 var text_cell = this.element;
128 var output = text_cell.find("div.text_cell_render");
129 128 if (this.get_text() === this.placeholder) {
130 129 this.set_text('');
131 130 }
132 131 this.refresh();
133 132 }
134 133 return cont;
135 134 };
136 135
137 136 TextCell.prototype.execute = function () {
138 137 this.render();
139 138 };
140 139
141 140 /**
142 141 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
143 142 * @method get_text
144 143 * @retrun {string} CodeMirror current text value
145 144 */
146 145 TextCell.prototype.get_text = function() {
147 146 return this.code_mirror.getValue();
148 147 };
149 148
150 149 /**
151 150 * @param {string} text - Codemiror text value
152 151 * @see TextCell#get_text
153 152 * @method set_text
154 153 * */
155 154 TextCell.prototype.set_text = function(text) {
156 155 this.code_mirror.setValue(text);
157 156 this.unrender();
158 157 this.code_mirror.refresh();
159 158 };
160 159
161 160 /**
162 161 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
163 162 * @method get_rendered
164 163 * */
165 164 TextCell.prototype.get_rendered = function() {
166 165 return this.element.find('div.text_cell_render').html();
167 166 };
168 167
169 168 /**
170 169 * @method set_rendered
171 170 */
172 171 TextCell.prototype.set_rendered = function(text) {
173 172 this.element.find('div.text_cell_render').html(text);
174 173 };
175 174
176 175
177 176 /**
178 177 * Create Text cell from JSON
179 178 * @param {json} data - JSON serialized text-cell
180 179 * @method fromJSON
181 180 */
182 181 TextCell.prototype.fromJSON = function (data) {
183 182 Cell.prototype.fromJSON.apply(this, arguments);
184 183 if (data.cell_type === this.cell_type) {
185 184 if (data.source !== undefined) {
186 185 this.set_text(data.source);
187 186 // make this value the starting point, so that we can only undo
188 187 // to this state, instead of a blank cell
189 188 this.code_mirror.clearHistory();
190 189 // TODO: This HTML needs to be treated as potentially dangerous
191 190 // user input and should be handled before set_rendered.
192 191 this.set_rendered(data.rendered || '');
193 192 this.rendered = false;
194 193 this.render();
195 194 }
196 195 }
197 196 };
198 197
199 198 /** Generate JSON from cell
200 199 * @return {object} cell data serialised to json
201 200 */
202 201 TextCell.prototype.toJSON = function () {
203 202 var data = Cell.prototype.toJSON.apply(this);
204 203 data.source = this.get_text();
205 204 if (data.source == this.placeholder) {
206 205 data.source = "";
207 206 }
208 207 return data;
209 208 };
210 209
211 210
212 211 var MarkdownCell = function (options) {
213 212 /**
214 213 * Constructor
215 214 *
216 215 * Parameters:
217 216 * options: dictionary
218 217 * Dictionary of keyword arguments.
219 218 * events: $(Events) instance
220 219 * config: ConfigSection instance
221 220 * keyboard_manager: KeyboardManager instance
222 221 * notebook: Notebook instance
223 222 */
224 223 options = options || {};
225 224 var config = utils.mergeopt(MarkdownCell, {});
226 225 TextCell.apply(this, [$.extend({}, options, {config: config})]);
227 226
228 227 this.class_config = new configmod.ConfigWithDefaults(options.config,
229 228 {}, 'MarkdownCell');
230 229 this.cell_type = 'markdown';
231 230 };
232 231
233 232 MarkdownCell.options_default = {
234 233 cm_config: {
235 234 mode: 'ipythongfm'
236 235 },
237 236 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
238 237 };
239 238
240 239 MarkdownCell.prototype = Object.create(TextCell.prototype);
241 240
242 241 MarkdownCell.prototype.set_heading_level = function (level) {
243 242 /**
244 243 * make a markdown cell a heading
245 244 */
246 245 level = level || 1;
247 246 var source = this.get_text();
248 247 source = source.replace(/^(#*)\s?/,
249 248 new Array(level + 1).join('#') + ' ');
250 249 this.set_text(source);
251 250 this.refresh();
252 251 if (this.rendered) {
253 252 this.render();
254 253 }
255 254 };
256 255
257 256 /**
258 257 * @method render
259 258 */
260 259 MarkdownCell.prototype.render = function () {
261 260 var cont = TextCell.prototype.render.apply(this);
262 261 if (cont) {
263 262 var that = this;
264 263 var text = this.get_text();
265 264 var math = null;
266 265 if (text === "") { text = this.placeholder; }
267 266 var text_and_math = mathjaxutils.remove_math(text);
268 267 text = text_and_math[0];
269 268 math = text_and_math[1];
270 269 marked(text, function (err, html) {
271 270 html = mathjaxutils.replace_math(html, math);
272 271 html = security.sanitize_html(html);
273 272 html = $($.parseHTML(html));
274 273 // add anchors to headings
275 274 html.find(":header").addBack(":header").each(function (i, h) {
276 275 h = $(h);
277 276 var hash = h.text().replace(/ /g, '-');
278 277 h.attr('id', hash);
279 278 h.append(
280 279 $('<a/>')
281 280 .addClass('anchor-link')
282 281 .attr('href', '#' + hash)
283 282 .text('ΒΆ')
284 283 );
285 284 });
286 285 // links in markdown cells should open in new tabs
287 286 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
288 287 that.set_rendered(html);
289 288 that.typeset();
290 289 that.events.trigger("rendered.MarkdownCell", {cell: that});
291 290 });
292 291 }
293 292 return cont;
294 293 };
295 294
296 295
297 296 var RawCell = function (options) {
298 297 /**
299 298 * Constructor
300 299 *
301 300 * Parameters:
302 301 * options: dictionary
303 302 * Dictionary of keyword arguments.
304 303 * events: $(Events) instance
305 304 * config: ConfigSection instance
306 305 * keyboard_manager: KeyboardManager instance
307 306 * notebook: Notebook instance
308 307 */
309 308 options = options || {};
310 309 var config = utils.mergeopt(RawCell, {});
311 310 TextCell.apply(this, [$.extend({}, options, {config: config})]);
312 311
313 312 this.class_config = new configmod.ConfigWithDefaults(options.config,
314 313 RawCell.config_defaults, 'RawCell');
315 314 this.cell_type = 'raw';
316 315 };
317 316
318 317 RawCell.options_default = {
319 318 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
320 319 "It will not be rendered in the notebook. " +
321 320 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
322 321 };
323 322
324 323 RawCell.config_defaults = {
325 324 highlight_modes : {
326 325 'diff' :{'reg':[/^diff/]}
327 326 },
328 327 };
329 328
330 329 RawCell.prototype = Object.create(TextCell.prototype);
331 330
332 331 /** @method bind_events **/
333 332 RawCell.prototype.bind_events = function () {
334 333 TextCell.prototype.bind_events.apply(this);
335 334 var that = this;
336 335 this.element.focusout(function() {
337 336 that.auto_highlight();
338 337 that.render();
339 338 });
340 339
341 340 this.code_mirror.on('focus', function() { that.unrender(); });
342 341 };
343 342
344 343 /**
345 344 * Trigger autodetection of highlight scheme for current cell
346 345 * @method auto_highlight
347 346 */
348 347 RawCell.prototype.auto_highlight = function () {
349 348 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
350 349 };
351 350
352 351 /** @method render **/
353 352 RawCell.prototype.render = function () {
354 353 var cont = TextCell.prototype.render.apply(this);
355 354 if (cont){
356 355 var text = this.get_text();
357 356 if (text === "") { text = this.placeholder; }
358 357 this.set_text(text);
359 358 this.element.removeClass('rendered');
360 359 }
361 360 return cont;
362 361 };
363 362
364 363 // Backwards compatability.
365 364 IPython.TextCell = TextCell;
366 365 IPython.MarkdownCell = MarkdownCell;
367 366 IPython.RawCell = RawCell;
368 367
369 368 var textcell = {
370 369 TextCell: TextCell,
371 370 MarkdownCell: MarkdownCell,
372 371 RawCell: RawCell
373 372 };
374 373 return textcell;
375 374 });
@@ -1,53 +1,54 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 require([
5 5 'jquery',
6 6 'termjs',
7 7 'base/js/utils',
8 8 'base/js/page',
9 9 'terminal/js/terminado',
10 10 'custom/custom',
11 11 ], function(
12 12 $,
13 13 termjs,
14 14 utils,
15 15 page,
16 16 terminado
17 17 ){
18 "use strict";
18 19 page = new page.Page();
19 20 // Test size: 25x80
20 21 var termRowHeight = function(){ return 1.00 * $("#dummy-screen")[0].offsetHeight / 25;};
21 22 // 1.02 here arrived at by trial and error to make the spacing look right
22 23 var termColWidth = function() { return 1.02 * $("#dummy-screen-rows")[0].offsetWidth / 80;};
23 24
24 25 var base_url = utils.get_body_data('baseUrl');
25 26 var ws_path = utils.get_body_data('wsPath');
26 27 var ws_url = location.protocol.replace('http', 'ws') + "//" + location.host
27 28 + base_url + ws_path;
28 29
29 30 var header = $("#header")[0]
30 31 function calculate_size() {
31 height = window.innerHeight - header.offsetHeight;
32 width = $('#terminado-container').width();
32 var height = window.innerHeight - header.offsetHeight;
33 var width = $('#terminado-container').width();
33 34 var rows = Math.min(1000, Math.max(20, Math.floor(height/termRowHeight())-1));
34 35 var cols = Math.min(1000, Math.max(40, Math.floor(width/termColWidth())-1));
35 36 console.log("resize to :", rows , 'rows by ', cols, 'columns');
36 37 return {rows: rows, cols: cols};
37 38 }
38 39
39 40 page.show_header();
40 41
41 size = calculate_size();
42 var size = calculate_size();
42 43 var terminal = terminado.make_terminal($("#terminado-container")[0], size, ws_url);
43 44
44 45 page.show_site();
45 46
46 47 window.onresize = function() {
47 48 var geom = calculate_size();
48 49 terminal.term.resize(geom.cols, geom.rows);
49 50 terminal.socket.send(JSON.stringify(["set_size", geom.rows, geom.cols,
50 51 window.innerHeight, window.innerWidth]));
51 52 };
52 53
53 54 });
@@ -1,39 +1,40 b''
1 1 define ([], function() {
2 "use strict";
2 3 function make_terminal(element, size, ws_url) {
3 4 var ws = new WebSocket(ws_url);
4 5 var term = new Terminal({
5 6 cols: size.cols,
6 7 rows: size.rows,
7 8 screenKeys: true,
8 9 useStyle: false
9 10 });
10 11 ws.onopen = function(event) {
11 12 ws.send(JSON.stringify(["set_size", size.rows, size.cols,
12 13 window.innerHeight, window.innerWidth]));
13 14 term.on('data', function(data) {
14 15 ws.send(JSON.stringify(['stdin', data]));
15 16 });
16 17
17 18 term.on('title', function(title) {
18 19 document.title = title;
19 20 });
20 21
21 22 term.open(element);
22 23
23 24 ws.onmessage = function(event) {
24 json_msg = JSON.parse(event.data);
25 var json_msg = JSON.parse(event.data);
25 26 switch(json_msg[0]) {
26 27 case "stdout":
27 28 term.write(json_msg[1]);
28 29 break;
29 30 case "disconnect":
30 31 term.write("\r\n\r\n[CLOSED]\r\n");
31 32 break;
32 33 }
33 34 };
34 35 };
35 36 return {socket: ws, term: term};
36 37 }
37 38
38 39 return {make_terminal: make_terminal};
39 40 });
@@ -1,122 +1,121 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'base/js/utils',
7 7 'jquery',
8 8 'tree/js/notebooklist',
9 9 ], function(IPython, utils, $, notebooklist) {
10 10 "use strict";
11 11
12 12 var TerminalList = function (selector, options) {
13 13 /**
14 14 * Constructor
15 15 *
16 16 * Parameters:
17 17 * selector: string
18 18 * options: dictionary
19 19 * Dictionary of keyword arguments.
20 20 * base_url: string
21 21 */
22 22 this.base_url = options.base_url || utils.get_body_data("baseUrl");
23 23 this.element_name = options.element_name || 'terminal';
24 24 this.selector = selector;
25 25 this.terminals = [];
26 26 if (this.selector !== undefined) {
27 27 this.element = $(selector);
28 28 this.style();
29 29 this.bind_events();
30 30 this.load_terminals();
31 31 }
32 32 };
33 33
34 34 TerminalList.prototype = Object.create(notebooklist.NotebookList.prototype);
35 35
36 36 TerminalList.prototype.bind_events = function () {
37 37 var that = this;
38 38 $('#refresh_' + this.element_name + '_list').click(function () {
39 39 that.load_terminals();
40 40 });
41 41 $('#new_terminal').click($.proxy(this.new_terminal, this));
42 42 };
43 43
44 44 TerminalList.prototype.new_terminal = function () {
45 45 var w = window.open();
46 46 var base_url = this.base_url;
47 47 var settings = {
48 48 type : "POST",
49 49 dataType: "json",
50 50 success : function (data, status, xhr) {
51 51 var name = data.name;
52 52 w.location = utils.url_join_encode(base_url, 'terminals', name);
53 53 },
54 54 error : function(jqXHR, status, error){
55 55 w.close();
56 56 utils.log_ajax_error(jqXHR, status, error);
57 57 },
58 58 };
59 59 var url = utils.url_join_encode(
60 60 this.base_url,
61 61 'api/terminals'
62 62 );
63 63 $.ajax(url, settings);
64 64 };
65 65
66 66 TerminalList.prototype.load_terminals = function() {
67 var that = this;
68 67 var url = utils.url_join_encode(this.base_url, 'api/terminals');
69 68 $.ajax(url, {
70 69 type: "GET",
71 70 cache: false,
72 71 dataType: "json",
73 72 success: $.proxy(this.terminals_loaded, this),
74 73 error : utils.log_ajax_error
75 74 });
76 75 };
77 76
78 77 TerminalList.prototype.terminals_loaded = function (data) {
79 78 this.terminals = data;
80 79 this.clear_list();
81 var item, path_name, term;
80 var item, term;
82 81 for (var i=0; i < this.terminals.length; i++) {
83 82 term = this.terminals[i];
84 83 item = this.new_item(-1);
85 84 this.add_link(term.name, item);
86 85 this.add_shutdown_button(term.name, item);
87 86 }
88 87 $('#terminal_list_header').toggle(data.length === 0);
89 88 };
90 89
91 90 TerminalList.prototype.add_link = function(name, item) {
92 91 item.data('term-name', name);
93 92 item.find(".item_name").text("terminals/" + name);
94 93 item.find(".item_icon").addClass("fa fa-terminal");
95 94 var link = item.find("a.item_link")
96 95 .attr('href', utils.url_join_encode(this.base_url, "terminals", name));
97 96 link.attr('target', '_blank');
98 97 this.add_shutdown_button(name, item);
99 98 };
100 99
101 100 TerminalList.prototype.add_shutdown_button = function(name, item) {
102 101 var that = this;
103 102 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-warning").
104 103 click(function (e) {
105 104 var settings = {
106 105 processData : false,
107 106 type : "DELETE",
108 107 dataType : "json",
109 108 success : function () {
110 109 that.load_terminals();
111 110 },
112 111 error : utils.log_ajax_error,
113 112 };
114 113 var url = utils.url_join_encode(that.base_url, 'api/terminals', name);
115 114 $.ajax(url, settings);
116 115 return false;
117 116 });
118 117 item.find(".item_buttons").text("").append(shutdown_button);
119 118 };
120 119
121 120 return {TerminalList: TerminalList};
122 121 });
@@ -1,762 +1,762 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(["widgets/js/manager",
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/utils",
9 9 "base/js/namespace",
10 10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
11 "use strict";
11 12
12 13 var WidgetModel = Backbone.Model.extend({
13 14 constructor: function (widget_manager, model_id, comm) {
14 15 /**
15 16 * Constructor
16 17 *
17 18 * Creates a WidgetModel instance.
18 19 *
19 20 * Parameters
20 21 * ----------
21 22 * widget_manager : WidgetManager instance
22 23 * model_id : string
23 24 * An ID unique to this model.
24 25 * comm : Comm instance (optional)
25 26 */
26 27 this.widget_manager = widget_manager;
27 28 this.state_change = Promise.resolve();
28 29 this._buffered_state_diff = {};
29 30 this.pending_msgs = 0;
30 31 this.msg_buffer = null;
31 32 this.state_lock = null;
32 33 this.id = model_id;
33 34 this.views = {};
34 35 this._resolve_received_state = {};
35 36
36 37 if (comm !== undefined) {
37 38 // Remember comm associated with the model.
38 39 this.comm = comm;
39 40 comm.model = this;
40 41
41 42 // Hook comm messages up to model.
42 43 comm.on_close($.proxy(this._handle_comm_closed, this));
43 44 comm.on_msg($.proxy(this._handle_comm_msg, this));
44 45
45 46 // Assume the comm is alive.
46 47 this.set_comm_live(true);
47 48 } else {
48 49 this.set_comm_live(false);
49 50 }
50 51
51 52 // Listen for the events that lead to the websocket being terminated.
52 53 var that = this;
53 54 var died = function() {
54 55 that.set_comm_live(false);
55 56 };
56 57 widget_manager.notebook.events.on('kernel_disconnected.Kernel', died);
57 58 widget_manager.notebook.events.on('kernel_killed.Kernel', died);
58 59 widget_manager.notebook.events.on('kernel_restarting.Kernel', died);
59 60 widget_manager.notebook.events.on('kernel_dead.Kernel', died);
60 61
61 62 return Backbone.Model.apply(this);
62 63 },
63 64
64 65 send: function (content, callbacks) {
65 66 /**
66 67 * Send a custom msg over the comm.
67 68 */
68 69 if (this.comm !== undefined) {
69 70 var data = {method: 'custom', content: content};
70 71 this.comm.send(data, callbacks);
71 72 this.pending_msgs++;
72 73 }
73 74 },
74 75
75 76 request_state: function(callbacks) {
76 77 /**
77 78 * Request a state push from the back-end.
78 79 */
79 80 if (!this.comm) {
80 81 console.error("Could not request_state because comm doesn't exist!");
81 82 return;
82 83 }
83 84
84 85 var msg_id = this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks());
85 86
86 87 // Promise that is resolved when a state is received
87 88 // from the back-end.
88 89 var that = this;
89 90 var received_state = new Promise(function(resolve) {
90 91 that._resolve_received_state[msg_id] = resolve;
91 92 });
92 93 return received_state;
93 94 },
94 95
95 96 set_comm_live: function(live) {
96 97 /**
97 98 * Change the comm_live state of the model.
98 99 */
99 100 if (this.comm_live === undefined || this.comm_live != live) {
100 101 this.comm_live = live;
101 102 this.trigger(live ? 'comm:live' : 'comm:dead', {model: this});
102 103 }
103 104 },
104 105
105 106 close: function(comm_closed) {
106 107 /**
107 108 * Close model
108 109 */
109 110 if (this.comm && !comm_closed) {
110 111 this.comm.close();
111 112 }
112 113 this.stopListening();
113 114 this.trigger('destroy', this);
114 115 delete this.comm.model; // Delete ref so GC will collect widget model.
115 116 delete this.comm;
116 117 delete this.model_id; // Delete id from model so widget manager cleans up.
117 118 _.each(this.views, function(v, id, views) {
118 119 v.then(function(view) {
119 120 view.remove();
120 121 delete views[id];
121 122 });
122 123 });
123 124 },
124 125
125 126 _handle_comm_closed: function (msg) {
126 127 /**
127 128 * Handle when a widget is closed.
128 129 */
129 130 this.trigger('comm:close');
130 131 this.close(true);
131 132 },
132 133
133 134 _handle_comm_msg: function (msg) {
134 135 /**
135 136 * Handle incoming comm msg.
136 137 */
137 138 var method = msg.content.data.method;
138 139 var that = this;
139 140 switch (method) {
140 141 case 'update':
141 142 this.state_change = this.state_change
142 143 .then(function() {
143 144 return that.set_state(msg.content.data.state);
144 145 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true))
145 146 .then(function() {
146 147 var parent_id = msg.parent_header.msg_id;
147 148 if (that._resolve_received_state[parent_id] !== undefined) {
148 149 that._resolve_received_state[parent_id].call();
149 150 delete that._resolve_received_state[parent_id];
150 151 }
151 152 }).catch(utils.reject("Couldn't resolve state request promise", true));
152 153 break;
153 154 case 'custom':
154 155 this.trigger('msg:custom', msg.content.data.content);
155 156 break;
156 157 case 'display':
157 158 this.widget_manager.display_view(msg, this)
158 159 .catch(utils.reject('Could not process display view msg', true));
159 160 break;
160 161 }
161 162 },
162 163
163 164 set_state: function (state) {
164 165 var that = this;
165 166 // Handle when a widget is updated via the python side.
166 167 return this._unpack_models(state).then(function(state) {
167 168 that.state_lock = state;
168 169 try {
169 170 WidgetModel.__super__.set.call(that, state);
170 171 } finally {
171 172 that.state_lock = null;
172 173 }
173 174 }).catch(utils.reject("Couldn't set model state", true));
174 175 },
175 176
176 177 get_state: function() {
177 178 // Get the serializable state of the model.
178 state = this.toJSON();
179 var state = this.toJSON();
179 180 for (var key in state) {
180 181 if (state.hasOwnProperty(key)) {
181 182 state[key] = this._pack_models(state[key]);
182 183 }
183 184 }
184 185 return state;
185 186 },
186 187
187 188 _handle_status: function (msg, callbacks) {
188 189 /**
189 190 * Handle status msgs.
190 191 *
191 192 * execution_state : ('busy', 'idle', 'starting')
192 193 */
193 194 if (this.comm !== undefined) {
194 195 if (msg.content.execution_state ==='idle') {
195 196 // Send buffer if this message caused another message to be
196 197 // throttled.
197 198 if (this.msg_buffer !== null &&
198 199 (this.get('msg_throttle') || 3) === this.pending_msgs) {
199 200 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
200 201 this.comm.send(data, callbacks);
201 202 this.msg_buffer = null;
202 203 } else {
203 204 --this.pending_msgs;
204 205 }
205 206 }
206 207 }
207 208 },
208 209
209 210 callbacks: function(view) {
210 211 /**
211 212 * Create msg callbacks for a comm msg.
212 213 */
213 214 var callbacks = this.widget_manager.callbacks(view);
214 215
215 216 if (callbacks.iopub === undefined) {
216 217 callbacks.iopub = {};
217 218 }
218 219
219 220 var that = this;
220 221 callbacks.iopub.status = function (msg) {
221 222 that._handle_status(msg, callbacks);
222 223 };
223 224 return callbacks;
224 225 },
225 226
226 227 set: function(key, val, options) {
227 228 /**
228 229 * Set a value.
229 230 */
230 231 var return_value = WidgetModel.__super__.set.apply(this, arguments);
231 232
232 233 // Backbone only remembers the diff of the most recent set()
233 234 // operation. Calling set multiple times in a row results in a
234 235 // loss of diff information. Here we keep our own running diff.
235 236 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
236 237 return return_value;
237 238 },
238 239
239 240 sync: function (method, model, options) {
240 241 /**
241 242 * Handle sync to the back-end. Called when a model.save() is called.
242 243 *
243 244 * Make sure a comm exists.
244 245 */
245 246 var error = options.error || function() {
246 247 console.error('Backbone sync error:', arguments);
247 248 };
248 249 if (this.comm === undefined) {
249 250 error();
250 251 return false;
251 252 }
252 253
253 254 // Delete any key value pairs that the back-end already knows about.
254 255 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
255 256 if (this.state_lock !== null) {
256 257 var keys = Object.keys(this.state_lock);
257 258 for (var i=0; i<keys.length; i++) {
258 259 var key = keys[i];
259 260 if (attrs[key] === this.state_lock[key]) {
260 261 delete attrs[key];
261 262 }
262 263 }
263 264 }
264 265
265 266 // Only sync if there are attributes to send to the back-end.
266 267 attrs = this._pack_models(attrs);
267 268 if (_.size(attrs) > 0) {
268 269
269 270 // If this message was sent via backbone itself, it will not
270 271 // have any callbacks. It's important that we create callbacks
271 272 // so we can listen for status messages, etc...
272 273 var callbacks = options.callbacks || this.callbacks();
273 274
274 275 // Check throttle.
275 276 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
276 277 // The throttle has been exceeded, buffer the current msg so
277 278 // it can be sent once the kernel has finished processing
278 279 // some of the existing messages.
279 280
280 281 // Combine updates if it is a 'patch' sync, otherwise replace updates
281 282 switch (method) {
282 283 case 'patch':
283 284 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
284 285 break;
285 286 case 'update':
286 287 case 'create':
287 288 this.msg_buffer = attrs;
288 289 break;
289 290 default:
290 291 error();
291 292 return false;
292 293 }
293 294 this.msg_buffer_callbacks = callbacks;
294 295
295 296 } else {
296 297 // We haven't exceeded the throttle, send the message like
297 298 // normal.
298 299 var data = {method: 'backbone', sync_data: attrs};
299 300 this.comm.send(data, callbacks);
300 301 this.pending_msgs++;
301 302 }
302 303 }
303 304 // Since the comm is a one-way communication, assume the message
304 305 // arrived. Don't call success since we don't have a model back from the server
305 306 // this means we miss out on the 'sync' event.
306 307 this._buffered_state_diff = {};
307 308 },
308 309
309 310 save_changes: function(callbacks) {
310 311 /**
311 312 * Push this model's state to the back-end
312 313 *
313 314 * This invokes a Backbone.Sync.
314 315 */
315 316 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
316 317 },
317 318
318 319 _pack_models: function(value) {
319 320 /**
320 321 * Replace models with model ids recursively.
321 322 */
322 323 var that = this;
323 324 var packed;
324 325 if (value instanceof Backbone.Model) {
325 326 return "IPY_MODEL_" + value.id;
326 327
327 328 } else if ($.isArray(value)) {
328 329 packed = [];
329 330 _.each(value, function(sub_value, key) {
330 331 packed.push(that._pack_models(sub_value));
331 332 });
332 333 return packed;
333 334 } else if (value instanceof Date || value instanceof String) {
334 335 return value;
335 336 } else if (value instanceof Object) {
336 337 packed = {};
337 338 _.each(value, function(sub_value, key) {
338 339 packed[key] = that._pack_models(sub_value);
339 340 });
340 341 return packed;
341 342
342 343 } else {
343 344 return value;
344 345 }
345 346 },
346 347
347 348 _unpack_models: function(value) {
348 349 /**
349 350 * Replace model ids with models recursively.
350 351 */
351 352 var that = this;
352 353 var unpacked;
353 354 if ($.isArray(value)) {
354 355 unpacked = [];
355 356 _.each(value, function(sub_value, key) {
356 357 unpacked.push(that._unpack_models(sub_value));
357 358 });
358 359 return Promise.all(unpacked);
359 360 } else if (value instanceof Object) {
360 361 unpacked = {};
361 362 _.each(value, function(sub_value, key) {
362 363 unpacked[key] = that._unpack_models(sub_value);
363 364 });
364 365 return utils.resolve_promises_dict(unpacked);
365 366 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
366 367 // get_model returns a promise already
367 368 return this.widget_manager.get_model(value.slice(10, value.length));
368 369 } else {
369 370 return Promise.resolve(value);
370 371 }
371 372 },
372 373
373 374 on_some_change: function(keys, callback, context) {
374 375 /**
375 376 * on_some_change(["key1", "key2"], foo, context) differs from
376 377 * on("change:key1 change:key2", foo, context).
377 378 * If the widget attributes key1 and key2 are both modified,
378 379 * the second form will result in foo being called twice
379 380 * while the first will call foo only once.
380 381 */
381 382 this.on('change', function() {
382 383 if (keys.some(this.hasChanged, this)) {
383 384 callback.apply(context);
384 385 }
385 386 }, this);
386 387
387 388 },
388 389 });
389 390 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
390 391
391 392
392 393 var WidgetView = Backbone.View.extend({
393 394 initialize: function(parameters) {
394 395 /**
395 396 * Public constructor.
396 397 */
397 398 this.model.on('change',this.update,this);
398 399
399 400 // Bubble the comm live events.
400 401 this.model.on('comm:live', function() {
401 402 this.trigger('comm:live', this);
402 403 }, this);
403 404 this.model.on('comm:dead', function() {
404 405 this.trigger('comm:dead', this);
405 406 }, this);
406 407
407 408 this.options = parameters.options;
408 409 this.on('displayed', function() {
409 410 this.is_displayed = true;
410 411 }, this);
411 412 },
412 413
413 414 update: function(){
414 415 /**
415 416 * Triggered on model change.
416 417 *
417 418 * Update view to be consistent with this.model
418 419 */
419 420 },
420 421
421 422 create_child_view: function(child_model, options) {
422 423 /**
423 424 * Create and promise that resolves to a child view of a given model
424 425 */
425 426 var that = this;
426 427 options = $.extend({ parent: this }, options || {});
427 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 431 callbacks: function(){
431 432 /**
432 433 * Create msg callbacks for a comm msg.
433 434 */
434 435 return this.model.callbacks(this);
435 436 },
436 437
437 438 render: function(){
438 439 /**
439 440 * Render the view.
440 441 *
441 442 * By default, this is only called the first time the view is created
442 443 */
443 444 },
444 445
445 446 send: function (content) {
446 447 /**
447 448 * Send a custom msg associated with this view.
448 449 */
449 450 this.model.send(content, this.callbacks());
450 451 },
451 452
452 453 touch: function () {
453 454 this.model.save_changes(this.callbacks());
454 455 },
455 456
456 457 after_displayed: function (callback, context) {
457 458 /**
458 459 * Calls the callback right away is the view is already displayed
459 460 * otherwise, register the callback to the 'displayed' event.
460 461 */
461 462 if (this.is_displayed) {
462 463 callback.apply(context);
463 464 } else {
464 465 this.on('displayed', callback, context);
465 466 }
466 467 },
467 468
468 469 remove: function () {
469 470 // Raise a remove event when the view is removed.
470 471 WidgetView.__super__.remove.apply(this, arguments);
471 472 this.trigger('remove');
472 473 }
473 474 });
474 475
475 476
476 477 var DOMWidgetView = WidgetView.extend({
477 478 initialize: function (parameters) {
478 479 /**
479 480 * Public constructor
480 481 */
481 482 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
482 483 this.model.on('change:visible', this.update_visible, this);
483 484 this.model.on('change:_css', this.update_css, this);
484 485
485 486 this.model.on('change:_dom_classes', function(model, new_classes) {
486 487 var old_classes = model.previous('_dom_classes');
487 488 this.update_classes(old_classes, new_classes);
488 489 }, this);
489 490
490 491 this.model.on('change:color', function (model, value) {
491 492 this.update_attr('color', value); }, this);
492 493
493 494 this.model.on('change:background_color', function (model, value) {
494 495 this.update_attr('background', value); }, this);
495 496
496 497 this.model.on('change:width', function (model, value) {
497 498 this.update_attr('width', value); }, this);
498 499
499 500 this.model.on('change:height', function (model, value) {
500 501 this.update_attr('height', value); }, this);
501 502
502 503 this.model.on('change:border_color', function (model, value) {
503 504 this.update_attr('border-color', value); }, this);
504 505
505 506 this.model.on('change:border_width', function (model, value) {
506 507 this.update_attr('border-width', value); }, this);
507 508
508 509 this.model.on('change:border_style', function (model, value) {
509 510 this.update_attr('border-style', value); }, this);
510 511
511 512 this.model.on('change:font_style', function (model, value) {
512 513 this.update_attr('font-style', value); }, this);
513 514
514 515 this.model.on('change:font_weight', function (model, value) {
515 516 this.update_attr('font-weight', value); }, this);
516 517
517 518 this.model.on('change:font_size', function (model, value) {
518 519 this.update_attr('font-size', this._default_px(value)); }, this);
519 520
520 521 this.model.on('change:font_family', function (model, value) {
521 522 this.update_attr('font-family', value); }, this);
522 523
523 524 this.model.on('change:padding', function (model, value) {
524 525 this.update_attr('padding', value); }, this);
525 526
526 527 this.model.on('change:margin', function (model, value) {
527 528 this.update_attr('margin', this._default_px(value)); }, this);
528 529
529 530 this.model.on('change:border_radius', function (model, value) {
530 531 this.update_attr('border-radius', this._default_px(value)); }, this);
531 532
532 533 this.after_displayed(function() {
533 534 this.update_visible(this.model, this.model.get("visible"));
534 535 this.update_classes([], this.model.get('_dom_classes'));
535 536
536 537 this.update_attr('color', this.model.get('color'));
537 538 this.update_attr('background', this.model.get('background_color'));
538 539 this.update_attr('width', this.model.get('width'));
539 540 this.update_attr('height', this.model.get('height'));
540 541 this.update_attr('border-color', this.model.get('border_color'));
541 542 this.update_attr('border-width', this.model.get('border_width'));
542 543 this.update_attr('border-style', this.model.get('border_style'));
543 544 this.update_attr('font-style', this.model.get('font_style'));
544 545 this.update_attr('font-weight', this.model.get('font_weight'));
545 546 this.update_attr('font-size', this.model.get('font_size'));
546 547 this.update_attr('font-family', this.model.get('font_family'));
547 548 this.update_attr('padding', this.model.get('padding'));
548 549 this.update_attr('margin', this.model.get('margin'));
549 550 this.update_attr('border-radius', this.model.get('border_radius'));
550 551
551 552 this.update_css(this.model, this.model.get("_css"));
552 553 }, this);
553 554 },
554 555
555 556 _default_px: function(value) {
556 557 /**
557 558 * Makes browser interpret a numerical string as a pixel value.
558 559 */
559 560 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
560 561 return value.trim() + 'px';
561 562 }
562 563 return value;
563 564 },
564 565
565 566 update_attr: function(name, value) {
566 567 /**
567 568 * Set a css attr of the widget view.
568 569 */
569 570 this.$el.css(name, value);
570 571 },
571 572
572 573 update_visible: function(model, value) {
573 574 /**
574 575 * Update visibility
575 576 */
576 577 switch(value) {
577 578 case null: // python None
578 579 this.$el.show().css('visibility', 'hidden'); break;
579 580 case false:
580 581 this.$el.hide(); break;
581 582 case true:
582 583 this.$el.show().css('visibility', ''); break;
583 584 }
584 585 },
585 586
586 587 update_css: function (model, css) {
587 588 /**
588 589 * Update the css styling of this view.
589 590 */
590 var e = this.$el;
591 591 if (css === undefined) {return;}
592 592 for (var i = 0; i < css.length; i++) {
593 593 // Apply the css traits to all elements that match the selector.
594 594 var selector = css[i][0];
595 595 var elements = this._get_selector_element(selector);
596 596 if (elements.length > 0) {
597 597 var trait_key = css[i][1];
598 598 var trait_value = css[i][2];
599 599 elements.css(trait_key ,trait_value);
600 600 }
601 601 }
602 602 },
603 603
604 604 update_classes: function (old_classes, new_classes, $el) {
605 605 /**
606 606 * Update the DOM classes applied to an element, default to this.$el.
607 607 */
608 608 if ($el===undefined) {
609 609 $el = this.$el;
610 610 }
611 611 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
612 612 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
613 613 },
614 614
615 615 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
616 616 /**
617 617 * Update the DOM classes applied to the widget based on a single
618 618 * trait's value.
619 619 *
620 620 * Given a trait value classes map, this function automatically
621 621 * handles applying the appropriate classes to the widget element
622 622 * and removing classes that are no longer valid.
623 623 *
624 624 * Parameters
625 625 * ----------
626 626 * class_map: dictionary
627 627 * Dictionary of trait values to class lists.
628 628 * Example:
629 629 * {
630 630 * success: ['alert', 'alert-success'],
631 631 * info: ['alert', 'alert-info'],
632 632 * warning: ['alert', 'alert-warning'],
633 633 * danger: ['alert', 'alert-danger']
634 634 * };
635 635 * trait_name: string
636 636 * Name of the trait to check the value of.
637 637 * previous_trait_value: optional string, default ''
638 638 * Last trait value
639 639 * $el: optional jQuery element handle, defaults to this.$el
640 640 * Element that the classes are applied to.
641 641 */
642 642 var key = previous_trait_value;
643 643 if (key === undefined) {
644 644 key = this.model.previous(trait_name);
645 645 }
646 646 var old_classes = class_map[key] ? class_map[key] : [];
647 647 key = this.model.get(trait_name);
648 648 var new_classes = class_map[key] ? class_map[key] : [];
649 649
650 650 this.update_classes(old_classes, new_classes, $el || this.$el);
651 651 },
652 652
653 653 _get_selector_element: function (selector) {
654 654 /**
655 655 * Get the elements via the css selector.
656 656 */
657 657 var elements;
658 658 if (!selector) {
659 659 elements = this.$el;
660 660 } else {
661 661 elements = this.$el.find(selector).addBack(selector);
662 662 }
663 663 return elements;
664 664 },
665 665
666 666 typeset: function(element, text){
667 667 utils.typeset.apply(null, arguments);
668 668 },
669 669 });
670 670
671 671
672 672 var ViewList = function(create_view, remove_view, context) {
673 673 /**
674 674 * - create_view and remove_view are default functions called when adding or removing views
675 675 * - create_view takes a model and returns a view or a promise for a view for that model
676 676 * - remove_view takes a view and destroys it (including calling `view.remove()`)
677 677 * - each time the update() function is called with a new list, the create and remove
678 678 * callbacks will be called in an order so that if you append the views created in the
679 679 * create callback and remove the views in the remove callback, you will duplicate
680 680 * the order of the list.
681 681 * - the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
682 682 * - the context defaults to the created ViewList. If you pass another context, the create and remove
683 683 * will be called in that context.
684 684 */
685 685
686 686 this.initialize.apply(this, arguments);
687 687 };
688 688
689 689 _.extend(ViewList.prototype, {
690 690 initialize: function(create_view, remove_view, context) {
691 691 this._handler_context = context || this;
692 692 this._models = [];
693 693 this.views = []; // list of promises for views
694 694 this._create_view = create_view;
695 695 this._remove_view = remove_view || function(view) {view.remove();};
696 696 },
697 697
698 698 update: function(new_models, create_view, remove_view, context) {
699 699 /**
700 700 * the create_view, remove_view, and context arguments override the defaults
701 701 * specified when the list is created.
702 702 * after this function, the .views attribute is a list of promises for views
703 703 * if you want to perform some action on the list of views, do something like
704 704 * `Promise.all(myviewlist.views).then(function(views) {...});`
705 705 */
706 706 var remove = remove_view || this._remove_view;
707 707 var create = create_view || this._create_view;
708 var context = context || this._handler_context;
708 context = context || this._handler_context;
709 709 var i = 0;
710 710 // first, skip past the beginning of the lists if they are identical
711 711 for (; i < new_models.length; i++) {
712 712 if (i >= this._models.length || new_models[i] !== this._models[i]) {
713 713 break;
714 714 }
715 715 }
716 716
717 717 var first_removed = i;
718 718 // Remove the non-matching items from the old list.
719 719 var removed = this.views.splice(first_removed, this.views.length-first_removed);
720 720 for (var j = 0; j < removed.length; j++) {
721 721 removed[j].then(function(view) {
722 722 remove.call(context, view)
723 723 });
724 724 }
725 725
726 726 // Add the rest of the new list items.
727 727 for (; i < new_models.length; i++) {
728 728 this.views.push(Promise.resolve(create.call(context, new_models[i])));
729 729 }
730 730 // make a copy of the input array
731 731 this._models = new_models.slice();
732 732 },
733 733
734 734 remove: function() {
735 735 /**
736 736 * removes every view in the list; convenience function for `.update([])`
737 737 * that should be faster
738 738 * returns a promise that resolves after this removal is done
739 739 */
740 740 var that = this;
741 741 return Promise.all(this.views).then(function(views) {
742 742 for (var i = 0; i < that.views.length; i++) {
743 743 that._remove_view.call(that._handler_context, views[i]);
744 744 }
745 745 that.views = [];
746 746 that._models = [];
747 747 });
748 748 },
749 749 });
750 750
751 751 var widget = {
752 752 'WidgetModel': WidgetModel,
753 753 'WidgetView': WidgetView,
754 754 'DOMWidgetView': DOMWidgetView,
755 755 'ViewList': ViewList,
756 756 };
757 757
758 758 // For backwards compatability.
759 759 $.extend(IPython, widget);
760 760
761 761 return widget;
762 762 });
@@ -1,369 +1,370 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jqueryui",
7 7 "base/js/utils",
8 8 "bootstrap",
9 9 ], function(widget, $, utils){
10 "use strict";
10 11
11 12 var BoxView = widget.DOMWidgetView.extend({
12 13 initialize: function(){
13 14 /**
14 15 * Public constructor
15 16 */
16 17 BoxView.__super__.initialize.apply(this, arguments);
17 18 this.children_views = new widget.ViewList(this.add_child_model, null, this);
18 19 this.listenTo(this.model, 'change:children', function(model, value) {
19 20 this.children_views.update(value);
20 21 }, this);
21 22 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
22 23 this.update_overflow_x();
23 24 }, this);
24 25 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
25 26 this.update_overflow_y();
26 27 }, this);
27 28 this.listenTo(this.model, 'change:box_style', function(model, value) {
28 29 this.update_box_style();
29 30 }, this);
30 31 },
31 32
32 33 update_attr: function(name, value) {
33 34 /**
34 35 * Set a css attr of the widget view.
35 36 */
36 37 this.$box.css(name, value);
37 38 },
38 39
39 40 render: function(){
40 41 /**
41 42 * Called when view is rendered.
42 43 */
43 44 this.$box = this.$el;
44 45 this.$box.addClass('widget-box');
45 46 this.children_views.update(this.model.get('children'));
46 47 this.update_overflow_x();
47 48 this.update_overflow_y();
48 49 this.update_box_style('');
49 50 },
50 51
51 52 update_overflow_x: function() {
52 53 /**
53 54 * Called when the x-axis overflow setting is changed.
54 55 */
55 56 this.$box.css('overflow-x', this.model.get('overflow_x'));
56 57 },
57 58
58 59 update_overflow_y: function() {
59 60 /**
60 61 * Called when the y-axis overflow setting is changed.
61 62 */
62 63 this.$box.css('overflow-y', this.model.get('overflow_y'));
63 64 },
64 65
65 66 update_box_style: function(previous_trait_value) {
66 67 var class_map = {
67 68 success: ['alert', 'alert-success'],
68 69 info: ['alert', 'alert-info'],
69 70 warning: ['alert', 'alert-warning'],
70 71 danger: ['alert', 'alert-danger']
71 72 };
72 73 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
73 74 },
74 75
75 76 add_child_model: function(model) {
76 77 /**
77 78 * Called when a model is added to the children list.
78 79 */
79 80 var that = this;
80 81 var dummy = $('<div/>');
81 82 that.$box.append(dummy);
82 83 return this.create_child_view(model).then(function(view) {
83 84 dummy.replaceWith(view.el);
84 85
85 86 // Trigger the displayed event of the child view.
86 87 that.after_displayed(function() {
87 88 view.trigger('displayed');
88 89 });
89 90 return view;
90 91 }).catch(utils.reject("Couldn't add child view to box", true));
91 92 },
92 93
93 94 remove: function() {
94 95 /**
95 96 * We remove this widget before removing the children as an optimization
96 97 * we want to remove the entire container from the DOM first before
97 98 * removing each individual child separately.
98 99 */
99 100 BoxView.__super__.remove.apply(this, arguments);
100 101 this.children_views.remove();
101 102 },
102 103 });
103 104
104 105
105 106 var FlexBoxView = BoxView.extend({
106 107 render: function(){
107 108 FlexBoxView.__super__.render.apply(this);
108 109 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
109 110 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
110 111 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
111 112 this.listenTo(this.model, 'change:align', this._align_changed, this);
112 113 this._flex_changed();
113 114 this._pack_changed();
114 115 this._align_changed();
115 116 this.update_orientation();
116 117 },
117 118
118 119 update_orientation: function(){
119 120 var orientation = this.model.get("orientation");
120 121 if (orientation == "vertical") {
121 122 this.$box.removeClass("hbox").addClass("vbox");
122 123 } else {
123 124 this.$box.removeClass("vbox").addClass("hbox");
124 125 }
125 126 },
126 127
127 128 _flex_changed: function(){
128 129 if (this.model.previous('flex')) {
129 130 this.$box.removeClass('box-flex' + this.model.previous('flex'));
130 131 }
131 132 this.$box.addClass('box-flex' + this.model.get('flex'));
132 133 },
133 134
134 135 _pack_changed: function(){
135 136 if (this.model.previous('pack')) {
136 137 this.$box.removeClass(this.model.previous('pack'));
137 138 }
138 139 this.$box.addClass(this.model.get('pack'));
139 140 },
140 141
141 142 _align_changed: function(){
142 143 if (this.model.previous('align')) {
143 144 this.$box.removeClass('align-' + this.model.previous('align'));
144 145 }
145 146 this.$box.addClass('align-' + this.model.get('align'));
146 147 },
147 148 });
148 149
149 150 var PopupView = BoxView.extend({
150 151
151 152 render: function(){
152 153 /**
153 154 * Called when view is rendered.
154 155 */
155 156 var that = this;
156 157
157 158 this.$el.on("remove", function(){
158 159 that.$backdrop.remove();
159 160 });
160 161 this.$backdrop = $('<div />')
161 162 .appendTo($('#notebook-container'))
162 163 .addClass('modal-dialog')
163 164 .css('position', 'absolute')
164 165 .css('left', '0px')
165 166 .css('top', '0px');
166 167 this.$window = $('<div />')
167 168 .appendTo(this.$backdrop)
168 169 .addClass('modal-content widget-modal')
169 170 .mousedown(function(){
170 171 that.bring_to_front();
171 172 });
172 173
173 174 // Set the elements array since the this.$window element is not child
174 175 // of this.$el and the parent widget manager or other widgets may
175 176 // need to know about all of the top-level widgets. The IPython
176 177 // widget manager uses this to register the elements with the
177 178 // keyboard manager.
178 179 this.additional_elements = [this.$window];
179 180
180 181 this.$title_bar = $('<div />')
181 182 .addClass('popover-title')
182 183 .appendTo(this.$window)
183 184 .mousedown(function(){
184 185 that.bring_to_front();
185 186 });
186 187 this.$close = $('<button />')
187 188 .addClass('close fa fa-remove')
188 189 .css('margin-left', '5px')
189 190 .appendTo(this.$title_bar)
190 191 .click(function(){
191 192 that.hide();
192 193 event.stopPropagation();
193 194 });
194 195 this.$minimize = $('<button />')
195 196 .addClass('close fa fa-arrow-down')
196 197 .appendTo(this.$title_bar)
197 198 .click(function(){
198 199 that.popped_out = !that.popped_out;
199 200 if (!that.popped_out) {
200 201 that.$minimize
201 202 .removeClass('fa-arrow-down')
202 203 .addClass('fa-arrow-up');
203 204
204 205 that.$window
205 206 .draggable('destroy')
206 207 .resizable('destroy')
207 208 .removeClass('widget-modal modal-content')
208 209 .addClass('docked-widget-modal')
209 210 .detach()
210 211 .insertBefore(that.$show_button);
211 212 that.$show_button.hide();
212 213 that.$close.hide();
213 214 } else {
214 215 that.$minimize
215 216 .addClass('fa-arrow-down')
216 217 .removeClass('fa-arrow-up');
217 218
218 219 that.$window
219 220 .removeClass('docked-widget-modal')
220 221 .addClass('widget-modal modal-content')
221 222 .detach()
222 223 .appendTo(that.$backdrop)
223 224 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
224 225 .resizable()
225 226 .children('.ui-resizable-handle').show();
226 227 that.show();
227 228 that.$show_button.show();
228 229 that.$close.show();
229 230 }
230 231 event.stopPropagation();
231 232 });
232 233 this.$title = $('<div />')
233 234 .addClass('widget-modal-title')
234 235 .html("&nbsp;")
235 236 .appendTo(this.$title_bar);
236 237 this.$box = $('<div />')
237 238 .addClass('modal-body')
238 239 .addClass('widget-modal-body')
239 240 .addClass('widget-box')
240 241 .addClass('vbox')
241 242 .appendTo(this.$window);
242 243
243 244 this.$show_button = $('<button />')
244 245 .html("&nbsp;")
245 246 .addClass('btn btn-info widget-modal-show')
246 247 .appendTo(this.$el)
247 248 .click(function(){
248 249 that.show();
249 250 });
250 251
251 252 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
252 253 this.$window.resizable();
253 254 this.$window.on('resize', function(){
254 255 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
255 256 });
256 257
257 258 this._shown_once = false;
258 259 this.popped_out = true;
259 260
260 261 this.children_views.update(this.model.get('children'))
261 262 },
262 263
263 264 hide: function() {
264 265 /**
265 266 * Called when the modal hide button is clicked.
266 267 */
267 268 this.$window.hide();
268 269 this.$show_button.removeClass('btn-info');
269 270 },
270 271
271 272 show: function() {
272 273 /**
273 274 * Called when the modal show button is clicked.
274 275 */
275 276 this.$show_button.addClass('btn-info');
276 277 this.$window.show();
277 278 if (this.popped_out) {
278 279 this.$window.css("positon", "absolute");
279 280 this.$window.css("top", "0px");
280 281 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
281 282 $(window).scrollLeft()) + "px");
282 283 this.bring_to_front();
283 284 }
284 285 },
285 286
286 287 bring_to_front: function() {
287 288 /**
288 289 * Make the modal top-most, z-ordered about the other modals.
289 290 */
290 291 var $widget_modals = $(".widget-modal");
291 292 var max_zindex = 0;
292 293 $widget_modals.each(function (index, el){
293 294 var zindex = parseInt($(el).css('z-index'));
294 295 if (!isNaN(zindex)) {
295 296 max_zindex = Math.max(max_zindex, zindex);
296 297 }
297 298 });
298 299
299 300 // Start z-index of widget modals at 2000
300 301 max_zindex = Math.max(max_zindex, 2000);
301 302
302 303 $widget_modals.each(function (index, el){
303 304 $el = $(el);
304 305 if (max_zindex == parseInt($el.css('z-index'))) {
305 306 $el.css('z-index', max_zindex - 1);
306 307 }
307 308 });
308 309 this.$window.css('z-index', max_zindex);
309 310 },
310 311
311 312 update: function(){
312 313 /**
313 314 * Update the contents of this view
314 315 *
315 316 * Called when the model is changed. The model may have been
316 317 * changed by another view or by a state update from the back-end.
317 318 */
318 319 var description = this.model.get('description');
319 320 if (description.trim().length === 0) {
320 321 this.$title.html("&nbsp;"); // Preserve title height
321 322 } else {
322 323 this.typeset(this.$title, description);
323 324 }
324 325
325 326 var button_text = this.model.get('button_text');
326 327 if (button_text.trim().length === 0) {
327 328 this.$show_button.html("&nbsp;"); // Preserve button height
328 329 } else {
329 330 this.$show_button.text(button_text);
330 331 }
331 332
332 333 if (!this._shown_once) {
333 334 this._shown_once = true;
334 335 this.show();
335 336 }
336 337
337 338 return PopupView.__super__.update.apply(this);
338 339 },
339 340
340 341 _get_selector_element: function(selector) {
341 342 /**
342 343 * Get an element view a 'special' jquery selector. (see widget.js)
343 344 *
344 345 * Since the modal actually isn't within the $el in the DOM, we need to extend
345 346 * the selector logic to allow the user to set css on the modal if need be.
346 347 * The convention used is:
347 348 * "modal" - select the modal div
348 349 * "modal [selector]" - select element(s) within the modal div.
349 350 * "[selector]" - select elements within $el
350 351 * "" - select the $el
351 352 */
352 353 if (selector.substring(0, 5) == 'modal') {
353 354 if (selector == 'modal') {
354 355 return this.$window;
355 356 } else {
356 357 return this.$window.find(selector.substring(6));
357 358 }
358 359 } else {
359 360 return PopupView.__super__._get_selector_element.apply(this, [selector]);
360 361 }
361 362 },
362 363 });
363 364
364 365 return {
365 366 'BoxView': BoxView,
366 367 'PopupView': PopupView,
367 368 'FlexBoxView': FlexBoxView,
368 369 };
369 370 });
@@ -1,490 +1,491 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "jqueryui",
7 7 "base/js/keyboard",
8 8 "bootstrap"
9 9 ], function(widget, $, keyboard){
10 10
11 11 var IntSliderView = widget.DOMWidgetView.extend({
12 12 render : function(){
13 13 /**
14 14 * Called when view is rendered.
15 15 */
16 16 this.$el
17 17 .addClass('widget-hbox widget-slider');
18 18 this.$label = $('<div />')
19 19 .appendTo(this.$el)
20 20 .addClass('widget-label')
21 21 .hide();
22 22
23 23 this.$slider = $('<div />')
24 24 .slider({})
25 25 .addClass('slider');
26 26 // Put the slider in a container
27 27 this.$slider_container = $('<div />')
28 28 .addClass('widget-hslider')
29 29 .append(this.$slider);
30 30 this.$el.append(this.$slider_container);
31 31
32 32 this.$readout = $('<div/>')
33 33 .appendTo(this.$el)
34 34 .addClass('widget-readout')
35 35 .attr('contentEditable', true)
36 36 .hide();
37 37
38 38 this.model.on('change:slider_color', function(sender, value) {
39 39 this.$slider.find('a').css('background', value);
40 40 }, this);
41 41 this.$slider.find('a').css('background', this.model.get('slider_color'));
42 42
43 43 // Set defaults.
44 44 this.update();
45 45 },
46 46
47 47 update_attr: function(name, value) {
48 48 /**
49 49 * Set a css attr of the widget view.
50 50 */
51 51 if (name == 'color') {
52 52 this.$readout.css(name, value);
53 53 } else if (name.substring(0, 4) == 'font') {
54 54 this.$readout.css(name, value);
55 55 } else if (name.substring(0, 6) == 'border') {
56 56 this.$slider.find('a').css(name, value);
57 57 this.$slider_container.css(name, value);
58 58 } else if (name == 'width' || name == 'height' || name == 'background') {
59 59 this.$slider_container.css(name, value);
60 60 } else if (name == 'padding' || name == 'margin') {
61 61 this.$el.css(name, value);
62 62 } else {
63 63 this.$slider.css(name, value);
64 64 }
65 65 },
66 66
67 67 update : function(options){
68 68 /**
69 69 * Update the contents of this view
70 70 *
71 71 * Called when the model is changed. The model may have been
72 72 * changed by another view or by a state update from the back-end.
73 73 */
74 74 if (options === undefined || options.updated_view != this) {
75 75 // JQuery slider option keys. These keys happen to have a
76 76 // one-to-one mapping with the corrosponding keys of the model.
77 77 var jquery_slider_keys = ['step', 'disabled'];
78 78 var that = this;
79 79 that.$slider.slider({});
80 80 _.each(jquery_slider_keys, function(key, i) {
81 81 var model_value = that.model.get(key);
82 82 if (model_value !== undefined) {
83 83 that.$slider.slider("option", key, model_value);
84 84 }
85 85 });
86 86
87 87 var max = this.model.get('max');
88 88 var min = this.model.get('min');
89 89 if (min <= max) {
90 90 if (max !== undefined) this.$slider.slider('option', 'max', max);
91 91 if (min !== undefined) this.$slider.slider('option', 'min', min);
92 92 }
93 93
94 94 var range_value = this.model.get("_range");
95 95 if (range_value !== undefined) {
96 96 this.$slider.slider("option", "range", range_value);
97 97 }
98 98
99 99 // WORKAROUND FOR JQUERY SLIDER BUG.
100 100 // The horizontal position of the slider handle
101 101 // depends on the value of the slider at the time
102 102 // of orientation change. Before applying the new
103 103 // workaround, we set the value to the minimum to
104 104 // make sure that the horizontal placement of the
105 105 // handle in the vertical slider is always
106 106 // consistent.
107 107 var orientation = this.model.get('orientation');
108 108 var min = this.model.get('min');
109 109 var max = this.model.get('max');
110 110 if (this.model.get('_range')) {
111 111 this.$slider.slider('option', 'values', [min, min]);
112 112 } else {
113 113 this.$slider.slider('option', 'value', min);
114 114 }
115 115 this.$slider.slider('option', 'orientation', orientation);
116 116 var value = this.model.get('value');
117 117 if (this.model.get('_range')) {
118 118 // values for the range case are validated python-side in
119 119 // _Bounded{Int,Float}RangeWidget._validate
120 120 this.$slider.slider('option', 'values', value);
121 121 this.$readout.text(value.join("-"));
122 122 } else {
123 123 if(value > max) {
124 124 value = max;
125 125 }
126 126 else if(value < min){
127 127 value = min;
128 128 }
129 129 this.$slider.slider('option', 'value', value);
130 130 this.$readout.text(value);
131 131 }
132 132
133 133 if(this.model.get('value')!=value) {
134 134 this.model.set('value', value, {updated_view: this});
135 135 this.touch();
136 136 }
137 137
138 138 // Use the right CSS classes for vertical & horizontal sliders
139 139 if (orientation=='vertical') {
140 140 this.$slider_container
141 141 .removeClass('widget-hslider')
142 142 .addClass('widget-vslider');
143 143 this.$el
144 144 .removeClass('widget-hbox')
145 145 .addClass('widget-vbox');
146 146
147 147 } else {
148 148 this.$slider_container
149 149 .removeClass('widget-vslider')
150 150 .addClass('widget-hslider');
151 151 this.$el
152 152 .removeClass('widget-vbox')
153 153 .addClass('widget-hbox');
154 154 }
155 155
156 156 var description = this.model.get('description');
157 157 if (description.length === 0) {
158 158 this.$label.hide();
159 159 } else {
160 160 this.typeset(this.$label, description);
161 161 this.$label.show();
162 162 }
163 163
164 164 var readout = this.model.get('readout');
165 165 if (readout) {
166 166 this.$readout.show();
167 167 } else {
168 168 this.$readout.hide();
169 169 }
170 170 }
171 171 return IntSliderView.__super__.update.apply(this);
172 172 },
173 173
174 174 events: {
175 175 // Dictionary of events and their handlers.
176 176 "slide" : "handleSliderChange",
177 177 "blur [contentEditable=true]": "handleTextChange",
178 178 "keydown [contentEditable=true]": "handleKeyDown"
179 179 },
180 180
181 181 handleKeyDown: function(e) {
182 182 if (e.keyCode == keyboard.keycodes.enter) {
183 183 e.preventDefault();
184 184 this.handleTextChange();
185 185 }
186 186 },
187 187
188 188 handleTextChange: function() {
189 189 /**
190 190 * this handles the entry of text into the contentEditable label
191 191 * first, the value is checked if it contains a parseable number
192 192 * (or pair of numbers, for the _range case)
193 193 * then it is clamped within the min-max range of the slider
194 194 * finally, the model is updated if the value is to be changed
195 195 *
196 196 * if any of these conditions are not met, the text is reset
197 197 *
198 198 * the step size is not enforced
199 199 */
200 200
201 201 var text = this.$readout.text();
202 202 var vmin = this.model.get('min');
203 203 var vmax = this.model.get('max');
204 204 if (this.model.get("_range")) {
205 205 // range case
206 206 // ranges can be expressed either "val-val" or "val:val" (+spaces)
207 207 var match = this._range_regex.exec(text);
208 208 if (match) {
209 209 var values = [this._parse_value(match[1]),
210 210 this._parse_value(match[2])];
211 211 // reject input where NaN or lower > upper
212 212 if (isNaN(values[0]) ||
213 213 isNaN(values[1]) ||
214 214 (values[0] > values[1])) {
215 215 this.$readout.text(this.model.get('value').join('-'));
216 216 } else {
217 217 // clamp to range
218 218 values = [Math.max(Math.min(values[0], vmax), vmin),
219 219 Math.max(Math.min(values[1], vmax), vmin)];
220 220
221 221 if ((values[0] != this.model.get('value')[0]) ||
222 222 (values[1] != this.model.get('value')[1])) {
223 223 this.$readout.text(values.join('-'));
224 224 this.model.set('value', values, {updated_view: this});
225 225 this.touch();
226 226 } else {
227 227 this.$readout.text(this.model.get('value').join('-'));
228 228 }
229 229 }
230 230 } else {
231 231 this.$readout.text(this.model.get('value').join('-'));
232 232 }
233 233 } else {
234 234 // single value case
235 235 var value = this._parse_value(text);
236 236 if (isNaN(value)) {
237 237 this.$readout.text(this.model.get('value'));
238 238 } else {
239 239 value = Math.max(Math.min(value, vmax), vmin);
240 240
241 241 if (value != this.model.get('value')) {
242 242 this.$readout.text(value);
243 243 this.model.set('value', value, {updated_view: this});
244 244 this.touch();
245 245 } else {
246 246 this.$readout.text(this.model.get('value'));
247 247 }
248 248 }
249 249 }
250 250 },
251 251
252 252 _parse_value: parseInt,
253 253
254 254 _range_regex: /^\s*([+-]?\d+)\s*[-:]\s*([+-]?\d+)/,
255 255
256 256 handleSliderChange: function(e, ui) {
257 257 /**
258 258 * Called when the slider value is changed.
259 259 *
260 260 * Calling model.set will trigger all of the other views of the
261 261 * model to update.
262 262 */
263 var actual_value;
263 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 266 this.$readout.text(actual_value.join("-"));
266 267 } else {
267 var actual_value = this._validate_slide_value(ui.value);
268 actual_value = this._validate_slide_value(ui.value);
268 269 this.$readout.text(actual_value);
269 270 }
270 271 this.model.set('value', actual_value, {updated_view: this});
271 272 this.touch();
272 273 },
273 274
274 275 _validate_slide_value: function(x) {
275 276 /**
276 277 * Validate the value of the slider before sending it to the back-end
277 278 * and applying it to the other views on the page.
278 279 *
279 280 * Double bit-wise not truncates the decimel (int cast).
280 281 */
281 282 return ~~x;
282 283 },
283 284 });
284 285
285 286
286 287 var IntTextView = widget.DOMWidgetView.extend({
287 288 render : function(){
288 289 /**
289 290 * Called when view is rendered.
290 291 */
291 292 this.$el
292 293 .addClass('widget-hbox widget-text');
293 294 this.$label = $('<div />')
294 295 .appendTo(this.$el)
295 296 .addClass('widget-label')
296 297 .hide();
297 298 this.$textbox = $('<input type="text" />')
298 299 .addClass('form-control')
299 300 .addClass('widget-numeric-text')
300 301 .appendTo(this.$el);
301 302 this.update(); // Set defaults.
302 303 },
303 304
304 305 update : function(options){
305 306 /**
306 307 * Update the contents of this view
307 308 *
308 309 * Called when the model is changed. The model may have been
309 310 * changed by another view or by a state update from the back-end.
310 311 */
311 312 if (options === undefined || options.updated_view != this) {
312 313 var value = this.model.get('value');
313 314 if (this._parse_value(this.$textbox.val()) != value) {
314 315 this.$textbox.val(value);
315 316 }
316 317
317 318 if (this.model.get('disabled')) {
318 319 this.$textbox.attr('disabled','disabled');
319 320 } else {
320 321 this.$textbox.removeAttr('disabled');
321 322 }
322 323
323 324 var description = this.model.get('description');
324 325 if (description.length === 0) {
325 326 this.$label.hide();
326 327 } else {
327 328 this.typeset(this.$label, description);
328 329 this.$label.show();
329 330 }
330 331 }
331 332 return IntTextView.__super__.update.apply(this);
332 333 },
333 334
334 335 update_attr: function(name, value) {
335 336 /**
336 337 * Set a css attr of the widget view.
337 338 */
338 339 if (name == 'padding' || name == 'margin') {
339 340 this.$el.css(name, value);
340 341 } else {
341 342 this.$textbox.css(name, value);
342 343 }
343 344 },
344 345
345 346 events: {
346 347 // Dictionary of events and their handlers.
347 348 "keyup input" : "handleChanging",
348 349 "paste input" : "handleChanging",
349 350 "cut input" : "handleChanging",
350 351
351 352 // Fires only when control is validated or looses focus.
352 353 "change input" : "handleChanged"
353 354 },
354 355
355 356 handleChanging: function(e) {
356 357 /**
357 358 * Handles and validates user input.
358 359 *
359 360 * Try to parse value as a int.
360 361 */
361 362 var numericalValue = 0;
362 363 var trimmed = e.target.value.trim();
363 364 if (trimmed === '') {
364 365 return;
365 366 } else {
366 367 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
367 368 numericalValue = this._parse_value(e.target.value);
368 369 }
369 370 }
370 371
371 372 // If parse failed, reset value to value stored in model.
372 373 if (isNaN(numericalValue)) {
373 374 e.target.value = this.model.get('value');
374 375 } else if (!isNaN(numericalValue)) {
375 376 if (this.model.get('max') !== undefined) {
376 377 numericalValue = Math.min(this.model.get('max'), numericalValue);
377 378 }
378 379 if (this.model.get('min') !== undefined) {
379 380 numericalValue = Math.max(this.model.get('min'), numericalValue);
380 381 }
381 382
382 383 // Apply the value if it has changed.
383 384 if (numericalValue != this.model.get('value')) {
384 385
385 386 // Calling model.set will trigger all of the other views of the
386 387 // model to update.
387 388 this.model.set('value', numericalValue, {updated_view: this});
388 389 this.touch();
389 390 }
390 391 }
391 392 },
392 393
393 394 handleChanged: function(e) {
394 395 /**
395 396 * Applies validated input.
396 397 */
397 398 if (e.target.value.trim() === '' || e.target.value !== this.model.get('value')) {
398 399 e.target.value = this.model.get('value');
399 400 }
400 401 },
401 402
402 403 _parse_value: parseInt
403 404 });
404 405
405 406
406 407 var ProgressView = widget.DOMWidgetView.extend({
407 408 render : function(){
408 409 /**
409 410 * Called when view is rendered.
410 411 */
411 412 this.$el
412 413 .addClass('widget-hbox widget-progress');
413 414 this.$label = $('<div />')
414 415 .appendTo(this.$el)
415 416 .addClass('widget-label')
416 417 .hide();
417 418 this.$progress = $('<div />')
418 419 .addClass('progress')
419 420 .addClass('widget-progress')
420 421 .appendTo(this.$el);
421 422 this.$bar = $('<div />')
422 423 .addClass('progress-bar')
423 424 .css('width', '50%')
424 425 .appendTo(this.$progress);
425 426 this.update(); // Set defaults.
426 427
427 428 this.model.on('change:bar_style', function(model, value) {
428 429 this.update_bar_style();
429 430 }, this);
430 431 this.update_bar_style('');
431 432 },
432 433
433 434 update : function(){
434 435 /**
435 436 * Update the contents of this view
436 437 *
437 438 * Called when the model is changed. The model may have been
438 439 * changed by another view or by a state update from the back-end.
439 440 */
440 441 var value = this.model.get('value');
441 442 var max = this.model.get('max');
442 443 var min = this.model.get('min');
443 444 var percent = 100.0 * (value - min) / (max - min);
444 445 this.$bar.css('width', percent + '%');
445 446
446 447 var description = this.model.get('description');
447 448 if (description.length === 0) {
448 449 this.$label.hide();
449 450 } else {
450 451 this.typeset(this.$label, description);
451 452 this.$label.show();
452 453 }
453 454 return ProgressView.__super__.update.apply(this);
454 455 },
455 456
456 457 update_bar_style: function(previous_trait_value) {
457 458 var class_map = {
458 459 success: ['progress-bar-success'],
459 460 info: ['progress-bar-info'],
460 461 warning: ['progress-bar-warning'],
461 462 danger: ['progress-bar-danger']
462 463 };
463 464 this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar);
464 465 },
465 466
466 467 update_attr: function(name, value) {
467 468 /**
468 469 * Set a css attr of the widget view.
469 470 */
470 471 if (name.substring(0, 6) == 'border' || name == 'width' ||
471 472 name == 'height' || name == 'background' || name == 'margin' ||
472 473 name == 'padding') {
473 474
474 475 this.$progress.css(name, value);
475 476 } else if (name == 'color') {
476 477 this.$bar.css('background', value);
477 478 } else if (name == 'padding' || name == 'margin') {
478 479 this.$el.css(name, value);
479 480 } else {
480 481 this.$bar.css(name, value);
481 482 }
482 483 },
483 484 });
484 485
485 486 return {
486 487 'IntSliderView': IntSliderView,
487 488 'IntTextView': IntTextView,
488 489 'ProgressView': ProgressView,
489 490 };
490 491 });
@@ -1,301 +1,300 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "widgets/js/widget",
6 6 "base/js/utils",
7 7 "jquery",
8 8 "bootstrap",
9 9 ], function(widget, utils, $){
10 10
11 11 var AccordionView = widget.DOMWidgetView.extend({
12 12 initialize: function(){
13 13 AccordionView.__super__.initialize.apply(this, arguments);
14 14
15 15 this.containers = [];
16 16 this.model_containers = {};
17 17 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
18 18 this.listenTo(this.model, 'change:children', function(model, value) {
19 19 this.children_views.update(value);
20 20 }, this);
21 21 },
22 22
23 23 render: function(){
24 24 /**
25 25 * Called when view is rendered.
26 26 */
27 27 var guid = 'panel-group' + utils.uuid();
28 28 this.$el
29 29 .attr('id', guid)
30 30 .addClass('panel-group');
31 31 this.model.on('change:selected_index', function(model, value, options) {
32 32 this.update_selected_index(model.previous('selected_index'), value, options);
33 33 }, this);
34 34 this.model.on('change:_titles', function(model, value, options) {
35 35 this.update_titles(value);
36 36 }, this);
37 37 this.on('displayed', function() {
38 38 this.update_titles();
39 39 }, this);
40 40 this.children_views.update(this.model.get('children'));
41 41 },
42 42
43 43 update_titles: function(titles) {
44 44 /**
45 45 * Set tab titles
46 46 */
47 47 if (!titles) {
48 48 titles = this.model.get('_titles');
49 49 }
50 50
51 51 var that = this;
52 52 _.each(titles, function(title, page_index) {
53 53 var accordian = that.containers[page_index];
54 54 if (accordian !== undefined) {
55 55 accordian
56 56 .find('.panel-heading')
57 57 .find('.accordion-toggle')
58 58 .text(title);
59 59 }
60 60 });
61 61 },
62 62
63 63 update_selected_index: function(old_index, new_index, options) {
64 64 /**
65 65 * Only update the selection if the selection wasn't triggered
66 66 * by the front-end. It must be triggered by the back-end.
67 67 */
68 68 if (options === undefined || options.updated_view != this) {
69 69 this.containers[old_index].find('.panel-collapse').collapse('hide');
70 70 if (0 <= new_index && new_index < this.containers.length) {
71 71 this.containers[new_index].find('.panel-collapse').collapse('show');
72 72 }
73 73 }
74 74 },
75 75
76 76 remove_child_view: function(view) {
77 77 /**
78 78 * Called when a child is removed from children list.
79 79 * TODO: does this handle two different views of the same model as children?
80 80 */
81 81 var model = view.model;
82 82 var accordion_group = this.model_containers[model.id];
83 83 this.containers.splice(accordion_group.container_index, 1);
84 84 delete this.model_containers[model.id];
85 85 accordion_group.remove();
86 86 },
87 87
88 88 add_child_view: function(model) {
89 89 /**
90 90 * Called when a child is added to children list.
91 91 */
92 92 var index = this.containers.length;
93 93 var uuid = utils.uuid();
94 94 var accordion_group = $('<div />')
95 95 .addClass('panel panel-default')
96 96 .appendTo(this.$el);
97 97 var accordion_heading = $('<div />')
98 98 .addClass('panel-heading')
99 99 .appendTo(accordion_group);
100 100 var that = this;
101 101 var accordion_toggle = $('<a />')
102 102 .addClass('accordion-toggle')
103 103 .attr('data-toggle', 'collapse')
104 104 .attr('data-parent', '#' + this.$el.attr('id'))
105 105 .attr('href', '#' + uuid)
106 106 .click(function(evt){
107 107
108 108 // Calling model.set will trigger all of the other views of the
109 109 // model to update.
110 110 that.model.set("selected_index", index, {updated_view: that});
111 111 that.touch();
112 112 })
113 113 .text('Page ' + index)
114 114 .appendTo(accordion_heading);
115 115 var accordion_body = $('<div />', {id: uuid})
116 116 .addClass('panel-collapse collapse')
117 117 .appendTo(accordion_group);
118 118 var accordion_inner = $('<div />')
119 119 .addClass('panel-body')
120 120 .appendTo(accordion_body);
121 121 var container_index = this.containers.push(accordion_group) - 1;
122 122 accordion_group.container_index = container_index;
123 123 this.model_containers[model.id] = accordion_group;
124 124
125 125 var dummy = $('<div/>');
126 126 accordion_inner.append(dummy);
127 127 return this.create_child_view(model).then(function(view) {
128 128 dummy.replaceWith(view.$el);
129 129 that.update();
130 130 that.update_titles();
131 131
132 132 // Trigger the displayed event of the child view.
133 133 that.after_displayed(function() {
134 134 view.trigger('displayed');
135 135 });
136 136 return view;
137 137 }).catch(utils.reject("Couldn't add child view to box", true));
138 138 },
139 139
140 140 remove: function() {
141 141 /**
142 142 * We remove this widget before removing the children as an optimization
143 143 * we want to remove the entire container from the DOM first before
144 144 * removing each individual child separately.
145 145 */
146 146 AccordionView.__super__.remove.apply(this, arguments);
147 147 this.children_views.remove();
148 148 },
149 149 });
150 150
151 151
152 152 var TabView = widget.DOMWidgetView.extend({
153 153 initialize: function() {
154 154 /**
155 155 * Public constructor.
156 156 */
157 157 TabView.__super__.initialize.apply(this, arguments);
158 158
159 159 this.containers = [];
160 160 this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
161 161 this.listenTo(this.model, 'change:children', function(model, value) {
162 162 this.children_views.update(value);
163 163 }, this);
164 164 },
165 165
166 166 render: function(){
167 167 /**
168 168 * Called when view is rendered.
169 169 */
170 170 var uuid = 'tabs'+utils.uuid();
171 var that = this;
172 171 this.$tabs = $('<div />', {id: uuid})
173 172 .addClass('nav')
174 173 .addClass('nav-tabs')
175 174 .appendTo(this.$el);
176 175 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
177 176 .addClass('tab-content')
178 177 .appendTo(this.$el);
179 178 this.children_views.update(this.model.get('children'));
180 179 },
181 180
182 181 update_attr: function(name, value) {
183 182 /**
184 183 * Set a css attr of the widget view.
185 184 */
186 185 if (name == 'padding' || name == 'margin') {
187 186 this.$el.css(name, value);
188 187 } else {
189 188 this.$tabs.css(name, value);
190 189 }
191 190 },
192 191
193 192 remove_child_view: function(view) {
194 193 /**
195 194 * Called when a child is removed from children list.
196 195 */
197 196 this.containers.splice(view.parent_tab.tab_text_index, 1);
198 197 view.parent_tab.remove();
199 198 view.parent_container.remove();
200 199 view.remove();
201 200 },
202 201
203 202 add_child_view: function(model) {
204 203 /**
205 204 * Called when a child is added to children list.
206 205 */
207 206 var index = this.containers.length;
208 207 var uuid = utils.uuid();
209 208
210 209 var that = this;
211 210 var tab = $('<li />')
212 211 .css('list-style-type', 'none')
213 212 .appendTo(this.$tabs);
214 213
215 214
216 215 var tab_text = $('<a />')
217 216 .attr('href', '#' + uuid)
218 217 .attr('data-toggle', 'tab')
219 218 .text('Page ' + index)
220 219 .appendTo(tab)
221 220 .click(function (e) {
222 221
223 222 // Calling model.set will trigger all of the other views of the
224 223 // model to update.
225 224 that.model.set("selected_index", index, {updated_view: that});
226 225 that.touch();
227 226 that.select_page(index);
228 227 });
229 228 tab.tab_text_index = that.containers.push(tab_text) - 1;
230 229
231 230 var dummy = $('<div />');
232 231 var contents_div = $('<div />', {id: uuid})
233 232 .addClass('tab-pane')
234 233 .addClass('fade')
235 234 .append(dummy)
236 235 .appendTo(that.$tab_contents);
237 236
238 237 return this.create_child_view(model).then(function(view) {
239 238 dummy.replaceWith(view.$el);
240 239 view.parent_tab = tab;
241 240 view.parent_container = contents_div;
242 241
243 242 // Trigger the displayed event of the child view.
244 243 that.after_displayed(function() {
245 244 view.trigger('displayed');
246 245 });
247 246 return view;
248 247 }).catch(utils.reject("Couldn't add child view to box", true));
249 248 },
250 249
251 250 update: function(options) {
252 251 /**
253 252 * Update the contents of this view
254 253 *
255 254 * Called when the model is changed. The model may have been
256 255 * changed by another view or by a state update from the back-end.
257 256 */
258 257 if (options === undefined || options.updated_view != this) {
259 258 // Set tab titles
260 259 var titles = this.model.get('_titles');
261 260 var that = this;
262 261 _.each(titles, function(title, page_index) {
263 262 var tab_text = that.containers[page_index];
264 263 if (tab_text !== undefined) {
265 264 tab_text.text(title);
266 265 }
267 266 });
268 267
269 268 var selected_index = this.model.get('selected_index');
270 269 if (0 <= selected_index && selected_index < this.containers.length) {
271 270 this.select_page(selected_index);
272 271 }
273 272 }
274 273 return TabView.__super__.update.apply(this);
275 274 },
276 275
277 276 select_page: function(index) {
278 277 /**
279 278 * Select a page.
280 279 */
281 280 this.$tabs.find('li')
282 281 .removeClass('active');
283 282 this.containers[index].tab('show');
284 283 },
285 284
286 285 remove: function() {
287 286 /**
288 287 * We remove this widget before removing the children as an optimization
289 288 * we want to remove the entire container from the DOM first before
290 289 * removing each individual child separately.
291 290 */
292 291 TabView.__super__.remove.apply(this, arguments);
293 292 this.children_views.remove();
294 293 },
295 294 });
296 295
297 296 return {
298 297 'AccordionView': AccordionView,
299 298 'TabView': TabView,
300 299 };
301 300 });
@@ -1,62 +1,63 b''
1 1 //
2 2 // Test that a Markdown cell is rendered to HTML.
3 3 //
4 4 casper.notebook_test(function () {
5 "use strict";
5 6 // Test JavaScript models.
6 7 var output = this.evaluate(function () {
7 8 IPython.notebook.to_markdown();
8 9 var cell = IPython.notebook.get_selected_cell();
9 10 cell.set_text('# Foo');
10 11 cell.render();
11 12 return cell.get_rendered();
12 13 });
13 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 16 // Test menubar entries.
16 17 output = this.evaluate(function () {
17 18 $('#to_code').mouseenter().click();
18 19 $('#to_markdown').mouseenter().click();
19 20 var cell = IPython.notebook.get_selected_cell();
20 21 cell.set_text('**Bar**');
21 22 $('#run_cell').mouseenter().click();
22 23 return cell.get_rendered();
23 24 });
24 25 this.test.assertEquals(output.trim(), '<p><strong>Bar</strong></p>', 'Markdown menubar items work.');
25 26
26 27 // Test toolbar buttons.
27 28 output = this.evaluate(function () {
28 29 $('#cell_type').val('code').change();
29 30 $('#cell_type').val('markdown').change();
30 31 var cell = IPython.notebook.get_selected_cell();
31 32 cell.set_text('*Baz*');
32 33 $("button[data-jupyter-action='ipython.run-select-next']")[0].click();
33 34 return cell.get_rendered();
34 35 });
35 36 this.test.assertEquals(output.trim(), '<p><em>Baz</em></p>', 'Markdown toolbar items work.');
36 37
37 38 // Test markdown headings
38 39
39 40 var text = 'multi\nline';
40 41
41 42 this.evaluate(function (text) {
42 43 var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
43 44 cell.set_text(text);
44 45 }, {text: text});
45 46
46 47 var set_level = function (level) {
47 48 return casper.evaluate(function (level) {
48 49 var cell = IPython.notebook.get_cell(0);
49 50 cell.set_heading_level(level);
50 51 return cell.get_text();
51 52 }, {level: level});
52 53 };
53 54
54 55 var level_text;
55 56 var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ];
56 57 for (var idx=0; idx < levels.length; idx++) {
57 58 var level = levels[idx];
58 59 level_text = set_level(level);
59 hashes = new Array(level + 1).join('#');
60 var hashes = new Array(level + 1).join('#');
60 61 this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
61 62 }
62 63 });
@@ -1,247 +1,247 b''
1 1 // Test opening a rich notebook, saving it, and reopening it again.
2 2 //
3 3 //toJSON fromJSON toJSON and do a string comparison
4 4
5 5
6 6 // this is just a copy of OutputArea.mime_mape_r in IPython/html/static/notebook/js/outputarea.js
7 7 mime = {
8 8 "text" : "text/plain",
9 9 "html" : "text/html",
10 10 "svg" : "image/svg+xml",
11 11 "png" : "image/png",
12 12 "jpeg" : "image/jpeg",
13 13 "latex" : "text/latex",
14 14 "json" : "application/json",
15 15 "javascript" : "application/javascript",
16 16 };
17 17
18 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 19 var black_dot_png = 'u\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QA\\niAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AA\\nAAACAAHiIbwzAAAAAElFTkSuQmCC\"';
20 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 22 // helper function to ensure that the short_name is found in the toJSON
23 23 // represetnation, while the original in-memory cell retains its long mimetype
24 24 // name, and that fromJSON also gets its long mimetype name
25 25 function assert_has(short_name, json, result, result2) {
26 long_name = mime[short_name];
26 var long_name = mime[short_name];
27 27 this.test.assertFalse(json[0].data.hasOwnProperty(short_name),
28 28 "toJSON() representation doesn't use " + short_name);
29 29 this.test.assertTrue(json[0].data.hasOwnProperty(long_name),
30 30 'toJSON() representation uses ' + long_name);
31 31 this.test.assertTrue(result.data.hasOwnProperty(long_name),
32 32 'toJSON() original embedded JSON keeps ' + long_name);
33 33 this.test.assertTrue(result2.data.hasOwnProperty(long_name),
34 34 'fromJSON() embedded ' + short_name + ' gets mime key ' + long_name);
35 35 }
36 36
37 37 // helper function for checkout that the first two cells have a particular
38 38 // output_type (either 'execute_result' or 'display_data'), and checks the to/fromJSON
39 39 // for a set of mimetype keys, ensuring the old short names ('javascript', 'text',
40 40 // 'png', etc) are not used.
41 41 function check_output_area(output_type, keys) {
42 42 this.wait_for_output(0);
43 json = this.evaluate(function() {
43 var json = this.evaluate(function() {
44 44 var json = IPython.notebook.get_cell(0).output_area.toJSON();
45 45 // appended cell will initially be empty, let's add some output
46 46 IPython.notebook.get_cell(1).output_area.fromJSON(json);
47 47 return json;
48 48 });
49 49 // The evaluate call above happens asynchronously: wait for cell[1] to have output
50 50 this.wait_for_output(1);
51 51 var result = this.get_output_cell(0);
52 52 var result2 = this.get_output_cell(1);
53 53 this.test.assertEquals(result.output_type, output_type,
54 54 'testing ' + output_type + ' for ' + keys.join(' and '));
55 55
56 56 for (var idx in keys) {
57 57 assert_has.apply(this, [keys[idx], json, result, result2]);
58 58 }
59 59 }
60 60
61 61
62 62 // helper function to clear the first two cells, set the text of and execute
63 63 // the first one
64 64 function clear_and_execute(that, code) {
65 65 that.evaluate(function() {
66 66 IPython.notebook.get_cell(0).clear_output();
67 67 IPython.notebook.get_cell(1).clear_output();
68 68 });
69 69 that.then(function () {
70 70 that.set_cell_text(0, code);
71 71 that.execute_cell(0);
72 72 that.wait_for_idle();
73 73 });
74 74 }
75 75
76 76 casper.notebook_test(function () {
77 77 this.evaluate(function () {
78 78 var cell = IPython.notebook.get_cell(0);
79 79 // "we have to make messes to find out who we are"
80 80 cell.set_text([
81 81 "%%javascript",
82 82 "IPython.notebook.insert_cell_below('code')"
83 83 ].join('\n')
84 84 );
85 85 });
86 86
87 87 this.execute_cell_then(0, function () {
88 88 var result = this.get_output_cell(0);
89 89 var num_cells = this.get_cells_length();
90 90 this.test.assertEquals(num_cells, 2, '%%javascript magic works');
91 91 this.test.assertTrue(result.data.hasOwnProperty('application/javascript'),
92 92 'testing JS embedded with mime key');
93 93 });
94 94
95 95 //this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
96 96 this.then(function () {
97 97 clear_and_execute(this, [
98 98 "%%javascript",
99 99 "var a=5;"
100 100 ].join('\n'));
101 101 });
102 102
103 103
104 104 this.then(function () {
105 105 check_output_area.apply(this, ['display_data', ['javascript']]);
106 106
107 107 });
108 108
109 109 this.then(function() {
110 110 clear_and_execute(this, '%lsmagic');
111 111 });
112 112
113 113 this.then(function () {
114 114 check_output_area.apply(this, ['execute_result', ['text', 'json']]);
115 115 });
116 116
117 117 this.then(function() {
118 118 clear_and_execute(this,
119 119 "x = %lsmagic\nfrom IPython.display import display; display(x)");
120 120 });
121 121
122 122 this.then(function ( ) {
123 123 check_output_area.apply(this, ['display_data', ['text', 'json']]);
124 124 });
125 125
126 126 this.then(function() {
127 127 clear_and_execute(this,
128 128 "from IPython.display import Latex; Latex('$X^2$')");
129 129 });
130 130
131 131 this.then(function ( ) {
132 132 check_output_area.apply(this, ['execute_result', ['text', 'latex']]);
133 133 });
134 134
135 135 this.then(function() {
136 136 clear_and_execute(this,
137 137 "from IPython.display import Latex, display; display(Latex('$X^2$'))");
138 138 });
139 139
140 140 this.then(function ( ) {
141 141 check_output_area.apply(this, ['display_data', ['text', 'latex']]);
142 142 });
143 143
144 144 this.then(function() {
145 145 clear_and_execute(this,
146 146 "from IPython.display import HTML; HTML('<b>it works!</b>')");
147 147 });
148 148
149 149 this.then(function ( ) {
150 150 check_output_area.apply(this, ['execute_result', ['text', 'html']]);
151 151 });
152 152
153 153 this.then(function() {
154 154 clear_and_execute(this,
155 155 "from IPython.display import HTML, display; display(HTML('<b>it works!</b>'))");
156 156 });
157 157
158 158 this.then(function ( ) {
159 159 check_output_area.apply(this, ['display_data', ['text', 'html']]);
160 160 });
161 161
162 162
163 163 this.then(function() {
164 164 clear_and_execute(this,
165 165 "from IPython.display import Image; Image(" + black_dot_png + ")");
166 166 });
167 167 this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
168 168
169 169 this.then(function ( ) {
170 170 check_output_area.apply(this, ['execute_result', ['text', 'png']]);
171 171 });
172 172
173 173 this.then(function() {
174 174 clear_and_execute(this,
175 175 "from IPython.display import Image, display; display(Image(" + black_dot_png + "))");
176 176 });
177 177
178 178 this.then(function ( ) {
179 179 check_output_area.apply(this, ['display_data', ['text', 'png']]);
180 180 });
181 181
182 182
183 183 this.then(function() {
184 184 clear_and_execute(this,
185 185 "from IPython.display import Image; Image(" + black_dot_jpeg + ", format='jpeg')");
186 186 });
187 187
188 188 this.then(function ( ) {
189 189 check_output_area.apply(this, ['execute_result', ['text', 'jpeg']]);
190 190 });
191 191
192 192 this.then(function() {
193 193 clear_and_execute(this,
194 194 "from IPython.display import Image, display; display(Image(" + black_dot_jpeg + ", format='jpeg'))");
195 195 });
196 196
197 197 this.then(function ( ) {
198 198 check_output_area.apply(this, ['display_data', ['text', 'jpeg']]);
199 199 });
200 200
201 201 this.then(function() {
202 202 clear_and_execute(this,
203 203 "from IPython.core.display import SVG; SVG(" + svg + ")");
204 204 });
205 205
206 206 this.then(function ( ) {
207 207 check_output_area.apply(this, ['execute_result', ['text', 'svg']]);
208 208 });
209 209
210 210 this.then(function() {
211 211 clear_and_execute(this,
212 212 "from IPython.core.display import SVG, display; display(SVG(" + svg + "))");
213 213 });
214 214
215 215 this.then(function ( ) {
216 216 check_output_area.apply(this, ['display_data', ['text', 'svg']]);
217 217 });
218 218
219 219 this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
220 220
221 221 this.then(function() {
222 222 clear_and_execute(this, [
223 223 "from IPython.core.formatters import HTMLFormatter",
224 224 "x = HTMLFormatter()",
225 225 "x.format_type = 'text/superfancymimetype'",
226 226 "get_ipython().display_formatter.formatters['text/superfancymimetype'] = x",
227 227 "from IPython.display import HTML, display",
228 228 'display(HTML("yo"))',
229 229 "HTML('hello')"].join('\n')
230 230 );
231 231
232 232 });
233 233
234 234 this.wait_for_output(0, 1);
235 235
236 236 this.then(function () {
237 237 var long_name = 'text/superfancymimetype';
238 238 var result = this.get_output_cell(0);
239 239 this.test.assertTrue(result.data.hasOwnProperty(long_name),
240 240 'display_data custom mimetype ' + long_name);
241 241 result = this.get_output_cell(0, 1);
242 242 this.test.assertTrue(result.data.hasOwnProperty(long_name),
243 243 'execute_result custom mimetype ' + long_name);
244 244
245 245 });
246 246
247 247 });
@@ -1,830 +1,831 b''
1 1 //
2 2 // Utility functions for the HTML notebook's CasperJS tests.
3 3 //
4 4 casper.get_notebook_server = function () {
5 5 // Get the URL of a notebook server on which to run tests.
6 6 var port = casper.cli.get("port");
7 7 port = (typeof port === 'undefined') ? '8888' : port;
8 8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
9 9 };
10 10
11 11 casper.open_new_notebook = function () {
12 12 // Create and open a new notebook.
13 13 var baseUrl = this.get_notebook_server();
14 14 this.start(baseUrl);
15 15 this.waitFor(this.page_loaded);
16 16 this.thenClick('#kernel-python2 a, #kernel-python3 a');
17 17
18 18 this.waitForPopup('');
19 19
20 20 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
21 21 this.then(function () {
22 22 this.open(this.popups[0].url);
23 23 });
24 24 this.waitFor(this.page_loaded);
25 25
26 26 // Hook the log and error methods of the console, forcing them to
27 27 // serialize their arguments before printing. This allows the
28 28 // Objects to cross into the phantom/slimer regime for display.
29 29 this.thenEvaluate(function(){
30 30 var serialize_arguments = function(f, context) {
31 31 return function() {
32 32 var pretty_arguments = [];
33 33 for (var i = 0; i < arguments.length; i++) {
34 34 var value = arguments[i];
35 35 if (value instanceof Object) {
36 36 var name = value.name || 'Object';
37 37 // Print a JSON string representation of the object.
38 38 // If we don't do this, [Object object] gets printed
39 39 // by casper, which is useless. The long regular
40 40 // expression reduces the verbosity of the JSON.
41 41 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
42 42 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
43 43 .replace(/\n(\s+)?\n/g, '\n'));
44 44 } else {
45 45 pretty_arguments.push(value);
46 46 }
47 47 }
48 48 f.apply(context, pretty_arguments);
49 49 };
50 50 };
51 51 console.log = serialize_arguments(console.log, console);
52 52 console.error = serialize_arguments(console.error, console);
53 53 });
54 54
55 55 // Make sure the kernel has started
56 56 this.waitFor(this.kernel_running);
57 57 // track the IPython busy/idle state
58 58 this.thenEvaluate(function () {
59 59 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
60 60
61 61 events.on('kernel_idle.Kernel',function () {
62 62 IPython._status = 'idle';
63 63 });
64 64 events.on('kernel_busy.Kernel',function () {
65 65 IPython._status = 'busy';
66 66 });
67 67 });
68 68 });
69 69
70 70 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
71 71 // sure the notebook has actually been loaded into the IPython namespace
72 72 // before running any tests.
73 73 this.waitFor(function() {
74 74 return this.evaluate(function () {
75 75 return IPython.notebook;
76 76 });
77 77 });
78 78 };
79 79
80 80 casper.page_loaded = function() {
81 81 // Return whether or not the kernel is running.
82 82 return this.evaluate(function() {
83 83 return typeof IPython !== "undefined" &&
84 84 IPython.page !== undefined;
85 85 });
86 86 };
87 87
88 88 casper.kernel_running = function() {
89 89 // Return whether or not the kernel is running.
90 90 return this.evaluate(function() {
91 91 return IPython &&
92 92 IPython.notebook &&
93 93 IPython.notebook.kernel &&
94 94 IPython.notebook.kernel.is_connected();
95 95 });
96 96 };
97 97
98 98 casper.kernel_disconnected = function() {
99 99 return this.evaluate(function() {
100 100 return IPython.notebook.kernel.is_fully_disconnected();
101 101 });
102 102 };
103 103
104 104 casper.wait_for_kernel_ready = function () {
105 105 this.waitFor(this.kernel_running);
106 106 this.thenEvaluate(function () {
107 107 IPython._kernel_ready = false;
108 108 IPython.notebook.kernel.kernel_info(
109 109 function () {
110 110 IPython._kernel_ready = true;
111 111 });
112 112 });
113 113 this.waitFor(function () {
114 114 return this.evaluate(function () {
115 115 return IPython._kernel_ready;
116 116 });
117 117 });
118 118 };
119 119
120 120 casper.shutdown_current_kernel = function () {
121 121 // Shut down the current notebook's kernel.
122 122 this.thenEvaluate(function() {
123 123 IPython.notebook.session.delete();
124 124 });
125 125 // We close the page right after this so we need to give it time to complete.
126 126 this.wait(1000);
127 127 };
128 128
129 129 casper.delete_current_notebook = function () {
130 130 // Delete created notebook.
131 131
132 132 // For some unknown reason, this doesn't work?!?
133 133 this.thenEvaluate(function() {
134 134 IPython.notebook.delete();
135 135 });
136 136 };
137 137
138 138 casper.wait_for_busy = function () {
139 139 // Waits for the notebook to enter a busy state.
140 140 this.waitFor(function () {
141 141 return this.evaluate(function () {
142 142 return IPython._status == 'busy';
143 143 });
144 144 });
145 145 };
146 146
147 147 casper.wait_for_idle = function () {
148 148 // Waits for the notebook to idle.
149 149 this.waitFor(function () {
150 150 return this.evaluate(function () {
151 151 return IPython._status == 'idle';
152 152 });
153 153 });
154 154 };
155 155
156 156 casper.wait_for_output = function (cell_num, out_num) {
157 157 // wait for the nth output in a given cell
158 158 this.wait_for_idle();
159 159 out_num = out_num || 0;
160 160 this.then(function() {
161 161 this.waitFor(function (c, o) {
162 162 return this.evaluate(function get_output(c, o) {
163 163 var cell = IPython.notebook.get_cell(c);
164 164 return cell.output_area.outputs.length > o;
165 165 },
166 166 // pass parameter from the test suite js to the browser code js
167 167 {c : cell_num, o : out_num});
168 168 });
169 169 },
170 170 function then() { },
171 171 function timeout() {
172 172 this.echo("wait_for_output timed out!");
173 173 });
174 174 };
175 175
176 176 casper.wait_for_widget = function (widget_info) {
177 177 // wait for a widget msg que to reach 0
178 178 //
179 179 // Parameters
180 180 // ----------
181 181 // widget_info : object
182 182 // Object which contains info related to the widget. The model_id property
183 183 // is used to identify the widget.
184 184
185 185 // Clear the results of a previous query, if they exist. Make sure a
186 186 // dictionary exists to store the async results in.
187 187 this.thenEvaluate(function(model_id) {
188 188 if (window.pending_msgs === undefined) {
189 189 window.pending_msgs = {};
190 190 } else {
191 191 window.pending_msgs[model_id] = -1;
192 192 }
193 193 }, {model_id: widget_info.model_id});
194 194
195 195 // Wait for the pending messages to be 0.
196 196 this.waitFor(function () {
197 197 var pending = this.evaluate(function (model_id) {
198 198
199 199 // Get the model. Once the model is had, store it's pending_msgs
200 200 // count in the window's dictionary.
201 201 IPython.notebook.kernel.widget_manager.get_model(model_id)
202 202 .then(function(model) {
203 203 window.pending_msgs[model_id] = model.pending_msgs;
204 204 });
205 205
206 206 // Return the pending_msgs result.
207 207 return window.pending_msgs[model_id];
208 208 }, {model_id: widget_info.model_id});
209 209
210 210 if (pending === 0) {
211 211 return true;
212 212 } else {
213 213 return false;
214 214 }
215 215 });
216 216 };
217 217
218 218 casper.get_output_cell = function (cell_num, out_num) {
219 219 // return an output of a given cell
220 220 out_num = out_num || 0;
221 221 var result = casper.evaluate(function (c, o) {
222 222 var cell = IPython.notebook.get_cell(c);
223 223 return cell.output_area.outputs[o];
224 224 },
225 225 {c : cell_num, o : out_num});
226 226 if (!result) {
227 227 var num_outputs = casper.evaluate(function (c) {
228 228 var cell = IPython.notebook.get_cell(c);
229 229 return cell.output_area.outputs.length;
230 230 },
231 231 {c : cell_num});
232 232 this.test.assertTrue(false,
233 233 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
234 234 );
235 235 } else {
236 236 return result;
237 237 }
238 238 };
239 239
240 240 casper.get_cells_length = function () {
241 241 // return the number of cells in the notebook
242 242 var result = casper.evaluate(function () {
243 243 return IPython.notebook.get_cells().length;
244 244 });
245 245 return result;
246 246 };
247 247
248 248 casper.set_cell_text = function(index, text){
249 249 // Set the text content of a cell.
250 250 this.evaluate(function (index, text) {
251 251 var cell = IPython.notebook.get_cell(index);
252 252 cell.set_text(text);
253 253 }, index, text);
254 254 };
255 255
256 256 casper.get_cell_text = function(index){
257 257 // Get the text content of a cell.
258 258 return this.evaluate(function (index) {
259 259 var cell = IPython.notebook.get_cell(index);
260 260 return cell.get_text();
261 261 }, index);
262 262 };
263 263
264 264 casper.insert_cell_at_bottom = function(cell_type){
265 265 // Inserts a cell at the bottom of the notebook
266 266 // Returns the new cell's index.
267 267 return this.evaluate(function (cell_type) {
268 268 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
269 269 return IPython.notebook.find_cell_index(cell);
270 270 }, cell_type);
271 271 };
272 272
273 273 casper.append_cell = function(text, cell_type) {
274 274 // Insert a cell at the bottom of the notebook and set the cells text.
275 275 // Returns the new cell's index.
276 276 var index = this.insert_cell_at_bottom(cell_type);
277 277 if (text !== undefined) {
278 278 this.set_cell_text(index, text);
279 279 }
280 280 return index;
281 281 };
282 282
283 283 casper.execute_cell = function(index, expect_failure){
284 284 // Asynchronously executes a cell by index.
285 285 // Returns the cell's index.
286 286
287 287 if (expect_failure === undefined) expect_failure = false;
288 288 var that = this;
289 289 this.then(function(){
290 290 that.evaluate(function (index) {
291 291 var cell = IPython.notebook.get_cell(index);
292 292 cell.execute();
293 293 }, index);
294 294 });
295 295 this.wait_for_idle();
296 296
297 297 this.then(function () {
298 298 var error = that.evaluate(function (index) {
299 299 var cell = IPython.notebook.get_cell(index);
300 300 var outputs = cell.output_area.outputs;
301 301 for (var i = 0; i < outputs.length; i++) {
302 302 if (outputs[i].output_type == 'error') {
303 303 return outputs[i];
304 304 }
305 305 }
306 306 return false;
307 307 }, index);
308 308 if (error === null) {
309 309 this.test.fail("Failed to check for error output");
310 310 }
311 311 if (expect_failure && error === false) {
312 312 this.test.fail("Expected error while running cell");
313 313 } else if (!expect_failure && error !== false) {
314 314 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
315 315 }
316 316 });
317 317 return index;
318 318 };
319 319
320 320 casper.execute_cell_then = function(index, then_callback, expect_failure) {
321 321 // Synchronously executes a cell by index.
322 322 // Optionally accepts a then_callback parameter. then_callback will get called
323 323 // when the cell has finished executing.
324 324 // Returns the cell's index.
325 325 var return_val = this.execute_cell(index, expect_failure);
326 326
327 327 this.wait_for_idle();
328 328
329 329 var that = this;
330 330 this.then(function(){
331 331 if (then_callback!==undefined) {
332 332 then_callback.apply(that, [index]);
333 333 }
334 334 });
335 335
336 336 return return_val;
337 337 };
338 338
339 339 casper.wait_for_element = function(index, selector){
340 340 // Utility function that allows us to easily wait for an element
341 341 // within a cell. Uses JQuery selector to look for the element.
342 342 var that = this;
343 343 this.waitFor(function() {
344 344 return that.cell_element_exists(index, selector);
345 345 });
346 346 };
347 347
348 348 casper.cell_element_exists = function(index, selector){
349 349 // Utility function that allows us to easily check if an element exists
350 350 // within a cell. Uses JQuery selector to look for the element.
351 351 return casper.evaluate(function (index, selector) {
352 352 var $cell = IPython.notebook.get_cell(index).element;
353 353 return $cell.find(selector).length > 0;
354 354 }, index, selector);
355 355 };
356 356
357 357 casper.cell_element_function = function(index, selector, function_name, function_args){
358 358 // Utility function that allows us to execute a jQuery function on an
359 359 // element within a cell.
360 360 return casper.evaluate(function (index, selector, function_name, function_args) {
361 361 var $cell = IPython.notebook.get_cell(index).element;
362 362 var $el = $cell.find(selector);
363 363 return $el[function_name].apply($el, function_args);
364 364 }, index, selector, function_name, function_args);
365 365 };
366 366
367 367 casper.validate_notebook_state = function(message, mode, cell_index) {
368 368 // Validate the entire dual mode state of the notebook. Make sure no more than
369 369 // one cell is selected, focused, in edit mode, etc...
370 370
371 371 // General tests.
372 372 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
373 373 message + '; keyboard and notebook modes match');
374 374 // Is the selected cell the only cell that is selected?
375 375 if (cell_index!==undefined) {
376 376 this.test.assert(this.is_only_cell_selected(cell_index),
377 377 message + '; cell ' + cell_index + ' is the only cell selected');
378 378 }
379 379
380 380 // Mode specific tests.
381 381 if (mode==='command') {
382 382 // Are the notebook and keyboard manager in command mode?
383 383 this.test.assertEquals(this.get_keyboard_mode(), 'command',
384 384 message + '; in command mode');
385 385 // Make sure there isn't a single cell in edit mode.
386 386 this.test.assert(this.is_only_cell_edit(null),
387 387 message + '; all cells in command mode');
388 388 this.test.assert(this.is_cell_editor_focused(null),
389 389 message + '; no cell editors are focused while in command mode');
390 390
391 391 } else if (mode==='edit') {
392 392 // Are the notebook and keyboard manager in edit mode?
393 393 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
394 394 message + '; in edit mode');
395 395 if (cell_index!==undefined) {
396 396 // Is the specified cell the only cell in edit mode?
397 397 this.test.assert(this.is_only_cell_edit(cell_index),
398 398 message + '; cell ' + cell_index + ' is the only cell in edit mode '+ this.cells_modes());
399 399 // Is the specified cell the only cell with a focused code mirror?
400 400 this.test.assert(this.is_cell_editor_focused(cell_index),
401 401 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
402 402 }
403 403
404 404 } else {
405 405 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
406 406 }
407 407 };
408 408
409 409 casper.select_cell = function(index) {
410 410 // Select a cell in the notebook.
411 411 this.evaluate(function (i) {
412 412 IPython.notebook.select(i);
413 413 }, {i: index});
414 414 };
415 415
416 416 casper.click_cell_editor = function(index) {
417 417 // Emulate a click on a cell's editor.
418 418
419 419 // Code Mirror does not play nicely with emulated brower events.
420 420 // Instead of trying to emulate a click, here we run code similar to
421 421 // the code used in Code Mirror that handles the mousedown event on a
422 422 // region of codemirror that the user can focus.
423 423 this.evaluate(function (i) {
424 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 426 cm.display.input.focus();
427 }
427 428 }, {i: index});
428 429 };
429 430
430 431 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
431 432 // Set the Code Mirror instance cursor's location.
432 433 this.evaluate(function (i, l, c) {
433 434 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
434 435 }, {i: index, l: line_index, c: char_index});
435 436 };
436 437
437 438 casper.focus_notebook = function() {
438 439 // Focus the notebook div.
439 440 this.evaluate(function (){
440 441 $('#notebook').focus();
441 442 }, {});
442 443 };
443 444
444 445 casper.trigger_keydown = function() {
445 446 // Emulate a keydown in the notebook.
446 447 for (var i = 0; i < arguments.length; i++) {
447 448 this.evaluate(function (k) {
448 449 var element = $(document);
449 450 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
450 451 element.trigger(event);
451 452 }, {k: arguments[i]});
452 453 }
453 454 };
454 455
455 456 casper.get_keyboard_mode = function() {
456 457 // Get the mode of the keyboard manager.
457 458 return this.evaluate(function() {
458 459 return IPython.keyboard_manager.mode;
459 460 }, {});
460 461 };
461 462
462 463 casper.get_notebook_mode = function() {
463 464 // Get the mode of the notebook.
464 465 return this.evaluate(function() {
465 466 return IPython.notebook.mode;
466 467 }, {});
467 468 };
468 469
469 470 casper.get_cell = function(index) {
470 471 // Get a single cell.
471 472 //
472 473 // Note: Handles to DOM elements stored in the cell will be useless once in
473 474 // CasperJS context.
474 475 return this.evaluate(function(i) {
475 476 var cell = IPython.notebook.get_cell(i);
476 477 if (cell) {
477 478 return cell;
478 479 }
479 480 return null;
480 481 }, {i : index});
481 482 };
482 483
483 484 casper.is_cell_editor_focused = function(index) {
484 485 // Make sure a cell's editor is the only editor focused on the page.
485 486 return this.evaluate(function(i) {
486 487 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
487 488 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
488 489 if (i === null) {
489 490 return focused_textarea.length === 0;
490 491 } else {
491 492 var cell = IPython.notebook.get_cell(i);
492 493 if (cell) {
493 494 return cell.code_mirror.getInputField() == focused_textarea[0];
494 495 }
495 496 }
496 497 return false;
497 498 }, {i : index});
498 499 };
499 500
500 501 casper.is_only_cell_selected = function(index) {
501 502 // Check if a cell is the only cell selected.
502 503 // Pass null as the index to check if no cells are selected.
503 504 return this.is_only_cell_on(index, 'selected', 'unselected');
504 505 };
505 506
506 507 casper.is_only_cell_edit = function(index) {
507 508 // Check if a cell is the only cell in edit mode.
508 509 // Pass null as the index to check if all of the cells are in command mode.
509 510 var cells_length = this.get_cells_length();
510 511 for (var j = 0; j < cells_length; j++) {
511 512 if (j === index) {
512 513 if (!this.cell_mode_is(j, 'edit')) {
513 514 return false;
514 515 }
515 516 } else {
516 517 if (this.cell_mode_is(j, 'edit')) {
517 518 return false;
518 519 }
519 520 }
520 521 }
521 522 return true;
522 523 };
523 524
524 525 casper.is_only_cell_on = function(i, on_class, off_class) {
525 526 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
526 527 // All of the other cells are checked for the `off_class` DOM class.
527 528 // Pass null as the index to check if all of the cells have the `off_class`.
528 529 var cells_length = this.get_cells_length();
529 530 for (var j = 0; j < cells_length; j++) {
530 531 if (j === i) {
531 532 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
532 533 return false;
533 534 }
534 535 } else {
535 536 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
536 537 return false;
537 538 }
538 539 }
539 540 }
540 541 return true;
541 542 };
542 543
543 544 casper.cells_modes = function(){
544 545 return this.evaluate(function(){
545 546 return IPython.notebook.get_cells().map(function(x,c){return x.mode})
546 547 }, {});
547 548 };
548 549
549 550 casper.cell_mode_is = function(index, mode) {
550 551 // Check if a cell is in a specific mode
551 552 return this.evaluate(function(i, m) {
552 553 var cell = IPython.notebook.get_cell(i);
553 554 if (cell) {
554 555 return cell.mode === m;
555 556 }
556 557 return false;
557 558 }, {i : index, m: mode});
558 559 };
559 560
560 561
561 562 casper.cell_has_class = function(index, classes) {
562 563 // Check if a cell has a class.
563 564 return this.evaluate(function(i, c) {
564 565 var cell = IPython.notebook.get_cell(i);
565 566 if (cell) {
566 567 return cell.element.hasClass(c);
567 568 }
568 569 return false;
569 570 }, {i : index, c: classes});
570 571 };
571 572
572 573 casper.is_cell_rendered = function (index) {
573 574 return this.evaluate(function(i) {
574 575 return !!IPython.notebook.get_cell(i).rendered;
575 576 }, {i:index});
576 577 };
577 578
578 579 casper.assert_colors_equal = function (hex_color, local_color, msg) {
579 580 // Tests to see if two colors are equal.
580 581 //
581 582 // Parameters
582 583 // hex_color: string
583 584 // Hexadecimal color code, with or without preceeding hash character.
584 585 // local_color: string
585 586 // Local color representation. Can either be hexadecimal (default for
586 587 // phantom) or rgb (default for slimer).
587 588
588 589 // Remove parentheses, hashes, semi-colons, and space characters.
589 590 hex_color = hex_color.replace(/[\(\); #]/, '');
590 591 local_color = local_color.replace(/[\(\); #]/, '');
591 592
592 593 // If the local color is rgb, clean it up and replace
593 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 596 local_color = '';
596 597 for (var i = 0; i < components.length; i++) {
597 598 var part = parseInt(components[i]).toString(16);
598 599 while (part.length < 2) part = '0' + part;
599 600 local_color += part;
600 601 }
601 602 }
602 603
603 604 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
604 605 };
605 606
606 607 casper.notebook_test = function(test) {
607 608 // Wrap a notebook test to reduce boilerplate.
608 609 this.open_new_notebook();
609 610
610 611 // Echo whether or not we are running this test using SlimerJS
611 612 if (this.evaluate(function(){
612 613 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
613 614 })) {
614 615 console.log('This test is running in SlimerJS.');
615 616 this.slimerjs = true;
616 617 }
617 618
618 619 // Make sure to remove the onbeforeunload callback. This callback is
619 620 // responsible for the "Are you sure you want to quit?" type messages.
620 621 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
621 622 this.then(function(){
622 623 this.evaluate(function(){
623 624 window.onbeforeunload = function(){};
624 625 });
625 626 });
626 627
627 628 this.then(test);
628 629
629 630 // Kill the kernel and delete the notebook.
630 631 this.shutdown_current_kernel();
631 632 // This is still broken but shouldn't be a problem for now.
632 633 // this.delete_current_notebook();
633 634
634 635 // This is required to clean up the page we just finished with. If we don't call this
635 636 // casperjs will leak file descriptors of all the open WebSockets in that page. We
636 637 // have to set this.page=null so that next time casper.start runs, it will create a
637 638 // new page from scratch.
638 639 this.then(function () {
639 640 this.page.close();
640 641 this.page = null;
641 642 });
642 643
643 644 // Run the browser automation.
644 645 this.run(function() {
645 646 this.test.done();
646 647 });
647 648 };
648 649
649 650 casper.wait_for_dashboard = function () {
650 651 // Wait for the dashboard list to load.
651 652 casper.waitForSelector('.list_item');
652 653 };
653 654
654 655 casper.open_dashboard = function () {
655 656 // Start casper by opening the dashboard page.
656 657 var baseUrl = this.get_notebook_server();
657 658 this.start(baseUrl);
658 659 this.waitFor(this.page_loaded);
659 660 this.wait_for_dashboard();
660 661 };
661 662
662 663 casper.dashboard_test = function (test) {
663 664 // Open the dashboard page and run a test.
664 665 this.open_dashboard();
665 666 this.then(test);
666 667
667 668 this.then(function () {
668 669 this.page.close();
669 670 this.page = null;
670 671 });
671 672
672 673 // Run the browser automation.
673 674 this.run(function() {
674 675 this.test.done();
675 676 });
676 677 };
677 678
678 679 // note that this will only work for UNIQUE events -- if you want to
679 680 // listen for the same event twice, this will not work!
680 681 casper.event_test = function (name, events, action, timeout) {
681 682
682 683 // set up handlers to listen for each of the events
683 684 this.thenEvaluate(function (events) {
684 685 var make_handler = function (event) {
685 686 return function () {
686 687 IPython._events_triggered.push(event);
687 688 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
688 689 delete IPython._event_handlers[event];
689 690 };
690 691 };
691 692 IPython._event_handlers = {};
692 693 IPython._events_triggered = [];
693 694 for (var i=0; i < events.length; i++) {
694 695 IPython._event_handlers[events[i]] = make_handler(events[i]);
695 696 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
696 697 }
697 698 }, [events]);
698 699
699 700 // execute the requested action
700 701 this.then(action);
701 702
702 703 // wait for all the events to be triggered
703 704 this.waitFor(function () {
704 705 return this.evaluate(function (events) {
705 706 return IPython._events_triggered.length >= events.length;
706 707 }, [events]);
707 708 }, undefined, undefined, timeout);
708 709
709 710 // test that the events were triggered in the proper order
710 711 this.then(function () {
711 712 var triggered = this.evaluate(function () {
712 713 return IPython._events_triggered;
713 714 });
714 715 var handlers = this.evaluate(function () {
715 716 return Object.keys(IPython._event_handlers);
716 717 });
717 718 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
718 719 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
719 720 for (var i=0; i < events.length; i++) {
720 721 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
721 722 }
722 723 });
723 724
724 725 // turn off any remaining event listeners
725 726 this.thenEvaluate(function () {
726 727 for (var event in IPython._event_handlers) {
727 728 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
728 729 delete IPython._event_handlers[event];
729 730 }
730 731 });
731 732 };
732 733
733 734 casper.options.waitTimeout=10000;
734 735 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
735 736 this.echo("Timeout for " + casper.get_notebook_server());
736 737 this.echo("Is the notebook server running?");
737 738 });
738 739
739 740 casper.print_log = function () {
740 741 // Pass `console.log` calls from page JS to casper.
741 742 this.on('remote.message', function(msg) {
742 743 this.echo('Remote message caught: ' + msg);
743 744 });
744 745 };
745 746
746 747 casper.on("page.error", function onError(msg, trace) {
747 748 // show errors in the browser
748 749 this.echo("Page Error");
749 750 this.echo(" Message: " + msg.split('\n').join('\n '));
750 751 this.echo(" Call stack:");
751 752 var local_path = this.get_notebook_server();
752 753 for (var i = 0; i < trace.length; i++) {
753 754 var frame = trace[i];
754 755 var file = frame.file;
755 756 // shorten common phantomjs evaluate url
756 757 // this will have a different value on slimerjs
757 758 if (file === "phantomjs://webpage.evaluate()") {
758 759 file = "evaluate";
759 760 }
760 761 // remove the version tag from the path
761 762 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
762 763 // remove the local address from the beginning of the path
763 764 if (file.indexOf(local_path) === 0) {
764 765 file = file.substr(local_path.length);
765 766 }
766 767 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
767 768 this.echo(" line " + frame.line + " of " + file + frame_text);
768 769 }
769 770 });
770 771
771 772
772 773 casper.capture_log = function () {
773 774 // show captured errors
774 775 var captured_log = [];
775 776 var seen_errors = 0;
776 777 this.on('remote.message', function(msg) {
777 778 captured_log.push(msg);
778 779 });
779 780
780 781 var that = this;
781 782 this.test.on("test.done", function (result) {
782 783 // test.done runs per-file,
783 784 // but suiteResults is per-suite (directory)
784 785 var current_errors;
785 786 if (this.suiteResults) {
786 787 // casper 1.1 has suiteResults
787 788 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
788 789 } else {
789 790 // casper 1.0 has testResults instead
790 791 current_errors = this.testResults.failed;
791 792 }
792 793
793 794 if (current_errors > seen_errors && captured_log.length > 0) {
794 795 casper.echo("\nCaptured console.log:");
795 796 for (var i = 0; i < captured_log.length; i++) {
796 797 var output = String(captured_log[i]).split('\n');
797 798 for (var j = 0; j < output.length; j++) {
798 799 casper.echo(" " + output[j]);
799 800 }
800 801 }
801 802 }
802 803
803 804 seen_errors = current_errors;
804 805 captured_log = [];
805 806 });
806 807 };
807 808
808 809 casper.interact = function() {
809 810 // Start an interactive Javascript console.
810 811 var system = require('system');
811 812 system.stdout.writeLine('JS interactive console.');
812 813 system.stdout.writeLine('Type `exit` to quit.');
813 814
814 815 function read_line() {
815 816 system.stdout.writeLine('JS: ');
816 817 var line = system.stdin.readLine();
817 818 return line;
818 819 }
819 820
820 821 var input = read_line();
821 822 while (input.trim() != 'exit') {
822 823 var output = this.evaluate(function(code) {
823 824 return String(eval(code));
824 825 }, {code: input});
825 826 system.stdout.writeLine('\nOut: ' + output);
826 827 input = read_line();
827 828 }
828 829 };
829 830
830 831 casper.capture_log();
@@ -1,91 +1,92 b''
1 1 // Test widget bool class
2 2 casper.notebook_test(function () {
3 "use strict";
3 4
4 5 // Create a checkbox and togglebutton.
5 6 var bool_index = this.append_cell(
6 7 'from IPython.html import widgets\n' +
7 8 'from IPython.display import display, clear_output\n' +
8 9 'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' +
9 10 ' widgets.ToggleButton(description="Title", value=True)]\n' +
10 11 'display(bool_widgets[0])\n' +
11 12 'display(bool_widgets[1])\n' +
12 13 'print("Success")');
13 14 this.execute_cell_then(bool_index, function(index){
14 15 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
15 16 'Create bool widget cell executed with correct output.');
16 17 });
17 18
18 19 // Wait for the widgets to actually display.
19 20 var widget_checkbox_selector = '.widget-area .widget-subarea .widget-hbox input';
20 21 var widget_togglebutton_selector = '.widget-area .widget-subarea button';
21 22 this.wait_for_element(bool_index, widget_checkbox_selector);
22 23 this.wait_for_element(bool_index, widget_togglebutton_selector);
23 24
24 25 // Continue the tests.
25 26 this.then(function() {
26 27 this.test.assert(this.cell_element_exists(bool_index,
27 28 '.widget-area .widget-subarea'),
28 29 'Widget subarea exists.');
29 30
30 31 this.test.assert(this.cell_element_exists(bool_index,
31 32 widget_checkbox_selector),
32 33 'Checkbox exists.');
33 34
34 35 this.test.assert(this.cell_element_function(bool_index,
35 36 widget_checkbox_selector, 'prop', ['checked']),
36 37 'Checkbox is checked.');
37 38
38 39 this.test.assert(this.cell_element_exists(bool_index,
39 40 '.widget-area .widget-subarea .widget-hbox .widget-label'),
40 41 'Checkbox label exists.');
41 42
42 43 this.test.assert(this.cell_element_function(bool_index,
43 44 '.widget-area .widget-subarea .widget-hbox .widget-label', 'html')=="Title",
44 45 'Checkbox labeled correctly.');
45 46
46 47 this.test.assert(this.cell_element_exists(bool_index,
47 48 widget_togglebutton_selector),
48 49 'Toggle button exists.');
49 50
50 51 this.test.assert(this.cell_element_function(bool_index,
51 52 widget_togglebutton_selector, 'html')=="Title",
52 53 'Toggle button labeled correctly.');
53 54
54 55 this.test.assert(this.cell_element_function(bool_index,
55 56 widget_togglebutton_selector, 'hasClass', ['active']),
56 57 'Toggle button is toggled.');
57 58 });
58 59
59 60 // Try changing the state of the widgets programatically.
60 index = this.append_cell(
61 var index = this.append_cell(
61 62 'bool_widgets[0].value = False\n' +
62 63 'bool_widgets[1].value = False\n' +
63 64 'print("Success")');
64 65 this.execute_cell_then(index, function(index){
65 66 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
66 67 'Change bool widget value cell executed with correct output.');
67 68
68 69 this.test.assert(! this.cell_element_function(bool_index,
69 70 widget_checkbox_selector, 'prop', ['checked']),
70 71 'Checkbox is not checked. (1)');
71 72
72 73 this.test.assert(! this.cell_element_function(bool_index,
73 74 widget_togglebutton_selector, 'hasClass', ['active']),
74 75 'Toggle button is not toggled. (1)');
75 76
76 77 // Try toggling the bool by clicking on the checkbox.
77 78 this.cell_element_function(bool_index, widget_checkbox_selector, 'click');
78 79
79 80 this.test.assert(this.cell_element_function(bool_index,
80 81 widget_checkbox_selector, 'prop', ['checked']),
81 82 'Checkbox is checked. (2)');
82 83
83 84 // Try toggling the bool by clicking on the toggle button.
84 85 this.cell_element_function(bool_index, widget_togglebutton_selector, 'click');
85 86
86 87 this.test.assert(this.cell_element_function(bool_index,
87 88 widget_togglebutton_selector, 'hasClass', ['active']),
88 89 'Toggle button is toggled. (3)');
89 90
90 91 });
91 92 });
@@ -1,48 +1,49 b''
1 1 // Test image class
2 2 casper.notebook_test(function () {
3 index = this.append_cell(
3 "use strict";
4 var index = this.append_cell(
4 5 'from IPython.html import widgets\n' +
5 6 'from IPython.display import display, clear_output\n' +
6 7 'print("Success")');
7 8 this.execute_cell_then(index);
8 9
9 10 // Get the temporary directory that the test server is running in.
10 11 var cwd = '';
11 12 index = this.append_cell('!echo $(pwd)');
12 13 this.execute_cell_then(index, function(index){
13 14 cwd = this.get_output_cell(index).text.trim();
14 15 });
15 16
16 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 19 var image_index = this.append_cell(
19 20 'import base64\n' +
20 21 'data = base64.b64decode("' + test_jpg + '")\n' +
21 22 'image = widgets.Image()\n' +
22 23 'image.format = "jpeg"\n' +
23 24 'image.value = data\n' +
24 25 'image.width = "50px"\n' +
25 26 'image.height = "50px"\n' +
26 27 'display(image)\n' +
27 28 'print("Success")\n');
28 29 this.execute_cell_then(image_index, function(index){
29 30 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
30 31 'Create image executed with correct output.');
31 32 });
32 33
33 34 // Wait for the widget to actually display.
34 35 var img_selector = '.widget-area .widget-subarea img';
35 36 this.wait_for_element(image_index, img_selector);
36 37
37 38 this.then(function(){
38 39 this.test.assert(this.cell_element_exists(image_index,
39 40 '.widget-area .widget-subarea'),
40 41 'Widget subarea exists.');
41 42
42 43 this.test.assert(this.cell_element_exists(image_index, img_selector), 'Image exists.');
43 44
44 45 // Verify that the image's base64 data has made it into the DOM.
45 46 var img_src = this.cell_element_function(image_index, img_selector, 'attr', ['src']);
46 47 this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.');
47 48 });
48 49 });
@@ -1,586 +1,587 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import os
10 10 import sys
11 11 import errno
12 12 import shutil
13 13 import random
14 14 import tempfile
15 15 import glob
16 16 from warnings import warn
17 17 from hashlib import md5
18 18
19 19 import IPython
20 20 from IPython.testing.skipdoctest import skip_doctest
21 21 from IPython.utils.process import system
22 22 from IPython.utils.importstring import import_item
23 23 from IPython.utils import py3compat
24 24 #-----------------------------------------------------------------------------
25 25 # Code
26 26 #-----------------------------------------------------------------------------
27 27
28 28 fs_encoding = sys.getfilesystemencoding()
29 29
30 def _get_long_path_name(path):
31 """Dummy no-op."""
32 return path
33
34 30 def _writable_dir(path):
35 31 """Whether `path` is a directory, to which the user has write access."""
36 32 return os.path.isdir(path) and os.access(path, os.W_OK)
37 33
38 34 if sys.platform == 'win32':
39 35 @skip_doctest
40 36 def _get_long_path_name(path):
41 37 """Get a long path name (expand ~) on Windows using ctypes.
42 38
43 39 Examples
44 40 --------
45 41
46 42 >>> get_long_path_name('c:\\docume~1')
47 43 u'c:\\\\Documents and Settings'
48 44
49 45 """
50 46 try:
51 47 import ctypes
52 48 except ImportError:
53 49 raise ImportError('you need to have ctypes installed for this to work')
54 50 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
55 51 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
56 52 ctypes.c_uint ]
57 53
58 54 buf = ctypes.create_unicode_buffer(260)
59 55 rv = _GetLongPathName(path, buf, 260)
60 56 if rv == 0 or rv > 260:
61 57 return path
62 58 else:
63 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 67 def get_long_path_name(path):
67 68 """Expand a path into its long form.
68 69
69 70 On Windows this expands any ~ in the paths. On other platforms, it is
70 71 a null operation.
71 72 """
72 73 return _get_long_path_name(path)
73 74
74 75
75 76 def unquote_filename(name, win32=(sys.platform=='win32')):
76 77 """ On Windows, remove leading and trailing quotes from filenames.
77 78 """
78 79 if win32:
79 80 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
80 81 name = name[1:-1]
81 82 return name
82 83
83 84 def compress_user(path):
84 85 """Reverse of :func:`os.path.expanduser`
85 86 """
86 87 home = os.path.expanduser('~')
87 88 if path.startswith(home):
88 89 path = "~" + path[len(home):]
89 90 return path
90 91
91 92 def get_py_filename(name, force_win32=None):
92 93 """Return a valid python filename in the current directory.
93 94
94 95 If the given name is not a file, it adds '.py' and searches again.
95 96 Raises IOError with an informative message if the file isn't found.
96 97
97 98 On Windows, apply Windows semantics to the filename. In particular, remove
98 99 any quoting that has been applied to it. This option can be forced for
99 100 testing purposes.
100 101 """
101 102
102 103 name = os.path.expanduser(name)
103 104 if force_win32 is None:
104 105 win32 = (sys.platform == 'win32')
105 106 else:
106 107 win32 = force_win32
107 108 name = unquote_filename(name, win32=win32)
108 109 if not os.path.isfile(name) and not name.endswith('.py'):
109 110 name += '.py'
110 111 if os.path.isfile(name):
111 112 return name
112 113 else:
113 114 raise IOError('File `%r` not found.' % name)
114 115
115 116
116 117 def filefind(filename, path_dirs=None):
117 118 """Find a file by looking through a sequence of paths.
118 119
119 120 This iterates through a sequence of paths looking for a file and returns
120 121 the full, absolute path of the first occurence of the file. If no set of
121 122 path dirs is given, the filename is tested as is, after running through
122 123 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
123 124
124 125 filefind('myfile.txt')
125 126
126 127 will find the file in the current working dir, but::
127 128
128 129 filefind('~/myfile.txt')
129 130
130 131 Will find the file in the users home directory. This function does not
131 132 automatically try any paths, such as the cwd or the user's home directory.
132 133
133 134 Parameters
134 135 ----------
135 136 filename : str
136 137 The filename to look for.
137 138 path_dirs : str, None or sequence of str
138 139 The sequence of paths to look for the file in. If None, the filename
139 140 need to be absolute or be in the cwd. If a string, the string is
140 141 put into a sequence and the searched. If a sequence, walk through
141 142 each element and join with ``filename``, calling :func:`expandvars`
142 143 and :func:`expanduser` before testing for existence.
143 144
144 145 Returns
145 146 -------
146 147 Raises :exc:`IOError` or returns absolute path to file.
147 148 """
148 149
149 150 # If paths are quoted, abspath gets confused, strip them...
150 151 filename = filename.strip('"').strip("'")
151 152 # If the input is an absolute path, just check it exists
152 153 if os.path.isabs(filename) and os.path.isfile(filename):
153 154 return filename
154 155
155 156 if path_dirs is None:
156 157 path_dirs = ("",)
157 158 elif isinstance(path_dirs, py3compat.string_types):
158 159 path_dirs = (path_dirs,)
159 160
160 161 for path in path_dirs:
161 162 if path == '.': path = py3compat.getcwd()
162 163 testname = expand_path(os.path.join(path, filename))
163 164 if os.path.isfile(testname):
164 165 return os.path.abspath(testname)
165 166
166 167 raise IOError("File %r does not exist in any of the search paths: %r" %
167 168 (filename, path_dirs) )
168 169
169 170
170 171 class HomeDirError(Exception):
171 172 pass
172 173
173 174
174 175 def get_home_dir(require_writable=False):
175 176 """Return the 'home' directory, as a unicode string.
176 177
177 178 Uses os.path.expanduser('~'), and checks for writability.
178 179
179 180 See stdlib docs for how this is determined.
180 181 $HOME is first priority on *ALL* platforms.
181 182
182 183 Parameters
183 184 ----------
184 185
185 186 require_writable : bool [default: False]
186 187 if True:
187 188 guarantees the return value is a writable directory, otherwise
188 189 raises HomeDirError
189 190 if False:
190 191 The path is resolved, but it is not guaranteed to exist or be writable.
191 192 """
192 193
193 194 homedir = os.path.expanduser('~')
194 195 # Next line will make things work even when /home/ is a symlink to
195 196 # /usr/home as it is on FreeBSD, for example
196 197 homedir = os.path.realpath(homedir)
197 198
198 199 if not _writable_dir(homedir) and os.name == 'nt':
199 200 # expanduser failed, use the registry to get the 'My Documents' folder.
200 201 try:
201 202 try:
202 203 import winreg as wreg # Py 3
203 204 except ImportError:
204 205 import _winreg as wreg # Py 2
205 206 key = wreg.OpenKey(
206 207 wreg.HKEY_CURRENT_USER,
207 208 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
208 209 )
209 210 homedir = wreg.QueryValueEx(key,'Personal')[0]
210 211 key.Close()
211 212 except:
212 213 pass
213 214
214 215 if (not require_writable) or _writable_dir(homedir):
215 216 return py3compat.cast_unicode(homedir, fs_encoding)
216 217 else:
217 218 raise HomeDirError('%s is not a writable dir, '
218 219 'set $HOME environment variable to override' % homedir)
219 220
220 221 def get_xdg_dir():
221 222 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
222 223
223 224 This is only for non-OS X posix (Linux,Unix,etc.) systems.
224 225 """
225 226
226 227 env = os.environ
227 228
228 229 if os.name == 'posix' and sys.platform != 'darwin':
229 230 # Linux, Unix, AIX, etc.
230 231 # use ~/.config if empty OR not set
231 232 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
232 233 if xdg and _writable_dir(xdg):
233 234 return py3compat.cast_unicode(xdg, fs_encoding)
234 235
235 236 return None
236 237
237 238
238 239 def get_xdg_cache_dir():
239 240 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
240 241
241 242 This is only for non-OS X posix (Linux,Unix,etc.) systems.
242 243 """
243 244
244 245 env = os.environ
245 246
246 247 if os.name == 'posix' and sys.platform != 'darwin':
247 248 # Linux, Unix, AIX, etc.
248 249 # use ~/.cache if empty OR not set
249 250 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
250 251 if xdg and _writable_dir(xdg):
251 252 return py3compat.cast_unicode(xdg, fs_encoding)
252 253
253 254 return None
254 255
255 256
256 257 def get_ipython_dir():
257 258 """Get the IPython directory for this platform and user.
258 259
259 260 This uses the logic in `get_home_dir` to find the home directory
260 261 and then adds .ipython to the end of the path.
261 262 """
262 263
263 264 env = os.environ
264 265 pjoin = os.path.join
265 266
266 267
267 268 ipdir_def = '.ipython'
268 269
269 270 home_dir = get_home_dir()
270 271 xdg_dir = get_xdg_dir()
271 272
272 273 # import pdb; pdb.set_trace() # dbg
273 274 if 'IPYTHON_DIR' in env:
274 275 warn('The environment variable IPYTHON_DIR is deprecated. '
275 276 'Please use IPYTHONDIR instead.')
276 277 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
277 278 if ipdir is None:
278 279 # not set explicitly, use ~/.ipython
279 280 ipdir = pjoin(home_dir, ipdir_def)
280 281 if xdg_dir:
281 282 # Several IPython versions (up to 1.x) defaulted to .config/ipython
282 283 # on Linux. We have decided to go back to using .ipython everywhere
283 284 xdg_ipdir = pjoin(xdg_dir, 'ipython')
284 285
285 286 if _writable_dir(xdg_ipdir):
286 287 cu = compress_user
287 288 if os.path.exists(ipdir):
288 289 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
289 290 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
290 291 elif os.path.islink(xdg_ipdir):
291 292 warn(('{0} is deprecated. Move link to {1} to '
292 293 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
293 294 else:
294 295 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
295 296 shutil.move(xdg_ipdir, ipdir)
296 297
297 298 ipdir = os.path.normpath(os.path.expanduser(ipdir))
298 299
299 300 if os.path.exists(ipdir) and not _writable_dir(ipdir):
300 301 # ipdir exists, but is not writable
301 302 warn("IPython dir '{0}' is not a writable location,"
302 303 " using a temp directory.".format(ipdir))
303 304 ipdir = tempfile.mkdtemp()
304 305 elif not os.path.exists(ipdir):
305 306 parent = os.path.dirname(ipdir)
306 307 if not _writable_dir(parent):
307 308 # ipdir does not exist and parent isn't writable
308 309 warn("IPython parent '{0}' is not a writable location,"
309 310 " using a temp directory.".format(parent))
310 311 ipdir = tempfile.mkdtemp()
311 312
312 313 return py3compat.cast_unicode(ipdir, fs_encoding)
313 314
314 315
315 316 def get_ipython_cache_dir():
316 317 """Get the cache directory it is created if it does not exist."""
317 318 xdgdir = get_xdg_cache_dir()
318 319 if xdgdir is None:
319 320 return get_ipython_dir()
320 321 ipdir = os.path.join(xdgdir, "ipython")
321 322 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
322 323 ensure_dir_exists(ipdir)
323 324 elif not _writable_dir(xdgdir):
324 325 return get_ipython_dir()
325 326
326 327 return py3compat.cast_unicode(ipdir, fs_encoding)
327 328
328 329
329 330 def get_ipython_package_dir():
330 331 """Get the base directory where IPython itself is installed."""
331 332 ipdir = os.path.dirname(IPython.__file__)
332 333 return py3compat.cast_unicode(ipdir, fs_encoding)
333 334
334 335
335 336 def get_ipython_module_path(module_str):
336 337 """Find the path to an IPython module in this version of IPython.
337 338
338 339 This will always find the version of the module that is in this importable
339 340 IPython package. This will always return the path to the ``.py``
340 341 version of the module.
341 342 """
342 343 if module_str == 'IPython':
343 344 return os.path.join(get_ipython_package_dir(), '__init__.py')
344 345 mod = import_item(module_str)
345 346 the_path = mod.__file__.replace('.pyc', '.py')
346 347 the_path = the_path.replace('.pyo', '.py')
347 348 return py3compat.cast_unicode(the_path, fs_encoding)
348 349
349 350 def locate_profile(profile='default'):
350 351 """Find the path to the folder associated with a given profile.
351 352
352 353 I.e. find $IPYTHONDIR/profile_whatever.
353 354 """
354 355 from IPython.core.profiledir import ProfileDir, ProfileDirError
355 356 try:
356 357 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
357 358 except ProfileDirError:
358 359 # IOError makes more sense when people are expecting a path
359 360 raise IOError("Couldn't find profile %r" % profile)
360 361 return pd.location
361 362
362 363 def expand_path(s):
363 364 """Expand $VARS and ~names in a string, like a shell
364 365
365 366 :Examples:
366 367
367 368 In [2]: os.environ['FOO']='test'
368 369
369 370 In [3]: expand_path('variable FOO is $FOO')
370 371 Out[3]: 'variable FOO is test'
371 372 """
372 373 # This is a pretty subtle hack. When expand user is given a UNC path
373 374 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
374 375 # the $ to get (\\server\share\%username%). I think it considered $
375 376 # alone an empty var. But, we need the $ to remains there (it indicates
376 377 # a hidden share).
377 378 if os.name=='nt':
378 379 s = s.replace('$\\', 'IPYTHON_TEMP')
379 380 s = os.path.expandvars(os.path.expanduser(s))
380 381 if os.name=='nt':
381 382 s = s.replace('IPYTHON_TEMP', '$\\')
382 383 return s
383 384
384 385
385 386 def unescape_glob(string):
386 387 """Unescape glob pattern in `string`."""
387 388 def unescape(s):
388 389 for pattern in '*[]!?':
389 390 s = s.replace(r'\{0}'.format(pattern), pattern)
390 391 return s
391 392 return '\\'.join(map(unescape, string.split('\\\\')))
392 393
393 394
394 395 def shellglob(args):
395 396 """
396 397 Do glob expansion for each element in `args` and return a flattened list.
397 398
398 399 Unmatched glob pattern will remain as-is in the returned list.
399 400
400 401 """
401 402 expanded = []
402 403 # Do not unescape backslash in Windows as it is interpreted as
403 404 # path separator:
404 405 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
405 406 for a in args:
406 407 expanded.extend(glob.glob(a) or [unescape(a)])
407 408 return expanded
408 409
409 410
410 411 def target_outdated(target,deps):
411 412 """Determine whether a target is out of date.
412 413
413 414 target_outdated(target,deps) -> 1/0
414 415
415 416 deps: list of filenames which MUST exist.
416 417 target: single filename which may or may not exist.
417 418
418 419 If target doesn't exist or is older than any file listed in deps, return
419 420 true, otherwise return false.
420 421 """
421 422 try:
422 423 target_time = os.path.getmtime(target)
423 424 except os.error:
424 425 return 1
425 426 for dep in deps:
426 427 dep_time = os.path.getmtime(dep)
427 428 if dep_time > target_time:
428 429 #print "For target",target,"Dep failed:",dep # dbg
429 430 #print "times (dep,tar):",dep_time,target_time # dbg
430 431 return 1
431 432 return 0
432 433
433 434
434 435 def target_update(target,deps,cmd):
435 436 """Update a target with a given command given a list of dependencies.
436 437
437 438 target_update(target,deps,cmd) -> runs cmd if target is outdated.
438 439
439 440 This is just a wrapper around target_outdated() which calls the given
440 441 command if target is outdated."""
441 442
442 443 if target_outdated(target,deps):
443 444 system(cmd)
444 445
445 446 def filehash(path):
446 447 """Make an MD5 hash of a file, ignoring any differences in line
447 448 ending characters."""
448 449 with open(path, "rU") as f:
449 450 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
450 451
451 452 # If the config is unmodified from the default, we'll just delete it.
452 453 # These are consistent for 0.10.x, thankfully. We're not going to worry about
453 454 # older versions.
454 455 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
455 456 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
456 457
457 458 def check_for_old_config(ipython_dir=None):
458 459 """Check for old config files, and present a warning if they exist.
459 460
460 461 A link to the docs of the new config is included in the message.
461 462
462 463 This should mitigate confusion with the transition to the new
463 464 config system in 0.11.
464 465 """
465 466 if ipython_dir is None:
466 467 ipython_dir = get_ipython_dir()
467 468
468 469 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
469 470 warned = False
470 471 for cfg in old_configs:
471 472 f = os.path.join(ipython_dir, cfg)
472 473 if os.path.exists(f):
473 474 if filehash(f) == old_config_md5.get(cfg, ''):
474 475 os.unlink(f)
475 476 else:
476 477 warn("Found old IPython config file {!r} (modified by user)".format(f))
477 478 warned = True
478 479
479 480 if warned:
480 481 warn("""
481 482 The IPython configuration system has changed as of 0.11, and these files will
482 483 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
483 484 of the new config system.
484 485 To start configuring IPython, do `ipython profile create`, and edit
485 486 `ipython_config.py` in <ipython_dir>/profile_default.
486 487 If you need to leave the old config files in place for an older version of
487 488 IPython and want to suppress this warning message, set
488 489 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
489 490
490 491 def get_security_file(filename, profile='default'):
491 492 """Return the absolute path of a security file given by filename and profile
492 493
493 494 This allows users and developers to find security files without
494 495 knowledge of the IPython directory structure. The search path
495 496 will be ['.', profile.security_dir]
496 497
497 498 Parameters
498 499 ----------
499 500
500 501 filename : str
501 502 The file to be found. If it is passed as an absolute path, it will
502 503 simply be returned.
503 504 profile : str [default: 'default']
504 505 The name of the profile to search. Leaving this unspecified
505 506 The file to be found. If it is passed as an absolute path, fname will
506 507 simply be returned.
507 508
508 509 Returns
509 510 -------
510 511 Raises :exc:`IOError` if file not found or returns absolute path to file.
511 512 """
512 513 # import here, because profiledir also imports from utils.path
513 514 from IPython.core.profiledir import ProfileDir
514 515 try:
515 516 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
516 517 except Exception:
517 518 # will raise ProfileDirError if no such profile
518 519 raise IOError("Profile %r not found")
519 520 return filefind(filename, ['.', pd.security_dir])
520 521
521 522
522 523 ENOLINK = 1998
523 524
524 525 def link(src, dst):
525 526 """Hard links ``src`` to ``dst``, returning 0 or errno.
526 527
527 528 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
528 529 supported by the operating system.
529 530 """
530 531
531 532 if not hasattr(os, "link"):
532 533 return ENOLINK
533 534 link_errno = 0
534 535 try:
535 536 os.link(src, dst)
536 537 except OSError as e:
537 538 link_errno = e.errno
538 539 return link_errno
539 540
540 541
541 542 def link_or_copy(src, dst):
542 543 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
543 544
544 545 Attempts to maintain the semantics of ``shutil.copy``.
545 546
546 547 Because ``os.link`` does not overwrite files, a unique temporary file
547 548 will be used if the target already exists, then that file will be moved
548 549 into place.
549 550 """
550 551
551 552 if os.path.isdir(dst):
552 553 dst = os.path.join(dst, os.path.basename(src))
553 554
554 555 link_errno = link(src, dst)
555 556 if link_errno == errno.EEXIST:
556 557 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
557 558 try:
558 559 link_or_copy(src, new_dst)
559 560 except:
560 561 try:
561 562 os.remove(new_dst)
562 563 except OSError:
563 564 pass
564 565 raise
565 566 os.rename(new_dst, dst)
566 567 elif link_errno != 0:
567 568 # Either link isn't supported, or the filesystem doesn't support
568 569 # linking, or 'src' and 'dst' are on different filesystems.
569 570 shutil.copy(src, dst)
570 571
571 572 def ensure_dir_exists(path, mode=0o755):
572 573 """ensure that a directory exists
573 574
574 575 If it doesn't exist, try to create it and protect against a race condition
575 576 if another process is doing the same.
576 577
577 578 The default permissions are 755, which differ from os.makedirs default of 777.
578 579 """
579 580 if not os.path.exists(path):
580 581 try:
581 582 os.makedirs(path, mode=mode)
582 583 except OSError as e:
583 584 if e.errno != errno.EEXIST:
584 585 raise
585 586 elif not os.path.isdir(path):
586 587 raise IOError("%r exists but is not a directory" % path)
@@ -1,164 +1,156 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with terminals.
4 4
5 5 Authors:
6 6
7 7 * Brian E. Granger
8 8 * Fernando Perez
9 9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import struct
25 25 import sys
26 26 import warnings
27 27
28 28 from . import py3compat
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Code
32 32 #-----------------------------------------------------------------------------
33 33
34 34 # This variable is part of the expected API of the module:
35 35 ignore_termtitle = True
36 36
37 37
38 def _term_clear():
39 pass
40
41 38
42 39 if os.name == 'posix':
43 40 def _term_clear():
44 41 os.system('clear')
45
46
47 if sys.platform == 'win32':
42 elif sys.platform == 'win32':
48 43 def _term_clear():
49 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 51 def toggle_set_term_title(val):
57 52 """Control whether set_term_title is active or not.
58 53
59 54 set_term_title() allows writing to the console titlebar. In embedded
60 55 widgets this can cause problems, so this call can be used to toggle it on
61 56 or off as needed.
62 57
63 58 The default state of the module is for the function to be disabled.
64 59
65 60 Parameters
66 61 ----------
67 62 val : bool
68 63 If True, set_term_title() actually writes to the terminal (using the
69 64 appropriate platform-specific module). If False, it is a no-op.
70 65 """
71 66 global ignore_termtitle
72 67 ignore_termtitle = not(val)
73 68
74 69
75 70 def _set_term_title(*args,**kw):
76 71 """Dummy no-op."""
77 72 pass
78 73
79 74
80 75 def _set_term_title_xterm(title):
81 76 """ Change virtual terminal title in xterm-workalikes """
82 77 sys.stdout.write('\033]0;%s\007' % title)
83 78
84 79 if os.name == 'posix':
85 80 TERM = os.environ.get('TERM','')
86 81 if TERM.startswith('xterm'):
87 82 _set_term_title = _set_term_title_xterm
88
89
90 if sys.platform == 'win32':
83 elif sys.platform == 'win32':
91 84 try:
92 85 import ctypes
93 86
94 87 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
95 88 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
96 89
97 90 def _set_term_title(title):
98 91 """Set terminal title using ctypes to access the Win32 APIs."""
99 92 SetConsoleTitleW(title)
100 93 except ImportError:
101 94 def _set_term_title(title):
102 95 """Set terminal title using the 'title' command."""
103 96 global ignore_termtitle
104 97
105 98 try:
106 99 # Cannot be on network share when issuing system commands
107 100 curr = py3compat.getcwd()
108 101 os.chdir("C:")
109 102 ret = os.system("title " + title)
110 103 finally:
111 104 os.chdir(curr)
112 105 if ret:
113 106 # non-zero return code signals error, don't try again
114 107 ignore_termtitle = True
115 108
116 109
117 110 def set_term_title(title):
118 111 """Set terminal title using the necessary platform-dependent calls."""
119 112 if ignore_termtitle:
120 113 return
121 114 _set_term_title(title)
122 115
123 116
124 117 def freeze_term_title():
125 118 warnings.warn("This function is deprecated, use toggle_set_term_title()")
126 119 global ignore_termtitle
127 120 ignore_termtitle = True
128 121
129 122
130 def get_terminal_size(defaultx=80, defaulty=25):
131 return defaultx, defaulty
132
133
134 123 if sys.platform == 'win32':
135 124 def get_terminal_size(defaultx=80, defaulty=25):
136 125 """Return size of current terminal console.
137 126
138 127 This function try to determine actual size of current working
139 128 console window and return tuple (sizex, sizey) if success,
140 129 or default size (defaultx, defaulty) otherwise.
141 130
142 131 Dependencies: ctypes should be installed.
143 132
144 133 Author: Alexander Belchenko (e-mail: bialix AT ukr.net)
145 134 """
146 135 try:
147 136 import ctypes
148 137 except ImportError:
149 138 return defaultx, defaulty
150 139
151 140 h = ctypes.windll.kernel32.GetStdHandle(-11)
152 141 csbi = ctypes.create_string_buffer(22)
153 142 res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
154 143
155 144 if res:
156 145 (bufx, bufy, curx, cury, wattr,
157 146 left, top, right, bottom, maxx, maxy) = struct.unpack(
158 147 "hhhhHhhhhhh", csbi.raw)
159 148 sizex = right - left + 1
160 149 sizey = bottom - top + 1
161 150 return (sizex, sizey)
162 151 else:
163 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 1 """Tests for tokenutil"""
2 2 # Copyright (c) IPython Development Team.
3 3 # Distributed under the terms of the Modified BSD License.
4 4
5 5 import nose.tools as nt
6 6
7 7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
8 8
9 9 def expect_token(expected, cell, cursor_pos):
10 10 token = token_at_cursor(cell, cursor_pos)
11 11 offset = 0
12 12 for line in cell.splitlines():
13 13 if offset + len(line) >= cursor_pos:
14 14 break
15 15 else:
16 16 offset += len(line)
17 17 column = cursor_pos - offset
18 18 line_with_cursor = '%s|%s' % (line[:column], line[column:])
19 line
20 19 nt.assert_equal(token, expected,
21 20 "Expected %r, got %r in: %r (pos %i)" % (
22 21 expected, token, line_with_cursor, cursor_pos)
23 22 )
24 23
25 24 def test_simple():
26 25 cell = "foo"
27 26 for i in range(len(cell)):
28 27 expect_token("foo", cell, i)
29 28
30 29 def test_function():
31 30 cell = "foo(a=5, b='10')"
32 31 expected = 'foo'
33 32 # up to `foo(|a=`
34 33 for i in range(cell.find('a=') + 1):
35 34 expect_token("foo", cell, i)
36 35 # find foo after `=`
37 36 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
38 37 expect_token("foo", cell, i)
39 38 # in between `5,|` and `|b=`
40 39 for i in range(cell.find(','), cell.find('b=')):
41 40 expect_token("foo", cell, i)
42 41
43 42 def test_multiline():
44 43 cell = '\n'.join([
45 44 'a = 5',
46 45 'b = hello("string", there)'
47 46 ])
48 47 expected = 'hello'
49 48 start = cell.index(expected) + 1
50 49 for i in range(start, start + len(expected)):
51 50 expect_token(expected, cell, i)
52 51 expected = 'there'
53 52 start = cell.index(expected) + 1
54 53 for i in range(start, start + len(expected)):
55 54 expect_token(expected, cell, i)
56 55
57 56 def test_attrs():
58 57 cell = "foo(a=obj.attr.subattr)"
59 58 expected = 'obj'
60 59 idx = cell.find('obj') + 1
61 60 for i in range(idx, idx + 3):
62 61 expect_token(expected, cell, i)
63 62 idx = cell.find('.attr') + 2
64 63 expected = 'obj.attr'
65 64 for i in range(idx, idx + 4):
66 65 expect_token(expected, cell, i)
67 66 idx = cell.find('.subattr') + 2
68 67 expected = 'obj.attr.subattr'
69 68 for i in range(idx, len(cell)):
70 69 expect_token(expected, cell, i)
71 70
72 71 def test_line_at_cursor():
73 72 cell = ""
74 73 (line, offset) = line_at_cursor(cell, cursor_pos=11)
75 74 assert line == "", ("Expected '', got %r" % line)
76 75 assert offset == 0, ("Expected '', got %r" % line)
@@ -1,333 +1,333 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
3 3 """Setup script for IPython.
4 4
5 5 Under Posix environments it works like a typical setup.py script.
6 6 Under Windows, the command sdist is not supported, since IPython
7 7 requires utilities which are not available under Windows."""
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (c) 2008-2011, IPython Development Team.
11 11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 14 #
15 15 # Distributed under the terms of the Modified BSD License.
16 16 #
17 17 # The full license is in the file COPYING.rst, distributed with this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Minimal Python version sanity check
22 22 #-----------------------------------------------------------------------------
23 23 from __future__ import print_function
24 24
25 25 import sys
26 26
27 27 # This check is also made in IPython/__init__, don't forget to update both when
28 28 # changing Python version requirements.
29 29 v = sys.version_info
30 30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
31 31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
32 32 print(error, file=sys.stderr)
33 33 sys.exit(1)
34 34
35 35 PY3 = (sys.version_info[0] >= 3)
36 36
37 37 # At least we're on the python version we need, move on.
38 38
39 39 #-------------------------------------------------------------------------------
40 40 # Imports
41 41 #-------------------------------------------------------------------------------
42 42
43 43 # Stdlib imports
44 44 import os
45 45 import shutil
46 46
47 47 from glob import glob
48 48
49 49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
50 50 # update it when the contents of directories change.
51 51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
52 52
53 53 from distutils.core import setup
54 54
55 55 # Our own imports
56 56 from setupbase import target_update
57 57
58 58 from setupbase import (
59 59 setup_args,
60 60 find_packages,
61 61 find_package_data,
62 62 check_package_data_first,
63 63 find_entry_points,
64 64 build_scripts_entrypt,
65 65 find_data_files,
66 66 check_for_dependencies,
67 67 git_prebuild,
68 68 check_submodule_status,
69 69 update_submodules,
70 70 require_submodules,
71 71 UpdateSubmodules,
72 72 get_bdist_wheel,
73 73 CompileCSS,
74 74 JavascriptVersion,
75 75 css_js_prerelease,
76 76 install_symlinked,
77 77 install_lib_symlink,
78 78 install_scripts_for_symlink,
79 79 unsymlink,
80 80 )
81 81 from setupext import setupext
82 82
83 83 isfile = os.path.isfile
84 84 pjoin = os.path.join
85 85
86 86 #-------------------------------------------------------------------------------
87 87 # Handle OS specific things
88 88 #-------------------------------------------------------------------------------
89 89
90 90 if os.name in ('nt','dos'):
91 91 os_name = 'windows'
92 92 else:
93 93 os_name = os.name
94 94
95 95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
96 96 # Sphinx it might work, but let's not turn it on until someone confirms that it
97 97 # actually works.
98 98 if os_name == 'windows' and 'sdist' in sys.argv:
99 99 print('The sdist command is not available under Windows. Exiting.')
100 100 sys.exit(1)
101 101
102 102 #-------------------------------------------------------------------------------
103 103 # Make sure we aren't trying to run without submodules
104 104 #-------------------------------------------------------------------------------
105 105 here = os.path.abspath(os.path.dirname(__file__))
106 106
107 107 def require_clean_submodules():
108 108 """Check on git submodules before distutils can do anything
109 109
110 110 Since distutils cannot be trusted to update the tree
111 111 after everything has been set in motion,
112 112 this is not a distutils command.
113 113 """
114 114 # PACKAGERS: Add a return here to skip checks for git submodules
115 115
116 116 # don't do anything if nothing is actually supposed to happen
117 117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
118 118 if do_nothing in sys.argv:
119 119 return
120 120
121 121 status = check_submodule_status(here)
122 122
123 123 if status == "missing":
124 124 print("checking out submodules for the first time")
125 125 update_submodules(here)
126 126 elif status == "unclean":
127 127 print('\n'.join([
128 128 "Cannot build / install IPython with unclean submodules",
129 129 "Please update submodules with",
130 130 " python setup.py submodule",
131 131 "or",
132 132 " git submodule update",
133 133 "or commit any submodule changes you have made."
134 134 ]))
135 135 sys.exit(1)
136 136
137 137 require_clean_submodules()
138 138
139 139 #-------------------------------------------------------------------------------
140 140 # Things related to the IPython documentation
141 141 #-------------------------------------------------------------------------------
142 142
143 143 # update the manuals when building a source dist
144 144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
145 145
146 146 # List of things to be updated. Each entry is a triplet of args for
147 147 # target_update()
148 148 to_update = [
149 149 # FIXME - Disabled for now: we need to redo an automatic way
150 150 # of generating the magic info inside the rst.
151 151 #('docs/magic.tex',
152 152 #['IPython/Magic.py'],
153 153 #"cd doc && ./update_magic.sh" ),
154 154
155 155 ('docs/man/ipcluster.1.gz',
156 156 ['docs/man/ipcluster.1'],
157 157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
158 158
159 159 ('docs/man/ipcontroller.1.gz',
160 160 ['docs/man/ipcontroller.1'],
161 161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
162 162
163 163 ('docs/man/ipengine.1.gz',
164 164 ['docs/man/ipengine.1'],
165 165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
166 166
167 167 ('docs/man/ipython.1.gz',
168 168 ['docs/man/ipython.1'],
169 169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
170 170
171 171 ]
172 172
173 173
174 174 [ target_update(*t) for t in to_update ]
175 175
176 176 #---------------------------------------------------------------------------
177 177 # Find all the packages, package data, and data_files
178 178 #---------------------------------------------------------------------------
179 179
180 180 packages = find_packages()
181 181 package_data = find_package_data()
182 182
183 183 data_files = find_data_files()
184 184
185 185 setup_args['packages'] = packages
186 186 setup_args['package_data'] = package_data
187 187 setup_args['data_files'] = data_files
188 188
189 189 #---------------------------------------------------------------------------
190 190 # custom distutils commands
191 191 #---------------------------------------------------------------------------
192 192 # imports here, so they are after setuptools import if there was one
193 193 from distutils.command.sdist import sdist
194 194 from distutils.command.upload import upload
195 195
196 196 class UploadWindowsInstallers(upload):
197 197
198 198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
199 199 user_options = upload.user_options + [
200 200 ('files=', 'f', 'exe file (or glob) to upload')
201 201 ]
202 202 def initialize_options(self):
203 203 upload.initialize_options(self)
204 204 meta = self.distribution.metadata
205 205 base = '{name}-{version}'.format(
206 206 name=meta.get_name(),
207 207 version=meta.get_version()
208 208 )
209 209 self.files = os.path.join('dist', '%s.*.exe' % base)
210 210
211 211 def run(self):
212 212 for dist_file in glob(self.files):
213 213 self.upload_file('bdist_wininst', 'any', dist_file)
214 214
215 215 setup_args['cmdclass'] = {
216 216 'build_py': css_js_prerelease(
217 217 check_package_data_first(git_prebuild('IPython')),
218 218 strict=False),
219 219 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
220 220 'upload_wininst' : UploadWindowsInstallers,
221 221 'submodule' : UpdateSubmodules,
222 222 'css' : CompileCSS,
223 223 'symlink': install_symlinked,
224 224 'install_lib_symlink': install_lib_symlink,
225 225 'install_scripts_sym': install_scripts_for_symlink,
226 226 'unsymlink': unsymlink,
227 227 'jsversion' : JavascriptVersion,
228 228 }
229 229
230 230 #---------------------------------------------------------------------------
231 231 # Handle scripts, dependencies, and setuptools specific things
232 232 #---------------------------------------------------------------------------
233 233
234 234 # For some commands, use setuptools. Note that we do NOT list install here!
235 235 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
236 236 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
237 237 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
238 238 'egg_info', 'easy_install', 'upload', 'install_egg_info',
239 239 ))
240 240
241 241 if len(needs_setuptools.intersection(sys.argv)) > 0:
242 242 import setuptools
243 243
244 244 # This dict is used for passing extra arguments that are setuptools
245 245 # specific to setup
246 246 setuptools_extra_args = {}
247 247
248 248 # setuptools requirements
249 249
250 250 extras_require = dict(
251 251 parallel = ['pyzmq>=2.1.11'],
252 252 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
253 253 zmq = ['pyzmq>=2.1.11'],
254 254 doc = ['Sphinx>=1.1', 'numpydoc'],
255 255 test = ['nose>=0.10.1', 'requests'],
256 256 terminal = [],
257 257 nbformat = ['jsonschema>=2.0'],
258 258 notebook = ['tornado>=4.0', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.5'],
259 259 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
260 260 )
261 261
262 262 if sys.version_info < (3, 3):
263 263 extras_require['test'].append('mock')
264 264
265 265 extras_require['notebook'].extend(extras_require['nbformat'])
266 266 extras_require['nbconvert'].extend(extras_require['nbformat'])
267 267
268 268 everything = set()
269 269 for deps in extras_require.values():
270 270 everything.update(deps)
271 271 extras_require['all'] = everything
272 272
273 273 install_requires = []
274 274
275 275 # add readline
276 276 if sys.platform == 'darwin':
277 277 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
278 278 install_requires.append('gnureadline')
279 279 elif sys.platform.startswith('win'):
280 280 extras_require['terminal'].append('pyreadline>=2.0')
281 281
282 282
283 283 if 'setuptools' in sys.modules:
284 284 # setup.py develop should check for submodules
285 285 from setuptools.command.develop import develop
286 286 setup_args['cmdclass']['develop'] = require_submodules(develop)
287 287 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
288 288
289 289 setuptools_extra_args['zip_safe'] = False
290 290 setuptools_extra_args['entry_points'] = {'console_scripts':find_entry_points()}
291 291 setup_args['extras_require'] = extras_require
292 292 requires = setup_args['install_requires'] = install_requires
293 293
294 294 # Script to be run by the windows binary installer after the default setup
295 295 # routine, to add shortcuts and similar windows-only things. Windows
296 296 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
297 297 # doesn't find them.
298 298 if 'bdist_wininst' in sys.argv:
299 299 if len(sys.argv) > 2 and \
300 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 302 sys.exit(1)
303 303 setup_args['data_files'].append(
304 304 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
305 305 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
306 306 setup_args['options'] = {"bdist_wininst":
307 307 {"install_script":
308 308 "ipython_win_post_install.py"}}
309 309
310 310 else:
311 311 # If we are installing without setuptools, call this function which will
312 312 # check for dependencies an inform the user what is needed. This is
313 313 # just to make life easy for users.
314 314 for install_cmd in ('install', 'symlink'):
315 315 if install_cmd in sys.argv:
316 316 check_for_dependencies()
317 317 break
318 318 # scripts has to be a non-empty list, or install_scripts isn't called
319 319 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
320 320
321 321 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
322 322
323 323 #---------------------------------------------------------------------------
324 324 # Do the actual setup now
325 325 #---------------------------------------------------------------------------
326 326
327 327 setup_args.update(setuptools_extra_args)
328 328
329 329 def main():
330 330 setup(**setup_args)
331 331
332 332 if __name__ == '__main__':
333 333 main()
General Comments 0
You need to be logged in to leave comments. Login now