1 #include "diffoperations.h"
2 #include "latexdocument.h"
3 #include "latexeditorview.h"
4 #include "smallUsefulFunctions.h"
DiffOp()5 DiffOp::DiffOp(): start(0), length(0), type(Insert), lineWasModified(false), dlh(nullptr) {}
6
diffDocs(LatexDocument * doc,LatexDocument * doc2,bool dontAddLines)7 void diffDocs(LatexDocument *doc, LatexDocument *doc2, bool dontAddLines)
8 {
9 QString text = doc->text();
10 QString text2 = doc2->text();
11 diff_match_patch dmp;
12 QString error;
13 QList<Diff> diffList;
14 try {
15 diffList = dmp.diff_main(text, text2, true);
16 dmp.diff_cleanupSemantic(diffList);
17 } catch (const char *c) {
18 if (c) error = QString(c);
19 #ifndef _MSC_VER
20 } catch (char *c) {
21 if (c) error = QString(c);
22 #endif
23 } catch (const QString &s) {
24 error = s;
25 } catch (...) {
26 error = LatexDocument::tr("Unknown error. Potential crash. You are advised to restart TeXstudio");
27 }
28
29 if (!error.isEmpty()) {
30 UtilsUi::txsWarning("Diff: " + error);
31 return;
32 }
33
34 int lineNr = 0;
35 int lineNr2 = 0;
36 int col = 0;
37
38 for (int i = 0; i < diffList.size(); ++i) {
39 const Diff elem = diffList.at(i);
40 if (elem.operation == EQUAL) {
41 lineNr += elem.text.count("\n");
42 lineNr2 += elem.text.count("\n");
43 if (elem.text.count("\n") > 0)
44 col = 0;
45 col += elem.text.length();
46 if (elem.text.lastIndexOf("\n") >= 0)
47 col -= elem.text.lastIndexOf("\n") + 1;
48 }
49 if (elem.operation == DELETE) {
50 QStringList splitList = elem.text.split("\n");
51 QStringList splitListInsert;
52 if (splitList.isEmpty())
53 continue;
54 QString lineDelete = splitList.takeFirst();
55 int diff = lineDelete.length();
56 bool toBeReplaced = false;
57 if (i + 1 < diffList.size() && diffList[i + 1].operation == INSERT) {
58 splitListInsert = diffList[i + 1].text.split("\n");
59 }
60 if ( splitListInsert.size() > 0 && (splitList.size() > 1 || splitListInsert.size() > 1) ) {
61 toBeReplaced = true;
62 }
63 QVariant var = doc->line(lineNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
64 DiffList lineData;
65
66 bool lineModified;
67
68 if (var.isValid()) {
69 lineData = var.value<DiffList>();
70 if (lineData.isEmpty())
71 lineModified = doc->isLineModified(doc->line(lineNr));
72 else
73 lineModified = lineData.first().lineWasModified;
74 } else {
75 lineModified = doc->isLineModified(doc->line(lineNr));
76 }
77
78 //doc->line(lineNr).addOverlay(QFormatRange(col,diff,fid));
79 DiffOp diffOperation;
80 diffOperation.start = col;
81 diffOperation.length = diff;
82 diffOperation.lineWasModified = lineModified;
83 if (toBeReplaced) {
84 diffOperation.type = DiffOp::Replace;
85 diffOperation.text = splitListInsert.takeFirst();
86 diffOperation.dlh = doc2->line(lineNr2).handle();
87 //qDebug()<<doc->line(lineNr).text()<<" <-> "<< diffOperation.dlh->text();
88 if (splitList.isEmpty()) {
89 diffOperation.text += "\n" + splitListInsert.join("\n");
90 lineNr2 += splitListInsert.size();
91 }
92 } else {
93 diffOperation.type = DiffOp::Delete;
94 }
95 lineData.append(diffOperation);
96 doc->line(lineNr).setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(lineData));
97 col += diff;
98 int sz = splitList.size();
99 for (int j = 0; j < sz; j++) {
100 col = 0;
101 QString ln = splitList.takeFirst();
102 DiffOp diffOperation;
103 diffOperation.start = col;
104 diffOperation.length = ln.length();
105 if (toBeReplaced) {
106 lineNr2++;
107 diffOperation.type = DiffOp::Replace;
108 if (splitListInsert.isEmpty()) {
109 diffOperation.text = "";
110 } else {
111 diffOperation.text = splitListInsert.takeFirst();
112 diffOperation.dlh = doc2->line(lineNr2).handle();
113 //qDebug()<<doc->line(lineNr+j+1).text()<<" <-> "<< diffOperation.dlh->text();
114 if (splitList.isEmpty() && !splitListInsert.isEmpty()) {
115 diffOperation.text += "\n" + splitListInsert.join("\n");
116 lineNr2 += splitListInsert.size();
117 }
118 }
119 } else {
120 diffOperation.type = DiffOp::Delete;
121 }
122
123 var = doc->line(lineNr + j + 1).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
124 DiffList lineData;
125
126 if (var.isValid()) {
127 lineData = var.value<DiffList>();
128 if (lineData.isEmpty())
129 lineModified = doc->isLineModified(doc->line(lineNr + j + 1));
130 else
131 lineModified = lineData.first().lineWasModified;
132 } else {
133 lineModified = doc->isLineModified(doc->line(lineNr + j + 1));
134 }
135 diffOperation.lineWasModified = lineModified;
136
137 lineData.append(diffOperation);
138 doc->line(lineNr + j + 1).setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(lineData));
139 col = ln.length();
140 }
141 lineNr += elem.text.count("\n");
142 if (toBeReplaced)
143 i++;
144 }
145 if (elem.operation == INSERT) {
146 QStringList splitList = elem.text.split("\n");
147 if (splitList.isEmpty())
148 continue;
149 QDocumentCursor cur(doc);
150 if (lineNr + 1 > doc->lines()) {
151 if (dontAddLines)
152 continue;
153 cur.moveTo(lineNr - 1, 0);
154 cur.movePosition(1, QDocumentCursor::EndOfLine);
155 cur.insertText("\n");
156 } else {
157 cur.moveTo(lineNr, col);
158 }
159 int diff = splitList.first().length();
160 cur.insertText(splitList.first());
161 int lnNr = cur.lineNumber();
162 if (splitList.size() > 1 && !dontAddLines)
163 cur.insertText("\n");
164 QVariant var = doc->line(lnNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
165 DiffList lineData;
166 bool lineModified;
167
168 if (var.isValid()) {
169 lineData = var.value<DiffList>();
170 if (lineData.isEmpty())
171 lineModified = doc->isLineModified(doc->line(lnNr));
172 else
173 lineModified = lineData.first().lineWasModified;
174 } else {
175 lineModified = doc->isLineModified(doc->line(lnNr));
176 }
177
178 DiffOp diffOperation;
179 diffOperation.start = col;
180 diffOperation.length = diff;
181 diffOperation.type = DiffOp::Insert;
182 diffOperation.text = "";
183 diffOperation.lineWasModified = lineModified;
184 diffOperation.dlh = doc2->line(lineNr2).handle();
185 //qDebug()<<doc->line(lnNr).text()<<" <-> "<< diffOperation.dlh->text();
186 lineData.append(diffOperation);
187 doc->line(lnNr).setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(lineData));
188 //doc->line(lnNr).addOverlay(QFormatRange(col,diff,fid_Insert));
189 col += diff;
190 splitList.removeFirst();
191 if (dontAddLines)
192 continue;
193 for (int i = 0; i < splitList.size(); i++) {
194 lineNr2++;
195 QString ln = splitList.at(i);
196 cur.insertText(ln);
197 lnNr = cur.lineNumber();
198 if (i + 1 < splitList.size())
199 cur.insertText("\n");
200 QVariant var = doc->line(lnNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
201 DiffList lineData;
202
203 if (var.isValid()) {
204 lineData = var.value<DiffList>();
205 if (lineData.isEmpty())
206 lineModified = doc->isLineModified(doc->line(lnNr));
207 else
208 lineModified = lineData.first().lineWasModified;
209 } else {
210 lineModified = doc->isLineModified(doc->line(lnNr));
211 }
212 DiffOp diffOperation;
213 diffOperation.start = 0;
214 diffOperation.length = ln.length();
215 diffOperation.type = DiffOp::Insert;
216 diffOperation.text = "";
217 diffOperation.lineWasModified = lineModified;
218 diffOperation.dlh = doc2->line(lineNr2).handle();
219 //qDebug()<<doc->line(lnNr).text()<<" <-> "<< diffOperation.dlh->text();
220 lineData.append(diffOperation);
221 doc->line(lnNr).setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(lineData));
222 //doc->line(lnNr).addOverlay(QFormatRange(0,ln.length(),fid_Insert));
223 col = ln.length();
224 }
225 lineNr += elem.text.count("\n");
226 }
227 }
228 doc->mayHaveDiffMarkers = true;
229 }
230
diffRemoveMarkers(LatexDocument * doc,bool theirs)231 void diffRemoveMarkers(LatexDocument *doc, bool theirs)
232 {
233 if (!doc || !doc->mayHaveDiffMarkers)
234 return;
235
236 QDocumentCursor cur(doc);
237
238 for (int i = 0; i < doc->lineCount(); i++) {
239 QVariant var = doc->line(i).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
240
241 if (var.isValid()) {
242 DiffList lineData = var.value<DiffList>();
243 int offset = 0;
244 for (int j = 0; j < lineData.size(); j++) {
245 DiffOp op = lineData.at(j);
246 bool removeLine = false;
247 if (theirs) { //keep theirs
248 switch (op.type) {
249 case DiffOp::Delete:
250 cur.moveTo(i, op.start + offset);
251 cur.movePosition(op.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
252 cur.removeSelectedText();
253 break;
254 case DiffOp::Insert:
255 break;
256 case DiffOp::Replace:
257 cur.moveTo(i, op.start + offset);
258 cur.movePosition(op.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
259 cur.insertText(op.text);
260 if (op.text.isEmpty() && cur.line().text().isEmpty()) {
261 cur.deletePreviousChar();
262 i--;
263 }
264 break;
265 default:
266 ;
267 }
268 } else { // keep mine
269 switch (op.type) {
270 case DiffOp::Delete:
271 break;
272 case DiffOp::Insert:
273 cur.moveTo(i, op.start + offset);
274 removeLine = op.length == doc->line(i).length();
275 cur.movePosition(op.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
276 cur.removeSelectedText();
277 offset = -op.length;
278 if (removeLine) {
279 cur.deletePreviousChar();
280 i--;
281 }
282 break;
283 case DiffOp::Replace:
284 break;
285 default:
286 ;
287 }
288 }
289 }
290 doc->line(i).removeCookie(QDocumentLine::DIFF_LIST_COOCKIE);
291 }
292 }
293 doc->mayHaveDiffMarkers = false;
294 }
295
diffChange(LatexDocument * doc,int ln,int col,bool theirs)296 void diffChange(LatexDocument *doc, int ln, int col, bool theirs)
297 {
298 LatexEditorView *edView = doc->getEditorView();
299 QDocumentCursor cursor(doc);
300 cursor.moveTo(ln, col);
301
302 QList<int> fids;
303 fids << edView->deleteFormat << edView->insertFormat << edView->replaceFormat;
304 foreach (int fid, fids) {
305 QFormatRange fr;
306 if (cursor.hasSelection()) fr = cursor.line().getOverlayAt((cursor.columnNumber() + cursor.anchorColumnNumber()) / 2, fid);
307 else fr = cursor.line().getOverlayAt(cursor.columnNumber(), fid);
308 if (fr.length > 0 ) {
309 QDocumentCursor range = diffSearchBoundaries(doc, ln, col, fid);
310 cursor.moveTo(range.lineNumber(), range.columnNumber());
311
312 QVariant var = cursor.line().getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
313 if (var.isValid()) {
314 DiffList diffList = var.value<DiffList>();
315 //QString word=cursor.line().text().mid(fr.offset,fr.length);
316 DiffOp op;
317
318 QList<DiffOp>::iterator i;
319 for (i = diffList.begin(); i != diffList.end(); ++i) {
320 op = *i;
321 if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
322 break;
323 }
324 }
325 QStringList splitText;
326 if (i != diffList.end()) {
327 QString txt;
328 QString altText;
329 int ln2;
330 QDocumentCursor range2(doc);
331 if (theirs) {
332 switch (op.type) {
333 case DiffOp::Delete:
334 range.beginBoundary(ln2, col);
335 range.endBoundary(ln, col);
336 if (ln2 - ln == 0) {
337 range2 = diffSearchBoundaries(doc, ln, col, edView->insertFormat);
338 diffChangeOpType(range2, DiffOp::Inserted);
339 }
340 range.removeSelectedText();
341 diffList.erase(i);
342 cursor.line().setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(diffList));
343 break;
344 case DiffOp::Insert:
345 //op.type=DiffOp::Inserted;
346 //*i=op;
347 range.endBoundary(ln, col);
348 range.beginBoundary(ln2, col);
349
350 diffChangeOpType(range, DiffOp::Inserted);
351 if ((ln2 - ln == 0) && (col > 0)) {
352 range2 = diffSearchBoundaries(doc, ln2, col - 1, edView->deleteFormat);
353 range2.removeSelectedText();
354 diffChangeOpType(range2, DiffOp::Deleted);
355 }
356 break;
357 case DiffOp::Replace:
358 altText = diffCollectText(range);
359 txt = range.selectedText();
360 range.removeSelectedText();
361 range.insertText(altText);
362 op.type = DiffOp::Replaced;
363 op.text = txt;
364 splitText = txt.split("\n");
365 op.length = splitText.first().length();
366 *i = op;
367 cursor.line().setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(diffList));
368 break;
369 default:
370 ;
371 } // end theirs
372 } else {
373 switch (op.type) {
374 case DiffOp::Delete:
375 //op.type=DiffOp::NotDeleted;
376 //*i=op;
377 range.beginBoundary(ln2, col);
378 range.endBoundary(ln, col);
379 if (ln2 - ln == 0) {
380 range2 = diffSearchBoundaries(doc, ln, col, edView->insertFormat);
381 range2.removeSelectedText();
382 }
383 diffChangeOpType(range, DiffOp::NotDeleted);
384 break;
385 case DiffOp::Insert:
386 range.endBoundary(ln, col);
387 range.beginBoundary(ln2, col);
388 if ((ln2 - ln == 0) && (col > 0)) {
389 range2 = diffSearchBoundaries(doc, ln2, col - 1, edView->deleteFormat);
390 diffChangeOpType(range2, DiffOp::NotDeleted);
391 }
392 range.removeSelectedText();
393 diffList.erase(i);
394 cursor.line().setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(diffList));
395 if (range.line().text().isEmpty())
396 range.eraseLine();
397 //if(removeLine)
398 // cursor.deletePreviousChar();
399 break;
400 case DiffOp::Replace:
401 //op.type=DiffOp::NotReplaced;
402 //*i=op;
403 diffChangeOpType(range, DiffOp::NotReplaced);
404 break;
405 default:
406 ;
407 }
408 } // end mine
409
410 }
411 }
412 return;
413 } // if fr.length>0
414 }
415 }
416
diffSearchBoundaries(LatexDocument * doc,int ln,int col,int fid,int direction)417 QDocumentCursor diffSearchBoundaries(LatexDocument *doc, int ln, int col, int fid, int direction)
418 {
419 // direction 0 both, 1 forward, -1 backward
420 if (!doc)
421 return QDocumentCursor();
422 QDocumentCursor result(doc);
423 QDocumentCursor cursor(doc);
424 cursor.moveTo(ln, col);
425 QFormatRange fr = cursor.line().getOverlayAt(cursor.columnNumber(), fid);
426 if (fr.length == 0)
427 return QDocumentCursor();
428 QDocumentCursor beginCur(doc);
429 beginCur.moveTo(ln, fr.offset);
430 QDocumentCursor endCur(doc);
431 endCur.moveTo(ln, fr.offset + fr.length);
432 if (fr.length > 0 && fr.offset == 0 && direction < 1) {
433 //search backward
434 if (ln > 0) {
435 beginCur.movePosition(1, QDocumentCursor::PreviousCharacter);
436 QDocumentCursor zw = diffSearchBoundaries(doc, beginCur.lineNumber(), beginCur.columnNumber(), fid, -1);
437 if (zw.isValid())
438 beginCur = zw;
439 }
440 }
441 if (fr.length > 0 && fr.offset + fr.length == endCur.line().length() && direction > -1) {
442 //search backward
443 if (ln + 1 < doc->lineCount()) {
444 endCur.movePosition(1, QDocumentCursor::NextCharacter);
445 QDocumentCursor zw = diffSearchBoundaries(doc, endCur.lineNumber(), endCur.columnNumber(), fid, 1);
446 if (zw.isValid())
447 endCur = zw;
448 }
449 }
450 if (beginCur.isValid())
451 result.moveTo(beginCur.lineNumber(), beginCur.columnNumber());
452 if (endCur.isValid())
453 result.moveTo(endCur.anchorLineNumber(), endCur.anchorColumnNumber(), QDocumentCursor::KeepAnchor);
454 return result;
455 }
456
diffCollectText(QDocumentCursor range)457 QString diffCollectText(QDocumentCursor range)
458 {
459 QDocumentCursor cursor(range);
460 QString result;
461
462 while (cursor.lineNumber() <= range.anchorLineNumber()) {
463 QVariant var = cursor.line().getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
464 if (var.isValid()) {
465 DiffList diffList = var.value<DiffList>();
466 //QString word=cursor.line().text().mid(fr.offset,fr.length);
467 DiffOp op;
468
469 QList<DiffOp>::const_iterator i;
470 for (i = diffList.constBegin(); i != diffList.constEnd(); ++i) {
471 op = *i;
472 if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
473 break;
474 }
475 }
476 if (i != diffList.constEnd()) {
477 if (!result.isEmpty())
478 result += "\n";
479 result += op.text;
480 }
481 }
482 if (cursor.lineNumber() + 1 == cursor.document()->lineCount())
483 break;
484 cursor.movePosition(1, QDocumentCursor::NextLine);
485 }
486 return result;
487 }
488
diffChangeOpType(QDocumentCursor range,DiffOp::DiffType type)489 void diffChangeOpType(QDocumentCursor range, DiffOp::DiffType type)
490 {
491 QDocumentCursor cursor(range);
492
493 while (cursor.lineNumber() <= range.anchorLineNumber()) {
494 QVariant var = cursor.line().getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
495 if (var.isValid()) {
496 DiffList diffList = var.value<DiffList>();
497 //QString word=cursor.line().text().mid(fr.offset,fr.length);
498 DiffOp op;
499
500 QList<DiffOp>::iterator i;
501 for (i = diffList.begin(); i != diffList.end(); ++i) {
502 op = *i;
503 if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
504 break;
505 }
506 }
507 if (i != diffList.end()) {
508 op.type = type;
509 *i = op;
510 }
511 cursor.line().setCookie(QDocumentLine::DIFF_LIST_COOCKIE, QVariant::fromValue<DiffList>(diffList));
512 }
513 if (cursor.lineNumber() + 1 == cursor.document()->lineCount())
514 break;
515 cursor.movePosition(1, QDocumentCursor::NextLine);
516 }
517 }
518
diffMerge(LatexDocument * doc)519 void diffMerge(LatexDocument *doc)
520 {
521 if (!doc)
522 return;
523
524 //LatexDocument *doc2=doc->findChild("diffObejct");
525 enum Version {mine, their, conflict};
526 Version whose;
527
528 QDocumentCursor cur(doc);
529
530 for (int i = 0; i < doc->lineCount(); i++) {
531 QVariant var = doc->line(i).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
532
533 if (var.isValid()) {
534 DiffList lineData = var.value<DiffList>();
535 for (int j = 0; j < lineData.size(); j++) {
536 DiffOp op = lineData.at(j);
537 if (op.lineWasModified) {
538 whose = mine;
539 if (op.dlh != nullptr) {
540 QVariant var = op.dlh->getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
541 if (var.isValid()) {
542 whose = conflict;
543 }
544 }
545 } else {
546 whose = their;
547 }
548
549 bool removeLine = false;
550 if (whose == their) { //keep theirs
551 switch (op.type) {
552 case DiffOp::Delete:
553 cur.moveTo(i, op.start);
554 cur.movePosition(op.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
555 cur.removeSelectedText();
556 if (cur.line().text().isEmpty()) {
557 cur.eraseLine();
558 i--;
559 }
560 break;
561 case DiffOp::Insert:
562 break;
563 case DiffOp::Replace:
564 cur.moveTo(i, op.start);
565 cur.movePosition(op.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
566 cur.insertText(op.text);
567 if (op.text.isEmpty() && cur.line().text().isEmpty()) {
568 cur.deletePreviousChar();
569 i--;
570 }
571 break;
572 default:
573 ;
574 }
575 }
576 if (whose == mine) { // keep mine
577 switch (op.type) {
578 case DiffOp::Delete:
579 break;
580 case DiffOp::Insert:
581 cur.moveTo(i, op.start);
582 removeLine = op.length == doc->line(i).length();
583 cur.movePosition(op.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
584 cur.removeSelectedText();
585 if (removeLine) {
586 cur.deletePreviousChar();
587 i--;
588 }
589 break;
590 case DiffOp::Replace:
591 break;
592 default:
593 ;
594 }
595 }
596 }
597 doc->line(i).removeCookie(QDocumentLine::DIFF_LIST_COOCKIE);
598 }
599 }
600 }
601