Conflicts: examples/Interactive Widgets/Widget Events.ipynb
commit
9ca509d915
@ -0,0 +1,311 @@
|
||||
"""WebsocketProtocol76 from tornado 3.2.2 for tornado >= 4.0
|
||||
|
||||
The contents of this file are Copyright (c) Tornado
|
||||
Used under the Apache 2.0 license
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function, with_statement
|
||||
# Author: Jacob Kristhammar, 2010
|
||||
|
||||
import functools
|
||||
import hashlib
|
||||
import struct
|
||||
import time
|
||||
import tornado.escape
|
||||
import tornado.web
|
||||
|
||||
from tornado.log import gen_log, app_log
|
||||
from tornado.util import bytes_type, unicode_type
|
||||
|
||||
from tornado.websocket import WebSocketHandler, WebSocketProtocol13
|
||||
|
||||
class AllowDraftWebSocketHandler(WebSocketHandler):
|
||||
"""Restore Draft76 support for tornado 4
|
||||
|
||||
Remove when we can run tests without phantomjs + qt4
|
||||
"""
|
||||
|
||||
# get is unmodified except between the BEGIN/END PATCH lines
|
||||
@tornado.web.asynchronous
|
||||
def get(self, *args, **kwargs):
|
||||
self.open_args = args
|
||||
self.open_kwargs = kwargs
|
||||
|
||||
# Upgrade header should be present and should be equal to WebSocket
|
||||
if self.request.headers.get("Upgrade", "").lower() != 'websocket':
|
||||
self.set_status(400)
|
||||
self.finish("Can \"Upgrade\" only to \"WebSocket\".")
|
||||
return
|
||||
|
||||
# Connection header should be upgrade. Some proxy servers/load balancers
|
||||
# might mess with it.
|
||||
headers = self.request.headers
|
||||
connection = map(lambda s: s.strip().lower(), headers.get("Connection", "").split(","))
|
||||
if 'upgrade' not in connection:
|
||||
self.set_status(400)
|
||||
self.finish("\"Connection\" must be \"Upgrade\".")
|
||||
return
|
||||
|
||||
# Handle WebSocket Origin naming convention differences
|
||||
# The difference between version 8 and 13 is that in 8 the
|
||||
# client sends a "Sec-Websocket-Origin" header and in 13 it's
|
||||
# simply "Origin".
|
||||
if "Origin" in self.request.headers:
|
||||
origin = self.request.headers.get("Origin")
|
||||
else:
|
||||
origin = self.request.headers.get("Sec-Websocket-Origin", None)
|
||||
|
||||
|
||||
# If there was an origin header, check to make sure it matches
|
||||
# according to check_origin. When the origin is None, we assume it
|
||||
# did not come from a browser and that it can be passed on.
|
||||
if origin is not None and not self.check_origin(origin):
|
||||
self.set_status(403)
|
||||
self.finish("Cross origin websockets not allowed")
|
||||
return
|
||||
|
||||
self.stream = self.request.connection.detach()
|
||||
self.stream.set_close_callback(self.on_connection_close)
|
||||
|
||||
if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
|
||||
self.ws_connection = WebSocketProtocol13(self)
|
||||
self.ws_connection.accept_connection()
|
||||
#--------------- BEGIN PATCH ----------------
|
||||
elif (self.allow_draft76() and
|
||||
"Sec-WebSocket-Version" not in self.request.headers):
|
||||
self.ws_connection = WebSocketProtocol76(self)
|
||||
self.ws_connection.accept_connection()
|
||||
#--------------- END PATCH ----------------
|
||||
else:
|
||||
if not self.stream.closed():
|
||||
self.stream.write(tornado.escape.utf8(
|
||||
"HTTP/1.1 426 Upgrade Required\r\n"
|
||||
"Sec-WebSocket-Version: 8\r\n\r\n"))
|
||||
self.stream.close()
|
||||
|
||||
# 3.2 methods removed in 4.0:
|
||||
def allow_draft76(self):
|
||||
"""Using this class allows draft76 connections by default"""
|
||||
return True
|
||||
|
||||
def get_websocket_scheme(self):
|
||||
"""Return the url scheme used for this request, either "ws" or "wss".
|
||||
This is normally decided by HTTPServer, but applications
|
||||
may wish to override this if they are using an SSL proxy
|
||||
that does not provide the X-Scheme header as understood
|
||||
by HTTPServer.
|
||||
Note that this is only used by the draft76 protocol.
|
||||
"""
|
||||
return "wss" if self.request.protocol == "https" else "ws"
|
||||
|
||||
|
||||
|
||||
# No modifications from tornado-3.2.2 below this line
|
||||
|
||||
class WebSocketProtocol(object):
|
||||
"""Base class for WebSocket protocol versions.
|
||||
"""
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
self.request = handler.request
|
||||
self.stream = handler.stream
|
||||
self.client_terminated = False
|
||||
self.server_terminated = False
|
||||
|
||||
def async_callback(self, callback, *args, **kwargs):
|
||||
"""Wrap callbacks with this if they are used on asynchronous requests.
|
||||
|
||||
Catches exceptions properly and closes this WebSocket if an exception
|
||||
is uncaught.
|
||||
"""
|
||||
if args or kwargs:
|
||||
callback = functools.partial(callback, *args, **kwargs)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return callback(*args, **kwargs)
|
||||
except Exception:
|
||||
app_log.error("Uncaught exception in %s",
|
||||
self.request.path, exc_info=True)
|
||||
self._abort()
|
||||
return wrapper
|
||||
|
||||
def on_connection_close(self):
|
||||
self._abort()
|
||||
|
||||
def _abort(self):
|
||||
"""Instantly aborts the WebSocket connection by closing the socket"""
|
||||
self.client_terminated = True
|
||||
self.server_terminated = True
|
||||
self.stream.close() # forcibly tear down the connection
|
||||
self.close() # let the subclass cleanup
|
||||
|
||||
|
||||
class WebSocketProtocol76(WebSocketProtocol):
|
||||
"""Implementation of the WebSockets protocol, version hixie-76.
|
||||
|
||||
This class provides basic functionality to process WebSockets requests as
|
||||
specified in
|
||||
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||
"""
|
||||
def __init__(self, handler):
|
||||
WebSocketProtocol.__init__(self, handler)
|
||||
self.challenge = None
|
||||
self._waiting = None
|
||||
|
||||
def accept_connection(self):
|
||||
try:
|
||||
self._handle_websocket_headers()
|
||||
except ValueError:
|
||||
gen_log.debug("Malformed WebSocket request received")
|
||||
self._abort()
|
||||
return
|
||||
|
||||
scheme = self.handler.get_websocket_scheme()
|
||||
|
||||
# draft76 only allows a single subprotocol
|
||||
subprotocol_header = ''
|
||||
subprotocol = self.request.headers.get("Sec-WebSocket-Protocol", None)
|
||||
if subprotocol:
|
||||
selected = self.handler.select_subprotocol([subprotocol])
|
||||
if selected:
|
||||
assert selected == subprotocol
|
||||
subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % selected
|
||||
|
||||
# Write the initial headers before attempting to read the challenge.
|
||||
# This is necessary when using proxies (such as HAProxy), which
|
||||
# need to see the Upgrade headers before passing through the
|
||||
# non-HTTP traffic that follows.
|
||||
self.stream.write(tornado.escape.utf8(
|
||||
"HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
||||
"Upgrade: WebSocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Server: TornadoServer/%(version)s\r\n"
|
||||
"Sec-WebSocket-Origin: %(origin)s\r\n"
|
||||
"Sec-WebSocket-Location: %(scheme)s://%(host)s%(uri)s\r\n"
|
||||
"%(subprotocol)s"
|
||||
"\r\n" % (dict(
|
||||
version=tornado.version,
|
||||
origin=self.request.headers["Origin"],
|
||||
scheme=scheme,
|
||||
host=self.request.host,
|
||||
uri=self.request.uri,
|
||||
subprotocol=subprotocol_header))))
|
||||
self.stream.read_bytes(8, self._handle_challenge)
|
||||
|
||||
def challenge_response(self, challenge):
|
||||
"""Generates the challenge response that's needed in the handshake
|
||||
|
||||
The challenge parameter should be the raw bytes as sent from the
|
||||
client.
|
||||
"""
|
||||
key_1 = self.request.headers.get("Sec-Websocket-Key1")
|
||||
key_2 = self.request.headers.get("Sec-Websocket-Key2")
|
||||
try:
|
||||
part_1 = self._calculate_part(key_1)
|
||||
part_2 = self._calculate_part(key_2)
|
||||
except ValueError:
|
||||
raise ValueError("Invalid Keys/Challenge")
|
||||
return self._generate_challenge_response(part_1, part_2, challenge)
|
||||
|
||||
def _handle_challenge(self, challenge):
|
||||
try:
|
||||
challenge_response = self.challenge_response(challenge)
|
||||
except ValueError:
|
||||
gen_log.debug("Malformed key data in WebSocket request")
|
||||
self._abort()
|
||||
return
|
||||
self._write_response(challenge_response)
|
||||
|
||||
def _write_response(self, challenge):
|
||||
self.stream.write(challenge)
|
||||
self.async_callback(self.handler.open)(*self.handler.open_args, **self.handler.open_kwargs)
|
||||
self._receive_message()
|
||||
|
||||
def _handle_websocket_headers(self):
|
||||
"""Verifies all invariant- and required headers
|
||||
|
||||
If a header is missing or have an incorrect value ValueError will be
|
||||
raised
|
||||
"""
|
||||
fields = ("Origin", "Host", "Sec-Websocket-Key1",
|
||||
"Sec-Websocket-Key2")
|
||||
if not all(map(lambda f: self.request.headers.get(f), fields)):
|
||||
raise ValueError("Missing/Invalid WebSocket headers")
|
||||
|
||||
def _calculate_part(self, key):
|
||||
"""Processes the key headers and calculates their key value.
|
||||
|
||||
Raises ValueError when feed invalid key."""
|
||||
# pyflakes complains about variable reuse if both of these lines use 'c'
|
||||
number = int(''.join(c for c in key if c.isdigit()))
|
||||
spaces = len([c2 for c2 in key if c2.isspace()])
|
||||
try:
|
||||
key_number = number // spaces
|
||||
except (ValueError, ZeroDivisionError):
|
||||
raise ValueError
|
||||
return struct.pack(">I", key_number)
|
||||
|
||||
def _generate_challenge_response(self, part_1, part_2, part_3):
|
||||
m = hashlib.md5()
|
||||
m.update(part_1)
|
||||
m.update(part_2)
|
||||
m.update(part_3)
|
||||
return m.digest()
|
||||
|
||||
def _receive_message(self):
|
||||
self.stream.read_bytes(1, self._on_frame_type)
|
||||
|
||||
def _on_frame_type(self, byte):
|
||||
frame_type = ord(byte)
|
||||
if frame_type == 0x00:
|
||||
self.stream.read_until(b"\xff", self._on_end_delimiter)
|
||||
elif frame_type == 0xff:
|
||||
self.stream.read_bytes(1, self._on_length_indicator)
|
||||
else:
|
||||
self._abort()
|
||||
|
||||
def _on_end_delimiter(self, frame):
|
||||
if not self.client_terminated:
|
||||
self.async_callback(self.handler.on_message)(
|
||||
frame[:-1].decode("utf-8", "replace"))
|
||||
if not self.client_terminated:
|
||||
self._receive_message()
|
||||
|
||||
def _on_length_indicator(self, byte):
|
||||
if ord(byte) != 0x00:
|
||||
self._abort()
|
||||
return
|
||||
self.client_terminated = True
|
||||
self.close()
|
||||
|
||||
def write_message(self, message, binary=False):
|
||||
"""Sends the given message to the client of this Web Socket."""
|
||||
if binary:
|
||||
raise ValueError(
|
||||
"Binary messages not supported by this version of websockets")
|
||||
if isinstance(message, unicode_type):
|
||||
message = message.encode("utf-8")
|
||||
assert isinstance(message, bytes_type)
|
||||
self.stream.write(b"\x00" + message + b"\xff")
|
||||
|
||||
def write_ping(self, data):
|
||||
"""Send ping frame."""
|
||||
raise ValueError("Ping messages not supported by this version of websockets")
|
||||
|
||||
def close(self):
|
||||
"""Closes the WebSocket connection."""
|
||||
if not self.server_terminated:
|
||||
if not self.stream.closed():
|
||||
self.stream.write("\xff\x00")
|
||||
self.server_terminated = True
|
||||
if self.client_terminated:
|
||||
if self._waiting is not None:
|
||||
self.stream.io_loop.remove_timeout(self._waiting)
|
||||
self._waiting = None
|
||||
self.stream.close()
|
||||
elif self._waiting is None:
|
||||
self._waiting = self.stream.io_loop.add_timeout(
|
||||
time.time() + 5, self._abort)
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
#encoding: utf-8
|
||||
"""Tornado handlers for the terminal emulator."""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from tornado import web
|
||||
from ..base.handlers import IPythonHandler, path_regex
|
||||
from ..utils import url_escape
|
||||
|
||||
class EditorHandler(IPythonHandler):
|
||||
"""Render the text editor interface."""
|
||||
@web.authenticated
|
||||
def get(self, path):
|
||||
path = path.strip('/')
|
||||
if not self.contents_manager.file_exists(path):
|
||||
raise web.HTTPError(404, u'File does not exist: %s' % path)
|
||||
|
||||
basename = path.rsplit('/', 1)[-1]
|
||||
self.write(self.render_template('edit.html',
|
||||
file_path=url_escape(path),
|
||||
basename=basename,
|
||||
page_title=basename + " (editing)",
|
||||
)
|
||||
)
|
||||
|
||||
default_handlers = [
|
||||
(r"/edit%s" % path_regex, EditorHandler),
|
||||
]
|
||||
@ -0,0 +1,54 @@
|
||||
"""Serve files directly from the ContentsManager."""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import mimetypes
|
||||
import json
|
||||
import base64
|
||||
|
||||
from tornado import web
|
||||
|
||||
from IPython.html.base.handlers import IPythonHandler
|
||||
|
||||
class FilesHandler(IPythonHandler):
|
||||
"""serve files via ContentsManager"""
|
||||
|
||||
@web.authenticated
|
||||
def get(self, path):
|
||||
cm = self.contents_manager
|
||||
if cm.is_hidden(path):
|
||||
self.log.info("Refusing to serve hidden file, via 404 Error")
|
||||
raise web.HTTPError(404)
|
||||
|
||||
path = path.strip('/')
|
||||
if '/' in path:
|
||||
_, name = path.rsplit('/', 1)
|
||||
else:
|
||||
name = path
|
||||
|
||||
model = cm.get(path)
|
||||
|
||||
if self.get_argument("download", False):
|
||||
self.set_header('Content-Disposition','attachment; filename="%s"' % name)
|
||||
|
||||
if model['type'] == 'notebook':
|
||||
self.set_header('Content-Type', 'application/json')
|
||||
else:
|
||||
cur_mime = mimetypes.guess_type(name)[0]
|
||||
if cur_mime is not None:
|
||||
self.set_header('Content-Type', cur_mime)
|
||||
|
||||
if model['format'] == 'base64':
|
||||
b64_bytes = model['content'].encode('ascii')
|
||||
self.write(base64.decodestring(b64_bytes))
|
||||
elif model['format'] == 'json':
|
||||
self.write(json.dumps(model['content']))
|
||||
else:
|
||||
self.write(model['content'])
|
||||
self.flush()
|
||||
|
||||
default_handlers = [
|
||||
(r"/files/(.*)", FilesHandler),
|
||||
]
|
||||
@ -0,0 +1 @@
|
||||
from .manager import ConfigManager
|
||||
@ -0,0 +1,44 @@
|
||||
"""Tornado handlers for frontend config storage."""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import json
|
||||
import os
|
||||
import io
|
||||
import errno
|
||||
from tornado import web
|
||||
|
||||
from IPython.utils.py3compat import PY3
|
||||
from ...base.handlers import IPythonHandler, json_errors
|
||||
|
||||
class ConfigHandler(IPythonHandler):
|
||||
SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH')
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
def get(self, section_name):
|
||||
self.set_header("Content-Type", 'application/json')
|
||||
self.finish(json.dumps(self.config_manager.get(section_name)))
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
def put(self, section_name):
|
||||
data = self.get_json_body() # Will raise 400 if content is not valid JSON
|
||||
self.config_manager.set(section_name, data)
|
||||
self.set_status(204)
|
||||
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
def patch(self, section_name):
|
||||
new_data = self.get_json_body()
|
||||
section = self.config_manager.update(section_name, new_data)
|
||||
self.finish(json.dumps(section))
|
||||
|
||||
|
||||
# URL to handler mappings
|
||||
|
||||
section_name_regex = r"(?P<section_name>\w+)"
|
||||
|
||||
default_handlers = [
|
||||
(r"/api/config/%s" % section_name_regex, ConfigHandler),
|
||||
]
|
||||
@ -0,0 +1,90 @@
|
||||
"""Manager to read and modify frontend config data in JSON files.
|
||||
"""
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import errno
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
||||
from IPython.config import LoggingConfigurable
|
||||
from IPython.utils.path import locate_profile
|
||||
from IPython.utils.py3compat import PY3
|
||||
from IPython.utils.traitlets import Unicode
|
||||
|
||||
|
||||
def recursive_update(target, new):
|
||||
"""Recursively update one dictionary using another.
|
||||
|
||||
None values will delete their keys.
|
||||
"""
|
||||
for k, v in new.items():
|
||||
if isinstance(v, dict):
|
||||
if k not in target:
|
||||
target[k] = {}
|
||||
recursive_update(target[k], v)
|
||||
if not target[k]:
|
||||
# Prune empty subdicts
|
||||
del target[k]
|
||||
|
||||
elif v is None:
|
||||
target.pop(k, None)
|
||||
|
||||
else:
|
||||
target[k] = v
|
||||
|
||||
|
||||
class ConfigManager(LoggingConfigurable):
|
||||
profile_dir = Unicode()
|
||||
def _profile_dir_default(self):
|
||||
return locate_profile()
|
||||
|
||||
@property
|
||||
def config_dir(self):
|
||||
return os.path.join(self.profile_dir, 'nbconfig')
|
||||
|
||||
def ensure_config_dir_exists(self):
|
||||
try:
|
||||
os.mkdir(self.config_dir, 0o755)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def file_name(self, section_name):
|
||||
return os.path.join(self.config_dir, section_name+'.json')
|
||||
|
||||
def get(self, section_name):
|
||||
"""Retrieve the config data for the specified section.
|
||||
|
||||
Returns the data as a dictionary, or an empty dictionary if the file
|
||||
doesn't exist.
|
||||
"""
|
||||
filename = self.file_name(section_name)
|
||||
if os.path.isfile(filename):
|
||||
with io.open(filename, encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
return {}
|
||||
|
||||
def set(self, section_name, data):
|
||||
"""Store the given config data.
|
||||
"""
|
||||
filename = self.file_name(section_name)
|
||||
self.ensure_config_dir_exists()
|
||||
|
||||
if PY3:
|
||||
f = io.open(filename, 'w', encoding='utf-8')
|
||||
else:
|
||||
f = open(filename, 'wb')
|
||||
with f:
|
||||
json.dump(data, f)
|
||||
|
||||
def update(self, section_name, new_data):
|
||||
"""Modify the config section by recursively updating it with new_data.
|
||||
|
||||
Returns the modified config data as a dictionary.
|
||||
"""
|
||||
data = self.get(section_name)
|
||||
recursive_update(data, new_data)
|
||||
self.set(section_name, data)
|
||||
return data
|
||||
@ -0,0 +1,68 @@
|
||||
# coding: utf-8
|
||||
"""Test the config webservice API."""
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from IPython.html.utils import url_path_join
|
||||
from IPython.html.tests.launchnotebook import NotebookTestBase
|
||||
|
||||
|
||||
class ConfigAPI(object):
|
||||
"""Wrapper for notebook API calls."""
|
||||
def __init__(self, base_url):
|
||||
self.base_url = base_url
|
||||
|
||||
def _req(self, verb, section, body=None):
|
||||
response = requests.request(verb,
|
||||
url_path_join(self.base_url, 'api/config', section),
|
||||
data=body,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
def get(self, section):
|
||||
return self._req('GET', section)
|
||||
|
||||
def set(self, section, values):
|
||||
return self._req('PUT', section, json.dumps(values))
|
||||
|
||||
def modify(self, section, values):
|
||||
return self._req('PATCH', section, json.dumps(values))
|
||||
|
||||
class APITest(NotebookTestBase):
|
||||
"""Test the config web service API"""
|
||||
def setUp(self):
|
||||
self.config_api = ConfigAPI(self.base_url())
|
||||
|
||||
def test_create_retrieve_config(self):
|
||||
sample = {'foo': 'bar', 'baz': 73}
|
||||
r = self.config_api.set('example', sample)
|
||||
self.assertEqual(r.status_code, 204)
|
||||
|
||||
r = self.config_api.get('example')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.json(), sample)
|
||||
|
||||
def test_modify(self):
|
||||
sample = {'foo': 'bar', 'baz': 73,
|
||||
'sub': {'a': 6, 'b': 7}, 'sub2': {'c': 8}}
|
||||
self.config_api.set('example', sample)
|
||||
|
||||
r = self.config_api.modify('example', {'foo': None, # should delete foo
|
||||
'baz': 75,
|
||||
'wib': [1,2,3],
|
||||
'sub': {'a': 8, 'b': None, 'd': 9},
|
||||
'sub2': {'c': None} # should delete sub2
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.json(), {'baz': 75, 'wib': [1,2,3],
|
||||
'sub': {'a': 8, 'd': 9}})
|
||||
|
||||
def test_get_unknown(self):
|
||||
# We should get an empty config dictionary instead of a 404
|
||||
r = self.config_api.get('nonexistant')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.json(), {})
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
# URI for the CSP Report. Included here to prevent a cyclic dependency.
|
||||
# csp_report_uri is needed both by the BaseHandler (for setting the report-uri)
|
||||
# and by the CSPReportHandler (which depends on the BaseHandler).
|
||||
csp_report_uri = r"/api/security/csp-report"
|
||||
@ -0,0 +1,23 @@
|
||||
"""Tornado handlers for security logging."""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from tornado import gen, web
|
||||
|
||||
from ...base.handlers import IPythonHandler, json_errors
|
||||
from . import csp_report_uri
|
||||
|
||||
class CSPReportHandler(IPythonHandler):
|
||||
'''Accepts a content security policy violation report'''
|
||||
@web.authenticated
|
||||
@json_errors
|
||||
def post(self):
|
||||
'''Log a content security policy violation report'''
|
||||
csp_report = self.get_json_body()
|
||||
self.log.warn("Content security violation: %s",
|
||||
self.request.body.decode('utf8', 'replace'))
|
||||
|
||||
default_handlers = [
|
||||
(csp_report_uri, CSPReportHandler)
|
||||
]
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
@ -0,0 +1,83 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/notificationwidget',
|
||||
], function($, notificationwidget) {
|
||||
"use strict";
|
||||
|
||||
// store reference to the NotificationWidget class
|
||||
var NotificationWidget = notificationwidget.NotificationWidget;
|
||||
|
||||
/**
|
||||
* Construct the NotificationArea object. Options are:
|
||||
* events: $(Events) instance
|
||||
* save_widget: SaveWidget instance
|
||||
* notebook: Notebook instance
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} selector - a jQuery selector string for the
|
||||
* notification area element
|
||||
* @param {Object} [options] - a dictionary of keyword arguments.
|
||||
*/
|
||||
var NotificationArea = function (selector, options) {
|
||||
this.selector = selector;
|
||||
this.events = options.events;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
}
|
||||
this.widget_dict = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a widget by name, creating it if it doesn't exist.
|
||||
*
|
||||
* @method widget
|
||||
* @param {string} name - the widget name
|
||||
*/
|
||||
NotificationArea.prototype.widget = function (name) {
|
||||
if (this.widget_dict[name] === undefined) {
|
||||
return this.new_notification_widget(name);
|
||||
}
|
||||
return this.get_widget(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a widget by name, throwing an error if it doesn't exist.
|
||||
*
|
||||
* @method get_widget
|
||||
* @param {string} name - the widget name
|
||||
*/
|
||||
NotificationArea.prototype.get_widget = function (name) {
|
||||
if(this.widget_dict[name] === undefined) {
|
||||
throw('no widgets with this name');
|
||||
}
|
||||
return this.widget_dict[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new notification widget with the given name. The
|
||||
* widget must not already exist.
|
||||
*
|
||||
* @method new_notification_widget
|
||||
* @param {string} name - the widget name
|
||||
*/
|
||||
NotificationArea.prototype.new_notification_widget = function (name) {
|
||||
if (this.widget_dict[name] !== undefined) {
|
||||
throw('widget with that name already exists!');
|
||||
}
|
||||
|
||||
// create the element for the notification widget and add it
|
||||
// to the notification aread element
|
||||
var div = $('<div/>').attr('id', 'notification_' + name);
|
||||
$(this.selector).append(div);
|
||||
|
||||
// create the widget object and return it
|
||||
this.widget_dict[name] = new NotificationWidget('#notification_' + name);
|
||||
return this.widget_dict[name];
|
||||
};
|
||||
|
||||
return {'NotificationArea': NotificationArea};
|
||||
});
|
||||
@ -0,0 +1,160 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
], function(IPython, $) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Construct a NotificationWidget object.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} selector - a jQuery selector string for the
|
||||
* notification widget element
|
||||
*/
|
||||
var NotificationWidget = function (selector) {
|
||||
this.selector = selector;
|
||||
this.timeout = null;
|
||||
this.busy = false;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.style();
|
||||
}
|
||||
this.element.hide();
|
||||
this.inner = $('<span/>');
|
||||
this.element.append(this.inner);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the 'notification_widget' CSS class to the widget element.
|
||||
*
|
||||
* @method style
|
||||
*/
|
||||
NotificationWidget.prototype.style = function () {
|
||||
this.element.addClass('notification_widget');
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the notification widget message to display for a certain
|
||||
* amount of time (timeout). The widget will be shown forever if
|
||||
* timeout is <= 0 or undefined. If the widget is clicked while it
|
||||
* is still displayed, execute an optional callback
|
||||
* (click_callback). If the callback returns false, it will
|
||||
* prevent the notification from being dismissed.
|
||||
*
|
||||
* Options:
|
||||
* class - CSS class name for styling
|
||||
* icon - CSS class name for the widget icon
|
||||
* title - HTML title attribute for the widget
|
||||
*
|
||||
* @method set_message
|
||||
* @param {string} msg - The notification to display
|
||||
* @param {integer} [timeout] - The amount of time in milliseconds to display the widget
|
||||
* @param {function} [click_callback] - The function to run when the widget is clicked
|
||||
* @param {Object} [options] - Additional options
|
||||
*/
|
||||
NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
|
||||
// unbind potential previous callback
|
||||
this.element.unbind('click');
|
||||
this.inner.attr('class', options.icon);
|
||||
this.inner.attr('title', options.title);
|
||||
this.inner.text(msg);
|
||||
this.element.fadeIn(100);
|
||||
|
||||
// reset previous set style
|
||||
this.element.removeClass();
|
||||
this.style();
|
||||
if (options.class) {
|
||||
this.element.addClass(options.class);
|
||||
}
|
||||
|
||||
// clear previous timer
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
|
||||
// set the timer if a timeout is given
|
||||
var that = this;
|
||||
if (timeout !== undefined && timeout >= 0) {
|
||||
this.timeout = setTimeout(function () {
|
||||
that.element.fadeOut(100, function () {that.inner.text('');});
|
||||
that.element.unbind('click');
|
||||
that.timeout = null;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// bind the click callback if it is given
|
||||
if (click_callback !== undefined) {
|
||||
this.element.click(function () {
|
||||
if (click_callback() !== false) {
|
||||
that.element.fadeOut(100, function () {that.inner.text('');});
|
||||
}
|
||||
that.element.unbind('click');
|
||||
if (that.timeout !== null) {
|
||||
clearTimeout(that.timeout);
|
||||
that.timeout = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Display an information message (styled with the 'info'
|
||||
* class). Arguments are the same as in set_message. Default
|
||||
* timeout is 3500 milliseconds.
|
||||
*
|
||||
* @method info
|
||||
*/
|
||||
NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
options.class = options.class + ' info';
|
||||
timeout = timeout || 3500;
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a warning message (styled with the 'warning'
|
||||
* class). Arguments are the same as in set_message. Messages are
|
||||
* sticky by default.
|
||||
*
|
||||
* @method warning
|
||||
*/
|
||||
NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
options.class = options.class + ' warning';
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a danger message (styled with the 'danger'
|
||||
* class). Arguments are the same as in set_message. Messages are
|
||||
* sticky by default.
|
||||
*
|
||||
* @method danger
|
||||
*/
|
||||
NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
options.class = options.class + ' danger';
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text of the widget message.
|
||||
*
|
||||
* @method get_message
|
||||
* @return {string} - the message text
|
||||
*/
|
||||
NotificationWidget.prototype.get_message = function () {
|
||||
return this.inner.html();
|
||||
};
|
||||
|
||||
// For backwards compatibility.
|
||||
IPython.NotificationWidget = NotificationWidget;
|
||||
|
||||
return {'NotificationWidget': NotificationWidget};
|
||||
});
|
||||
@ -0,0 +1,78 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/meta',
|
||||
'codemirror/addon/search/search'
|
||||
],
|
||||
function($,
|
||||
utils,
|
||||
CodeMirror
|
||||
) {
|
||||
var Editor = function(selector, options) {
|
||||
this.selector = selector;
|
||||
this.contents = options.contents;
|
||||
this.events = options.events;
|
||||
this.base_url = options.base_url;
|
||||
this.file_path = options.file_path;
|
||||
|
||||
this.codemirror = CodeMirror($(this.selector)[0]);
|
||||
|
||||
// It appears we have to set commands on the CodeMirror class, not the
|
||||
// instance. I'd like to be wrong, but since there should only be one CM
|
||||
// instance on the page, this is good enough for now.
|
||||
CodeMirror.commands.save = $.proxy(this.save, this);
|
||||
|
||||
this.save_enabled = false;
|
||||
};
|
||||
|
||||
Editor.prototype.load = function() {
|
||||
var that = this;
|
||||
var cm = this.codemirror;
|
||||
this.contents.get(this.file_path, {type: 'file', format: 'text'})
|
||||
.then(function(model) {
|
||||
cm.setValue(model.content);
|
||||
|
||||
// Setting the file's initial value creates a history entry,
|
||||
// which we don't want.
|
||||
cm.clearHistory();
|
||||
|
||||
// Find and load the highlighting mode
|
||||
var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
|
||||
if (modeinfo) {
|
||||
utils.requireCodeMirrorMode(modeinfo.mode, function() {
|
||||
cm.setOption('mode', modeinfo.mode);
|
||||
});
|
||||
}
|
||||
that.save_enabled = true;
|
||||
},
|
||||
function(error) {
|
||||
cm.setValue("Error! " + error.message +
|
||||
"\nSaving disabled.");
|
||||
that.save_enabled = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Editor.prototype.save = function() {
|
||||
if (!this.save_enabled) {
|
||||
console.log("Not saving, save disabled");
|
||||
return;
|
||||
}
|
||||
var model = {
|
||||
path: this.file_path,
|
||||
type: 'file',
|
||||
format: 'text',
|
||||
content: this.codemirror.getValue(),
|
||||
};
|
||||
var that = this;
|
||||
this.contents.save(this.file_path, model).then(function() {
|
||||
that.events.trigger("save_succeeded.TextEditor");
|
||||
});
|
||||
};
|
||||
|
||||
return {Editor: Editor};
|
||||
});
|
||||
@ -0,0 +1,64 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
require([
|
||||
'base/js/namespace',
|
||||
'base/js/utils',
|
||||
'base/js/page',
|
||||
'base/js/events',
|
||||
'contents',
|
||||
'services/config',
|
||||
'edit/js/editor',
|
||||
'edit/js/menubar',
|
||||
'edit/js/notificationarea',
|
||||
'custom/custom',
|
||||
], function(
|
||||
IPython,
|
||||
utils,
|
||||
page,
|
||||
events,
|
||||
contents,
|
||||
configmod,
|
||||
editor,
|
||||
menubar,
|
||||
notificationarea
|
||||
){
|
||||
page = new page.Page();
|
||||
|
||||
var base_url = utils.get_body_data('baseUrl');
|
||||
var file_path = utils.get_body_data('filePath');
|
||||
contents = new contents.Contents({base_url: base_url});
|
||||
var config = new configmod.ConfigSection('edit', {base_url: base_url})
|
||||
config.load();
|
||||
|
||||
var editor = new editor.Editor('#texteditor-container', {
|
||||
base_url: base_url,
|
||||
events: events,
|
||||
contents: contents,
|
||||
file_path: file_path,
|
||||
});
|
||||
|
||||
// Make it available for debugging
|
||||
IPython.editor = editor;
|
||||
|
||||
var menus = new menubar.MenuBar('#menubar', {
|
||||
base_url: base_url,
|
||||
editor: editor,
|
||||
});
|
||||
|
||||
var notification_area = new notificationarea.EditorNotificationArea(
|
||||
'#notification_area', {
|
||||
events: events,
|
||||
});
|
||||
notification_area.init_notification_widgets();
|
||||
|
||||
config.loaded.then(function() {
|
||||
if (config.data.load_extensions) {
|
||||
var nbextension_paths = Object.getOwnPropertyNames(
|
||||
config.data.load_extensions);
|
||||
IPython.load_extensions.apply(this, nbextension_paths);
|
||||
}
|
||||
});
|
||||
editor.load();
|
||||
page.show();
|
||||
});
|
||||
@ -0,0 +1,50 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'bootstrap',
|
||||
], function(IPython, $, utils, bootstrap) {
|
||||
"use strict";
|
||||
|
||||
var MenuBar = function (selector, options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* A MenuBar Class to generate the menubar of IPython notebook
|
||||
*
|
||||
* Parameters:
|
||||
* selector: string
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* codemirror: CodeMirror instance
|
||||
* contents: ContentManager instance
|
||||
* events: $(Events) instance
|
||||
* base_url : string
|
||||
* file_path : string
|
||||
*/
|
||||
options = options || {};
|
||||
this.base_url = options.base_url || utils.get_body_data("baseUrl");
|
||||
this.selector = selector;
|
||||
this.editor = options.editor;
|
||||
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.bind_events();
|
||||
}
|
||||
};
|
||||
|
||||
MenuBar.prototype.bind_events = function () {
|
||||
/**
|
||||
* File
|
||||
*/
|
||||
var that = this;
|
||||
this.element.find('#save_file').click(function () {
|
||||
that.editor.save();
|
||||
});
|
||||
};
|
||||
|
||||
return {'MenuBar': MenuBar};
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
define([
|
||||
'base/js/notificationarea'
|
||||
], function(notificationarea) {
|
||||
"use strict";
|
||||
var NotificationArea = notificationarea.NotificationArea;
|
||||
|
||||
var EditorNotificationArea = function(selector, options) {
|
||||
NotificationArea.apply(this, [selector, options]);
|
||||
}
|
||||
|
||||
EditorNotificationArea.prototype = Object.create(NotificationArea.prototype);
|
||||
|
||||
/**
|
||||
* Initialize the default set of notification widgets.
|
||||
*
|
||||
* @method init_notification_widgets
|
||||
*/
|
||||
EditorNotificationArea.prototype.init_notification_widgets = function () {
|
||||
var that = this;
|
||||
var enw = this.new_notification_widget('editor');
|
||||
|
||||
this.events.on("save_succeeded.TextEditor", function() {
|
||||
enw.set_message("File saved", 2000);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return {EditorNotificationArea: EditorNotificationArea};
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
require([
|
||||
'jquery',
|
||||
'base/js/dialog',
|
||||
'underscore',
|
||||
'base/js/namespace'
|
||||
], function ($, dialog, _, IPython) {
|
||||
'use strict';
|
||||
$('#notebook_about').click(function () {
|
||||
// use underscore template to auto html escape
|
||||
var text = 'You are using IPython notebook.<br/><br/>';
|
||||
text = text + 'The version of the notebook server is ';
|
||||
text = text + _.template('<b><%- version %></b>')({ version: sys_info.ipython_version });
|
||||
if (sys_info.commit_hash) {
|
||||
text = text + _.template('-<%- hash %>')({ hash: sys_info.commit_hash });
|
||||
}
|
||||
text = text + _.template(' and is running on:<br/><pre>Python <%- pyver %></pre>')({ pyver: sys_info.sys_version });
|
||||
var kinfo = $('<div/>').attr('id', '#about-kinfo').text('Waiting for kernel to be available...');
|
||||
var body = $('<div/>');
|
||||
body.append($('<h4/>').text('Server Information:'));
|
||||
body.append($('<p/>').html(text));
|
||||
body.append($('<h4/>').text('Current Kernel Information:'));
|
||||
body.append(kinfo);
|
||||
dialog.modal({
|
||||
title: 'About IPython Notebook',
|
||||
body: body,
|
||||
buttons: { 'OK': {} }
|
||||
});
|
||||
try {
|
||||
IPython.notebook.session.kernel.kernel_info(function (data) {
|
||||
kinfo.html($('<pre/>').text(data.content.banner));
|
||||
});
|
||||
} catch (e) {
|
||||
kinfo.html($('<p/>').text('unable to contact kernel'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,503 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['require'
|
||||
], function(require) {
|
||||
"use strict";
|
||||
|
||||
var ActionHandler = function (env) {
|
||||
this.env = env || {};
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* A bunch of predefined `Simple Actions` used by IPython.
|
||||
* `Simple Actions` have the following keys:
|
||||
* help (optional): a short string the describe the action.
|
||||
* will be used in various context, like as menu name, tool tips on buttons,
|
||||
* and short description in help menu.
|
||||
* help_index (optional): a string used to sort action in help menu.
|
||||
* icon (optional): a short string that represent the icon that have to be used with this
|
||||
* action. this should mainly correspond to a Font_awesome class.
|
||||
* handler : a function which is called when the action is activated. It will receive at first parameter
|
||||
* a dictionary containing various handle to element of the notebook.
|
||||
*
|
||||
* action need to be registered with a **name** that can be use to refer to this action.
|
||||
*
|
||||
*
|
||||
* if `help` is not provided it will be derived by replacing any dash by space
|
||||
* in the **name** of the action. It is advised to provide a prefix to action name to
|
||||
* avoid conflict the prefix should be all lowercase and end with a dot `.`
|
||||
* in the absence of a prefix the behavior of the action is undefined.
|
||||
*
|
||||
* All action provided by IPython are prefixed with `ipython.`.
|
||||
*
|
||||
* One can register extra actions or replace an existing action with another one is possible
|
||||
* but is considered undefined behavior.
|
||||
*
|
||||
**/
|
||||
var _action = {
|
||||
'run-select-next': {
|
||||
icon: 'fa-play',
|
||||
help : 'run cell, select below',
|
||||
help_index : 'ba',
|
||||
handler : function (env) {
|
||||
env.notebook.execute_cell_and_select_below();
|
||||
}
|
||||
},
|
||||
'execute-in-place':{
|
||||
help : 'run cell',
|
||||
help_index : 'bb',
|
||||
handler : function (env) {
|
||||
env.notebook.execute_cell();
|
||||
}
|
||||
},
|
||||
'execute-and-insert-after':{
|
||||
help : 'run cell, insert below',
|
||||
help_index : 'bc',
|
||||
handler : function (env) {
|
||||
env.notebook.execute_cell_and_insert_below();
|
||||
}
|
||||
},
|
||||
'go-to-command-mode': {
|
||||
help : 'command mode',
|
||||
help_index : 'aa',
|
||||
handler : function (env) {
|
||||
env.notebook.command_mode();
|
||||
}
|
||||
},
|
||||
'split-cell-at-cursor': {
|
||||
help : 'split cell',
|
||||
help_index : 'ea',
|
||||
handler : function (env) {
|
||||
env.notebook.split_cell();
|
||||
}
|
||||
},
|
||||
'enter-edit-mode' : {
|
||||
help_index : 'aa',
|
||||
handler : function (env) {
|
||||
env.notebook.edit_mode();
|
||||
}
|
||||
},
|
||||
'select-previous-cell' : {
|
||||
help_index : 'da',
|
||||
handler : function (env) {
|
||||
var index = env.notebook.get_selected_index();
|
||||
if (index !== 0 && index !== null) {
|
||||
env.notebook.select_prev();
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
}
|
||||
},
|
||||
'select-next-cell' : {
|
||||
help_index : 'db',
|
||||
handler : function (env) {
|
||||
var index = env.notebook.get_selected_index();
|
||||
if (index !== (env.notebook.ncells()-1) && index !== null) {
|
||||
env.notebook.select_next();
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
}
|
||||
},
|
||||
'cut-selected-cell' : {
|
||||
icon: 'fa-cut',
|
||||
help_index : 'ee',
|
||||
handler : function (env) {
|
||||
env.notebook.cut_cell();
|
||||
}
|
||||
},
|
||||
'copy-selected-cell' : {
|
||||
icon: 'fa-copy',
|
||||
help_index : 'ef',
|
||||
handler : function (env) {
|
||||
env.notebook.copy_cell();
|
||||
}
|
||||
},
|
||||
'paste-cell-before' : {
|
||||
help_index : 'eg',
|
||||
handler : function (env) {
|
||||
env.notebook.paste_cell_above();
|
||||
}
|
||||
},
|
||||
'paste-cell-after' : {
|
||||
icon: 'fa-paste',
|
||||
help_index : 'eh',
|
||||
handler : function (env) {
|
||||
env.notebook.paste_cell_below();
|
||||
}
|
||||
},
|
||||
'insert-cell-before' : {
|
||||
help_index : 'ec',
|
||||
handler : function (env) {
|
||||
env.notebook.insert_cell_above();
|
||||
env.notebook.select_prev();
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
},
|
||||
'insert-cell-after' : {
|
||||
icon : 'fa-plus',
|
||||
help_index : 'ed',
|
||||
handler : function (env) {
|
||||
env.notebook.insert_cell_below();
|
||||
env.notebook.select_next();
|
||||
env.notebook.focus_cell();
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-code-cell' : {
|
||||
help : 'to code',
|
||||
help_index : 'ca',
|
||||
handler : function (env) {
|
||||
env.notebook.to_code();
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-markdown-cell' : {
|
||||
help : 'to markdown',
|
||||
help_index : 'cb',
|
||||
handler : function (env) {
|
||||
env.notebook.to_markdown();
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-raw-cell' : {
|
||||
help : 'to raw',
|
||||
help_index : 'cc',
|
||||
handler : function (env) {
|
||||
env.notebook.to_raw();
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-heading-1' : {
|
||||
help : 'to heading 1',
|
||||
help_index : 'cd',
|
||||
handler : function (env) {
|
||||
env.notebook.to_heading(undefined, 1);
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-heading-2' : {
|
||||
help : 'to heading 2',
|
||||
help_index : 'ce',
|
||||
handler : function (env) {
|
||||
env.notebook.to_heading(undefined, 2);
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-heading-3' : {
|
||||
help : 'to heading 3',
|
||||
help_index : 'cf',
|
||||
handler : function (env) {
|
||||
env.notebook.to_heading(undefined, 3);
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-heading-4' : {
|
||||
help : 'to heading 4',
|
||||
help_index : 'cg',
|
||||
handler : function (env) {
|
||||
env.notebook.to_heading(undefined, 4);
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-heading-5' : {
|
||||
help : 'to heading 5',
|
||||
help_index : 'ch',
|
||||
handler : function (env) {
|
||||
env.notebook.to_heading(undefined, 5);
|
||||
}
|
||||
},
|
||||
'change-selected-cell-to-heading-6' : {
|
||||
help : 'to heading 6',
|
||||
help_index : 'ci',
|
||||
handler : function (env) {
|
||||
env.notebook.to_heading(undefined, 6);
|
||||
}
|
||||
},
|
||||
'toggle-output-visibility-selected-cell' : {
|
||||
help : 'toggle output',
|
||||
help_index : 'gb',
|
||||
handler : function (env) {
|
||||
env.notebook.toggle_output();
|
||||
}
|
||||
},
|
||||
'toggle-output-scrolling-selected-cell' : {
|
||||
help : 'toggle output scrolling',
|
||||
help_index : 'gc',
|
||||
handler : function (env) {
|
||||
env.notebook.toggle_output_scroll();
|
||||
}
|
||||
},
|
||||
'move-selected-cell-down' : {
|
||||
icon: 'fa-arrow-down',
|
||||
help_index : 'eb',
|
||||
handler : function (env) {
|
||||
env.notebook.move_cell_down();
|
||||
}
|
||||
},
|
||||
'move-selected-cell-up' : {
|
||||
icon: 'fa-arrow-up',
|
||||
help_index : 'ea',
|
||||
handler : function (env) {
|
||||
env.notebook.move_cell_up();
|
||||
}
|
||||
},
|
||||
'toggle-line-number-selected-cell' : {
|
||||
help : 'toggle line numbers',
|
||||
help_index : 'ga',
|
||||
handler : function (env) {
|
||||
env.notebook.cell_toggle_line_numbers();
|
||||
}
|
||||
},
|
||||
'show-keyboard-shortcut-help-dialog' : {
|
||||
help_index : 'ge',
|
||||
handler : function (env) {
|
||||
env.quick_help.show_keyboard_shortcuts();
|
||||
}
|
||||
},
|
||||
'delete-cell': {
|
||||
help_index : 'ej',
|
||||
handler : function (env) {
|
||||
env.notebook.delete_cell();
|
||||
}
|
||||
},
|
||||
'interrupt-kernel':{
|
||||
icon: 'fa-stop',
|
||||
help_index : 'ha',
|
||||
handler : function (env) {
|
||||
env.notebook.kernel.interrupt();
|
||||
}
|
||||
},
|
||||
'restart-kernel':{
|
||||
icon: 'fa-repeat',
|
||||
help_index : 'hb',
|
||||
handler : function (env) {
|
||||
env.notebook.restart_kernel();
|
||||
}
|
||||
},
|
||||
'undo-last-cell-deletion' : {
|
||||
help_index : 'ei',
|
||||
handler : function (env) {
|
||||
env.notebook.undelete_cell();
|
||||
}
|
||||
},
|
||||
'merge-selected-cell-with-cell-after' : {
|
||||
help : 'merge cell below',
|
||||
help_index : 'ek',
|
||||
handler : function (env) {
|
||||
env.notebook.merge_cell_below();
|
||||
}
|
||||
},
|
||||
'close-pager' : {
|
||||
help_index : 'gd',
|
||||
handler : function (env) {
|
||||
env.pager.collapse();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A bunch of `Advance actions` for IPython.
|
||||
* Cf `Simple Action` plus the following properties.
|
||||
*
|
||||
* handler: first argument of the handler is the event that triggerd the action
|
||||
* (typically keypress). The handler is responsible for any modification of the
|
||||
* event and event propagation.
|
||||
* Is also responsible for returning false if the event have to be further ignored,
|
||||
* true, to tell keyboard manager that it ignored the event.
|
||||
*
|
||||
* the second parameter of the handler is the environemnt passed to Simple Actions
|
||||
*
|
||||
**/
|
||||
var custom_ignore = {
|
||||
'ignore':{
|
||||
handler : function () {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'move-cursor-up-or-previous-cell':{
|
||||
handler : function (env, event) {
|
||||
var index = env.notebook.get_selected_index();
|
||||
var cell = env.notebook.get_cell(index);
|
||||
var cm = env.notebook.get_selected_cell().code_mirror;
|
||||
var cur = cm.getCursor();
|
||||
if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
env.notebook.command_mode();
|
||||
env.notebook.select_prev();
|
||||
env.notebook.edit_mode();
|
||||
cm = env.notebook.get_selected_cell().code_mirror;
|
||||
cm.setCursor(cm.lastLine(), 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'move-cursor-down-or-next-cell':{
|
||||
handler : function (env, event) {
|
||||
var index = env.notebook.get_selected_index();
|
||||
var cell = env.notebook.get_cell(index);
|
||||
if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) {
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
env.notebook.command_mode();
|
||||
env.notebook.select_next();
|
||||
env.notebook.edit_mode();
|
||||
var cm = env.notebook.get_selected_cell().code_mirror;
|
||||
cm.setCursor(0, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'scroll-down': {
|
||||
handler: function(env, event) {
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
return env.notebook.scroll_manager.scroll(1);
|
||||
},
|
||||
},
|
||||
'scroll-up': {
|
||||
handler: function(env, event) {
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
return env.notebook.scroll_manager.scroll(-1);
|
||||
},
|
||||
},
|
||||
'save-notebook':{
|
||||
help: "Save and Checkpoint",
|
||||
help_index : 'fb',
|
||||
icon: 'fa-save',
|
||||
handler : function (env, event) {
|
||||
env.notebook.save_checkpoint();
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// private stuff that prepend `.ipython` to actions names
|
||||
// and uniformize/fill in missing pieces in of an action.
|
||||
var _prepare_handler = function(registry, subkey, source){
|
||||
registry['ipython.'+subkey] = {};
|
||||
registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
|
||||
registry['ipython.'+subkey].help_index = source[subkey].help_index;
|
||||
registry['ipython.'+subkey].icon = source[subkey].icon;
|
||||
return source[subkey].handler;
|
||||
};
|
||||
|
||||
// Will actually generate/register all the IPython actions
|
||||
var fun = function(){
|
||||
var final_actions = {};
|
||||
for(var k in _action){
|
||||
// Js closure are function level not block level need to wrap in a IIFE
|
||||
// and append ipython to event name these things do intercept event so are wrapped
|
||||
// in a function that return false.
|
||||
var handler = _prepare_handler(final_actions, k, _action);
|
||||
(function(key, handler){
|
||||
final_actions['ipython.'+key].handler = function(env, event){
|
||||
handler(env);
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
})(k, handler);
|
||||
}
|
||||
|
||||
for(var k in custom_ignore){
|
||||
// Js closure are function level not block level need to wrap in a IIFE
|
||||
// same as above, but decide for themselves wether or not they intercept events.
|
||||
var handler = _prepare_handler(final_actions, k, custom_ignore);
|
||||
(function(key, handler){
|
||||
final_actions['ipython.'+key].handler = function(env, event){
|
||||
return handler(env, event);
|
||||
};
|
||||
})(k, handler);
|
||||
}
|
||||
|
||||
return final_actions;
|
||||
};
|
||||
ActionHandler.prototype._actions = fun();
|
||||
|
||||
|
||||
/**
|
||||
* extend the environment variable that will be pass to handlers
|
||||
**/
|
||||
ActionHandler.prototype.extend_env = function(env){
|
||||
for(var k in env){
|
||||
this.env[k] = env[k];
|
||||
}
|
||||
};
|
||||
|
||||
ActionHandler.prototype.register = function(action, name, prefix){
|
||||
/**
|
||||
* Register an `action` with an optional name and prefix.
|
||||
*
|
||||
* if name and prefix are not given they will be determined automatically.
|
||||
* if action if just a `function` it will be wrapped in an anonymous action.
|
||||
*
|
||||
* @return the full name to access this action .
|
||||
**/
|
||||
action = this.normalise(action);
|
||||
if( !name ){
|
||||
name = 'autogenerated-'+String(action.handler);
|
||||
}
|
||||
prefix = prefix || 'auto';
|
||||
var full_name = prefix+'.'+name;
|
||||
this._actions[full_name] = action;
|
||||
return full_name;
|
||||
|
||||
};
|
||||
|
||||
|
||||
ActionHandler.prototype.normalise = function(data){
|
||||
/**
|
||||
* given an `action` or `function`, return a normalised `action`
|
||||
* by setting all known attributes and removing unknown attributes;
|
||||
**/
|
||||
if(typeof(data) === 'function'){
|
||||
data = {handler:data};
|
||||
}
|
||||
if(typeof(data.handler) !== 'function'){
|
||||
throw('unknown datatype, cannot register');
|
||||
}
|
||||
var _data = data;
|
||||
data = {};
|
||||
data.handler = _data.handler;
|
||||
data.help = data.help || '';
|
||||
data.icon = data.icon || '';
|
||||
data.help_index = data.help_index || '';
|
||||
return data;
|
||||
};
|
||||
|
||||
ActionHandler.prototype.get_name = function(name_or_data){
|
||||
/**
|
||||
* given an `action` or `name` of a action, return the name attached to this action.
|
||||
* if given the name of and corresponding actions does not exist in registry, return `null`.
|
||||
**/
|
||||
|
||||
if(typeof(name_or_data) === 'string'){
|
||||
if(this.exists(name_or_data)){
|
||||
return name_or_data;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return this.register(name_or_data);
|
||||
}
|
||||
};
|
||||
|
||||
ActionHandler.prototype.get = function(name){
|
||||
return this._actions[name];
|
||||
};
|
||||
|
||||
ActionHandler.prototype.call = function(name, event, env){
|
||||
return this._actions[name].handler(env|| this.env, event);
|
||||
};
|
||||
|
||||
ActionHandler.prototype.exists = function(name){
|
||||
return (typeof(this._actions[name]) !== 'undefined');
|
||||
};
|
||||
|
||||
return {init:ActionHandler};
|
||||
|
||||
});
|
||||
@ -1,44 +1,62 @@
|
||||
// IPython GFM (GitHub Flavored Markdown) mode is just a slightly altered GFM
|
||||
// Mode with support for latex.
|
||||
// IPython GFM (GitHub Flavored Markdown) mode is just a slightly altered GFM
|
||||
// Mode with support for latex.
|
||||
//
|
||||
// Latex support was supported by Codemirror GFM as of
|
||||
// Latex support was supported by Codemirror GFM as of
|
||||
// https://github.com/codemirror/CodeMirror/pull/567
|
||||
// But was later removed in
|
||||
// https://github.com/codemirror/CodeMirror/commit/d9c9f1b1ffe984aee41307f3e927f80d1f23590c
|
||||
|
||||
CodeMirror.requireMode('gfm', function(){
|
||||
CodeMirror.requireMode('stex', function(){
|
||||
CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
|
||||
|
||||
var gfm_mode = CodeMirror.getMode(config, "gfm");
|
||||
var tex_mode = CodeMirror.getMode(config, "stex");
|
||||
|
||||
return CodeMirror.multiplexingMode(
|
||||
gfm_mode,
|
||||
{
|
||||
open: "$", close: "$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "$$", close: "$$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\(", close: "\\)",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\[", close: "\\]",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
}
|
||||
// .. more multiplexed styles can follow here
|
||||
);
|
||||
}, 'gfm');
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm");
|
||||
});
|
||||
});
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object"){ // CommonJS
|
||||
mod(require("codemirror/lib/codemirror")
|
||||
,require("codemirror/addon/mode/multiplex")
|
||||
,require("codemirror/mode/gfm/gfm")
|
||||
,require("codemirror/mode/stex/stex")
|
||||
);
|
||||
} else if (typeof define == "function" && define.amd){ // AMD
|
||||
define(["codemirror/lib/codemirror"
|
||||
,"codemirror/addon/mode/multiplex"
|
||||
,"codemirror/mode/python/python"
|
||||
,"codemirror/mode/stex/stex"
|
||||
], mod);
|
||||
} else {// Plain browser env
|
||||
mod(CodeMirror);
|
||||
}
|
||||
})( function(CodeMirror){
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
|
||||
|
||||
var gfm_mode = CodeMirror.getMode(config, "gfm");
|
||||
var tex_mode = CodeMirror.getMode(config, "stex");
|
||||
|
||||
return CodeMirror.multiplexingMode(
|
||||
gfm_mode,
|
||||
{
|
||||
open: "$", close: "$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
// not sure this works as $$ is interpreted at (opening $, closing $, as defined just above)
|
||||
open: "$$", close: "$$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\(", close: "\\)",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\[", close: "\\]",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
}
|
||||
// .. more multiplexed styles can follow here
|
||||
);
|
||||
}, 'gfm');
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm");
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,104 +0,0 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
], function(IPython, $) {
|
||||
"use strict";
|
||||
|
||||
var NotificationWidget = function (selector) {
|
||||
this.selector = selector;
|
||||
this.timeout = null;
|
||||
this.busy = false;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.style();
|
||||
}
|
||||
this.element.hide();
|
||||
var that = this;
|
||||
|
||||
this.inner = $('<span/>');
|
||||
this.element.append(this.inner);
|
||||
|
||||
};
|
||||
|
||||
NotificationWidget.prototype.style = function () {
|
||||
this.element.addClass('notification_widget');
|
||||
};
|
||||
|
||||
// msg : message to display
|
||||
// timeout : time in ms before diseapearing
|
||||
//
|
||||
// if timeout <= 0
|
||||
// click_callback : function called if user click on notification
|
||||
// could return false to prevent the notification to be dismissed
|
||||
NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
|
||||
var options = options || {};
|
||||
var callback = click_callback || function() {return true;};
|
||||
var that = this;
|
||||
// unbind potential previous callback
|
||||
this.element.unbind('click');
|
||||
this.inner.attr('class', options.icon);
|
||||
this.inner.attr('title', options.title);
|
||||
this.inner.text(msg);
|
||||
this.element.fadeIn(100);
|
||||
|
||||
// reset previous set style
|
||||
this.element.removeClass();
|
||||
this.style();
|
||||
if (options.class){
|
||||
|
||||
this.element.addClass(options.class)
|
||||
}
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
if (timeout !== undefined && timeout >=0) {
|
||||
this.timeout = setTimeout(function () {
|
||||
that.element.fadeOut(100, function () {that.inner.text('');});
|
||||
that.timeout = null;
|
||||
}, timeout);
|
||||
} else {
|
||||
this.element.click(function() {
|
||||
if( callback() !== false ) {
|
||||
that.element.fadeOut(100, function () {that.inner.text('');});
|
||||
that.element.unbind('click');
|
||||
}
|
||||
if (that.timeout !== undefined) {
|
||||
that.timeout = undefined;
|
||||
clearTimeout(that.timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) {
|
||||
var options = options || {};
|
||||
options.class = options.class +' info';
|
||||
var timeout = timeout || 3500;
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
}
|
||||
NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) {
|
||||
var options = options || {};
|
||||
options.class = options.class +' warning';
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
}
|
||||
NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) {
|
||||
var options = options || {};
|
||||
options.class = options.class +' danger';
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
}
|
||||
|
||||
|
||||
NotificationWidget.prototype.get_message = function () {
|
||||
return this.inner.html();
|
||||
};
|
||||
|
||||
// For backwards compatibility.
|
||||
IPython.NotificationWidget = NotificationWidget;
|
||||
|
||||
return {'NotificationWidget': NotificationWidget};
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
.terminal {
|
||||
float: left;
|
||||
border: black solid 5px;
|
||||
font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
|
||||
font-size: 11px;
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
|
||||
.terminal-cursor {
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#terminado-container {
|
||||
margin: 8px;
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
],
|
||||
function($, utils) {
|
||||
var ConfigSection = function(section_name, options) {
|
||||
this.section_name = section_name;
|
||||
this.base_url = options.base_url;
|
||||
this.data = {};
|
||||
|
||||
var that = this;
|
||||
|
||||
/* .loaded is a promise, fulfilled the first time the config is loaded
|
||||
* from the server. Code can do:
|
||||
* conf.loaded.then(function() { ... using conf.data ... });
|
||||
*/
|
||||
this._one_load_finished = false;
|
||||
this.loaded = new Promise(function(resolve, reject) {
|
||||
that._finish_firstload = resolve;
|
||||
});
|
||||
};
|
||||
|
||||
ConfigSection.prototype.api_url = function() {
|
||||
return utils.url_join_encode(this.base_url, 'api/config', this.section_name);
|
||||
};
|
||||
|
||||
ConfigSection.prototype._load_done = function() {
|
||||
if (!this._one_load_finished) {
|
||||
this._one_load_finished = true;
|
||||
this._finish_firstload();
|
||||
}
|
||||
};
|
||||
|
||||
ConfigSection.prototype.load = function() {
|
||||
var that = this;
|
||||
return utils.promising_ajax(this.api_url(), {
|
||||
cache: false,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
}).then(function(data) {
|
||||
that.data = data;
|
||||
that._load_done();
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
ConfigSection.prototype.update = function(newdata) {
|
||||
var that = this;
|
||||
return utils.promising_ajax(this.api_url(), {
|
||||
processData: false,
|
||||
type : "PATCH",
|
||||
data: JSON.stringify(newdata),
|
||||
dataType : "json",
|
||||
contentType: 'application/json',
|
||||
}).then(function(data) {
|
||||
that.data = data;
|
||||
that._load_done();
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
return {ConfigSection: ConfigSection};
|
||||
|
||||
});
|
||||
@ -0,0 +1,250 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
], function(IPython, $, utils) {
|
||||
var Contents = function(options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* A contents handles passing file operations
|
||||
* to the back-end. This includes checkpointing
|
||||
* with the normal file operations.
|
||||
*
|
||||
* Parameters:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* base_url: string
|
||||
*/
|
||||
this.base_url = options.base_url;
|
||||
};
|
||||
|
||||
/** Error type */
|
||||
Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
|
||||
|
||||
Contents.DirectoryNotEmptyError = function() {
|
||||
// Constructor
|
||||
//
|
||||
// An error representing the result of attempting to delete a non-empty
|
||||
// directory.
|
||||
this.message = 'A directory must be empty before being deleted.';
|
||||
};
|
||||
|
||||
Contents.DirectoryNotEmptyError.prototype = Object.create(Error.prototype);
|
||||
Contents.DirectoryNotEmptyError.prototype.name =
|
||||
Contents.DIRECTORY_NOT_EMPTY_ERROR;
|
||||
|
||||
|
||||
Contents.prototype.api_url = function() {
|
||||
var url_parts = [this.base_url, 'api/contents'].concat(
|
||||
Array.prototype.slice.apply(arguments));
|
||||
return utils.url_join_encode.apply(null, url_parts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a basic error handler that wraps a jqXHR error as an Error.
|
||||
*
|
||||
* Takes a callback that accepts an Error, and returns a callback that can
|
||||
* be passed directly to $.ajax, which will wrap the error from jQuery
|
||||
* as an Error, and pass that to the original callback.
|
||||
*
|
||||
* @method create_basic_error_handler
|
||||
* @param{Function} callback
|
||||
* @return{Function}
|
||||
*/
|
||||
Contents.prototype.create_basic_error_handler = function(callback) {
|
||||
if (!callback) {
|
||||
return utils.log_ajax_error;
|
||||
}
|
||||
return function(xhr, status, error) {
|
||||
callback(utils.wrap_ajax_error(xhr, status, error));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* File Functions (including notebook operations)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a file.
|
||||
*
|
||||
* Calls success with file JSON model, or error with error.
|
||||
*
|
||||
* @method get
|
||||
* @param {String} path
|
||||
* @param {Object} options
|
||||
* type : 'notebook', 'file', or 'directory'
|
||||
* format: 'text' or 'base64'; only relevant for type: 'file'
|
||||
*/
|
||||
Contents.prototype.get = function (path, options) {
|
||||
/**
|
||||
* We do the call with settings so we can set cache to false.
|
||||
*/
|
||||
var settings = {
|
||||
processData : false,
|
||||
cache : false,
|
||||
type : "GET",
|
||||
dataType : "json",
|
||||
};
|
||||
var url = this.api_url(path);
|
||||
params = {};
|
||||
if (options.type) { params.type = options.type; }
|
||||
if (options.format) { params.format = options.format; }
|
||||
return utils.promising_ajax(url + '?' + $.param(params), settings);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new untitled file or directory in the specified directory path.
|
||||
*
|
||||
* @method new
|
||||
* @param {String} path: the directory in which to create the new file/directory
|
||||
* @param {Object} options:
|
||||
* ext: file extension to use
|
||||
* type: model type to create ('notebook', 'file', or 'directory')
|
||||
*/
|
||||
Contents.prototype.new_untitled = function(path, options) {
|
||||
var data = JSON.stringify({
|
||||
ext: options.ext,
|
||||
type: options.type
|
||||
});
|
||||
|
||||
var settings = {
|
||||
processData : false,
|
||||
type : "POST",
|
||||
data: data,
|
||||
dataType : "json",
|
||||
};
|
||||
return utils.promising_ajax(this.api_url(path), settings);
|
||||
};
|
||||
|
||||
Contents.prototype.delete = function(path) {
|
||||
var settings = {
|
||||
processData : false,
|
||||
type : "DELETE",
|
||||
dataType : "json",
|
||||
};
|
||||
var url = this.api_url(path);
|
||||
return utils.promising_ajax(url, settings).catch(
|
||||
// Translate certain errors to more specific ones.
|
||||
function(error) {
|
||||
// TODO: update IPEP27 to specify errors more precisely, so
|
||||
// that error types can be detected here with certainty.
|
||||
if (error.xhr.status === 400) {
|
||||
throw new Contents.DirectoryNotEmptyError();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Contents.prototype.rename = function(path, new_path) {
|
||||
var data = {path: new_path};
|
||||
var settings = {
|
||||
processData : false,
|
||||
type : "PATCH",
|
||||
data : JSON.stringify(data),
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
};
|
||||
var url = this.api_url(path);
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
Contents.prototype.save = function(path, model) {
|
||||
/**
|
||||
* We do the call with settings so we can set cache to false.
|
||||
*/
|
||||
var settings = {
|
||||
processData : false,
|
||||
type : "PUT",
|
||||
data : JSON.stringify(model),
|
||||
contentType: 'application/json',
|
||||
};
|
||||
var url = this.api_url(path);
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
Contents.prototype.copy = function(from_file, to_dir) {
|
||||
/**
|
||||
* Copy a file into a given directory via POST
|
||||
* The server will select the name of the copied file
|
||||
*/
|
||||
var url = this.api_url(to_dir);
|
||||
|
||||
var settings = {
|
||||
processData : false,
|
||||
type: "POST",
|
||||
data: JSON.stringify({copy_from: from_file}),
|
||||
dataType : "json",
|
||||
};
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checkpointing Functions
|
||||
*/
|
||||
|
||||
Contents.prototype.create_checkpoint = function(path) {
|
||||
var url = this.api_url(path, 'checkpoints');
|
||||
var settings = {
|
||||
type : "POST",
|
||||
dataType : "json",
|
||||
};
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
Contents.prototype.list_checkpoints = function(path) {
|
||||
var url = this.api_url(path, 'checkpoints');
|
||||
var settings = {
|
||||
type : "GET",
|
||||
cache: false,
|
||||
dataType: "json",
|
||||
};
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
Contents.prototype.restore_checkpoint = function(path, checkpoint_id) {
|
||||
var url = this.api_url(path, 'checkpoints', checkpoint_id);
|
||||
var settings = {
|
||||
type : "POST",
|
||||
};
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
Contents.prototype.delete_checkpoint = function(path, checkpoint_id) {
|
||||
var url = this.api_url(path, 'checkpoints', checkpoint_id);
|
||||
var settings = {
|
||||
type : "DELETE",
|
||||
};
|
||||
return utils.promising_ajax(url, settings);
|
||||
};
|
||||
|
||||
/**
|
||||
* File management functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* List notebooks and directories at a given path
|
||||
*
|
||||
* On success, load_callback is called with an array of dictionaries
|
||||
* representing individual files or directories. Each dictionary has
|
||||
* the keys:
|
||||
* type: "notebook" or "directory"
|
||||
* created: created date
|
||||
* last_modified: last modified dat
|
||||
* @method list_notebooks
|
||||
* @param {String} path The path to list notebooks in
|
||||
*/
|
||||
Contents.prototype.list_contents = function(path) {
|
||||
return this.get(path, {type: 'directory'});
|
||||
};
|
||||
|
||||
|
||||
IPython.Contents = Contents;
|
||||
|
||||
return {'Contents': Contents};
|
||||
});
|
||||
@ -1,626 +0,0 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'services/kernels/js/comm',
|
||||
'widgets/js/init',
|
||||
], function(IPython, $, utils, comm, widgetmanager) {
|
||||
"use strict";
|
||||
|
||||
// Initialization and connection.
|
||||
/**
|
||||
* A Kernel Class to communicate with the Python kernel
|
||||
* @Class Kernel
|
||||
*/
|
||||
var Kernel = function (kernel_service_url, ws_url, notebook, name) {
|
||||
this.events = notebook.events;
|
||||
this.kernel_id = null;
|
||||
this.shell_channel = null;
|
||||
this.iopub_channel = null;
|
||||
this.stdin_channel = null;
|
||||
this.kernel_service_url = kernel_service_url;
|
||||
this.name = name;
|
||||
this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
|
||||
if (!this.ws_url) {
|
||||
// trailing 's' in https will become wss for secure web sockets
|
||||
this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
|
||||
}
|
||||
this.running = false;
|
||||
this.username = "username";
|
||||
this.session_id = utils.uuid();
|
||||
this._msg_callbacks = {};
|
||||
this.post = $.post;
|
||||
|
||||
if (typeof(WebSocket) !== 'undefined') {
|
||||
this.WebSocket = WebSocket;
|
||||
} else if (typeof(MozWebSocket) !== 'undefined') {
|
||||
this.WebSocket = MozWebSocket;
|
||||
} else {
|
||||
alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
|
||||
}
|
||||
|
||||
this.bind_events();
|
||||
this.init_iopub_handlers();
|
||||
this.comm_manager = new comm.CommManager(this);
|
||||
this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
|
||||
|
||||
this.last_msg_id = null;
|
||||
this.last_msg_callbacks = {};
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype._get_msg = function (msg_type, content, metadata) {
|
||||
var msg = {
|
||||
header : {
|
||||
msg_id : utils.uuid(),
|
||||
username : this.username,
|
||||
session : this.session_id,
|
||||
msg_type : msg_type,
|
||||
version : "5.0"
|
||||
},
|
||||
metadata : metadata || {},
|
||||
content : content,
|
||||
parent_header : {}
|
||||
};
|
||||
return msg;
|
||||
};
|
||||
|
||||
Kernel.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
this.events.on('send_input_reply.Kernel', function(evt, data) {
|
||||
that.send_input_reply(data);
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize the iopub handlers
|
||||
|
||||
Kernel.prototype.init_iopub_handlers = function () {
|
||||
var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
|
||||
this._iopub_handlers = {};
|
||||
this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
|
||||
this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
|
||||
|
||||
for (var i=0; i < output_msg_types.length; i++) {
|
||||
this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the Python kernel
|
||||
* @method start
|
||||
*/
|
||||
Kernel.prototype.start = function (params) {
|
||||
params = params || {};
|
||||
if (!this.running) {
|
||||
var qs = $.param(params);
|
||||
this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
|
||||
$.proxy(this._kernel_started, this),
|
||||
'json'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restart the python kernel.
|
||||
*
|
||||
* Emit a 'status_restarting.Kernel' event with
|
||||
* the current object as parameter
|
||||
*
|
||||
* @method restart
|
||||
*/
|
||||
Kernel.prototype.restart = function () {
|
||||
this.events.trigger('status_restarting.Kernel', {kernel: this});
|
||||
if (this.running) {
|
||||
this.stop_channels();
|
||||
this.post(utils.url_join_encode(this.kernel_url, "restart"),
|
||||
$.proxy(this._kernel_started, this),
|
||||
'json'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype._kernel_started = function (json) {
|
||||
console.log("Kernel started: ", json.id);
|
||||
this.running = true;
|
||||
this.kernel_id = json.id;
|
||||
this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
|
||||
this.start_channels();
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype._websocket_closed = function(ws_url, early) {
|
||||
this.stop_channels();
|
||||
this.events.trigger('websocket_closed.Kernel',
|
||||
{ws_url: ws_url, kernel: this, early: early}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the `shell`and `iopub` channels.
|
||||
* Will stop and restart them if they already exist.
|
||||
*
|
||||
* @method start_channels
|
||||
*/
|
||||
Kernel.prototype.start_channels = function () {
|
||||
var that = this;
|
||||
this.stop_channels();
|
||||
var ws_host_url = this.ws_url + this.kernel_url;
|
||||
console.log("Starting WebSockets:", ws_host_url);
|
||||
this.shell_channel = new this.WebSocket(
|
||||
this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
|
||||
);
|
||||
this.stdin_channel = new this.WebSocket(
|
||||
this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
|
||||
);
|
||||
this.iopub_channel = new this.WebSocket(
|
||||
this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
|
||||
);
|
||||
|
||||
var already_called_onclose = false; // only alert once
|
||||
var ws_closed_early = function(evt){
|
||||
if (already_called_onclose){
|
||||
return;
|
||||
}
|
||||
already_called_onclose = true;
|
||||
if ( ! evt.wasClean ){
|
||||
that._websocket_closed(ws_host_url, true);
|
||||
}
|
||||
};
|
||||
var ws_closed_late = function(evt){
|
||||
if (already_called_onclose){
|
||||
return;
|
||||
}
|
||||
already_called_onclose = true;
|
||||
if ( ! evt.wasClean ){
|
||||
that._websocket_closed(ws_host_url, false);
|
||||
}
|
||||
};
|
||||
var ws_error = function(evt){
|
||||
if (already_called_onclose){
|
||||
return;
|
||||
}
|
||||
already_called_onclose = true;
|
||||
that._websocket_closed(ws_host_url, false);
|
||||
};
|
||||
var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
|
||||
for (var i=0; i < channels.length; i++) {
|
||||
channels[i].onopen = $.proxy(this._ws_opened, this);
|
||||
channels[i].onclose = ws_closed_early;
|
||||
channels[i].onerror = ws_error;
|
||||
}
|
||||
// switch from early-close to late-close message after 1s
|
||||
setTimeout(function() {
|
||||
for (var i=0; i < channels.length; i++) {
|
||||
if (channels[i] !== null) {
|
||||
channels[i].onclose = ws_closed_late;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
|
||||
this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
|
||||
this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a websocket entering the open state
|
||||
* sends session and cookie authentication info as first message.
|
||||
* Once all sockets are open, signal the Kernel.status_started event.
|
||||
* @method _ws_opened
|
||||
*/
|
||||
Kernel.prototype._ws_opened = function (evt) {
|
||||
// send the session id so the Session object Python-side
|
||||
// has the same identity
|
||||
evt.target.send(this.session_id + ':' + document.cookie);
|
||||
|
||||
var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
|
||||
for (var i=0; i < channels.length; i++) {
|
||||
// if any channel is not ready, don't trigger event.
|
||||
if ( channels[i].readyState !== WebSocket.OPEN ) return;
|
||||
}
|
||||
// all events ready, trigger started event.
|
||||
this.events.trigger('status_started.Kernel', {kernel: this});
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the websocket channels.
|
||||
* @method stop_channels
|
||||
*/
|
||||
Kernel.prototype.stop_channels = function () {
|
||||
var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
|
||||
for (var i=0; i < channels.length; i++) {
|
||||
if ( channels[i] !== null ) {
|
||||
channels[i].onclose = null;
|
||||
channels[i].close();
|
||||
}
|
||||
}
|
||||
this.shell_channel = this.iopub_channel = this.stdin_channel = null;
|
||||
};
|
||||
|
||||
// Main public methods.
|
||||
|
||||
// send a message on the Kernel's shell channel
|
||||
Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
|
||||
var msg = this._get_msg(msg_type, content, metadata);
|
||||
this.shell_channel.send(JSON.stringify(msg));
|
||||
this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
|
||||
return msg.header.msg_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get kernel info
|
||||
*
|
||||
* @param callback {function}
|
||||
* @method kernel_info
|
||||
*
|
||||
* When calling this method, pass a callback function that expects one argument.
|
||||
* The callback will be passed the complete `kernel_info_reply` message documented
|
||||
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
|
||||
*/
|
||||
Kernel.prototype.kernel_info = function (callback) {
|
||||
var callbacks;
|
||||
if (callback) {
|
||||
callbacks = { shell : { reply : callback } };
|
||||
}
|
||||
return this.send_shell_message("kernel_info_request", {}, callbacks);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get info on an object
|
||||
*
|
||||
* @param code {string}
|
||||
* @param cursor_pos {integer}
|
||||
* @param callback {function}
|
||||
* @method inspect
|
||||
*
|
||||
* When calling this method, pass a callback function that expects one argument.
|
||||
* The callback will be passed the complete `inspect_reply` message documented
|
||||
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
|
||||
*/
|
||||
Kernel.prototype.inspect = function (code, cursor_pos, callback) {
|
||||
var callbacks;
|
||||
if (callback) {
|
||||
callbacks = { shell : { reply : callback } };
|
||||
}
|
||||
|
||||
var content = {
|
||||
code : code,
|
||||
cursor_pos : cursor_pos,
|
||||
detail_level : 0,
|
||||
};
|
||||
return this.send_shell_message("inspect_request", content, callbacks);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute given code into kernel, and pass result to callback.
|
||||
*
|
||||
* @async
|
||||
* @method execute
|
||||
* @param {string} code
|
||||
* @param [callbacks] {Object} With the following keys (all optional)
|
||||
* @param callbacks.shell.reply {function}
|
||||
* @param callbacks.shell.payload.[payload_name] {function}
|
||||
* @param callbacks.iopub.output {function}
|
||||
* @param callbacks.iopub.clear_output {function}
|
||||
* @param callbacks.input {function}
|
||||
* @param {object} [options]
|
||||
* @param [options.silent=false] {Boolean}
|
||||
* @param [options.user_expressions=empty_dict] {Dict}
|
||||
* @param [options.allow_stdin=false] {Boolean} true|false
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* The options object should contain the options for the execute call. Its default
|
||||
* values are:
|
||||
*
|
||||
* options = {
|
||||
* silent : true,
|
||||
* user_expressions : {},
|
||||
* allow_stdin : false
|
||||
* }
|
||||
*
|
||||
* When calling this method pass a callbacks structure of the form:
|
||||
*
|
||||
* callbacks = {
|
||||
* shell : {
|
||||
* reply : execute_reply_callback,
|
||||
* payload : {
|
||||
* set_next_input : set_next_input_callback,
|
||||
* }
|
||||
* },
|
||||
* iopub : {
|
||||
* output : output_callback,
|
||||
* clear_output : clear_output_callback,
|
||||
* },
|
||||
* input : raw_input_callback
|
||||
* }
|
||||
*
|
||||
* Each callback will be passed the entire message as a single arugment.
|
||||
* Payload handlers will be passed the corresponding payload and the execute_reply message.
|
||||
*/
|
||||
Kernel.prototype.execute = function (code, callbacks, options) {
|
||||
|
||||
var content = {
|
||||
code : code,
|
||||
silent : true,
|
||||
store_history : false,
|
||||
user_expressions : {},
|
||||
allow_stdin : false
|
||||
};
|
||||
callbacks = callbacks || {};
|
||||
if (callbacks.input !== undefined) {
|
||||
content.allow_stdin = true;
|
||||
}
|
||||
$.extend(true, content, options);
|
||||
this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
|
||||
return this.send_shell_message("execute_request", content, callbacks);
|
||||
};
|
||||
|
||||
/**
|
||||
* When calling this method, pass a function to be called with the `complete_reply` message
|
||||
* as its only argument when it arrives.
|
||||
*
|
||||
* `complete_reply` is documented
|
||||
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
|
||||
*
|
||||
* @method complete
|
||||
* @param code {string}
|
||||
* @param cursor_pos {integer}
|
||||
* @param callback {function}
|
||||
*
|
||||
*/
|
||||
Kernel.prototype.complete = function (code, cursor_pos, callback) {
|
||||
var callbacks;
|
||||
if (callback) {
|
||||
callbacks = { shell : { reply : callback } };
|
||||
}
|
||||
var content = {
|
||||
code : code,
|
||||
cursor_pos : cursor_pos,
|
||||
};
|
||||
return this.send_shell_message("complete_request", content, callbacks);
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype.interrupt = function () {
|
||||
if (this.running) {
|
||||
this.events.trigger('status_interrupting.Kernel', {kernel: this});
|
||||
this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype.kill = function (success, error) {
|
||||
if (this.running) {
|
||||
this.running = false;
|
||||
var settings = {
|
||||
cache : false,
|
||||
type : "DELETE",
|
||||
success : success,
|
||||
error : error || utils.log_ajax_error,
|
||||
};
|
||||
$.ajax(utils.url_join_encode(this.kernel_url), settings);
|
||||
this.stop_channels();
|
||||
}
|
||||
};
|
||||
|
||||
Kernel.prototype.send_input_reply = function (input) {
|
||||
var content = {
|
||||
value : input,
|
||||
};
|
||||
this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
|
||||
var msg = this._get_msg("input_reply", content);
|
||||
this.stdin_channel.send(JSON.stringify(msg));
|
||||
return msg.header.msg_id;
|
||||
};
|
||||
|
||||
|
||||
// Reply handlers
|
||||
|
||||
Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
|
||||
this._iopub_handlers[msg_type] = callback;
|
||||
};
|
||||
|
||||
Kernel.prototype.get_iopub_handler = function (msg_type) {
|
||||
// get iopub handler for a specific message type
|
||||
return this._iopub_handlers[msg_type];
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
|
||||
// get callbacks for a specific message
|
||||
if (msg_id == this.last_msg_id) {
|
||||
return this.last_msg_callbacks;
|
||||
} else {
|
||||
return this._msg_callbacks[msg_id];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
|
||||
if (this._msg_callbacks[msg_id] !== undefined ) {
|
||||
delete this._msg_callbacks[msg_id];
|
||||
}
|
||||
};
|
||||
|
||||
Kernel.prototype._finish_shell = function (msg_id) {
|
||||
var callbacks = this._msg_callbacks[msg_id];
|
||||
if (callbacks !== undefined) {
|
||||
callbacks.shell_done = true;
|
||||
if (callbacks.iopub_done) {
|
||||
this.clear_callbacks_for_msg(msg_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Kernel.prototype._finish_iopub = function (msg_id) {
|
||||
var callbacks = this._msg_callbacks[msg_id];
|
||||
if (callbacks !== undefined) {
|
||||
callbacks.iopub_done = true;
|
||||
if (callbacks.shell_done) {
|
||||
this.clear_callbacks_for_msg(msg_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Set callbacks for a particular message.
|
||||
* Callbacks should be a struct of the following form:
|
||||
* shell : {
|
||||
*
|
||||
* }
|
||||
|
||||
*/
|
||||
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
|
||||
this.last_msg_id = msg_id;
|
||||
if (callbacks) {
|
||||
// shallow-copy mapping, because we will modify it at the top level
|
||||
var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
|
||||
cbcopy.shell = callbacks.shell;
|
||||
cbcopy.iopub = callbacks.iopub;
|
||||
cbcopy.input = callbacks.input;
|
||||
cbcopy.shell_done = (!callbacks.shell);
|
||||
cbcopy.iopub_done = (!callbacks.iopub);
|
||||
} else {
|
||||
this.last_msg_callbacks = {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype._handle_shell_reply = function (e) {
|
||||
var reply = $.parseJSON(e.data);
|
||||
this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
|
||||
var content = reply.content;
|
||||
var metadata = reply.metadata;
|
||||
var parent_id = reply.parent_header.msg_id;
|
||||
var callbacks = this.get_callbacks_for_msg(parent_id);
|
||||
if (!callbacks || !callbacks.shell) {
|
||||
return;
|
||||
}
|
||||
var shell_callbacks = callbacks.shell;
|
||||
|
||||
// signal that shell callbacks are done
|
||||
this._finish_shell(parent_id);
|
||||
|
||||
if (shell_callbacks.reply !== undefined) {
|
||||
shell_callbacks.reply(reply);
|
||||
}
|
||||
if (content.payload && shell_callbacks.payload) {
|
||||
this._handle_payloads(content.payload, shell_callbacks.payload, reply);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
|
||||
var l = payloads.length;
|
||||
// Payloads are handled by triggering events because we don't want the Kernel
|
||||
// to depend on the Notebook or Pager classes.
|
||||
for (var i=0; i<l; i++) {
|
||||
var payload = payloads[i];
|
||||
var callback = payload_callbacks[payload.source];
|
||||
if (callback) {
|
||||
callback(payload, msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Kernel.prototype._handle_status_message = function (msg) {
|
||||
var execution_state = msg.content.execution_state;
|
||||
var parent_id = msg.parent_header.msg_id;
|
||||
|
||||
// dispatch status msg callbacks, if any
|
||||
var callbacks = this.get_callbacks_for_msg(parent_id);
|
||||
if (callbacks && callbacks.iopub && callbacks.iopub.status) {
|
||||
try {
|
||||
callbacks.iopub.status(msg);
|
||||
} catch (e) {
|
||||
console.log("Exception in status msg handler", e, e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
if (execution_state === 'busy') {
|
||||
this.events.trigger('status_busy.Kernel', {kernel: this});
|
||||
} else if (execution_state === 'idle') {
|
||||
// signal that iopub callbacks are (probably) done
|
||||
// async output may still arrive,
|
||||
// but only for the most recent request
|
||||
this._finish_iopub(parent_id);
|
||||
|
||||
// trigger status_idle event
|
||||
this.events.trigger('status_idle.Kernel', {kernel: this});
|
||||
} else if (execution_state === 'restarting') {
|
||||
// autorestarting is distinct from restarting,
|
||||
// in that it means the kernel died and the server is restarting it.
|
||||
// status_restarting sets the notification widget,
|
||||
// autorestart shows the more prominent dialog.
|
||||
this.events.trigger('status_autorestarting.Kernel', {kernel: this});
|
||||
this.events.trigger('status_restarting.Kernel', {kernel: this});
|
||||
} else if (execution_state === 'dead') {
|
||||
this.stop_channels();
|
||||
this.events.trigger('status_dead.Kernel', {kernel: this});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// handle clear_output message
|
||||
Kernel.prototype._handle_clear_output = function (msg) {
|
||||
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
|
||||
if (!callbacks || !callbacks.iopub) {
|
||||
return;
|
||||
}
|
||||
var callback = callbacks.iopub.clear_output;
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// handle an output message (execute_result, display_data, etc.)
|
||||
Kernel.prototype._handle_output_message = function (msg) {
|
||||
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
|
||||
if (!callbacks || !callbacks.iopub) {
|
||||
return;
|
||||
}
|
||||
var callback = callbacks.iopub.output;
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// dispatch IOPub messages to respective handlers.
|
||||
// each message type should have a handler.
|
||||
Kernel.prototype._handle_iopub_message = function (e) {
|
||||
var msg = $.parseJSON(e.data);
|
||||
|
||||
var handler = this.get_iopub_handler(msg.header.msg_type);
|
||||
if (handler !== undefined) {
|
||||
handler(msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype._handle_input_request = function (e) {
|
||||
var request = $.parseJSON(e.data);
|
||||
var header = request.header;
|
||||
var content = request.content;
|
||||
var metadata = request.metadata;
|
||||
var msg_type = header.msg_type;
|
||||
if (msg_type !== 'input_request') {
|
||||
console.log("Invalid input request!", request);
|
||||
return;
|
||||
}
|
||||
var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
|
||||
if (callbacks) {
|
||||
if (callbacks.input) {
|
||||
callbacks.input(request);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Backwards compatability.
|
||||
IPython.Kernel = Kernel;
|
||||
|
||||
return {'Kernel': Kernel};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,120 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
], function (_) {
|
||||
"use strict";
|
||||
|
||||
var _deserialize_array_buffer = function (buf) {
|
||||
var data = new DataView(buf);
|
||||
// read the header: 1 + nbufs 32b integers
|
||||
var nbufs = data.getUint32(0);
|
||||
var offsets = [];
|
||||
var i;
|
||||
for (i = 1; i <= nbufs; i++) {
|
||||
offsets.push(data.getUint32(i * 4));
|
||||
}
|
||||
var json_bytes = new Uint8Array(buf.slice(offsets[0], offsets[1]));
|
||||
var msg = JSON.parse(
|
||||
(new TextDecoder('utf8')).decode(json_bytes)
|
||||
);
|
||||
// the remaining chunks are stored as DataViews in msg.buffers
|
||||
msg.buffers = [];
|
||||
var start, stop;
|
||||
for (i = 1; i < nbufs; i++) {
|
||||
start = offsets[i];
|
||||
stop = offsets[i+1] || buf.byteLength;
|
||||
msg.buffers.push(new DataView(buf.slice(start, stop)));
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
var _deserialize_binary = function(data, callback) {
|
||||
/**
|
||||
* deserialize the binary message format
|
||||
* callback will be called with a message whose buffers attribute
|
||||
* will be an array of DataViews.
|
||||
*/
|
||||
if (data instanceof Blob) {
|
||||
// data is Blob, have to deserialize from ArrayBuffer in reader callback
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
var msg = _deserialize_array_buffer(this.result);
|
||||
callback(msg);
|
||||
};
|
||||
reader.readAsArrayBuffer(data);
|
||||
} else {
|
||||
// data is ArrayBuffer, can deserialize directly
|
||||
var msg = _deserialize_array_buffer(data);
|
||||
callback(msg);
|
||||
}
|
||||
};
|
||||
|
||||
var deserialize = function (data, callback) {
|
||||
/**
|
||||
* deserialize a message and pass the unpacked message object to callback
|
||||
*/
|
||||
if (typeof data === "string") {
|
||||
// text JSON message
|
||||
callback(JSON.parse(data));
|
||||
} else {
|
||||
// binary message
|
||||
_deserialize_binary(data, callback);
|
||||
}
|
||||
};
|
||||
|
||||
var _serialize_binary = function (msg) {
|
||||
/**
|
||||
* implement the binary serialization protocol
|
||||
* serializes JSON message to ArrayBuffer
|
||||
*/
|
||||
msg = _.clone(msg);
|
||||
var offsets = [];
|
||||
var buffers = [];
|
||||
msg.buffers.map(function (buf) {
|
||||
buffers.push(buf);
|
||||
});
|
||||
delete msg.buffers;
|
||||
var json_utf8 = (new TextEncoder('utf8')).encode(JSON.stringify(msg));
|
||||
buffers.unshift(json_utf8);
|
||||
var nbufs = buffers.length;
|
||||
offsets.push(4 * (nbufs + 1));
|
||||
var i;
|
||||
for (i = 0; i + 1 < buffers.length; i++) {
|
||||
offsets.push(offsets[offsets.length-1] + buffers[i].byteLength);
|
||||
}
|
||||
var msg_buf = new Uint8Array(
|
||||
offsets[offsets.length-1] + buffers[buffers.length-1].byteLength
|
||||
);
|
||||
// use DataView.setUint32 for network byte-order
|
||||
var view = new DataView(msg_buf.buffer);
|
||||
// write nbufs to first 4 bytes
|
||||
view.setUint32(0, nbufs);
|
||||
// write offsets to next 4 * nbufs bytes
|
||||
for (i = 0; i < offsets.length; i++) {
|
||||
view.setUint32(4 * (i+1), offsets[i]);
|
||||
}
|
||||
// write all the buffers at their respective offsets
|
||||
for (i = 0; i < buffers.length; i++) {
|
||||
msg_buf.set(new Uint8Array(buffers[i].buffer), offsets[i]);
|
||||
}
|
||||
|
||||
// return raw ArrayBuffer
|
||||
return msg_buf.buffer;
|
||||
};
|
||||
|
||||
var serialize = function (msg) {
|
||||
if (msg.buffers && msg.buffers.length) {
|
||||
return _serialize_binary(msg);
|
||||
} else {
|
||||
return JSON.stringify(msg);
|
||||
}
|
||||
};
|
||||
|
||||
var exports = {
|
||||
deserialize : deserialize,
|
||||
serialize: serialize
|
||||
};
|
||||
return exports;
|
||||
});
|
||||
@ -1,150 +0,0 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'services/kernels/js/kernel',
|
||||
], function(IPython, $, utils, kernel) {
|
||||
"use strict";
|
||||
|
||||
var Session = function(options){
|
||||
this.kernel = null;
|
||||
this.id = null;
|
||||
this.notebook = options.notebook;
|
||||
this.events = options.notebook.events;
|
||||
this.name = options.notebook_name;
|
||||
this.path = options.notebook_path;
|
||||
this.kernel_name = options.kernel_name;
|
||||
this.base_url = options.base_url;
|
||||
this.ws_url = options.ws_url;
|
||||
};
|
||||
|
||||
Session.prototype.start = function (success, error) {
|
||||
var that = this;
|
||||
var model = {
|
||||
notebook : {
|
||||
name : this.name,
|
||||
path : this.path
|
||||
},
|
||||
kernel : {
|
||||
name : this.kernel_name
|
||||
}
|
||||
};
|
||||
var settings = {
|
||||
processData : false,
|
||||
cache : false,
|
||||
type : "POST",
|
||||
data: JSON.stringify(model),
|
||||
dataType : "json",
|
||||
success : function (data, status, xhr) {
|
||||
that._handle_start_success(data);
|
||||
if (success) {
|
||||
success(data, status, xhr);
|
||||
}
|
||||
},
|
||||
error : function (xhr, status, err) {
|
||||
that._handle_start_failure(xhr, status, err);
|
||||
if (error !== undefined) {
|
||||
error(xhr, status, err);
|
||||
}
|
||||
utils.log_ajax_error(xhr, status, err);
|
||||
}
|
||||
};
|
||||
var url = utils.url_join_encode(this.base_url, 'api/sessions');
|
||||
$.ajax(url, settings);
|
||||
};
|
||||
|
||||
Session.prototype.rename_notebook = function (name, path) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
var model = {
|
||||
notebook : {
|
||||
name : this.name,
|
||||
path : this.path
|
||||
}
|
||||
};
|
||||
var settings = {
|
||||
processData : false,
|
||||
cache : false,
|
||||
type : "PATCH",
|
||||
data: JSON.stringify(model),
|
||||
dataType : "json",
|
||||
error : utils.log_ajax_error,
|
||||
};
|
||||
var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
|
||||
$.ajax(url, settings);
|
||||
};
|
||||
|
||||
Session.prototype.delete = function (success, error) {
|
||||
var settings = {
|
||||
processData : false,
|
||||
cache : false,
|
||||
type : "DELETE",
|
||||
dataType : "json",
|
||||
success : success,
|
||||
error : error || utils.log_ajax_error,
|
||||
};
|
||||
if (this.kernel) {
|
||||
this.kernel.running = false;
|
||||
this.kernel.stop_channels();
|
||||
}
|
||||
var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
|
||||
$.ajax(url, settings);
|
||||
};
|
||||
|
||||
// Kernel related things
|
||||
/**
|
||||
* Create the Kernel object associated with this Session.
|
||||
*
|
||||
* @method _handle_start_success
|
||||
*/
|
||||
Session.prototype._handle_start_success = function (data, status, xhr) {
|
||||
this.id = data.id;
|
||||
// If we asked for 'python', the response will have 'python3' or 'python2'.
|
||||
this.kernel_name = data.kernel.name;
|
||||
this.events.trigger('started.Session', this);
|
||||
var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
|
||||
this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
|
||||
this.kernel._kernel_started(data.kernel);
|
||||
};
|
||||
|
||||
Session.prototype._handle_start_failure = function (xhr, status, error) {
|
||||
this.events.trigger('start_failed.Session', [this, xhr, status, error]);
|
||||
this.events.trigger('status_dead.Kernel');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt the user to restart the IPython kernel.
|
||||
*
|
||||
* @method restart_kernel
|
||||
*/
|
||||
Session.prototype.restart_kernel = function () {
|
||||
this.kernel.restart();
|
||||
};
|
||||
|
||||
Session.prototype.interrupt_kernel = function() {
|
||||
this.kernel.interrupt();
|
||||
};
|
||||
|
||||
|
||||
Session.prototype.kill_kernel = function() {
|
||||
this.kernel.kill();
|
||||
};
|
||||
|
||||
var SessionAlreadyStarting = function (message) {
|
||||
this.name = "SessionAlreadyStarting";
|
||||
this.message = (message || "");
|
||||
};
|
||||
|
||||
SessionAlreadyStarting.prototype = Error.prototype;
|
||||
|
||||
// For backwards compatability.
|
||||
IPython.Session = Session;
|
||||
|
||||
return {
|
||||
Session: Session,
|
||||
SessionAlreadyStarting: SessionAlreadyStarting,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,319 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'services/kernels/kernel',
|
||||
], function(IPython, $, utils, kernel) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Session object for accessing the session REST api. The session
|
||||
* should be used to start kernels and then shut them down -- for
|
||||
* all other operations, the kernel object should be used.
|
||||
*
|
||||
* Options should include:
|
||||
* - notebook_path: the path (not including name) to the notebook
|
||||
* - kernel_name: the type of kernel (e.g. python3)
|
||||
* - base_url: the root url of the notebook server
|
||||
* - ws_url: the url to access websockets
|
||||
* - notebook: Notebook object
|
||||
*
|
||||
* @class Session
|
||||
* @param {Object} options
|
||||
*/
|
||||
var Session = function (options) {
|
||||
this.id = null;
|
||||
this.notebook_model = {
|
||||
path: options.notebook_path
|
||||
};
|
||||
this.kernel_model = {
|
||||
id: null,
|
||||
name: options.kernel_name
|
||||
};
|
||||
|
||||
this.base_url = options.base_url;
|
||||
this.ws_url = options.ws_url;
|
||||
this.session_service_url = utils.url_join_encode(this.base_url, 'api/sessions');
|
||||
this.session_url = null;
|
||||
|
||||
this.notebook = options.notebook;
|
||||
this.kernel = null;
|
||||
this.events = options.notebook.events;
|
||||
|
||||
this.bind_events();
|
||||
};
|
||||
|
||||
Session.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
var record_status = function (evt, info) {
|
||||
console.log('Session: ' + evt.type + ' (' + info.session.id + ')');
|
||||
};
|
||||
|
||||
this.events.on('kernel_created.Session', record_status);
|
||||
this.events.on('kernel_dead.Session', record_status);
|
||||
this.events.on('kernel_killed.Session', record_status);
|
||||
|
||||
// if the kernel dies, then also remove the session
|
||||
this.events.on('kernel_dead.Kernel', function () {
|
||||
that.delete();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Public REST api functions
|
||||
|
||||
/**
|
||||
* GET /api/sessions
|
||||
*
|
||||
* Get a list of the current sessions.
|
||||
*
|
||||
* @function list
|
||||
* @param {function} [success] - function executed on ajax success
|
||||
* @param {function} [error] - functon executed on ajax error
|
||||
*/
|
||||
Session.prototype.list = function (success, error) {
|
||||
$.ajax(this.session_service_url, {
|
||||
processData: false,
|
||||
cache: false,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: success,
|
||||
error: this._on_error(error)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/sessions
|
||||
*
|
||||
* Start a new session. This function can only executed once.
|
||||
*
|
||||
* @function start
|
||||
* @param {function} [success] - function executed on ajax success
|
||||
* @param {function} [error] - functon executed on ajax error
|
||||
*/
|
||||
Session.prototype.start = function (success, error) {
|
||||
var that = this;
|
||||
var on_success = function (data, status, xhr) {
|
||||
if (that.kernel) {
|
||||
that.kernel.name = that.kernel_model.name;
|
||||
} else {
|
||||
var kernel_service_url = utils.url_path_join(that.base_url, "api/kernels");
|
||||
that.kernel = new kernel.Kernel(kernel_service_url, that.ws_url, that.notebook, that.kernel_model.name);
|
||||
}
|
||||
that.events.trigger('kernel_created.Session', {session: that, kernel: that.kernel});
|
||||
that.kernel._kernel_created(data.kernel);
|
||||
if (success) {
|
||||
success(data, status, xhr);
|
||||
}
|
||||
};
|
||||
var on_error = function (xhr, status, err) {
|
||||
that.events.trigger('kernel_dead.Session', {session: that, xhr: xhr, status: status, error: err});
|
||||
if (error) {
|
||||
error(xhr, status, err);
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax(this.session_service_url, {
|
||||
processData: false,
|
||||
cache: false,
|
||||
type: "POST",
|
||||
data: JSON.stringify(this._get_model()),
|
||||
dataType: "json",
|
||||
success: this._on_success(on_success),
|
||||
error: this._on_error(on_error)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* GET /api/sessions/[:session_id]
|
||||
*
|
||||
* Get information about a session.
|
||||
*
|
||||
* @function get_info
|
||||
* @param {function} [success] - function executed on ajax success
|
||||
* @param {function} [error] - functon executed on ajax error
|
||||
*/
|
||||
Session.prototype.get_info = function (success, error) {
|
||||
$.ajax(this.session_url, {
|
||||
processData: false,
|
||||
cache: false,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: this._on_success(success),
|
||||
error: this._on_error(error)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* PATCH /api/sessions/[:session_id]
|
||||
*
|
||||
* Rename or move a notebook. If the given name or path are
|
||||
* undefined, then they will not be changed.
|
||||
*
|
||||
* @function rename_notebook
|
||||
* @param {string} [path] - new notebook path
|
||||
* @param {function} [success] - function executed on ajax success
|
||||
* @param {function} [error] - functon executed on ajax error
|
||||
*/
|
||||
Session.prototype.rename_notebook = function (path, success, error) {
|
||||
if (path !== undefined) {
|
||||
this.notebook_model.path = path;
|
||||
}
|
||||
|
||||
$.ajax(this.session_url, {
|
||||
processData: false,
|
||||
cache: false,
|
||||
type: "PATCH",
|
||||
data: JSON.stringify(this._get_model()),
|
||||
dataType: "json",
|
||||
success: this._on_success(success),
|
||||
error: this._on_error(error)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/sessions/[:session_id]
|
||||
*
|
||||
* Kill the kernel and shutdown the session.
|
||||
*
|
||||
* @function delete
|
||||
* @param {function} [success] - function executed on ajax success
|
||||
* @param {function} [error] - functon executed on ajax error
|
||||
*/
|
||||
Session.prototype.delete = function (success, error) {
|
||||
if (this.kernel) {
|
||||
this.events.trigger('kernel_killed.Session', {session: this, kernel: this.kernel});
|
||||
this.kernel._kernel_dead();
|
||||
}
|
||||
|
||||
$.ajax(this.session_url, {
|
||||
processData: false,
|
||||
cache: false,
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
success: this._on_success(success),
|
||||
error: this._on_error(error)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Restart the session by deleting it and the starting it
|
||||
* fresh. If options are given, they can include any of the
|
||||
* following:
|
||||
*
|
||||
* - notebook_path - the path to the notebook
|
||||
* - kernel_name - the name (type) of the kernel
|
||||
*
|
||||
* @function restart
|
||||
* @param {Object} [options] - options for the new kernel
|
||||
* @param {function} [success] - function executed on ajax success
|
||||
* @param {function} [error] - functon executed on ajax error
|
||||
*/
|
||||
Session.prototype.restart = function (options, success, error) {
|
||||
var that = this;
|
||||
var start = function () {
|
||||
if (options && options.notebook_path) {
|
||||
that.notebook_model.path = options.notebook_path;
|
||||
}
|
||||
if (options && options.kernel_name) {
|
||||
that.kernel_model.name = options.kernel_name;
|
||||
}
|
||||
that.kernel_model.id = null;
|
||||
that.start(success, error);
|
||||
};
|
||||
this.delete(start, start);
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
|
||||
/**
|
||||
* Get the data model for the session, which includes the notebook path
|
||||
* and kernel (name and id).
|
||||
*
|
||||
* @function _get_model
|
||||
* @returns {Object} - the data model
|
||||
*/
|
||||
Session.prototype._get_model = function () {
|
||||
return {
|
||||
notebook: this.notebook_model,
|
||||
kernel: this.kernel_model
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the data model from the given JSON object, which should
|
||||
* have attributes of `id`, `notebook`, and/or `kernel`. If
|
||||
* provided, the notebook data must include name and path, and the
|
||||
* kernel data must include name and id.
|
||||
*
|
||||
* @function _update_model
|
||||
* @param {Object} data - updated data model
|
||||
*/
|
||||
Session.prototype._update_model = function (data) {
|
||||
if (data && data.id) {
|
||||
this.id = data.id;
|
||||
this.session_url = utils.url_join_encode(this.session_service_url, this.id);
|
||||
}
|
||||
if (data && data.notebook) {
|
||||
this.notebook_model.path = data.notebook.path;
|
||||
}
|
||||
if (data && data.kernel) {
|
||||
this.kernel_model.name = data.kernel.name;
|
||||
this.kernel_model.id = data.kernel.id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a successful AJAX request by updating the session data
|
||||
* model with the response, and then optionally calling a provided
|
||||
* callback.
|
||||
*
|
||||
* @function _on_success
|
||||
* @param {function} success - callback
|
||||
*/
|
||||
Session.prototype._on_success = function (success) {
|
||||
var that = this;
|
||||
return function (data, status, xhr) {
|
||||
that._update_model(data);
|
||||
if (success) {
|
||||
success(data, status, xhr);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a failed AJAX request by logging the error message, and
|
||||
* then optionally calling a provided callback.
|
||||
*
|
||||
* @function _on_error
|
||||
* @param {function} error - callback
|
||||
*/
|
||||
Session.prototype._on_error = function (error) {
|
||||
return function (xhr, status, err) {
|
||||
utils.log_ajax_error(xhr, status, err);
|
||||
if (error) {
|
||||
error(xhr, status, err);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Error type indicating that the session is already starting.
|
||||
*/
|
||||
var SessionAlreadyStarting = function (message) {
|
||||
this.name = "SessionAlreadyStarting";
|
||||
this.message = (message || "");
|
||||
};
|
||||
SessionAlreadyStarting.prototype = Error.prototype;
|
||||
|
||||
// For backwards compatability.
|
||||
IPython.Session = Session;
|
||||
|
||||
return {
|
||||
Session: Session,
|
||||
SessionAlreadyStarting: SessionAlreadyStarting
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
require([
|
||||
'jquery',
|
||||
'termjs',
|
||||
'base/js/utils',
|
||||
'base/js/page',
|
||||
'terminal/js/terminado',
|
||||
'custom/custom',
|
||||
], function(
|
||||
$,
|
||||
termjs,
|
||||
utils,
|
||||
page,
|
||||
terminado
|
||||
){
|
||||
page = new page.Page();
|
||||
// Test size: 25x80
|
||||
var termRowHeight = function(){ return 1.00 * $("#dummy-screen")[0].offsetHeight / 25;};
|
||||
// 1.02 here arrived at by trial and error to make the spacing look right
|
||||
var termColWidth = function() { return 1.02 * $("#dummy-screen-rows")[0].offsetWidth / 80;};
|
||||
|
||||
var base_url = utils.get_body_data('baseUrl');
|
||||
var ws_path = utils.get_body_data('wsPath');
|
||||
var ws_url = location.protocol.replace('http', 'ws') + "//" + location.host
|
||||
+ base_url + ws_path;
|
||||
|
||||
var header = $("#header")[0]
|
||||
function calculate_size() {
|
||||
height = window.innerHeight - header.offsetHeight;
|
||||
width = window.innerWidth;
|
||||
var rows = Math.min(1000, Math.max(20, Math.floor(height/termRowHeight())-1));
|
||||
var cols = Math.min(1000, Math.max(40, Math.floor(width/termColWidth())-1));
|
||||
console.log("resize to :", rows , 'rows by ', cols, 'columns');
|
||||
return {rows: rows, cols: cols};
|
||||
}
|
||||
|
||||
page.show_header();
|
||||
|
||||
size = calculate_size();
|
||||
var terminal = terminado.make_terminal($("#terminado-container")[0], size, ws_url);
|
||||
|
||||
page.show_site();
|
||||
|
||||
window.onresize = function() {
|
||||
var geom = calculate_size();
|
||||
terminal.term.resize(geom.cols, geom.rows);
|
||||
terminal.socket.send(JSON.stringify(["set_size", geom.rows, geom.cols,
|
||||
window.innerHeight, window.innerWidth]));
|
||||
};
|
||||
|
||||
});
|
||||
@ -0,0 +1,39 @@
|
||||
define ([], function() {
|
||||
function make_terminal(element, size, ws_url) {
|
||||
var ws = new WebSocket(ws_url);
|
||||
var term = new Terminal({
|
||||
cols: size.cols,
|
||||
rows: size.rows,
|
||||
screenKeys: true,
|
||||
useStyle: false
|
||||
});
|
||||
ws.onopen = function(event) {
|
||||
ws.send(JSON.stringify(["set_size", size.rows, size.cols,
|
||||
window.innerHeight, window.innerWidth]));
|
||||
term.on('data', function(data) {
|
||||
ws.send(JSON.stringify(['stdin', data]));
|
||||
});
|
||||
|
||||
term.on('title', function(title) {
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
term.open(element);
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
json_msg = JSON.parse(event.data);
|
||||
switch(json_msg[0]) {
|
||||
case "stdout":
|
||||
term.write(json_msg[1]);
|
||||
break;
|
||||
case "disconnect":
|
||||
term.write("\r\n\r\n[CLOSED]\r\n");
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
return {socket: ws, term: term};
|
||||
}
|
||||
|
||||
return {make_terminal: make_terminal};
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue