<commit_before>/* @flow */

import {
  Emitter,
  CompositeDisposable,
  Disposable,
  Point,
  TextEditor
} from "atom";

import _ from "lodash";
import { autorun } from "mobx";
import React from "react";

import InspectorPane from "./panes/inspector";
import WatchesPane from "./panes/watches";
import OutputPane from "./panes/output-area";
import KernelMonitorPane from "./panes/kernel-monitor";
import Config from "./config";
import ZMQKernel from "./zmq-kernel";
import WSKernel from "./ws-kernel";
import Kernel from "./kernel";
import KernelPicker from "./kernel-picker";
import WSKernelPicker from "./ws-kernel-picker";
import ExistingKernelPicker from "./existing-kernel-picker";
import HydrogenProvider from "./plugin-api/hydrogen-provider";

import store from "./store";
import kernelManager from "./kernel-manager";
import services from "./services";
import * as commands from "./commands";
import * as codeManager from "./code-manager";
import * as result from "./result";

import type MarkerStore from "./store/markers";

import {
  log,
  reactFactory,
  isMultilanguageGrammar,
  renderDevTools,
  INSPECTOR_URI,
  WATCHES_URI,
  OUTPUT_AREA_URI,
  KERNEL_MONITOR_URI,
  hotReloadPackage,
  openOrShowDock,
  kernelSpecProvidesGrammar
} from "./utils";

import exportNotebook from "./export-notebook";
import { importNotebook, ipynbOpener } from "./import-notebook";

const Hydrogen = {
  config: Config.schema,

  activate() {
    this.emitter = new Emitter();

    let skipLanguageMappingsChange = false;
    store.subscriptions.add(
      atom.config.onDidChange(
        "Hydrogen.languageMappings",
        ({ newValue, oldValue }) => {
          if (skipLanguageMappingsChange) {
            skipLanguageMappingsChange = false;
            return;
          }

          if (store.runningKernels.length != 0) {
            skipLanguageMappingsChange = true;

            atom.config.set("Hydrogen.languageMappings", oldValue);

            atom.notifications.addError("Hydrogen", {
              description:
                "`languageMappings` cannot be updated while kernels are running",
              dismissable: false
            });
          }
        }
      )
    );

    store.subscriptions.add(
      // enable/disable mobx-react-devtools logging
      atom.config.onDidChange("Hydrogen.debug", ({ newValue }) =>
        renderDevTools(newValue)
      )
    );

    store.subscriptions.add(
      atom.config.observe("Hydrogen.statusBarDisable", newValue => {
        store.setConfigValue("Hydrogen.statusBarDisable", Boolean(newValue));
      })
    );

    store.subscriptions.add(
      atom.commands.add("atom-text-editor:not([mini])", {
        "hydrogen:run": () => this.run(),
        "hydrogen:run-all": () => this.runAll(),
        "hydrogen:run-all-above": () => this.runAllAbove(),
        "hydrogen:run-and-move-down": () => this.run(true),
        "hydrogen:run-cell": () => this.runCell(),
        "hydrogen:run-cell-and-move-down": () => this.runCell(true),
        "hydrogen:toggle-watches": () => atom.workspace.toggle(WATCHES_URI),
        "hydrogen:toggle-output-area": () => commands.toggleOutputMode(),
        "hydrogen:toggle-kernel-monitor": async () => {
          const lastItem = atom.workspace.getActivePaneItem();
          const lastPane = atom.workspace.paneForItem(lastItem);
          await atom.workspace.toggle(KERNEL_MONITOR_URI);
          if (lastPane) lastPane.activate();
        },
        "hydrogen:start-local-kernel": () => this.startZMQKernel(),
        "hydrogen:connect-to-remote-kernel": () => this.connectToWSKernel(),
        "hydrogen:connect-to-existing-kernel": () =>
          this.connectToExistingKernel(),
        "hydrogen:add-watch": () => {
          if (store.kernel) {
            store.kernel.watchesStore.addWatchFromEditor(store.editor);
            openOrShowDock(WATCHES_URI);
          }
        },
        "hydrogen:remove-watch": () => {
          if (store.kernel) {
            store.kernel.watchesStore.removeWatch();
            openOrShowDock(WATCHES_URI);
          }
        },
        "hydrogen:update-kernels": () => kernelManager.updateKernelSpecs(),
        "hydrogen:toggle-inspector": () => commands.toggleInspector(store),
        "hydrogen:interrupt-kernel": () =>
          this.handleKernelCommand({ command: "interrupt-kernel" }, store),
        "hydrogen:restart-kernel": () =>
          this.handleKernelCommand({ command: "restart-kernel" }, store),
        "hydrogen:shutdown-kernel": () =>
          this.handleKernelCommand({ command: "shutdown-kernel" }, store),
        "hydrogen:clear-result": () => result.clearResult(store),
        "hydrogen:export-notebook": () => exportNotebook(),
        "hydrogen:fold-current-cell": () => this.foldCurrentCell(),
        "hydrogen:fold-all-but-current-cell": () =>
          this.foldAllButCurrentCell(),
        "hydrogen:clear-results": () => result.clearResults(store)
      })
    );

    store.subscriptions.add(
      atom.commands.add("atom-workspace", {
        "hydrogen:import-notebook": importNotebook
      })
    );

    if (atom.inDevMode()) {
      store.subscriptions.add(
        atom.commands.add("atom-workspace", {
          "hydrogen:hot-reload-package": () => hotReloadPackage()
        })
      );
    }

    store.subscriptions.add(
      atom.workspace.observeActiveTextEditor(editor => {
        store.updateEditor(editor);
      })
    );

    store.subscriptions.add(
      atom.workspace.observeTextEditors(editor => {
        const editorSubscriptions = new CompositeDisposable();
        editorSubscriptions.add(
          editor.onDidChangeGrammar(() => {
            store.setGrammar(editor);
          })
        );

        if (isMultilanguageGrammar(editor.getGrammar())) {
          editorSubscriptions.add(
            editor.onDidChangeCursorPosition(
              _.debounce(() => {
                store.setGrammar(editor);
              }, 75)
            )
          );
        }

        editorSubscriptions.add(
          editor.onDidDestroy(() => {
            editorSubscriptions.dispose();
          })
        );

        editorSubscriptions.add(
          editor.onDidChangeTitle(newTitle => store.forceEditorUpdate())
        );

        store.subscriptions.add(editorSubscriptions);
      })
    );

    this.hydrogenProvider = null;

    store.subscriptions.add(
      atom.workspace.addOpener(uri => {
        switch (uri) {
          case INSPECTOR_URI:
            return new InspectorPane(store);
          case WATCHES_URI:
            return new WatchesPane(store);
          case OUTPUT_AREA_URI:
            return new OutputPane(store);
          case KERNEL_MONITOR_URI:
            return new KernelMonitorPane(store);
        }
      })
    );
    store.subscriptions.add(atom.workspace.addOpener(ipynbOpener));

    store.subscriptions.add(
      // Destroy any Panes when the package is deactivated.
      new Disposable(() => {
        atom.workspace.getPaneItems().forEach(item => {
          if (
            item instanceof InspectorPane ||
            item instanceof WatchesPane ||
            item instanceof OutputPane ||
            item instanceof KernelMonitorPane
          ) {
            item.destroy();
          }
        });
      })
    );

    renderDevTools(atom.config.get("Hydrogen.debug") === true);

    autorun(() => {
      this.emitter.emit("did-change-kernel", store.kernel);
    });
  },

  deactivate() {
    store.dispose();
  },

  /*-------------- Service Providers --------------*/
  provideHydrogen() {
    if (!this.hydrogenProvider) {
      this.hydrogenProvider = new HydrogenProvider(this);
    }

    return this.hydrogenProvider;
  },

  provideAutocompleteResults() {
    return services.provided.autocomplete.provideAutocompleteResults(store);
  },
  /*-----------------------------------------------*/

  /*-------------- Service Consumers --------------*/
  consumeStatusBar(statusBar: atom$StatusBar) {
    const disposable = services.consumed.statusBar.addStatusBar(
      store,
      statusBar,
      this.handleKernelCommand.bind(this)
    );
    store.subscriptions.add(disposable);
    return disposable;
  },
  /*-----------------------------------------------*/

  connectToExistingKernel() {
    if (!this.existingKernelPicker) {
      this.existingKernelPicker = new ExistingKernelPicker();
    }
    this.existingKernelPicker.toggle();
  },

  handleKernelCommand(
    { command, payload }: { command: string, payload: ?Kernelspec },
    { kernel, markers }: { kernel: ?Kernel, markers: ?MarkerStore }
  ) {
    log("handleKernelCommand:", arguments);

    if (!kernel) {
      const message = "No running kernel for grammar or editor found";
      atom.notifications.addError(message);
      return;
    }

    if (command === "interrupt-kernel") {
      kernel.interrupt();
    } else if (command === "restart-kernel") {
      kernel.restart();
    } else if (command === "shutdown-kernel") {
      if (markers) markers.clear();
      // Note that destroy alone does not shut down a WSKernel
      kernel.shutdown();
      kernel.destroy();
    } else if (
      command === "rename-kernel" &&
      kernel.transport instanceof WSKernel
    ) {
      kernel.transport.promptRename();
    } else if (command === "disconnect-kernel") {
      if (markers) markers.clear();
      kernel.destroy();
    }
  },

  run(moveDown: boolean = false) {
    const editor = store.editor;
    if (!editor) return;
    // https://github.com/nteract/hydrogen/issues/1452
    atom.commands.dispatch(editor.element, "autocomplete-plus:cancel");
    const codeBlock = codeManager.findCodeBlock(editor);
    if (!codeBlock) {
      return;
    }

    const { row } = codeBlock;
    let { code } = codeBlock;
    const cellType = codeManager.getMetadataForRow(editor, new Point(row, 0));
    if (code || code === "") {
      if (cellType === "markdown") {
        code = codeManager.removeCommentsMarkdownCell(editor, code);
      }
      if (moveDown === true) {
        codeManager.moveDown(editor, row);
      }
      this.checkForKernel(store, kernel => {
        //$FlowFixMe
        result.createResult(store, { code, row, cellType });
      });
    }
  },

  runAll(breakpoints: ?Array<atom$Point>) {
    const { editor, kernel, grammar, filePath } = store;
    if (!editor || !grammar || !filePath) return;
    if (isMultilanguageGrammar(editor.getGrammar())) {
      atom.notifications.addError(
        '"Run All" is not supported for this file type!'
      );
      return;
    }

    if (editor && kernel) {
      this._runAll(editor, kernel, breakpoints);
      return;
    }

    kernelManager.startKernelFor(
      grammar,
      editor,
      filePath,
      (kernel: Kernel) => {
        this._runAll(editor, kernel, breakpoints);
      }
    );
  },

  _runAll(
    editor: atom$TextEditor,
    kernel: Kernel,
    breakpoints?: Array<atom$Point>
  ) {
    let cells = codeManager.getCells(editor, breakpoints);
    for (const cell of cells) {
      const { start, end } = cell;
      let code = codeManager.getTextInRange(editor, start, end);
      const row = codeManager.escapeBlankRows(
        editor,
        start.row,
        end.row == editor.getLastBufferRow() ? end.row : end.row - 1
      );
      const cellType = codeManager.getMetadataForRow(editor, start);
      if (code || code === "") {
        if (cellType === "markdown") {
          code = codeManager.removeCommentsMarkdownCell(editor, code);
        }
        this.checkForKernel(store, kernel => {
          //$FlowFixMe
          result.createResult(store, { code, row, cellType });
        });
      }
    }
  },

  runAllAbove() {
    const { editor, kernel, grammar, filePath } = store;
    if (!editor || !grammar || !filePath) return;
    if (isMultilanguageGrammar(editor.getGrammar())) {
      atom.notifications.addError(
        '"Run All Above" is not supported for this file type!'
      );
      return;
    }

    if (editor && kernel) {
      this._runAllAbove(editor, kernel);
      return;
    }

    kernelManager.startKernelFor(
      grammar,
      editor,
      filePath,
      (kernel: Kernel) => {
        this._runAllAbove(editor, kernel);
      }
    );
  },

  _runAllAbove(editor: atom$TextEditor, kernel: Kernel) {
    const cursor = editor.getCursorBufferPosition();
    cursor.column = editor.getBuffer().lineLengthForRow(cursor.row);
    const breakpoints = codeManager.getBreakpoints(editor);
    breakpoints.push(cursor);
    const cells = codeManager.getCells(editor, breakpoints);
    for (const cell of cells) {
      const { start, end } = cell;
      let code = codeManager.getTextInRange(editor, start, end);
      const row = codeManager.escapeBlankRows(
        editor,
        start.row,
        end.row == editor.getLastBufferRow() ? end.row : end.row - 1
      );
      const cellType = codeManager.getMetadataForRow(editor, start);
      if (code || code === "") {
        if (cellType === "markdown") {
          code = codeManager.removeCommentsMarkdownCell(editor, code);
        }
        this.checkForKernel(store, kernel => {
          //$FlowFixMe
          result.createResult(store, { code, row, cellType });
        });
      }
      if (cell.containsPoint(cursor)) {
        break;
      }
    }
  },

  runCell(moveDown: boolean = false) {
    const editor = store.editor;
    if (!editor) return;
    // https://github.com/nteract/hydrogen/issues/1452
    atom.commands.dispatch(editor.element, "autocomplete-plus:cancel");
    const { start, end } = codeManager.getCurrentCell(editor);
    let code = codeManager.getTextInRange(editor, start, end);
    const row = codeManager.escapeBlankRows(
      editor,
      start.row,
      end.row == editor.getLastBufferRow() ? end.row : end.row - 1
    );
    const cellType = codeManager.getMetadataForRow(editor, start);
    if (code || code === "") {
      if (cellType === "markdown") {
        code = codeManager.removeCommentsMarkdownCell(editor, code);
      }
      if (moveDown === true) {
        codeManager.moveDown(editor, row);
      }
      this.checkForKernel(store, kernel => {
        //$FlowFixMe
        result.createResult(store, { code, row, cellType });
      });
    }
  },

  foldCurrentCell() {
    const editor = store.editor;
    if (!editor) return;
    codeManager.foldCurrentCell(editor);
  },

  foldAllButCurrentCell() {
    const editor = store.editor;
    if (!editor) return;
    codeManager.foldAllButCurrentCell(editor);
  },

  startZMQKernel() {
    kernelManager
      .getAllKernelSpecsForGrammar(store.grammar)
      .then(kernelSpecs => {
        if (this.kernelPicker) {
          this.kernelPicker.kernelSpecs = kernelSpecs;
        } else {
          this.kernelPicker = new KernelPicker(kernelSpecs);

          this.kernelPicker.onConfirmed = (kernelSpec: Kernelspec) => {
            const { editor, grammar, filePath, markers } = store;
            if (!editor || !grammar || !filePath || !markers) return;
            markers.clear();

            kernelManager.startKernel(kernelSpec, grammar, editor, filePath);
          };
        }

        this.kernelPicker.toggle();
      });
  },

  connectToWSKernel() {
    if (!this.wsKernelPicker) {
      this.wsKernelPicker = new WSKernelPicker((transport: WSKernel) => {
        const kernel = new Kernel(transport);
        const { editor, grammar, filePath, markers } = store;
        if (!editor || !grammar || !filePath || !markers) return;
        markers.clear();

        if (kernel.transport instanceof ZMQKernel) kernel.destroy();

        store.newKernel(kernel, filePath, editor, grammar);
      });
    }

    this.wsKernelPicker.toggle((kernelSpec: Kernelspec) =>
      kernelSpecProvidesGrammar(kernelSpec, store.grammar)
    );
  },

  // Accepts store as an arg
  checkForKernel(
    {
      editor,
      grammar,
      filePath,
      kernel
    }: {
      editor: atom$TextEditor,
      grammar: atom$Grammar,
      filePath: string,
      kernel?: Kernel
    },
    callback: (kernel: Kernel) => void
  ) {
    if (!filePath || !grammar) {
      return atom.notifications.addError(
        "The language grammar must be set in order to start a kernel. The easiest way to do this is to save the file."
      );
    }

    if (kernel) {
      callback(kernel);
      return;
    }

    kernelManager.startKernelFor(grammar, editor, filePath, (kernel: Kernel) =>
      callback(kernel)
    );
  }
};

