From 66970564dc18c54b9d2fae052356850f372a3c81 Mon Sep 17 00:00:00 2001 From: Rodrigo Fernandes Date: Sat, 30 Aug 2014 17:29:27 +0100 Subject: [PATCH] initial word diff --- diff2html.js | 93 ++++++++++++++++++++--------- style.css | 18 +++++- template.html | 138 +++++++++++++++++++++++++++++++++++++++++++- word-diff-parser.js | 136 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 355 insertions(+), 30 deletions(-) create mode 100644 word-diff-parser.js diff --git a/diff2html.js b/diff2html.js index c8e4344..8fd76db 100644 --- a/diff2html.js +++ b/diff2html.js @@ -4,6 +4,10 @@ * Author: rtfpessoa * Date: Friday 29 August 2014 * + * Useful commands: + * git diff HEAD~1 + * git diff HEAD~1 --word-diff-regex='[[:alnum:]]+|[^[:space:]]' + * git diff HEAD~1 --word-diff-regex=. */ (function($, window) { @@ -18,13 +22,21 @@ DELETED: "delete" }; - var BLOCK_HEADER_LINE = "..." + var BLOCK_HEADER_LINE = "..."; - function Diff2Html() {} + var wordDiffParser = WordDiffParser.getInstance(); - Diff2Html.prototype.generatePrettyDiff = function(diffInput) { + function Diff2Html() {} + + Diff2Html.prototype.generatePrettyDiff = function(diffInput, wordDiffInput) { var diffFiles = splitByFile(diffInput); - var html = generateHtml(diffFiles); + var changedWords = wordDiffParser.generateChangedWords(wordDiffInput); + + if (!changedWords) { + changedWords = {}; + } + + var html = generateHtml(diffFiles, changedWords); return html; }; @@ -147,47 +159,74 @@ return files; }; - var generateHtml = function( diffFiles ) { - return diffFiles.map(function( file ) { + var generateHtml = function(diffFiles, changedWords) { + return diffFiles.map(function(file, index) { var fileHeader = file.oldName === file.newName ? file.newName : file.oldName + " -> " + file.newName return "
" + - "
" + - "
" + - " +" + file.addedLines + "" + - " -" + file.deletedLines + "" + - "
" + - "
" + fileHeader + "
" + - "
" + - "
" + - "
" + - " " + - " " + - generateFileHtml(file) + - " " + - "
" + - "
" + - "
" + - "
"; + "
" + + "
" + + " +" + file.addedLines + "" + + " -" + file.deletedLines + "" + + "
" + + "
" + fileHeader + "
" + + "
" + + "
" + + "
" + + " " + + " " + + generateFileHtml(file, changedWords[index]) + + " " + + "
" + + "
" + + "
" + + ""; }); }; - var generateFileHtml = function(file) { + var generateFileHtml = function(file, changedWords) { return file.blocks.map(function(block) { return block.lines.map(function(line) { - var oldLine = line.oldNumber ? line.oldNumber : "", - newLine = line.newNumber ? line.newNumber : ""; + var oldLine = line.oldNumber ? line.oldNumber : ""; + var newLine = line.newNumber ? line.newNumber : ""; + + var oldWords = []; + var newWords = []; + + if (oldLine && oldLine !== BLOCK_HEADER_LINE && + changedWords && changedWords.deletedWords && changedWords.deletedWords[oldLine]) { + oldWords = changedWords.deletedWords[oldLine]; + } + + if (newLine && newLine !== BLOCK_HEADER_LINE && + changedWords && changedWords.addedWords && changedWords.addedWords[newLine]) { + newWords = changedWords.addedWords[newLine]; + } + + //var newLine = escape(line.content); + var newCodeLine = line.content; + newCodeLine = markWords(oldWords, newCodeLine, "del"); + newCodeLine = markWords(newWords, newCodeLine, "ins"); return "" + " " + oldLine + "" + " " + newLine + "" + - " " + escape(line.content) + "" + + "
" + newCodeLine + "
" + ""; }).join("\n"); }).join("\n"); }; + var markWords = function(words, line, clazz) { + var newLine = line; + words.forEach(function(word) { + newLine = newLine.replace(word, "<" + clazz + ">" + word + ""); + }); + + return newLine; + }; + var escape = function(str) { return str .replace(/&/g, "&") diff --git a/style.css b/style.css index 4298065..33e2eb3 100644 --- a/style.css +++ b/style.css @@ -150,8 +150,6 @@ td, th { vertical-align: top; white-space: pre; overflow: visible; - padding-top: 4px; - padding-bottom: 4px; } .delete { @@ -172,3 +170,19 @@ td, th { color: rgba(0,0,0,0.3); border-color: #d5e4f2; } + +.code-line del { + display: inline-block; + margin-top: -1px; + text-decoration: none; + background-color: #ffb6ba; + font-weight: 700; +} + +.code-line ins { + display: inline-block; + margin-top: -1px; + text-decoration: none; + background-color: #97f295; + font-weight: 700; +} diff --git a/template.html b/template.html index 746cc33..dd5a384 100644 --- a/template.html +++ b/template.html @@ -13,6 +13,7 @@ + diff --git a/word-diff-parser.js b/word-diff-parser.js new file mode 100644 index 0000000..26938e2 --- /dev/null +++ b/word-diff-parser.js @@ -0,0 +1,136 @@ +/* + * + * Word Diff Parser (word-diff-parser.js) + * Author: rtfpessoa + * Date: Saturday 30 August 2014 + * + */ + +(function($, window) { + var ClassVariable; + + ClassVariable = (function() { + + function WordDiffParser() {} + + WordDiffParser.prototype.generateChangedWords = function(wordDiffInput) { + return wordDiffInput ? parseChangedWords(wordDiffInput) : null; + }; + + var parseChangedWords = function(wordDiffInput) { + var files = [], + currentFile = null, + oldLine = null, + newLine = null; + + wordDiffInput.split("\n").forEach(function(line) { + // Unmerged paths, and possibly other non-diffable files + // https://github.com/scottgonzalez/pretty-diff/issues/11 + // Also, remove some useless lines + if (!line || line.charAt(0) === "*" || + line.indexOf("new") === 0 || + line.indexOf("index") === 0 || + line.indexOf("---") === 0 || + line.indexOf("+++") === 0) { + return; + } + + if (line.indexOf("diff") === 0) { + /* File Diff Line */ + + /* add previous file(if exists) before start a new one */ + if (currentFile && + (currentFile.addedWords.length || currentFile.deletedWords.length)) { + files.push(currentFile); + currentFile = null; + } + + /* create file structure */ + currentFile = {}; + currentFile.addedWords = []; + currentFile.deletedWords = []; + + /* save file paths, before and after the diff */ + var values = /^diff --git a\/(\S+) b\/(\S+).*$/.exec(line); + currentFile.oldName = values[1]; + currentFile.newName = values[2]; + + } else if (line.indexOf("@@") === 0) { + /* Diff Block Header Line */ + + var values = /^(@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*/.exec(line); + + oldLine = values[2]; + newLine = values[4]; + + } else { + /* Regular Diff Line */ + + var addedWords = []; + if (addedWords = line.match(/\{\+(.+?)\+\}/g)) { + addedWords = addedWords.map(function(word) { + return cleanWordMatch(word); + }); + } else { + addedWords = []; + } + + var deletedWords = []; + if (deletedWords = line.match(/\[-(.+?)-\]/g)) { + deletedWords = deletedWords.map(function(word) { + return cleanWordMatch(word); + }); + } else { + deletedWords = []; + } + + if (!addedWords.length && !deletedWords.length) { + oldLine++; + newLine++; + } else { + if (addedWords.length) { + currentFile.addedWords[newLine] = addedWords; + newLine++; + } + + if (deletedWords.length) { + currentFile.deletedWords[oldLine] = deletedWords; + oldLine++; + } + } + } + }); + + /* add previous file(if exists) before start a new one */ + if (currentFile && + (currentFile.addedWords.length || currentFile.deletedWords.length)) { + files.push(currentFile); + currentFile = null; + } + + return files; + }; + + var cleanWordMatch = function(str) { + return str.substr(2, str.length - 4); + }; + + /* singleton pattern */ + var instance; + return { + getInstance: function() { + if (instance === undefined) { + instance = new WordDiffParser(); + /* Hide the constructor so the returned objected can't be new'd */ + instance.constructor = null; + } + return instance; + } + }; + + })(); + + window.WordDiffParser = ClassVariable; + return window.WordDiffParser; + +})(jQuery, window);