<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Utility functions for handling procedures.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * @name Blockly.Procedures
 * @namespace
 **/
goog.provide('Blockly.Procedures');

goog.require('Blockly.Blocks');
goog.require('Blockly.constants');
goog.require('Blockly.Field');
goog.require('Blockly.Names');
goog.require('Blockly.Workspace');

/**
 * Constant to separate procedure names from variables and generated functions
 * when running generators.
 * @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME
 */
Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME;

/**
 * Find all user-created procedure definitions in a workspace.
 * @param {!Blockly.Workspace} root Root workspace.
 * @return {!Array.<!Array.<!Array>>} Pair of arrays, the
 *     first contains procedures without return variables, the second with.
 *     Each procedure is defined by a three-element list of name, parameter
 *     list, and return value boolean.
 */
Blockly.Procedures.allProcedures = function(root) {
  var blocks = root.getAllBlocks();
  var proceduresReturn = [];
  var proceduresNoReturn = [];
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i].getProcedureDef) {
      var tuple = blocks[i].getProcedureDef();
      if (tuple) {
        if (tuple[2]) {
          proceduresReturn.push(tuple);
        } else {
          proceduresNoReturn.push(tuple);
        }
      }
    }
  }
  proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_);
  proceduresReturn.sort(Blockly.Procedures.procTupleComparator_);
  return [proceduresNoReturn, proceduresReturn];
};

/**
 * Find all user-created procedure definition mutations in a workspace.
 * @param {!Blockly.Workspace} root Root workspace.
 * @return {!Array.<Element>} Array of mutation xml elements.
 */
Blockly.Procedures.allProcedureMutations = function(root) {
  var blocks = root.getAllBlocks();
  var mutations = [];
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i].type === 'procedures_prototype') {
      var mutation = blocks[i].mutationToDom();
      if (mutation) {
        mutations.push(mutation);
      }
    }
  }
  return mutations;
};

/**
 * Sorts an array of procedure definition mutations alphabetically.
 * (Does not mutate the given array.)
 * @param {!Array.<Element>} mutations Array of mutation xml elements.
 * @return {!Array.<Element>} Sorted array of mutation xml elements.
 * @Private
 */
Blockly.Procedures.sortProcedureMutations_ = function(mutations) {
  var newMutations = mutations.slice();

  newMutations.sort(function(a, b) {
    var procCodeA = a.getAttribute('proccode');
    var procCodeB = b.getAttribute('proccode');

    if (procCodeA < procCodeB) {
      return -1;
    } else if (procCodeA > procCodeB) {
      return 1;
    } else {
      return 0;
    }
  });

  return newMutations;
};

/**
 * Comparison function for case-insensitive sorting of the first element of
 * a tuple.
 * @param {!Array} ta First tuple.
 * @param {!Array} tb Second tuple.
 * @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
 * @private
 */
Blockly.Procedures.procTupleComparator_ = function(ta, tb) {
  return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase());
};

/**
 * Ensure two identically-named procedures don't exist.
 * @param {string} name Proposed procedure name.
 * @param {!Blockly.Block} block Block to disambiguate.
 * @return {string} Non-colliding name.
 */
Blockly.Procedures.findLegalName = function(name, block) {
  if (block.isInFlyout) {
    // Flyouts can have multiple procedures called 'do something'.
    return name;
  }
  while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) {
    // Collision with another procedure.
    var r = name.match(/^(.*?)(\d+)$/);
    if (!r) {
      name += '2';
    } else {
      name = r[1] + (parseInt(r[2], 10) + 1);
    }
  }
  return name;
};

/**
 * Does this procedure have a legal name?  Illegal names include names of
 * procedures already defined.
 * @param {string} name The questionable name.
 * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
 * @param {Blockly.Block=} opt_exclude Optional block to exclude from
 *     comparisons (one doesn't want to collide with oneself).
 * @return {boolean} True if the name is legal.
 * @private
 */
Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
  return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude);
};

/**
 * Return if the given name is already a procedure name.
 * @param {string} name The questionable name.
 * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
 * @param {Blockly.Block=} opt_exclude Optional block to exclude from
 *     comparisons (one doesn't want to collide with oneself).
 * @return {boolean} True if the name is used, otherwise return false.
 */
Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) {
  var blocks = workspace.getAllBlocks();
  // Iterate through every block and check the name.
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i] == opt_exclude) {
      continue;
    }
    if (blocks[i].getProcedureDef) {
      var procName = blocks[i].getProcedureDef();
      if (Blockly.Names.equals(procName[0], name)) {
        return false;
      }
    }
  }
  return true;
};

/**
 * Rename a procedure.  Called by the editable field.
 * @param {string} name The proposed new name.
 * @return {string} The accepted name.
 * @this {Blockly.Field}
 */
Blockly.Procedures.rename = function(name) {
  // Strip leading and trailing whitespace.  Beyond this, all names are legal.
  name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');

  // Ensure two identically-named procedures don't exist.
  var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_);
  var oldName = this.text_;
  if (oldName != name && oldName != legalName) {
    // Rename any callers.
    var blocks = this.sourceBlock_.workspace.getAllBlocks();
    for (var i = 0; i < blocks.length; i++) {
      if (blocks[i].renameProcedure) {
        blocks[i].renameProcedure(oldName, legalName);
      }
    }
  }
  return legalName;
};

/**
 * Construct the blocks required by the flyout for the procedure category.
 * @param {!Blockly.Workspace} workspace The workspace contianing procedures.
 * @return {!Array.<!Element>} Array of XML block elements.
 */
Blockly.Procedures.flyoutCategory = function(workspace) {
  var xmlList = [];

  Blockly.Procedures.addCreateButton_(workspace, xmlList);

  // Create call blocks for each procedure defined in the workspace
  var mutations = Blockly.Procedures.allProcedureMutations(workspace);
  mutations = Blockly.Procedures.sortProcedureMutations_(mutations);
  for (var i = 0; i < mutations.length; i++) {
    var mutation = mutations[i];
    // <block type="procedures_call">
    //   <mutation ...></mutation>
    // </block>
    var block = goog.dom.createDom('block');
    block.setAttribute('type', 'procedures_call');
    block.setAttribute('gap', 16);
    block.appendChild(mutation);
    xmlList.push(block);
  }
  return xmlList;
};

/**
 * Create the "Make a Block..." button.
 * @param {!Blockly.Workspace} workspace The workspace contianing procedures.
 * @param {!Array.<!Element>} xmlList Array of XML block elements to add to.
 * @private
 */
Blockly.Procedures.addCreateButton_ = function(workspace, xmlList) {
  var button = goog.dom.createDom('button');
  var msg = Blockly.Msg.NEW_PROCEDURE;
  var callbackKey = 'CREATE_PROCEDURE';
  var callback = function() {
    Blockly.Procedures.createProcedureDefCallback_();
  };
  button.setAttribute('text', msg);
  button.setAttribute('callbackKey', callbackKey);
  workspace.registerButtonCallback(callbackKey, callback);
  xmlList.push(button);
};

/**
 * Find all callers of a named procedure.
 * @param {string} name Name of procedure (procCode in scratch-blocks).
 * @param {!Blockly.Workspace} ws The workspace to find callers in.
 * @param {!Blockly.Block} definitionRoot The root of the stack where the
 *     procedure is defined.
 * @param {boolean} allowRecursive True if the search should include recursive
 *     procedure calls.  False if the search should ignore the stack starting
 *     with definitionRoot.
 * @return {!Array.<!Blockly.Block>} Array of caller blocks.
 * @package
 */
Blockly.Procedures.getCallers = function(name, ws, definitionRoot,
    allowRecursive) {
  var allBlocks = [];
  var topBlocks = ws.getTopBlocks();

  // Start by deciding which stacks to investigate.
  for (var i = 0; i < topBlocks.length; i++) {
    var block = topBlocks[i];
    if (block.id == definitionRoot.id && !allowRecursive) {
      continue;
    }
    allBlocks.push.apply(allBlocks, block.getDescendants());
  }

  var callers = [];
  for (var i = 0; i < allBlocks.length; i++) {
    var block = allBlocks[i];
    if (block.type == Blockly.PROCEDURES_CALL_BLOCK_TYPE ) {
      var procCode = block.getProcCode();
      if (procCode && procCode == name) {
        callers.push(block);
      }
    }
  }
  return callers;
};

/**
 * When a procedure definition changes its parameters, find and edit all its
 * callers.
 * @param {!Blockly.Block} defBlock Procedure definition block.
 */
Blockly.Procedures.mutateCallers = function(defBlock) {
  // TODO(#1143) Update this for scratch procedures.
  var oldRecordUndo = Blockly.Events.recordUndo;
  var name = defBlock.getProcedureDef()[0];
  var xmlElement = defBlock.mutationToDom(true);
  var callers = Blockly.Procedures.getCallers(name, defBlock.workspace);
  for (var i = 0, caller; caller = callers[i]; i++) {
    var oldMutationDom = caller.mutationToDom();
    var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
    caller.domToMutation(xmlElement);
    var newMutationDom = caller.mutationToDom();
    var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
    if (oldMutation != newMutation) {
      // Fire a mutation on every caller block.  But don't record this as an
      // undo action since it is deterministically tied to the procedure's
      // definition mutation.
      Blockly.Events.recordUndo = false;
      Blockly.Events.fire(new Blockly.Events.BlockChange(
          caller, 'mutation', null, oldMutation, newMutation));
      Blockly.Events.recordUndo = oldRecordUndo;
    }
  }
};

/**
 * Find the definition block for the named procedure.
 * @param {string} name Name of procedure.
 * @param {!Blockly.Workspace} workspace The workspace to search.
 * @return {Blockly.Block} The procedure definition block, or null not found.
 */
Blockly.Procedures.getDefinition = function(name, workspace) {
  // Assume that a procedure definition is a top block.
  var blocks = workspace.getTopBlocks(false);
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i].getProcedureDef) {
      var tuple = blocks[i].getProcedureDef();
      if (tuple && Blockly.Names.equals(tuple[0], name)) {
        return blocks[i];
      }
    }
  }
  return null;
};

/**
 * Callback to create a new procedure custom command block.
 * TODO(#1299): Implement.
 * @private
 */
Blockly.Procedures.createProcedureDefCallback_ = function() {
  alert('TODO(#1299) Implement procedure creation callback.');
};

/**
 * Callback to open the modal for editing custom procedures.
 * TODO(#603): Implement.
 * @param {!Blockly.Block} block The block that was right-clicked.
 * @private
 */