export default Hydrogen;
<commit_msg>rename for clarity<commit_after>/* @flow */

import {
  Emitter,
  CompositeDisposable,
  Disposable,
  Point,
  TextEditor
} from "atom";

import _ from "lodash";
import { autorun } from "mobx";
import React from "react";

import InspectorPane from "./panes/inspector";
import WatchesPane from "./panes/watches";
import OutputPane from "./panes/output-area";
import KernelMonitorPane from "./panes/kernel-monitor";
import Config from "./config";
import ZMQKernel from "./zmq-kernel";
import WSKernel from "./ws-kernel";
import Kernel from "./kernel";
import KernelPicker from "./kernel-picker";
import WSKernelPicker from "./ws-kernel-picker";
import ExistingKernelPicker from "./existing-kernel-picker";
import HydrogenProvider from "./plugin-api/hydrogen-provider";

import store from "./store";
import kernelManager from "./kernel-manager";
import services from "./services";
import * as commands from "./commands";
import * as codeManager from "./code-manager";
import * as result from "./result";

import type MarkerStore from "./store/markers";

import {
  log,
  reactFactory,
  isMultilanguageGrammar,
  renderDevTools,
  INSPECTOR_URI,
  WATCHES_URI,
  OUTPUT_AREA_URI,
  KERNEL_MONITOR_URI,
  hotReloadPackage,
  openOrShowDock,
  kernelSpecProvidesGrammar
} from "./utils";

import exportNotebook from "./export-notebook";
import { importNotebook, ipynbOpener } from "./import-notebook";

