main.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. define([
  2. 'require',
  3. 'jquery',
  4. 'base/js/namespace',
  5. 'base/js/events',
  6. 'notebook/js/codecell'
  7. ], function(
  8. requirejs,
  9. $,
  10. Jupyter,
  11. events,
  12. codecell
  13. ) {
  14. "use strict";
  15. var mod_name = "mickaSearch";
  16. var log_prefix = '[' + mod_name + '] ';
  17. // ...........Parameters configuration......................
  18. // define default values for config parameters if they were not present in general settings (notebook.json)
  19. var cfg = {
  20. 'window_display': false,
  21. 'cols': {
  22. 'lenName': 16,
  23. 'lenType': 16,
  24. 'lenVar': 40
  25. },
  26. 'kernels_config' : {
  27. 'python': {
  28. library: 'var_list.py',
  29. delete_cmd_prefix: 'del ',
  30. delete_cmd_postfix: '',
  31. varRefreshCmd: 'print(var_dic_list())'
  32. },
  33. 'r': {
  34. library: 'var_list.r',
  35. delete_cmd_prefix: 'rm(',
  36. delete_cmd_postfix: ') ',
  37. varRefreshCmd: 'cat(var_dic_list()) '
  38. }
  39. },
  40. 'types_to_exclude': ['module', 'function', 'builtin_function_or_method', 'instance', '_Feature']
  41. }
  42. //.....................global variables....
  43. var st = {}
  44. st.config_loaded = false;
  45. st.extension_initialized = false;
  46. st.code_init = "";
  47. function read_config(cfg, callback) { // read after nb is loaded
  48. var config = Jupyter.notebook.config;
  49. config.loaded.then(function() {
  50. // config may be specified at system level or at document level.
  51. // first, update defaults with config loaded from server
  52. cfg = $.extend(true, cfg, config.data.mickaSearch);
  53. // then update cfg with some vars found in current notebook metadata
  54. // and save in nb metadata (then can be modified per document)
  55. // window_display is taken from notebook metadata
  56. if (Jupyter.notebook.metadata.mickaSearch) {
  57. if (Jupyter.notebook.metadata.mickaSearch.window_display)
  58. cfg.window_display = Jupyter.notebook.metadata.mickaSearch.window_display;
  59. }
  60. cfg = Jupyter.notebook.metadata.mickaSearch = $.extend(true,
  61. cfg, Jupyter.notebook.metadata.mickaSearch);
  62. // but cols and kernels_config are taken from system (if defined)
  63. if (config.data.mickaSearch) {
  64. if (config.data.mickaSearch.cols) {
  65. cfg.cols = $.extend(true, cfg.cols, config.data.mickaSearch.cols);
  66. }
  67. if (config.data.mickaSearch.kernels_config) {
  68. cfg.kernels_config = $.extend(true, cfg.kernels_config, config.data.mickaSearch.kernels_config);
  69. }
  70. }
  71. // call callbacks
  72. callback && callback();
  73. st.config_loaded = true;
  74. })
  75. return cfg;
  76. }
  77. var sortable;
  78. function togglemickaSearch() {
  79. toggle_mickaSearch(cfg, st)
  80. }
  81. var mickaSearch_button = function() {
  82. if (!Jupyter.toolbar) {
  83. events.on("app_initialized.NotebookApp", mickaSearch_button);
  84. return;
  85. }
  86. if ($("#mickaSearch_button").length === 0) {
  87. $(Jupyter.toolbar.add_buttons_group([
  88. Jupyter.keyboard_manager.actions.register ({
  89. 'help' : 'MIcKA Search',
  90. 'icon' : 'fa-cat',
  91. 'handler': togglemickaSearch,
  92. }, 'toggle-variable-inspector', 'mickaSearch')
  93. ])).find('.btn').attr('id', 'mickaSearch_button');
  94. }
  95. };
  96. var load_css = function() {
  97. var link = document.createElement("link");
  98. link.type = "text/css";
  99. link.rel = "stylesheet";
  100. link.href = requirejs.toUrl("./main.css");
  101. document.getElementsByTagName("head")[0].appendChild(link);
  102. };
  103. function html_table(jsonVars) {
  104. function _trunc(x, L) {
  105. x = String(x)
  106. if (x.length < L) return x
  107. else return x.substring(0, L - 3) + '...'
  108. }
  109. var kernelLanguage = Jupyter.notebook.metadata.kernelspec.language.toLowerCase()
  110. var kernel_config = cfg.kernels_config[kernelLanguage];
  111. var varList = JSON.parse(String(jsonVars))
  112. var shape_str = '';
  113. var has_shape = false;
  114. if (varList.some(listVar => "varShape" in listVar && listVar.varShape !== '')) { //if any of them have a shape
  115. shape_str = '<th >Shape</th>';
  116. has_shape = true;
  117. }
  118. var beg_table = '<div class=\"inspector\"><table class=\"table fixed table-condensed table-nonfluid \"><col /> \
  119. <col /><col /><thead><tr><th >X</th><th >Name</th><th >Type</th><th >Size</th>' + shape_str + '<th >Value</th></tr></thead><tr><td> \
  120. </td></tr>';
  121. varList.forEach(listVar => {
  122. var shape_col_str = '</td><td>';
  123. if (has_shape) {
  124. shape_col_str = '</td><td>' + listVar.varShape + '</td><td>';
  125. }
  126. beg_table +=
  127. '<tr><td><a href=\"#\" onClick=\"Jupyter.notebook.kernel.execute(\'' +
  128. kernel_config.delete_cmd_prefix + listVar.varName + kernel_config.delete_cmd_postfix + '\'' + '); ' +
  129. 'Jupyter.notebook.events.trigger(\'varRefresh\'); \">x</a></td>' +
  130. '<td>' + _trunc(listVar.varName, cfg.cols.lenName) + '</td><td>' + _trunc(listVar.varType, cfg.cols.lenType) +
  131. '</td><td>' + listVar.varSize + shape_col_str + _trunc(listVar.varContent, cfg.cols.lenVar) +
  132. '</td></tr>';
  133. });
  134. var full_table = beg_table + '</table></div>';
  135. return full_table;
  136. }
  137. function code_exec_callback(msg) {
  138. var jsonVars = msg.content['text'];
  139. var notWellDefined = false;
  140. if (msg.content.evalue)
  141. notWellDefined = msg.content.evalue == "name 'var_dic_list' is not defined" ||
  142. msg.content.evalue.substr(0,28) == "Error in cat(var_dic_list())"
  143. //means that var_dic_list was cleared ==> need to retart the extension
  144. if (notWellDefined) mickaSearch_init()
  145. else $('#mickaSearch').html(html_table(jsonVars))
  146. requirejs(['nbextensions/mickaSearch/jquery.tablesorter.min'],
  147. function() {
  148. setTimeout(function() { if ($('#mickaSearch').length>0)
  149. $('#mickaSearch table').tablesorter()}, 50)
  150. });
  151. }
  152. function tableSort() {
  153. requirejs(['nbextensions/mickaSearch/jquery.tablesorter.min'])
  154. $('#mickaSearch table').tablesorter()
  155. }
  156. var varRefresh = function() {
  157. var kernelLanguage = Jupyter.notebook.metadata.kernelspec.language.toLowerCase()
  158. var kernel_config = cfg.kernels_config[kernelLanguage];
  159. requirejs(['nbextensions/mickaSearch/jquery.tablesorter.min'],
  160. function() {
  161. Jupyter.notebook.kernel.execute(
  162. kernel_config.varRefreshCmd, { iopub: { output: code_exec_callback } }, { silent: false }
  163. );
  164. });
  165. }
  166. var mickaSearch_init = function() {
  167. // Define code_init
  168. // read and execute code_init
  169. function read_code_init(lib) {
  170. var libName = Jupyter.notebook.base_url + "nbextensions/mickaSearch/" + lib;
  171. $.get(libName).done(function(data) {
  172. st.code_init = data;
  173. st.code_init = st.code_init.replace('lenName', cfg.cols.lenName).replace('lenType', cfg.cols.lenType)
  174. .replace('lenVar', cfg.cols.lenVar)
  175. //.replace('types_to_exclude', JSON.stringify(cfg.types_to_exclude).replace(/\"/g, "'"))
  176. requirejs(
  177. [
  178. 'nbextensions/mickaSearch/jquery.tablesorter.min'
  179. //'nbextensions/mickaSearch/colResizable-1.6.min'
  180. ],
  181. function() {
  182. Jupyter.notebook.kernel.execute(st.code_init, { iopub: { output: code_exec_callback } }, { silent: false });
  183. })
  184. variable_inspector(cfg, st); // create window if not already present
  185. console.log(log_prefix + 'loaded library');
  186. }).fail(function() {
  187. console.log(log_prefix + 'failed to load ' + lib + ' library')
  188. });
  189. }
  190. // read configuration
  191. cfg = read_config(cfg, function() {
  192. // Called when config is available
  193. if (typeof Jupyter.notebook.kernel !== "undefined" && Jupyter.notebook.kernel !== null) {
  194. var kernelLanguage = Jupyter.notebook.metadata.kernelspec.language.toLowerCase()
  195. var kernel_config = cfg.kernels_config[kernelLanguage];
  196. if (kernel_config === undefined) { // Kernel is not supported
  197. console.warn(log_prefix + " Sorry, can't use kernel language " + kernelLanguage + ".\n" +
  198. "Configurations are currently only defined for the following languages:\n" +
  199. Object.keys(cfg.kernels_config).join(', ') + "\n" +
  200. "See readme for more details.");
  201. if ($("#mickaSearch_button").length > 0) { // extension was present
  202. $("#mickaSearch_button").remove();
  203. $('#mickaSearch-wrapper').remove();
  204. // turn off events
  205. events.off('execute.CodeCell', varRefresh);
  206. events.off('varRefresh', varRefresh);
  207. }
  208. return
  209. }
  210. mickaSearch_button(); // In case button was removed
  211. // read and execute code_init (if kernel is supported)
  212. read_code_init(kernel_config.library);
  213. // console.log("code_init-->", st.code_init)
  214. } else {
  215. console.warn(log_prefix + "Kernel not available?");
  216. }
  217. }); // called after config is stable
  218. // event: on cell execution, update the list of variables
  219. events.on('execute.CodeCell', varRefresh);
  220. events.on('varRefresh', varRefresh);
  221. }
  222. var create_mickaSearch_div = function(cfg, st) {
  223. function save_position(){
  224. Jupyter.notebook.metadata.mickaSearch.position = {
  225. 'left': $('#mickaSearch-wrapper').css('left'),
  226. 'top': $('#mickaSearch-wrapper').css('top'),
  227. 'width': $('#mickaSearch-wrapper').css('width'),
  228. 'height': $('#mickaSearch-wrapper').css('height'),
  229. 'right': $('#mickaSearch-wrapper').css('right')
  230. };
  231. }
  232. var mickaSearch_wrapper = $('<div id="mickaSearch-wrapper"/>')
  233. .append(
  234. $('<div id="mickaSearch-header"/>')
  235. .addClass("header")
  236. .text("Variable Inspector ")
  237. .append(
  238. $("<a/>")
  239. .attr("href", "#")
  240. .text("[x]")
  241. .addClass("kill-btn")
  242. .attr('title', 'Close window')
  243. .click(function() {
  244. togglemickaSearch();
  245. return false;
  246. })
  247. )
  248. .append(
  249. $("<a/>")
  250. .attr("href", "#")
  251. .addClass("hide-btn")
  252. .attr('title', 'Hide Variable Inspector')
  253. .text("[-]")
  254. .click(function() {
  255. $('#mickaSearch-wrapper').css('position', 'fixed');
  256. $('#mickaSearch').slideToggle({
  257. start: function(event, ui) {
  258. // $(this).width($(this).width());
  259. },
  260. 'complete': function() {
  261. Jupyter.notebook.metadata.mickaSearch['mickaSearch_section_display'] = $('#mickaSearch').css('display');
  262. save_position();
  263. Jupyter.notebook.set_dirty();
  264. }
  265. });
  266. $('#mickaSearch-wrapper').toggleClass('closed');
  267. if ($('#mickaSearch-wrapper').hasClass('closed')) {
  268. cfg.oldHeight = $('#mickaSearch-wrapper').height(); //.css('height');
  269. $('#mickaSearch-wrapper').css({ height: 40 });
  270. $('#mickaSearch-wrapper .hide-btn')
  271. .text('[+]')
  272. .attr('title', 'Show Variable Inspector');
  273. } else {
  274. $('#mickaSearch-wrapper').height(cfg.oldHeight); //css({ height: cfg.oldHeight });
  275. $('#mickaSearch').height(cfg.oldHeight - $('#mickaSearch-header').height() - 30 )
  276. $('#mickaSearch-wrapper .hide-btn')
  277. .text('[-]')
  278. .attr('title', 'Hide Variable Inspector');
  279. }
  280. return false;
  281. })
  282. ).append(
  283. $("<a/>")
  284. .attr("href", "#")
  285. .text(" \u21BB")
  286. .addClass("reload-btn")
  287. .attr('title', 'Reload Variable Inspector')
  288. .click(function() {
  289. //variable_inspector(cfg,st);
  290. varRefresh();
  291. return false;
  292. })
  293. ).append(
  294. $("<span/>")
  295. .html("&nbsp;&nbsp")
  296. ).append(
  297. $("<span/>")
  298. .html("&nbsp;&nbsp;")
  299. )
  300. ).append(
  301. $("<div/>").attr("id", "mickaSearch").addClass('mickaSearch')
  302. )
  303. $("body").append(mickaSearch_wrapper);
  304. // Ensure position is fixed
  305. $('#mickaSearch-wrapper').css('position', 'fixed');
  306. // enable dragging and save position on stop moving
  307. $('#mickaSearch-wrapper').draggable({
  308. drag: function(event, ui) {}, //end of drag function
  309. start: function(event, ui) {
  310. $(this).width($(this).width());
  311. },
  312. stop: function(event, ui) { // on save, store window position
  313. save_position();
  314. Jupyter.notebook.set_dirty();
  315. // Ensure position is fixed (again)
  316. $('#mickaSearch-wrapper').css('position', 'fixed');
  317. },
  318. });
  319. $('#mickaSearch-wrapper').resizable({
  320. resize: function(event, ui) {
  321. $('#mickaSearch').height($('#mickaSearch-wrapper').height() - $('#mickaSearch-header').height());
  322. },
  323. start: function(event, ui) {
  324. //$(this).width($(this).width());
  325. $(this).css('position', 'fixed');
  326. },
  327. stop: function(event, ui) { // on save, store window position
  328. save_position();
  329. $('#mickaSearch').height($('#mickaSearch-wrapper').height() - $('#mickaSearch-header').height())
  330. Jupyter.notebook.set_dirty();
  331. // Ensure position is fixed (again)
  332. //$(this).css('position', 'fixed');
  333. }
  334. })
  335. // restore window position at startup
  336. if (Jupyter.notebook.metadata.mickaSearch.position !== undefined) {
  337. $('#mickaSearch-wrapper').css(Jupyter.notebook.metadata.mickaSearch.position);
  338. }
  339. // Ensure position is fixed
  340. $('#mickaSearch-wrapper').css('position', 'fixed');
  341. // Restore window display
  342. if (Jupyter.notebook.metadata.mickaSearch !== undefined) {
  343. if (Jupyter.notebook.metadata.mickaSearch['mickaSearch_section_display'] !== undefined) {
  344. $('#mickaSearch').css('display', Jupyter.notebook.metadata.mickaSearch['mickaSearch_section_display'])
  345. //$('#mickaSearch').css('height', $('#mickaSearch-wrapper').height() - $('#mickaSearch-header').height())
  346. if (Jupyter.notebook.metadata.mickaSearch['mickaSearch_section_display'] == 'none') {
  347. $('#mickaSearch-wrapper').addClass('closed');
  348. $('#mickaSearch-wrapper').css({ height: 40 });
  349. $('#mickaSearch-wrapper .hide-btn')
  350. .text('[+]')
  351. .attr('title', 'Show Variable Inspector');
  352. }
  353. }
  354. if (Jupyter.notebook.metadata.mickaSearch['window_display'] !== undefined) {
  355. console.log(log_prefix + "Restoring Variable Inspector window");
  356. $('#mickaSearch-wrapper').css('display', Jupyter.notebook.metadata.mickaSearch['window_display'] ? 'block' : 'none');
  357. if ($('#mickaSearch-wrapper').hasClass('closed')){
  358. $('#mickaSearch').height(cfg.oldHeight - $('#mickaSearch-header').height())
  359. }else{
  360. $('#mickaSearch').height($('#mickaSearch-wrapper').height() - $('#mickaSearch-header').height()-30)
  361. }
  362. }
  363. }
  364. // if mickaSearch-wrapper is undefined (first run(?), then hide it)
  365. if ($('#mickaSearch-wrapper').css('display') == undefined) $('#mickaSearch-wrapper').css('display', "none") //block
  366. mickaSearch_wrapper.addClass('mickaSearch-float-wrapper');
  367. }
  368. var variable_inspector = function(cfg, st) {
  369. var mickaSearch_wrapper = $("#mickaSearch-wrapper");
  370. if (mickaSearch_wrapper.length === 0) {
  371. create_mickaSearch_div(cfg, st);
  372. }
  373. $(window).resize(function() {
  374. $('#mickaSearch').css({ maxHeight: $(window).height() - 30 });
  375. $('#mickaSearch-wrapper').css({ maxHeight: $(window).height() - 10 });
  376. });
  377. $(window).trigger('resize');
  378. varRefresh();
  379. };
  380. var toggle_mickaSearch = function(cfg, st) {
  381. // toggle draw (first because of first-click behavior)
  382. $("#mickaSearch-wrapper").toggle({
  383. 'progress': function() {},
  384. 'complete': function() {
  385. Jupyter.notebook.metadata.mickaSearch['window_display'] = $('#mickaSearch-wrapper').css('display') == 'block';
  386. Jupyter.notebook.set_dirty();
  387. // recompute:
  388. variable_inspector(cfg, st);
  389. }
  390. });
  391. };
  392. var load_jupyter_extension = function() {
  393. load_css(); //console.log("Loading css")
  394. mickaSearch_button(); //console.log("Adding mickaSearch_button")
  395. // If a kernel is available,
  396. if (typeof Jupyter.notebook.kernel !== "undefined" && Jupyter.notebook.kernel !== null) {
  397. console.log(log_prefix + "Kernel is available -- mickaSearch initializing ")
  398. mickaSearch_init();
  399. }
  400. // if a kernel wasn't available, we still wait for one. Anyway, we will run this for new kernel
  401. // (test if is is a Python kernel and initialize)
  402. // on kernel_ready.Kernel, a new kernel has been started and we shall initialize the extension
  403. events.on("kernel_ready.Kernel", function(evt, data) {
  404. console.log(log_prefix + "Kernel is available -- reading configuration");
  405. mickaSearch_init();
  406. });
  407. };
  408. return {
  409. load_ipython_extension: load_jupyter_extension,
  410. varRefresh: varRefresh
  411. };
  412. });