<commit_before>/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

/*
Jupyter actions -- these are the actions for the underlying document structure.
This can be used both on the frontend and the backend.
*/

// Uncomment to temporarily force build, since otherwise webpack doesn't find:
// require('./test/export-to-ipynb-ts');
// require("./project-actions");

declare const localStorage: any;

import * as immutable from "immutable";
import { reuseInFlight } from "async-await-utils/hof";

import { debounce } from "lodash";

// NOTE! The smc-util relative path is so we can import this same
// code in the project as well as here, due to me not
// being able to properly figure out some typescript path issue.
// **It's just a hack.**
import { callback2, retry_until_success } from "../../smc-util/async-utils";
import * as misc from "../../smc-util/misc";
const { required, defaults } = misc;

import * as awaiting from "awaiting";
import { three_way_merge } from "../../smc-util/sync/editor/generic/util";

import { Cell, KernelInfo } from "./types";
import { Syntax } from "../../smc-util/code-formatter";

import { Actions } from "../app-framework";
import {
  JupyterStoreState,
  JupyterStore,
  show_kernel_selector_reasons,
} from "./store";
import * as parsing from "./parsing";
import * as cell_utils from "./cell-utils";
import { cm_options } from "./cm_options";

// map project_id (string) -> kernels (immutable)
import { Kernels, Kernel } from "./util";
let jupyter_kernels = immutable.Map<string, Kernels>();

import { IPynbImporter } from "./import-from-ipynb";

import { JupyterKernelInterface } from "./project-interface";

import { parse_headings } from "./contents";

import {
  codemirror_to_jupyter_pos,
  js_idx_to_char_idx,
  char_idx_to_js_idx,
} from "./util";

import { Config as FormatterConfig } from "../../smc-project/formatters/prettier";

import { SyncDB } from "../../smc-util/sync/editor/db/sync";

/*
The actions -- what you can do with a jupyter notebook, and also the
underlying synchronized state.
*/

// no worries, they don't break react rendering even when they escape
const CellWriteProtectedException = new Error("CellWriteProtectedException");
const CellDeleteProtectedException = new Error("CellDeleteProtectedException");

export class JupyterActions extends Actions<JupyterStoreState> {
  private is_project: boolean;
  protected path: string;
  protected project_id: string;
  private _last_start?: number;
  protected jupyter_kernel?: JupyterKernelInterface;
  private last_cursor_move_time: Date = new Date(0);

  public _account_id: string; // Note: this is used in test

  // TODO: type these
  private _cursor_locs?: any;
  private _introspect_request?: any;
  protected set_save_status: any;
  private project_conn: any;

  protected _client: any;
  protected _file_watcher: any;
  protected _state: any;

  public _complete_request?: any;
  public _output_handler?: any;
  public ensure_backend_kernel_setup?: any;
  public initialize_manager: any;
  public manager_on_cell_change: any;
  public manager_run_cell_process_queue: any;
  public nbconvert_change: any;
  public store: any;
  public syncdb: SyncDB;