const Hydrogen = {
  config: Config.schema,

  activate() {
    this.emitter = new Emitter();

    let skipLanguageMappingsChange = false;
    store.subscriptions.add(
      atom.config.onDidChange(
        "Hydrogen.languageMappings",
        ({ newValue, oldValue }) => {
          if (skipLanguageMappingsChange) {
            skipLanguageMappingsChange = false;
            return;
          }

          if (store.runningKernels.length != 0) {
            skipLanguageMappingsChange = true;

            atom.config.set("Hydrogen.languageMappings", oldValue);

            atom.notifications.addError("Hydrogen", {
              description:
                "`languageMappings` cannot be updated while kernels are running",
              dismissable: false
            });
          }
        }
      )
    );

    store.subscriptions.add(
      // enable/disable mobx-react-devtools logging
      atom.config.onDidChange("Hydrogen.debug", ({ newValue }) =>
        renderDevTools(newValue)
      )
    );

    store.subscriptions.add(
      atom.config.observe("Hydrogen.statusBarDisable", newValue => {
        store.setConfigValue("Hydrogen.statusBarDisable", Boolean(newValue));
      })
    );

    store.subscriptions.add(
      atom.commands.add("atom-text-editor:not([mini])", {
        "hydrogen:run": () => this.run(),
        "hydrogen:run-all": () => this.runAll(),
        "hydrogen:run-all-above": () => this.runAllAbove(),
        "hydrogen:run-and-move-down": () => this.run(true),
        "hydrogen:run-cell": () => this.runCell(),
        "hydrogen:run-cell-and-move-down": () => this.runCell(true),
        "hydrogen:toggle-watches": () => atom.workspace.toggle(WATCHES_URI),
        "hydrogen:toggle-output-area": () => commands.toggleOutputMode(),
        "hydrogen:toggle-kernel-monitor": async () => {
          const lastItem = atom.workspace.getActivePaneItem();
          const lastPane = atom.workspace.paneForItem(lastItem);
          await atom.workspace.toggle(KERNEL_MONITOR_URI);
          if (lastPane) lastPane.activate();
        },
        "hydrogen:start-local-kernel": () => this.startZMQKernel(),
        "hydrogen:connect-to-remote-kernel": () => this.connectToWSKernel(),
        "hydrogen:connect-to-existing-kernel": () =>
          this.connectToExistingKernel(),
        "hydrogen:add-watch": () => {
          if (store.kernel) {
            store.kernel.watchesStore.addWatchFromEditor(store.editor);
            openOrShowDock(WATCHES_URI);
          }
        },
        "hydrogen:remove-watch": () => {
          if (store.kernel) {
            store.kernel.watchesStore.removeWatch();
            openOrShowDock(WATCHES_URI);
          }
        },
        "hydrogen:update-kernels": () => kernelManager.updateKernelSpecs(),
        "hydrogen:toggle-inspector": () => commands.toggleInspector(store),
        "hydrogen:interrupt-kernel": () =>
          this.handleKernelCommand({ command: "interrupt-kernel" }, store),
        "hydrogen:restart-kernel": () =>
          this.handleKernelCommand({ command: "restart-kernel" }, store),
        "hydrogen:shutdown-kernel": () =>
          this.handleKernelCommand({ command: "shutdown-kernel" }, store),
        "hydrogen:clear-result": () => result.clearResult(store),
        "hydrogen:export-notebook": () => exportNotebook(),
        "hydrogen:fold-current-cell": () => this.foldCurrentCell(),
        "hydrogen:fold-all-but-current-cell": () =>
          this.foldAllButCurrentCell(),
        "hydrogen:clear-results": () => result.clearResults(store)
      })
    );

    store.subscriptions.add(
      atom.commands.add("atom-workspace", {
        "hydrogen:import-notebook": importNotebook
      })
    );

    if (atom.inDevMode()) {
      store.subscriptions.add(
        atom.commands.add("atom-workspace", {
          "hydrogen:hot-reload-package": () => hotReloadPackage()
        })
      );
    }

    store.subscriptions.add(
      atom.workspace.observeActiveTextEditor(editor => {
        store.updateEditor(editor);
      })
    );

    store.subscriptions.add(
      atom.workspace.observeTextEditors(editor => {
        const editorSubscriptions = new CompositeDisposable();
        editorSubscriptions.add(
          editor.onDidChangeGrammar(() => {
            store.setGrammar(editor);
          })
        );

        if (isMultilanguageGrammar(editor.getGrammar())) {
          editorSubscriptions.add(
            editor.onDidChangeCursorPosition(
              _.debounce(() => {
                store.setGrammar(editor);
              }, 75)
            )
          );
        }

        editorSubscriptions.add(
          editor.onDidDestroy(() => {
            editorSubscriptions.dispose();
          })
        );

        editorSubscriptions.add(
          editor.onDidChangeTitle(newTitle => store.forceEditorUpdate())
        );

        store.subscriptions.add(editorSubscriptions);
      })
    );

    this.hydrogenProvider = null;

    store.subscriptions.add(
      atom.workspace.addOpener(uri => {
        switch (uri) {
          case INSPECTOR_URI:
            return new InspectorPane(store);
          case WATCHES_URI:
            return new WatchesPane(store);
          case OUTPUT_AREA_URI:
            return new OutputPane(store);
          case KERNEL_MONITOR_URI:
            return new KernelMonitorPane(store);
        }
      })
    );
    store.subscriptions.add(atom.workspace.addOpener(ipynbOpener));

    store.subscriptions.add(
      // Destroy any Panes when the package is deactivated.
      new Disposable(() => {
        atom.workspace.getPaneItems().forEach(item => {
          if (
            item instanceof InspectorPane ||
            item instanceof WatchesPane ||
            item instanceof OutputPane ||
            item instanceof KernelMonitorPane
          ) {
            item.destroy();
          }
        });
      })
    );

    renderDevTools(atom.config.get("Hydrogen.debug") === true);

    autorun(() => {
      this.emitter.emit("did-change-kernel", store.kernel);
    });
  },

  deactivate() {
    store.dispose();
  },

  /*-------------- Service Providers --------------*/
  provideHydrogen() {
    if (!this.hydrogenProvider) {
      this.hydrogenProvider = new HydrogenProvider(this);
    }

    return this.hydrogenProvider;
  },

  provideAutocompleteResults() {
    return services.provided.autocomplete.provideAutocompleteResults(store);
  },
  /*-----------------------------------------------*/

  /*-------------- Service Consumers --------------*/
  consumeStatusBar(statusBar: atom$StatusBar) {
    const disposable = services.consumed.statusBar.addStatusBar(
      store,
      statusBar,
      this.handleKernelCommand.bind(this)
    );
    store.subscriptions.add(disposable);
    return disposable;
  },
  /*-----------------------------------------------*/

  connectToExistingKernel() {
    if (!this.existingKernelPicker) {
      this.existingKernelPicker = new ExistingKernelPicker();
    }
    this.existingKernelPicker.toggle();
  },

  handleKernelCommand(
    { command, payload }: { command: string, payload: ?Kernelspec },
    { kernel, markers }: { kernel: ?Kernel, markers: ?MarkerStore }
  ) {
    log("handleKernelCommand:", arguments);

    if (!kernel) {
      const message = "No running kernel for grammar or editor found";
      atom.notifications.addError(message);
      return;
    }

    if (command === "interrupt-kernel") {
      kernel.interrupt();
    } else if (command === "restart-kernel") {
      kernel.restart();
    } else if (command === "shutdown-kernel") {
      if (markers) markers.clear();
      // Note that destroy alone does not shut down a WSKernel
      kernel.shutdown();
      kernel.destroy();
    } else if (
      command === "rename-kernel" &&
      kernel.transport instanceof WSKernel
    ) {
      kernel.transport.promptRename();
    } else if (command === "disconnect-kernel") {
      if (markers) markers.clear();
      kernel.destroy();
    }
  },

  run(moveDown: boolean = false) {
    const editor = store.editor;
    if (!editor) return;
    // https://github.com/nteract/hydrogen/issues/1452
    atom.commands.dispatch(editor.element, "autocomplete-plus:cancel");
    const codeBlock = codeManager.findCodeBlock(editor);
    if (!codeBlock) {
      return;
    }

    const { row } = codeBlock;
    let { code } = codeBlock;
    const cellType = codeManager.getMetadataForRow(editor, new Point(row, 0));
    if (code || code === "") {
      if (cellType === "markdown") {
        code = codeManager.removeCommentsMarkdownCell(editor, code);
      }
      if (moveDown === true) {
        codeManager.moveDown(editor, row);
      }
      this.checkForKernel(store, kernel => {
        //$FlowFixMe
        result.createResult(store, { code, row, cellType });
      });
    }
  },

  runAll(breakpoints: ?Array<atom$Point>) {
    const { editor, kernel, grammar, filePath } = store;
    if (!editor || !grammar || !filePath) return;
    if (isMultilanguageGrammar(editor.getGrammar())) {
      atom.notifications.addError(
        '"Run All" is not supported for this file type!'
      );
      return;
    }

    if (editor && kernel) {
      this._runAll(editor, kernel, breakpoints);
      return;
    }

    kernelManager.startKernelFor(
      grammar,
      editor,
      filePath,
      (kernel: Kernel) => {
        this._runAll(editor, kernel, breakpoints);
      }
    );
  },

  _runAll(
    editor: atom$TextEditor,
    kernel: Kernel,
    breakpoints?: Array<atom$Point>
  ) {
    let cells = codeManager.getCells(editor, breakpoints);
    for (const cell of cells) {
      const { start, end } = cell;
      let code = codeManager.getTextInRange(editor, start, end);
      const row = codeManager.escapeBlankRows(
        editor,
        start.row,
        end.row == editor.getLastBufferRow() ? end.row : end.row - 1
      );
      const cellType = codeManager.getMetadataForRow(editor, start);
      if (code || code === "") {
        if (cellType === "markdown") {
          code = codeManager.removeCommentsMarkdownCell(editor, code);
        }
        this.checkForKernel(store, kernel => {
          //$FlowFixMe
          result.createResult(store, { code, row, cellType });
        });
      }
    }
  },

  runAllAbove() {
    const { editor, kernel, grammar, filePath } = store;
    if (!editor || !grammar || !filePath) return;
    if (isMultilanguageGrammar(editor.getGrammar())) {
      atom.notifications.addError(
        '"Run All Above" is not supported for this file type!'
      );
      return;
    }

    if (editor && kernel) {
      this._runAllAbove(editor, kernel);
      return;
    }

    kernelManager.startKernelFor(
      grammar,
      editor,
      filePath,
      (kernel: Kernel) => {
        this._runAllAbove(editor, kernel);
      }
    );
  },

  _runAllAbove(editor: atom$TextEditor, kernel: Kernel) {
    const cursor = editor.getCursorBufferPosition();
    cursor.column = editor.getBuffer().lineLengthForRow(cursor.row);
    const breakpoints = codeManager.getBreakpoints(editor);
    breakpoints.push(cursor);
    const cells = codeManager.getCells(editor, breakpoints);
    for (const cell of cells) {
      const { start, end } = cell;
      let code = codeManager.getTextInRange(editor, start, end);
      const row = codeManager.escapeBlankRows(
        editor,
        start.row,
        end.row == editor.getLastBufferRow() ? end.row : end.row - 1
      );
      const cellType = codeManager.getMetadataForRow(editor, start);
      if (code || code === "") {
        if (cellType === "markdown") {
          code = codeManager.removeCommentsMarkdownCell(editor, code);
        }
        this.checkForKernel(store, kernel => {
          //$FlowFixMe
          result.createResult(store, { code, row, cellType });
        });
      }
      if (cell.containsPoint(cursor)) {
        break;
      }
    }
  },

  runCell(moveDown: boolean = false) {
    const editor = store.editor;
    if (!editor) return;
    // https://github.com/nteract/hydrogen/issues/1452
    atom.commands.dispatch(editor.element, "autocomplete-plus:cancel");
    const { start, end } = codeManager.getCurrentCell(editor);
    let code = codeManager.getTextInRange(editor, start, end);
    const row = codeManager.escapeBlankRows(
      editor,
      start.row,
      end.row == editor.getLastBufferRow() ? end.row : end.row - 1
    );
    const cellType = codeManager.getMetadataForRow(editor, start);
    if (code || code === "") {
      if (cellType === "markdown") {
        code = codeManager.removeCommentsMarkdownCell(editor, code);
      }
      if (moveDown === true) {
        codeManager.moveDown(editor, row);
      }
      this.checkForKernel(store, kernel => {
        //$FlowFixMe
        result.createResult(store, { code, row, cellType });
      });
    }
  },

  foldCurrentCell() {
    const editor = store.editor;
    if (!editor) return;
    codeManager.foldCurrentCell(editor);
  },

  foldAllButCurrentCell() {
    const editor = store.editor;
    if (!editor) return;
    codeManager.foldAllButCurrentCell(editor);
  },

  startZMQKernel() {
    kernelManager
      .getAllKernelSpecsForGrammar(store.grammar)
      .then(kernelSpecs => {
        if (this.kernelPicker) {
          this.kernelPicker.kernelSpecs = kernelSpecs;
        } else {
          this.kernelPicker = new KernelPicker(kernelSpecs);

          this.kernelPicker.onConfirmed = (kernelSpec: Kernelspec) => {
            const { editor, grammar, filePath, markers } = store;
            if (!editor || !grammar || !filePath || !markers) return;
            markers.clear();

            kernelManager.startKernel(kernelSpec, grammar, editor, filePath);
          };
        }

        this.kernelPicker.toggle();
      });
  },

  connectToWSKernel() {
    if (!this.wsKernelPicker) {
      this.wsKernelPicker = new WSKernelPicker((transport: WSKernel) => {
        const kernel = new Kernel(transport);
        const { editor, grammar, filePath, markers } = store;
        if (!editor || !grammar || !filePath || !markers) return;
        markers.clear();

        if (kernel.transport instanceof ZMQKernel) kernel.destroy();

        store.newKernel(kernel, filePath, editor, grammar);
      });
    }

    this.wsKernelPicker.toggle((kernelSpec: Kernelspec) =>
      kernelSpecProvidesGrammar(kernelSpec, store.grammar)
    );
  },

  // Accepts store as an arg
  checkForKernel(
    {
      editor,
      grammar,
      filePath,
      kernel
    }: {
      editor: atom$TextEditor,
      grammar: atom$Grammar,
      filePath: string,
      kernel?: Kernel
    },
    callback: (kernel: Kernel) => void
  ) {
    if (!filePath || !grammar) {
      return atom.notifications.addError(
        "The language grammar must be set in order to start a kernel. The easiest way to do this is to save the file."
      );
    }

    if (kernel) {
      callback(kernel);
      return;
    }

    kernelManager.startKernelFor(
      grammar,
      editor,
      filePath,
      (newKernel: Kernel) => callback(newKernel)
    );
  }
};

