Merge commit 'origin/jg/tree_context'
[GitX.git] / html / lib / diffHighlighter.js
blob0ced30ffa90f2b3831098f9bbf84d82ffc796c40
1 // If we run from a Safari instance, we don't
2 // have a Controller object. Instead, we fake it by
3 // using the console
4 if (typeof Controller == 'undefined') {
5         Controller = console;
6         Controller.log_ = console.log;
9 var highlightDiff = function(diff, element, callbacks) {
10         if (!diff || diff == "")
11                 return;
13         if (!callbacks)
14                 callbacks = {};
15         var start = new Date().getTime();
16         element.className = "diff"
17         var content = diff.escapeHTML().replace(/\t/g, "    ");;
19         var file_index = 0;
21         var startname = "";
22         var endname = "";
23         var line1 = "";
24         var line2 = "";
25         var diffContent = "";
26         var finalContent = "";
27         var lines = content.split('\n');
28         var binary = false;
29         var mode_change = false;
30         var old_mode = "";
31         var new_mode = "";
33         var hunk_start_line_1 = -1;
34         var hunk_start_line_2 = -1;
36         var header = false;
38         var finishContent = function()
39         {
40                 if (!file_index)
41                 {
42                         file_index++;
43                         return;
44                 }
46                 if (callbacks["newfile"])
47                         callbacks["newfile"](startname, endname, "file_index_" + (file_index - 1), mode_change, old_mode, new_mode);
49                 var title = startname;
50                 var binaryname = endname;
51                 if (endname == "/dev/null") {
52                         binaryname = startname;
53                         title = startname;
54                 }
55                 else if (startname == "/dev/null")
56                         title = endname;
57                 else if (startname != endname)
58                         title = startname + " renamed to " + endname;
59                 
60                 if (binary && endname == "/dev/null") { // in cases of a deleted binary file, there is no diff/file to display
61                         line1 = "";
62                         line2 = "";
63                         diffContent = "";
64                         file_index++;
65                         startname = "";
66                         endname = "";
67                         return;                         // so printing the filename in the file-list is enough
68                 }
70                 if (diffContent != "" || binary) {
71                         finalContent += '<div class="file" id="file_index_' + (file_index - 1) + '">' +
72                                 '<div class="fileHeader">' + title + '</div>';
73                 }
75                 if (!binary && (diffContent != ""))  {
76                         finalContent +=         '<div class="diffContent">' +
77                                                                 '<div class="lineno">' + line1 + "</div>" +
78                                                                 '<div class="lineno">' + line2 + "</div>" +
79                                                                 '<div class="lines">' + diffContent + "</div>" +
80                                                         '</div>';
81                 }
82                 else {
83                         if (binary) {
84                                 if (callbacks["binaryFile"])
85                                         finalContent += callbacks["binaryFile"](binaryname);
86                                 else
87                                         finalContent += "<div>Binary file differs</div>";
88                         }
89                 }
91                 if (diffContent != "" || binary)
92                         finalContent += '</div>';
94                 line1 = "";
95                 line2 = "";
96                 diffContent = "";
97                 file_index++;
98                 startname = "";
99                 endname = "";
100         }
101         for (var lineno = 0; lineno < lines.length; lineno++) {
102                 var l = lines[lineno];
104                 var firstChar = l.charAt(0);
106                 if (firstChar == "d" && l.charAt(1) == "i") {                   // "diff", i.e. new file, we have to reset everything
107                         header = true;                                          // diff always starts with a header
109                         finishContent(); // Finish last file
111                         binary = false;
112                         mode_change = false;
114                         if(match = l.match(/^diff --git (a\/)+(.*) (b\/)+(.*)$/)) {     // there are cases when we need to capture filenames from
115                                 startname = match[2];                                   // the diff line, like with mode-changes.
116                                 endname = match[4];                                     // this can get overwritten later if there is a diff or if
117                         }                                                               // the file is binary
119                         continue;
120                 }
122                 if (header) {
123                         if (firstChar == "n") {
124                                 if (l.match(/^new file mode .*$/))
125                                         startname = "/dev/null";
127                                 if (match = l.match(/^new mode (.*)$/)) {
128                                         mode_change = true;
129                                         new_mode = match[1];
130                                 }
131                                 continue;
132                         }
133                         if (firstChar == "o") {
134                                 if (match = l.match(/^old mode (.*)$/)) {
135                                         mode_change = true;
136                                         old_mode = match[1];
137                                 }
138                                 continue;
139                         }
141                         if (firstChar == "d") {
142                                 if (l.match(/^deleted file mode .*$/))
143                                         endname = "/dev/null";
144                                 continue;
145                         }
146                         if (firstChar == "-") {
147                                 if (match = l.match(/^--- (a\/)?(.*)$/))
148                                         startname = match[2];
149                                 continue;
150                         }
151                         if (firstChar == "+") {
152                                 if (match = l.match(/^\+\+\+ (b\/)?(.*)$/))
153                                         endname = match[2];
154                                 continue;
155                         }
156                         // If it is a complete rename, we don't know the name yet
157                         // We can figure this out from the 'rename from.. rename to.. thing
158                         if (firstChar == 'r')
159                         {
160                                 if (match = l.match(/^rename (from|to) (.*)$/))
161                                 {
162                                         if (match[1] == "from")
163                                                 startname = match[2];
164                                         else
165                                                 endname = match[2];
166                                 }
167                                 continue;
168                         }
169                         if (firstChar == "B") // "Binary files .. and .. differ"
170                         {
171                                 binary = true;
172                                 // We might not have a diff from the binary file if it's new.
173                                 // So, we use a regex to figure that out
175                                 if (match = l.match(/^Binary files (a\/)?(.*) and (b\/)?(.*) differ$/))
176                                 {
177                                         startname = match[2];
178                                         endname = match[4];
179                                 }
180                         }
182                         // Finish the header
183                         if (firstChar == "@")
184                                 header = false;
185                         else
186                                 continue;
187                 }
189                 if (firstChar == "+") {
190                         // Highlight trailing whitespace
191                         if (m = l.match(/\s+$/))
192                                 l = l.replace(/\s+$/, "<span class='whitespace'>" + m + "</span>");
194                         line1 += "\n";
195                         line2 += ++hunk_start_line_2 + "\n";
196                         diffContent += "<div class='addline'>" + l + "</div>";
197                 } else if (firstChar == "-") {
198                         line1 += ++hunk_start_line_1 + "\n";
199                         line2 += "\n";
200                         diffContent += "<div class='delline'>" + l + "</div>";
201                 } else if (firstChar == "@") {
202                         if (header) {
203                                 header = false;
204                         }
206                         if (m = l.match(/@@ \-([0-9]+),?\d* \+(\d+),?\d* @@/))
207                         {
208                                 hunk_start_line_1 = parseInt(m[1]) - 1;
209                                 hunk_start_line_2 = parseInt(m[2]) - 1;
210                         }
211                         line1 += "...\n";
212                         line2 += "...\n";
213                         diffContent += "<div class='hunkheader'>" + l + "</div>";
214                 } else if (firstChar == " ") {
215                         line1 += ++hunk_start_line_1 + "\n";
216                         line2 += ++hunk_start_line_2 + "\n";
217                         diffContent += "<div class='noopline'>" + l + "</div>";
218                 }
219         }
221         finishContent();
223         // This takes about 7ms
224         element.innerHTML = finalContent;
226         // TODO: Replace this with a performance pref call
227         if (false)
228                 Controller.log_("Total time:" + (new Date().getTime() - start));