##// END OF EJS Templates
js: updated mergely to 3.4.1
marcink -
r347:99b6d323 default
parent child Browse files
Show More
@@ -1,41 +1,50 b''
1
2 1 /* required */
3 2 .mergely-column textarea { width: 80px; height: 200px; }
4 3 .mergely-column { float: left; }
5 4 .mergely-margin { float: left; }
6 5 .mergely-canvas { float: left; width: 28px; }
7 6
8 7 /* resizeable */
9 8 .mergely-resizer { width: 100%; height: 100%; }
10 9
11 10 /* style configuration */
12 11 .mergely-column { border: 1px solid #ccc; }
13 12 .mergely-active { border: 1px solid #a3d1ff; }
14 13
15 .mergely.a.rhs.start { border-top: 1px solid #ddffdd; }
16 .mergely.a.lhs.start.end,
17 .mergely.a.rhs.end { border-bottom: 1px solid #ddffdd; }
18 .mergely.a.rhs { background-color: #ddffdd; }
19 .mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #ddffdd; }
14 .mergely.a,.mergely.d,.mergely.c { color: #000; }
20 15
21 .mergely.d.lhs { background-color: #edc0c0; }
16 .mergely.a.rhs.start { border-top: 1px solid #a3d1ff; }
17 .mergely.a.lhs.start.end,
18 .mergely.a.rhs.end { border-bottom: 1px solid #a3d1ff; }
19 .mergely.a.rhs { background-color: #ddeeff; }
20 .mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #a3d1ff; }
21
22 .mergely.d.lhs { background-color: #ffe9e9; }
22 23 .mergely.d.lhs.end,
23 .mergely.d.rhs.start.end { border-bottom: 1px solid #ffdddd; }
24 .mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #ffdddd; }
25 .mergely.d.lhs.start { border-top: 1px solid #ffdddd; }
24 .mergely.d.rhs.start.end { border-bottom: 1px solid #f8e8e8; }
25 .mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #f8e8e8; }
26 .mergely.d.lhs.start { border-top: 1px solid #f8e8e8; }
26 27
27 28 .mergely.c.lhs,
28 29 .mergely.c.rhs { background-color: #fafafa; }
29 30 .mergely.c.lhs.start,
30 31 .mergely.c.rhs.start { border-top: 1px solid #a3a3a3; }
31 32 .mergely.c.lhs.end,
32 33 .mergely.c.rhs.end { border-bottom: 1px solid #a3a3a3; }
33 34
34 .mergely.ch.a.rhs { background-color: #ddffdd; }
35 .mergely.ch.d.lhs { background-color: #ffdddd; }
36
35 .mergely.ch.a.rhs { background-color: #ddeeff; }
36 .mergely.ch.d.lhs { background-color: #ffe9e9; text-decoration: line-through; color: red !important; }
37 37
38 38 .mergely-margin #compare-lhs-margin,
39 39 .mergely-margin #compare-rhs-margin {
40 40 cursor: pointer
41 41 }
42
43 .mergely.current.start { border-top: 1px solid #000 !important; }
44 .mergely.current.end { border-bottom: 1px solid #000 !important; }
45 .mergely.current.lhs.a.start.end,
46 .mergely.current.rhs.d.start.end { border-top: 0 !important; }
47 .mergely.current.CodeMirror-linenumber { color: #F9F9F9; font-weight: bold; background-color: #777; }
48
49 .CodeMirror-linenumber { cursor: pointer; }
50 .CodeMirror-code { color: #717171; } No newline at end of file
@@ -1,1606 +1,1669 b''
1 1 "use strict";
2 2
3 3 (function( window, document, jQuery, CodeMirror ){
4 4
5 5 var Mgly = {};
6 6
7 7 Mgly.Timer = function(){
8 8 var self = this;
9 9 self.start = function() { self.t0 = new Date().getTime(); };
10 10 self.stop = function() {
11 11 var t1 = new Date().getTime();
12 12 var d = t1 - self.t0;
13 13 self.t0 = t1;
14 14 return d;
15 15 };
16 16 self.start();
17 17 };
18 18
19 19 Mgly.ChangeExpression = new RegExp(/(^(?![><\-])*\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/);
20 20
21 21 Mgly.DiffParser = function(diff) {
22 22 var changes = [];
23 23 var change_id = 0;
24 24 // parse diff
25 25 var diff_lines = diff.split(/\n/);
26 26 for (var i = 0; i < diff_lines.length; ++i) {
27 27 if (diff_lines[i].length == 0) continue;
28 28 var change = {};
29 29 var test = Mgly.ChangeExpression.exec(diff_lines[i]);
30 30 if (test == null) continue;
31 31 // lines are zero-based
32 32 var fr = test[1].split(',');
33 33 change['lhs-line-from'] = fr[0] - 1;
34 34 if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1;
35 35 else change['lhs-line-to'] = fr[1] - 1;
36 36 var to = test[3].split(',');
37 37 change['rhs-line-from'] = to[0] - 1;
38 38 if (to.length == 1) change['rhs-line-to'] = to[0] - 1;
39 39 else change['rhs-line-to'] = to[1] - 1;
40 40 change['op'] = test[2];
41 41 changes[change_id++] = change;
42 42 }
43 43 return changes;
44 44 };
45 45
46 46 Mgly.sizeOf = function(obj) {
47 47 var size = 0, key;
48 48 for (key in obj) {
49 49 if (obj.hasOwnProperty(key)) size++;
50 50 }
51 51 return size;
52 52 };
53 53
54 54 Mgly.LCS = function(x, y) {
55 55 this.x = x.replace(/[ ]{1}/g, '\n');
56 56 this.y = y.replace(/[ ]{1}/g, '\n');
57 57 };
58 58
59 59 jQuery.extend(Mgly.LCS.prototype, {
60 60 clear: function() { this.ready = 0; },
61 61 diff: function(added, removed) {
62 62 var d = new Mgly.diff(this.x, this.y, {ignorews: false});
63 63 var changes = Mgly.DiffParser(d.normal_form());
64 64 var li = 0, lj = 0;
65 65 for (var i = 0; i < changes.length; ++i) {
66 66 var change = changes[i];
67 67 if (change.op != 'a') {
68 68 // find the starting index of the line
69 69 li = d.getLines('lhs').slice(0, change['lhs-line-from']).join(' ').length;
70 70 // get the index of the the span of the change
71 71 lj = change['lhs-line-to'] + 1;
72 72 // get the changed text
73 73 var lchange = d.getLines('lhs').slice(change['lhs-line-from'], lj).join(' ');
74 74 if (change.op == 'd') lchange += ' ';// include the leading space
75 75 else if (li > 0 && change.op == 'c') li += 1; // ignore leading space if not first word
76 76 // output the changed index and text
77 77 removed(li, li + lchange.length);
78 78 }
79 79 if (change.op != 'd') {
80 80 // find the starting index of the line
81 81 li = d.getLines('rhs').slice(0, change['rhs-line-from']).join(' ').length;
82 82 // get the index of the the span of the change
83 83 lj = change['rhs-line-to'] + 1;
84 84 // get the changed text
85 85 var rchange = d.getLines('rhs').slice(change['rhs-line-from'], lj).join(' ');
86 86 if (change.op == 'a') rchange += ' ';// include the leading space
87 87 else if (li > 0 && change.op == 'c') li += 1; // ignore leading space if not first word
88 88 // output the changed index and text
89 89 added(li, li + rchange.length);
90 90 }
91 91 }
92 92 }
93 93 });
94 94
95 95 Mgly.CodeifyText = function(settings) {
96 96 this._max_code = 0;
97 97 this._diff_codes = {};
98 98 this.ctxs = {};
99 99 this.options = {ignorews: false};
100 100 jQuery.extend(this, settings);
101 101 this.lhs = settings.lhs.split('\n');
102 102 this.rhs = settings.rhs.split('\n');
103 103 };
104 104
105 105 jQuery.extend(Mgly.CodeifyText.prototype, {
106 106 getCodes: function(side) {
107 107 if (!this.ctxs.hasOwnProperty(side)) {
108 108 var ctx = this._diff_ctx(this[side]);
109 109 this.ctxs[side] = ctx;
110 110 ctx.codes.length = Object.keys(ctx.codes).length;
111 111 }
112 112 return this.ctxs[side].codes;
113 113 },
114 114 getLines: function(side) {
115 115 return this.ctxs[side].lines;
116 116 },
117 117 _diff_ctx: function(lines) {
118 118 var ctx = {i: 0, codes: {}, lines: lines};
119 119 this._codeify(lines, ctx);
120 120 return ctx;
121 121 },
122 122 _codeify: function(lines, ctx) {
123 123 var code = this._max_code;
124 124 for (var i = 0; i < lines.length; ++i) {
125 125 var line = lines[i];
126 126 if (this.options.ignorews) {
127 127 line = line.replace(/\s+/g, '');
128 128 }
129 129 var aCode = this._diff_codes[line];
130 130 if (aCode != undefined) {
131 131 ctx.codes[i] = aCode;
132 132 }
133 133 else {
134 134 this._max_code++;
135 135 this._diff_codes[line] = this._max_code;
136 136 ctx.codes[i] = this._max_code;
137 137 }
138 138 }
139 139 }
140 140 });
141 141
142 142 Mgly.diff = function(lhs, rhs, options) {
143 143 var opts = jQuery.extend({ignorews: false}, options);
144 144 this.codeify = new Mgly.CodeifyText({
145 145 lhs: lhs,
146 146 rhs: rhs,
147 147 options: opts
148 148 });
149 149 var lhs_ctx = {
150 150 codes: this.codeify.getCodes('lhs'),
151 151 modified: {}
152 152 };
153 153 var rhs_ctx = {
154 154 codes: this.codeify.getCodes('rhs'),
155 155 modified: {}
156 156 };
157 157 var max = (lhs_ctx.codes.length + rhs_ctx.codes.length + 1);
158 158 var vector_d = [];
159 159 var vector_u = [];
160 160 this._lcs(lhs_ctx, 0, lhs_ctx.codes.length, rhs_ctx, 0, rhs_ctx.codes.length, vector_u, vector_d);
161 161 this._optimize(lhs_ctx);
162 162 this._optimize(rhs_ctx);
163 163 this.items = this._create_diffs(lhs_ctx, rhs_ctx);
164 164 };
165 165
166 166 jQuery.extend(Mgly.diff.prototype, {
167 167 changes: function() { return this.items; },
168 168 getLines: function(side) {
169 169 return this.codeify.getLines(side);
170 170 },
171 171 normal_form: function() {
172 172 var nf = '';
173 173 for (var index = 0; index < this.items.length; ++index) {
174 174 var item = this.items[index];
175 175 var lhs_str = '';
176 176 var rhs_str = '';
177 177 var change = 'c';
178 178 if (item.lhs_deleted_count == 0 && item.rhs_inserted_count > 0) change = 'a';
179 179 else if (item.lhs_deleted_count > 0 && item.rhs_inserted_count == 0) change = 'd';
180 180
181 181 if (item.lhs_deleted_count == 1) lhs_str = item.lhs_start + 1;
182 182 else if (item.lhs_deleted_count == 0) lhs_str = item.lhs_start;
183 183 else lhs_str = (item.lhs_start + 1) + ',' + (item.lhs_start + item.lhs_deleted_count);
184 184
185 185 if (item.rhs_inserted_count == 1) rhs_str = item.rhs_start + 1;
186 186 else if (item.rhs_inserted_count == 0) rhs_str = item.rhs_start;
187 187 else rhs_str = (item.rhs_start + 1) + ',' + (item.rhs_start + item.rhs_inserted_count);
188 188 nf += lhs_str + change + rhs_str + '\n';
189 189
190 190 var lhs_lines = this.getLines('lhs');
191 191 var rhs_lines = this.getLines('rhs');
192 192 if (rhs_lines && lhs_lines) {
193 193 var i;
194 194 // if rhs/lhs lines have been retained, output contextual diff
195 195 for (i = item.lhs_start; i < item.lhs_start + item.lhs_deleted_count; ++i) {
196 196 nf += '< ' + lhs_lines[i] + '\n';
197 197 }
198 198 if (item.rhs_inserted_count && item.lhs_deleted_count) nf += '---\n';
199 199 for (i = item.rhs_start; i < item.rhs_start + item.rhs_inserted_count; ++i) {
200 200 nf += '> ' + rhs_lines[i] + '\n';
201 201 }
202 202 }
203 203 }
204 204 return nf;
205 205 },
206 206 _lcs: function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
207 207 while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_lower] == rhs_ctx.codes[rhs_lower]) ) {
208 208 ++lhs_lower;
209 209 ++rhs_lower;
210 210 }
211 211 while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_upper - 1] == rhs_ctx.codes[rhs_upper - 1]) ) {
212 212 --lhs_upper;
213 213 --rhs_upper;
214 214 }
215 215 if (lhs_lower == lhs_upper) {
216 216 while (rhs_lower < rhs_upper) {
217 217 rhs_ctx.modified[ rhs_lower++ ] = true;
218 218 }
219 219 }
220 220 else if (rhs_lower == rhs_upper) {
221 221 while (lhs_lower < lhs_upper) {
222 222 lhs_ctx.modified[ lhs_lower++ ] = true;
223 223 }
224 224 }
225 225 else {
226 226 var sms = this._sms(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d);
227 227 this._lcs(lhs_ctx, lhs_lower, sms.x, rhs_ctx, rhs_lower, sms.y, vector_u, vector_d);
228 228 this._lcs(lhs_ctx, sms.x, lhs_upper, rhs_ctx, sms.y, rhs_upper, vector_u, vector_d);
229 229 }
230 230 },
231 231 _sms: function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
232 232 var max = lhs_ctx.codes.length + rhs_ctx.codes.length + 1;
233 233 var kdown = lhs_lower - rhs_lower;
234 234 var kup = lhs_upper - rhs_upper;
235 235 var delta = (lhs_upper - lhs_lower) - (rhs_upper - rhs_lower);
236 236 var odd = (delta & 1) != 0;
237 237 var offset_down = max - kdown;
238 238 var offset_up = max - kup;
239 239 var maxd = ((lhs_upper - lhs_lower + rhs_upper - rhs_lower) / 2) + 1;
240 240 vector_d[ offset_down + kdown + 1 ] = lhs_lower;
241 241 vector_u[ offset_up + kup - 1 ] = lhs_upper;
242 242 var ret = {x:0,y:0}, d, k, x, y;
243 243 for (d = 0; d <= maxd; ++d) {
244 244 for (k = kdown - d; k <= kdown + d; k += 2) {
245 245 if (k == kdown - d) {
246 246 x = vector_d[ offset_down + k + 1 ];//down
247 247 }
248 248 else {
249 249 x = vector_d[ offset_down + k - 1 ] + 1;//right
250 250 if ((k < (kdown + d)) && (vector_d[ offset_down + k + 1 ] >= x)) {
251 251 x = vector_d[ offset_down + k + 1 ];//down
252 252 }
253 253 }
254 254 y = x - k;
255 255 // find the end of the furthest reaching forward D-path in diagonal k.
256 256 while ((x < lhs_upper) && (y < rhs_upper) && (lhs_ctx.codes[x] == rhs_ctx.codes[y])) {
257 257 x++; y++;
258 258 }
259 259 vector_d[ offset_down + k ] = x;
260 260 // overlap ?
261 261 if (odd && (kup - d < k) && (k < kup + d)) {
262 262 if (vector_u[offset_up + k] <= vector_d[offset_down + k]) {
263 263 ret.x = vector_d[offset_down + k];
264 264 ret.y = vector_d[offset_down + k] - k;
265 265 return (ret);
266 266 }
267 267 }
268 268 }
269 269 // Extend the reverse path.
270 270 for (k = kup - d; k <= kup + d; k += 2) {
271 271 // find the only or better starting point
272 272 if (k == kup + d) {
273 273 x = vector_u[offset_up + k - 1]; // up
274 274 } else {
275 275 x = vector_u[offset_up + k + 1] - 1; // left
276 276 if ((k > kup - d) && (vector_u[offset_up + k - 1] < x))
277 277 x = vector_u[offset_up + k - 1]; // up
278 278 }
279 279 y = x - k;
280 280 while ((x > lhs_lower) && (y > rhs_lower) && (lhs_ctx.codes[x - 1] == rhs_ctx.codes[y - 1])) {
281 281 // diagonal
282 282 x--;
283 283 y--;
284 284 }
285 285 vector_u[offset_up + k] = x;
286 286 // overlap ?
287 287 if (!odd && (kdown - d <= k) && (k <= kdown + d)) {
288 288 if (vector_u[offset_up + k] <= vector_d[offset_down + k]) {
289 289 ret.x = vector_d[offset_down + k];
290 290 ret.y = vector_d[offset_down + k] - k;
291 291 return (ret);
292 292 }
293 293 }
294 294 }
295 295 }
296 296 throw "the algorithm should never come here.";
297 297 },
298 298 _optimize: function(ctx) {
299 299 var start = 0, end = 0;
300 while (start < ctx.length) {
301 while ((start < ctx.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) {
300 while (start < ctx.codes.length) {
301 while ((start < ctx.codes.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) {
302 302 start++;
303 303 }
304 304 end = start;
305 while ((end < ctx.length) && (ctx.modified[end] == true)) {
305 while ((end < ctx.codes.length) && (ctx.modified[end] == true)) {
306 306 end++;
307 307 }
308 if ((end < ctx.length) && (ctx.ctx[start] == ctx.codes[end])) {
308 if ((end < ctx.codes.length) && (ctx.codes[start] == ctx.codes[end])) {
309 309 ctx.modified[start] = false;
310 310 ctx.modified[end] = true;
311 311 }
312 312 else {
313 313 start = end;
314 314 }
315 315 }
316 316 },
317 317 _create_diffs: function(lhs_ctx, rhs_ctx) {
318 318 var items = [];
319 319 var lhs_start = 0, rhs_start = 0;
320 320 var lhs_line = 0, rhs_line = 0;
321 321
322 322 while (lhs_line < lhs_ctx.codes.length || rhs_line < rhs_ctx.codes.length) {
323 323 if ((lhs_line < lhs_ctx.codes.length) && (!lhs_ctx.modified[lhs_line])
324 324 && (rhs_line < rhs_ctx.codes.length) && (!rhs_ctx.modified[rhs_line])) {
325 325 // equal lines
326 326 lhs_line++;
327 327 rhs_line++;
328 328 }
329 329 else {
330 330 // maybe deleted and/or inserted lines
331 331 lhs_start = lhs_line;
332 332 rhs_start = rhs_line;
333 333
334 334 while (lhs_line < lhs_ctx.codes.length && (rhs_line >= rhs_ctx.codes.length || lhs_ctx.modified[lhs_line]))
335 335 lhs_line++;
336 336
337 337 while (rhs_line < rhs_ctx.codes.length && (lhs_line >= lhs_ctx.codes.length || rhs_ctx.modified[rhs_line]))
338 338 rhs_line++;
339 339
340 340 if ((lhs_start < lhs_line) || (rhs_start < rhs_line)) {
341 341 // store a new difference-item
342 342 items.push({
343 343 lhs_start: lhs_start,
344 344 rhs_start: rhs_start,
345 345 lhs_deleted_count: lhs_line - lhs_start,
346 346 rhs_inserted_count: rhs_line - rhs_start
347 347 });
348 348 }
349 349 }
350 350 }
351 351 return items;
352 352 }
353 353 });
354 354
355 355 Mgly.mergely = function(el, options) {
356 356 if (el) {
357 357 this.init(el, options);
358 358 }
359 359 };
360 360
361 361 jQuery.extend(Mgly.mergely.prototype, {
362 362 name: 'mergely',
363 363 //http://jupiterjs.com/news/writing-the-perfect-jquery-plugin
364 364 init: function(el, options) {
365 365 this.diffView = new Mgly.CodeMirrorDiffView(el, options);
366 366 this.bind(el);
367 367 },
368 368 bind: function(el) {
369 369 this.diffView.bind(el);
370 370 }
371 371 });
372 372
373 373 Mgly.CodeMirrorDiffView = function(el, options) {
374 374 CodeMirror.defineExtension('centerOnCursor', function() {
375 375 var coords = this.cursorCoords(null, 'local');
376 376 this.scrollTo(null,
377 377 (coords.y + coords.yBot) / 2 - (this.getScrollerElement().clientHeight / 2));
378 378 });
379 379 this.init(el, options);
380 380 };
381 381
382 382 jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
383 383 init: function(el, options) {
384 384 this.settings = {
385 385 autoupdate: true,
386 386 autoresize: true,
387 387 rhs_margin: 'right',
388 388 wrap_lines: false,
389 389 line_numbers: true,
390 390 lcs: true,
391 391 sidebar: true,
392 392 viewport: false,
393 393 ignorews: false,
394 394 fadein: 'fast',
395 395 editor_width: '650px',
396 396 editor_height: '400px',
397 397 resize_timeout: 500,
398 398 change_timeout: 150,
399 399 fgcolor: {a:'#4ba3fa',c:'#a3a3a3',d:'#ff7f7f', // color for differences (soft color)
400 400 ca:'#4b73ff',cc:'#434343',cd:'#ff4f4f'}, // color for currently active difference (bright color)
401 401 bgcolor: '#eee',
402 402 vpcolor: 'rgba(0, 0, 200, 0.5)',
403 403 lhs: function(setValue) { },
404 404 rhs: function(setValue) { },
405 405 loaded: function() { },
406 406 _auto_width: function(w) { return w; },
407 407 resize: function(init) {
408 408 var scrollbar = init ? 16 : 0;
409 409 var w = jQuery(el).parent().width() + scrollbar, h = 0;
410 410 if (this.width == 'auto') {
411 411 w = this._auto_width(w);
412 412 }
413 413 else {
414 414 w = this.width;
415 415 this.editor_width = w;
416 416 }
417 417 if (this.height == 'auto') {
418 418 //h = this._auto_height(h);
419 419 h = jQuery(el).parent().height();
420 420 }
421 421 else {
422 422 h = this.height;
423 423 this.editor_height = h;
424 424 }
425 425 var content_width = w / 2.0 - 2 * 8 - 8;
426 426 var content_height = h;
427 427 var self = jQuery(el);
428 428 self.find('.mergely-column').css({ width: content_width + 'px' });
429 429 self.find('.mergely-column, .mergely-canvas, .mergely-margin, .mergely-column textarea, .CodeMirror-scroll, .cm-s-default').css({ height: content_height + 'px' });
430 430 self.find('.mergely-canvas').css({ height: content_height + 'px' });
431 431 self.find('.mergely-column textarea').css({ width: content_width + 'px' });
432 432 self.css({ width: w, height: h, clear: 'both' });
433 433 if (self.css('display') == 'none') {
434 434 if (this.fadein != false) self.fadeIn(this.fadein);
435 435 else self.show();
436 436 if (this.loaded) this.loaded();
437 437 }
438 438 if (this.resized) this.resized();
439 439 },
440 440 _debug: '', //scroll,draw,calc,diff,markup,change
441 resized: function() { },
442 finished: function () { }
441 resized: function() { }
443 442 };
444 443 var cmsettings = {
445 444 mode: 'text/plain',
446 445 readOnly: false,
447 446 lineWrapping: this.settings.wrap_lines,
448 447 lineNumbers: this.settings.line_numbers,
449 448 gutters: ['merge', 'CodeMirror-linenumbers']
450 449 };
451 450 this.lhs_cmsettings = {};
452 451 this.rhs_cmsettings = {};
453 452
454 453 // save this element for faster queries
455 454 this.element = jQuery(el);
456 455
457 456 // save options if there are any
458 457 if (options && options.cmsettings) jQuery.extend(this.lhs_cmsettings, cmsettings, options.cmsettings, options.lhs_cmsettings);
459 458 if (options && options.cmsettings) jQuery.extend(this.rhs_cmsettings, cmsettings, options.cmsettings, options.rhs_cmsettings);
460 459 //if (options) jQuery.extend(this.settings, options);
461 460
462 461 // bind if the element is destroyed
463 462 this.element.bind('destroyed', jQuery.proxy(this.teardown, this));
464 463
465 464 // save this instance in jQuery data, binding this view to the node
466 465 jQuery.data(el, 'mergely', this);
467 466
468 467 this._setOptions(options);
469 468 },
470 469 unbind: function() {
471 470 if (this.changed_timeout != null) clearTimeout(this.changed_timeout);
472 471 this.editor[this.id + '-lhs'].toTextArea();
473 472 this.editor[this.id + '-rhs'].toTextArea();
474 473 jQuery(window).off('.mergely');
475 474 },
476 475 destroy: function() {
477 476 this.element.unbind('destroyed', this.teardown);
478 477 this.teardown();
479 478 },
480 479 teardown: function() {
481 480 this.unbind();
482 481 },
483 482 lhs: function(text) {
484 483 this.editor[this.id + '-lhs'].setValue(text);
485 484 },
486 485 rhs: function(text) {
487 486 this.editor[this.id + '-rhs'].setValue(text);
488 487 },
489 488 update: function() {
490 489 this._changing(this.id + '-lhs', this.id + '-rhs');
491 490 },
492 491 unmarkup: function() {
493 492 this._clear();
494 493 },
495 494 scrollToDiff: function(direction) {
496 495 if (!this.changes.length) return;
497 496 if (direction == 'next') {
498 497 this._current_diff = Math.min(++this._current_diff, this.changes.length - 1);
499 498 }
500 else {
499 else if (direction == 'prev') {
501 500 this._current_diff = Math.max(--this._current_diff, 0);
502 501 }
503 502 this._scroll_to_change(this.changes[this._current_diff]);
504 503 this._changed(this.id + '-lhs', this.id + '-rhs');
505 504 },
506 505 mergeCurrentChange: function(side) {
507 506 if (!this.changes.length) return;
508 507 if (side == 'lhs' && !this.lhs_cmsettings.readOnly) {
509 508 this._merge_change(this.changes[this._current_diff], 'rhs', 'lhs');
510 509 }
511 510 else if (side == 'rhs' && !this.rhs_cmsettings.readOnly) {
512 511 this._merge_change(this.changes[this._current_diff], 'lhs', 'rhs');
513 512 }
514 513 },
515 514 scrollTo: function(side, num) {
516 515 var le = this.editor[this.id + '-lhs'];
517 516 var re = this.editor[this.id + '-rhs'];
518 517 if (side == 'lhs') {
519 518 le.setCursor(num);
520 519 le.centerOnCursor();
521 520 }
522 521 else {
523 522 re.setCursor(num);
524 523 re.centerOnCursor();
525 524 }
526 525 },
527 526 _setOptions: function(opts) {
528 527 jQuery.extend(this.settings, opts);
529 528 if (this.settings.hasOwnProperty('rhs_margin')) {
530 529 // dynamically swap the margin
531 530 if (this.settings.rhs_margin == 'left') {
532 531 this.element.find('.mergely-margin:last-child').insertAfter(
533 532 this.element.find('.mergely-canvas'));
534 533 }
535 534 else {
536 535 var target = this.element.find('.mergely-margin').last();
537 536 target.appendTo(target.parent());
538 537 }
539 538 }
540 539 if (this.settings.hasOwnProperty('sidebar')) {
541 540 // dynamically enable sidebars
542 541 if (this.settings.sidebar) {
543 jQuery(this.element).find('.mergely-margin').css({display: 'block'});
542 this.element.find('.mergely-margin').css({display: 'block'});
544 543 }
545 544 else {
546 jQuery(this.element).find('.mergely-margin').css({display: 'none'});
545 this.element.find('.mergely-margin').css({display: 'none'});
547 546 }
548 547 }
549 548 var le, re;
550 549 if (this.settings.hasOwnProperty('wrap_lines')) {
551 550 if (this.editor) {
552 551 le = this.editor[this.id + '-lhs'];
553 552 re = this.editor[this.id + '-rhs'];
554 553 le.setOption('lineWrapping', this.settings.wrap_lines);
555 554 re.setOption('lineWrapping', this.settings.wrap_lines);
556 555 }
557 556 }
558 557 if (this.settings.hasOwnProperty('line_numbers')) {
559 558 if (this.editor) {
560 559 le = this.editor[this.id + '-lhs'];
561 560 re = this.editor[this.id + '-rhs'];
562 561 le.setOption('lineNumbers', this.settings.line_numbers);
563 562 re.setOption('lineNumbers', this.settings.line_numbers);
564 563 }
565 564 }
566 565 },
567 566 options: function(opts) {
568 567 if (opts) {
569 568 this._setOptions(opts);
570 569 if (this.settings.autoresize) this.resize();
571 570 if (this.settings.autoupdate) this.update();
572 571 }
573 572 else {
574 573 return this.settings;
575 574 }
576 575 },
577 576 swap: function() {
578 577 if (this.lhs_cmsettings.readOnly || this.rhs_cmsettings.readOnly) return;
579 578 var le = this.editor[this.id + '-lhs'];
580 579 var re = this.editor[this.id + '-rhs'];
581 580 var tmp = re.getValue();
582 581 re.setValue(le.getValue());
583 582 le.setValue(tmp);
584 583 },
585 584 merge: function(side) {
586 585 var le = this.editor[this.id + '-lhs'];
587 586 var re = this.editor[this.id + '-rhs'];
588 587 if (side == 'lhs' && !this.lhs_cmsettings.readOnly) le.setValue(re.getValue());
589 588 else if (!this.rhs_cmsettings.readOnly) re.setValue(le.getValue());
590 589 },
591 590 get: function(side) {
592 591 var ed = this.editor[this.id + '-' + side];
593 592 var t = ed.getValue();
594 593 if (t == undefined) return '';
595 594 return t;
596 595 },
597 596 clear: function(side) {
598 597 if (side == 'lhs' && this.lhs_cmsettings.readOnly) return;
599 598 if (side == 'rhs' && this.rhs_cmsettings.readOnly) return;
600 599 var ed = this.editor[this.id + '-' + side];
601 600 ed.setValue('');
602 601 },
603 602 cm: function(side) {
604 603 return this.editor[this.id + '-' + side];
605 604 },
606 605 search: function(side, query, direction) {
607 606 var le = this.editor[this.id + '-lhs'];
608 607 var re = this.editor[this.id + '-rhs'];
609 608 var editor;
610 609 if (side == 'lhs') editor = le;
611 610 else editor = re;
612 611 direction = (direction == 'prev') ? 'findPrevious' : 'findNext';
613 612 if ((editor.getSelection().length == 0) || (this.prev_query[side] != query)) {
614 613 this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false);
615 614 this.prev_query[side] = query;
616 615 }
617 616 var cursor = this.cursor[this.id];
618 617
619 618 if (cursor[direction]()) {
620 619 editor.setSelection(cursor.from(), cursor.to());
621 620 }
622 621 else {
623 622 cursor = editor.getSearchCursor(query, { line: 0, ch: 0 }, false);
624 623 }
625 624 },
626 625 resize: function() {
627 626 this.settings.resize();
628 627 this._changing(this.id + '-lhs', this.id + '-rhs');
629 628 this._set_top_offset(this.id + '-lhs');
630 629 },
631 630 diff: function() {
632 631 var lhs = this.editor[this.id + '-lhs'].getValue();
633 632 var rhs = this.editor[this.id + '-rhs'].getValue();
634 633 var d = new Mgly.diff(lhs, rhs, this.settings);
635 634 return d.normal_form();
636 635 },
637 636 bind: function(el) {
638 637 this.element.hide();//hide
639 638 this.id = jQuery(el).attr('id');
640 639 this.changed_timeout = null;
641 640 this.chfns = {};
642 641 this.chfns[this.id + '-lhs'] = [];
643 642 this.chfns[this.id + '-rhs'] = [];
644 643 this.prev_query = [];
645 644 this.cursor = [];
646 645 this._skipscroll = {};
647 646 this.change_exp = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/);
648 647 var merge_lhs_button;
649 648 var merge_rhs_button;
650 649 if (jQuery.button != undefined) {
651 650 //jquery ui
652 651 merge_lhs_button = '<button title="Merge left"></button>';
653 652 merge_rhs_button = '<button title="Merge right"></button>';
654 653 }
655 654 else {
656 655 // homebrew
657 656 var style = 'opacity:0.4;width:10px;height:15px;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;margin-top: -2px;';
658 657 merge_lhs_button = '<div style="' + style + '" title="Merge left">&lt;</div>';
659 658 merge_rhs_button = '<div style="' + style + '" title="Merge right">&gt;</div>';
660 659 }
661 660 this.merge_rhs_button = jQuery(merge_rhs_button);
662 661 this.merge_lhs_button = jQuery(merge_lhs_button);
663 662
664 663 // create the textarea and canvas elements
665 664 var height = this.settings.editor_height;
666 665 var width = this.settings.editor_width;
667 666 this.element.append(jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-margin" width="8px" height="' + height + '"></canvas></div>'));
668 667 this.element.append(jQuery('<div style="position:relative;width:' + width + '; height:' + height + '" id="' + this.id + '-editor-lhs" class="mergely-column"><textarea style="" id="' + this.id + '-lhs"></textarea></div>'));
669 668 this.element.append(jQuery('<div class="mergely-canvas" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-' + this.id + '-rhs-canvas" style="width:28px" width="28px" height="' + height + '"></canvas></div>'));
670 669 var rmargin = jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-rhs-margin" width="8px" height="' + height + '"></canvas></div>');
671 670 if (!this.settings.sidebar) {
672 671 this.element.find('.mergely-margin').css({display: 'none'});
673 672 }
674 673 if (this.settings.rhs_margin == 'left') {
675 674 this.element.append(rmargin);
676 675 }
677 676 this.element.append(jQuery('<div style="width:' + width + '; height:' + height + '" id="' + this.id + '-editor-rhs" class="mergely-column"><textarea style="" id="' + this.id + '-rhs"></textarea></div>'));
678 677 if (this.settings.rhs_margin != 'left') {
679 678 this.element.append(rmargin);
680 679 }
681 680 //codemirror
682 681 var cmstyle = '#' + this.id + ' .CodeMirror-gutter-text { padding: 5px 0 0 0; }' +
683 682 '#' + this.id + ' .CodeMirror-lines pre, ' + '#' + this.id + ' .CodeMirror-gutter-text pre { line-height: 18px; }' +
684 683 '.CodeMirror-linewidget { overflow: hidden; };';
685 684 if (this.settings.autoresize) {
686 685 cmstyle += this.id + ' .CodeMirror-scroll { height: 100%; overflow: auto; }';
687 686 }
688 687 // adjust the margin line height
689 688 cmstyle += '\n.CodeMirror { line-height: 18px; }';
690 689 jQuery('<style type="text/css">' + cmstyle + '</style>').appendTo('head');
691 690
692 691 //bind
693 var rhstx = jQuery('#' + this.id + '-rhs').get(0);
692 var rhstx = this.element.find('#' + this.id + '-rhs').get(0);
694 693 if (!rhstx) {
695 694 console.error('rhs textarea not defined - Mergely not initialized properly');
696 695 return;
697 696 }
698 var lhstx = jQuery('#' + this.id + '-lhs').get(0);
697 var lhstx = this.element.find('#' + this.id + '-lhs').get(0);
699 698 if (!rhstx) {
700 699 console.error('lhs textarea not defined - Mergely not initialized properly');
701 700 return;
702 701 }
703 702 var self = this;
704 703 this.editor = [];
705 704 this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea(lhstx, this.lhs_cmsettings);
706 705 this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea(rhstx, this.rhs_cmsettings);
707 706 this.editor[this.id + '-lhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); });
708 707 this.editor[this.id + '-lhs'].on('scroll', function(){ self._scrolling(self.id + '-lhs'); });
709 708 this.editor[this.id + '-rhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); });
710 709 this.editor[this.id + '-rhs'].on('scroll', function(){ self._scrolling(self.id + '-rhs'); });
711 710 // resize
712 711 if (this.settings.autoresize) {
713 712 var sz_timeout1 = null;
714 713 var sz = function(init) {
715 714 //self.em_height = null; //recalculate
716 715 if (self.settings.resize) self.settings.resize(init);
717 716 self.editor[self.id + '-lhs'].refresh();
718 717 self.editor[self.id + '-rhs'].refresh();
719 718 if (self.settings.autoupdate) {
720 719 self._changing(self.id + '-lhs', self.id + '-rhs');
721 720 }
722 721 };
723 722 jQuery(window).on('resize.mergely',
724 723 function () {
725 724 if (sz_timeout1) clearTimeout(sz_timeout1);
726 725 sz_timeout1 = setTimeout(sz, self.settings.resize_timeout);
727 726 }
728 727 );
729 728 sz(true);
730 729 }
730
731 // scrollToDiff() from gutter
732 function gutterClicked(side, line, ev) {
733 // The "Merge left/right" buttons are also located in the gutter.
734 // Don't interfere with them:
735 if (ev.target && (jQuery(ev.target).closest('.merge-button').length > 0)) {
736 return;
737 }
738
739 // See if the user clicked the line number of a difference:
740 var i, change;
741 for (i = 0; i < this.changes.length; i++) {
742 change = this.changes[i];
743 if (line >= change[side+'-line-from'] && line <= change[side+'-line-to']) {
744 this._current_diff = i;
745 // I really don't like this here - something about gutterClick does not
746 // like mutating editor here. Need to trigger the scroll to diff from
747 // a timeout.
748 setTimeout(function() { this.scrollToDiff(); }.bind(this), 10);
749 break;
750 }
751 }
752 }
753
754 this.editor[this.id + '-lhs'].on('gutterClick', function(cm, n, gutterClass, ev) {
755 gutterClicked.call(this, 'lhs', n, ev);
756 }.bind(this));
757
758 this.editor[this.id + '-rhs'].on('gutterClick', function(cm, n, gutterClass, ev) {
759 gutterClicked.call(this, 'rhs', n, ev);
760 }.bind(this));
761
731 762 //bind
732 763 var setv;
733 764 if (this.settings.lhs) {
734 765 setv = this.editor[this.id + '-lhs'].getDoc().setValue;
735 766 this.settings.lhs(setv.bind(this.editor[this.id + '-lhs'].getDoc()));
736 767 }
737 768 if (this.settings.rhs) {
738 769 setv = this.editor[this.id + '-rhs'].getDoc().setValue;
739 770 this.settings.rhs(setv.bind(this.editor[this.id + '-rhs'].getDoc()));
740 771 }
741 772 },
742 773
743 774 _scroll_to_change : function(change) {
744 775 if (!change) return;
745 776 var self = this;
746 777 var led = self.editor[self.id+'-lhs'];
747 778 var red = self.editor[self.id+'-rhs'];
748
749 var yref = led.getScrollerElement().offsetHeight * 0.5; // center between >0 and 1/2
750
751 779 // set cursors
752 780 led.setCursor(Math.max(change["lhs-line-from"],0), 0); // use led.getCursor().ch ?
753 781 red.setCursor(Math.max(change["rhs-line-from"],0), 0);
754
755 // using directly CodeMirror breaks canvas alignment
756 // var ly = led.charCoords({line: Math.max(change["lhs-line-from"],0), ch: 0}, "local").top;
757
758 // calculate scroll offset for current change. Warning: returns relative y position so we scroll to 0 first.
759 led.scrollTo(null, 0);
760 red.scrollTo(null, 0);
761 self._calculate_offsets(self.id+'-lhs', self.id+'-rhs', [change]);
762 led.scrollTo(null, Math.max(change["lhs-y-start"]-yref, 0));
763 red.scrollTo(null, Math.max(change["rhs-y-start"]-yref, 0));
764 // right pane should simply follows
782 led.scrollIntoView({line: change["lhs-line-to"]});
765 783 },
766 784
767 785 _scrolling: function(editor_name) {
768 786 if (this._skipscroll[editor_name] === true) {
769 787 // scrolling one side causes the other to event - ignore it
770 788 this._skipscroll[editor_name] = false;
771 789 return;
772 790 }
773 791 var scroller = jQuery(this.editor[editor_name].getScrollerElement());
774 792 if (this.midway == undefined) {
775 793 this.midway = (scroller.height() / 2.0 + scroller.offset().top).toFixed(2);
776 794 }
777 795 // balance-line
778 796 var midline = this.editor[editor_name].coordsChar({left:0, top:this.midway});
779 797 var top_to = scroller.scrollTop();
780 798 var left_to = scroller.scrollLeft();
781 799
782 800 this.trace('scroll', 'side', editor_name);
783 801 this.trace('scroll', 'midway', this.midway);
784 802 this.trace('scroll', 'midline', midline);
785 803 this.trace('scroll', 'top_to', top_to);
786 804 this.trace('scroll', 'left_to', left_to);
787 805
788 806 var editor_name1 = this.id + '-lhs';
789 807 var editor_name2 = this.id + '-rhs';
790 808
791 809 for (var name in this.editor) {
792 810 if (!this.editor.hasOwnProperty(name)) continue;
793 811 if (editor_name == name) continue; //same editor
794 812 var this_side = editor_name.replace(this.id + '-', '');
795 813 var other_side = name.replace(this.id + '-', '');
796 814 var top_adjust = 0;
797 815
798 816 // find the last change that is less than or within the midway point
799 817 // do not move the rhs until the lhs end point is >= the rhs end point.
800 818 var last_change = null;
801 819 var force_scroll = false;
802 820 for (var i = 0; i < this.changes.length; ++i) {
803 821 var change = this.changes[i];
804 822 if ((midline.line >= change[this_side+'-line-from'])) {
805 823 last_change = change;
806 824 if (midline.line >= last_change[this_side+'-line-to']) {
807 825 if (!change.hasOwnProperty(this_side+'-y-start') ||
808 826 !change.hasOwnProperty(this_side+'-y-end') ||
809 827 !change.hasOwnProperty(other_side+'-y-start') ||
810 828 !change.hasOwnProperty(other_side+'-y-end')){
811 829 // change outside of viewport
812 830 force_scroll = true;
813 831 }
814 832 else {
815 833 top_adjust +=
816 834 (change[this_side+'-y-end'] - change[this_side+'-y-start']) -
817 835 (change[other_side+'-y-end'] - change[other_side+'-y-start']);
818 836 }
819 837 }
820 838 }
821 839 }
822 840
823 841 var vp = this.editor[name].getViewport();
824 842 var scroll = true;
825 843 if (last_change) {
826 844 this.trace('scroll', 'last change before midline', last_change);
827 845 if (midline.line >= vp.from && midline <= vp.to) {
828 846 scroll = false;
829 847 }
830 848 }
831 849 this.trace('scroll', 'scroll', scroll);
832 850 if (scroll || force_scroll) {
833 851 // scroll the other side
834 852 this.trace('scroll', 'scrolling other side', top_to - top_adjust);
835 853 this._skipscroll[name] = true;//disable next event
836 854 this.editor[name].scrollTo(left_to, top_to - top_adjust);
837 855 }
838 856 else this.trace('scroll', 'not scrolling other side');
839 857
840 858 if (this.settings.autoupdate) {
841 859 var timer = new Mgly.Timer();
842 860 this._calculate_offsets(editor_name1, editor_name2, this.changes);
843 861 this.trace('change', 'offsets time', timer.stop());
844 862 this._markup_changes(editor_name1, editor_name2, this.changes);
845 863 this.trace('change', 'markup time', timer.stop());
846 864 this._draw_diff(editor_name1, editor_name2, this.changes);
847 865 this.trace('change', 'draw time', timer.stop());
848 866 }
849 867 this.trace('scroll', 'scrolled');
850 868 }
851 869 },
852 870 _changing: function(editor_name1, editor_name2) {
853 871 this.trace('change', 'changing-timeout', this.changed_timeout);
854 872 var self = this;
855 873 if (this.changed_timeout != null) clearTimeout(this.changed_timeout);
856 874 this.changed_timeout = setTimeout(function(){
857 875 var timer = new Mgly.Timer();
858 876 self._changed(editor_name1, editor_name2);
859 877 self.trace('change', 'total time', timer.stop());
860 878 }, this.settings.change_timeout);
861 879 },
862 880 _changed: function(editor_name1, editor_name2) {
863 881 this._clear();
864 882 this._diff(editor_name1, editor_name2);
865 883 },
866 884 _clear: function() {
867 885 var self = this, name, editor, fns, timer, i, change, l;
868 886
869 887 var clear_changes = function() {
870 888 timer = new Mgly.Timer();
871 889 for (i = 0, l = editor.lineCount(); i < l; ++i) {
872 890 editor.removeLineClass(i, 'background');
873 891 }
874 892 for (i = 0; i < fns.length; ++i) {
875 893 //var edid = editor.getDoc().id;
876 894 change = fns[i];
877 895 //if (change.doc.id != edid) continue;
878 896 if (change.lines.length) {
879 897 self.trace('change', 'clear text', change.lines[0].text);
880 898 }
881 899 change.clear();
882 900 }
883 901 editor.clearGutter('merge');
884 902 self.trace('change', 'clear time', timer.stop());
885 903 };
886 904
887 905 for (name in this.editor) {
888 906 if (!this.editor.hasOwnProperty(name)) continue;
889 907 editor = this.editor[name];
890 908 fns = self.chfns[name];
891 909 // clear editor changes
892 910 editor.operation(clear_changes);
893 911 }
894 912 self.chfns[name] = [];
895 913
896 914 var ex = this._draw_info(this.id + '-lhs', this.id + '-rhs');
897 915 var ctx_lhs = ex.clhs.get(0).getContext('2d');
898 916 var ctx_rhs = ex.crhs.get(0).getContext('2d');
899 917 var ctx = ex.dcanvas.getContext('2d');
900 918
901 919 ctx_lhs.beginPath();
902 920 ctx_lhs.fillStyle = this.settings.bgcolor;
903 921 ctx_lhs.strokeStyle = '#888';
904 922 ctx_lhs.fillRect(0, 0, 6.5, ex.visible_page_height);
905 923 ctx_lhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
906 924
907 925 ctx_rhs.beginPath();
908 926 ctx_rhs.fillStyle = this.settings.bgcolor;
909 927 ctx_rhs.strokeStyle = '#888';
910 928 ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height);
911 929 ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
912 930
913 931 ctx.beginPath();
914 932 ctx.fillStyle = '#fff';
915 933 ctx.fillRect(0, 0, this.draw_mid_width, ex.visible_page_height);
916 934 },
917 935 _diff: function(editor_name1, editor_name2) {
918 936 var lhs = this.editor[editor_name1].getValue();
919 937 var rhs = this.editor[editor_name2].getValue();
920 938 var timer = new Mgly.Timer();
921 939 var d = new Mgly.diff(lhs, rhs, this.settings);
922 940 this.trace('change', 'diff time', timer.stop());
923 941 this.changes = Mgly.DiffParser(d.normal_form());
924 942 this.trace('change', 'parse time', timer.stop());
925
926 943 if (this._current_diff === undefined && this.changes.length) {
927 944 // go to first difference on start-up
928 945 this._current_diff = 0;
929 946 this._scroll_to_change(this.changes[0]);
930 947 }
931
932 948 this.trace('change', 'scroll_to_change time', timer.stop());
933 949 this._calculate_offsets(editor_name1, editor_name2, this.changes);
934 950 this.trace('change', 'offsets time', timer.stop());
935 951 this._markup_changes(editor_name1, editor_name2, this.changes);
936 952 this.trace('change', 'markup time', timer.stop());
937 953 this._draw_diff(editor_name1, editor_name2, this.changes);
938 954 this.trace('change', 'draw time', timer.stop());
939 955 },
940 956 _parse_diff: function (editor_name1, editor_name2, diff) {
941 957 this.trace('diff', 'diff results:\n', diff);
942 958 var changes = [];
943 959 var change_id = 0;
944 960 // parse diff
945 961 var diff_lines = diff.split(/\n/);
946 962 for (var i = 0; i < diff_lines.length; ++i) {
947 963 if (diff_lines[i].length == 0) continue;
948 964 var change = {};
949 965 var test = this.change_exp.exec(diff_lines[i]);
950 966 if (test == null) continue;
951 967 // lines are zero-based
952 968 var fr = test[1].split(',');
953 969 change['lhs-line-from'] = fr[0] - 1;
954 970 if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1;
955 971 else change['lhs-line-to'] = fr[1] - 1;
956 972 var to = test[3].split(',');
957 973 change['rhs-line-from'] = to[0] - 1;
958 974 if (to.length == 1) change['rhs-line-to'] = to[0] - 1;
959 975 else change['rhs-line-to'] = to[1] - 1;
960 976 // TODO: optimize for changes that are adds/removes
961 977 if (change['lhs-line-from'] < 0) change['lhs-line-from'] = 0;
962 978 if (change['lhs-line-to'] < 0) change['lhs-line-to'] = 0;
963 979 if (change['rhs-line-from'] < 0) change['rhs-line-from'] = 0;
964 980 if (change['rhs-line-to'] < 0) change['rhs-line-to'] = 0;
965 981 change['op'] = test[2];
966 982 changes[change_id++] = change;
967 983 this.trace('diff', 'change', change);
968 984 }
969 985 return changes;
970 986 },
971 987 _get_viewport: function(editor_name1, editor_name2) {
972 988 var lhsvp = this.editor[editor_name1].getViewport();
973 989 var rhsvp = this.editor[editor_name2].getViewport();
974 990 return {from: Math.min(lhsvp.from, rhsvp.from), to: Math.max(lhsvp.to, rhsvp.to)};
975 991 },
976 992 _is_change_in_view: function(vp, change) {
977 993 if (!this.settings.viewport) return true;
978 994 if ((change['lhs-line-from'] < vp.from && change['lhs-line-to'] < vp.to) ||
979 995 (change['lhs-line-from'] > vp.from && change['lhs-line-to'] > vp.to) ||
980 996 (change['rhs-line-from'] < vp.from && change['rhs-line-to'] < vp.to) ||
981 997 (change['rhs-line-from'] > vp.from && change['rhs-line-to'] > vp.to)) {
982 998 // if the change is outside the viewport, skip
983 999 return false;
984 1000 }
985 1001 return true;
986 1002 },
987 1003 _set_top_offset: function (editor_name1) {
988 1004 // save the current scroll position of the editor
989 1005 var saveY = this.editor[editor_name1].getScrollInfo().top;
990 1006 // temporarily scroll to top
991 1007 this.editor[editor_name1].scrollTo(null, 0);
992 1008
993 1009 // this is the distance from the top of the screen to the top of the
994 1010 // content of the first codemirror editor
995 var topnode = jQuery('#' + this.id + ' .CodeMirror-measure').first();
1011 var topnode = this.element.find('.CodeMirror-measure').first();
996 1012 var top_offset = topnode.offset().top - 4;
997 1013 if(!top_offset) return false;
998 1014
999 1015 // restore editor's scroll position
1000 1016 this.editor[editor_name1].scrollTo(null, saveY);
1001 1017
1002 1018 this.draw_top_offset = 0.5 - top_offset;
1003 1019 return true;
1004 1020 },
1005 1021 _calculate_offsets: function (editor_name1, editor_name2, changes) {
1006 1022 if (this.em_height == null) {
1007 1023 if(!this._set_top_offset(editor_name1)) return; //try again
1008 1024 this.em_height = this.editor[editor_name1].defaultTextHeight();
1009 1025 if (!this.em_height) {
1010 1026 console.warn('Failed to calculate offsets, using 18 by default');
1011 1027 this.em_height = 18;
1012 1028 }
1013 1029 this.draw_lhs_min = 0.5;
1014 1030 var c = jQuery('#' + editor_name1 + '-' + editor_name2 + '-canvas');
1015 1031 if (!c.length) {
1016 1032 console.error('failed to find canvas', '#' + editor_name1 + '-' + editor_name2 + '-canvas');
1017 1033 }
1018 1034 if (!c.width()) {
1019 1035 console.error('canvas width is 0');
1020 1036 return;
1021 1037 }
1022 1038 this.draw_mid_width = jQuery('#' + editor_name1 + '-' + editor_name2 + '-canvas').width();
1023 1039 this.draw_rhs_max = this.draw_mid_width - 0.5; //24.5;
1024 1040 this.draw_lhs_width = 5;
1025 1041 this.draw_rhs_width = 5;
1026 1042 this.trace('calc', 'change offsets calculated', {top_offset: this.draw_top_offset, lhs_min: this.draw_lhs_min, rhs_max: this.draw_rhs_max, lhs_width: this.draw_lhs_width, rhs_width: this.draw_rhs_width});
1027 1043 }
1028 1044 var lhschc = this.editor[editor_name1].charCoords({line: 0});
1029 1045 var rhschc = this.editor[editor_name2].charCoords({line: 0});
1030 1046 var vp = this._get_viewport(editor_name1, editor_name2);
1031 1047
1032 1048 for (var i = 0; i < changes.length; ++i) {
1033 1049 var change = changes[i];
1034 1050
1035 1051 if (!this.settings.sidebar && !this._is_change_in_view(vp, change)) {
1036 1052 // if the change is outside the viewport, skip
1037 1053 delete change['lhs-y-start'];
1038 1054 delete change['lhs-y-end'];
1039 1055 delete change['rhs-y-start'];
1040 1056 delete change['rhs-y-end'];
1041 1057 continue;
1042 1058 }
1043 1059 var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
1044 1060 var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
1045 1061 var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
1046 1062 var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
1047 1063
1048 1064 var ls, le, rs, re, tls, tle, lhseh, lhssh, rhssh, rhseh;
1049 1065 if (this.editor[editor_name1].getOption('lineWrapping') || this.editor[editor_name2].getOption('lineWrapping')) {
1050 1066 // If using line-wrapping, we must get the height of the line
1051 1067 tls = this.editor[editor_name1].cursorCoords({line: llf, ch: 0}, 'page');
1052 1068 lhssh = this.editor[editor_name1].getLineHandle(llf);
1053 1069 ls = { top: tls.top, bottom: tls.top + lhssh.height };
1054 1070
1055 1071 tle = this.editor[editor_name1].cursorCoords({line: llt, ch: 0}, 'page');
1056 1072 lhseh = this.editor[editor_name1].getLineHandle(llt);
1057 1073 le = { top: tle.top, bottom: tle.top + lhseh.height };
1058 1074
1059 1075 tls = this.editor[editor_name2].cursorCoords({line: rlf, ch: 0}, 'page');
1060 1076 rhssh = this.editor[editor_name2].getLineHandle(rlf);
1061 1077 rs = { top: tls.top, bottom: tls.top + rhssh.height };
1062 1078
1063 1079 tle = this.editor[editor_name2].cursorCoords({line: rlt, ch: 0}, 'page');
1064 1080 rhseh = this.editor[editor_name2].getLineHandle(rlt);
1065 1081 re = { top: tle.top, bottom: tle.top + rhseh.height };
1066 1082 }
1067 1083 else {
1068 1084 // If not using line-wrapping, we can calculate the line position
1069 1085 ls = {
1070 1086 top: lhschc.top + llf * this.em_height,
1071 1087 bottom: lhschc.bottom + llf * this.em_height + 2
1072 1088 };
1073 1089 le = {
1074 1090 top: lhschc.top + llt * this.em_height,
1075 1091 bottom: lhschc.bottom + llt * this.em_height + 2
1076 1092 };
1077 1093 rs = {
1078 1094 top: rhschc.top + rlf * this.em_height,
1079 1095 bottom: rhschc.bottom + rlf * this.em_height + 2
1080 1096 };
1081 1097 re = {
1082 1098 top: rhschc.top + rlt * this.em_height,
1083 1099 bottom: rhschc.bottom + rlt * this.em_height + 2
1084 1100 };
1085 1101 }
1086 1102
1087 1103 if (change['op'] == 'a') {
1088 1104 // adds (right), normally start from the end of the lhs,
1089 1105 // except for the case when the start of the rhs is 0
1090 1106 if (rlf > 0) {
1091 1107 ls.top = ls.bottom;
1092 1108 ls.bottom += this.em_height;
1093 1109 le = ls;
1094 1110 }
1095 1111 }
1096 1112 else if (change['op'] == 'd') {
1097 1113 // deletes (left) normally finish from the end of the rhs,
1098 1114 // except for the case when the start of the lhs is 0
1099 1115 if (llf > 0) {
1100 1116 rs.top = rs.bottom;
1101 1117 rs.bottom += this.em_height;
1102 1118 re = rs;
1103 1119 }
1104 1120 }
1105 1121 change['lhs-y-start'] = this.draw_top_offset + ls.top;
1106 1122 if (change['op'] == 'c' || change['op'] == 'd') {
1107 1123 change['lhs-y-end'] = this.draw_top_offset + le.bottom;
1108 1124 }
1109 1125 else {
1110 1126 change['lhs-y-end'] = this.draw_top_offset + le.top;
1111 1127 }
1112 1128 change['rhs-y-start'] = this.draw_top_offset + rs.top;
1113 1129 if (change['op'] == 'c' || change['op'] == 'a') {
1114 1130 change['rhs-y-end'] = this.draw_top_offset + re.bottom;
1115 1131 }
1116 1132 else {
1117 1133 change['rhs-y-end'] = this.draw_top_offset + re.top;
1118 1134 }
1119 1135 this.trace('calc', 'change calculated', i, change);
1120 1136 }
1121 1137 return changes;
1122 1138 },
1123 1139 _markup_changes: function (editor_name1, editor_name2, changes) {
1124 jQuery('.merge-button').remove(); // clear
1140 this.element.find('.merge-button').remove(); //clear
1125 1141
1126 1142 var self = this;
1127 1143 var led = this.editor[editor_name1];
1128 1144 var red = this.editor[editor_name2];
1145 var current_diff = this._current_diff;
1129 1146
1130 1147 var timer = new Mgly.Timer();
1131 1148 led.operation(function() {
1132 1149 for (var i = 0; i < changes.length; ++i) {
1133 1150 var change = changes[i];
1134 1151 var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
1135 1152 var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
1136 1153 var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
1137 1154 var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
1138 1155
1139 1156 var clazz = ['mergely', 'lhs', change['op'], 'cid-' + i];
1140 1157 led.addLineClass(llf, 'background', 'start');
1141 1158 led.addLineClass(llt, 'background', 'end');
1142 1159
1160 if (current_diff == i) {
1161 if (llf != llt) {
1162 led.addLineClass(llf, 'background', 'current');
1163 }
1164 led.addLineClass(llt, 'background', 'current');
1165 }
1143 1166 if (llf == 0 && llt == 0 && rlf == 0) {
1144 1167 led.addLineClass(llf, 'background', clazz.join(' '));
1145 1168 led.addLineClass(llf, 'background', 'first');
1146 1169 }
1147 1170 else {
1148 1171 // apply change for each line in-between the changed lines
1149 1172 for (var j = llf; j <= llt; ++j) {
1150 1173 led.addLineClass(j, 'background', clazz.join(' '));
1151 1174 led.addLineClass(j, 'background', clazz.join(' '));
1152 1175 }
1153 1176 }
1154 1177
1155 1178 if (!red.getOption('readOnly')) {
1156 1179 // add widgets to lhs, if rhs is not read only
1157 1180 var rhs_button = self.merge_rhs_button.clone();
1158 1181 if (rhs_button.button) {
1159 1182 //jquery-ui support
1160 1183 rhs_button.button({icons: {primary: 'ui-icon-triangle-1-e'}, text: false});
1161 1184 }
1162 1185 rhs_button.addClass('merge-button');
1163 1186 rhs_button.attr('id', 'merge-rhs-' + i);
1164 1187 led.setGutterMarker(llf, 'merge', rhs_button.get(0));
1165 1188 }
1166 1189 }
1167 1190 });
1168 1191
1169 1192 var vp = this._get_viewport(editor_name1, editor_name2);
1170 1193
1171 1194 this.trace('change', 'markup lhs-editor time', timer.stop());
1172 1195 red.operation(function() {
1173 1196 for (var i = 0; i < changes.length; ++i) {
1174 1197 var change = changes[i];
1175 1198 var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
1176 1199 var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
1177 1200 var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
1178 1201 var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
1179 1202
1180 1203 if (!self._is_change_in_view(vp, change)) {
1181 1204 // if the change is outside the viewport, skip
1182 1205 continue;
1183 1206 }
1184 1207
1185 1208 var clazz = ['mergely', 'rhs', change['op'], 'cid-' + i];
1186 1209 red.addLineClass(rlf, 'background', 'start');
1187 1210 red.addLineClass(rlt, 'background', 'end');
1188 1211
1212 if (current_diff == i) {
1213 if (rlf != rlt) {
1214 red.addLineClass(rlf, 'background', 'current');
1215 }
1216 red.addLineClass(rlt, 'background', 'current');
1217 }
1189 1218 if (rlf == 0 && rlt == 0 && llf == 0) {
1190 1219 red.addLineClass(rlf, 'background', clazz.join(' '));
1191 1220 red.addLineClass(rlf, 'background', 'first');
1192 1221 }
1193 1222 else {
1194 1223 // apply change for each line in-between the changed lines
1195 1224 for (var j = rlf; j <= rlt; ++j) {
1196 1225 red.addLineClass(j, 'background', clazz.join(' '));
1197 1226 red.addLineClass(j, 'background', clazz.join(' '));
1198 1227 }
1199 1228 }
1200 1229
1201 1230 if (!led.getOption('readOnly')) {
1202 1231 // add widgets to rhs, if lhs is not read only
1203 1232 var lhs_button = self.merge_lhs_button.clone();
1204 1233 if (lhs_button.button) {
1205 1234 //jquery-ui support
1206 1235 lhs_button.button({icons: {primary: 'ui-icon-triangle-1-w'}, text: false});
1207 1236 }
1208 1237 lhs_button.addClass('merge-button');
1209 1238 lhs_button.attr('id', 'merge-lhs-' + i);
1210 1239 red.setGutterMarker(rlf, 'merge', lhs_button.get(0));
1211 1240 }
1212 1241 }
1213 1242 });
1214 1243 this.trace('change', 'markup rhs-editor time', timer.stop());
1215 1244
1216 1245 // mark text deleted, LCS changes
1217 1246 var marktext = [], i, j, k, p;
1218 1247 for (i = 0; this.settings.lcs && i < changes.length; ++i) {
1219 1248 var change = changes[i];
1220 1249 var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
1221 1250 var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
1222 1251 var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
1223 1252 var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
1224 1253
1225 1254 if (!this._is_change_in_view(vp, change)) {
1226 1255 // if the change is outside the viewport, skip
1227 1256 continue;
1228 1257 }
1229 1258 if (change['op'] == 'd') {
1230 1259 // apply delete to cross-out (left-hand side only)
1231 1260 var from = llf;
1232 1261 var to = llt;
1233 1262 var to_ln = led.lineInfo(to);
1234 1263 if (to_ln) {
1235 1264 marktext.push([led, {line:from, ch:0}, {line:to, ch:to_ln.text.length}, {className: 'mergely ch d lhs'}]);
1236 1265 }
1237 1266 }
1238 1267 else if (change['op'] == 'c') {
1239 1268 // apply LCS changes to each line
1240 1269 for (j = llf, k = rlf, p = 0;
1241 1270 ((j >= 0) && (j <= llt)) || ((k >= 0) && (k <= rlt));
1242 1271 ++j, ++k) {
1243 1272 var lhs_line, rhs_line;
1244 1273 if (k + p > rlt) {
1245 1274 // lhs continues past rhs, mark lhs as deleted
1246 1275 lhs_line = led.getLine( j );
1247 1276 marktext.push([led, {line:j, ch:0}, {line:j, ch:lhs_line.length}, {className: 'mergely ch d lhs'}]);
1248 1277 continue;
1249 1278 }
1250 1279 if (j + p > llt) {
1251 1280 // rhs continues past lhs, mark rhs as added
1252 1281 rhs_line = red.getLine( k );
1253 1282 marktext.push([red, {line:k, ch:0}, {line:k, ch:rhs_line.length}, {className: 'mergely ch a rhs'}]);
1254 1283 continue;
1255 1284 }
1256 1285 lhs_line = led.getLine( j );
1257 1286 rhs_line = red.getLine( k );
1258 1287 var lcs = new Mgly.LCS(lhs_line, rhs_line);
1259 1288 lcs.diff(
1260 1289 function added (from, to) {
1261 1290 marktext.push([red, {line:k, ch:from}, {line:k, ch:to}, {className: 'mergely ch a rhs'}]);
1262 1291 },
1263 1292 function removed (from, to) {
1264 1293 marktext.push([led, {line:j, ch:from}, {line:j, ch:to}, {className: 'mergely ch d lhs'}]);
1265 1294 }
1266 1295 );
1267 1296 }
1268 1297 }
1269 1298 }
1270 1299 this.trace('change', 'LCS marktext time', timer.stop());
1271 1300
1272 1301 // mark changes outside closure
1273 1302 led.operation(function() {
1274 1303 // apply lhs markup
1275 1304 for (var i = 0; i < marktext.length; ++i) {
1276 1305 var m = marktext[i];
1277 1306 if (m[0].doc.id != led.getDoc().id) continue;
1278 1307 self.chfns[self.id + '-lhs'].push(m[0].markText(m[1], m[2], m[3]));
1279 1308 }
1280 1309 });
1281 1310 red.operation(function() {
1282 1311 // apply lhs markup
1283 1312 for (var i = 0; i < marktext.length; ++i) {
1284 1313 var m = marktext[i];
1285 1314 if (m[0].doc.id != red.getDoc().id) continue;
1286 1315 self.chfns[self.id + '-rhs'].push(m[0].markText(m[1], m[2], m[3]));
1287 1316 }
1288 1317 });
1318
1289 1319 this.trace('change', 'LCS markup time', timer.stop());
1290 1320
1291 1321 // merge buttons
1292 1322 var ed = {lhs:led, rhs:red};
1293 jQuery('.merge-button').on('click', function(ev){
1323 this.element.find('.merge-button').on('click', function(ev){
1294 1324 // side of mouseenter
1295 1325 var side = 'rhs';
1296 1326 var oside = 'lhs';
1297 1327 var parent = jQuery(this).parents('#' + self.id + '-editor-lhs');
1298 1328 if (parent.length) {
1299 1329 side = 'lhs';
1300 1330 oside = 'rhs';
1301 1331 }
1302 1332 var pos = ed[side].coordsChar({left:ev.pageX, top:ev.pageY});
1303 1333
1304 1334 // get the change id
1305 1335 var cid = null;
1306 1336 var info = ed[side].lineInfo(pos.line);
1307 1337 jQuery.each(info.bgClass.split(' '), function(i, clazz) {
1308 1338 if (clazz.indexOf('cid-') == 0) {
1309 1339 cid = parseInt(clazz.split('-')[1], 10);
1310 1340 return false;
1311 1341 }
1312 1342 });
1313 1343 var change = self.changes[cid];
1314 1344 self._merge_change(change, side, oside);
1315 1345 return false;
1316 1346 });
1347
1348 // gutter markup
1349 var lhsLineNumbers = $('#mergely-lhs ~ .CodeMirror').find('.CodeMirror-linenumber');
1350 var rhsLineNumbers = $('#mergely-rhs ~ .CodeMirror').find('.CodeMirror-linenumber');
1351 rhsLineNumbers.removeClass('mergely current');
1352 lhsLineNumbers.removeClass('mergely current');
1353 for (var i = 0; i < changes.length; ++i) {
1354 if (current_diff == i && change.op !== 'd') {
1355 var change = changes[i];
1356 var j, jf = change['rhs-line-from'], jt = change['rhs-line-to'] + 1;
1357 for (j = jf; j < jt; j++) {
1358 var n = (j + 1).toString();
1359 rhsLineNumbers
1360 .filter(function(i, node) { return $(node).text() === n; })
1361 .addClass('mergely current');
1362 }
1363 }
1364 if (current_diff == i && change.op !== 'a') {
1365 var change = changes[i];
1366 jf = change['lhs-line-from'], jt = change['lhs-line-to'] + 1;
1367 for (j = jf; j < jt; j++) {
1368 var n = (j + 1).toString();
1369 lhsLineNumbers
1370 .filter(function(i, node) { return $(node).text() === n; })
1371 .addClass('mergely current');
1372 }
1373 }
1374 }
1375
1317 1376 this.trace('change', 'markup buttons time', timer.stop());
1318 1377 },
1319 1378 _merge_change : function(change, side, oside) {
1320 1379 if (!change) return;
1321 1380 var led = this.editor[this.id+'-lhs'];
1322 1381 var red = this.editor[this.id+'-rhs'];
1323 1382 var ed = {lhs:led, rhs:red};
1324 1383 var i, from, to;
1325 1384
1326 1385 var text = ed[side].getRange(
1327 1386 CodeMirror.Pos(change[side + '-line-from'], 0),
1328 1387 CodeMirror.Pos(change[side + '-line-to'] + 1, 0));
1329 1388
1330 1389 if (change['op'] == 'c') {
1331 1390 ed[oside].replaceRange(text,
1332 1391 CodeMirror.Pos(change[oside + '-line-from'], 0),
1333 1392 CodeMirror.Pos(change[oside + '-line-to'] + 1, 0));
1334 1393 }
1335 1394 else if (side == 'rhs') {
1336 1395 if (change['op'] == 'a') {
1337 1396 ed[oside].replaceRange(text,
1338 1397 CodeMirror.Pos(change[oside + '-line-from'] + 1, 0),
1339 1398 CodeMirror.Pos(change[oside + '-line-to'] + 1, 0));
1340 1399 }
1341 1400 else {// 'd'
1342 1401 from = parseInt(change[oside + '-line-from'], 10);
1343 1402 to = parseInt(change[oside + '-line-to'], 10);
1344 1403 for (i = to; i >= from; --i) {
1345 1404 ed[oside].setCursor({line: i, ch: -1});
1346 1405 ed[oside].execCommand('deleteLine');
1347 1406 }
1348 1407 }
1349 1408 }
1350 1409 else if (side == 'lhs') {
1351 1410 if (change['op'] == 'a') {
1352 1411 from = parseInt(change[oside + '-line-from'], 10);
1353 1412 to = parseInt(change[oside + '-line-to'], 10);
1354 1413 for (i = to; i >= from; --i) {
1355 1414 //ed[oside].removeLine(i);
1356 1415 ed[oside].setCursor({line: i, ch: -1});
1357 1416 ed[oside].execCommand('deleteLine');
1358 1417 }
1359 1418 }
1360 1419 else {// 'd'
1361 1420 ed[oside].replaceRange( text,
1362 1421 CodeMirror.Pos(change[oside + '-line-from'] + 1, 0));
1363 1422 }
1364 1423 }
1365 1424 //reset
1366 1425 ed['lhs'].setValue(ed['lhs'].getValue());
1367 1426 ed['rhs'].setValue(ed['rhs'].getValue());
1368 1427
1369 1428 this._scroll_to_change(change);
1370 1429 },
1371 1430 _draw_info: function(editor_name1, editor_name2) {
1372 1431 var visible_page_height = jQuery(this.editor[editor_name1].getScrollerElement()).height();
1373 1432 var gutter_height = jQuery(this.editor[editor_name1].getScrollerElement()).children(':first-child').height();
1374 1433 var dcanvas = document.getElementById(editor_name1 + '-' + editor_name2 + '-canvas');
1375 1434 if (dcanvas == undefined) throw 'Failed to find: ' + editor_name1 + '-' + editor_name2 + '-canvas';
1376 var clhs = jQuery('#' + this.id + '-lhs-margin');
1377 var crhs = jQuery('#' + this.id + '-rhs-margin');
1435 var clhs = this.element.find('#' + this.id + '-lhs-margin');
1436 var crhs = this.element.find('#' + this.id + '-rhs-margin');
1378 1437 return {
1379 1438 visible_page_height: visible_page_height,
1380 1439 gutter_height: gutter_height,
1381 1440 visible_page_ratio: (visible_page_height / gutter_height),
1382 1441 margin_ratio: (visible_page_height / gutter_height),
1383 1442 lhs_scroller: jQuery(this.editor[editor_name1].getScrollerElement()),
1384 1443 rhs_scroller: jQuery(this.editor[editor_name2].getScrollerElement()),
1385 1444 lhs_lines: this.editor[editor_name1].lineCount(),
1386 1445 rhs_lines: this.editor[editor_name2].lineCount(),
1387 1446 dcanvas: dcanvas,
1388 1447 clhs: clhs,
1389 1448 crhs: crhs,
1390 1449 lhs_xyoffset: jQuery(clhs).offset(),
1391 1450 rhs_xyoffset: jQuery(crhs).offset()
1392 1451 };
1393 1452 },
1394 1453 _draw_diff: function(editor_name1, editor_name2, changes) {
1395 1454 var ex = this._draw_info(editor_name1, editor_name2);
1396 1455 var mcanvas_lhs = ex.clhs.get(0);
1397 1456 var mcanvas_rhs = ex.crhs.get(0);
1398 1457 var ctx = ex.dcanvas.getContext('2d');
1399 1458 var ctx_lhs = mcanvas_lhs.getContext('2d');
1400 1459 var ctx_rhs = mcanvas_rhs.getContext('2d');
1401 1460
1402 1461 this.trace('draw', 'visible_page_height', ex.visible_page_height);
1403 1462 this.trace('draw', 'gutter_height', ex.gutter_height);
1404 1463 this.trace('draw', 'visible_page_ratio', ex.visible_page_ratio);
1405 1464 this.trace('draw', 'lhs-scroller-top', ex.lhs_scroller.scrollTop());
1406 1465 this.trace('draw', 'rhs-scroller-top', ex.rhs_scroller.scrollTop());
1407 1466
1408 jQuery.each(jQuery.find('#' + this.id + ' canvas'), function () {
1467 jQuery.each(this.element.find('canvas'), function () {
1409 1468 jQuery(this).get(0).height = ex.visible_page_height;
1410 1469 });
1411 1470
1412 1471 ex.clhs.unbind('click');
1413 1472 ex.crhs.unbind('click');
1414 1473
1415 1474 ctx_lhs.beginPath();
1416 1475 ctx_lhs.fillStyle = this.settings.bgcolor;
1417 1476 ctx_lhs.strokeStyle = '#888';
1418 1477 ctx_lhs.fillRect(0, 0, 6.5, ex.visible_page_height);
1419 1478 ctx_lhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
1420 1479
1421 1480 ctx_rhs.beginPath();
1422 1481 ctx_rhs.fillStyle = this.settings.bgcolor;
1423 1482 ctx_rhs.strokeStyle = '#888';
1424 1483 ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height);
1425 1484 ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
1426 1485
1427 1486 var vp = this._get_viewport(editor_name1, editor_name2);
1428 1487 for (var i = 0; i < changes.length; ++i) {
1429 1488 var change = changes[i];
1489 var fill = this.settings.fgcolor[change['op']];
1490 if (this._current_diff==i) {
1491 fill = '#000';
1492 }
1430 1493
1431 1494 this.trace('draw', change);
1432 1495 // margin indicators
1433 1496 var lhs_y_start = ((change['lhs-y-start'] + ex.lhs_scroller.scrollTop()) * ex.visible_page_ratio);
1434 1497 var lhs_y_end = ((change['lhs-y-end'] + ex.lhs_scroller.scrollTop()) * ex.visible_page_ratio) + 1;
1435 1498 var rhs_y_start = ((change['rhs-y-start'] + ex.rhs_scroller.scrollTop()) * ex.visible_page_ratio);
1436 1499 var rhs_y_end = ((change['rhs-y-end'] + ex.rhs_scroller.scrollTop()) * ex.visible_page_ratio) + 1;
1437 1500 this.trace('draw', 'marker calculated', lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end);
1438 1501
1439 1502 ctx_lhs.beginPath();
1440 ctx_lhs.fillStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']];
1503 ctx_lhs.fillStyle = fill;
1441 1504 ctx_lhs.strokeStyle = '#000';
1442 1505 ctx_lhs.lineWidth = 0.5;
1443 1506 ctx_lhs.fillRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5));
1444 1507 ctx_lhs.strokeRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5));
1445 1508
1446 1509 ctx_rhs.beginPath();
1447 ctx_rhs.fillStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']];
1510 ctx_rhs.fillStyle = fill;
1448 1511 ctx_rhs.strokeStyle = '#000';
1449 1512 ctx_rhs.lineWidth = 0.5;
1450 1513 ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
1451 1514 ctx_rhs.strokeRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
1452 1515
1453 1516 if (!this._is_change_in_view(vp, change)) {
1454 1517 continue;
1455 1518 }
1456 1519
1457 1520 lhs_y_start = change['lhs-y-start'];
1458 1521 lhs_y_end = change['lhs-y-end'];
1459 1522 rhs_y_start = change['rhs-y-start'];
1460 1523 rhs_y_end = change['rhs-y-end'];
1461 1524
1462 1525 var radius = 3;
1463 1526
1464 1527 // draw left box
1465 1528 ctx.beginPath();
1466 ctx.strokeStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']];
1529 ctx.strokeStyle = fill;
1467 1530 ctx.lineWidth = (this._current_diff==i) ? 1.5 : 1;
1468 1531
1469 1532 var rectWidth = this.draw_lhs_width;
1470 1533 var rectHeight = lhs_y_end - lhs_y_start - 1;
1471 1534 var rectX = this.draw_lhs_min;
1472 1535 var rectY = lhs_y_start;
1473 1536 // top and top top-right corner
1474 1537
1475 1538 // draw left box
1476 1539 ctx.moveTo(rectX, rectY);
1477 1540 if (navigator.appName == 'Microsoft Internet Explorer') {
1478 1541 // IE arcs look awful
1479 1542 ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_start);
1480 1543 ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_end + 1);
1481 1544 ctx.lineTo(this.draw_lhs_min, lhs_y_end + 1);
1482 1545 }
1483 1546 else {
1484 1547 if (rectHeight <= 0) {
1485 1548 ctx.lineTo(rectX + rectWidth, rectY);
1486 1549 }
1487 1550 else {
1488 1551 ctx.arcTo(rectX + rectWidth, rectY, rectX + rectWidth, rectY + radius, radius);
1489 1552 ctx.arcTo(rectX + rectWidth, rectY + rectHeight, rectX + rectWidth - radius, rectY + rectHeight, radius);
1490 1553 }
1491 1554 // bottom line
1492 1555 ctx.lineTo(rectX, rectY + rectHeight);
1493 1556 }
1494 1557 ctx.stroke();
1495 1558
1496 1559 rectWidth = this.draw_rhs_width;
1497 1560 rectHeight = rhs_y_end - rhs_y_start - 1;
1498 1561 rectX = this.draw_rhs_max;
1499 1562 rectY = rhs_y_start;
1500 1563
1501 1564 // draw right box
1502 1565 ctx.moveTo(rectX, rectY);
1503 1566 if (navigator.appName == 'Microsoft Internet Explorer') {
1504 1567 ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_start);
1505 1568 ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_end + 1);
1506 1569 ctx.lineTo(this.draw_rhs_max, rhs_y_end + 1);
1507 1570 }
1508 1571 else {
1509 1572 if (rectHeight <= 0) {
1510 1573 ctx.lineTo(rectX - rectWidth, rectY);
1511 1574 }
1512 1575 else {
1513 1576 ctx.arcTo(rectX - rectWidth, rectY, rectX - rectWidth, rectY + radius, radius);
1514 1577 ctx.arcTo(rectX - rectWidth, rectY + rectHeight, rectX - radius, rectY + rectHeight, radius);
1515 1578 }
1516 1579 ctx.lineTo(rectX, rectY + rectHeight);
1517 1580 }
1518 1581 ctx.stroke();
1519 1582
1520 1583 // connect boxes
1521 1584 var cx = this.draw_lhs_min + this.draw_lhs_width;
1522 1585 var cy = lhs_y_start + (lhs_y_end + 1 - lhs_y_start) / 2.0;
1523 1586 var dx = this.draw_rhs_max - this.draw_rhs_width;
1524 1587 var dy = rhs_y_start + (rhs_y_end + 1 - rhs_y_start) / 2.0;
1525 1588 ctx.moveTo(cx, cy);
1526 1589 if (cy == dy) {
1527 1590 ctx.lineTo(dx, dy);
1528 1591 }
1529 1592 else {
1530 1593 // fancy!
1531 1594 ctx.bezierCurveTo(
1532 1595 cx + 12, cy - 3, // control-1 X,Y
1533 1596 dx - 12, dy - 3, // control-2 X,Y
1534 1597 dx, dy);
1535 1598 }
1536 1599 ctx.stroke();
1537 1600 }
1538 1601
1539 1602 // visible window feedback
1540 1603 ctx_lhs.fillStyle = this.settings.vpcolor;
1541 1604 ctx_rhs.fillStyle = this.settings.vpcolor;
1542 1605
1543 1606 var lto = ex.clhs.height() * ex.visible_page_ratio;
1544 1607 var lfrom = (ex.lhs_scroller.scrollTop() / ex.gutter_height) * ex.clhs.height();
1545 1608 var rto = ex.crhs.height() * ex.visible_page_ratio;
1546 1609 var rfrom = (ex.rhs_scroller.scrollTop() / ex.gutter_height) * ex.crhs.height();
1547 1610 this.trace('draw', 'cls.height', ex.clhs.height());
1548 1611 this.trace('draw', 'lhs_scroller.scrollTop()', ex.lhs_scroller.scrollTop());
1549 1612 this.trace('draw', 'gutter_height', ex.gutter_height);
1550 1613 this.trace('draw', 'visible_page_ratio', ex.visible_page_ratio);
1551 1614 this.trace('draw', 'lhs from', lfrom, 'lhs to', lto);
1552 1615 this.trace('draw', 'rhs from', rfrom, 'rhs to', rto);
1553 1616
1554 1617 ctx_lhs.fillRect(1.5, lfrom, 4.5, lto);
1555 1618 ctx_rhs.fillRect(1.5, rfrom, 4.5, rto);
1556 1619
1557 1620 ex.clhs.click(function (ev) {
1558 1621 var y = ev.pageY - ex.lhs_xyoffset.top - (lto / 2);
1559 1622 var sto = Math.max(0, (y / mcanvas_lhs.height) * ex.lhs_scroller.get(0).scrollHeight);
1560 1623 ex.lhs_scroller.scrollTop(sto);
1561 1624 });
1562 1625 ex.crhs.click(function (ev) {
1563 1626 var y = ev.pageY - ex.rhs_xyoffset.top - (rto / 2);
1564 1627 var sto = Math.max(0, (y / mcanvas_rhs.height) * ex.rhs_scroller.get(0).scrollHeight);
1565 1628 ex.rhs_scroller.scrollTop(sto);
1566 1629 });
1567 1630 },
1568 1631 trace: function(name) {
1569 1632 if(this.settings._debug.indexOf(name) >= 0) {
1570 1633 arguments[0] = name + ':';
1571 1634 console.log([].slice.apply(arguments));
1572 1635 }
1573 1636 }
1574 1637 });
1575 1638
1576 1639 jQuery.pluginMaker = function(plugin) {
1577 1640 // add the plugin function as a jQuery plugin
1578 1641 jQuery.fn[plugin.prototype.name] = function(options) {
1579 1642 // get the arguments
1580 1643 var args = jQuery.makeArray(arguments),
1581 1644 after = args.slice(1);
1582 1645 var rc;
1583 1646 this.each(function() {
1584 1647 // see if we have an instance
1585 1648 var instance = jQuery.data(this, plugin.prototype.name);
1586 1649 if (instance) {
1587 1650 // call a method on the instance
1588 1651 if (typeof options == "string") {
1589 1652 rc = instance[options].apply(instance, after);
1590 1653 } else if (instance.update) {
1591 1654 // call update on the instance
1592 1655 return instance.update.apply(instance, args);
1593 1656 }
1594 1657 } else {
1595 1658 // create the plugin
1596 1659 var _plugin = new plugin(this, options);
1597 1660 }
1598 1661 });
1599 1662 if (rc != undefined) return rc;
1600 1663 };
1601 1664 };
1602 1665
1603 1666 // make the mergely widget
1604 1667 jQuery.pluginMaker(Mgly.mergely);
1605 1668
1606 1669 })( window, document, jQuery, CodeMirror ); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now