export default Hydrogen;
<|endoftext|>


<commit_before>/**
 * main.js (hybrid)
 */
"use strict";
{
  /* sdk */
  const {basename, join} = require("sdk/fs/path");
  const {ns} = require("sdk/core/namespace");
  const fs = require("sdk/io/fs");
  const self = require("sdk/self");
  const simplePrefs = require("sdk/simple-prefs");
  const simpleStorage = require("sdk/simple-storage");
  const system = require("sdk/system");
  const urls = require("sdk/url");
  const webExtension = require("sdk/webextension");

  /* constants */
  const LABEL = "withExEditor";
  const PREFS = "WithExEditorPrefs";
  const CHAR = "UTF-8";
  const MASK_BIT = 0o111;
  const PERM_FILE = 0o666;
  const PERM_DIR = 0o777;
  const TMP_FILES = "tmpFiles";
  const TMP_FILES_PB = "tmpFilesPb";
  const DIR_PREF = system.pathFor("PrefD");
  const PREF_LABEL = /^.+\.([^.]+)$/.exec(basename(DIR_PREF));
  const DIR_TMP = join(
    system.pathFor("TmpD"),
    LABEL,
    PREF_LABEL && PREF_LABEL[1] || LABEL
  );
  const DIR_TMP_FILES = join(DIR_TMP, TMP_FILES);
  const DIR_TMP_FILES_PB = join(DIR_TMP, TMP_FILES_PB);

  const CHECK_EXECUTABLE = "checkExecutable";
  const CREATE_TMP_FILE = "createTmpFile";
  const FORCE_REMOVE = "forceRemove";
  const GET_APP_MANIFEST = "getAppManifest";
  const GET_FILE_PATH = "getFilePath";
  const GET_TMP_FILE = "getTmpFile";
  const PORT_FILE_PATH = "portFilePath";
  const REMOVE_PB_TMP_FILES = "removePrivateTmpFiles";
  const REMOVE_SDK_PREFS = "removeSdkPrefs";
  const REMOVE_TAB_STORAGE = "removeTabRelatedStorage";
  const RES_APP_MANIFEST = "resAppManifest";
  const RES_EXECUTABLE = "resExecutable";
  const SET_VARS = "setVars";
  const SYNC_TEXT = "syncText";

  /* variables */
  const vars = {
    [FORCE_REMOVE]: true,
  };

  /**
   * log error
   * @param {!Object} e - Error
   * @return {boolean} - false
   */
  const logError = e => {
    console.error(e);
    return false;
  };

  /**
   * is string
   * @param {*} o - object to check
   * @return {boolean} - result
   */
  const isString = o =>
    o && (typeof o === "string" || o instanceof String) || false;

  /**
   * get file name from native file path
   * @param {string} path - file path
   * @return {string} - file name
   */
  const getFileNameFromFilePath = async path => {
    const name = isString(path) && /^([^.]+)(?:\..+)?$/.exec(basename(path));
    return name && name[1] || LABEL;
  };

  /**
   * convert URI to native file path
   * @param {string} uri - URI
   * @return {?string} - file path
   */
  const convUriToFilePath = async uri =>
    isString(uri) && urls.toFilename(uri) || null;

  /**
   * the file is a file
   * @param {string} path - file path
   * @return {boolean} - result
   */
  const isFile = async path =>
    isString(path) && fs.existsSync(path) && (new fs.Stats(path)).isFile();

  /**
   * the file is executable
   * @param {string} path - file path
   * @param {number} mask - mask bit
   * @return {Object} - Promise.<boolean>
   */
  const isExecutable = async (path, mask = MASK_BIT) =>
    isFile(path).then(bool => bool && !!((new fs.Stats(path)).mode & mask));

  /**
   * remove the directory
   * @param {string} path - directory path
   * @param {boolean} rec - recursive
   * @return {void}
   */
  const removeDir = async (path, rec) => {
    isString(path) && fs.existsSync(path) && fs.rmdirSync(path, !!rec);
  };

  /**
   * create a directory
   * @param {string} path - directory path
   * @param {string|number} mode - permission
   * @return {void}
   */
  const createDir = async (path, mode = PERM_DIR) => {
    isString(path) && !fs.existsSync(path) && fs.mkdirSync(path, mode);
  };

  /**
   * create a file
   * @param {string} path - file path
   * @param {string} value - value to write
   * @param {Function} callback - callback when write completes
   * @param {Object} opt - callback option
   * @param {number|string} mode - file permission
   * @param {string} encoding - file encoding
   * @return {void}
   */
  const createFile = async (path, value = "", callback = null, opt = null,
                            mode = PERM_FILE, encoding = CHAR) => {
    isString(path) && fs.writeFile(path, value, encoding, e => {
      e ?
        logError(e) :
        (fs.chmodSync(path, mode), callback && callback(path, opt));
    });
  };

  /**
   * read a file
   * @param {string} path - file path
   * @param {Function} callback - callback when read completes
   * @param {Object} opt - callback option
   * @param {string} encoding - file encoding
   * @return {void}
   */
  const readFile = async (path, callback = null, opt = null,
                          encoding = CHAR) => {
    isString(path) && fs.readFile(path, encoding, (e, data) => {
      e ?
        logError(e) :
        callback && callback(data, opt);
    });
  };

  /* handle temporary files */
  /* namespace */
  const internal = ns();

  /**
   * temporary files storage class
   */
  class TemporaryStorage {
    /**
     * @private
     */
    constructor() {
      internal(this).storage = {tmpFiles: {}, tmpFilesPb: {}};
    }

    /**
     * get target property value
     * @param {Object} keys - descendant property keys
     * @return {*}
     */
    get(keys = {}) {
      const store = internal(this).storage;
      const {dir, tabId, host, target} = keys;
      return dir && tabId && host && target &&
             store[dir] && store[dir][tabId] && store[dir][tabId][host] &&
               store[dir][tabId][host][target];
    }

    /**
     * set property / value
     * @param {Object} keys - descendant property keys
     * @param {*} val - value
     * @return {void}
     */
    set(keys = {}, val = null) {
      const store = internal(this).storage;
      const {dir, tabId, host, target} = keys;
      dir && (store[dir] = store[dir] || tabId && {} || val) &&
      tabId && (store[dir][tabId] = store[dir][tabId] || host && {} || val) &&
      host && (store[dir][tabId][host] = store[dir][tabId][host] ||
                                         target && {} || val) &&
      target && (store[dir][tabId][host][target] = val);
    }

    /**
     * remove property
     * @param {Object} keys - descendant property keys
     * @return {boolean} - result
     */
    remove(keys = {}) {
      const store = internal(this).storage;
      const {dir, tabId, host, target} = keys;
      let bool;
      if (dir && store[dir]) {
        if (tabId && host && target &&
            store[dir][tabId] && store[dir][tabId][host]) {
          bool = delete store[dir][tabId][host][target];
        } else if (tabId && host && store[dir][tabId]) {
          bool = delete store[dir][tabId][host];
        } else if (tabId) {
          bool = delete store[dir][tabId];
        } else {
          bool = delete store[dir];
        }
      }
      return bool || false;
    }
  }

  /* temporary files storage */
  const tmpStorage = new TemporaryStorage();

  /**
   * remove tmpStorage data on tab close
   * @param {Object} data - closed tab data
   * @return {boolean} - result
   */
  const removeTabRelatedStorage = async data => {
    let bool;
    if (data) {
      const {tabId} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      bool = tabId && tmpStorage.remove({dir, tabId});
    }
    return bool || false;
  };

  /**
   * remove private temporary files
   * @return {Object} - Promise.<void>
   */
  const removePrivateTmpFiles = () =>
    removeDir(DIR_TMP_FILES_PB, vars[FORCE_REMOVE]).then(() =>
      createDir(DIR_TMP_FILES_PB)
    ).then(() => {
      tmpStorage.remove({dir: TMP_FILES_PB});
      tmpStorage.set({dir: TMP_FILES_PB}, {});
    });

  /**
   * get temporary file data from tmpStorage
   * @param {Object} data - target file data
   * @return {Object} - temporary file data
   */
  const getTmpFileData = async data => {
    let store;
    if (data) {
      const {tabId, dataId: target} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      const host = data.host || LABEL;
      store = tmpStorage.get({dir, tabId, host, target});
      store && store.path && (
        store.dataId = target,
        store.timestamp = (new fs.Stats(store.path)).mtime,
        tmpStorage.set({dir, tabId, host, target}, store)
      );
    }
    return store || null;
  };

  /**
   * store temporary file path
   * @param {string} path - temporary file path
   * @param {Object} data - temporary file data
   * @return {void}
   */
  const storeTmpFilePath = async (path, data) => {
    if (path && data) {
      const {tabId} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      const host = data.host || LABEL;
      const target = await getFileNameFromFilePath(path);
      dir && tabId && host && target && (
        data.path = path,
        tmpStorage.set({dir, tabId, host, target}, data)
      );
    }
  };

  /**
   * create temporary file
   * @param {Object} data - temporary file data
   * @param {string} value - value
   * @param {Function} callback - callback
   * @return {Object} - ?Promise.<void>
   */
  const createTmpFile = async (data, value = "", callback = null) => {
    let name, path;
    if (data) {
      const {tabId, host} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      name = data.fileName;
      path = join(DIR_TMP, dir, tabId, host);
    }
    return name && path && createDir(path).then(() =>
      createFile(join(path, name), value, callback, data)
    ) || null;
  };

  /**
   * remove sdk prefs and storage
   * @return {void}
   */
  const removeSdkPrefs = async () => {
    /* remove sdk prefs */
    const {prefs} = simplePrefs;
    delete prefs[`${PREFS}File`];
    delete prefs[`${PREFS}Path`];
    delete prefs[`${PREFS}CmdArgs`];
    delete prefs[`${PREFS}CmdPos`];
    delete prefs[`${PREFS}Shell`];
    delete prefs[`${PREFS}AccessKey`];
    delete prefs[`${PREFS}Hotkey`];
    delete prefs[`${PREFS}ExecEditor`];
    delete prefs[`${PREFS}EnablePB`];
    delete prefs[`${PREFS}Context`];
    delete prefs[`${PREFS}ForceRemove`];
    delete prefs[`${PREFS}Panel`];
    delete simpleStorage.storage.withExEditor;

    /* remove jetpack storage */
    removeDir(join(DIR_PREF, "jetpack", self.id), true).catch(logError);
  };

  exports.main = () => Promise.all([
    webExtension.startup().then(api => {
      /* api */
      const {browser} = api;
      const {runtime} = browser;

      /**
       * port webextension
       * @param {Object} port - runtime.Port
       * @return {void}
       */
      const portWebExtension = async port => {
        /**
         * port message
         * @param {*} msg - message
         * @return {void}
         */
        const portMsg = async msg => {
          msg && port.postMessage(msg);
        };

        /**
         * port file path
         * @param {string} path - file path
         * @return {Object} - ?Promise.<void>
         */
        const portFilePath = async path =>
          path && portMsg({
            [PORT_FILE_PATH]: {path},
          }) || null;

        /**
         * port temporary file path
         * @param {string} path - temporary file path
         * @param {Object} data - temporary file data
         * @return {Object} - ?Promise.<?Promise.<void>>
         */
        const portTmpFilePath = async (path, data) =>
          path && data && storeTmpFilePath(path, data).then(() =>
            portFilePath(path)
          ).catch(logError) || null;

        /**
         * check the file of given path is executable
         * @param {string} path - path
         * @return {Object} - result data
         */
        const checkExecutable = async path => {
          const executable = await isExecutable(path) || false;
          const name = executable &&
                       await getFileNameFromFilePath(path) || "";
          return {
            [RES_EXECUTABLE]: {
              executable, name,
              value: path,
            },
          };
        };

        /**
         * port sync text
         * @param {Object} res - Response
         * @param {Object} data - file data
         * @return {Object} - ?Promise.<void>
         */
        const portSyncText = async (res, data) => {
          let func;
          if (res && data) {
            const {dataId, tabId} = data;
            func = portMsg({
              [SYNC_TEXT]: {
                data, dataId, tabId,
                value: res,
              },
            }).catch(logError);
          }
          return func || null;
        };

        /**
         * get temporary file
         * @param {Object} data - temporary file data
         * @return {Object} - ?Promise.<void>
         */
        const getTmpFile = async data => {
          const path = data && data.path;
          return path && readFile(path, portSyncText, data) || null;
        };

        /**
         * port app manifest
         * @param {Object} res - Response
         * @return {Object} - ?Promise.<void>
         */
        const portAppManifest = async res =>
          res && portMsg({
            [RES_APP_MANIFEST]: {
              value: res,
            },
          }).catch(logError) || null;

        /**
         * get app manifest
         * @param {string} path - app manifest path
         * @return {Object} - ?Promise.<void>
         */
        const getAppManifest = async path =>
          await isFile(path) && readFile(path, portAppManifest) || null;

        /**
         * handle port message
         * @param {*} msg - message
         * @return {Object} - Promise.<Array<*>>
         */
        const handleMsg = async msg => {
          const func = [];
          const items = msg && Object.keys(msg);
          if (items && items.length) {
            for (const item of items) {
              const obj = msg[item];
              switch (item) {
                case SET_VARS:
                  func.push(handleMsg(obj));
                  break;
                case CHECK_EXECUTABLE:
                  func.push(checkExecutable(obj.path).then(portMsg));
                  break;
                case CREATE_TMP_FILE:
                  func.push(createTmpFile(
                    obj.data, obj.value, portTmpFilePath
                  ));
                  break;
                case GET_APP_MANIFEST:
                  func.push(getAppManifest(obj.path));
                  break;
                case GET_FILE_PATH:
                  func.push(convUriToFilePath(obj.uri).then(portFilePath));
                  break;
                case GET_TMP_FILE:
                  func.push(getTmpFileData(obj).then(getTmpFile));
                  break;
                case REMOVE_PB_TMP_FILES:
                  obj && func.push(removePrivateTmpFiles());
                  break;
                case REMOVE_SDK_PREFS:
                  obj && func.push(removeSdkPrefs());
                  break;
                case REMOVE_TAB_STORAGE:
                  func.push(removeTabRelatedStorage(obj));
                  break;
                case FORCE_REMOVE:
                  vars[item] = !!obj;
                  break;
                default:
              }
            }
          }
          return Promise.all(func);
        };

        /* listener */
        port.onMessage.addListener(msg => handleMsg(msg).catch(logError));
      };

      /* listener */
      runtime.onConnect.addListener(port =>
        portWebExtension(port).catch(logError)
      );
    }),
    createDir(DIR_TMP_FILES),
    createDir(DIR_TMP_FILES_PB),
  ]).catch(logError);

  exports.onUnload = () => {
    /* remove temporary files */
    removeDir(DIR_TMP, vars[FORCE_REMOVE]).catch(logError);
  };
}
<commit_msg>Refactor portSyncText<commit_after>/**
 * main.js (hybrid)
 */
