##// END OF EJS Templates
change standard money keys
Zachary Sailer -
Show More
@@ -1,104 +1,104 b''
1 """A base class contents manager.
1 """A base class contents manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import datetime
19 import datetime
20 import io
20 import io
21 import os
21 import os
22 import glob
22 import glob
23 import shutil
23 import shutil
24 import ast
24 import ast
25 import base64
25 import base64
26
26
27 from tornado import web
27 from tornado import web
28
28
29 from IPython.config.configurable import LoggingConfigurable
29 from IPython.config.configurable import LoggingConfigurable
30 from IPython.nbformat import current
30 from IPython.nbformat import current
31 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
31 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
32 from IPython.utils import tz
32 from IPython.utils import tz
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Classes
35 # Classes
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class ContentManager(LoggingConfigurable):
38 class ContentManager(LoggingConfigurable):
39
39
40 content_dir = Unicode(os.getcwdu(), config=True, help="""
40 content_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for contents.
41 The directory to use for contents.
42 """)
42 """)
43
43
44 contents = List()
44 contents = List()
45
45
46 def get_content_names(self, content_path):
46 def get_content_names(self, content_path):
47 """List of dicts of files in content_path"""
47 """List of dicts of files in content_path"""
48 names = glob.glob(os.path.join(self.content_dir, content_path,'*'))
48 names = glob.glob(os.path.join(self.content_dir, content_path,'*'))
49 contents = list()
49 contents = list()
50 dirs = list()
50 dirs = list()
51 notebooks = list()
51 notebooks = list()
52 for name in names:
52 for name in names:
53 if os.path.isdir(name) == True:
53 if os.path.isdir(name) == True:
54 dirs.append(os.path.split(name)[1])
54 dirs.append(os.path.split(name)[1])
55 elif os.path.splitext(name)[1] == '.ipynb':
55 elif os.path.splitext(name)[1] == '.ipynb':
56 notebooks.append(os.path.split(name)[1])
56 notebooks.append(os.path.split(name)[1])
57 else:
57 else:
58 contents.append(os.path.split(name)[1])
58 contents.append(os.path.split(name)[1])
59 return dirs, notebooks, contents
59 return dirs, notebooks, contents
60
60
61 def list_contents(self, content_path):
61 def list_contents(self, content_path):
62 """List all contents in the named path."""
62 """List all contents in the named path."""
63 dir_names, notebook_names, content_names = self.get_content_names(content_path)
63 dir_names, notebook_names, content_names = self.get_content_names(content_path)
64 content_mapping = []
64 content_mapping = []
65 for name in dir_names:
65 for name in dir_names:
66 model = self.content_model(name, content_path, type='dir')
66 model = self.content_model(name, content_path, type='dir')
67 content_mapping.append(model)
67 content_mapping.append(model)
68 for name in content_names:
68 for name in content_names:
69 model = self.content_model(name, content_path, type='file')
69 model = self.content_model(name, content_path, type='file')
70 content_mapping.append(model)
70 content_mapping.append(model)
71 for name in notebook_names:
71 for name in notebook_names:
72 model = self.content_model(name, content_path, type='notebook')
72 model = self.content_model(name, content_path, type='notebook')
73 content_mapping.append(model)
73 content_mapping.append(model)
74 return content_mapping
74 return content_mapping
75
75
76 def get_path_by_name(self, name, content_path):
76 def get_path_by_name(self, name, content_path):
77 """Return a full path to content"""
77 """Return a full path to content"""
78 path = os.path.join(self.content_dir, content_path, name)
78 path = os.path.join(self.content_dir, content_path, name)
79 return path
79 return path
80
80
81 def content_info(self, name, content_path):
81 def content_info(self, name, content_path):
82 """Read the content of a named file"""
82 """Read the content of a named file"""
83 file_type = os.path.splitext(os.path.basename(name))[1]
83 file_type = os.path.splitext(os.path.basename(name))[1]
84 full_path = self.get_path_by_name(name, content_path)
84 full_path = self.get_path_by_name(name, content_path)
85 info = os.stat(full_path)
85 info = os.stat(full_path)
86 size = info.st_size
86 size = info.st_size
87 last_modified = tz.utcfromtimestamp(info.st_mtime)
87 last_modified = tz.utcfromtimestamp(info.st_mtime)
88 return last_modified, file_type, size
88 return last_modified, file_type, size
89
89
90 def content_model(self, name, content_path, type=None):
90 def content_model(self, name, content_path, type=None):
91 """Create a dict standard model for any file (other than notebooks)"""
91 """Create a dict standard model for any file (other than notebooks)"""
92 last_modified, file_type, size = self.content_info(name, content_path)
92 last_modified, file_type, size = self.content_info(name, content_path)
93 model = {"name": name,
93 model = {"name": name,
94 "path": content_path,
94 "path": content_path,
95 "type": type,
95 "type": type,
96 "MIME-type": "",
96 "MIME-type": "",
97 "last_modified": last_modified.ctime(),
97 "last_modified (UTC)": last_modified.ctime(),
98 "size": size}
98 "size": size}
99 return model
99 return model
100
100
101 def delete_content(self, content_path):
101 def delete_content(self, content_path):
102 """Delete a file"""
102 """Delete a file"""
103 os.unlink(os.path.join(self.content_dir, content_path))
103 os.unlink(os.path.join(self.content_dir, content_path))
104 No newline at end of file
104
@@ -1,68 +1,85 b''
1 """Tornado handlers for the contents web service.
1 """Tornado handlers for the contents web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from tornado import web
19 from tornado import web
20
20
21 from zmq.utils import jsonapi
21 from zmq.utils import jsonapi
22
22
23 from IPython.utils.jsonutil import date_default
23 from IPython.utils.jsonutil import date_default
24
24
25 from ...base.handlers import IPythonHandler
25 from ...base.handlers import IPythonHandler
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Contents web service handlers
28 # Contents web service handlers
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class ContentRootHandler(IPythonHandler):
32 class ContentRootHandler(IPythonHandler):
33
33
34 @web.authenticated
34 @web.authenticated
35 def get(self):
35 def get(self):
36 cm = self.content_manager
36 cm = self.content_manager
37 contents = cm.list_contents("")
37 contents = cm.list_contents("")
38 self.finish(jsonapi.dumps(contents))
38 self.finish(jsonapi.dumps(contents))
39
39
40
40
41 class ContentHandler(IPythonHandler):
41 class ContentHandler(IPythonHandler):
42
42
43 @web.authenticated
43 @web.authenticated
44 def get(self, content_path):
44 def get(self, content_path):
45 cm = self.content_manager
45 cm = self.content_manager
46 contents = cm.list_contents(content_path)
46 contents = cm.list_contents(content_path)
47 self.finish(jsonapi.dumps(contents))
47 self.finish(jsonapi.dumps(contents))
48
48
49 @web.authenticated
49 @web.authenticated
50 def delete(self, content_path):
50 def delete(self, content_path):
51 cm = self.content_manager
51 cm = self.content_manager
52 cm.delete_content(content_path)
52 cm.delete_content(content_path)
53 self.set_status(204)
53 self.set_status(204)
54 self.finish()
54 self.finish()
55
55
56 class ServicesRedirectHandler(IPythonHandler):
57
58 @web.authenticated
59 def get(self):
60 url = self.base_project_url + 'api'
61 self.redirect(url)
62
63 class ServicesHandler(IPythonHandler):
64
65 @web.authenticated
66 def get(self):
67 services = ['contents', 'notebooks', 'sessions', 'kernels', 'clusters']
68 self.finish(jsonapi.dumps(services))
69
56
70
57 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
58 # URL to handler mappings
72 # URL to handler mappings
59 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
60
74
61 _content_path_regex = r"(?P<content_path>.+)"
75 _content_path_regex = r"(?P<content_path>.+)"
62
76
63 default_handlers = [
77 default_handlers = [
64 (r"api/contents/%s" % _content_path_regex, ContentHandler),
78 (r"api/contents/%s" % _content_path_regex, ContentHandler),
65 (r"api/contents", ContentRootHandler)
79 (r"api/contents", ContentRootHandler),
80 (r"api/", ServicesRedirectHandler),
81 (r"api", ServicesHandler)
82
66 ]
83 ]
67
84
68
85
@@ -1,96 +1,96 b''
1 """A kernel manager relating notebooks and kernels
1 """A kernel manager relating notebooks and kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from tornado import web
19 from tornado import web
20
20
21 from IPython.kernel.multikernelmanager import MultiKernelManager
21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 from IPython.utils.traitlets import (
22 from IPython.utils.traitlets import (
23 Dict, List, Unicode,
23 Dict, List, Unicode,
24 )
24 )
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Classes
27 # Classes
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31 class MappingKernelManager(MultiKernelManager):
31 class MappingKernelManager(MultiKernelManager):
32 """A KernelManager that handles notebook mapping and HTTP error handling"""
32 """A KernelManager that handles notebook mapping and HTTP error handling"""
33
33
34 def _kernel_manager_class_default(self):
34 def _kernel_manager_class_default(self):
35 return "IPython.kernel.ioloop.IOLoopKernelManager"
35 return "IPython.kernel.ioloop.IOLoopKernelManager"
36
36
37 kernel_argv = List(Unicode)
37 kernel_argv = List(Unicode)
38 kernels = []
38 kernels = []
39
39
40 #-------------------------------------------------------------------------
40 #-------------------------------------------------------------------------
41 # Methods for managing kernels and sessions
41 # Methods for managing kernels and sessions
42 #-------------------------------------------------------------------------
42 #-------------------------------------------------------------------------
43
43
44 def _handle_kernel_died(self, kernel_id):
44 def _handle_kernel_died(self, kernel_id):
45 """notice that a kernel died"""
45 """notice that a kernel died"""
46 self.log.warn("Kernel %s died, removing from map.", kernel_id)
46 self.log.warn("Kernel %s died, removing from map.", kernel_id)
47 self.remove_kernel(kernel_id)
47 self.remove_kernel(kernel_id)
48
48
49 def start_kernel(self, **kwargs):
49 def start_kernel(self, **kwargs):
50 """Start a kernel for a session an return its kernel_id.
50 """Start a kernel for a session an return its kernel_id.
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54 session_id : uuid
54 session_id : uuid
55 The uuid of the session to associate the new kernel with. If this
55 The uuid of the session to associate the new kernel with. If this
56 is not None, this kernel will be persistent whenever the session
56 is not None, this kernel will be persistent whenever the session
57 requests a kernel.
57 requests a kernel.
58 """
58 """
59 kernel_id = None
59 kernel_id = None
60 if kernel_id is None:
60 if kernel_id is None:
61 kwargs['extra_arguments'] = self.kernel_argv
61 kwargs['extra_arguments'] = self.kernel_argv
62 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
62 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
63 self.log.info("Kernel started: %s" % kernel_id)
63 self.log.info("Kernel started: %s" % kernel_id)
64 self.log.debug("Kernel args: %r" % kwargs)
64 self.log.debug("Kernel args: %r" % kwargs)
65 # register callback for failed auto-restart
65 # register callback for failed auto-restart
66 self.add_restart_callback(kernel_id,
66 self.add_restart_callback(kernel_id,
67 lambda : self._handle_kernel_died(kernel_id),
67 lambda : self._handle_kernel_died(kernel_id),
68 'dead',
68 'dead',
69 )
69 )
70 else:
70 else:
71 self.log.info("Using existing kernel: %s" % kernel_id)
71 self.log.info("Using existing kernel: %s" % kernel_id)
72
72
73 return kernel_id
73 return kernel_id
74
74
75 def shutdown_kernel(self, kernel_id, now=False):
75 def shutdown_kernel(self, kernel_id, now=False):
76 """Shutdown a kernel by kernel_id"""
76 """Shutdown a kernel by kernel_id"""
77 i = 0
77 i = 0
78 for kernel in self.kernels:
78 for kernel in self.kernels:
79 if kernel['kernel_id'] == kernel_id:
79 if kernel['id'] == kernel_id:
80 del self.kernels[i]
80 del self.kernels[i]
81 i = i+1
81 i = i+1
82 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
82 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
83
83
84 def kernel_model(self, kernel_id, ws_url):
84 def kernel_model(self, kernel_id, ws_url):
85 model = {"kernel_id":kernel_id, "ws_url": ws_url}
85 model = {"id":kernel_id, "ws_url": ws_url}
86 self.kernels.append(model)
86 self.kernels.append(model)
87 return model
87 return model
88
88
89 def list_kernels(self):
89 def list_kernels(self):
90 return self.kernels
90 return self.kernels
91
91
92 # override _check_kernel_id to raise 404 instead of KeyError
92 # override _check_kernel_id to raise 404 instead of KeyError
93 def _check_kernel_id(self, kernel_id):
93 def _check_kernel_id(self, kernel_id):
94 """Check a that a kernel_id exists and raise 404 if not."""
94 """Check a that a kernel_id exists and raise 404 if not."""
95 if kernel_id not in self:
95 if kernel_id not in self:
96 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
96 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
@@ -1,266 +1,266 b''
1 """A base class notebook manager.
1 """A base class notebook manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import uuid
20 import uuid
21
21
22 from tornado import web
22 from tornado import web
23 from urllib import quote, unquote
23 from urllib import quote, unquote
24
24
25 from IPython.config.configurable import LoggingConfigurable
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.nbformat import current
26 from IPython.nbformat import current
27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Classes
30 # Classes
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 class NotebookManager(LoggingConfigurable):
33 class NotebookManager(LoggingConfigurable):
34
34
35 # Todo:
35 # Todo:
36 # The notebook_dir attribute is used to mean a couple of different things:
36 # The notebook_dir attribute is used to mean a couple of different things:
37 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 1. Where the notebooks are stored if FileNotebookManager is used.
38 # 2. The cwd of the kernel for a project.
38 # 2. The cwd of the kernel for a project.
39 # Right now we use this attribute in a number of different places and
39 # Right now we use this attribute in a number of different places and
40 # we are going to have to disentangle all of this.
40 # we are going to have to disentangle all of this.
41 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
42 The directory to use for notebooks.
42 The directory to use for notebooks.
43 """)
43 """)
44
44
45 def named_notebook_path(self, notebook_path):
45 def named_notebook_path(self, notebook_path):
46
46
47 l = len(notebook_path)
47 l = len(notebook_path)
48 names = notebook_path.split('/')
48 names = notebook_path.split('/')
49 if len(names) > 1:
49 if len(names) > 1:
50 name = names[len(names)-1]
50 name = names[len(names)-1]
51 if name[(len(name)-6):(len(name))] == ".ipynb":
51 if name[(len(name)-6):(len(name))] == ".ipynb":
52 name = name
52 name = name
53 path = notebook_path[0:l-len(name)-1]+'/'
53 path = notebook_path[0:l-len(name)-1]+'/'
54 else:
54 else:
55 name = None
55 name = None
56 path = notebook_path+'/'
56 path = notebook_path+'/'
57 else:
57 else:
58 name = names[0]
58 name = names[0]
59 if name[(len(name)-6):(len(name))] == ".ipynb":
59 if name[(len(name)-6):(len(name))] == ".ipynb":
60 name = name
60 name = name
61 path = None
61 path = None
62 else:
62 else:
63 name = None
63 name = None
64 path = notebook_path+'/'
64 path = notebook_path+'/'
65 return name, path
65 return name, path
66
66
67 def url_encode(self, path):
67 def url_encode(self, path):
68 parts = path.split('/')
68 parts = path.split('/')
69 path=""
69 path=""
70 for part in parts:
70 for part in parts:
71 part = quote(part)
71 part = quote(part)
72 path = os.path.join(path,part)
72 path = os.path.join(path,part)
73 return path
73 return path
74
74
75 def url_decode(self, path):
75 def url_decode(self, path):
76 parts = path.split('/')
76 parts = path.split('/')
77 path=""
77 path=""
78 for part in parts:
78 for part in parts:
79 part = unquote(part)
79 part = unquote(part)
80 path = os.path.join(path,part)
80 path = os.path.join(path,part)
81 return path
81 return path
82
82
83 def _notebook_dir_changed(self, new):
83 def _notebook_dir_changed(self, new):
84 """do a bit of validation of the notebook dir"""
84 """do a bit of validation of the notebook dir"""
85 if not os.path.isabs(new):
85 if not os.path.isabs(new):
86 # If we receive a non-absolute path, make it absolute.
86 # If we receive a non-absolute path, make it absolute.
87 abs_new = os.path.abspath(new)
87 abs_new = os.path.abspath(new)
88 #self.notebook_dir = os.path.dirname(abs_new)
88 #self.notebook_dir = os.path.dirname(abs_new)
89 return
89 return
90 if os.path.exists(new) and not os.path.isdir(new):
90 if os.path.exists(new) and not os.path.isdir(new):
91 raise TraitError("notebook dir %r is not a directory" % new)
91 raise TraitError("notebook dir %r is not a directory" % new)
92 if not os.path.exists(new):
92 if not os.path.exists(new):
93 self.log.info("Creating notebook dir %s", new)
93 self.log.info("Creating notebook dir %s", new)
94 try:
94 try:
95 os.mkdir(new)
95 os.mkdir(new)
96 except:
96 except:
97 raise TraitError("Couldn't create notebook dir %r" % new)
97 raise TraitError("Couldn't create notebook dir %r" % new)
98
98
99 allowed_formats = List([u'json',u'py'])
99 allowed_formats = List([u'json',u'py'])
100
100
101 def add_new_folder(self, path=None):
101 def add_new_folder(self, path=None):
102 new_path = os.path.join(self.notebook_dir, path)
102 new_path = os.path.join(self.notebook_dir, path)
103 if not os.path.exists(new_path):
103 if not os.path.exists(new_path):
104 os.makedirs(new_path)
104 os.makedirs(new_path)
105 else:
105 else:
106 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
106 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
107
107
108 def load_notebook_names(self, path):
108 def load_notebook_names(self, path):
109 """Load the notebook names into memory.
109 """Load the notebook names into memory.
110
110
111 This should be called once immediately after the notebook manager
111 This should be called once immediately after the notebook manager
112 is created to load the existing notebooks into the mapping in
112 is created to load the existing notebooks into the mapping in
113 memory.
113 memory.
114 """
114 """
115 self.list_notebooks(path)
115 self.list_notebooks(path)
116
116
117 def list_notebooks(self):
117 def list_notebooks(self):
118 """List all notebooks.
118 """List all notebooks.
119
119
120 This returns a list of dicts, each of the form::
120 This returns a list of dicts, each of the form::
121
121
122 dict(notebook_id=notebook,name=name)
122 dict(notebook_id=notebook,name=name)
123
123
124 This list of dicts should be sorted by name::
124 This list of dicts should be sorted by name::
125
125
126 data = sorted(data, key=lambda item: item['name'])
126 data = sorted(data, key=lambda item: item['name'])
127 """
127 """
128 raise NotImplementedError('must be implemented in a subclass')
128 raise NotImplementedError('must be implemented in a subclass')
129
129
130
130
131 def notebook_exists(self, notebook_path):
131 def notebook_exists(self, notebook_path):
132 """Does a notebook exist?"""
132 """Does a notebook exist?"""
133
133
134
134
135 def notebook_model(self, notebook_name, notebook_path=None, content=True):
135 def notebook_model(self, notebook_name, notebook_path=None, content=True):
136 """ Creates the standard notebook model """
136 """ Creates the standard notebook model """
137 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
137 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
138 model = {"notebook_name": notebook_name,
138 model = {"name": notebook_name,
139 "notebook_path": notebook_path,
139 "path": notebook_path,
140 "last_modified": last_modified.ctime()}
140 "last_modified (UTC)": last_modified.ctime()}
141 if content == True:
141 if content == True:
142 model['content'] = contents
142 model['content'] = contents
143 return model
143 return model
144
144
145 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
145 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
146 """Get the representation of a notebook in format by notebook_name."""
146 """Get the representation of a notebook in format by notebook_name."""
147 format = unicode(format)
147 format = unicode(format)
148 if format not in self.allowed_formats:
148 if format not in self.allowed_formats:
149 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
149 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
150 kwargs = {}
150 kwargs = {}
151 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
151 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
152 if format == 'json':
152 if format == 'json':
153 # don't split lines for sending over the wire, because it
153 # don't split lines for sending over the wire, because it
154 # should match the Python in-memory format.
154 # should match the Python in-memory format.
155 kwargs['split_lines'] = False
155 kwargs['split_lines'] = False
156 representation = current.writes(nb, format, **kwargs)
156 representation = current.writes(nb, format, **kwargs)
157 name = nb.metadata.get('name', 'notebook')
157 name = nb.metadata.get('name', 'notebook')
158 return last_mod, representation, name
158 return last_mod, representation, name
159
159
160 def read_notebook_object(self, notebook_name, notebook_path):
160 def read_notebook_object(self, notebook_name, notebook_path):
161 """Get the object representation of a notebook by notebook_id."""
161 """Get the object representation of a notebook by notebook_id."""
162 raise NotImplementedError('must be implemented in a subclass')
162 raise NotImplementedError('must be implemented in a subclass')
163
163
164 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
164 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
165 """Save a new notebook and return its name.
165 """Save a new notebook and return its name.
166
166
167 If a name is passed in, it overrides any values in the notebook data
167 If a name is passed in, it overrides any values in the notebook data
168 and the value in the data is updated to use that value.
168 and the value in the data is updated to use that value.
169 """
169 """
170 if format not in self.allowed_formats:
170 if format not in self.allowed_formats:
171 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
171 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
172
172
173 try:
173 try:
174 nb = current.reads(data.decode('utf-8'), format)
174 nb = current.reads(data.decode('utf-8'), format)
175 except:
175 except:
176 raise web.HTTPError(400, u'Invalid JSON data')
176 raise web.HTTPError(400, u'Invalid JSON data')
177
177
178 if name is None:
178 if name is None:
179 try:
179 try:
180 name = nb.metadata.name
180 name = nb.metadata.name
181 except AttributeError:
181 except AttributeError:
182 raise web.HTTPError(400, u'Missing notebook name')
182 raise web.HTTPError(400, u'Missing notebook name')
183 nb.metadata.name = name
183 nb.metadata.name = name
184
184
185 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
185 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
186 return notebook_name
186 return notebook_name
187
187
188 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
188 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
189 """Save an existing notebook by notebook_name."""
189 """Save an existing notebook by notebook_name."""
190 if format not in self.allowed_formats:
190 if format not in self.allowed_formats:
191 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
191 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
192
192
193 try:
193 try:
194 nb = current.reads(data.decode('utf-8'), format)
194 nb = current.reads(data.decode('utf-8'), format)
195 except:
195 except:
196 raise web.HTTPError(400, u'Invalid JSON data')
196 raise web.HTTPError(400, u'Invalid JSON data')
197
197
198 if name is not None:
198 if name is not None:
199 nb.metadata.name = name
199 nb.metadata.name = name
200 self.write_notebook_object(nb, name, notebook_path, new_name)
200 self.write_notebook_object(nb, name, notebook_path, new_name)
201
201
202 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
202 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
203 """Write a notebook object and return its notebook_name.
203 """Write a notebook object and return its notebook_name.
204
204
205 If notebook_name is None, this method should create a new notebook_name.
205 If notebook_name is None, this method should create a new notebook_name.
206 If notebook_name is not None, this method should check to make sure it
206 If notebook_name is not None, this method should check to make sure it
207 exists and is valid.
207 exists and is valid.
208 """
208 """
209 raise NotImplementedError('must be implemented in a subclass')
209 raise NotImplementedError('must be implemented in a subclass')
210
210
211 def delete_notebook(self, notebook_name, notebook_path):
211 def delete_notebook(self, notebook_name, notebook_path):
212 """Delete notebook by notebook_id."""
212 """Delete notebook by notebook_id."""
213 raise NotImplementedError('must be implemented in a subclass')
213 raise NotImplementedError('must be implemented in a subclass')
214
214
215 def increment_filename(self, name):
215 def increment_filename(self, name):
216 """Increment a filename to make it unique.
216 """Increment a filename to make it unique.
217
217
218 This exists for notebook stores that must have unique names. When a notebook
218 This exists for notebook stores that must have unique names. When a notebook
219 is created or copied this method constructs a unique filename, typically
219 is created or copied this method constructs a unique filename, typically
220 by appending an integer to the name.
220 by appending an integer to the name.
221 """
221 """
222 return name
222 return name
223
223
224 def new_notebook(self, notebook_path=None):
224 def new_notebook(self, notebook_path=None):
225 """Create a new notebook and return its notebook_id."""
225 """Create a new notebook and return its notebook_id."""
226 name = self.increment_filename('Untitled', notebook_path)
226 name = self.increment_filename('Untitled', notebook_path)
227 metadata = current.new_metadata(name=name)
227 metadata = current.new_metadata(name=name)
228 nb = current.new_notebook(metadata=metadata)
228 nb = current.new_notebook(metadata=metadata)
229 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
229 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
230 return notebook_name
230 return notebook_name
231
231
232 def copy_notebook(self, name, path):
232 def copy_notebook(self, name, path):
233 """Copy an existing notebook and return its notebook_id."""
233 """Copy an existing notebook and return its notebook_id."""
234 last_mod, nb = self.read_notebook_object(name, path)
234 last_mod, nb = self.read_notebook_object(name, path)
235 name = nb.metadata.name + '-Copy'
235 name = nb.metadata.name + '-Copy'
236 name = self.increment_filename(name, path)
236 name = self.increment_filename(name, path)
237 nb.metadata.name = name
237 nb.metadata.name = name
238 notebook_name = self.write_notebook_object(nb, notebook_path = path)
238 notebook_name = self.write_notebook_object(nb, notebook_path = path)
239 return notebook_name
239 return notebook_name
240
240
241 # Checkpoint-related
241 # Checkpoint-related
242
242
243 def create_checkpoint(self, notebook_name, notebook_path=None):
243 def create_checkpoint(self, notebook_name, notebook_path=None):
244 """Create a checkpoint of the current state of a notebook
244 """Create a checkpoint of the current state of a notebook
245
245
246 Returns a checkpoint_id for the new checkpoint.
246 Returns a checkpoint_id for the new checkpoint.
247 """
247 """
248 raise NotImplementedError("must be implemented in a subclass")
248 raise NotImplementedError("must be implemented in a subclass")
249
249
250 def list_checkpoints(self, notebook_name, notebook_path=None):
250 def list_checkpoints(self, notebook_name, notebook_path=None):
251 """Return a list of checkpoints for a given notebook"""
251 """Return a list of checkpoints for a given notebook"""
252 return []
252 return []
253
253
254 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
254 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
255 """Restore a notebook from one of its checkpoints"""
255 """Restore a notebook from one of its checkpoints"""
256 raise NotImplementedError("must be implemented in a subclass")
256 raise NotImplementedError("must be implemented in a subclass")
257
257
258 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
258 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
259 """delete a checkpoint for a notebook"""
259 """delete a checkpoint for a notebook"""
260 raise NotImplementedError("must be implemented in a subclass")
260 raise NotImplementedError("must be implemented in a subclass")
261
261
262 def log_info(self):
262 def log_info(self):
263 self.log.info(self.info_string())
263 self.log.info(self.info_string())
264
264
265 def info_string(self):
265 def info_string(self):
266 return "Serving notebooks"
266 return "Serving notebooks"
@@ -1,97 +1,97 b''
1 """A base class session manager.
1 """A base class session manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import uuid
20 import uuid
21
21
22 from tornado import web
22 from tornado import web
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.nbformat import current
25 from IPython.nbformat import current
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class SessionManager(LoggingConfigurable):
32 class SessionManager(LoggingConfigurable):
33
33
34 # Use session_ids to map notebook names to kernel_ids
34 # Use session_ids to map notebook names to kernel_ids
35 sessions = List()
35 sessions = List()
36
36
37 def get_session(self, nb_name, nb_path=None):
37 def get_session(self, nb_name, nb_path=None):
38 """Get an existing session or create a new one"""
38 """Get an existing session or create a new one"""
39 model = None
39 model = None
40 for session in self.sessions:
40 for session in self.sessions:
41 if session['notebook_name'] == nb_name and session['notebook_path'] == nb_path:
41 if session['name'] == nb_name and session['path'] == nb_path:
42 session_id = session['session_id']
42 session_id = session['id']
43 model = session
43 model = session
44 if model != None:
44 if model != None:
45 return session_id, model
45 return session_id, model
46 else:
46 else:
47 session_id = unicode(uuid.uuid4())
47 session_id = unicode(uuid.uuid4())
48 return session_id, model
48 return session_id, model
49
49
50 def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
50 def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
51 """ Create a session that links notebooks with kernels """
51 """ Create a session that links notebooks with kernels """
52 model = dict(session_id=session_id,
52 model = dict(id=session_id,
53 notebook_name=notebook_name,
53 name=notebook_name,
54 notebook_path=notebook_path,
54 path=notebook_path,
55 kernel=kernel)
55 kernel=kernel)
56 if notebook_path == None:
56 if notebook_path == None:
57 model['notebook_path']=""
57 model['path']=""
58 self.sessions.append(model)
58 self.sessions.append(model)
59 return model
59 return model
60
60
61 def list_sessions(self):
61 def list_sessions(self):
62 """List all sessions and their information"""
62 """List all sessions and their information"""
63 return self.sessions
63 return self.sessions
64
64
65 def set_kernel_for_sessions(self, session_id, kernel_id):
65 def set_kernel_for_sessions(self, session_id, kernel_id):
66 """Maps the kernel_ids to the session_id in session_mapping"""
66 """Maps the kernel_ids to the session_id in session_mapping"""
67 for session in self.sessions:
67 for session in self.sessions:
68 if session['session_id'] == session_id:
68 if session['id'] == session_id:
69 session['kernel_id'] = kernel_id
69 session['kernel']['id'] = kernel_id
70 return self.sessions
70 return self.sessions
71
71
72 def delete_mapping_for_session(self, session_id):
72 def delete_mapping_for_session(self, session_id):
73 """Delete the session from session_mapping with the given session_id"""
73 """Delete the session from session_mapping with the given session_id"""
74 i = 0
74 i = 0
75 for session in self.sessions:
75 for session in self.sessions:
76 if session['session_id'] == session_id:
76 if session['id'] == session_id:
77 del self.sessions[i]
77 del self.sessions[i]
78 i = i + 1
78 i = i + 1
79 return self.sessions
79 return self.sessions
80
80
81 def get_session_from_id(self, session_id):
81 def get_session_from_id(self, session_id):
82 for session in self.sessions:
82 for session in self.sessions:
83 if session['session_id'] == session_id:
83 if session['id'] == session_id:
84 return session
84 return session
85
85
86 def get_notebook_from_session(self, session_id):
86 def get_notebook_from_session(self, session_id):
87 """Returns the notebook_path for the given session_id"""
87 """Returns the notebook_path for the given session_id"""
88 for session in self.sessions:
88 for session in self.sessions:
89 if session['session_id'] == session_id:
89 if session['id'] == session_id:
90 return session['notebook_name']
90 return session['name']
91
91
92 def get_kernel_from_session(self, session_id):
92 def get_kernel_from_session(self, session_id):
93 """Returns the kernel_id for the given session_id"""
93 """Returns the kernel_id for the given session_id"""
94 for session in self.sessions:
94 for session in self.sessions:
95 if session['session_id'] == session_id:
95 if session['id'] == session_id:
96 return session['kernel']['kernel_id']
96 return session['kernel']['id']
97
97
@@ -1,2153 +1,2143 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16 var key = IPython.utils.keycodes;
16 var key = IPython.utils.keycodes;
17
17
18 /**
18 /**
19 * A notebook contains and manages cells.
19 * A notebook contains and manages cells.
20 *
20 *
21 * @class Notebook
21 * @class Notebook
22 * @constructor
22 * @constructor
23 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {String} selector A jQuery selector for the notebook's DOM element
24 * @param {Object} [options] A config object
24 * @param {Object} [options] A config object
25 */
25 */
26 var Notebook = function (selector, options) {
26 var Notebook = function (selector, options) {
27 var options = options || {};
27 var options = options || {};
28 this._baseProjectUrl = options.baseProjectUrl;
28 this._baseProjectUrl = options.baseProjectUrl;
29 this.notebook_path = options.notebookPath;
29 this.notebook_path = options.notebookPath;
30 this.notebook_name = options.notebookName;
30 this.notebook_name = options.notebookName;
31 this.element = $(selector);
31 this.element = $(selector);
32 this.element.scroll();
32 this.element.scroll();
33 this.element.data("notebook", this);
33 this.element.data("notebook", this);
34 this.next_prompt_number = 1;
34 this.next_prompt_number = 1;
35 this.session = null;
35 this.session = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 this.set_dirty(false);
41 this.set_dirty(false);
42 this.metadata = {};
42 this.metadata = {};
43 this._checkpoint_after_save = false;
43 this._checkpoint_after_save = false;
44 this.last_checkpoint = null;
44 this.last_checkpoint = null;
45 this.checkpoints = [];
45 this.checkpoints = [];
46 this.autosave_interval = 0;
46 this.autosave_interval = 0;
47 this.autosave_timer = null;
47 this.autosave_timer = null;
48 // autosave *at most* every two minutes
48 // autosave *at most* every two minutes
49 this.minimum_autosave_interval = 120000;
49 this.minimum_autosave_interval = 120000;
50 // single worksheet for now
50 // single worksheet for now
51 this.worksheet_metadata = {};
51 this.worksheet_metadata = {};
52 this.control_key_active = false;
52 this.control_key_active = false;
53 this.notebook_name = null;
53 this.notebook_name = null;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.nbformat = 3 // Increment this when changing the nbformat
55 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.style();
57 this.style();
58 this.create_elements();
58 this.create_elements();
59 this.bind_events();
59 this.bind_events();
60 };
60 };
61
61
62 /**
62 /**
63 * Tweak the notebook's CSS style.
63 * Tweak the notebook's CSS style.
64 *
64 *
65 * @method style
65 * @method style
66 */
66 */
67 Notebook.prototype.style = function () {
67 Notebook.prototype.style = function () {
68 $('div#notebook').addClass('border-box-sizing');
68 $('div#notebook').addClass('border-box-sizing');
69 };
69 };
70
70
71 /**
71 /**
72 * Get the root URL of the notebook server.
72 * Get the root URL of the notebook server.
73 *
73 *
74 * @method baseProjectUrl
74 * @method baseProjectUrl
75 * @return {String} The base project URL
75 * @return {String} The base project URL
76 */
76 */
77 Notebook.prototype.baseProjectUrl = function(){
77 Notebook.prototype.baseProjectUrl = function(){
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 };
79 };
80
80
81 Notebook.prototype.notebookName = function() {
81 Notebook.prototype.notebookName = function() {
82 var name = $('body').data('notebookName');
82 var name = $('body').data('notebookName');
83 return name;
83 return name;
84 };
84 };
85
85
86 Notebook.prototype.notebookPath = function() {
86 Notebook.prototype.notebookPath = function() {
87 var path = $('body').data('notebookPath');
87 var path = $('body').data('notebookPath');
88 path = decodeURIComponent(path);
88 path = decodeURIComponent(path);
89 if (path != 'None') {
89 if (path != 'None') {
90 if (path[path.length-1] != '/') {
90 if (path[path.length-1] != '/') {
91 path = path.substring(0,path.length);
91 path = path.substring(0,path.length);
92 };
92 };
93 return path;
93 return path;
94 } else {
94 } else {
95 return '';
95 return '';
96 }
96 }
97 };
97 };
98
98
99 /**
99 /**
100 * Create an HTML and CSS representation of the notebook.
100 * Create an HTML and CSS representation of the notebook.
101 *
101 *
102 * @method create_elements
102 * @method create_elements
103 */
103 */
104 Notebook.prototype.create_elements = function () {
104 Notebook.prototype.create_elements = function () {
105 // We add this end_space div to the end of the notebook div to:
105 // We add this end_space div to the end of the notebook div to:
106 // i) provide a margin between the last cell and the end of the notebook
106 // i) provide a margin between the last cell and the end of the notebook
107 // ii) to prevent the div from scrolling up when the last cell is being
107 // ii) to prevent the div from scrolling up when the last cell is being
108 // edited, but is too low on the page, which browsers will do automatically.
108 // edited, but is too low on the page, which browsers will do automatically.
109 var that = this;
109 var that = this;
110 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
110 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
111 var end_space = $('<div/>').addClass('end_space');
111 var end_space = $('<div/>').addClass('end_space');
112 end_space.dblclick(function (e) {
112 end_space.dblclick(function (e) {
113 var ncells = that.ncells();
113 var ncells = that.ncells();
114 that.insert_cell_below('code',ncells-1);
114 that.insert_cell_below('code',ncells-1);
115 });
115 });
116 this.element.append(this.container);
116 this.element.append(this.container);
117 this.container.append(end_space);
117 this.container.append(end_space);
118 $('div#notebook').addClass('border-box-sizing');
118 $('div#notebook').addClass('border-box-sizing');
119 };
119 };
120
120
121 /**
121 /**
122 * Bind JavaScript events: key presses and custom IPython events.
122 * Bind JavaScript events: key presses and custom IPython events.
123 *
123 *
124 * @method bind_events
124 * @method bind_events
125 */
125 */
126 Notebook.prototype.bind_events = function () {
126 Notebook.prototype.bind_events = function () {
127 var that = this;
127 var that = this;
128
128
129 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
129 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
130 var index = that.find_cell_index(data.cell);
130 var index = that.find_cell_index(data.cell);
131 var new_cell = that.insert_cell_below('code',index);
131 var new_cell = that.insert_cell_below('code',index);
132 new_cell.set_text(data.text);
132 new_cell.set_text(data.text);
133 that.dirty = true;
133 that.dirty = true;
134 });
134 });
135
135
136 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
136 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
137 that.dirty = data.value;
137 that.dirty = data.value;
138 });
138 });
139
139
140 $([IPython.events]).on('select.Cell', function (event, data) {
140 $([IPython.events]).on('select.Cell', function (event, data) {
141 var index = that.find_cell_index(data.cell);
141 var index = that.find_cell_index(data.cell);
142 that.select(index);
142 that.select(index);
143 });
143 });
144
144
145 $([IPython.events]).on('status_autorestarting.Kernel', function () {
145 $([IPython.events]).on('status_autorestarting.Kernel', function () {
146 IPython.dialog.modal({
146 IPython.dialog.modal({
147 title: "Kernel Restarting",
147 title: "Kernel Restarting",
148 body: "The kernel appears to have died. It will restart automatically.",
148 body: "The kernel appears to have died. It will restart automatically.",
149 buttons: {
149 buttons: {
150 OK : {
150 OK : {
151 class : "btn-primary"
151 class : "btn-primary"
152 }
152 }
153 }
153 }
154 });
154 });
155 });
155 });
156
156
157
157
158 $(document).keydown(function (event) {
158 $(document).keydown(function (event) {
159
159
160 // Save (CTRL+S) or (AppleKey+S)
160 // Save (CTRL+S) or (AppleKey+S)
161 //metaKey = applekey on mac
161 //metaKey = applekey on mac
162 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
162 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
163 that.save_checkpoint();
163 that.save_checkpoint();
164 event.preventDefault();
164 event.preventDefault();
165 return false;
165 return false;
166 } else if (event.which === key.ESC) {
166 } else if (event.which === key.ESC) {
167 // Intercept escape at highest level to avoid closing
167 // Intercept escape at highest level to avoid closing
168 // websocket connection with firefox
168 // websocket connection with firefox
169 IPython.pager.collapse();
169 IPython.pager.collapse();
170 event.preventDefault();
170 event.preventDefault();
171 } else if (event.which === key.SHIFT) {
171 } else if (event.which === key.SHIFT) {
172 // ignore shift keydown
172 // ignore shift keydown
173 return true;
173 return true;
174 }
174 }
175 if (event.which === key.UPARROW && !event.shiftKey) {
175 if (event.which === key.UPARROW && !event.shiftKey) {
176 var cell = that.get_selected_cell();
176 var cell = that.get_selected_cell();
177 if (cell && cell.at_top()) {
177 if (cell && cell.at_top()) {
178 event.preventDefault();
178 event.preventDefault();
179 that.select_prev();
179 that.select_prev();
180 };
180 };
181 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
181 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
182 var cell = that.get_selected_cell();
182 var cell = that.get_selected_cell();
183 if (cell && cell.at_bottom()) {
183 if (cell && cell.at_bottom()) {
184 event.preventDefault();
184 event.preventDefault();
185 that.select_next();
185 that.select_next();
186 };
186 };
187 } else if (event.which === key.ENTER && event.shiftKey) {
187 } else if (event.which === key.ENTER && event.shiftKey) {
188 that.execute_selected_cell();
188 that.execute_selected_cell();
189 return false;
189 return false;
190 } else if (event.which === key.ENTER && event.altKey) {
190 } else if (event.which === key.ENTER && event.altKey) {
191 // Execute code cell, and insert new in place
191 // Execute code cell, and insert new in place
192 that.execute_selected_cell();
192 that.execute_selected_cell();
193 // Only insert a new cell, if we ended up in an already populated cell
193 // Only insert a new cell, if we ended up in an already populated cell
194 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
194 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
195 that.insert_cell_above('code');
195 that.insert_cell_above('code');
196 }
196 }
197 return false;
197 return false;
198 } else if (event.which === key.ENTER && event.ctrlKey) {
198 } else if (event.which === key.ENTER && event.ctrlKey) {
199 that.execute_selected_cell({terminal:true});
199 that.execute_selected_cell({terminal:true});
200 return false;
200 return false;
201 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
201 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
202 that.control_key_active = true;
202 that.control_key_active = true;
203 return false;
203 return false;
204 } else if (event.which === 88 && that.control_key_active) {
204 } else if (event.which === 88 && that.control_key_active) {
205 // Cut selected cell = x
205 // Cut selected cell = x
206 that.cut_cell();
206 that.cut_cell();
207 that.control_key_active = false;
207 that.control_key_active = false;
208 return false;
208 return false;
209 } else if (event.which === 67 && that.control_key_active) {
209 } else if (event.which === 67 && that.control_key_active) {
210 // Copy selected cell = c
210 // Copy selected cell = c
211 that.copy_cell();
211 that.copy_cell();
212 that.control_key_active = false;
212 that.control_key_active = false;
213 return false;
213 return false;
214 } else if (event.which === 86 && that.control_key_active) {
214 } else if (event.which === 86 && that.control_key_active) {
215 // Paste below selected cell = v
215 // Paste below selected cell = v
216 that.paste_cell_below();
216 that.paste_cell_below();
217 that.control_key_active = false;
217 that.control_key_active = false;
218 return false;
218 return false;
219 } else if (event.which === 68 && that.control_key_active) {
219 } else if (event.which === 68 && that.control_key_active) {
220 // Delete selected cell = d
220 // Delete selected cell = d
221 that.delete_cell();
221 that.delete_cell();
222 that.control_key_active = false;
222 that.control_key_active = false;
223 return false;
223 return false;
224 } else if (event.which === 65 && that.control_key_active) {
224 } else if (event.which === 65 && that.control_key_active) {
225 // Insert code cell above selected = a
225 // Insert code cell above selected = a
226 that.insert_cell_above('code');
226 that.insert_cell_above('code');
227 that.control_key_active = false;
227 that.control_key_active = false;
228 return false;
228 return false;
229 } else if (event.which === 66 && that.control_key_active) {
229 } else if (event.which === 66 && that.control_key_active) {
230 // Insert code cell below selected = b
230 // Insert code cell below selected = b
231 that.insert_cell_below('code');
231 that.insert_cell_below('code');
232 that.control_key_active = false;
232 that.control_key_active = false;
233 return false;
233 return false;
234 } else if (event.which === 89 && that.control_key_active) {
234 } else if (event.which === 89 && that.control_key_active) {
235 // To code = y
235 // To code = y
236 that.to_code();
236 that.to_code();
237 that.control_key_active = false;
237 that.control_key_active = false;
238 return false;
238 return false;
239 } else if (event.which === 77 && that.control_key_active) {
239 } else if (event.which === 77 && that.control_key_active) {
240 // To markdown = m
240 // To markdown = m
241 that.to_markdown();
241 that.to_markdown();
242 that.control_key_active = false;
242 that.control_key_active = false;
243 return false;
243 return false;
244 } else if (event.which === 84 && that.control_key_active) {
244 } else if (event.which === 84 && that.control_key_active) {
245 // To Raw = t
245 // To Raw = t
246 that.to_raw();
246 that.to_raw();
247 that.control_key_active = false;
247 that.control_key_active = false;
248 return false;
248 return false;
249 } else if (event.which === 49 && that.control_key_active) {
249 } else if (event.which === 49 && that.control_key_active) {
250 // To Heading 1 = 1
250 // To Heading 1 = 1
251 that.to_heading(undefined, 1);
251 that.to_heading(undefined, 1);
252 that.control_key_active = false;
252 that.control_key_active = false;
253 return false;
253 return false;
254 } else if (event.which === 50 && that.control_key_active) {
254 } else if (event.which === 50 && that.control_key_active) {
255 // To Heading 2 = 2
255 // To Heading 2 = 2
256 that.to_heading(undefined, 2);
256 that.to_heading(undefined, 2);
257 that.control_key_active = false;
257 that.control_key_active = false;
258 return false;
258 return false;
259 } else if (event.which === 51 && that.control_key_active) {
259 } else if (event.which === 51 && that.control_key_active) {
260 // To Heading 3 = 3
260 // To Heading 3 = 3
261 that.to_heading(undefined, 3);
261 that.to_heading(undefined, 3);
262 that.control_key_active = false;
262 that.control_key_active = false;
263 return false;
263 return false;
264 } else if (event.which === 52 && that.control_key_active) {
264 } else if (event.which === 52 && that.control_key_active) {
265 // To Heading 4 = 4
265 // To Heading 4 = 4
266 that.to_heading(undefined, 4);
266 that.to_heading(undefined, 4);
267 that.control_key_active = false;
267 that.control_key_active = false;
268 return false;
268 return false;
269 } else if (event.which === 53 && that.control_key_active) {
269 } else if (event.which === 53 && that.control_key_active) {
270 // To Heading 5 = 5
270 // To Heading 5 = 5
271 that.to_heading(undefined, 5);
271 that.to_heading(undefined, 5);
272 that.control_key_active = false;
272 that.control_key_active = false;
273 return false;
273 return false;
274 } else if (event.which === 54 && that.control_key_active) {
274 } else if (event.which === 54 && that.control_key_active) {
275 // To Heading 6 = 6
275 // To Heading 6 = 6
276 that.to_heading(undefined, 6);
276 that.to_heading(undefined, 6);
277 that.control_key_active = false;
277 that.control_key_active = false;
278 return false;
278 return false;
279 } else if (event.which === 79 && that.control_key_active) {
279 } else if (event.which === 79 && that.control_key_active) {
280 // Toggle output = o
280 // Toggle output = o
281 if (event.shiftKey){
281 if (event.shiftKey){
282 that.toggle_output_scroll();
282 that.toggle_output_scroll();
283 } else {
283 } else {
284 that.toggle_output();
284 that.toggle_output();
285 }
285 }
286 that.control_key_active = false;
286 that.control_key_active = false;
287 return false;
287 return false;
288 } else if (event.which === 83 && that.control_key_active) {
288 } else if (event.which === 83 && that.control_key_active) {
289 // Save notebook = s
289 // Save notebook = s
290 that.save_checkpoint();
290 that.save_checkpoint();
291 that.control_key_active = false;
291 that.control_key_active = false;
292 return false;
292 return false;
293 } else if (event.which === 74 && that.control_key_active) {
293 } else if (event.which === 74 && that.control_key_active) {
294 // Move cell down = j
294 // Move cell down = j
295 that.move_cell_down();
295 that.move_cell_down();
296 that.control_key_active = false;
296 that.control_key_active = false;
297 return false;
297 return false;
298 } else if (event.which === 75 && that.control_key_active) {
298 } else if (event.which === 75 && that.control_key_active) {
299 // Move cell up = k
299 // Move cell up = k
300 that.move_cell_up();
300 that.move_cell_up();
301 that.control_key_active = false;
301 that.control_key_active = false;
302 return false;
302 return false;
303 } else if (event.which === 80 && that.control_key_active) {
303 } else if (event.which === 80 && that.control_key_active) {
304 // Select previous = p
304 // Select previous = p
305 that.select_prev();
305 that.select_prev();
306 that.control_key_active = false;
306 that.control_key_active = false;
307 return false;
307 return false;
308 } else if (event.which === 78 && that.control_key_active) {
308 } else if (event.which === 78 && that.control_key_active) {
309 // Select next = n
309 // Select next = n
310 that.select_next();
310 that.select_next();
311 that.control_key_active = false;
311 that.control_key_active = false;
312 return false;
312 return false;
313 } else if (event.which === 76 && that.control_key_active) {
313 } else if (event.which === 76 && that.control_key_active) {
314 // Toggle line numbers = l
314 // Toggle line numbers = l
315 that.cell_toggle_line_numbers();
315 that.cell_toggle_line_numbers();
316 that.control_key_active = false;
316 that.control_key_active = false;
317 return false;
317 return false;
318 } else if (event.which === 73 && that.control_key_active) {
318 } else if (event.which === 73 && that.control_key_active) {
319 // Interrupt kernel = i
319 // Interrupt kernel = i
320 that.session.interrupt_kernel();
320 that.session.interrupt_kernel();
321 that.control_key_active = false;
321 that.control_key_active = false;
322 return false;
322 return false;
323 } else if (event.which === 190 && that.control_key_active) {
323 } else if (event.which === 190 && that.control_key_active) {
324 // Restart kernel = . # matches qt console
324 // Restart kernel = . # matches qt console
325 that.restart_kernel();
325 that.restart_kernel();
326 that.control_key_active = false;
326 that.control_key_active = false;
327 return false;
327 return false;
328 } else if (event.which === 72 && that.control_key_active) {
328 } else if (event.which === 72 && that.control_key_active) {
329 // Show keyboard shortcuts = h
329 // Show keyboard shortcuts = h
330 IPython.quick_help.show_keyboard_shortcuts();
330 IPython.quick_help.show_keyboard_shortcuts();
331 that.control_key_active = false;
331 that.control_key_active = false;
332 return false;
332 return false;
333 } else if (event.which === 90 && that.control_key_active) {
333 } else if (event.which === 90 && that.control_key_active) {
334 // Undo last cell delete = z
334 // Undo last cell delete = z
335 that.undelete();
335 that.undelete();
336 that.control_key_active = false;
336 that.control_key_active = false;
337 return false;
337 return false;
338 } else if ((event.which === 189 || event.which === 173) &&
338 } else if ((event.which === 189 || event.which === 173) &&
339 that.control_key_active) {
339 that.control_key_active) {
340 // how fun! '-' is 189 in Chrome, but 173 in FF and Opera
340 // how fun! '-' is 189 in Chrome, but 173 in FF and Opera
341 // Split cell = -
341 // Split cell = -
342 that.split_cell();
342 that.split_cell();
343 that.control_key_active = false;
343 that.control_key_active = false;
344 return false;
344 return false;
345 } else if (that.control_key_active) {
345 } else if (that.control_key_active) {
346 that.control_key_active = false;
346 that.control_key_active = false;
347 return true;
347 return true;
348 }
348 }
349 return true;
349 return true;
350 });
350 });
351
351
352 var collapse_time = function(time){
352 var collapse_time = function(time){
353 var app_height = $('#ipython-main-app').height(); // content height
353 var app_height = $('#ipython-main-app').height(); // content height
354 var splitter_height = $('div#pager_splitter').outerHeight(true);
354 var splitter_height = $('div#pager_splitter').outerHeight(true);
355 var new_height = app_height - splitter_height;
355 var new_height = app_height - splitter_height;
356 that.element.animate({height : new_height + 'px'}, time);
356 that.element.animate({height : new_height + 'px'}, time);
357 }
357 }
358
358
359 this.element.bind('collapse_pager', function (event,extrap) {
359 this.element.bind('collapse_pager', function (event,extrap) {
360 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
360 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
361 collapse_time(time);
361 collapse_time(time);
362 });
362 });
363
363
364 var expand_time = function(time) {
364 var expand_time = function(time) {
365 var app_height = $('#ipython-main-app').height(); // content height
365 var app_height = $('#ipython-main-app').height(); // content height
366 var splitter_height = $('div#pager_splitter').outerHeight(true);
366 var splitter_height = $('div#pager_splitter').outerHeight(true);
367 var pager_height = $('div#pager').outerHeight(true);
367 var pager_height = $('div#pager').outerHeight(true);
368 var new_height = app_height - pager_height - splitter_height;
368 var new_height = app_height - pager_height - splitter_height;
369 that.element.animate({height : new_height + 'px'}, time);
369 that.element.animate({height : new_height + 'px'}, time);
370 }
370 }
371
371
372 this.element.bind('expand_pager', function (event, extrap) {
372 this.element.bind('expand_pager', function (event, extrap) {
373 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
373 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
374 expand_time(time);
374 expand_time(time);
375 });
375 });
376
376
377 // Firefox 22 broke $(window).on("beforeunload")
377 // Firefox 22 broke $(window).on("beforeunload")
378 // I'm not sure why or how.
378 // I'm not sure why or how.
379 window.onbeforeunload = function (e) {
379 window.onbeforeunload = function (e) {
380 // TODO: Make killing the kernel configurable.
380 // TODO: Make killing the kernel configurable.
381 var kill_kernel = false;
381 var kill_kernel = false;
382 if (kill_kernel) {
382 if (kill_kernel) {
383 that.session.kill_kernel();
383 that.session.kill_kernel();
384 }
384 }
385 // if we are autosaving, trigger an autosave on nav-away.
385 // if we are autosaving, trigger an autosave on nav-away.
386 // still warn, because if we don't the autosave may fail.
386 // still warn, because if we don't the autosave may fail.
387 if (that.dirty) {
387 if (that.dirty) {
388 if ( that.autosave_interval ) {
388 if ( that.autosave_interval ) {
389 // schedule autosave in a timeout
389 // schedule autosave in a timeout
390 // this gives you a chance to forcefully discard changes
390 // this gives you a chance to forcefully discard changes
391 // by reloading the page if you *really* want to.
391 // by reloading the page if you *really* want to.
392 // the timer doesn't start until you *dismiss* the dialog.
392 // the timer doesn't start until you *dismiss* the dialog.
393 setTimeout(function () {
393 setTimeout(function () {
394 if (that.dirty) {
394 if (that.dirty) {
395 that.save_notebook();
395 that.save_notebook();
396 }
396 }
397 }, 1000);
397 }, 1000);
398 return "Autosave in progress, latest changes may be lost.";
398 return "Autosave in progress, latest changes may be lost.";
399 } else {
399 } else {
400 return "Unsaved changes will be lost.";
400 return "Unsaved changes will be lost.";
401 }
401 }
402 };
402 };
403 // Null is the *only* return value that will make the browser not
403 // Null is the *only* return value that will make the browser not
404 // pop up the "don't leave" dialog.
404 // pop up the "don't leave" dialog.
405 return null;
405 return null;
406 };
406 };
407 };
407 };
408
408
409 /**
409 /**
410 * Set the dirty flag, and trigger the set_dirty.Notebook event
410 * Set the dirty flag, and trigger the set_dirty.Notebook event
411 *
411 *
412 * @method set_dirty
412 * @method set_dirty
413 */
413 */
414 Notebook.prototype.set_dirty = function (value) {
414 Notebook.prototype.set_dirty = function (value) {
415 if (value === undefined) {
415 if (value === undefined) {
416 value = true;
416 value = true;
417 }
417 }
418 if (this.dirty == value) {
418 if (this.dirty == value) {
419 return;
419 return;
420 }
420 }
421 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
421 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
422 };
422 };
423
423
424 /**
424 /**
425 * Scroll the top of the page to a given cell.
425 * Scroll the top of the page to a given cell.
426 *
426 *
427 * @method scroll_to_cell
427 * @method scroll_to_cell
428 * @param {Number} cell_number An index of the cell to view
428 * @param {Number} cell_number An index of the cell to view
429 * @param {Number} time Animation time in milliseconds
429 * @param {Number} time Animation time in milliseconds
430 * @return {Number} Pixel offset from the top of the container
430 * @return {Number} Pixel offset from the top of the container
431 */
431 */
432 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
432 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
433 var cells = this.get_cells();
433 var cells = this.get_cells();
434 var time = time || 0;
434 var time = time || 0;
435 cell_number = Math.min(cells.length-1,cell_number);
435 cell_number = Math.min(cells.length-1,cell_number);
436 cell_number = Math.max(0 ,cell_number);
436 cell_number = Math.max(0 ,cell_number);
437 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
437 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
438 this.element.animate({scrollTop:scroll_value}, time);
438 this.element.animate({scrollTop:scroll_value}, time);
439 return scroll_value;
439 return scroll_value;
440 };
440 };
441
441
442 /**
442 /**
443 * Scroll to the bottom of the page.
443 * Scroll to the bottom of the page.
444 *
444 *
445 * @method scroll_to_bottom
445 * @method scroll_to_bottom
446 */
446 */
447 Notebook.prototype.scroll_to_bottom = function () {
447 Notebook.prototype.scroll_to_bottom = function () {
448 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
448 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
449 };
449 };
450
450
451 /**
451 /**
452 * Scroll to the top of the page.
452 * Scroll to the top of the page.
453 *
453 *
454 * @method scroll_to_top
454 * @method scroll_to_top
455 */
455 */
456 Notebook.prototype.scroll_to_top = function () {
456 Notebook.prototype.scroll_to_top = function () {
457 this.element.animate({scrollTop:0}, 0);
457 this.element.animate({scrollTop:0}, 0);
458 };
458 };
459
459
460 // Edit Notebook metadata
460 // Edit Notebook metadata
461
461
462 Notebook.prototype.edit_metadata = function () {
462 Notebook.prototype.edit_metadata = function () {
463 var that = this;
463 var that = this;
464 IPython.dialog.edit_metadata(this.metadata, function (md) {
464 IPython.dialog.edit_metadata(this.metadata, function (md) {
465 that.metadata = md;
465 that.metadata = md;
466 }, 'Notebook');
466 }, 'Notebook');
467 };
467 };
468
468
469 // Cell indexing, retrieval, etc.
469 // Cell indexing, retrieval, etc.
470
470
471 /**
471 /**
472 * Get all cell elements in the notebook.
472 * Get all cell elements in the notebook.
473 *
473 *
474 * @method get_cell_elements
474 * @method get_cell_elements
475 * @return {jQuery} A selector of all cell elements
475 * @return {jQuery} A selector of all cell elements
476 */
476 */
477 Notebook.prototype.get_cell_elements = function () {
477 Notebook.prototype.get_cell_elements = function () {
478 return this.container.children("div.cell");
478 return this.container.children("div.cell");
479 };
479 };
480
480
481 /**
481 /**
482 * Get a particular cell element.
482 * Get a particular cell element.
483 *
483 *
484 * @method get_cell_element
484 * @method get_cell_element
485 * @param {Number} index An index of a cell to select
485 * @param {Number} index An index of a cell to select
486 * @return {jQuery} A selector of the given cell.
486 * @return {jQuery} A selector of the given cell.
487 */
487 */
488 Notebook.prototype.get_cell_element = function (index) {
488 Notebook.prototype.get_cell_element = function (index) {
489 var result = null;
489 var result = null;
490 var e = this.get_cell_elements().eq(index);
490 var e = this.get_cell_elements().eq(index);
491 if (e.length !== 0) {
491 if (e.length !== 0) {
492 result = e;
492 result = e;
493 }
493 }
494 return result;
494 return result;
495 };
495 };
496
496
497 /**
497 /**
498 * Count the cells in this notebook.
498 * Count the cells in this notebook.
499 *
499 *
500 * @method ncells
500 * @method ncells
501 * @return {Number} The number of cells in this notebook
501 * @return {Number} The number of cells in this notebook
502 */
502 */
503 Notebook.prototype.ncells = function () {
503 Notebook.prototype.ncells = function () {
504 return this.get_cell_elements().length;
504 return this.get_cell_elements().length;
505 };
505 };
506
506
507 /**
507 /**
508 * Get all Cell objects in this notebook.
508 * Get all Cell objects in this notebook.
509 *
509 *
510 * @method get_cells
510 * @method get_cells
511 * @return {Array} This notebook's Cell objects
511 * @return {Array} This notebook's Cell objects
512 */
512 */
513 // TODO: we are often calling cells as cells()[i], which we should optimize
513 // TODO: we are often calling cells as cells()[i], which we should optimize
514 // to cells(i) or a new method.
514 // to cells(i) or a new method.
515 Notebook.prototype.get_cells = function () {
515 Notebook.prototype.get_cells = function () {
516 return this.get_cell_elements().toArray().map(function (e) {
516 return this.get_cell_elements().toArray().map(function (e) {
517 return $(e).data("cell");
517 return $(e).data("cell");
518 });
518 });
519 };
519 };
520
520
521 /**
521 /**
522 * Get a Cell object from this notebook.
522 * Get a Cell object from this notebook.
523 *
523 *
524 * @method get_cell
524 * @method get_cell
525 * @param {Number} index An index of a cell to retrieve
525 * @param {Number} index An index of a cell to retrieve
526 * @return {Cell} A particular cell
526 * @return {Cell} A particular cell
527 */
527 */
528 Notebook.prototype.get_cell = function (index) {
528 Notebook.prototype.get_cell = function (index) {
529 var result = null;
529 var result = null;
530 var ce = this.get_cell_element(index);
530 var ce = this.get_cell_element(index);
531 if (ce !== null) {
531 if (ce !== null) {
532 result = ce.data('cell');
532 result = ce.data('cell');
533 }
533 }
534 return result;
534 return result;
535 }
535 }
536
536
537 /**
537 /**
538 * Get the cell below a given cell.
538 * Get the cell below a given cell.
539 *
539 *
540 * @method get_next_cell
540 * @method get_next_cell
541 * @param {Cell} cell The provided cell
541 * @param {Cell} cell The provided cell
542 * @return {Cell} The next cell
542 * @return {Cell} The next cell
543 */
543 */
544 Notebook.prototype.get_next_cell = function (cell) {
544 Notebook.prototype.get_next_cell = function (cell) {
545 var result = null;
545 var result = null;
546 var index = this.find_cell_index(cell);
546 var index = this.find_cell_index(cell);
547 if (this.is_valid_cell_index(index+1)) {
547 if (this.is_valid_cell_index(index+1)) {
548 result = this.get_cell(index+1);
548 result = this.get_cell(index+1);
549 }
549 }
550 return result;
550 return result;
551 }
551 }
552
552
553 /**
553 /**
554 * Get the cell above a given cell.
554 * Get the cell above a given cell.
555 *
555 *
556 * @method get_prev_cell
556 * @method get_prev_cell
557 * @param {Cell} cell The provided cell
557 * @param {Cell} cell The provided cell
558 * @return {Cell} The previous cell
558 * @return {Cell} The previous cell
559 */
559 */
560 Notebook.prototype.get_prev_cell = function (cell) {
560 Notebook.prototype.get_prev_cell = function (cell) {
561 // TODO: off-by-one
561 // TODO: off-by-one
562 // nb.get_prev_cell(nb.get_cell(1)) is null
562 // nb.get_prev_cell(nb.get_cell(1)) is null
563 var result = null;
563 var result = null;
564 var index = this.find_cell_index(cell);
564 var index = this.find_cell_index(cell);
565 if (index !== null && index > 1) {
565 if (index !== null && index > 1) {
566 result = this.get_cell(index-1);
566 result = this.get_cell(index-1);
567 }
567 }
568 return result;
568 return result;
569 }
569 }
570
570
571 /**
571 /**
572 * Get the numeric index of a given cell.
572 * Get the numeric index of a given cell.
573 *
573 *
574 * @method find_cell_index
574 * @method find_cell_index
575 * @param {Cell} cell The provided cell
575 * @param {Cell} cell The provided cell
576 * @return {Number} The cell's numeric index
576 * @return {Number} The cell's numeric index
577 */
577 */
578 Notebook.prototype.find_cell_index = function (cell) {
578 Notebook.prototype.find_cell_index = function (cell) {
579 var result = null;
579 var result = null;
580 this.get_cell_elements().filter(function (index) {
580 this.get_cell_elements().filter(function (index) {
581 if ($(this).data("cell") === cell) {
581 if ($(this).data("cell") === cell) {
582 result = index;
582 result = index;
583 };
583 };
584 });
584 });
585 return result;
585 return result;
586 };
586 };
587
587
588 /**
588 /**
589 * Get a given index , or the selected index if none is provided.
589 * Get a given index , or the selected index if none is provided.
590 *
590 *
591 * @method index_or_selected
591 * @method index_or_selected
592 * @param {Number} index A cell's index
592 * @param {Number} index A cell's index
593 * @return {Number} The given index, or selected index if none is provided.
593 * @return {Number} The given index, or selected index if none is provided.
594 */
594 */
595 Notebook.prototype.index_or_selected = function (index) {
595 Notebook.prototype.index_or_selected = function (index) {
596 var i;
596 var i;
597 if (index === undefined || index === null) {
597 if (index === undefined || index === null) {
598 i = this.get_selected_index();
598 i = this.get_selected_index();
599 if (i === null) {
599 if (i === null) {
600 i = 0;
600 i = 0;
601 }
601 }
602 } else {
602 } else {
603 i = index;
603 i = index;
604 }
604 }
605 return i;
605 return i;
606 };
606 };
607
607
608 /**
608 /**
609 * Get the currently selected cell.
609 * Get the currently selected cell.
610 * @method get_selected_cell
610 * @method get_selected_cell
611 * @return {Cell} The selected cell
611 * @return {Cell} The selected cell
612 */
612 */
613 Notebook.prototype.get_selected_cell = function () {
613 Notebook.prototype.get_selected_cell = function () {
614 var index = this.get_selected_index();
614 var index = this.get_selected_index();
615 return this.get_cell(index);
615 return this.get_cell(index);
616 };
616 };
617
617
618 /**
618 /**
619 * Check whether a cell index is valid.
619 * Check whether a cell index is valid.
620 *
620 *
621 * @method is_valid_cell_index
621 * @method is_valid_cell_index
622 * @param {Number} index A cell index
622 * @param {Number} index A cell index
623 * @return True if the index is valid, false otherwise
623 * @return True if the index is valid, false otherwise
624 */
624 */
625 Notebook.prototype.is_valid_cell_index = function (index) {
625 Notebook.prototype.is_valid_cell_index = function (index) {
626 if (index !== null && index >= 0 && index < this.ncells()) {
626 if (index !== null && index >= 0 && index < this.ncells()) {
627 return true;
627 return true;
628 } else {
628 } else {
629 return false;
629 return false;
630 };
630 };
631 }
631 }
632
632
633 /**
633 /**
634 * Get the index of the currently selected cell.
634 * Get the index of the currently selected cell.
635
635
636 * @method get_selected_index
636 * @method get_selected_index
637 * @return {Number} The selected cell's numeric index
637 * @return {Number} The selected cell's numeric index
638 */
638 */
639 Notebook.prototype.get_selected_index = function () {
639 Notebook.prototype.get_selected_index = function () {
640 var result = null;
640 var result = null;
641 this.get_cell_elements().filter(function (index) {
641 this.get_cell_elements().filter(function (index) {
642 if ($(this).data("cell").selected === true) {
642 if ($(this).data("cell").selected === true) {
643 result = index;
643 result = index;
644 };
644 };
645 });
645 });
646 return result;
646 return result;
647 };
647 };
648
648
649
649
650 // Cell selection.
650 // Cell selection.
651
651
652 /**
652 /**
653 * Programmatically select a cell.
653 * Programmatically select a cell.
654 *
654 *
655 * @method select
655 * @method select
656 * @param {Number} index A cell's index
656 * @param {Number} index A cell's index
657 * @return {Notebook} This notebook
657 * @return {Notebook} This notebook
658 */
658 */
659 Notebook.prototype.select = function (index) {
659 Notebook.prototype.select = function (index) {
660 if (this.is_valid_cell_index(index)) {
660 if (this.is_valid_cell_index(index)) {
661 var sindex = this.get_selected_index()
661 var sindex = this.get_selected_index()
662 if (sindex !== null && index !== sindex) {
662 if (sindex !== null && index !== sindex) {
663 this.get_cell(sindex).unselect();
663 this.get_cell(sindex).unselect();
664 };
664 };
665 var cell = this.get_cell(index);
665 var cell = this.get_cell(index);
666 cell.select();
666 cell.select();
667 if (cell.cell_type === 'heading') {
667 if (cell.cell_type === 'heading') {
668 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
668 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
669 {'cell_type':cell.cell_type,level:cell.level}
669 {'cell_type':cell.cell_type,level:cell.level}
670 );
670 );
671 } else {
671 } else {
672 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
672 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
673 {'cell_type':cell.cell_type}
673 {'cell_type':cell.cell_type}
674 );
674 );
675 };
675 };
676 };
676 };
677 return this;
677 return this;
678 };
678 };
679
679
680 /**
680 /**
681 * Programmatically select the next cell.
681 * Programmatically select the next cell.
682 *
682 *
683 * @method select_next
683 * @method select_next
684 * @return {Notebook} This notebook
684 * @return {Notebook} This notebook
685 */
685 */
686 Notebook.prototype.select_next = function () {
686 Notebook.prototype.select_next = function () {
687 var index = this.get_selected_index();
687 var index = this.get_selected_index();
688 this.select(index+1);
688 this.select(index+1);
689 return this;
689 return this;
690 };
690 };
691
691
692 /**
692 /**
693 * Programmatically select the previous cell.
693 * Programmatically select the previous cell.
694 *
694 *
695 * @method select_prev
695 * @method select_prev
696 * @return {Notebook} This notebook
696 * @return {Notebook} This notebook
697 */
697 */
698 Notebook.prototype.select_prev = function () {
698 Notebook.prototype.select_prev = function () {
699 var index = this.get_selected_index();
699 var index = this.get_selected_index();
700 this.select(index-1);
700 this.select(index-1);
701 return this;
701 return this;
702 };
702 };
703
703
704
704
705 // Cell movement
705 // Cell movement
706
706
707 /**
707 /**
708 * Move given (or selected) cell up and select it.
708 * Move given (or selected) cell up and select it.
709 *
709 *
710 * @method move_cell_up
710 * @method move_cell_up
711 * @param [index] {integer} cell index
711 * @param [index] {integer} cell index
712 * @return {Notebook} This notebook
712 * @return {Notebook} This notebook
713 **/
713 **/
714 Notebook.prototype.move_cell_up = function (index) {
714 Notebook.prototype.move_cell_up = function (index) {
715 var i = this.index_or_selected(index);
715 var i = this.index_or_selected(index);
716 if (this.is_valid_cell_index(i) && i > 0) {
716 if (this.is_valid_cell_index(i) && i > 0) {
717 var pivot = this.get_cell_element(i-1);
717 var pivot = this.get_cell_element(i-1);
718 var tomove = this.get_cell_element(i);
718 var tomove = this.get_cell_element(i);
719 if (pivot !== null && tomove !== null) {
719 if (pivot !== null && tomove !== null) {
720 tomove.detach();
720 tomove.detach();
721 pivot.before(tomove);
721 pivot.before(tomove);
722 this.select(i-1);
722 this.select(i-1);
723 };
723 };
724 this.set_dirty(true);
724 this.set_dirty(true);
725 };
725 };
726 return this;
726 return this;
727 };
727 };
728
728
729
729
730 /**
730 /**
731 * Move given (or selected) cell down and select it
731 * Move given (or selected) cell down and select it
732 *
732 *
733 * @method move_cell_down
733 * @method move_cell_down
734 * @param [index] {integer} cell index
734 * @param [index] {integer} cell index
735 * @return {Notebook} This notebook
735 * @return {Notebook} This notebook
736 **/
736 **/
737 Notebook.prototype.move_cell_down = function (index) {
737 Notebook.prototype.move_cell_down = function (index) {
738 var i = this.index_or_selected(index);
738 var i = this.index_or_selected(index);
739 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
739 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
740 var pivot = this.get_cell_element(i+1);
740 var pivot = this.get_cell_element(i+1);
741 var tomove = this.get_cell_element(i);
741 var tomove = this.get_cell_element(i);
742 if (pivot !== null && tomove !== null) {
742 if (pivot !== null && tomove !== null) {
743 tomove.detach();
743 tomove.detach();
744 pivot.after(tomove);
744 pivot.after(tomove);
745 this.select(i+1);
745 this.select(i+1);
746 };
746 };
747 };
747 };
748 this.set_dirty();
748 this.set_dirty();
749 return this;
749 return this;
750 };
750 };
751
751
752
752
753 // Insertion, deletion.
753 // Insertion, deletion.
754
754
755 /**
755 /**
756 * Delete a cell from the notebook.
756 * Delete a cell from the notebook.
757 *
757 *
758 * @method delete_cell
758 * @method delete_cell
759 * @param [index] A cell's numeric index
759 * @param [index] A cell's numeric index
760 * @return {Notebook} This notebook
760 * @return {Notebook} This notebook
761 */
761 */
762 Notebook.prototype.delete_cell = function (index) {
762 Notebook.prototype.delete_cell = function (index) {
763 var i = this.index_or_selected(index);
763 var i = this.index_or_selected(index);
764 var cell = this.get_selected_cell();
764 var cell = this.get_selected_cell();
765 this.undelete_backup = cell.toJSON();
765 this.undelete_backup = cell.toJSON();
766 $('#undelete_cell').removeClass('ui-state-disabled');
766 $('#undelete_cell').removeClass('ui-state-disabled');
767 if (this.is_valid_cell_index(i)) {
767 if (this.is_valid_cell_index(i)) {
768 var ce = this.get_cell_element(i);
768 var ce = this.get_cell_element(i);
769 ce.remove();
769 ce.remove();
770 if (i === (this.ncells())) {
770 if (i === (this.ncells())) {
771 this.select(i-1);
771 this.select(i-1);
772 this.undelete_index = i - 1;
772 this.undelete_index = i - 1;
773 this.undelete_below = true;
773 this.undelete_below = true;
774 } else {
774 } else {
775 this.select(i);
775 this.select(i);
776 this.undelete_index = i;
776 this.undelete_index = i;
777 this.undelete_below = false;
777 this.undelete_below = false;
778 };
778 };
779 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
779 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
780 this.set_dirty(true);
780 this.set_dirty(true);
781 };
781 };
782 return this;
782 return this;
783 };
783 };
784
784
785 /**
785 /**
786 * Insert a cell so that after insertion the cell is at given index.
786 * Insert a cell so that after insertion the cell is at given index.
787 *
787 *
788 * Similar to insert_above, but index parameter is mandatory
788 * Similar to insert_above, but index parameter is mandatory
789 *
789 *
790 * Index will be brought back into the accissible range [0,n]
790 * Index will be brought back into the accissible range [0,n]
791 *
791 *
792 * @method insert_cell_at_index
792 * @method insert_cell_at_index
793 * @param type {string} in ['code','markdown','heading']
793 * @param type {string} in ['code','markdown','heading']
794 * @param [index] {int} a valid index where to inser cell
794 * @param [index] {int} a valid index where to inser cell
795 *
795 *
796 * @return cell {cell|null} created cell or null
796 * @return cell {cell|null} created cell or null
797 **/
797 **/
798 Notebook.prototype.insert_cell_at_index = function(type, index){
798 Notebook.prototype.insert_cell_at_index = function(type, index){
799
799
800 var ncells = this.ncells();
800 var ncells = this.ncells();
801 var index = Math.min(index,ncells);
801 var index = Math.min(index,ncells);
802 index = Math.max(index,0);
802 index = Math.max(index,0);
803 var cell = null;
803 var cell = null;
804
804
805 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
805 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
806 if (type === 'code') {
806 if (type === 'code') {
807 cell = new IPython.CodeCell(this.session);
807 cell = new IPython.CodeCell(this.session);
808 cell.set_input_prompt();
808 cell.set_input_prompt();
809 } else if (type === 'markdown') {
809 } else if (type === 'markdown') {
810 cell = new IPython.MarkdownCell();
810 cell = new IPython.MarkdownCell();
811 } else if (type === 'raw') {
811 } else if (type === 'raw') {
812 cell = new IPython.RawCell();
812 cell = new IPython.RawCell();
813 } else if (type === 'heading') {
813 } else if (type === 'heading') {
814 cell = new IPython.HeadingCell();
814 cell = new IPython.HeadingCell();
815 }
815 }
816
816
817 if(this._insert_element_at_index(cell.element,index)){
817 if(this._insert_element_at_index(cell.element,index)){
818 cell.render();
818 cell.render();
819 this.select(this.find_cell_index(cell));
819 this.select(this.find_cell_index(cell));
820 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
820 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
821 this.set_dirty(true);
821 this.set_dirty(true);
822 }
822 }
823 }
823 }
824 return cell;
824 return cell;
825
825
826 };
826 };
827
827
828 /**
828 /**
829 * Insert an element at given cell index.
829 * Insert an element at given cell index.
830 *
830 *
831 * @method _insert_element_at_index
831 * @method _insert_element_at_index
832 * @param element {dom element} a cell element
832 * @param element {dom element} a cell element
833 * @param [index] {int} a valid index where to inser cell
833 * @param [index] {int} a valid index where to inser cell
834 * @private
834 * @private
835 *
835 *
836 * return true if everything whent fine.
836 * return true if everything whent fine.
837 **/
837 **/
838 Notebook.prototype._insert_element_at_index = function(element, index){
838 Notebook.prototype._insert_element_at_index = function(element, index){
839 if (element === undefined){
839 if (element === undefined){
840 return false;
840 return false;
841 }
841 }
842
842
843 var ncells = this.ncells();
843 var ncells = this.ncells();
844
844
845 if (ncells === 0) {
845 if (ncells === 0) {
846 // special case append if empty
846 // special case append if empty
847 this.element.find('div.end_space').before(element);
847 this.element.find('div.end_space').before(element);
848 } else if ( ncells === index ) {
848 } else if ( ncells === index ) {
849 // special case append it the end, but not empty
849 // special case append it the end, but not empty
850 this.get_cell_element(index-1).after(element);
850 this.get_cell_element(index-1).after(element);
851 } else if (this.is_valid_cell_index(index)) {
851 } else if (this.is_valid_cell_index(index)) {
852 // otherwise always somewhere to append to
852 // otherwise always somewhere to append to
853 this.get_cell_element(index).before(element);
853 this.get_cell_element(index).before(element);
854 } else {
854 } else {
855 return false;
855 return false;
856 }
856 }
857
857
858 if (this.undelete_index !== null && index <= this.undelete_index) {
858 if (this.undelete_index !== null && index <= this.undelete_index) {
859 this.undelete_index = this.undelete_index + 1;
859 this.undelete_index = this.undelete_index + 1;
860 this.set_dirty(true);
860 this.set_dirty(true);
861 }
861 }
862 return true;
862 return true;
863 };
863 };
864
864
865 /**
865 /**
866 * Insert a cell of given type above given index, or at top
866 * Insert a cell of given type above given index, or at top
867 * of notebook if index smaller than 0.
867 * of notebook if index smaller than 0.
868 *
868 *
869 * default index value is the one of currently selected cell
869 * default index value is the one of currently selected cell
870 *
870 *
871 * @method insert_cell_above
871 * @method insert_cell_above
872 * @param type {string} cell type
872 * @param type {string} cell type
873 * @param [index] {integer}
873 * @param [index] {integer}
874 *
874 *
875 * @return handle to created cell or null
875 * @return handle to created cell or null
876 **/
876 **/
877 Notebook.prototype.insert_cell_above = function (type, index) {
877 Notebook.prototype.insert_cell_above = function (type, index) {
878 index = this.index_or_selected(index);
878 index = this.index_or_selected(index);
879 return this.insert_cell_at_index(type, index);
879 return this.insert_cell_at_index(type, index);
880 };
880 };
881
881
882 /**
882 /**
883 * Insert a cell of given type below given index, or at bottom
883 * Insert a cell of given type below given index, or at bottom
884 * of notebook if index greater thatn number of cell
884 * of notebook if index greater thatn number of cell
885 *
885 *
886 * default index value is the one of currently selected cell
886 * default index value is the one of currently selected cell
887 *
887 *
888 * @method insert_cell_below
888 * @method insert_cell_below
889 * @param type {string} cell type
889 * @param type {string} cell type
890 * @param [index] {integer}
890 * @param [index] {integer}
891 *
891 *
892 * @return handle to created cell or null
892 * @return handle to created cell or null
893 *
893 *
894 **/
894 **/
895 Notebook.prototype.insert_cell_below = function (type, index) {
895 Notebook.prototype.insert_cell_below = function (type, index) {
896 index = this.index_or_selected(index);
896 index = this.index_or_selected(index);
897 return this.insert_cell_at_index(type, index+1);
897 return this.insert_cell_at_index(type, index+1);
898 };
898 };
899
899
900
900
901 /**
901 /**
902 * Insert cell at end of notebook
902 * Insert cell at end of notebook
903 *
903 *
904 * @method insert_cell_at_bottom
904 * @method insert_cell_at_bottom
905 * @param {String} type cell type
905 * @param {String} type cell type
906 *
906 *
907 * @return the added cell; or null
907 * @return the added cell; or null
908 **/
908 **/
909 Notebook.prototype.insert_cell_at_bottom = function (type){
909 Notebook.prototype.insert_cell_at_bottom = function (type){
910 var len = this.ncells();
910 var len = this.ncells();
911 return this.insert_cell_below(type,len-1);
911 return this.insert_cell_below(type,len-1);
912 };
912 };
913
913
914 /**
914 /**
915 * Turn a cell into a code cell.
915 * Turn a cell into a code cell.
916 *
916 *
917 * @method to_code
917 * @method to_code
918 * @param {Number} [index] A cell's index
918 * @param {Number} [index] A cell's index
919 */
919 */
920 Notebook.prototype.to_code = function (index) {
920 Notebook.prototype.to_code = function (index) {
921 var i = this.index_or_selected(index);
921 var i = this.index_or_selected(index);
922 if (this.is_valid_cell_index(i)) {
922 if (this.is_valid_cell_index(i)) {
923 var source_element = this.get_cell_element(i);
923 var source_element = this.get_cell_element(i);
924 var source_cell = source_element.data("cell");
924 var source_cell = source_element.data("cell");
925 if (!(source_cell instanceof IPython.CodeCell)) {
925 if (!(source_cell instanceof IPython.CodeCell)) {
926 var target_cell = this.insert_cell_below('code',i);
926 var target_cell = this.insert_cell_below('code',i);
927 var text = source_cell.get_text();
927 var text = source_cell.get_text();
928 if (text === source_cell.placeholder) {
928 if (text === source_cell.placeholder) {
929 text = '';
929 text = '';
930 }
930 }
931 target_cell.set_text(text);
931 target_cell.set_text(text);
932 // make this value the starting point, so that we can only undo
932 // make this value the starting point, so that we can only undo
933 // to this state, instead of a blank cell
933 // to this state, instead of a blank cell
934 target_cell.code_mirror.clearHistory();
934 target_cell.code_mirror.clearHistory();
935 source_element.remove();
935 source_element.remove();
936 this.set_dirty(true);
936 this.set_dirty(true);
937 };
937 };
938 };
938 };
939 };
939 };
940
940
941 /**
941 /**
942 * Turn a cell into a Markdown cell.
942 * Turn a cell into a Markdown cell.
943 *
943 *
944 * @method to_markdown
944 * @method to_markdown
945 * @param {Number} [index] A cell's index
945 * @param {Number} [index] A cell's index
946 */
946 */
947 Notebook.prototype.to_markdown = function (index) {
947 Notebook.prototype.to_markdown = function (index) {
948 var i = this.index_or_selected(index);
948 var i = this.index_or_selected(index);
949 if (this.is_valid_cell_index(i)) {
949 if (this.is_valid_cell_index(i)) {
950 var source_element = this.get_cell_element(i);
950 var source_element = this.get_cell_element(i);
951 var source_cell = source_element.data("cell");
951 var source_cell = source_element.data("cell");
952 if (!(source_cell instanceof IPython.MarkdownCell)) {
952 if (!(source_cell instanceof IPython.MarkdownCell)) {
953 var target_cell = this.insert_cell_below('markdown',i);
953 var target_cell = this.insert_cell_below('markdown',i);
954 var text = source_cell.get_text();
954 var text = source_cell.get_text();
955 if (text === source_cell.placeholder) {
955 if (text === source_cell.placeholder) {
956 text = '';
956 text = '';
957 };
957 };
958 // The edit must come before the set_text.
958 // The edit must come before the set_text.
959 target_cell.edit();
959 target_cell.edit();
960 target_cell.set_text(text);
960 target_cell.set_text(text);
961 // make this value the starting point, so that we can only undo
961 // make this value the starting point, so that we can only undo
962 // to this state, instead of a blank cell
962 // to this state, instead of a blank cell
963 target_cell.code_mirror.clearHistory();
963 target_cell.code_mirror.clearHistory();
964 source_element.remove();
964 source_element.remove();
965 this.set_dirty(true);
965 this.set_dirty(true);
966 };
966 };
967 };
967 };
968 };
968 };
969
969
970 /**
970 /**
971 * Turn a cell into a raw text cell.
971 * Turn a cell into a raw text cell.
972 *
972 *
973 * @method to_raw
973 * @method to_raw
974 * @param {Number} [index] A cell's index
974 * @param {Number} [index] A cell's index
975 */
975 */
976 Notebook.prototype.to_raw = function (index) {
976 Notebook.prototype.to_raw = function (index) {
977 var i = this.index_or_selected(index);
977 var i = this.index_or_selected(index);
978 if (this.is_valid_cell_index(i)) {
978 if (this.is_valid_cell_index(i)) {
979 var source_element = this.get_cell_element(i);
979 var source_element = this.get_cell_element(i);
980 var source_cell = source_element.data("cell");
980 var source_cell = source_element.data("cell");
981 var target_cell = null;
981 var target_cell = null;
982 if (!(source_cell instanceof IPython.RawCell)) {
982 if (!(source_cell instanceof IPython.RawCell)) {
983 target_cell = this.insert_cell_below('raw',i);
983 target_cell = this.insert_cell_below('raw',i);
984 var text = source_cell.get_text();
984 var text = source_cell.get_text();
985 if (text === source_cell.placeholder) {
985 if (text === source_cell.placeholder) {
986 text = '';
986 text = '';
987 };
987 };
988 // The edit must come before the set_text.
988 // The edit must come before the set_text.
989 target_cell.edit();
989 target_cell.edit();
990 target_cell.set_text(text);
990 target_cell.set_text(text);
991 // make this value the starting point, so that we can only undo
991 // make this value the starting point, so that we can only undo
992 // to this state, instead of a blank cell
992 // to this state, instead of a blank cell
993 target_cell.code_mirror.clearHistory();
993 target_cell.code_mirror.clearHistory();
994 source_element.remove();
994 source_element.remove();
995 this.set_dirty(true);
995 this.set_dirty(true);
996 };
996 };
997 };
997 };
998 };
998 };
999
999
1000 /**
1000 /**
1001 * Turn a cell into a heading cell.
1001 * Turn a cell into a heading cell.
1002 *
1002 *
1003 * @method to_heading
1003 * @method to_heading
1004 * @param {Number} [index] A cell's index
1004 * @param {Number} [index] A cell's index
1005 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1005 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1006 */
1006 */
1007 Notebook.prototype.to_heading = function (index, level) {
1007 Notebook.prototype.to_heading = function (index, level) {
1008 level = level || 1;
1008 level = level || 1;
1009 var i = this.index_or_selected(index);
1009 var i = this.index_or_selected(index);
1010 if (this.is_valid_cell_index(i)) {
1010 if (this.is_valid_cell_index(i)) {
1011 var source_element = this.get_cell_element(i);
1011 var source_element = this.get_cell_element(i);
1012 var source_cell = source_element.data("cell");
1012 var source_cell = source_element.data("cell");
1013 var target_cell = null;
1013 var target_cell = null;
1014 if (source_cell instanceof IPython.HeadingCell) {
1014 if (source_cell instanceof IPython.HeadingCell) {
1015 source_cell.set_level(level);
1015 source_cell.set_level(level);
1016 } else {
1016 } else {
1017 target_cell = this.insert_cell_below('heading',i);
1017 target_cell = this.insert_cell_below('heading',i);
1018 var text = source_cell.get_text();
1018 var text = source_cell.get_text();
1019 if (text === source_cell.placeholder) {
1019 if (text === source_cell.placeholder) {
1020 text = '';
1020 text = '';
1021 };
1021 };
1022 // The edit must come before the set_text.
1022 // The edit must come before the set_text.
1023 target_cell.set_level(level);
1023 target_cell.set_level(level);
1024 target_cell.edit();
1024 target_cell.edit();
1025 target_cell.set_text(text);
1025 target_cell.set_text(text);
1026 // make this value the starting point, so that we can only undo
1026 // make this value the starting point, so that we can only undo
1027 // to this state, instead of a blank cell
1027 // to this state, instead of a blank cell
1028 target_cell.code_mirror.clearHistory();
1028 target_cell.code_mirror.clearHistory();
1029 source_element.remove();
1029 source_element.remove();
1030 this.set_dirty(true);
1030 this.set_dirty(true);
1031 };
1031 };
1032 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1032 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1033 {'cell_type':'heading',level:level}
1033 {'cell_type':'heading',level:level}
1034 );
1034 );
1035 };
1035 };
1036 };
1036 };
1037
1037
1038
1038
1039 // Cut/Copy/Paste
1039 // Cut/Copy/Paste
1040
1040
1041 /**
1041 /**
1042 * Enable UI elements for pasting cells.
1042 * Enable UI elements for pasting cells.
1043 *
1043 *
1044 * @method enable_paste
1044 * @method enable_paste
1045 */
1045 */
1046 Notebook.prototype.enable_paste = function () {
1046 Notebook.prototype.enable_paste = function () {
1047 var that = this;
1047 var that = this;
1048 if (!this.paste_enabled) {
1048 if (!this.paste_enabled) {
1049 $('#paste_cell_replace').removeClass('ui-state-disabled')
1049 $('#paste_cell_replace').removeClass('ui-state-disabled')
1050 .on('click', function () {that.paste_cell_replace();});
1050 .on('click', function () {that.paste_cell_replace();});
1051 $('#paste_cell_above').removeClass('ui-state-disabled')
1051 $('#paste_cell_above').removeClass('ui-state-disabled')
1052 .on('click', function () {that.paste_cell_above();});
1052 .on('click', function () {that.paste_cell_above();});
1053 $('#paste_cell_below').removeClass('ui-state-disabled')
1053 $('#paste_cell_below').removeClass('ui-state-disabled')
1054 .on('click', function () {that.paste_cell_below();});
1054 .on('click', function () {that.paste_cell_below();});
1055 this.paste_enabled = true;
1055 this.paste_enabled = true;
1056 };
1056 };
1057 };
1057 };
1058
1058
1059 /**
1059 /**
1060 * Disable UI elements for pasting cells.
1060 * Disable UI elements for pasting cells.
1061 *
1061 *
1062 * @method disable_paste
1062 * @method disable_paste
1063 */
1063 */
1064 Notebook.prototype.disable_paste = function () {
1064 Notebook.prototype.disable_paste = function () {
1065 if (this.paste_enabled) {
1065 if (this.paste_enabled) {
1066 $('#paste_cell_replace').addClass('ui-state-disabled').off('click');
1066 $('#paste_cell_replace').addClass('ui-state-disabled').off('click');
1067 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
1067 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
1068 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
1068 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
1069 this.paste_enabled = false;
1069 this.paste_enabled = false;
1070 };
1070 };
1071 };
1071 };
1072
1072
1073 /**
1073 /**
1074 * Cut a cell.
1074 * Cut a cell.
1075 *
1075 *
1076 * @method cut_cell
1076 * @method cut_cell
1077 */
1077 */
1078 Notebook.prototype.cut_cell = function () {
1078 Notebook.prototype.cut_cell = function () {
1079 this.copy_cell();
1079 this.copy_cell();
1080 this.delete_cell();
1080 this.delete_cell();
1081 }
1081 }
1082
1082
1083 /**
1083 /**
1084 * Copy a cell.
1084 * Copy a cell.
1085 *
1085 *
1086 * @method copy_cell
1086 * @method copy_cell
1087 */
1087 */
1088 Notebook.prototype.copy_cell = function () {
1088 Notebook.prototype.copy_cell = function () {
1089 var cell = this.get_selected_cell();
1089 var cell = this.get_selected_cell();
1090 this.clipboard = cell.toJSON();
1090 this.clipboard = cell.toJSON();
1091 this.enable_paste();
1091 this.enable_paste();
1092 };
1092 };
1093
1093
1094 /**
1094 /**
1095 * Replace the selected cell with a cell in the clipboard.
1095 * Replace the selected cell with a cell in the clipboard.
1096 *
1096 *
1097 * @method paste_cell_replace
1097 * @method paste_cell_replace
1098 */
1098 */
1099 Notebook.prototype.paste_cell_replace = function () {
1099 Notebook.prototype.paste_cell_replace = function () {
1100 if (this.clipboard !== null && this.paste_enabled) {
1100 if (this.clipboard !== null && this.paste_enabled) {
1101 var cell_data = this.clipboard;
1101 var cell_data = this.clipboard;
1102 var new_cell = this.insert_cell_above(cell_data.cell_type);
1102 var new_cell = this.insert_cell_above(cell_data.cell_type);
1103 new_cell.fromJSON(cell_data);
1103 new_cell.fromJSON(cell_data);
1104 var old_cell = this.get_next_cell(new_cell);
1104 var old_cell = this.get_next_cell(new_cell);
1105 this.delete_cell(this.find_cell_index(old_cell));
1105 this.delete_cell(this.find_cell_index(old_cell));
1106 this.select(this.find_cell_index(new_cell));
1106 this.select(this.find_cell_index(new_cell));
1107 };
1107 };
1108 };
1108 };
1109
1109
1110 /**
1110 /**
1111 * Paste a cell from the clipboard above the selected cell.
1111 * Paste a cell from the clipboard above the selected cell.
1112 *
1112 *
1113 * @method paste_cell_above
1113 * @method paste_cell_above
1114 */
1114 */
1115 Notebook.prototype.paste_cell_above = function () {
1115 Notebook.prototype.paste_cell_above = function () {
1116 if (this.clipboard !== null && this.paste_enabled) {
1116 if (this.clipboard !== null && this.paste_enabled) {
1117 var cell_data = this.clipboard;
1117 var cell_data = this.clipboard;
1118 var new_cell = this.insert_cell_above(cell_data.cell_type);
1118 var new_cell = this.insert_cell_above(cell_data.cell_type);
1119 new_cell.fromJSON(cell_data);
1119 new_cell.fromJSON(cell_data);
1120 };
1120 };
1121 };
1121 };
1122
1122
1123 /**
1123 /**
1124 * Paste a cell from the clipboard below the selected cell.
1124 * Paste a cell from the clipboard below the selected cell.
1125 *
1125 *
1126 * @method paste_cell_below
1126 * @method paste_cell_below
1127 */
1127 */
1128 Notebook.prototype.paste_cell_below = function () {
1128 Notebook.prototype.paste_cell_below = function () {
1129 if (this.clipboard !== null && this.paste_enabled) {
1129 if (this.clipboard !== null && this.paste_enabled) {
1130 var cell_data = this.clipboard;
1130 var cell_data = this.clipboard;
1131 var new_cell = this.insert_cell_below(cell_data.cell_type);
1131 var new_cell = this.insert_cell_below(cell_data.cell_type);
1132 new_cell.fromJSON(cell_data);
1132 new_cell.fromJSON(cell_data);
1133 };
1133 };
1134 };
1134 };
1135
1135
1136 // Cell undelete
1136 // Cell undelete
1137
1137
1138 /**
1138 /**
1139 * Restore the most recently deleted cell.
1139 * Restore the most recently deleted cell.
1140 *
1140 *
1141 * @method undelete
1141 * @method undelete
1142 */
1142 */
1143 Notebook.prototype.undelete = function() {
1143 Notebook.prototype.undelete = function() {
1144 if (this.undelete_backup !== null && this.undelete_index !== null) {
1144 if (this.undelete_backup !== null && this.undelete_index !== null) {
1145 var current_index = this.get_selected_index();
1145 var current_index = this.get_selected_index();
1146 if (this.undelete_index < current_index) {
1146 if (this.undelete_index < current_index) {
1147 current_index = current_index + 1;
1147 current_index = current_index + 1;
1148 }
1148 }
1149 if (this.undelete_index >= this.ncells()) {
1149 if (this.undelete_index >= this.ncells()) {
1150 this.select(this.ncells() - 1);
1150 this.select(this.ncells() - 1);
1151 }
1151 }
1152 else {
1152 else {
1153 this.select(this.undelete_index);
1153 this.select(this.undelete_index);
1154 }
1154 }
1155 var cell_data = this.undelete_backup;
1155 var cell_data = this.undelete_backup;
1156 var new_cell = null;
1156 var new_cell = null;
1157 if (this.undelete_below) {
1157 if (this.undelete_below) {
1158 new_cell = this.insert_cell_below(cell_data.cell_type);
1158 new_cell = this.insert_cell_below(cell_data.cell_type);
1159 } else {
1159 } else {
1160 new_cell = this.insert_cell_above(cell_data.cell_type);
1160 new_cell = this.insert_cell_above(cell_data.cell_type);
1161 }
1161 }
1162 new_cell.fromJSON(cell_data);
1162 new_cell.fromJSON(cell_data);
1163 this.select(current_index);
1163 this.select(current_index);
1164 this.undelete_backup = null;
1164 this.undelete_backup = null;
1165 this.undelete_index = null;
1165 this.undelete_index = null;
1166 }
1166 }
1167 $('#undelete_cell').addClass('ui-state-disabled');
1167 $('#undelete_cell').addClass('ui-state-disabled');
1168 }
1168 }
1169
1169
1170 // Split/merge
1170 // Split/merge
1171
1171
1172 /**
1172 /**
1173 * Split the selected cell into two, at the cursor.
1173 * Split the selected cell into two, at the cursor.
1174 *
1174 *
1175 * @method split_cell
1175 * @method split_cell
1176 */
1176 */
1177 Notebook.prototype.split_cell = function () {
1177 Notebook.prototype.split_cell = function () {
1178 // Todo: implement spliting for other cell types.
1178 // Todo: implement spliting for other cell types.
1179 var cell = this.get_selected_cell();
1179 var cell = this.get_selected_cell();
1180 if (cell.is_splittable()) {
1180 if (cell.is_splittable()) {
1181 var texta = cell.get_pre_cursor();
1181 var texta = cell.get_pre_cursor();
1182 var textb = cell.get_post_cursor();
1182 var textb = cell.get_post_cursor();
1183 if (cell instanceof IPython.CodeCell) {
1183 if (cell instanceof IPython.CodeCell) {
1184 cell.set_text(texta);
1184 cell.set_text(texta);
1185 var new_cell = this.insert_cell_below('code');
1185 var new_cell = this.insert_cell_below('code');
1186 new_cell.set_text(textb);
1186 new_cell.set_text(textb);
1187 } else if (cell instanceof IPython.MarkdownCell) {
1187 } else if (cell instanceof IPython.MarkdownCell) {
1188 cell.set_text(texta);
1188 cell.set_text(texta);
1189 cell.render();
1189 cell.render();
1190 var new_cell = this.insert_cell_below('markdown');
1190 var new_cell = this.insert_cell_below('markdown');
1191 new_cell.edit(); // editor must be visible to call set_text
1191 new_cell.edit(); // editor must be visible to call set_text
1192 new_cell.set_text(textb);
1192 new_cell.set_text(textb);
1193 new_cell.render();
1193 new_cell.render();
1194 }
1194 }
1195 };
1195 };
1196 };
1196 };
1197
1197
1198 /**
1198 /**
1199 * Combine the selected cell into the cell above it.
1199 * Combine the selected cell into the cell above it.
1200 *
1200 *
1201 * @method merge_cell_above
1201 * @method merge_cell_above
1202 */
1202 */
1203 Notebook.prototype.merge_cell_above = function () {
1203 Notebook.prototype.merge_cell_above = function () {
1204 var index = this.get_selected_index();
1204 var index = this.get_selected_index();
1205 var cell = this.get_cell(index);
1205 var cell = this.get_cell(index);
1206 if (!cell.is_mergeable()) {
1206 if (!cell.is_mergeable()) {
1207 return;
1207 return;
1208 }
1208 }
1209 if (index > 0) {
1209 if (index > 0) {
1210 var upper_cell = this.get_cell(index-1);
1210 var upper_cell = this.get_cell(index-1);
1211 if (!upper_cell.is_mergeable()) {
1211 if (!upper_cell.is_mergeable()) {
1212 return;
1212 return;
1213 }
1213 }
1214 var upper_text = upper_cell.get_text();
1214 var upper_text = upper_cell.get_text();
1215 var text = cell.get_text();
1215 var text = cell.get_text();
1216 if (cell instanceof IPython.CodeCell) {
1216 if (cell instanceof IPython.CodeCell) {
1217 cell.set_text(upper_text+'\n'+text);
1217 cell.set_text(upper_text+'\n'+text);
1218 } else if (cell instanceof IPython.MarkdownCell) {
1218 } else if (cell instanceof IPython.MarkdownCell) {
1219 cell.edit();
1219 cell.edit();
1220 cell.set_text(upper_text+'\n'+text);
1220 cell.set_text(upper_text+'\n'+text);
1221 cell.render();
1221 cell.render();
1222 };
1222 };
1223 this.delete_cell(index-1);
1223 this.delete_cell(index-1);
1224 this.select(this.find_cell_index(cell));
1224 this.select(this.find_cell_index(cell));
1225 };
1225 };
1226 };
1226 };
1227
1227
1228 /**
1228 /**
1229 * Combine the selected cell into the cell below it.
1229 * Combine the selected cell into the cell below it.
1230 *
1230 *
1231 * @method merge_cell_below
1231 * @method merge_cell_below
1232 */
1232 */
1233 Notebook.prototype.merge_cell_below = function () {
1233 Notebook.prototype.merge_cell_below = function () {
1234 var index = this.get_selected_index();
1234 var index = this.get_selected_index();
1235 var cell = this.get_cell(index);
1235 var cell = this.get_cell(index);
1236 if (!cell.is_mergeable()) {
1236 if (!cell.is_mergeable()) {
1237 return;
1237 return;
1238 }
1238 }
1239 if (index < this.ncells()-1) {
1239 if (index < this.ncells()-1) {
1240 var lower_cell = this.get_cell(index+1);
1240 var lower_cell = this.get_cell(index+1);
1241 if (!lower_cell.is_mergeable()) {
1241 if (!lower_cell.is_mergeable()) {
1242 return;
1242 return;
1243 }
1243 }
1244 var lower_text = lower_cell.get_text();
1244 var lower_text = lower_cell.get_text();
1245 var text = cell.get_text();
1245 var text = cell.get_text();
1246 if (cell instanceof IPython.CodeCell) {
1246 if (cell instanceof IPython.CodeCell) {
1247 cell.set_text(text+'\n'+lower_text);
1247 cell.set_text(text+'\n'+lower_text);
1248 } else if (cell instanceof IPython.MarkdownCell) {
1248 } else if (cell instanceof IPython.MarkdownCell) {
1249 cell.edit();
1249 cell.edit();
1250 cell.set_text(text+'\n'+lower_text);
1250 cell.set_text(text+'\n'+lower_text);
1251 cell.render();
1251 cell.render();
1252 };
1252 };
1253 this.delete_cell(index+1);
1253 this.delete_cell(index+1);
1254 this.select(this.find_cell_index(cell));
1254 this.select(this.find_cell_index(cell));
1255 };
1255 };
1256 };
1256 };
1257
1257
1258
1258
1259 // Cell collapsing and output clearing
1259 // Cell collapsing and output clearing
1260
1260
1261 /**
1261 /**
1262 * Hide a cell's output.
1262 * Hide a cell's output.
1263 *
1263 *
1264 * @method collapse
1264 * @method collapse
1265 * @param {Number} index A cell's numeric index
1265 * @param {Number} index A cell's numeric index
1266 */
1266 */
1267 Notebook.prototype.collapse = function (index) {
1267 Notebook.prototype.collapse = function (index) {
1268 var i = this.index_or_selected(index);
1268 var i = this.index_or_selected(index);
1269 this.get_cell(i).collapse();
1269 this.get_cell(i).collapse();
1270 this.set_dirty(true);
1270 this.set_dirty(true);
1271 };
1271 };
1272
1272
1273 /**
1273 /**
1274 * Show a cell's output.
1274 * Show a cell's output.
1275 *
1275 *
1276 * @method expand
1276 * @method expand
1277 * @param {Number} index A cell's numeric index
1277 * @param {Number} index A cell's numeric index
1278 */
1278 */
1279 Notebook.prototype.expand = function (index) {
1279 Notebook.prototype.expand = function (index) {
1280 var i = this.index_or_selected(index);
1280 var i = this.index_or_selected(index);
1281 this.get_cell(i).expand();
1281 this.get_cell(i).expand();
1282 this.set_dirty(true);
1282 this.set_dirty(true);
1283 };
1283 };
1284
1284
1285 /** Toggle whether a cell's output is collapsed or expanded.
1285 /** Toggle whether a cell's output is collapsed or expanded.
1286 *
1286 *
1287 * @method toggle_output
1287 * @method toggle_output
1288 * @param {Number} index A cell's numeric index
1288 * @param {Number} index A cell's numeric index
1289 */
1289 */
1290 Notebook.prototype.toggle_output = function (index) {
1290 Notebook.prototype.toggle_output = function (index) {
1291 var i = this.index_or_selected(index);
1291 var i = this.index_or_selected(index);
1292 this.get_cell(i).toggle_output();
1292 this.get_cell(i).toggle_output();
1293 this.set_dirty(true);
1293 this.set_dirty(true);
1294 };
1294 };
1295
1295
1296 /**
1296 /**
1297 * Toggle a scrollbar for long cell outputs.
1297 * Toggle a scrollbar for long cell outputs.
1298 *
1298 *
1299 * @method toggle_output_scroll
1299 * @method toggle_output_scroll
1300 * @param {Number} index A cell's numeric index
1300 * @param {Number} index A cell's numeric index
1301 */
1301 */
1302 Notebook.prototype.toggle_output_scroll = function (index) {
1302 Notebook.prototype.toggle_output_scroll = function (index) {
1303 var i = this.index_or_selected(index);
1303 var i = this.index_or_selected(index);
1304 this.get_cell(i).toggle_output_scroll();
1304 this.get_cell(i).toggle_output_scroll();
1305 };
1305 };
1306
1306
1307 /**
1307 /**
1308 * Hide each code cell's output area.
1308 * Hide each code cell's output area.
1309 *
1309 *
1310 * @method collapse_all_output
1310 * @method collapse_all_output
1311 */
1311 */
1312 Notebook.prototype.collapse_all_output = function () {
1312 Notebook.prototype.collapse_all_output = function () {
1313 var ncells = this.ncells();
1313 var ncells = this.ncells();
1314 var cells = this.get_cells();
1314 var cells = this.get_cells();
1315 for (var i=0; i<ncells; i++) {
1315 for (var i=0; i<ncells; i++) {
1316 if (cells[i] instanceof IPython.CodeCell) {
1316 if (cells[i] instanceof IPython.CodeCell) {
1317 cells[i].output_area.collapse();
1317 cells[i].output_area.collapse();
1318 }
1318 }
1319 };
1319 };
1320 // this should not be set if the `collapse` key is removed from nbformat
1320 // this should not be set if the `collapse` key is removed from nbformat
1321 this.set_dirty(true);
1321 this.set_dirty(true);
1322 };
1322 };
1323
1323
1324 /**
1324 /**
1325 * Expand each code cell's output area, and add a scrollbar for long output.
1325 * Expand each code cell's output area, and add a scrollbar for long output.
1326 *
1326 *
1327 * @method scroll_all_output
1327 * @method scroll_all_output
1328 */
1328 */
1329 Notebook.prototype.scroll_all_output = function () {
1329 Notebook.prototype.scroll_all_output = function () {
1330 var ncells = this.ncells();
1330 var ncells = this.ncells();
1331 var cells = this.get_cells();
1331 var cells = this.get_cells();
1332 for (var i=0; i<ncells; i++) {
1332 for (var i=0; i<ncells; i++) {
1333 if (cells[i] instanceof IPython.CodeCell) {
1333 if (cells[i] instanceof IPython.CodeCell) {
1334 cells[i].output_area.expand();
1334 cells[i].output_area.expand();
1335 cells[i].output_area.scroll_if_long();
1335 cells[i].output_area.scroll_if_long();
1336 }
1336 }
1337 };
1337 };
1338 // this should not be set if the `collapse` key is removed from nbformat
1338 // this should not be set if the `collapse` key is removed from nbformat
1339 this.set_dirty(true);
1339 this.set_dirty(true);
1340 };
1340 };
1341
1341
1342 /**
1342 /**
1343 * Expand each code cell's output area, and remove scrollbars.
1343 * Expand each code cell's output area, and remove scrollbars.
1344 *
1344 *
1345 * @method expand_all_output
1345 * @method expand_all_output
1346 */
1346 */
1347 Notebook.prototype.expand_all_output = function () {
1347 Notebook.prototype.expand_all_output = function () {
1348 var ncells = this.ncells();
1348 var ncells = this.ncells();
1349 var cells = this.get_cells();
1349 var cells = this.get_cells();
1350 for (var i=0; i<ncells; i++) {
1350 for (var i=0; i<ncells; i++) {
1351 if (cells[i] instanceof IPython.CodeCell) {
1351 if (cells[i] instanceof IPython.CodeCell) {
1352 cells[i].output_area.expand();
1352 cells[i].output_area.expand();
1353 cells[i].output_area.unscroll_area();
1353 cells[i].output_area.unscroll_area();
1354 }
1354 }
1355 };
1355 };
1356 // this should not be set if the `collapse` key is removed from nbformat
1356 // this should not be set if the `collapse` key is removed from nbformat
1357 this.set_dirty(true);
1357 this.set_dirty(true);
1358 };
1358 };
1359
1359
1360 /**
1360 /**
1361 * Clear each code cell's output area.
1361 * Clear each code cell's output area.
1362 *
1362 *
1363 * @method clear_all_output
1363 * @method clear_all_output
1364 */
1364 */
1365 Notebook.prototype.clear_all_output = function () {
1365 Notebook.prototype.clear_all_output = function () {
1366 var ncells = this.ncells();
1366 var ncells = this.ncells();
1367 var cells = this.get_cells();
1367 var cells = this.get_cells();
1368 for (var i=0; i<ncells; i++) {
1368 for (var i=0; i<ncells; i++) {
1369 if (cells[i] instanceof IPython.CodeCell) {
1369 if (cells[i] instanceof IPython.CodeCell) {
1370 cells[i].clear_output();
1370 cells[i].clear_output();
1371 // Make all In[] prompts blank, as well
1371 // Make all In[] prompts blank, as well
1372 // TODO: make this configurable (via checkbox?)
1372 // TODO: make this configurable (via checkbox?)
1373 cells[i].set_input_prompt();
1373 cells[i].set_input_prompt();
1374 }
1374 }
1375 };
1375 };
1376 this.set_dirty(true);
1376 this.set_dirty(true);
1377 };
1377 };
1378
1378
1379
1379
1380 // Other cell functions: line numbers, ...
1380 // Other cell functions: line numbers, ...
1381
1381
1382 /**
1382 /**
1383 * Toggle line numbers in the selected cell's input area.
1383 * Toggle line numbers in the selected cell's input area.
1384 *
1384 *
1385 * @method cell_toggle_line_numbers
1385 * @method cell_toggle_line_numbers
1386 */
1386 */
1387 Notebook.prototype.cell_toggle_line_numbers = function() {
1387 Notebook.prototype.cell_toggle_line_numbers = function() {
1388 this.get_selected_cell().toggle_line_numbers();
1388 this.get_selected_cell().toggle_line_numbers();
1389 };
1389 };
1390
1390
1391 // Session related things
1391 // Session related things
1392
1392
1393 /**
1393 /**
1394 * Start a new session and set it on each code cell.
1394 * Start a new session and set it on each code cell.
1395 *
1395 *
1396 * @method start_session
1396 * @method start_session
1397 */
1397 */
1398 Notebook.prototype.start_session = function () {
1398 Notebook.prototype.start_session = function () {
1399 var notebook_info = this.notebookPath() + this.notebook_name;
1399 var notebook_info = this.notebookPath() + this.notebook_name;
1400 this.session = new IPython.Session(notebook_info, this);
1400 this.session = new IPython.Session(notebook_info, this);
1401 this.session.start();
1401 this.session.start();
1402 this.link_cells_to_session();
1402 this.link_cells_to_session();
1403 };
1403 };
1404
1404
1405
1405
1406 /**
1406 /**
1407 * Once a session is started, link the code cells to the session
1407 * Once a session is started, link the code cells to the session
1408 *
1408 *
1409 */
1409 */
1410 Notebook.prototype.link_cells_to_session= function(){
1410 Notebook.prototype.link_cells_to_session= function(){
1411 var ncells = this.ncells();
1411 var ncells = this.ncells();
1412 for (var i=0; i<ncells; i++) {
1412 for (var i=0; i<ncells; i++) {
1413 var cell = this.get_cell(i);
1413 var cell = this.get_cell(i);
1414 if (cell instanceof IPython.CodeCell) {
1414 if (cell instanceof IPython.CodeCell) {
1415 cell.set_session(this.session);
1415 cell.set_session(this.session);
1416 };
1416 };
1417 };
1417 };
1418 };
1418 };
1419
1419
1420 /**
1420 /**
1421 * Prompt the user to restart the IPython kernel.
1421 * Prompt the user to restart the IPython kernel.
1422 *
1422 *
1423 * @method restart_kernel
1423 * @method restart_kernel
1424 */
1424 */
1425 Notebook.prototype.restart_kernel = function () {
1425 Notebook.prototype.restart_kernel = function () {
1426 var that = this;
1426 var that = this;
1427 IPython.dialog.modal({
1427 IPython.dialog.modal({
1428 title : "Restart kernel or continue running?",
1428 title : "Restart kernel or continue running?",
1429 body : $("<p/>").html(
1429 body : $("<p/>").html(
1430 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1430 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1431 ),
1431 ),
1432 buttons : {
1432 buttons : {
1433 "Continue running" : {},
1433 "Continue running" : {},
1434 "Restart" : {
1434 "Restart" : {
1435 "class" : "btn-danger",
1435 "class" : "btn-danger",
1436 "click" : function() {
1436 "click" : function() {
1437 that.session.restart_kernel();
1437 that.session.restart_kernel();
1438 }
1438 }
1439 }
1439 }
1440 }
1440 }
1441 });
1441 });
1442 };
1442 };
1443
1443
1444 /**
1444 /**
1445 * Run the selected cell.
1445 * Run the selected cell.
1446 *
1446 *
1447 * Execute or render cell outputs.
1447 * Execute or render cell outputs.
1448 *
1448 *
1449 * @method execute_selected_cell
1449 * @method execute_selected_cell
1450 * @param {Object} options Customize post-execution behavior
1450 * @param {Object} options Customize post-execution behavior
1451 */
1451 */
1452 Notebook.prototype.execute_selected_cell = function (options) {
1452 Notebook.prototype.execute_selected_cell = function (options) {
1453 // add_new: should a new cell be added if we are at the end of the nb
1453 // add_new: should a new cell be added if we are at the end of the nb
1454 // terminal: execute in terminal mode, which stays in the current cell
1454 // terminal: execute in terminal mode, which stays in the current cell
1455 var default_options = {terminal: false, add_new: true};
1455 var default_options = {terminal: false, add_new: true};
1456 $.extend(default_options, options);
1456 $.extend(default_options, options);
1457 var that = this;
1457 var that = this;
1458 var cell = that.get_selected_cell();
1458 var cell = that.get_selected_cell();
1459 var cell_index = that.find_cell_index(cell);
1459 var cell_index = that.find_cell_index(cell);
1460 if (cell instanceof IPython.CodeCell) {
1460 if (cell instanceof IPython.CodeCell) {
1461 cell.execute();
1461 cell.execute();
1462 }
1462 }
1463 if (default_options.terminal) {
1463 if (default_options.terminal) {
1464 cell.select_all();
1464 cell.select_all();
1465 } else {
1465 } else {
1466 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1466 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1467 that.insert_cell_below('code');
1467 that.insert_cell_below('code');
1468 // If we are adding a new cell at the end, scroll down to show it.
1468 // If we are adding a new cell at the end, scroll down to show it.
1469 that.scroll_to_bottom();
1469 that.scroll_to_bottom();
1470 } else {
1470 } else {
1471 that.select(cell_index+1);
1471 that.select(cell_index+1);
1472 };
1472 };
1473 };
1473 };
1474 this.set_dirty(true);
1474 this.set_dirty(true);
1475 };
1475 };
1476
1476
1477 /**
1477 /**
1478 * Execute all cells below the selected cell.
1478 * Execute all cells below the selected cell.
1479 *
1479 *
1480 * @method execute_cells_below
1480 * @method execute_cells_below
1481 */
1481 */
1482 Notebook.prototype.execute_cells_below = function () {
1482 Notebook.prototype.execute_cells_below = function () {
1483 this.execute_cell_range(this.get_selected_index(), this.ncells());
1483 this.execute_cell_range(this.get_selected_index(), this.ncells());
1484 this.scroll_to_bottom();
1484 this.scroll_to_bottom();
1485 };
1485 };
1486
1486
1487 /**
1487 /**
1488 * Execute all cells above the selected cell.
1488 * Execute all cells above the selected cell.
1489 *
1489 *
1490 * @method execute_cells_above
1490 * @method execute_cells_above
1491 */
1491 */
1492 Notebook.prototype.execute_cells_above = function () {
1492 Notebook.prototype.execute_cells_above = function () {
1493 this.execute_cell_range(0, this.get_selected_index());
1493 this.execute_cell_range(0, this.get_selected_index());
1494 };
1494 };
1495
1495
1496 /**
1496 /**
1497 * Execute all cells.
1497 * Execute all cells.
1498 *
1498 *
1499 * @method execute_all_cells
1499 * @method execute_all_cells
1500 */
1500 */
1501 Notebook.prototype.execute_all_cells = function () {
1501 Notebook.prototype.execute_all_cells = function () {
1502 this.execute_cell_range(0, this.ncells());
1502 this.execute_cell_range(0, this.ncells());
1503 this.scroll_to_bottom();
1503 this.scroll_to_bottom();
1504 };
1504 };
1505
1505
1506 /**
1506 /**
1507 * Execute a contiguous range of cells.
1507 * Execute a contiguous range of cells.
1508 *
1508 *
1509 * @method execute_cell_range
1509 * @method execute_cell_range
1510 * @param {Number} start Index of the first cell to execute (inclusive)
1510 * @param {Number} start Index of the first cell to execute (inclusive)
1511 * @param {Number} end Index of the last cell to execute (exclusive)
1511 * @param {Number} end Index of the last cell to execute (exclusive)
1512 */
1512 */
1513 Notebook.prototype.execute_cell_range = function (start, end) {
1513 Notebook.prototype.execute_cell_range = function (start, end) {
1514 for (var i=start; i<end; i++) {
1514 for (var i=start; i<end; i++) {
1515 this.select(i);
1515 this.select(i);
1516 this.execute_selected_cell({add_new:false});
1516 this.execute_selected_cell({add_new:false});
1517 };
1517 };
1518 };
1518 };
1519
1519
1520 // Persistance and loading
1520 // Persistance and loading
1521
1521
1522 /**
1522 /**
1523 * Getter method for this notebook's ID.
1524 *
1525 * @method get_notebook_id
1526 * @return {String} This notebook's ID
1527 */
1528 Notebook.prototype.get_notebook_id = function () {
1529 return this.notebook_id;
1530 };
1531
1532 /**
1533 * Getter method for this notebook's name.
1523 * Getter method for this notebook's name.
1534 *
1524 *
1535 * @method get_notebook_name
1525 * @method get_notebook_name
1536 * @return {String} This notebook's name
1526 * @return {String} This notebook's name
1537 */
1527 */
1538 Notebook.prototype.get_notebook_name = function () {
1528 Notebook.prototype.get_notebook_name = function () {
1539 nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1529 nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1540 return nbname;
1530 return nbname;
1541 };
1531 };
1542
1532
1543 /**
1533 /**
1544 * Setter method for this notebook's name.
1534 * Setter method for this notebook's name.
1545 *
1535 *
1546 * @method set_notebook_name
1536 * @method set_notebook_name
1547 * @param {String} name A new name for this notebook
1537 * @param {String} name A new name for this notebook
1548 */
1538 */
1549 Notebook.prototype.set_notebook_name = function (name) {
1539 Notebook.prototype.set_notebook_name = function (name) {
1550 this.notebook_name = name;
1540 this.notebook_name = name;
1551 };
1541 };
1552
1542
1553 /**
1543 /**
1554 * Check that a notebook's name is valid.
1544 * Check that a notebook's name is valid.
1555 *
1545 *
1556 * @method test_notebook_name
1546 * @method test_notebook_name
1557 * @param {String} nbname A name for this notebook
1547 * @param {String} nbname A name for this notebook
1558 * @return {Boolean} True if the name is valid, false if invalid
1548 * @return {Boolean} True if the name is valid, false if invalid
1559 */
1549 */
1560 Notebook.prototype.test_notebook_name = function (nbname) {
1550 Notebook.prototype.test_notebook_name = function (nbname) {
1561 nbname = nbname || '';
1551 nbname = nbname || '';
1562 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1552 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1563 return true;
1553 return true;
1564 } else {
1554 } else {
1565 return false;
1555 return false;
1566 };
1556 };
1567 };
1557 };
1568
1558
1569 /**
1559 /**
1570 * Load a notebook from JSON (.ipynb).
1560 * Load a notebook from JSON (.ipynb).
1571 *
1561 *
1572 * This currently handles one worksheet: others are deleted.
1562 * This currently handles one worksheet: others are deleted.
1573 *
1563 *
1574 * @method fromJSON
1564 * @method fromJSON
1575 * @param {Object} data JSON representation of a notebook
1565 * @param {Object} data JSON representation of a notebook
1576 */
1566 */
1577 Notebook.prototype.fromJSON = function (data) {
1567 Notebook.prototype.fromJSON = function (data) {
1578 data = data.content;
1568 data = data.content;
1579 var ncells = this.ncells();
1569 var ncells = this.ncells();
1580 var i;
1570 var i;
1581 for (i=0; i<ncells; i++) {
1571 for (i=0; i<ncells; i++) {
1582 // Always delete cell 0 as they get renumbered as they are deleted.
1572 // Always delete cell 0 as they get renumbered as they are deleted.
1583 this.delete_cell(0);
1573 this.delete_cell(0);
1584 };
1574 };
1585 // Save the metadata and name.
1575 // Save the metadata and name.
1586 this.metadata = data.metadata;
1576 this.metadata = data.metadata;
1587 this.notebook_name = data.metadata.name +'.ipynb';
1577 this.notebook_name = data.metadata.name +'.ipynb';
1588 // Only handle 1 worksheet for now.
1578 // Only handle 1 worksheet for now.
1589 var worksheet = data.worksheets[0];
1579 var worksheet = data.worksheets[0];
1590 if (worksheet !== undefined) {
1580 if (worksheet !== undefined) {
1591 if (worksheet.metadata) {
1581 if (worksheet.metadata) {
1592 this.worksheet_metadata = worksheet.metadata;
1582 this.worksheet_metadata = worksheet.metadata;
1593 }
1583 }
1594 var new_cells = worksheet.cells;
1584 var new_cells = worksheet.cells;
1595 ncells = new_cells.length;
1585 ncells = new_cells.length;
1596 var cell_data = null;
1586 var cell_data = null;
1597 var new_cell = null;
1587 var new_cell = null;
1598 for (i=0; i<ncells; i++) {
1588 for (i=0; i<ncells; i++) {
1599 cell_data = new_cells[i];
1589 cell_data = new_cells[i];
1600 // VERSIONHACK: plaintext -> raw
1590 // VERSIONHACK: plaintext -> raw
1601 // handle never-released plaintext name for raw cells
1591 // handle never-released plaintext name for raw cells
1602 if (cell_data.cell_type === 'plaintext'){
1592 if (cell_data.cell_type === 'plaintext'){
1603 cell_data.cell_type = 'raw';
1593 cell_data.cell_type = 'raw';
1604 }
1594 }
1605
1595
1606 new_cell = this.insert_cell_below(cell_data.cell_type);
1596 new_cell = this.insert_cell_below(cell_data.cell_type);
1607 new_cell.fromJSON(cell_data);
1597 new_cell.fromJSON(cell_data);
1608 };
1598 };
1609 };
1599 };
1610 if (data.worksheets.length > 1) {
1600 if (data.worksheets.length > 1) {
1611 IPython.dialog.modal({
1601 IPython.dialog.modal({
1612 title : "Multiple worksheets",
1602 title : "Multiple worksheets",
1613 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1603 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1614 "but this version of IPython can only handle the first. " +
1604 "but this version of IPython can only handle the first. " +
1615 "If you save this notebook, worksheets after the first will be lost.",
1605 "If you save this notebook, worksheets after the first will be lost.",
1616 buttons : {
1606 buttons : {
1617 OK : {
1607 OK : {
1618 class : "btn-danger"
1608 class : "btn-danger"
1619 }
1609 }
1620 }
1610 }
1621 });
1611 });
1622 }
1612 }
1623 };
1613 };
1624
1614
1625 /**
1615 /**
1626 * Dump this notebook into a JSON-friendly object.
1616 * Dump this notebook into a JSON-friendly object.
1627 *
1617 *
1628 * @method toJSON
1618 * @method toJSON
1629 * @return {Object} A JSON-friendly representation of this notebook.
1619 * @return {Object} A JSON-friendly representation of this notebook.
1630 */
1620 */
1631 Notebook.prototype.toJSON = function () {
1621 Notebook.prototype.toJSON = function () {
1632 var cells = this.get_cells();
1622 var cells = this.get_cells();
1633 var ncells = cells.length;
1623 var ncells = cells.length;
1634 var cell_array = new Array(ncells);
1624 var cell_array = new Array(ncells);
1635 for (var i=0; i<ncells; i++) {
1625 for (var i=0; i<ncells; i++) {
1636 cell_array[i] = cells[i].toJSON();
1626 cell_array[i] = cells[i].toJSON();
1637 };
1627 };
1638 var data = {
1628 var data = {
1639 // Only handle 1 worksheet for now.
1629 // Only handle 1 worksheet for now.
1640 worksheets : [{
1630 worksheets : [{
1641 cells: cell_array,
1631 cells: cell_array,
1642 metadata: this.worksheet_metadata
1632 metadata: this.worksheet_metadata
1643 }],
1633 }],
1644 metadata : this.metadata
1634 metadata : this.metadata
1645 };
1635 };
1646 return data;
1636 return data;
1647 };
1637 };
1648
1638
1649 /**
1639 /**
1650 * Start an autosave timer, for periodically saving the notebook.
1640 * Start an autosave timer, for periodically saving the notebook.
1651 *
1641 *
1652 * @method set_autosave_interval
1642 * @method set_autosave_interval
1653 * @param {Integer} interval the autosave interval in milliseconds
1643 * @param {Integer} interval the autosave interval in milliseconds
1654 */
1644 */
1655 Notebook.prototype.set_autosave_interval = function (interval) {
1645 Notebook.prototype.set_autosave_interval = function (interval) {
1656 var that = this;
1646 var that = this;
1657 // clear previous interval, so we don't get simultaneous timers
1647 // clear previous interval, so we don't get simultaneous timers
1658 if (this.autosave_timer) {
1648 if (this.autosave_timer) {
1659 clearInterval(this.autosave_timer);
1649 clearInterval(this.autosave_timer);
1660 }
1650 }
1661
1651
1662 this.autosave_interval = this.minimum_autosave_interval = interval;
1652 this.autosave_interval = this.minimum_autosave_interval = interval;
1663 if (interval) {
1653 if (interval) {
1664 this.autosave_timer = setInterval(function() {
1654 this.autosave_timer = setInterval(function() {
1665 if (that.dirty) {
1655 if (that.dirty) {
1666 that.save_notebook();
1656 that.save_notebook();
1667 }
1657 }
1668 }, interval);
1658 }, interval);
1669 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1659 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1670 } else {
1660 } else {
1671 this.autosave_timer = null;
1661 this.autosave_timer = null;
1672 $([IPython.events]).trigger("autosave_disabled.Notebook");
1662 $([IPython.events]).trigger("autosave_disabled.Notebook");
1673 };
1663 };
1674 };
1664 };
1675
1665
1676 /**
1666 /**
1677 * Save this notebook on the server.
1667 * Save this notebook on the server.
1678 *
1668 *
1679 * @method save_notebook
1669 * @method save_notebook
1680 */
1670 */
1681 Notebook.prototype.save_notebook = function () {
1671 Notebook.prototype.save_notebook = function () {
1682 // We may want to move the name/id/nbformat logic inside toJSON?
1672 // We may want to move the name/id/nbformat logic inside toJSON?
1683 var data = this.toJSON();
1673 var data = this.toJSON();
1684 data.metadata.name = this.notebook_name;
1674 data.metadata.name = this.notebook_name;
1685 data.nbformat = this.nbformat;
1675 data.nbformat = this.nbformat;
1686 data.nbformat_minor = this.nbformat_minor;
1676 data.nbformat_minor = this.nbformat_minor;
1687
1677
1688 // time the ajax call for autosave tuning purposes.
1678 // time the ajax call for autosave tuning purposes.
1689 var start = new Date().getTime();
1679 var start = new Date().getTime();
1690 // We do the call with settings so we can set cache to false.
1680 // We do the call with settings so we can set cache to false.
1691 var settings = {
1681 var settings = {
1692 processData : false,
1682 processData : false,
1693 cache : false,
1683 cache : false,
1694 type : "PUT",
1684 type : "PUT",
1695 data : JSON.stringify(data),
1685 data : JSON.stringify(data),
1696 headers : {'Content-Type': 'application/json'},
1686 headers : {'Content-Type': 'application/json'},
1697 success : $.proxy(this.save_notebook_success, this, start),
1687 success : $.proxy(this.save_notebook_success, this, start),
1698 error : $.proxy(this.save_notebook_error, this)
1688 error : $.proxy(this.save_notebook_error, this)
1699 };
1689 };
1700 $([IPython.events]).trigger('notebook_saving.Notebook');
1690 $([IPython.events]).trigger('notebook_saving.Notebook');
1701 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1691 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1702 $.ajax(url, settings);
1692 $.ajax(url, settings);
1703 };
1693 };
1704
1694
1705 /**
1695 /**
1706 * Success callback for saving a notebook.
1696 * Success callback for saving a notebook.
1707 *
1697 *
1708 * @method save_notebook_success
1698 * @method save_notebook_success
1709 * @param {Integer} start the time when the save request started
1699 * @param {Integer} start the time when the save request started
1710 * @param {Object} data JSON representation of a notebook
1700 * @param {Object} data JSON representation of a notebook
1711 * @param {String} status Description of response status
1701 * @param {String} status Description of response status
1712 * @param {jqXHR} xhr jQuery Ajax object
1702 * @param {jqXHR} xhr jQuery Ajax object
1713 */
1703 */
1714 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1704 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1715 this.set_dirty(false);
1705 this.set_dirty(false);
1716 $([IPython.events]).trigger('notebook_saved.Notebook');
1706 $([IPython.events]).trigger('notebook_saved.Notebook');
1717 this._update_autosave_interval(start);
1707 this._update_autosave_interval(start);
1718 if (this._checkpoint_after_save) {
1708 if (this._checkpoint_after_save) {
1719 this.create_checkpoint();
1709 this.create_checkpoint();
1720 this._checkpoint_after_save = false;
1710 this._checkpoint_after_save = false;
1721 };
1711 };
1722 };
1712 };
1723
1713
1724 /**
1714 /**
1725 * update the autosave interval based on how long the last save took
1715 * update the autosave interval based on how long the last save took
1726 *
1716 *
1727 * @method _update_autosave_interval
1717 * @method _update_autosave_interval
1728 * @param {Integer} timestamp when the save request started
1718 * @param {Integer} timestamp when the save request started
1729 */
1719 */
1730 Notebook.prototype._update_autosave_interval = function (start) {
1720 Notebook.prototype._update_autosave_interval = function (start) {
1731 var duration = (new Date().getTime() - start);
1721 var duration = (new Date().getTime() - start);
1732 if (this.autosave_interval) {
1722 if (this.autosave_interval) {
1733 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1723 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1734 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1724 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1735 // round to 10 seconds, otherwise we will be setting a new interval too often
1725 // round to 10 seconds, otherwise we will be setting a new interval too often
1736 interval = 10000 * Math.round(interval / 10000);
1726 interval = 10000 * Math.round(interval / 10000);
1737 // set new interval, if it's changed
1727 // set new interval, if it's changed
1738 if (interval != this.autosave_interval) {
1728 if (interval != this.autosave_interval) {
1739 this.set_autosave_interval(interval);
1729 this.set_autosave_interval(interval);
1740 }
1730 }
1741 }
1731 }
1742 };
1732 };
1743
1733
1744 /**
1734 /**
1745 * Failure callback for saving a notebook.
1735 * Failure callback for saving a notebook.
1746 *
1736 *
1747 * @method save_notebook_error
1737 * @method save_notebook_error
1748 * @param {jqXHR} xhr jQuery Ajax object
1738 * @param {jqXHR} xhr jQuery Ajax object
1749 * @param {String} status Description of response status
1739 * @param {String} status Description of response status
1750 * @param {String} error_msg HTTP error message
1740 * @param {String} error_msg HTTP error message
1751 */
1741 */
1752 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1742 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1753 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1743 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1754 };
1744 };
1755
1745
1756
1746
1757 Notebook.prototype.notebook_rename = function (nbname) {
1747 Notebook.prototype.notebook_rename = function (nbname) {
1758 var that = this;
1748 var that = this;
1759 var new_name = nbname + '.ipynb'
1749 var new_name = nbname + '.ipynb'
1760 var name = {'notebook_name': new_name};
1750 var name = {'name': new_name};
1761 var settings = {
1751 var settings = {
1762 processData : false,
1752 processData : false,
1763 cache : false,
1753 cache : false,
1764 type : "PATCH",
1754 type : "PATCH",
1765 data : JSON.stringify(name),
1755 data : JSON.stringify(name),
1766 dataType: "json",
1756 dataType: "json",
1767 headers : {'Content-Type': 'application/json'},
1757 headers : {'Content-Type': 'application/json'},
1768 success : $.proxy(that.rename_success, this)
1758 success : $.proxy(that.rename_success, this)
1769 };
1759 };
1770 $([IPython.events]).trigger('notebook_rename.Notebook');
1760 $([IPython.events]).trigger('notebook_rename.Notebook');
1771 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1761 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath()+ this.notebook_name;
1772 $.ajax(url, settings);
1762 $.ajax(url, settings);
1773 };
1763 };
1774
1764
1775
1765
1776 Notebook.prototype.rename_success = function (json, status, xhr) {
1766 Notebook.prototype.rename_success = function (json, status, xhr) {
1777 this.notebook_name = json.notebook_name
1767 this.notebook_name = json.name
1778 var notebook_path = this.notebookPath() + this.notebook_name;
1768 var notebook_path = this.notebookPath() + this.notebook_name;
1779 this.session.notebook_rename(notebook_path);
1769 this.session.notebook_rename(notebook_path);
1780 $([IPython.events]).trigger('notebook_renamed.Notebook');
1770 $([IPython.events]).trigger('notebook_renamed.Notebook');
1781 }
1771 }
1782
1772
1783 /**
1773 /**
1784 * Request a notebook's data from the server.
1774 * Request a notebook's data from the server.
1785 *
1775 *
1786 * @method load_notebook
1776 * @method load_notebook
1787 * @param {String} notebook_naem and path A notebook to load
1777 * @param {String} notebook_naem and path A notebook to load
1788 */
1778 */
1789 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1779 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1790 var that = this;
1780 var that = this;
1791 this.notebook_name = notebook_name;
1781 this.notebook_name = notebook_name;
1792 this.notebook_path = notebook_path;
1782 this.notebook_path = notebook_path;
1793 // We do the call with settings so we can set cache to false.
1783 // We do the call with settings so we can set cache to false.
1794 var settings = {
1784 var settings = {
1795 processData : false,
1785 processData : false,
1796 cache : false,
1786 cache : false,
1797 type : "GET",
1787 type : "GET",
1798 dataType : "json",
1788 dataType : "json",
1799 success : $.proxy(this.load_notebook_success,this),
1789 success : $.proxy(this.load_notebook_success,this),
1800 error : $.proxy(this.load_notebook_error,this),
1790 error : $.proxy(this.load_notebook_error,this),
1801 };
1791 };
1802 $([IPython.events]).trigger('notebook_loading.Notebook');
1792 $([IPython.events]).trigger('notebook_loading.Notebook');
1803 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name;
1793 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name;
1804 $.ajax(url, settings);
1794 $.ajax(url, settings);
1805 };
1795 };
1806
1796
1807 /**
1797 /**
1808 * Success callback for loading a notebook from the server.
1798 * Success callback for loading a notebook from the server.
1809 *
1799 *
1810 * Load notebook data from the JSON response.
1800 * Load notebook data from the JSON response.
1811 *
1801 *
1812 * @method load_notebook_success
1802 * @method load_notebook_success
1813 * @param {Object} data JSON representation of a notebook
1803 * @param {Object} data JSON representation of a notebook
1814 * @param {String} status Description of response status
1804 * @param {String} status Description of response status
1815 * @param {jqXHR} xhr jQuery Ajax object
1805 * @param {jqXHR} xhr jQuery Ajax object
1816 */
1806 */
1817 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1807 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1818 this.fromJSON(data);
1808 this.fromJSON(data);
1819 if (this.ncells() === 0) {
1809 if (this.ncells() === 0) {
1820 this.insert_cell_below('code');
1810 this.insert_cell_below('code');
1821 };
1811 };
1822 this.set_dirty(false);
1812 this.set_dirty(false);
1823 this.select(0);
1813 this.select(0);
1824 this.scroll_to_top();
1814 this.scroll_to_top();
1825 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1815 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1826 var msg = "This notebook has been converted from an older " +
1816 var msg = "This notebook has been converted from an older " +
1827 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1817 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1828 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1818 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1829 "newer notebook format will be used and older versions of IPython " +
1819 "newer notebook format will be used and older versions of IPython " +
1830 "may not be able to read it. To keep the older version, close the " +
1820 "may not be able to read it. To keep the older version, close the " +
1831 "notebook without saving it.";
1821 "notebook without saving it.";
1832 IPython.dialog.modal({
1822 IPython.dialog.modal({
1833 title : "Notebook converted",
1823 title : "Notebook converted",
1834 body : msg,
1824 body : msg,
1835 buttons : {
1825 buttons : {
1836 OK : {
1826 OK : {
1837 class : "btn-primary"
1827 class : "btn-primary"
1838 }
1828 }
1839 }
1829 }
1840 });
1830 });
1841 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1831 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1842 var that = this;
1832 var that = this;
1843 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1833 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1844 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1834 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1845 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1835 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1846 this_vs + ". You can still work with this notebook, but some features " +
1836 this_vs + ". You can still work with this notebook, but some features " +
1847 "introduced in later notebook versions may not be available."
1837 "introduced in later notebook versions may not be available."
1848
1838
1849 IPython.dialog.modal({
1839 IPython.dialog.modal({
1850 title : "Newer Notebook",
1840 title : "Newer Notebook",
1851 body : msg,
1841 body : msg,
1852 buttons : {
1842 buttons : {
1853 OK : {
1843 OK : {
1854 class : "btn-danger"
1844 class : "btn-danger"
1855 }
1845 }
1856 }
1846 }
1857 });
1847 });
1858
1848
1859 }
1849 }
1860
1850
1861 // Create the session after the notebook is completely loaded to prevent
1851 // Create the session after the notebook is completely loaded to prevent
1862 // code execution upon loading, which is a security risk.
1852 // code execution upon loading, which is a security risk.
1863 if (this.session == null) {
1853 if (this.session == null) {
1864 this.start_session(this.notebook_path);
1854 this.start_session(this.notebook_path);
1865 }
1855 }
1866 // load our checkpoint list
1856 // load our checkpoint list
1867 IPython.notebook.list_checkpoints();
1857 IPython.notebook.list_checkpoints();
1868 $([IPython.events]).trigger('notebook_loaded.Notebook');
1858 $([IPython.events]).trigger('notebook_loaded.Notebook');
1869 };
1859 };
1870
1860
1871 /**
1861 /**
1872 * Failure callback for loading a notebook from the server.
1862 * Failure callback for loading a notebook from the server.
1873 *
1863 *
1874 * @method load_notebook_error
1864 * @method load_notebook_error
1875 * @param {jqXHR} xhr jQuery Ajax object
1865 * @param {jqXHR} xhr jQuery Ajax object
1876 * @param {String} textStatus Description of response status
1866 * @param {String} textStatus Description of response status
1877 * @param {String} errorThrow HTTP error message
1867 * @param {String} errorThrow HTTP error message
1878 */
1868 */
1879 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1869 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1880 if (xhr.status === 400) {
1870 if (xhr.status === 400) {
1881 var msg = errorThrow;
1871 var msg = errorThrow;
1882 } else if (xhr.status === 500) {
1872 } else if (xhr.status === 500) {
1883 var msg = "An unknown error occurred while loading this notebook. " +
1873 var msg = "An unknown error occurred while loading this notebook. " +
1884 "This version can load notebook formats " +
1874 "This version can load notebook formats " +
1885 "v" + this.nbformat + " or earlier.";
1875 "v" + this.nbformat + " or earlier.";
1886 }
1876 }
1887 IPython.dialog.modal({
1877 IPython.dialog.modal({
1888 title: "Error loading notebook",
1878 title: "Error loading notebook",
1889 body : msg,
1879 body : msg,
1890 buttons : {
1880 buttons : {
1891 "OK": {}
1881 "OK": {}
1892 }
1882 }
1893 });
1883 });
1894 }
1884 }
1895
1885
1896 /********************* checkpoint-related *********************/
1886 /********************* checkpoint-related *********************/
1897
1887
1898 /**
1888 /**
1899 * Save the notebook then immediately create a checkpoint.
1889 * Save the notebook then immediately create a checkpoint.
1900 *
1890 *
1901 * @method save_checkpoint
1891 * @method save_checkpoint
1902 */
1892 */
1903 Notebook.prototype.save_checkpoint = function () {
1893 Notebook.prototype.save_checkpoint = function () {
1904 this._checkpoint_after_save = true;
1894 this._checkpoint_after_save = true;
1905 this.save_notebook();
1895 this.save_notebook();
1906 };
1896 };
1907
1897
1908 /**
1898 /**
1909 * Add a checkpoint for this notebook.
1899 * Add a checkpoint for this notebook.
1910 * for use as a callback from checkpoint creation.
1900 * for use as a callback from checkpoint creation.
1911 *
1901 *
1912 * @method add_checkpoint
1902 * @method add_checkpoint
1913 */
1903 */
1914 Notebook.prototype.add_checkpoint = function (checkpoint) {
1904 Notebook.prototype.add_checkpoint = function (checkpoint) {
1915 var found = false;
1905 var found = false;
1916 for (var i = 0; i < this.checkpoints.length; i++) {
1906 for (var i = 0; i < this.checkpoints.length; i++) {
1917 var existing = this.checkpoints[i];
1907 var existing = this.checkpoints[i];
1918 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1908 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1919 found = true;
1909 found = true;
1920 this.checkpoints[i] = checkpoint;
1910 this.checkpoints[i] = checkpoint;
1921 break;
1911 break;
1922 }
1912 }
1923 }
1913 }
1924 if (!found) {
1914 if (!found) {
1925 this.checkpoints.push(checkpoint);
1915 this.checkpoints.push(checkpoint);
1926 }
1916 }
1927 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1917 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1928 };
1918 };
1929
1919
1930 /**
1920 /**
1931 * List checkpoints for this notebook.
1921 * List checkpoints for this notebook.
1932 *
1922 *
1933 * @method list_checkpoints
1923 * @method list_checkpoints
1934 */
1924 */
1935 Notebook.prototype.list_checkpoints = function () {
1925 Notebook.prototype.list_checkpoints = function () {
1936 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1926 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1937 $.get(url).done(
1927 $.get(url).done(
1938 $.proxy(this.list_checkpoints_success, this)
1928 $.proxy(this.list_checkpoints_success, this)
1939 ).fail(
1929 ).fail(
1940 $.proxy(this.list_checkpoints_error, this)
1930 $.proxy(this.list_checkpoints_error, this)
1941 );
1931 );
1942 };
1932 };
1943
1933
1944 /**
1934 /**
1945 * Success callback for listing checkpoints.
1935 * Success callback for listing checkpoints.
1946 *
1936 *
1947 * @method list_checkpoint_success
1937 * @method list_checkpoint_success
1948 * @param {Object} data JSON representation of a checkpoint
1938 * @param {Object} data JSON representation of a checkpoint
1949 * @param {String} status Description of response status
1939 * @param {String} status Description of response status
1950 * @param {jqXHR} xhr jQuery Ajax object
1940 * @param {jqXHR} xhr jQuery Ajax object
1951 */
1941 */
1952 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1942 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1953 var data = $.parseJSON(data);
1943 var data = $.parseJSON(data);
1954 this.checkpoints = data;
1944 this.checkpoints = data;
1955 if (data.length) {
1945 if (data.length) {
1956 this.last_checkpoint = data[data.length - 1];
1946 this.last_checkpoint = data[data.length - 1];
1957 } else {
1947 } else {
1958 this.last_checkpoint = null;
1948 this.last_checkpoint = null;
1959 }
1949 }
1960 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1950 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1961 };
1951 };
1962
1952
1963 /**
1953 /**
1964 * Failure callback for listing a checkpoint.
1954 * Failure callback for listing a checkpoint.
1965 *
1955 *
1966 * @method list_checkpoint_error
1956 * @method list_checkpoint_error
1967 * @param {jqXHR} xhr jQuery Ajax object
1957 * @param {jqXHR} xhr jQuery Ajax object
1968 * @param {String} status Description of response status
1958 * @param {String} status Description of response status
1969 * @param {String} error_msg HTTP error message
1959 * @param {String} error_msg HTTP error message
1970 */
1960 */
1971 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1961 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1972 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1962 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1973 };
1963 };
1974
1964
1975 /**
1965 /**
1976 * Create a checkpoint of this notebook on the server from the most recent save.
1966 * Create a checkpoint of this notebook on the server from the most recent save.
1977 *
1967 *
1978 * @method create_checkpoint
1968 * @method create_checkpoint
1979 */
1969 */
1980 Notebook.prototype.create_checkpoint = function () {
1970 Notebook.prototype.create_checkpoint = function () {
1981 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1971 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints';
1982 $.post(url).done(
1972 $.post(url).done(
1983 $.proxy(this.create_checkpoint_success, this)
1973 $.proxy(this.create_checkpoint_success, this)
1984 ).fail(
1974 ).fail(
1985 $.proxy(this.create_checkpoint_error, this)
1975 $.proxy(this.create_checkpoint_error, this)
1986 );
1976 );
1987 };
1977 };
1988
1978
1989 /**
1979 /**
1990 * Success callback for creating a checkpoint.
1980 * Success callback for creating a checkpoint.
1991 *
1981 *
1992 * @method create_checkpoint_success
1982 * @method create_checkpoint_success
1993 * @param {Object} data JSON representation of a checkpoint
1983 * @param {Object} data JSON representation of a checkpoint
1994 * @param {String} status Description of response status
1984 * @param {String} status Description of response status
1995 * @param {jqXHR} xhr jQuery Ajax object
1985 * @param {jqXHR} xhr jQuery Ajax object
1996 */
1986 */
1997 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1987 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1998 var data = $.parseJSON(data);
1988 var data = $.parseJSON(data);
1999 this.add_checkpoint(data);
1989 this.add_checkpoint(data);
2000 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1990 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2001 };
1991 };
2002
1992
2003 /**
1993 /**
2004 * Failure callback for creating a checkpoint.
1994 * Failure callback for creating a checkpoint.
2005 *
1995 *
2006 * @method create_checkpoint_error
1996 * @method create_checkpoint_error
2007 * @param {jqXHR} xhr jQuery Ajax object
1997 * @param {jqXHR} xhr jQuery Ajax object
2008 * @param {String} status Description of response status
1998 * @param {String} status Description of response status
2009 * @param {String} error_msg HTTP error message
1999 * @param {String} error_msg HTTP error message
2010 */
2000 */
2011 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2001 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2012 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2002 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2013 };
2003 };
2014
2004
2015 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2005 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2016 var that = this;
2006 var that = this;
2017 var checkpoint = checkpoint || this.last_checkpoint;
2007 var checkpoint = checkpoint || this.last_checkpoint;
2018 if ( ! checkpoint ) {
2008 if ( ! checkpoint ) {
2019 console.log("restore dialog, but no checkpoint to restore to!");
2009 console.log("restore dialog, but no checkpoint to restore to!");
2020 return;
2010 return;
2021 }
2011 }
2022 var body = $('<div/>').append(
2012 var body = $('<div/>').append(
2023 $('<p/>').addClass("p-space").text(
2013 $('<p/>').addClass("p-space").text(
2024 "Are you sure you want to revert the notebook to " +
2014 "Are you sure you want to revert the notebook to " +
2025 "the latest checkpoint?"
2015 "the latest checkpoint?"
2026 ).append(
2016 ).append(
2027 $("<strong/>").text(
2017 $("<strong/>").text(
2028 " This cannot be undone."
2018 " This cannot be undone."
2029 )
2019 )
2030 )
2020 )
2031 ).append(
2021 ).append(
2032 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2022 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2033 ).append(
2023 ).append(
2034 $('<p/>').addClass("p-space").text(
2024 $('<p/>').addClass("p-space").text(
2035 Date(checkpoint.last_modified)
2025 Date(checkpoint.last_modified)
2036 ).css("text-align", "center")
2026 ).css("text-align", "center")
2037 );
2027 );
2038
2028
2039 IPython.dialog.modal({
2029 IPython.dialog.modal({
2040 title : "Revert notebook to checkpoint",
2030 title : "Revert notebook to checkpoint",
2041 body : body,
2031 body : body,
2042 buttons : {
2032 buttons : {
2043 Revert : {
2033 Revert : {
2044 class : "btn-danger",
2034 class : "btn-danger",
2045 click : function () {
2035 click : function () {
2046 that.restore_checkpoint(checkpoint.checkpoint_id);
2036 that.restore_checkpoint(checkpoint.checkpoint_id);
2047 }
2037 }
2048 },
2038 },
2049 Cancel : {}
2039 Cancel : {}
2050 }
2040 }
2051 });
2041 });
2052 }
2042 }
2053
2043
2054 /**
2044 /**
2055 * Restore the notebook to a checkpoint state.
2045 * Restore the notebook to a checkpoint state.
2056 *
2046 *
2057 * @method restore_checkpoint
2047 * @method restore_checkpoint
2058 * @param {String} checkpoint ID
2048 * @param {String} checkpoint ID
2059 */
2049 */
2060 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2050 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2061 <<<<<<< HEAD
2051 <<<<<<< HEAD
2062 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
2052 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
2063 if (this.notebook_path != "") {
2053 if (this.notebook_path != "") {
2064 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebook_path + this.notebook_name + '/checkpoints/' + checkpoint;
2054 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebook_path + this.notebook_name + '/checkpoints/' + checkpoint;
2065 }
2055 }
2066 else {
2056 else {
2067 var url = this.baseProjectUrl() + 'api/notebooks/' +this.notebook_name + '/checkpoints/' + checkpoint;
2057 var url = this.baseProjectUrl() + 'api/notebooks/' +this.notebook_name + '/checkpoints/' + checkpoint;
2068 }
2058 }
2069 =======
2059 =======
2070 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2060 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2071 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2061 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2072 >>>>>>> fixing path redirects, cleaning path logic
2062 >>>>>>> fixing path redirects, cleaning path logic
2073 $.post(url).done(
2063 $.post(url).done(
2074 $.proxy(this.restore_checkpoint_success, this)
2064 $.proxy(this.restore_checkpoint_success, this)
2075 ).fail(
2065 ).fail(
2076 $.proxy(this.restore_checkpoint_error, this)
2066 $.proxy(this.restore_checkpoint_error, this)
2077 );
2067 );
2078 };
2068 };
2079
2069
2080 /**
2070 /**
2081 * Success callback for restoring a notebook to a checkpoint.
2071 * Success callback for restoring a notebook to a checkpoint.
2082 *
2072 *
2083 * @method restore_checkpoint_success
2073 * @method restore_checkpoint_success
2084 * @param {Object} data (ignored, should be empty)
2074 * @param {Object} data (ignored, should be empty)
2085 * @param {String} status Description of response status
2075 * @param {String} status Description of response status
2086 * @param {jqXHR} xhr jQuery Ajax object
2076 * @param {jqXHR} xhr jQuery Ajax object
2087 */
2077 */
2088 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2078 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2089 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2079 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2090 this.load_notebook(this.notebook_name, this.notebook_path);
2080 this.load_notebook(this.notebook_name, this.notebook_path);
2091 };
2081 };
2092
2082
2093 /**
2083 /**
2094 * Failure callback for restoring a notebook to a checkpoint.
2084 * Failure callback for restoring a notebook to a checkpoint.
2095 *
2085 *
2096 * @method restore_checkpoint_error
2086 * @method restore_checkpoint_error
2097 * @param {jqXHR} xhr jQuery Ajax object
2087 * @param {jqXHR} xhr jQuery Ajax object
2098 * @param {String} status Description of response status
2088 * @param {String} status Description of response status
2099 * @param {String} error_msg HTTP error message
2089 * @param {String} error_msg HTTP error message
2100 */
2090 */
2101 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2091 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2102 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2092 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2103 };
2093 };
2104
2094
2105 /**
2095 /**
2106 * Delete a notebook checkpoint.
2096 * Delete a notebook checkpoint.
2107 *
2097 *
2108 * @method delete_checkpoint
2098 * @method delete_checkpoint
2109 * @param {String} checkpoint ID
2099 * @param {String} checkpoint ID
2110 */
2100 */
2111 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2101 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2112 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2102 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2113 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2103 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath() + this.notebook_name + '/checkpoints/' + checkpoint;
2114 $.ajax(url, {
2104 $.ajax(url, {
2115 type: 'DELETE',
2105 type: 'DELETE',
2116 success: $.proxy(this.delete_checkpoint_success, this),
2106 success: $.proxy(this.delete_checkpoint_success, this),
2117 error: $.proxy(this.delete_notebook_error,this)
2107 error: $.proxy(this.delete_notebook_error,this)
2118 });
2108 });
2119 };
2109 };
2120
2110
2121 /**
2111 /**
2122 * Success callback for deleting a notebook checkpoint
2112 * Success callback for deleting a notebook checkpoint
2123 *
2113 *
2124 * @method delete_checkpoint_success
2114 * @method delete_checkpoint_success
2125 * @param {Object} data (ignored, should be empty)
2115 * @param {Object} data (ignored, should be empty)
2126 * @param {String} status Description of response status
2116 * @param {String} status Description of response status
2127 * @param {jqXHR} xhr jQuery Ajax object
2117 * @param {jqXHR} xhr jQuery Ajax object
2128 */
2118 */
2129 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2119 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2130 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2120 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2131 this.load_notebook(this.notebook_name, this.notebook_path);
2121 this.load_notebook(this.notebook_name, this.notebook_path);
2132 };
2122 };
2133
2123
2134 /**
2124 /**
2135 * Failure callback for deleting a notebook checkpoint.
2125 * Failure callback for deleting a notebook checkpoint.
2136 *
2126 *
2137 * @method delete_checkpoint_error
2127 * @method delete_checkpoint_error
2138 * @param {jqXHR} xhr jQuery Ajax object
2128 * @param {jqXHR} xhr jQuery Ajax object
2139 * @param {String} status Description of response status
2129 * @param {String} status Description of response status
2140 * @param {String} error_msg HTTP error message
2130 * @param {String} error_msg HTTP error message
2141 */
2131 */
2142 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2132 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2143 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2133 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2144 };
2134 };
2145
2135
2146
2136
2147 IPython.Notebook = Notebook;
2137 IPython.Notebook = Notebook;
2148
2138
2149
2139
2150 return IPython;
2140 return IPython;
2151
2141
2152 }(IPython));
2142 }(IPython));
2153
2143
@@ -1,527 +1,527 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Kernel
9 // Kernel
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule Kernel
15 * @submodule Kernel
16 */
16 */
17
17
18 var IPython = (function (IPython) {
18 var IPython = (function (IPython) {
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 // Initialization and connection.
22 // Initialization and connection.
23 /**
23 /**
24 * A Kernel Class to communicate with the Python kernel
24 * A Kernel Class to communicate with the Python kernel
25 * @Class Kernel
25 * @Class Kernel
26 */
26 */
27 var Kernel = function (base_url, session_id) {
27 var Kernel = function (base_url, session_id) {
28 this.kernel_id = null;
28 this.kernel_id = null;
29 this.session_id = session_id
29 this.session_id = session_id
30 this.shell_channel = null;
30 this.shell_channel = null;
31 this.iopub_channel = null;
31 this.iopub_channel = null;
32 this.stdin_channel = null;
32 this.stdin_channel = null;
33 this.base_url = base_url;
33 this.base_url = base_url;
34 this.running = false;
34 this.running = false;
35 this.username = "username";
35 this.username = "username";
36 this.base_session_id = utils.uuid();
36 this.base_session_id = utils.uuid();
37 this._msg_callbacks = {};
37 this._msg_callbacks = {};
38
38
39 if (typeof(WebSocket) !== 'undefined') {
39 if (typeof(WebSocket) !== 'undefined') {
40 this.WebSocket = WebSocket;
40 this.WebSocket = WebSocket;
41 } else if (typeof(MozWebSocket) !== 'undefined') {
41 } else if (typeof(MozWebSocket) !== 'undefined') {
42 this.WebSocket = MozWebSocket;
42 this.WebSocket = MozWebSocket;
43 } else {
43 } else {
44 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
44 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
45 };
45 };
46 this.bind_events();
46 this.bind_events();
47 };
47 };
48
48
49
49
50 Kernel.prototype._get_msg = function (msg_type, content) {
50 Kernel.prototype._get_msg = function (msg_type, content) {
51 var msg = {
51 var msg = {
52 header : {
52 header : {
53 msg_id : utils.uuid(),
53 msg_id : utils.uuid(),
54 username : this.username,
54 username : this.username,
55 session : this.base_session_id,
55 session : this.base_session_id,
56 msg_type : msg_type
56 msg_type : msg_type
57 },
57 },
58 metadata : {},
58 metadata : {},
59 content : content,
59 content : content,
60 parent_header : {}
60 parent_header : {}
61 };
61 };
62 return msg;
62 return msg;
63 };
63 };
64
64
65 Kernel.prototype.bind_events = function() {
65 Kernel.prototype.bind_events = function() {
66 var that = this;
66 var that = this;
67 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
67 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
68 that.send_input_reply(data);
68 that.send_input_reply(data);
69 });
69 });
70 }
70 }
71
71
72 /**
72 /**
73 * Start the Python kernel
73 * Start the Python kernel
74 * @method start
74 * @method start
75 */
75 */
76 Kernel.prototype.start = function (params) {
76 Kernel.prototype.start = function (params) {
77 var that = this;
77 var that = this;
78 params = params || {};
78 params = params || {};
79 params.session = this.session_id;
79 params.session = this.session_id;
80 if (!this.running) {
80 if (!this.running) {
81 var qs = $.param(params);
81 var qs = $.param(params);
82 var url = this.base_url + '?' + qs;
82 var url = this.base_url + '?' + qs;
83 $.post(url,
83 $.post(url,
84 $.proxy(that._kernel_started,that),
84 $.proxy(that._kernel_started,that),
85 'json'
85 'json'
86 );
86 );
87 };
87 };
88 };
88 };
89
89
90 /**
90 /**
91 * Restart the python kernel.
91 * Restart the python kernel.
92 *
92 *
93 * Emit a 'status_restarting.Kernel' event with
93 * Emit a 'status_restarting.Kernel' event with
94 * the current object as parameter
94 * the current object as parameter
95 *
95 *
96 * @method restart
96 * @method restart
97 */
97 */
98 Kernel.prototype.restart = function () {
98 Kernel.prototype.restart = function () {
99 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
99 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
100 var that = this;
100 var that = this;
101 if (this.running) {
101 if (this.running) {
102 this.stop_channels();
102 this.stop_channels();
103 var url = this.kernel_url + "/restart";
103 var url = this.kernel_url + "/restart";
104 $.post(url,
104 $.post(url,
105 $.proxy(that._kernel_started, that),
105 $.proxy(that._kernel_started, that),
106 'json'
106 'json'
107 );
107 );
108 };
108 };
109 };
109 };
110
110
111
111
112 Kernel.prototype._kernel_started = function (json) {
112 Kernel.prototype._kernel_started = function (json) {
113 console.log("Kernel started: ", json.kernel_id);
113 console.log("Kernel started: ", json.id);
114 this.running = true;
114 this.running = true;
115 this.kernel_id = json.kernel_id;
115 this.kernel_id = json.id;
116 var ws_url = json.ws_url;
116 var ws_url = json.ws_url;
117 if (ws_url.match(/wss?:\/\//) == null) {
117 if (ws_url.match(/wss?:\/\//) == null) {
118 // trailing 's' in https will become wss for secure web sockets
118 // trailing 's' in https will become wss for secure web sockets
119 prot = location.protocol.replace('http', 'ws') + "//";
119 prot = location.protocol.replace('http', 'ws') + "//";
120 ws_url = prot + location.host + ws_url;
120 ws_url = prot + location.host + ws_url;
121 };
121 };
122 this.ws_url = ws_url;
122 this.ws_url = ws_url;
123 this.kernel_url = this.base_url + "/" + this.kernel_id;
123 this.kernel_url = this.base_url + "/" + this.kernel_id;
124 this.start_channels();
124 this.start_channels();
125 };
125 };
126
126
127
127
128 Kernel.prototype._websocket_closed = function(ws_url, early) {
128 Kernel.prototype._websocket_closed = function(ws_url, early) {
129 this.stop_channels();
129 this.stop_channels();
130 $([IPython.events]).trigger('websocket_closed.Kernel',
130 $([IPython.events]).trigger('websocket_closed.Kernel',
131 {ws_url: ws_url, kernel: this, early: early}
131 {ws_url: ws_url, kernel: this, early: early}
132 );
132 );
133 };
133 };
134
134
135 /**
135 /**
136 * Start the `shell`and `iopub` channels.
136 * Start the `shell`and `iopub` channels.
137 * Will stop and restart them if they already exist.
137 * Will stop and restart them if they already exist.
138 *
138 *
139 * @method start_channels
139 * @method start_channels
140 */
140 */
141 Kernel.prototype.start_channels = function () {
141 Kernel.prototype.start_channels = function () {
142 var that = this;
142 var that = this;
143 this.stop_channels();
143 this.stop_channels();
144 var ws_url = this.ws_url + this.kernel_url;
144 var ws_url = this.ws_url + this.kernel_url;
145 console.log("Starting WebSockets:", ws_url);
145 console.log("Starting WebSockets:", ws_url);
146 this.shell_channel = new this.WebSocket(ws_url + "/shell");
146 this.shell_channel = new this.WebSocket(ws_url + "/shell");
147 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
147 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
148 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
148 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
149
149
150 var already_called_onclose = false; // only alert once
150 var already_called_onclose = false; // only alert once
151 var ws_closed_early = function(evt){
151 var ws_closed_early = function(evt){
152 if (already_called_onclose){
152 if (already_called_onclose){
153 return;
153 return;
154 }
154 }
155 already_called_onclose = true;
155 already_called_onclose = true;
156 if ( ! evt.wasClean ){
156 if ( ! evt.wasClean ){
157 that._websocket_closed(ws_url, true);
157 that._websocket_closed(ws_url, true);
158 }
158 }
159 };
159 };
160 var ws_closed_late = function(evt){
160 var ws_closed_late = function(evt){
161 if (already_called_onclose){
161 if (already_called_onclose){
162 return;
162 return;
163 }
163 }
164 already_called_onclose = true;
164 already_called_onclose = true;
165 if ( ! evt.wasClean ){
165 if ( ! evt.wasClean ){
166 that._websocket_closed(ws_url, false);
166 that._websocket_closed(ws_url, false);
167 }
167 }
168 };
168 };
169 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
169 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
170 for (var i=0; i < channels.length; i++) {
170 for (var i=0; i < channels.length; i++) {
171 channels[i].onopen = $.proxy(this._ws_opened, this);
171 channels[i].onopen = $.proxy(this._ws_opened, this);
172 channels[i].onclose = ws_closed_early;
172 channels[i].onclose = ws_closed_early;
173 }
173 }
174 // switch from early-close to late-close message after 1s
174 // switch from early-close to late-close message after 1s
175 setTimeout(function() {
175 setTimeout(function() {
176 for (var i=0; i < channels.length; i++) {
176 for (var i=0; i < channels.length; i++) {
177 if (channels[i] !== null) {
177 if (channels[i] !== null) {
178 channels[i].onclose = ws_closed_late;
178 channels[i].onclose = ws_closed_late;
179 }
179 }
180 }
180 }
181 }, 1000);
181 }, 1000);
182 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
182 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
183 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
183 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
184 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
184 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
185 };
185 };
186
186
187 /**
187 /**
188 * Handle a websocket entering the open state
188 * Handle a websocket entering the open state
189 * sends session and cookie authentication info as first message.
189 * sends session and cookie authentication info as first message.
190 * Once all sockets are open, signal the Kernel.status_started event.
190 * Once all sockets are open, signal the Kernel.status_started event.
191 * @method _ws_opened
191 * @method _ws_opened
192 */
192 */
193 Kernel.prototype._ws_opened = function (evt) {
193 Kernel.prototype._ws_opened = function (evt) {
194 // send the session id so the Session object Python-side
194 // send the session id so the Session object Python-side
195 // has the same identity
195 // has the same identity
196 evt.target.send(this.session_id + ':' + document.cookie);
196 evt.target.send(this.session_id + ':' + document.cookie);
197
197
198 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
198 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
199 for (var i=0; i < channels.length; i++) {
199 for (var i=0; i < channels.length; i++) {
200 // if any channel is not ready, don't trigger event.
200 // if any channel is not ready, don't trigger event.
201 if ( !channels[i].readyState ) return;
201 if ( !channels[i].readyState ) return;
202 }
202 }
203 // all events ready, trigger started event.
203 // all events ready, trigger started event.
204 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
204 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
205 };
205 };
206
206
207 /**
207 /**
208 * Stop the websocket channels.
208 * Stop the websocket channels.
209 * @method stop_channels
209 * @method stop_channels
210 */
210 */
211 Kernel.prototype.stop_channels = function () {
211 Kernel.prototype.stop_channels = function () {
212 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
212 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
213 for (var i=0; i < channels.length; i++) {
213 for (var i=0; i < channels.length; i++) {
214 if ( channels[i] !== null ) {
214 if ( channels[i] !== null ) {
215 channels[i].onclose = function (evt) {};
215 channels[i].onclose = function (evt) {};
216 channels[i].close();
216 channels[i].close();
217 }
217 }
218 };
218 };
219 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
219 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
220 };
220 };
221
221
222 // Main public methods.
222 // Main public methods.
223
223
224 /**
224 /**
225 * Get info on object asynchronoulsy
225 * Get info on object asynchronoulsy
226 *
226 *
227 * @async
227 * @async
228 * @param objname {string}
228 * @param objname {string}
229 * @param callback {dict}
229 * @param callback {dict}
230 * @method object_info_request
230 * @method object_info_request
231 *
231 *
232 * @example
232 * @example
233 *
233 *
234 * When calling this method pass a callbacks structure of the form:
234 * When calling this method pass a callbacks structure of the form:
235 *
235 *
236 * callbacks = {
236 * callbacks = {
237 * 'object_info_reply': object_info_reply_callback
237 * 'object_info_reply': object_info_reply_callback
238 * }
238 * }
239 *
239 *
240 * The `object_info_reply_callback` will be passed the content object of the
240 * The `object_info_reply_callback` will be passed the content object of the
241 *
241 *
242 * `object_into_reply` message documented in
242 * `object_into_reply` message documented in
243 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
243 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
244 */
244 */
245 Kernel.prototype.object_info_request = function (objname, callbacks) {
245 Kernel.prototype.object_info_request = function (objname, callbacks) {
246 if(typeof(objname)!=null && objname!=null)
246 if(typeof(objname)!=null && objname!=null)
247 {
247 {
248 var content = {
248 var content = {
249 oname : objname.toString(),
249 oname : objname.toString(),
250 detail_level : 0,
250 detail_level : 0,
251 };
251 };
252 var msg = this._get_msg("object_info_request", content);
252 var msg = this._get_msg("object_info_request", content);
253 this.shell_channel.send(JSON.stringify(msg));
253 this.shell_channel.send(JSON.stringify(msg));
254 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
254 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
255 return msg.header.msg_id;
255 return msg.header.msg_id;
256 }
256 }
257 return;
257 return;
258 }
258 }
259
259
260 /**
260 /**
261 * Execute given code into kernel, and pass result to callback.
261 * Execute given code into kernel, and pass result to callback.
262 *
262 *
263 * TODO: document input_request in callbacks
263 * TODO: document input_request in callbacks
264 *
264 *
265 * @async
265 * @async
266 * @method execute
266 * @method execute
267 * @param {string} code
267 * @param {string} code
268 * @param [callbacks] {Object} With the optional following keys
268 * @param [callbacks] {Object} With the optional following keys
269 * @param callbacks.'execute_reply' {function}
269 * @param callbacks.'execute_reply' {function}
270 * @param callbacks.'output' {function}
270 * @param callbacks.'output' {function}
271 * @param callbacks.'clear_output' {function}
271 * @param callbacks.'clear_output' {function}
272 * @param callbacks.'set_next_input' {function}
272 * @param callbacks.'set_next_input' {function}
273 * @param {object} [options]
273 * @param {object} [options]
274 * @param [options.silent=false] {Boolean}
274 * @param [options.silent=false] {Boolean}
275 * @param [options.user_expressions=empty_dict] {Dict}
275 * @param [options.user_expressions=empty_dict] {Dict}
276 * @param [options.user_variables=empty_list] {List od Strings}
276 * @param [options.user_variables=empty_list] {List od Strings}
277 * @param [options.allow_stdin=false] {Boolean} true|false
277 * @param [options.allow_stdin=false] {Boolean} true|false
278 *
278 *
279 * @example
279 * @example
280 *
280 *
281 * The options object should contain the options for the execute call. Its default
281 * The options object should contain the options for the execute call. Its default
282 * values are:
282 * values are:
283 *
283 *
284 * options = {
284 * options = {
285 * silent : true,
285 * silent : true,
286 * user_variables : [],
286 * user_variables : [],
287 * user_expressions : {},
287 * user_expressions : {},
288 * allow_stdin : false
288 * allow_stdin : false
289 * }
289 * }
290 *
290 *
291 * When calling this method pass a callbacks structure of the form:
291 * When calling this method pass a callbacks structure of the form:
292 *
292 *
293 * callbacks = {
293 * callbacks = {
294 * 'execute_reply': execute_reply_callback,
294 * 'execute_reply': execute_reply_callback,
295 * 'output': output_callback,
295 * 'output': output_callback,
296 * 'clear_output': clear_output_callback,
296 * 'clear_output': clear_output_callback,
297 * 'set_next_input': set_next_input_callback
297 * 'set_next_input': set_next_input_callback
298 * }
298 * }
299 *
299 *
300 * The `execute_reply_callback` will be passed the content and metadata
300 * The `execute_reply_callback` will be passed the content and metadata
301 * objects of the `execute_reply` message documented
301 * objects of the `execute_reply` message documented
302 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
302 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
303 *
303 *
304 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
304 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
305 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
305 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
306 * output:
306 * output:
307 *
307 *
308 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
308 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
309 *
309 *
310 * The `clear_output_callback` will be passed a content object that contains
310 * The `clear_output_callback` will be passed a content object that contains
311 * stdout, stderr and other fields that are booleans, as well as the metadata object.
311 * stdout, stderr and other fields that are booleans, as well as the metadata object.
312 *
312 *
313 * The `set_next_input_callback` will be passed the text that should become the next
313 * The `set_next_input_callback` will be passed the text that should become the next
314 * input cell.
314 * input cell.
315 */
315 */
316 Kernel.prototype.execute = function (code, callbacks, options) {
316 Kernel.prototype.execute = function (code, callbacks, options) {
317
317
318 var content = {
318 var content = {
319 code : code,
319 code : code,
320 silent : true,
320 silent : true,
321 store_history : false,
321 store_history : false,
322 user_variables : [],
322 user_variables : [],
323 user_expressions : {},
323 user_expressions : {},
324 allow_stdin : false
324 allow_stdin : false
325 };
325 };
326 callbacks = callbacks || {};
326 callbacks = callbacks || {};
327 if (callbacks.input_request !== undefined) {
327 if (callbacks.input_request !== undefined) {
328 content.allow_stdin = true;
328 content.allow_stdin = true;
329 }
329 }
330 $.extend(true, content, options)
330 $.extend(true, content, options)
331 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
331 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
332 var msg = this._get_msg("execute_request", content);
332 var msg = this._get_msg("execute_request", content);
333 this.shell_channel.send(JSON.stringify(msg));
333 this.shell_channel.send(JSON.stringify(msg));
334 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
334 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
335 return msg.header.msg_id;
335 return msg.header.msg_id;
336 };
336 };
337
337
338 /**
338 /**
339 * When calling this method pass a callbacks structure of the form:
339 * When calling this method pass a callbacks structure of the form:
340 *
340 *
341 * callbacks = {
341 * callbacks = {
342 * 'complete_reply': complete_reply_callback
342 * 'complete_reply': complete_reply_callback
343 * }
343 * }
344 *
344 *
345 * The `complete_reply_callback` will be passed the content object of the
345 * The `complete_reply_callback` will be passed the content object of the
346 * `complete_reply` message documented
346 * `complete_reply` message documented
347 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
347 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
348 *
348 *
349 * @method complete
349 * @method complete
350 * @param line {integer}
350 * @param line {integer}
351 * @param cursor_pos {integer}
351 * @param cursor_pos {integer}
352 * @param {dict} callbacks
352 * @param {dict} callbacks
353 * @param callbacks.complete_reply {function} `complete_reply_callback`
353 * @param callbacks.complete_reply {function} `complete_reply_callback`
354 *
354 *
355 */
355 */
356 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
356 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
357 callbacks = callbacks || {};
357 callbacks = callbacks || {};
358 var content = {
358 var content = {
359 text : '',
359 text : '',
360 line : line,
360 line : line,
361 block : null,
361 block : null,
362 cursor_pos : cursor_pos
362 cursor_pos : cursor_pos
363 };
363 };
364 var msg = this._get_msg("complete_request", content);
364 var msg = this._get_msg("complete_request", content);
365 this.shell_channel.send(JSON.stringify(msg));
365 this.shell_channel.send(JSON.stringify(msg));
366 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
366 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
367 return msg.header.msg_id;
367 return msg.header.msg_id;
368 };
368 };
369
369
370
370
371 Kernel.prototype.interrupt = function () {
371 Kernel.prototype.interrupt = function () {
372 if (this.running) {
372 if (this.running) {
373 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
373 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
374 $.post(this.kernel_url + "/interrupt");
374 $.post(this.kernel_url + "/interrupt");
375 };
375 };
376 };
376 };
377
377
378
378
379 Kernel.prototype.kill = function () {
379 Kernel.prototype.kill = function () {
380 if (this.running) {
380 if (this.running) {
381 this.running = false;
381 this.running = false;
382 var settings = {
382 var settings = {
383 cache : false,
383 cache : false,
384 type : "DELETE"
384 type : "DELETE"
385 };
385 };
386 $.ajax(this.kernel_url, settings);
386 $.ajax(this.kernel_url, settings);
387 };
387 };
388 };
388 };
389
389
390 Kernel.prototype.send_input_reply = function (input) {
390 Kernel.prototype.send_input_reply = function (input) {
391 var content = {
391 var content = {
392 value : input,
392 value : input,
393 };
393 };
394 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
394 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
395 var msg = this._get_msg("input_reply", content);
395 var msg = this._get_msg("input_reply", content);
396 this.stdin_channel.send(JSON.stringify(msg));
396 this.stdin_channel.send(JSON.stringify(msg));
397 return msg.header.msg_id;
397 return msg.header.msg_id;
398 };
398 };
399
399
400
400
401 // Reply handlers
401 // Reply handlers
402
402
403 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
403 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
404 var callbacks = this._msg_callbacks[msg_id];
404 var callbacks = this._msg_callbacks[msg_id];
405 return callbacks;
405 return callbacks;
406 };
406 };
407
407
408
408
409 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
409 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
410 if (this._msg_callbacks[msg_id] !== undefined ) {
410 if (this._msg_callbacks[msg_id] !== undefined ) {
411 delete this._msg_callbacks[msg_id];
411 delete this._msg_callbacks[msg_id];
412 }
412 }
413 };
413 };
414
414
415
415
416 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
416 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
417 this._msg_callbacks[msg_id] = callbacks || {};
417 this._msg_callbacks[msg_id] = callbacks || {};
418 };
418 };
419
419
420
420
421 Kernel.prototype._handle_shell_reply = function (e) {
421 Kernel.prototype._handle_shell_reply = function (e) {
422 var reply = $.parseJSON(e.data);
422 var reply = $.parseJSON(e.data);
423 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
423 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
424 var header = reply.header;
424 var header = reply.header;
425 var content = reply.content;
425 var content = reply.content;
426 var metadata = reply.metadata;
426 var metadata = reply.metadata;
427 var msg_type = header.msg_type;
427 var msg_type = header.msg_type;
428 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
428 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
429 if (callbacks !== undefined) {
429 if (callbacks !== undefined) {
430 var cb = callbacks[msg_type];
430 var cb = callbacks[msg_type];
431 if (cb !== undefined) {
431 if (cb !== undefined) {
432 cb(content, metadata);
432 cb(content, metadata);
433 }
433 }
434 };
434 };
435
435
436 if (content.payload !== undefined) {
436 if (content.payload !== undefined) {
437 var payload = content.payload || [];
437 var payload = content.payload || [];
438 this._handle_payload(callbacks, payload);
438 this._handle_payload(callbacks, payload);
439 }
439 }
440 };
440 };
441
441
442
442
443 Kernel.prototype._handle_payload = function (callbacks, payload) {
443 Kernel.prototype._handle_payload = function (callbacks, payload) {
444 var l = payload.length;
444 var l = payload.length;
445 // Payloads are handled by triggering events because we don't want the Kernel
445 // Payloads are handled by triggering events because we don't want the Kernel
446 // to depend on the Notebook or Pager classes.
446 // to depend on the Notebook or Pager classes.
447 for (var i=0; i<l; i++) {
447 for (var i=0; i<l; i++) {
448 if (payload[i].source === 'page') {
448 if (payload[i].source === 'page') {
449 var data = {'text':payload[i].text}
449 var data = {'text':payload[i].text}
450 $([IPython.events]).trigger('open_with_text.Pager', data);
450 $([IPython.events]).trigger('open_with_text.Pager', data);
451 } else if (payload[i].source === 'set_next_input') {
451 } else if (payload[i].source === 'set_next_input') {
452 if (callbacks.set_next_input !== undefined) {
452 if (callbacks.set_next_input !== undefined) {
453 callbacks.set_next_input(payload[i].text)
453 callbacks.set_next_input(payload[i].text)
454 }
454 }
455 }
455 }
456 };
456 };
457 };
457 };
458
458
459
459
460 Kernel.prototype._handle_iopub_reply = function (e) {
460 Kernel.prototype._handle_iopub_reply = function (e) {
461 var reply = $.parseJSON(e.data);
461 var reply = $.parseJSON(e.data);
462 var content = reply.content;
462 var content = reply.content;
463 var msg_type = reply.header.msg_type;
463 var msg_type = reply.header.msg_type;
464 var metadata = reply.metadata;
464 var metadata = reply.metadata;
465 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
465 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
466 if (msg_type !== 'status' && callbacks === undefined) {
466 if (msg_type !== 'status' && callbacks === undefined) {
467 // Message not from one of this notebook's cells and there are no
467 // Message not from one of this notebook's cells and there are no
468 // callbacks to handle it.
468 // callbacks to handle it.
469 return;
469 return;
470 }
470 }
471 var output_types = ['stream','display_data','pyout','pyerr'];
471 var output_types = ['stream','display_data','pyout','pyerr'];
472 if (output_types.indexOf(msg_type) >= 0) {
472 if (output_types.indexOf(msg_type) >= 0) {
473 var cb = callbacks['output'];
473 var cb = callbacks['output'];
474 if (cb !== undefined) {
474 if (cb !== undefined) {
475 cb(msg_type, content, metadata);
475 cb(msg_type, content, metadata);
476 }
476 }
477 } else if (msg_type === 'status') {
477 } else if (msg_type === 'status') {
478 if (content.execution_state === 'busy') {
478 if (content.execution_state === 'busy') {
479 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
479 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
480 } else if (content.execution_state === 'idle') {
480 } else if (content.execution_state === 'idle') {
481 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
481 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
482 } else if (content.execution_state === 'restarting') {
482 } else if (content.execution_state === 'restarting') {
483 // autorestarting is distinct from restarting,
483 // autorestarting is distinct from restarting,
484 // in that it means the kernel died and the server is restarting it.
484 // in that it means the kernel died and the server is restarting it.
485 // status_restarting sets the notification widget,
485 // status_restarting sets the notification widget,
486 // autorestart shows the more prominent dialog.
486 // autorestart shows the more prominent dialog.
487 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
487 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
488 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
488 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
489 } else if (content.execution_state === 'dead') {
489 } else if (content.execution_state === 'dead') {
490 this.stop_channels();
490 this.stop_channels();
491 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
491 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
492 };
492 };
493 } else if (msg_type === 'clear_output') {
493 } else if (msg_type === 'clear_output') {
494 var cb = callbacks['clear_output'];
494 var cb = callbacks['clear_output'];
495 if (cb !== undefined) {
495 if (cb !== undefined) {
496 cb(content, metadata);
496 cb(content, metadata);
497 }
497 }
498 };
498 };
499 };
499 };
500
500
501
501
502 Kernel.prototype._handle_input_request = function (e) {
502 Kernel.prototype._handle_input_request = function (e) {
503 var request = $.parseJSON(e.data);
503 var request = $.parseJSON(e.data);
504 var header = request.header;
504 var header = request.header;
505 var content = request.content;
505 var content = request.content;
506 var metadata = request.metadata;
506 var metadata = request.metadata;
507 var msg_type = header.msg_type;
507 var msg_type = header.msg_type;
508 if (msg_type !== 'input_request') {
508 if (msg_type !== 'input_request') {
509 console.log("Invalid input request!", request);
509 console.log("Invalid input request!", request);
510 return;
510 return;
511 }
511 }
512 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
512 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
513 if (callbacks !== undefined) {
513 if (callbacks !== undefined) {
514 var cb = callbacks[msg_type];
514 var cb = callbacks[msg_type];
515 if (cb !== undefined) {
515 if (cb !== undefined) {
516 cb(content, metadata);
516 cb(content, metadata);
517 }
517 }
518 };
518 };
519 };
519 };
520
520
521
521
522 IPython.Kernel = Kernel;
522 IPython.Kernel = Kernel;
523
523
524 return IPython;
524 return IPython;
525
525
526 }(IPython));
526 }(IPython));
527
527
@@ -1,96 +1,96 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var Session = function(notebook_path, Notebook){
14 var Session = function(notebook_path, Notebook){
15 this.kernel = null;
15 this.kernel = null;
16 this.kernel_id = null;
16 this.kernel_id = null;
17 this.session_id = null;
17 this.session_id = null;
18 this.notebook_path = notebook_path;
18 this.notebook_path = notebook_path;
19 this.notebook = Notebook;
19 this.notebook = Notebook;
20 this._baseProjectUrl = Notebook.baseProjectUrl()
20 this._baseProjectUrl = Notebook.baseProjectUrl()
21 };
21 };
22
22
23 Session.prototype.start = function(){
23 Session.prototype.start = function(){
24 var that = this
24 var that = this
25 var qs = $.param({notebook_path:this.notebook_path});
25 var qs = $.param({notebook_path:this.notebook_path});
26 var url = '/api/sessions' + '?' + qs;
26 var url = '/api/sessions' + '?' + qs;
27 $.post(url,
27 $.post(url,
28 $.proxy(this.start_kernel, that),
28 $.proxy(this.start_kernel, that),
29 'json'
29 'json'
30 );
30 );
31 };
31 };
32
32
33 Session.prototype.notebook_rename = function (notebook_path) {
33 Session.prototype.notebook_rename = function (notebook_path) {
34 this.notebook_path = notebook_path;
34 this.notebook_path = notebook_path;
35 var settings = {
35 var settings = {
36 processData : false,
36 processData : false,
37 cache : false,
37 cache : false,
38 type : "PATCH",
38 type : "PATCH",
39 data: notebook_path,
39 data: notebook_path,
40 dataType : "json",
40 dataType : "json",
41 };
41 };
42 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
42 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
43 $.ajax(url, settings);
43 $.ajax(url, settings);
44 }
44 }
45
45
46
46
47 Session.prototype.delete_session = function() {
47 Session.prototype.delete_session = function() {
48 var settings = {
48 var settings = {
49 processData : false,
49 processData : false,
50 cache : false,
50 cache : false,
51 type : "DELETE",
51 type : "DELETE",
52 dataType : "json",
52 dataType : "json",
53 };
53 };
54 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
54 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
55 $.ajax(url, settings);
55 $.ajax(url, settings);
56 };
56 };
57
57
58 // Kernel related things
58 // Kernel related things
59 /**
59 /**
60 * Start a new kernel and set it on each code cell.
60 * Start a new kernel and set it on each code cell.
61 *
61 *
62 * @method start_kernel
62 * @method start_kernel
63 */
63 */
64 Session.prototype.start_kernel = function (json) {
64 Session.prototype.start_kernel = function (json) {
65 this.session_id = json.session_id;
65 this.session_id = json.id;
66 this.kernel_content = json.kernel;
66 this.kernel_content = json.kernel;
67 var base_url = $('body').data('baseKernelUrl') + "api/kernels";
67 var base_url = $('body').data('baseKernelUrl') + "api/kernels";
68 this.kernel = new IPython.Kernel(base_url, this.session_id);
68 this.kernel = new IPython.Kernel(base_url, this.session_id);
69 // Now that the kernel has been created, tell the CodeCells about it.
69 // Now that the kernel has been created, tell the CodeCells about it.
70 this.kernel._kernel_started(this.kernel_content);
70 this.kernel._kernel_started(this.kernel_content);
71 };
71 };
72
72
73 /**
73 /**
74 * Prompt the user to restart the IPython kernel.
74 * Prompt the user to restart the IPython kernel.
75 *
75 *
76 * @method restart_kernel
76 * @method restart_kernel
77 */
77 */
78 Session.prototype.restart_kernel = function () {
78 Session.prototype.restart_kernel = function () {
79 this.kernel.restart();
79 this.kernel.restart();
80 };
80 };
81
81
82 Session.prototype.interrupt_kernel = function() {
82 Session.prototype.interrupt_kernel = function() {
83 this.kernel.interrupt();
83 this.kernel.interrupt();
84 };
84 };
85
85
86
86
87 Session.prototype.kill_kernel = function() {
87 Session.prototype.kill_kernel = function() {
88 this.kernel.kill();
88 this.kernel.kill();
89 };
89 };
90
90
91 IPython.Session = Session;
91 IPython.Session = Session;
92
92
93
93
94 return IPython;
94 return IPython;
95
95
96 }(IPython));
96 }(IPython));
@@ -1,346 +1,346 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var NotebookList = function (selector) {
14 var NotebookList = function (selector) {
15 this.selector = selector;
15 this.selector = selector;
16 if (this.selector !== undefined) {
16 if (this.selector !== undefined) {
17 this.element = $(selector);
17 this.element = $(selector);
18 this.style();
18 this.style();
19 this.bind_events();
19 this.bind_events();
20 }
20 }
21 this.notebooks_list = new Array();
21 this.notebooks_list = new Array();
22 this.sessions = new Object();
22 this.sessions = new Object();
23 };
23 };
24
24
25 NotebookList.prototype.baseProjectUrl = function () {
25 NotebookList.prototype.baseProjectUrl = function () {
26 return $('body').data('baseProjectUrl');
26 return $('body').data('baseProjectUrl');
27 };
27 };
28
28
29 NotebookList.prototype.notebookPath = function() {
29 NotebookList.prototype.notebookPath = function() {
30 var path = $('body').data('notebookPath');
30 var path = $('body').data('notebookPath');
31 path = decodeURIComponent(path);
31 path = decodeURIComponent(path);
32 if (path != "") {
32 if (path != "") {
33 if (path[path.length-1] != '/') {
33 if (path[path.length-1] != '/') {
34 path = path.substring(0,path.length);
34 path = path.substring(0,path.length);
35 };
35 };
36 return path;
36 return path;
37 } else {
37 } else {
38 return path;
38 return path;
39 };
39 };
40 };
40 };
41
41
42 NotebookList.prototype.url_name = function(name){
42 NotebookList.prototype.url_name = function(name){
43 return encodeURIComponent(name);
43 return encodeURIComponent(name);
44 };
44 };
45
45
46 NotebookList.prototype.style = function () {
46 NotebookList.prototype.style = function () {
47 $('#notebook_toolbar').addClass('list_toolbar');
47 $('#notebook_toolbar').addClass('list_toolbar');
48 $('#drag_info').addClass('toolbar_info');
48 $('#drag_info').addClass('toolbar_info');
49 $('#notebook_buttons').addClass('toolbar_buttons');
49 $('#notebook_buttons').addClass('toolbar_buttons');
50 $('#notebook_list_header').addClass('list_header');
50 $('#notebook_list_header').addClass('list_header');
51 this.element.addClass("list_container");
51 this.element.addClass("list_container");
52 };
52 };
53
53
54
54
55 NotebookList.prototype.bind_events = function () {
55 NotebookList.prototype.bind_events = function () {
56 var that = this;
56 var that = this;
57 $('#refresh_notebook_list').click(function () {
57 $('#refresh_notebook_list').click(function () {
58 that.load_list();
58 that.load_list();
59 });
59 });
60 this.element.bind('dragover', function () {
60 this.element.bind('dragover', function () {
61 return false;
61 return false;
62 });
62 });
63 this.element.bind('drop', function(event){
63 this.element.bind('drop', function(event){
64 that.handelFilesUpload(event,'drop');
64 that.handelFilesUpload(event,'drop');
65 return false;
65 return false;
66 });
66 });
67 };
67 };
68
68
69 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
69 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
70 var that = this;
70 var that = this;
71 var files;
71 var files;
72 if(dropOrForm =='drop'){
72 if(dropOrForm =='drop'){
73 files = event.originalEvent.dataTransfer.files;
73 files = event.originalEvent.dataTransfer.files;
74 } else
74 } else
75 {
75 {
76 files = event.originalEvent.target.files
76 files = event.originalEvent.target.files
77 }
77 }
78 for (var i = 0, f; f = files[i]; i++) {
78 for (var i = 0, f; f = files[i]; i++) {
79 var reader = new FileReader();
79 var reader = new FileReader();
80 reader.readAsText(f);
80 reader.readAsText(f);
81 var fname = f.name.split('.');
81 var fname = f.name.split('.');
82 var nbname = fname.slice(0,-1).join('.');
82 var nbname = fname.slice(0,-1).join('.');
83 var nbformat = fname.slice(-1)[0];
83 var nbformat = fname.slice(-1)[0];
84 if (nbformat === 'ipynb') {nbformat = 'json';};
84 if (nbformat === 'ipynb') {nbformat = 'json';};
85 if (nbformat === 'py' || nbformat === 'json') {
85 if (nbformat === 'py' || nbformat === 'json') {
86 var item = that.new_notebook_item(0);
86 var item = that.new_notebook_item(0);
87 that.add_name_input(nbname, item);
87 that.add_name_input(nbname, item);
88 item.data('nbformat', nbformat);
88 item.data('nbformat', nbformat);
89 // Store the notebook item in the reader so we can use it later
89 // Store the notebook item in the reader so we can use it later
90 // to know which item it belongs to.
90 // to know which item it belongs to.
91 $(reader).data('item', item);
91 $(reader).data('item', item);
92 reader.onload = function (event) {
92 reader.onload = function (event) {
93 var nbitem = $(event.target).data('item');
93 var nbitem = $(event.target).data('item');
94 that.add_notebook_data(event.target.result, nbitem);
94 that.add_notebook_data(event.target.result, nbitem);
95 that.add_upload_button(nbitem);
95 that.add_upload_button(nbitem);
96 };
96 };
97 };
97 };
98 }
98 }
99 return false;
99 return false;
100 };
100 };
101
101
102 NotebookList.prototype.clear_list = function () {
102 NotebookList.prototype.clear_list = function () {
103 this.element.children('.list_item').remove();
103 this.element.children('.list_item').remove();
104 };
104 };
105
105
106 NotebookList.prototype.load_sessions = function(){
106 NotebookList.prototype.load_sessions = function(){
107 var that = this;
107 var that = this;
108 var settings = {
108 var settings = {
109 processData : false,
109 processData : false,
110 cache : false,
110 cache : false,
111 type : "GET",
111 type : "GET",
112 dataType : "json",
112 dataType : "json",
113 success : $.proxy(that.sessions_loaded, this)
113 success : $.proxy(that.sessions_loaded, this)
114 };
114 };
115 var url = this.baseProjectUrl() + 'api/sessions';
115 var url = this.baseProjectUrl() + 'api/sessions';
116 $.ajax(url,settings);
116 $.ajax(url,settings);
117 };
117 };
118
118
119
119
120 NotebookList.prototype.sessions_loaded = function(data){
120 NotebookList.prototype.sessions_loaded = function(data){
121 this.sessions = new Object();
121 this.sessions = new Object();
122 var len = data.length;
122 var len = data.length;
123 if (len != 0) {
123 if (len != 0) {
124 for (var i=0; i<len; i++) {
124 for (var i=0; i<len; i++) {
125 if (data[i]['notebook_path']==null) {
125 if (data[i]['path']==null) {
126 nb_path = data[i]['notebook_name'];
126 nb_path = data[i]['name'];
127 }
127 }
128 else {
128 else {
129 nb_path = data[i]['notebook_path'] + data[i]['notebook_name'];
129 nb_path = data[i]['path'] + data[i]['name'];
130 }
130 }
131 this.sessions[nb_path]= data[i]['session_id'];
131 this.sessions[nb_path]= data[i]['id'];
132 }
132 }
133 };
133 };
134 this.load_list();
134 this.load_list();
135 };
135 };
136
136
137 NotebookList.prototype.load_list = function () {
137 NotebookList.prototype.load_list = function () {
138 var that = this;
138 var that = this;
139 var settings = {
139 var settings = {
140 processData : false,
140 processData : false,
141 cache : false,
141 cache : false,
142 type : "GET",
142 type : "GET",
143 dataType : "json",
143 dataType : "json",
144 success : $.proxy(this.list_loaded, this),
144 success : $.proxy(this.list_loaded, this),
145 error : $.proxy( function(){
145 error : $.proxy( function(){
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
147 },this)
147 },this)
148 };
148 };
149
149
150 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath();
150 var url = this.baseProjectUrl() + 'api/notebooks/' + this.notebookPath();
151 $.ajax(url, settings);
151 $.ajax(url, settings);
152 };
152 };
153
153
154
154
155 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
155 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
156 var message = 'Notebook list empty.';
156 var message = 'Notebook list empty.';
157 if (param !== undefined && param.msg) {
157 if (param !== undefined && param.msg) {
158 var message = param.msg;
158 var message = param.msg;
159 }
159 }
160 var len = data.length;
160 var len = data.length;
161 this.clear_list();
161 this.clear_list();
162 if(len == 0)
162 if(len == 0)
163 {
163 {
164 $(this.new_notebook_item(0))
164 $(this.new_notebook_item(0))
165 .append(
165 .append(
166 $('<div style="margin:auto;text-align:center;color:grey"/>')
166 $('<div style="margin:auto;text-align:center;color:grey"/>')
167 .text(message)
167 .text(message)
168 )
168 )
169 }
169 }
170 for (var i=0; i<len; i++) {
170 for (var i=0; i<len; i++) {
171 var name = data[i].notebook_name;
171 var name = data[i].name;
172 var path = this.notebookPath();
172 var path = this.notebookPath();
173 var nbname = name.split(".")[0];
173 var nbname = name.split(".")[0];
174 var item = this.new_notebook_item(i);
174 var item = this.new_notebook_item(i);
175 this.add_link(path, nbname, item);
175 this.add_link(path, nbname, item);
176 name = this.notebookPath() + name;
176 name = this.notebookPath() + name;
177 if(this.sessions[name] == undefined){
177 if(this.sessions[name] == undefined){
178 this.add_delete_button(item);
178 this.add_delete_button(item);
179 } else {
179 } else {
180 this.add_shutdown_button(item,this.sessions[name]);
180 this.add_shutdown_button(item,this.sessions[name]);
181 }
181 }
182 };
182 };
183 };
183 };
184
184
185
185
186 NotebookList.prototype.new_notebook_item = function (index) {
186 NotebookList.prototype.new_notebook_item = function (index) {
187 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
187 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
188 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
188 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
189 // item.css('border-top-style','none');
189 // item.css('border-top-style','none');
190 item.append($("<div/>").addClass("span12").append(
190 item.append($("<div/>").addClass("span12").append(
191 $("<a/>").addClass("item_link").append(
191 $("<a/>").addClass("item_link").append(
192 $("<span/>").addClass("item_name")
192 $("<span/>").addClass("item_name")
193 )
193 )
194 ).append(
194 ).append(
195 $('<div/>').addClass("item_buttons btn-group pull-right")
195 $('<div/>').addClass("item_buttons btn-group pull-right")
196 ));
196 ));
197
197
198 if (index === -1) {
198 if (index === -1) {
199 this.element.append(item);
199 this.element.append(item);
200 } else {
200 } else {
201 this.element.children().eq(index).after(item);
201 this.element.children().eq(index).after(item);
202 }
202 }
203 return item;
203 return item;
204 };
204 };
205
205
206
206
207 NotebookList.prototype.add_link = function (path, nbname, item) {
207 NotebookList.prototype.add_link = function (path, nbname, item) {
208 item.data('nbname', nbname);
208 item.data('nbname', nbname);
209 item.data('path', path);
209 item.data('path', path);
210 item.find(".item_name").text(nbname);
210 item.find(".item_name").text(nbname);
211 item.find("a.item_link")
211 item.find("a.item_link")
212 .attr('href', this.baseProjectUrl() + "notebooks/" + this.notebookPath() + nbname + ".ipynb")
212 .attr('href', this.baseProjectUrl() + "notebooks/" + this.notebookPath() + nbname + ".ipynb")
213 .attr('target','_blank');
213 .attr('target','_blank');
214 };
214 };
215
215
216
216
217 NotebookList.prototype.add_name_input = function (nbname, item) {
217 NotebookList.prototype.add_name_input = function (nbname, item) {
218 item.data('nbname', nbname);
218 item.data('nbname', nbname);
219 item.find(".item_name").empty().append(
219 item.find(".item_name").empty().append(
220 $('<input/>')
220 $('<input/>')
221 .addClass("nbname_input")
221 .addClass("nbname_input")
222 .attr('value', nbname)
222 .attr('value', nbname)
223 .attr('size', '30')
223 .attr('size', '30')
224 .attr('type', 'text')
224 .attr('type', 'text')
225 );
225 );
226 };
226 };
227
227
228
228
229 NotebookList.prototype.add_notebook_data = function (data, item) {
229 NotebookList.prototype.add_notebook_data = function (data, item) {
230 item.data('nbdata',data);
230 item.data('nbdata',data);
231 };
231 };
232
232
233
233
234 NotebookList.prototype.add_shutdown_button = function (item, session) {
234 NotebookList.prototype.add_shutdown_button = function (item, session) {
235 var that = this;
235 var that = this;
236 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
236 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
237 click(function (e) {
237 click(function (e) {
238 var settings = {
238 var settings = {
239 processData : false,
239 processData : false,
240 cache : false,
240 cache : false,
241 type : "DELETE",
241 type : "DELETE",
242 dataType : "json",
242 dataType : "json",
243 success : function () {
243 success : function () {
244 that.load_sessions();
244 that.load_sessions();
245 }
245 }
246 };
246 };
247 var url = that.baseProjectUrl() + 'api/sessions/' + session;
247 var url = that.baseProjectUrl() + 'api/sessions/' + session;
248 $.ajax(url, settings);
248 $.ajax(url, settings);
249 return false;
249 return false;
250 });
250 });
251 // var new_buttons = item.find('a'); // shutdown_button;
251 // var new_buttons = item.find('a'); // shutdown_button;
252 item.find(".item_buttons").html("").append(shutdown_button);
252 item.find(".item_buttons").html("").append(shutdown_button);
253 };
253 };
254
254
255 NotebookList.prototype.add_delete_button = function (item) {
255 NotebookList.prototype.add_delete_button = function (item) {
256 var new_buttons = $('<span/>').addClass("btn-group pull-right");
256 var new_buttons = $('<span/>').addClass("btn-group pull-right");
257 var notebooklist = this;
257 var notebooklist = this;
258 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
258 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
259 click(function (e) {
259 click(function (e) {
260 // $(this) is the button that was clicked.
260 // $(this) is the button that was clicked.
261 var that = $(this);
261 var that = $(this);
262 // We use the nbname and notebook_id from the parent notebook_item element's
262 // We use the nbname and notebook_id from the parent notebook_item element's
263 // data because the outer scopes values change as we iterate through the loop.
263 // data because the outer scopes values change as we iterate through the loop.
264 var parent_item = that.parents('div.list_item');
264 var parent_item = that.parents('div.list_item');
265 var nbname = parent_item.data('nbname');
265 var nbname = parent_item.data('nbname');
266 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
266 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
267 IPython.dialog.modal({
267 IPython.dialog.modal({
268 title : "Delete notebook",
268 title : "Delete notebook",
269 body : message,
269 body : message,
270 buttons : {
270 buttons : {
271 Delete : {
271 Delete : {
272 class: "btn-danger",
272 class: "btn-danger",
273 click: function() {
273 click: function() {
274 var settings = {
274 var settings = {
275 processData : false,
275 processData : false,
276 cache : false,
276 cache : false,
277 type : "DELETE",
277 type : "DELETE",
278 dataType : "json",
278 dataType : "json",
279 success : function (data, status, xhr) {
279 success : function (data, status, xhr) {
280 parent_item.remove();
280 parent_item.remove();
281 }
281 }
282 };
282 };
283 var url = notebooklist.baseProjectUrl() + 'api/notebooks/' + notebooklist.notebookPath() + nbname + '.ipynb';
283 var url = notebooklist.baseProjectUrl() + 'api/notebooks/' + notebooklist.notebookPath() + nbname + '.ipynb';
284 $.ajax(url, settings);
284 $.ajax(url, settings);
285 }
285 }
286 },
286 },
287 Cancel : {}
287 Cancel : {}
288 }
288 }
289 });
289 });
290 return false;
290 return false;
291 });
291 });
292 item.find(".item_buttons").html("").append(delete_button);
292 item.find(".item_buttons").html("").append(delete_button);
293 };
293 };
294
294
295
295
296 NotebookList.prototype.add_upload_button = function (item) {
296 NotebookList.prototype.add_upload_button = function (item) {
297 var that = this;
297 var that = this;
298 var upload_button = $('<button/>').text("Upload")
298 var upload_button = $('<button/>').text("Upload")
299 .addClass('btn btn-primary btn-mini upload_button')
299 .addClass('btn btn-primary btn-mini upload_button')
300 .click(function (e) {
300 .click(function (e) {
301 var nbname = item.find('.item_name > input').attr('value');
301 var nbname = item.find('.item_name > input').attr('value');
302 var nbformat = item.data('nbformat');
302 var nbformat = item.data('nbformat');
303 var nbdata = item.data('nbdata');
303 var nbdata = item.data('nbdata');
304 var content_type = 'text/plain';
304 var content_type = 'text/plain';
305 if (nbformat === 'json') {
305 if (nbformat === 'json') {
306 content_type = 'application/json';
306 content_type = 'application/json';
307 } else if (nbformat === 'py') {
307 } else if (nbformat === 'py') {
308 content_type = 'application/x-python';
308 content_type = 'application/x-python';
309 };
309 };
310 var settings = {
310 var settings = {
311 processData : false,
311 processData : false,
312 cache : false,
312 cache : false,
313 type : 'POST',
313 type : 'POST',
314 dataType : 'json',
314 dataType : 'json',
315 data : nbdata,
315 data : nbdata,
316 headers : {'Content-Type': content_type},
316 headers : {'Content-Type': content_type},
317 success : function (data, status, xhr) {
317 success : function (data, status, xhr) {
318 that.add_link(data, nbname, item);
318 that.add_link(data, nbname, item);
319 that.add_delete_button(item);
319 that.add_delete_button(item);
320 }
320 }
321 };
321 };
322
322
323 var qs = $.param({name:nbname, format:nbformat});
323 var qs = $.param({name:nbname, format:nbformat});
324 var url = that.baseProjectUrl() + 'notebooks?' + qs;
324 var url = that.baseProjectUrl() + 'notebooks?' + qs;
325 $.ajax(url, settings);
325 $.ajax(url, settings);
326 return false;
326 return false;
327 });
327 });
328 var cancel_button = $('<button/>').text("Cancel")
328 var cancel_button = $('<button/>').text("Cancel")
329 .addClass("btn btn-mini")
329 .addClass("btn btn-mini")
330 .click(function (e) {
330 .click(function (e) {
331 console.log('cancel click');
331 console.log('cancel click');
332 item.remove();
332 item.remove();
333 return false;
333 return false;
334 });
334 });
335 item.find(".item_buttons").empty()
335 item.find(".item_buttons").empty()
336 .append(upload_button)
336 .append(upload_button)
337 .append(cancel_button);
337 .append(cancel_button);
338 };
338 };
339
339
340
340
341 IPython.NotebookList = NotebookList;
341 IPython.NotebookList = NotebookList;
342
342
343 return IPython;
343 return IPython;
344
344
345 }(IPython));
345 }(IPython));
346
346
General Comments 0
You need to be logged in to leave comments. Login now