Blockly.Procedures.editProcedureCallback_ = function(block) {
  if (block.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) {
    var input = block.getInput('custom_block');
    if (!input) {
      alert('Bad input'); // TODO: Decide what to do about this.
      return;
    }
    var conn = input.connection;
    if (!conn) {
      alert('Bad connection'); // TODO: Decide what to do about this.
      return;
    }
    var innerBlock = conn.targetBlock();
    if (!innerBlock ||
        !innerBlock.type == Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE) {
      alert('Bad inner block'); // TODO: Decide what to do about this.
      return;
    }
    block = innerBlock;
  }
  alert('TODO(#603): implement function editing (procCode was "' +
      block.procCode_ + '")');
};

/**
 * Make a context menu option for editing a custom procedure.
 * This appears in the context menu for procedure definitions and procedure
 * calls.
 * @param {!Blockly.BlockSvg} block The block where the right-click originated.
 * @return {!Object} A menu option, containing text, enabled, and a callback.
 * @package
 */
Blockly.Procedures.makeEditOption = function(block) {
  var editOption = {
    enabled: true,
    text: Blockly.Msg.EDIT_PROCEDURE,
    callback: function() {
      Blockly.Procedures.editProcedureCallback_(block);
    }
  };
  return editOption;
};

/**
 * Callback to show the procedure definition corresponding to a custom command
 * block.
 * TODO(#1136): Implement.
 * @param {!Blockly.Block} block The block that was right-clicked.
 * @private
 */
Blockly.Procedures.showProcedureDefCallback_ = function(block) {
  alert('TODO(#1136): implement showing procedure definition (procCode was "' +
      block.procCode_ + '")');
};

/**
 * Make a context menu option for showing the definition for a custom procedure,
 * based on a right-click on a custom command block.
 * @param {!Blockly.BlockSvg} block The block where the right-click originated.
 * @return {!Object} A menu option, containing text, enabled, and a callback.
 * @package
 */
Blockly.Procedures.makeShowDefinitionOption = function(block) {
  var option = {
    enabled: true,
    text: Blockly.Msg.SHOW_PROCEDURE_DEFINITION,
    callback: function() {
      Blockly.Procedures.showProcedureDefCallback_(block);
    }
  };
  return option;
};

/**
 * Callback to try to delete a custom block definitions.
 * @param {string} procCode The identifier of the procedure to delete.
 * @param {!Blockly.Block} definitionRoot The root block of the stack that
 *     defines the custom procedure.
 * @return {boolean} True if the custom procedure was deleted, false otherwise.
 * @package
 */
Blockly.Procedures.deleteProcedureDefCallback = function(procCode,
    definitionRoot) {
  var callers = Blockly.Procedures.getCallers(procCode,
      definitionRoot.workspace, definitionRoot, false /* allowRecursive */);
  if (callers.length > 0) {
    return false;
  }
  // Delete the whole stack.
  Blockly.Events.setGroup(true);
  definitionRoot.dispose();
  Blockly.Events.setGroup(false);
  return true;
};
<commit_msg>Change '@Private' to '@private' in jsdoc for sortProcedureMutations<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Utility functions for handling procedures.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * @name Blockly.Procedures
 * @namespace
 **/
goog.provide('Blockly.Procedures');

goog.require('Blockly.Blocks');
goog.require('Blockly.constants');
goog.require('Blockly.Field');
goog.require('Blockly.Names');
goog.require('Blockly.Workspace');

/**
 * Constant to separate procedure names from variables and generated functions
 * when running generators.
 * @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME
 */
Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME;

/**
 * Find all user-created procedure definitions in a workspace.
 * @param {!Blockly.Workspace} root Root workspace.
 * @return {!Array.<!Array.<!Array>>} Pair of arrays, the
 *     first contains procedures without return variables, the second with.
 *     Each procedure is defined by a three-element list of name, parameter
 *     list, and return value boolean.
 */
Blockly.Procedures.allProcedures = function(root) {
  var blocks = root.getAllBlocks();
  var proceduresReturn = [];
  var proceduresNoReturn = [];
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i].getProcedureDef) {
      var tuple = blocks[i].getProcedureDef();
      if (tuple) {
        if (tuple[2]) {
          proceduresReturn.push(tuple);
        } else {
          proceduresNoReturn.push(tuple);
        }
      }
    }
  }
  proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_);
  proceduresReturn.sort(Blockly.Procedures.procTupleComparator_);
  return [proceduresNoReturn, proceduresReturn];
};

/**
 * Find all user-created procedure definition mutations in a workspace.
 * @param {!Blockly.Workspace} root Root workspace.
 * @return {!Array.<Element>} Array of mutation xml elements.
 */
Blockly.Procedures.allProcedureMutations = function(root) {
  var blocks = root.getAllBlocks();
  var mutations = [];
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i].type === 'procedures_prototype') {
      var mutation = blocks[i].mutationToDom();
      if (mutation) {
        mutations.push(mutation);
      }
    }
  }
  return mutations;
};

/**
 * Sorts an array of procedure definition mutations alphabetically.
 * (Does not mutate the given array.)
 * @param {!Array.<Element>} mutations Array of mutation xml elements.
 * @return {!Array.<Element>} Sorted array of mutation xml elements.
 * @private
 */
Blockly.Procedures.sortProcedureMutations_ = function(mutations) {
  var newMutations = mutations.slice();

  newMutations.sort(function(a, b) {
    var procCodeA = a.getAttribute('proccode');
    var procCodeB = b.getAttribute('proccode');

    if (procCodeA < procCodeB) {
      return -1;
    } else if (procCodeA > procCodeB) {
      return 1;
    } else {
      return 0;
    }
  });

  return newMutations;
};

/**
 * Comparison function for case-insensitive sorting of the first element of
 * a tuple.
 * @param {!Array} ta First tuple.
 * @param {!Array} tb Second tuple.
 * @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
 * @private
 */
Blockly.Procedures.procTupleComparator_ = function(ta, tb) {
  return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase());
};

/**
 * Ensure two identically-named procedures don't exist.
 * @param {string} name Proposed procedure name.
 * @param {!Blockly.Block} block Block to disambiguate.
 * @return {string} Non-colliding name.
 */
Blockly.Procedures.findLegalName = function(name, block) {
  if (block.isInFlyout) {
    // Flyouts can have multiple procedures called 'do something'.
    return name;
  }
  while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) {
    // Collision with another procedure.
    var r = name.match(/^(.*?)(\d+)$/);
    if (!r) {
      name += '2';
    } else {
      name = r[1] + (parseInt(r[2], 10) + 1);
    }
  }
  return name;
};

/**
 * Does this procedure have a legal name?  Illegal names include names of
 * procedures already defined.
 * @param {string} name The questionable name.
 * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
 * @param {Blockly.Block=} opt_exclude Optional block to exclude from
 *     comparisons (one doesn't want to collide with oneself).
 * @return {boolean} True if the name is legal.
 * @private
 */
Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
  return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude);
};

/**
 * Return if the given name is already a procedure name.
 * @param {string} name The questionable name.
 * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
 * @param {Blockly.Block=} opt_exclude Optional block to exclude from
 *     comparisons (one doesn't want to collide with oneself).
 * @return {boolean} True if the name is used, otherwise return false.
 */
Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) {
  var blocks = workspace.getAllBlocks();
  // Iterate through every block and check the name.
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i] == opt_exclude) {
      continue;
    }
    if (blocks[i].getProcedureDef) {
      var procName = blocks[i].getProcedureDef();
      if (Blockly.Names.equals(procName[0], name)) {
        return false;
      }
    }
  }
  return true;
};

/**
 * Rename a procedure.  Called by the editable field.
 * @param {string} name The proposed new name.
 * @return {string} The accepted name.
 * @this {Blockly.Field}
 */
Blockly.Procedures.rename = function(name) {
  // Strip leading and trailing whitespace.  Beyond this, all names are legal.
  name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');

  // Ensure two identically-named procedures don't exist.
  var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_);
  var oldName = this.text_;
  if (oldName != name && oldName != legalName) {
    // Rename any callers.
    var blocks = this.sourceBlock_.workspace.getAllBlocks();
    for (var i = 0; i < blocks.length; i++) {
      if (blocks[i].renameProcedure) {
        blocks[i].renameProcedure(oldName, legalName);
      }
    }
  }
  return legalName;
};

/**
 * Construct the blocks required by the flyout for the procedure category.
 * @param {!Blockly.Workspace} workspace The workspace contianing procedures.
 * @return {!Array.<!Element>} Array of XML block elements.
 */
Blockly.Procedures.flyoutCategory = function(workspace) {
  var xmlList = [];

  Blockly.Procedures.addCreateButton_(workspace, xmlList);

  // Create call blocks for each procedure defined in the workspace
  var mutations = Blockly.Procedures.allProcedureMutations(workspace);
  mutations = Blockly.Procedures.sortProcedureMutations_(mutations);
  for (var i = 0; i < mutations.length; i++) {
    var mutation = mutations[i];
    // <block type="procedures_call">
    //   <mutation ...></mutation>
    // </block>
    var block = goog.dom.createDom('block');
    block.setAttribute('type', 'procedures_call');
    block.setAttribute('gap', 16);
    block.appendChild(mutation);
    xmlList.push(block);
  }
  return xmlList;
};

/**
 * Create the "Make a Block..." button.
 * @param {!Blockly.Workspace} workspace The workspace contianing procedures.
 * @param {!Array.<!Element>} xmlList Array of XML block elements to add to.
 * @private
 */
Blockly.Procedures.addCreateButton_ = function(workspace, xmlList) {
  var button = goog.dom.createDom('button');
  var msg = Blockly.Msg.NEW_PROCEDURE;
  var callbackKey = 'CREATE_PROCEDURE';
  var callback = function() {
    Blockly.Procedures.createProcedureDefCallback_();
  };
  button.setAttribute('text', msg);
  button.setAttribute('callbackKey', callbackKey);
  workspace.registerButtonCallback(callbackKey, callback);
  xmlList.push(button);
};

/**
 * Find all callers of a named procedure.
 * @param {string} name Name of procedure (procCode in scratch-blocks).
 * @param {!Blockly.Workspace} ws The workspace to find callers in.
 * @param {!Blockly.Block} definitionRoot The root of the stack where the
 *     procedure is defined.
 * @param {boolean} allowRecursive True if the search should include recursive
 *     procedure calls.  False if the search should ignore the stack starting
 *     with definitionRoot.
 * @return {!Array.<!Blockly.Block>} Array of caller blocks.
 * @package
 */
Blockly.Procedures.getCallers = function(name, ws, definitionRoot,
    allowRecursive) {
  var allBlocks = [];
  var topBlocks = ws.getTopBlocks();

  // Start by deciding which stacks to investigate.
  for (var i = 0; i < topBlocks.length; i++) {
    var block = topBlocks[i];
    if (block.id == definitionRoot.id && !allowRecursive) {
      continue;
    }
    allBlocks.push.apply(allBlocks, block.getDescendants());
  }

  var callers = [];
  for (var i = 0; i < allBlocks.length; i++) {
    var block = allBlocks[i];
    if (block.type == Blockly.PROCEDURES_CALL_BLOCK_TYPE ) {
      var procCode = block.getProcCode();
      if (procCode && procCode == name) {
        callers.push(block);
      }
    }
  }
  return callers;
};