"use strict";
{
  /* sdk */
  const {basename, join} = require("sdk/fs/path");
  const {ns} = require("sdk/core/namespace");
  const fs = require("sdk/io/fs");
  const self = require("sdk/self");
  const simplePrefs = require("sdk/simple-prefs");
  const simpleStorage = require("sdk/simple-storage");
  const system = require("sdk/system");
  const urls = require("sdk/url");
  const webExtension = require("sdk/webextension");

  /* constants */
  const LABEL = "withExEditor";
  const PREFS = "WithExEditorPrefs";
  const CHAR = "UTF-8";
  const MASK_BIT = 0o111;
  const PERM_FILE = 0o666;
  const PERM_DIR = 0o777;
  const TMP_FILES = "tmpFiles";
  const TMP_FILES_PB = "tmpFilesPb";
  const DIR_PREF = system.pathFor("PrefD");
  const PREF_LABEL = /^.+\.([^.]+)$/.exec(basename(DIR_PREF));
  const DIR_TMP = join(
    system.pathFor("TmpD"),
    LABEL,
    PREF_LABEL && PREF_LABEL[1] || LABEL
  );
  const DIR_TMP_FILES = join(DIR_TMP, TMP_FILES);
  const DIR_TMP_FILES_PB = join(DIR_TMP, TMP_FILES_PB);

  const CHECK_EXECUTABLE = "checkExecutable";
  const CREATE_TMP_FILE = "createTmpFile";
  const FORCE_REMOVE = "forceRemove";
  const GET_APP_MANIFEST = "getAppManifest";
  const GET_FILE_PATH = "getFilePath";
  const GET_TMP_FILE = "getTmpFile";
  const PORT_FILE_PATH = "portFilePath";
  const REMOVE_PB_TMP_FILES = "removePrivateTmpFiles";
  const REMOVE_SDK_PREFS = "removeSdkPrefs";
  const REMOVE_TAB_STORAGE = "removeTabRelatedStorage";
  const RES_APP_MANIFEST = "resAppManifest";
  const RES_EXECUTABLE = "resExecutable";
  const SET_VARS = "setVars";
  const SYNC_TEXT = "syncText";

  /* variables */
  const vars = {
    [FORCE_REMOVE]: true,
  };

  /**
   * log error
   * @param {!Object} e - Error
   * @return {boolean} - false
   */
  const logError = e => {
    console.error(e);
    return false;
  };

  /**
   * is string
   * @param {*} o - object to check
   * @return {boolean} - result
   */
  const isString = o =>
    o && (typeof o === "string" || o instanceof String) || false;

  /**
   * get file name from native file path
   * @param {string} path - file path
   * @return {string} - file name
   */
  const getFileNameFromFilePath = async path => {
    const name = isString(path) && /^([^.]+)(?:\..+)?$/.exec(basename(path));
    return name && name[1] || LABEL;
  };

  /**
   * convert URI to native file path
   * @param {string} uri - URI
   * @return {?string} - file path
   */
  const convUriToFilePath = async uri =>
    isString(uri) && urls.toFilename(uri) || null;

  /**
   * the file is a file
   * @param {string} path - file path
   * @return {boolean} - result
   */
  const isFile = async path =>
    isString(path) && fs.existsSync(path) && (new fs.Stats(path)).isFile();

  /**
   * the file is executable
   * @param {string} path - file path
   * @param {number} mask - mask bit
   * @return {Object} - Promise.<boolean>
   */
  const isExecutable = async (path, mask = MASK_BIT) =>
    isFile(path).then(bool => bool && !!((new fs.Stats(path)).mode & mask));

  /**
   * remove the directory
   * @param {string} path - directory path
   * @param {boolean} rec - recursive
   * @return {void}
   */
  const removeDir = async (path, rec) => {
    isString(path) && fs.existsSync(path) && fs.rmdirSync(path, !!rec);
  };

  /**
   * create a directory
   * @param {string} path - directory path
   * @param {string|number} mode - permission
   * @return {void}
   */
  const createDir = async (path, mode = PERM_DIR) => {
    isString(path) && !fs.existsSync(path) && fs.mkdirSync(path, mode);
  };

  /**
   * create a file
   * @param {string} path - file path
   * @param {string} value - value to write
   * @param {Function} callback - callback when write completes
   * @param {Object} opt - callback option
   * @param {number|string} mode - file permission
   * @param {string} encoding - file encoding
   * @return {void}
   */
  const createFile = async (path, value = "", callback = null, opt = null,
                            mode = PERM_FILE, encoding = CHAR) => {
    isString(path) && fs.writeFile(path, value, encoding, e => {
      e ?
        logError(e) :
        (fs.chmodSync(path, mode), callback && callback(path, opt));
    });
  };

  /**
   * read a file
   * @param {string} path - file path
   * @param {Function} callback - callback when read completes
   * @param {Object} opt - callback option
   * @param {string} encoding - file encoding
   * @return {void}
   */
  const readFile = async (path, callback = null, opt = null,
                          encoding = CHAR) => {
    isString(path) && fs.readFile(path, encoding, (e, data) => {
      e ?
        logError(e) :
        callback && callback(data, opt);
    });
  };

  /* handle temporary files */
  /* namespace */
  const internal = ns();

  /**
   * temporary files storage class
   */
  class TemporaryStorage {
    /**
     * @private
     */
    constructor() {
      internal(this).storage = {tmpFiles: {}, tmpFilesPb: {}};
    }

    /**
     * get target property value
     * @param {Object} keys - descendant property keys
     * @return {*}
     */
    get(keys = {}) {
      const store = internal(this).storage;
      const {dir, tabId, host, target} = keys;
      return dir && tabId && host && target &&
             store[dir] && store[dir][tabId] && store[dir][tabId][host] &&
               store[dir][tabId][host][target];
    }

    /**
     * set property / value
     * @param {Object} keys - descendant property keys
     * @param {*} val - value
     * @return {void}
     */
    set(keys = {}, val = null) {
      const store = internal(this).storage;
      const {dir, tabId, host, target} = keys;
      dir && (store[dir] = store[dir] || tabId && {} || val) &&
      tabId && (store[dir][tabId] = store[dir][tabId] || host && {} || val) &&
      host && (store[dir][tabId][host] = store[dir][tabId][host] ||
                                         target && {} || val) &&
      target && (store[dir][tabId][host][target] = val);
    }

    /**
     * remove property
     * @param {Object} keys - descendant property keys
     * @return {boolean} - result
     */
    remove(keys = {}) {
      const store = internal(this).storage;
      const {dir, tabId, host, target} = keys;
      let bool;
      if (dir && store[dir]) {
        if (tabId && host && target &&
            store[dir][tabId] && store[dir][tabId][host]) {
          bool = delete store[dir][tabId][host][target];
        } else if (tabId && host && store[dir][tabId]) {
          bool = delete store[dir][tabId][host];
        } else if (tabId) {
          bool = delete store[dir][tabId];
        } else {
          bool = delete store[dir];
        }
      }
      return bool || false;
    }
  }

  /* temporary files storage */
  const tmpStorage = new TemporaryStorage();

  /**
   * remove tmpStorage data on tab close
   * @param {Object} data - closed tab data
   * @return {boolean} - result
   */
  const removeTabRelatedStorage = async data => {
    let bool;
    if (data) {
      const {tabId} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      bool = tabId && tmpStorage.remove({dir, tabId});
    }
    return bool || false;
  };

  /**
   * remove private temporary files
   * @return {Object} - Promise.<void>
   */
  const removePrivateTmpFiles = () =>
    removeDir(DIR_TMP_FILES_PB, vars[FORCE_REMOVE]).then(() =>
      createDir(DIR_TMP_FILES_PB)
    ).then(() => {
      tmpStorage.remove({dir: TMP_FILES_PB});
      tmpStorage.set({dir: TMP_FILES_PB}, {});
    });

  /**
   * get temporary file data from tmpStorage
   * @param {Object} data - target file data
   * @return {Object} - temporary file data
   */
  const getTmpFileData = async data => {
    let store;
    if (data) {
      const {tabId, dataId: target} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      const host = data.host || LABEL;
      store = tmpStorage.get({dir, tabId, host, target});
      store && store.path && (
        store.dataId = target,
        store.timestamp = (new fs.Stats(store.path)).mtime,
        tmpStorage.set({dir, tabId, host, target}, store)
      );
    }
    return store || null;
  };

  /**
   * store temporary file path
   * @param {string} path - temporary file path
   * @param {Object} data - temporary file data
   * @return {void}
   */
  const storeTmpFilePath = async (path, data) => {
    if (path && data) {
      const {tabId} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      const host = data.host || LABEL;
      const target = await getFileNameFromFilePath(path);
      dir && tabId && host && target && (
        data.path = path,
        tmpStorage.set({dir, tabId, host, target}, data)
      );
    }
  };

  /**
   * create temporary file
   * @param {Object} data - temporary file data
   * @param {string} value - value
   * @param {Function} callback - callback
   * @return {Object} - ?Promise.<void>
   */
  const createTmpFile = async (data, value = "", callback = null) => {
    let name, path;
    if (data) {
      const {tabId, host} = data;
      const dir = !!data.incognito && TMP_FILES_PB || TMP_FILES;
      name = data.fileName;
      path = join(DIR_TMP, dir, tabId, host);
    }
    return name && path && createDir(path).then(() =>
      createFile(join(path, name), value, callback, data)
    ) || null;
  };

  /**
   * remove sdk prefs and storage
   * @return {void}
   */
  const removeSdkPrefs = async () => {
    /* remove sdk prefs */
    const {prefs} = simplePrefs;
    delete prefs[`${PREFS}File`];
    delete prefs[`${PREFS}Path`];
    delete prefs[`${PREFS}CmdArgs`];
    delete prefs[`${PREFS}CmdPos`];
    delete prefs[`${PREFS}Shell`];
    delete prefs[`${PREFS}AccessKey`];
    delete prefs[`${PREFS}Hotkey`];
    delete prefs[`${PREFS}ExecEditor`];
    delete prefs[`${PREFS}EnablePB`];
    delete prefs[`${PREFS}Context`];
    delete prefs[`${PREFS}ForceRemove`];
    delete prefs[`${PREFS}Panel`];
    delete simpleStorage.storage.withExEditor;

    /* remove jetpack storage */
    removeDir(join(DIR_PREF, "jetpack", self.id), true).catch(logError);
  };

  exports.main = () => Promise.all([
    webExtension.startup().then(api => {
      /* api */
      const {browser} = api;
      const {runtime} = browser;

      /**
       * port webextension
       * @param {Object} port - runtime.Port
       * @return {void}
       */
      const portWebExtension = async port => {
        /**
         * port message
         * @param {*} msg - message
         * @return {void}
         */
        const portMsg = async msg => {
          msg && port.postMessage(msg);
        };

        /**
         * port file path
         * @param {string} path - file path
         * @return {Object} - ?Promise.<void>
         */
        const portFilePath = async path =>
          path && portMsg({
            [PORT_FILE_PATH]: {path},
          }) || null;

        /**
         * port temporary file path
         * @param {string} path - temporary file path
         * @param {Object} data - temporary file data
         * @return {Object} - ?Promise.<?Promise.<void>>
         */
        const portTmpFilePath = async (path, data) =>
          path && data && storeTmpFilePath(path, data).then(() =>
            portFilePath(path)
          ).catch(logError) || null;

        /**
         * check the file of given path is executable
         * @param {string} path - path
         * @return {Object} - result data
         */
        const checkExecutable = async path => {
          const executable = await isExecutable(path) || false;
          const name = executable &&
                       await getFileNameFromFilePath(path) || "";
          return {
            [RES_EXECUTABLE]: {
              executable, name,
              value: path,
            },
          };
        };

        /**
         * port sync text
         * @param {Object} value - text
         * @param {Object} data - file data
         * @return {Object} - ?Promise.<void>
         */
        const portSyncText = async (value, data) => {
          let msg;
          if (value && data) {
            const {dataId, tabId} = data;
            msg = {
              [SYNC_TEXT]: {data, dataId, tabId, value},
            };
          }
          return msg && portMsg(msg).catch(logError) || null;
        };

        /**
         * get temporary file
         * @param {Object} data - temporary file data
         * @return {Object} - ?Promise.<void>
         */
        const getTmpFile = async data => {
          const path = data && data.path;
          return path && readFile(path, portSyncText, data) || null;
        };

        /**
         * port app manifest
         * @param {Object} res - Response
         * @return {Object} - ?Promise.<void>
         */
        const portAppManifest = async res =>
          res && portMsg({
            [RES_APP_MANIFEST]: {
              value: res,
            },
          }).catch(logError) || null;

        /**
         * get app manifest
         * @param {string} path - app manifest path
         * @return {Object} - ?Promise.<void>
         */
        const getAppManifest = async path =>
          await isFile(path) && readFile(path, portAppManifest) || null;

        /**
         * handle port message
         * @param {*} msg - message
         * @return {Object} - Promise.<Array<*>>
         */
        const handleMsg = async msg => {
          const func = [];
          const items = msg && Object.keys(msg);
          if (items && items.length) {
            for (const item of items) {
              const obj = msg[item];
              switch (item) {
                case SET_VARS:
                  func.push(handleMsg(obj));
                  break;
                case CHECK_EXECUTABLE:
                  func.push(checkExecutable(obj.path).then(portMsg));
                  break;
                case CREATE_TMP_FILE:
                  func.push(createTmpFile(
                    obj.data, obj.value, portTmpFilePath
                  ));
                  break;
                case GET_APP_MANIFEST:
                  func.push(getAppManifest(obj.path));
                  break;
                case GET_FILE_PATH:
                  func.push(convUriToFilePath(obj.uri).then(portFilePath));
                  break;
                case GET_TMP_FILE:
                  func.push(getTmpFileData(obj).then(getTmpFile));
                  break;
                case REMOVE_PB_TMP_FILES:
                  obj && func.push(removePrivateTmpFiles());
                  break;
                case REMOVE_SDK_PREFS:
                  obj && func.push(removeSdkPrefs());
                  break;
                case REMOVE_TAB_STORAGE:
                  func.push(removeTabRelatedStorage(obj));
                  break;
                case FORCE_REMOVE:
                  vars[item] = !!obj;
                  break;
                default:
              }
            }
          }
          return Promise.all(func);
        };

        /* listener */
        port.onMessage.addListener(msg => handleMsg(msg).catch(logError));
      };

      /* listener */
      runtime.onConnect.addListener(port =>
        portWebExtension(port).catch(logError)
      );
    }),
    createDir(DIR_TMP_FILES),
    createDir(DIR_TMP_FILES_PB),
  ]).catch(logError);

  exports.onUnload = () => {
    /* remove temporary files */
    removeDir(DIR_TMP, vars[FORCE_REMOVE]).catch(logError);
  };
}
<|endoftext|>


