Skip to content

Adding Diff Trimmed Lines #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 1, 2015
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ or

Returns a list of change objects (See below).

* `JsDiff.diffTrimmedLines(oldStr, newStr[, callback])` - diffs two blocks of text, comparing line by line, ignoring leading and trailing whitespace.

Returns a list of change objects (See below).

* `JsDiff.diffSentences(oldStr, newStr[, callback])` - diffs two blocks of text, comparing sentence by sentence.

Returns a list of change objects (See below).
Expand Down Expand Up @@ -166,4 +170,3 @@ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/kpdecker/jsdiff/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

34 changes: 28 additions & 6 deletions diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
*/
(function(global, undefined) {

var JsDiff = (function() {
/*jshint maxparams: 5*/
/*istanbul ignore next*/
Expand Down Expand Up @@ -174,7 +175,7 @@
editLength++;
}

// Performs the length of edit iteration. Is a bit fugly as this has to support the
// Performs the length of edit iteration. Is a bit fugly as this has to support the
// sync and async mode which is never fun. Loops over execEditLength until a value
// is produced.
var editLength = 1;
Expand Down Expand Up @@ -256,25 +257,44 @@
};

var LineDiff = new Diff();
LineDiff.tokenize = function(value) {

var TrimmedLineDiff = new Diff();
TrimmedLineDiff.ignoreTrim = true;

LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) {
var retLines = [],
lines = value.split(/^/m);

for(var i = 0; i < lines.length; i++) {
var line = lines[i],
lastLine = lines[i - 1];
lastLine = lines[i - 1],
lastLineLastChar = lastLine ? lastLine[lastLine.length - 1] : '';

// Merge lines that may contain windows new lines
if (line === '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
retLines[retLines.length - 1] += '\n';
if (line === '\n' && lastLine &&
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lastLine check isn't necessary since it was there to prevent a undefined is not an object error when reading from the array and that is now done when initializing the lastLineLastChar variable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch.

(lastLineLastChar === '\r' || lastLineLastChar === '\n')) {
if(this.ignoreTrim || lastLineLastChar === '\n'){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please match whitespace style. (Space after if and before {, no else on the same line)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean else should be on on the same line, it appears to be same line throughout the rest of the file, like this:

if () {

} else {

}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several places in applyPatch where these rules are not followed, I'll fix that as well.

//to avoid merging to \n\n, remove \n and add \r\n.
retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0,-1) + '\r\n';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this \r\n here but \n below?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.trim() in JS removes the newline characters, which puts the entire diff on one line, so I added the '\n' character after trimming every line but the last line. However, then when it tried to merge empty lines using:

retLines[retLines.length - 1] += '\n';

It would end up with '\n\n' on the end of the line, instead of '\r\n' I fixed this by slicing and re-adding the '\r\n'.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should use \r\n here. We are normalizing it to \n for all other lines so it's surprising to have it here and nowhere else. Instead, I would do nothing. I.e.

if (line === '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
  if (!this.ignoreTrim) {
    retLines[retLines.length - 1] += '\n';
  }
}

We can do this because we know that the line will always have a \n at the end due to the += operation below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes the tests I wrote to fail, which isn't necessarily a problem, but I followed the test set by the Line Diff tests, so I don't think it is exhibiting different behavior that that.

The final Line Diff test is:

it('should handle windows line endings', function() {
    var diffResult = diff.diffLines(
      'line\r\nold value \r\nline',
      'line\r\nnew value\r\nline');
    diff.convertChangesToXML(diffResult).should.equal('line\r\nnew value\r\nold value \r\nline');
  });

The final Trimmed Line Diff test is:

  it('should handle windows line endings', function() {
    var diffResult = diff.diffTrimmedLines(
      'line\r\nold value \r\nline',
      'line\r\nnew value\r\nline');
    diff.convertChangesToXML(diffResult).should.equal('line\r\nnew value\r\nold value\r\nline');
  });

Changing to your suggested code causes the second test to fail with the following value (no \r):

'line\n<ins>new value\n</ins><del>old value\n</del>line'

This is the only thing I'm not sure about, everything else should be updated in my most recent commit.

}
else{
retLines[retLines.length - 1] += '\n';
}
} else if (line) {
if(this.ignoreTrim){
line = line.trim();
//add a newline unless this is the last line.
if(!(i + 1 === lines.length)){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit pick: if (i < lines.length - 1) { save a few characters when minimized (or i + 1, the key thing is using the < operator rather than ! and ===)

line += '\n';
}
}
retLines.push(line);
}
}

return retLines;
};


var SentenceDiff = new Diff();
SentenceDiff.tokenize = function (value) {
return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/));
Expand Down Expand Up @@ -344,6 +364,8 @@
diffWords: function(oldStr, newStr, callback) { return WordDiff.diff(oldStr, newStr, callback); },
diffWordsWithSpace: function(oldStr, newStr, callback) { return WordWithSpaceDiff.diff(oldStr, newStr, callback); },
diffLines: function(oldStr, newStr, callback) { return LineDiff.diff(oldStr, newStr, callback); },
diffTrimmedLines: function(oldStr, newStr, callback) { return TrimmedLineDiff.diff(oldStr, newStr, callback); },

diffSentences: function(oldStr, newStr, callback) { return SentenceDiff.diff(oldStr, newStr, callback); },

diffCss: function(oldStr, newStr, callback) { return CssDiff.diff(oldStr, newStr, callback); },
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ <h1>Diff</h1>
<label><input type="radio" name="diff_type" value="diffChars" checked> Chars</label>
<label><input type="radio" name="diff_type" value="diffWords"> Words</label>
<label><input type="radio" name="diff_type" value="diffLines"> Lines</label>
<label><input type="radio" name="diff_type" value="diffTrimmedLines"> Trimmed Lines</label>
<label><input type="radio" name="diff_type" value="createPatch"> Patch</label>
<label><input type="radio" name="diff_type" value="applyPatch"> Merge</label>
</div>
Expand Down
30 changes: 30 additions & 0 deletions test/diffTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,36 @@ describe('#diffLines', function() {
});
});

// Line Diff
describe('#TrimmedLineDiff', function() {
it('should diff lines', function() {
var diffResult = diff.diffTrimmedLines(
'line\nold value\nline',
'line\nnew value\nline');
diff.convertChangesToXML(diffResult).should.equal('line\n<ins>new value\n</ins><del>old value\n</del>line');
});
it('should the same lines in diff', function() {
var diffResult = diff.diffTrimmedLines(
'line\nvalue\nline',
'line\nvalue\nline');
diff.convertChangesToXML(diffResult).should.equal('line\nvalue\nline');
});

it('should ignore shorespace', function() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you say something other than shorespace? This isn't really clear what the test is intending.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line 188, in the tests for #diffLines:

it('should handle shorespace', function() {

I was reversing that test. Shorespace refers to the leading and trailing whitespace. I think both tests should be titled similarly, so they can be easily juxtaposed. Should I switch both to say "leading and trailing whitespace" instead of "shorespace"?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think leading and trailing whitespace is a bit clearer. I didn't know what shorespace meant when I read it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I'll change that.

var diffResult = diff.diffTrimmedLines(
'line\nvalue \nline',
'line\nvalue\nline');
diff.convertChangesToXML(diffResult).should.equal('line\nvalue\nline');
});

it('should handle windows line endings', function() {
var diffResult = diff.diffTrimmedLines(
'line\r\nold value \r\nline',
'line\r\nnew value\r\nline');
diff.convertChangesToXML(diffResult).should.equal('line\r\n<ins>new value\r\n</ins><del>old value\r\n</del>line');
});
});

describe('#diffJson', function() {
it('should accept objects', function() {
diff.diffJson(
Expand Down