<commit_before>define([
  'underscore',
  'backbone',
  'models/memento'
], function( _, Backbone, Memento ) {
  'use strict';

  // Utility functions.

  /**
   * Converts an object to an array.
   * Is the identity function if an array is passed in.
   */
  function toArray( object ) {
    return _.isArray( object ) ? object : [ object ];
  }

  /**
   * If the array is of length one, just return that element.
   * Is the identity function otherwise.
   */
  function extractSingleton( array ) {
    return array.length === 1 ? array[0] : array;
  }

  function EditorHistory() {
    this.current = null;

    this.undoStack = [];
    this.redoStack = [];

    // Store a state to determine whether we've changed a model/collection.
    this.previousState = null;
  }

  /**
   * There are four types of history states:
   * - A Backbone.Model.
   * - A Backbone.Collection.
   * - An array of Backbone.Models.
   * - An array of Backbone.Collections.
   */
  EditorHistory.prototype = {
    /**
     * Save the target's attributes.
     * Wipes the redo stack.
     */
    save: function( target ) {
      if ( this.current ) {
        this.undoStack.push( this.current );
      }

      this.current = this.snapshot( target );
      this.redoStack = [];
    },

    /**
     * Take a snapshot of the current state of the target.
     * Returns an array of mementos if target is an array.
     * Returns a single memento otherwise.
     */
    snapshot: function( target ) {
      if ( _.isArray( target ) ) {
        var mementos = target.map(function( element ) {
          return new Memento( element );
        });

        return extractSingleton( mementos );
      }

      return new Memento( target );
    },

    /**
     * Begin tracking a target.
     */
    begin: function( target ) {
      // Can't track nothing.
      if ( _.isEmpty( target ) ) {
        return;
      }

      this.previousState = this.snapshot( target );
      // Save if we don't have a baseline history.
      if ( !this.undoStack.length ) {
        this.save( target );
      }
    },

    /**
     * Stop tracking a target and push changes to history if anything changed.
     */
    end: function() {
      if ( !this.previousState ) {
        return;
      }

      var targets = toArray( this.previousState ).filter(function( memento ) {
        return !_.isEqual( memento.state, memento.target.toJSON() );
      }).map(function( memento ) {
        return memento.target;
      });

      // Save only if we have anything to save.
      if ( targets.length ) {
        // Don't save it as an array if there's only one object.
        this.save( extractSingleton( targets ) );
      }
    },

    /**
     * Time travel towards the forwardStack and away from the backwardStack.
     */
    timeTravel: function( forwardStack, backwardStack ) {
      // Don't do anything if we can't time travel further.
      if ( !forwardStack.length ) {
        return;
      }

      if ( this.current ) {
        backwardStack.push( this.current );
      }

      this.current = forwardStack.pop();

      var that = this;
      toArray( this.current ).forEach(function( memento ) {
        memento.restore();

        // Restore model references in mementos.
        if ( memento.target instanceof Backbone.Collection ) {
          that.reference( memento.target );
        }
      });

      return this;
    },

    undo: function() { return this.timeTravel( this.undoStack, this.redoStack ); },
    redo: function() { return this.timeTravel( this.redoStack, this.undoStack ); },

    clear: function() {
      this.undoStack = [];
      this.redoStack = [];
    },

    /**
     * Goes through the undo and redo stacks and rebuilds references to models.
     */
    reference: function( collection ) {
      this.undoStack.concat( this.redoStack ).forEach(function( state ) {
        toArray( state ).forEach(function( memento ) {
          memento.reference( collection );
        });
      });
    }
  };

  return EditorHistory;
});<commit_msg>Remove need for utility functions in EditorHistory by storing all states as arrays of Mementos.<commit_after>define([
  'underscore',
  'backbone',
  'models/memento'
], function( _, Backbone, Memento ) {
  'use strict';

  /**
   * A state is an array of Mementos.
   */
  function EditorHistory() {
    this.current = null;

    this.undoStack = [];
    this.redoStack = [];

    // Store a state to determine whether we've changed a model/collection.
    this.previousState = null;
  }

  /**
   * There are four types of history states:
   * - A Backbone.Model.
   * - A Backbone.Collection.
   * - An array of Backbone.Models.
   * - An array of Backbone.Collections.
   *
   * These states are all stored as an array of Mementos.
   */
  EditorHistory.prototype = {
    /**
     * Save the target's attributes.
     * Wipes the redo stack.
     */
    save: function( target ) {
      if ( this.current ) {
        this.undoStack.push( this.current );
      }

      this.current = this.snapshot( target );
      this.redoStack = [];
    },

    /**
     * Take a snapshot of the current state of the target.
     * Returns an array of mementos.
     */
    snapshot: function( target ) {
      if ( _.isArray( target ) ) {
        return target.map(function( element ) {
          return new Memento( element );
        });
      }

      return [ new Memento( target ) ];
    },

    /**
     * Begin tracking a target.
     */
    begin: function( target ) {
      // Can't track nothing.
      if ( _.isEmpty( target ) ) {
        return;
      }

      this.previousState = this.snapshot( target );
      // Save if we don't have a baseline history.
      if ( !this.undoStack.length ) {
        this.save( target );
      }
    },

    /**
     * Stop tracking a target and push changes to history if anything changed.
     */
    end: function() {
      if ( !this.previousState ) {
        return;
      }

      // Get all targets that have changed since begin() was called.
      var targets = this.previousState.filter(function( memento ) {
        return !_.isEqual( memento.state, memento.target.toJSON() );
      }).map(function( memento ) {
        return memento.target;
      });

      // Save only if we have anything to save.
      if ( targets.length ) {
        // Don't save it as an array if there's only one object.
        this.save( targets );
      }
    },

    /**
     * Time travel towards the forwardStack and away from the backwardStack.
     */
    timeTravel: function( forwardStack, backwardStack ) {
      // Don't do anything if we can't time travel further.
      if ( !forwardStack.length ) {
        return;
      }

      if ( this.current ) {
        backwardStack.push( this.current );
      }

      this.current = forwardStack.pop();

      var that = this;
      this.current.forEach(function( memento ) {
        memento.restore();

        // Restore model references in mementos.
        if ( memento.target instanceof Backbone.Collection ) {
          that.reference( memento.target );
        }
      });

      return this;
    },

    undo: function() { return this.timeTravel( this.undoStack, this.redoStack ); },
    redo: function() { return this.timeTravel( this.redoStack, this.undoStack ); },

    clear: function() {
      this.undoStack = [];
      this.redoStack = [];
    },

    /**
     * Goes through the undo and redo stacks and rebuilds references to models.
     */
    reference: function( collection ) {
      this.undoStack.concat( this.redoStack ).forEach(function( state ) {
        state.forEach(function( memento ) {
          memento.reference( collection );
        });
      });
    }
  };

  return EditorHistory;
});
{'fraction_non_alphanumeric': 0.11842954927030866, 'fraction_numerical': 0.0002582978173834431, 'mean_word_length': 1.995744680851064, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7458851', 'n_tokens_mistral': 2298, 'n_tokens_neox': 2227, 'n_words': 654}