<commit_before>'use strict'

const childProcess = require('child_process')
const fs = require('fs')
const path = require('path')
const util = require('util')

const promisify = require('promisify-node')
const yaml = require('js-yaml')

promisify(fs)

const logger = require('./logging').logger
const parseArguments = require('./arguments').parseArguments

function main () {
  const pkg = require('../package')
  const defaults = {
    name: pkg.name,
    version: pkg.version,
    description: pkg.description,
    logLevel: 'info'
  }
  const program = parseArguments(defaults, process.argv)
  logger.level = program.logLevel

  logger.info('%s v%s', pkg.name, pkg.version)

  const travisFile = path.join(process.cwd(), '.travis.yml')
  if (!fs.existsSync(travisFile)) {
    logger.error('Travis file not found: %s', travisFile)
    return 1
  }

  const travisConfig = yaml.safeLoad(fs.readFileSync(travisFile))
  if (typeof travisConfig.node_js === 'undefined') {
    logger.error('language field not found in travis configuration')
    return 1
  }

  if (travisConfig.language !== 'node_js') {
    logger.error(
      'Unexpected language in travis configuration: %s', travisConfig.language)
    return 1
  }

  if (typeof travisConfig.node_js === 'undefined') {
    logger.error('node_js field not found in travis configuration')
    return 1
  }
  logger.info('Node versions to use for testing: %s', travisConfig.node_js)

  const outputDir = '.' + pkg.name
  let promise
  if (!fs.existsSync(outputDir)) {
    promise = fs.mkdir(outputDir)
    promise.then(function () {
      logger.debug('Directory created: %s', outputDir)
    })
  } else {
    promise = Promise.resolve()
  }
  return promise
    .then(function () {
      return Promise.all(travisConfig.node_js.map(function (version) {
        return runTests(outputDir, version)
      }))
    })
    .then(function (results) {
      const failures = results.filter(result => result.returnCode !== 0)
      if (failures.length > 0) {
        logger.error(
          'Test execution failed for: %s',
          failures.map(result => result.version)
        )
        return Promise.resolve(1)
      }

      logger.info('Test execution success')
      return Promise.resolve(0)
    })
}