/**
 * When a procedure definition changes its parameters, find and edit all its
 * callers.
 * @param {!Blockly.Block} defBlock Procedure definition block.
 */
Blockly.Procedures.mutateCallers = function(defBlock) {
  // TODO(#1143) Update this for scratch procedures.
  var oldRecordUndo = Blockly.Events.recordUndo;
  var name = defBlock.getProcedureDef()[0];
  var xmlElement = defBlock.mutationToDom(true);
  var callers = Blockly.Procedures.getCallers(name, defBlock.workspace);
  for (var i = 0, caller; caller = callers[i]; i++) {
    var oldMutationDom = caller.mutationToDom();
    var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
    caller.domToMutation(xmlElement);
    var newMutationDom = caller.mutationToDom();
    var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
    if (oldMutation != newMutation) {
      // Fire a mutation on every caller block.  But don't record this as an
      // undo action since it is deterministically tied to the procedure's
      // definition mutation.
      Blockly.Events.recordUndo = false;
      Blockly.Events.fire(new Blockly.Events.BlockChange(
          caller, 'mutation', null, oldMutation, newMutation));
      Blockly.Events.recordUndo = oldRecordUndo;
    }
  }
};

/**
 * Find the definition block for the named procedure.
 * @param {string} name Name of procedure.
 * @param {!Blockly.Workspace} workspace The workspace to search.
 * @return {Blockly.Block} The procedure definition block, or null not found.
 */
Blockly.Procedures.getDefinition = function(name, workspace) {
  // Assume that a procedure definition is a top block.
  var blocks = workspace.getTopBlocks(false);
  for (var i = 0; i < blocks.length; i++) {
    if (blocks[i].getProcedureDef) {
      var tuple = blocks[i].getProcedureDef();
      if (tuple && Blockly.Names.equals(tuple[0], name)) {
        return blocks[i];
      }
    }
  }
  return null;
};

/**
 * Callback to create a new procedure custom command block.
 * TODO(#1299): Implement.
 * @private
 */
Blockly.Procedures.createProcedureDefCallback_ = function() {
  alert('TODO(#1299) Implement procedure creation callback.');
};

/**
 * Callback to open the modal for editing custom procedures.
 * TODO(#603): Implement.
 * @param {!Blockly.Block} block The block that was right-clicked.
 * @private
 */
Blockly.Procedures.editProcedureCallback_ = function(block) {
  if (block.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) {
    var input = block.getInput('custom_block');
    if (!input) {
      alert('Bad input'); // TODO: Decide what to do about this.
      return;
    }
    var conn = input.connection;
    if (!conn) {
      alert('Bad connection'); // TODO: Decide what to do about this.
      return;
    }
    var innerBlock = conn.targetBlock();
    if (!innerBlock ||
        !innerBlock.type == Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE) {
      alert('Bad inner block'); // TODO: Decide what to do about this.
      return;
    }
    block = innerBlock;
  }
  alert('TODO(#603): implement function editing (procCode was "' +
      block.procCode_ + '")');
};

/**
 * Make a context menu option for editing a custom procedure.
 * This appears in the context menu for procedure definitions and procedure
 * calls.
 * @param {!Blockly.BlockSvg} block The block where the right-click originated.
 * @return {!Object} A menu option, containing text, enabled, and a callback.
 * @package
 */
Blockly.Procedures.makeEditOption = function(block) {
  var editOption = {
    enabled: true,
    text: Blockly.Msg.EDIT_PROCEDURE,
    callback: function() {
      Blockly.Procedures.editProcedureCallback_(block);
    }
  };
  return editOption;
};

/**
 * Callback to show the procedure definition corresponding to a custom command
 * block.
 * TODO(#1136): Implement.
 * @param {!Blockly.Block} block The block that was right-clicked.
 * @private
 */
Blockly.Procedures.showProcedureDefCallback_ = function(block) {
  alert('TODO(#1136): implement showing procedure definition (procCode was "' +
      block.procCode_ + '")');
};

/**
 * Make a context menu option for showing the definition for a custom procedure,
 * based on a right-click on a custom command block.
 * @param {!Blockly.BlockSvg} block The block where the right-click originated.
 * @return {!Object} A menu option, containing text, enabled, and a callback.
 * @package
 */
Blockly.Procedures.makeShowDefinitionOption = function(block) {
  var option = {
    enabled: true,
    text: Blockly.Msg.SHOW_PROCEDURE_DEFINITION,
    callback: function() {
      Blockly.Procedures.showProcedureDefCallback_(block);
    }
  };
  return option;
};

/**
 * Callback to try to delete a custom block definitions.
 * @param {string} procCode The identifier of the procedure to delete.
 * @param {!Blockly.Block} definitionRoot The root block of the stack that
 *     defines the custom procedure.
 * @return {boolean} True if the custom procedure was deleted, false otherwise.
 * @package
 */
Blockly.Procedures.deleteProcedureDefCallback = function(procCode,
    definitionRoot) {
  var callers = Blockly.Procedures.getCallers(procCode,
      definitionRoot.workspace, definitionRoot, false /* allowRecursive */);
  if (callers.length > 0) {
    return false;
  }
  // Delete the whole stack.
  Blockly.Events.setGroup(true);
  definitionRoot.dispose();
  Blockly.Events.setGroup(false);
  return true;
};
<|endoftext|>


<commit_before>
var Transform = require('./transform.js');

module.exports = function Graphic(options)
{
    'use strict';

    if (!(this instanceof Graphic))
    {
        return new Graphic(options);
    }

    var me = this;
    var renderContext;

    var _imageData;
    var imageData8ClampedView;
    var imageData32View;

    var renderedX;
    var renderedY;

    var dummyFunction = function() {}

    me.x = 0;
    me.y = 0;

    me.onRollOver;
    me.onRollOut;
    me.onClick;

    var update = dummyFunction;

    function init()
    {
        if (options)
        {
            if (options.imageData)
            {
                me.setImageData(options.imageData);
            }

            me.x = options.x || 0;
            me.y = options.y || 0;

            me.onRollOver = options.onRollOver;
            me.onRollOut = options.onRollOut;
            me.onClick = options.onClick;

            update = options.update || dummyFunction;
        }
    }

    me.getImageData = function()
    {
        return _imageData;
    }

    me.setImageData = function(imageData)
    {
        _imageData = imageData;
        imageData8ClampedView = _imageData.data;
        imageData32View = new Uint32Array(imageData8ClampedView.buffer);
    }

    me.setRenderContext = function(context)
    {
        renderContext = context;
    };

    me.render = function()
    {
        saveRenderedPosition();

        var existingImageData = renderContext.getImageData(me.x, me.y, _imageData.width, _imageData.height);
        var transform = new Transform(existingImageData, renderContext);

        transform.do(Transform.WeightedAlphaBlend, {imageData2:_imageData});
        renderContext.putImageData(transform.getImageData(), me.x, me.y);
    }

    me.clear = function()
    {
        renderContext.clearRect(renderedX-1, renderedY-1, _imageData.width+2, _imageData.height+2);
    }

    me.update = function()
    {
        update.call(this);
    }

    me.globalToLocal = function(x, y)
    {
        return {
            x: x - me.x,
            y: y - me.y
        }
    }

    me.localToGlobal = function(x, y)
    {
        return {
            x: me.x + x,
            y: me.y + y
        }
    }

    me.hasGlobalPixelAt = function(x, y)
    {
        var result = false;

        if (isGlobalPositionWithinBoundaries(x, y))
        {
            var distanceFromLeft = x - me.x;
            var distanceFromTop = y - me.y;
            var pixel32 = getPixel32At(distanceFromLeft, distanceFromTop);

            if (pixel32 !== 0)
            {
                result = true;
            }
        }

        return result;
    }

    function isGlobalPositionWithinBoundaries(x, y)
    {
        //var distanceFromLeft = x - me.x;
        //var distanceFromTop = y - me.y;
        //var distanceFromRight = x - (me.x + _imageData.width);
        //var distanceFromBottom = y - (me.y + _imageData.height);
        //return (distanceFromLeft >= 0 && distanceFromRight <= 0
        //    && distanceFromTop >= 0 && distanceFromBottom <= 0);

        // the below statement implements the same functionality as above
        return ((x - me.x) >= 0
                && (y - me.y) >= 0
                && (x - (me.x + _imageData.width)) <= 0
                && (y - (me.y + _imageData.height)) <= 0);
    }

    function saveRenderedPosition()
    {
        renderedX = me.x;
        renderedY = me.y;
    }

    function getPixel32At(x, y)
    {
        return imageData32View[y * _imageData.width + x];
    }

    init();

    return this;
}
<commit_msg>Reduce clearing area size<commit_after>
var Transform = require('./transform.js');

