Skip to content

Instantly share code, notes, and snippets.

@max-mapper
Created March 29, 2011 21:36
Show Gist options
  • Select an option

  • Save max-mapper/893372 to your computer and use it in GitHub Desktop.

Select an option

Save max-mapper/893372 to your computer and use it in GitHub Desktop.

Revisions

  1. max-mapper revised this gist Mar 6, 2012. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -111,8 +111,8 @@
    clear: both;
    }
    </style>
    <link href="https://d3nwyuy0nl342s.cloudfront.net/ee07ce745ff25f59afd4abd5fef96243fa0bb640/stylesheets/bundle_common.css" media="screen" rel="stylesheet" type="text/css" />
    <link href="https://d3nwyuy0nl342s.cloudfront.net/ee07ce745ff25f59afd4abd5fef96243fa0bb640/stylesheets/bundle_github.css" media="screen" rel="stylesheet" type="text/css" />
    <link href="https://a248.e.akamai.net/assets.github.com/stylesheets/bundles/github-25d3866eecbdad76dacc91ca80d2434cc8f22c04.css" media="screen" rel="stylesheet" type="text/css" />
    <link href="https://a248.e.akamai.net/assets.github.com/stylesheets/bundles/github2-0362aac7f9b6a26a7d1f5bcb8c3d429a39498c73.css" media="screen" rel="stylesheet" type="text/css" />
    </head>
    <body>
    <div id="files" class="diff-view commentable">
  2. max-mapper revised this gist Mar 6, 2012. 1 changed file with 8 additions and 1 deletion.
    9 changes: 8 additions & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,14 @@
    <html>
    <head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
    <script src="https://github.com/maxogden/couchappspora/raw/master/_attachments/script/jquery.mustache.js" type="text/javascript"></script>
    <script type="text/javascript">
    /*
    Shameless port of a shameless port
    @defunkt => @janl => @aq
    See http://github.com/defunkt/mustache for more info.
    */(function(a){var b=function(){var a=function(){};return a.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":!0},context:{},render:function(a,b,c,d){d||(this.context=b,this.buffer=[]);if(!this.includes("",a)){if(d)return a;this.send(a);return}a=this.render_pragmas(a);var e=this.render_section(a,b,c);if(d)return this.render_tags(e,b,c,d);this.render_tags(e,b,c,d)},send:function(a){a!=""&&this.buffer.push(a)},render_pragmas:function(a){if(!this.includes("%",a))return a;var b=this,c=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag);return a.replace(c,function(a,c,d){if(!b.pragmas_implemented[c])throw{message:"This implementation of mustache doesn't understand the '"+c+"' pragma"};b.pragmas[c]={};if(d){var e=d.split("=");b.pragmas[c][e[0]]=e[1]}return""})},render_partial:function(a,b,c){a=this.trim(a);if(!c||c[a]===undefined)throw{message:"unknown_partial '"+a+"'"};return typeof b[a]!="object"?this.render(c[a],b,c,!0):this.render(c[a],b[a],c,!0)},render_section:function(a,b,c){if(!this.includes("#",a)&&!this.includes("^",a))return a;var d=this,e=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return a.replace(e,function(a,e,f,g){var h=d.find(f,b);if(e=="^")return!h||d.is_array(h)&&h.length===0?d.render(g,b,c,!0):"";if(e=="#")return d.is_array(h)?d.map(h,function(a){return d.render(g,d.create_context(a),c,!0)}).join(""):d.is_object(h)?d.render(g,d.create_context(h),c,!0):typeof h=="function"?h.call(b,g,function(a){return d.render(a,b,c,!0)}):h?d.render(g,b,c,!0):""})},render_tags:function(a,b,c,d){var e=this,f=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")},g=f(),h=function(a,d,h){switch(d){case"!":return"";case"=":return e.set_delimiters(h),g=f(),"";case">":return e.render_partial(h,b,c);case"{":return e.find(h,b);default:return e.escape(e.find(h,b))}},i=a.split("\n");for(var j=0;j<i.length;j++)i[j]=i[j].replace(g,h,this),d||this.send(i[j]);if(d)return i.join("\n")},set_delimiters:function(a){var b=a.split(" ");this.otag=this.escape_regex(b[0]),this.ctag=this.escape_regex(b[1])},escape_regex:function(a){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return a.replace(arguments.callee.sRE,"\\$1")},find:function(a,b){function c(a){return a===!1||a===0||a}a=this.trim(a);var d;return c(b[a])?d=b[a]:c(this.context[a])&&(d=this.context[a]),typeof d=="function"?d.apply(b):d!==undefined?d:""},includes:function(a,b){return b.indexOf(this.otag+a)!=-1},escape:function(a){return a=String(a===null?"":a),a.replace(/&(?!\w+;)|["<>\\]/g,function(a){switch(a){case"&":return"&amp;";case"\\":return"\\\\";case'"':return'"';case"<":return"&lt;";case">":return"&gt;";default:return a}})},create_context:function(a){if(this.is_object(a))return a;var b=".";this.pragmas["IMPLICIT-ITERATOR"]&&(b=this.pragmas["IMPLICIT-ITERATOR"].iterator);var c={};return c[b]=a,c},is_object:function(a){return a&&typeof a=="object"},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]"},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},map:function(a,b){if(typeof a.map=="function")return a.map(b);var c=[],d=a.length;for(var e=0;e<d;e++)c.push(b(a[e]));return c}},{name:"mustache.js",version:"0.3.1-dev",to_html:function(b,c,d,e){var f=new a;e&&(f.send=e),f.render(b,c,d);if(!e)return f.buffer.join("\n")},escape:function(b){return(new a).escape(b)}}}();a.mustache=function(a,c,d){return b.to_html(a,c,d)},a.mustache.escape=function(a){return b.escape(a)}})(jQuery);
    </script>
    <script src="https://github.com/ajaxorg/ace/raw/master/build/src/ace.js" type="text/javascript" charset="utf-8"></script>
    <script src="https://github.com/ajaxorg/ace/raw/master/build/src/mode-javascript.js" type="text/javascript" charset="utf-8"></script>
    <script src="https://github.com/kpdecker/jsdiff/raw/master/diff.js" type="text/javascript"></script>
  3. max-mapper revised this gist Mar 29, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    Uses [jsdiff](https://github.com/kpdecker/jsdiff), [ace editor](https://github.com/ajaxorg/ace) and github's css to do 100% client side line diffing
    Uses [jsdiff](https://github.com/kpdecker/jsdiff), [ace editor](https://github.com/ajaxorg/ace) and github's css to do 100% client side line diffing. You should [fork the code](http://gist.github.com/893372) and build a UI for word and character diffing (it's supported by the jsdiff library, I just haven't hooked it up yet).
  4. max-mapper revised this gist Mar 29, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    Uses "jsdiff":https://github.com/kpdecker/jsdiff, "ace editor":https://github.com/ajaxorg/ace and github's css to do 100% client side line diffing
    Uses [jsdiff](https://github.com/kpdecker/jsdiff), [ace editor](https://github.com/ajaxorg/ace) and github's css to do 100% client side line diffing
  5. max-mapper revised this gist Mar 29, 2011. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Uses "jsdiff":https://github.com/kpdecker/jsdiff, "ace editor":https://github.com/ajaxorg/ace and github's css to do 100% client side line diffing
  6. max-mapper revised this gist Mar 29, 2011. 1 changed file with 120 additions and 265 deletions.
    385 changes: 120 additions & 265 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,275 +1,130 @@
    /* See license.txt for terms of usage */

    /*
    * Text diff implementation.
    *
    * This library supports the following APIS:
    * JsDiff.diffChars: Character by character diff
    * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
    * JsDiff.diffLines: Line based diff
    *
    * JsDiff.diffCss: Diff targeted at CSS content
    *
    * These methods are based on the implementation proposed in
    * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
    * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
    */
    var JsDiff = (function() {
    function clonePath(path) {
    return { newPos: path.newPos, components: path.components.slice(0) };
    }
    function removeEmpty(array) {
    var ret = [];
    for (var i = 0; i < array.length; i++) {
    if (array[i]) {
    ret.push(array[i]);
    }
    }
    return ret;
    }
    function escapeHTML(s) {
    var n = s;
    n = n.replace(/&/g, "&amp;");
    n = n.replace(/</g, "&lt;");
    n = n.replace(/>/g, "&gt;");
    n = n.replace(/"/g, "&quot;");

    return n;
    }


    var fbDiff = function(ignoreWhitespace) {
    this.ignoreWhitespace = ignoreWhitespace;
    };
    fbDiff.prototype = {
    diff: function(oldString, newString) {
    // Handle the identity case (this is due to unrolling editLength == 0
    if (newString == oldString) {
    return [{ value: newString }];
    }
    if (!newString) {
    return [{ value: oldString, removed: true }];
    }
    if (!oldString) {
    return [{ value: newString, added: true }];
    }

    newString = this.tokenize(newString);
    oldString = this.tokenize(oldString);

    var newLen = newString.length, oldLen = oldString.length;
    var maxEditLength = newLen + oldLen;
    var bestPath = [{ newPos: -1, components: [] }];

    // Seed editLength = 0
    var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
    if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
    return bestPath[0].components;
    }

    for (var editLength = 1; editLength <= maxEditLength; editLength++) {
    for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
    var basePath;
    var addPath = bestPath[diagonalPath-1],
    removePath = bestPath[diagonalPath+1];
    oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
    if (addPath) {
    // No one else is going to attempt to use this value, clear it
    bestPath[diagonalPath-1] = undefined;
    }

    var canAdd = addPath && addPath.newPos+1 < newLen;
    var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
    if (!canAdd && !canRemove) {
    bestPath[diagonalPath] = undefined;
    continue;
    }

    // Select the diagonal that we want to branch from. We select the prior
    // path whose position in the new string is the farthest from the origin
    // and does not pass the bounds of the diff graph
    if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
    basePath = clonePath(removePath);
    this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
    } else {
    basePath = clonePath(addPath);
    basePath.newPos++;
    this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
    }

    var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);

    if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
    return basePath.components;
    } else {
    bestPath[diagonalPath] = basePath;
    }
    }
    }
    },

    pushComponent: function(components, value, added, removed) {
    var last = components[components.length-1];
    if (last && last.added === added && last.removed === removed) {
    // We need to clone here as the component clone operation is just
    // as shallow array clone
    components[components.length-1] =
    {value: this.join(last.value, value), added: added, removed: removed };
    } else {
    components.push({value: value, added: added, removed: removed });
    }
    },
    extractCommon: function(basePath, newString, oldString, diagonalPath) {
    var newLen = newString.length,
    oldLen = oldString.length,
    newPos = basePath.newPos,
    oldPos = newPos - diagonalPath;
    while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
    newPos++;
    oldPos++;
    <!DOCTYPE html>
    <html>
    <head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
    <script src="https://github.com/maxogden/couchappspora/raw/master/_attachments/script/jquery.mustache.js" type="text/javascript"></script>
    <script src="https://github.com/ajaxorg/ace/raw/master/build/src/ace.js" type="text/javascript" charset="utf-8"></script>
    <script src="https://github.com/ajaxorg/ace/raw/master/build/src/mode-javascript.js" type="text/javascript" charset="utf-8"></script>
    <script src="https://github.com/kpdecker/jsdiff/raw/master/diff.js" type="text/javascript"></script>
    <script type="text/diff" id="old"></script>
    <script type="text/diff" id="new"></script>
    <script type="text/mustache" id="diffTemplate">
    <tr data-position="8">
    <td id="L0L241" class="line_numbers linkable-line-number"></td>
    <td id="L0R241" class="line_numbers linkable-line-number"></td>
    <td width="100%">
    <b class="add-bubble" remote=""></b>
    <pre><div class="{{#added}}gi{{/added}}{{#removed}}gd{{/removed}}">{{value}}</div></pre>
    </td>
    </tr>
    </script>
    <script type="text/javascript">

    $(function() {

    function renderDiff() {
    $('#diffLines').html("");

    this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
    var oldFile = $( '#old' ).text(),
    newFile = $( '#new' ).text(),
    diffTemplate = $( '#diffTemplate' ).text();

    $( JsDiff.diffLines( oldFile, newFile ) ).map( function( i, diff ) {
    $('#diffLines').append( $.mustache(diffTemplate, diff) );
    } )
    }
    basePath.newPos = newPos;
    return oldPos;
    },

    equals: function(left, right) {
    var reWhitespace = /\S/;
    if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
    return true;
    } else {
    return left == right;
    }
    },
    join: function(left, right) {
    return left + right;
    },
    tokenize: function(value) {
    return value;
    }
    };

    var CharDiff = new fbDiff();

    var WordDiff = new fbDiff(true);
    WordDiff.tokenize = function(value) {
    return removeEmpty(value.split(/(\s+|\b)/g));
    };

    var CssDiff = new fbDiff(true);
    CssDiff.tokenize = function(value) {
    return removeEmpty(value.split(/([{}:;,]|\s+)/g));
    };

    var LineDiff = new fbDiff();
    LineDiff.tokenize = function(value) {
    var values = value.split(/\n/g),
    ret = [];
    for (var i = 0; i < values.length-1; i++) {
    ret.push(values[i] + "\n");
    }
    if (values.length) {
    ret.push(values[values.length-1]);
    }
    return ret;
    };

    return {
    diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
    diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
    diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },

    diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },

    createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
    var ret = [];

    ret.push("Index: " + fileName);
    ret.push("===================================================================");
    ret.push("--- " + fileName + "\t" + oldHeader);
    ret.push("+++ " + fileName + "\t" + newHeader);

    var diff = LineDiff.diff(oldStr, newStr);
    diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier

    var oldRangeStart = 0, newRangeStart = 0, curRange = [],
    oldLine = 1, newLine = 1;
    for (var i = 0; i < diff.length; i++) {
    var current = diff[i],
    lines = current.lines || current.value.replace(/\n$/, "").split("\n");
    current.lines = lines;

    var editor;

    $('#old').text($('#editor').html());

    function jsonParty() {
    $('#new').text(editor.getSession().toString());
    renderDiff();
    }

    editor = ace.edit("editor");
    jsonParty();

    $('textarea').keydown(function() {
    window.setTimeout(jsonParty, 100, true);
    });

    var JavaScriptMode = require("ace/mode/javascript").Mode;
    editor.getSession().setMode(new JavaScriptMode());

    if (current.added || current.removed) {
    if (!oldRangeStart) {
    var prev = diff[i-1];
    oldRangeStart = oldLine;
    newRangeStart = newLine;

    if (prev) {
    curRange.push.apply(curRange, prev.lines.slice(-4).map(function(entry) { return " " + entry; }));
    oldRangeStart -= 4;
    newRangeStart -= 4;
    }
    }
    curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; }));
    if (current.added) {
    newLine += lines.length;
    } else {
    oldLine += lines.length;
    }
    } else {
    if (oldRangeStart) {
    if (lines.length <= 8 && i < diff.length-1) {
    // Overlapping
    curRange.push.apply(curRange, lines.map(function(entry) { return " " + entry; }));
    } else {
    // end the range and output
    var contextSize = Math.min(lines.length, 4);
    ret.push(
    "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize)
    + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize)
    + " @@");
    ret.push.apply(ret, curRange);
    ret.push.apply(ret, lines.slice(0, contextSize).map(function(entry) { return " " + entry; }));

    oldRangeStart = 0; newRangeStart = 0; curRange = [];
    }
    }
    oldLine += lines.length;
    newLine += lines.length;
    }
    })

    </script>
    <style type="text/css" media="screen">
    body {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    color: #666;
    overflow: hidden;
    }
    if (diff.length > 1 && !/\n$/.test(diff[diff.length-2].value)) {
    ret.push("\\ No newline at end of file\n");
    h1 {
    margin-top: 0;
    }
    ul {
    list-style: none;
    padding: 0;
    margin: 0;
    }
    li {
    margin-bottom: 20px;
    clear: both;
    }
    label {
    font-size: 10px;
    font-weight: bold;
    text-transform: uppercase;
    display: block;
    margin-bottom: 3px;
    clear: both;
    }

    return ret.join("\n");
    },

    convertChangesToXML: function(changes){
    var ret = [];
    for ( var i = 0; i < changes.length; i++) {
    var change = changes[i];
    if (change.added) {
    ret.push("<ins>");
    } else if (change.removed) {
    ret.push("<del>");
    }

    ret.push(escapeHTML(change.value));
    #editor {
    width: 100%;
    height: 300px;
    margin: 0;
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    }

    if (change.added) {
    ret.push("</ins>");
    } else if (change.removed) {
    ret.push("</del>");
    }
    .formOutput {
    float: left;
    clear: both;
    }
    return ret.join("");
    }
    };
    })();

    if (typeof module !== "undefined") {
    module.exports = JsDiff;
    .htmlOutput {
    padding: 25px 0px 0px 0px;
    font-size: 12px;
    clear: both;
    }
    </style>
    <link href="https://d3nwyuy0nl342s.cloudfront.net/ee07ce745ff25f59afd4abd5fef96243fa0bb640/stylesheets/bundle_common.css" media="screen" rel="stylesheet" type="text/css" />
    <link href="https://d3nwyuy0nl342s.cloudfront.net/ee07ce745ff25f59afd4abd5fef96243fa0bb640/stylesheets/bundle_github.css" media="screen" rel="stylesheet" type="text/css" />
    </head>
    <body>
    <div id="files" class="diff-view commentable">
    <div id="diff-0" class="file">
    <div class="data highlight">
    <table cellpadding="0" cellspacing="0" width="100%">
    <tbody id="diffLines">

    </tbody>
    </table>
    </div>
    <div class="file-comments-place-holder" data-path="_attachments/pages/monocles.html"></div>
    </div>
    </div>
    <pre id="editor">{
    "edit" : "this text",
    "remove" : "and add lines",
    "to see" : "diffs!"
    }
    </pre>
    </body>
    </html>
  7. max-mapper created this gist Mar 29, 2011.
    275 changes: 275 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,275 @@
    /* See license.txt for terms of usage */

    /*
    * Text diff implementation.
    *
    * This library supports the following APIS:
    * JsDiff.diffChars: Character by character diff
    * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
    * JsDiff.diffLines: Line based diff
    *
    * JsDiff.diffCss: Diff targeted at CSS content
    *
    * These methods are based on the implementation proposed in
    * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
    * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
    */
    var JsDiff = (function() {
    function clonePath(path) {
    return { newPos: path.newPos, components: path.components.slice(0) };
    }
    function removeEmpty(array) {
    var ret = [];
    for (var i = 0; i < array.length; i++) {
    if (array[i]) {
    ret.push(array[i]);
    }
    }
    return ret;
    }
    function escapeHTML(s) {
    var n = s;
    n = n.replace(/&/g, "&amp;");
    n = n.replace(/</g, "&lt;");
    n = n.replace(/>/g, "&gt;");
    n = n.replace(/"/g, "&quot;");

    return n;
    }


    var fbDiff = function(ignoreWhitespace) {
    this.ignoreWhitespace = ignoreWhitespace;
    };
    fbDiff.prototype = {
    diff: function(oldString, newString) {
    // Handle the identity case (this is due to unrolling editLength == 0
    if (newString == oldString) {
    return [{ value: newString }];
    }
    if (!newString) {
    return [{ value: oldString, removed: true }];
    }
    if (!oldString) {
    return [{ value: newString, added: true }];
    }

    newString = this.tokenize(newString);
    oldString = this.tokenize(oldString);

    var newLen = newString.length, oldLen = oldString.length;
    var maxEditLength = newLen + oldLen;
    var bestPath = [{ newPos: -1, components: [] }];

    // Seed editLength = 0
    var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
    if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
    return bestPath[0].components;
    }

    for (var editLength = 1; editLength <= maxEditLength; editLength++) {
    for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
    var basePath;
    var addPath = bestPath[diagonalPath-1],
    removePath = bestPath[diagonalPath+1];
    oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
    if (addPath) {
    // No one else is going to attempt to use this value, clear it
    bestPath[diagonalPath-1] = undefined;
    }

    var canAdd = addPath && addPath.newPos+1 < newLen;
    var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
    if (!canAdd && !canRemove) {
    bestPath[diagonalPath] = undefined;
    continue;
    }

    // Select the diagonal that we want to branch from. We select the prior
    // path whose position in the new string is the farthest from the origin
    // and does not pass the bounds of the diff graph
    if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
    basePath = clonePath(removePath);
    this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
    } else {
    basePath = clonePath(addPath);
    basePath.newPos++;
    this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
    }

    var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);

    if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
    return basePath.components;
    } else {
    bestPath[diagonalPath] = basePath;
    }
    }
    }
    },

    pushComponent: function(components, value, added, removed) {
    var last = components[components.length-1];
    if (last && last.added === added && last.removed === removed) {
    // We need to clone here as the component clone operation is just
    // as shallow array clone
    components[components.length-1] =
    {value: this.join(last.value, value), added: added, removed: removed };
    } else {
    components.push({value: value, added: added, removed: removed });
    }
    },
    extractCommon: function(basePath, newString, oldString, diagonalPath) {
    var newLen = newString.length,
    oldLen = oldString.length,
    newPos = basePath.newPos,
    oldPos = newPos - diagonalPath;
    while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
    newPos++;
    oldPos++;

    this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
    }
    basePath.newPos = newPos;
    return oldPos;
    },

    equals: function(left, right) {
    var reWhitespace = /\S/;
    if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
    return true;
    } else {
    return left == right;
    }
    },
    join: function(left, right) {
    return left + right;
    },
    tokenize: function(value) {
    return value;
    }
    };

    var CharDiff = new fbDiff();

    var WordDiff = new fbDiff(true);
    WordDiff.tokenize = function(value) {
    return removeEmpty(value.split(/(\s+|\b)/g));
    };

    var CssDiff = new fbDiff(true);
    CssDiff.tokenize = function(value) {
    return removeEmpty(value.split(/([{}:;,]|\s+)/g));
    };

    var LineDiff = new fbDiff();
    LineDiff.tokenize = function(value) {
    var values = value.split(/\n/g),
    ret = [];
    for (var i = 0; i < values.length-1; i++) {
    ret.push(values[i] + "\n");
    }
    if (values.length) {
    ret.push(values[values.length-1]);
    }
    return ret;
    };

    return {
    diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
    diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
    diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },

    diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },

    createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
    var ret = [];

    ret.push("Index: " + fileName);
    ret.push("===================================================================");
    ret.push("--- " + fileName + "\t" + oldHeader);
    ret.push("+++ " + fileName + "\t" + newHeader);

    var diff = LineDiff.diff(oldStr, newStr);
    diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier

    var oldRangeStart = 0, newRangeStart = 0, curRange = [],
    oldLine = 1, newLine = 1;
    for (var i = 0; i < diff.length; i++) {
    var current = diff[i],
    lines = current.lines || current.value.replace(/\n$/, "").split("\n");
    current.lines = lines;

    if (current.added || current.removed) {
    if (!oldRangeStart) {
    var prev = diff[i-1];
    oldRangeStart = oldLine;
    newRangeStart = newLine;

    if (prev) {
    curRange.push.apply(curRange, prev.lines.slice(-4).map(function(entry) { return " " + entry; }));
    oldRangeStart -= 4;
    newRangeStart -= 4;
    }
    }
    curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; }));
    if (current.added) {
    newLine += lines.length;
    } else {
    oldLine += lines.length;
    }
    } else {
    if (oldRangeStart) {
    if (lines.length <= 8 && i < diff.length-1) {
    // Overlapping
    curRange.push.apply(curRange, lines.map(function(entry) { return " " + entry; }));
    } else {
    // end the range and output
    var contextSize = Math.min(lines.length, 4);
    ret.push(
    "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize)
    + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize)
    + " @@");
    ret.push.apply(ret, curRange);
    ret.push.apply(ret, lines.slice(0, contextSize).map(function(entry) { return " " + entry; }));

    oldRangeStart = 0; newRangeStart = 0; curRange = [];
    }
    }
    oldLine += lines.length;
    newLine += lines.length;
    }
    }
    if (diff.length > 1 && !/\n$/.test(diff[diff.length-2].value)) {
    ret.push("\\ No newline at end of file\n");
    }

    return ret.join("\n");
    },

    convertChangesToXML: function(changes){
    var ret = [];
    for ( var i = 0; i < changes.length; i++) {
    var change = changes[i];
    if (change.added) {
    ret.push("<ins>");
    } else if (change.removed) {
    ret.push("<del>");
    }

    ret.push(escapeHTML(change.value));

    if (change.added) {
    ret.push("</ins>");
    } else if (change.removed) {
    ret.push("</del>");
    }
    }
    return ret.join("");
    }
    };
    })();

    if (typeof module !== "undefined") {
    module.exports = JsDiff;
    }