function runTests (outputDir, version) {
  let promise
  const versionDir = path.join(outputDir, String(version))

  function execToPromise (resolve, reject) {
    return function (err, stdout, stderr) {
      logger.debug('[%s] stdout:\n%s', version, stdout)
      logger.debug('[%s] stderr:\n%s', version, stderr)
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    }
  }

  if (!fs.existsSync(versionDir)) {
    const dirPromise = fs.mkdir(versionDir)
    dirPromise.then(function () {
      logger.debug('[%s] Directory created: %s', version, versionDir)
    })
    const clonePromise = dirPromise.then(function () {
      return new Promise(function (resolve, reject) {
        childProcess.exec(
          util.format('git clone . %s', versionDir),
          execToPromise(resolve, reject))
      })
    })
    clonePromise.then(function () {
      logger.debug('[%s] Source code cloned', version)
    })
    promise = clonePromise
  } else {
    const pullPromise = new Promise(function (resolve, reject) {
      childProcess.exec(
        util.format('git pull %s', versionDir),
        execToPromise(resolve, reject))
    })
    pullPromise.then(function () {
      logger.debug('[%s] Source code pulled', version)
    })
    promise = pullPromise
  }
  return promise.then(function () {
    const command = util.format(
      'source %s/nvm.sh && nvm use %s && npm install && npm test',
      process.env.NVM_DIR, version)
    logger.debug('[%s] command: %s', version, command)
    return new Promise(function (resolve, reject) {
      childProcess.exec(
        command,
        {shell: '/bin/bash', cwd: versionDir},
        execToPromise(resolve, reject)
      )
    })
  })
  .then(function () {
    logger.info('[%s] Test case execution success', version)
    return Promise.resolve({version, returnCode: 0})
  })
  .catch(function (err) {
    logger.error('[%s] %s', version, err.message)
    return Promise.resolve({version, returnCode: err.code})
  })
}

module.exports = main
<commit_msg>Move code to run all test to its own function<commit_after>'use strict'

const childProcess = require('child_process')
const fs = require('fs')
const path = require('path')
const util = require('util')

const promisify = require('promisify-node')
const yaml = require('js-yaml')

promisify(fs)

const logger = require('./logging').logger
const parseArguments = require('./arguments').parseArguments
const pkg = require('../package')

function main () {
  const defaults = {
    name: pkg.name,
    version: pkg.version,
    description: pkg.description,
    logLevel: 'info'
  }
  const program = parseArguments(defaults, process.argv)
  logger.level = program.logLevel

  logger.info('%s v%s', pkg.name, pkg.version)

  const travisFile = path.join(process.cwd(), '.travis.yml')
  if (!fs.existsSync(travisFile)) {
    logger.error('Travis file not found: %s', travisFile)
    return 1
  }

  const travisConfig = yaml.safeLoad(fs.readFileSync(travisFile))
  if (typeof travisConfig.node_js === 'undefined') {
    logger.error('language field not found in travis configuration')
    return 1
  }

  if (travisConfig.language !== 'node_js') {
    logger.error(
      'Unexpected language in travis configuration: %s', travisConfig.language)
    return 1
  }

  if (typeof travisConfig.node_js === 'undefined') {
    logger.error('node_js field not found in travis configuration')
    return 1
  }
  logger.info('Node versions to use for testing: %s', travisConfig.node_js)
  return runAllTests(travisConfig.node_js)
}

