Deprecated: Creation of dynamic property wpdb::$categories is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$post2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$link2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Return type of WP_Theme::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 554

Deprecated: Return type of WP_Theme::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 595

Deprecated: Return type of WP_Theme::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 535

Deprecated: Return type of WP_Theme::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 544

Deprecated: Return type of WP_REST_Request::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 960

Deprecated: Return type of WP_REST_Request::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 980

Deprecated: Return type of WP_REST_Request::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 992

Deprecated: Return type of WP_REST_Request::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 1003

Deprecated: Return type of WP_Block_List::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 151

Deprecated: Return type of WP_Block_List::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 175

Deprecated: Return type of WP_Block_List::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 164

Deprecated: Return type of WP_Block_List::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 186

Deprecated: Return type of WP_Block_List::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 138

Deprecated: Return type of WP_Block_List::offsetExists($index) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 75

Deprecated: Return type of WP_Block_List::offsetGet($index) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 89

Deprecated: Return type of WP_Block_List::offsetSet($index, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 110

Deprecated: Return type of WP_Block_List::offsetUnset($index) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 127

Deprecated: Return type of WP_Block_List::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 199

Deprecated: Creation of dynamic property Automattic\Jetpack\Connection\Manager::$error_handler is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-connection/src/class-manager.php on line 91

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Instagram_Gallery::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-instagram-gallery.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Mailchimp::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php on line 17

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 21

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 22

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php on line 20

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 24

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Subscribers::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php on line 15

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property WPCOM_Widget_Goodreads::$shelves is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/modules/widgets/goodreads.php on line 33

Deprecated: DateTime::__construct(): Passing null to parameter #1 ($datetime) of type string is deprecated in /var/www/twodee/blog/wp-includes/script-loader.php on line 348

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_classic_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 347

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 349

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block_side is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 350

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$panels_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 351

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$used_block_buttons is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 354

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198
» Keystrokes Patcher

teaching machines

Keystrokes Patcher

June 3, 2013 by . Filed under
Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383
keystrokes, public.

For our Keystrokes project, we grab “frames” for our text movie as snapshots of the file being edited. We don’t really want to pass the complete file around for every snapshot, so instead we store diffs between frames that allow us to reconstruct any particular frame beyond the first.

To produce frame 2, we apply diff 2 to frame 1. To produce frame 3, we apply diff 3 to (diff 2 to frame 1). To produce frame N, we apply diff N to (diff N – 1 to (… to frame 1)).

Since we take snapshots at every cursor change, the diffs are likely to be quite small, often a line or two. Grabbing a text movie from the web should be much faster compared to grabbing the complete set of snapshots.

The reconstruction process involves parsing the diff file and applying it just like the Unix patch utility does. What follows is my attempt at understanding the diff protocol and reconstructing patch. The Patcher class takes a complete frame and a patch (both stored as String lists to make indexing into the files easier) and applies the patch to produce the next complete frame.

package org.twodee.undiff;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.twodee.utilities.Utilities;

/**
 Patches a source text modified by changes, deletes, and appends.
 */
public class Patcher {
  private static final Pattern TRANSACTION_PATTERN = Pattern.compile("(\\d+)(?:,(\\d+))?([acd])(\\d+)(?:,(\\d+))?");

  private ArrayList<String> pre;
  private ArrayList<String> patch;
  private int iPatchLine;
  private int iPreLine;
  private ArrayList<String> post;

  /**
   Applies a patch to a source text.
   @param pre The source text, given as a list of lines.
   @param patch The patch text as produced by diff, given as a list of lines.
   @return The destination text, given as a list of lines.
   */
  public static ArrayList<String> patch(ArrayList<String> pre, ArrayList<String> patch) {
    Patcher patcher = new Patcher(pre, patch);
    patcher.apply();
    return patcher.post;
  }

  /*
   Not meant for public use.
   */
  private Patcher(ArrayList<String> pre, ArrayList<String> patch) {
    this.pre = pre;
    this.patch = patch;
  }

  private void apply() {
    // We start at the beginning of the patch and the pre. Post gets built up
    // as we advance through the patch, but it starts off empty.
    iPatchLine = 0;
    iPreLine = 0;
    post = new ArrayList<String>();

    // Read and apply the patch, one transaction at a time. Since not every
    // line may be altered by the transactions, before we apply a transaction,
    // we make sure all the lines previous to the lines the transaction affects
    // get copied over from pre to post.
    while (hasTransaction()) {
      Transaction transaction = readTransaction();
      appendPreLinesToPost(transaction.getPreStart());
      transaction.execute();
      iPreLine = transaction.getPreEnd() + 1;
    }

    // We also want to copy any lines after the last transaction left off up
    // to the end of the file.
    appendPreLinesToPost(pre.size());
  }

  /*
   Determines whether the patch contains another transaction to process. If
   there are any unvisited lines in the patch, we have more transactions.
   */
  private boolean hasTransaction() {
    return iPatchLine < patch.size();
  }

  /*
   Appends lines from pre to post.
   */
  private void appendPreLinesToPost(int to) {
    for (; iPreLine < to; ++iPreLine) {
      post.add(pre.get(iPreLine));
    }
  }

  /*
   Gets the next transaction that is to be executed. It does not execute the
   transaction. It does not advance iPatchLine.
   */
  private Transaction readTransaction() {
    Matcher matcher = TRANSACTION_PATTERN.matcher(patch.get(iPatchLine));

    if (!matcher.find()) {
      throw new RuntimeException("Bad transaction type");
    }

    Transaction transaction;
    String command = matcher.group(3);
    if (command.equals("a")) {
      transaction = new AppendTransaction();
    } else if (command.equals("c")) {
      transaction = new ChangeTransaction();
    } else {
      transaction = new DeleteTransaction();
    }

    int preStart = Integer.parseInt(matcher.group(1)) - 1;
    int preEnd = matcher.group(2) == null ? preStart : Integer.parseInt(matcher.group(2)) - 1;
    int postStart = Integer.parseInt(matcher.group(4)) - 1;
    int postEnd = matcher.group(5) == null ? postStart : Integer.parseInt(matcher.group(5)) - 1;
    transaction.setBounds(preStart, preEnd, postStart, postEnd);

    return transaction;
  }

  /*
   Represents a transaction, which turns ranges of lines in pre to ranges of
   lines in post.
   */
  private abstract class Transaction {
    public int preStart;
    public int preEnd;
    public int postStart;
    public int postEnd;

    public void setBounds(int preStart, int preEnd, int postStart, int postEnd) {
      this.preStart = preStart;
      this.preEnd = preEnd;
      this.postStart = postStart;
      this.postEnd = postEnd;
    }

    public int getPreStart() {
      return preStart;
    }

    public int getPreEnd() {
      return preEnd;
    }

    /*
     Executes this transaction and puts the patch line cursor at the start of
     the next transaction. After this method, we are ready to read the next
     transaction from the patch.
     */
    public abstract void execute();
  }

  /*
   Changes the specified pre lines into the specified post lines. A change
   transaction has the following representation in the patch:

     9c7,8
     < first line replace
     ---
     > first line to insert
     > second line to insert
   */
  private class ChangeTransaction extends Transaction {
    public void execute() {
      // Move past the transaction header, the pre lines on the chopping block,
      // and the --- separator.
      iPatchLine += (preEnd - preStart + 1) + 2;

      // Read each line to append from the patch, removing the leading "> ",
      // and insert the line into post.
      for (int i = postStart; i <= postEnd; ++i) {
        post.add(patch.get(iPatchLine).substring(2));
        ++iPatchLine;
      }
    }
  }

  /*
   Appends the enumerated lines into post. An append transaction has the
   following representation in the patch:

     9a10,11                                                                                                                            
     > first line to append
     > second line to append
   */
  private class AppendTransaction extends Transaction {
    public void execute() {
      // Move past the transaction header line.
      ++iPatchLine;

      // The transaction is responsible for dealing with pre lines in
      // [preStart, preEnd]. On append, only one pre line is in this range,
      // which we simply copy over.
      appendPreLinesToPost(preStart + 1);

      // Read each line to append from the patch, removing the leading "> ",
      // and insert the line into post.
      for (int i = postStart; i <= postEnd; ++i) {
        post.add(patch.get(iPatchLine).substring(2));
        ++iPatchLine;
      }
    }
  }

  /*
   Advances past all the deleted pre lines in this transaction in the patch.
   We don't really need to do anything more, since the lines in this
   transaction are not to appear in post.

   A delete transaction that removes two lines has this representation in the
   patch:

     1,2d0
     < first line to be removed
     < second line to be removed

   We move past the transaction line and the two pre lines, so that the next
   thing we read will be the transaction line.
   */
  private class DeleteTransaction extends Transaction {
    public void execute() {
      iPatchLine += (preEnd - preStart + 1) + 1;
    }
  }
}

My primary goal was to keep the methods short. It didn’t really make sense to create a stateful object, since Patcher has no meaningful lifespan. Thus, I wanted a static method. The code was too long for a single static method. Breaking it up meant passing a bunch of parameters around to the helper methods, and that didn’t sound fun. The solution was to make all instance-related tasks private. That way I could safely store the “global” data in instance variables that I didn’t have to pass around. (This is safe because I’m the only that can alter the Patcher instance I create.) I’m not sure I like the design, but it does have minimal conditional statements and the methods are pretty short. They’re not really individually testable.