  public _init(
    project_id: string,
    path: string,
    syncdb: SyncDB,
    store: any,
    client: any
  ): void {
    if (project_id == null || path == null) {
      // typescript should ensure this, but just in case.
      throw Error("type error -- project_id and path can't be null");
      return;
    }
    store.dbg = (f) => {
      return client.dbg(`JupyterStore('${store.get("path")}').${f}`);
    };
    this._state = "init"; // 'init', 'load', 'ready', 'closed'
    this.store = store;
    this.project_id = project_id;
    this.path = path;
    store.syncdb = syncdb;
    this.syncdb = syncdb;
    this._client = client;
    this.update_contents = debounce(this.update_contents.bind(this), 2000);
    // the project client is designated to manage execution/conflict, etc.
    this.is_project = client.is_project();
    store._is_project = this.is_project;
    this._account_id = client.client_id(); // project or account's id

    let font_size: any = this.store.get_local_storage("font_size");
    if (font_size == null) {
      const account = this.redux.getStore<JupyterStoreState, JupyterStore>(
        "account"
      );
      if (account != null) {
        font_size = account.get("font_size");
      }
    }
    if (font_size == null) {
      font_size = 14;
    }

    let directory: any;
    const split_path = misc.path_split(path);
    if (split_path != null) {
      directory = split_path.head;
    }

    this.setState({
      view_mode: "normal",
      error: undefined,
      cur_id: this.store.get_local_storage("cur_id"),
      toolbar: !this.store.get_local_storage("hide_toolbar"),
      has_unsaved_changes: false,
      sel_ids: immutable.Set(), // immutable set of selected cells
      md_edit_ids: immutable.Set(), // set of ids of markdown cells in edit mode
      mode: "escape",
      font_size,
      project_id,
      directory,
      path,
      max_output_length: 10000,
      cell_toolbar: this.store.get_local_storage("cell_toolbar"),
    });

    this.syncdb.on("change", this._syncdb_change);

    if (!this.is_project) {
      this.init_client_only();
    }
  }<commit_msg>fix #4590 -- jupyter/sage9: large animations are not displayed<commit_after>/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

/*
Jupyter actions -- these are the actions for the underlying document structure.
This can be used both on the frontend and the backend.
*/

// Uncomment to temporarily force build, since otherwise webpack doesn't find:
// require('./test/export-to-ipynb-ts');
// require("./project-actions");

// This was 10000 for a while and that caused regular noticeable problems:
//    https://github.com/sagemathinc/cocalc/issues/4590
// It seems like 50000 provides a better tradeoff.  With 10000 we got about
// four support messages *per year* about this...
const DEFAULT_MAX_OUTPUT_LENGTH = 50000;

declare const localStorage: any;

import * as immutable from "immutable";
import { reuseInFlight } from "async-await-utils/hof";

import { debounce } from "lodash";

// NOTE! The smc-util relative path is so we can import this same
// code in the project as well as here, due to me not
// being able to properly figure out some typescript path issue.
// **It's just a hack.**
import { callback2, retry_until_success } from "../../smc-util/async-utils";
import * as misc from "../../smc-util/misc";
const { required, defaults } = misc;

import * as awaiting from "awaiting";
import { three_way_merge } from "../../smc-util/sync/editor/generic/util";

import { Cell, KernelInfo } from "./types";
import { Syntax } from "../../smc-util/code-formatter";

import { Actions } from "../app-framework";
import {
  JupyterStoreState,
  JupyterStore,
  show_kernel_selector_reasons,
} from "./store";
import * as parsing from "./parsing";
import * as cell_utils from "./cell-utils";
import { cm_options } from "./cm_options";

// map project_id (string) -> kernels (immutable)
import { Kernels, Kernel } from "./util";
let jupyter_kernels = immutable.Map<string, Kernels>();

import { IPynbImporter } from "./import-from-ipynb";

import { JupyterKernelInterface } from "./project-interface";

import { parse_headings } from "./contents";

import {
  codemirror_to_jupyter_pos,
  js_idx_to_char_idx,
  char_idx_to_js_idx,
} from "./util";

import { Config as FormatterConfig } from "../../smc-project/formatters/prettier";

import { SyncDB } from "../../smc-util/sync/editor/db/sync";

/*
The actions -- what you can do with a jupyter notebook, and also the
underlying synchronized state.
*/

// no worries, they don't break react rendering even when they escape
const CellWriteProtectedException = new Error("CellWriteProtectedException");
const CellDeleteProtectedException = new Error("CellDeleteProtectedException");

export class JupyterActions extends Actions<JupyterStoreState> {
  private is_project: boolean;
  protected path: string;
  protected project_id: string;
  private _last_start?: number;
  protected jupyter_kernel?: JupyterKernelInterface;
  private last_cursor_move_time: Date = new Date(0);

  public _account_id: string; // Note: this is used in test

  // TODO: type these
  private _cursor_locs?: any;
  private _introspect_request?: any;
  protected set_save_status: any;
  private project_conn: any;

  protected _client: any;
  protected _file_watcher: any;
  protected _state: any;

  public _complete_request?: any;
  public _output_handler?: any;
  public ensure_backend_kernel_setup?: any;
  public initialize_manager: any;
  public manager_on_cell_change: any;
  public manager_run_cell_process_queue: any;
  public nbconvert_change: any;
  public store: any;
  public syncdb: SyncDB;

  public _init(
    project_id: string,
    path: string,
    syncdb: SyncDB,
    store: any,
    client: any
  ): void {
    if (project_id == null || path == null) {
      // typescript should ensure this, but just in case.
      throw Error("type error -- project_id and path can't be null");
      return;
    }
    store.dbg = (f) => {
      return client.dbg(`JupyterStore('${store.get("path")}').${f}`);
    };
    this._state = "init"; // 'init', 'load', 'ready', 'closed'
    this.store = store;
    this.project_id = project_id;
    this.path = path;
    store.syncdb = syncdb;
    this.syncdb = syncdb;
    this._client = client;
    this.update_contents = debounce(this.update_contents.bind(this), 2000);
    // the project client is designated to manage execution/conflict, etc.
    this.is_project = client.is_project();
    store._is_project = this.is_project;
    this._account_id = client.client_id(); // project or account's id

    let font_size: any = this.store.get_local_storage("font_size");
    if (font_size == null) {
      const account = this.redux.getStore<JupyterStoreState, JupyterStore>(
        "account"
      );
      if (account != null) {
        font_size = account.get("font_size");
      }
    }
    if (font_size == null) {
      font_size = 14;
    }

    let directory: any;
    const split_path = misc.path_split(path);
    if (split_path != null) {
      directory = split_path.head;
    }

    this.setState({
      view_mode: "normal",
      error: undefined,
      cur_id: this.store.get_local_storage("cur_id"),
      toolbar: !this.store.get_local_storage("hide_toolbar"),
      has_unsaved_changes: false,
      sel_ids: immutable.Set(), // immutable set of selected cells
      md_edit_ids: immutable.Set(), // set of ids of markdown cells in edit mode
      mode: "escape",
      font_size,
      project_id,
      directory,
      path,
      max_output_length: DEFAULT_MAX_OUTPUT_LENGTH,
      cell_toolbar: this.store.get_local_storage("cell_toolbar"),
    });

    this.syncdb.on("change", this._syncdb_change);

    if (!this.is_project) {
      this.init_client_only();
    }
  }
{'fraction_non_alphanumeric': 0.12208676945141628, 'fraction_numerical': 0.005378271782000717, 'mean_word_length': 3.4753309265944643, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, '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': '4803321', 'n_tokens_mistral': 3600, 'n_tokens_neox': 3525, 'n_words': 1015}