function runAllTests (versions) {
  const outputDir = '.' + pkg.name
  let promise
  if (!fs.existsSync(outputDir)) {
    promise = fs.mkdir(outputDir)
    promise.then(function () {
      logger.debug('Directory created: %s', outputDir)
    })
  } else {
    promise = Promise.resolve()
  }
  return promise
    .then(function () {
      return Promise.all(versions.map(function (version) {
        return runTests(outputDir, version)
      }))
    })
    .then(function (results) {
      const failures = results.filter(result => result.returnCode !== 0)
      if (failures.length > 0) {
        logger.error(
          'Test execution failed for: %s',
          failures.map(result => result.version)
        )
        return Promise.resolve(1)
      }

      logger.info('Test execution success')
      return Promise.resolve(0)
    })
}

function runTests (outputDir, version) {
  let promise
  const versionDir = path.join(outputDir, String(version))

  function execToPromise (resolve, reject) {
    return function (err, stdout, stderr) {
      logger.debug('[%s] stdout:\n%s', version, stdout)
      logger.debug('[%s] stderr:\n%s', version, stderr)
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    }
  }

  if (!fs.existsSync(versionDir)) {
    const dirPromise = fs.mkdir(versionDir)
    dirPromise.then(function () {
      logger.debug('[%s] Directory created: %s', version, versionDir)
    })
    const clonePromise = dirPromise.then(function () {
      return new Promise(function (resolve, reject) {
        childProcess.exec(
          util.format('git clone . %s', versionDir),
          execToPromise(resolve, reject))
      })
    })
    clonePromise.then(function () {
      logger.debug('[%s] Source code cloned', version)
    })
    promise = clonePromise
  } else {
    const pullPromise = new Promise(function (resolve, reject) {
      childProcess.exec(
        util.format('git pull %s', versionDir),
        execToPromise(resolve, reject))
    })
    pullPromise.then(function () {
      logger.debug('[%s] Source code pulled', version)
    })
    promise = pullPromise
  }
  return promise.then(function () {
    const command = util.format(
      'source %s/nvm.sh && nvm use %s && npm install && npm test',
      process.env.NVM_DIR, version)
    logger.debug('[%s] command: %s', version, command)
    return new Promise(function (resolve, reject) {
      childProcess.exec(
        command,
        {shell: '/bin/bash', cwd: versionDir},
        execToPromise(resolve, reject)
      )
    })
  })
  .then(function () {
    logger.info('[%s] Test case execution success', version)
    return Promise.resolve({version, returnCode: 0})
  })
  .catch(function (err) {
    logger.error('[%s] %s', version, err.message)
    return Promise.resolve({version, returnCode: err.code})
  })
}

module.exports = main
<|endoftext|>


<commit_before>const { app, ipcMain, BrowserWindow, dialog } = require('electron')
const Menubar = require('menubar')
const electron = require('electron')
const path = require('path')
const url = require('url')
const fs = require('fs')

let baseRecipe = null
const allRecipes = 'recipes.json'
const allGroceries = 'grocery-list.json'
const windows = new Set()
const menubar = Menubar({
  width:680,
  height:800,
  icon: './images/kitchen-fork-icon.png'
})
let mainWindow

menubar.on('ready', () => {
  console.log('READY?! OK!');
})

menubar.on('after-create-window', () => {
  menubar.window.loadURL(url.format({
    pathname: path.join(__dirname, '../index.html'),
    protocol: 'file:',
    slashes: true,
    })
  )
})

const createGroceryList = exports.createGroceryList = (file) => {
  let groceryWindow = new BrowserWindow({show: false})
  windows.add(groceryWindow)

  groceryWindow.loadURL(url.format({
    pathname: path.join(__dirname, '../grocery-list.html'),
    protocol: 'file:',
    slashes: true
    })
  )

  groceryWindow.once('ready-to-show', () => {
    if (file) openFile(groceryWindow, file)
    groceryWindow.show()
  })

  return groceryWindow
}

const saveFile = (content) => {
  let fileName = dialog.showSaveDialog(mainWindow, {
    title: 'Save Grocery List',
    defaultPath: app.getPath('documents'),
    filters: [
      { name: 'Grocery List', extensions: ['pdf'] }
    ]
  });

  fs.writeFileSync(fileName, content);
}

const createMainWindow = exports.createMainWindow = (file) => {
  let mainWindow = new BrowserWindow({show:false})
  windows.add(mainWindow)

  mainWindow.loadURL(url.format({
    pathname: path.join(__dirname, '../index.html'),
    protocol: 'file:',
    slashes: true
  })
  )

  mainWindow.once('ready-to-show', () => {
    if (file) openFile(mainWindow, file)
    mainWindow.show()
  })
  return mainWindow
}

const saveGroceryList = (list) => {
  if (!list) { return }
  writeList(readList(), list)
}

const saveRecipe = (data) => {
  if (!data) {return }
  writeRecipe(readRecipe(), data)
}

const readList = () => {
  return JSON.parse(fs.readFileSync('grocery-list.json'))
}

const readRecipe = () => {
  return JSON.parse(fs.readFileSync('recipes.json'))
}

const writeList = (lists, newList) => {
  lists.grocery_list.push(newList)
  fs.writeFileSync('grocery-list.json', JSON.stringify(lists, null, 2))
}

const writeRecipe = (recipes, newRecipe) => {
  recipes.all_recipes.push(newRecipe)
  fs.writeFileSync('recipes.json', JSON.stringify(recipes, null, 2))
}

// Listen for load message from renderer process
ipcMain.on('load', (event, arg) => {
  if (arg !== null) return
  else {
  //  Reply on load message from renderer process
    event.sender.send('load', fs.readFileSync('recipes.json'));
    event.sender.send('load', fs.readFileSync('grocery-list.json'))
  }
})

ipcMain.on('currentRecipe', (event, data) => {
  saveRecipe(data)
})

ipcMain.on('currentItem', (event, data) => {
  saveGroceryList(data)
})

menubar.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

menubar.on('activate', function () {
  if (mainWindow === null) {
    createWindow()
  }
})

exports.saveFile = saveFile
<commit_msg>Changes offset positioning of grocery list window<commit_after>const { app, ipcMain, BrowserWindow, dialog } = require('electron')
const Menubar = require('menubar')
const electron = require('electron')
const path = require('path')
const url = require('url')
const fs = require('fs')

let baseRecipe = null
const allRecipes = 'recipes.json'
const allGroceries = 'grocery-list.json'
const windows = new Set()
const menubar = Menubar({
  width:680,
  height:800,
  icon: './images/kitchen-fork-icon.png'
})
let mainWindow

menubar.on('ready', () => {
  console.log('READY?! OK!');
})

menubar.on('after-create-window', () => {
  menubar.window.loadURL(url.format({
    pathname: path.join(__dirname, '../index.html'),
    protocol: 'file:',
    slashes: true,
    })
  )
})

const createGroceryList = exports.createGroceryList = (file) => {
  let groceryWindow = new BrowserWindow({
    show: false,
    x:0,
    y:0
  })
  windows.add(groceryWindow)

  groceryWindow.loadURL(url.format({
    pathname: path.join(__dirname, '../grocery-list.html'),
    protocol: 'file:',
    slashes: true
    })
  )

  groceryWindow.once('ready-to-show', () => {
    if (file) openFile(groceryWindow, file)
    groceryWindow.show()
  })

  return groceryWindow
}

const saveFile = (content) => {
  let fileName = dialog.showSaveDialog(mainWindow, {
    title: 'Save Grocery List',
    defaultPath: app.getPath('documents'),
    filters: [
      { name: 'Grocery List', extensions: ['pdf'] }
    ]
  });

  fs.writeFileSync(fileName, content);
}

const createMainWindow = exports.createMainWindow = (file) => {
  let mainWindow = new BrowserWindow({ show:false })
  windows.add(mainWindow)

  mainWindow.loadURL(url.format({
    pathname: path.join(__dirname, '../index.html'),
    protocol: 'file:',
    slashes: true
  })
  )

  mainWindow.once('ready-to-show', () => {
    if (file) openFile(mainWindow, file)
    mainWindow.show()
  })
  return mainWindow
}

const saveGroceryList = (list) => {
  if (!list) { return }
  writeList(readList(), list)
}

const saveRecipe = (data) => {
  if (!data) {return }
  writeRecipe(readRecipe(), data)
}

const readList = () => {
  return JSON.parse(fs.readFileSync('grocery-list.json'))
}

const readRecipe = () => {
  return JSON.parse(fs.readFileSync('recipes.json'))
}

const writeList = (lists, newList) => {
  lists.grocery_list.push(newList)
  fs.writeFileSync('grocery-list.json', JSON.stringify(lists, null, 2))
}

const writeRecipe = (recipes, newRecipe) => {
  recipes.all_recipes.push(newRecipe)
  fs.writeFileSync('recipes.json', JSON.stringify(recipes, null, 2))
}

// Listen for load message from renderer process
ipcMain.on('load', (event, arg) => {
  if (arg !== null) return
  else {
  //  Reply on load message from renderer process
    event.sender.send('load', fs.readFileSync('recipes.json'));
    event.sender.send('load', fs.readFileSync('grocery-list.json'))
  }
})

ipcMain.on('currentRecipe', (event, data) => {
  saveRecipe(data)
})

ipcMain.on('currentItem', (event, data) => {
  saveGroceryList(data)
})

menubar.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

menubar.on('activate', function () {
  if (mainWindow === null) {
    createWindow()
  }
})

exports.saveFile = saveFile
<|endoftext|>