diff --git a/.cache/pip/http-v2/8/1/3/c/c/813cc7096c25ba43edba14a4ab40832db33af638c43e975795995aec.body b/.cache/pip/http-v2/8/1/3/c/c/813cc7096c25ba43edba14a4ab40832db33af638c43e975795995aec.body new file mode 100644 index 0000000000000000000000000000000000000000..2f88de74fe81d1c14f7b6264f282604d2a34e27c Binary files /dev/null and b/.cache/pip/http-v2/8/1/3/c/c/813cc7096c25ba43edba14a4ab40832db33af638c43e975795995aec.body differ diff --git a/.cache/pip/http-v2/8/5/e/f/e/85efe0b09056afaed0a9932320bb2a590a03afe4bc35e3fffc1e85cd.body b/.cache/pip/http-v2/8/5/e/f/e/85efe0b09056afaed0a9932320bb2a590a03afe4bc35e3fffc1e85cd.body new file mode 100644 index 0000000000000000000000000000000000000000..5b7a279b833e11ea977794e2d89d3295e23a1c93 Binary files /dev/null and b/.cache/pip/http-v2/8/5/e/f/e/85efe0b09056afaed0a9932320bb2a590a03afe4bc35e3fffc1e85cd.body differ diff --git a/.cache/pip/http-v2/8/f/4/1/0/8f410bbfc5d1848453018f07fdeb351a0d0ae8a3bcdc5a16f937935c.body b/.cache/pip/http-v2/8/f/4/1/0/8f410bbfc5d1848453018f07fdeb351a0d0ae8a3bcdc5a16f937935c.body new file mode 100644 index 0000000000000000000000000000000000000000..14fa25044476386ac4ab23ced715ba27775a8f44 Binary files /dev/null and b/.cache/pip/http-v2/8/f/4/1/0/8f410bbfc5d1848453018f07fdeb351a0d0ae8a3bcdc5a16f937935c.body differ diff --git a/.cache/pip/http-v2/c/2/0/9/b/c209b6730b4a3a67d7d68f436a023e57f848dcba7c8db3fd04a54862 b/.cache/pip/http-v2/c/2/0/9/b/c209b6730b4a3a67d7d68f436a023e57f848dcba7c8db3fd04a54862 new file mode 100644 index 0000000000000000000000000000000000000000..7f397309670ff25721cf98aa9a2adbc136c72501 Binary files /dev/null and b/.cache/pip/http-v2/c/2/0/9/b/c209b6730b4a3a67d7d68f436a023e57f848dcba7c8db3fd04a54862 differ diff --git a/.cache/pip/http-v2/c/5/4/0/3/c5403a101bbaf526810b4c472004bc3b03b96b7c8118e06c7a081e63.body b/.cache/pip/http-v2/c/5/4/0/3/c5403a101bbaf526810b4c472004bc3b03b96b7c8118e06c7a081e63.body new file mode 100644 index 0000000000000000000000000000000000000000..6c5aa965a5cf81aa1a858ac575b2c1ab0cf86f54 Binary files /dev/null and b/.cache/pip/http-v2/c/5/4/0/3/c5403a101bbaf526810b4c472004bc3b03b96b7c8118e06c7a081e63.body differ diff --git a/.cache/pip/http-v2/f/0/4/f/6/f04f619fe804fd38ac24d3bad7c02b3e6461727bd3a1ce45e316d44a b/.cache/pip/http-v2/f/0/4/f/6/f04f619fe804fd38ac24d3bad7c02b3e6461727bd3a1ce45e316d44a new file mode 100644 index 0000000000000000000000000000000000000000..92d7e83496e1db88abd83281fc5fb4181d0e3134 Binary files /dev/null and b/.cache/pip/http-v2/f/0/4/f/6/f04f619fe804fd38ac24d3bad7c02b3e6461727bd3a1ce45e316d44a differ diff --git a/.cache/pip/http-v2/f/0/4/f/6/f04f619fe804fd38ac24d3bad7c02b3e6461727bd3a1ce45e316d44a.body b/.cache/pip/http-v2/f/0/4/f/6/f04f619fe804fd38ac24d3bad7c02b3e6461727bd3a1ce45e316d44a.body new file mode 100644 index 0000000000000000000000000000000000000000..89666817ec55a553c8894e6284d8632463d79c62 Binary files /dev/null and b/.cache/pip/http-v2/f/0/4/f/6/f04f619fe804fd38ac24d3bad7c02b3e6461727bd3a1ce45e316d44a.body differ diff --git a/.cache/pip/http-v2/f/3/c/4/d/f3c4dedd1435641845fe96a7938402191795947fd69de9d823bd65e1.body b/.cache/pip/http-v2/f/3/c/4/d/f3c4dedd1435641845fe96a7938402191795947fd69de9d823bd65e1.body new file mode 100644 index 0000000000000000000000000000000000000000..d6b0103e7b4a180d05d9febda38651204942309c Binary files /dev/null and b/.cache/pip/http-v2/f/3/c/4/d/f3c4dedd1435641845fe96a7938402191795947fd69de9d823bd65e1.body differ diff --git a/.local/share/jupyter/nbextensions/highlight_selected_word/main.js b/.local/share/jupyter/nbextensions/highlight_selected_word/main.js new file mode 100644 index 0000000000000000000000000000000000000000..f290546e844450ae00211fe38fe4ca8cdafaf139 --- /dev/null +++ b/.local/share/jupyter/nbextensions/highlight_selected_word/main.js @@ -0,0 +1,419 @@ +/** + * Enable highlighting of matching words in cells' CodeMirror editors. + * + * This extension was adapted from the CodeMirror addon + * codemirror/addon/search/match-highlighter.js + */ + +define([ + 'require', + 'jquery', + 'base/js/namespace', + 'notebook/js/cell', + 'notebook/js/codecell', + 'codemirror/lib/codemirror', + // The mark-selection addon is need to ensure that the highlighting styles + // are *not* applied to the actual selection, as otherwise it can become + // difficult to see which is selected vs just highlighted. + 'codemirror/addon/selection/mark-selection' +], function ( + requirejs, + $, + Jupyter, + cell, + codecell, + CodeMirror +) { + 'use strict'; + + var Cell = cell.Cell; + var CodeCell = codecell.CodeCell; + + var mod_name = 'highlight_selected_word'; + var log_prefix = '[' + mod_name + ']'; + var menu_toggle_class = 'highlight_selected_word_toggle'; + + // Parameters (potentially) stored in server config. + // This object gets updated on config load. + var params = { + highlight_across_all_cells: true, + enable_on_load : true, + code_cells_only: false, + delay: 100, + words_only: false, + highlight_only_whole_words: true, + min_chars: 2, + show_token: '[\\w$]', + highlight_color: '#90EE90', + highlight_color_blurred: '#BBFFBB', + highlight_style: 'matchhighlight', + trim: true, + use_toggle_hotkey: false, + toggle_hotkey: 'alt-h', + outlines_only: false, + outline_width: 2, + only_cells_in_scroll: true, + scroll_min_delay: 100, + hide_selections_in_unfocussed: false, + }; + + // these are set on registering the action(s) + var action_names = { + toggle: '', + }; + + /** + * the codemirror matchHighlighter has a separate state object for each cm + * instance, but since our state is global over all cells' editors, we can + * use a single object for simplicity, and don't need to store options + * inside the state, since we have closure-level access to the params + * object above. + */ + var globalState = { + active: false, + timeout: null, // only want one timeout + scrollTimeout: null, + overlay: null, // one overlay suffices, as all cells use the same one + }; + + // define a CodeMirror option for highlighting matches in all cells + CodeMirror.defineOption("highlightSelectionMatchesInJupyterCells", false, function (cm, val, old) { + if (old && old != CodeMirror.Init) { + globalState.active = false; + // remove from all relevant, this can fail gracefully if not present + get_relevant_cells().forEach(function (cell, idx, array) { + cell.code_mirror.removeOverlay(mod_name); + }); + globalState.overlay = null; + clearTimeout(globalState.timeout); + globalState.timeout = null; + cm.off("cursorActivity", callbackCursorActivity); + cm.off("focus", callbackOnFocus); + } + if (val) { + if (cm.hasFocus()) { + globalState.active = true; + highlightMatchesInAllRelevantCells(cm); + } + else { + cm.on("focus", callbackOnFocus); + } + cm.on("cursorActivity", callbackCursorActivity); + } + }); + + /** + * The functions callbackCursorActivity, callbackOnFocus and + * scheduleHighlight are taken without major modification from cm's + * match-highlighter. + * The main difference is using our global state rather than + * match-highlighter's per-cm state, and a different highlighting function + * is scheduled. + */ + function callbackCursorActivity (cm) { + if (globalState.active || cm.hasFocus()) { + scheduleHighlight(cm); + } + } + + function callbackOnFocus (cm) { + // unlike cm match-highlighter, we *do* want to schedule a highight on + // focussing the editor + globalState.active = true; + scheduleHighlight(cm); + } + + function scheduleHighlight (cm) { + clearTimeout(globalState.timeout); + globalState.timeout = setTimeout(function () { highlightMatchesInAllRelevantCells(cm); }, params.delay); + } + + /** + * Adapted from cm match-highlighter's highlightMatches, but adapted to + * use our global state and parameters, plus work either for only the + * current editor, or multiple cells' editors. + */ + function highlightMatchesInAllRelevantCells (cm) { + var newOverlay = null; + + var re = params.show_token === true ? /[\w$]/ : params.show_token; + var from = cm.getCursor('from'); + if (!cm.somethingSelected() && params.show_token) { + var line = cm.getLine(from.line), start = from.ch, end = start; + while (start && re.test(line.charAt(start - 1))) { + --start; + } + while (end < line.length && re.test(line.charAt(end))) { + ++end; + } + if (start < end) { + newOverlay = makeOverlay(line.slice(start, end), re, params.highlight_style); + } + } + else { + var to = cm.getCursor("to"); + if (from.line == to.line) { + if (!params.words_only || isWord(cm, from, to)) { + var selection = cm.getRange(from, to); + if (params.trim) { + selection = selection.replace(/^\s+|\s+$/g, ""); + } + if (selection.length >= params.min_chars) { + var hasBoundary = params.highlight_only_whole_words ? (re instanceof RegExp ? re : /[\w$]/) : false; + newOverlay = makeOverlay(selection, hasBoundary, params.highlight_style); + } + } + } + } + + var siterect = document.getElementById('site').getBoundingClientRect(); + var viewtop = siterect.top, viewbot = siterect.bottom; + var cells = params.highlight_across_all_cells ? get_relevant_cells() : [ + $(cm.getWrapperElement()).closest('.cell').data('cell') + ]; + cells.forEach(function (cell, idx, array) { + // cm.operation to delay updating DOM until all work is done + cell.code_mirror.operation(function () { + cell.code_mirror.removeOverlay(mod_name); + if (newOverlay && is_in_view(cell.element[0], viewtop, viewbot)) { + cell.code_mirror.addOverlay(newOverlay); + } + }); + }); + } + + /** + * isWord, boundariesAround and makeOverlay come pretty much directly from + * Codemirror/addon/search/matchHighlighter + * since they don't use state or config values. + */ + function isWord (cm, from, to) { + var str = cm.getRange(from, to); + if (str.match(/^\w+$/) !== null) { + var pos, chr; + if (from.ch > 0) { + pos = {line: from.line, ch: from.ch - 1}; + chr = cm.getRange(pos, from); + if (chr.match(/\W/) === null) { + return false; + } + } + if (to.ch < cm.getLine(from.line).length) { + pos = {line: to.line, ch: to.ch + 1}; + chr = cm.getRange(to, pos); + if (chr.match(/\W/) === null) { + return false; + } + } + return true; + } + return false; + } + function boundariesAround (stream, re) { + return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && + (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); + } + function makeOverlay (query, hasBoundary, style) { + return { + name: mod_name, + token: function (stream) { + if (stream.match(query) && + (!hasBoundary || boundariesAround(stream, hasBoundary))) { + return style; + } + stream.next(); + if (!stream.skipTo(query.charAt(0))) { + stream.skipToEnd(); + } + } + }; + } + + /** + * Returns true if part of elem is visible between viewtop & viewbot + */ + var is_in_view = function (elem, viewtop, viewbot) { + var rect = elem.getBoundingClientRect(); + // hidden elements show height 0 + return (rect.top < viewbot) && (rect.bottom > viewtop) && rect.height; + } + + /** + * Return an array of cells to which match highlighting is relevant, + * dependent on the code_cells_only parameter + */ + function get_relevant_cells () { + var cells = Jupyter.notebook.get_cells(); + return params.code_cells_only ? cells.filter(function (c) { return (c instanceof CodeCell); }) : cells; + } + + function add_menu_item () { + if ($('#view_menu').find('.' + menu_toggle_class).length < 1) { + var menu_item = $('
') + .appendTo('#view_menu'); + var menu_link = $('') + .text('Highlight selected word') + .addClass(menu_toggle_class) + .attr({ + title: 'Highlight all instances of the selected word in the current editor', + href: '#', + }) + .on('click', function () { toggle_highlight_selected(); }) + .appendTo(menu_item); + $('') + .addClass('fa menu-icon pull-right') + .css({'margin-top': '-2px', 'margin-right': '-16px'}) + .prependTo(menu_link); + } + } + + var throttled_highlight = (function () { + var last, throttle_timeout; + return function throttled_highlight (cm) { + var now = Number(new Date()); + var do_it = function () { + last = Number(new Date()); + highlightMatchesInAllRelevantCells(cm); + }; + var remaining = last + params.scroll_min_delay - now; + if (last && remaining > 0) { + clearTimeout(throttle_timeout); + throttle_timeout = setTimeout(do_it, remaining); + } + else { + last = undefined; // so we will do it first time next streak + do_it(); + } + } + })(); + + function scroll_handler (evt) { + if (globalState.active && Jupyter.notebook.mode === 'edit' && globalState.overlay) { + // add overlay to cells now in view which don't already have it. + // Don't bother removing from those no longer in view, as it would just + // cause more work for the browser, without any benefit + var siterect = document.getElementById('site').getBoundingClientRect(); + get_relevant_cells().forEach(function (cell) { + var cm = cell.code_mirror; + if (is_in_view(cell.element, siterect.top, siterect.bot)) { + var need_it = !cm.state.overlays.some(function(ovr) { + return ovr.modeSpec.name === mod_name; }); + if (need_it) cm.addOverlay(globalState.overlay); + } + }); + } + } + + function toggle_highlight_selected (set_on) { + set_on = (set_on !== undefined) ? set_on : !params.enable_on_load; + // update config to make changes persistent + if (set_on !== params.enable_on_load) { + params.enable_on_load = set_on; + Jupyter.notebook.config.update({highlight_selected_word: {enable_on_load: set_on}}); + } + + // Change defaults for new cells: + var cm_conf = (params.code_cells_only ? CodeCell : Cell).options_default.cm_config; + cm_conf.highlightSelectionMatchesInJupyterCells = cm_conf.styleSelectedText = set_on; + + // And change any existing cells: + get_relevant_cells().forEach(function (cell, idx, array) { + cell.code_mirror.setOption('highlightSelectionMatchesInJupyterCells', set_on); + cell.code_mirror.setOption('styleSelectedText', set_on); + }); + // update menu class + $('.' + menu_toggle_class + ' > .fa').toggleClass('fa-check', set_on); + // bind/unbind scroll handler + $('#site')[ + (params.only_cells_in_scroll && params.scroll_min_delay > 0) ? 'on' : 'off' + ]('scroll', scroll_handler); + console.log(log_prefix, 'toggled', set_on ? 'on' : 'off'); + return set_on; + } + + function register_new_actions () { + action_names.toggle = Jupyter.keyboard_manager.actions.register({ + handler : function (env) { toggle_highlight_selected(); }, + help : "Toggle highlighting of selected word", + icon : 'fa-language', + help_index: 'c1' + }, 'toggle', mod_name); + } + + function bind_hotkeys () { + if (params.use_toggle_hotkey && params.toggle_hotkey) { + Jupyter.keyboard_manager.command_shortcuts.add_shortcut(params.toggle_hotkey, action_names.toggle); + Jupyter.keyboard_manager.edit_shortcuts.add_shortcut(params.toggle_hotkey, action_names.toggle); + } + } + + function insert_css () { + var css = [// in unselected cells, matches have blurred color + // in selected cells, we keep CodeMirror highlight for the actual selection to avoid confusion + '.edit_mode .unselected .CodeMirror .cm-matchhighlight {', + ' background-color: ' + params.highlight_color_blurred + ';', + '}', + + // in active cell, matches which are not the current selection have focussed color + '.edit_mode .CodeMirror.CodeMirror-focused :not(.CodeMirror-selectedtext).cm-matchhighlight {', + ' background-color: ' + params.highlight_color + ';', + '}', + + // in all cells, outline matches have blurred color + '.edit_mode .CodeMirror .cm-matchhighlight-outline {', + ' outline-style: solid;', + ' outline-width: ' + params.outline_width + 'px;', + ' outline-color: ' + params.highlight_color_blurred + ';', + '}', + + // in active cell, outline matches have focussed color + '.edit_mode .CodeMirror.CodeMirror-focused .cm-matchhighlight-outline {', + ' outline-color: ' + params.highlight_color + ';', + '}' + ].join('\n'); + + if (params.hide_selections_in_unfocussed) { + css += [ + // in unselected cells, selections which are not matches should have no background + '.unselected .CodeMirror :not(.cm-matchhighlight).CodeMirror-selected,', + '.unselected .CodeMirror :not(.cm-matchhighlight).CodeMirror-selectedtext {', + ' background: initial;', + '}', + ].join('\n'); + } + + $(' + + + + + + + + + + + + + + + + + +highlighter.css
. The last button enables to remove all highlightings in the current cell. The extension can be installed with the nice UI available on jupyter_contrib_nbextensions website, which also allows to enable/disable the extension.
+You may also install the extension from the original repo: issue
+jupyter nbextension install https://rawgit.com/jfbercher/small_nbextensions/master/highlighter.zip --user
+
at the command line.
+Use a code cell with
+%%javascript
+require("base/js/utils").load_extensions("highlighter/highlighter")
+
You may also automatically load the extension for any notebook via
+jupyter nbextension enable highlighter/highlighter
+
%%javascript
+require("base/js/utils").load_extensions("highlighter/highlighter")
+