module.exports = function Graphic(options)
{
    'use strict';

    if (!(this instanceof Graphic))
    {
        return new Graphic(options);
    }

    var me = this;
    var renderContext;

    var _imageData;
    var imageData8ClampedView;
    var imageData32View;

    var renderedX;
    var renderedY;

    var dummyFunction = function() {}

    me.x = 0;
    me.y = 0;

    me.onRollOver;
    me.onRollOut;
    me.onClick;

    var update = dummyFunction;

    function init()
    {
        if (options)
        {
            if (options.imageData)
            {
                me.setImageData(options.imageData);
            }

            me.x = options.x || 0;
            me.y = options.y || 0;

            me.onRollOver = options.onRollOver;
            me.onRollOut = options.onRollOut;
            me.onClick = options.onClick;

            update = options.update || dummyFunction;
        }
    }

    me.getImageData = function()
    {
        return _imageData;
    }

    me.setImageData = function(imageData)
    {
        _imageData = imageData;
        imageData8ClampedView = _imageData.data;
        imageData32View = new Uint32Array(imageData8ClampedView.buffer);
    }

    me.setRenderContext = function(context)
    {
        renderContext = context;
    };

    me.render = function()
    {
        saveRenderedPosition();

        var existingImageData = renderContext.getImageData(me.x, me.y, _imageData.width, _imageData.height);
        var transform = new Transform(existingImageData, renderContext);

        transform.do(Transform.WeightedAlphaBlend, {imageData2:_imageData});
        renderContext.putImageData(transform.getImageData(), me.x, me.y);
    }

    me.clear = function()
    {
        renderContext.clearRect(renderedX-1, renderedY-1, _imageData.width+1, _imageData.height+1);
    }

    me.update = function()
    {
        update.call(this);
    }

    me.globalToLocal = function(x, y)
    {
        return {
            x: x - me.x,
            y: y - me.y
        }
    }

    me.localToGlobal = function(x, y)
    {
        return {
            x: me.x + x,
            y: me.y + y
        }
    }

    me.hasGlobalPixelAt = function(x, y)
    {
        var result = false;

        if (isGlobalPositionWithinBoundaries(x, y))
        {
            var distanceFromLeft = x - me.x;
            var distanceFromTop = y - me.y;
            var pixel32 = getPixel32At(distanceFromLeft, distanceFromTop);

            if (pixel32 !== 0)
            {
                result = true;
            }
        }

        return result;
    }

    function isGlobalPositionWithinBoundaries(x, y)
    {
        //var distanceFromLeft = x - me.x;
        //var distanceFromTop = y - me.y;
        //var distanceFromRight = x - (me.x + _imageData.width);
        //var distanceFromBottom = y - (me.y + _imageData.height);
        //return (distanceFromLeft >= 0 && distanceFromRight <= 0
        //    && distanceFromTop >= 0 && distanceFromBottom <= 0);

        // the below statement implements the same functionality as above
        return ((x - me.x) >= 0
                && (y - me.y) >= 0
                && (x - (me.x + _imageData.width)) <= 0
                && (y - (me.y + _imageData.height)) <= 0);
    }

    function saveRenderedPosition()
    {
        renderedX = me.x;
        renderedY = me.y;
    }

    function getPixel32At(x, y)
    {
        return imageData32View[y * _imageData.width + x];
    }

    init();

    return this;
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2017-present PlatformIO <contact@platformio.org>
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */

import * as pioNodeHelpers from 'platformio-node-helpers';
import * as utils from './utils';

import { IS_WINDOWS } from './constants';
import path from 'path';
import vscode from 'vscode';

export default class TaskManager {

  static AUTO_REFRESH_DELAY = 500; // 0.5 sec
  static type = 'PlatformIO';

  constructor() {
    this.subscriptions = [];
    this.internalSubscriptions = [];
    this.onDidProjectTasksUpdatedCallbacks = [];

    this._projectDir = undefined;
    this._refreshTimeout = undefined;
    this._projecTasks = undefined;

    this.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(
      () => this.checkActiveProjectDir())
    );
    this.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(
      () => this.checkActiveProjectDir())
    );
  }

  async getTasks() {
    return [...(await this.getProjectTasks()), ...this.getServiceTasks()];
  }

  async getProjectTasks() {
    if (this._projecTasks) {
      return this._projecTasks;
    }
    const pt = new pioNodeHelpers.project.ProjectTasks(this._projectDir, 'vscode');
    this._projecTasks = await pt.getTasks();
    return this._projecTasks;
  }

  getServiceTasks() {
    return [
      new pioNodeHelpers.project.TaskItem('Update All (libraries, platforms, and packages)', undefined, ['update']),
      new pioNodeHelpers.project.TaskItem('Upgrade PlatformIO Core', undefined, ['upgrade'])
    ];
  }

  toVSCodeTask(projectTask) {
    const vscodeTask = new vscode.Task(
      {
        type: TaskManager.type,
        task: projectTask.id
      },
      vscode.workspace.getWorkspaceFolder(vscode.Uri.file(this._projectDir)),
      projectTask.title,
      TaskManager.type,
      new vscode.ShellExecution(IS_WINDOWS ? 'platformio.exe' : 'platformio', projectTask.args, {
        cwd: this._projectDir,
        env: process.env
      }),
      '$platformio'
    );
    vscodeTask.presentationOptions = {
      panel: vscode.TaskPanelKind.Dedicated
    };
    if (projectTask.isBuild()) {
      vscodeTask.group = vscode.TaskGroup.Build;
    } else if (projectTask.isClean()) {
      vscodeTask.group = vscode.TaskGroup.Clean;
    } else if (projectTask.isTest()) {
      vscodeTask.group = vscode.TaskGroup.Test;
    }
    return vscodeTask;
  }

  registerProvider() {
    this.checkActiveProjectDir();
  }

  checkActiveProjectDir() {
    const projectDir = utils.getActivePIOProjectDir();
    if (!projectDir) {
      this._projectDir = undefined;
      this.refresh();
      return;
    }
    if (this._projectDir === projectDir) {
      return;
    }
    this._projectDir = projectDir;
    this.requestRefresh();
  }

  onDidProjectTasksUpdated(callback) {
    this.onDidProjectTasksUpdatedCallbacks.push(callback);
    return new vscode.Disposable(() => pioNodeHelpers.misc.arrayRemove(this.onDidProjectTasksUpdatedCallbacks, callback));
  }

  disposeInternal() {
    pioNodeHelpers.misc.disposeSubscriptions(this.internalSubscriptions);
    this._projecTasks = undefined;
  }

  dispose() {
    this.disposeInternal();
    pioNodeHelpers.misc.disposeSubscriptions(this.subscriptions);
  }

  requestRefresh() {
    if (this._refreshTimeout) {
      clearTimeout(this._refreshTimeout);
    }
    this._refreshTimeout = setTimeout(this.refresh.bind(this), TaskManager.AUTO_REFRESH_DELAY);
  }

  async refresh() {
    this.disposeInternal();
    if (this._projectDir) {
      const provider = vscode.tasks.registerTaskProvider(TaskManager.type, {
        provideTasks: async () => {
          return (await this.getTasks()).map(task => this.toVSCodeTask(task));
        },
        resolveTask: () => {
          return undefined;
        }
      });
      this.internalSubscriptions.push(provider);
      this.addProjectConfigWatcher(this._projectDir);
      this.controlDeviceMonitorTasks();
    }
    const projectTasks = await this.getProjectTasks();
    this.onDidProjectTasksUpdatedCallbacks.forEach(cb => cb(projectTasks));
  }

  addProjectConfigWatcher(projectDir) {
    try {
      const watcher = vscode.workspace.createFileSystemWatcher(
        path.join(projectDir, 'platformio.ini')
      );
      this.internalSubscriptions.push(watcher);

      this.internalSubscriptions.push(watcher.onDidCreate(() => {
        this.requestRefresh();
      }));
      this.internalSubscriptions.push(watcher.onDidChange(() => {
        this.requestRefresh();
      }));
      this.internalSubscriptions.push(watcher.onDidDelete(() => {
        this.dispose();
      }));

    } catch (err) {
      utils.notifyError('Tasks FileSystemWatcher', err);
    }
  }

  controlDeviceMonitorTasks() {
    let restoreAfterTask = undefined;
    let restoreTasks = [];

    this.internalSubscriptions.push(vscode.tasks.onDidStartTaskProcess((event) => {
      if (!vscode.workspace.getConfiguration('platformio-ide').get('autoCloseSerialMonitor')) {
        return;
      }
      if (!['upload', 'test'].some(arg => event.execution.task.execution.args.includes(arg))) {
        return;
      }
      vscode.tasks.taskExecutions.forEach((e) => {
        if (event.execution.task === e.task) {
          return;
        }
        if (['device', 'monitor'].every(arg => e.task.execution.args.includes(arg))) {
          restoreTasks.push(e.task);
        }
        if (e.task.execution.args.includes('monitor')) {
          e.terminate();
        }
      });
      restoreAfterTask = event.execution.task;
    }));

    this.internalSubscriptions.push(vscode.tasks.onDidEndTaskProcess((event) => {
      if (event.execution.task !== restoreAfterTask) {
        return;
      }
      setTimeout(() => {
        restoreTasks.forEach(task => {
          vscode.tasks.executeTask(task);
        });
        restoreTasks = [];
      }, parseInt(vscode.workspace.getConfiguration('platformio-ide').get('reopenSerialMonitorDelay')));

    }));
  }
}
<commit_msg>Revert back to `vscode.ProcessExecution` for Tasks // Resolve #957<commit_after>/**
 * Copyright (c) 2017-present PlatformIO <contact@platformio.org>
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */

import * as pioNodeHelpers from 'platformio-node-helpers';
import * as utils from './utils';

import { IS_WINDOWS } from './constants';
import path from 'path';
import vscode from 'vscode';

export default class TaskManager {

  static AUTO_REFRESH_DELAY = 500; // 0.5 sec
  static type = 'PlatformIO';

