HistoryText.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #nullable enable
  2. namespace Terminal.Gui.Views;
  3. internal class HistoryText
  4. {
  5. private readonly List<HistoryTextItemEventArgs> _historyTextItems = [];
  6. private int _idxHistoryText = -1;
  7. private readonly List<List<Cell>> _originalCellsList = [];
  8. public bool HasHistoryChanges => _idxHistoryText > -1;
  9. public bool IsFromHistory { get; private set; }
  10. public void Add (List<List<Cell>> lines, Point curPos, TextEditingLineStatus lineStatus = TextEditingLineStatus.Original)
  11. {
  12. if (lineStatus == TextEditingLineStatus.Original && _historyTextItems.Count > 0 && _historyTextItems.Last ().LineStatus == TextEditingLineStatus.Original)
  13. {
  14. return;
  15. }
  16. if (lineStatus == TextEditingLineStatus.Replaced && _historyTextItems.Count > 0 && _historyTextItems.Last ().LineStatus == TextEditingLineStatus.Replaced)
  17. {
  18. return;
  19. }
  20. if (_historyTextItems.Count == 0 && lineStatus != TextEditingLineStatus.Original)
  21. {
  22. throw new ArgumentException ("The first item must be the original.");
  23. }
  24. if (_idxHistoryText >= 0 && _idxHistoryText + 1 < _historyTextItems.Count)
  25. {
  26. _historyTextItems.RemoveRange (
  27. _idxHistoryText + 1,
  28. _historyTextItems.Count - _idxHistoryText - 1
  29. );
  30. }
  31. _historyTextItems.Add (new (lines, curPos, lineStatus));
  32. _idxHistoryText++;
  33. }
  34. public event EventHandler<HistoryTextItemEventArgs>? ChangeText;
  35. public void Clear (List<List<Cell>> cellsList)
  36. {
  37. _historyTextItems.Clear ();
  38. _idxHistoryText = -1;
  39. _originalCellsList.Clear ();
  40. // Save a copy of the original, not the reference
  41. foreach (List<Cell> cells in cellsList)
  42. {
  43. _originalCellsList.Add ([.. cells]);
  44. }
  45. OnChangeText (null);
  46. }
  47. public bool IsDirty (List<List<Cell>> cellsList)
  48. {
  49. if (cellsList.Count != _originalCellsList.Count)
  50. {
  51. return true;
  52. }
  53. for (var r = 0; r < cellsList.Count; r++)
  54. {
  55. List<Cell> cells = cellsList [r];
  56. List<Cell> originalCells = _originalCellsList [r];
  57. if (cells.Count != originalCells.Count)
  58. {
  59. return true;
  60. }
  61. for (var c = 0; c < cells.Count; c++)
  62. {
  63. Cell cell = cells [c];
  64. Cell originalCell = originalCells [c];
  65. if (!cell.Equals (originalCell))
  66. {
  67. return true;
  68. }
  69. }
  70. }
  71. return false;
  72. }
  73. public void Redo ()
  74. {
  75. if (_historyTextItems?.Count > 0 && _idxHistoryText < _historyTextItems.Count - 1)
  76. {
  77. IsFromHistory = true;
  78. _idxHistoryText++;
  79. var historyTextItem = new HistoryTextItemEventArgs (_historyTextItems [_idxHistoryText]) { IsUndoing = false };
  80. ProcessChanges (ref historyTextItem);
  81. IsFromHistory = false;
  82. }
  83. }
  84. public void ReplaceLast (List<List<Cell>> lines, Point curPos, TextEditingLineStatus lineStatus)
  85. {
  86. HistoryTextItemEventArgs? found = _historyTextItems.FindLast (x => x.LineStatus == lineStatus);
  87. if (found is { })
  88. {
  89. found.Lines = lines;
  90. found.CursorPosition = curPos;
  91. }
  92. }
  93. public void Undo ()
  94. {
  95. if (_historyTextItems?.Count > 0 && _idxHistoryText > 0)
  96. {
  97. IsFromHistory = true;
  98. _idxHistoryText--;
  99. var historyTextItem = new HistoryTextItemEventArgs (_historyTextItems [_idxHistoryText]) { IsUndoing = true };
  100. ProcessChanges (ref historyTextItem);
  101. IsFromHistory = false;
  102. }
  103. }
  104. private void OnChangeText (HistoryTextItemEventArgs? lines) { ChangeText?.Invoke (this, lines!); }
  105. private void ProcessChanges (ref HistoryTextItemEventArgs historyTextItem)
  106. {
  107. if (historyTextItem.IsUndoing)
  108. {
  109. if (_idxHistoryText - 1 > -1
  110. && (_historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Added
  111. || _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Removed
  112. || (historyTextItem.LineStatus == TextEditingLineStatus.Replaced && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Original)
  113. || (historyTextItem.LineStatus == TextEditingLineStatus.Attribute && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Original)))
  114. {
  115. _idxHistoryText--;
  116. while (_historyTextItems [_idxHistoryText].LineStatus == TextEditingLineStatus.Added
  117. && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Removed)
  118. {
  119. _idxHistoryText--;
  120. }
  121. historyTextItem = new (_historyTextItems [_idxHistoryText]);
  122. historyTextItem.IsUndoing = true;
  123. historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
  124. }
  125. if (historyTextItem.LineStatus == TextEditingLineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == TextEditingLineStatus.Added)
  126. {
  127. historyTextItem.RemovedOnAdded =
  128. new (_historyTextItems [_idxHistoryText + 1]);
  129. }
  130. if ((historyTextItem.LineStatus == TextEditingLineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Original)
  131. || (historyTextItem.LineStatus == TextEditingLineStatus.Removed && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Original)
  132. || (historyTextItem.LineStatus == TextEditingLineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Removed))
  133. {
  134. if (!historyTextItem.Lines [0]
  135. .SequenceEqual (_historyTextItems [_idxHistoryText - 1].Lines [0])
  136. && historyTextItem.CursorPosition == _historyTextItems [_idxHistoryText - 1].CursorPosition)
  137. {
  138. historyTextItem.Lines [0] =
  139. new (_historyTextItems [_idxHistoryText - 1].Lines [0]);
  140. }
  141. if (historyTextItem.LineStatus == TextEditingLineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Removed)
  142. {
  143. historyTextItem.FinalCursorPosition =
  144. _historyTextItems [_idxHistoryText - 2].CursorPosition;
  145. }
  146. else
  147. {
  148. historyTextItem.FinalCursorPosition =
  149. _historyTextItems [_idxHistoryText - 1].CursorPosition;
  150. }
  151. }
  152. else
  153. {
  154. historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
  155. }
  156. OnChangeText (historyTextItem);
  157. while (_historyTextItems [_idxHistoryText].LineStatus == TextEditingLineStatus.Removed
  158. || _historyTextItems [_idxHistoryText].LineStatus == TextEditingLineStatus.Added)
  159. {
  160. _idxHistoryText--;
  161. }
  162. }
  163. else if (!historyTextItem.IsUndoing)
  164. {
  165. if (_idxHistoryText + 1 < _historyTextItems.Count
  166. && (historyTextItem.LineStatus == TextEditingLineStatus.Original
  167. || _historyTextItems [_idxHistoryText + 1].LineStatus == TextEditingLineStatus.Added
  168. || _historyTextItems [_idxHistoryText + 1].LineStatus == TextEditingLineStatus.Removed))
  169. {
  170. _idxHistoryText++;
  171. historyTextItem = new (_historyTextItems [_idxHistoryText]);
  172. historyTextItem.IsUndoing = false;
  173. historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
  174. }
  175. if (historyTextItem.LineStatus == TextEditingLineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == TextEditingLineStatus.Removed)
  176. {
  177. historyTextItem.RemovedOnAdded =
  178. new (_historyTextItems [_idxHistoryText - 1]);
  179. }
  180. if ((historyTextItem.LineStatus == TextEditingLineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == TextEditingLineStatus.Replaced)
  181. || (historyTextItem.LineStatus == TextEditingLineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == TextEditingLineStatus.Original)
  182. || (historyTextItem.LineStatus == TextEditingLineStatus.Added && _historyTextItems [_idxHistoryText + 1].LineStatus == TextEditingLineStatus.Replaced))
  183. {
  184. if (historyTextItem.LineStatus == TextEditingLineStatus.Removed
  185. && !historyTextItem.Lines [0]
  186. .SequenceEqual (_historyTextItems [_idxHistoryText + 1].Lines [0]))
  187. {
  188. historyTextItem.Lines [0] =
  189. new (_historyTextItems [_idxHistoryText + 1].Lines [0]);
  190. }
  191. historyTextItem.FinalCursorPosition =
  192. _historyTextItems [_idxHistoryText + 1].CursorPosition;
  193. }
  194. else
  195. {
  196. historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
  197. }
  198. OnChangeText (historyTextItem);
  199. while (_historyTextItems [_idxHistoryText].LineStatus == TextEditingLineStatus.Removed
  200. || _historyTextItems [_idxHistoryText].LineStatus == TextEditingLineStatus.Added)
  201. {
  202. _idxHistoryText++;
  203. }
  204. }
  205. }
  206. }