<commit_before>/*
 *  Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

(function() {
  'use strict';
  var module = angular.module('bk.notebook');

  module.directive('bkCodeCell', function(
      bkUtils,
      bkEvaluatorManager,
      bkCellMenuPluginManager,
      bkSessionManager,
      bkCoreManager) {

    var notebookCellOp = bkSessionManager.getNotebookCellOp();
    var getBkNotebookWidget = function() {
      return bkCoreManager.getBkApp().getBkNotebookWidget();
    };
    var CELL_TYPE = "code";
    return {
      restrict: 'E',
      templateUrl: "./app/mainapp/components/notebook/codecell.html",
      scope: { cellmodel: "=", cellmenu: "="},
      controller: function($scope) {
        $scope.cellview = {
          inputMenu: [],
          displays: []
        };
        var isLocked = function() {
          return bkSessionManager.isNotebookLocked();
        };

        $scope.isShowInput = function() {
          if (isLocked()) {
            return false;
          }
          if ($scope.cellmodel.input.hidden === true) {
            return false;
          }
          return true;
        };

        $scope.bkNotebook = getBkNotebookWidget();
        // ensure cm refreshes when 'unhide'
        $scope.$watch("isShowInput()", function(newValue, oldValue) {
          if ($scope.cm && newValue === true && newValue !== oldValue) {
            bkUtils.fcall(function() {
              $scope.cm.refresh();
            });
          }
        });

        $scope.isShowOutput = function() {
          if ($scope.cellmodel.output.hidden === true) {
            return false;
          }
          var result = $scope.cellmodel.output.result;
          if (result && result.hidden === true) {
            return false;
          }
          return !(result === undefined || result === null);
        };
        $scope.evaluate = function() {
          bkCoreManager.getBkApp().evaluate($scope.cellmodel).
              catch(function(data) {
                console.error(data);
              });
        };
        var editedListener = function(newValue, oldValue) {
          if (newValue !== oldValue) {
            bkSessionManager.setNotebookModelEdited(true);
          }
        };
        $scope.$watch('cellmodel.id', editedListener);
        $scope.$watch('cellmodel.evaluator', editedListener);
        $scope.$watch('cellmodel.initialization', editedListener);
        $scope.$watch('cellmodel.input.body', editedListener);
        $scope.$watch('cellmodel.output.result', editedListener);

        $scope.autocomplete = function(cpos, onResults) {
          var evaluator = bkEvaluatorManager.getEvaluator($scope.cellmodel.evaluator);
          if (!evaluator) {
            return;
          }
          if (evaluator.autocomplete) {
            evaluator.autocomplete(
                $scope.cellmodel.input.body,
                cpos, onResults);
          } else if (evaluator.autocomplete2) {
            evaluator.autocomplete2(
                $scope.cm, null, onResults);
          }
        };

        $scope.getEvaluators = function() {
          return bkEvaluatorManager.getAllEvaluators();
        };

        $scope.getEvaluator = function() {
          return bkEvaluatorManager.getEvaluator($scope.cellmodel.evaluator);
        };
        $scope.updateUI = function(evaluator) {
          if ($scope.cm && evaluator) {
            $scope.cm.setOption("mode", evaluator.cmMode);
            var bg = evaluator.background ? evaluator.background : "white";
            $($scope.cm.getWrapperElement()).css("background", bg);
            $scope.evaluatorReader = true;
          }
        };
        $scope.$watch("getEvaluator()", function(newValue, oldValue) {
          if (newValue === oldValue) {
            return;
          }
          $scope.updateUI(newValue);
        });
        $scope.appendCodeCell = function(evaluatorName) {
          var thisCellId = $scope.cellmodel.id;
          if (!evaluatorName) {
            // if no evaluator specified, use the current evaluator
            evaluatorName = $scope.cellmodel.evaluator;
          }
          var newCell = bkSessionManager.getNotebookNewCellFactory().newCodeCell(evaluatorName);
          notebookCellOp.appendAfter(thisCellId, newCell);
          bkUtils.refreshRootScope();
        };
        $scope.getShareMenuPlugin = function() {
          return bkCellMenuPluginManager.getPlugin(CELL_TYPE);
        };
        var shareMenu = {
          name: "Share",
          items: []
        };
        $scope.cellmenu.addItem(shareMenu);
        $scope.$watch("getShareMenuPlugin()", function() {
          shareMenu.items = bkCellMenuPluginManager.getMenuItems(CELL_TYPE, $scope);
        });

        $scope.cellmenu.addItem({
          name: "Show input cell",
          isChecked: function() {
            return !$scope.cellmodel.input.hidden;
          },
          action: function() {
            if ($scope.cellmodel.input.hidden) {
              delete $scope.cellmodel.input.hidden;
            } else {
              $scope.cellmodel.input.hidden = true;
            }
          }
        });
        $scope.cellmenu.addItem({
          name: "Show output cell (if available)",
          isChecked: function() {
            return !$scope.cellmodel.output.hidden;
          },
          action: function() {
            if ($scope.cellmodel.output.hidden) {
              delete $scope.cellmodel.output.hidden;
            } else {
              $scope.cellmodel.output.hidden = true;
            }
          }
        });

        $scope.isInitializationCell = function() {
          return $scope.cellmodel.initialization;
        };

        $scope.cellmenu.addItem({
          name: "Initialization Cell",
          isChecked: function() {
            return $scope.isInitializationCell();
          },
          action: function() {
            if ($scope.isInitializationCell()) {
              $scope.cellmodel.initialization = undefined;
            } else {
              $scope.cellmodel.initialization = true;
            }
            notebookCellOp.reset();
          }
        });
        $scope.$on("$destroy", function() {
          $scope.bkNotebook.unregisterFocusable($scope.cellmodel.id);
          $scope.bkNotebook.unregisterCM($scope.cellmodel.id);
        });
      },
      link: function(scope, element, attrs) {
        scope.showDebug = false;

        function isFullScreen(cm) {
          return /\bCodeMirror-fullscreen\b/.test(cm.getWrapperElement().className);
        }

        function winHeight() {
          return window.innerHeight || (document.documentElement || document.body).clientHeight;
        }

        function setFullScreen(cm, full) {
          var wrap = cm.getWrapperElement();
          if (full) {
            wrap.className += " CodeMirror-fullscreen";
            wrap.style.height = winHeight() + "px";
            document.documentElement.style.overflow = "hidden";
          } else {
            wrap.className = wrap.className.replace(" CodeMirror-fullscreen", "");
            wrap.style.height = "";
            document.documentElement.style.overflow = "";
          }
          cm.refresh();
        }

        CodeMirror.on(window, "resize", function() {
          var showing = document.body.getElementsByClassName("CodeMirror-fullscreen")[0];
          if (!showing)
            return;
          showing.CodeMirror.getWrapperElement().style.height = winHeight() + "px";
        });
        var moveFocusDown = function() {
          // move focus to next code cell
          var thisCellId = scope.cellmodel.id;
          var nextCell = notebookCellOp.getNext(thisCellId);
          while (nextCell) {
            if (scope.bkNotebook.getFocusable(nextCell.id)) {
              scope.bkNotebook.getFocusable(nextCell.id).focus();
              break;
            } else {
              nextCell = notebookCellOp.getNext(nextCell.id);
            }
          }
        };
        var moveFocusUp = function() {
          // move focus to prev code cell
          var thisCellID = scope.cellmodel.id;
          var prevCell = notebookCellOp.getPrev(thisCellID);
          while (prevCell) {
            if (scope.bkNotebook.getFocusable(prevCell.id)) {
              scope.bkNotebook.getFocusable(prevCell.id).focus();
              break;
            } else {
              prevCell = notebookCellOp.getPrev(prevCell.id);
            }
          }
        };
        scope.cm = CodeMirror.fromTextArea(element.find("textarea")[0], {
          lineNumbers: true,
          matchBrackets: true,
          onKeyEvent: function(cm, event) {
            if (event.type === "keydown") {
              if (event.keyCode === 38) {
                if ($('.CodeMirror-hint').length > 0) {
                  //codecomplete is up, skip
                  return;
                }
                if (cm.getCursor().line === 0 && event.shiftKey === false) {
                  moveFocusUp();
                }
              } else if (event.keyCode === 40) {
                if ($('.CodeMirror-hint').length > 0) {
                  //codecomplete is up, skip
                  return;
                }
                if (cm.getCursor().line === cm.doc.size - 1 && event.shiftKey === false) {
                  moveFocusDown();
                }
              } else if (event.keyCode === 27) { // ESC
                if (cm.state.vim && cm.state.vim.insertMode) {
                  return;
                } else {
                  if (isFullScreen(cm)) {
                    setFullScreen(cm, false);
                  }
                }
              }
            }
          },
          extraKeys: {
            "Shift-Ctrl-A": function(cm) {
              scope.appendCodeCell();
            },
            "Shift-Ctrl-E": function(cm) {
              scope.popupMenu();
              element.find(".inputcellmenu").find('li').find('a')[0].focus();
            },
            "Alt-Down": moveFocusDown,
            "Alt-J": moveFocusDown,
            "Alt-Up": moveFocusUp,
            "Alt-K": moveFocusUp,
            "Alt-F11": function(cm) {
              setFullScreen(cm, !isFullScreen(cm));
            },
            "Ctrl-Enter": function(cm) {
              scope.evaluate();
            },
            "Shift-Enter": function(cm) {
              scope.evaluate();
              moveFocusDown();
            },
            "Ctrl-Space": function(cm) {
              var getToken = function(editor, cur) {
                return editor.getTokenAt(cur);
              };
              var getHints = function(editor, showHintCB, options) {
                var cur = editor.getCursor();
                var token = getToken(editor, cur);
                var cursorPos = editor.indexFromPos(cur);
                // We might want this defined by the plugin.
                var onResults = function(results, matched_text) {
                  var start = token.start;
                  var end = token.end;
                  if (token.string === ".") {
                    start += 1;
                  }
                  if (matched_text) {
                    start += (cur.ch - token.start - matched_text.length);
                    end = start + matched_text.length;
                  }
                  showHintCB({
                    list: _.uniq(results),
                    from: CodeMirror.Pos(cur.line, start),
                    to: CodeMirror.Pos(cur.line, end)
                  });
                };
                scope.autocomplete(cursorPos, onResults);
              };

              var options = {
                async: true,
                closeOnUnfocus: true,
                alignWithWord: true,
                completeSingle: true
              };
              CodeMirror.showHint(cm, getHints, options);
            }
          }
        });

        scope.cm.focus();
        scope.updateUI(scope.getEvaluator());
        scope.bkNotebook.registerFocusable(scope.cellmodel.id, scope.cm);
        scope.bkNotebook.registerCM(scope.cellmodel.id, scope.cm);

        // cellmodel.body --> CodeMirror
        scope.$watch('cellmodel.input.body', function(newVal, oldVal) {
          if (scope.cm && newVal !== scope.cm.getValue()) {
            if (newVal === null) {
              newVal = "";
            }
            scope.cm.setValue(newVal);
          }
        });
        // cellmodel.body <-- CodeMirror
        scope.cm.on("change", function(cm, e) {
          scope.cellmodel.input.body = cm.getValue();
          bkUtils.refreshRootScope();
        });

        var inputMenuDiv = element.find(".bkcell").first();
        scope.popupMenu = function(event) {
          var menu = inputMenuDiv.find('.dropdown').first();
          var clicked = event && event.hasOwnProperty("clientY");
          menu.css("top", clicked ? event.clientY : "");
          menu.css("left", clicked ? event.clientX - 150 : "");
          menu.find('.dropdown-toggle').first().dropdown('toggle');
        };
        inputMenuDiv.click(function(event) {
          if (bkUtils.getEventOffsetX(inputMenuDiv, event) >= inputMenuDiv.width()) {
            scope.popupMenu(event);
            event.stopPropagation();
          }
        });
        inputMenuDiv.mousemove(function(event) {
          if (bkUtils.getEventOffsetX(inputMenuDiv, event) >= inputMenuDiv.width()) {
            inputMenuDiv.css('cursor', 'pointer');
          } else {
            inputMenuDiv.css('cursor', 'default');
          }
        });

        if (scope.isInitializationCell()) {
          element.closest(".bkcell").addClass("initcell");
        } else {
          element.closest(".bkcell").removeClass("initcell");
        }
        scope.$watch('isInitializationCell()', function(newValue, oldValue) {
          if (newValue !== oldValue) {
            if (newValue) {
              element.closest(".bkcell").addClass("initcell");
            } else {
              element.closest(".bkcell").removeClass("initcell");
            }
          }
        });

        scope.getShareData = function() {
          var evaluator = _(bkSessionManager.getRawNotebookModel().evaluators)
              .find(function (evaluator) {
                return evaluator.name === scope.cellmodel.evaluator;
              });
          var cells = [scope.cellmodel];
          return bkUtils.generateNotebook([evaluator], cells);
        };
      }
    };
  });

})();
<commit_msg>Fix DOM leaks for bkCodeCell<commit_after>/*
 *  Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

(function() {
  'use strict';
  var module = angular.module('bk.notebook');

  module.directive('bkCodeCell', function(
      bkUtils,
      bkEvaluatorManager,
      bkCellMenuPluginManager,
      bkSessionManager,
      bkCoreManager) {

    var notebookCellOp = bkSessionManager.getNotebookCellOp();
    var getBkNotebookWidget = function() {
      return bkCoreManager.getBkApp().getBkNotebookWidget();
    };
    var CELL_TYPE = "code";
    return {
      restrict: 'E',
      templateUrl: "./app/mainapp/components/notebook/codecell.html",
      scope: { cellmodel: "=", cellmenu: "="},
      controller: function($scope) {
        $scope.cellview = {
          inputMenu: [],
          displays: []
        };
        var isLocked = function() {
          return bkSessionManager.isNotebookLocked();
        };

        $scope.isShowInput = function() {
          if (isLocked()) {
            return false;
          }
          if ($scope.cellmodel.input.hidden === true) {
            return false;
          }
          return true;
        };

        $scope.bkNotebook = getBkNotebookWidget();
        // ensure cm refreshes when 'unhide'
        $scope.$watch("isShowInput()", function(newValue, oldValue) {
          if ($scope.cm && newValue === true && newValue !== oldValue) {
            bkUtils.fcall(function() {
              $scope.cm.refresh();
            });
          }
        });

        $scope.isShowOutput = function() {
          if ($scope.cellmodel.output.hidden === true) {
            return false;
          }
          var result = $scope.cellmodel.output.result;
          if (result && result.hidden === true) {
            return false;
          }
          return !(result === undefined || result === null);
        };
        $scope.evaluate = function() {
          bkCoreManager.getBkApp().evaluate($scope.cellmodel).
              catch(function(data) {
                console.error(data);
              });
        };
        var editedListener = function(newValue, oldValue) {
          if (newValue !== oldValue) {
            bkSessionManager.setNotebookModelEdited(true);
          }
        };
        $scope.$watch('cellmodel.id', editedListener);
        $scope.$watch('cellmodel.evaluator', editedListener);
        $scope.$watch('cellmodel.initialization', editedListener);
        $scope.$watch('cellmodel.input.body', editedListener);
        $scope.$watch('cellmodel.output.result', editedListener);

        $scope.autocomplete = function(cpos, onResults) {
          var evaluator = bkEvaluatorManager.getEvaluator($scope.cellmodel.evaluator);
          if (!evaluator) {
            return;
          }
          if (evaluator.autocomplete) {
            evaluator.autocomplete(
                $scope.cellmodel.input.body,
                cpos, onResults);
          } else if (evaluator.autocomplete2) {
            evaluator.autocomplete2(
                $scope.cm, null, onResults);
          }
        };

        $scope.getEvaluators = function() {
          return bkEvaluatorManager.getAllEvaluators();
        };

        $scope.getEvaluator = function() {
          return bkEvaluatorManager.getEvaluator($scope.cellmodel.evaluator);
        };
        $scope.updateUI = function(evaluator) {
          if ($scope.cm && evaluator) {
            $scope.cm.setOption("mode", evaluator.cmMode);
            var bg = evaluator.background ? evaluator.background : "white";
            $($scope.cm.getWrapperElement()).css("background", bg);
            $scope.evaluatorReader = true;
          }
        };
        $scope.$watch("getEvaluator()", function(newValue, oldValue) {
          if (newValue === oldValue) {
            return;
          }
          $scope.updateUI(newValue);
        });
        $scope.appendCodeCell = function(evaluatorName) {
          var thisCellId = $scope.cellmodel.id;
          if (!evaluatorName) {
            // if no evaluator specified, use the current evaluator
            evaluatorName = $scope.cellmodel.evaluator;
          }
          var newCell = bkSessionManager.getNotebookNewCellFactory().newCodeCell(evaluatorName);
          notebookCellOp.appendAfter(thisCellId, newCell);
          bkUtils.refreshRootScope();
        };
        $scope.getShareMenuPlugin = function() {
          return bkCellMenuPluginManager.getPlugin(CELL_TYPE);
        };
        var shareMenu = {
          name: "Share",
          items: []
        };
        $scope.cellmenu.addItem(shareMenu);
        $scope.$watch("getShareMenuPlugin()", function() {
          shareMenu.items = bkCellMenuPluginManager.getMenuItems(CELL_TYPE, $scope);
        });

        $scope.cellmenu.addItem({
          name: "Show input cell",
          isChecked: function() {
            return !$scope.cellmodel.input.hidden;
          },
          action: function() {
            if ($scope.cellmodel.input.hidden) {
              delete $scope.cellmodel.input.hidden;
            } else {
              $scope.cellmodel.input.hidden = true;
            }
          }
        });
        $scope.cellmenu.addItem({
          name: "Show output cell (if available)",
          isChecked: function() {
            return !$scope.cellmodel.output.hidden;
          },
          action: function() {
            if ($scope.cellmodel.output.hidden) {
              delete $scope.cellmodel.output.hidden;
            } else {
              $scope.cellmodel.output.hidden = true;
            }
          }
        });

        $scope.isInitializationCell = function() {
          return $scope.cellmodel.initialization;
        };

        $scope.cellmenu.addItem({
          name: "Initialization Cell",
          isChecked: function() {
            return $scope.isInitializationCell();
          },
          action: function() {
            if ($scope.isInitializationCell()) {
              $scope.cellmodel.initialization = undefined;
            } else {
              $scope.cellmodel.initialization = true;
            }
            notebookCellOp.reset();
          }
        });
      },
      link: function(scope, element, attrs) {
        scope.showDebug = false;

        function isFullScreen(cm) {
          return /\bCodeMirror-fullscreen\b/.test(cm.getWrapperElement().className);
        }

        function winHeight() {
          return window.innerHeight || (document.documentElement || document.body).clientHeight;
        }

        function setFullScreen(cm, full) {
          var wrap = cm.getWrapperElement();
          if (full) {
            wrap.className += " CodeMirror-fullscreen";
            wrap.style.height = winHeight() + "px";
            document.documentElement.style.overflow = "hidden";
          } else {
            wrap.className = wrap.className.replace(" CodeMirror-fullscreen", "");
            wrap.style.height = "";
            document.documentElement.style.overflow = "";
          }
          cm.refresh();
        }
        var resizeHandler = function() {
          var showing = document.body.getElementsByClassName("CodeMirror-fullscreen")[0];
          if (!showing)
            return;
          showing.CodeMirror.getWrapperElement().style.height = winHeight() + "px";
        };

        CodeMirror.on(window, "resize", resizeHandler);
        var moveFocusDown = function() {
          // move focus to next code cell
          var thisCellId = scope.cellmodel.id;
          var nextCell = notebookCellOp.getNext(thisCellId);
          while (nextCell) {
            if (scope.bkNotebook.getFocusable(nextCell.id)) {
              scope.bkNotebook.getFocusable(nextCell.id).focus();
              break;
            } else {
              nextCell = notebookCellOp.getNext(nextCell.id);
            }
          }
        };
        var moveFocusUp = function() {
          // move focus to prev code cell
          var thisCellID = scope.cellmodel.id;
          var prevCell = notebookCellOp.getPrev(thisCellID);
          while (prevCell) {
            if (scope.bkNotebook.getFocusable(prevCell.id)) {
              scope.bkNotebook.getFocusable(prevCell.id).focus();
              break;
            } else {
              prevCell = notebookCellOp.getPrev(prevCell.id);
            }
          }
        };
        scope.cm = CodeMirror.fromTextArea(element.find("textarea")[0], {
          lineNumbers: true,
          matchBrackets: true,
          onKeyEvent: function(cm, event) {
            if (event.type === "keydown") {
              if (event.keyCode === 38) {
                if ($('.CodeMirror-hint').length > 0) {
                  //codecomplete is up, skip
                  return;
                }
                if (cm.getCursor().line === 0 && event.shiftKey === false) {
                  moveFocusUp();
                }
              } else if (event.keyCode === 40) {
                if ($('.CodeMirror-hint').length > 0) {
                  //codecomplete is up, skip
                  return;
                }
                if (cm.getCursor().line === cm.doc.size - 1 && event.shiftKey === false) {
                  moveFocusDown();
                }
              } else if (event.keyCode === 27) { // ESC
                if (cm.state.vim && cm.state.vim.insertMode) {
                  return;
                } else {
                  if (isFullScreen(cm)) {
                    setFullScreen(cm, false);
                  }
                }
              }
            }
          },
          extraKeys: {
            "Shift-Ctrl-A": function(cm) {
              scope.appendCodeCell();
            },
            "Shift-Ctrl-E": function(cm) {
              scope.popupMenu();
              element.find(".inputcellmenu").find('li').find('a')[0].focus();
            },
            "Alt-Down": moveFocusDown,
            "Alt-J": moveFocusDown,
            "Alt-Up": moveFocusUp,
            "Alt-K": moveFocusUp,
            "Alt-F11": function(cm) {
              setFullScreen(cm, !isFullScreen(cm));
            },
            "Ctrl-Enter": function(cm) {
              scope.evaluate();
            },
            "Shift-Enter": function(cm) {
              scope.evaluate();
              moveFocusDown();
            },
            "Ctrl-Space": function(cm) {
              var getToken = function(editor, cur) {
                return editor.getTokenAt(cur);
              };
              var getHints = function(editor, showHintCB, options) {
                var cur = editor.getCursor();
                var token = getToken(editor, cur);
                var cursorPos = editor.indexFromPos(cur);
                // We might want this defined by the plugin.
                var onResults = function(results, matched_text) {
                  var start = token.start;
                  var end = token.end;
                  if (token.string === ".") {
                    start += 1;
                  }
                  if (matched_text) {
                    start += (cur.ch - token.start - matched_text.length);
                    end = start + matched_text.length;
                  }
                  showHintCB({
                    list: _.uniq(results),
                    from: CodeMirror.Pos(cur.line, start),
                    to: CodeMirror.Pos(cur.line, end)
                  });
                };
                scope.autocomplete(cursorPos, onResults);
              };

              var options = {
                async: true,
                closeOnUnfocus: true,
                alignWithWord: true,
                completeSingle: true
              };
              CodeMirror.showHint(cm, getHints, options);
            }
          }
        });

        scope.cm.focus();
        scope.updateUI(scope.getEvaluator());
        scope.bkNotebook.registerFocusable(scope.cellmodel.id, scope.cm);
        scope.bkNotebook.registerCM(scope.cellmodel.id, scope.cm);

        // cellmodel.body --> CodeMirror
        scope.$watch('cellmodel.input.body', function(newVal, oldVal) {
          if (scope.cm && newVal !== scope.cm.getValue()) {
            if (newVal === null) {
              newVal = "";
            }
            scope.cm.setValue(newVal);
          }
        });
        // cellmodel.body <-- CodeMirror
        var changeHandler = function(cm, e) {
          scope.cellmodel.input.body = cm.getValue();
          bkUtils.refreshRootScope();
        };
        scope.cm.on("change", changeHandler);

        var inputMenuDiv = element.find(".bkcell").first();
        scope.popupMenu = function(event) {
          var menu = inputMenuDiv.find('.dropdown').first();
          var clicked = event && event.hasOwnProperty("clientY");
          menu.css("top", clicked ? event.clientY : "");
          menu.css("left", clicked ? event.clientX - 150 : "");
          menu.find('.dropdown-toggle').first().dropdown('toggle');
        };
        var inputClickHandler = function(event) {
          if (bkUtils.getEventOffsetX(inputMenuDiv, event) >= inputMenuDiv.width()) {
            scope.popupMenu(event);
            event.stopPropagation();
          }
        };
        inputMenuDiv.click(inputClickHandler);
        var inputMousemoveHandler = function(event) {
          if (bkUtils.getEventOffsetX(inputMenuDiv, event) >= inputMenuDiv.width()) {
            inputMenuDiv.css('cursor', 'pointer');
          } else {
            inputMenuDiv.css('cursor', 'default');
          }
        };
        inputMenuDiv.mousemove(inputMousemoveHandler);

        if (scope.isInitializationCell()) {
          element.closest(".bkcell").addClass("initcell");
        } else {
          element.closest(".bkcell").removeClass("initcell");
        }
        scope.$watch('isInitializationCell()', function(newValue, oldValue) {
          if (newValue !== oldValue) {
            if (newValue) {
              element.closest(".bkcell").addClass("initcell");
            } else {
              element.closest(".bkcell").removeClass("initcell");
            }
          }
        });

        scope.getShareData = function() {
          var evaluator = _(bkSessionManager.getRawNotebookModel().evaluators)
              .find(function (evaluator) {
                return evaluator.name === scope.cellmodel.evaluator;
              });
          var cells = [scope.cellmodel];
          return bkUtils.generateNotebook([evaluator], cells);
        };

        scope.$on("$destroy", function() {
          inputMenuDiv.off("click", inputClickHandler);
          inputMenuDiv.off("mousemove", inputMousemoveHandler);
          CodeMirror.off(window, "resize", resizeHandler);
          CodeMirror.off("change", changeHandler);
          scope.bkNotebook.unregisterFocusable(scope.cellmodel.id);
          scope.bkNotebook.unregisterCM(scope.cellmodel.id);
          scope.bkNotebook = null;
        });
      }
    };
  });

})();

{'fraction_non_alphanumeric': 0.12526976652933097, 'fraction_numerical': 0.0018965404486299131, 'mean_word_length': 1.6430732002419843, 'pattern_counts': {'":': 20, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1465004', 'n_tokens_mistral': 8290, 'n_tokens_neox': 8055, 'n_words': 1469}