  constructor() {
    this.subscriptions = [];
    this.internalSubscriptions = [];
    this.onDidProjectTasksUpdatedCallbacks = [];

    this._projectDir = undefined;
    this._refreshTimeout = undefined;
    this._projecTasks = undefined;

    this.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(
      () => this.checkActiveProjectDir())
    );
    this.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(
      () => this.checkActiveProjectDir())
    );
  }

  async getTasks() {
    return [...(await this.getProjectTasks()), ...this.getServiceTasks()];
  }

  async getProjectTasks() {
    if (this._projecTasks) {
      return this._projecTasks;
    }
    const pt = new pioNodeHelpers.project.ProjectTasks(this._projectDir, 'vscode');
    this._projecTasks = await pt.getTasks();
    return this._projecTasks;
  }

  getServiceTasks() {
    return [
      new pioNodeHelpers.project.TaskItem('Update All (libraries, platforms, and packages)', undefined, ['update']),
      new pioNodeHelpers.project.TaskItem('Upgrade PlatformIO Core', undefined, ['upgrade'])
    ];
  }

  toVSCodeTask(projectTask) {
    const vscodeTask = new vscode.Task(
      {
        type: TaskManager.type,
        task: projectTask.id
      },
      vscode.workspace.getWorkspaceFolder(vscode.Uri.file(this._projectDir)),
      projectTask.title,
      TaskManager.type,
      new vscode.ProcessExecution(IS_WINDOWS ? 'platformio.exe' : 'platformio', projectTask.args, {
        cwd: this._projectDir,
        env: process.env
      }),
      '$platformio'
    );
    vscodeTask.presentationOptions = {
      panel: vscode.TaskPanelKind.Dedicated
    };
    if (projectTask.isBuild()) {
      vscodeTask.group = vscode.TaskGroup.Build;
    } else if (projectTask.isClean()) {
      vscodeTask.group = vscode.TaskGroup.Clean;
    } else if (projectTask.isTest()) {
      vscodeTask.group = vscode.TaskGroup.Test;
    }
    return vscodeTask;
  }

  registerProvider() {
    this.checkActiveProjectDir();
  }

  checkActiveProjectDir() {
    const projectDir = utils.getActivePIOProjectDir();
    if (!projectDir) {
      this._projectDir = undefined;
      this.refresh();
      return;
    }
    if (this._projectDir === projectDir) {
      return;
    }
    this._projectDir = projectDir;
    this.requestRefresh();
  }

  onDidProjectTasksUpdated(callback) {
    this.onDidProjectTasksUpdatedCallbacks.push(callback);
    return new vscode.Disposable(() => pioNodeHelpers.misc.arrayRemove(this.onDidProjectTasksUpdatedCallbacks, callback));
  }

  disposeInternal() {
    pioNodeHelpers.misc.disposeSubscriptions(this.internalSubscriptions);
    this._projecTasks = undefined;
  }

  dispose() {
    this.disposeInternal();
    pioNodeHelpers.misc.disposeSubscriptions(this.subscriptions);
  }

  requestRefresh() {
    if (this._refreshTimeout) {
      clearTimeout(this._refreshTimeout);
    }
    this._refreshTimeout = setTimeout(this.refresh.bind(this), TaskManager.AUTO_REFRESH_DELAY);
  }

  async refresh() {
    this.disposeInternal();
    if (this._projectDir) {
      const provider = vscode.tasks.registerTaskProvider(TaskManager.type, {
        provideTasks: async () => {
          return (await this.getTasks()).map(task => this.toVSCodeTask(task));
        },
        resolveTask: () => {
          return undefined;
        }
      });
      this.internalSubscriptions.push(provider);
      this.addProjectConfigWatcher(this._projectDir);
      this.controlDeviceMonitorTasks();
    }
    const projectTasks = await this.getProjectTasks();
    this.onDidProjectTasksUpdatedCallbacks.forEach(cb => cb(projectTasks));
  }

  addProjectConfigWatcher(projectDir) {
    try {
      const watcher = vscode.workspace.createFileSystemWatcher(
        path.join(projectDir, 'platformio.ini')
      );
      this.internalSubscriptions.push(watcher);

      this.internalSubscriptions.push(watcher.onDidCreate(() => {
        this.requestRefresh();
      }));
      this.internalSubscriptions.push(watcher.onDidChange(() => {
        this.requestRefresh();
      }));
      this.internalSubscriptions.push(watcher.onDidDelete(() => {
        this.dispose();
      }));

    } catch (err) {
      utils.notifyError('Tasks FileSystemWatcher', err);
    }
  }

  controlDeviceMonitorTasks() {
    let restoreAfterTask = undefined;
    let restoreTasks = [];

    this.internalSubscriptions.push(vscode.tasks.onDidStartTaskProcess((event) => {
      if (!vscode.workspace.getConfiguration('platformio-ide').get('autoCloseSerialMonitor')) {
        return;
      }
      if (!['upload', 'test'].some(arg => event.execution.task.execution.args.includes(arg))) {
        return;
      }
      vscode.tasks.taskExecutions.forEach((e) => {
        if (event.execution.task === e.task) {
          return;
        }
        if (['device', 'monitor'].every(arg => e.task.execution.args.includes(arg))) {
          restoreTasks.push(e.task);
        }
        if (e.task.execution.args.includes('monitor')) {
          e.terminate();
        }
      });
      restoreAfterTask = event.execution.task;
    }));

    this.internalSubscriptions.push(vscode.tasks.onDidEndTaskProcess((event) => {
      if (event.execution.task !== restoreAfterTask) {
        return;
      }
      setTimeout(() => {
        restoreTasks.forEach(task => {
          vscode.tasks.executeTask(task);
        });
        restoreTasks = [];
      }, parseInt(vscode.workspace.getConfiguration('platformio-ide').get('reopenSerialMonitorDelay')));

    }));
  }
}
<|endoftext|>


<commit_before>/****
 * Grapnel.js
 * https://github.com/EngineeringMode/Grapnel.js
 *
 * @author Greg Sabia Tucker <greg@artificer.io>
 * @link http://artificer.io
 * @version 0.5.8
 *
 * Released under MIT License. See LICENSE.txt or http://opensource.org/licenses/MIT
*/

;(function(root){

    function Grapnel(opts){
        "use strict";

        var self = this; // Scope reference
        this.events = {}; // Event Listeners
        this.state = null; // Router state object
        this.options = opts || {}; // Options
        this.options.env = this.options.env || (!!(Object.keys(root).length === 0 && process && process.browser !== true) ? 'server' : 'client');
        this.options.mode = this.options.mode || (!!(this.options.env !== 'server' && this.options.pushState && root.history && root.history.pushState) ? 'pushState' : 'hashchange');
        this.version = '0.5.8'; // Version

        if('function' === typeof root.addEventListener){
            root.addEventListener('hashchange', function(){
                self.trigger('hashchange');
            });

            root.addEventListener('popstate', function(e){
                // Make sure popstate doesn't run on init -- this is a common issue with Safari and old versions of Chrome
                if(self.state && self.state.previousState === null) return false;

                self.trigger('navigate');
            });
        }
        /**
         * Deprecation warning: this.fragment may eventually be evolved into this.path(pathname) function eventually
        */
        this.fragment = {
            /**
             * Get pathname relative to root
             * @return {String} pathname
            */
            get : function(){
                var frag;

                if(self.options.mode === 'pushState'){
                    frag = root.location.pathname.replace(self.options.root, '');
                }else if(self.options.mode !== 'pushState' && root.location){
                    frag = (root.location.hash) ? root.location.hash.split((self.options.hashBang ? '#!' : '#'))[1] : '';
                }else{
                    frag = root._pathname || '';
                }

                return frag;
            },
            /**
             * Set pathname relative to root
             * @param {String} pathname
             * @return {Object} router
            */
            set : function(frag){
                if(self.options.mode === 'pushState'){
                    frag = (self.options.root) ? (self.options.root + frag) : frag;
                    root.history.pushState({}, null, frag);
                }else if(root.location){
                    root.location.hash = (self.options.hashBang ? '!' : '') + frag;
                }else{
                    root._pathname = frag || '';
                }

                return self;
            },
            clear : function(){
                if(self.options.mode === 'pushState'){
                    root.history.pushState({}, null, self.options.root || '/');
                }else if(root.location){
                    root.location.hash = (self.options.hashBang) ? '!' : '';
                }

                return self;
            }
        }

        return this;
    }
    /**
     * Create a RegExp Route from a string
     * This is the heart of the router and I've made it as small as possible!
     *
     * @param {String} Path of route
     * @param {Array} Array of keys to fill
     * @param {Bool} Case sensitive comparison
     * @param {Bool} Strict mode
    */
    Grapnel.regexRoute = function(path, keys, sensitive, strict){
        if(path instanceof RegExp) return path;
        if(path instanceof Array) path = '(' + path.join('|') + ')';
        // Build route RegExp
        path = path.concat(strict ? '' : '/?')
            .replace(/\/\(/g, '(?:/')
            .replace(/\+/g, '__plus__')
            .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
                keys.push({ name : key, optional : !!optional });
                slash = slash || '';

                return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '');
            })
            .replace(/([\/.])/g, '\\$1')
            .replace(/__plus__/g, '(.+)')
            .replace(/\*/g, '(.*)');

        return new RegExp('^' + path + '$', sensitive ? '' : 'i');
    }
    /**
     * ForEach workaround utility
     *
     * @param {Array} to iterate
     * @param {Function} callback
    */
    Grapnel._forEach = function(a, callback){
        if(typeof Array.prototype.forEach === 'function') return Array.prototype.forEach.call(a, callback);
        // Replicate forEach()
        return function(c, next){
            for(var i=0, n = this.length; i<n; ++i){
                c.call(next, this[i], i, this);
            }
        }.call(a, callback);
    }
    /**
     * Add an route and handler
     *
     * @param {String|RegExp} route name
     * @return {self} Router
    */
    Grapnel.prototype.get = Grapnel.prototype.add = function(route){
        var self = this,
            keys = [],
            middleware = Array.prototype.slice.call(arguments, 1, -1),
            handler = Array.prototype.slice.call(arguments, -1)[0],
            regex = Grapnel.regexRoute(route, keys);

        var invoke = function RouteHandler(){
            // If route is instance of RegEx, match the route
            var match = self.fragment.get().match(regex);
            // Test matches against current route
            if(match){
                // Match found
                var req = { params : {}, keys : keys, matches : match.slice(1) };
                // Build parameters
                Grapnel._forEach(req.matches, function(value, i){
                    var key = (keys[i] && keys[i].name) ? keys[i].name : i;
                    // Parameter key will be its key or the iteration index. This is useful if a wildcard (*) is matched
                    req.params[key] = (value) ? decodeURIComponent(value) : undefined;
                });
                // Route events should have an object detailing the event -- route events also change the state of the router
                var event = {
                    route : route,
                    value : self.fragment.get(),
                    params : req.params,
                    regex : match,
                    stack : [],
                    runCallback : true,
                    callbackRan : false,
                    propagateEvent : true,
                    next : function(){
                        return this.stack.shift().call(self, req, event, function(){
                            event.next.call(event);
                        });
                    },
                    preventDefault : function(){
                        this.runCallback = false;
                    },
                    stopPropagation : function(){
                        this.propagateEvent = false;
                    },
                    parent : function(){
                        var hasParentEvents = !!(this.previousState && this.previousState.value && this.previousState.value == this.value);
                        return (hasParentEvents) ? this.previousState : false;
                    },
                    callback : function(){
                        event.callbackRan = true;
                        event.timeStamp = Date.now();
                        event.next();
                    }
                }
                // Middleware
                event.stack = middleware.concat(handler);
                // Trigger main event
                self.trigger('match', event, req);
                // Continue?
                if(!event.runCallback) return self;
                // Previous state becomes current state
                event.previousState = self.state;
                // Save new state
                self.state = event;
                // Prevent this handler from being called if parent handler in stack has instructed not to propagate any more events
                if(event.parent() && event.parent().propagateEvent === false){
                    event.propagateEvent = false;
                    return self;
                }
                // Call handler
                event.callback();
            }
            // Returns self
            return self;
        }
        // Event name
        var eventName = (self.options.mode !== 'pushState' && self.options.env !== 'server') ? 'hashchange' : 'navigate';
        // Invoke when route is defined, and once again when app navigates
        return invoke().on(eventName, invoke);
    }
    /**
     * Fire an event listener
     *
     * @param {String} event name (multiple events can be called when seperated by a space " ")
     * @param {Mixed} [attributes] Parameters that will be applied to event handler
     * @return {self} Router
    */
    Grapnel.prototype.trigger = function(event){
        var self = this,
            params = Array.prototype.slice.call(arguments, 1);
        // Call matching events
        if(this.events[event]){
            Grapnel._forEach(this.events[event], function(fn){
                fn.apply(self, params);
            });
        }

        return this;
    }
    /**
     * Add an event listener
     *
     * @param {String} event name (multiple events can be called when seperated by a space " ")
     * @param {Function} callback
     * @return {self} Router
    */
    Grapnel.prototype.on = Grapnel.prototype.bind = function(event, handler){
        var self = this,
            events = event.split(' ');

        Grapnel._forEach(events, function(event){
            if(self.events[event]){
                self.events[event].push(handler);
            }else{
                self.events[event] = [handler];
            }
        });

        return this;
    }
    /**
     * Allow context
     *
     * @param {String} Route context
     * @return {Function} Adds route to context
    */
    Grapnel.prototype.context = function(context){
        var self = this;

        return function(value, callback){
            var prefix = (context.slice(-1) !== '/') ? context + '/' : context,
                pattern = prefix + value;

            return self.get.call(self, pattern, callback);
        }
    }
    /**
     * Navigate through history API
     *
     * @param {String} Pathname
     * @return {self} Router
    */
    Grapnel.prototype.navigate = function(path){
        return this.fragment.set(path).trigger('navigate');
    }
    /**
     * Create routes based on an object
     *
     * @param {Object} [Options, Routes]
     * @param {Object Routes}
     * @return {self} Router
    */
    Grapnel.listen = function(){
        var opts, routes;
        if(arguments[0] && arguments[1]){
            opts = arguments[0];
            routes = arguments[1];
        }else{
            routes = arguments[0];
        }
        // Return a new Grapnel instance
        return (function(){
            // TODO: Accept multi-level routes
            for(var key in routes){
                this.get.call(this, key, routes[key]);
            }

            return this;
        }).call(new Grapnel(opts || {}));
    }

    if('function' === typeof root.define && !root.define.amd.grapnel){
        root.define(function(require, exports, module){
            root.define.amd.grapnel = true;
            return Grapnel;
        });
    }else if('object' === typeof module && 'object' === typeof module.exports){
        module.exports = exports = Grapnel;
    }else{
        root.Grapnel = Grapnel;
    }

}).call({}, ('object' === typeof window) ? window : this);
<commit_msg>JavaDoc inconsistencies & Typo; fix for #34<commit_after>/****
 * Grapnel.js
 * https://github.com/EngineeringMode/Grapnel.js
 *
 * @author Greg Sabia Tucker <greg@artificer.io>
 * @link http://artificer.io
 * @version 0.5.8
 *
 * Released under MIT License. See LICENSE.txt or http://opensource.org/licenses/MIT
