##// END OF EJS Templates
Merge pull request #5076 from ellisonbg/keyboard...
Min RK -
r15625:b721408d merge
parent child Browse files
Show More
@@ -0,0 +1,251 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Keyboard management
10 //============================================================================
11
12 IPython.namespace('IPython.keyboard');
13
14 IPython.keyboard = (function (IPython) {
15 "use strict";
16
17 // Setup global keycodes and inverse keycodes.
18
19 // See http://unixpapa.com/js/key.html for a complete description. The short of
20 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
21 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
22 // but have minor differences.
23
24 // These apply to Firefox, (Webkit and IE)
25 var _keycodes = {
26 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
27 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
28 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
29 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
30 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
31 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
32 '\\ |': 220, '\' "': 222,
33 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
34 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
35 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
36 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
37 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
38 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
39 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
40 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
41 'insert': 45, 'delete': 46, 'numlock': 144,
42 };
43
44 // These apply to Firefox and Opera
45 var _mozilla_keycodes = {
46 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
47 }
48
49 // This apply to Webkit and IE
50 var _ie_keycodes = {
51 '; :': 186, '= +': 187, '- _': 189,
52 }
53
54 var browser = IPython.utils.browser[0];
55 var platform = IPython.utils.platform;
56
57 if (browser === 'Firefox' || browser === 'Opera') {
58 $.extend(_keycodes, _mozilla_keycodes);
59 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
60 $.extend(_keycodes, _ie_keycodes);
61 }
62
63 var keycodes = {};
64 var inv_keycodes = {};
65 for (var name in _keycodes) {
66 var names = name.split(' ');
67 if (names.length === 1) {
68 var n = names[0]
69 keycodes[n] = _keycodes[n]
70 inv_keycodes[_keycodes[n]] = n
71 } else {
72 var primary = names[0];
73 var secondary = names[1];
74 keycodes[primary] = _keycodes[name]
75 keycodes[secondary] = _keycodes[name]
76 inv_keycodes[_keycodes[name]] = primary
77 }
78 }
79
80 var normalize_key = function (key) {
81 return inv_keycodes[keycodes[key]];
82 }
83
84 var normalize_shortcut = function (shortcut) {
85 // Put a shortcut into normalized form:
86 // 1. Make lowercase
87 // 2. Replace cmd by meta
88 // 3. Sort '+' separated modifiers into the order alt+ctrl+meta+shift
89 // 4. Normalize keys
90 shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
91 var values = shortcut.split("+");
92 if (values.length === 1) {
93 return normalize_key(values[0])
94 } else {
95 var modifiers = values.slice(0,-1);
96 var key = normalize_key(values[values.length-1]);
97 modifiers.sort();
98 return modifiers.join('+') + '+' + key;
99 }
100 }
101
102 var shortcut_to_event = function (shortcut, type) {
103 // Convert a shortcut (shift+r) to a jQuery Event object
104 type = type || 'keydown';
105 shortcut = normalize_shortcut(shortcut);
106 var values = shortcut.split("+");
107 var modifiers = values.slice(0,-1);
108 var key = values[values.length-1];
109 var opts = {which: keycodes[key]};
110 if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
111 if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
112 if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
113 if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
114 return $.Event(type, opts);
115 }
116
117 var event_to_shortcut = function (event) {
118 // Convert a jQuery Event object to a shortcut (shift+r)
119 var shortcut = '';
120 var key = inv_keycodes[event.which]
121 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
122 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
123 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
124 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
125 shortcut += key;
126 return shortcut
127 }
128
129 var trigger_keydown = function (shortcut, element) {
130 // Trigger shortcut keydown on an element
131 element = element || document;
132 element = $(element);
133 var event = shortcut_to_event(shortcut, 'keydown');
134 element.trigger(event);
135 }
136
137
138 // Shortcut manager class
139
140 var ShortcutManager = function (delay) {
141 this._shortcuts = {}
142 this._counts = {}
143 this._timers = {}
144 this.delay = delay || 800; // delay in milliseconds
145 }
146
147 ShortcutManager.prototype.help = function () {
148 var help = [];
149 for (var shortcut in this._shortcuts) {
150 var help_string = this._shortcuts[shortcut]['help'];
151 var help_index = this._shortcuts[shortcut]['help_index'];
152 if (help_string) {
153 if (platform === 'MacOS') {
154 shortcut = shortcut.replace('meta', 'cmd');
155 }
156 help.push({
157 shortcut: shortcut,
158 help: help_string,
159 help_index: help_index}
160 );
161 }
162 }
163 help.sort(function (a, b) {
164 if (a.help_index > b.help_index)
165 return 1;
166 if (a.help_index < b.help_index)
167 return -1;
168 return 0;
169 });
170 return help;
171 }
172
173 ShortcutManager.prototype.clear_shortcuts = function () {
174 this._shortcuts = {};
175 }
176
177 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
178 if (typeof(data) === 'function') {
179 data = {help: '', help_index: '', handler: data}
180 }
181 data.help_index = data.help_index || '';
182 data.help = data.help || '';
183 data.count = data.count || 1;
184 if (data.help_index === '') {
185 data.help_index = 'zz';
186 }
187 shortcut = normalize_shortcut(shortcut);
188 this._counts[shortcut] = 0;
189 this._shortcuts[shortcut] = data;
190 }
191
192 ShortcutManager.prototype.add_shortcuts = function (data) {
193 for (var shortcut in data) {
194 this.add_shortcut(shortcut, data[shortcut]);
195 }
196 }
197
198 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
199 shortcut = normalize_shortcut(shortcut);
200 delete this._counts[shortcut];
201 delete this._shortcuts[shortcut];
202 }
203
204 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
205 var that = this;
206 var c = this._counts;
207 var t = this._timers;
208 var timer = null;
209 if (c[shortcut] === data.count-1) {
210 c[shortcut] = 0;
211 var timer = t[shortcut];
212 if (timer) {clearTimeout(timer); delete t[shortcut];}
213 return data.handler(event);
214 } else {
215 c[shortcut] = c[shortcut] + 1;
216 timer = setTimeout(function () {
217 c[shortcut] = 0;
218 }, that.delay);
219 t[shortcut] = timer;
220 }
221 return false;
222 }
223
224 ShortcutManager.prototype.call_handler = function (event) {
225 var shortcut = event_to_shortcut(event);
226 var data = this._shortcuts[shortcut];
227 if (data) {
228 var handler = data['handler'];
229 if (handler) {
230 if (data.count === 1) {
231 return handler(event);
232 } else if (data.count > 1) {
233 return this.count_handler(shortcut, event, data);
234 }
235 }
236 }
237 return true;
238 }
239
240 return {
241 keycodes : keycodes,
242 inv_keycodes : inv_keycodes,
243 ShortcutManager : ShortcutManager,
244 normalize_key : normalize_key,
245 normalize_shortcut : normalize_shortcut,
246 shortcut_to_event : shortcut_to_event,
247 event_to_shortcut : event_to_shortcut,
248 trigger_keydown : trigger_keydown
249 }
250
251 }(IPython));
@@ -0,0 +1,49 b''
1
2
3 var normalized_shortcuts = [
4 'ctrl+shift+m',
5 'alt+meta+p',
6 ];
7
8 var to_normalize = [
9 ['shift+%', 'shift+5'],
10 ['ShiFT+MeTa+CtRl+AlT+m', 'alt+ctrl+meta+shift+m'],
11 ];
12
13 var unshifted = "` 1 2 3 4 5 6 7 8 9 0 - = q w e r t y u i o p [ ] \\ a s d f g h j k l ; ' z x c v b n m , . /";
14 // shifted = '~ ! @ # $ % ^ & * ( ) _ + Q W E R T Y U I O P { } | A S D F G H J K L : " Z X C V B N M < > ?';
15
16 casper.notebook_test(function () {
17
18 this.then(function () {
19 this.each(unshifted.split(' '), function (self, item) {
20 var result = this.evaluate(function (sc) {
21 var e = IPython.keyboard.shortcut_to_event(sc);
22 var sc2 = IPython.keyboard.event_to_shortcut(e);
23 return sc2;
24 }, item);
25 this.test.assertEquals(result, item, 'Shortcut to event roundtrip: '+item);
26 });
27 });
28
29 this.then(function () {
30 this.each(to_normalize, function (self, item) {
31 var result = this.evaluate(function (pair) {
32 return IPython.keyboard.normalize_shortcut(pair[0]);
33 }, item);
34 this.test.assertEquals(result, item[1], 'Normalize shortcut: '+item[0]);
35 });
36 })
37
38 this.then(function () {
39 this.each(normalized_shortcuts, function (self, item) {
40 var result = this.evaluate(function (sc) {
41 var e = IPython.keyboard.shortcut_to_event(sc);
42 var sc2 = IPython.keyboard.event_to_shortcut(e);
43 return sc2;
44 }, item);
45 this.test.assertEquals(result, item, 'Shortcut to event roundtrip: '+item);
46 });
47 });
48
49 }); No newline at end of file
@@ -1,582 +1,514 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Utilities
9 // Utilities
10 //============================================================================
10 //============================================================================
11 IPython.namespace('IPython.utils');
11 IPython.namespace('IPython.utils');
12
12
13 IPython.utils = (function (IPython) {
13 IPython.utils = (function (IPython) {
14 "use strict";
14 "use strict";
15
15
16 IPython.load_extensions = function () {
16 IPython.load_extensions = function () {
17 // load one or more IPython notebook extensions with requirejs
17 // load one or more IPython notebook extensions with requirejs
18
18
19 var extensions = [];
19 var extensions = [];
20 var extension_names = arguments;
20 var extension_names = arguments;
21 for (var i = 0; i < extension_names.length; i++) {
21 for (var i = 0; i < extension_names.length; i++) {
22 extensions.push("nbextensions/" + arguments[i]);
22 extensions.push("nbextensions/" + arguments[i]);
23 }
23 }
24
24
25 require(extensions,
25 require(extensions,
26 function () {
26 function () {
27 for (var i = 0; i < arguments.length; i++) {
27 for (var i = 0; i < arguments.length; i++) {
28 var ext = arguments[i];
28 var ext = arguments[i];
29 var ext_name = extension_names[i];
29 var ext_name = extension_names[i];
30 // success callback
30 // success callback
31 console.log("Loaded extension: " + ext_name);
31 console.log("Loaded extension: " + ext_name);
32 if (ext && ext.load_ipython_extension !== undefined) {
32 if (ext && ext.load_ipython_extension !== undefined) {
33 ext.load_ipython_extension();
33 ext.load_ipython_extension();
34 }
34 }
35 }
35 }
36 },
36 },
37 function (err) {
37 function (err) {
38 // failure callback
38 // failure callback
39 console.log("Failed to load extension(s):", err.requireModules, err);
39 console.log("Failed to load extension(s):", err.requireModules, err);
40 }
40 }
41 );
41 );
42 };
42 };
43
43
44 //============================================================================
44 //============================================================================
45 // Cross-browser RegEx Split
45 // Cross-browser RegEx Split
46 //============================================================================
46 //============================================================================
47
47
48 // This code has been MODIFIED from the code licensed below to not replace the
48 // This code has been MODIFIED from the code licensed below to not replace the
49 // default browser split. The license is reproduced here.
49 // default browser split. The license is reproduced here.
50
50
51 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
51 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
52 /*!
52 /*!
53 * Cross-Browser Split 1.1.1
53 * Cross-Browser Split 1.1.1
54 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
54 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
55 * Available under the MIT License
55 * Available under the MIT License
56 * ECMAScript compliant, uniform cross-browser split method
56 * ECMAScript compliant, uniform cross-browser split method
57 */
57 */
58
58
59 /**
59 /**
60 * Splits a string into an array of strings using a regex or string
60 * Splits a string into an array of strings using a regex or string
61 * separator. Matches of the separator are not included in the result array.
61 * separator. Matches of the separator are not included in the result array.
62 * However, if `separator` is a regex that contains capturing groups,
62 * However, if `separator` is a regex that contains capturing groups,
63 * backreferences are spliced into the result each time `separator` is
63 * backreferences are spliced into the result each time `separator` is
64 * matched. Fixes browser bugs compared to the native
64 * matched. Fixes browser bugs compared to the native
65 * `String.prototype.split` and can be used reliably cross-browser.
65 * `String.prototype.split` and can be used reliably cross-browser.
66 * @param {String} str String to split.
66 * @param {String} str String to split.
67 * @param {RegExp|String} separator Regex or string to use for separating
67 * @param {RegExp|String} separator Regex or string to use for separating
68 * the string.
68 * the string.
69 * @param {Number} [limit] Maximum number of items to include in the result
69 * @param {Number} [limit] Maximum number of items to include in the result
70 * array.
70 * array.
71 * @returns {Array} Array of substrings.
71 * @returns {Array} Array of substrings.
72 * @example
72 * @example
73 *
73 *
74 * // Basic use
74 * // Basic use
75 * regex_split('a b c d', ' ');
75 * regex_split('a b c d', ' ');
76 * // -> ['a', 'b', 'c', 'd']
76 * // -> ['a', 'b', 'c', 'd']
77 *
77 *
78 * // With limit
78 * // With limit
79 * regex_split('a b c d', ' ', 2);
79 * regex_split('a b c d', ' ', 2);
80 * // -> ['a', 'b']
80 * // -> ['a', 'b']
81 *
81 *
82 * // Backreferences in result array
82 * // Backreferences in result array
83 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
83 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
84 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
84 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
85 */
85 */
86 var regex_split = function (str, separator, limit) {
86 var regex_split = function (str, separator, limit) {
87 // If `separator` is not a regex, use `split`
87 // If `separator` is not a regex, use `split`
88 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
88 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
89 return split.call(str, separator, limit);
89 return split.call(str, separator, limit);
90 }
90 }
91 var output = [],
91 var output = [],
92 flags = (separator.ignoreCase ? "i" : "") +
92 flags = (separator.ignoreCase ? "i" : "") +
93 (separator.multiline ? "m" : "") +
93 (separator.multiline ? "m" : "") +
94 (separator.extended ? "x" : "") + // Proposed for ES6
94 (separator.extended ? "x" : "") + // Proposed for ES6
95 (separator.sticky ? "y" : ""), // Firefox 3+
95 (separator.sticky ? "y" : ""), // Firefox 3+
96 lastLastIndex = 0,
96 lastLastIndex = 0,
97 // Make `global` and avoid `lastIndex` issues by working with a copy
97 // Make `global` and avoid `lastIndex` issues by working with a copy
98 separator = new RegExp(separator.source, flags + "g"),
98 separator = new RegExp(separator.source, flags + "g"),
99 separator2, match, lastIndex, lastLength;
99 separator2, match, lastIndex, lastLength;
100 str += ""; // Type-convert
100 str += ""; // Type-convert
101
101
102 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
102 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
103 if (!compliantExecNpcg) {
103 if (!compliantExecNpcg) {
104 // Doesn't need flags gy, but they don't hurt
104 // Doesn't need flags gy, but they don't hurt
105 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
105 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
106 }
106 }
107 /* Values for `limit`, per the spec:
107 /* Values for `limit`, per the spec:
108 * If undefined: 4294967295 // Math.pow(2, 32) - 1
108 * If undefined: 4294967295 // Math.pow(2, 32) - 1
109 * If 0, Infinity, or NaN: 0
109 * If 0, Infinity, or NaN: 0
110 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
110 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
111 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
111 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
112 * If other: Type-convert, then use the above rules
112 * If other: Type-convert, then use the above rules
113 */
113 */
114 limit = typeof(limit) === "undefined" ?
114 limit = typeof(limit) === "undefined" ?
115 -1 >>> 0 : // Math.pow(2, 32) - 1
115 -1 >>> 0 : // Math.pow(2, 32) - 1
116 limit >>> 0; // ToUint32(limit)
116 limit >>> 0; // ToUint32(limit)
117 while (match = separator.exec(str)) {
117 while (match = separator.exec(str)) {
118 // `separator.lastIndex` is not reliable cross-browser
118 // `separator.lastIndex` is not reliable cross-browser
119 lastIndex = match.index + match[0].length;
119 lastIndex = match.index + match[0].length;
120 if (lastIndex > lastLastIndex) {
120 if (lastIndex > lastLastIndex) {
121 output.push(str.slice(lastLastIndex, match.index));
121 output.push(str.slice(lastLastIndex, match.index));
122 // Fix browsers whose `exec` methods don't consistently return `undefined` for
122 // Fix browsers whose `exec` methods don't consistently return `undefined` for
123 // nonparticipating capturing groups
123 // nonparticipating capturing groups
124 if (!compliantExecNpcg && match.length > 1) {
124 if (!compliantExecNpcg && match.length > 1) {
125 match[0].replace(separator2, function () {
125 match[0].replace(separator2, function () {
126 for (var i = 1; i < arguments.length - 2; i++) {
126 for (var i = 1; i < arguments.length - 2; i++) {
127 if (typeof(arguments[i]) === "undefined") {
127 if (typeof(arguments[i]) === "undefined") {
128 match[i] = undefined;
128 match[i] = undefined;
129 }
129 }
130 }
130 }
131 });
131 });
132 }
132 }
133 if (match.length > 1 && match.index < str.length) {
133 if (match.length > 1 && match.index < str.length) {
134 Array.prototype.push.apply(output, match.slice(1));
134 Array.prototype.push.apply(output, match.slice(1));
135 }
135 }
136 lastLength = match[0].length;
136 lastLength = match[0].length;
137 lastLastIndex = lastIndex;
137 lastLastIndex = lastIndex;
138 if (output.length >= limit) {
138 if (output.length >= limit) {
139 break;
139 break;
140 }
140 }
141 }
141 }
142 if (separator.lastIndex === match.index) {
142 if (separator.lastIndex === match.index) {
143 separator.lastIndex++; // Avoid an infinite loop
143 separator.lastIndex++; // Avoid an infinite loop
144 }
144 }
145 }
145 }
146 if (lastLastIndex === str.length) {
146 if (lastLastIndex === str.length) {
147 if (lastLength || !separator.test("")) {
147 if (lastLength || !separator.test("")) {
148 output.push("");
148 output.push("");
149 }
149 }
150 } else {
150 } else {
151 output.push(str.slice(lastLastIndex));
151 output.push(str.slice(lastLastIndex));
152 }
152 }
153 return output.length > limit ? output.slice(0, limit) : output;
153 return output.length > limit ? output.slice(0, limit) : output;
154 };
154 };
155
155
156 //============================================================================
156 //============================================================================
157 // End contributed Cross-browser RegEx Split
157 // End contributed Cross-browser RegEx Split
158 //============================================================================
158 //============================================================================
159
159
160
160
161 var uuid = function () {
161 var uuid = function () {
162 // http://www.ietf.org/rfc/rfc4122.txt
162 // http://www.ietf.org/rfc/rfc4122.txt
163 var s = [];
163 var s = [];
164 var hexDigits = "0123456789ABCDEF";
164 var hexDigits = "0123456789ABCDEF";
165 for (var i = 0; i < 32; i++) {
165 for (var i = 0; i < 32; i++) {
166 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
166 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
167 }
167 }
168 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
168 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
169 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
169 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
170
170
171 var uuid = s.join("");
171 var uuid = s.join("");
172 return uuid;
172 return uuid;
173 };
173 };
174
174
175
175
176 //Fix raw text to parse correctly in crazy XML
176 //Fix raw text to parse correctly in crazy XML
177 function xmlencode(string) {
177 function xmlencode(string) {
178 return string.replace(/\&/g,'&'+'amp;')
178 return string.replace(/\&/g,'&'+'amp;')
179 .replace(/</g,'&'+'lt;')
179 .replace(/</g,'&'+'lt;')
180 .replace(/>/g,'&'+'gt;')
180 .replace(/>/g,'&'+'gt;')
181 .replace(/\'/g,'&'+'apos;')
181 .replace(/\'/g,'&'+'apos;')
182 .replace(/\"/g,'&'+'quot;')
182 .replace(/\"/g,'&'+'quot;')
183 .replace(/`/g,'&'+'#96;');
183 .replace(/`/g,'&'+'#96;');
184 }
184 }
185
185
186
186
187 //Map from terminal commands to CSS classes
187 //Map from terminal commands to CSS classes
188 var ansi_colormap = {
188 var ansi_colormap = {
189 "01":"ansibold",
189 "01":"ansibold",
190
190
191 "30":"ansiblack",
191 "30":"ansiblack",
192 "31":"ansired",
192 "31":"ansired",
193 "32":"ansigreen",
193 "32":"ansigreen",
194 "33":"ansiyellow",
194 "33":"ansiyellow",
195 "34":"ansiblue",
195 "34":"ansiblue",
196 "35":"ansipurple",
196 "35":"ansipurple",
197 "36":"ansicyan",
197 "36":"ansicyan",
198 "37":"ansigray",
198 "37":"ansigray",
199
199
200 "40":"ansibgblack",
200 "40":"ansibgblack",
201 "41":"ansibgred",
201 "41":"ansibgred",
202 "42":"ansibggreen",
202 "42":"ansibggreen",
203 "43":"ansibgyellow",
203 "43":"ansibgyellow",
204 "44":"ansibgblue",
204 "44":"ansibgblue",
205 "45":"ansibgpurple",
205 "45":"ansibgpurple",
206 "46":"ansibgcyan",
206 "46":"ansibgcyan",
207 "47":"ansibggray"
207 "47":"ansibggray"
208 };
208 };
209
209
210 function _process_numbers(attrs, numbers) {
210 function _process_numbers(attrs, numbers) {
211 // process ansi escapes
211 // process ansi escapes
212 var n = numbers.shift();
212 var n = numbers.shift();
213 if (ansi_colormap[n]) {
213 if (ansi_colormap[n]) {
214 if ( ! attrs["class"] ) {
214 if ( ! attrs["class"] ) {
215 attrs["class"] = ansi_colormap[n];
215 attrs["class"] = ansi_colormap[n];
216 } else {
216 } else {
217 attrs["class"] += " " + ansi_colormap[n];
217 attrs["class"] += " " + ansi_colormap[n];
218 }
218 }
219 } else if (n == "38" || n == "48") {
219 } else if (n == "38" || n == "48") {
220 // VT100 256 color or 24 bit RGB
220 // VT100 256 color or 24 bit RGB
221 if (numbers.length < 2) {
221 if (numbers.length < 2) {
222 console.log("Not enough fields for VT100 color", numbers);
222 console.log("Not enough fields for VT100 color", numbers);
223 return;
223 return;
224 }
224 }
225
225
226 var index_or_rgb = numbers.shift();
226 var index_or_rgb = numbers.shift();
227 var r,g,b;
227 var r,g,b;
228 if (index_or_rgb == "5") {
228 if (index_or_rgb == "5") {
229 // 256 color
229 // 256 color
230 var idx = parseInt(numbers.shift());
230 var idx = parseInt(numbers.shift());
231 if (idx < 16) {
231 if (idx < 16) {
232 // indexed ANSI
232 // indexed ANSI
233 // ignore bright / non-bright distinction
233 // ignore bright / non-bright distinction
234 idx = idx % 8;
234 idx = idx % 8;
235 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
235 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
236 if ( ! attrs["class"] ) {
236 if ( ! attrs["class"] ) {
237 attrs["class"] = ansiclass;
237 attrs["class"] = ansiclass;
238 } else {
238 } else {
239 attrs["class"] += " " + ansiclass;
239 attrs["class"] += " " + ansiclass;
240 }
240 }
241 return;
241 return;
242 } else if (idx < 232) {
242 } else if (idx < 232) {
243 // 216 color 6x6x6 RGB
243 // 216 color 6x6x6 RGB
244 idx = idx - 16;
244 idx = idx - 16;
245 b = idx % 6;
245 b = idx % 6;
246 g = Math.floor(idx / 6) % 6;
246 g = Math.floor(idx / 6) % 6;
247 r = Math.floor(idx / 36) % 6;
247 r = Math.floor(idx / 36) % 6;
248 // convert to rgb
248 // convert to rgb
249 r = (r * 51);
249 r = (r * 51);
250 g = (g * 51);
250 g = (g * 51);
251 b = (b * 51);
251 b = (b * 51);
252 } else {
252 } else {
253 // grayscale
253 // grayscale
254 idx = idx - 231;
254 idx = idx - 231;
255 // it's 1-24 and should *not* include black or white,
255 // it's 1-24 and should *not* include black or white,
256 // so a 26 point scale
256 // so a 26 point scale
257 r = g = b = Math.floor(idx * 256 / 26);
257 r = g = b = Math.floor(idx * 256 / 26);
258 }
258 }
259 } else if (index_or_rgb == "2") {
259 } else if (index_or_rgb == "2") {
260 // Simple 24 bit RGB
260 // Simple 24 bit RGB
261 if (numbers.length > 3) {
261 if (numbers.length > 3) {
262 console.log("Not enough fields for RGB", numbers);
262 console.log("Not enough fields for RGB", numbers);
263 return;
263 return;
264 }
264 }
265 r = numbers.shift();
265 r = numbers.shift();
266 g = numbers.shift();
266 g = numbers.shift();
267 b = numbers.shift();
267 b = numbers.shift();
268 } else {
268 } else {
269 console.log("unrecognized control", numbers);
269 console.log("unrecognized control", numbers);
270 return;
270 return;
271 }
271 }
272 if (r !== undefined) {
272 if (r !== undefined) {
273 // apply the rgb color
273 // apply the rgb color
274 var line;
274 var line;
275 if (n == "38") {
275 if (n == "38") {
276 line = "color: ";
276 line = "color: ";
277 } else {
277 } else {
278 line = "background-color: ";
278 line = "background-color: ";
279 }
279 }
280 line = line + "rgb(" + r + "," + g + "," + b + ");"
280 line = line + "rgb(" + r + "," + g + "," + b + ");"
281 if ( !attrs["style"] ) {
281 if ( !attrs["style"] ) {
282 attrs["style"] = line;
282 attrs["style"] = line;
283 } else {
283 } else {
284 attrs["style"] += " " + line;
284 attrs["style"] += " " + line;
285 }
285 }
286 }
286 }
287 }
287 }
288 }
288 }
289
289
290 function ansispan(str) {
290 function ansispan(str) {
291 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
291 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
292 // regular ansi escapes (using the table above)
292 // regular ansi escapes (using the table above)
293 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
293 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
294 if (!pattern) {
294 if (!pattern) {
295 // [(01|22|39|)m close spans
295 // [(01|22|39|)m close spans
296 return "</span>";
296 return "</span>";
297 }
297 }
298 // consume sequence of color escapes
298 // consume sequence of color escapes
299 var numbers = pattern.match(/\d+/g);
299 var numbers = pattern.match(/\d+/g);
300 var attrs = {};
300 var attrs = {};
301 while (numbers.length > 0) {
301 while (numbers.length > 0) {
302 _process_numbers(attrs, numbers);
302 _process_numbers(attrs, numbers);
303 }
303 }
304
304
305 var span = "<span ";
305 var span = "<span ";
306 for (var attr in attrs) {
306 for (var attr in attrs) {
307 var value = attrs[attr];
307 var value = attrs[attr];
308 span = span + " " + attr + '="' + attrs[attr] + '"';
308 span = span + " " + attr + '="' + attrs[attr] + '"';
309 }
309 }
310 return span + ">";
310 return span + ">";
311 });
311 });
312 };
312 };
313
313
314 // Transform ANSI color escape codes into HTML <span> tags with css
314 // Transform ANSI color escape codes into HTML <span> tags with css
315 // classes listed in the above ansi_colormap object. The actual color used
315 // classes listed in the above ansi_colormap object. The actual color used
316 // are set in the css file.
316 // are set in the css file.
317 function fixConsole(txt) {
317 function fixConsole(txt) {
318 txt = xmlencode(txt);
318 txt = xmlencode(txt);
319 var re = /\033\[([\dA-Fa-f;]*?)m/;
319 var re = /\033\[([\dA-Fa-f;]*?)m/;
320 var opened = false;
320 var opened = false;
321 var cmds = [];
321 var cmds = [];
322 var opener = "";
322 var opener = "";
323 var closer = "";
323 var closer = "";
324
324
325 // Strip all ANSI codes that are not color related. Matches
325 // Strip all ANSI codes that are not color related. Matches
326 // all ANSI codes that do not end with "m".
326 // all ANSI codes that do not end with "m".
327 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
327 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
328 txt = txt.replace(ignored_re, "");
328 txt = txt.replace(ignored_re, "");
329
329
330 // color ansi codes
330 // color ansi codes
331 txt = ansispan(txt);
331 txt = ansispan(txt);
332 return txt;
332 return txt;
333 }
333 }
334
334
335 // Remove chunks that should be overridden by the effect of
335 // Remove chunks that should be overridden by the effect of
336 // carriage return characters
336 // carriage return characters
337 function fixCarriageReturn(txt) {
337 function fixCarriageReturn(txt) {
338 var tmp = txt;
338 var tmp = txt;
339 do {
339 do {
340 txt = tmp;
340 txt = tmp;
341 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
341 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
342 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
342 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
343 } while (tmp.length < txt.length);
343 } while (tmp.length < txt.length);
344 return txt;
344 return txt;
345 }
345 }
346
346
347 // Locate any URLs and convert them to a anchor tag
347 // Locate any URLs and convert them to a anchor tag
348 function autoLinkUrls(txt) {
348 function autoLinkUrls(txt) {
349 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
349 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
350 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
350 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
351 }
351 }
352
352
353 // some keycodes that seem to be platform/browser independent
354 var keycodes = {
355 BACKSPACE: 8,
356 TAB : 9,
357 ENTER : 13,
358 SHIFT : 16,
359 CTRL : 17,
360 CONTROL : 17,
361 ALT : 18,
362 CAPS_LOCK: 20,
363 ESC : 27,
364 SPACE : 32,
365 PGUP : 33,
366 PGDOWN : 34,
367 END : 35,
368 HOME : 36,
369 LEFT_ARROW: 37,
370 LEFTARROW: 37,
371 LEFT : 37,
372 UP_ARROW : 38,
373 UPARROW : 38,
374 UP : 38,
375 RIGHT_ARROW:39,
376 RIGHTARROW:39,
377 RIGHT : 39,
378 DOWN_ARROW: 40,
379 DOWNARROW: 40,
380 DOWN : 40,
381 I : 73,
382 M : 77,
383 // all three of these keys may be COMMAND on OS X:
384 LEFT_SUPER : 91,
385 RIGHT_SUPER : 92,
386 COMMAND : 93,
387 };
388
389 // trigger a key press event
390 var press = function (key) {
391 var key_press = $.Event('keydown', {which: key});
392 $(document).trigger(key_press);
393 }
394
395 var press_up = function() { press(keycodes.UP); };
396 var press_down = function() { press(keycodes.DOWN); };
397
398 var press_ctrl_enter = function() {
399 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
400 };
401
402 var press_shift_enter = function() {
403 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
404 };
405
406 // trigger the ctrl-m shortcut followed by one of our keys
407 var press_ghetto = function(key) {
408 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
409 press(key);
410 };
411
412
413 var points_to_pixels = function (points) {
353 var points_to_pixels = function (points) {
414 // A reasonably good way of converting between points and pixels.
354 // A reasonably good way of converting between points and pixels.
415 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
355 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
416 $(body).append(test);
356 $(body).append(test);
417 var pixel_per_point = test.width()/10000;
357 var pixel_per_point = test.width()/10000;
418 test.remove();
358 test.remove();
419 return Math.floor(points*pixel_per_point);
359 return Math.floor(points*pixel_per_point);
420 };
360 };
421
361
422 var always_new = function (constructor) {
362 var always_new = function (constructor) {
423 // wrapper around contructor to avoid requiring `var a = new constructor()`
363 // wrapper around contructor to avoid requiring `var a = new constructor()`
424 // useful for passing constructors as callbacks,
364 // useful for passing constructors as callbacks,
425 // not for programmer laziness.
365 // not for programmer laziness.
426 // from http://programmers.stackexchange.com/questions/118798
366 // from http://programmers.stackexchange.com/questions/118798
427 return function () {
367 return function () {
428 var obj = Object.create(constructor.prototype);
368 var obj = Object.create(constructor.prototype);
429 constructor.apply(obj, arguments);
369 constructor.apply(obj, arguments);
430 return obj;
370 return obj;
431 };
371 };
432 };
372 };
433
373
434
435 var url_path_join = function () {
374 var url_path_join = function () {
436 // join a sequence of url components with '/'
375 // join a sequence of url components with '/'
437 var url = '';
376 var url = '';
438 for (var i = 0; i < arguments.length; i++) {
377 for (var i = 0; i < arguments.length; i++) {
439 if (arguments[i] === '') {
378 if (arguments[i] === '') {
440 continue;
379 continue;
441 }
380 }
442 if (url.length > 0 && url[url.length-1] != '/') {
381 if (url.length > 0 && url[url.length-1] != '/') {
443 url = url + '/' + arguments[i];
382 url = url + '/' + arguments[i];
444 } else {
383 } else {
445 url = url + arguments[i];
384 url = url + arguments[i];
446 }
385 }
447 }
386 }
448 url = url.replace(/\/\/+/, '/');
387 url = url.replace(/\/\/+/, '/');
449 return url;
388 return url;
450 };
389 };
451
390
452 var parse_url = function (url) {
391 var parse_url = function (url) {
453 // an `a` element with an href allows attr-access to the parsed segments of a URL
392 // an `a` element with an href allows attr-access to the parsed segments of a URL
454 // a = parse_url("http://localhost:8888/path/name#hash")
393 // a = parse_url("http://localhost:8888/path/name#hash")
455 // a.protocol = "http:"
394 // a.protocol = "http:"
456 // a.host = "localhost:8888"
395 // a.host = "localhost:8888"
457 // a.hostname = "localhost"
396 // a.hostname = "localhost"
458 // a.port = 8888
397 // a.port = 8888
459 // a.pathname = "/path/name"
398 // a.pathname = "/path/name"
460 // a.hash = "#hash"
399 // a.hash = "#hash"
461 var a = document.createElement("a");
400 var a = document.createElement("a");
462 a.href = url;
401 a.href = url;
463 return a;
402 return a;
464 };
403 };
465
404
466 var encode_uri_components = function (uri) {
405 var encode_uri_components = function (uri) {
467 // encode just the components of a multi-segment uri,
406 // encode just the components of a multi-segment uri,
468 // leaving '/' separators
407 // leaving '/' separators
469 return uri.split('/').map(encodeURIComponent).join('/');
408 return uri.split('/').map(encodeURIComponent).join('/');
470 };
409 };
471
410
472 var url_join_encode = function () {
411 var url_join_encode = function () {
473 // join a sequence of url components with '/',
412 // join a sequence of url components with '/',
474 // encoding each component with encodeURIComponent
413 // encoding each component with encodeURIComponent
475 return encode_uri_components(url_path_join.apply(null, arguments));
414 return encode_uri_components(url_path_join.apply(null, arguments));
476 };
415 };
477
416
478
417
479 var splitext = function (filename) {
418 var splitext = function (filename) {
480 // mimic Python os.path.splitext
419 // mimic Python os.path.splitext
481 // Returns ['base', '.ext']
420 // Returns ['base', '.ext']
482 var idx = filename.lastIndexOf('.');
421 var idx = filename.lastIndexOf('.');
483 if (idx > 0) {
422 if (idx > 0) {
484 return [filename.slice(0, idx), filename.slice(idx)];
423 return [filename.slice(0, idx), filename.slice(idx)];
485 } else {
424 } else {
486 return [filename, ''];
425 return [filename, ''];
487 }
426 }
488 };
427 };
489
428
490
429
491 var escape_html = function (text) {
430 var escape_html = function (text) {
492 // escape text to HTML
431 // escape text to HTML
493 return $("<div/>").text(text).html();
432 return $("<div/>").text(text).html();
494 }
433 }
495
434
496
435
497 var get_body_data = function(key) {
436 var get_body_data = function(key) {
498 // get a url-encoded item from body.data and decode it
437 // get a url-encoded item from body.data and decode it
499 // we should never have any encoded URLs anywhere else in code
438 // we should never have any encoded URLs anywhere else in code
500 // until we are building an actual request
439 // until we are building an actual request
501 return decodeURIComponent($('body').data(key));
440 return decodeURIComponent($('body').data(key));
502 };
441 };
503
442
504
443
505 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
444 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
506 var browser = (function() {
445 var browser = (function() {
507 if (typeof navigator === 'undefined') {
446 if (typeof navigator === 'undefined') {
508 // navigator undefined in node
447 // navigator undefined in node
509 return 'None';
448 return 'None';
510 }
449 }
511 var N= navigator.appName, ua= navigator.userAgent, tem;
450 var N= navigator.appName, ua= navigator.userAgent, tem;
512 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
451 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
513 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
452 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
514 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
453 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
515 return M;
454 return M;
516 })();
455 })();
517
456
518 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
457 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
519 var platform = (function () {
458 var platform = (function () {
520 if (typeof navigator === 'undefined') {
459 if (typeof navigator === 'undefined') {
521 // navigator undefined in node
460 // navigator undefined in node
522 return 'None';
461 return 'None';
523 }
462 }
524 var OSName="None";
463 var OSName="None";
525 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
464 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
526 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
465 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
527 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
466 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
528 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
467 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
529 return OSName
468 return OSName
530 })();
469 })();
531
470
532 var is_or_has = function (a, b) {
471 var is_or_has = function (a, b) {
533 // Is b a child of a or a itself?
472 // Is b a child of a or a itself?
534 return a.has(b).length !==0 || a.is(b);
473 return a.has(b).length !==0 || a.is(b);
535 }
474 }
536
475
537 var is_focused = function (e) {
476 var is_focused = function (e) {
538 // Is element e, or one of its children focused?
477 // Is element e, or one of its children focused?
539 e = $(e);
478 e = $(e);
540 var target = $(document.activeElement);
479 var target = $(document.activeElement);
541 if (target.length > 0) {
480 if (target.length > 0) {
542 if (is_or_has(e, target)) {
481 if (is_or_has(e, target)) {
543 return true;
482 return true;
544 } else {
483 } else {
545 return false;
484 return false;
546 }
485 }
547 } else {
486 } else {
548 return false;
487 return false;
549 }
488 }
550 }
489 }
551
490
552
491
553 return {
492 return {
554 regex_split : regex_split,
493 regex_split : regex_split,
555 uuid : uuid,
494 uuid : uuid,
556 fixConsole : fixConsole,
495 fixConsole : fixConsole,
557 keycodes : keycodes,
558 press : press,
559 press_up : press_up,
560 press_down : press_down,
561 press_ctrl_enter : press_ctrl_enter,
562 press_shift_enter : press_shift_enter,
563 press_ghetto : press_ghetto,
564 fixCarriageReturn : fixCarriageReturn,
496 fixCarriageReturn : fixCarriageReturn,
565 autoLinkUrls : autoLinkUrls,
497 autoLinkUrls : autoLinkUrls,
566 points_to_pixels : points_to_pixels,
498 points_to_pixels : points_to_pixels,
567 get_body_data : get_body_data,
499 get_body_data : get_body_data,
568 parse_url : parse_url,
500 parse_url : parse_url,
569 url_path_join : url_path_join,
501 url_path_join : url_path_join,
570 url_join_encode : url_join_encode,
502 url_join_encode : url_join_encode,
571 encode_uri_components : encode_uri_components,
503 encode_uri_components : encode_uri_components,
572 splitext : splitext,
504 splitext : splitext,
573 escape_html : escape_html,
505 escape_html : escape_html,
574 always_new : always_new,
506 always_new : always_new,
575 browser : browser,
507 browser : browser,
576 platform: platform,
508 platform: platform,
577 is_or_has : is_or_has,
509 is_or_has : is_or_has,
578 is_focused : is_focused
510 is_focused : is_focused
579 };
511 };
580
512
581 }(IPython));
513 }(IPython));
582
514
@@ -1,90 +1,89 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CellToolbar Example
9 // CellToolbar Example
10 //============================================================================
10 //============================================================================
11
11
12 (function(IPython) {
12 (function(IPython) {
13 "use strict";
13 "use strict";
14
14
15 var CellToolbar = IPython.CellToolbar;
15 var CellToolbar = IPython.CellToolbar;
16 var raw_cell_preset = [];
16 var raw_cell_preset = [];
17 var utils = IPython.utils;
18
17
19 var select_type = CellToolbar.utils.select_ui_generator([
18 var select_type = CellToolbar.utils.select_ui_generator([
20 ["None", "-"],
19 ["None", "-"],
21 ["LaTeX", "text/latex"],
20 ["LaTeX", "text/latex"],
22 ["reST", "text/restructuredtext"],
21 ["reST", "text/restructuredtext"],
23 ["HTML", "text/html"],
22 ["HTML", "text/html"],
24 ["Markdown", "text/markdown"],
23 ["Markdown", "text/markdown"],
25 ["Python", "text/x-python"],
24 ["Python", "text/x-python"],
26 ["Custom", "dialog"],
25 ["Custom", "dialog"],
27
26
28 ],
27 ],
29 // setter
28 // setter
30 function(cell, value) {
29 function(cell, value) {
31 if (value === "-") {
30 if (value === "-") {
32 delete cell.metadata.raw_mimetype;
31 delete cell.metadata.raw_mimetype;
33 } else if (value === 'dialog'){
32 } else if (value === 'dialog'){
34 var dialog = $('<div/>').append(
33 var dialog = $('<div/>').append(
35 $("<p/>")
34 $("<p/>")
36 .text("Set the MIME type of the raw cell:")
35 .text("Set the MIME type of the raw cell:")
37 ).append(
36 ).append(
38 $("<br/>")
37 $("<br/>")
39 ).append(
38 ).append(
40 $('<input/>').attr('type','text').attr('size','25')
39 $('<input/>').attr('type','text').attr('size','25')
41 .val(cell.metadata.raw_mimetype || "-")
40 .val(cell.metadata.raw_mimetype || "-")
42 );
41 );
43 IPython.dialog.modal({
42 IPython.dialog.modal({
44 title: "Raw Cell MIME Type",
43 title: "Raw Cell MIME Type",
45 body: dialog,
44 body: dialog,
46 buttons : {
45 buttons : {
47 "Cancel": {},
46 "Cancel": {},
48 "OK": {
47 "OK": {
49 class: "btn-primary",
48 class: "btn-primary",
50 click: function () {
49 click: function () {
51 console.log(cell);
50 console.log(cell);
52 cell.metadata.raw_mimetype = $(this).find('input').val();
51 cell.metadata.raw_mimetype = $(this).find('input').val();
53 console.log(cell.metadata);
52 console.log(cell.metadata);
54 }
53 }
55 }
54 }
56 },
55 },
57 open : function (event, ui) {
56 open : function (event, ui) {
58 var that = $(this);
57 var that = $(this);
59 // Upon ENTER, click the OK button.
58 // Upon ENTER, click the OK button.
60 that.find('input[type="text"]').keydown(function (event, ui) {
59 that.find('input[type="text"]').keydown(function (event, ui) {
61 if (event.which === utils.keycodes.ENTER) {
60 if (event.which === IPython.keyboard.keycodes.enter) {
62 that.find('.btn-primary').first().click();
61 that.find('.btn-primary').first().click();
63 return false;
62 return false;
64 }
63 }
65 });
64 });
66 that.find('input[type="text"]').focus().select();
65 that.find('input[type="text"]').focus().select();
67 }
66 }
68 });
67 });
69 } else {
68 } else {
70 cell.metadata.raw_mimetype = value;
69 cell.metadata.raw_mimetype = value;
71 }
70 }
72 },
71 },
73 //getter
72 //getter
74 function(cell) {
73 function(cell) {
75 return cell.metadata.raw_mimetype || "";
74 return cell.metadata.raw_mimetype || "";
76 },
75 },
77 // name
76 // name
78 "Raw NBConvert Format",
77 "Raw NBConvert Format",
79 // cell_types
78 // cell_types
80 ["raw"]
79 ["raw"]
81 );
80 );
82
81
83 CellToolbar.register_callback('raw_cell.select', select_type);
82 CellToolbar.register_callback('raw_cell.select', select_type);
84
83
85 raw_cell_preset.push('raw_cell.select');
84 raw_cell_preset.push('raw_cell.select');
86
85
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
86 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
88 console.log('Raw Cell Format toolbar preset loaded.');
87 console.log('Raw Cell Format toolbar preset loaded.');
89
88
90 }(IPython));
89 }(IPython));
@@ -1,579 +1,579 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule CodeCell
15 * @submodule CodeCell
16 */
16 */
17
17
18
18
19 /* local util for codemirror */
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
21
22 /**
22 /**
23 *
23 *
24 * function to delete until previous non blanking space character
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
25 * or first multiple of 4 tabstop.
26 * @private
26 * @private
27 */
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
37 cm.replaceRange("",from,cur);
38 } else {
38 } else {
39 cm.deleteH(-1,"char");
39 cm.deleteH(-1,"char");
40 }
40 }
41 };
41 };
42
42
43
43
44 var IPython = (function (IPython) {
44 var IPython = (function (IPython) {
45 "use strict";
45 "use strict";
46
46
47 var utils = IPython.utils;
47 var utils = IPython.utils;
48 var key = IPython.utils.keycodes;
48 var keycodes = IPython.keyboard.keycodes;
49
49
50 /**
50 /**
51 * A Cell conceived to write code.
51 * A Cell conceived to write code.
52 *
52 *
53 * The kernel doesn't have to be set at creation time, in that case
53 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
55 * @class CodeCell
56 * @extends IPython.Cell
56 * @extends IPython.Cell
57 *
57 *
58 * @constructor
58 * @constructor
59 * @param {Object|null} kernel
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
62 */
63 var CodeCell = function (kernel, options) {
63 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
64 this.kernel = kernel || null;
65 this.collapsed = false;
65 this.collapsed = false;
66
66
67 // create all attributed in constructor function
67 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
68 // even if null for V8 VM optimisation
69 this.input_prompt_number = null;
69 this.input_prompt_number = null;
70 this.celltoolbar = null;
70 this.celltoolbar = null;
71 this.output_area = null;
71 this.output_area = null;
72 this.last_msg_id = null;
72 this.last_msg_id = null;
73 this.completer = null;
73 this.completer = null;
74
74
75
75
76 var cm_overwrite_options = {
76 var cm_overwrite_options = {
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
78 };
79
79
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81
81
82 IPython.Cell.apply(this,[options]);
82 IPython.Cell.apply(this,[options]);
83
83
84 // Attributes we want to override in this subclass.
84 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
85 this.cell_type = "code";
86
86
87 var that = this;
87 var that = this;
88 this.element.focusout(
88 this.element.focusout(
89 function() { that.auto_highlight(); }
89 function() { that.auto_highlight(); }
90 );
90 );
91 };
91 };
92
92
93 CodeCell.options_default = {
93 CodeCell.options_default = {
94 cm_config : {
94 cm_config : {
95 extraKeys: {
95 extraKeys: {
96 "Tab" : "indentMore",
96 "Tab" : "indentMore",
97 "Shift-Tab" : "indentLess",
97 "Shift-Tab" : "indentLess",
98 "Backspace" : "delSpaceToPrevTabStop",
98 "Backspace" : "delSpaceToPrevTabStop",
99 "Cmd-/" : "toggleComment",
99 "Cmd-/" : "toggleComment",
100 "Ctrl-/" : "toggleComment"
100 "Ctrl-/" : "toggleComment"
101 },
101 },
102 mode: 'ipython',
102 mode: 'ipython',
103 theme: 'ipython',
103 theme: 'ipython',
104 matchBrackets: true,
104 matchBrackets: true,
105 autoCloseBrackets: true
105 autoCloseBrackets: true
106 }
106 }
107 };
107 };
108
108
109 CodeCell.msg_cells = {};
109 CodeCell.msg_cells = {};
110
110
111 CodeCell.prototype = new IPython.Cell();
111 CodeCell.prototype = new IPython.Cell();
112
112
113 /**
113 /**
114 * @method auto_highlight
114 * @method auto_highlight
115 */
115 */
116 CodeCell.prototype.auto_highlight = function () {
116 CodeCell.prototype.auto_highlight = function () {
117 this._auto_highlight(IPython.config.cell_magic_highlight);
117 this._auto_highlight(IPython.config.cell_magic_highlight);
118 };
118 };
119
119
120 /** @method create_element */
120 /** @method create_element */
121 CodeCell.prototype.create_element = function () {
121 CodeCell.prototype.create_element = function () {
122 IPython.Cell.prototype.create_element.apply(this, arguments);
122 IPython.Cell.prototype.create_element.apply(this, arguments);
123
123
124 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
124 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
125 cell.attr('tabindex','2');
125 cell.attr('tabindex','2');
126
126
127 var input = $('<div></div>').addClass('input');
127 var input = $('<div></div>').addClass('input');
128 var prompt = $('<div/>').addClass('prompt input_prompt');
128 var prompt = $('<div/>').addClass('prompt input_prompt');
129 var inner_cell = $('<div/>').addClass('inner_cell');
129 var inner_cell = $('<div/>').addClass('inner_cell');
130 this.celltoolbar = new IPython.CellToolbar(this);
130 this.celltoolbar = new IPython.CellToolbar(this);
131 inner_cell.append(this.celltoolbar.element);
131 inner_cell.append(this.celltoolbar.element);
132 var input_area = $('<div/>').addClass('input_area');
132 var input_area = $('<div/>').addClass('input_area');
133 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
133 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
134 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
134 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
135 inner_cell.append(input_area);
135 inner_cell.append(input_area);
136 input.append(prompt).append(inner_cell);
136 input.append(prompt).append(inner_cell);
137
137
138 var widget_area = $('<div/>')
138 var widget_area = $('<div/>')
139 .addClass('widget-area')
139 .addClass('widget-area')
140 .hide();
140 .hide();
141 this.widget_area = widget_area;
141 this.widget_area = widget_area;
142 var widget_prompt = $('<div/>')
142 var widget_prompt = $('<div/>')
143 .addClass('prompt')
143 .addClass('prompt')
144 .appendTo(widget_area);
144 .appendTo(widget_area);
145 var widget_subarea = $('<div/>')
145 var widget_subarea = $('<div/>')
146 .addClass('widget-subarea')
146 .addClass('widget-subarea')
147 .appendTo(widget_area);
147 .appendTo(widget_area);
148 this.widget_subarea = widget_subarea;
148 this.widget_subarea = widget_subarea;
149 var widget_clear_buton = $('<button />')
149 var widget_clear_buton = $('<button />')
150 .addClass('close')
150 .addClass('close')
151 .html('&times;')
151 .html('&times;')
152 .click(function() {
152 .click(function() {
153 widget_area.slideUp('', function(){ widget_subarea.html(''); });
153 widget_area.slideUp('', function(){ widget_subarea.html(''); });
154 })
154 })
155 .appendTo(widget_prompt);
155 .appendTo(widget_prompt);
156
156
157 var output = $('<div></div>');
157 var output = $('<div></div>');
158 cell.append(input).append(widget_area).append(output);
158 cell.append(input).append(widget_area).append(output);
159 this.element = cell;
159 this.element = cell;
160 this.output_area = new IPython.OutputArea(output, true);
160 this.output_area = new IPython.OutputArea(output, true);
161 this.completer = new IPython.Completer(this);
161 this.completer = new IPython.Completer(this);
162 };
162 };
163
163
164 /** @method bind_events */
164 /** @method bind_events */
165 CodeCell.prototype.bind_events = function () {
165 CodeCell.prototype.bind_events = function () {
166 IPython.Cell.prototype.bind_events.apply(this);
166 IPython.Cell.prototype.bind_events.apply(this);
167 var that = this;
167 var that = this;
168
168
169 this.element.focusout(
169 this.element.focusout(
170 function() { that.auto_highlight(); }
170 function() { that.auto_highlight(); }
171 );
171 );
172 };
172 };
173
173
174 CodeCell.prototype.handle_keyevent = function (editor, event) {
174 CodeCell.prototype.handle_keyevent = function (editor, event) {
175
175
176 // console.log('CM', this.mode, event.which, event.type)
176 // console.log('CM', this.mode, event.which, event.type)
177
177
178 if (this.mode === 'command') {
178 if (this.mode === 'command') {
179 return true;
179 return true;
180 } else if (this.mode === 'edit') {
180 } else if (this.mode === 'edit') {
181 return this.handle_codemirror_keyevent(editor, event);
181 return this.handle_codemirror_keyevent(editor, event);
182 }
182 }
183 };
183 };
184
184
185 /**
185 /**
186 * This method gets called in CodeMirror's onKeyDown/onKeyPress
186 * This method gets called in CodeMirror's onKeyDown/onKeyPress
187 * handlers and is used to provide custom key handling. Its return
187 * handlers and is used to provide custom key handling. Its return
188 * value is used to determine if CodeMirror should ignore the event:
188 * value is used to determine if CodeMirror should ignore the event:
189 * true = ignore, false = don't ignore.
189 * true = ignore, false = don't ignore.
190 * @method handle_codemirror_keyevent
190 * @method handle_codemirror_keyevent
191 */
191 */
192 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
192 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
193
193
194 var that = this;
194 var that = this;
195 // whatever key is pressed, first, cancel the tooltip request before
195 // whatever key is pressed, first, cancel the tooltip request before
196 // they are sent, and remove tooltip if any, except for tab again
196 // they are sent, and remove tooltip if any, except for tab again
197 var tooltip_closed = null;
197 var tooltip_closed = null;
198 if (event.type === 'keydown' && event.which != key.TAB ) {
198 if (event.type === 'keydown' && event.which != keycodes.tab ) {
199 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
199 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
200 }
200 }
201
201
202 var cur = editor.getCursor();
202 var cur = editor.getCursor();
203 if (event.keyCode === key.ENTER){
203 if (event.keyCode === keycodes.enter){
204 this.auto_highlight();
204 this.auto_highlight();
205 }
205 }
206
206
207 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
207 if (event.keyCode === keycodes.enter && (event.shiftKey || event.ctrlKey || event.altKey)) {
208 // Always ignore shift-enter in CodeMirror as we handle it.
208 // Always ignore shift-enter in CodeMirror as we handle it.
209 return true;
209 return true;
210 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
210 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
211 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
211 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
212 // browser and keyboard layout !
212 // browser and keyboard layout !
213 // Pressing '(' , request tooltip, don't forget to reappend it
213 // Pressing '(' , request tooltip, don't forget to reappend it
214 // The second argument says to hide the tooltip if the docstring
214 // The second argument says to hide the tooltip if the docstring
215 // is actually empty
215 // is actually empty
216 IPython.tooltip.pending(that, true);
216 IPython.tooltip.pending(that, true);
217 } else if (event.which === key.UPARROW && event.type === 'keydown') {
217 } else if (event.which === keycodes.up && event.type === 'keydown') {
218 // If we are not at the top, let CM handle the up arrow and
218 // If we are not at the top, let CM handle the up arrow and
219 // prevent the global keydown handler from handling it.
219 // prevent the global keydown handler from handling it.
220 if (!that.at_top()) {
220 if (!that.at_top()) {
221 event.stop();
221 event.stop();
222 return false;
222 return false;
223 } else {
223 } else {
224 return true;
224 return true;
225 }
225 }
226 } else if (event.which === key.ESC && event.type === 'keydown') {
226 } else if (event.which === keycodes.esc && event.type === 'keydown') {
227 // First see if the tooltip is active and if so cancel it.
227 // First see if the tooltip is active and if so cancel it.
228 if (tooltip_closed) {
228 if (tooltip_closed) {
229 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
229 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
230 // force=true. Because of this it won't actually close the tooltip
230 // force=true. Because of this it won't actually close the tooltip
231 // if it is in sticky mode. Thus, we have to check again if it is open
231 // if it is in sticky mode. Thus, we have to check again if it is open
232 // and close it with force=true.
232 // and close it with force=true.
233 if (!IPython.tooltip._hidden) {
233 if (!IPython.tooltip._hidden) {
234 IPython.tooltip.remove_and_cancel_tooltip(true);
234 IPython.tooltip.remove_and_cancel_tooltip(true);
235 }
235 }
236 // If we closed the tooltip, don't let CM or the global handlers
236 // If we closed the tooltip, don't let CM or the global handlers
237 // handle this event.
237 // handle this event.
238 event.stop();
238 event.stop();
239 return true;
239 return true;
240 }
240 }
241 if (that.code_mirror.options.keyMap === "vim-insert") {
241 if (that.code_mirror.options.keyMap === "vim-insert") {
242 // vim keyMap is active and in insert mode. In this case we leave vim
242 // vim keyMap is active and in insert mode. In this case we leave vim
243 // insert mode, but remain in notebook edit mode.
243 // insert mode, but remain in notebook edit mode.
244 // Let' CM handle this event and prevent global handling.
244 // Let' CM handle this event and prevent global handling.
245 event.stop();
245 event.stop();
246 return false;
246 return false;
247 } else {
247 } else {
248 // vim keyMap is not active. Leave notebook edit mode.
248 // vim keyMap is not active. Leave notebook edit mode.
249 // Don't let CM handle the event, defer to global handling.
249 // Don't let CM handle the event, defer to global handling.
250 return true;
250 return true;
251 }
251 }
252 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
252 } else if (event.which === keycodes.down && event.type === 'keydown') {
253 // If we are not at the bottom, let CM handle the down arrow and
253 // If we are not at the bottom, let CM handle the down arrow and
254 // prevent the global keydown handler from handling it.
254 // prevent the global keydown handler from handling it.
255 if (!that.at_bottom()) {
255 if (!that.at_bottom()) {
256 event.stop();
256 event.stop();
257 return false;
257 return false;
258 } else {
258 } else {
259 return true;
259 return true;
260 }
260 }
261 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
261 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
262 if (editor.somethingSelected()){
262 if (editor.somethingSelected()){
263 var anchor = editor.getCursor("anchor");
263 var anchor = editor.getCursor("anchor");
264 var head = editor.getCursor("head");
264 var head = editor.getCursor("head");
265 if( anchor.line != head.line){
265 if( anchor.line != head.line){
266 return false;
266 return false;
267 }
267 }
268 }
268 }
269 IPython.tooltip.request(that);
269 IPython.tooltip.request(that);
270 event.stop();
270 event.stop();
271 return true;
271 return true;
272 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
272 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
273 // Tab completion.
273 // Tab completion.
274 IPython.tooltip.remove_and_cancel_tooltip();
274 IPython.tooltip.remove_and_cancel_tooltip();
275 if (editor.somethingSelected()) {
275 if (editor.somethingSelected()) {
276 return false;
276 return false;
277 }
277 }
278 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
278 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
279 if (pre_cursor.trim() === "") {
279 if (pre_cursor.trim() === "") {
280 // Don't autocomplete if the part of the line before the cursor
280 // Don't autocomplete if the part of the line before the cursor
281 // is empty. In this case, let CodeMirror handle indentation.
281 // is empty. In this case, let CodeMirror handle indentation.
282 return false;
282 return false;
283 } else {
283 } else {
284 event.stop();
284 event.stop();
285 this.completer.startCompletion();
285 this.completer.startCompletion();
286 return true;
286 return true;
287 }
287 }
288 } else {
288 } else {
289 // keypress/keyup also trigger on TAB press, and we don't want to
289 // keypress/keyup also trigger on TAB press, and we don't want to
290 // use those to disable tab completion.
290 // use those to disable tab completion.
291 return false;
291 return false;
292 }
292 }
293 return false;
293 return false;
294 };
294 };
295
295
296 // Kernel related calls.
296 // Kernel related calls.
297
297
298 CodeCell.prototype.set_kernel = function (kernel) {
298 CodeCell.prototype.set_kernel = function (kernel) {
299 this.kernel = kernel;
299 this.kernel = kernel;
300 };
300 };
301
301
302 /**
302 /**
303 * Execute current code cell to the kernel
303 * Execute current code cell to the kernel
304 * @method execute
304 * @method execute
305 */
305 */
306 CodeCell.prototype.execute = function () {
306 CodeCell.prototype.execute = function () {
307 this.output_area.clear_output();
307 this.output_area.clear_output();
308
308
309 // Clear widget area
309 // Clear widget area
310 this.widget_subarea.html('');
310 this.widget_subarea.html('');
311 this.widget_subarea.height('');
311 this.widget_subarea.height('');
312 this.widget_area.height('');
312 this.widget_area.height('');
313 this.widget_area.hide();
313 this.widget_area.hide();
314
314
315 this.set_input_prompt('*');
315 this.set_input_prompt('*');
316 this.element.addClass("running");
316 this.element.addClass("running");
317 if (this.last_msg_id) {
317 if (this.last_msg_id) {
318 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
318 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
319 }
319 }
320 var callbacks = this.get_callbacks();
320 var callbacks = this.get_callbacks();
321
321
322 var old_msg_id = this.last_msg_id;
322 var old_msg_id = this.last_msg_id;
323 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
323 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
324 if (old_msg_id) {
324 if (old_msg_id) {
325 delete CodeCell.msg_cells[old_msg_id];
325 delete CodeCell.msg_cells[old_msg_id];
326 }
326 }
327 CodeCell.msg_cells[this.last_msg_id] = this;
327 CodeCell.msg_cells[this.last_msg_id] = this;
328 };
328 };
329
329
330 /**
330 /**
331 * Construct the default callbacks for
331 * Construct the default callbacks for
332 * @method get_callbacks
332 * @method get_callbacks
333 */
333 */
334 CodeCell.prototype.get_callbacks = function () {
334 CodeCell.prototype.get_callbacks = function () {
335 return {
335 return {
336 shell : {
336 shell : {
337 reply : $.proxy(this._handle_execute_reply, this),
337 reply : $.proxy(this._handle_execute_reply, this),
338 payload : {
338 payload : {
339 set_next_input : $.proxy(this._handle_set_next_input, this),
339 set_next_input : $.proxy(this._handle_set_next_input, this),
340 page : $.proxy(this._open_with_pager, this)
340 page : $.proxy(this._open_with_pager, this)
341 }
341 }
342 },
342 },
343 iopub : {
343 iopub : {
344 output : $.proxy(this.output_area.handle_output, this.output_area),
344 output : $.proxy(this.output_area.handle_output, this.output_area),
345 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
345 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
346 },
346 },
347 input : $.proxy(this._handle_input_request, this)
347 input : $.proxy(this._handle_input_request, this)
348 };
348 };
349 };
349 };
350
350
351 CodeCell.prototype._open_with_pager = function (payload) {
351 CodeCell.prototype._open_with_pager = function (payload) {
352 $([IPython.events]).trigger('open_with_text.Pager', payload);
352 $([IPython.events]).trigger('open_with_text.Pager', payload);
353 };
353 };
354
354
355 /**
355 /**
356 * @method _handle_execute_reply
356 * @method _handle_execute_reply
357 * @private
357 * @private
358 */
358 */
359 CodeCell.prototype._handle_execute_reply = function (msg) {
359 CodeCell.prototype._handle_execute_reply = function (msg) {
360 this.set_input_prompt(msg.content.execution_count);
360 this.set_input_prompt(msg.content.execution_count);
361 this.element.removeClass("running");
361 this.element.removeClass("running");
362 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
362 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
363 };
363 };
364
364
365 /**
365 /**
366 * @method _handle_set_next_input
366 * @method _handle_set_next_input
367 * @private
367 * @private
368 */
368 */
369 CodeCell.prototype._handle_set_next_input = function (payload) {
369 CodeCell.prototype._handle_set_next_input = function (payload) {
370 var data = {'cell': this, 'text': payload.text};
370 var data = {'cell': this, 'text': payload.text};
371 $([IPython.events]).trigger('set_next_input.Notebook', data);
371 $([IPython.events]).trigger('set_next_input.Notebook', data);
372 };
372 };
373
373
374 /**
374 /**
375 * @method _handle_input_request
375 * @method _handle_input_request
376 * @private
376 * @private
377 */
377 */
378 CodeCell.prototype._handle_input_request = function (msg) {
378 CodeCell.prototype._handle_input_request = function (msg) {
379 this.output_area.append_raw_input(msg);
379 this.output_area.append_raw_input(msg);
380 };
380 };
381
381
382
382
383 // Basic cell manipulation.
383 // Basic cell manipulation.
384
384
385 CodeCell.prototype.select = function () {
385 CodeCell.prototype.select = function () {
386 var cont = IPython.Cell.prototype.select.apply(this);
386 var cont = IPython.Cell.prototype.select.apply(this);
387 if (cont) {
387 if (cont) {
388 this.code_mirror.refresh();
388 this.code_mirror.refresh();
389 this.auto_highlight();
389 this.auto_highlight();
390 }
390 }
391 return cont;
391 return cont;
392 };
392 };
393
393
394 CodeCell.prototype.render = function () {
394 CodeCell.prototype.render = function () {
395 var cont = IPython.Cell.prototype.render.apply(this);
395 var cont = IPython.Cell.prototype.render.apply(this);
396 // Always execute, even if we are already in the rendered state
396 // Always execute, even if we are already in the rendered state
397 return cont;
397 return cont;
398 };
398 };
399
399
400 CodeCell.prototype.unrender = function () {
400 CodeCell.prototype.unrender = function () {
401 // CodeCell is always rendered
401 // CodeCell is always rendered
402 return false;
402 return false;
403 };
403 };
404
404
405 /**
405 /**
406 * Determine whether or not the unfocus event should be aknowledged.
406 * Determine whether or not the unfocus event should be aknowledged.
407 *
407 *
408 * @method should_cancel_blur
408 * @method should_cancel_blur
409 *
409 *
410 * @return results {bool} Whether or not to ignore the cell's blur event.
410 * @return results {bool} Whether or not to ignore the cell's blur event.
411 **/
411 **/
412 CodeCell.prototype.should_cancel_blur = function () {
412 CodeCell.prototype.should_cancel_blur = function () {
413 // Cancel this unfocus event if the base wants to cancel or the cell
413 // Cancel this unfocus event if the base wants to cancel or the cell
414 // completer is open or the tooltip is open.
414 // completer is open or the tooltip is open.
415 return IPython.Cell.prototype.should_cancel_blur.apply(this) ||
415 return IPython.Cell.prototype.should_cancel_blur.apply(this) ||
416 (this.completer && this.completer.is_visible()) ||
416 (this.completer && this.completer.is_visible()) ||
417 (IPython.tooltip && IPython.tooltip.is_visible());
417 (IPython.tooltip && IPython.tooltip.is_visible());
418 };
418 };
419
419
420 CodeCell.prototype.select_all = function () {
420 CodeCell.prototype.select_all = function () {
421 var start = {line: 0, ch: 0};
421 var start = {line: 0, ch: 0};
422 var nlines = this.code_mirror.lineCount();
422 var nlines = this.code_mirror.lineCount();
423 var last_line = this.code_mirror.getLine(nlines-1);
423 var last_line = this.code_mirror.getLine(nlines-1);
424 var end = {line: nlines-1, ch: last_line.length};
424 var end = {line: nlines-1, ch: last_line.length};
425 this.code_mirror.setSelection(start, end);
425 this.code_mirror.setSelection(start, end);
426 };
426 };
427
427
428
428
429 CodeCell.prototype.collapse_output = function () {
429 CodeCell.prototype.collapse_output = function () {
430 this.collapsed = true;
430 this.collapsed = true;
431 this.output_area.collapse();
431 this.output_area.collapse();
432 };
432 };
433
433
434
434
435 CodeCell.prototype.expand_output = function () {
435 CodeCell.prototype.expand_output = function () {
436 this.collapsed = false;
436 this.collapsed = false;
437 this.output_area.expand();
437 this.output_area.expand();
438 this.output_area.unscroll_area();
438 this.output_area.unscroll_area();
439 };
439 };
440
440
441 CodeCell.prototype.scroll_output = function () {
441 CodeCell.prototype.scroll_output = function () {
442 this.output_area.expand();
442 this.output_area.expand();
443 this.output_area.scroll_if_long();
443 this.output_area.scroll_if_long();
444 };
444 };
445
445
446 CodeCell.prototype.toggle_output = function () {
446 CodeCell.prototype.toggle_output = function () {
447 this.collapsed = Boolean(1 - this.collapsed);
447 this.collapsed = Boolean(1 - this.collapsed);
448 this.output_area.toggle_output();
448 this.output_area.toggle_output();
449 };
449 };
450
450
451 CodeCell.prototype.toggle_output_scroll = function () {
451 CodeCell.prototype.toggle_output_scroll = function () {
452 this.output_area.toggle_scroll();
452 this.output_area.toggle_scroll();
453 };
453 };
454
454
455
455
456 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
456 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
457 var ns;
457 var ns;
458 if (prompt_value == undefined) {
458 if (prompt_value == undefined) {
459 ns = "&nbsp;";
459 ns = "&nbsp;";
460 } else {
460 } else {
461 ns = encodeURIComponent(prompt_value);
461 ns = encodeURIComponent(prompt_value);
462 }
462 }
463 return 'In&nbsp;[' + ns + ']:';
463 return 'In&nbsp;[' + ns + ']:';
464 };
464 };
465
465
466 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
466 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
467 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
467 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
468 for(var i=1; i < lines_number; i++) {
468 for(var i=1; i < lines_number; i++) {
469 html.push(['...:']);
469 html.push(['...:']);
470 }
470 }
471 return html.join('<br/>');
471 return html.join('<br/>');
472 };
472 };
473
473
474 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
474 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
475
475
476
476
477 CodeCell.prototype.set_input_prompt = function (number) {
477 CodeCell.prototype.set_input_prompt = function (number) {
478 var nline = 1;
478 var nline = 1;
479 if (this.code_mirror !== undefined) {
479 if (this.code_mirror !== undefined) {
480 nline = this.code_mirror.lineCount();
480 nline = this.code_mirror.lineCount();
481 }
481 }
482 this.input_prompt_number = number;
482 this.input_prompt_number = number;
483 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
483 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
484 // This HTML call is okay because the user contents are escaped.
484 // This HTML call is okay because the user contents are escaped.
485 this.element.find('div.input_prompt').html(prompt_html);
485 this.element.find('div.input_prompt').html(prompt_html);
486 };
486 };
487
487
488
488
489 CodeCell.prototype.clear_input = function () {
489 CodeCell.prototype.clear_input = function () {
490 this.code_mirror.setValue('');
490 this.code_mirror.setValue('');
491 };
491 };
492
492
493
493
494 CodeCell.prototype.get_text = function () {
494 CodeCell.prototype.get_text = function () {
495 return this.code_mirror.getValue();
495 return this.code_mirror.getValue();
496 };
496 };
497
497
498
498
499 CodeCell.prototype.set_text = function (code) {
499 CodeCell.prototype.set_text = function (code) {
500 return this.code_mirror.setValue(code);
500 return this.code_mirror.setValue(code);
501 };
501 };
502
502
503
503
504 CodeCell.prototype.at_top = function () {
504 CodeCell.prototype.at_top = function () {
505 var cursor = this.code_mirror.getCursor();
505 var cursor = this.code_mirror.getCursor();
506 if (cursor.line === 0 && cursor.ch === 0) {
506 if (cursor.line === 0 && cursor.ch === 0) {
507 return true;
507 return true;
508 } else {
508 } else {
509 return false;
509 return false;
510 }
510 }
511 };
511 };
512
512
513
513
514 CodeCell.prototype.at_bottom = function () {
514 CodeCell.prototype.at_bottom = function () {
515 var cursor = this.code_mirror.getCursor();
515 var cursor = this.code_mirror.getCursor();
516 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
516 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
517 return true;
517 return true;
518 } else {
518 } else {
519 return false;
519 return false;
520 }
520 }
521 };
521 };
522
522
523
523
524 CodeCell.prototype.clear_output = function (wait) {
524 CodeCell.prototype.clear_output = function (wait) {
525 this.output_area.clear_output(wait);
525 this.output_area.clear_output(wait);
526 this.set_input_prompt();
526 this.set_input_prompt();
527 };
527 };
528
528
529
529
530 // JSON serialization
530 // JSON serialization
531
531
532 CodeCell.prototype.fromJSON = function (data) {
532 CodeCell.prototype.fromJSON = function (data) {
533 IPython.Cell.prototype.fromJSON.apply(this, arguments);
533 IPython.Cell.prototype.fromJSON.apply(this, arguments);
534 if (data.cell_type === 'code') {
534 if (data.cell_type === 'code') {
535 if (data.input !== undefined) {
535 if (data.input !== undefined) {
536 this.set_text(data.input);
536 this.set_text(data.input);
537 // make this value the starting point, so that we can only undo
537 // make this value the starting point, so that we can only undo
538 // to this state, instead of a blank cell
538 // to this state, instead of a blank cell
539 this.code_mirror.clearHistory();
539 this.code_mirror.clearHistory();
540 this.auto_highlight();
540 this.auto_highlight();
541 }
541 }
542 if (data.prompt_number !== undefined) {
542 if (data.prompt_number !== undefined) {
543 this.set_input_prompt(data.prompt_number);
543 this.set_input_prompt(data.prompt_number);
544 } else {
544 } else {
545 this.set_input_prompt();
545 this.set_input_prompt();
546 }
546 }
547 this.output_area.trusted = data.trusted || false;
547 this.output_area.trusted = data.trusted || false;
548 this.output_area.fromJSON(data.outputs);
548 this.output_area.fromJSON(data.outputs);
549 if (data.collapsed !== undefined) {
549 if (data.collapsed !== undefined) {
550 if (data.collapsed) {
550 if (data.collapsed) {
551 this.collapse_output();
551 this.collapse_output();
552 } else {
552 } else {
553 this.expand_output();
553 this.expand_output();
554 }
554 }
555 }
555 }
556 }
556 }
557 };
557 };
558
558
559
559
560 CodeCell.prototype.toJSON = function () {
560 CodeCell.prototype.toJSON = function () {
561 var data = IPython.Cell.prototype.toJSON.apply(this);
561 var data = IPython.Cell.prototype.toJSON.apply(this);
562 data.input = this.get_text();
562 data.input = this.get_text();
563 // is finite protect against undefined and '*' value
563 // is finite protect against undefined and '*' value
564 if (isFinite(this.input_prompt_number)) {
564 if (isFinite(this.input_prompt_number)) {
565 data.prompt_number = this.input_prompt_number;
565 data.prompt_number = this.input_prompt_number;
566 }
566 }
567 var outputs = this.output_area.toJSON();
567 var outputs = this.output_area.toJSON();
568 data.outputs = outputs;
568 data.outputs = outputs;
569 data.language = 'python';
569 data.language = 'python';
570 data.trusted = this.output_area.trusted;
570 data.trusted = this.output_area.trusted;
571 data.collapsed = this.collapsed;
571 data.collapsed = this.collapsed;
572 return data;
572 return data;
573 };
573 };
574
574
575
575
576 IPython.CodeCell = CodeCell;
576 IPython.CodeCell = CodeCell;
577
577
578 return IPython;
578 return IPython;
579 }(IPython));
579 }(IPython));
@@ -1,391 +1,384 b''
1 // function completer.
1 // function completer.
2 //
2 //
3 // completer should be a class that takes an cell instance
3 // completer should be a class that takes an cell instance
4 var IPython = (function (IPython) {
4 var IPython = (function (IPython) {
5 // that will prevent us from misspelling
5 // that will prevent us from misspelling
6 "use strict";
6 "use strict";
7
7
8 // easier key mapping
8 // easier key mapping
9 var key = IPython.utils.keycodes;
9 var keycodes = IPython.keyboard.keycodes;
10
10
11 function prepend_n_prc(str, n) {
11 function prepend_n_prc(str, n) {
12 for( var i =0 ; i< n ; i++){
12 for( var i =0 ; i< n ; i++){
13 str = '%'+str ;
13 str = '%'+str ;
14 }
14 }
15 return str;
15 return str;
16 }
16 }
17
17
18 function _existing_completion(item, completion_array){
18 function _existing_completion(item, completion_array){
19 for( var c in completion_array ) {
19 for( var c in completion_array ) {
20 if(completion_array[c].trim().substr(-item.length) == item)
20 if(completion_array[c].trim().substr(-item.length) == item)
21 { return true; }
21 { return true; }
22 }
22 }
23 return false;
23 return false;
24 }
24 }
25
25
26 // what is the common start of all completions
26 // what is the common start of all completions
27 function shared_start(B, drop_prct) {
27 function shared_start(B, drop_prct) {
28 if (B.length == 1) {
28 if (B.length == 1) {
29 return B[0];
29 return B[0];
30 }
30 }
31 var A = [];
31 var A = [];
32 var common;
32 var common;
33 var min_lead_prct = 10;
33 var min_lead_prct = 10;
34 for (var i = 0; i < B.length; i++) {
34 for (var i = 0; i < B.length; i++) {
35 var str = B[i].str;
35 var str = B[i].str;
36 var localmin = 0;
36 var localmin = 0;
37 if(drop_prct === true){
37 if(drop_prct === true){
38 while ( str.substr(0, 1) == '%') {
38 while ( str.substr(0, 1) == '%') {
39 localmin = localmin+1;
39 localmin = localmin+1;
40 str = str.substring(1);
40 str = str.substring(1);
41 }
41 }
42 }
42 }
43 min_lead_prct = Math.min(min_lead_prct, localmin);
43 min_lead_prct = Math.min(min_lead_prct, localmin);
44 A.push(str);
44 A.push(str);
45 }
45 }
46
46
47 if (A.length > 1) {
47 if (A.length > 1) {
48 var tem1, tem2, s;
48 var tem1, tem2, s;
49 A = A.slice(0).sort();
49 A = A.slice(0).sort();
50 tem1 = A[0];
50 tem1 = A[0];
51 s = tem1.length;
51 s = tem1.length;
52 tem2 = A.pop();
52 tem2 = A.pop();
53 while (s && tem2.indexOf(tem1) == -1) {
53 while (s && tem2.indexOf(tem1) == -1) {
54 tem1 = tem1.substring(0, --s);
54 tem1 = tem1.substring(0, --s);
55 }
55 }
56 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
56 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
57 return {
57 return {
58 str:prepend_n_prc('', min_lead_prct),
58 str:prepend_n_prc('', min_lead_prct),
59 type: "computed",
59 type: "computed",
60 from: B[0].from,
60 from: B[0].from,
61 to: B[0].to
61 to: B[0].to
62 };
62 };
63 }
63 }
64 return {
64 return {
65 str: prepend_n_prc(tem1, min_lead_prct),
65 str: prepend_n_prc(tem1, min_lead_prct),
66 type: "computed",
66 type: "computed",
67 from: B[0].from,
67 from: B[0].from,
68 to: B[0].to
68 to: B[0].to
69 };
69 };
70 }
70 }
71 return null;
71 return null;
72 }
72 }
73
73
74
74
75 var Completer = function (cell) {
75 var Completer = function (cell) {
76 this._visible = false;
76 this._visible = false;
77 this.cell = cell;
77 this.cell = cell;
78 this.editor = cell.code_mirror;
78 this.editor = cell.code_mirror;
79 var that = this;
79 var that = this;
80 $([IPython.events]).on('status_busy.Kernel', function () {
80 $([IPython.events]).on('status_busy.Kernel', function () {
81 that.skip_kernel_completion = true;
81 that.skip_kernel_completion = true;
82 });
82 });
83 $([IPython.events]).on('status_idle.Kernel', function () {
83 $([IPython.events]).on('status_idle.Kernel', function () {
84 that.skip_kernel_completion = false;
84 that.skip_kernel_completion = false;
85 });
85 });
86 };
86 };
87
87
88 Completer.prototype.is_visible = function () {
88 Completer.prototype.is_visible = function () {
89 // Return whether or not the completer is visible.
89 // Return whether or not the completer is visible.
90 return this._visible;
90 return this._visible;
91 };
91 };
92
92
93 Completer.prototype.startCompletion = function () {
93 Completer.prototype.startCompletion = function () {
94 // call for a 'first' completion, that will set the editor and do some
94 // call for a 'first' completion, that will set the editor and do some
95 // special behaviour like autopicking if only one completion availlable
95 // special behaviour like autopicking if only one completion availlable
96 //
96 //
97 if (this.editor.somethingSelected()) return;
97 if (this.editor.somethingSelected()) return;
98 this.done = false;
98 this.done = false;
99 // use to get focus back on opera
99 // use to get focus back on opera
100 this.carry_on_completion(true);
100 this.carry_on_completion(true);
101 };
101 };
102
102
103
103
104 // easy access for julia to monkeypatch
104 // easy access for julia to monkeypatch
105 //
105 //
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
107
107
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
109 return Completer.reinvoke_re.test(pre_cursor);
109 return Completer.reinvoke_re.test(pre_cursor);
110 };
110 };
111
111
112 /**
112 /**
113 *
113 *
114 * pass true as parameter if this is the first invocation of the completer
114 * pass true as parameter if this is the first invocation of the completer
115 * this will prevent the completer to dissmiss itself if it is not on a
115 * this will prevent the completer to dissmiss itself if it is not on a
116 * word boundary like pressing tab after a space, and make it autopick the
116 * word boundary like pressing tab after a space, and make it autopick the
117 * only choice if there is only one which prevent from popping the UI. as
117 * only choice if there is only one which prevent from popping the UI. as
118 * well as fast-forwarding the typing if all completion have a common
118 * well as fast-forwarding the typing if all completion have a common
119 * shared start
119 * shared start
120 **/
120 **/
121 Completer.prototype.carry_on_completion = function (first_invocation) {
121 Completer.prototype.carry_on_completion = function (first_invocation) {
122 // Pass true as parameter if you want the completer to autopick when
122 // Pass true as parameter if you want the completer to autopick when
123 // only one completion. This function is automatically reinvoked at
123 // only one completion. This function is automatically reinvoked at
124 // each keystroke with first_invocation = false
124 // each keystroke with first_invocation = false
125 var cur = this.editor.getCursor();
125 var cur = this.editor.getCursor();
126 var line = this.editor.getLine(cur.line);
126 var line = this.editor.getLine(cur.line);
127 var pre_cursor = this.editor.getRange({
127 var pre_cursor = this.editor.getRange({
128 line: cur.line,
128 line: cur.line,
129 ch: cur.ch - 1
129 ch: cur.ch - 1
130 }, cur);
130 }, cur);
131
131
132 // we need to check that we are still on a word boundary
132 // we need to check that we are still on a word boundary
133 // because while typing the completer is still reinvoking itself
133 // because while typing the completer is still reinvoking itself
134 // so dismiss if we are on a "bad" caracter
134 // so dismiss if we are on a "bad" caracter
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
136 this.close();
136 this.close();
137 return;
137 return;
138 }
138 }
139
139
140 this.autopick = false;
140 this.autopick = false;
141 if (first_invocation) {
141 if (first_invocation) {
142 this.autopick = true;
142 this.autopick = true;
143 }
143 }
144
144
145 // We want a single cursor position.
145 // We want a single cursor position.
146 if (this.editor.somethingSelected()) {
146 if (this.editor.somethingSelected()) {
147 return;
147 return;
148 }
148 }
149
149
150 // one kernel completion came back, finish_completing will be called with the results
150 // one kernel completion came back, finish_completing will be called with the results
151 // we fork here and directly call finish completing if kernel is busy
151 // we fork here and directly call finish completing if kernel is busy
152 if (this.skip_kernel_completion) {
152 if (this.skip_kernel_completion) {
153 this.finish_completing({
153 this.finish_completing({
154 'matches': [],
154 'matches': [],
155 matched_text: ""
155 matched_text: ""
156 });
156 });
157 } else {
157 } else {
158 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
158 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
159 }
159 }
160 };
160 };
161
161
162 Completer.prototype.finish_completing = function (msg) {
162 Completer.prototype.finish_completing = function (msg) {
163 // let's build a function that wrap all that stuff into what is needed
163 // let's build a function that wrap all that stuff into what is needed
164 // for the new completer:
164 // for the new completer:
165 var content = msg.content;
165 var content = msg.content;
166 var matched_text = content.matched_text;
166 var matched_text = content.matched_text;
167 var matches = content.matches;
167 var matches = content.matches;
168
168
169 var cur = this.editor.getCursor();
169 var cur = this.editor.getCursor();
170 var results = CodeMirror.contextHint(this.editor);
170 var results = CodeMirror.contextHint(this.editor);
171 var filtered_results = [];
171 var filtered_results = [];
172 //remove results from context completion
172 //remove results from context completion
173 //that are already in kernel completion
173 //that are already in kernel completion
174 for (var elm in results) {
174 for (var elm in results) {
175 if (!_existing_completion(results[elm].str, matches)) {
175 if (!_existing_completion(results[elm].str, matches)) {
176 filtered_results.push(results[elm]);
176 filtered_results.push(results[elm]);
177 }
177 }
178 }
178 }
179
179
180 // append the introspection result, in order, at at the beginning of
180 // append the introspection result, in order, at at the beginning of
181 // the table and compute the replacement range from current cursor
181 // the table and compute the replacement range from current cursor
182 // positon and matched_text length.
182 // positon and matched_text length.
183 for (var i = matches.length - 1; i >= 0; --i) {
183 for (var i = matches.length - 1; i >= 0; --i) {
184 filtered_results.unshift({
184 filtered_results.unshift({
185 str: matches[i],
185 str: matches[i],
186 type: "introspection",
186 type: "introspection",
187 from: {
187 from: {
188 line: cur.line,
188 line: cur.line,
189 ch: cur.ch - matched_text.length
189 ch: cur.ch - matched_text.length
190 },
190 },
191 to: {
191 to: {
192 line: cur.line,
192 line: cur.line,
193 ch: cur.ch
193 ch: cur.ch
194 }
194 }
195 });
195 });
196 }
196 }
197
197
198 // one the 2 sources results have been merge, deal with it
198 // one the 2 sources results have been merge, deal with it
199 this.raw_result = filtered_results;
199 this.raw_result = filtered_results;
200
200
201 // if empty result return
201 // if empty result return
202 if (!this.raw_result || !this.raw_result.length) return;
202 if (!this.raw_result || !this.raw_result.length) return;
203
203
204 // When there is only one completion, use it directly.
204 // When there is only one completion, use it directly.
205 if (this.autopick && this.raw_result.length == 1) {
205 if (this.autopick && this.raw_result.length == 1) {
206 this.insert(this.raw_result[0]);
206 this.insert(this.raw_result[0]);
207 return;
207 return;
208 }
208 }
209
209
210 if (this.raw_result.length == 1) {
210 if (this.raw_result.length == 1) {
211 // test if first and only completion totally matches
211 // test if first and only completion totally matches
212 // what is typed, in this case dismiss
212 // what is typed, in this case dismiss
213 var str = this.raw_result[0].str;
213 var str = this.raw_result[0].str;
214 var pre_cursor = this.editor.getRange({
214 var pre_cursor = this.editor.getRange({
215 line: cur.line,
215 line: cur.line,
216 ch: cur.ch - str.length
216 ch: cur.ch - str.length
217 }, cur);
217 }, cur);
218 if (pre_cursor == str) {
218 if (pre_cursor == str) {
219 this.close();
219 this.close();
220 return;
220 return;
221 }
221 }
222 }
222 }
223
223
224 this.complete = $('<div/>').addClass('completions');
224 this.complete = $('<div/>').addClass('completions');
225 this.complete.attr('id', 'complete');
225 this.complete.attr('id', 'complete');
226
226
227 // Currently webkit doesn't use the size attr correctly. See:
227 // Currently webkit doesn't use the size attr correctly. See:
228 // https://code.google.com/p/chromium/issues/detail?id=4579
228 // https://code.google.com/p/chromium/issues/detail?id=4579
229 this.sel = $('<select style="width: auto"/>')
229 this.sel = $('<select style="width: auto"/>')
230 .attr('multiple', 'true')
230 .attr('multiple', 'true')
231 .attr('size', Math.min(10, this.raw_result.length));
231 .attr('size', Math.min(10, this.raw_result.length));
232 this.complete.append(this.sel);
232 this.complete.append(this.sel);
233 this._visible = true;
233 this._visible = true;
234 $('body').append(this.complete);
234 $('body').append(this.complete);
235
235
236 // After everything is on the page, compute the postion.
236 // After everything is on the page, compute the postion.
237 // We put it above the code if it is too close to the bottom of the page.
237 // We put it above the code if it is too close to the bottom of the page.
238 cur.ch = cur.ch-matched_text.length;
238 cur.ch = cur.ch-matched_text.length;
239 var pos = this.editor.cursorCoords(cur);
239 var pos = this.editor.cursorCoords(cur);
240 var left = pos.left-3;
240 var left = pos.left-3;
241 var top;
241 var top;
242 var cheight = this.complete.height();
242 var cheight = this.complete.height();
243 var wheight = $(window).height();
243 var wheight = $(window).height();
244 if (pos.bottom+cheight+5 > wheight) {
244 if (pos.bottom+cheight+5 > wheight) {
245 top = pos.top-cheight-4;
245 top = pos.top-cheight-4;
246 } else {
246 } else {
247 top = pos.bottom+1;
247 top = pos.bottom+1;
248 }
248 }
249 this.complete.css('left', left + 'px');
249 this.complete.css('left', left + 'px');
250 this.complete.css('top', top + 'px');
250 this.complete.css('top', top + 'px');
251
251
252
252
253 //build the container
253 //build the container
254 var that = this;
254 var that = this;
255 this.sel.dblclick(function () {
255 this.sel.dblclick(function () {
256 that.pick();
256 that.pick();
257 });
257 });
258 this.sel.blur(this.close);
258 this.sel.blur(this.close);
259 this.sel.keydown(function (event) {
259 this.sel.keydown(function (event) {
260 that.keydown(event);
260 that.keydown(event);
261 });
261 });
262 this.sel.keypress(function (event) {
262 this.sel.keypress(function (event) {
263 that.keypress(event);
263 that.keypress(event);
264 });
264 });
265
265
266 this.build_gui_list(this.raw_result);
266 this.build_gui_list(this.raw_result);
267
267
268 this.sel.focus();
268 this.sel.focus();
269 IPython.keyboard_manager.disable();
269 IPython.keyboard_manager.disable();
270 // Opera sometimes ignores focusing a freshly created node
270 // Opera sometimes ignores focusing a freshly created node
271 if (window.opera) setTimeout(function () {
271 if (window.opera) setTimeout(function () {
272 if (!this.done) this.sel.focus();
272 if (!this.done) this.sel.focus();
273 }, 100);
273 }, 100);
274 return true;
274 return true;
275 };
275 };
276
276
277 Completer.prototype.insert = function (completion) {
277 Completer.prototype.insert = function (completion) {
278 this.editor.replaceRange(completion.str, completion.from, completion.to);
278 this.editor.replaceRange(completion.str, completion.from, completion.to);
279 };
279 };
280
280
281 Completer.prototype.build_gui_list = function (completions) {
281 Completer.prototype.build_gui_list = function (completions) {
282 for (var i = 0; i < completions.length; ++i) {
282 for (var i = 0; i < completions.length; ++i) {
283 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
283 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
284 this.sel.append(opt);
284 this.sel.append(opt);
285 }
285 }
286 this.sel.children().first().attr('selected', 'true');
286 this.sel.children().first().attr('selected', 'true');
287 this.sel.scrollTop(0);
287 this.sel.scrollTop(0);
288 };
288 };
289
289
290 Completer.prototype.close = function () {
290 Completer.prototype.close = function () {
291 this._visible = false;
291 this._visible = false;
292 if (this.done) return;
292 if (this.done) return;
293 this.done = true;
293 this.done = true;
294 $('.completions').remove();
294 $('.completions').remove();
295 IPython.keyboard_manager.enable();
295 IPython.keyboard_manager.enable();
296 };
296 };
297
297
298 Completer.prototype.pick = function () {
298 Completer.prototype.pick = function () {
299 this.insert(this.raw_result[this.sel[0].selectedIndex]);
299 this.insert(this.raw_result[this.sel[0].selectedIndex]);
300 this.close();
300 this.close();
301 var that = this;
301 var that = this;
302 setTimeout(function () {
302 setTimeout(function () {
303 that.editor.focus();
303 that.editor.focus();
304 }, 50);
304 }, 50);
305 };
305 };
306
306
307 Completer.prototype.keydown = function (event) {
307 Completer.prototype.keydown = function (event) {
308 var code = event.keyCode;
308 var code = event.keyCode;
309 var that = this;
309 var that = this;
310 var special_key = false;
311
312 // detect special keys like SHIFT,PGUP,...
313 for( var _key in key ) {
314 if (code == key[_key] ) {
315 special_key = true;
316 }
317 }
318
310
319 // Enter
311 // Enter
320 if (code == key.ENTER) {
312 if (code == keycodes.enter) {
321 CodeMirror.e_stop(event);
313 CodeMirror.e_stop(event);
322 this.pick();
314 this.pick();
323 }
315 }
324 // Escape or backspace
316 // Escape or backspace
325 else if (code == key.ESC) {
317 else if (code == keycodes.esc) {
326 CodeMirror.e_stop(event);
318 CodeMirror.e_stop(event);
327 this.close();
319 this.close();
328 this.editor.focus();
320 this.editor.focus();
329 } else if (code == key.BACKSPACE) {
321
322 } else if (code == keycodes.backspace) {
330 this.close();
323 this.close();
331 this.editor.focus();
324 this.editor.focus();
332 } else if (code == key.TAB) {
325 } else if (code == keycodes.tab) {
333 //all the fastforwarding operation,
326 //all the fastforwarding operation,
334 //Check that shared start is not null which can append with prefixed completion
327 //Check that shared start is not null which can append with prefixed completion
335 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
336 // to erase py
329 // to erase py
337 var sh = shared_start(this.raw_result, true);
330 var sh = shared_start(this.raw_result, true);
338 if (sh) {
331 if (sh) {
339 this.insert(sh);
332 this.insert(sh);
340 }
333 }
341 this.close();
334 this.close();
342 CodeMirror.e_stop(event);
335 CodeMirror.e_stop(event);
343 this.editor.focus();
336 this.editor.focus();
344 //reinvoke self
337 //reinvoke self
345 setTimeout(function () {
338 setTimeout(function () {
346 that.carry_on_completion();
339 that.carry_on_completion();
347 }, 50);
340 }, 50);
348 } else if (code == key.UPARROW || code == key.DOWNARROW) {
341 } else if (code == keycodes.up || code == keycodes.down) {
349 // need to do that to be able to move the arrow
342 // need to do that to be able to move the arrow
350 // when on the first or last line ofo a code cell
343 // when on the first or last line ofo a code cell
351 event.stopPropagation();
344 event.stopPropagation();
352 }
345 }
353 };
346 };
354
347
355 Completer.prototype.keypress = function (event) {
348 Completer.prototype.keypress = function (event) {
356 // FIXME: This is a band-aid.
349 // FIXME: This is a band-aid.
357 // on keypress, trigger insertion of a single character.
350 // on keypress, trigger insertion of a single character.
358 // This simulates the old behavior of completion as you type,
351 // This simulates the old behavior of completion as you type,
359 // before events were disconnected and CodeMirror stopped
352 // before events were disconnected and CodeMirror stopped
360 // receiving events while the completer is focused.
353 // receiving events while the completer is focused.
361
354
362 var that = this;
355 var that = this;
363 var code = event.keyCode;
356 var code = event.keyCode;
364
357
365 // don't handle keypress if it's not a character (arrows on FF)
358 // don't handle keypress if it's not a character (arrows on FF)
366 // or ENTER/TAB
359 // or ENTER/TAB
367 if (event.charCode === 0 ||
360 if (event.charCode === 0 ||
368 code == key.ENTER ||
361 code == keycodes.enter ||
369 code == key.TAB
362 code == keycodes.tab
370 ) return;
363 ) return;
371
364
372 var cur = this.editor.getCursor();
365 var cur = this.editor.getCursor();
373 var completion = {
366 var completion = {
374 str: String.fromCharCode(event.which),
367 str: String.fromCharCode(event.which),
375 type: "introspection",
368 type: "introspection",
376 from: cur,
369 from: cur,
377 to: cur,
370 to: cur,
378 };
371 };
379 this.insert(completion);
372 this.insert(completion);
380
373
381 this.close();
374 this.close();
382 this.editor.focus();
375 this.editor.focus();
383 setTimeout(function () {
376 setTimeout(function () {
384 that.carry_on_completion();
377 that.carry_on_completion();
385 }, 50);
378 }, 50);
386 };
379 };
387
380
388 IPython.Completer = Completer;
381 IPython.Completer = Completer;
389
382
390 return IPython;
383 return IPython;
391 }(IPython));
384 }(IPython));
@@ -1,792 +1,598 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
21
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
41
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 };
46
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
50 };
51
52 var browser = IPython.utils.browser[0];
15 var browser = IPython.utils.browser[0];
53 var platform = IPython.utils.platform;
16 var platform = IPython.utils.platform;
54
55 if (browser === 'Firefox' || browser === 'Opera') {
56 $.extend(_keycodes, _mozilla_keycodes);
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
58 $.extend(_keycodes, _ie_keycodes);
59 }
60
61 var keycodes = {};
62 var inv_keycodes = {};
63 for (var name in _keycodes) {
64 var names = name.split(' ');
65 if (names.length === 1) {
66 var n = names[0];
67 keycodes[n] = _keycodes[n];
68 inv_keycodes[_keycodes[n]] = n;
69 } else {
70 var primary = names[0];
71 var secondary = names[1];
72 keycodes[primary] = _keycodes[name];
73 keycodes[secondary] = _keycodes[name];
74 inv_keycodes[_keycodes[name]] = primary;
75 }
76 }
77
78
17
79 // Default keyboard shortcuts
18 // Default keyboard shortcuts
80
19
81 var default_common_shortcuts = {
20 var default_common_shortcuts = {
82 'shift' : {
21 'shift' : {
83 help : '',
22 help : '',
84 help_index : '',
23 help_index : '',
85 handler : function (event) {
24 handler : function (event) {
86 // ignore shift keydown
25 // ignore shift keydown
87 return true;
26 return true;
88 }
27 }
89 },
28 },
90 'shift+enter' : {
29 'shift+enter' : {
91 help : 'run cell, select below',
30 help : 'run cell, select below',
92 help_index : 'ba',
31 help_index : 'ba',
93 handler : function (event) {
32 handler : function (event) {
94 IPython.notebook.execute_cell_and_select_below();
33 IPython.notebook.execute_cell_and_select_below();
95 return false;
34 return false;
96 }
35 }
97 },
36 },
98 'ctrl+enter' : {
37 'ctrl+enter' : {
99 help : 'run cell',
38 help : 'run cell',
100 help_index : 'bb',
39 help_index : 'bb',
101 handler : function (event) {
40 handler : function (event) {
102 IPython.notebook.execute_cell();
41 IPython.notebook.execute_cell();
103 return false;
42 return false;
104 }
43 }
105 },
44 },
106 'alt+enter' : {
45 'alt+enter' : {
107 help : 'run cell, insert below',
46 help : 'run cell, insert below',
108 help_index : 'bc',
47 help_index : 'bc',
109 handler : function (event) {
48 handler : function (event) {
110 IPython.notebook.execute_cell_and_insert_below();
49 IPython.notebook.execute_cell_and_insert_below();
111 return false;
50 return false;
112 }
51 }
113 }
52 }
114 };
53 };
115
54
116 if (platform === 'MacOS') {
55 if (platform === 'MacOS') {
117 default_common_shortcuts['cmd+s'] =
56 default_common_shortcuts['cmd+s'] =
118 {
57 {
119 help : 'save notebook',
58 help : 'save notebook',
120 help_index : 'fb',
59 help_index : 'fb',
121 handler : function (event) {
60 handler : function (event) {
122 IPython.notebook.save_checkpoint();
61 IPython.notebook.save_checkpoint();
123 event.preventDefault();
62 event.preventDefault();
124 return false;
63 return false;
125 }
64 }
126 };
65 };
127 } else {
66 } else {
128 default_common_shortcuts['ctrl+s'] =
67 default_common_shortcuts['ctrl+s'] =
129 {
68 {
130 help : 'save notebook',
69 help : 'save notebook',
131 help_index : 'fb',
70 help_index : 'fb',
132 handler : function (event) {
71 handler : function (event) {
133 IPython.notebook.save_checkpoint();
72 IPython.notebook.save_checkpoint();
134 event.preventDefault();
73 event.preventDefault();
135 return false;
74 return false;
136 }
75 }
137 };
76 };
138 }
77 }
139
78
140 // Edit mode defaults
79 // Edit mode defaults
141
80
142 var default_edit_shortcuts = {
81 var default_edit_shortcuts = {
143 'esc' : {
82 'esc' : {
144 help : 'command mode',
83 help : 'command mode',
145 help_index : 'aa',
84 help_index : 'aa',
146 handler : function (event) {
85 handler : function (event) {
147 IPython.notebook.command_mode();
86 IPython.notebook.command_mode();
148 IPython.notebook.focus_cell();
87 IPython.notebook.focus_cell();
149 return false;
88 return false;
150 }
89 }
151 },
90 },
152 'ctrl+m' : {
91 'ctrl+m' : {
153 help : 'command mode',
92 help : 'command mode',
154 help_index : 'ab',
93 help_index : 'ab',
155 handler : function (event) {
94 handler : function (event) {
156 IPython.notebook.command_mode();
95 IPython.notebook.command_mode();
157 IPython.notebook.focus_cell();
96 IPython.notebook.focus_cell();
158 return false;
97 return false;
159 }
98 }
160 },
99 },
161 'up' : {
100 'up' : {
162 help : '',
101 help : '',
163 help_index : '',
102 help_index : '',
164 handler : function (event) {
103 handler : function (event) {
165 var index = IPython.notebook.get_selected_index();
104 var index = IPython.notebook.get_selected_index();
166 if (index !== null && index !== 0) {
105 if (index !== null && index !== 0) {
167 var cell = IPython.notebook.get_cell(index);
106 var cell = IPython.notebook.get_cell(index);
168 if (cell && cell.at_top()) {
107 if (cell && cell.at_top()) {
169 event.preventDefault();
108 event.preventDefault();
170 IPython.notebook.command_mode();
109 IPython.notebook.command_mode();
171 IPython.notebook.select_prev();
110 IPython.notebook.select_prev();
172 IPython.notebook.edit_mode();
111 IPython.notebook.edit_mode();
173 return false;
112 return false;
174 }
113 }
175 }
114 }
176 }
115 }
177 },
116 },
178 'down' : {
117 'down' : {
179 help : '',
118 help : '',
180 help_index : '',
119 help_index : '',
181 handler : function (event) {
120 handler : function (event) {
182 var index = IPython.notebook.get_selected_index();
121 var index = IPython.notebook.get_selected_index();
183 if (index !== null && index !== (IPython.notebook.ncells()-1)) {
122 if (index !== null && index !== (IPython.notebook.ncells()-1)) {
184 var cell = IPython.notebook.get_cell(index);
123 var cell = IPython.notebook.get_cell(index);
185 if (cell && cell.at_bottom()) {
124 if (cell && cell.at_bottom()) {
186 event.preventDefault();
125 event.preventDefault();
187 IPython.notebook.command_mode();
126 IPython.notebook.command_mode();
188 IPython.notebook.select_next();
127 IPython.notebook.select_next();
189 IPython.notebook.edit_mode();
128 IPython.notebook.edit_mode();
190 return false;
129 return false;
191 }
130 }
192 }
131 }
193 }
132 }
194 },
133 },
195 'alt+-' : {
134 'alt+-' : {
196 help : 'split cell',
135 help : 'split cell',
197 help_index : 'ea',
136 help_index : 'ea',
198 handler : function (event) {
137 handler : function (event) {
199 IPython.notebook.split_cell();
138 IPython.notebook.split_cell();
200 return false;
139 return false;
201 }
140 }
202 },
141 },
203 'alt+subtract' : {
142 'alt+subtract' : {
204 help : '',
143 help : '',
205 help_index : 'eb',
144 help_index : 'eb',
206 handler : function (event) {
145 handler : function (event) {
207 IPython.notebook.split_cell();
146 IPython.notebook.split_cell();
208 return false;
147 return false;
209 }
148 }
210 },
149 },
211 'tab' : {
150 'tab' : {
212 help : 'indent or complete',
151 help : 'indent or complete',
213 help_index : 'ec',
152 help_index : 'ec',
214 },
153 },
215 'shift+tab' : {
154 'shift+tab' : {
216 help : 'tooltip',
155 help : 'tooltip',
217 help_index : 'ed',
156 help_index : 'ed',
218 },
157 },
219 };
158 };
220
159
221 if (platform === 'MacOS') {
160 if (platform === 'MacOS') {
222 default_edit_shortcuts['cmd+/'] =
161 default_edit_shortcuts['cmd+/'] =
223 {
162 {
224 help : 'toggle comment',
163 help : 'toggle comment',
225 help_index : 'ee'
164 help_index : 'ee'
226 };
165 };
227 default_edit_shortcuts['cmd+]'] =
166 default_edit_shortcuts['cmd+]'] =
228 {
167 {
229 help : 'indent',
168 help : 'indent',
230 help_index : 'ef'
169 help_index : 'ef'
231 };
170 };
232 default_edit_shortcuts['cmd+['] =
171 default_edit_shortcuts['cmd+['] =
233 {
172 {
234 help : 'dedent',
173 help : 'dedent',
235 help_index : 'eg'
174 help_index : 'eg'
236 };
175 };
237 } else {
176 } else {
238 default_edit_shortcuts['ctrl+/'] =
177 default_edit_shortcuts['ctrl+/'] =
239 {
178 {
240 help : 'toggle comment',
179 help : 'toggle comment',
241 help_index : 'ee'
180 help_index : 'ee'
242 };
181 };
243 default_edit_shortcuts['ctrl+]'] =
182 default_edit_shortcuts['ctrl+]'] =
244 {
183 {
245 help : 'indent',
184 help : 'indent',
246 help_index : 'ef'
185 help_index : 'ef'
247 };
186 };
248 default_edit_shortcuts['ctrl+['] =
187 default_edit_shortcuts['ctrl+['] =
249 {
188 {
250 help : 'dedent',
189 help : 'dedent',
251 help_index : 'eg'
190 help_index : 'eg'
252 };
191 };
253 }
192 }
254
193
255 // Command mode defaults
194 // Command mode defaults
256
195
257 var default_command_shortcuts = {
196 var default_command_shortcuts = {
258 'enter' : {
197 'enter' : {
259 help : 'edit mode',
198 help : 'edit mode',
260 help_index : 'aa',
199 help_index : 'aa',
261 handler : function (event) {
200 handler : function (event) {
262 IPython.notebook.edit_mode();
201 IPython.notebook.edit_mode();
263 return false;
202 return false;
264 }
203 }
265 },
204 },
266 'up' : {
205 'up' : {
267 help : 'select previous cell',
206 help : 'select previous cell',
268 help_index : 'da',
207 help_index : 'da',
269 handler : function (event) {
208 handler : function (event) {
270 var index = IPython.notebook.get_selected_index();
209 var index = IPython.notebook.get_selected_index();
271 if (index !== 0 && index !== null) {
210 if (index !== 0 && index !== null) {
272 IPython.notebook.select_prev();
211 IPython.notebook.select_prev();
273 IPython.notebook.focus_cell();
212 IPython.notebook.focus_cell();
274 }
213 }
275 return false;
214 return false;
276 }
215 }
277 },
216 },
278 'down' : {
217 'down' : {
279 help : 'select next cell',
218 help : 'select next cell',
280 help_index : 'db',
219 help_index : 'db',
281 handler : function (event) {
220 handler : function (event) {
282 var index = IPython.notebook.get_selected_index();
221 var index = IPython.notebook.get_selected_index();
283 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
222 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
284 IPython.notebook.select_next();
223 IPython.notebook.select_next();
285 IPython.notebook.focus_cell();
224 IPython.notebook.focus_cell();
286 }
225 }
287 return false;
226 return false;
288 }
227 }
289 },
228 },
290 'k' : {
229 'k' : {
291 help : 'select previous cell',
230 help : 'select previous cell',
292 help_index : 'dc',
231 help_index : 'dc',
293 handler : function (event) {
232 handler : function (event) {
294 var index = IPython.notebook.get_selected_index();
233 var index = IPython.notebook.get_selected_index();
295 if (index !== 0 && index !== null) {
234 if (index !== 0 && index !== null) {
296 IPython.notebook.select_prev();
235 IPython.notebook.select_prev();
297 IPython.notebook.focus_cell();
236 IPython.notebook.focus_cell();
298 }
237 }
299 return false;
238 return false;
300 }
239 }
301 },
240 },
302 'j' : {
241 'j' : {
303 help : 'select next cell',
242 help : 'select next cell',
304 help_index : 'dd',
243 help_index : 'dd',
305 handler : function (event) {
244 handler : function (event) {
306 var index = IPython.notebook.get_selected_index();
245 var index = IPython.notebook.get_selected_index();
307 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
246 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
308 IPython.notebook.select_next();
247 IPython.notebook.select_next();
309 IPython.notebook.focus_cell();
248 IPython.notebook.focus_cell();
310 }
249 }
311 return false;
250 return false;
312 }
251 }
313 },
252 },
314 'x' : {
253 'x' : {
315 help : 'cut cell',
254 help : 'cut cell',
316 help_index : 'ee',
255 help_index : 'ee',
317 handler : function (event) {
256 handler : function (event) {
318 IPython.notebook.cut_cell();
257 IPython.notebook.cut_cell();
319 return false;
258 return false;
320 }
259 }
321 },
260 },
322 'c' : {
261 'c' : {
323 help : 'copy cell',
262 help : 'copy cell',
324 help_index : 'ef',
263 help_index : 'ef',
325 handler : function (event) {
264 handler : function (event) {
326 IPython.notebook.copy_cell();
265 IPython.notebook.copy_cell();
327 return false;
266 return false;
328 }
267 }
329 },
268 },
330 'shift+v' : {
269 'shift+v' : {
331 help : 'paste cell above',
270 help : 'paste cell above',
332 help_index : 'eg',
271 help_index : 'eg',
333 handler : function (event) {
272 handler : function (event) {
334 IPython.notebook.paste_cell_above();
273 IPython.notebook.paste_cell_above();
335 return false;
274 return false;
336 }
275 }
337 },
276 },
338 'v' : {
277 'v' : {
339 help : 'paste cell below',
278 help : 'paste cell below',
340 help_index : 'eh',
279 help_index : 'eh',
341 handler : function (event) {
280 handler : function (event) {
342 IPython.notebook.paste_cell_below();
281 IPython.notebook.paste_cell_below();
343 return false;
282 return false;
344 }
283 }
345 },
284 },
346 'd' : {
285 'd' : {
347 help : 'delete cell (press twice)',
286 help : 'delete cell (press twice)',
348 help_index : 'ej',
287 help_index : 'ej',
349 count: 2,
288 count: 2,
350 handler : function (event) {
289 handler : function (event) {
351 IPython.notebook.delete_cell();
290 IPython.notebook.delete_cell();
352 return false;
291 return false;
353 }
292 }
354 },
293 },
355 'a' : {
294 'a' : {
356 help : 'insert cell above',
295 help : 'insert cell above',
357 help_index : 'ec',
296 help_index : 'ec',
358 handler : function (event) {
297 handler : function (event) {
359 IPython.notebook.insert_cell_above('code');
298 IPython.notebook.insert_cell_above('code');
360 IPython.notebook.select_prev();
299 IPython.notebook.select_prev();
361 IPython.notebook.focus_cell();
300 IPython.notebook.focus_cell();
362 return false;
301 return false;
363 }
302 }
364 },
303 },
365 'b' : {
304 'b' : {
366 help : 'insert cell below',
305 help : 'insert cell below',
367 help_index : 'ed',
306 help_index : 'ed',
368 handler : function (event) {
307 handler : function (event) {
369 IPython.notebook.insert_cell_below('code');
308 IPython.notebook.insert_cell_below('code');
370 IPython.notebook.select_next();
309 IPython.notebook.select_next();
371 IPython.notebook.focus_cell();
310 IPython.notebook.focus_cell();
372 return false;
311 return false;
373 }
312 }
374 },
313 },
375 'y' : {
314 'y' : {
376 help : 'to code',
315 help : 'to code',
377 help_index : 'ca',
316 help_index : 'ca',
378 handler : function (event) {
317 handler : function (event) {
379 IPython.notebook.to_code();
318 IPython.notebook.to_code();
380 return false;
319 return false;
381 }
320 }
382 },
321 },
383 'm' : {
322 'm' : {
384 help : 'to markdown',
323 help : 'to markdown',
385 help_index : 'cb',
324 help_index : 'cb',
386 handler : function (event) {
325 handler : function (event) {
387 IPython.notebook.to_markdown();
326 IPython.notebook.to_markdown();
388 return false;
327 return false;
389 }
328 }
390 },
329 },
391 'r' : {
330 'r' : {
392 help : 'to raw',
331 help : 'to raw',
393 help_index : 'cc',
332 help_index : 'cc',
394 handler : function (event) {
333 handler : function (event) {
395 IPython.notebook.to_raw();
334 IPython.notebook.to_raw();
396 return false;
335 return false;
397 }
336 }
398 },
337 },
399 '1' : {
338 '1' : {
400 help : 'to heading 1',
339 help : 'to heading 1',
401 help_index : 'cd',
340 help_index : 'cd',
402 handler : function (event) {
341 handler : function (event) {
403 IPython.notebook.to_heading(undefined, 1);
342 IPython.notebook.to_heading(undefined, 1);
404 return false;
343 return false;
405 }
344 }
406 },
345 },
407 '2' : {
346 '2' : {
408 help : 'to heading 2',
347 help : 'to heading 2',
409 help_index : 'ce',
348 help_index : 'ce',
410 handler : function (event) {
349 handler : function (event) {
411 IPython.notebook.to_heading(undefined, 2);
350 IPython.notebook.to_heading(undefined, 2);
412 return false;
351 return false;
413 }
352 }
414 },
353 },
415 '3' : {
354 '3' : {
416 help : 'to heading 3',
355 help : 'to heading 3',
417 help_index : 'cf',
356 help_index : 'cf',
418 handler : function (event) {
357 handler : function (event) {
419 IPython.notebook.to_heading(undefined, 3);
358 IPython.notebook.to_heading(undefined, 3);
420 return false;
359 return false;
421 }
360 }
422 },
361 },
423 '4' : {
362 '4' : {
424 help : 'to heading 4',
363 help : 'to heading 4',
425 help_index : 'cg',
364 help_index : 'cg',
426 handler : function (event) {
365 handler : function (event) {
427 IPython.notebook.to_heading(undefined, 4);
366 IPython.notebook.to_heading(undefined, 4);
428 return false;
367 return false;
429 }
368 }
430 },
369 },
431 '5' : {
370 '5' : {
432 help : 'to heading 5',
371 help : 'to heading 5',
433 help_index : 'ch',
372 help_index : 'ch',
434 handler : function (event) {
373 handler : function (event) {
435 IPython.notebook.to_heading(undefined, 5);
374 IPython.notebook.to_heading(undefined, 5);
436 return false;
375 return false;
437 }
376 }
438 },
377 },
439 '6' : {
378 '6' : {
440 help : 'to heading 6',
379 help : 'to heading 6',
441 help_index : 'ci',
380 help_index : 'ci',
442 handler : function (event) {
381 handler : function (event) {
443 IPython.notebook.to_heading(undefined, 6);
382 IPython.notebook.to_heading(undefined, 6);
444 return false;
383 return false;
445 }
384 }
446 },
385 },
447 'o' : {
386 'o' : {
448 help : 'toggle output',
387 help : 'toggle output',
449 help_index : 'gb',
388 help_index : 'gb',
450 handler : function (event) {
389 handler : function (event) {
451 IPython.notebook.toggle_output();
390 IPython.notebook.toggle_output();
452 return false;
391 return false;
453 }
392 }
454 },
393 },
455 'shift+o' : {
394 'shift+o' : {
456 help : 'toggle output scrolling',
395 help : 'toggle output scrolling',
457 help_index : 'gc',
396 help_index : 'gc',
458 handler : function (event) {
397 handler : function (event) {
459 IPython.notebook.toggle_output_scroll();
398 IPython.notebook.toggle_output_scroll();
460 return false;
399 return false;
461 }
400 }
462 },
401 },
463 's' : {
402 's' : {
464 help : 'save notebook',
403 help : 'save notebook',
465 help_index : 'fa',
404 help_index : 'fa',
466 handler : function (event) {
405 handler : function (event) {
467 IPython.notebook.save_checkpoint();
406 IPython.notebook.save_checkpoint();
468 return false;
407 return false;
469 }
408 }
470 },
409 },
471 'ctrl+j' : {
410 'ctrl+j' : {
472 help : 'move cell down',
411 help : 'move cell down',
473 help_index : 'eb',
412 help_index : 'eb',
474 handler : function (event) {
413 handler : function (event) {
475 IPython.notebook.move_cell_down();
414 IPython.notebook.move_cell_down();
476 return false;
415 return false;
477 }
416 }
478 },
417 },
479 'ctrl+k' : {
418 'ctrl+k' : {
480 help : 'move cell up',
419 help : 'move cell up',
481 help_index : 'ea',
420 help_index : 'ea',
482 handler : function (event) {
421 handler : function (event) {
483 IPython.notebook.move_cell_up();
422 IPython.notebook.move_cell_up();
484 return false;
423 return false;
485 }
424 }
486 },
425 },
487 'l' : {
426 'l' : {
488 help : 'toggle line numbers',
427 help : 'toggle line numbers',
489 help_index : 'ga',
428 help_index : 'ga',
490 handler : function (event) {
429 handler : function (event) {
491 IPython.notebook.cell_toggle_line_numbers();
430 IPython.notebook.cell_toggle_line_numbers();
492 return false;
431 return false;
493 }
432 }
494 },
433 },
495 'i' : {
434 'i' : {
496 help : 'interrupt kernel (press twice)',
435 help : 'interrupt kernel (press twice)',
497 help_index : 'ha',
436 help_index : 'ha',
498 count: 2,
437 count: 2,
499 handler : function (event) {
438 handler : function (event) {
500 IPython.notebook.kernel.interrupt();
439 IPython.notebook.kernel.interrupt();
501 return false;
440 return false;
502 }
441 }
503 },
442 },
504 '0' : {
443 '0' : {
505 help : 'restart kernel (press twice)',
444 help : 'restart kernel (press twice)',
506 help_index : 'hb',
445 help_index : 'hb',
507 count: 2,
446 count: 2,
508 handler : function (event) {
447 handler : function (event) {
509 IPython.notebook.restart_kernel();
448 IPython.notebook.restart_kernel();
510 return false;
449 return false;
511 }
450 }
512 },
451 },
513 'h' : {
452 'h' : {
514 help : 'keyboard shortcuts',
453 help : 'keyboard shortcuts',
515 help_index : 'ge',
454 help_index : 'ge',
516 handler : function (event) {
455 handler : function (event) {
517 IPython.quick_help.show_keyboard_shortcuts();
456 IPython.quick_help.show_keyboard_shortcuts();
518 return false;
457 return false;
519 }
458 }
520 },
459 },
521 'z' : {
460 'z' : {
522 help : 'undo last delete',
461 help : 'undo last delete',
523 help_index : 'ei',
462 help_index : 'ei',
524 handler : function (event) {
463 handler : function (event) {
525 IPython.notebook.undelete_cell();
464 IPython.notebook.undelete_cell();
526 return false;
465 return false;
527 }
466 }
528 },
467 },
529 'shift+m' : {
468 'shift+m' : {
530 help : 'merge cell below',
469 help : 'merge cell below',
531 help_index : 'ek',
470 help_index : 'ek',
532 handler : function (event) {
471 handler : function (event) {
533 IPython.notebook.merge_cell_below();
472 IPython.notebook.merge_cell_below();
534 return false;
473 return false;
535 }
474 }
536 },
475 },
537 'q' : {
476 'q' : {
538 help : 'close pager',
477 help : 'close pager',
539 help_index : 'gd',
478 help_index : 'gd',
540 handler : function (event) {
479 handler : function (event) {
541 IPython.pager.collapse();
480 IPython.pager.collapse();
542 return false;
481 return false;
543 }
482 }
544 },
483 },
545 };
484 };
546
485
547
486
548 // Shortcut manager class
487 // Main keyboard manager for the notebook
549
550 var ShortcutManager = function (delay) {
551 this._shortcuts = {};
552 this._counts = {};
553 this._timers = {};
554 this.delay = delay || 800; // delay in milliseconds
555 };
556
557 ShortcutManager.prototype.help = function () {
558 var help = [];
559 for (var shortcut in this._shortcuts) {
560 var help_string = this._shortcuts[shortcut].help;
561 var help_index = this._shortcuts[shortcut].help_index;
562 if (help_string) {
563 if (platform === 'MacOS') {
564 shortcut = shortcut.replace('meta', 'cmd');
565 }
566 help.push({
567 shortcut: shortcut,
568 help: help_string,
569 help_index: help_index}
570 );
571 }
572 }
573 help.sort(function (a, b) {
574 if (a.help_index > b.help_index)
575 return 1;
576 if (a.help_index < b.help_index)
577 return -1;
578 return 0;
579 });
580 return help;
581 };
582
583 ShortcutManager.prototype.normalize_key = function (key) {
584 return inv_keycodes[keycodes[key]];
585 };
586
587 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
588 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
589 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
590 var values = shortcut.split("+");
591 if (values.length === 1) {
592 return this.normalize_key(values[0]);
593 } else {
594 var modifiers = values.slice(0,-1);
595 var key = this.normalize_key(values[values.length-1]);
596 modifiers.sort();
597 return modifiers.join('+') + '+' + key;
598 }
599 };
600
601 ShortcutManager.prototype.event_to_shortcut = function (event) {
602 // Convert a jQuery keyboard event to a strong based keyboard shortcut
603 var shortcut = '';
604 var key = inv_keycodes[event.which];
605 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
606 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
607 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
608 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
609 shortcut += key;
610 return shortcut;
611 };
612
613 ShortcutManager.prototype.clear_shortcuts = function () {
614 this._shortcuts = {};
615 };
616
617 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
618 if (typeof(data) === 'function') {
619 data = {help: '', help_index: '', handler: data};
620 }
621 data.help_index = data.help_index || '';
622 data.help = data.help || '';
623 data.count = data.count || 1;
624 if (data.help_index === '') {
625 data.help_index = 'zz';
626 }
627 shortcut = this.normalize_shortcut(shortcut);
628 this._counts[shortcut] = 0;
629 this._shortcuts[shortcut] = data;
630 };
631
632 ShortcutManager.prototype.add_shortcuts = function (data) {
633 for (var shortcut in data) {
634 this.add_shortcut(shortcut, data[shortcut]);
635 }
636 };
637
638 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
639 shortcut = this.normalize_shortcut(shortcut);
640 delete this._counts[shortcut];
641 delete this._shortcuts[shortcut];
642 };
643
644 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
645 var that = this;
646 var c = this._counts;
647 var t = this._timers;
648 var timer = null;
649 if (c[shortcut] === data.count-1) {
650 c[shortcut] = 0;
651 timer = t[shortcut];
652 if (timer) {clearTimeout(timer); delete t[shortcut];}
653 return data.handler(event);
654 } else {
655 c[shortcut] = c[shortcut] + 1;
656 timer = setTimeout(function () {
657 c[shortcut] = 0;
658 }, that.delay);
659 t[shortcut] = timer;
660 }
661 return false;
662 };
663
664 ShortcutManager.prototype.call_handler = function (event) {
665 var shortcut = this.event_to_shortcut(event);
666 var data = this._shortcuts[shortcut];
667 if (data) {
668 var handler = data.handler;
669 if (handler) {
670 if (data.count === 1) {
671 return handler(event);
672 } else if (data.count > 1) {
673 return this.count_handler(shortcut, event, data);
674 }
675 }
676 }
677 return true;
678 };
679
488
489 var ShortcutManager = IPython.keyboard.ShortcutManager;
490 var keycodes = IPython.keyboard.keycodes;
680
491
681 // Main keyboard manager for the notebook
682
683 var KeyboardManager = function () {
492 var KeyboardManager = function () {
684 this.mode = 'command';
493 this.mode = 'command';
685 this.enabled = true;
494 this.enabled = true;
686 this.bind_events();
495 this.bind_events();
687 this.command_shortcuts = new ShortcutManager();
496 this.command_shortcuts = new ShortcutManager();
688 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
497 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
689 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
498 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
690 this.edit_shortcuts = new ShortcutManager();
499 this.edit_shortcuts = new ShortcutManager();
691 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
500 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
692 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
501 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
693 };
502 };
694
503
695 KeyboardManager.prototype.bind_events = function () {
504 KeyboardManager.prototype.bind_events = function () {
696 var that = this;
505 var that = this;
697 $(document).keydown(function (event) {
506 $(document).keydown(function (event) {
698 return that.handle_keydown(event);
507 return that.handle_keydown(event);
699 });
508 });
700 };
509 };
701
510
702 KeyboardManager.prototype.handle_keydown = function (event) {
511 KeyboardManager.prototype.handle_keydown = function (event) {
703 var notebook = IPython.notebook;
512 var notebook = IPython.notebook;
704
513
705 if (event.which === keycodes.esc) {
514 if (event.which === keycodes.esc) {
706 // Intercept escape at highest level to avoid closing
515 // Intercept escape at highest level to avoid closing
707 // websocket connection with firefox
516 // websocket connection with firefox
708 event.preventDefault();
517 event.preventDefault();
709 }
518 }
710
519
711 if (!this.enabled) {
520 if (!this.enabled) {
712 if (event.which === keycodes.esc) {
521 if (event.which === keycodes.esc) {
713 // ESC
522 // ESC
714 notebook.command_mode();
523 notebook.command_mode();
715 return false;
524 return false;
716 }
525 }
717 return true;
526 return true;
718 }
527 }
719
528
720 if (this.mode === 'edit') {
529 if (this.mode === 'edit') {
721 return this.edit_shortcuts.call_handler(event);
530 return this.edit_shortcuts.call_handler(event);
722 } else if (this.mode === 'command') {
531 } else if (this.mode === 'command') {
723 return this.command_shortcuts.call_handler(event);
532 return this.command_shortcuts.call_handler(event);
724 }
533 }
725 return true;
534 return true;
726 };
535 };
727
536
728 KeyboardManager.prototype.edit_mode = function () {
537 KeyboardManager.prototype.edit_mode = function () {
729 this.last_mode = this.mode;
538 this.last_mode = this.mode;
730 this.mode = 'edit';
539 this.mode = 'edit';
731 };
540 };
732
541
733 KeyboardManager.prototype.command_mode = function () {
542 KeyboardManager.prototype.command_mode = function () {
734 this.last_mode = this.mode;
543 this.last_mode = this.mode;
735 this.mode = 'command';
544 this.mode = 'command';
736 };
545 };
737
546
738 KeyboardManager.prototype.enable = function () {
547 KeyboardManager.prototype.enable = function () {
739 this.enabled = true;
548 this.enabled = true;
740 };
549 };
741
550
742 KeyboardManager.prototype.disable = function () {
551 KeyboardManager.prototype.disable = function () {
743 this.enabled = false;
552 this.enabled = false;
744 };
553 };
745
554
746 KeyboardManager.prototype.register_events = function (e) {
555 KeyboardManager.prototype.register_events = function (e) {
747 var that = this;
556 var that = this;
748 var handle_focus = function () {
557 var handle_focus = function () {
749 that.disable();
558 that.disable();
750 };
559 };
751 var handle_blur = function () {
560 var handle_blur = function () {
752 that.enable();
561 that.enable();
753 };
562 };
754 e.on('focusin', handle_focus);
563 e.on('focusin', handle_focus);
755 e.on('focusout', handle_blur);
564 e.on('focusout', handle_blur);
756 // TODO: Very strange. The focusout event does not seem fire for the
565 // TODO: Very strange. The focusout event does not seem fire for the
757 // bootstrap textboxes on FF25&26... This works around that by
566 // bootstrap textboxes on FF25&26... This works around that by
758 // registering focus and blur events recursively on all inputs within
567 // registering focus and blur events recursively on all inputs within
759 // registered element.
568 // registered element.
760 e.find('input').blur(handle_blur);
569 e.find('input').blur(handle_blur);
761 e.on('DOMNodeInserted', function (event) {
570 e.on('DOMNodeInserted', function (event) {
762 var target = $(event.target);
571 var target = $(event.target);
763 if (target.is('input')) {
572 if (target.is('input')) {
764 target.blur(handle_blur);
573 target.blur(handle_blur);
765 } else {
574 } else {
766 target.find('input').blur(handle_blur);
575 target.find('input').blur(handle_blur);
767 }
576 }
768 });
577 });
769 // There are times (raw_input) where we remove the element from the DOM before
578 // There are times (raw_input) where we remove the element from the DOM before
770 // focusout is called. In this case we bind to the remove event of jQueryUI,
579 // focusout is called. In this case we bind to the remove event of jQueryUI,
771 // which gets triggered upon removal, iff it is focused at the time.
580 // which gets triggered upon removal, iff it is focused at the time.
772 // is_focused must be used to check for the case where an element within
581 // is_focused must be used to check for the case where an element within
773 // the element being removed is focused.
582 // the element being removed is focused.
774 e.on('remove', function () {
583 e.on('remove', function () {
775 if (IPython.utils.is_focused(e[0])) {
584 if (IPython.utils.is_focused(e[0])) {
776 that.enable();
585 that.enable();
777 }
586 }
778 });
587 });
779 };
588 };
780
589
781
590
782 IPython.keycodes = keycodes;
783 IPython.inv_keycodes = inv_keycodes;
784 IPython.default_common_shortcuts = default_common_shortcuts;
591 IPython.default_common_shortcuts = default_common_shortcuts;
785 IPython.default_edit_shortcuts = default_edit_shortcuts;
592 IPython.default_edit_shortcuts = default_edit_shortcuts;
786 IPython.default_command_shortcuts = default_command_shortcuts;
593 IPython.default_command_shortcuts = default_command_shortcuts;
787 IPython.ShortcutManager = ShortcutManager;
788 IPython.KeyboardManager = KeyboardManager;
594 IPython.KeyboardManager = KeyboardManager;
789
595
790 return IPython;
596 return IPython;
791
597
792 }(IPython));
598 }(IPython));
@@ -1,2332 +1,2332 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 this.options = options = options || {};
26 this.options = options = options || {};
27 this.base_url = options.base_url;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Create an HTML and CSS representation of the notebook.
73 * Create an HTML and CSS representation of the notebook.
74 *
74 *
75 * @method create_elements
75 * @method create_elements
76 */
76 */
77 Notebook.prototype.create_elements = function () {
77 Notebook.prototype.create_elements = function () {
78 var that = this;
78 var that = this;
79 this.element.attr('tabindex','-1');
79 this.element.attr('tabindex','-1');
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
81 // We add this end_space div to the end of the notebook div to:
81 // We add this end_space div to the end of the notebook div to:
82 // i) provide a margin between the last cell and the end of the notebook
82 // i) provide a margin between the last cell and the end of the notebook
83 // ii) to prevent the div from scrolling up when the last cell is being
83 // ii) to prevent the div from scrolling up when the last cell is being
84 // edited, but is too low on the page, which browsers will do automatically.
84 // edited, but is too low on the page, which browsers will do automatically.
85 var end_space = $('<div/>').addClass('end_space');
85 var end_space = $('<div/>').addClass('end_space');
86 end_space.dblclick(function (e) {
86 end_space.dblclick(function (e) {
87 var ncells = that.ncells();
87 var ncells = that.ncells();
88 that.insert_cell_below('code',ncells-1);
88 that.insert_cell_below('code',ncells-1);
89 });
89 });
90 this.element.append(this.container);
90 this.element.append(this.container);
91 this.container.append(end_space);
91 this.container.append(end_space);
92 };
92 };
93
93
94 /**
94 /**
95 * Bind JavaScript events: key presses and custom IPython events.
95 * Bind JavaScript events: key presses and custom IPython events.
96 *
96 *
97 * @method bind_events
97 * @method bind_events
98 */
98 */
99 Notebook.prototype.bind_events = function () {
99 Notebook.prototype.bind_events = function () {
100 var that = this;
100 var that = this;
101
101
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
103 var index = that.find_cell_index(data.cell);
103 var index = that.find_cell_index(data.cell);
104 var new_cell = that.insert_cell_below('code',index);
104 var new_cell = that.insert_cell_below('code',index);
105 new_cell.set_text(data.text);
105 new_cell.set_text(data.text);
106 that.dirty = true;
106 that.dirty = true;
107 });
107 });
108
108
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
110 that.dirty = data.value;
110 that.dirty = data.value;
111 });
111 });
112
112
113 $([IPython.events]).on('select.Cell', function (event, data) {
113 $([IPython.events]).on('select.Cell', function (event, data) {
114 var index = that.find_cell_index(data.cell);
114 var index = that.find_cell_index(data.cell);
115 that.select(index);
115 that.select(index);
116 });
116 });
117
117
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
119 that.handle_edit_mode(that.find_cell_index(data.cell));
119 that.handle_edit_mode(that.find_cell_index(data.cell));
120 });
120 });
121
121
122 $([IPython.events]).on('command_mode.Cell', function (event, data) {
122 $([IPython.events]).on('command_mode.Cell', function (event, data) {
123 that.command_mode();
123 that.command_mode();
124 });
124 });
125
125
126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
127 IPython.dialog.modal({
127 IPython.dialog.modal({
128 title: "Kernel Restarting",
128 title: "Kernel Restarting",
129 body: "The kernel appears to have died. It will restart automatically.",
129 body: "The kernel appears to have died. It will restart automatically.",
130 buttons: {
130 buttons: {
131 OK : {
131 OK : {
132 class : "btn-primary"
132 class : "btn-primary"
133 }
133 }
134 }
134 }
135 });
135 });
136 });
136 });
137
137
138 var collapse_time = function (time) {
138 var collapse_time = function (time) {
139 var app_height = $('#ipython-main-app').height(); // content height
139 var app_height = $('#ipython-main-app').height(); // content height
140 var splitter_height = $('div#pager_splitter').outerHeight(true);
140 var splitter_height = $('div#pager_splitter').outerHeight(true);
141 var new_height = app_height - splitter_height;
141 var new_height = app_height - splitter_height;
142 that.element.animate({height : new_height + 'px'}, time);
142 that.element.animate({height : new_height + 'px'}, time);
143 };
143 };
144
144
145 this.element.bind('collapse_pager', function (event, extrap) {
145 this.element.bind('collapse_pager', function (event, extrap) {
146 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
146 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
147 collapse_time(time);
147 collapse_time(time);
148 });
148 });
149
149
150 var expand_time = function (time) {
150 var expand_time = function (time) {
151 var app_height = $('#ipython-main-app').height(); // content height
151 var app_height = $('#ipython-main-app').height(); // content height
152 var splitter_height = $('div#pager_splitter').outerHeight(true);
152 var splitter_height = $('div#pager_splitter').outerHeight(true);
153 var pager_height = $('div#pager').outerHeight(true);
153 var pager_height = $('div#pager').outerHeight(true);
154 var new_height = app_height - pager_height - splitter_height;
154 var new_height = app_height - pager_height - splitter_height;
155 that.element.animate({height : new_height + 'px'}, time);
155 that.element.animate({height : new_height + 'px'}, time);
156 };
156 };
157
157
158 this.element.bind('expand_pager', function (event, extrap) {
158 this.element.bind('expand_pager', function (event, extrap) {
159 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
159 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
160 expand_time(time);
160 expand_time(time);
161 });
161 });
162
162
163 // Firefox 22 broke $(window).on("beforeunload")
163 // Firefox 22 broke $(window).on("beforeunload")
164 // I'm not sure why or how.
164 // I'm not sure why or how.
165 window.onbeforeunload = function (e) {
165 window.onbeforeunload = function (e) {
166 // TODO: Make killing the kernel configurable.
166 // TODO: Make killing the kernel configurable.
167 var kill_kernel = false;
167 var kill_kernel = false;
168 if (kill_kernel) {
168 if (kill_kernel) {
169 that.session.kill_kernel();
169 that.session.kill_kernel();
170 }
170 }
171 // if we are autosaving, trigger an autosave on nav-away.
171 // if we are autosaving, trigger an autosave on nav-away.
172 // still warn, because if we don't the autosave may fail.
172 // still warn, because if we don't the autosave may fail.
173 if (that.dirty) {
173 if (that.dirty) {
174 if ( that.autosave_interval ) {
174 if ( that.autosave_interval ) {
175 // schedule autosave in a timeout
175 // schedule autosave in a timeout
176 // this gives you a chance to forcefully discard changes
176 // this gives you a chance to forcefully discard changes
177 // by reloading the page if you *really* want to.
177 // by reloading the page if you *really* want to.
178 // the timer doesn't start until you *dismiss* the dialog.
178 // the timer doesn't start until you *dismiss* the dialog.
179 setTimeout(function () {
179 setTimeout(function () {
180 if (that.dirty) {
180 if (that.dirty) {
181 that.save_notebook();
181 that.save_notebook();
182 }
182 }
183 }, 1000);
183 }, 1000);
184 return "Autosave in progress, latest changes may be lost.";
184 return "Autosave in progress, latest changes may be lost.";
185 } else {
185 } else {
186 return "Unsaved changes will be lost.";
186 return "Unsaved changes will be lost.";
187 }
187 }
188 }
188 }
189 // Null is the *only* return value that will make the browser not
189 // Null is the *only* return value that will make the browser not
190 // pop up the "don't leave" dialog.
190 // pop up the "don't leave" dialog.
191 return null;
191 return null;
192 };
192 };
193 };
193 };
194
194
195 /**
195 /**
196 * Set the dirty flag, and trigger the set_dirty.Notebook event
196 * Set the dirty flag, and trigger the set_dirty.Notebook event
197 *
197 *
198 * @method set_dirty
198 * @method set_dirty
199 */
199 */
200 Notebook.prototype.set_dirty = function (value) {
200 Notebook.prototype.set_dirty = function (value) {
201 if (value === undefined) {
201 if (value === undefined) {
202 value = true;
202 value = true;
203 }
203 }
204 if (this.dirty == value) {
204 if (this.dirty == value) {
205 return;
205 return;
206 }
206 }
207 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
207 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
208 };
208 };
209
209
210 /**
210 /**
211 * Scroll the top of the page to a given cell.
211 * Scroll the top of the page to a given cell.
212 *
212 *
213 * @method scroll_to_cell
213 * @method scroll_to_cell
214 * @param {Number} cell_number An index of the cell to view
214 * @param {Number} cell_number An index of the cell to view
215 * @param {Number} time Animation time in milliseconds
215 * @param {Number} time Animation time in milliseconds
216 * @return {Number} Pixel offset from the top of the container
216 * @return {Number} Pixel offset from the top of the container
217 */
217 */
218 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
218 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
219 var cells = this.get_cells();
219 var cells = this.get_cells();
220 time = time || 0;
220 time = time || 0;
221 cell_number = Math.min(cells.length-1,cell_number);
221 cell_number = Math.min(cells.length-1,cell_number);
222 cell_number = Math.max(0 ,cell_number);
222 cell_number = Math.max(0 ,cell_number);
223 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
223 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
224 this.element.animate({scrollTop:scroll_value}, time);
224 this.element.animate({scrollTop:scroll_value}, time);
225 return scroll_value;
225 return scroll_value;
226 };
226 };
227
227
228 /**
228 /**
229 * Scroll to the bottom of the page.
229 * Scroll to the bottom of the page.
230 *
230 *
231 * @method scroll_to_bottom
231 * @method scroll_to_bottom
232 */
232 */
233 Notebook.prototype.scroll_to_bottom = function () {
233 Notebook.prototype.scroll_to_bottom = function () {
234 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
234 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
235 };
235 };
236
236
237 /**
237 /**
238 * Scroll to the top of the page.
238 * Scroll to the top of the page.
239 *
239 *
240 * @method scroll_to_top
240 * @method scroll_to_top
241 */
241 */
242 Notebook.prototype.scroll_to_top = function () {
242 Notebook.prototype.scroll_to_top = function () {
243 this.element.animate({scrollTop:0}, 0);
243 this.element.animate({scrollTop:0}, 0);
244 };
244 };
245
245
246 // Edit Notebook metadata
246 // Edit Notebook metadata
247
247
248 Notebook.prototype.edit_metadata = function () {
248 Notebook.prototype.edit_metadata = function () {
249 var that = this;
249 var that = this;
250 IPython.dialog.edit_metadata(this.metadata, function (md) {
250 IPython.dialog.edit_metadata(this.metadata, function (md) {
251 that.metadata = md;
251 that.metadata = md;
252 }, 'Notebook');
252 }, 'Notebook');
253 };
253 };
254
254
255 // Cell indexing, retrieval, etc.
255 // Cell indexing, retrieval, etc.
256
256
257 /**
257 /**
258 * Get all cell elements in the notebook.
258 * Get all cell elements in the notebook.
259 *
259 *
260 * @method get_cell_elements
260 * @method get_cell_elements
261 * @return {jQuery} A selector of all cell elements
261 * @return {jQuery} A selector of all cell elements
262 */
262 */
263 Notebook.prototype.get_cell_elements = function () {
263 Notebook.prototype.get_cell_elements = function () {
264 return this.container.children("div.cell");
264 return this.container.children("div.cell");
265 };
265 };
266
266
267 /**
267 /**
268 * Get a particular cell element.
268 * Get a particular cell element.
269 *
269 *
270 * @method get_cell_element
270 * @method get_cell_element
271 * @param {Number} index An index of a cell to select
271 * @param {Number} index An index of a cell to select
272 * @return {jQuery} A selector of the given cell.
272 * @return {jQuery} A selector of the given cell.
273 */
273 */
274 Notebook.prototype.get_cell_element = function (index) {
274 Notebook.prototype.get_cell_element = function (index) {
275 var result = null;
275 var result = null;
276 var e = this.get_cell_elements().eq(index);
276 var e = this.get_cell_elements().eq(index);
277 if (e.length !== 0) {
277 if (e.length !== 0) {
278 result = e;
278 result = e;
279 }
279 }
280 return result;
280 return result;
281 };
281 };
282
282
283 /**
283 /**
284 * Try to get a particular cell by msg_id.
284 * Try to get a particular cell by msg_id.
285 *
285 *
286 * @method get_msg_cell
286 * @method get_msg_cell
287 * @param {String} msg_id A message UUID
287 * @param {String} msg_id A message UUID
288 * @return {Cell} Cell or null if no cell was found.
288 * @return {Cell} Cell or null if no cell was found.
289 */
289 */
290 Notebook.prototype.get_msg_cell = function (msg_id) {
290 Notebook.prototype.get_msg_cell = function (msg_id) {
291 return IPython.CodeCell.msg_cells[msg_id] || null;
291 return IPython.CodeCell.msg_cells[msg_id] || null;
292 };
292 };
293
293
294 /**
294 /**
295 * Count the cells in this notebook.
295 * Count the cells in this notebook.
296 *
296 *
297 * @method ncells
297 * @method ncells
298 * @return {Number} The number of cells in this notebook
298 * @return {Number} The number of cells in this notebook
299 */
299 */
300 Notebook.prototype.ncells = function () {
300 Notebook.prototype.ncells = function () {
301 return this.get_cell_elements().length;
301 return this.get_cell_elements().length;
302 };
302 };
303
303
304 /**
304 /**
305 * Get all Cell objects in this notebook.
305 * Get all Cell objects in this notebook.
306 *
306 *
307 * @method get_cells
307 * @method get_cells
308 * @return {Array} This notebook's Cell objects
308 * @return {Array} This notebook's Cell objects
309 */
309 */
310 // TODO: we are often calling cells as cells()[i], which we should optimize
310 // TODO: we are often calling cells as cells()[i], which we should optimize
311 // to cells(i) or a new method.
311 // to cells(i) or a new method.
312 Notebook.prototype.get_cells = function () {
312 Notebook.prototype.get_cells = function () {
313 return this.get_cell_elements().toArray().map(function (e) {
313 return this.get_cell_elements().toArray().map(function (e) {
314 return $(e).data("cell");
314 return $(e).data("cell");
315 });
315 });
316 };
316 };
317
317
318 /**
318 /**
319 * Get a Cell object from this notebook.
319 * Get a Cell object from this notebook.
320 *
320 *
321 * @method get_cell
321 * @method get_cell
322 * @param {Number} index An index of a cell to retrieve
322 * @param {Number} index An index of a cell to retrieve
323 * @return {Cell} A particular cell
323 * @return {Cell} A particular cell
324 */
324 */
325 Notebook.prototype.get_cell = function (index) {
325 Notebook.prototype.get_cell = function (index) {
326 var result = null;
326 var result = null;
327 var ce = this.get_cell_element(index);
327 var ce = this.get_cell_element(index);
328 if (ce !== null) {
328 if (ce !== null) {
329 result = ce.data('cell');
329 result = ce.data('cell');
330 }
330 }
331 return result;
331 return result;
332 };
332 };
333
333
334 /**
334 /**
335 * Get the cell below a given cell.
335 * Get the cell below a given cell.
336 *
336 *
337 * @method get_next_cell
337 * @method get_next_cell
338 * @param {Cell} cell The provided cell
338 * @param {Cell} cell The provided cell
339 * @return {Cell} The next cell
339 * @return {Cell} The next cell
340 */
340 */
341 Notebook.prototype.get_next_cell = function (cell) {
341 Notebook.prototype.get_next_cell = function (cell) {
342 var result = null;
342 var result = null;
343 var index = this.find_cell_index(cell);
343 var index = this.find_cell_index(cell);
344 if (this.is_valid_cell_index(index+1)) {
344 if (this.is_valid_cell_index(index+1)) {
345 result = this.get_cell(index+1);
345 result = this.get_cell(index+1);
346 }
346 }
347 return result;
347 return result;
348 };
348 };
349
349
350 /**
350 /**
351 * Get the cell above a given cell.
351 * Get the cell above a given cell.
352 *
352 *
353 * @method get_prev_cell
353 * @method get_prev_cell
354 * @param {Cell} cell The provided cell
354 * @param {Cell} cell The provided cell
355 * @return {Cell} The previous cell
355 * @return {Cell} The previous cell
356 */
356 */
357 Notebook.prototype.get_prev_cell = function (cell) {
357 Notebook.prototype.get_prev_cell = function (cell) {
358 // TODO: off-by-one
358 // TODO: off-by-one
359 // nb.get_prev_cell(nb.get_cell(1)) is null
359 // nb.get_prev_cell(nb.get_cell(1)) is null
360 var result = null;
360 var result = null;
361 var index = this.find_cell_index(cell);
361 var index = this.find_cell_index(cell);
362 if (index !== null && index > 1) {
362 if (index !== null && index > 1) {
363 result = this.get_cell(index-1);
363 result = this.get_cell(index-1);
364 }
364 }
365 return result;
365 return result;
366 };
366 };
367
367
368 /**
368 /**
369 * Get the numeric index of a given cell.
369 * Get the numeric index of a given cell.
370 *
370 *
371 * @method find_cell_index
371 * @method find_cell_index
372 * @param {Cell} cell The provided cell
372 * @param {Cell} cell The provided cell
373 * @return {Number} The cell's numeric index
373 * @return {Number} The cell's numeric index
374 */
374 */
375 Notebook.prototype.find_cell_index = function (cell) {
375 Notebook.prototype.find_cell_index = function (cell) {
376 var result = null;
376 var result = null;
377 this.get_cell_elements().filter(function (index) {
377 this.get_cell_elements().filter(function (index) {
378 if ($(this).data("cell") === cell) {
378 if ($(this).data("cell") === cell) {
379 result = index;
379 result = index;
380 }
380 }
381 });
381 });
382 return result;
382 return result;
383 };
383 };
384
384
385 /**
385 /**
386 * Get a given index , or the selected index if none is provided.
386 * Get a given index , or the selected index if none is provided.
387 *
387 *
388 * @method index_or_selected
388 * @method index_or_selected
389 * @param {Number} index A cell's index
389 * @param {Number} index A cell's index
390 * @return {Number} The given index, or selected index if none is provided.
390 * @return {Number} The given index, or selected index if none is provided.
391 */
391 */
392 Notebook.prototype.index_or_selected = function (index) {
392 Notebook.prototype.index_or_selected = function (index) {
393 var i;
393 var i;
394 if (index === undefined || index === null) {
394 if (index === undefined || index === null) {
395 i = this.get_selected_index();
395 i = this.get_selected_index();
396 if (i === null) {
396 if (i === null) {
397 i = 0;
397 i = 0;
398 }
398 }
399 } else {
399 } else {
400 i = index;
400 i = index;
401 }
401 }
402 return i;
402 return i;
403 };
403 };
404
404
405 /**
405 /**
406 * Get the currently selected cell.
406 * Get the currently selected cell.
407 * @method get_selected_cell
407 * @method get_selected_cell
408 * @return {Cell} The selected cell
408 * @return {Cell} The selected cell
409 */
409 */
410 Notebook.prototype.get_selected_cell = function () {
410 Notebook.prototype.get_selected_cell = function () {
411 var index = this.get_selected_index();
411 var index = this.get_selected_index();
412 return this.get_cell(index);
412 return this.get_cell(index);
413 };
413 };
414
414
415 /**
415 /**
416 * Check whether a cell index is valid.
416 * Check whether a cell index is valid.
417 *
417 *
418 * @method is_valid_cell_index
418 * @method is_valid_cell_index
419 * @param {Number} index A cell index
419 * @param {Number} index A cell index
420 * @return True if the index is valid, false otherwise
420 * @return True if the index is valid, false otherwise
421 */
421 */
422 Notebook.prototype.is_valid_cell_index = function (index) {
422 Notebook.prototype.is_valid_cell_index = function (index) {
423 if (index !== null && index >= 0 && index < this.ncells()) {
423 if (index !== null && index >= 0 && index < this.ncells()) {
424 return true;
424 return true;
425 } else {
425 } else {
426 return false;
426 return false;
427 }
427 }
428 };
428 };
429
429
430 /**
430 /**
431 * Get the index of the currently selected cell.
431 * Get the index of the currently selected cell.
432
432
433 * @method get_selected_index
433 * @method get_selected_index
434 * @return {Number} The selected cell's numeric index
434 * @return {Number} The selected cell's numeric index
435 */
435 */
436 Notebook.prototype.get_selected_index = function () {
436 Notebook.prototype.get_selected_index = function () {
437 var result = null;
437 var result = null;
438 this.get_cell_elements().filter(function (index) {
438 this.get_cell_elements().filter(function (index) {
439 if ($(this).data("cell").selected === true) {
439 if ($(this).data("cell").selected === true) {
440 result = index;
440 result = index;
441 }
441 }
442 });
442 });
443 return result;
443 return result;
444 };
444 };
445
445
446
446
447 // Cell selection.
447 // Cell selection.
448
448
449 /**
449 /**
450 * Programmatically select a cell.
450 * Programmatically select a cell.
451 *
451 *
452 * @method select
452 * @method select
453 * @param {Number} index A cell's index
453 * @param {Number} index A cell's index
454 * @return {Notebook} This notebook
454 * @return {Notebook} This notebook
455 */
455 */
456 Notebook.prototype.select = function (index) {
456 Notebook.prototype.select = function (index) {
457 if (this.is_valid_cell_index(index)) {
457 if (this.is_valid_cell_index(index)) {
458 var sindex = this.get_selected_index();
458 var sindex = this.get_selected_index();
459 if (sindex !== null && index !== sindex) {
459 if (sindex !== null && index !== sindex) {
460 this.get_cell(sindex).unselect();
460 this.get_cell(sindex).unselect();
461 }
461 }
462 var cell = this.get_cell(index);
462 var cell = this.get_cell(index);
463 cell.select();
463 cell.select();
464 if (cell.cell_type === 'heading') {
464 if (cell.cell_type === 'heading') {
465 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
465 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
466 {'cell_type':cell.cell_type,level:cell.level}
466 {'cell_type':cell.cell_type,level:cell.level}
467 );
467 );
468 } else {
468 } else {
469 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
469 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
470 {'cell_type':cell.cell_type}
470 {'cell_type':cell.cell_type}
471 );
471 );
472 }
472 }
473 }
473 }
474 return this;
474 return this;
475 };
475 };
476
476
477 /**
477 /**
478 * Programmatically select the next cell.
478 * Programmatically select the next cell.
479 *
479 *
480 * @method select_next
480 * @method select_next
481 * @return {Notebook} This notebook
481 * @return {Notebook} This notebook
482 */
482 */
483 Notebook.prototype.select_next = function () {
483 Notebook.prototype.select_next = function () {
484 var index = this.get_selected_index();
484 var index = this.get_selected_index();
485 this.select(index+1);
485 this.select(index+1);
486 return this;
486 return this;
487 };
487 };
488
488
489 /**
489 /**
490 * Programmatically select the previous cell.
490 * Programmatically select the previous cell.
491 *
491 *
492 * @method select_prev
492 * @method select_prev
493 * @return {Notebook} This notebook
493 * @return {Notebook} This notebook
494 */
494 */
495 Notebook.prototype.select_prev = function () {
495 Notebook.prototype.select_prev = function () {
496 var index = this.get_selected_index();
496 var index = this.get_selected_index();
497 this.select(index-1);
497 this.select(index-1);
498 return this;
498 return this;
499 };
499 };
500
500
501
501
502 // Edit/Command mode
502 // Edit/Command mode
503
503
504 /**
504 /**
505 * Gets the index of the cell that is in edit mode.
505 * Gets the index of the cell that is in edit mode.
506 *
506 *
507 * @method get_edit_index
507 * @method get_edit_index
508 *
508 *
509 * @return index {int}
509 * @return index {int}
510 **/
510 **/
511 Notebook.prototype.get_edit_index = function () {
511 Notebook.prototype.get_edit_index = function () {
512 var result = null;
512 var result = null;
513 this.get_cell_elements().filter(function (index) {
513 this.get_cell_elements().filter(function (index) {
514 if ($(this).data("cell").mode === 'edit') {
514 if ($(this).data("cell").mode === 'edit') {
515 result = index;
515 result = index;
516 }
516 }
517 });
517 });
518 return result;
518 return result;
519 };
519 };
520
520
521 /**
521 /**
522 * Make the notebook enter command mode.
522 * Make the notebook enter command mode.
523 *
523 *
524 * @method command_mode
524 * @method command_mode
525 **/
525 **/
526 Notebook.prototype.command_mode = function () {
526 Notebook.prototype.command_mode = function () {
527 // Make sure there isn't an edit mode cell lingering around.
527 // Make sure there isn't an edit mode cell lingering around.
528 var cell = this.get_cell(this.get_edit_index());
528 var cell = this.get_cell(this.get_edit_index());
529 if (cell) {
529 if (cell) {
530 cell.command_mode();
530 cell.command_mode();
531 }
531 }
532
532
533 // Notify the keyboard manager if this is a change of mode for the
533 // Notify the keyboard manager if this is a change of mode for the
534 // notebook as a whole.
534 // notebook as a whole.
535 if (this.mode !== 'command') {
535 if (this.mode !== 'command') {
536 this.mode = 'command';
536 this.mode = 'command';
537 $([IPython.events]).trigger('command_mode.Notebook');
537 $([IPython.events]).trigger('command_mode.Notebook');
538 IPython.keyboard_manager.command_mode();
538 IPython.keyboard_manager.command_mode();
539 }
539 }
540 };
540 };
541
541
542 /**
542 /**
543 * Handle when a cell fires it's edit_mode event.
543 * Handle when a cell fires it's edit_mode event.
544 *
544 *
545 * @method handle_edit_mode
545 * @method handle_edit_mode
546 * @param [index] {int} Cell index to select. If no index is provided,
546 * @param [index] {int} Cell index to select. If no index is provided,
547 * the current selected cell is used.
547 * the current selected cell is used.
548 **/
548 **/
549 Notebook.prototype.handle_edit_mode = function (index) {
549 Notebook.prototype.handle_edit_mode = function (index) {
550 // Make sure the cell exists.
550 // Make sure the cell exists.
551 var cell = this.get_cell(index);
551 var cell = this.get_cell(index);
552 if (cell === null) { return; }
552 if (cell === null) { return; }
553
553
554 // Set the cell to edit mode and notify the keyboard manager if this
554 // Set the cell to edit mode and notify the keyboard manager if this
555 // is a change of mode for the notebook as a whole.
555 // is a change of mode for the notebook as a whole.
556 if (this.mode !== 'edit') {
556 if (this.mode !== 'edit') {
557 cell.edit_mode();
557 cell.edit_mode();
558 this.mode = 'edit';
558 this.mode = 'edit';
559 $([IPython.events]).trigger('edit_mode.Notebook');
559 $([IPython.events]).trigger('edit_mode.Notebook');
560 IPython.keyboard_manager.edit_mode();
560 IPython.keyboard_manager.edit_mode();
561 }
561 }
562 };
562 };
563
563
564 /**
564 /**
565 * Make a cell enter edit mode.
565 * Make a cell enter edit mode.
566 *
566 *
567 * @method edit_mode
567 * @method edit_mode
568 * @param [index] {int} Cell index to select. If no index is provided,
568 * @param [index] {int} Cell index to select. If no index is provided,
569 * the current selected cell is used.
569 * the current selected cell is used.
570 **/
570 **/
571 Notebook.prototype.edit_mode = function (index) {
571 Notebook.prototype.edit_mode = function (index) {
572 if (index===undefined) {
572 if (index===undefined) {
573 index = this.get_selected_index();
573 index = this.get_selected_index();
574 }
574 }
575 // Make sure the cell exists.
575 // Make sure the cell exists.
576 var cell = this.get_cell(index);
576 var cell = this.get_cell(index);
577 if (cell === null) { return; }
577 if (cell === null) { return; }
578 if (cell.mode != 'edit') {
578 if (cell.mode != 'edit') {
579 cell.unrender();
579 cell.unrender();
580 cell.focus_editor();
580 cell.focus_editor();
581 }
581 }
582 };
582 };
583
583
584 /**
584 /**
585 * Focus the currently selected cell.
585 * Focus the currently selected cell.
586 *
586 *
587 * @method focus_cell
587 * @method focus_cell
588 **/
588 **/
589 Notebook.prototype.focus_cell = function () {
589 Notebook.prototype.focus_cell = function () {
590 var cell = this.get_selected_cell();
590 var cell = this.get_selected_cell();
591 if (cell === null) {return;} // No cell is selected
591 if (cell === null) {return;} // No cell is selected
592 cell.focus_cell();
592 cell.focus_cell();
593 };
593 };
594
594
595 // Cell movement
595 // Cell movement
596
596
597 /**
597 /**
598 * Move given (or selected) cell up and select it.
598 * Move given (or selected) cell up and select it.
599 *
599 *
600 * @method move_cell_up
600 * @method move_cell_up
601 * @param [index] {integer} cell index
601 * @param [index] {integer} cell index
602 * @return {Notebook} This notebook
602 * @return {Notebook} This notebook
603 **/
603 **/
604 Notebook.prototype.move_cell_up = function (index) {
604 Notebook.prototype.move_cell_up = function (index) {
605 var i = this.index_or_selected(index);
605 var i = this.index_or_selected(index);
606 if (this.is_valid_cell_index(i) && i > 0) {
606 if (this.is_valid_cell_index(i) && i > 0) {
607 var pivot = this.get_cell_element(i-1);
607 var pivot = this.get_cell_element(i-1);
608 var tomove = this.get_cell_element(i);
608 var tomove = this.get_cell_element(i);
609 if (pivot !== null && tomove !== null) {
609 if (pivot !== null && tomove !== null) {
610 tomove.detach();
610 tomove.detach();
611 pivot.before(tomove);
611 pivot.before(tomove);
612 this.select(i-1);
612 this.select(i-1);
613 var cell = this.get_selected_cell();
613 var cell = this.get_selected_cell();
614 cell.focus_cell();
614 cell.focus_cell();
615 }
615 }
616 this.set_dirty(true);
616 this.set_dirty(true);
617 }
617 }
618 return this;
618 return this;
619 };
619 };
620
620
621
621
622 /**
622 /**
623 * Move given (or selected) cell down and select it
623 * Move given (or selected) cell down and select it
624 *
624 *
625 * @method move_cell_down
625 * @method move_cell_down
626 * @param [index] {integer} cell index
626 * @param [index] {integer} cell index
627 * @return {Notebook} This notebook
627 * @return {Notebook} This notebook
628 **/
628 **/
629 Notebook.prototype.move_cell_down = function (index) {
629 Notebook.prototype.move_cell_down = function (index) {
630 var i = this.index_or_selected(index);
630 var i = this.index_or_selected(index);
631 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
631 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
632 var pivot = this.get_cell_element(i+1);
632 var pivot = this.get_cell_element(i+1);
633 var tomove = this.get_cell_element(i);
633 var tomove = this.get_cell_element(i);
634 if (pivot !== null && tomove !== null) {
634 if (pivot !== null && tomove !== null) {
635 tomove.detach();
635 tomove.detach();
636 pivot.after(tomove);
636 pivot.after(tomove);
637 this.select(i+1);
637 this.select(i+1);
638 var cell = this.get_selected_cell();
638 var cell = this.get_selected_cell();
639 cell.focus_cell();
639 cell.focus_cell();
640 }
640 }
641 }
641 }
642 this.set_dirty();
642 this.set_dirty();
643 return this;
643 return this;
644 };
644 };
645
645
646
646
647 // Insertion, deletion.
647 // Insertion, deletion.
648
648
649 /**
649 /**
650 * Delete a cell from the notebook.
650 * Delete a cell from the notebook.
651 *
651 *
652 * @method delete_cell
652 * @method delete_cell
653 * @param [index] A cell's numeric index
653 * @param [index] A cell's numeric index
654 * @return {Notebook} This notebook
654 * @return {Notebook} This notebook
655 */
655 */
656 Notebook.prototype.delete_cell = function (index) {
656 Notebook.prototype.delete_cell = function (index) {
657 var i = this.index_or_selected(index);
657 var i = this.index_or_selected(index);
658 var cell = this.get_selected_cell();
658 var cell = this.get_selected_cell();
659 this.undelete_backup = cell.toJSON();
659 this.undelete_backup = cell.toJSON();
660 $('#undelete_cell').removeClass('disabled');
660 $('#undelete_cell').removeClass('disabled');
661 if (this.is_valid_cell_index(i)) {
661 if (this.is_valid_cell_index(i)) {
662 var old_ncells = this.ncells();
662 var old_ncells = this.ncells();
663 var ce = this.get_cell_element(i);
663 var ce = this.get_cell_element(i);
664 ce.remove();
664 ce.remove();
665 if (i === 0) {
665 if (i === 0) {
666 // Always make sure we have at least one cell.
666 // Always make sure we have at least one cell.
667 if (old_ncells === 1) {
667 if (old_ncells === 1) {
668 this.insert_cell_below('code');
668 this.insert_cell_below('code');
669 }
669 }
670 this.select(0);
670 this.select(0);
671 this.undelete_index = 0;
671 this.undelete_index = 0;
672 this.undelete_below = false;
672 this.undelete_below = false;
673 } else if (i === old_ncells-1 && i !== 0) {
673 } else if (i === old_ncells-1 && i !== 0) {
674 this.select(i-1);
674 this.select(i-1);
675 this.undelete_index = i - 1;
675 this.undelete_index = i - 1;
676 this.undelete_below = true;
676 this.undelete_below = true;
677 } else {
677 } else {
678 this.select(i);
678 this.select(i);
679 this.undelete_index = i;
679 this.undelete_index = i;
680 this.undelete_below = false;
680 this.undelete_below = false;
681 }
681 }
682 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
682 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
683 this.set_dirty(true);
683 this.set_dirty(true);
684 }
684 }
685 return this;
685 return this;
686 };
686 };
687
687
688 /**
688 /**
689 * Restore the most recently deleted cell.
689 * Restore the most recently deleted cell.
690 *
690 *
691 * @method undelete
691 * @method undelete
692 */
692 */
693 Notebook.prototype.undelete_cell = function() {
693 Notebook.prototype.undelete_cell = function() {
694 if (this.undelete_backup !== null && this.undelete_index !== null) {
694 if (this.undelete_backup !== null && this.undelete_index !== null) {
695 var current_index = this.get_selected_index();
695 var current_index = this.get_selected_index();
696 if (this.undelete_index < current_index) {
696 if (this.undelete_index < current_index) {
697 current_index = current_index + 1;
697 current_index = current_index + 1;
698 }
698 }
699 if (this.undelete_index >= this.ncells()) {
699 if (this.undelete_index >= this.ncells()) {
700 this.select(this.ncells() - 1);
700 this.select(this.ncells() - 1);
701 }
701 }
702 else {
702 else {
703 this.select(this.undelete_index);
703 this.select(this.undelete_index);
704 }
704 }
705 var cell_data = this.undelete_backup;
705 var cell_data = this.undelete_backup;
706 var new_cell = null;
706 var new_cell = null;
707 if (this.undelete_below) {
707 if (this.undelete_below) {
708 new_cell = this.insert_cell_below(cell_data.cell_type);
708 new_cell = this.insert_cell_below(cell_data.cell_type);
709 } else {
709 } else {
710 new_cell = this.insert_cell_above(cell_data.cell_type);
710 new_cell = this.insert_cell_above(cell_data.cell_type);
711 }
711 }
712 new_cell.fromJSON(cell_data);
712 new_cell.fromJSON(cell_data);
713 if (this.undelete_below) {
713 if (this.undelete_below) {
714 this.select(current_index+1);
714 this.select(current_index+1);
715 } else {
715 } else {
716 this.select(current_index);
716 this.select(current_index);
717 }
717 }
718 this.undelete_backup = null;
718 this.undelete_backup = null;
719 this.undelete_index = null;
719 this.undelete_index = null;
720 }
720 }
721 $('#undelete_cell').addClass('disabled');
721 $('#undelete_cell').addClass('disabled');
722 };
722 };
723
723
724 /**
724 /**
725 * Insert a cell so that after insertion the cell is at given index.
725 * Insert a cell so that after insertion the cell is at given index.
726 *
726 *
727 * Similar to insert_above, but index parameter is mandatory
727 * Similar to insert_above, but index parameter is mandatory
728 *
728 *
729 * Index will be brought back into the accissible range [0,n]
729 * Index will be brought back into the accissible range [0,n]
730 *
730 *
731 * @method insert_cell_at_index
731 * @method insert_cell_at_index
732 * @param type {string} in ['code','markdown','heading']
732 * @param type {string} in ['code','markdown','heading']
733 * @param [index] {int} a valid index where to inser cell
733 * @param [index] {int} a valid index where to inser cell
734 *
734 *
735 * @return cell {cell|null} created cell or null
735 * @return cell {cell|null} created cell or null
736 **/
736 **/
737 Notebook.prototype.insert_cell_at_index = function(type, index){
737 Notebook.prototype.insert_cell_at_index = function(type, index){
738
738
739 var ncells = this.ncells();
739 var ncells = this.ncells();
740 index = Math.min(index,ncells);
740 index = Math.min(index,ncells);
741 index = Math.max(index,0);
741 index = Math.max(index,0);
742 var cell = null;
742 var cell = null;
743
743
744 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
744 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
745 if (type === 'code') {
745 if (type === 'code') {
746 cell = new IPython.CodeCell(this.kernel);
746 cell = new IPython.CodeCell(this.kernel);
747 cell.set_input_prompt();
747 cell.set_input_prompt();
748 } else if (type === 'markdown') {
748 } else if (type === 'markdown') {
749 cell = new IPython.MarkdownCell();
749 cell = new IPython.MarkdownCell();
750 } else if (type === 'raw') {
750 } else if (type === 'raw') {
751 cell = new IPython.RawCell();
751 cell = new IPython.RawCell();
752 } else if (type === 'heading') {
752 } else if (type === 'heading') {
753 cell = new IPython.HeadingCell();
753 cell = new IPython.HeadingCell();
754 }
754 }
755
755
756 if(this._insert_element_at_index(cell.element,index)) {
756 if(this._insert_element_at_index(cell.element,index)) {
757 cell.render();
757 cell.render();
758 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
758 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
759 cell.refresh();
759 cell.refresh();
760 // We used to select the cell after we refresh it, but there
760 // We used to select the cell after we refresh it, but there
761 // are now cases were this method is called where select is
761 // are now cases were this method is called where select is
762 // not appropriate. The selection logic should be handled by the
762 // not appropriate. The selection logic should be handled by the
763 // caller of the the top level insert_cell methods.
763 // caller of the the top level insert_cell methods.
764 this.set_dirty(true);
764 this.set_dirty(true);
765 }
765 }
766 }
766 }
767 return cell;
767 return cell;
768
768
769 };
769 };
770
770
771 /**
771 /**
772 * Insert an element at given cell index.
772 * Insert an element at given cell index.
773 *
773 *
774 * @method _insert_element_at_index
774 * @method _insert_element_at_index
775 * @param element {dom element} a cell element
775 * @param element {dom element} a cell element
776 * @param [index] {int} a valid index where to inser cell
776 * @param [index] {int} a valid index where to inser cell
777 * @private
777 * @private
778 *
778 *
779 * return true if everything whent fine.
779 * return true if everything whent fine.
780 **/
780 **/
781 Notebook.prototype._insert_element_at_index = function(element, index){
781 Notebook.prototype._insert_element_at_index = function(element, index){
782 if (element === undefined){
782 if (element === undefined){
783 return false;
783 return false;
784 }
784 }
785
785
786 var ncells = this.ncells();
786 var ncells = this.ncells();
787
787
788 if (ncells === 0) {
788 if (ncells === 0) {
789 // special case append if empty
789 // special case append if empty
790 this.element.find('div.end_space').before(element);
790 this.element.find('div.end_space').before(element);
791 } else if ( ncells === index ) {
791 } else if ( ncells === index ) {
792 // special case append it the end, but not empty
792 // special case append it the end, but not empty
793 this.get_cell_element(index-1).after(element);
793 this.get_cell_element(index-1).after(element);
794 } else if (this.is_valid_cell_index(index)) {
794 } else if (this.is_valid_cell_index(index)) {
795 // otherwise always somewhere to append to
795 // otherwise always somewhere to append to
796 this.get_cell_element(index).before(element);
796 this.get_cell_element(index).before(element);
797 } else {
797 } else {
798 return false;
798 return false;
799 }
799 }
800
800
801 if (this.undelete_index !== null && index <= this.undelete_index) {
801 if (this.undelete_index !== null && index <= this.undelete_index) {
802 this.undelete_index = this.undelete_index + 1;
802 this.undelete_index = this.undelete_index + 1;
803 this.set_dirty(true);
803 this.set_dirty(true);
804 }
804 }
805 return true;
805 return true;
806 };
806 };
807
807
808 /**
808 /**
809 * Insert a cell of given type above given index, or at top
809 * Insert a cell of given type above given index, or at top
810 * of notebook if index smaller than 0.
810 * of notebook if index smaller than 0.
811 *
811 *
812 * default index value is the one of currently selected cell
812 * default index value is the one of currently selected cell
813 *
813 *
814 * @method insert_cell_above
814 * @method insert_cell_above
815 * @param type {string} cell type
815 * @param type {string} cell type
816 * @param [index] {integer}
816 * @param [index] {integer}
817 *
817 *
818 * @return handle to created cell or null
818 * @return handle to created cell or null
819 **/
819 **/
820 Notebook.prototype.insert_cell_above = function (type, index) {
820 Notebook.prototype.insert_cell_above = function (type, index) {
821 index = this.index_or_selected(index);
821 index = this.index_or_selected(index);
822 return this.insert_cell_at_index(type, index);
822 return this.insert_cell_at_index(type, index);
823 };
823 };
824
824
825 /**
825 /**
826 * Insert a cell of given type below given index, or at bottom
826 * Insert a cell of given type below given index, or at bottom
827 * of notebook if index greater thatn number of cell
827 * of notebook if index greater thatn number of cell
828 *
828 *
829 * default index value is the one of currently selected cell
829 * default index value is the one of currently selected cell
830 *
830 *
831 * @method insert_cell_below
831 * @method insert_cell_below
832 * @param type {string} cell type
832 * @param type {string} cell type
833 * @param [index] {integer}
833 * @param [index] {integer}
834 *
834 *
835 * @return handle to created cell or null
835 * @return handle to created cell or null
836 *
836 *
837 **/
837 **/
838 Notebook.prototype.insert_cell_below = function (type, index) {
838 Notebook.prototype.insert_cell_below = function (type, index) {
839 index = this.index_or_selected(index);
839 index = this.index_or_selected(index);
840 return this.insert_cell_at_index(type, index+1);
840 return this.insert_cell_at_index(type, index+1);
841 };
841 };
842
842
843
843
844 /**
844 /**
845 * Insert cell at end of notebook
845 * Insert cell at end of notebook
846 *
846 *
847 * @method insert_cell_at_bottom
847 * @method insert_cell_at_bottom
848 * @param {String} type cell type
848 * @param {String} type cell type
849 *
849 *
850 * @return the added cell; or null
850 * @return the added cell; or null
851 **/
851 **/
852 Notebook.prototype.insert_cell_at_bottom = function (type){
852 Notebook.prototype.insert_cell_at_bottom = function (type){
853 var len = this.ncells();
853 var len = this.ncells();
854 return this.insert_cell_below(type,len-1);
854 return this.insert_cell_below(type,len-1);
855 };
855 };
856
856
857 /**
857 /**
858 * Turn a cell into a code cell.
858 * Turn a cell into a code cell.
859 *
859 *
860 * @method to_code
860 * @method to_code
861 * @param {Number} [index] A cell's index
861 * @param {Number} [index] A cell's index
862 */
862 */
863 Notebook.prototype.to_code = function (index) {
863 Notebook.prototype.to_code = function (index) {
864 var i = this.index_or_selected(index);
864 var i = this.index_or_selected(index);
865 if (this.is_valid_cell_index(i)) {
865 if (this.is_valid_cell_index(i)) {
866 var source_element = this.get_cell_element(i);
866 var source_element = this.get_cell_element(i);
867 var source_cell = source_element.data("cell");
867 var source_cell = source_element.data("cell");
868 if (!(source_cell instanceof IPython.CodeCell)) {
868 if (!(source_cell instanceof IPython.CodeCell)) {
869 var target_cell = this.insert_cell_below('code',i);
869 var target_cell = this.insert_cell_below('code',i);
870 var text = source_cell.get_text();
870 var text = source_cell.get_text();
871 if (text === source_cell.placeholder) {
871 if (text === source_cell.placeholder) {
872 text = '';
872 text = '';
873 }
873 }
874 target_cell.set_text(text);
874 target_cell.set_text(text);
875 // make this value the starting point, so that we can only undo
875 // make this value the starting point, so that we can only undo
876 // to this state, instead of a blank cell
876 // to this state, instead of a blank cell
877 target_cell.code_mirror.clearHistory();
877 target_cell.code_mirror.clearHistory();
878 source_element.remove();
878 source_element.remove();
879 this.select(i);
879 this.select(i);
880 this.set_dirty(true);
880 this.set_dirty(true);
881 }
881 }
882 }
882 }
883 };
883 };
884
884
885 /**
885 /**
886 * Turn a cell into a Markdown cell.
886 * Turn a cell into a Markdown cell.
887 *
887 *
888 * @method to_markdown
888 * @method to_markdown
889 * @param {Number} [index] A cell's index
889 * @param {Number} [index] A cell's index
890 */
890 */
891 Notebook.prototype.to_markdown = function (index) {
891 Notebook.prototype.to_markdown = function (index) {
892 var i = this.index_or_selected(index);
892 var i = this.index_or_selected(index);
893 if (this.is_valid_cell_index(i)) {
893 if (this.is_valid_cell_index(i)) {
894 var source_element = this.get_cell_element(i);
894 var source_element = this.get_cell_element(i);
895 var source_cell = source_element.data("cell");
895 var source_cell = source_element.data("cell");
896 if (!(source_cell instanceof IPython.MarkdownCell)) {
896 if (!(source_cell instanceof IPython.MarkdownCell)) {
897 var target_cell = this.insert_cell_below('markdown',i);
897 var target_cell = this.insert_cell_below('markdown',i);
898 var text = source_cell.get_text();
898 var text = source_cell.get_text();
899 if (text === source_cell.placeholder) {
899 if (text === source_cell.placeholder) {
900 text = '';
900 text = '';
901 }
901 }
902 // We must show the editor before setting its contents
902 // We must show the editor before setting its contents
903 target_cell.unrender();
903 target_cell.unrender();
904 target_cell.set_text(text);
904 target_cell.set_text(text);
905 // make this value the starting point, so that we can only undo
905 // make this value the starting point, so that we can only undo
906 // to this state, instead of a blank cell
906 // to this state, instead of a blank cell
907 target_cell.code_mirror.clearHistory();
907 target_cell.code_mirror.clearHistory();
908 source_element.remove();
908 source_element.remove();
909 this.select(i);
909 this.select(i);
910 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
910 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
911 target_cell.render();
911 target_cell.render();
912 }
912 }
913 this.set_dirty(true);
913 this.set_dirty(true);
914 }
914 }
915 }
915 }
916 };
916 };
917
917
918 /**
918 /**
919 * Turn a cell into a raw text cell.
919 * Turn a cell into a raw text cell.
920 *
920 *
921 * @method to_raw
921 * @method to_raw
922 * @param {Number} [index] A cell's index
922 * @param {Number} [index] A cell's index
923 */
923 */
924 Notebook.prototype.to_raw = function (index) {
924 Notebook.prototype.to_raw = function (index) {
925 var i = this.index_or_selected(index);
925 var i = this.index_or_selected(index);
926 if (this.is_valid_cell_index(i)) {
926 if (this.is_valid_cell_index(i)) {
927 var source_element = this.get_cell_element(i);
927 var source_element = this.get_cell_element(i);
928 var source_cell = source_element.data("cell");
928 var source_cell = source_element.data("cell");
929 var target_cell = null;
929 var target_cell = null;
930 if (!(source_cell instanceof IPython.RawCell)) {
930 if (!(source_cell instanceof IPython.RawCell)) {
931 target_cell = this.insert_cell_below('raw',i);
931 target_cell = this.insert_cell_below('raw',i);
932 var text = source_cell.get_text();
932 var text = source_cell.get_text();
933 if (text === source_cell.placeholder) {
933 if (text === source_cell.placeholder) {
934 text = '';
934 text = '';
935 }
935 }
936 // We must show the editor before setting its contents
936 // We must show the editor before setting its contents
937 target_cell.unrender();
937 target_cell.unrender();
938 target_cell.set_text(text);
938 target_cell.set_text(text);
939 // make this value the starting point, so that we can only undo
939 // make this value the starting point, so that we can only undo
940 // to this state, instead of a blank cell
940 // to this state, instead of a blank cell
941 target_cell.code_mirror.clearHistory();
941 target_cell.code_mirror.clearHistory();
942 source_element.remove();
942 source_element.remove();
943 this.select(i);
943 this.select(i);
944 this.set_dirty(true);
944 this.set_dirty(true);
945 }
945 }
946 }
946 }
947 };
947 };
948
948
949 /**
949 /**
950 * Turn a cell into a heading cell.
950 * Turn a cell into a heading cell.
951 *
951 *
952 * @method to_heading
952 * @method to_heading
953 * @param {Number} [index] A cell's index
953 * @param {Number} [index] A cell's index
954 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
954 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
955 */
955 */
956 Notebook.prototype.to_heading = function (index, level) {
956 Notebook.prototype.to_heading = function (index, level) {
957 level = level || 1;
957 level = level || 1;
958 var i = this.index_or_selected(index);
958 var i = this.index_or_selected(index);
959 if (this.is_valid_cell_index(i)) {
959 if (this.is_valid_cell_index(i)) {
960 var source_element = this.get_cell_element(i);
960 var source_element = this.get_cell_element(i);
961 var source_cell = source_element.data("cell");
961 var source_cell = source_element.data("cell");
962 var target_cell = null;
962 var target_cell = null;
963 if (source_cell instanceof IPython.HeadingCell) {
963 if (source_cell instanceof IPython.HeadingCell) {
964 source_cell.set_level(level);
964 source_cell.set_level(level);
965 } else {
965 } else {
966 target_cell = this.insert_cell_below('heading',i);
966 target_cell = this.insert_cell_below('heading',i);
967 var text = source_cell.get_text();
967 var text = source_cell.get_text();
968 if (text === source_cell.placeholder) {
968 if (text === source_cell.placeholder) {
969 text = '';
969 text = '';
970 }
970 }
971 // We must show the editor before setting its contents
971 // We must show the editor before setting its contents
972 target_cell.set_level(level);
972 target_cell.set_level(level);
973 target_cell.unrender();
973 target_cell.unrender();
974 target_cell.set_text(text);
974 target_cell.set_text(text);
975 // make this value the starting point, so that we can only undo
975 // make this value the starting point, so that we can only undo
976 // to this state, instead of a blank cell
976 // to this state, instead of a blank cell
977 target_cell.code_mirror.clearHistory();
977 target_cell.code_mirror.clearHistory();
978 source_element.remove();
978 source_element.remove();
979 this.select(i);
979 this.select(i);
980 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
980 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
981 target_cell.render();
981 target_cell.render();
982 }
982 }
983 }
983 }
984 this.set_dirty(true);
984 this.set_dirty(true);
985 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
985 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
986 {'cell_type':'heading',level:level}
986 {'cell_type':'heading',level:level}
987 );
987 );
988 }
988 }
989 };
989 };
990
990
991
991
992 // Cut/Copy/Paste
992 // Cut/Copy/Paste
993
993
994 /**
994 /**
995 * Enable UI elements for pasting cells.
995 * Enable UI elements for pasting cells.
996 *
996 *
997 * @method enable_paste
997 * @method enable_paste
998 */
998 */
999 Notebook.prototype.enable_paste = function () {
999 Notebook.prototype.enable_paste = function () {
1000 var that = this;
1000 var that = this;
1001 if (!this.paste_enabled) {
1001 if (!this.paste_enabled) {
1002 $('#paste_cell_replace').removeClass('disabled')
1002 $('#paste_cell_replace').removeClass('disabled')
1003 .on('click', function () {that.paste_cell_replace();});
1003 .on('click', function () {that.paste_cell_replace();});
1004 $('#paste_cell_above').removeClass('disabled')
1004 $('#paste_cell_above').removeClass('disabled')
1005 .on('click', function () {that.paste_cell_above();});
1005 .on('click', function () {that.paste_cell_above();});
1006 $('#paste_cell_below').removeClass('disabled')
1006 $('#paste_cell_below').removeClass('disabled')
1007 .on('click', function () {that.paste_cell_below();});
1007 .on('click', function () {that.paste_cell_below();});
1008 this.paste_enabled = true;
1008 this.paste_enabled = true;
1009 }
1009 }
1010 };
1010 };
1011
1011
1012 /**
1012 /**
1013 * Disable UI elements for pasting cells.
1013 * Disable UI elements for pasting cells.
1014 *
1014 *
1015 * @method disable_paste
1015 * @method disable_paste
1016 */
1016 */
1017 Notebook.prototype.disable_paste = function () {
1017 Notebook.prototype.disable_paste = function () {
1018 if (this.paste_enabled) {
1018 if (this.paste_enabled) {
1019 $('#paste_cell_replace').addClass('disabled').off('click');
1019 $('#paste_cell_replace').addClass('disabled').off('click');
1020 $('#paste_cell_above').addClass('disabled').off('click');
1020 $('#paste_cell_above').addClass('disabled').off('click');
1021 $('#paste_cell_below').addClass('disabled').off('click');
1021 $('#paste_cell_below').addClass('disabled').off('click');
1022 this.paste_enabled = false;
1022 this.paste_enabled = false;
1023 }
1023 }
1024 };
1024 };
1025
1025
1026 /**
1026 /**
1027 * Cut a cell.
1027 * Cut a cell.
1028 *
1028 *
1029 * @method cut_cell
1029 * @method cut_cell
1030 */
1030 */
1031 Notebook.prototype.cut_cell = function () {
1031 Notebook.prototype.cut_cell = function () {
1032 this.copy_cell();
1032 this.copy_cell();
1033 this.delete_cell();
1033 this.delete_cell();
1034 };
1034 };
1035
1035
1036 /**
1036 /**
1037 * Copy a cell.
1037 * Copy a cell.
1038 *
1038 *
1039 * @method copy_cell
1039 * @method copy_cell
1040 */
1040 */
1041 Notebook.prototype.copy_cell = function () {
1041 Notebook.prototype.copy_cell = function () {
1042 var cell = this.get_selected_cell();
1042 var cell = this.get_selected_cell();
1043 this.clipboard = cell.toJSON();
1043 this.clipboard = cell.toJSON();
1044 this.enable_paste();
1044 this.enable_paste();
1045 };
1045 };
1046
1046
1047 /**
1047 /**
1048 * Replace the selected cell with a cell in the clipboard.
1048 * Replace the selected cell with a cell in the clipboard.
1049 *
1049 *
1050 * @method paste_cell_replace
1050 * @method paste_cell_replace
1051 */
1051 */
1052 Notebook.prototype.paste_cell_replace = function () {
1052 Notebook.prototype.paste_cell_replace = function () {
1053 if (this.clipboard !== null && this.paste_enabled) {
1053 if (this.clipboard !== null && this.paste_enabled) {
1054 var cell_data = this.clipboard;
1054 var cell_data = this.clipboard;
1055 var new_cell = this.insert_cell_above(cell_data.cell_type);
1055 var new_cell = this.insert_cell_above(cell_data.cell_type);
1056 new_cell.fromJSON(cell_data);
1056 new_cell.fromJSON(cell_data);
1057 var old_cell = this.get_next_cell(new_cell);
1057 var old_cell = this.get_next_cell(new_cell);
1058 this.delete_cell(this.find_cell_index(old_cell));
1058 this.delete_cell(this.find_cell_index(old_cell));
1059 this.select(this.find_cell_index(new_cell));
1059 this.select(this.find_cell_index(new_cell));
1060 }
1060 }
1061 };
1061 };
1062
1062
1063 /**
1063 /**
1064 * Paste a cell from the clipboard above the selected cell.
1064 * Paste a cell from the clipboard above the selected cell.
1065 *
1065 *
1066 * @method paste_cell_above
1066 * @method paste_cell_above
1067 */
1067 */
1068 Notebook.prototype.paste_cell_above = function () {
1068 Notebook.prototype.paste_cell_above = function () {
1069 if (this.clipboard !== null && this.paste_enabled) {
1069 if (this.clipboard !== null && this.paste_enabled) {
1070 var cell_data = this.clipboard;
1070 var cell_data = this.clipboard;
1071 var new_cell = this.insert_cell_above(cell_data.cell_type);
1071 var new_cell = this.insert_cell_above(cell_data.cell_type);
1072 new_cell.fromJSON(cell_data);
1072 new_cell.fromJSON(cell_data);
1073 new_cell.focus_cell();
1073 new_cell.focus_cell();
1074 }
1074 }
1075 };
1075 };
1076
1076
1077 /**
1077 /**
1078 * Paste a cell from the clipboard below the selected cell.
1078 * Paste a cell from the clipboard below the selected cell.
1079 *
1079 *
1080 * @method paste_cell_below
1080 * @method paste_cell_below
1081 */
1081 */
1082 Notebook.prototype.paste_cell_below = function () {
1082 Notebook.prototype.paste_cell_below = function () {
1083 if (this.clipboard !== null && this.paste_enabled) {
1083 if (this.clipboard !== null && this.paste_enabled) {
1084 var cell_data = this.clipboard;
1084 var cell_data = this.clipboard;
1085 var new_cell = this.insert_cell_below(cell_data.cell_type);
1085 var new_cell = this.insert_cell_below(cell_data.cell_type);
1086 new_cell.fromJSON(cell_data);
1086 new_cell.fromJSON(cell_data);
1087 new_cell.focus_cell();
1087 new_cell.focus_cell();
1088 }
1088 }
1089 };
1089 };
1090
1090
1091 // Split/merge
1091 // Split/merge
1092
1092
1093 /**
1093 /**
1094 * Split the selected cell into two, at the cursor.
1094 * Split the selected cell into two, at the cursor.
1095 *
1095 *
1096 * @method split_cell
1096 * @method split_cell
1097 */
1097 */
1098 Notebook.prototype.split_cell = function () {
1098 Notebook.prototype.split_cell = function () {
1099 var mdc = IPython.MarkdownCell;
1099 var mdc = IPython.MarkdownCell;
1100 var rc = IPython.RawCell;
1100 var rc = IPython.RawCell;
1101 var cell = this.get_selected_cell();
1101 var cell = this.get_selected_cell();
1102 if (cell.is_splittable()) {
1102 if (cell.is_splittable()) {
1103 var texta = cell.get_pre_cursor();
1103 var texta = cell.get_pre_cursor();
1104 var textb = cell.get_post_cursor();
1104 var textb = cell.get_post_cursor();
1105 if (cell instanceof IPython.CodeCell) {
1105 if (cell instanceof IPython.CodeCell) {
1106 // In this case the operations keep the notebook in its existing mode
1106 // In this case the operations keep the notebook in its existing mode
1107 // so we don't need to do any post-op mode changes.
1107 // so we don't need to do any post-op mode changes.
1108 cell.set_text(textb);
1108 cell.set_text(textb);
1109 var new_cell = this.insert_cell_above('code');
1109 var new_cell = this.insert_cell_above('code');
1110 new_cell.set_text(texta);
1110 new_cell.set_text(texta);
1111 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1111 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1112 // We know cell is !rendered so we can use set_text.
1112 // We know cell is !rendered so we can use set_text.
1113 cell.set_text(textb);
1113 cell.set_text(textb);
1114 var new_cell = this.insert_cell_above(cell.cell_type);
1114 var new_cell = this.insert_cell_above(cell.cell_type);
1115 // Unrender the new cell so we can call set_text.
1115 // Unrender the new cell so we can call set_text.
1116 new_cell.unrender();
1116 new_cell.unrender();
1117 new_cell.set_text(texta);
1117 new_cell.set_text(texta);
1118 }
1118 }
1119 }
1119 }
1120 };
1120 };
1121
1121
1122 /**
1122 /**
1123 * Combine the selected cell into the cell above it.
1123 * Combine the selected cell into the cell above it.
1124 *
1124 *
1125 * @method merge_cell_above
1125 * @method merge_cell_above
1126 */
1126 */
1127 Notebook.prototype.merge_cell_above = function () {
1127 Notebook.prototype.merge_cell_above = function () {
1128 var mdc = IPython.MarkdownCell;
1128 var mdc = IPython.MarkdownCell;
1129 var rc = IPython.RawCell;
1129 var rc = IPython.RawCell;
1130 var index = this.get_selected_index();
1130 var index = this.get_selected_index();
1131 var cell = this.get_cell(index);
1131 var cell = this.get_cell(index);
1132 var render = cell.rendered;
1132 var render = cell.rendered;
1133 if (!cell.is_mergeable()) {
1133 if (!cell.is_mergeable()) {
1134 return;
1134 return;
1135 }
1135 }
1136 if (index > 0) {
1136 if (index > 0) {
1137 var upper_cell = this.get_cell(index-1);
1137 var upper_cell = this.get_cell(index-1);
1138 if (!upper_cell.is_mergeable()) {
1138 if (!upper_cell.is_mergeable()) {
1139 return;
1139 return;
1140 }
1140 }
1141 var upper_text = upper_cell.get_text();
1141 var upper_text = upper_cell.get_text();
1142 var text = cell.get_text();
1142 var text = cell.get_text();
1143 if (cell instanceof IPython.CodeCell) {
1143 if (cell instanceof IPython.CodeCell) {
1144 cell.set_text(upper_text+'\n'+text);
1144 cell.set_text(upper_text+'\n'+text);
1145 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1145 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1146 cell.unrender(); // Must unrender before we set_text.
1146 cell.unrender(); // Must unrender before we set_text.
1147 cell.set_text(upper_text+'\n\n'+text);
1147 cell.set_text(upper_text+'\n\n'+text);
1148 if (render) {
1148 if (render) {
1149 // The rendered state of the final cell should match
1149 // The rendered state of the final cell should match
1150 // that of the original selected cell;
1150 // that of the original selected cell;
1151 cell.render();
1151 cell.render();
1152 }
1152 }
1153 }
1153 }
1154 this.delete_cell(index-1);
1154 this.delete_cell(index-1);
1155 this.select(this.find_cell_index(cell));
1155 this.select(this.find_cell_index(cell));
1156 }
1156 }
1157 };
1157 };
1158
1158
1159 /**
1159 /**
1160 * Combine the selected cell into the cell below it.
1160 * Combine the selected cell into the cell below it.
1161 *
1161 *
1162 * @method merge_cell_below
1162 * @method merge_cell_below
1163 */
1163 */
1164 Notebook.prototype.merge_cell_below = function () {
1164 Notebook.prototype.merge_cell_below = function () {
1165 var mdc = IPython.MarkdownCell;
1165 var mdc = IPython.MarkdownCell;
1166 var rc = IPython.RawCell;
1166 var rc = IPython.RawCell;
1167 var index = this.get_selected_index();
1167 var index = this.get_selected_index();
1168 var cell = this.get_cell(index);
1168 var cell = this.get_cell(index);
1169 var render = cell.rendered;
1169 var render = cell.rendered;
1170 if (!cell.is_mergeable()) {
1170 if (!cell.is_mergeable()) {
1171 return;
1171 return;
1172 }
1172 }
1173 if (index < this.ncells()-1) {
1173 if (index < this.ncells()-1) {
1174 var lower_cell = this.get_cell(index+1);
1174 var lower_cell = this.get_cell(index+1);
1175 if (!lower_cell.is_mergeable()) {
1175 if (!lower_cell.is_mergeable()) {
1176 return;
1176 return;
1177 }
1177 }
1178 var lower_text = lower_cell.get_text();
1178 var lower_text = lower_cell.get_text();
1179 var text = cell.get_text();
1179 var text = cell.get_text();
1180 if (cell instanceof IPython.CodeCell) {
1180 if (cell instanceof IPython.CodeCell) {
1181 cell.set_text(text+'\n'+lower_text);
1181 cell.set_text(text+'\n'+lower_text);
1182 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1182 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1183 cell.unrender(); // Must unrender before we set_text.
1183 cell.unrender(); // Must unrender before we set_text.
1184 cell.set_text(text+'\n\n'+lower_text);
1184 cell.set_text(text+'\n\n'+lower_text);
1185 if (render) {
1185 if (render) {
1186 // The rendered state of the final cell should match
1186 // The rendered state of the final cell should match
1187 // that of the original selected cell;
1187 // that of the original selected cell;
1188 cell.render();
1188 cell.render();
1189 }
1189 }
1190 }
1190 }
1191 this.delete_cell(index+1);
1191 this.delete_cell(index+1);
1192 this.select(this.find_cell_index(cell));
1192 this.select(this.find_cell_index(cell));
1193 }
1193 }
1194 };
1194 };
1195
1195
1196
1196
1197 // Cell collapsing and output clearing
1197 // Cell collapsing and output clearing
1198
1198
1199 /**
1199 /**
1200 * Hide a cell's output.
1200 * Hide a cell's output.
1201 *
1201 *
1202 * @method collapse_output
1202 * @method collapse_output
1203 * @param {Number} index A cell's numeric index
1203 * @param {Number} index A cell's numeric index
1204 */
1204 */
1205 Notebook.prototype.collapse_output = function (index) {
1205 Notebook.prototype.collapse_output = function (index) {
1206 var i = this.index_or_selected(index);
1206 var i = this.index_or_selected(index);
1207 var cell = this.get_cell(i);
1207 var cell = this.get_cell(i);
1208 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1208 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1209 cell.collapse_output();
1209 cell.collapse_output();
1210 this.set_dirty(true);
1210 this.set_dirty(true);
1211 }
1211 }
1212 };
1212 };
1213
1213
1214 /**
1214 /**
1215 * Hide each code cell's output area.
1215 * Hide each code cell's output area.
1216 *
1216 *
1217 * @method collapse_all_output
1217 * @method collapse_all_output
1218 */
1218 */
1219 Notebook.prototype.collapse_all_output = function () {
1219 Notebook.prototype.collapse_all_output = function () {
1220 $.map(this.get_cells(), function (cell, i) {
1220 $.map(this.get_cells(), function (cell, i) {
1221 if (cell instanceof IPython.CodeCell) {
1221 if (cell instanceof IPython.CodeCell) {
1222 cell.collapse_output();
1222 cell.collapse_output();
1223 }
1223 }
1224 });
1224 });
1225 // this should not be set if the `collapse` key is removed from nbformat
1225 // this should not be set if the `collapse` key is removed from nbformat
1226 this.set_dirty(true);
1226 this.set_dirty(true);
1227 };
1227 };
1228
1228
1229 /**
1229 /**
1230 * Show a cell's output.
1230 * Show a cell's output.
1231 *
1231 *
1232 * @method expand_output
1232 * @method expand_output
1233 * @param {Number} index A cell's numeric index
1233 * @param {Number} index A cell's numeric index
1234 */
1234 */
1235 Notebook.prototype.expand_output = function (index) {
1235 Notebook.prototype.expand_output = function (index) {
1236 var i = this.index_or_selected(index);
1236 var i = this.index_or_selected(index);
1237 var cell = this.get_cell(i);
1237 var cell = this.get_cell(i);
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1239 cell.expand_output();
1239 cell.expand_output();
1240 this.set_dirty(true);
1240 this.set_dirty(true);
1241 }
1241 }
1242 };
1242 };
1243
1243
1244 /**
1244 /**
1245 * Expand each code cell's output area, and remove scrollbars.
1245 * Expand each code cell's output area, and remove scrollbars.
1246 *
1246 *
1247 * @method expand_all_output
1247 * @method expand_all_output
1248 */
1248 */
1249 Notebook.prototype.expand_all_output = function () {
1249 Notebook.prototype.expand_all_output = function () {
1250 $.map(this.get_cells(), function (cell, i) {
1250 $.map(this.get_cells(), function (cell, i) {
1251 if (cell instanceof IPython.CodeCell) {
1251 if (cell instanceof IPython.CodeCell) {
1252 cell.expand_output();
1252 cell.expand_output();
1253 }
1253 }
1254 });
1254 });
1255 // this should not be set if the `collapse` key is removed from nbformat
1255 // this should not be set if the `collapse` key is removed from nbformat
1256 this.set_dirty(true);
1256 this.set_dirty(true);
1257 };
1257 };
1258
1258
1259 /**
1259 /**
1260 * Clear the selected CodeCell's output area.
1260 * Clear the selected CodeCell's output area.
1261 *
1261 *
1262 * @method clear_output
1262 * @method clear_output
1263 * @param {Number} index A cell's numeric index
1263 * @param {Number} index A cell's numeric index
1264 */
1264 */
1265 Notebook.prototype.clear_output = function (index) {
1265 Notebook.prototype.clear_output = function (index) {
1266 var i = this.index_or_selected(index);
1266 var i = this.index_or_selected(index);
1267 var cell = this.get_cell(i);
1267 var cell = this.get_cell(i);
1268 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1268 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1269 cell.clear_output();
1269 cell.clear_output();
1270 this.set_dirty(true);
1270 this.set_dirty(true);
1271 }
1271 }
1272 };
1272 };
1273
1273
1274 /**
1274 /**
1275 * Clear each code cell's output area.
1275 * Clear each code cell's output area.
1276 *
1276 *
1277 * @method clear_all_output
1277 * @method clear_all_output
1278 */
1278 */
1279 Notebook.prototype.clear_all_output = function () {
1279 Notebook.prototype.clear_all_output = function () {
1280 $.map(this.get_cells(), function (cell, i) {
1280 $.map(this.get_cells(), function (cell, i) {
1281 if (cell instanceof IPython.CodeCell) {
1281 if (cell instanceof IPython.CodeCell) {
1282 cell.clear_output();
1282 cell.clear_output();
1283 }
1283 }
1284 });
1284 });
1285 this.set_dirty(true);
1285 this.set_dirty(true);
1286 };
1286 };
1287
1287
1288 /**
1288 /**
1289 * Scroll the selected CodeCell's output area.
1289 * Scroll the selected CodeCell's output area.
1290 *
1290 *
1291 * @method scroll_output
1291 * @method scroll_output
1292 * @param {Number} index A cell's numeric index
1292 * @param {Number} index A cell's numeric index
1293 */
1293 */
1294 Notebook.prototype.scroll_output = function (index) {
1294 Notebook.prototype.scroll_output = function (index) {
1295 var i = this.index_or_selected(index);
1295 var i = this.index_or_selected(index);
1296 var cell = this.get_cell(i);
1296 var cell = this.get_cell(i);
1297 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1297 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1298 cell.scroll_output();
1298 cell.scroll_output();
1299 this.set_dirty(true);
1299 this.set_dirty(true);
1300 }
1300 }
1301 };
1301 };
1302
1302
1303 /**
1303 /**
1304 * Expand each code cell's output area, and add a scrollbar for long output.
1304 * Expand each code cell's output area, and add a scrollbar for long output.
1305 *
1305 *
1306 * @method scroll_all_output
1306 * @method scroll_all_output
1307 */
1307 */
1308 Notebook.prototype.scroll_all_output = function () {
1308 Notebook.prototype.scroll_all_output = function () {
1309 $.map(this.get_cells(), function (cell, i) {
1309 $.map(this.get_cells(), function (cell, i) {
1310 if (cell instanceof IPython.CodeCell) {
1310 if (cell instanceof IPython.CodeCell) {
1311 cell.scroll_output();
1311 cell.scroll_output();
1312 }
1312 }
1313 });
1313 });
1314 // this should not be set if the `collapse` key is removed from nbformat
1314 // this should not be set if the `collapse` key is removed from nbformat
1315 this.set_dirty(true);
1315 this.set_dirty(true);
1316 };
1316 };
1317
1317
1318 /** Toggle whether a cell's output is collapsed or expanded.
1318 /** Toggle whether a cell's output is collapsed or expanded.
1319 *
1319 *
1320 * @method toggle_output
1320 * @method toggle_output
1321 * @param {Number} index A cell's numeric index
1321 * @param {Number} index A cell's numeric index
1322 */
1322 */
1323 Notebook.prototype.toggle_output = function (index) {
1323 Notebook.prototype.toggle_output = function (index) {
1324 var i = this.index_or_selected(index);
1324 var i = this.index_or_selected(index);
1325 var cell = this.get_cell(i);
1325 var cell = this.get_cell(i);
1326 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1326 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1327 cell.toggle_output();
1327 cell.toggle_output();
1328 this.set_dirty(true);
1328 this.set_dirty(true);
1329 }
1329 }
1330 };
1330 };
1331
1331
1332 /**
1332 /**
1333 * Hide/show the output of all cells.
1333 * Hide/show the output of all cells.
1334 *
1334 *
1335 * @method toggle_all_output
1335 * @method toggle_all_output
1336 */
1336 */
1337 Notebook.prototype.toggle_all_output = function () {
1337 Notebook.prototype.toggle_all_output = function () {
1338 $.map(this.get_cells(), function (cell, i) {
1338 $.map(this.get_cells(), function (cell, i) {
1339 if (cell instanceof IPython.CodeCell) {
1339 if (cell instanceof IPython.CodeCell) {
1340 cell.toggle_output();
1340 cell.toggle_output();
1341 }
1341 }
1342 });
1342 });
1343 // this should not be set if the `collapse` key is removed from nbformat
1343 // this should not be set if the `collapse` key is removed from nbformat
1344 this.set_dirty(true);
1344 this.set_dirty(true);
1345 };
1345 };
1346
1346
1347 /**
1347 /**
1348 * Toggle a scrollbar for long cell outputs.
1348 * Toggle a scrollbar for long cell outputs.
1349 *
1349 *
1350 * @method toggle_output_scroll
1350 * @method toggle_output_scroll
1351 * @param {Number} index A cell's numeric index
1351 * @param {Number} index A cell's numeric index
1352 */
1352 */
1353 Notebook.prototype.toggle_output_scroll = function (index) {
1353 Notebook.prototype.toggle_output_scroll = function (index) {
1354 var i = this.index_or_selected(index);
1354 var i = this.index_or_selected(index);
1355 var cell = this.get_cell(i);
1355 var cell = this.get_cell(i);
1356 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1356 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1357 cell.toggle_output_scroll();
1357 cell.toggle_output_scroll();
1358 this.set_dirty(true);
1358 this.set_dirty(true);
1359 }
1359 }
1360 };
1360 };
1361
1361
1362 /**
1362 /**
1363 * Toggle the scrolling of long output on all cells.
1363 * Toggle the scrolling of long output on all cells.
1364 *
1364 *
1365 * @method toggle_all_output_scrolling
1365 * @method toggle_all_output_scrolling
1366 */
1366 */
1367 Notebook.prototype.toggle_all_output_scroll = function () {
1367 Notebook.prototype.toggle_all_output_scroll = function () {
1368 $.map(this.get_cells(), function (cell, i) {
1368 $.map(this.get_cells(), function (cell, i) {
1369 if (cell instanceof IPython.CodeCell) {
1369 if (cell instanceof IPython.CodeCell) {
1370 cell.toggle_output_scroll();
1370 cell.toggle_output_scroll();
1371 }
1371 }
1372 });
1372 });
1373 // this should not be set if the `collapse` key is removed from nbformat
1373 // this should not be set if the `collapse` key is removed from nbformat
1374 this.set_dirty(true);
1374 this.set_dirty(true);
1375 };
1375 };
1376
1376
1377 // Other cell functions: line numbers, ...
1377 // Other cell functions: line numbers, ...
1378
1378
1379 /**
1379 /**
1380 * Toggle line numbers in the selected cell's input area.
1380 * Toggle line numbers in the selected cell's input area.
1381 *
1381 *
1382 * @method cell_toggle_line_numbers
1382 * @method cell_toggle_line_numbers
1383 */
1383 */
1384 Notebook.prototype.cell_toggle_line_numbers = function() {
1384 Notebook.prototype.cell_toggle_line_numbers = function() {
1385 this.get_selected_cell().toggle_line_numbers();
1385 this.get_selected_cell().toggle_line_numbers();
1386 };
1386 };
1387
1387
1388 // Session related things
1388 // Session related things
1389
1389
1390 /**
1390 /**
1391 * Start a new session and set it on each code cell.
1391 * Start a new session and set it on each code cell.
1392 *
1392 *
1393 * @method start_session
1393 * @method start_session
1394 */
1394 */
1395 Notebook.prototype.start_session = function () {
1395 Notebook.prototype.start_session = function () {
1396 this.session = new IPython.Session(this, this.options);
1396 this.session = new IPython.Session(this, this.options);
1397 this.session.start($.proxy(this._session_started, this));
1397 this.session.start($.proxy(this._session_started, this));
1398 };
1398 };
1399
1399
1400
1400
1401 /**
1401 /**
1402 * Once a session is started, link the code cells to the kernel and pass the
1402 * Once a session is started, link the code cells to the kernel and pass the
1403 * comm manager to the widget manager
1403 * comm manager to the widget manager
1404 *
1404 *
1405 */
1405 */
1406 Notebook.prototype._session_started = function(){
1406 Notebook.prototype._session_started = function(){
1407 this.kernel = this.session.kernel;
1407 this.kernel = this.session.kernel;
1408 var ncells = this.ncells();
1408 var ncells = this.ncells();
1409 for (var i=0; i<ncells; i++) {
1409 for (var i=0; i<ncells; i++) {
1410 var cell = this.get_cell(i);
1410 var cell = this.get_cell(i);
1411 if (cell instanceof IPython.CodeCell) {
1411 if (cell instanceof IPython.CodeCell) {
1412 cell.set_kernel(this.session.kernel);
1412 cell.set_kernel(this.session.kernel);
1413 }
1413 }
1414 }
1414 }
1415 };
1415 };
1416
1416
1417 /**
1417 /**
1418 * Prompt the user to restart the IPython kernel.
1418 * Prompt the user to restart the IPython kernel.
1419 *
1419 *
1420 * @method restart_kernel
1420 * @method restart_kernel
1421 */
1421 */
1422 Notebook.prototype.restart_kernel = function () {
1422 Notebook.prototype.restart_kernel = function () {
1423 var that = this;
1423 var that = this;
1424 IPython.dialog.modal({
1424 IPython.dialog.modal({
1425 title : "Restart kernel or continue running?",
1425 title : "Restart kernel or continue running?",
1426 body : $("<p/>").text(
1426 body : $("<p/>").text(
1427 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1427 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1428 ),
1428 ),
1429 buttons : {
1429 buttons : {
1430 "Continue running" : {},
1430 "Continue running" : {},
1431 "Restart" : {
1431 "Restart" : {
1432 "class" : "btn-danger",
1432 "class" : "btn-danger",
1433 "click" : function() {
1433 "click" : function() {
1434 that.session.restart_kernel();
1434 that.session.restart_kernel();
1435 }
1435 }
1436 }
1436 }
1437 }
1437 }
1438 });
1438 });
1439 };
1439 };
1440
1440
1441 /**
1441 /**
1442 * Execute or render cell outputs and go into command mode.
1442 * Execute or render cell outputs and go into command mode.
1443 *
1443 *
1444 * @method execute_cell
1444 * @method execute_cell
1445 */
1445 */
1446 Notebook.prototype.execute_cell = function () {
1446 Notebook.prototype.execute_cell = function () {
1447 // mode = shift, ctrl, alt
1447 // mode = shift, ctrl, alt
1448 var cell = this.get_selected_cell();
1448 var cell = this.get_selected_cell();
1449 var cell_index = this.find_cell_index(cell);
1449 var cell_index = this.find_cell_index(cell);
1450
1450
1451 cell.execute();
1451 cell.execute();
1452 cell.focus_cell();
1452 cell.focus_cell();
1453 this.command_mode();
1453 this.command_mode();
1454 this.set_dirty(true);
1454 this.set_dirty(true);
1455 };
1455 };
1456
1456
1457 /**
1457 /**
1458 * Execute or render cell outputs and insert a new cell below.
1458 * Execute or render cell outputs and insert a new cell below.
1459 *
1459 *
1460 * @method execute_cell_and_insert_below
1460 * @method execute_cell_and_insert_below
1461 */
1461 */
1462 Notebook.prototype.execute_cell_and_insert_below = function () {
1462 Notebook.prototype.execute_cell_and_insert_below = function () {
1463 var cell = this.get_selected_cell();
1463 var cell = this.get_selected_cell();
1464 var cell_index = this.find_cell_index(cell);
1464 var cell_index = this.find_cell_index(cell);
1465
1465
1466 cell.execute();
1466 cell.execute();
1467
1467
1468 // If we are at the end always insert a new cell and return
1468 // If we are at the end always insert a new cell and return
1469 if (cell_index === (this.ncells()-1)) {
1469 if (cell_index === (this.ncells()-1)) {
1470 this.insert_cell_below('code');
1470 this.insert_cell_below('code');
1471 this.edit_mode(cell_index+1);
1471 this.edit_mode(cell_index+1);
1472 this.scroll_to_bottom();
1472 this.scroll_to_bottom();
1473 this.set_dirty(true);
1473 this.set_dirty(true);
1474 return;
1474 return;
1475 }
1475 }
1476
1476
1477 this.insert_cell_below('code');
1477 this.insert_cell_below('code');
1478 this.edit_mode(cell_index+1);
1478 this.edit_mode(cell_index+1);
1479 this.set_dirty(true);
1479 this.set_dirty(true);
1480 };
1480 };
1481
1481
1482 /**
1482 /**
1483 * Execute or render cell outputs and select the next cell.
1483 * Execute or render cell outputs and select the next cell.
1484 *
1484 *
1485 * @method execute_cell_and_select_below
1485 * @method execute_cell_and_select_below
1486 */
1486 */
1487 Notebook.prototype.execute_cell_and_select_below = function () {
1487 Notebook.prototype.execute_cell_and_select_below = function () {
1488
1488
1489 var cell = this.get_selected_cell();
1489 var cell = this.get_selected_cell();
1490 var cell_index = this.find_cell_index(cell);
1490 var cell_index = this.find_cell_index(cell);
1491
1491
1492 cell.execute();
1492 cell.execute();
1493
1493
1494 // If we are at the end always insert a new cell and return
1494 // If we are at the end always insert a new cell and return
1495 if (cell_index === (this.ncells()-1)) {
1495 if (cell_index === (this.ncells()-1)) {
1496 this.insert_cell_below('code');
1496 this.insert_cell_below('code');
1497 this.edit_mode(cell_index+1);
1497 this.edit_mode(cell_index+1);
1498 this.scroll_to_bottom();
1498 this.scroll_to_bottom();
1499 this.set_dirty(true);
1499 this.set_dirty(true);
1500 return;
1500 return;
1501 }
1501 }
1502
1502
1503 this.select(cell_index+1);
1503 this.select(cell_index+1);
1504 this.get_cell(cell_index+1).focus_cell();
1504 this.get_cell(cell_index+1).focus_cell();
1505 this.command_mode();
1505 this.command_mode();
1506 this.set_dirty(true);
1506 this.set_dirty(true);
1507 };
1507 };
1508
1508
1509 /**
1509 /**
1510 * Execute all cells below the selected cell.
1510 * Execute all cells below the selected cell.
1511 *
1511 *
1512 * @method execute_cells_below
1512 * @method execute_cells_below
1513 */
1513 */
1514 Notebook.prototype.execute_cells_below = function () {
1514 Notebook.prototype.execute_cells_below = function () {
1515 this.execute_cell_range(this.get_selected_index(), this.ncells());
1515 this.execute_cell_range(this.get_selected_index(), this.ncells());
1516 this.scroll_to_bottom();
1516 this.scroll_to_bottom();
1517 };
1517 };
1518
1518
1519 /**
1519 /**
1520 * Execute all cells above the selected cell.
1520 * Execute all cells above the selected cell.
1521 *
1521 *
1522 * @method execute_cells_above
1522 * @method execute_cells_above
1523 */
1523 */
1524 Notebook.prototype.execute_cells_above = function () {
1524 Notebook.prototype.execute_cells_above = function () {
1525 this.execute_cell_range(0, this.get_selected_index());
1525 this.execute_cell_range(0, this.get_selected_index());
1526 };
1526 };
1527
1527
1528 /**
1528 /**
1529 * Execute all cells.
1529 * Execute all cells.
1530 *
1530 *
1531 * @method execute_all_cells
1531 * @method execute_all_cells
1532 */
1532 */
1533 Notebook.prototype.execute_all_cells = function () {
1533 Notebook.prototype.execute_all_cells = function () {
1534 this.execute_cell_range(0, this.ncells());
1534 this.execute_cell_range(0, this.ncells());
1535 this.scroll_to_bottom();
1535 this.scroll_to_bottom();
1536 };
1536 };
1537
1537
1538 /**
1538 /**
1539 * Execute a contiguous range of cells.
1539 * Execute a contiguous range of cells.
1540 *
1540 *
1541 * @method execute_cell_range
1541 * @method execute_cell_range
1542 * @param {Number} start Index of the first cell to execute (inclusive)
1542 * @param {Number} start Index of the first cell to execute (inclusive)
1543 * @param {Number} end Index of the last cell to execute (exclusive)
1543 * @param {Number} end Index of the last cell to execute (exclusive)
1544 */
1544 */
1545 Notebook.prototype.execute_cell_range = function (start, end) {
1545 Notebook.prototype.execute_cell_range = function (start, end) {
1546 for (var i=start; i<end; i++) {
1546 for (var i=start; i<end; i++) {
1547 this.select(i);
1547 this.select(i);
1548 this.execute_cell();
1548 this.execute_cell();
1549 }
1549 }
1550 };
1550 };
1551
1551
1552 // Persistance and loading
1552 // Persistance and loading
1553
1553
1554 /**
1554 /**
1555 * Getter method for this notebook's name.
1555 * Getter method for this notebook's name.
1556 *
1556 *
1557 * @method get_notebook_name
1557 * @method get_notebook_name
1558 * @return {String} This notebook's name (excluding file extension)
1558 * @return {String} This notebook's name (excluding file extension)
1559 */
1559 */
1560 Notebook.prototype.get_notebook_name = function () {
1560 Notebook.prototype.get_notebook_name = function () {
1561 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1561 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1562 return nbname;
1562 return nbname;
1563 };
1563 };
1564
1564
1565 /**
1565 /**
1566 * Setter method for this notebook's name.
1566 * Setter method for this notebook's name.
1567 *
1567 *
1568 * @method set_notebook_name
1568 * @method set_notebook_name
1569 * @param {String} name A new name for this notebook
1569 * @param {String} name A new name for this notebook
1570 */
1570 */
1571 Notebook.prototype.set_notebook_name = function (name) {
1571 Notebook.prototype.set_notebook_name = function (name) {
1572 this.notebook_name = name;
1572 this.notebook_name = name;
1573 };
1573 };
1574
1574
1575 /**
1575 /**
1576 * Check that a notebook's name is valid.
1576 * Check that a notebook's name is valid.
1577 *
1577 *
1578 * @method test_notebook_name
1578 * @method test_notebook_name
1579 * @param {String} nbname A name for this notebook
1579 * @param {String} nbname A name for this notebook
1580 * @return {Boolean} True if the name is valid, false if invalid
1580 * @return {Boolean} True if the name is valid, false if invalid
1581 */
1581 */
1582 Notebook.prototype.test_notebook_name = function (nbname) {
1582 Notebook.prototype.test_notebook_name = function (nbname) {
1583 nbname = nbname || '';
1583 nbname = nbname || '';
1584 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1584 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1585 return true;
1585 return true;
1586 } else {
1586 } else {
1587 return false;
1587 return false;
1588 }
1588 }
1589 };
1589 };
1590
1590
1591 /**
1591 /**
1592 * Load a notebook from JSON (.ipynb).
1592 * Load a notebook from JSON (.ipynb).
1593 *
1593 *
1594 * This currently handles one worksheet: others are deleted.
1594 * This currently handles one worksheet: others are deleted.
1595 *
1595 *
1596 * @method fromJSON
1596 * @method fromJSON
1597 * @param {Object} data JSON representation of a notebook
1597 * @param {Object} data JSON representation of a notebook
1598 */
1598 */
1599 Notebook.prototype.fromJSON = function (data) {
1599 Notebook.prototype.fromJSON = function (data) {
1600 var content = data.content;
1600 var content = data.content;
1601 var ncells = this.ncells();
1601 var ncells = this.ncells();
1602 var i;
1602 var i;
1603 for (i=0; i<ncells; i++) {
1603 for (i=0; i<ncells; i++) {
1604 // Always delete cell 0 as they get renumbered as they are deleted.
1604 // Always delete cell 0 as they get renumbered as they are deleted.
1605 this.delete_cell(0);
1605 this.delete_cell(0);
1606 }
1606 }
1607 // Save the metadata and name.
1607 // Save the metadata and name.
1608 this.metadata = content.metadata;
1608 this.metadata = content.metadata;
1609 this.notebook_name = data.name;
1609 this.notebook_name = data.name;
1610 // Only handle 1 worksheet for now.
1610 // Only handle 1 worksheet for now.
1611 var worksheet = content.worksheets[0];
1611 var worksheet = content.worksheets[0];
1612 if (worksheet !== undefined) {
1612 if (worksheet !== undefined) {
1613 if (worksheet.metadata) {
1613 if (worksheet.metadata) {
1614 this.worksheet_metadata = worksheet.metadata;
1614 this.worksheet_metadata = worksheet.metadata;
1615 }
1615 }
1616 var new_cells = worksheet.cells;
1616 var new_cells = worksheet.cells;
1617 ncells = new_cells.length;
1617 ncells = new_cells.length;
1618 var cell_data = null;
1618 var cell_data = null;
1619 var new_cell = null;
1619 var new_cell = null;
1620 for (i=0; i<ncells; i++) {
1620 for (i=0; i<ncells; i++) {
1621 cell_data = new_cells[i];
1621 cell_data = new_cells[i];
1622 // VERSIONHACK: plaintext -> raw
1622 // VERSIONHACK: plaintext -> raw
1623 // handle never-released plaintext name for raw cells
1623 // handle never-released plaintext name for raw cells
1624 if (cell_data.cell_type === 'plaintext'){
1624 if (cell_data.cell_type === 'plaintext'){
1625 cell_data.cell_type = 'raw';
1625 cell_data.cell_type = 'raw';
1626 }
1626 }
1627
1627
1628 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1628 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1629 new_cell.fromJSON(cell_data);
1629 new_cell.fromJSON(cell_data);
1630 }
1630 }
1631 }
1631 }
1632 if (content.worksheets.length > 1) {
1632 if (content.worksheets.length > 1) {
1633 IPython.dialog.modal({
1633 IPython.dialog.modal({
1634 title : "Multiple worksheets",
1634 title : "Multiple worksheets",
1635 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1635 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1636 "but this version of IPython can only handle the first. " +
1636 "but this version of IPython can only handle the first. " +
1637 "If you save this notebook, worksheets after the first will be lost.",
1637 "If you save this notebook, worksheets after the first will be lost.",
1638 buttons : {
1638 buttons : {
1639 OK : {
1639 OK : {
1640 class : "btn-danger"
1640 class : "btn-danger"
1641 }
1641 }
1642 }
1642 }
1643 });
1643 });
1644 }
1644 }
1645 };
1645 };
1646
1646
1647 /**
1647 /**
1648 * Dump this notebook into a JSON-friendly object.
1648 * Dump this notebook into a JSON-friendly object.
1649 *
1649 *
1650 * @method toJSON
1650 * @method toJSON
1651 * @return {Object} A JSON-friendly representation of this notebook.
1651 * @return {Object} A JSON-friendly representation of this notebook.
1652 */
1652 */
1653 Notebook.prototype.toJSON = function () {
1653 Notebook.prototype.toJSON = function () {
1654 var cells = this.get_cells();
1654 var cells = this.get_cells();
1655 var ncells = cells.length;
1655 var ncells = cells.length;
1656 var cell_array = new Array(ncells);
1656 var cell_array = new Array(ncells);
1657 for (var i=0; i<ncells; i++) {
1657 for (var i=0; i<ncells; i++) {
1658 cell_array[i] = cells[i].toJSON();
1658 cell_array[i] = cells[i].toJSON();
1659 }
1659 }
1660 var data = {
1660 var data = {
1661 // Only handle 1 worksheet for now.
1661 // Only handle 1 worksheet for now.
1662 worksheets : [{
1662 worksheets : [{
1663 cells: cell_array,
1663 cells: cell_array,
1664 metadata: this.worksheet_metadata
1664 metadata: this.worksheet_metadata
1665 }],
1665 }],
1666 metadata : this.metadata
1666 metadata : this.metadata
1667 };
1667 };
1668 return data;
1668 return data;
1669 };
1669 };
1670
1670
1671 /**
1671 /**
1672 * Start an autosave timer, for periodically saving the notebook.
1672 * Start an autosave timer, for periodically saving the notebook.
1673 *
1673 *
1674 * @method set_autosave_interval
1674 * @method set_autosave_interval
1675 * @param {Integer} interval the autosave interval in milliseconds
1675 * @param {Integer} interval the autosave interval in milliseconds
1676 */
1676 */
1677 Notebook.prototype.set_autosave_interval = function (interval) {
1677 Notebook.prototype.set_autosave_interval = function (interval) {
1678 var that = this;
1678 var that = this;
1679 // clear previous interval, so we don't get simultaneous timers
1679 // clear previous interval, so we don't get simultaneous timers
1680 if (this.autosave_timer) {
1680 if (this.autosave_timer) {
1681 clearInterval(this.autosave_timer);
1681 clearInterval(this.autosave_timer);
1682 }
1682 }
1683
1683
1684 this.autosave_interval = this.minimum_autosave_interval = interval;
1684 this.autosave_interval = this.minimum_autosave_interval = interval;
1685 if (interval) {
1685 if (interval) {
1686 this.autosave_timer = setInterval(function() {
1686 this.autosave_timer = setInterval(function() {
1687 if (that.dirty) {
1687 if (that.dirty) {
1688 that.save_notebook();
1688 that.save_notebook();
1689 }
1689 }
1690 }, interval);
1690 }, interval);
1691 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1691 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1692 } else {
1692 } else {
1693 this.autosave_timer = null;
1693 this.autosave_timer = null;
1694 $([IPython.events]).trigger("autosave_disabled.Notebook");
1694 $([IPython.events]).trigger("autosave_disabled.Notebook");
1695 }
1695 }
1696 };
1696 };
1697
1697
1698 /**
1698 /**
1699 * Save this notebook on the server.
1699 * Save this notebook on the server.
1700 *
1700 *
1701 * @method save_notebook
1701 * @method save_notebook
1702 */
1702 */
1703 Notebook.prototype.save_notebook = function (extra_settings) {
1703 Notebook.prototype.save_notebook = function (extra_settings) {
1704 // Create a JSON model to be sent to the server.
1704 // Create a JSON model to be sent to the server.
1705 var model = {};
1705 var model = {};
1706 model.name = this.notebook_name;
1706 model.name = this.notebook_name;
1707 model.path = this.notebook_path;
1707 model.path = this.notebook_path;
1708 model.content = this.toJSON();
1708 model.content = this.toJSON();
1709 model.content.nbformat = this.nbformat;
1709 model.content.nbformat = this.nbformat;
1710 model.content.nbformat_minor = this.nbformat_minor;
1710 model.content.nbformat_minor = this.nbformat_minor;
1711 // time the ajax call for autosave tuning purposes.
1711 // time the ajax call for autosave tuning purposes.
1712 var start = new Date().getTime();
1712 var start = new Date().getTime();
1713 // We do the call with settings so we can set cache to false.
1713 // We do the call with settings so we can set cache to false.
1714 var settings = {
1714 var settings = {
1715 processData : false,
1715 processData : false,
1716 cache : false,
1716 cache : false,
1717 type : "PUT",
1717 type : "PUT",
1718 data : JSON.stringify(model),
1718 data : JSON.stringify(model),
1719 headers : {'Content-Type': 'application/json'},
1719 headers : {'Content-Type': 'application/json'},
1720 success : $.proxy(this.save_notebook_success, this, start),
1720 success : $.proxy(this.save_notebook_success, this, start),
1721 error : $.proxy(this.save_notebook_error, this)
1721 error : $.proxy(this.save_notebook_error, this)
1722 };
1722 };
1723 if (extra_settings) {
1723 if (extra_settings) {
1724 for (var key in extra_settings) {
1724 for (var key in extra_settings) {
1725 settings[key] = extra_settings[key];
1725 settings[key] = extra_settings[key];
1726 }
1726 }
1727 }
1727 }
1728 $([IPython.events]).trigger('notebook_saving.Notebook');
1728 $([IPython.events]).trigger('notebook_saving.Notebook');
1729 var url = utils.url_join_encode(
1729 var url = utils.url_join_encode(
1730 this.base_url,
1730 this.base_url,
1731 'api/notebooks',
1731 'api/notebooks',
1732 this.notebook_path,
1732 this.notebook_path,
1733 this.notebook_name
1733 this.notebook_name
1734 );
1734 );
1735 $.ajax(url, settings);
1735 $.ajax(url, settings);
1736 };
1736 };
1737
1737
1738 /**
1738 /**
1739 * Success callback for saving a notebook.
1739 * Success callback for saving a notebook.
1740 *
1740 *
1741 * @method save_notebook_success
1741 * @method save_notebook_success
1742 * @param {Integer} start the time when the save request started
1742 * @param {Integer} start the time when the save request started
1743 * @param {Object} data JSON representation of a notebook
1743 * @param {Object} data JSON representation of a notebook
1744 * @param {String} status Description of response status
1744 * @param {String} status Description of response status
1745 * @param {jqXHR} xhr jQuery Ajax object
1745 * @param {jqXHR} xhr jQuery Ajax object
1746 */
1746 */
1747 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1747 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1748 this.set_dirty(false);
1748 this.set_dirty(false);
1749 $([IPython.events]).trigger('notebook_saved.Notebook');
1749 $([IPython.events]).trigger('notebook_saved.Notebook');
1750 this._update_autosave_interval(start);
1750 this._update_autosave_interval(start);
1751 if (this._checkpoint_after_save) {
1751 if (this._checkpoint_after_save) {
1752 this.create_checkpoint();
1752 this.create_checkpoint();
1753 this._checkpoint_after_save = false;
1753 this._checkpoint_after_save = false;
1754 }
1754 }
1755 };
1755 };
1756
1756
1757 /**
1757 /**
1758 * update the autosave interval based on how long the last save took
1758 * update the autosave interval based on how long the last save took
1759 *
1759 *
1760 * @method _update_autosave_interval
1760 * @method _update_autosave_interval
1761 * @param {Integer} timestamp when the save request started
1761 * @param {Integer} timestamp when the save request started
1762 */
1762 */
1763 Notebook.prototype._update_autosave_interval = function (start) {
1763 Notebook.prototype._update_autosave_interval = function (start) {
1764 var duration = (new Date().getTime() - start);
1764 var duration = (new Date().getTime() - start);
1765 if (this.autosave_interval) {
1765 if (this.autosave_interval) {
1766 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1766 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1767 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1767 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1768 // round to 10 seconds, otherwise we will be setting a new interval too often
1768 // round to 10 seconds, otherwise we will be setting a new interval too often
1769 interval = 10000 * Math.round(interval / 10000);
1769 interval = 10000 * Math.round(interval / 10000);
1770 // set new interval, if it's changed
1770 // set new interval, if it's changed
1771 if (interval != this.autosave_interval) {
1771 if (interval != this.autosave_interval) {
1772 this.set_autosave_interval(interval);
1772 this.set_autosave_interval(interval);
1773 }
1773 }
1774 }
1774 }
1775 };
1775 };
1776
1776
1777 /**
1777 /**
1778 * Failure callback for saving a notebook.
1778 * Failure callback for saving a notebook.
1779 *
1779 *
1780 * @method save_notebook_error
1780 * @method save_notebook_error
1781 * @param {jqXHR} xhr jQuery Ajax object
1781 * @param {jqXHR} xhr jQuery Ajax object
1782 * @param {String} status Description of response status
1782 * @param {String} status Description of response status
1783 * @param {String} error HTTP error message
1783 * @param {String} error HTTP error message
1784 */
1784 */
1785 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1785 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1786 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1786 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1787 };
1787 };
1788
1788
1789 Notebook.prototype.new_notebook = function(){
1789 Notebook.prototype.new_notebook = function(){
1790 var path = this.notebook_path;
1790 var path = this.notebook_path;
1791 var base_url = this.base_url;
1791 var base_url = this.base_url;
1792 var settings = {
1792 var settings = {
1793 processData : false,
1793 processData : false,
1794 cache : false,
1794 cache : false,
1795 type : "POST",
1795 type : "POST",
1796 dataType : "json",
1796 dataType : "json",
1797 async : false,
1797 async : false,
1798 success : function (data, status, xhr){
1798 success : function (data, status, xhr){
1799 var notebook_name = data.name;
1799 var notebook_name = data.name;
1800 window.open(
1800 window.open(
1801 utils.url_join_encode(
1801 utils.url_join_encode(
1802 base_url,
1802 base_url,
1803 'notebooks',
1803 'notebooks',
1804 path,
1804 path,
1805 notebook_name
1805 notebook_name
1806 ),
1806 ),
1807 '_blank'
1807 '_blank'
1808 );
1808 );
1809 }
1809 }
1810 };
1810 };
1811 var url = utils.url_join_encode(
1811 var url = utils.url_join_encode(
1812 base_url,
1812 base_url,
1813 'api/notebooks',
1813 'api/notebooks',
1814 path
1814 path
1815 );
1815 );
1816 $.ajax(url,settings);
1816 $.ajax(url,settings);
1817 };
1817 };
1818
1818
1819
1819
1820 Notebook.prototype.copy_notebook = function(){
1820 Notebook.prototype.copy_notebook = function(){
1821 var path = this.notebook_path;
1821 var path = this.notebook_path;
1822 var base_url = this.base_url;
1822 var base_url = this.base_url;
1823 var settings = {
1823 var settings = {
1824 processData : false,
1824 processData : false,
1825 cache : false,
1825 cache : false,
1826 type : "POST",
1826 type : "POST",
1827 dataType : "json",
1827 dataType : "json",
1828 data : JSON.stringify({copy_from : this.notebook_name}),
1828 data : JSON.stringify({copy_from : this.notebook_name}),
1829 async : false,
1829 async : false,
1830 success : function (data, status, xhr) {
1830 success : function (data, status, xhr) {
1831 window.open(utils.url_join_encode(
1831 window.open(utils.url_join_encode(
1832 base_url,
1832 base_url,
1833 'notebooks',
1833 'notebooks',
1834 data.path,
1834 data.path,
1835 data.name
1835 data.name
1836 ), '_blank');
1836 ), '_blank');
1837 }
1837 }
1838 };
1838 };
1839 var url = utils.url_join_encode(
1839 var url = utils.url_join_encode(
1840 base_url,
1840 base_url,
1841 'api/notebooks',
1841 'api/notebooks',
1842 path
1842 path
1843 );
1843 );
1844 $.ajax(url,settings);
1844 $.ajax(url,settings);
1845 };
1845 };
1846
1846
1847 Notebook.prototype.rename = function (nbname) {
1847 Notebook.prototype.rename = function (nbname) {
1848 var that = this;
1848 var that = this;
1849 if (!nbname.match(/\.ipynb$/)) {
1849 if (!nbname.match(/\.ipynb$/)) {
1850 nbname = nbname + ".ipynb";
1850 nbname = nbname + ".ipynb";
1851 }
1851 }
1852 var data = {name: nbname};
1852 var data = {name: nbname};
1853 var settings = {
1853 var settings = {
1854 processData : false,
1854 processData : false,
1855 cache : false,
1855 cache : false,
1856 type : "PATCH",
1856 type : "PATCH",
1857 data : JSON.stringify(data),
1857 data : JSON.stringify(data),
1858 dataType: "json",
1858 dataType: "json",
1859 headers : {'Content-Type': 'application/json'},
1859 headers : {'Content-Type': 'application/json'},
1860 success : $.proxy(that.rename_success, this),
1860 success : $.proxy(that.rename_success, this),
1861 error : $.proxy(that.rename_error, this)
1861 error : $.proxy(that.rename_error, this)
1862 };
1862 };
1863 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1863 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1864 var url = utils.url_join_encode(
1864 var url = utils.url_join_encode(
1865 this.base_url,
1865 this.base_url,
1866 'api/notebooks',
1866 'api/notebooks',
1867 this.notebook_path,
1867 this.notebook_path,
1868 this.notebook_name
1868 this.notebook_name
1869 );
1869 );
1870 $.ajax(url, settings);
1870 $.ajax(url, settings);
1871 };
1871 };
1872
1872
1873 Notebook.prototype.delete = function () {
1873 Notebook.prototype.delete = function () {
1874 var that = this;
1874 var that = this;
1875 var settings = {
1875 var settings = {
1876 processData : false,
1876 processData : false,
1877 cache : false,
1877 cache : false,
1878 type : "DELETE",
1878 type : "DELETE",
1879 dataType: "json",
1879 dataType: "json",
1880 };
1880 };
1881 var url = utils.url_join_encode(
1881 var url = utils.url_join_encode(
1882 this.base_url,
1882 this.base_url,
1883 'api/notebooks',
1883 'api/notebooks',
1884 this.notebook_path,
1884 this.notebook_path,
1885 this.notebook_name
1885 this.notebook_name
1886 );
1886 );
1887 $.ajax(url, settings);
1887 $.ajax(url, settings);
1888 };
1888 };
1889
1889
1890
1890
1891 Notebook.prototype.rename_success = function (json, status, xhr) {
1891 Notebook.prototype.rename_success = function (json, status, xhr) {
1892 var name = this.notebook_name = json.name;
1892 var name = this.notebook_name = json.name;
1893 var path = json.path;
1893 var path = json.path;
1894 this.session.rename_notebook(name, path);
1894 this.session.rename_notebook(name, path);
1895 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1895 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1896 };
1896 };
1897
1897
1898 Notebook.prototype.rename_error = function (xhr, status, error) {
1898 Notebook.prototype.rename_error = function (xhr, status, error) {
1899 var that = this;
1899 var that = this;
1900 var dialog = $('<div/>').append(
1900 var dialog = $('<div/>').append(
1901 $("<p/>").addClass("rename-message")
1901 $("<p/>").addClass("rename-message")
1902 .text('This notebook name already exists.')
1902 .text('This notebook name already exists.')
1903 );
1903 );
1904 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1904 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1905 IPython.dialog.modal({
1905 IPython.dialog.modal({
1906 title: "Notebook Rename Error!",
1906 title: "Notebook Rename Error!",
1907 body: dialog,
1907 body: dialog,
1908 buttons : {
1908 buttons : {
1909 "Cancel": {},
1909 "Cancel": {},
1910 "OK": {
1910 "OK": {
1911 class: "btn-primary",
1911 class: "btn-primary",
1912 click: function () {
1912 click: function () {
1913 IPython.save_widget.rename_notebook();
1913 IPython.save_widget.rename_notebook();
1914 }}
1914 }}
1915 },
1915 },
1916 open : function (event, ui) {
1916 open : function (event, ui) {
1917 var that = $(this);
1917 var that = $(this);
1918 // Upon ENTER, click the OK button.
1918 // Upon ENTER, click the OK button.
1919 that.find('input[type="text"]').keydown(function (event, ui) {
1919 that.find('input[type="text"]').keydown(function (event, ui) {
1920 if (event.which === utils.keycodes.ENTER) {
1920 if (event.which === IPython.keyboard.keycodes.enter) {
1921 that.find('.btn-primary').first().click();
1921 that.find('.btn-primary').first().click();
1922 }
1922 }
1923 });
1923 });
1924 that.find('input[type="text"]').focus();
1924 that.find('input[type="text"]').focus();
1925 }
1925 }
1926 });
1926 });
1927 };
1927 };
1928
1928
1929 /**
1929 /**
1930 * Request a notebook's data from the server.
1930 * Request a notebook's data from the server.
1931 *
1931 *
1932 * @method load_notebook
1932 * @method load_notebook
1933 * @param {String} notebook_name and path A notebook to load
1933 * @param {String} notebook_name and path A notebook to load
1934 */
1934 */
1935 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1935 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1936 var that = this;
1936 var that = this;
1937 this.notebook_name = notebook_name;
1937 this.notebook_name = notebook_name;
1938 this.notebook_path = notebook_path;
1938 this.notebook_path = notebook_path;
1939 // We do the call with settings so we can set cache to false.
1939 // We do the call with settings so we can set cache to false.
1940 var settings = {
1940 var settings = {
1941 processData : false,
1941 processData : false,
1942 cache : false,
1942 cache : false,
1943 type : "GET",
1943 type : "GET",
1944 dataType : "json",
1944 dataType : "json",
1945 success : $.proxy(this.load_notebook_success,this),
1945 success : $.proxy(this.load_notebook_success,this),
1946 error : $.proxy(this.load_notebook_error,this),
1946 error : $.proxy(this.load_notebook_error,this),
1947 };
1947 };
1948 $([IPython.events]).trigger('notebook_loading.Notebook');
1948 $([IPython.events]).trigger('notebook_loading.Notebook');
1949 var url = utils.url_join_encode(
1949 var url = utils.url_join_encode(
1950 this.base_url,
1950 this.base_url,
1951 'api/notebooks',
1951 'api/notebooks',
1952 this.notebook_path,
1952 this.notebook_path,
1953 this.notebook_name
1953 this.notebook_name
1954 );
1954 );
1955 $.ajax(url, settings);
1955 $.ajax(url, settings);
1956 };
1956 };
1957
1957
1958 /**
1958 /**
1959 * Success callback for loading a notebook from the server.
1959 * Success callback for loading a notebook from the server.
1960 *
1960 *
1961 * Load notebook data from the JSON response.
1961 * Load notebook data from the JSON response.
1962 *
1962 *
1963 * @method load_notebook_success
1963 * @method load_notebook_success
1964 * @param {Object} data JSON representation of a notebook
1964 * @param {Object} data JSON representation of a notebook
1965 * @param {String} status Description of response status
1965 * @param {String} status Description of response status
1966 * @param {jqXHR} xhr jQuery Ajax object
1966 * @param {jqXHR} xhr jQuery Ajax object
1967 */
1967 */
1968 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1968 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1969 this.fromJSON(data);
1969 this.fromJSON(data);
1970 if (this.ncells() === 0) {
1970 if (this.ncells() === 0) {
1971 this.insert_cell_below('code');
1971 this.insert_cell_below('code');
1972 this.edit_mode(0);
1972 this.edit_mode(0);
1973 } else {
1973 } else {
1974 this.select(0);
1974 this.select(0);
1975 this.command_mode();
1975 this.command_mode();
1976 }
1976 }
1977 this.set_dirty(false);
1977 this.set_dirty(false);
1978 this.scroll_to_top();
1978 this.scroll_to_top();
1979 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1979 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1980 var msg = "This notebook has been converted from an older " +
1980 var msg = "This notebook has been converted from an older " +
1981 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1981 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1982 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1982 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1983 "newer notebook format will be used and older versions of IPython " +
1983 "newer notebook format will be used and older versions of IPython " +
1984 "may not be able to read it. To keep the older version, close the " +
1984 "may not be able to read it. To keep the older version, close the " +
1985 "notebook without saving it.";
1985 "notebook without saving it.";
1986 IPython.dialog.modal({
1986 IPython.dialog.modal({
1987 title : "Notebook converted",
1987 title : "Notebook converted",
1988 body : msg,
1988 body : msg,
1989 buttons : {
1989 buttons : {
1990 OK : {
1990 OK : {
1991 class : "btn-primary"
1991 class : "btn-primary"
1992 }
1992 }
1993 }
1993 }
1994 });
1994 });
1995 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1995 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1996 var that = this;
1996 var that = this;
1997 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1997 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1998 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1998 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1999 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1999 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2000 this_vs + ". You can still work with this notebook, but some features " +
2000 this_vs + ". You can still work with this notebook, but some features " +
2001 "introduced in later notebook versions may not be available.";
2001 "introduced in later notebook versions may not be available.";
2002
2002
2003 IPython.dialog.modal({
2003 IPython.dialog.modal({
2004 title : "Newer Notebook",
2004 title : "Newer Notebook",
2005 body : msg,
2005 body : msg,
2006 buttons : {
2006 buttons : {
2007 OK : {
2007 OK : {
2008 class : "btn-danger"
2008 class : "btn-danger"
2009 }
2009 }
2010 }
2010 }
2011 });
2011 });
2012
2012
2013 }
2013 }
2014
2014
2015 // Create the session after the notebook is completely loaded to prevent
2015 // Create the session after the notebook is completely loaded to prevent
2016 // code execution upon loading, which is a security risk.
2016 // code execution upon loading, which is a security risk.
2017 if (this.session === null) {
2017 if (this.session === null) {
2018 this.start_session();
2018 this.start_session();
2019 }
2019 }
2020 // load our checkpoint list
2020 // load our checkpoint list
2021 this.list_checkpoints();
2021 this.list_checkpoints();
2022
2022
2023 // load toolbar state
2023 // load toolbar state
2024 if (this.metadata.celltoolbar) {
2024 if (this.metadata.celltoolbar) {
2025 IPython.CellToolbar.global_show();
2025 IPython.CellToolbar.global_show();
2026 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2026 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2027 }
2027 }
2028
2028
2029 $([IPython.events]).trigger('notebook_loaded.Notebook');
2029 $([IPython.events]).trigger('notebook_loaded.Notebook');
2030 };
2030 };
2031
2031
2032 /**
2032 /**
2033 * Failure callback for loading a notebook from the server.
2033 * Failure callback for loading a notebook from the server.
2034 *
2034 *
2035 * @method load_notebook_error
2035 * @method load_notebook_error
2036 * @param {jqXHR} xhr jQuery Ajax object
2036 * @param {jqXHR} xhr jQuery Ajax object
2037 * @param {String} status Description of response status
2037 * @param {String} status Description of response status
2038 * @param {String} error HTTP error message
2038 * @param {String} error HTTP error message
2039 */
2039 */
2040 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2040 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2041 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2041 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2042 var msg;
2042 var msg;
2043 if (xhr.status === 400) {
2043 if (xhr.status === 400) {
2044 msg = error;
2044 msg = error;
2045 } else if (xhr.status === 500) {
2045 } else if (xhr.status === 500) {
2046 msg = "An unknown error occurred while loading this notebook. " +
2046 msg = "An unknown error occurred while loading this notebook. " +
2047 "This version can load notebook formats " +
2047 "This version can load notebook formats " +
2048 "v" + this.nbformat + " or earlier.";
2048 "v" + this.nbformat + " or earlier.";
2049 }
2049 }
2050 IPython.dialog.modal({
2050 IPython.dialog.modal({
2051 title: "Error loading notebook",
2051 title: "Error loading notebook",
2052 body : msg,
2052 body : msg,
2053 buttons : {
2053 buttons : {
2054 "OK": {}
2054 "OK": {}
2055 }
2055 }
2056 });
2056 });
2057 };
2057 };
2058
2058
2059 /********************* checkpoint-related *********************/
2059 /********************* checkpoint-related *********************/
2060
2060
2061 /**
2061 /**
2062 * Save the notebook then immediately create a checkpoint.
2062 * Save the notebook then immediately create a checkpoint.
2063 *
2063 *
2064 * @method save_checkpoint
2064 * @method save_checkpoint
2065 */
2065 */
2066 Notebook.prototype.save_checkpoint = function () {
2066 Notebook.prototype.save_checkpoint = function () {
2067 this._checkpoint_after_save = true;
2067 this._checkpoint_after_save = true;
2068 this.save_notebook();
2068 this.save_notebook();
2069 };
2069 };
2070
2070
2071 /**
2071 /**
2072 * Add a checkpoint for this notebook.
2072 * Add a checkpoint for this notebook.
2073 * for use as a callback from checkpoint creation.
2073 * for use as a callback from checkpoint creation.
2074 *
2074 *
2075 * @method add_checkpoint
2075 * @method add_checkpoint
2076 */
2076 */
2077 Notebook.prototype.add_checkpoint = function (checkpoint) {
2077 Notebook.prototype.add_checkpoint = function (checkpoint) {
2078 var found = false;
2078 var found = false;
2079 for (var i = 0; i < this.checkpoints.length; i++) {
2079 for (var i = 0; i < this.checkpoints.length; i++) {
2080 var existing = this.checkpoints[i];
2080 var existing = this.checkpoints[i];
2081 if (existing.id == checkpoint.id) {
2081 if (existing.id == checkpoint.id) {
2082 found = true;
2082 found = true;
2083 this.checkpoints[i] = checkpoint;
2083 this.checkpoints[i] = checkpoint;
2084 break;
2084 break;
2085 }
2085 }
2086 }
2086 }
2087 if (!found) {
2087 if (!found) {
2088 this.checkpoints.push(checkpoint);
2088 this.checkpoints.push(checkpoint);
2089 }
2089 }
2090 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2090 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2091 };
2091 };
2092
2092
2093 /**
2093 /**
2094 * List checkpoints for this notebook.
2094 * List checkpoints for this notebook.
2095 *
2095 *
2096 * @method list_checkpoints
2096 * @method list_checkpoints
2097 */
2097 */
2098 Notebook.prototype.list_checkpoints = function () {
2098 Notebook.prototype.list_checkpoints = function () {
2099 var url = utils.url_join_encode(
2099 var url = utils.url_join_encode(
2100 this.base_url,
2100 this.base_url,
2101 'api/notebooks',
2101 'api/notebooks',
2102 this.notebook_path,
2102 this.notebook_path,
2103 this.notebook_name,
2103 this.notebook_name,
2104 'checkpoints'
2104 'checkpoints'
2105 );
2105 );
2106 $.get(url).done(
2106 $.get(url).done(
2107 $.proxy(this.list_checkpoints_success, this)
2107 $.proxy(this.list_checkpoints_success, this)
2108 ).fail(
2108 ).fail(
2109 $.proxy(this.list_checkpoints_error, this)
2109 $.proxy(this.list_checkpoints_error, this)
2110 );
2110 );
2111 };
2111 };
2112
2112
2113 /**
2113 /**
2114 * Success callback for listing checkpoints.
2114 * Success callback for listing checkpoints.
2115 *
2115 *
2116 * @method list_checkpoint_success
2116 * @method list_checkpoint_success
2117 * @param {Object} data JSON representation of a checkpoint
2117 * @param {Object} data JSON representation of a checkpoint
2118 * @param {String} status Description of response status
2118 * @param {String} status Description of response status
2119 * @param {jqXHR} xhr jQuery Ajax object
2119 * @param {jqXHR} xhr jQuery Ajax object
2120 */
2120 */
2121 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2121 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2122 data = $.parseJSON(data);
2122 data = $.parseJSON(data);
2123 this.checkpoints = data;
2123 this.checkpoints = data;
2124 if (data.length) {
2124 if (data.length) {
2125 this.last_checkpoint = data[data.length - 1];
2125 this.last_checkpoint = data[data.length - 1];
2126 } else {
2126 } else {
2127 this.last_checkpoint = null;
2127 this.last_checkpoint = null;
2128 }
2128 }
2129 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2129 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2130 };
2130 };
2131
2131
2132 /**
2132 /**
2133 * Failure callback for listing a checkpoint.
2133 * Failure callback for listing a checkpoint.
2134 *
2134 *
2135 * @method list_checkpoint_error
2135 * @method list_checkpoint_error
2136 * @param {jqXHR} xhr jQuery Ajax object
2136 * @param {jqXHR} xhr jQuery Ajax object
2137 * @param {String} status Description of response status
2137 * @param {String} status Description of response status
2138 * @param {String} error_msg HTTP error message
2138 * @param {String} error_msg HTTP error message
2139 */
2139 */
2140 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2140 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2141 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2141 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2142 };
2142 };
2143
2143
2144 /**
2144 /**
2145 * Create a checkpoint of this notebook on the server from the most recent save.
2145 * Create a checkpoint of this notebook on the server from the most recent save.
2146 *
2146 *
2147 * @method create_checkpoint
2147 * @method create_checkpoint
2148 */
2148 */
2149 Notebook.prototype.create_checkpoint = function () {
2149 Notebook.prototype.create_checkpoint = function () {
2150 var url = utils.url_join_encode(
2150 var url = utils.url_join_encode(
2151 this.base_url,
2151 this.base_url,
2152 'api/notebooks',
2152 'api/notebooks',
2153 this.notebook_path,
2153 this.notebook_path,
2154 this.notebook_name,
2154 this.notebook_name,
2155 'checkpoints'
2155 'checkpoints'
2156 );
2156 );
2157 $.post(url).done(
2157 $.post(url).done(
2158 $.proxy(this.create_checkpoint_success, this)
2158 $.proxy(this.create_checkpoint_success, this)
2159 ).fail(
2159 ).fail(
2160 $.proxy(this.create_checkpoint_error, this)
2160 $.proxy(this.create_checkpoint_error, this)
2161 );
2161 );
2162 };
2162 };
2163
2163
2164 /**
2164 /**
2165 * Success callback for creating a checkpoint.
2165 * Success callback for creating a checkpoint.
2166 *
2166 *
2167 * @method create_checkpoint_success
2167 * @method create_checkpoint_success
2168 * @param {Object} data JSON representation of a checkpoint
2168 * @param {Object} data JSON representation of a checkpoint
2169 * @param {String} status Description of response status
2169 * @param {String} status Description of response status
2170 * @param {jqXHR} xhr jQuery Ajax object
2170 * @param {jqXHR} xhr jQuery Ajax object
2171 */
2171 */
2172 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2172 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2173 data = $.parseJSON(data);
2173 data = $.parseJSON(data);
2174 this.add_checkpoint(data);
2174 this.add_checkpoint(data);
2175 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2175 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2176 };
2176 };
2177
2177
2178 /**
2178 /**
2179 * Failure callback for creating a checkpoint.
2179 * Failure callback for creating a checkpoint.
2180 *
2180 *
2181 * @method create_checkpoint_error
2181 * @method create_checkpoint_error
2182 * @param {jqXHR} xhr jQuery Ajax object
2182 * @param {jqXHR} xhr jQuery Ajax object
2183 * @param {String} status Description of response status
2183 * @param {String} status Description of response status
2184 * @param {String} error_msg HTTP error message
2184 * @param {String} error_msg HTTP error message
2185 */
2185 */
2186 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2186 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2187 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2187 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2188 };
2188 };
2189
2189
2190 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2190 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2191 var that = this;
2191 var that = this;
2192 checkpoint = checkpoint || this.last_checkpoint;
2192 checkpoint = checkpoint || this.last_checkpoint;
2193 if ( ! checkpoint ) {
2193 if ( ! checkpoint ) {
2194 console.log("restore dialog, but no checkpoint to restore to!");
2194 console.log("restore dialog, but no checkpoint to restore to!");
2195 return;
2195 return;
2196 }
2196 }
2197 var body = $('<div/>').append(
2197 var body = $('<div/>').append(
2198 $('<p/>').addClass("p-space").text(
2198 $('<p/>').addClass("p-space").text(
2199 "Are you sure you want to revert the notebook to " +
2199 "Are you sure you want to revert the notebook to " +
2200 "the latest checkpoint?"
2200 "the latest checkpoint?"
2201 ).append(
2201 ).append(
2202 $("<strong/>").text(
2202 $("<strong/>").text(
2203 " This cannot be undone."
2203 " This cannot be undone."
2204 )
2204 )
2205 )
2205 )
2206 ).append(
2206 ).append(
2207 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2207 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2208 ).append(
2208 ).append(
2209 $('<p/>').addClass("p-space").text(
2209 $('<p/>').addClass("p-space").text(
2210 Date(checkpoint.last_modified)
2210 Date(checkpoint.last_modified)
2211 ).css("text-align", "center")
2211 ).css("text-align", "center")
2212 );
2212 );
2213
2213
2214 IPython.dialog.modal({
2214 IPython.dialog.modal({
2215 title : "Revert notebook to checkpoint",
2215 title : "Revert notebook to checkpoint",
2216 body : body,
2216 body : body,
2217 buttons : {
2217 buttons : {
2218 Revert : {
2218 Revert : {
2219 class : "btn-danger",
2219 class : "btn-danger",
2220 click : function () {
2220 click : function () {
2221 that.restore_checkpoint(checkpoint.id);
2221 that.restore_checkpoint(checkpoint.id);
2222 }
2222 }
2223 },
2223 },
2224 Cancel : {}
2224 Cancel : {}
2225 }
2225 }
2226 });
2226 });
2227 };
2227 };
2228
2228
2229 /**
2229 /**
2230 * Restore the notebook to a checkpoint state.
2230 * Restore the notebook to a checkpoint state.
2231 *
2231 *
2232 * @method restore_checkpoint
2232 * @method restore_checkpoint
2233 * @param {String} checkpoint ID
2233 * @param {String} checkpoint ID
2234 */
2234 */
2235 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2235 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2236 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2236 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2237 var url = utils.url_join_encode(
2237 var url = utils.url_join_encode(
2238 this.base_url,
2238 this.base_url,
2239 'api/notebooks',
2239 'api/notebooks',
2240 this.notebook_path,
2240 this.notebook_path,
2241 this.notebook_name,
2241 this.notebook_name,
2242 'checkpoints',
2242 'checkpoints',
2243 checkpoint
2243 checkpoint
2244 );
2244 );
2245 $.post(url).done(
2245 $.post(url).done(
2246 $.proxy(this.restore_checkpoint_success, this)
2246 $.proxy(this.restore_checkpoint_success, this)
2247 ).fail(
2247 ).fail(
2248 $.proxy(this.restore_checkpoint_error, this)
2248 $.proxy(this.restore_checkpoint_error, this)
2249 );
2249 );
2250 };
2250 };
2251
2251
2252 /**
2252 /**
2253 * Success callback for restoring a notebook to a checkpoint.
2253 * Success callback for restoring a notebook to a checkpoint.
2254 *
2254 *
2255 * @method restore_checkpoint_success
2255 * @method restore_checkpoint_success
2256 * @param {Object} data (ignored, should be empty)
2256 * @param {Object} data (ignored, should be empty)
2257 * @param {String} status Description of response status
2257 * @param {String} status Description of response status
2258 * @param {jqXHR} xhr jQuery Ajax object
2258 * @param {jqXHR} xhr jQuery Ajax object
2259 */
2259 */
2260 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2260 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2261 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2261 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2262 this.load_notebook(this.notebook_name, this.notebook_path);
2262 this.load_notebook(this.notebook_name, this.notebook_path);
2263 };
2263 };
2264
2264
2265 /**
2265 /**
2266 * Failure callback for restoring a notebook to a checkpoint.
2266 * Failure callback for restoring a notebook to a checkpoint.
2267 *
2267 *
2268 * @method restore_checkpoint_error
2268 * @method restore_checkpoint_error
2269 * @param {jqXHR} xhr jQuery Ajax object
2269 * @param {jqXHR} xhr jQuery Ajax object
2270 * @param {String} status Description of response status
2270 * @param {String} status Description of response status
2271 * @param {String} error_msg HTTP error message
2271 * @param {String} error_msg HTTP error message
2272 */
2272 */
2273 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2273 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2274 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2274 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2275 };
2275 };
2276
2276
2277 /**
2277 /**
2278 * Delete a notebook checkpoint.
2278 * Delete a notebook checkpoint.
2279 *
2279 *
2280 * @method delete_checkpoint
2280 * @method delete_checkpoint
2281 * @param {String} checkpoint ID
2281 * @param {String} checkpoint ID
2282 */
2282 */
2283 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2283 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2284 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2284 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2285 var url = utils.url_join_encode(
2285 var url = utils.url_join_encode(
2286 this.base_url,
2286 this.base_url,
2287 'api/notebooks',
2287 'api/notebooks',
2288 this.notebook_path,
2288 this.notebook_path,
2289 this.notebook_name,
2289 this.notebook_name,
2290 'checkpoints',
2290 'checkpoints',
2291 checkpoint
2291 checkpoint
2292 );
2292 );
2293 $.ajax(url, {
2293 $.ajax(url, {
2294 type: 'DELETE',
2294 type: 'DELETE',
2295 success: $.proxy(this.delete_checkpoint_success, this),
2295 success: $.proxy(this.delete_checkpoint_success, this),
2296 error: $.proxy(this.delete_notebook_error,this)
2296 error: $.proxy(this.delete_notebook_error,this)
2297 });
2297 });
2298 };
2298 };
2299
2299
2300 /**
2300 /**
2301 * Success callback for deleting a notebook checkpoint
2301 * Success callback for deleting a notebook checkpoint
2302 *
2302 *
2303 * @method delete_checkpoint_success
2303 * @method delete_checkpoint_success
2304 * @param {Object} data (ignored, should be empty)
2304 * @param {Object} data (ignored, should be empty)
2305 * @param {String} status Description of response status
2305 * @param {String} status Description of response status
2306 * @param {jqXHR} xhr jQuery Ajax object
2306 * @param {jqXHR} xhr jQuery Ajax object
2307 */
2307 */
2308 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2308 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2309 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2309 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2310 this.load_notebook(this.notebook_name, this.notebook_path);
2310 this.load_notebook(this.notebook_name, this.notebook_path);
2311 };
2311 };
2312
2312
2313 /**
2313 /**
2314 * Failure callback for deleting a notebook checkpoint.
2314 * Failure callback for deleting a notebook checkpoint.
2315 *
2315 *
2316 * @method delete_checkpoint_error
2316 * @method delete_checkpoint_error
2317 * @param {jqXHR} xhr jQuery Ajax object
2317 * @param {jqXHR} xhr jQuery Ajax object
2318 * @param {String} status Description of response status
2318 * @param {String} status Description of response status
2319 * @param {String} error_msg HTTP error message
2319 * @param {String} error_msg HTTP error message
2320 */
2320 */
2321 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2321 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2322 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2322 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2323 };
2323 };
2324
2324
2325
2325
2326 IPython.Notebook = Notebook;
2326 IPython.Notebook = Notebook;
2327
2327
2328
2328
2329 return IPython;
2329 return IPython;
2330
2330
2331 }(IPython));
2331 }(IPython));
2332
2332
@@ -1,865 +1,865 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/latex',
251 'text/latex',
252 'image/svg+xml',
252 'image/svg+xml',
253 'image/png',
253 'image/png',
254 'image/jpeg',
254 'image/jpeg',
255 'application/pdf',
255 'application/pdf',
256 'text/plain'
256 'text/plain'
257 ];
257 ];
258
258
259 OutputArea.prototype.validate_output = function (json) {
259 OutputArea.prototype.validate_output = function (json) {
260 // scrub invalid outputs
260 // scrub invalid outputs
261 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 // nbformat 4 will fix that.
262 // nbformat 4 will fix that.
263 $.map(OutputArea.output_types, function(key){
263 $.map(OutputArea.output_types, function(key){
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 console.log("Invalid type for " + key, json[key]);
265 console.log("Invalid type for " + key, json[key]);
266 delete json[key];
266 delete json[key];
267 }
267 }
268 });
268 });
269 return json;
269 return json;
270 };
270 };
271
271
272 OutputArea.prototype.append_output = function (json) {
272 OutputArea.prototype.append_output = function (json) {
273 this.expand();
273 this.expand();
274 // Clear the output if clear is queued.
274 // Clear the output if clear is queued.
275 var needs_height_reset = false;
275 var needs_height_reset = false;
276 if (this.clear_queued) {
276 if (this.clear_queued) {
277 this.clear_output(false);
277 this.clear_output(false);
278 needs_height_reset = true;
278 needs_height_reset = true;
279 }
279 }
280
280
281 // validate output data types
281 // validate output data types
282 json = this.validate_output(json);
282 json = this.validate_output(json);
283
283
284 if (json.output_type === 'pyout') {
284 if (json.output_type === 'pyout') {
285 this.append_pyout(json);
285 this.append_pyout(json);
286 } else if (json.output_type === 'pyerr') {
286 } else if (json.output_type === 'pyerr') {
287 this.append_pyerr(json);
287 this.append_pyerr(json);
288 } else if (json.output_type === 'display_data') {
288 } else if (json.output_type === 'display_data') {
289 this.append_display_data(json);
289 this.append_display_data(json);
290 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
291 this.append_stream(json);
291 this.append_stream(json);
292 }
292 }
293
293
294 this.outputs.push(json);
294 this.outputs.push(json);
295
295
296 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
297 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
298 if (needs_height_reset) {
298 if (needs_height_reset) {
299 this.element.height('');
299 this.element.height('');
300 }
300 }
301
301
302 var that = this;
302 var that = this;
303 setTimeout(function(){that.element.trigger('resize');}, 100);
303 setTimeout(function(){that.element.trigger('resize');}, 100);
304 };
304 };
305
305
306
306
307 OutputArea.prototype.create_output_area = function () {
307 OutputArea.prototype.create_output_area = function () {
308 var oa = $("<div/>").addClass("output_area");
308 var oa = $("<div/>").addClass("output_area");
309 if (this.prompt_area) {
309 if (this.prompt_area) {
310 oa.append($('<div/>').addClass('prompt'));
310 oa.append($('<div/>').addClass('prompt'));
311 }
311 }
312 return oa;
312 return oa;
313 };
313 };
314
314
315
315
316 function _get_metadata_key(metadata, key, mime) {
316 function _get_metadata_key(metadata, key, mime) {
317 var mime_md = metadata[mime];
317 var mime_md = metadata[mime];
318 // mime-specific higher priority
318 // mime-specific higher priority
319 if (mime_md && mime_md[key] !== undefined) {
319 if (mime_md && mime_md[key] !== undefined) {
320 return mime_md[key];
320 return mime_md[key];
321 }
321 }
322 // fallback on global
322 // fallback on global
323 return metadata[key];
323 return metadata[key];
324 }
324 }
325
325
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
328 if (_get_metadata_key(md, 'isolated', mime)) {
328 if (_get_metadata_key(md, 'isolated', mime)) {
329 // Create an iframe to isolate the subarea from the rest of the
329 // Create an iframe to isolate the subarea from the rest of the
330 // document
330 // document
331 var iframe = $('<iframe/>').addClass('box-flex1');
331 var iframe = $('<iframe/>').addClass('box-flex1');
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
333 iframe.attr('frameborder', 0);
333 iframe.attr('frameborder', 0);
334 iframe.attr('scrolling', 'auto');
334 iframe.attr('scrolling', 'auto');
335
335
336 // Once the iframe is loaded, the subarea is dynamically inserted
336 // Once the iframe is loaded, the subarea is dynamically inserted
337 iframe.on('load', function() {
337 iframe.on('load', function() {
338 // Workaround needed by Firefox, to properly render svg inside
338 // Workaround needed by Firefox, to properly render svg inside
339 // iframes, see http://stackoverflow.com/questions/10177190/
339 // iframes, see http://stackoverflow.com/questions/10177190/
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
341 this.contentDocument.open();
341 this.contentDocument.open();
342
342
343 // Insert the subarea into the iframe
343 // Insert the subarea into the iframe
344 // We must directly write the html. When using Jquery's append
344 // We must directly write the html. When using Jquery's append
345 // method, javascript is evaluated in the parent document and
345 // method, javascript is evaluated in the parent document and
346 // not in the iframe document. At this point, subarea doesn't
346 // not in the iframe document. At this point, subarea doesn't
347 // contain any user content.
347 // contain any user content.
348 this.contentDocument.write(subarea.html());
348 this.contentDocument.write(subarea.html());
349
349
350 this.contentDocument.close();
350 this.contentDocument.close();
351
351
352 var body = this.contentDocument.body;
352 var body = this.contentDocument.body;
353 // Adjust the iframe height automatically
353 // Adjust the iframe height automatically
354 iframe.height(body.scrollHeight + 'px');
354 iframe.height(body.scrollHeight + 'px');
355 });
355 });
356
356
357 // Elements should be appended to the inner subarea and not to the
357 // Elements should be appended to the inner subarea and not to the
358 // iframe
358 // iframe
359 iframe.append = function(that) {
359 iframe.append = function(that) {
360 subarea.append(that);
360 subarea.append(that);
361 };
361 };
362
362
363 return iframe;
363 return iframe;
364 } else {
364 } else {
365 return subarea;
365 return subarea;
366 }
366 }
367 }
367 }
368
368
369
369
370 OutputArea.prototype._append_javascript_error = function (err, element) {
370 OutputArea.prototype._append_javascript_error = function (err, element) {
371 // display a message when a javascript error occurs in display output
371 // display a message when a javascript error occurs in display output
372 var msg = "Javascript error adding output!"
372 var msg = "Javascript error adding output!"
373 if ( element === undefined ) return;
373 if ( element === undefined ) return;
374 element
374 element
375 .append($('<div/>').text(msg).addClass('js-error'))
375 .append($('<div/>').text(msg).addClass('js-error'))
376 .append($('<div/>').text(err.toString()).addClass('js-error'))
376 .append($('<div/>').text(err.toString()).addClass('js-error'))
377 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
377 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
378 };
378 };
379
379
380 OutputArea.prototype._safe_append = function (toinsert) {
380 OutputArea.prototype._safe_append = function (toinsert) {
381 // safely append an item to the document
381 // safely append an item to the document
382 // this is an object created by user code,
382 // this is an object created by user code,
383 // and may have errors, which should not be raised
383 // and may have errors, which should not be raised
384 // under any circumstances.
384 // under any circumstances.
385 try {
385 try {
386 this.element.append(toinsert);
386 this.element.append(toinsert);
387 } catch(err) {
387 } catch(err) {
388 console.log(err);
388 console.log(err);
389 // Create an actual output_area and output_subarea, which creates
389 // Create an actual output_area and output_subarea, which creates
390 // the prompt area and the proper indentation.
390 // the prompt area and the proper indentation.
391 var toinsert = this.create_output_area();
391 var toinsert = this.create_output_area();
392 var subarea = $('<div/>').addClass('output_subarea');
392 var subarea = $('<div/>').addClass('output_subarea');
393 toinsert.append(subarea);
393 toinsert.append(subarea);
394 this._append_javascript_error(err, subarea);
394 this._append_javascript_error(err, subarea);
395 this.element.append(toinsert);
395 this.element.append(toinsert);
396 }
396 }
397 };
397 };
398
398
399
399
400 OutputArea.prototype.append_pyout = function (json) {
400 OutputArea.prototype.append_pyout = function (json) {
401 var n = json.prompt_number || ' ';
401 var n = json.prompt_number || ' ';
402 var toinsert = this.create_output_area();
402 var toinsert = this.create_output_area();
403 if (this.prompt_area) {
403 if (this.prompt_area) {
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
405 }
405 }
406 this.append_mime_type(json, toinsert);
406 this.append_mime_type(json, toinsert);
407 this._safe_append(toinsert);
407 this._safe_append(toinsert);
408 // If we just output latex, typeset it.
408 // If we just output latex, typeset it.
409 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
409 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
410 this.typeset();
410 this.typeset();
411 }
411 }
412 };
412 };
413
413
414
414
415 OutputArea.prototype.append_pyerr = function (json) {
415 OutputArea.prototype.append_pyerr = function (json) {
416 var tb = json.traceback;
416 var tb = json.traceback;
417 if (tb !== undefined && tb.length > 0) {
417 if (tb !== undefined && tb.length > 0) {
418 var s = '';
418 var s = '';
419 var len = tb.length;
419 var len = tb.length;
420 for (var i=0; i<len; i++) {
420 for (var i=0; i<len; i++) {
421 s = s + tb[i] + '\n';
421 s = s + tb[i] + '\n';
422 }
422 }
423 s = s + '\n';
423 s = s + '\n';
424 var toinsert = this.create_output_area();
424 var toinsert = this.create_output_area();
425 this.append_text(s, {}, toinsert);
425 this.append_text(s, {}, toinsert);
426 this._safe_append(toinsert);
426 this._safe_append(toinsert);
427 }
427 }
428 };
428 };
429
429
430
430
431 OutputArea.prototype.append_stream = function (json) {
431 OutputArea.prototype.append_stream = function (json) {
432 // temporary fix: if stream undefined (json file written prior to this patch),
432 // temporary fix: if stream undefined (json file written prior to this patch),
433 // default to most likely stdout:
433 // default to most likely stdout:
434 if (json.stream == undefined){
434 if (json.stream == undefined){
435 json.stream = 'stdout';
435 json.stream = 'stdout';
436 }
436 }
437 var text = json.text;
437 var text = json.text;
438 var subclass = "output_"+json.stream;
438 var subclass = "output_"+json.stream;
439 if (this.outputs.length > 0){
439 if (this.outputs.length > 0){
440 // have at least one output to consider
440 // have at least one output to consider
441 var last = this.outputs[this.outputs.length-1];
441 var last = this.outputs[this.outputs.length-1];
442 if (last.output_type == 'stream' && json.stream == last.stream){
442 if (last.output_type == 'stream' && json.stream == last.stream){
443 // latest output was in the same stream,
443 // latest output was in the same stream,
444 // so append directly into its pre tag
444 // so append directly into its pre tag
445 // escape ANSI & HTML specials:
445 // escape ANSI & HTML specials:
446 var pre = this.element.find('div.'+subclass).last().find('pre');
446 var pre = this.element.find('div.'+subclass).last().find('pre');
447 var html = utils.fixCarriageReturn(
447 var html = utils.fixCarriageReturn(
448 pre.html() + utils.fixConsole(text));
448 pre.html() + utils.fixConsole(text));
449 // The only user content injected with this HTML call is
449 // The only user content injected with this HTML call is
450 // escaped by the fixConsole() method.
450 // escaped by the fixConsole() method.
451 pre.html(html);
451 pre.html(html);
452 return;
452 return;
453 }
453 }
454 }
454 }
455
455
456 if (!text.replace("\r", "")) {
456 if (!text.replace("\r", "")) {
457 // text is nothing (empty string, \r, etc.)
457 // text is nothing (empty string, \r, etc.)
458 // so don't append any elements, which might add undesirable space
458 // so don't append any elements, which might add undesirable space
459 return;
459 return;
460 }
460 }
461
461
462 // If we got here, attach a new div
462 // If we got here, attach a new div
463 var toinsert = this.create_output_area();
463 var toinsert = this.create_output_area();
464 this.append_text(text, {}, toinsert, "output_stream "+subclass);
464 this.append_text(text, {}, toinsert, "output_stream "+subclass);
465 this._safe_append(toinsert);
465 this._safe_append(toinsert);
466 };
466 };
467
467
468
468
469 OutputArea.prototype.append_display_data = function (json) {
469 OutputArea.prototype.append_display_data = function (json) {
470 var toinsert = this.create_output_area();
470 var toinsert = this.create_output_area();
471 if (this.append_mime_type(json, toinsert)) {
471 if (this.append_mime_type(json, toinsert)) {
472 this._safe_append(toinsert);
472 this._safe_append(toinsert);
473 // If we just output latex, typeset it.
473 // If we just output latex, typeset it.
474 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
474 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
475 this.typeset();
475 this.typeset();
476 }
476 }
477 }
477 }
478 };
478 };
479
479
480
480
481 OutputArea.safe_outputs = {
481 OutputArea.safe_outputs = {
482 'text/plain' : true,
482 'text/plain' : true,
483 'image/png' : true,
483 'image/png' : true,
484 'image/jpeg' : true
484 'image/jpeg' : true
485 };
485 };
486
486
487 OutputArea.prototype.append_mime_type = function (json, element) {
487 OutputArea.prototype.append_mime_type = function (json, element) {
488 for (var type_i in OutputArea.display_order) {
488 for (var type_i in OutputArea.display_order) {
489 var type = OutputArea.display_order[type_i];
489 var type = OutputArea.display_order[type_i];
490 var append = OutputArea.append_map[type];
490 var append = OutputArea.append_map[type];
491 if ((json[type] !== undefined) && append) {
491 if ((json[type] !== undefined) && append) {
492 if (!this.trusted && !OutputArea.safe_outputs[type]) {
492 if (!this.trusted && !OutputArea.safe_outputs[type]) {
493 // not trusted show warning and do not display
493 // not trusted show warning and do not display
494 var content = {
494 var content = {
495 text : "Untrusted " + type + " output ignored.",
495 text : "Untrusted " + type + " output ignored.",
496 stream : "stderr"
496 stream : "stderr"
497 }
497 }
498 this.append_stream(content);
498 this.append_stream(content);
499 continue;
499 continue;
500 }
500 }
501 var md = json.metadata || {};
501 var md = json.metadata || {};
502 var toinsert = append.apply(this, [json[type], md, element]);
502 var toinsert = append.apply(this, [json[type], md, element]);
503 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
503 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
504 return true;
504 return true;
505 }
505 }
506 }
506 }
507 return false;
507 return false;
508 };
508 };
509
509
510
510
511 OutputArea.prototype.append_html = function (html, md, element) {
511 OutputArea.prototype.append_html = function (html, md, element) {
512 var type = 'text/html';
512 var type = 'text/html';
513 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
513 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
514 IPython.keyboard_manager.register_events(toinsert);
514 IPython.keyboard_manager.register_events(toinsert);
515 toinsert.append(html);
515 toinsert.append(html);
516 element.append(toinsert);
516 element.append(toinsert);
517 return toinsert;
517 return toinsert;
518 };
518 };
519
519
520
520
521 OutputArea.prototype.append_javascript = function (js, md, element) {
521 OutputArea.prototype.append_javascript = function (js, md, element) {
522 // We just eval the JS code, element appears in the local scope.
522 // We just eval the JS code, element appears in the local scope.
523 var type = 'application/javascript';
523 var type = 'application/javascript';
524 var toinsert = this.create_output_subarea(md, "output_javascript", type);
524 var toinsert = this.create_output_subarea(md, "output_javascript", type);
525 IPython.keyboard_manager.register_events(toinsert);
525 IPython.keyboard_manager.register_events(toinsert);
526 element.append(toinsert);
526 element.append(toinsert);
527 // FIXME TODO : remove `container element for 3.0`
527 // FIXME TODO : remove `container element for 3.0`
528 //backward compat, js should be eval'ed in a context where `container` is defined.
528 //backward compat, js should be eval'ed in a context where `container` is defined.
529 var container = element;
529 var container = element;
530 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
530 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
531 // end backward compat
531 // end backward compat
532 try {
532 try {
533 eval(js);
533 eval(js);
534 } catch(err) {
534 } catch(err) {
535 console.log(err);
535 console.log(err);
536 this._append_javascript_error(err, toinsert);
536 this._append_javascript_error(err, toinsert);
537 }
537 }
538 return toinsert;
538 return toinsert;
539 };
539 };
540
540
541
541
542 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
542 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
543 var type = 'text/plain';
543 var type = 'text/plain';
544 var toinsert = this.create_output_subarea(md, "output_text", type);
544 var toinsert = this.create_output_subarea(md, "output_text", type);
545 // escape ANSI & HTML specials in plaintext:
545 // escape ANSI & HTML specials in plaintext:
546 data = utils.fixConsole(data);
546 data = utils.fixConsole(data);
547 data = utils.fixCarriageReturn(data);
547 data = utils.fixCarriageReturn(data);
548 data = utils.autoLinkUrls(data);
548 data = utils.autoLinkUrls(data);
549 if (extra_class){
549 if (extra_class){
550 toinsert.addClass(extra_class);
550 toinsert.addClass(extra_class);
551 }
551 }
552 // The only user content injected with this HTML call is
552 // The only user content injected with this HTML call is
553 // escaped by the fixConsole() method.
553 // escaped by the fixConsole() method.
554 toinsert.append($("<pre/>").html(data));
554 toinsert.append($("<pre/>").html(data));
555 element.append(toinsert);
555 element.append(toinsert);
556 return toinsert;
556 return toinsert;
557 };
557 };
558
558
559
559
560 OutputArea.prototype.append_svg = function (svg, md, element) {
560 OutputArea.prototype.append_svg = function (svg, md, element) {
561 var type = 'image/svg+xml';
561 var type = 'image/svg+xml';
562 var toinsert = this.create_output_subarea(md, "output_svg", type);
562 var toinsert = this.create_output_subarea(md, "output_svg", type);
563 toinsert.append(svg);
563 toinsert.append(svg);
564 element.append(toinsert);
564 element.append(toinsert);
565 return toinsert;
565 return toinsert;
566 };
566 };
567
567
568
568
569 OutputArea.prototype._dblclick_to_reset_size = function (img) {
569 OutputArea.prototype._dblclick_to_reset_size = function (img) {
570 // wrap image after it's loaded on the page,
570 // wrap image after it's loaded on the page,
571 // otherwise the measured initial size will be incorrect
571 // otherwise the measured initial size will be incorrect
572 img.on("load", function (){
572 img.on("load", function (){
573 var h0 = img.height();
573 var h0 = img.height();
574 var w0 = img.width();
574 var w0 = img.width();
575 if (!(h0 && w0)) {
575 if (!(h0 && w0)) {
576 // zero size, don't make it resizable
576 // zero size, don't make it resizable
577 return;
577 return;
578 }
578 }
579 img.resizable({
579 img.resizable({
580 aspectRatio: true,
580 aspectRatio: true,
581 autoHide: true
581 autoHide: true
582 });
582 });
583 img.dblclick(function () {
583 img.dblclick(function () {
584 // resize wrapper & image together for some reason:
584 // resize wrapper & image together for some reason:
585 img.parent().height(h0);
585 img.parent().height(h0);
586 img.height(h0);
586 img.height(h0);
587 img.parent().width(w0);
587 img.parent().width(w0);
588 img.width(w0);
588 img.width(w0);
589 });
589 });
590 });
590 });
591 };
591 };
592
592
593 var set_width_height = function (img, md, mime) {
593 var set_width_height = function (img, md, mime) {
594 // set width and height of an img element from metadata
594 // set width and height of an img element from metadata
595 var height = _get_metadata_key(md, 'height', mime);
595 var height = _get_metadata_key(md, 'height', mime);
596 if (height !== undefined) img.attr('height', height);
596 if (height !== undefined) img.attr('height', height);
597 var width = _get_metadata_key(md, 'width', mime);
597 var width = _get_metadata_key(md, 'width', mime);
598 if (width !== undefined) img.attr('width', width);
598 if (width !== undefined) img.attr('width', width);
599 };
599 };
600
600
601 OutputArea.prototype.append_png = function (png, md, element) {
601 OutputArea.prototype.append_png = function (png, md, element) {
602 var type = 'image/png';
602 var type = 'image/png';
603 var toinsert = this.create_output_subarea(md, "output_png", type);
603 var toinsert = this.create_output_subarea(md, "output_png", type);
604 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
604 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
605 set_width_height(img, md, 'image/png');
605 set_width_height(img, md, 'image/png');
606 this._dblclick_to_reset_size(img);
606 this._dblclick_to_reset_size(img);
607 toinsert.append(img);
607 toinsert.append(img);
608 element.append(toinsert);
608 element.append(toinsert);
609 return toinsert;
609 return toinsert;
610 };
610 };
611
611
612
612
613 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
613 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
614 var type = 'image/jpeg';
614 var type = 'image/jpeg';
615 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
615 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
616 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
616 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
617 set_width_height(img, md, 'image/jpeg');
617 set_width_height(img, md, 'image/jpeg');
618 this._dblclick_to_reset_size(img);
618 this._dblclick_to_reset_size(img);
619 toinsert.append(img);
619 toinsert.append(img);
620 element.append(toinsert);
620 element.append(toinsert);
621 return toinsert;
621 return toinsert;
622 };
622 };
623
623
624
624
625 OutputArea.prototype.append_pdf = function (pdf, md, element) {
625 OutputArea.prototype.append_pdf = function (pdf, md, element) {
626 var type = 'application/pdf';
626 var type = 'application/pdf';
627 var toinsert = this.create_output_subarea(md, "output_pdf", type);
627 var toinsert = this.create_output_subarea(md, "output_pdf", type);
628 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
628 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
629 a.attr('target', '_blank');
629 a.attr('target', '_blank');
630 a.text('View PDF')
630 a.text('View PDF')
631 toinsert.append(a);
631 toinsert.append(a);
632 element.append(toinsert);
632 element.append(toinsert);
633 return toinsert;
633 return toinsert;
634 }
634 }
635
635
636 OutputArea.prototype.append_latex = function (latex, md, element) {
636 OutputArea.prototype.append_latex = function (latex, md, element) {
637 // This method cannot do the typesetting because the latex first has to
637 // This method cannot do the typesetting because the latex first has to
638 // be on the page.
638 // be on the page.
639 var type = 'text/latex';
639 var type = 'text/latex';
640 var toinsert = this.create_output_subarea(md, "output_latex", type);
640 var toinsert = this.create_output_subarea(md, "output_latex", type);
641 toinsert.append(latex);
641 toinsert.append(latex);
642 element.append(toinsert);
642 element.append(toinsert);
643 return toinsert;
643 return toinsert;
644 };
644 };
645
645
646
646
647 OutputArea.prototype.append_raw_input = function (msg) {
647 OutputArea.prototype.append_raw_input = function (msg) {
648 var that = this;
648 var that = this;
649 this.expand();
649 this.expand();
650 var content = msg.content;
650 var content = msg.content;
651 var area = this.create_output_area();
651 var area = this.create_output_area();
652
652
653 // disable any other raw_inputs, if they are left around
653 // disable any other raw_inputs, if they are left around
654 $("div.output_subarea.raw_input").remove();
654 $("div.output_subarea.raw_input").remove();
655
655
656 area.append(
656 area.append(
657 $("<div/>")
657 $("<div/>")
658 .addClass("box-flex1 output_subarea raw_input")
658 .addClass("box-flex1 output_subarea raw_input")
659 .append(
659 .append(
660 $("<span/>")
660 $("<span/>")
661 .addClass("input_prompt")
661 .addClass("input_prompt")
662 .text(content.prompt)
662 .text(content.prompt)
663 )
663 )
664 .append(
664 .append(
665 $("<input/>")
665 $("<input/>")
666 .addClass("raw_input")
666 .addClass("raw_input")
667 .attr('type', 'text')
667 .attr('type', 'text')
668 .attr("size", 47)
668 .attr("size", 47)
669 .keydown(function (event, ui) {
669 .keydown(function (event, ui) {
670 // make sure we submit on enter,
670 // make sure we submit on enter,
671 // and don't re-execute the *cell* on shift-enter
671 // and don't re-execute the *cell* on shift-enter
672 if (event.which === utils.keycodes.ENTER) {
672 if (event.which === IPython.keyboard.keycodes.enter) {
673 that._submit_raw_input();
673 that._submit_raw_input();
674 return false;
674 return false;
675 }
675 }
676 })
676 })
677 )
677 )
678 );
678 );
679
679
680 this.element.append(area);
680 this.element.append(area);
681 var raw_input = area.find('input.raw_input');
681 var raw_input = area.find('input.raw_input');
682 // Register events that enable/disable the keyboard manager while raw
682 // Register events that enable/disable the keyboard manager while raw
683 // input is focused.
683 // input is focused.
684 IPython.keyboard_manager.register_events(raw_input);
684 IPython.keyboard_manager.register_events(raw_input);
685 // Note, the following line used to read raw_input.focus().focus().
685 // Note, the following line used to read raw_input.focus().focus().
686 // This seemed to be needed otherwise only the cell would be focused.
686 // This seemed to be needed otherwise only the cell would be focused.
687 // But with the modal UI, this seems to work fine with one call to focus().
687 // But with the modal UI, this seems to work fine with one call to focus().
688 raw_input.focus();
688 raw_input.focus();
689 }
689 }
690
690
691 OutputArea.prototype._submit_raw_input = function (evt) {
691 OutputArea.prototype._submit_raw_input = function (evt) {
692 var container = this.element.find("div.raw_input");
692 var container = this.element.find("div.raw_input");
693 var theprompt = container.find("span.input_prompt");
693 var theprompt = container.find("span.input_prompt");
694 var theinput = container.find("input.raw_input");
694 var theinput = container.find("input.raw_input");
695 var value = theinput.val();
695 var value = theinput.val();
696 var content = {
696 var content = {
697 output_type : 'stream',
697 output_type : 'stream',
698 name : 'stdout',
698 name : 'stdout',
699 text : theprompt.text() + value + '\n'
699 text : theprompt.text() + value + '\n'
700 }
700 }
701 // remove form container
701 // remove form container
702 container.parent().remove();
702 container.parent().remove();
703 // replace with plaintext version in stdout
703 // replace with plaintext version in stdout
704 this.append_output(content, false);
704 this.append_output(content, false);
705 $([IPython.events]).trigger('send_input_reply.Kernel', value);
705 $([IPython.events]).trigger('send_input_reply.Kernel', value);
706 }
706 }
707
707
708
708
709 OutputArea.prototype.handle_clear_output = function (msg) {
709 OutputArea.prototype.handle_clear_output = function (msg) {
710 // msg spec v4 had stdout, stderr, display keys
710 // msg spec v4 had stdout, stderr, display keys
711 // v4.1 replaced these with just wait
711 // v4.1 replaced these with just wait
712 // The default behavior is the same (stdout=stderr=display=True, wait=False),
712 // The default behavior is the same (stdout=stderr=display=True, wait=False),
713 // so v4 messages will still be properly handled,
713 // so v4 messages will still be properly handled,
714 // except for the rarely used clearing less than all output.
714 // except for the rarely used clearing less than all output.
715 this.clear_output(msg.content.wait || false);
715 this.clear_output(msg.content.wait || false);
716 };
716 };
717
717
718
718
719 OutputArea.prototype.clear_output = function(wait) {
719 OutputArea.prototype.clear_output = function(wait) {
720 if (wait) {
720 if (wait) {
721
721
722 // If a clear is queued, clear before adding another to the queue.
722 // If a clear is queued, clear before adding another to the queue.
723 if (this.clear_queued) {
723 if (this.clear_queued) {
724 this.clear_output(false);
724 this.clear_output(false);
725 };
725 };
726
726
727 this.clear_queued = true;
727 this.clear_queued = true;
728 } else {
728 } else {
729
729
730 // Fix the output div's height if the clear_output is waiting for
730 // Fix the output div's height if the clear_output is waiting for
731 // new output (it is being used in an animation).
731 // new output (it is being used in an animation).
732 if (this.clear_queued) {
732 if (this.clear_queued) {
733 var height = this.element.height();
733 var height = this.element.height();
734 this.element.height(height);
734 this.element.height(height);
735 this.clear_queued = false;
735 this.clear_queued = false;
736 }
736 }
737
737
738 // clear all, no need for logic
738 // clear all, no need for logic
739 this.element.html("");
739 this.element.html("");
740 this.outputs = [];
740 this.outputs = [];
741 this.trusted = true;
741 this.trusted = true;
742 this.unscroll_area();
742 this.unscroll_area();
743 return;
743 return;
744 };
744 };
745 };
745 };
746
746
747
747
748 // JSON serialization
748 // JSON serialization
749
749
750 OutputArea.prototype.fromJSON = function (outputs) {
750 OutputArea.prototype.fromJSON = function (outputs) {
751 var len = outputs.length;
751 var len = outputs.length;
752 var data;
752 var data;
753
753
754 for (var i=0; i<len; i++) {
754 for (var i=0; i<len; i++) {
755 data = outputs[i];
755 data = outputs[i];
756 var msg_type = data.output_type;
756 var msg_type = data.output_type;
757 if (msg_type === "display_data" || msg_type === "pyout") {
757 if (msg_type === "display_data" || msg_type === "pyout") {
758 // convert short keys to mime keys
758 // convert short keys to mime keys
759 // TODO: remove mapping of short keys when we update to nbformat 4
759 // TODO: remove mapping of short keys when we update to nbformat 4
760 data = this.rename_keys(data, OutputArea.mime_map_r);
760 data = this.rename_keys(data, OutputArea.mime_map_r);
761 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
761 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
762 }
762 }
763
763
764 this.append_output(data);
764 this.append_output(data);
765 }
765 }
766 };
766 };
767
767
768
768
769 OutputArea.prototype.toJSON = function () {
769 OutputArea.prototype.toJSON = function () {
770 var outputs = [];
770 var outputs = [];
771 var len = this.outputs.length;
771 var len = this.outputs.length;
772 var data;
772 var data;
773 for (var i=0; i<len; i++) {
773 for (var i=0; i<len; i++) {
774 data = this.outputs[i];
774 data = this.outputs[i];
775 var msg_type = data.output_type;
775 var msg_type = data.output_type;
776 if (msg_type === "display_data" || msg_type === "pyout") {
776 if (msg_type === "display_data" || msg_type === "pyout") {
777 // convert mime keys to short keys
777 // convert mime keys to short keys
778 data = this.rename_keys(data, OutputArea.mime_map);
778 data = this.rename_keys(data, OutputArea.mime_map);
779 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
779 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
780 }
780 }
781 outputs[i] = data;
781 outputs[i] = data;
782 }
782 }
783 return outputs;
783 return outputs;
784 };
784 };
785
785
786 /**
786 /**
787 * Class properties
787 * Class properties
788 **/
788 **/
789
789
790 /**
790 /**
791 * Threshold to trigger autoscroll when the OutputArea is resized,
791 * Threshold to trigger autoscroll when the OutputArea is resized,
792 * typically when new outputs are added.
792 * typically when new outputs are added.
793 *
793 *
794 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
794 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
795 * unless it is < 0, in which case autoscroll will never be triggered
795 * unless it is < 0, in which case autoscroll will never be triggered
796 *
796 *
797 * @property auto_scroll_threshold
797 * @property auto_scroll_threshold
798 * @type Number
798 * @type Number
799 * @default 100
799 * @default 100
800 *
800 *
801 **/
801 **/
802 OutputArea.auto_scroll_threshold = 100;
802 OutputArea.auto_scroll_threshold = 100;
803
803
804 /**
804 /**
805 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
805 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
806 * shorter than this are never scrolled.
806 * shorter than this are never scrolled.
807 *
807 *
808 * @property minimum_scroll_threshold
808 * @property minimum_scroll_threshold
809 * @type Number
809 * @type Number
810 * @default 20
810 * @default 20
811 *
811 *
812 **/
812 **/
813 OutputArea.minimum_scroll_threshold = 20;
813 OutputArea.minimum_scroll_threshold = 20;
814
814
815
815
816
816
817 OutputArea.mime_map = {
817 OutputArea.mime_map = {
818 "text/plain" : "text",
818 "text/plain" : "text",
819 "text/html" : "html",
819 "text/html" : "html",
820 "image/svg+xml" : "svg",
820 "image/svg+xml" : "svg",
821 "image/png" : "png",
821 "image/png" : "png",
822 "image/jpeg" : "jpeg",
822 "image/jpeg" : "jpeg",
823 "text/latex" : "latex",
823 "text/latex" : "latex",
824 "application/json" : "json",
824 "application/json" : "json",
825 "application/javascript" : "javascript",
825 "application/javascript" : "javascript",
826 };
826 };
827
827
828 OutputArea.mime_map_r = {
828 OutputArea.mime_map_r = {
829 "text" : "text/plain",
829 "text" : "text/plain",
830 "html" : "text/html",
830 "html" : "text/html",
831 "svg" : "image/svg+xml",
831 "svg" : "image/svg+xml",
832 "png" : "image/png",
832 "png" : "image/png",
833 "jpeg" : "image/jpeg",
833 "jpeg" : "image/jpeg",
834 "latex" : "text/latex",
834 "latex" : "text/latex",
835 "json" : "application/json",
835 "json" : "application/json",
836 "javascript" : "application/javascript",
836 "javascript" : "application/javascript",
837 };
837 };
838
838
839 OutputArea.display_order = [
839 OutputArea.display_order = [
840 'application/javascript',
840 'application/javascript',
841 'text/html',
841 'text/html',
842 'text/latex',
842 'text/latex',
843 'image/svg+xml',
843 'image/svg+xml',
844 'image/png',
844 'image/png',
845 'image/jpeg',
845 'image/jpeg',
846 'application/pdf',
846 'application/pdf',
847 'text/plain'
847 'text/plain'
848 ];
848 ];
849
849
850 OutputArea.append_map = {
850 OutputArea.append_map = {
851 "text/plain" : OutputArea.prototype.append_text,
851 "text/plain" : OutputArea.prototype.append_text,
852 "text/html" : OutputArea.prototype.append_html,
852 "text/html" : OutputArea.prototype.append_html,
853 "image/svg+xml" : OutputArea.prototype.append_svg,
853 "image/svg+xml" : OutputArea.prototype.append_svg,
854 "image/png" : OutputArea.prototype.append_png,
854 "image/png" : OutputArea.prototype.append_png,
855 "image/jpeg" : OutputArea.prototype.append_jpeg,
855 "image/jpeg" : OutputArea.prototype.append_jpeg,
856 "text/latex" : OutputArea.prototype.append_latex,
856 "text/latex" : OutputArea.prototype.append_latex,
857 "application/javascript" : OutputArea.prototype.append_javascript,
857 "application/javascript" : OutputArea.prototype.append_javascript,
858 "application/pdf" : OutputArea.prototype.append_pdf
858 "application/pdf" : OutputArea.prototype.append_pdf
859 };
859 };
860
860
861 IPython.OutputArea = OutputArea;
861 IPython.OutputArea = OutputArea;
862
862
863 return IPython;
863 return IPython;
864
864
865 }(IPython));
865 }(IPython));
@@ -1,175 +1,175 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // SaveWidget
9 // SaveWidget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var SaveWidget = function (selector) {
17 var SaveWidget = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 };
24 };
25
25
26
26
27 SaveWidget.prototype.style = function () {
27 SaveWidget.prototype.style = function () {
28 };
28 };
29
29
30
30
31 SaveWidget.prototype.bind_events = function () {
31 SaveWidget.prototype.bind_events = function () {
32 var that = this;
32 var that = this;
33 this.element.find('span#notebook_name').click(function () {
33 this.element.find('span#notebook_name').click(function () {
34 that.rename_notebook();
34 that.rename_notebook();
35 });
35 });
36 this.element.find('span#notebook_name').hover(function () {
36 this.element.find('span#notebook_name').hover(function () {
37 $(this).addClass("ui-state-hover");
37 $(this).addClass("ui-state-hover");
38 }, function () {
38 }, function () {
39 $(this).removeClass("ui-state-hover");
39 $(this).removeClass("ui-state-hover");
40 });
40 });
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
42 that.update_notebook_name();
42 that.update_notebook_name();
43 that.update_document_title();
43 that.update_document_title();
44 });
44 });
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
46 that.update_notebook_name();
46 that.update_notebook_name();
47 that.update_document_title();
47 that.update_document_title();
48 });
48 });
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
50 that.update_notebook_name();
50 that.update_notebook_name();
51 that.update_document_title();
51 that.update_document_title();
52 that.update_address_bar();
52 that.update_address_bar();
53 });
53 });
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
55 that.set_save_status('Autosave Failed!');
55 that.set_save_status('Autosave Failed!');
56 });
56 });
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
58 that.set_last_checkpoint(data[0]);
58 that.set_last_checkpoint(data[0]);
59 });
59 });
60
60
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
62 that.set_last_checkpoint(data);
62 that.set_last_checkpoint(data);
63 });
63 });
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
65 that.set_autosaved(data.value);
65 that.set_autosaved(data.value);
66 });
66 });
67 };
67 };
68
68
69
69
70 SaveWidget.prototype.rename_notebook = function () {
70 SaveWidget.prototype.rename_notebook = function () {
71 var that = this;
71 var that = this;
72 var dialog = $('<div/>').append(
72 var dialog = $('<div/>').append(
73 $("<p/>").addClass("rename-message")
73 $("<p/>").addClass("rename-message")
74 .text('Enter a new notebook name:')
74 .text('Enter a new notebook name:')
75 ).append(
75 ).append(
76 $("<br/>")
76 $("<br/>")
77 ).append(
77 ).append(
78 $('<input/>').attr('type','text').attr('size','25')
78 $('<input/>').attr('type','text').attr('size','25')
79 .val(IPython.notebook.get_notebook_name())
79 .val(IPython.notebook.get_notebook_name())
80 );
80 );
81 IPython.dialog.modal({
81 IPython.dialog.modal({
82 title: "Rename Notebook",
82 title: "Rename Notebook",
83 body: dialog,
83 body: dialog,
84 buttons : {
84 buttons : {
85 "Cancel": {},
85 "Cancel": {},
86 "OK": {
86 "OK": {
87 class: "btn-primary",
87 class: "btn-primary",
88 click: function () {
88 click: function () {
89 var new_name = $(this).find('input').val();
89 var new_name = $(this).find('input').val();
90 if (!IPython.notebook.test_notebook_name(new_name)) {
90 if (!IPython.notebook.test_notebook_name(new_name)) {
91 $(this).find('.rename-message').text(
91 $(this).find('.rename-message').text(
92 "Invalid notebook name. Notebook names must "+
92 "Invalid notebook name. Notebook names must "+
93 "have 1 or more characters and can contain any characters " +
93 "have 1 or more characters and can contain any characters " +
94 "except :/\\. Please enter a new notebook name:"
94 "except :/\\. Please enter a new notebook name:"
95 );
95 );
96 return false;
96 return false;
97 } else {
97 } else {
98 IPython.notebook.rename(new_name);
98 IPython.notebook.rename(new_name);
99 }
99 }
100 }}
100 }}
101 },
101 },
102 open : function (event, ui) {
102 open : function (event, ui) {
103 var that = $(this);
103 var that = $(this);
104 // Upon ENTER, click the OK button.
104 // Upon ENTER, click the OK button.
105 that.find('input[type="text"]').keydown(function (event, ui) {
105 that.find('input[type="text"]').keydown(function (event, ui) {
106 if (event.which === utils.keycodes.ENTER) {
106 if (event.which === IPython.keyboard.keycodes.enter) {
107 that.find('.btn-primary').first().click();
107 that.find('.btn-primary').first().click();
108 return false;
108 return false;
109 }
109 }
110 });
110 });
111 that.find('input[type="text"]').focus().select();
111 that.find('input[type="text"]').focus().select();
112 }
112 }
113 });
113 });
114 }
114 }
115
115
116
116
117 SaveWidget.prototype.update_notebook_name = function () {
117 SaveWidget.prototype.update_notebook_name = function () {
118 var nbname = IPython.notebook.get_notebook_name();
118 var nbname = IPython.notebook.get_notebook_name();
119 this.element.find('span#notebook_name').text(nbname);
119 this.element.find('span#notebook_name').text(nbname);
120 };
120 };
121
121
122
122
123 SaveWidget.prototype.update_document_title = function () {
123 SaveWidget.prototype.update_document_title = function () {
124 var nbname = IPython.notebook.get_notebook_name();
124 var nbname = IPython.notebook.get_notebook_name();
125 document.title = nbname;
125 document.title = nbname;
126 };
126 };
127
127
128 SaveWidget.prototype.update_address_bar = function(){
128 SaveWidget.prototype.update_address_bar = function(){
129 var base_url = IPython.notebook.base_url;
129 var base_url = IPython.notebook.base_url;
130 var nbname = IPython.notebook.notebook_name;
130 var nbname = IPython.notebook.notebook_name;
131 var path = IPython.notebook.notebook_path;
131 var path = IPython.notebook.notebook_path;
132 var state = {path : path, name: nbname};
132 var state = {path : path, name: nbname};
133 window.history.replaceState(state, "", utils.url_join_encode(
133 window.history.replaceState(state, "", utils.url_join_encode(
134 base_url,
134 base_url,
135 "notebooks",
135 "notebooks",
136 path,
136 path,
137 nbname)
137 nbname)
138 );
138 );
139 };
139 };
140
140
141
141
142 SaveWidget.prototype.set_save_status = function (msg) {
142 SaveWidget.prototype.set_save_status = function (msg) {
143 this.element.find('span#autosave_status').text(msg);
143 this.element.find('span#autosave_status').text(msg);
144 }
144 }
145
145
146 SaveWidget.prototype.set_checkpoint_status = function (msg) {
146 SaveWidget.prototype.set_checkpoint_status = function (msg) {
147 this.element.find('span#checkpoint_status').text(msg);
147 this.element.find('span#checkpoint_status').text(msg);
148 }
148 }
149
149
150 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
150 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
151 if (!checkpoint) {
151 if (!checkpoint) {
152 this.set_checkpoint_status("");
152 this.set_checkpoint_status("");
153 return;
153 return;
154 }
154 }
155 var d = new Date(checkpoint.last_modified);
155 var d = new Date(checkpoint.last_modified);
156 this.set_checkpoint_status(
156 this.set_checkpoint_status(
157 "Last Checkpoint: " + d.format('mmm dd HH:MM')
157 "Last Checkpoint: " + d.format('mmm dd HH:MM')
158 );
158 );
159 }
159 }
160
160
161 SaveWidget.prototype.set_autosaved = function (dirty) {
161 SaveWidget.prototype.set_autosaved = function (dirty) {
162 if (dirty) {
162 if (dirty) {
163 this.set_save_status("(unsaved changes)");
163 this.set_save_status("(unsaved changes)");
164 } else {
164 } else {
165 this.set_save_status("(autosaved)");
165 this.set_save_status("(autosaved)");
166 }
166 }
167 };
167 };
168
168
169
169
170 IPython.SaveWidget = SaveWidget;
170 IPython.SaveWidget = SaveWidget;
171
171
172 return IPython;
172 return IPython;
173
173
174 }(IPython));
174 }(IPython));
175
175
@@ -1,561 +1,561 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12
12
13
13
14 /**
14 /**
15 A module that allow to create different type of Text Cell
15 A module that allow to create different type of Text Cell
16 @module IPython
16 @module IPython
17 @namespace IPython
17 @namespace IPython
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 // TextCell base class
22 // TextCell base class
23 var key = IPython.utils.keycodes;
23 var keycodes = IPython.keyboard.keycodes;
24
24
25 /**
25 /**
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 * cell start as not redered.
27 * cell start as not redered.
28 *
28 *
29 * @class TextCell
29 * @class TextCell
30 * @constructor TextCell
30 * @constructor TextCell
31 * @extend IPython.Cell
31 * @extend IPython.Cell
32 * @param {object|undefined} [options]
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 */
35 */
36 var TextCell = function (options) {
36 var TextCell = function (options) {
37 // in all TextCell/Cell subclasses
37 // in all TextCell/Cell subclasses
38 // do not assign most of members here, just pass it down
38 // do not assign most of members here, just pass it down
39 // in the options dict potentially overwriting what you wish.
39 // in the options dict potentially overwriting what you wish.
40 // they will be assigned in the base class.
40 // they will be assigned in the base class.
41
41
42 // we cannot put this as a class key as it has handle to "this".
42 // we cannot put this as a class key as it has handle to "this".
43 var cm_overwrite_options = {
43 var cm_overwrite_options = {
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 };
45 };
46
46
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50
50
51 IPython.Cell.apply(this, [options]);
51 IPython.Cell.apply(this, [options]);
52
52
53 this.rendered = false;
53 this.rendered = false;
54 };
54 };
55
55
56 TextCell.prototype = new IPython.Cell();
56 TextCell.prototype = new IPython.Cell();
57
57
58 TextCell.options_default = {
58 TextCell.options_default = {
59 cm_config : {
59 cm_config : {
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 mode: 'htmlmixed',
61 mode: 'htmlmixed',
62 lineWrapping : true,
62 lineWrapping : true,
63 }
63 }
64 };
64 };
65
65
66
66
67 /**
67 /**
68 * Create the DOM element of the TextCell
68 * Create the DOM element of the TextCell
69 * @method create_element
69 * @method create_element
70 * @private
70 * @private
71 */
71 */
72 TextCell.prototype.create_element = function () {
72 TextCell.prototype.create_element = function () {
73 IPython.Cell.prototype.create_element.apply(this, arguments);
73 IPython.Cell.prototype.create_element.apply(this, arguments);
74
74
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 cell.attr('tabindex','2');
76 cell.attr('tabindex','2');
77
77
78 var prompt = $('<div/>').addClass('prompt input_prompt');
78 var prompt = $('<div/>').addClass('prompt input_prompt');
79 cell.append(prompt);
79 cell.append(prompt);
80 var inner_cell = $('<div/>').addClass('inner_cell');
80 var inner_cell = $('<div/>').addClass('inner_cell');
81 this.celltoolbar = new IPython.CellToolbar(this);
81 this.celltoolbar = new IPython.CellToolbar(this);
82 inner_cell.append(this.celltoolbar.element);
82 inner_cell.append(this.celltoolbar.element);
83 var input_area = $('<div/>').addClass('input_area');
83 var input_area = $('<div/>').addClass('input_area');
84 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
84 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
85 // The tabindex=-1 makes this div focusable.
85 // The tabindex=-1 makes this div focusable.
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 addClass('rendered_html').attr('tabindex','-1');
87 addClass('rendered_html').attr('tabindex','-1');
88 inner_cell.append(input_area).append(render_area);
88 inner_cell.append(input_area).append(render_area);
89 cell.append(inner_cell);
89 cell.append(inner_cell);
90 this.element = cell;
90 this.element = cell;
91 };
91 };
92
92
93
93
94 /**
94 /**
95 * Bind the DOM evet to cell actions
95 * Bind the DOM evet to cell actions
96 * Need to be called after TextCell.create_element
96 * Need to be called after TextCell.create_element
97 * @private
97 * @private
98 * @method bind_event
98 * @method bind_event
99 */
99 */
100 TextCell.prototype.bind_events = function () {
100 TextCell.prototype.bind_events = function () {
101 IPython.Cell.prototype.bind_events.apply(this);
101 IPython.Cell.prototype.bind_events.apply(this);
102 var that = this;
102 var that = this;
103
103
104 this.element.dblclick(function () {
104 this.element.dblclick(function () {
105 if (that.selected === false) {
105 if (that.selected === false) {
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 }
107 }
108 var cont = that.unrender();
108 var cont = that.unrender();
109 if (cont) {
109 if (cont) {
110 that.focus_editor();
110 that.focus_editor();
111 }
111 }
112 });
112 });
113 };
113 };
114
114
115 TextCell.prototype.handle_keyevent = function (editor, event) {
115 TextCell.prototype.handle_keyevent = function (editor, event) {
116
116
117 // console.log('CM', this.mode, event.which, event.type)
117 // console.log('CM', this.mode, event.which, event.type)
118
118
119 if (this.mode === 'command') {
119 if (this.mode === 'command') {
120 return true;
120 return true;
121 } else if (this.mode === 'edit') {
121 } else if (this.mode === 'edit') {
122 return this.handle_codemirror_keyevent(editor, event);
122 return this.handle_codemirror_keyevent(editor, event);
123 }
123 }
124 };
124 };
125
125
126 /**
126 /**
127 * This method gets called in CodeMirror's onKeyDown/onKeyPress
127 * This method gets called in CodeMirror's onKeyDown/onKeyPress
128 * handlers and is used to provide custom key handling.
128 * handlers and is used to provide custom key handling.
129 *
129 *
130 * Subclass should override this method to have custom handeling
130 * Subclass should override this method to have custom handeling
131 *
131 *
132 * @method handle_codemirror_keyevent
132 * @method handle_codemirror_keyevent
133 * @param {CodeMirror} editor - The codemirror instance bound to the cell
133 * @param {CodeMirror} editor - The codemirror instance bound to the cell
134 * @param {event} event -
134 * @param {event} event -
135 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
135 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
136 */
136 */
137 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
137 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
138 var that = this;
138 var that = this;
139
139
140 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
140 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
141 // Always ignore shift-enter in CodeMirror as we handle it.
141 // Always ignore shift-enter in CodeMirror as we handle it.
142 return true;
142 return true;
143 } else if (event.which === key.UPARROW && event.type === 'keydown') {
143 } else if (event.which === keycodes.up && event.type === 'keydown') {
144 // If we are not at the top, let CM handle the up arrow and
144 // If we are not at the top, let CM handle the up arrow and
145 // prevent the global keydown handler from handling it.
145 // prevent the global keydown handler from handling it.
146 if (!that.at_top()) {
146 if (!that.at_top()) {
147 event.stop();
147 event.stop();
148 return false;
148 return false;
149 } else {
149 } else {
150 return true;
150 return true;
151 }
151 };
152 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
152 } else if (event.which === keycodes.down && event.type === 'keydown') {
153 // If we are not at the bottom, let CM handle the down arrow and
153 // If we are not at the bottom, let CM handle the down arrow and
154 // prevent the global keydown handler from handling it.
154 // prevent the global keydown handler from handling it.
155 if (!that.at_bottom()) {
155 if (!that.at_bottom()) {
156 event.stop();
156 event.stop();
157 return false;
157 return false;
158 } else {
158 } else {
159 return true;
159 return true;
160 }
160 };
161 } else if (event.which === key.ESC && event.type === 'keydown') {
161 } else if (event.which === keycodes.esc && event.type === 'keydown') {
162 if (that.code_mirror.options.keyMap === "vim-insert") {
162 if (that.code_mirror.options.keyMap === "vim-insert") {
163 // vim keyMap is active and in insert mode. In this case we leave vim
163 // vim keyMap is active and in insert mode. In this case we leave vim
164 // insert mode, but remain in notebook edit mode.
164 // insert mode, but remain in notebook edit mode.
165 // Let' CM handle this event and prevent global handling.
165 // Let' CM handle this event and prevent global handling.
166 event.stop();
166 event.stop();
167 return false;
167 return false;
168 } else {
168 } else {
169 // vim keyMap is not active. Leave notebook edit mode.
169 // vim keyMap is not active. Leave notebook edit mode.
170 // Don't let CM handle the event, defer to global handling.
170 // Don't let CM handle the event, defer to global handling.
171 return true;
171 return true;
172 }
172 }
173 }
173 }
174 return false;
174 return false;
175 };
175 };
176
176
177 // Cell level actions
177 // Cell level actions
178
178
179 TextCell.prototype.select = function () {
179 TextCell.prototype.select = function () {
180 var cont = IPython.Cell.prototype.select.apply(this);
180 var cont = IPython.Cell.prototype.select.apply(this);
181 if (cont) {
181 if (cont) {
182 if (this.mode === 'edit') {
182 if (this.mode === 'edit') {
183 this.code_mirror.refresh();
183 this.code_mirror.refresh();
184 }
184 }
185 }
185 }
186 return cont;
186 return cont;
187 };
187 };
188
188
189 TextCell.prototype.unrender = function () {
189 TextCell.prototype.unrender = function () {
190 if (this.read_only) return;
190 if (this.read_only) return;
191 var cont = IPython.Cell.prototype.unrender.apply(this);
191 var cont = IPython.Cell.prototype.unrender.apply(this);
192 if (cont) {
192 if (cont) {
193 var text_cell = this.element;
193 var text_cell = this.element;
194 var output = text_cell.find("div.text_cell_render");
194 var output = text_cell.find("div.text_cell_render");
195 output.hide();
195 output.hide();
196 text_cell.find('div.input_area').show();
196 text_cell.find('div.input_area').show();
197 if (this.get_text() === this.placeholder) {
197 if (this.get_text() === this.placeholder) {
198 this.set_text('');
198 this.set_text('');
199 }
199 }
200 this.refresh();
200 this.refresh();
201 }
201 }
202 return cont;
202 return cont;
203 };
203 };
204
204
205 TextCell.prototype.execute = function () {
205 TextCell.prototype.execute = function () {
206 this.render();
206 this.render();
207 };
207 };
208
208
209 /**
209 /**
210 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
210 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
211 * @method get_text
211 * @method get_text
212 * @retrun {string} CodeMirror current text value
212 * @retrun {string} CodeMirror current text value
213 */
213 */
214 TextCell.prototype.get_text = function() {
214 TextCell.prototype.get_text = function() {
215 return this.code_mirror.getValue();
215 return this.code_mirror.getValue();
216 };
216 };
217
217
218 /**
218 /**
219 * @param {string} text - Codemiror text value
219 * @param {string} text - Codemiror text value
220 * @see TextCell#get_text
220 * @see TextCell#get_text
221 * @method set_text
221 * @method set_text
222 * */
222 * */
223 TextCell.prototype.set_text = function(text) {
223 TextCell.prototype.set_text = function(text) {
224 this.code_mirror.setValue(text);
224 this.code_mirror.setValue(text);
225 this.code_mirror.refresh();
225 this.code_mirror.refresh();
226 };
226 };
227
227
228 /**
228 /**
229 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
229 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
230 * @method get_rendered
230 * @method get_rendered
231 * @return {html} html of rendered element
231 * @return {html} html of rendered element
232 * */
232 * */
233 TextCell.prototype.get_rendered = function() {
233 TextCell.prototype.get_rendered = function() {
234 return this.element.find('div.text_cell_render').html();
234 return this.element.find('div.text_cell_render').html();
235 };
235 };
236
236
237 /**
237 /**
238 * @method set_rendered
238 * @method set_rendered
239 */
239 */
240 TextCell.prototype.set_rendered = function(text) {
240 TextCell.prototype.set_rendered = function(text) {
241 this.element.find('div.text_cell_render').html(text);
241 this.element.find('div.text_cell_render').html(text);
242 };
242 };
243
243
244 /**
244 /**
245 * @method at_top
245 * @method at_top
246 * @return {Boolean}
246 * @return {Boolean}
247 */
247 */
248 TextCell.prototype.at_top = function () {
248 TextCell.prototype.at_top = function () {
249 if (this.rendered) {
249 if (this.rendered) {
250 return true;
250 return true;
251 } else {
251 } else {
252 var cursor = this.code_mirror.getCursor();
252 var cursor = this.code_mirror.getCursor();
253 if (cursor.line === 0 && cursor.ch === 0) {
253 if (cursor.line === 0 && cursor.ch === 0) {
254 return true;
254 return true;
255 } else {
255 } else {
256 return false;
256 return false;
257 }
257 }
258 }
258 }
259 };
259 };
260
260
261 /**
261 /**
262 * @method at_bottom
262 * @method at_bottom
263 * @return {Boolean}
263 * @return {Boolean}
264 * */
264 * */
265 TextCell.prototype.at_bottom = function () {
265 TextCell.prototype.at_bottom = function () {
266 if (this.rendered) {
266 if (this.rendered) {
267 return true;
267 return true;
268 } else {
268 } else {
269 var cursor = this.code_mirror.getCursor();
269 var cursor = this.code_mirror.getCursor();
270 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
270 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
271 return true;
271 return true;
272 } else {
272 } else {
273 return false;
273 return false;
274 }
274 }
275 }
275 }
276 };
276 };
277
277
278 /**
278 /**
279 * Create Text cell from JSON
279 * Create Text cell from JSON
280 * @param {json} data - JSON serialized text-cell
280 * @param {json} data - JSON serialized text-cell
281 * @method fromJSON
281 * @method fromJSON
282 */
282 */
283 TextCell.prototype.fromJSON = function (data) {
283 TextCell.prototype.fromJSON = function (data) {
284 IPython.Cell.prototype.fromJSON.apply(this, arguments);
284 IPython.Cell.prototype.fromJSON.apply(this, arguments);
285 if (data.cell_type === this.cell_type) {
285 if (data.cell_type === this.cell_type) {
286 if (data.source !== undefined) {
286 if (data.source !== undefined) {
287 this.set_text(data.source);
287 this.set_text(data.source);
288 // make this value the starting point, so that we can only undo
288 // make this value the starting point, so that we can only undo
289 // to this state, instead of a blank cell
289 // to this state, instead of a blank cell
290 this.code_mirror.clearHistory();
290 this.code_mirror.clearHistory();
291 // TODO: This HTML needs to be treated as potentially dangerous
291 // TODO: This HTML needs to be treated as potentially dangerous
292 // user input and should be handled before set_rendered.
292 // user input and should be handled before set_rendered.
293 this.set_rendered(data.rendered || '');
293 this.set_rendered(data.rendered || '');
294 this.rendered = false;
294 this.rendered = false;
295 this.render();
295 this.render();
296 }
296 }
297 }
297 }
298 };
298 };
299
299
300 /** Generate JSON from cell
300 /** Generate JSON from cell
301 * @return {object} cell data serialised to json
301 * @return {object} cell data serialised to json
302 */
302 */
303 TextCell.prototype.toJSON = function () {
303 TextCell.prototype.toJSON = function () {
304 var data = IPython.Cell.prototype.toJSON.apply(this);
304 var data = IPython.Cell.prototype.toJSON.apply(this);
305 data.source = this.get_text();
305 data.source = this.get_text();
306 if (data.source == this.placeholder) {
306 if (data.source == this.placeholder) {
307 data.source = "";
307 data.source = "";
308 }
308 }
309 return data;
309 return data;
310 };
310 };
311
311
312
312
313 /**
313 /**
314 * @class MarkdownCell
314 * @class MarkdownCell
315 * @constructor MarkdownCell
315 * @constructor MarkdownCell
316 * @extends IPython.HTMLCell
316 * @extends IPython.HTMLCell
317 */
317 */
318 var MarkdownCell = function (options) {
318 var MarkdownCell = function (options) {
319 options = this.mergeopt(MarkdownCell, options);
319 options = this.mergeopt(MarkdownCell, options);
320
320
321 this.cell_type = 'markdown';
321 this.cell_type = 'markdown';
322 TextCell.apply(this, [options]);
322 TextCell.apply(this, [options]);
323 };
323 };
324
324
325 MarkdownCell.options_default = {
325 MarkdownCell.options_default = {
326 cm_config: {
326 cm_config: {
327 mode: 'gfm'
327 mode: 'gfm'
328 },
328 },
329 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
329 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
330 };
330 };
331
331
332 MarkdownCell.prototype = new TextCell();
332 MarkdownCell.prototype = new TextCell();
333
333
334 /**
334 /**
335 * @method render
335 * @method render
336 */
336 */
337 MarkdownCell.prototype.render = function () {
337 MarkdownCell.prototype.render = function () {
338 var cont = IPython.TextCell.prototype.render.apply(this);
338 var cont = IPython.TextCell.prototype.render.apply(this);
339 if (cont) {
339 if (cont) {
340 var text = this.get_text();
340 var text = this.get_text();
341 var math = null;
341 var math = null;
342 if (text === "") { text = this.placeholder; }
342 if (text === "") { text = this.placeholder; }
343 var text_and_math = IPython.mathjaxutils.remove_math(text);
343 var text_and_math = IPython.mathjaxutils.remove_math(text);
344 text = text_and_math[0];
344 text = text_and_math[0];
345 math = text_and_math[1];
345 math = text_and_math[1];
346 var html = marked.parser(marked.lexer(text));
346 var html = marked.parser(marked.lexer(text));
347 html = $(IPython.mathjaxutils.replace_math(html, math));
347 html = $(IPython.mathjaxutils.replace_math(html, math));
348 // Links in markdown cells should open in new tabs.
348 // Links in markdown cells should open in new tabs.
349 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
349 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
350 try {
350 try {
351 // TODO: This HTML needs to be treated as potentially dangerous
351 // TODO: This HTML needs to be treated as potentially dangerous
352 // user input and should be handled before set_rendered.
352 // user input and should be handled before set_rendered.
353 this.set_rendered(html);
353 this.set_rendered(html);
354 } catch (e) {
354 } catch (e) {
355 console.log("Error running Javascript in Markdown:");
355 console.log("Error running Javascript in Markdown:");
356 console.log(e);
356 console.log(e);
357 this.set_rendered(
357 this.set_rendered(
358 $("<div/>")
358 $("<div/>")
359 .append($("<div/>").text('Error rendering Markdown!').addClass("js-error"))
359 .append($("<div/>").text('Error rendering Markdown!').addClass("js-error"))
360 .append($("<div/>").text(e.toString()).addClass("js-error"))
360 .append($("<div/>").text(e.toString()).addClass("js-error"))
361 .html()
361 .html()
362 );
362 );
363 }
363 }
364 this.element.find('div.input_area').hide();
364 this.element.find('div.input_area').hide();
365 this.element.find("div.text_cell_render").show();
365 this.element.find("div.text_cell_render").show();
366 this.typeset();
366 this.typeset();
367 }
367 }
368 return cont;
368 return cont;
369 };
369 };
370
370
371
371
372 // RawCell
372 // RawCell
373
373
374 /**
374 /**
375 * @class RawCell
375 * @class RawCell
376 * @constructor RawCell
376 * @constructor RawCell
377 * @extends IPython.TextCell
377 * @extends IPython.TextCell
378 */
378 */
379 var RawCell = function (options) {
379 var RawCell = function (options) {
380
380
381 options = this.mergeopt(RawCell,options);
381 options = this.mergeopt(RawCell,options);
382 TextCell.apply(this, [options]);
382 TextCell.apply(this, [options]);
383 this.cell_type = 'raw';
383 this.cell_type = 'raw';
384 // RawCell should always hide its rendered div
384 // RawCell should always hide its rendered div
385 this.element.find('div.text_cell_render').hide();
385 this.element.find('div.text_cell_render').hide();
386 };
386 };
387
387
388 RawCell.options_default = {
388 RawCell.options_default = {
389 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
389 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
390 "It will not be rendered in the notebook.\n" +
390 "It will not be rendered in the notebook.\n" +
391 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
391 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
392 };
392 };
393
393
394 RawCell.prototype = new TextCell();
394 RawCell.prototype = new TextCell();
395
395
396 /** @method bind_events **/
396 /** @method bind_events **/
397 RawCell.prototype.bind_events = function () {
397 RawCell.prototype.bind_events = function () {
398 TextCell.prototype.bind_events.apply(this);
398 TextCell.prototype.bind_events.apply(this);
399 var that = this;
399 var that = this;
400 this.element.focusout(function() {
400 this.element.focusout(function() {
401 that.auto_highlight();
401 that.auto_highlight();
402 });
402 });
403 };
403 };
404
404
405 /**
405 /**
406 * Trigger autodetection of highlight scheme for current cell
406 * Trigger autodetection of highlight scheme for current cell
407 * @method auto_highlight
407 * @method auto_highlight
408 */
408 */
409 RawCell.prototype.auto_highlight = function () {
409 RawCell.prototype.auto_highlight = function () {
410 this._auto_highlight(IPython.config.raw_cell_highlight);
410 this._auto_highlight(IPython.config.raw_cell_highlight);
411 };
411 };
412
412
413 /** @method render **/
413 /** @method render **/
414 RawCell.prototype.render = function () {
414 RawCell.prototype.render = function () {
415 // Make sure that this cell type can never be rendered
415 // Make sure that this cell type can never be rendered
416 if (this.rendered) {
416 if (this.rendered) {
417 this.unrender();
417 this.unrender();
418 }
418 }
419 var text = this.get_text();
419 var text = this.get_text();
420 if (text === "") { text = this.placeholder; }
420 if (text === "") { text = this.placeholder; }
421 this.set_text(text);
421 this.set_text(text);
422 };
422 };
423
423
424
424
425 /**
425 /**
426 * @class HeadingCell
426 * @class HeadingCell
427 * @extends IPython.TextCell
427 * @extends IPython.TextCell
428 */
428 */
429
429
430 /**
430 /**
431 * @constructor HeadingCell
431 * @constructor HeadingCell
432 * @extends IPython.TextCell
432 * @extends IPython.TextCell
433 */
433 */
434 var HeadingCell = function (options) {
434 var HeadingCell = function (options) {
435 options = this.mergeopt(HeadingCell, options);
435 options = this.mergeopt(HeadingCell, options);
436
436
437 this.level = 1;
437 this.level = 1;
438 this.cell_type = 'heading';
438 this.cell_type = 'heading';
439 TextCell.apply(this, [options]);
439 TextCell.apply(this, [options]);
440
440
441 /**
441 /**
442 * heading level of the cell, use getter and setter to access
442 * heading level of the cell, use getter and setter to access
443 * @property level
443 * @property level
444 */
444 */
445 };
445 };
446
446
447 HeadingCell.options_default = {
447 HeadingCell.options_default = {
448 placeholder: "Type Heading Here"
448 placeholder: "Type Heading Here"
449 };
449 };
450
450
451 HeadingCell.prototype = new TextCell();
451 HeadingCell.prototype = new TextCell();
452
452
453 /** @method fromJSON */
453 /** @method fromJSON */
454 HeadingCell.prototype.fromJSON = function (data) {
454 HeadingCell.prototype.fromJSON = function (data) {
455 if (data.level !== undefined){
455 if (data.level !== undefined){
456 this.level = data.level;
456 this.level = data.level;
457 }
457 }
458 TextCell.prototype.fromJSON.apply(this, arguments);
458 TextCell.prototype.fromJSON.apply(this, arguments);
459 };
459 };
460
460
461
461
462 /** @method toJSON */
462 /** @method toJSON */
463 HeadingCell.prototype.toJSON = function () {
463 HeadingCell.prototype.toJSON = function () {
464 var data = TextCell.prototype.toJSON.apply(this);
464 var data = TextCell.prototype.toJSON.apply(this);
465 data.level = this.get_level();
465 data.level = this.get_level();
466 return data;
466 return data;
467 };
467 };
468
468
469 /**
469 /**
470 * can the cell be split into two cells
470 * can the cell be split into two cells
471 * @method is_splittable
471 * @method is_splittable
472 **/
472 **/
473 HeadingCell.prototype.is_splittable = function () {
473 HeadingCell.prototype.is_splittable = function () {
474 return false;
474 return false;
475 };
475 };
476
476
477
477
478 /**
478 /**
479 * can the cell be merged with other cells
479 * can the cell be merged with other cells
480 * @method is_mergeable
480 * @method is_mergeable
481 **/
481 **/
482 HeadingCell.prototype.is_mergeable = function () {
482 HeadingCell.prototype.is_mergeable = function () {
483 return false;
483 return false;
484 };
484 };
485
485
486 /**
486 /**
487 * Change heading level of cell, and re-render
487 * Change heading level of cell, and re-render
488 * @method set_level
488 * @method set_level
489 */
489 */
490 HeadingCell.prototype.set_level = function (level) {
490 HeadingCell.prototype.set_level = function (level) {
491 this.level = level;
491 this.level = level;
492 if (this.rendered) {
492 if (this.rendered) {
493 this.rendered = false;
493 this.rendered = false;
494 this.render();
494 this.render();
495 }
495 }
496 };
496 };
497
497
498 /** The depth of header cell, based on html (h1 to h6)
498 /** The depth of header cell, based on html (h1 to h6)
499 * @method get_level
499 * @method get_level
500 * @return {integer} level - for 1 to 6
500 * @return {integer} level - for 1 to 6
501 */
501 */
502 HeadingCell.prototype.get_level = function () {
502 HeadingCell.prototype.get_level = function () {
503 return this.level;
503 return this.level;
504 };
504 };
505
505
506
506
507 HeadingCell.prototype.set_rendered = function (html) {
507 HeadingCell.prototype.set_rendered = function (html) {
508 this.element.find("div.text_cell_render").html(html);
508 this.element.find("div.text_cell_render").html(html);
509 };
509 };
510
510
511
511
512 HeadingCell.prototype.get_rendered = function () {
512 HeadingCell.prototype.get_rendered = function () {
513 var r = this.element.find("div.text_cell_render");
513 var r = this.element.find("div.text_cell_render");
514 return r.children().first().html();
514 return r.children().first().html();
515 };
515 };
516
516
517
517
518 HeadingCell.prototype.render = function () {
518 HeadingCell.prototype.render = function () {
519 var cont = IPython.TextCell.prototype.render.apply(this);
519 var cont = IPython.TextCell.prototype.render.apply(this);
520 if (cont) {
520 if (cont) {
521 var text = this.get_text();
521 var text = this.get_text();
522 var math = null;
522 var math = null;
523 // Markdown headings must be a single line
523 // Markdown headings must be a single line
524 text = text.replace(/\n/g, ' ');
524 text = text.replace(/\n/g, ' ');
525 if (text === "") { text = this.placeholder; }
525 if (text === "") { text = this.placeholder; }
526 text = Array(this.level + 1).join("#") + " " + text;
526 text = Array(this.level + 1).join("#") + " " + text;
527 var text_and_math = IPython.mathjaxutils.remove_math(text);
527 var text_and_math = IPython.mathjaxutils.remove_math(text);
528 text = text_and_math[0];
528 text = text_and_math[0];
529 math = text_and_math[1];
529 math = text_and_math[1];
530 var html = marked.parser(marked.lexer(text));
530 var html = marked.parser(marked.lexer(text));
531 var h = $(IPython.mathjaxutils.replace_math(html, math));
531 var h = $(IPython.mathjaxutils.replace_math(html, math));
532 // add id and linkback anchor
532 // add id and linkback anchor
533 var hash = h.text().replace(/ /g, '-');
533 var hash = h.text().replace(/ /g, '-');
534 h.attr('id', hash);
534 h.attr('id', hash);
535 h.append(
535 h.append(
536 $('<a/>')
536 $('<a/>')
537 .addClass('anchor-link')
537 .addClass('anchor-link')
538 .attr('href', '#' + hash)
538 .attr('href', '#' + hash)
539 .text('ΒΆ')
539 .text('ΒΆ')
540 );
540 );
541 // TODO: This HTML needs to be treated as potentially dangerous
541 // TODO: This HTML needs to be treated as potentially dangerous
542 // user input and should be handled before set_rendered.
542 // user input and should be handled before set_rendered.
543 this.set_rendered(h);
543 this.set_rendered(h);
544 this.typeset();
544 this.typeset();
545 this.element.find('div.input_area').hide();
545 this.element.find('div.input_area').hide();
546 this.element.find("div.text_cell_render").show();
546 this.element.find("div.text_cell_render").show();
547
547
548 }
548 }
549 return cont;
549 return cont;
550 };
550 };
551
551
552 IPython.TextCell = TextCell;
552 IPython.TextCell = TextCell;
553 IPython.MarkdownCell = MarkdownCell;
553 IPython.MarkdownCell = MarkdownCell;
554 IPython.RawCell = RawCell;
554 IPython.RawCell = RawCell;
555 IPython.HeadingCell = HeadingCell;
555 IPython.HeadingCell = HeadingCell;
556
556
557
557
558 return IPython;
558 return IPython;
559
559
560 }(IPython));
560 }(IPython));
561
561
@@ -1,352 +1,353 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15
15
16 {{super()}}
16 {{super()}}
17
17
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19
19
20 {% endblock %}
20 {% endblock %}
21
21
22 {% block params %}
22 {% block params %}
23
23
24 data-project="{{project}}"
24 data-project="{{project}}"
25 data-base-url="{{base_url}}"
25 data-base-url="{{base_url}}"
26 data-notebook-name="{{notebook_name}}"
26 data-notebook-name="{{notebook_name}}"
27 data-notebook-path="{{notebook_path}}"
27 data-notebook-path="{{notebook_path}}"
28 class="notebook_app"
28 class="notebook_app"
29
29
30 {% endblock %}
30 {% endblock %}
31
31
32
32
33 {% block header %}
33 {% block header %}
34
34
35 <span id="save_widget" class="nav pull-left">
35 <span id="save_widget" class="nav pull-left">
36 <span id="notebook_name"></span>
36 <span id="notebook_name"></span>
37 <span id="checkpoint_status"></span>
37 <span id="checkpoint_status"></span>
38 <span id="autosave_status"></span>
38 <span id="autosave_status"></span>
39 </span>
39 </span>
40
40
41 {% endblock %}
41 {% endblock %}
42
42
43
43
44 {% block site %}
44 {% block site %}
45
45
46 <div id="menubar-container" class="container">
46 <div id="menubar-container" class="container">
47 <div id="menubar">
47 <div id="menubar">
48 <div class="navbar">
48 <div class="navbar">
49 <div class="navbar-inner">
49 <div class="navbar-inner">
50 <div class="container">
50 <div class="container">
51 <ul id="menus" class="nav">
51 <ul id="menus" class="nav">
52 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
52 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <ul id="file_menu" class="dropdown-menu">
53 <ul id="file_menu" class="dropdown-menu">
54 <li id="new_notebook"
54 <li id="new_notebook"
55 title="Make a new notebook (Opens a new window)">
55 title="Make a new notebook (Opens a new window)">
56 <a href="#">New</a></li>
56 <a href="#">New</a></li>
57 <li id="open_notebook"
57 <li id="open_notebook"
58 title="Opens a new window with the Dashboard view">
58 title="Opens a new window with the Dashboard view">
59 <a href="#">Open...</a></li>
59 <a href="#">Open...</a></li>
60 <!-- <hr/> -->
60 <!-- <hr/> -->
61 <li class="divider"></li>
61 <li class="divider"></li>
62 <li id="copy_notebook"
62 <li id="copy_notebook"
63 title="Open a copy of this notebook's contents and start a new kernel">
63 title="Open a copy of this notebook's contents and start a new kernel">
64 <a href="#">Make a Copy...</a></li>
64 <a href="#">Make a Copy...</a></li>
65 <li id="rename_notebook"><a href="#">Rename...</a></li>
65 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
66 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <!-- <hr/> -->
67 <!-- <hr/> -->
68 <li class="divider"></li>
68 <li class="divider"></li>
69 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
69 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <ul class="dropdown-menu">
70 <ul class="dropdown-menu">
71 <li><a href="#"></a></li>
71 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
76 </ul>
76 </ul>
77 </li>
77 </li>
78 <li class="divider"></li>
78 <li class="divider"></li>
79 <li id="print_preview"><a href="#">Print Preview</a></li>
79 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li class="dropdown-submenu"><a href="#">Download as</a>
80 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <ul class="dropdown-menu">
81 <ul class="dropdown-menu">
82 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
82 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_py"><a href="#">Python (.py)</a></li>
83 <li id="download_py"><a href="#">Python (.py)</a></li>
84 <li id="download_html"><a href="#">HTML (.html)</a></li>
84 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 <li id="download_rst"><a href="#">reST (.rst)</a></li>
85 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 </ul>
86 </ul>
87 </li>
87 </li>
88 <li class="divider"></li>
88 <li class="divider"></li>
89
89
90 <li id="kill_and_exit"
90 <li id="kill_and_exit"
91 title="Shutdown this notebook's kernel, and close this window">
91 title="Shutdown this notebook's kernel, and close this window">
92 <a href="#" >Close and halt</a></li>
92 <a href="#" >Close and halt</a></li>
93 </ul>
93 </ul>
94 </li>
94 </li>
95 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
95 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
96 <ul id="edit_menu" class="dropdown-menu">
96 <ul id="edit_menu" class="dropdown-menu">
97 <li id="cut_cell"><a href="#">Cut Cell</a></li>
97 <li id="cut_cell"><a href="#">Cut Cell</a></li>
98 <li id="copy_cell"><a href="#">Copy Cell</a></li>
98 <li id="copy_cell"><a href="#">Copy Cell</a></li>
99 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
99 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
100 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
100 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
101 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
101 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
102 <li id="delete_cell"><a href="#">Delete Cell</a></li>
102 <li id="delete_cell"><a href="#">Delete Cell</a></li>
103 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
103 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
104 <li class="divider"></li>
104 <li class="divider"></li>
105 <li id="split_cell"><a href="#">Split Cell</a></li>
105 <li id="split_cell"><a href="#">Split Cell</a></li>
106 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
106 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
107 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
107 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
108 <li class="divider"></li>
108 <li class="divider"></li>
109 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
109 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
110 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
110 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
111 <li class="divider"></li>
111 <li class="divider"></li>
112 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
112 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
113 </ul>
113 </ul>
114 </li>
114 </li>
115 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
115 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
116 <ul id="view_menu" class="dropdown-menu">
116 <ul id="view_menu" class="dropdown-menu">
117 <li id="toggle_header"
117 <li id="toggle_header"
118 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
118 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
119 <a href="#">Toggle Header</a></li>
119 <a href="#">Toggle Header</a></li>
120 <li id="toggle_toolbar"
120 <li id="toggle_toolbar"
121 title="Show/Hide the action icons (below menu bar)">
121 title="Show/Hide the action icons (below menu bar)">
122 <a href="#">Toggle Toolbar</a></li>
122 <a href="#">Toggle Toolbar</a></li>
123 </ul>
123 </ul>
124 </li>
124 </li>
125 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
125 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
126 <ul id="insert_menu" class="dropdown-menu">
126 <ul id="insert_menu" class="dropdown-menu">
127 <li id="insert_cell_above"
127 <li id="insert_cell_above"
128 title="Insert an empty Code cell above the currently active cell">
128 title="Insert an empty Code cell above the currently active cell">
129 <a href="#">Insert Cell Above</a></li>
129 <a href="#">Insert Cell Above</a></li>
130 <li id="insert_cell_below"
130 <li id="insert_cell_below"
131 title="Insert an empty Code cell below the currently active cell">
131 title="Insert an empty Code cell below the currently active cell">
132 <a href="#">Insert Cell Below</a></li>
132 <a href="#">Insert Cell Below</a></li>
133 </ul>
133 </ul>
134 </li>
134 </li>
135 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
135 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
136 <ul id="cell_menu" class="dropdown-menu">
136 <ul id="cell_menu" class="dropdown-menu">
137 <li id="run_cell" title="Run this cell, and move cursor to the next one">
137 <li id="run_cell" title="Run this cell, and move cursor to the next one">
138 <a href="#">Run</a></li>
138 <a href="#">Run</a></li>
139 <li id="run_cell_select_below" title="Run this cell, select below">
139 <li id="run_cell_select_below" title="Run this cell, select below">
140 <a href="#">Run and Select Below</a></li>
140 <a href="#">Run and Select Below</a></li>
141 <li id="run_cell_insert_below" title="Run this cell, insert below">
141 <li id="run_cell_insert_below" title="Run this cell, insert below">
142 <a href="#">Run and Insert Below</a></li>
142 <a href="#">Run and Insert Below</a></li>
143 <li id="run_all_cells" title="Run all cells in the notebook">
143 <li id="run_all_cells" title="Run all cells in the notebook">
144 <a href="#">Run All</a></li>
144 <a href="#">Run All</a></li>
145 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
145 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
146 <a href="#">Run All Above</a></li>
146 <a href="#">Run All Above</a></li>
147 <li id="run_all_cells_below" title="Run this cell and all cells below it">
147 <li id="run_all_cells_below" title="Run this cell and all cells below it">
148 <a href="#">Run All Below</a></li>
148 <a href="#">Run All Below</a></li>
149 <li class="divider"></li>
149 <li class="divider"></li>
150 <li id="change_cell_type" class="dropdown-submenu"
150 <li id="change_cell_type" class="dropdown-submenu"
151 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
151 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
152 <a href="#">Cell Type</a>
152 <a href="#">Cell Type</a>
153 <ul class="dropdown-menu">
153 <ul class="dropdown-menu">
154 <li id="to_code"
154 <li id="to_code"
155 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
155 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
156 <a href="#">Code</a></li>
156 <a href="#">Code</a></li>
157 <li id="to_markdown"
157 <li id="to_markdown"
158 title="Contents will be rendered as HTML and serve as explanatory text">
158 title="Contents will be rendered as HTML and serve as explanatory text">
159 <a href="#">Markdown</a></li>
159 <a href="#">Markdown</a></li>
160 <li id="to_raw"
160 <li id="to_raw"
161 title="Contents will pass through nbconvert unmodified">
161 title="Contents will pass through nbconvert unmodified">
162 <a href="#">Raw NBConvert</a></li>
162 <a href="#">Raw NBConvert</a></li>
163 <li id="to_heading1"><a href="#">Heading 1</a></li>
163 <li id="to_heading1"><a href="#">Heading 1</a></li>
164 <li id="to_heading2"><a href="#">Heading 2</a></li>
164 <li id="to_heading2"><a href="#">Heading 2</a></li>
165 <li id="to_heading3"><a href="#">Heading 3</a></li>
165 <li id="to_heading3"><a href="#">Heading 3</a></li>
166 <li id="to_heading4"><a href="#">Heading 4</a></li>
166 <li id="to_heading4"><a href="#">Heading 4</a></li>
167 <li id="to_heading5"><a href="#">Heading 5</a></li>
167 <li id="to_heading5"><a href="#">Heading 5</a></li>
168 <li id="to_heading6"><a href="#">Heading 6</a></li>
168 <li id="to_heading6"><a href="#">Heading 6</a></li>
169 </ul>
169 </ul>
170 </li>
170 </li>
171 <li class="divider"></li>
171 <li class="divider"></li>
172 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
172 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
173 <ul class="dropdown-menu">
173 <ul class="dropdown-menu">
174 <li id="toggle_current_output"
174 <li id="toggle_current_output"
175 title="Hide/Show the output of the current cell">
175 title="Hide/Show the output of the current cell">
176 <a href="#">Toggle</a>
176 <a href="#">Toggle</a>
177 </li>
177 </li>
178 <li id="toggle_current_output_scroll"
178 <li id="toggle_current_output_scroll"
179 title="Scroll the output of the current cell">
179 title="Scroll the output of the current cell">
180 <a href="#">Toggle Scrolling</a>
180 <a href="#">Toggle Scrolling</a>
181 </li>
181 </li>
182 <li id="clear_current_output"
182 <li id="clear_current_output"
183 title="Clear the output of the current cell">
183 title="Clear the output of the current cell">
184 <a href="#">Clear</a>
184 <a href="#">Clear</a>
185 </li>
185 </li>
186 </ul>
186 </ul>
187 </li>
187 </li>
188 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
188 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
189 <ul class="dropdown-menu">
189 <ul class="dropdown-menu">
190 <li id="toggle_all_output"
190 <li id="toggle_all_output"
191 title="Hide/Show the output of all cells">
191 title="Hide/Show the output of all cells">
192 <a href="#">Toggle</a>
192 <a href="#">Toggle</a>
193 </li>
193 </li>
194 <li id="toggle_all_output_scroll"
194 <li id="toggle_all_output_scroll"
195 title="Scroll the output of all cells">
195 title="Scroll the output of all cells">
196 <a href="#">Toggle Scrolling</a>
196 <a href="#">Toggle Scrolling</a>
197 </li>
197 </li>
198 <li id="clear_all_output"
198 <li id="clear_all_output"
199 title="Clear the output of all cells">
199 title="Clear the output of all cells">
200 <a href="#">Clear</a>
200 <a href="#">Clear</a>
201 </li>
201 </li>
202 </ul>
202 </ul>
203 </li>
203 </li>
204 </ul>
204 </ul>
205 </li>
205 </li>
206 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
206 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
207 <ul id="kernel_menu" class="dropdown-menu">
207 <ul id="kernel_menu" class="dropdown-menu">
208 <li id="int_kernel"
208 <li id="int_kernel"
209 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
209 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
210 <a href="#">Interrupt</a></li>
210 <a href="#">Interrupt</a></li>
211 <li id="restart_kernel"
211 <li id="restart_kernel"
212 title="Restart the Kernel">
212 title="Restart the Kernel">
213 <a href="#">Restart</a></li>
213 <a href="#">Restart</a></li>
214 </ul>
214 </ul>
215 </li>
215 </li>
216 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
216 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
217 <ul id="help_menu" class="dropdown-menu">
217 <ul id="help_menu" class="dropdown-menu">
218 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
218 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
219 <li class="divider"></li>
219 <li class="divider"></li>
220 {% set
220 {% set
221 sections = (
221 sections = (
222 (
222 (
223 ("http://ipython.org/documentation.html","IPython Help",True),
223 ("http://ipython.org/documentation.html","IPython Help",True),
224 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
224 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
225 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
225 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
226 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
226 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
227 ),(
227 ),(
228 ("http://docs.python.org","Python",True),
228 ("http://docs.python.org","Python",True),
229 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
229 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
230 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
230 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
231 ("http://matplotlib.org/contents.html","Matplotlib",True),
231 ("http://matplotlib.org/contents.html","Matplotlib",True),
232 ("http://docs.sympy.org/dev/index.html","SymPy",True),
232 ("http://docs.sympy.org/dev/index.html","SymPy",True),
233 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
233 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
234 )
234 )
235 )
235 )
236 %}
236 %}
237
237
238 {% for helplinks in sections %}
238 {% for helplinks in sections %}
239 {% for link in helplinks %}
239 {% for link in helplinks %}
240 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
240 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
241 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
241 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
242 {{link[1]}}
242 {{link[1]}}
243 </a></li>
243 </a></li>
244 {% endfor %}
244 {% endfor %}
245 {% if not loop.last %}
245 {% if not loop.last %}
246 <li class="divider"></li>
246 <li class="divider"></li>
247 {% endif %}
247 {% endif %}
248 {% endfor %}
248 {% endfor %}
249 </li>
249 </li>
250 </ul>
250 </ul>
251 </li>
251 </li>
252 </ul>
252 </ul>
253 <div id="kernel_indicator" class="indicator_area pull-right">
253 <div id="kernel_indicator" class="indicator_area pull-right">
254 <i id="kernel_indicator_icon"></i>
254 <i id="kernel_indicator_icon"></i>
255 </div>
255 </div>
256 <div id="modal_indicator" class="indicator_area pull-right">
256 <div id="modal_indicator" class="indicator_area pull-right">
257 <i id="modal_indicator_icon"></i>
257 <i id="modal_indicator_icon"></i>
258 </div>
258 </div>
259 <div id="notification_area"></div>
259 <div id="notification_area"></div>
260 </div>
260 </div>
261 </div>
261 </div>
262 </div>
262 </div>
263 </div>
263 </div>
264 <div id="maintoolbar" class="navbar">
264 <div id="maintoolbar" class="navbar">
265 <div class="toolbar-inner navbar-inner navbar-nobg">
265 <div class="toolbar-inner navbar-inner navbar-nobg">
266 <div id="maintoolbar-container" class="container"></div>
266 <div id="maintoolbar-container" class="container"></div>
267 </div>
267 </div>
268 </div>
268 </div>
269 </div>
269 </div>
270
270
271 <div id="ipython-main-app">
271 <div id="ipython-main-app">
272
272
273 <div id="notebook_panel">
273 <div id="notebook_panel">
274 <div id="notebook"></div>
274 <div id="notebook"></div>
275 <div id="pager_splitter"></div>
275 <div id="pager_splitter"></div>
276 <div id="pager">
276 <div id="pager">
277 <div id='pager_button_area'>
277 <div id='pager_button_area'>
278 </div>
278 </div>
279 <div id="pager-container" class="container"></div>
279 <div id="pager-container" class="container"></div>
280 </div>
280 </div>
281 </div>
281 </div>
282
282
283 </div>
283 </div>
284 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
284 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
285
285
286
286
287 {% endblock %}
287 {% endblock %}
288
288
289
289
290 {% block script %}
290 {% block script %}
291
291
292 {{super()}}
292 {{super()}}
293
293
294 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
294 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
295 <script type="text/javascript">
295 <script type="text/javascript">
296 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
296 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
297 </script>
297 </script>
298 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
298 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
299 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
299 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
301 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
301 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
302 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
302 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
313
313
314 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
314 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
315
315
316 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
316 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
317
317
318 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
318 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
319 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
319 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
320 <script src="{{ static_url("base/js/keyboard.js") }}" type="text/javascript" charset="utf-8"></script>
320 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
321 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
321 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
322 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
322 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
323 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
323 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
345 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
345
346
346 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
347 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
347
348
348 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
351 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
351
352
352 {% endblock %}
353 {% endblock %}
@@ -1,24 +1,24 b''
1 //
1 //
2 // Check for errors with up and down arrow presses in a non-empty notebook.
2 // Check for errors with up and down arrow presses in a non-empty notebook.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 var result = this.evaluate(function() {
5 var result = this.evaluate(function() {
6 IPython.notebook.command_mode();
6 IPython.notebook.command_mode();
7 pos0 = IPython.notebook.get_selected_index();
7 pos0 = IPython.notebook.get_selected_index();
8 IPython.utils.press(IPython.keycodes.b)
8 IPython.keyboard.trigger_keydown('b');
9 pos1 = IPython.notebook.get_selected_index();
9 pos1 = IPython.notebook.get_selected_index();
10 IPython.utils.press(IPython.keycodes.b)
10 IPython.keyboard.trigger_keydown('b');
11 pos2 = IPython.notebook.get_selected_index();
11 pos2 = IPython.notebook.get_selected_index();
12 // Simulate the "up arrow" and "down arrow" keys.
12 // Simulate the "up arrow" and "down arrow" keys.
13 IPython.utils.press_up();
13 IPython.keyboard.trigger_keydown('up');
14 pos3 = IPython.notebook.get_selected_index();
14 pos3 = IPython.notebook.get_selected_index();
15 IPython.utils.press_down();
15 IPython.keyboard.trigger_keydown('down');
16 pos4 = IPython.notebook.get_selected_index();
16 pos4 = IPython.notebook.get_selected_index();
17 return pos0 == 0 &&
17 return pos0 == 0 &&
18 pos1 == 1 &&
18 pos1 == 1 &&
19 pos2 == 2 &&
19 pos2 == 2 &&
20 pos3 == 1 &&
20 pos3 == 1 &&
21 pos4 == 2;
21 pos4 == 2;
22 });
22 });
23 this.test.assertTrue(result, 'Up/down arrow okay in non-empty notebook.');
23 this.test.assertTrue(result, 'Up/down arrow okay in non-empty notebook.');
24 });
24 });
@@ -1,21 +1,21 b''
1 //
1 //
2 // Check for errors with up and down arrow presses in an empty notebook.
2 // Check for errors with up and down arrow presses in an empty notebook.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 var result = this.evaluate(function() {
5 var result = this.evaluate(function() {
6 var ncells = IPython.notebook.ncells();
6 var ncells = IPython.notebook.ncells();
7 var i;
7 var i;
8
8
9 // Delete all cells.
9 // Delete all cells.
10 for (i = 0; i < ncells; i++) {
10 for (i = 0; i < ncells; i++) {
11 IPython.notebook.delete_cell();
11 IPython.notebook.delete_cell();
12 }
12 }
13
13
14 // Simulate the "up arrow" and "down arrow" keys.
14 // Simulate the "up arrow" and "down arrow" keys.
15 //
15 //
16 IPython.utils.press_up();
16 IPython.keyboard.trigger_keydown('up');
17 IPython.utils.press_down();
17 IPython.keyboard.trigger_keydown('down');
18 return true;
18 return true;
19 });
19 });
20 this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
20 this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
21 });
21 });
@@ -1,71 +1,71 b''
1 //
1 //
2 // Test code cell execution.
2 // Test code cell execution.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 this.evaluate(function () {
5 this.evaluate(function () {
6 var cell = IPython.notebook.get_cell(0);
6 var cell = IPython.notebook.get_cell(0);
7 cell.set_text('a=10; print(a)');
7 cell.set_text('a=10; print(a)');
8 cell.execute();
8 cell.execute();
9 });
9 });
10
10
11 this.wait_for_output(0);
11 this.wait_for_output(0);
12
12
13 // refactor this into just a get_output(0)
13 // refactor this into just a get_output(0)
14 this.then(function () {
14 this.then(function () {
15 var result = this.get_output_cell(0);
15 var result = this.get_output_cell(0);
16 this.test.assertEquals(result.text, '10\n', 'cell execute (using js)');
16 this.test.assertEquals(result.text, '10\n', 'cell execute (using js)');
17 });
17 });
18
18
19
19
20 // do it again with the keyboard shortcut
20 // do it again with the keyboard shortcut
21 this.thenEvaluate(function () {
21 this.thenEvaluate(function () {
22 var cell = IPython.notebook.get_cell(0);
22 var cell = IPython.notebook.get_cell(0);
23 cell.set_text('a=11; print(a)');
23 cell.set_text('a=11; print(a)');
24 cell.clear_output();
24 cell.clear_output();
25 IPython.utils.press_shift_enter();
25 IPython.keyboard.trigger_keydown('shift+enter');
26 });
26 });
27
27
28 this.wait_for_output(0);
28 this.wait_for_output(0);
29
29
30 this.then(function () {
30 this.then(function () {
31 var result = this.get_output_cell(0);
31 var result = this.get_output_cell(0);
32 var num_cells = this.get_cells_length();
32 var num_cells = this.get_cells_length();
33 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
33 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
34 this.test.assertEquals(num_cells, 2, 'shift-enter adds a new cell at the bottom')
34 this.test.assertEquals(num_cells, 2, 'shift-enter adds a new cell at the bottom')
35 });
35 });
36
36
37 // do it again with the keyboard shortcut
37 // do it again with the keyboard shortcut
38 this.thenEvaluate(function () {
38 this.thenEvaluate(function () {
39 IPython.notebook.select(1);
39 IPython.notebook.select(1);
40 IPython.notebook.delete_cell();
40 IPython.notebook.delete_cell();
41 var cell = IPython.notebook.get_cell(0);
41 var cell = IPython.notebook.get_cell(0);
42 cell.set_text('a=12; print(a)');
42 cell.set_text('a=12; print(a)');
43 cell.clear_output();
43 cell.clear_output();
44 IPython.utils.press_ctrl_enter();
44 IPython.keyboard.trigger_keydown('ctrl+enter');
45 });
45 });
46
46
47 this.wait_for_output(0);
47 this.wait_for_output(0);
48
48
49 this.then(function () {
49 this.then(function () {
50 var result = this.get_output_cell(0);
50 var result = this.get_output_cell(0);
51 var num_cells = this.get_cells_length();
51 var num_cells = this.get_cells_length();
52 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
52 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
53 this.test.assertEquals(num_cells, 1, 'ctrl-enter adds no new cell at the bottom')
53 this.test.assertEquals(num_cells, 1, 'ctrl-enter adds no new cell at the bottom')
54 });
54 });
55
55
56 // press the "play" triangle button in the toolbar
56 // press the "play" triangle button in the toolbar
57 this.thenEvaluate(function () {
57 this.thenEvaluate(function () {
58 var cell = IPython.notebook.get_cell(0);
58 var cell = IPython.notebook.get_cell(0);
59 IPython.notebook.select(0);
59 IPython.notebook.select(0);
60 cell.clear_output();
60 cell.clear_output();
61 cell.set_text('a=13; print(a)');
61 cell.set_text('a=13; print(a)');
62 $('#run_b').click();
62 $('#run_b').click();
63 });
63 });
64
64
65 this.wait_for_output(0);
65 this.wait_for_output(0);
66
66
67 this.then(function () {
67 this.then(function () {
68 var result = this.get_output_cell(0);
68 var result = this.get_output_cell(0);
69 this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)')
69 this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)')
70 });
70 });
71 });
71 });
@@ -1,44 +1,44 b''
1 //
1 //
2 // Test kernel interrupt
2 // Test kernel interrupt
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 this.evaluate(function () {
5 this.evaluate(function () {
6 var cell = IPython.notebook.get_cell(0);
6 var cell = IPython.notebook.get_cell(0);
7 cell.set_text(
7 cell.set_text(
8 'import time'+
8 'import time'+
9 '\nfor x in range(3):'+
9 '\nfor x in range(3):'+
10 '\n time.sleep(1)'
10 '\n time.sleep(1)'
11 );
11 );
12 cell.execute();
12 cell.execute();
13 });
13 });
14
14
15 this.wait_for_busy();
15 this.wait_for_busy();
16
16
17 // interrupt using menu item (Kernel -> Interrupt)
17 // interrupt using menu item (Kernel -> Interrupt)
18 this.thenClick('li#int_kernel');
18 this.thenClick('li#int_kernel');
19
19
20 this.wait_for_output(0);
20 this.wait_for_output(0);
21
21
22 this.then(function () {
22 this.then(function () {
23 var result = this.get_output_cell(0);
23 var result = this.get_output_cell(0);
24 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (mouseclick)');
24 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (mouseclick)');
25 });
25 });
26
26
27 // run cell 0 again, now interrupting using keyboard shortcut
27 // run cell 0 again, now interrupting using keyboard shortcut
28 this.thenEvaluate(function () {
28 this.thenEvaluate(function () {
29 cell.clear_output();
29 cell.clear_output();
30 cell.execute();
30 cell.execute();
31 });
31 });
32
32
33 // interrupt using Ctrl-M I keyboard shortcut
33 // interrupt using Ctrl-M I keyboard shortcut
34 this.thenEvaluate( function() {
34 this.thenEvaluate( function() {
35 IPython.utils.press_ghetto(IPython.utils.keycodes.I)
35 IPython.keyboard.trigger_keydown('i');
36 });
36 });
37
37
38 this.wait_for_output(0);
38 this.wait_for_output(0);
39
39
40 this.then(function () {
40 this.then(function () {
41 var result = this.get_output_cell(0);
41 var result = this.get_output_cell(0);
42 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (shortcut)');
42 this.test.assertEquals(result.ename, 'KeyboardInterrupt', 'keyboard interrupt (shortcut)');
43 });
43 });
44 });
44 });
@@ -1,38 +1,38 b''
1 //
1 //
2 // Test merging two notebook cells.
2 // Test merging two notebook cells.
3 //
3 //
4 casper.notebook_test(function() {
4 casper.notebook_test(function() {
5 var output = this.evaluate(function () {
5 var output = this.evaluate(function () {
6 // Fill in test data.
6 // Fill in test data.
7 IPython.notebook.command_mode();
7 IPython.notebook.command_mode();
8 var set_cell_text = function () {
8 var set_cell_text = function () {
9 var cell_one = IPython.notebook.get_selected_cell();
9 var cell_one = IPython.notebook.get_selected_cell();
10 cell_one.set_text('a = 5');
10 cell_one.set_text('a = 5');
11
11
12 IPython.utils.press(IPython.keycodes.b)
12 IPython.keyboard.trigger_keydown('b');
13 var cell_two = IPython.notebook.get_selected_cell();
13 var cell_two = IPython.notebook.get_selected_cell();
14 cell_two.set_text('print(a)');
14 cell_two.set_text('print(a)');
15 };
15 };
16
16
17 // merge_cell_above()
17 // merge_cell_above()
18 set_cell_text();
18 set_cell_text();
19 IPython.notebook.merge_cell_above();
19 IPython.notebook.merge_cell_above();
20 var merged_above = IPython.notebook.get_selected_cell();
20 var merged_above = IPython.notebook.get_selected_cell();
21
21
22 // merge_cell_below()
22 // merge_cell_below()
23 set_cell_text();
23 set_cell_text();
24 IPython.notebook.select(0);
24 IPython.notebook.select(0);
25 IPython.notebook.merge_cell_below();
25 IPython.notebook.merge_cell_below();
26 var merged_below = IPython.notebook.get_selected_cell();
26 var merged_below = IPython.notebook.get_selected_cell();
27
27
28 return {
28 return {
29 above: merged_above.get_text(),
29 above: merged_above.get_text(),
30 below: merged_below.get_text()
30 below: merged_below.get_text()
31 };
31 };
32 });
32 });
33
33
34 this.test.assertEquals(output.above, 'a = 5\nprint(a)',
34 this.test.assertEquals(output.above, 'a = 5\nprint(a)',
35 'Successful merge_cell_above().');
35 'Successful merge_cell_above().');
36 this.test.assertEquals(output.below, 'a = 5\nprint(a)',
36 this.test.assertEquals(output.below, 'a = 5\nprint(a)',
37 'Successful merge_cell_below().');
37 'Successful merge_cell_below().');
38 });
38 });
General Comments 0
You need to be logged in to leave comments. Login now