mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-15 04:00:34 +08:00
Merge branch 'master' of https://github.com/jupyter/notebook
This commit is contained in:
commit
7d6c1c4774
@ -1,5 +1,6 @@
|
||||
# http://travis-ci.org/#!/ipython/ipython
|
||||
language: python
|
||||
group: edge
|
||||
cache:
|
||||
directories:
|
||||
- ~/.cache/bower
|
||||
|
224
docs/source/extending/frontend_extensions.rst
Normal file
224
docs/source/extending/frontend_extensions.rst
Normal file
@ -0,0 +1,224 @@
|
||||
Custom front-end extensions
|
||||
===========================
|
||||
|
||||
This describes the basic steps to write a JavaScript extension for the Jupyter
|
||||
notebook front-end. This allows you to customize the behaviour of the various
|
||||
pages like the dashboard, the notebook, or the text editor.
|
||||
|
||||
The structure of a front-end extension
|
||||
--------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
The notebook front-end and Javascript API are not stable, and are subject
|
||||
to a lot of changes. Any extension written for the current notebook is
|
||||
almost guaranteed to break in the next release.
|
||||
|
||||
.. _AMD module: https://en.wikipedia.org/wiki/Asynchronous_module_definition
|
||||
|
||||
A front-end extension is a JavaScript file that defines an `AMD module`_
|
||||
which exposes at least a function called ``load_ipython_extension``, which
|
||||
takes no arguments. We will not get into the details of what each of these
|
||||
terms consists of yet, but here is the minimal code needed for a working
|
||||
extension:
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
// file my_extension/main.js
|
||||
|
||||
define(function(){
|
||||
|
||||
function load_ipython_extension(){
|
||||
console.info('this is my first extension');
|
||||
}
|
||||
|
||||
return {
|
||||
load_ipython_extension: load_ipython_extension
|
||||
};
|
||||
});
|
||||
|
||||
.. note::
|
||||
|
||||
Although for historical reasons the function is called
|
||||
``load_ipython_extension``, it does apply to the Jupyter notebook in
|
||||
general, and will work regardless of the kernel in use.
|
||||
|
||||
If you are familiar with JavaScript, you can use this template to require any
|
||||
Jupyter module and modify its configuration, or do anything else in client-side
|
||||
Javascript. Your extension will be loaded at the right time during the notebook
|
||||
page initialisation for you to set up a listener for the various events that
|
||||
the page can trigger.
|
||||
|
||||
You might want access to the current instances of the various Jupyter notebook
|
||||
components on the page, as opposed to the classes defined in the modules. The
|
||||
current instances are exposed by a module named ``base/js/namespace``. If you
|
||||
plan on accessing instances on the page, you should ``require`` this module
|
||||
rather than accessing the global variable ``Jupyter``, which will be removed in
|
||||
future. The following example demonstrates how to access the current notebook
|
||||
instance:
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
// file my_extension/main.js
|
||||
|
||||
define([
|
||||
'base/js/namespace'
|
||||
], function(
|
||||
Jupyter
|
||||
) {
|
||||
function load_ipython_extension() {
|
||||
console.log(
|
||||
'This is the current notebook application instance:',
|
||||
Jupyter.notebook
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
load_ipython_extension: load_ipython_extension
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Modifying key bindings
|
||||
----------------------
|
||||
|
||||
One of the abilities of extensions is to modify key bindings, although once
|
||||
again this is an API which is not guaranteed to be stable. However, custom key
|
||||
bindings are frequently requested, and are helpful to increase accessibility,
|
||||
so in the following we show how to access them.
|
||||
|
||||
Here is an example of an extension that will unbind the shortcut ``0,0`` in
|
||||
command mode, which normally restarts the kernel, and bind ``0,0,0`` in its
|
||||
place:
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
// file my_extension/main.js
|
||||
|
||||
define([
|
||||
'base/js/namespace'
|
||||
], function(
|
||||
Jupyter
|
||||
) {
|
||||
|
||||
function load_ipython_extension() {
|
||||
Jupyter.keyboard_manager.command_shortcuts.remove_shortcut('0,0');
|
||||
Jupyter.keyboard_manager.command_shortcuts.add_shortcut('0,0,0', 'jupyter-notebook:restart-kernel');
|
||||
}
|
||||
|
||||
return {
|
||||
load_ipython_extension: load_ipython_extension
|
||||
};
|
||||
});
|
||||
|
||||
.. note::
|
||||
|
||||
The standard keybindings might not work correctly on non-US keyboards.
|
||||
Unfortunately, this is a limitation of browser implementations and the
|
||||
status of keyboard event handling on the web in general. We appreciate your
|
||||
feedback if you have issues binding keys, or have any ideas to help improve
|
||||
the situation.
|
||||
|
||||
You can see that I have used the **action name**
|
||||
``jupyter-notebook:restart-kernel`` to bind the new shortcut. There is no API
|
||||
yet to access the list of all available *actions*, though the following in the
|
||||
JavaScript console of your browser on a notebook page should give you an idea
|
||||
of what is available:
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
Object.keys(require('base/js/namespace').actions._actions);
|
||||
|
||||
In this example, we changed a keyboard shortcut in **command mode**; you
|
||||
can also customize keyboard shortcuts in **edit mode**.
|
||||
However, most of the keyboard shortcuts in edit mode are handled by CodeMirror,
|
||||
which supports custom key bindings via a completely different API.
|
||||
|
||||
|
||||
Defining and registering your own actions
|
||||
-----------------------------------------
|
||||
|
||||
As part of your front-end extension, you may wish to define actions, which can
|
||||
be attached to toolbar buttons, or called from the command palette. Here is an
|
||||
example of an extension that defines a (not very useful!) action to show an
|
||||
alert, and adds a toolabr button using the full action name:
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
// file my_extension/main.js
|
||||
|
||||
define([
|
||||
'base/js/namespace'
|
||||
], function(
|
||||
Jupyter
|
||||
) {
|
||||
function load_ipython_extension() {
|
||||
|
||||
var handler = function () {
|
||||
alert('this is an alert from my_extension!');
|
||||
};
|
||||
|
||||
var action = {
|
||||
icon: 'fa-comment-o', // a font-awesome class used on buttons, etc
|
||||
help : 'Show an alert',
|
||||
help_index : 'zz',
|
||||
handler : handler
|
||||
};
|
||||
var prefix = 'my_extension';
|
||||
var action_name = 'show-alert';
|
||||
|
||||
var full_action_name = Jupyter.actions.register(action, name, prefix); // returns 'my_extension:show-alert'
|
||||
Jupyter.toolbar.add_buttons_group([full_action_name]);
|
||||
}
|
||||
|
||||
return {
|
||||
load_ipython_extension: load_ipython_extension
|
||||
};
|
||||
});
|
||||
|
||||
Every action needs a name, which, when joined with its prefix to make the full
|
||||
action name, should be unique. Built-in actions, like the
|
||||
``jupyter-notebook:restart-kernel`` we bound in the earlier
|
||||
`Modifying key bindings`_ example, use the prefix ``jupyter-notebook``. For
|
||||
actions defined in an extension, it makes sense to use the extension name as
|
||||
the prefix. For the action name, the following guidelines should be considered:
|
||||
|
||||
.. adapted from notebook/static/notebook/js/actions.js
|
||||
* First pick a noun and a verb for the action. For example, if the action is
|
||||
"restart kernel," the verb is "restart" and the noun is "kernel".
|
||||
* Omit terms like "selected" and "active" by default, so "delete-cell", rather
|
||||
than "delete-selected-cell". Only provide a scope like "-all-" if it is other
|
||||
than the default "selected" or "active" scope.
|
||||
* If an action has a secondary action, separate the secondary action with
|
||||
"-and-", so "restart-kernel-and-clear-output".
|
||||
* Use above/below or previous/next to indicate spatial and sequential
|
||||
relationships.
|
||||
* Don't ever use before/after as they have a temporal connotation that is
|
||||
confusing when used in a spatial context.
|
||||
* For dialogs, use a verb that indicates what the dialog will accomplish, such
|
||||
as "confirm-restart-kernel".
|
||||
|
||||
|
||||
Installing and enabling extensions
|
||||
----------------------------------
|
||||
|
||||
You can install your nbextension with the command:
|
||||
|
||||
jupyter nbextension install path/to/my_extension/
|
||||
|
||||
Where my_extension is the directory containing the Javascript files.
|
||||
This will copy it to a Jupyter data directory (the exact location is platform
|
||||
dependent - see :ref:`jupyter_path`).
|
||||
|
||||
For development, you can use the ``--symlink`` flag to symlink your extension
|
||||
rather than copying it, so there's no need to reinstall after changes.
|
||||
|
||||
To use your extension, you'll also need to **enable** it, which tells the
|
||||
notebook interface to load it. You can do that with another command:
|
||||
|
||||
jupyter nbextension enable my_extension/main
|
||||
|
||||
The argument refers to the Javascript module containing your
|
||||
``load_ipython_extension`` function, which is ``my_extension/main.js`` in this
|
||||
example. There is a corresponding ``disable`` command to stop using an
|
||||
extension without uninstalling it.
|
@ -43,6 +43,6 @@ def log_request(handler):
|
||||
msg = msg + ' referer={referer}'
|
||||
if status >= 500 and status != 502:
|
||||
# log all headers if it caused an error
|
||||
log_method(json.dumps(request.headers, indent=2))
|
||||
log_method(json.dumps(dict(request.headers), indent=2))
|
||||
log_method(msg.format(**ns))
|
||||
|
||||
|
@ -120,7 +120,7 @@ class DeprecationHandler(IPythonHandler):
|
||||
def get(self, url_path):
|
||||
self.set_header("Content-Type", 'text/javascript')
|
||||
self.finish("""
|
||||
console.warn('`/static/widgets/js` is deprecated. Use `/nbextensions/widgets/widgets/js` instead.');
|
||||
console.warn('`/static/widgets/js` is deprecated. Use `nbextensions/widgets/widgets/js` instead.');
|
||||
define(['%s'], function(x) { return x; });
|
||||
""" % url_path_join('nbextensions', 'widgets', 'widgets', url_path.rstrip('.js')))
|
||||
self.log.warn('Deprecated widget Javascript path /static/widgets/js/*.js was used')
|
||||
|
@ -6,6 +6,7 @@ import os
|
||||
import sys
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from itertools import combinations
|
||||
|
||||
from nose import SkipTest
|
||||
from tornado.web import HTTPError
|
||||
@ -188,6 +189,41 @@ class TestFileContentsManager(TestCase):
|
||||
|
||||
|
||||
class TestContentsManager(TestCase):
|
||||
@contextmanager
|
||||
def assertRaisesHTTPError(self, status, msg=None):
|
||||
msg = msg or "Should have raised HTTPError(%i)" % status
|
||||
try:
|
||||
yield
|
||||
except HTTPError as e:
|
||||
self.assertEqual(e.status_code, status)
|
||||
else:
|
||||
self.fail(msg)
|
||||
|
||||
def make_populated_dir(self, api_path):
|
||||
cm = self.contents_manager
|
||||
|
||||
self.make_dir(api_path)
|
||||
|
||||
cm.new(path="/".join([api_path, "nb.ipynb"]))
|
||||
cm.new(path="/".join([api_path, "file.txt"]))
|
||||
|
||||
def check_populated_dir_files(self, api_path):
|
||||
dir_model = self.contents_manager.get(api_path)
|
||||
|
||||
self.assertEqual(dir_model['path'], api_path)
|
||||
self.assertEqual(dir_model['type'], "directory")
|
||||
|
||||
for entry in dir_model['content']:
|
||||
if entry['type'] == "directory":
|
||||
continue
|
||||
elif entry['type'] == "file":
|
||||
self.assertEqual(entry['name'], "file.txt")
|
||||
complete_path = "/".join([api_path, "file.txt"])
|
||||
self.assertEqual(entry["path"], complete_path)
|
||||
elif entry['type'] == "notebook":
|
||||
self.assertEqual(entry['name'], "nb.ipynb")
|
||||
complete_path = "/".join([api_path, "nb.ipynb"])
|
||||
self.assertEqual(entry["path"], complete_path)
|
||||
|
||||
def setUp(self):
|
||||
self._temp_dir = TemporaryDirectory()
|
||||
@ -460,6 +496,55 @@ class TestContentsManager(TestCase):
|
||||
# Check that a 'get' on the deleted notebook raises and error
|
||||
self.assertRaises(HTTPError, cm.get, path)
|
||||
|
||||
def test_rename(self):
|
||||
cm = self.contents_manager
|
||||
# Create a new notebook
|
||||
nb, name, path = self.new_notebook()
|
||||
|
||||
# Rename the notebook
|
||||
cm.rename(path, "changed_path")
|
||||
|
||||
# Attempting to get the notebook under the old name raises an error
|
||||
self.assertRaises(HTTPError, cm.get, path)
|
||||
# Fetching the notebook under the new name is successful
|
||||
assert isinstance(cm.get("changed_path"), dict)
|
||||
|
||||
# Ported tests on nested directory renaming from pgcontents
|
||||
all_dirs = ['foo', 'bar', 'foo/bar', 'foo/bar/foo', 'foo/bar/foo/bar']
|
||||
unchanged_dirs = all_dirs[:2]
|
||||
changed_dirs = all_dirs[2:]
|
||||
|
||||
for _dir in all_dirs:
|
||||
self.make_populated_dir(_dir)
|
||||
self.check_populated_dir_files(_dir)
|
||||
|
||||
# Renaming to an existing directory should fail
|
||||
for src, dest in combinations(all_dirs, 2):
|
||||
with self.assertRaisesHTTPError(409):
|
||||
cm.rename(src, dest)
|
||||
|
||||
# Creating a notebook in a non_existant directory should fail
|
||||
with self.assertRaisesHTTPError(404):
|
||||
cm.new_untitled("foo/bar_diff", ext=".ipynb")
|
||||
|
||||
cm.rename("foo/bar", "foo/bar_diff")
|
||||
|
||||
# Assert that unchanged directories remain so
|
||||
for unchanged in unchanged_dirs:
|
||||
self.check_populated_dir_files(unchanged)
|
||||
|
||||
# Assert changed directories can no longer be accessed under old names
|
||||
for changed_dirname in changed_dirs:
|
||||
with self.assertRaisesHTTPError(404):
|
||||
cm.get(changed_dirname)
|
||||
|
||||
new_dirname = changed_dirname.replace("foo/bar", "foo/bar_diff", 1)
|
||||
|
||||
self.check_populated_dir_files(new_dirname)
|
||||
|
||||
# Created a notebook in the renamed directory should work
|
||||
cm.new_untitled("foo/bar_diff", ext=".ipynb")
|
||||
|
||||
def test_delete_root(self):
|
||||
cm = self.contents_manager
|
||||
with self.assertRaises(HTTPError) as err:
|
||||
|
@ -25,12 +25,15 @@ class MainKernelHandler(APIHandler):
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
km = self.kernel_manager
|
||||
self.finish(json.dumps(km.list_kernels()))
|
||||
kernels = yield gen.maybe_future(km.list_kernels())
|
||||
self.finish(json.dumps(kernels))
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def post(self):
|
||||
km = self.kernel_manager
|
||||
model = self.get_json_body()
|
||||
@ -41,7 +44,7 @@ class MainKernelHandler(APIHandler):
|
||||
else:
|
||||
model.setdefault('name', km.default_kernel_name)
|
||||
|
||||
kernel_id = km.start_kernel(kernel_name=model['name'])
|
||||
kernel_id = yield gen.maybe_future(km.start_kernel(kernel_name=model['name']))
|
||||
model = km.kernel_model(kernel_id)
|
||||
location = url_path_join(self.base_url, 'api', 'kernels', url_escape(kernel_id))
|
||||
self.set_header('Location', location)
|
||||
@ -61,9 +64,10 @@ class KernelHandler(APIHandler):
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def delete(self, kernel_id):
|
||||
km = self.kernel_manager
|
||||
km.shutdown_kernel(kernel_id)
|
||||
yield gen.maybe_future(km.shutdown_kernel(kernel_id))
|
||||
self.set_status(204)
|
||||
self.finish()
|
||||
|
||||
|
@ -62,7 +62,8 @@ class MappingKernelManager(MultiKernelManager):
|
||||
while not os.path.isdir(os_path) and os_path != self.root_dir:
|
||||
os_path = os.path.dirname(os_path)
|
||||
return os_path
|
||||
|
||||
|
||||
@gen.coroutine
|
||||
def start_kernel(self, kernel_id=None, path=None, **kwargs):
|
||||
"""Start a kernel for a session and return its kernel_id.
|
||||
|
||||
@ -82,8 +83,9 @@ class MappingKernelManager(MultiKernelManager):
|
||||
if kernel_id is None:
|
||||
if path is not None:
|
||||
kwargs['cwd'] = self.cwd_for_path(path)
|
||||
kernel_id = super(MappingKernelManager, self).start_kernel(
|
||||
**kwargs)
|
||||
kernel_id = yield gen.maybe_future(
|
||||
super(MappingKernelManager, self).start_kernel(**kwargs)
|
||||
)
|
||||
self.log.info("Kernel started: %s" % kernel_id)
|
||||
self.log.debug("Kernel args: %r" % kwargs)
|
||||
# register callback for failed auto-restart
|
||||
@ -94,12 +96,13 @@ class MappingKernelManager(MultiKernelManager):
|
||||
else:
|
||||
self._check_kernel_id(kernel_id)
|
||||
self.log.info("Using existing kernel: %s" % kernel_id)
|
||||
return kernel_id
|
||||
# py2-compat
|
||||
raise gen.Return(kernel_id)
|
||||
|
||||
def shutdown_kernel(self, kernel_id, now=False):
|
||||
"""Shutdown a kernel by kernel_id"""
|
||||
self._check_kernel_id(kernel_id)
|
||||
super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
|
||||
return super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
|
||||
|
||||
def restart_kernel(self, kernel_id):
|
||||
"""Restart a kernel by kernel_id"""
|
||||
|
@ -8,7 +8,7 @@ Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-16%3A-
|
||||
|
||||
import json
|
||||
|
||||
from tornado import web
|
||||
from tornado import gen, web
|
||||
|
||||
from ...base.handlers import APIHandler, json_errors
|
||||
from jupyter_client.jsonutil import date_default
|
||||
@ -20,20 +20,20 @@ class SessionRootHandler(APIHandler):
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def get(self):
|
||||
# Return a list of running sessions
|
||||
sm = self.session_manager
|
||||
sessions = sm.list_sessions()
|
||||
sessions = yield gen.maybe_future(sm.list_sessions())
|
||||
self.finish(json.dumps(sessions, default=date_default))
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def post(self):
|
||||
# Creates a new session
|
||||
#(unless a session already exists for the named nb)
|
||||
sm = self.session_manager
|
||||
cm = self.contents_manager
|
||||
km = self.kernel_manager
|
||||
|
||||
model = self.get_json_body()
|
||||
if model is None:
|
||||
@ -49,11 +49,13 @@ class SessionRootHandler(APIHandler):
|
||||
kernel_name = None
|
||||
|
||||
# Check to see if session exists
|
||||
if sm.session_exists(path=path):
|
||||
model = sm.get_session(path=path)
|
||||
exists = yield gen.maybe_future(sm.session_exists(path=path))
|
||||
if exists:
|
||||
model = yield gen.maybe_future(sm.get_session(path=path))
|
||||
else:
|
||||
try:
|
||||
model = sm.create_session(path=path, kernel_name=kernel_name)
|
||||
model = yield gen.maybe_future(
|
||||
sm.create_session(path=path, kernel_name=kernel_name))
|
||||
except NoSuchKernel:
|
||||
msg = ("The '%s' kernel is not available. Please pick another "
|
||||
"suitable kernel instead, or install that kernel." % kernel_name)
|
||||
@ -73,14 +75,16 @@ class SessionHandler(APIHandler):
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def get(self, session_id):
|
||||
# Returns the JSON model for a single session
|
||||
sm = self.session_manager
|
||||
model = sm.get_session(session_id=session_id)
|
||||
model = yield gen.maybe_future(sm.get_session(session_id=session_id))
|
||||
self.finish(json.dumps(model, default=date_default))
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def patch(self, session_id):
|
||||
# Currently, this handler is strictly for renaming notebooks
|
||||
sm = self.session_manager
|
||||
@ -93,17 +97,18 @@ class SessionHandler(APIHandler):
|
||||
if 'path' in notebook:
|
||||
changes['path'] = notebook['path']
|
||||
|
||||
sm.update_session(session_id, **changes)
|
||||
model = sm.get_session(session_id=session_id)
|
||||
yield gen.maybe_future(sm.update_session(session_id, **changes))
|
||||
model = yield gen.maybe_future(sm.get_session(session_id=session_id))
|
||||
self.finish(json.dumps(model, default=date_default))
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
@gen.coroutine
|
||||
def delete(self, session_id):
|
||||
# Deletes the session with given session_id
|
||||
sm = self.session_manager
|
||||
try:
|
||||
sm.delete_session(session_id)
|
||||
yield gen.maybe_future(sm.delete_session(session_id))
|
||||
except KeyError:
|
||||
# the kernel was deleted but the session wasn't!
|
||||
raise web.HTTPError(410, "Kernel deleted before session")
|
||||
|
@ -6,7 +6,7 @@
|
||||
import uuid
|
||||
import sqlite3
|
||||
|
||||
from tornado import web
|
||||
from tornado import gen, web
|
||||
|
||||
from traitlets.config.configurable import LoggingConfigurable
|
||||
from ipython_genutils.py3compat import unicode_type
|
||||
@ -39,10 +39,16 @@ class SessionManager(LoggingConfigurable):
|
||||
self._connection = sqlite3.connect(':memory:')
|
||||
self._connection.row_factory = sqlite3.Row
|
||||
return self._connection
|
||||
|
||||
|
||||
def close(self):
|
||||
"""Close the sqlite connection"""
|
||||
if self._cursor is not None:
|
||||
self._cursor.close()
|
||||
self._cursor = None
|
||||
|
||||
def __del__(self):
|
||||
"""Close connection once SessionManager closes"""
|
||||
self.cursor.close()
|
||||
self.close()
|
||||
|
||||
def session_exists(self, path):
|
||||
"""Check to see if the session for a given notebook exists"""
|
||||
@ -56,17 +62,22 @@ class SessionManager(LoggingConfigurable):
|
||||
def new_session_id(self):
|
||||
"Create a uuid for a new session"
|
||||
return unicode_type(uuid.uuid4())
|
||||
|
||||
|
||||
@gen.coroutine
|
||||
def create_session(self, path=None, kernel_name=None):
|
||||
"""Creates a session and returns its model"""
|
||||
session_id = self.new_session_id()
|
||||
# allow nbm to specify kernels cwd
|
||||
kernel_path = self.contents_manager.get_kernel_path(path=path)
|
||||
kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
|
||||
kernel_name=kernel_name)
|
||||
return self.save_session(session_id, path=path,
|
||||
kernel_id=kernel_id)
|
||||
|
||||
kernel_id = yield gen.maybe_future(
|
||||
self.kernel_manager.start_kernel(path=kernel_path, kernel_name=kernel_name)
|
||||
)
|
||||
result = yield gen.maybe_future(
|
||||
self.save_session(session_id, path=path, kernel_id=kernel_id)
|
||||
)
|
||||
# py2-compat
|
||||
raise gen.Return(result)
|
||||
|
||||
def save_session(self, session_id, path=None, kernel_id=None):
|
||||
"""Saves the items for the session with the given session_id
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from tornado import web
|
||||
from tornado import gen, web
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from ..sessionmanager import SessionManager
|
||||
from notebook.services.kernels.kernelmanager import MappingKernelManager
|
||||
@ -37,11 +38,27 @@ class TestSessionManager(TestCase):
|
||||
kernel_manager=DummyMKM(),
|
||||
contents_manager=ContentsManager(),
|
||||
)
|
||||
|
||||
self.loop = IOLoop()
|
||||
|
||||
def tearDown(self):
|
||||
self.loop.close(all_fds=True)
|
||||
|
||||
def create_sessions(self, *kwarg_list):
|
||||
@gen.coroutine
|
||||
def co_add():
|
||||
sessions = []
|
||||
for kwargs in kwarg_list:
|
||||
session = yield self.sm.create_session(**kwargs)
|
||||
sessions.append(session)
|
||||
raise gen.Return(sessions)
|
||||
return self.loop.run_sync(co_add)
|
||||
|
||||
def create_session(self, **kwargs):
|
||||
return self.create_sessions(kwargs)[0]
|
||||
|
||||
def test_get_session(self):
|
||||
sm = self.sm
|
||||
session_id = sm.create_session(path='/path/to/test.ipynb',
|
||||
kernel_name='bar')['id']
|
||||
session_id = self.create_session(path='/path/to/test.ipynb', kernel_name='bar')['id']
|
||||
model = sm.get_session(session_id=session_id)
|
||||
expected = {'id':session_id,
|
||||
'notebook':{'path': u'/path/to/test.ipynb'},
|
||||
@ -51,13 +68,13 @@ class TestSessionManager(TestCase):
|
||||
def test_bad_get_session(self):
|
||||
# Should raise error if a bad key is passed to the database.
|
||||
sm = self.sm
|
||||
session_id = sm.create_session(path='/path/to/test.ipynb',
|
||||
session_id = self.create_session(path='/path/to/test.ipynb',
|
||||
kernel_name='foo')['id']
|
||||
self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
|
||||
|
||||
def test_get_session_dead_kernel(self):
|
||||
sm = self.sm
|
||||
session = sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python')
|
||||
session = self.create_session(path='/path/to/1/test1.ipynb', kernel_name='python')
|
||||
# kill the kernel
|
||||
sm.kernel_manager.shutdown_kernel(session['kernel']['id'])
|
||||
with self.assertRaises(KeyError):
|
||||
@ -68,11 +85,12 @@ class TestSessionManager(TestCase):
|
||||
|
||||
def test_list_sessions(self):
|
||||
sm = self.sm
|
||||
sessions = [
|
||||
sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'),
|
||||
sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'),
|
||||
sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'),
|
||||
]
|
||||
sessions = self.create_sessions(
|
||||
dict(path='/path/to/1/test1.ipynb', kernel_name='python'),
|
||||
dict(path='/path/to/2/test2.ipynb', kernel_name='python'),
|
||||
dict(path='/path/to/3/test3.ipynb', kernel_name='python'),
|
||||
)
|
||||
|
||||
sessions = sm.list_sessions()
|
||||
expected = [
|
||||
{
|
||||
@ -93,10 +111,10 @@ class TestSessionManager(TestCase):
|
||||
|
||||
def test_list_sessions_dead_kernel(self):
|
||||
sm = self.sm
|
||||
sessions = [
|
||||
sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'),
|
||||
sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'),
|
||||
]
|
||||
sessions = self.create_sessions(
|
||||
dict(path='/path/to/1/test1.ipynb', kernel_name='python'),
|
||||
dict(path='/path/to/2/test2.ipynb', kernel_name='python'),
|
||||
)
|
||||
# kill one of the kernels
|
||||
sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id'])
|
||||
listed = sm.list_sessions()
|
||||
@ -116,7 +134,7 @@ class TestSessionManager(TestCase):
|
||||
|
||||
def test_update_session(self):
|
||||
sm = self.sm
|
||||
session_id = sm.create_session(path='/path/to/test.ipynb',
|
||||
session_id = self.create_session(path='/path/to/test.ipynb',
|
||||
kernel_name='julia')['id']
|
||||
sm.update_session(session_id, path='/path/to/new_name.ipynb')
|
||||
model = sm.get_session(session_id=session_id)
|
||||
@ -128,17 +146,17 @@ class TestSessionManager(TestCase):
|
||||
def test_bad_update_session(self):
|
||||
# try to update a session with a bad keyword ~ raise error
|
||||
sm = self.sm
|
||||
session_id = sm.create_session(path='/path/to/test.ipynb',
|
||||
session_id = self.create_session(path='/path/to/test.ipynb',
|
||||
kernel_name='ir')['id']
|
||||
self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
|
||||
|
||||
def test_delete_session(self):
|
||||
sm = self.sm
|
||||
sessions = [
|
||||
sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'),
|
||||
sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'),
|
||||
sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'),
|
||||
]
|
||||
sessions = self.create_sessions(
|
||||
dict(path='/path/to/1/test1.ipynb', kernel_name='python'),
|
||||
dict(path='/path/to/2/test2.ipynb', kernel_name='python'),
|
||||
dict(path='/path/to/3/test3.ipynb', kernel_name='python'),
|
||||
)
|
||||
sm.delete_session(sessions[1]['id'])
|
||||
new_sessions = sm.list_sessions()
|
||||
expected = [{
|
||||
@ -156,7 +174,7 @@ class TestSessionManager(TestCase):
|
||||
def test_bad_delete_session(self):
|
||||
# try to delete a session that doesn't exist ~ raise error
|
||||
sm = self.sm
|
||||
sm.create_session(path='/path/to/test.ipynb', kernel_name='python')
|
||||
self.create_session(path='/path/to/test.ipynb', kernel_name='python')
|
||||
self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
|
||||
self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
|
||||
|
||||
|
@ -75,6 +75,8 @@ define(function(require) {
|
||||
|
||||
var footer = $("<div/>").addClass("modal-footer");
|
||||
|
||||
var default_button;
|
||||
|
||||
for (var label in options.buttons) {
|
||||
var btn_opts = options.buttons[label];
|
||||
var button = $("<button/>")
|
||||
@ -88,12 +90,18 @@ define(function(require) {
|
||||
button.addClass(btn_opts.class);
|
||||
}
|
||||
footer.append(button);
|
||||
if (options.default_button && label === options.default_button) {
|
||||
default_button = button;
|
||||
}
|
||||
}
|
||||
if (!options.default_button) {
|
||||
default_button = footer.find("button").last();
|
||||
}
|
||||
dialog_content.append(footer);
|
||||
// hook up on-open event
|
||||
modal.on("shown.bs.modal", function() {
|
||||
setTimeout(function() {
|
||||
footer.find("button").last().focus();
|
||||
modal.on("shown.bs.modal", function () {
|
||||
setTimeout(function () {
|
||||
default_button.focus();
|
||||
if (options.open) {
|
||||
$.proxy(options.open, modal)();
|
||||
}
|
||||
|
@ -255,7 +255,13 @@ define([
|
||||
}
|
||||
help.sort(function (a, b) {
|
||||
if (a.help_index === b.help_index) {
|
||||
return 0;
|
||||
if (a.shortcut === b.shortcut) {
|
||||
return 0;
|
||||
}
|
||||
if (a.shortcut > b.shortcut) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (a.help_index === undefined || a.help_index > b.help_index){
|
||||
return 1;
|
||||
@ -362,7 +368,7 @@ define([
|
||||
**/
|
||||
var action_name = this.actions.get_name(data);
|
||||
if (! action_name){
|
||||
throw new Error('does not know how to deal with', data);
|
||||
throw new Error('does not know how to deal with : ' + data);
|
||||
}
|
||||
shortcut = normalize_shortcut(shortcut);
|
||||
this.set_shortcut(shortcut, action_name);
|
||||
|
@ -107,10 +107,10 @@ define(function(require){
|
||||
}
|
||||
},
|
||||
'run-cell':{
|
||||
help : 'run marked cells',
|
||||
help : 'run selected cells',
|
||||
help_index : 'bb',
|
||||
handler : function (env) {
|
||||
env.notebook.execute_marked_cells();
|
||||
env.notebook.execute_selected_cells();
|
||||
}
|
||||
},
|
||||
'run-cell-and-insert-below':{
|
||||
@ -163,7 +163,7 @@ define(function(require){
|
||||
handler : function (env) {
|
||||
var index = env.notebook.get_selected_index();
|
||||
if (index !== 0 && index !== null) {
|
||||
env.notebook.select_prev();
|
||||
env.notebook.select_prev(true);
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
}
|
||||
@ -174,23 +174,23 @@ define(function(require){
|
||||
handler : function (env) {
|
||||
var index = env.notebook.get_selected_index();
|
||||
if (index !== (env.notebook.ncells()-1) && index !== null) {
|
||||
env.notebook.select_next();
|
||||
env.notebook.select_next(true);
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
}
|
||||
},
|
||||
'extend-marked-cells-above' : {
|
||||
help: 'extend marked cells above',
|
||||
'extend-selection-above' : {
|
||||
help: 'extend selected cells above',
|
||||
help_index : 'dc',
|
||||
handler : function (env) {
|
||||
env.notebook.extend_marked(-1);
|
||||
env.notebook.extend_selection_by(-1)
|
||||
}
|
||||
},
|
||||
'extend-marked-cells-below' : {
|
||||
help: 'extend marked cells below',
|
||||
'extend-selection-below' : {
|
||||
help: 'extend selected cells below',
|
||||
help_index : 'dd',
|
||||
handler : function (env) {
|
||||
env.notebook.extend_marked(1);
|
||||
env.notebook.extend_selection_by(1)
|
||||
}
|
||||
},
|
||||
'cut-cell' : {
|
||||
@ -229,7 +229,7 @@ define(function(require){
|
||||
help_index : 'ec',
|
||||
handler : function (env) {
|
||||
env.notebook.insert_cell_above();
|
||||
env.notebook.select_prev();
|
||||
env.notebook.select_prev(true);
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
},
|
||||
@ -239,7 +239,7 @@ define(function(require){
|
||||
help_index : 'ed',
|
||||
handler : function (env) {
|
||||
env.notebook.insert_cell_below();
|
||||
env.notebook.select_next();
|
||||
env.notebook.select_next(true);
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
},
|
||||
@ -380,16 +380,10 @@ define(function(require){
|
||||
}
|
||||
},
|
||||
'merge-cells' : {
|
||||
help : 'merge marked cells',
|
||||
help : 'merge selected cells',
|
||||
help_index: 'el',
|
||||
handler: function(env) {
|
||||
env.notebook.merge_marked_cells();
|
||||
}
|
||||
},
|
||||
'close-pager' : {
|
||||
help_index : 'gd',
|
||||
handler : function (env) {
|
||||
env.pager.collapse();
|
||||
env.notebook.merge_selected_cells();
|
||||
}
|
||||
},
|
||||
'show-command-palette': {
|
||||
@ -400,29 +394,6 @@ define(function(require){
|
||||
env.notebook.show_command_palette();
|
||||
}
|
||||
},
|
||||
'toggle-cell-marked': {
|
||||
help_index : 'cj',
|
||||
help: 'toggle marks',
|
||||
icon: 'fa-check',
|
||||
handler : function(env){
|
||||
// Use bitwise logic to toggle the marked state.
|
||||
env.notebook.get_selected_cell().marked ^= true;
|
||||
}
|
||||
},
|
||||
'unmark-all-cells': {
|
||||
help_index : 'ck',
|
||||
help : 'unmark all cells',
|
||||
handler : function(env) {
|
||||
env.notebook.unmark_all_cells();
|
||||
}
|
||||
},
|
||||
'mark-all-cells': {
|
||||
help_index : 'cl',
|
||||
help : 'mark all cells',
|
||||
handler : function(env) {
|
||||
env.notebook.mark_all_cells();
|
||||
}
|
||||
},
|
||||
'toggle-toolbar':{
|
||||
help: 'hide/show the toolbar',
|
||||
handler : function(env){
|
||||
@ -438,14 +409,12 @@ define(function(require){
|
||||
events.trigger('resize-header.Page');
|
||||
}
|
||||
},
|
||||
'close-pager-or-unmark-all-cells': {
|
||||
help : 'close the pager or unmark all cells',
|
||||
'close-pager': {
|
||||
help : 'close the pager',
|
||||
handler : function(env) {
|
||||
// Collapse the page if it is open, otherwise unmark all.
|
||||
// Collapse the page if it is open
|
||||
if (env.pager && env.pager.expanded) {
|
||||
env.pager.collapse();
|
||||
} else {
|
||||
env.notebook.unmark_all_cells();
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -481,7 +450,7 @@ define(function(require){
|
||||
event.preventDefault();
|
||||
}
|
||||
env.notebook.command_mode();
|
||||
env.notebook.select_prev();
|
||||
env.notebook.select_prev(true);
|
||||
env.notebook.edit_mode();
|
||||
cm = env.notebook.get_selected_cell().code_mirror;
|
||||
cm.setCursor(cm.lastLine(), 0);
|
||||
@ -498,7 +467,7 @@ define(function(require){
|
||||
event.preventDefault();
|
||||
}
|
||||
env.notebook.command_mode();
|
||||
env.notebook.select_next();
|
||||
env.notebook.select_next(true);
|
||||
env.notebook.edit_mode();
|
||||
var cm = env.notebook.get_selected_cell().code_mirror;
|
||||
cm.setCursor(0, 0);
|
||||
|
@ -1,4 +1,3 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
/**
|
||||
@ -21,7 +20,7 @@ define([
|
||||
"use strict";
|
||||
|
||||
var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
|
||||
|
||||
|
||||
CodeMirror.scrollbarModel.native.prototype.overlayHack = function () {
|
||||
overlayHack.apply(this, arguments);
|
||||
// Reverse `min-height: 18px` scrollbar hack on OS X
|
||||
@ -55,6 +54,7 @@ define([
|
||||
|
||||
this.placeholder = config.placeholder || '';
|
||||
this.selected = false;
|
||||
this.anchor = false;
|
||||
this.rendered = false;
|
||||
this.mode = 'command';
|
||||
|
||||
@ -154,30 +154,29 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* trigger on focus and on click to bubble up to the notebook and
|
||||
* potentially extend the selection if shift-click, contract the selection
|
||||
* if just codemirror focus (so edit mode).
|
||||
* We **might** be able to move that to notebook `handle_edit_mode`.
|
||||
*/
|
||||
Cell.prototype._on_click = function(event){
|
||||
if (!this.selected) {
|
||||
this.events.trigger('select.Cell', {'cell':this, 'extendSelection':event.shiftKey});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can implement override bind_events.
|
||||
* Be carefull to call the parent method when overwriting as it fires event.
|
||||
* this will be triggerd after create_element in constructor.
|
||||
* Be careful to call the parent method when overwriting as it fires event.
|
||||
* this will be triggered after create_element in constructor.
|
||||
* @method bind_events
|
||||
*/
|
||||
Cell.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
// We trigger events so that Cell doesn't have to depend on Notebook.
|
||||
that.element.click(function (event) {
|
||||
if (!that.selected) {
|
||||
that.events.trigger('select.Cell', {'cell':that});
|
||||
}
|
||||
|
||||
// Cmdtrl-click should mark the cell.
|
||||
var isMac = navigator.platform.slice(0, 3).toLowerCase() === 'mac';
|
||||
if ((!isMac && event.ctrlKey) || (isMac && event.metaKey)) {
|
||||
that.marked = !that.marked;
|
||||
}
|
||||
});
|
||||
that.element.focusin(function (event) {
|
||||
if (!that.selected) {
|
||||
that.events.trigger('select.Cell', {'cell':that});
|
||||
}
|
||||
that._on_click(event)
|
||||
});
|
||||
if (this.code_mirror) {
|
||||
this.code_mirror.on("change", function(cm, change) {
|
||||
@ -186,6 +185,9 @@ define([
|
||||
}
|
||||
if (this.code_mirror) {
|
||||
this.code_mirror.on('focus', function(cm, change) {
|
||||
if (!that.selected) {
|
||||
that.events.trigger('select.Cell', {'cell':that});
|
||||
}
|
||||
that.events.trigger('edit_mode.Cell', {cell: that});
|
||||
});
|
||||
}
|
||||
@ -239,7 +241,7 @@ define([
|
||||
|
||||
|
||||
/**
|
||||
* Triger typsetting of math by mathjax on current cell element
|
||||
* Triger typesetting of math by mathjax on current cell element
|
||||
* @method typeset
|
||||
*/
|
||||
Cell.prototype.typeset = function () {
|
||||
@ -251,7 +253,13 @@ define([
|
||||
* @method select
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.select = function () {
|
||||
Cell.prototype.select = function (moveanchor) {
|
||||
// if anchor is true, set the move the anchor
|
||||
moveanchor = (moveanchor === undefined)? true:moveanchor;
|
||||
if(moveanchor){
|
||||
this.anchor=true;
|
||||
}
|
||||
|
||||
if (!this.selected) {
|
||||
this.element.addClass('selected');
|
||||
this.element.removeClass('unselected');
|
||||
@ -265,10 +273,14 @@ define([
|
||||
/**
|
||||
* handle cell level logic when the cell is unselected
|
||||
* @method unselect
|
||||
* @param {bool} leave_selected - true to move cursor away and extend selection
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.unselect = function (leave_selected) {
|
||||
Cell.prototype.unselect = function (moveanchor) {
|
||||
// if anchor is true, remove also the anchor
|
||||
moveanchor = (moveanchor === undefined)? true:moveanchor;
|
||||
if (moveanchor){
|
||||
this.anchor = false
|
||||
}
|
||||
if (this.selected) {
|
||||
this.element.addClass('unselected');
|
||||
this.element.removeClass('selected');
|
||||
@ -279,32 +291,9 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not the cell is marked.
|
||||
* @return {boolean}
|
||||
*/
|
||||
Object.defineProperty(Cell.prototype, 'marked', {
|
||||
get: function() {
|
||||
return this.element.hasClass('marked');
|
||||
},
|
||||
set: function(value) {
|
||||
var isMarked = this.element.hasClass('marked');
|
||||
// Use a casting comparison. Allows for the caller to assign 0 or
|
||||
// 1 instead of a boolean value, which in return means the caller
|
||||
// can do cell.marked ^= true to toggle the mark.
|
||||
if (isMarked != value) {
|
||||
if (value) {
|
||||
this.element.addClass('marked');
|
||||
} else {
|
||||
this.element.removeClass('marked');
|
||||
}
|
||||
this.events.trigger('marked_changed.Cell', {cell: this, value: value});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* should be overritten by subclass
|
||||
* should be overwritten by subclass
|
||||
* @method execute
|
||||
*/
|
||||
Cell.prototype.execute = function () {
|
||||
@ -426,6 +415,7 @@ define([
|
||||
*/
|
||||
Cell.prototype.focus_cell = function () {
|
||||
this.element.focus();
|
||||
this._on_click({});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -354,7 +354,6 @@ define([
|
||||
|
||||
var chkb = $('<input/>').attr('type', 'checkbox');
|
||||
var lbl = $('<label/>').append($('<span/>').text(name));
|
||||
lbl.append(chkb);
|
||||
chkb.attr("checked", getter(cell));
|
||||
|
||||
chkb.click(function(){
|
||||
@ -362,7 +361,7 @@ define([
|
||||
setter(cell, !v);
|
||||
chkb.attr("checked", !v);
|
||||
});
|
||||
button_container.append($('<span/>').append(lbl));
|
||||
button_container.append($('<span/>').append(lbl).append(chkb));
|
||||
};
|
||||
};
|
||||
|
||||
@ -387,13 +386,12 @@ define([
|
||||
|
||||
var text = $('<input/>').attr('type', 'text');
|
||||
var lbl = $('<label/>').append($('<span/>').text(name));
|
||||
lbl.append(text);
|
||||
text.attr("value", getter(cell));
|
||||
|
||||
text.keyup(function(){
|
||||
setter(cell, text.val());
|
||||
});
|
||||
button_container.append($('<span/>').append(lbl));
|
||||
button_container.append($('<span/>').append(lbl).append(text));
|
||||
IPython.keyboard_manager.register_events(text);
|
||||
};
|
||||
};
|
||||
|
@ -50,7 +50,15 @@ define([
|
||||
*/
|
||||
CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
|
||||
var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
|
||||
if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
|
||||
if (sel) {
|
||||
var ranges = cm.listSelections();
|
||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||
var head = ranges[i].head;
|
||||
var anchor = ranges[i].anchor;
|
||||
cm.replaceRange("", Pos(head.line, head.ch), CodeMirror.Pos(anchor.line, anchor.ch));
|
||||
}
|
||||
return;
|
||||
}
|
||||
var cur = cm.getCursor(), line = cm.getLine(cur.line);
|
||||
var tabsize = cm.getOption('tabSize');
|
||||
var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
|
||||
@ -188,7 +196,7 @@ define([
|
||||
|
||||
/** @method bind_events */
|
||||
CodeCell.prototype.bind_events = function () {
|
||||
Cell.prototype.bind_events.apply(this);
|
||||
Cell.prototype.bind_events.apply(this, arguments);
|
||||
var that = this;
|
||||
|
||||
this.element.focusout(
|
||||
@ -393,7 +401,7 @@ define([
|
||||
// Basic cell manipulation.
|
||||
|
||||
CodeCell.prototype.select = function () {
|
||||
var cont = Cell.prototype.select.apply(this);
|
||||
var cont = Cell.prototype.select.apply(this, arguments);
|
||||
if (cont) {
|
||||
this.code_mirror.refresh();
|
||||
this.auto_highlight();
|
||||
@ -402,7 +410,7 @@ define([
|
||||
};
|
||||
|
||||
CodeCell.prototype.render = function () {
|
||||
var cont = Cell.prototype.render.apply(this);
|
||||
var cont = Cell.prototype.render.apply(this, arguments);
|
||||
// Always execute, even if we are already in the rendered state
|
||||
return cont;
|
||||
};
|
||||
@ -540,7 +548,7 @@ define([
|
||||
* @return is the action being taken
|
||||
*/
|
||||
CodeCell.prototype.unselect = function() {
|
||||
var cont = Cell.prototype.unselect.call(this);
|
||||
var cont = Cell.prototype.unselect.apply(this, arguments);
|
||||
if (cont) {
|
||||
// When a code cell is unselected, make sure that the corresponding
|
||||
// tooltip and completer to that cell is closed.
|
||||
|
@ -96,14 +96,14 @@ define([
|
||||
'i,i' : 'jupyter-notebook:interrupt-kernel',
|
||||
'0,0' : 'jupyter-notebook:confirm-restart-kernel',
|
||||
'd,d' : 'jupyter-notebook:delete-cell',
|
||||
'esc': 'jupyter-notebook:close-pager-or-unmark-all-cells',
|
||||
'esc': 'jupyter-notebook:close-pager',
|
||||
'up' : 'jupyter-notebook:select-previous-cell',
|
||||
'k' : 'jupyter-notebook:select-previous-cell',
|
||||
'j' : 'jupyter-notebook:select-next-cell',
|
||||
'shift-k': 'jupyter-notebook:extend-marked-cells-above',
|
||||
'shift-j': 'jupyter-notebook:extend-marked-cells-below',
|
||||
'shift-up': 'jupyter-notebook:extend-marked-cells-above',
|
||||
'shift-down': 'jupyter-notebook:extend-marked-cells-below',
|
||||
'shift-k': 'jupyter-notebook:extend-selection-above',
|
||||
'shift-j': 'jupyter-notebook:extend-selection-below',
|
||||
'shift-up': 'jupyter-notebook:extend-selection-above',
|
||||
'shift-down': 'jupyter-notebook:extend-selection-below',
|
||||
'x' : 'jupyter-notebook:cut-cell',
|
||||
'c' : 'jupyter-notebook:copy-cell',
|
||||
'v' : 'jupyter-notebook:paste-cell-below',
|
||||
|
@ -63,7 +63,7 @@ define([
|
||||
// The selected cell loses focus when the menu is entered, so we
|
||||
// re-select it upon selection.
|
||||
var i = that.notebook.get_selected_index();
|
||||
that.notebook.select(i);
|
||||
that.notebook.select(i, false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -29,6 +29,12 @@ define(function (require) {
|
||||
var scrollmanager = require('notebook/js/scrollmanager');
|
||||
var commandpalette = require('notebook/js/commandpalette');
|
||||
|
||||
var _SOFT_SELECTION_CLASS = 'jupyter-soft-selected';
|
||||
|
||||
function soft_selected(cell){
|
||||
return cell.element.hasClass(_SOFT_SELECTION_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains and manages cells.
|
||||
* @class Notebook
|
||||
@ -58,6 +64,8 @@ define(function (require) {
|
||||
this.ws_url = options.ws_url;
|
||||
this._session_starting = false;
|
||||
this.last_modified = null;
|
||||
// debug 484
|
||||
this._last_modified = 'init';
|
||||
|
||||
// Create default scroll manager.
|
||||
this.scroll_manager = new scrollmanager.ScrollManager(this);
|
||||
@ -185,9 +193,6 @@ define(function (require) {
|
||||
Notebook.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
|
||||
this.events.on('marked_changed.Cell', function() {
|
||||
that.update_marked_status();
|
||||
});
|
||||
|
||||
this.events.on('set_next_input.Notebook', function (event, data) {
|
||||
if (data.replace) {
|
||||
@ -219,7 +224,7 @@ define(function (require) {
|
||||
|
||||
this.events.on('select.Cell', function (event, data) {
|
||||
var index = that.find_cell_index(data.cell);
|
||||
that.select(index);
|
||||
that.select(index, !data.extendSelection);
|
||||
});
|
||||
|
||||
this.events.on('edit_mode.Cell', function (event, data) {
|
||||
@ -296,9 +301,6 @@ define(function (require) {
|
||||
expand_time(time);
|
||||
});
|
||||
|
||||
this.scroll_manager.onScroll(function () {
|
||||
that.update_marked_status();
|
||||
}, 100);
|
||||
|
||||
// Firefox 22 broke $(window).on("beforeunload")
|
||||
// I'm not sure why or how.
|
||||
@ -582,13 +584,30 @@ define(function (require) {
|
||||
return i;
|
||||
};
|
||||
|
||||
|
||||
Notebook.prototype.get_selected_cells = function () {
|
||||
return this.get_cells().filter(function(cell, index){ return cell.selected || soft_selected(cell) || cell.anchor})
|
||||
};
|
||||
|
||||
Notebook.prototype.get_selected_cells_indices = function () {
|
||||
|
||||
var result = [];
|
||||
this.get_cells().filter(function (cell, index) {
|
||||
if (cell.selected || soft_selected(cell) || cell.anchor) {
|
||||
result.push(index);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the currently selected cell.
|
||||
*
|
||||
* @return {Cell} The selected cell
|
||||
*/
|
||||
Notebook.prototype.get_selected_cell = function () {
|
||||
var index = this.get_selected_index();
|
||||
var index = this.get_selected_cells_indices();
|
||||
return this.get_cell(index);
|
||||
};
|
||||
|
||||
@ -606,6 +625,15 @@ define(function (require) {
|
||||
}
|
||||
};
|
||||
|
||||
Notebook.prototype.get_anchor_index = function () {
|
||||
var result = null;
|
||||
this.get_cell_elements().filter(function (index) {
|
||||
if ($(this).data("cell").anchor === true) {
|
||||
result = index;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
/**
|
||||
* Get the index of the currently selected cell.
|
||||
*
|
||||
@ -620,144 +648,52 @@ define(function (require) {
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the marks on the cells
|
||||
* @param {Cell[]} [cells] - optionally specify what cells should be toggled
|
||||
*/
|
||||
Notebook.prototype.toggle_cells_marked = function(cells) {
|
||||
cells = cells || this.get_cells();
|
||||
cells.forEach(function(cell) { cell.marked = !cell.marked; });
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all of the cells
|
||||
* @param {Cell[]} [cells] - optionally specify what cells should be marked
|
||||
*/
|
||||
Notebook.prototype.mark_all_cells = function(cells) {
|
||||
cells = cells || this.get_cells();
|
||||
cells.forEach(function(cell) { cell.marked = true; });
|
||||
};
|
||||
|
||||
/**
|
||||
* Unmark all of the cells
|
||||
* @param {Cell[]} [cells] - optionally specify what cells should be unmarked
|
||||
*/
|
||||
Notebook.prototype.unmark_all_cells = function(cells) {
|
||||
this.get_marked_cells(cells).forEach(function(cell) { cell.marked = false; });
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the cells that should be marked, exclusively
|
||||
* @param {Cell[]} cells
|
||||
*/
|
||||
Notebook.prototype.set_marked_cells = function(cells) {
|
||||
this.unmark_all_cells();
|
||||
this.mark_all_cells(cells);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the cells that are marked
|
||||
* @param {Cell[]} [cells] - optionally provide the cells to search through
|
||||
* @return {Cell[]} marked cells
|
||||
*/
|
||||
Notebook.prototype.get_marked_cells = function(cells) {
|
||||
cells = cells || this.get_cells();
|
||||
return cells.filter(function(cell) { return (cell.marked || cell.selected); });
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the cells that are marked by indices
|
||||
* @param {number[]} indices
|
||||
* @param {Cell[]} [cells] - optionally provide the cells to search through
|
||||
*/
|
||||
Notebook.prototype.set_marked_indices = function(indices, cells) {
|
||||
cells = cells || this.get_cells();
|
||||
this.unmark_all_cells(cells);
|
||||
this.mark_all_cells(cells.filter(function(cell, index) { return indices.indexOf(index) !== -1; }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the indices of the cells that are marked
|
||||
* @param {Cell[]} [cells] - optionally provide the cells to search through
|
||||
* @return {number[]} marked cell indices
|
||||
*/
|
||||
Notebook.prototype.get_marked_indices = function(cells) {
|
||||
cells = cells || this.get_cells();
|
||||
var markedCells = this.get_marked_cells(cells);
|
||||
return markedCells.map(function(cell) { return cells.indexOf(cell); });
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the marked cells are contiguous
|
||||
* @param {Cell[]} [cells] - optionally provide the cells to search through
|
||||
* @return {boolean}
|
||||
*/
|
||||
Notebook.prototype.are_marked_cells_contiguous = function(cells) {
|
||||
// Get a numerically sorted list of the marked indices.
|
||||
var markedIndices = this.get_marked_indices(cells).sort(
|
||||
function(a,b) { return a-b; });
|
||||
|
||||
// Check for contiguousness
|
||||
for (var i = 0; i < markedIndices.length - 1; i++) {
|
||||
if (markedIndices[i+1] - markedIndices[i] !== 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the marked cells specified by their indices are contiguous
|
||||
* @param {number[]} indices - the cell indices to search through
|
||||
* @param {Cell[]} [cells] - the cells to search through
|
||||
* @return {boolean}
|
||||
*/
|
||||
Notebook.prototype.are_marked_indices_contiguous = function(indices, cells) {
|
||||
cells = cells || this.get_cells();
|
||||
return this.are_marked_cells_contiguous(cells.filter(function(cell, index) {
|
||||
return indices.indexOf(index) !== -1;
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Extend the selected range
|
||||
*
|
||||
* @param {number} offset
|
||||
*/
|
||||
Notebook.prototype.extend_marked = function(offset) {
|
||||
// Mark currently selected cell
|
||||
this.get_selected_cell().marked = true;
|
||||
|
||||
// Select the cell in the offset direction. Bound index between 0 and
|
||||
// the number of cells -1.
|
||||
var selectedIndex = Math.min(Math.max(this.get_selected_index() + offset, 0), this.ncells()-1);
|
||||
this.select(selectedIndex);
|
||||
this.ensure_focused();
|
||||
};
|
||||
|
||||
Notebook.prototype.update_marked_status = function() {
|
||||
var marked_cells = this.get_marked_cells();
|
||||
var num_offscreen = 0;
|
||||
var i;
|
||||
for (i = 0; i < marked_cells.length; i++) {
|
||||
if (!this.scroll_manager.is_cell_visible(marked_cells[i])) {
|
||||
num_offscreen += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.events.trigger('marked_offscreen.Cell', num_offscreen);
|
||||
};
|
||||
|
||||
// Cell selection.
|
||||
|
||||
Notebook.prototype.extend_selection_by = function(delta) {
|
||||
var index = this.get_selected_index();
|
||||
// do not move anchor
|
||||
return this.select(index+delta, false);
|
||||
};
|
||||
|
||||
|
||||
Notebook.prototype.update_soft_selection = function(){
|
||||
var i1 = this.get_selected_index();
|
||||
var i2 = this.get_anchor_index();
|
||||
var low = Math.min(i1, i2);
|
||||
var high = Math.max(i1, i2);
|
||||
if (low !== high){
|
||||
$('body').addClass('jupyter-multi-select');
|
||||
} else {
|
||||
$('body').removeClass('jupyter-multi-select');
|
||||
}
|
||||
this.get_cells().map(function(cell, index, all){
|
||||
if( low <= index && index <= high ){
|
||||
cell.element.addClass(_SOFT_SELECTION_CLASS);
|
||||
} else {
|
||||
cell.element.removeClass(_SOFT_SELECTION_CLASS);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Notebook.prototype._contract_selection = function(){
|
||||
var i = this.get_selected_index();
|
||||
this.select(i, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically select a cell.
|
||||
*
|
||||
* @param {integer} index - A cell's index
|
||||
* @param {bool} moveanchor – whether to move the selection
|
||||
* anchor, default to true.
|
||||
* @return {Notebook} This notebook
|
||||
*/
|
||||
Notebook.prototype.select = function (index) {
|
||||
Notebook.prototype.select = function (index, moveanchor) {
|
||||
moveanchor = (moveanchor===undefined)? true : moveanchor;
|
||||
|
||||
if (this.is_valid_cell_index(index)) {
|
||||
var sindex = this.get_selected_index();
|
||||
if (sindex !== null && index !== sindex) {
|
||||
@ -766,11 +702,13 @@ define(function (require) {
|
||||
if (this.mode !== 'command') {
|
||||
this.command_mode();
|
||||
}
|
||||
this.get_cell(sindex).unselect();
|
||||
this.get_cell(sindex).unselect(moveanchor);
|
||||
}
|
||||
if(moveanchor){
|
||||
this.get_cell(this.get_anchor_index()).unselect(true);
|
||||
}
|
||||
var cell = this.get_cell(index);
|
||||
cell.select();
|
||||
this.update_marked_status();
|
||||
cell.select(moveanchor);
|
||||
if (cell.cell_type === 'heading') {
|
||||
this.events.trigger('selected_cell_type_changed.Notebook',
|
||||
{'cell_type':cell.cell_type,level:cell.level}
|
||||
@ -781,17 +719,20 @@ define(function (require) {
|
||||
);
|
||||
}
|
||||
}
|
||||
this.update_soft_selection();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Programmatically select the next cell.
|
||||
*
|
||||
* @param {bool} moveanchor – whether to move the selection
|
||||
* anchor, default to true.
|
||||
* @return {Notebook} This notebook
|
||||
*/
|
||||
Notebook.prototype.select_next = function () {
|
||||
Notebook.prototype.select_next = function (moveanchor) {
|
||||
var index = this.get_selected_index();
|
||||
this.select(index+1);
|
||||
this.select(index+1, moveanchor);
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -800,9 +741,9 @@ define(function (require) {
|
||||
*
|
||||
* @return {Notebook} This notebook
|
||||
*/
|
||||
Notebook.prototype.select_prev = function () {
|
||||
Notebook.prototype.select_prev = function (moveanchor) {
|
||||
var index = this.get_selected_index();
|
||||
this.select(index-1);
|
||||
this.select(index-1, moveanchor);
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -856,6 +797,7 @@ define(function (require) {
|
||||
* @param {Cell} [cell] Cell to enter edit mode on.
|
||||
*/
|
||||
Notebook.prototype.handle_edit_mode = function (cell) {
|
||||
this._contract_selection();
|
||||
if (cell && this.mode !== 'edit') {
|
||||
cell.edit_mode();
|
||||
this.mode = 'edit';
|
||||
@ -868,6 +810,7 @@ define(function (require) {
|
||||
* Make a cell enter edit mode.
|
||||
*/
|
||||
Notebook.prototype.edit_mode = function () {
|
||||
this._contract_selection();
|
||||
var cell = this.get_selected_cell();
|
||||
if (cell && this.mode !== 'edit') {
|
||||
cell.unrender();
|
||||
@ -976,7 +919,7 @@ define(function (require) {
|
||||
*/
|
||||
Notebook.prototype.delete_cells = function(indices) {
|
||||
if (indices === undefined) {
|
||||
indices = this.get_marked_indices();
|
||||
indices = this.get_selected_cells_indices();
|
||||
}
|
||||
|
||||
this.undelete_backup = [];
|
||||
@ -1416,7 +1359,7 @@ define(function (require) {
|
||||
* Copy cells.
|
||||
*/
|
||||
Notebook.prototype.copy_cell = function () {
|
||||
var cells = this.get_marked_cells();
|
||||
var cells = this.get_selected_cells();
|
||||
if (cells.length === 0) {
|
||||
cells = [this.get_selected_cell()];
|
||||
}
|
||||
@ -1505,7 +1448,6 @@ define(function (require) {
|
||||
// Unrender the new cell so we can call set_text.
|
||||
new_cell.unrender();
|
||||
new_cell.set_text(texta);
|
||||
new_cell.marked = cell.marked;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1562,14 +1504,13 @@ define(function (require) {
|
||||
this.delete_cells(indices);
|
||||
|
||||
this.select(this.find_cell_index(target));
|
||||
this.unmark_all_cells();
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge the selected range of cells
|
||||
*/
|
||||
Notebook.prototype.merge_marked_cells = function() {
|
||||
this.merge_cells(this.get_marked_indices());
|
||||
Notebook.prototype.merge_selected_cells = function() {
|
||||
this.merge_cells(this.get_selected_cells_indices());
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2007,27 +1948,27 @@ define(function (require) {
|
||||
/**
|
||||
* Execute or render cell outputs and go into command mode.
|
||||
*/
|
||||
Notebook.prototype.execute_marked_cells = function () {
|
||||
this.execute_cells(this.get_marked_indices());
|
||||
Notebook.prototype.execute_selected_cells = function () {
|
||||
this.execute_cells(this.get_selected_cells_indices());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Alias for execute_marked_cells, for backwards compatibility --
|
||||
* Alias for execute_selected_cells, for backwards compatibility --
|
||||
* previously, doing "Run Cell" would only ever run a single cell (hence
|
||||
* `execute_cell`), but now it runs all marked cells, so that's the
|
||||
* preferable function to use. But it is good to keep this function to avoid
|
||||
* breaking existing extensions, etc.
|
||||
*/
|
||||
Notebook.prototype.execute_cell = function () {
|
||||
this.execute_marked_cells();
|
||||
this.execute_selected_cells();
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute or render cell outputs and insert a new cell below.
|
||||
*/
|
||||
Notebook.prototype.execute_cell_and_insert_below = function () {
|
||||
// execute the marked cells, and don't insert anything
|
||||
var indices = this.get_marked_indices();
|
||||
var indices = this.get_selected_cells_indices();
|
||||
if (indices.length > 1) {
|
||||
this.execute_cells(indices);
|
||||
return;
|
||||
@ -2059,8 +2000,7 @@ define(function (require) {
|
||||
* Execute or render cell outputs and select the next cell.
|
||||
*/
|
||||
Notebook.prototype.execute_cell_and_select_below = function () {
|
||||
// execute the marked cells, and don't select anything
|
||||
var indices = this.get_marked_indices();
|
||||
var indices = this.get_selected_cells_indices();
|
||||
if (indices.length > 1) {
|
||||
this.execute_cells(indices);
|
||||
return;
|
||||
@ -2320,6 +2260,8 @@ define(function (require) {
|
||||
function (data) {
|
||||
var last_modified = new Date(data.last_modified);
|
||||
if (last_modified > that.last_modified) {
|
||||
console.warn("Last saving was done on `"+that.last_modified+"`("+that._last_modified+"), "+
|
||||
"while the current file seem to have been saved on `"+data.last_modified+"`")
|
||||
dialog.modal({
|
||||
notebook: that,
|
||||
keyboard_manager: that.keyboard_manager,
|
||||
@ -2365,6 +2307,8 @@ define(function (require) {
|
||||
Notebook.prototype.save_notebook_success = function (start, data) {
|
||||
this.set_dirty(false);
|
||||
this.last_modified = new Date(data.last_modified);
|
||||
// debug 484
|
||||
this._last_modified = 'save-success:'+data.last_modified;
|
||||
if (data.message) {
|
||||
// save succeeded, but validation failed.
|
||||
var body = $("<div>");
|
||||
@ -2513,6 +2457,8 @@ define(function (require) {
|
||||
that.notebook_name = json.name;
|
||||
that.notebook_path = json.path;
|
||||
that.last_modified = new Date(json.last_modified);
|
||||
// debug 484
|
||||
that._last_modified = json.last_modified;
|
||||
that.session.rename_notebook(json.path);
|
||||
that.events.trigger('notebook_renamed.Notebook', json);
|
||||
}
|
||||
@ -2610,6 +2556,8 @@ define(function (require) {
|
||||
this.scroll_to_top();
|
||||
this.writable = data.writable || false;
|
||||
this.last_modified = new Date(data.last_modified);
|
||||
// debug 484
|
||||
this._last_modified = 'load-success:'+data.last_modified
|
||||
var nbmodel = data.content;
|
||||
var orig_nbformat = nbmodel.metadata.orig_nbformat;
|
||||
var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
|
||||
|
@ -35,7 +35,7 @@ define([
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_kernel_notification_widget = function () {
|
||||
var that = this;
|
||||
var knw = this.new_notification_widget('kernel');
|
||||
var knw = this.widget('kernel');
|
||||
var $kernel_ind_icon = $("#kernel_indicator_icon");
|
||||
var $modal_ind_icon = $("#modal_indicator");
|
||||
var $readonly_ind_icon = $('#readonly-indicator');
|
||||
@ -284,7 +284,7 @@ define([
|
||||
* @method init_notebook_notification_widget
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_notebook_notification_widget = function () {
|
||||
var nnw = this.new_notification_widget('notebook');
|
||||
var nnw = this.widget('notebook');
|
||||
|
||||
// Notebook events
|
||||
this.events.on('notebook_loading.Notebook', function () {
|
||||
@ -346,7 +346,7 @@ define([
|
||||
* @method init_marked_cells_notification_widget
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_marked_cells_notification_widget = function () {
|
||||
var mcnw = this.new_notification_widget('marked_cells');
|
||||
var mcnw = this.widget('marked_cells');
|
||||
|
||||
this.events.on('marked_offscreen.Cell', function (evt, num) {
|
||||
if (num === 0) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jqueryui',
|
||||
'jquery-ui',
|
||||
'base/js/utils',
|
||||
'base/js/security',
|
||||
'base/js/keyboard',
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jqueryui',
|
||||
'jquery-ui',
|
||||
'base/js/utils',
|
||||
], function($, utils) {
|
||||
"use strict";
|
||||
|
@ -257,8 +257,8 @@ define([
|
||||
|
||||
QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
|
||||
var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
|
||||
jQuery.merge(cm_shortcuts, edit_shortcuts);
|
||||
return build_div('<h4>Edit Mode (press <kbd>Enter</kbd> to enable)</h4>', cm_shortcuts);
|
||||
edit_shortcuts = jQuery.merge(jQuery.merge([], cm_shortcuts), edit_shortcuts);
|
||||
return build_div('<h4>Edit Mode (press <kbd>Enter</kbd> to enable)</h4>', edit_shortcuts);
|
||||
};
|
||||
|
||||
var build_one = function (s) {
|
||||
|
@ -68,7 +68,7 @@ define(function(require){
|
||||
if(aborted){
|
||||
body.append($('<p/>').addClass('bg-warning').text("Warning, too many matches ("+html.length+"+), some changes might not be shown or applied"));
|
||||
} else {
|
||||
body.append($('<p/>').addClass('bg-info').text(html.length+" match"+(html.length==1?'':'es')));
|
||||
body.append($('<p/>').text(html.length+" match"+(html.length==1?'':'es')));
|
||||
}
|
||||
for(var rindex=0; rindex<html.length; rindex++){
|
||||
var pre = $('<pre/>')
|
||||
@ -147,58 +147,60 @@ define(function(require){
|
||||
* Search N' Replace action handler.
|
||||
**/
|
||||
var snr = function(env, event) {
|
||||
var search = $("<input/>")
|
||||
.addClass('form-control')
|
||||
.attr('placeholder','Find');
|
||||
|
||||
var isRegExpButton = $('<button/>')
|
||||
.attr('type', 'button')
|
||||
.attr('id', 'isreg')
|
||||
.addClass("btn btn-default")
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr('data-toggle','button')
|
||||
.css('font-weight', 'bold')
|
||||
.attr('title', 'Use regex (JavaScript regex syntax)')
|
||||
.text('.*');
|
||||
|
||||
var onlySelectedButton = $('<button/>')
|
||||
var onlySelectedButton = $('<button/>')
|
||||
.append($('<i/>').addClass('fa fa-align-left'))
|
||||
.attr('type', 'button')
|
||||
.addClass("btn btn-default")
|
||||
.append($('<i/>')
|
||||
.addClass("fa fa-check-square-o")
|
||||
)
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr('data-toggle','button')
|
||||
.attr('title', 'Replace only in selected cells');
|
||||
.attr('title', 'Replace in selected cells');
|
||||
|
||||
var isCaseSensitiveButton = $('<button/>')
|
||||
.attr('type', 'button')
|
||||
.addClass("btn btn-default")
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr('data-toggle','button')
|
||||
.attr('tabindex', '0')
|
||||
.attr('title', 'Case sensitive')
|
||||
.text('a≠A');
|
||||
.attr('title', 'Match case')
|
||||
.css('font-weight', 'bold')
|
||||
.text('Aa');
|
||||
|
||||
var search = $("<input/>")
|
||||
.addClass('form-control input-sm')
|
||||
.attr('placeholder','Find');
|
||||
|
||||
var repl = $("<input/>")
|
||||
.addClass('form-control')
|
||||
.attr('placeholder','Replace');
|
||||
var body = $('<div/>')
|
||||
.attr('id', 'replace-preview');
|
||||
var form = $('<form/>')
|
||||
.attr('id', 'find-and-replace')
|
||||
.append($('<div/>').addClass('form-group')
|
||||
var findFormGroup = $('<div/>').addClass('form-group');
|
||||
findFormGroup.append(
|
||||
$('<div/>').addClass('input-group')
|
||||
.append(
|
||||
$('<div/>').addClass('input-group')
|
||||
.append(
|
||||
$('<div/>').addClass('input-group-btn')
|
||||
.append(isCaseSensitiveButton)
|
||||
.append(isRegExpButton)
|
||||
.append(onlySelectedButton)
|
||||
)
|
||||
.append(search)
|
||||
)
|
||||
)
|
||||
.append($('<div/>').addClass('form-group')
|
||||
.append(repl)
|
||||
)
|
||||
.append(body);
|
||||
.append(search)
|
||||
)
|
||||
|
||||
var replace = $("<input/>")
|
||||
.addClass('form-control input-sm')
|
||||
.attr('placeholder','Replace');
|
||||
var replaceFormGroup = $('<div/>').addClass('form-group');
|
||||
replaceFormGroup.append(replace);
|
||||
|
||||
var body = $('<div/>').attr('id', 'replace-preview');
|
||||
|
||||
var form = $('<form/>').attr('id', 'find-and-replace')
|
||||
form.append(findFormGroup);
|
||||
form.append(replaceFormGroup);
|
||||
form.append(body);
|
||||
|
||||
// return whether the search is case sensitive
|
||||
var isCaseSensitive = function(){
|
||||
@ -272,14 +274,14 @@ define(function(require){
|
||||
}
|
||||
|
||||
// might want to warn if replace is empty
|
||||
var replace = repl.val();
|
||||
var replaceValue = replace.val();
|
||||
var lines = get_all_text(get_cells(env));
|
||||
|
||||
var _hb = compute_preview_model(sre, lines, isCaseSensitive(), RegExpOrNot, replace);
|
||||
var _hb = compute_preview_model(sre, lines, isCaseSensitive(), RegExpOrNot, replaceValue);
|
||||
var html = _hb[0];
|
||||
var aborted = _hb[1];
|
||||
|
||||
build_preview(body, aborted, html, replace);
|
||||
build_preview(body, aborted, html, replaceValue);
|
||||
|
||||
// done on type return false not to submit form
|
||||
return false;
|
||||
@ -287,7 +289,7 @@ define(function(require){
|
||||
|
||||
var onsubmit = function(event) {
|
||||
var sre = search.val();
|
||||
var replace = repl.val();
|
||||
var replaceValue = replace.val();
|
||||
if (!sre) {
|
||||
return false;
|
||||
}
|
||||
@ -300,7 +302,7 @@ define(function(require){
|
||||
for (var c = 0; c < cells.length; c++) {
|
||||
var cell = cells[c];
|
||||
var oldvalue = cell.code_mirror.getValue();
|
||||
var newvalue = oldvalue.replace(reg , replace);
|
||||
var newvalue = oldvalue.replace(reg , replaceValue);
|
||||
cell.code_mirror.setValue(newvalue);
|
||||
if (cell.cell_type === 'markdown') {
|
||||
cell.rendered = false;
|
||||
@ -322,19 +324,19 @@ define(function(require){
|
||||
});
|
||||
|
||||
onlySelectedButton.click(function(){
|
||||
repl.focus();
|
||||
replace.focus();
|
||||
setTimeout(function(){onChange();}, 100);
|
||||
});
|
||||
|
||||
|
||||
search.keypress(function (e) {
|
||||
if (e.which == 13) {//enter
|
||||
repl.focus();
|
||||
replace.focus();
|
||||
}
|
||||
});
|
||||
|
||||
search.on('input', onChange);
|
||||
repl.on('input', onChange);
|
||||
replace.on('input', onChange);
|
||||
|
||||
|
||||
var mod = dialog.modal({
|
||||
@ -352,7 +354,7 @@ define(function(require){
|
||||
}
|
||||
});
|
||||
|
||||
repl.keypress(function (e) {
|
||||
replace.keypress(function (e) {
|
||||
if (e.which == 13) {//enter
|
||||
onsubmit();
|
||||
mod.modal('hide');
|
||||
|
@ -118,7 +118,7 @@ define([
|
||||
// Cell level actions
|
||||
|
||||
TextCell.prototype.select = function () {
|
||||
var cont = Cell.prototype.select.apply(this);
|
||||
var cont = Cell.prototype.select.apply(this, arguments);
|
||||
if (cont) {
|
||||
if (this.mode === 'edit') {
|
||||
this.code_mirror.refresh();
|
||||
@ -286,6 +286,9 @@ define([
|
||||
.addClass('anchor-link')
|
||||
.attr('href', '#' + hash)
|
||||
.text('¶')
|
||||
.on('click',function(){
|
||||
setTimeout(function(){that.unrender(); that.render()}, 100)
|
||||
})
|
||||
);
|
||||
});
|
||||
// links in markdown cells should open in new tabs
|
||||
|
@ -1,59 +1,65 @@
|
||||
@_cell_padding_minus_border: @cell_padding - @cell_border_width;
|
||||
|
||||
._marked_style(@n) {
|
||||
border-left-width: @n;
|
||||
padding-left: @cell_padding - @n;
|
||||
._selected_style(@c1, @c2, @sep:0, @border_width:@cell_border_width) {
|
||||
border-left-width: @border_width;
|
||||
padding-left: @cell_padding - @border_width;
|
||||
background: linear-gradient(to right, @c1 -40px,@c1 @sep,@c2 @sep,@c2 100%);
|
||||
}
|
||||
|
||||
|
||||
div.cell {
|
||||
border: @cell_border_width solid transparent;
|
||||
.vbox();
|
||||
.corner-all();
|
||||
.border-box-sizing();
|
||||
|
||||
border-width: @cell_border_width;
|
||||
border-style: solid;
|
||||
|
||||
&.marked {
|
||||
._marked_style(3px);
|
||||
border-left-color: @marked_border_color_light;
|
||||
|
||||
/* Don't border the cells when printing */
|
||||
@media print {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: @border_color;
|
||||
border-left-color: @marked_border_color;
|
||||
._marked_style(2px);
|
||||
|
||||
&.marked {
|
||||
._marked_style(4px);
|
||||
}
|
||||
|
||||
/* Don't border the cells when printing */
|
||||
@media print {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.edit_mode &.selected {
|
||||
border-color: @edit_mode_border_color;
|
||||
|
||||
/* Don't border the cells when printing */
|
||||
@media print {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
border-color: transparent;
|
||||
|
||||
width: 100%;
|
||||
padding: @_cell_padding_minus_border;
|
||||
/* This acts as a spacer between cells, that is outside the border */
|
||||
margin: 0px;
|
||||
outline: none;
|
||||
|
||||
._selected_style(transparent, transparent, @cell_border_width);
|
||||
|
||||
.jupyter-multi-select &.jupyter-soft-selected {
|
||||
border-left-color: @selected_border_color_light;
|
||||
border-left-color: @soft_select_color;
|
||||
._selected_style(@selected_border_color_light, @soft_select_color, 5px, 0px);
|
||||
|
||||
@media print {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: @border_color;
|
||||
._selected_style(@selected_border_color, transparent, 5px, 0px);
|
||||
|
||||
|
||||
@media print {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.jupyter-multi-select &.selected.jupyter-soft-selected {
|
||||
._selected_style(@selected_border_color, @soft_select_color, 7px, 0);
|
||||
}
|
||||
|
||||
.edit_mode &.selected {
|
||||
border-color: @edit_mode_border_color;
|
||||
._selected_style(@edit_mode_border_color, transparent, 5px, 0px);
|
||||
|
||||
@media print {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.prompt {
|
||||
/* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
|
||||
min-width: 14ex;
|
||||
|
@ -1,31 +1,37 @@
|
||||
|
||||
#find-and-replace {
|
||||
#replace-preview .match, #replace-preview .insert{
|
||||
background-color:lightblue;
|
||||
border-color: darken(lightblue, 20%);
|
||||
|
||||
#replace-preview .match, #replace-preview .insert {
|
||||
background-color: #BBDEFB; // MD Blue 100
|
||||
border-color: #90CAF9; // MD Blue 200
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: @border-radius-base
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
#replace-preview .replace {
|
||||
|
||||
& .match{
|
||||
//display: none;
|
||||
background-color:salmon;
|
||||
text-decoration: line-through;
|
||||
border-color: darken(salmon, 20%);
|
||||
& .match {
|
||||
// text-decoration: line-through;
|
||||
background-color: #FFCDD2; // MD Red 100
|
||||
border-color: #EF9A9A; // MD Red 200
|
||||
border-radius: 0px;
|
||||
|
||||
}
|
||||
|
||||
& .insert{
|
||||
background-color: green;
|
||||
background-color:lightGreen;
|
||||
border-color: darken(lightGreen, 20%);
|
||||
& .insert {
|
||||
background-color: #C8E6C9; // MD Green 100
|
||||
border-color: #A5D6A7; // MD Green 200
|
||||
border-radius: 0px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
& #replace-preview {
|
||||
#replace-preview {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
pre {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,17 @@
|
||||
@code_line_height: 1.21429em; // changed from 1.231 to get 17px even
|
||||
@code_padding: 0.4em; // 5.6 px
|
||||
@rendered_html_border_color: black;
|
||||
@input_prompt_color: navy;
|
||||
@output_prompt_color: darkred;
|
||||
@input_prompt_color: #303F9F;
|
||||
@output_prompt_color: #D84315;
|
||||
@output_pre_color: black;
|
||||
@notification_widget_bg: rgba(240, 240, 240, 0.5);
|
||||
@marked_border_color: #009AF5;
|
||||
@marked_border_color_light: #7AC7F5;
|
||||
@edit_mode_border_color: green;
|
||||
|
||||
|
||||
@selected_border_color: #42A5F5;
|
||||
@selected_border_color_light: #90CAF9;
|
||||
@soft_select_color: #E3F2FD;
|
||||
|
||||
|
||||
@edit_mode_border_color: #66BB6A;
|
||||
@cell_padding: 6px;
|
||||
@cell_border_width: 1px;
|
||||
@cell_border_width: 1px;
|
||||
|
@ -93,7 +93,7 @@ function($, utils) {
|
||||
ConfigWithDefaults.prototype.get = function(key) {
|
||||
var that = this;
|
||||
return this.section.loaded.then(function() {
|
||||
return this._class_data()[key] || this.defaults[key]
|
||||
return that._class_data()[key] || that.defaults[key]
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ require([
|
||||
'tree/js/newnotebook',
|
||||
'auth/js/loginwidget',
|
||||
// only loaded, not used:
|
||||
'jqueryui',
|
||||
'jquery-ui',
|
||||
'bootstrap',
|
||||
'custom/custom',
|
||||
], function(
|
||||
|
@ -34,12 +34,17 @@
|
||||
jquery: 'components/jquery/jquery.min',
|
||||
bootstrap: 'components/bootstrap/js/bootstrap.min',
|
||||
bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
|
||||
jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
|
||||
'jquery-ui': 'components/jquery-ui/ui/minified/jquery-ui.min',
|
||||
moment: 'components/moment/moment',
|
||||
codemirror: 'components/codemirror',
|
||||
termjs: 'components/term.js/src/term',
|
||||
typeahead: 'components/jquery-typeahead/dist/jquery.typeahead'
|
||||
},
|
||||
map: { // for backward compatibility
|
||||
"*": {
|
||||
"jqueryui": "jquery-ui",
|
||||
}
|
||||
},
|
||||
shim: {
|
||||
typeahead: {
|
||||
deps: ["jquery"],
|
||||
@ -60,7 +65,7 @@
|
||||
deps: ["bootstrap"],
|
||||
exports: "Tour"
|
||||
},
|
||||
jqueryui: {
|
||||
"jquery-ui": {
|
||||
deps: ["jquery"],
|
||||
exports: "$"
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ except ImportError:
|
||||
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
import jupyter_core.paths
|
||||
from ..notebookapp import NotebookApp
|
||||
from ipython_genutils.tempdir import TemporaryDirectory
|
||||
|
||||
@ -51,9 +52,8 @@ class NotebookTestBase(TestCase):
|
||||
try:
|
||||
requests.get(url)
|
||||
except Exception as e:
|
||||
if cls.notebook.poll() is not None:
|
||||
raise RuntimeError("The notebook server exited with status %s" \
|
||||
% cls.notebook.poll())
|
||||
if not cls.notebook_thread.is_alive():
|
||||
raise RuntimeError("The notebook server failed to start")
|
||||
time.sleep(POLL_INTERVAL)
|
||||
else:
|
||||
return
|
||||
@ -77,31 +77,35 @@ class NotebookTestBase(TestCase):
|
||||
'JUPYTER_DATA_DIR' : data_dir.name
|
||||
})
|
||||
cls.env_patch.start()
|
||||
cls.path_patch = patch.object(jupyter_core.paths, 'SYSTEM_JUPYTER_PATH', [])
|
||||
cls.path_patch.start()
|
||||
cls.config_dir = TemporaryDirectory()
|
||||
cls.data_dir = data_dir
|
||||
cls.runtime_dir = TemporaryDirectory()
|
||||
cls.notebook_dir = TemporaryDirectory()
|
||||
app = cls.notebook = NotebookApp(
|
||||
port=cls.port,
|
||||
port_retries=0,
|
||||
open_browser=False,
|
||||
config_dir=cls.config_dir.name,
|
||||
data_dir=cls.data_dir.name,
|
||||
runtime_dir=cls.runtime_dir.name,
|
||||
notebook_dir=cls.notebook_dir.name,
|
||||
base_url=cls.url_prefix,
|
||||
config=cls.config,
|
||||
)
|
||||
|
||||
# clear log handlers and propagate to root for nose to capture it
|
||||
# needs to be redone after initialize, which reconfigures logging
|
||||
app.log.propagate = True
|
||||
app.log.handlers = []
|
||||
app.initialize(argv=[])
|
||||
app.log.propagate = True
|
||||
app.log.handlers = []
|
||||
started = Event()
|
||||
def start_thread():
|
||||
app = cls.notebook = NotebookApp(
|
||||
port=cls.port,
|
||||
port_retries=0,
|
||||
open_browser=False,
|
||||
config_dir=cls.config_dir.name,
|
||||
data_dir=cls.data_dir.name,
|
||||
runtime_dir=cls.runtime_dir.name,
|
||||
notebook_dir=cls.notebook_dir.name,
|
||||
base_url=cls.url_prefix,
|
||||
config=cls.config,
|
||||
)
|
||||
# don't register signal handler during tests
|
||||
app.init_signal = lambda : None
|
||||
# clear log handlers and propagate to root for nose to capture it
|
||||
# needs to be redone after initialize, which reconfigures logging
|
||||
app.log.propagate = True
|
||||
app.log.handlers = []
|
||||
app.initialize(argv=[])
|
||||
app.log.propagate = True
|
||||
app.log.handlers = []
|
||||
loop = IOLoop.current()
|
||||
loop.add_callback(started.set)
|
||||
try:
|
||||
@ -109,6 +113,7 @@ class NotebookTestBase(TestCase):
|
||||
finally:
|
||||
# set the event, so failure to start doesn't cause a hang
|
||||
started.set()
|
||||
app.session_manager.close()
|
||||
cls.notebook_thread = Thread(target=start_thread)
|
||||
cls.notebook_thread.start()
|
||||
started.wait()
|
||||
@ -118,12 +123,13 @@ class NotebookTestBase(TestCase):
|
||||
def teardown_class(cls):
|
||||
cls.notebook.stop()
|
||||
cls.wait_until_dead()
|
||||
cls.env_patch.start()
|
||||
cls.home_dir.cleanup()
|
||||
cls.config_dir.cleanup()
|
||||
cls.data_dir.cleanup()
|
||||
cls.runtime_dir.cleanup()
|
||||
cls.notebook_dir.cleanup()
|
||||
cls.env_patch.stop()
|
||||
cls.path_patch.stop()
|
||||
|
||||
@classmethod
|
||||
def base_url(cls):
|
||||
|
@ -41,7 +41,7 @@ casper.notebook_test(function () {
|
||||
this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.');
|
||||
this.validate_notebook_state('split', 'edit', 1);
|
||||
this.select_cell(0); // Move up to cell 0
|
||||
this.evaluate(function() { IPython.notebook.extend_marked(1);});
|
||||
this.evaluate(function() { IPython.notebook.extend_selection_by(1);});
|
||||
this.trigger_keydown('shift-m'); // Merge
|
||||
this.validate_notebook_state('merge', 'command', 0);
|
||||
this.test.assertEquals(this.get_cell_text(0), a, 'merge; Verify that cell 0 has the merged contents.');
|
||||
|
@ -3,16 +3,18 @@
|
||||
//
|
||||
casper.notebook_test(function () {
|
||||
var that = this;
|
||||
var assert_outputs = function (expected) {
|
||||
var assert_outputs = function (expected, msg_prefix) {
|
||||
var msg, i;
|
||||
msg_prefix = "(assert_outputs) "+(msg_prefix || 'no prefix')+": ";
|
||||
for (i = 0; i < that.get_cells_length(); i++) {
|
||||
if (expected[i] === undefined) {
|
||||
msg = 'cell ' + i + ' not executed';
|
||||
msg = msg_prefix + 'cell ' + i + ' not executed';
|
||||
that.test.assertFalse(that.cell_has_outputs(i), msg);
|
||||
|
||||
} else {
|
||||
msg = 'cell ' + i + ' executed';
|
||||
that.test.assertEquals(that.get_output_cell(i).text, expected[i], msg);
|
||||
msg = msg_prefix + 'cell ' + i + ' executed';
|
||||
var out = that.get_output_cell(i, undefined, msg_prefix).text
|
||||
that.test.assertEquals(out, expected[i], msg + 'out is: '+out);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -23,20 +25,24 @@ casper.notebook_test(function () {
|
||||
this.append_cell('print("c")');
|
||||
this.append_cell('print("d")');
|
||||
this.test.assertEquals(this.get_cells_length(), 4, "correct number of cells");
|
||||
});
|
||||
|
||||
this.evaluate(function () {
|
||||
IPython.notebook.unmark_all_cells();
|
||||
IPython.notebook.set_marked_indices([1, 2]);
|
||||
});
|
||||
this.then(function () {
|
||||
this.select_cell(1);
|
||||
this.select_cell(2, false);
|
||||
});
|
||||
|
||||
this.then(function () {
|
||||
this.evaluate(function () {
|
||||
IPython.notebook.clear_all_output();
|
||||
});
|
||||
})
|
||||
|
||||
this.then(function(){
|
||||
this.select_cell(1);
|
||||
this.validate_notebook_state('before execute', 'command', 1);
|
||||
this.validate_notebook_state('before execute 1', 'command', 1);
|
||||
this.select_cell(1);
|
||||
this.select_cell(2, false);
|
||||
this.trigger_keydown('ctrl-enter');
|
||||
});
|
||||
|
||||
@ -44,48 +50,29 @@ casper.notebook_test(function () {
|
||||
this.wait_for_output(2);
|
||||
|
||||
this.then(function () {
|
||||
assert_outputs([undefined, 'b\n', 'c\n', undefined]);
|
||||
this.validate_notebook_state('run marked cells', 'command', 2);
|
||||
assert_outputs([undefined, 'b\n', 'c\n', undefined], 'run selected 1');
|
||||
this.validate_notebook_state('run selected cells 1', 'command', 2);
|
||||
});
|
||||
|
||||
// execute cells in place when there are marked cells
|
||||
|
||||
// execute and insert below when there are selected cells
|
||||
this.then(function () {
|
||||
this.evaluate(function () {
|
||||
IPython.notebook.clear_all_output();
|
||||
});
|
||||
|
||||
this.select_cell(1);
|
||||
this.validate_notebook_state('before execute', 'command', 1);
|
||||
this.trigger_keydown('shift-enter');
|
||||
});
|
||||
|
||||
this.wait_for_output(1);
|
||||
this.wait_for_output(2);
|
||||
|
||||
this.then(function () {
|
||||
assert_outputs([undefined, 'b\n', 'c\n', undefined]);
|
||||
this.validate_notebook_state('run marked cells', 'command', 2);
|
||||
});
|
||||
|
||||
// execute and insert below when there are marked cells
|
||||
this.then(function () {
|
||||
this.evaluate(function () {
|
||||
IPython.notebook.clear_all_output();
|
||||
});
|
||||
|
||||
this.select_cell(1);
|
||||
this.validate_notebook_state('before execute', 'command', 1);
|
||||
this.validate_notebook_state('before execute 2', 'command', 1);
|
||||
this.evaluate(function () {
|
||||
$("#run_cell_insert_below").click();
|
||||
});
|
||||
});
|
||||
|
||||
this.wait_for_output(1);
|
||||
this.wait_for_output(2);
|
||||
|
||||
this.then(function () {
|
||||
assert_outputs([undefined, 'b\n', 'c\n', undefined]);
|
||||
this.validate_notebook_state('run marked cells', 'command', 2);
|
||||
assert_outputs([undefined, 'b\n', undefined, undefined , undefined],'run selected cells 2');
|
||||
this.validate_notebook_state('run selected cells 2', 'edit', 2);
|
||||
});
|
||||
|
||||
// check that it doesn't affect run all above
|
||||
@ -95,7 +82,7 @@ casper.notebook_test(function () {
|
||||
});
|
||||
|
||||
this.select_cell(1);
|
||||
this.validate_notebook_state('before execute', 'command', 1);
|
||||
this.validate_notebook_state('before execute 3', 'command', 1);
|
||||
this.evaluate(function () {
|
||||
$("#run_all_cells_above").click();
|
||||
});
|
||||
@ -104,7 +91,7 @@ casper.notebook_test(function () {
|
||||
this.wait_for_output(0);
|
||||
|
||||
this.then(function () {
|
||||
assert_outputs(['a\n', undefined, undefined, undefined]);
|
||||
assert_outputs(['a\n', undefined, undefined, undefined],'run cells above');
|
||||
this.validate_notebook_state('run cells above', 'command', 0);
|
||||
});
|
||||
|
||||
@ -115,7 +102,7 @@ casper.notebook_test(function () {
|
||||
});
|
||||
|
||||
this.select_cell(1);
|
||||
this.validate_notebook_state('before execute', 'command', 1);
|
||||
this.validate_notebook_state('before execute 4', 'command', 1);
|
||||
this.evaluate(function () {
|
||||
$("#run_all_cells_below").click();
|
||||
});
|
||||
@ -126,8 +113,8 @@ casper.notebook_test(function () {
|
||||
this.wait_for_output(3);
|
||||
|
||||
this.then(function () {
|
||||
assert_outputs([undefined, 'b\n', 'c\n', 'd\n']);
|
||||
this.validate_notebook_state('run cells below', 'command', 3);
|
||||
assert_outputs([undefined, 'b\n', undefined, 'c\n', 'd\n'],'run cells below');
|
||||
this.validate_notebook_state('run cells below', 'command', 4);
|
||||
});
|
||||
|
||||
// check that it doesn't affect run all
|
||||
@ -137,7 +124,7 @@ casper.notebook_test(function () {
|
||||
});
|
||||
|
||||
this.select_cell(1);
|
||||
this.validate_notebook_state('before execute', 'command', 1);
|
||||
this.validate_notebook_state('before execute 5', 'command', 1);
|
||||
this.evaluate(function () {
|
||||
$("#run_all_cells").click();
|
||||
});
|
||||
@ -149,7 +136,7 @@ casper.notebook_test(function () {
|
||||
this.wait_for_output(3);
|
||||
|
||||
this.then(function () {
|
||||
assert_outputs(['a\n', 'b\n', 'c\n', 'd\n']);
|
||||
this.validate_notebook_state('run all cells', 'command', 3);
|
||||
assert_outputs(['a\n', 'b\n', undefined, 'c\n', 'd\n'],'run all cells');
|
||||
this.validate_notebook_state('run all cells', 'command', 4);
|
||||
});
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
|
||||
// Test
|
||||
casper.notebook_test(function () {
|
||||
var that = this;
|
||||
|
||||
var a = 'print("a")';
|
||||
var index = this.append_cell(a);
|
||||
|
||||
var b = 'print("b")';
|
||||
index = this.append_cell(b);
|
||||
|
||||
var c = 'print("c")';
|
||||
index = this.append_cell(c);
|
||||
|
||||
this.then(function () {
|
||||
var selectedIndex = this.evaluate(function () {
|
||||
Jupyter.notebook.select(0);
|
||||
return Jupyter.notebook.get_selected_index();
|
||||
});
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_cells().length;
|
||||
}), 1, 'only one cell is marked programmatically');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_indices()[0];
|
||||
}), selectedIndex, 'marked cell is selected cell');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return $('.cell.marked').length;
|
||||
}), 0, 'no cells are marked visibily');
|
||||
|
||||
this.evaluate(function() {
|
||||
Jupyter.notebook.mark_all_cells();
|
||||
});
|
||||
|
||||
var cellCount = this.evaluate(function() {
|
||||
return Jupyter.notebook.ncells();
|
||||
});
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_cells().length;
|
||||
}), cellCount, 'mark_all');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return $('.cell.marked').length;
|
||||
}), cellCount, 'marked cells are marked visibily');
|
||||
|
||||
this.evaluate(function() {
|
||||
Jupyter.notebook.unmark_all_cells();
|
||||
});
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_cells().length;
|
||||
}), 1, 'unmark_all');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_indices()[0];
|
||||
}), selectedIndex, 'marked cell is selected cell');
|
||||
|
||||
this.evaluate(function() {
|
||||
Jupyter.notebook.set_marked_indices([1]);
|
||||
});
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_cells().length;
|
||||
}), 2, 'two cells are marked');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_marked_indices();
|
||||
}), [selectedIndex, 1], 'get/set_marked_indices');
|
||||
});
|
||||
});
|
49
notebook/tests/notebook/multiselect.js
Normal file
49
notebook/tests/notebook/multiselect.js
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
// Test
|
||||
casper.notebook_test(function () {
|
||||
var that = this;
|
||||
|
||||
var a = 'print("a")';
|
||||
var index = this.append_cell(a);
|
||||
|
||||
var b = 'print("b")';
|
||||
index = this.append_cell(b);
|
||||
|
||||
var c = 'print("c")';
|
||||
index = this.append_cell(c);
|
||||
|
||||
this.then(function () {
|
||||
var selectedIndex = this.evaluate(function () {
|
||||
Jupyter.notebook.select(0);
|
||||
return Jupyter.notebook.get_selected_index();
|
||||
});
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return Jupyter.notebook.get_selected_cells().length;
|
||||
}), 1, 'only one cell is selected programmatically');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
return $('.cell.jupyter-soft-selected').length;
|
||||
}), 1, 'one cell is selected');
|
||||
|
||||
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
Jupyter.notebook.extend_selection_by(1);
|
||||
return Jupyter.notebook.get_selected_cells().length;
|
||||
}), 2, 'extend selection by one');
|
||||
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
Jupyter.notebook.extend_selection_by(-1);
|
||||
return Jupyter.notebook.get_selected_cells().length;
|
||||
}), 1, 'contract selection by one');
|
||||
|
||||
this.test.assertEquals(this.evaluate(function() {
|
||||
Jupyter.notebook.select(1);
|
||||
Jupyter.notebook.extend_selection_by(-1);
|
||||
return Jupyter.notebook.get_selected_cells().length;
|
||||
}), 2, 'extend selection by one up');
|
||||
|
||||
});
|
||||
});
|
@ -4,11 +4,11 @@
|
||||
casper.notebook_test(function () {
|
||||
var that = this;
|
||||
|
||||
var assert_marked_cells = function (action, indices) {
|
||||
var marked = that.evaluate(function () {
|
||||
return IPython.notebook.get_marked_indices();
|
||||
var assert_selected_cells = function (action, indices) {
|
||||
var selected = that.evaluate(function () {
|
||||
return IPython.notebook.get_selected_cells_indices();
|
||||
});
|
||||
that.test.assertEquals(marked, indices, action + "; verify marked cells");
|
||||
that.test.assertEquals( selected, indices, action + "; verify selected cells");
|
||||
};
|
||||
|
||||
var assert_cells = function (action, cells, index) {
|
||||
@ -22,7 +22,7 @@ casper.notebook_test(function () {
|
||||
}
|
||||
|
||||
that.validate_notebook_state(action, 'command', index);
|
||||
assert_marked_cells(action, [index]);
|
||||
assert_selected_cells(action, [index]);
|
||||
};
|
||||
|
||||
var a = 'print("a")';
|
||||
@ -59,7 +59,7 @@ casper.notebook_test(function () {
|
||||
this.select_cell(1);
|
||||
this.trigger_keydown('esc');
|
||||
this.trigger_keydown('shift-j');
|
||||
assert_marked_cells("select cells 1-2", [1, 2]);
|
||||
assert_selected_cells("select cells 1-2", [1, 2]);
|
||||
this.trigger_keydown('shift-m');
|
||||
this.trigger_keydown('esc');
|
||||
assert_cells("merge cells 1-2", [a, bc, d], 1);
|
||||
@ -75,7 +75,7 @@ casper.notebook_test(function () {
|
||||
this.select_cell(3);
|
||||
this.trigger_keydown('esc');
|
||||
this.trigger_keydown('shift-k');
|
||||
assert_marked_cells("select cells 3-2", [2, 3]);
|
||||
assert_selected_cells("select cells 3-2", [2, 3]);
|
||||
this.trigger_keydown('shift-m');
|
||||
this.trigger_keydown('esc');
|
||||
assert_cells("merge cells 3-2", [a, bc, cd], 2);
|
||||
|
@ -166,11 +166,15 @@ casper.wait_for_output = function (cell_num, out_num) {
|
||||
},
|
||||
// pass parameter from the test suite js to the browser code js
|
||||
{c : cell_num, o : out_num});
|
||||
},
|
||||
function then() { },
|
||||
function timeout() {
|
||||
this.echo("wait_for_output timed out on cell "+cell_num+", waiting for "+out_num+" outputs .");
|
||||
var pn = this.evaluate(function get_prompt(c) {
|
||||
return (IPython.notebook.get_cell(c)|| {'input_prompt_number':'no cell'}).input_prompt_number;
|
||||
});
|
||||
this.echo("cell prompt was :'"+pn+"'.");
|
||||
});
|
||||
},
|
||||
function then() { },
|
||||
function timeout() {
|
||||
this.echo("wait_for_output timed out!");
|
||||
});
|
||||
};
|
||||
|
||||
@ -225,7 +229,8 @@ casper.cell_has_outputs = function (cell_num) {
|
||||
return result > 0;
|
||||
};
|
||||
|
||||
casper.get_output_cell = function (cell_num, out_num) {
|
||||
casper.get_output_cell = function (cell_num, out_num, message) {
|
||||
messsge = message+': ' ||'no category :'
|
||||
// return an output of a given cell
|
||||
out_num = out_num || 0;
|
||||
var result = casper.evaluate(function (c, o) {
|
||||
@ -240,7 +245,7 @@ casper.get_output_cell = function (cell_num, out_num) {
|
||||
},
|
||||
{c : cell_num});
|
||||
this.test.assertTrue(false,
|
||||
"Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
|
||||
message+"Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
|
||||
);
|
||||
} else {
|
||||
return result;
|
||||
@ -390,14 +395,19 @@ casper.cell_element_function = function(index, selector, function_name, function
|
||||
casper.validate_notebook_state = function(message, mode, cell_index) {
|
||||
// Validate the entire dual mode state of the notebook. Make sure no more than
|
||||
// one cell is selected, focused, in edit mode, etc...
|
||||
|
||||
// General tests.
|
||||
this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
|
||||
message + '; keyboard and notebook modes match');
|
||||
// Is the selected cell the only cell that is selected?
|
||||
if (cell_index!==undefined) {
|
||||
this.test.assert(this.is_only_cell_selected(cell_index),
|
||||
message + '; cell ' + cell_index + ' is the only cell selected');
|
||||
message + '; expecting cell ' + cell_index + ' to be the only cell selected. Got selected cell(s):'+
|
||||
(function(){
|
||||
return casper.evaluate(function(){
|
||||
return IPython.notebook.get_selected_cells_indices();
|
||||
})
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
// Mode specific tests.
|
||||
@ -429,11 +439,11 @@ casper.validate_notebook_state = function(message, mode, cell_index) {
|
||||
}
|
||||
};
|
||||
|
||||
casper.select_cell = function(index) {
|
||||
casper.select_cell = function(index, moveanchor) {
|
||||
// Select a cell in the notebook.
|
||||
this.evaluate(function (i) {
|
||||
IPython.notebook.select(i);
|
||||
}, {i: index});
|
||||
this.evaluate(function (i, moveanchor) {
|
||||
IPython.notebook.select(i, moveanchor);
|
||||
}, {i: index, moveanchor: moveanchor});
|
||||
};
|
||||
|
||||
casper.click_cell_editor = function(index) {
|
||||
|
1
setup.py
1
setup.py
@ -93,7 +93,6 @@ for more information.
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -18,13 +18,18 @@ var rjs_config = {
|
||||
jquery: 'components/jquery/jquery.min',
|
||||
bootstrap: 'components/bootstrap/js/bootstrap.min',
|
||||
bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
|
||||
jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
|
||||
"jquery-ui": 'components/jquery-ui/ui/minified/jquery-ui.min',
|
||||
moment: 'components/moment/moment',
|
||||
codemirror: 'components/codemirror',
|
||||
termjs: 'components/term.js/src/term',
|
||||
typeahead: 'components/jquery-typeahead/dist/jquery.typeahead',
|
||||
contents: 'empty:'
|
||||
},
|
||||
map: { // for backward compatibility
|
||||
"*": {
|
||||
"jqueryui": "jquery-ui",
|
||||
}
|
||||
},
|
||||
shim: {
|
||||
typeahead: {
|
||||
deps: ["jquery"],
|
||||
@ -45,7 +50,7 @@ var rjs_config = {
|
||||
deps: ["bootstrap"],
|
||||
exports: "Tour"
|
||||
},
|
||||
jqueryui: {
|
||||
"jquery-ui": {
|
||||
deps: ["jquery"],
|
||||
exports: "$"
|
||||
}
|
||||
|
101
tools/secure_notebook.py
Normal file
101
tools/secure_notebook.py
Normal file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from notebook.auth import passwd
|
||||
from traitlets.config.loader import JSONFileConfigLoader, ConfigFileNotFound
|
||||
import six
|
||||
from jupyter_core.paths import jupyter_config_dir
|
||||
from traitlets.config import Config
|
||||
|
||||
|
||||
|
||||
from OpenSSL import crypto, SSL
|
||||
from socket import gethostname
|
||||
from pprint import pprint
|
||||
from time import gmtime, mktime
|
||||
from os.path import exists, join
|
||||
|
||||
import io
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
def create_self_signed_cert(cert_dir, keyfile, certfiile):
|
||||
"""
|
||||
If datacard.crt and datacard.key don't exist in cert_dir, create a new
|
||||
self-signed cert and keypair and write them into that directory.
|
||||
"""
|
||||
|
||||
if not exists(join(cert_dir, certfiile)) \
|
||||
or not exists(join(cert_dir, keyfile)):
|
||||
|
||||
# create a key pair
|
||||
k = crypto.PKey()
|
||||
k.generate_key(crypto.TYPE_RSA, 1024)
|
||||
|
||||
# create a self-signed cert
|
||||
cert = crypto.X509()
|
||||
cert.get_subject().C = "US"
|
||||
cert.get_subject().ST = "Jupyter notebook self-signed certificate"
|
||||
cert.get_subject().L = "Jupyter notebook self-signed certificate"
|
||||
cert.get_subject().O = "Jupyter notebook self-signed certificate"
|
||||
cert.get_subject().OU = "my organization"
|
||||
cert.get_subject().CN = "Jupyter notebook self-signed certificate"
|
||||
cert.set_serial_number(1000)
|
||||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(365*24*60*60)
|
||||
cert.set_issuer(cert.get_subject())
|
||||
cert.set_pubkey(k)
|
||||
cert.sign(k, 'sha256')
|
||||
|
||||
with io.open(join(cert_dir, certfile), "wt") as f:
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf8'))
|
||||
with io.open(join(cert_dir, keyfile), "wt") as f:
|
||||
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode('utf8'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("This guide you into securing your notebook server")
|
||||
print("first choose a password.")
|
||||
pw = passwd()
|
||||
print("We will store your password encrypted in the notebook configuration file: ")
|
||||
print(pw)
|
||||
|
||||
loader = JSONFileConfigLoader('jupyter_notebook_config.json', jupyter_config_dir())
|
||||
try:
|
||||
config = loader.load_config()
|
||||
except ConfigFileNotFound:
|
||||
config = Config()
|
||||
|
||||
config.NotebookApp.password = pw
|
||||
|
||||
with io.open(os.path.join(jupyter_config_dir(), 'jupyter_notebook_config.json'), 'w') as f:
|
||||
f.write(six.u(json.dumps(config, indent=2)))
|
||||
|
||||
print('... done')
|
||||
print()
|
||||
|
||||
print("Now let's generate self-signed certificates to secure your connexion.")
|
||||
print("where should the certificate live?")
|
||||
location = input('path [~/.ssh]: ')
|
||||
if not location.strip():
|
||||
location = os.path.expanduser('~/.ssh')
|
||||
keyfile = input('keyfile name [jupyter_server.key]: ')
|
||||
if not keyfile.strip():
|
||||
keyfile = 'jupyter_server.key'
|
||||
certfile = input('certfile name [jupyter_server.crt]: ')
|
||||
if not certfile.strip():
|
||||
certfile = 'jupyter_server.crt'
|
||||
|
||||
create_self_signed_cert(location, keyfile, certfile)
|
||||
|
||||
fullkey = os.path.join(location, keyfile)
|
||||
fullcrt = os.path.join(location, certfile)
|
||||
|
||||
config.NotebookApp.certfile = fullcrt
|
||||
config.NotebookApp.keyfile = fullkey
|
||||
|
||||
with io.open(os.path.join(jupyter_config_dir(), 'jupyter_notebook_config.json'), 'w') as f:
|
||||
f.write(six.u(json.dumps(config, indent=2)))
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user