*/

;(function(root){

    function Grapnel(opts){
        "use strict";

        var self = this; // Scope reference
        this.events = {}; // Event Listeners
        this.state = null; // Router state object
        this.options = opts || {}; // Options
        this.options.env = this.options.env || (!!(Object.keys(root).length === 0 && process && process.browser !== true) ? 'server' : 'client');
        this.options.mode = this.options.mode || (!!(this.options.env !== 'server' && this.options.pushState && root.history && root.history.pushState) ? 'pushState' : 'hashchange');
        this.version = '0.5.8'; // Version

        if('function' === typeof root.addEventListener){
            root.addEventListener('hashchange', function(){
                self.trigger('hashchange');
            });

            root.addEventListener('popstate', function(e){
                // Make sure popstate doesn't run on init -- this is a common issue with Safari and old versions of Chrome
                if(self.state && self.state.previousState === null) return false;

                self.trigger('navigate');
            });
        }
        /**
         * Deprecation warning: this.fragment may eventually be evolved into this.path(pathname) function eventually
        */
        this.fragment = {
            /**
             * Get pathname relative to root
             * @return {String} pathname
            */
            get : function(){
                var frag;

                if(self.options.mode === 'pushState'){
                    frag = root.location.pathname.replace(self.options.root, '');
                }else if(self.options.mode !== 'pushState' && root.location){
                    frag = (root.location.hash) ? root.location.hash.split((self.options.hashBang ? '#!' : '#'))[1] : '';
                }else{
                    frag = root._pathname || '';
                }

                return frag;
            },
            /**
             * Set pathname relative to root
             * @param {String} pathname
             * @return {Object} router
            */
            set : function(frag){
                if(self.options.mode === 'pushState'){
                    frag = (self.options.root) ? (self.options.root + frag) : frag;
                    root.history.pushState({}, null, frag);
                }else if(root.location){
                    root.location.hash = (self.options.hashBang ? '!' : '') + frag;
                }else{
                    root._pathname = frag || '';
                }

                return self;
            },
            clear : function(){
                if(self.options.mode === 'pushState'){
                    root.history.pushState({}, null, self.options.root || '/');
                }else if(root.location){
                    root.location.hash = (self.options.hashBang) ? '!' : '';
                }

                return self;
            }
        }

        return this;
    }
    /**
     * Create a RegExp Route from a string
     * This is the heart of the router and I've made it as small as possible!
     *
     * @param {String} Path of route
     * @param {Array} Array of keys to fill
     * @param {Bool} Case sensitive comparison
     * @param {Bool} Strict mode
    */
    Grapnel.regexRoute = function(path, keys, sensitive, strict){
        if(path instanceof RegExp) return path;
        if(path instanceof Array) path = '(' + path.join('|') + ')';
        // Build route RegExp
        path = path.concat(strict ? '' : '/?')
            .replace(/\/\(/g, '(?:/')
            .replace(/\+/g, '__plus__')
            .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
                keys.push({ name : key, optional : !!optional });
                slash = slash || '';

                return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '');
            })
            .replace(/([\/.])/g, '\\$1')
            .replace(/__plus__/g, '(.+)')
            .replace(/\*/g, '(.*)');

        return new RegExp('^' + path + '$', sensitive ? '' : 'i');
    }
    /**
     * ForEach workaround utility
     *
     * @param {Array} to iterate
     * @param {Function} callback
    */
    Grapnel._forEach = function(a, callback){
        if(typeof Array.prototype.forEach === 'function') return Array.prototype.forEach.call(a, callback);
        // Replicate forEach()
        return function(c, next){
            for(var i=0, n = this.length; i<n; ++i){
                c.call(next, this[i], i, this);
            }
        }.call(a, callback);
    }
    /**
     * Add an route and handler
     *
     * @param {String|RegExp} route name
     * @return {self} Router
    */
    Grapnel.prototype.get = Grapnel.prototype.add = function(route){
        var self = this,
            keys = [],
            middleware = Array.prototype.slice.call(arguments, 1, -1),
            handler = Array.prototype.slice.call(arguments, -1)[0],
            regex = Grapnel.regexRoute(route, keys);

        var invoke = function RouteHandler(){
            // If route is instance of RegEx, match the route
            var match = self.fragment.get().match(regex);
            // Test matches against current route
            if(match){
                // Match found
                var req = { params : {}, keys : keys, matches : match.slice(1) };
                // Build parameters
                Grapnel._forEach(req.matches, function(value, i){
                    var key = (keys[i] && keys[i].name) ? keys[i].name : i;
                    // Parameter key will be its key or the iteration index. This is useful if a wildcard (*) is matched
                    req.params[key] = (value) ? decodeURIComponent(value) : undefined;
                });
                // Route events should have an object detailing the event -- route events also change the state of the router
                var event = {
                    route : route,
                    value : self.fragment.get(),
                    params : req.params,
                    regex : match,
                    stack : [],
                    runCallback : true,
                    callbackRan : false,
                    propagateEvent : true,
                    next : function(){
                        return this.stack.shift().call(self, req, event, function(){
                            event.next.call(event);
                        });
                    },
                    preventDefault : function(){
                        this.runCallback = false;
                    },
                    stopPropagation : function(){
                        this.propagateEvent = false;
                    },
                    parent : function(){
                        var hasParentEvents = !!(this.previousState && this.previousState.value && this.previousState.value == this.value);
                        return (hasParentEvents) ? this.previousState : false;
                    },
                    callback : function(){
                        event.callbackRan = true;
                        event.timeStamp = Date.now();
                        event.next();
                    }
                }
                // Middleware
                event.stack = middleware.concat(handler);
                // Trigger main event
                self.trigger('match', event, req);
                // Continue?
                if(!event.runCallback) return self;
                // Previous state becomes current state
                event.previousState = self.state;
                // Save new state
                self.state = event;
                // Prevent this handler from being called if parent handler in stack has instructed not to propagate any more events
                if(event.parent() && event.parent().propagateEvent === false){
                    event.propagateEvent = false;
                    return self;
                }
                // Call handler
                event.callback();
            }
            // Returns self
            return self;
        }
        // Event name
        var eventName = (self.options.mode !== 'pushState' && self.options.env !== 'server') ? 'hashchange' : 'navigate';
        // Invoke when route is defined, and once again when app navigates
        return invoke().on(eventName, invoke);
    }
    /**
     * Fire an event listener
     *
     * @param {String} event name
     * @param {Mixed} [attributes] Parameters that will be applied to event handler
     * @return {self} Router
    */
    Grapnel.prototype.trigger = function(event){
        var self = this,
            params = Array.prototype.slice.call(arguments, 1);
        // Call matching events
        if(this.events[event]){
            Grapnel._forEach(this.events[event], function(fn){
                fn.apply(self, params);
            });
        }

        return this;
    }
    /**
     * Add an event listener
     *
     * @param {String} event name (multiple events can be called when separated by a space " ")
     * @param {Function} callback
     * @return {self} Router
    */
    Grapnel.prototype.on = Grapnel.prototype.bind = function(event, handler){
        var self = this,
            events = event.split(' ');

        Grapnel._forEach(events, function(event){
            if(self.events[event]){
                self.events[event].push(handler);
            }else{
                self.events[event] = [handler];
            }
        });

        return this;
    }
    /**
     * Allow context
     *
     * @param {String} Route context
     * @return {Function} Adds route to context
    */
    Grapnel.prototype.context = function(context){
        var self = this;

        return function(value, callback){
            var prefix = (context.slice(-1) !== '/') ? context + '/' : context,
                pattern = prefix + value;

            return self.get.call(self, pattern, callback);
        }
    }
    /**
     * Navigate through history API
     *
     * @param {String} Pathname
     * @return {self} Router
    */
    Grapnel.prototype.navigate = function(path){
        return this.fragment.set(path).trigger('navigate');
    }
    /**
     * Create routes based on an object
     *
     * @param {Object} [Options, Routes]
     * @param {Object Routes}
     * @return {self} Router
    */
    Grapnel.listen = function(){
        var opts, routes;
        if(arguments[0] && arguments[1]){
            opts = arguments[0];
            routes = arguments[1];
        }else{
            routes = arguments[0];
        }
        // Return a new Grapnel instance
        return (function(){
            // TODO: Accept multi-level routes
            for(var key in routes){
                this.get.call(this, key, routes[key]);
            }

            return this;
        }).call(new Grapnel(opts || {}));
    }

    if('function' === typeof root.define && !root.define.amd.grapnel){
        root.define(function(require, exports, module){
            root.define.amd.grapnel = true;
            return Grapnel;
        });
    }else if('object' === typeof module && 'object' === typeof module.exports){
        module.exports = exports = Grapnel;
    }else{
        root.Grapnel = Grapnel;
    }

}).call({}, ('object' === typeof window) ? window : this);
<|endoftext|>


<commit_before>/**
 * Context (this):
 * {
 *   config: {base: {}, en: {}, ru: {}},
 *   sourcePath: 'en/mypost.md',
 *   url: 'en/mypost',
 *   content: '...html...',
 *   title: 'My post',
 *   ...frontmatter fields...
 * }
 */

/* eslint no-invalid-this:0 */

