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