File size: 15,598 Bytes
a1271b8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
// Copyright (c) Jupyter-Contrib Team.
// Distributed under the terms of the Modified BSD License.
define([
'jquery',
'base/js/namespace',
'base/js/events',
'notebook/js/codecell',
], function(
$,
Jupyter,
events,
codecell
) {
'use strict';
var CodeCell = codecell.CodeCell;
// this wrapper function allows config & hotkeys to be per-plugin
function KernelExecOnCells(mod_name, cfg) {
this.mod_name = mod_name;
this.mod_log_prefix = '[' + this.mod_name + ']';
this.mod_edit_shortcuts = {};
this.mod_cmd_shortcuts = {};
this.default_kernel_config = {
library: '',
prefix: '',
postfix: '',
replacements_json_to_kernel: [],
trim_formatted_text: true
};
// gives default settings
var default_cfg = {
add_toolbar_button: true,
hotkeys: {
process_selected: 'Ctrl-L',
process_all: 'Ctrl-Shift-L',
},
register_hotkey: true,
show_alerts_for_errors: true,
show_alerts_for_not_supported_kernel: false,
button_icon: 'fa-legal',
button_label: mod_name,
kbd_shortcut_text: mod_name,
kernel_config_map: {},
actions: null, // to be filled by register_actions
};
// extend a new object, to avoid interference with other nbextensions
// derived from the same base class
this.cfg = $.extend(true, {}, default_cfg, cfg);
// set default json string, will later be updated from config
// before it is parsed into an object
this.cfg.kernel_config_map_json = JSON.stringify(this.cfg.kernel_config_map);
} // end per-plugin wrapper define_plugin_functions
// Prototypes
// ----------
/**
* return a Promise which will resolve/reject based on the kernel message
* type.
* The returned promise will be
* - resolved if the message was not an error
* - rejected using the message's error text if msg.msg_type is "error"
*/
KernelExecOnCells.prototype.convert_error_msg_to_broken_promise = function(msg) {
var that = this;
return new Promise(function(resolve, reject) {
if (msg.msg_type == 'error') {
return reject(that.mod_log_prefix + '\n Error: ' + msg.content.ename + '\n' + msg.content.evalue);
}
return resolve(msg);
});
};
KernelExecOnCells.prototype.convert_loading_library_error_msg_to_broken_promise = function(msg) {
var that = this;
return new Promise(function(resolve, reject) {
if (msg.msg_type == 'error') {
return reject(that.mod_log_prefix + '\n Error loading library for ' +
Jupyter.notebook.metadata.kernelspec.language + ':\n' +
msg.content.ename + msg.content.evalue +
'\n\nCheck that the appropriate library/module is correctly installed (read ' +
that.mod_name + '\'s documentation for details)');
}
return resolve(msg);
});
};
KernelExecOnCells.prototype.get_kernel_config = function() {
var kernelLanguage = Jupyter.notebook.metadata.kernelspec.language.toLowerCase();
var kernel_config = this.cfg.kernel_config_map[kernelLanguage];
// true => deep
return $.extend(true, {}, this.default_kernel_config, kernel_config);
};
KernelExecOnCells.prototype.transform_json_string_to_kernel_string = function(str, kernel_config) {
for (var ii = 0; ii < kernel_config.replacements_json_to_kernel.length; ii++) {
var from = kernel_config.replacements_json_to_kernel[ii][0];
var to = kernel_config.replacements_json_to_kernel[ii][1];
str = str.replace(from, to);
}
return str;
};
/**
* construct functions as callbacks for the autoformat cell promise. This
* is necessary because javascript lacks loop scoping, so if we don't use
* this IIFE pattern, cell_index & cell are passed by reference, and every
* callback ends up using the same value
*/
KernelExecOnCells.prototype.construct_cell_callbacks = function(cell_index, cell) {
var that = this;
var on_success = function(formatted_text) {
cell.set_text(formatted_text);
};
var on_failure = function(reason) {
console.warn(
that.mod_log_prefix,
'error processing cell', cell_index + ':\n',
reason
);
if (that.cfg.show_alerts_for_errors) {
alert(reason);
}
};
return [on_success, on_failure];
};
KernelExecOnCells.prototype.autoformat_cells = function(indices) {
if (indices === undefined) {
indices = Jupyter.notebook.get_selected_cells_indices();
}
var kernel_config = this.get_kernel_config();
for (var ii = 0; ii < indices.length; ii++) {
var cell_index = indices[ii];
var cell = Jupyter.notebook.get_cell(cell_index);
if (!(cell instanceof CodeCell)) {
continue;
}
// IIFE because otherwise cell_index & cell are passed by reference
var callbacks = this.construct_cell_callbacks(cell_index, cell);
this.autoformat_text(cell.get_text(), kernel_config).then(callbacks[0], callbacks[1]);
}
};
KernelExecOnCells.prototype.autoformat_text = function(text, kernel_config) {
var that = this;
return new Promise(function(resolve, reject) {
kernel_config = kernel_config || that.get_kernel_config();
var kernel_str = that.transform_json_string_to_kernel_string(
JSON.stringify(text), kernel_config);
Jupyter.notebook.kernel.execute(
kernel_config.prefix + kernel_str + kernel_config.postfix, {
iopub: {
output: function(msg) {
return resolve(that.convert_error_msg_to_broken_promise(msg).then(
function on_success(msg) {
// print goes to stream text => msg.content.text
// but for some kernels (eg nodejs) can be called as result of exec
if (msg.content.text !== undefined) {
var formatted_text;
try {
formatted_text = String(JSON.parse(msg.content.text));
}
catch (err) {
return Promise.reject(err);
}
if (kernel_config.trim_formatted_text) {
formatted_text = formatted_text.trim();
}
return formatted_text;
}
}
));
}
}
}, { silent: false }
);
});
};
KernelExecOnCells.prototype.add_toolbar_button = function() {
if ($('#' + this.mod_name + '_button').length < 1) {
var button_group_id = this.mod_name + '_button';
var that = this;
Jupyter.toolbar.add_buttons_group([{
label: ' ', //space otherwise add_buttons fails -- This label is inserted as a button description AND bubble help
icon: this.cfg.button_icon,
callback: function(evt) {
that.autoformat_cells(
evt.shiftKey ? Jupyter.notebook.get_cells().map(function (cell, idx) { return idx; }) : undefined
);
},
}], button_group_id);
// Correct add_buttons_group default
// Change title --> inserts bubble help
// redefine icon to remove spurious space
var w = $('#'+ button_group_id +' > .btn')[0];
w.title = this.cfg.kbd_shortcut_text + ' selected cell(s) (add shift for all cells)'
w.innerHTML = '<i class="' + this.cfg.button_icon + ' fa"></i>'
}
}
KernelExecOnCells.prototype.add_keyboard_shortcuts = function() {
var new_shortcuts = {};
new_shortcuts[this.cfg.hotkeys.process_selected] = this.cfg.actions.process_selected.name;
new_shortcuts[this.cfg.hotkeys.process_all] = this.cfg.actions.process_all.name;
Jupyter.keyboard_manager.edit_shortcuts.add_shortcuts(new_shortcuts);
Jupyter.keyboard_manager.command_shortcuts.add_shortcuts(new_shortcuts);
};
KernelExecOnCells.prototype.register_actions = function() {
/**
* it's important that the actions created by registering keyboard
* shortcuts get different names, as otherwise a default action is
* created, whose name is a string representation of the handler
* function.
* Since this library uses the same handler function for all plugins,
* just with different contexts (different values of cfg), their
* string representations are the same, and the last one to be
* registered overwrites all previous versions.
* This is essentially an issue with notebook, but it encourages us to
* use actions, which is where notebook is going anyway.
*/
var actions = this.cfg.actions = {};
var that = this;
actions.process_selected = {
help: that.cfg.kbd_shortcut_text + ' selected cell(s)',
help_index: 'yf',
icon: that.cfg.button_icon,
handler: function(evt) { that.autoformat_cells(); },
};
actions.process_all = {
help: that.cfg.kbd_shortcut_text + " the whole notebook",
help_index: 'yf',
icon: that.cfg.button_icon,
handler: function(evt) {
that.autoformat_cells(Jupyter.notebook.get_cells().map(function (cell, idx) { return idx; }));
},
};
actions.process_selected.name = Jupyter.keyboard_manager.actions.register(
actions.process_selected, 'process_selected_cells', that.mod_name);
actions.process_all.name = Jupyter.keyboard_manager.actions.register(
actions.process_all, 'process_all_cells', that.mod_name);
};
KernelExecOnCells.prototype.setup_for_new_kernel = function() {
var that = this;
var kernelLanguage = Jupyter.notebook.metadata.kernelspec.language.toLowerCase();
var kernel_config = this.cfg.kernel_config_map[kernelLanguage];
if (kernel_config === undefined) {
$('#' + this.mod_name + '_button').remove();
var err = this.mod_log_prefix + " Sorry, can't use kernel language " + kernelLanguage + ".\n" +
"Configurations are currently only defined for the following languages:\n" +
Object.keys(this.cfg.kernel_config_map).join(', ') + "\n" +
"See readme for more details."
if (this.cfg.show_alerts_for_not_supported_kernel) {
alert(err);
}
else {
console.error(err);
}
// also remove keyboard shortcuts
if (this.cfg.register_hotkey) {
try {
Jupyter.keyboard_manager.edit_shortcuts.remove_shortcut(this.cfg.hotkeys.process_selected);
Jupyter.keyboard_manager.edit_shortcuts.remove_shortcut(this.cfg.hotkeys.process_all);
Jupyter.keyboard_manager.command_shortcuts.remove_shortcut(this.cfg.hotkeys.process_all);
} catch (err) {}
}
} else { // kernel language is supported
if (this.cfg.add_toolbar_button) {
this.add_toolbar_button();
}
if (this.cfg.register_hotkey) {
this.add_keyboard_shortcuts();
}
Jupyter.notebook.kernel.execute(
kernel_config.library, {
iopub: {
output: function(msg) {
return that.convert_loading_library_error_msg_to_broken_promise(msg)
.catch(
function on_failure(err) {
if (that.cfg.show_alerts_for_errors) {
alert(err);
}
else {
console.error(err);
}
}
);
}
}
}, { silent: false }
);
}
};
KernelExecOnCells.prototype.initialize_plugin = function() {
var that = this;
// first, load config
Jupyter.notebook.config.loaded
// now update default config with that loaded from server
.then(function on_success() {
$.extend(true, that.cfg, Jupyter.notebook.config.data[that.mod_name]);
}, function on_error(err) {
console.warn(that.mod_log_prefix, 'error loading config:', err);
})
// next parse json config values
.then(function on_success() {
var parsed_kernel_cfg = JSON.parse(that.cfg.kernel_config_map_json);
$.extend(that.cfg.kernel_config_map, parsed_kernel_cfg);
})
// if we failed to parse the json values in the config
// using catch pattern, we attempt to continue anyway using defaults
.catch(function on_error(err) {
console.warn(
that.mod_log_prefix, 'error parsing config variable',
that.mod_name + '.kernel_config_map_json to a json object:',
err
);
})
// now do things which required the config to be loaded
.then(function on_success() {
that.register_actions(); // register actions
// kernel may already have been loaded before we get here, in which
// case we've missed the kernel_ready.Kernel event, so try ctx
if (typeof Jupyter.notebook.kernel !== "undefined" && Jupyter.notebook.kernel !== null) {
that.setup_for_new_kernel();
}
// on kernel_ready.Kernel, a new kernel has been started
events.on("kernel_ready.Kernel", function(evt, data) {
console.log(that.mod_log_prefix, 'restarting for new kernel_ready.Kernel event');
that.setup_for_new_kernel();
});
}).catch(function on_error(err) {
console.error(that.mod_log_prefix, 'error loading:', err);
});
};
return {define_plugin: KernelExecOnCells};
});
|