import path from 'path';
import richtypo from 'richtypo';
import hashSum from 'hash-sum';
import vdo from 'vdo';
import _ from 'lodash';
import {
	readFile,
	removeExtension,
	cleanHtml,
	getMessageFormat,
	getDateTimeFormat,
	errorInlineHtml,
	getComponentStack,
	absolutizeUrl as absolutizeUrlBase,
	absolutizeLinks as absolutizeLinksBase,
} from './util';

export { safe } from './util';

const getOption = _.memoize(function(key, lang) {
	let value = _.get(this.config[lang], key);
	if (value === undefined) {
		value = this.config[key];
	}
	if (value === undefined) {
		throw new Error(`Config option "${key}" not found.`);
	}
	return value;
}, (key, lang) => `${key}/${lang}`);

const getMessage = _.memoize(function(key, lang, params) {
	const string = getOption.call(this, key, lang);
	const message = getMessageFormat(string, lang);
	return vdo.markSafe(message.format(params));
}, (key, lang, params) => `${key}/${lang}/${JSON.stringify(params)}`);

/**
 * Localized config option.
 *
 * @param {string} key Config key: bla.bla.
 * @return {string}
 */
export function option(key) {
	return getOption.call(this, key, this.lang || 'base');
}

/**
 * Page language (`lang` frontmatter field) or default language (`lang` config option) if page language is not specified.
 *
 * @return {string}
 */
export function pageLang() {
	return this.lang || this.config.base.lang;
}

/**
 * Localized config option with {} templates.
 *
 * @param {string} key Key in config.
 * @param {object} params Substitutions.
 * @return {string}
 */
export function __(key, params = {}) {
	return getMessage.call(this, key, this.pageLang(), params);
}

/**
 * Absolutize URL.
 *
 * @param {string} url URL.
 * @return {string}
 */
export function absolutizeUrl(url) {
	return absolutizeUrlBase(url, this.option('url'));
}

/**
 * Absolutize all links and image URLs.
 *
 * @param {string} html
 * @return {string}
 */
export function absolutizeLinks(html) {
	return absolutizeLinksBase(html, this.option('url'));
}

/**
 * Title to use in a <title> tag.
 *
 * @param {Object} $1
 * @param {string} $1.title Custom title.
 * @param {boolean} $1.suffix Do not append suffix if `false`.
 * @return {string}
 */
export function getPageTitle({ title, suffix } = {}) {
	if (this.pageTitle) {
		return this.pageTitle;
	}
	if (title || this.title) {
		if (suffix === undefined) {
			suffix = ' — ' + this.option('title');
		}
		return cleanHtml(title || this.title) + (suffix || '');
	}

	return this.option('title');
}

/**
 * Converts date to string.
 *
 * @param {Date} date
 * @return {string}
 */
export function dateToString(date) {
	const format = getDateTimeFormat(this.pageLang(), {
		year: 'numeric',
		month: 'long',
		day: 'numeric',
	});
	try {
		return format.format(date);
	} catch (exception) {
		return errorInlineHtml(
			`dateToString: invalid date "${date}" while rendering ${
				this.sourcePath
			}.\nComponent stack: ${getComponentStack(exception.stack)}`
		);
	}
}

/**
 * Path for a static file.
 *
 * @param {string} url
 * @return {string}
 */
export function assetFilepath(url) {
	return path.join(this.option('assetsFolder'), url);
}

/**
 * Fingerprinted URL for a static file.
 *
 * @param {string} url
 * @return {string}
 */
export const fingerprint = _.memoize(function(url) {
	const hash = hashSum(this.assetFilepath(url));
	return `${url}?${hash}`;
});

/**
 * Return a static file content.
 *
 * @param {string} url
 * @return {string}
 */
export const embedFile = _.memoize(function(url) {
	return readFile(this.assetFilepath(url));
});

/**
 * Return a static file content prefixed with a comment with a file name.
 *
 * @param {string} url
 * @return {string}
 */
export const inlineFile = _.memoize(function(url) {
	const name = removeExtension(path.basename(url));
	const comment = `/*${name}*/`;
	return comment + readFile(this.assetFilepath(url));
});

/**
 * Rich typo for body text.
 *
 * @param {string} string
 * @return {string}
 */
export function typo(string) {
	return (
		string && vdo.markSafe(richtypo.rich(string.toString(), this.pageLang()))
	);
}

/**
 * Rich typo for titles.
 *
 * @param {string} string
 * @return {string}
 */
export function typoTitle(string) {
	return (
		string && vdo.markSafe(richtypo.title(string.toString(), this.pageLang()))
	);
}

/**
 * Stringify JSON.
 * @param {object} object
 * @return {string}
 */
export function json(object) {
	return vdo.markSafe(JSON.stringify(object));
}
<commit_msg>Refactor: Disable __ helper memoization<commit_after>/**
 * Context (this):
 * {
 *   config: {base: {}, en: {}, ru: {}},
 *   sourcePath: 'en/mypost.md',
 *   url: 'en/mypost',
 *   content: '...html...',
 *   title: 'My post',
 *   ...frontmatter fields...
 * }
 */

/* eslint no-invalid-this:0 */

import path from 'path';
import richtypo from 'richtypo';
import hashSum from 'hash-sum';
import vdo from 'vdo';
import _ from 'lodash';
import {
	readFile,
	removeExtension,
	cleanHtml,
	getMessageFormat,
	getDateTimeFormat,
	errorInlineHtml,
	getComponentStack,
	absolutizeUrl as absolutizeUrlBase,
	absolutizeLinks as absolutizeLinksBase,
} from './util';

export { safe } from './util';

const getOption = _.memoize(function(key, lang) {
	let value = _.get(this.config[lang], key);
	if (value === undefined) {
		value = this.config[key];
	}
	if (value === undefined) {
		throw new Error(`Config option "${key}" not found.`);
	}
	return value;
}, (key, lang) => `${key}/${lang}`);

/**
 * Localized config option.
 *
 * @param {string} key Config key: bla.bla.
 * @return {string}
 */
export function option(key) {
	return getOption.call(this, key, this.lang || 'base');
}

/**
 * Page language (`lang` frontmatter field) or default language (`lang` config option) if page language is not specified.
 *
 * @return {string}
 */
export function pageLang() {
	return this.lang || this.config.base.lang;
}

/**
 * Localized config option with {} templates.
 *
 * @param {string} key Key in config.
 * @param {object} params Substitutions.
 * @return {string}
 */
export function __(key, params = {}) {
	const lang = this.pageLang();
	const string = getOption.call(this, key, lang);
	const message = getMessageFormat(string, lang);
	return vdo.markSafe(message.format(params));
}

/**
 * Absolutize URL.
 *
 * @param {string} url URL.
 * @return {string}
 */
export function absolutizeUrl(url) {
	return absolutizeUrlBase(url, this.option('url'));
}

/**
 * Absolutize all links and image URLs.
 *
 * @param {string} html
 * @return {string}
 */
export function absolutizeLinks(html) {
	return absolutizeLinksBase(html, this.option('url'));
}

/**
 * Title to use in a <title> tag.
 *
 * @param {Object} $1
 * @param {string} $1.title Custom title.
 * @param {boolean} $1.suffix Do not append suffix if `false`.
 * @return {string}
 */
export function getPageTitle({ title, suffix } = {}) {
	if (this.pageTitle) {
		return this.pageTitle;
	}
	if (title || this.title) {
		if (suffix === undefined) {
			suffix = ' — ' + this.option('title');
		}
		return cleanHtml(title || this.title) + (suffix || '');
	}

	return this.option('title');
}

/**
 * Converts date to string.
 *
 * @param {Date} date
 * @return {string}
 */
export function dateToString(date) {
	const format = getDateTimeFormat(this.pageLang(), {
		year: 'numeric',
		month: 'long',
		day: 'numeric',
	});
	try {
		return format.format(date);
	} catch (exception) {
		return errorInlineHtml(
			`dateToString: invalid date "${date}" while rendering ${
				this.sourcePath
			}.\nComponent stack: ${getComponentStack(exception.stack)}`
		);
	}
}

/**
 * Path for a static file.
 *
 * @param {string} url
 * @return {string}
 */
export function assetFilepath(url) {
	return path.join(this.option('assetsFolder'), url);
}

/**
 * Fingerprinted URL for a static file.
 *
 * @param {string} url
 * @return {string}
 */
export const fingerprint = _.memoize(function(url) {
	const hash = hashSum(this.assetFilepath(url));
	return `${url}?${hash}`;
});

/**
 * Return a static file content.
 *
 * @param {string} url
 * @return {string}
 */
export const embedFile = _.memoize(function(url) {
	return readFile(this.assetFilepath(url));
});

/**
 * Return a static file content prefixed with a comment with a file name.
 *
 * @param {string} url
 * @return {string}
 */
export const inlineFile = _.memoize(function(url) {
	const name = removeExtension(path.basename(url));
	const comment = `/*${name}*/`;
	return comment + readFile(this.assetFilepath(url));
});

/**
 * Rich typo for body text.
 *
 * @param {string} string
 * @return {string}
 */
export function typo(string) {
	return (
		string && vdo.markSafe(richtypo.rich(string.toString(), this.pageLang()))
	);
}

/**
 * Rich typo for titles.
 *
 * @param {string} string
 * @return {string}
 */
export function typoTitle(string) {
	return (
		string && vdo.markSafe(richtypo.title(string.toString(), this.pageLang()))
	);
}

/**
 * Stringify JSON.
 * @param {object} object
 * @return {string}
 */
export function json(object) {
	return vdo.markSafe(JSON.stringify(object));
}
<|endoftext|>


<commit_before>import { helpers } from 'mostly-feathers-mongoose';
import fp from 'mostly-func';
import path from 'path';
import { plural } from 'pluralize';
import shortid from 'shortid';
import slug from 'limax';

// get parent or root or top workspaces document
export const getParentDocument = (app, id, doc) => {
  let parentQuery = Promise.resolve();
  // get by id and type
  if (id) {
    const svcDocuments = app.service(plural(doc && doc.type || 'document'));
    return svcDocuments.get(id, {
      query: { $select: 'parent,*' }
    }).then(doc => doc && doc.parent);
  }
  // get by the parent id
  if (doc.parent) {
    const svcDocuments = app.service('documents');
    return svcDocuments.get(doc.parent);
  }
  // get the root folder or workspaces root
  if (doc.path && doc.path !== '/' && doc.path !== '/workspaces') {
    const svcFolder = app.service('folders');
    if (doc.path.startsWith('/workspaces')) {
      return svcFolder.action('first').find({ query: { path : '/workspaces' } });
    } else {
      return svcFolder.action('first').find({ query: { path : '/' } });
    }
  }
  return Promise.resolve();
};

// generate a short typed name for a document
export const shortname = (type, existing, title) => {
  let name = type + '-' + shortid.generate();
  if (existing) {
    name = path.basename(existing); // existing short name
  } else if (title) {
    name = type + '-' + slug(title, { tone: false });
  }
  // if name does not contain and start with doc type, add doc type to name
  if (name.indexOf('-') < 0 && !name.startsWith(type)) {
    name = type + '-' + name;
  }
  return name;
};

export const getAces = (app, docs, select = 'user,creator,*') => {
  const svcPermissions = app.service('user-permissions');
  const typedIds = fp.map(helpers.typedId, docs);
  return svcPermissions.find({
    query: {
      subject: { $in: typedIds },
      $select: select
    }
  }).then(results => {
    return fp.groupBy(permit => helpers.getId(permit.subject), results.data || results);
  });
};

export const getParentAces = (app, docs, select = 'user,creator,*') => {
  const svcDocuments = app.service('user-documents');
  const svcPermissions = app.service('user-permissions');
  const typedIds = fp.uniq(fp.flatMap(doc => {
    return fp.map(helpers.typedId, doc.ancestors || []);
  }, docs));

  const getAncestorPermissions = (ids) => ids.length > 0?
    svcPermissions.find({
      query: { subject: { $in: ids }, $select: select },
      paginate: false
    }) : Promise.resolve([]);
  const getAncestors = (ids) => ids.length > 0?
    helpers.findWithTypedIds(app, ids) : Promise.resolve([]);

  return Promise.all([
    getAncestorPermissions(typedIds),
    getAncestors(typedIds),
  ]).then(([permits, ancestors]) => {
    const permissions = permits.data || permits;
    return fp.reduce((arr, doc) => {
      arr[doc.id] = [];
      for (let i = (doc.ancestors || []).length - 1; i >= 0; i--) {
        if (doc.ancestors[i]) {
          const ancestor = fp.find(
            fp.propEq('id', helpers.getId(doc.ancestors[i])), ancestors);
          const ancestorAces = fp.filter(
            fp.propEq('subject', helpers.typedId(doc.ancestors[i])), permissions);
          if (ancestorAces.length > 0) {
            // subject change to current doc
            const aces = fp.map(fp.assoc('subject', helpers.typedId(doc)), ancestorAces);
            arr[doc.id] = arr[doc.id].concat(aces);
          }
          // continue to get inherited permissions or not
          if (!ancestor || !ancestor.inherited) break;
        }
      }
      return arr;
    }, {}, docs);
  });
};

export default {};<commit_msg>clean getParentAces code<commit_after>import { helpers } from 'mostly-feathers-mongoose';
import fp from 'mostly-func';
import path from 'path';
import { plural } from 'pluralize';
import shortid from 'shortid';
import slug from 'limax';

// get parent or root or top workspaces document
export const getParentDocument = (app, id, doc) => {
  let parentQuery = Promise.resolve();
  // get by id and type
  if (id) {
    const svcDocuments = app.service(plural(doc && doc.type || 'document'));
    return svcDocuments.get(id, {
      query: { $select: 'parent,*' }
    }).then(doc => doc && doc.parent);
  }
  // get by the parent id
  if (doc.parent) {
    const svcDocuments = app.service('documents');
    return svcDocuments.get(doc.parent);
  }
  // get the root folder or workspaces root
  if (doc.path && doc.path !== '/' && doc.path !== '/workspaces') {
    const svcFolder = app.service('folders');
    if (doc.path.startsWith('/workspaces')) {
      return svcFolder.action('first').find({ query: { path : '/workspaces' } });
    } else {
      return svcFolder.action('first').find({ query: { path : '/' } });
    }
  }
  return Promise.resolve();
};

// generate a short typed name for a document
export const shortname = (type, existing, title) => {
  let name = type + '-' + shortid.generate();
  if (existing) {
    name = path.basename(existing); // existing short name
  } else if (title) {
    name = type + '-' + slug(title, { tone: false });
  }
  // if name does not contain and start with doc type, add doc type to name
  if (name.indexOf('-') < 0 && !name.startsWith(type)) {
    name = type + '-' + name;
  }
  return name;
};

export const getAces = (app, docs, select = 'user,creator,*') => {
  const svcPermissions = app.service('user-permissions');
  const typedIds = fp.map(helpers.typedId, docs);
  return svcPermissions.find({
    query: {
      subject: { $in: typedIds },
      $select: select
    }
  }).then(results => {
    return fp.groupBy(permit => helpers.getId(permit.subject), results.data || results);
  });
};

export const getParentAces = (app, docs, select = 'user,creator,*') => {
  const svcDocuments = app.service('user-documents');
  const svcPermissions = app.service('user-permissions');
  const typedIds = fp.uniq(fp.flatMap(doc => {
    return fp.map(helpers.typedId, doc.ancestors || []);
  }, docs));

  const getAncestors = (ids) => ids.length > 0?
    helpers.findWithTypedIds(app, ids) : Promise.resolve([]);
  const getAncestorPermissions = (ids) => ids.length > 0?
    svcPermissions.find({
      query: { subject: { $in: ids }, $select: select },
      paginate: false
    }) : Promise.resolve([]);

  return Promise.all([
    getAncestors(typedIds),
    getAncestorPermissions(typedIds)
  ]).then(([ancestors, permits]) => {
    const permissions = permits.data || permits;
    return fp.reduce((arr, doc) => {
      if (!doc.ancestors) return arr;
      arr[doc.id] = [];
      // acnestor reverse loop to check inheritance
      for (let i = doc.ancestors.length - 1; i >= 0; i--) {
        const ancestor = fp.find(
          fp.propEq('id', helpers.getId(doc.ancestors[i])), ancestors);
        if (!ancestor) break;

        const ancestorAces = fp.filter(
          fp.propEq('subject', helpers.typedId(doc.ancestors[i])), permissions);
        if (ancestorAces.length > 0) {
          // subject change to current doc
          const aces = fp.map(fp.assoc('subject', helpers.typedId(doc)), ancestorAces);
          arr[doc.id] = arr[doc.id].concat(aces);
        }
        // continue to next inherited
        if (!ancestor.inherited) break;
      }
      return arr;
    }, {}, docs);
  });
};

export default {};<|endoftext|>


<commit_before>module.exports = [
    {
        type: 'helper',
        name: 'Line clamp',
        prefix: 'LineClamp',
        declaration: {
            '-webkit-line-clamp': '$0',
            'max-height': '$1'
        },
        rules: {
            '[class*=LineClamp]': {
               'display': '-webkit-box',
               '-webkit-box-orient': 'vertical',
               'overflow': 'hidden'
            },
            /**
             * 1. fixes WebKit bug that displays an extra line when the pseudo-element
             * is by itself on a next line
             */
            'a[class*=LineClamp]': {
               'display': 'inline-block',
               'display': '-webkit-box',
               'display': 'flex', /* 1 */
               '*display': 'inline',
               'zoom': 1
            },
            /**
             * Fix WebKit bug that displays ellipsis in middle of text inside *LINKS*
             * see: https://twitter.com/thierrykoblentz/status/443899465842176000
             * 1. removes that hack out of the flow (bug reported by Fonda)
             */
            'a[class*=LineClamp]:after': {
               'content': '"."',
               'font-size': 0,
               'visibility': 'hidden',
               'display': 'inline-block', /* 1 */
               'overflow': 'hidden', /* 1 */
               'height': 0, /* 1 */
               'width': 0 /* 1 */
            }
        }
    }
];<commit_msg>Revert LineClamp() fix<commit_after>module.exports = [
    {
        type: 'helper',
        name: 'Line clamp',
        prefix: 'LineClamp',
        declaration: {
            '-webkit-line-clamp': '$0',
            'max-height': '$1'
        },
        rules: {
            '[class*=LineClamp]': {
               'display': '-webkit-box',
               '-webkit-box-orient': 'vertical',
               'overflow': 'hidden'
            },
            'a[class*=LineClamp]': {
               'display': 'inline-block',
               'display': '-webkit-box',
               '*display': 'inline',
               'zoom': 1
            },
            /**
             * Fix WebKit bug that displays ellipsis in middle of text inside *LINKS*
             * see: https://twitter.com/thierrykoblentz/status/443899465842176000
             * 1. removes that hack out of the flow (bug reported by Fonda)
             */
            'a[class*=LineClamp]:after': {
               'content': '"."',
               'font-size': 0,
               'visibility': 'hidden',
               'display': 'inline-block', /* 1 */
               'overflow': 'hidden', /* 1 */
               'height': 0, /* 1 */
               'width': 0 /* 1 */
            }
        }
    }
];<|endoftext|>


<commit_before>const timetrack = require('./timetrack');
const Sugar = require('sugar-date');
const calculateDuration = require('./calculateDuration');

module.exports = (pluginContext) => {
    return (project, env = {}) => {
        return new Promise((resolve, reject) => {
            timetrack.loadData();
            resolve(timetrack.getHistory().map(entry => {
                try {
                // get the duration of the entry
                let duration = 'Running';
                if ('stop' in entry) {
                    // sugarjs sets it auto on 1 hour, so i'll just subtract 3600000 for now, fix this later
                    duration = Sugar.Date.format(Sugar.Date.create(entry.stop - entry.start - 3600000), '%H:%M:%S');
                }

                // get the project name
                const project = entry.project || 'No project';

                // get the start date
                const startDate = Sugar.Date.format(Sugar.Date.create(entry.start), '%c')

                return {
                    icon: 'fa-clock-o',
                    title: `[${duration}] ${project}`,
                    subtitle: `started on ${startDate}`
                }
                } catch (e) {
                    alert('fount: ' + e);
                }
            }));
        });
    }
}
<commit_msg>Limit the number of history items in the result<commit_after>const timetrack = require('./timetrack');
const Sugar = require('sugar-date');
const calculateDuration = require('./calculateDuration');

module.exports = (pluginContext) => {
    return (project, env = {}) => {
        return new Promise((resolve, reject) => {
            // we don't want to show all history, do we?
            const historyLimit = parseInt(env['historyNumItems'] || 25);

            // load the data from the json file
            timetrack.loadData();

            // render the history items
            resolve(timetrack.getHistory().slice(0, historyLimit).map(entry => {

                // get the duration of the entry
                let duration = 'Running';
                if ('stop' in entry) {
                    // sugarjs sets it auto on 1 hour, so i'll just subtract 3600000 for now, fix this later
                    duration = Sugar.Date.format(Sugar.Date.create(entry.stop - entry.start - 3600000), '%H:%M:%S');
                }

                // get the project name
                const project = entry.project || 'No project';

                // get the start date
                const startDate = Sugar.Date.format(Sugar.Date.create(entry.start), '%c')

                return {
                    icon: 'fa-clock-o',
                    title: `[${duration}] ${project}`,
                    subtitle: `started on ${startDate}`
                }

            }));
        });
    }
}
<|endoftext|>