WordWrapManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. namespace Terminal.Gui.Views;
  2. /// <summary>
  3. /// Manages word wrapping for a <see cref="TextModel"/> in a <see cref="TextView"/>.
  4. /// </summary>
  5. /// <remarks>
  6. /// The <see cref="WordWrapManager"/> class provides functionality to handle word wrapping for multi-line text.
  7. /// It works with a <see cref="TextModel"/> to manage wrapped lines, calculate positions, and update the model
  8. /// when text is added, removed, or modified. This is used internally by text input controls like
  9. /// <see cref="TextView"/>
  10. /// to ensure proper word wrapping behavior.
  11. /// </remarks>
  12. internal class WordWrapManager (TextModel model)
  13. {
  14. private int _frameWidth;
  15. private bool _isWrapModelRefreshing;
  16. private List<WrappedLine> _wrappedModelLines = [];
  17. public TextModel Model { get; private set; } = model;
  18. public void AddLine (int row, int col)
  19. {
  20. int modelRow = GetModelLineFromWrappedLines (row);
  21. int modelCol = GetModelColFromWrappedLines (row, col);
  22. List<Cell> line = GetCurrentLine (modelRow);
  23. int restCount = line.Count - modelCol;
  24. List<Cell> rest = line.GetRange (modelCol, restCount);
  25. line.RemoveRange (modelCol, restCount);
  26. Model.AddLine (modelRow + 1, rest);
  27. _isWrapModelRefreshing = true;
  28. WrapModel (_frameWidth, out _, out _, out _, out _, modelRow + 1);
  29. _isWrapModelRefreshing = false;
  30. }
  31. public int GetModelColFromWrappedLines (int line, int col)
  32. {
  33. if (_wrappedModelLines?.Count == 0)
  34. {
  35. return 0;
  36. }
  37. int modelLine = GetModelLineFromWrappedLines (line);
  38. int firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine);
  39. var modelCol = 0;
  40. for (int i = firstLine; i <= Math.Min (line, _wrappedModelLines!.Count - 1); i++)
  41. {
  42. WrappedLine wLine = _wrappedModelLines [i];
  43. if (i < line)
  44. {
  45. modelCol += wLine.ColWidth;
  46. }
  47. else
  48. {
  49. modelCol += col;
  50. }
  51. }
  52. return modelCol;
  53. }
  54. public int GetModelLineFromWrappedLines (int line)
  55. {
  56. return _wrappedModelLines.Count > 0
  57. ? _wrappedModelLines [Math.Min (
  58. line,
  59. _wrappedModelLines.Count - 1
  60. )].ModelLine
  61. : 0;
  62. }
  63. public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManager)
  64. {
  65. if (_wrappedModelLines?.Count == 0)
  66. {
  67. return 0;
  68. }
  69. List<WrappedLine> wModelLines = wrapManager._wrappedModelLines;
  70. int modelLine = GetModelLineFromWrappedLines (line);
  71. int firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine);
  72. var modelCol = 0;
  73. var colWidthOffset = 0;
  74. int i = firstLine;
  75. while (modelCol < col)
  76. {
  77. WrappedLine wLine = _wrappedModelLines! [i];
  78. WrappedLine wLineToCompare = wModelLines [i];
  79. if (wLine.ModelLine != modelLine || wLineToCompare.ModelLine != modelLine)
  80. {
  81. break;
  82. }
  83. modelCol += Math.Max (wLine.ColWidth, wLineToCompare.ColWidth);
  84. colWidthOffset += wLine.ColWidth - wLineToCompare.ColWidth;
  85. if (modelCol > col)
  86. {
  87. modelCol += col - modelCol;
  88. }
  89. i++;
  90. }
  91. return modelCol - colWidthOffset;
  92. }
  93. public bool Insert (int row, int col, Cell cell)
  94. {
  95. List<Cell> line = GetCurrentLine (GetModelLineFromWrappedLines (row));
  96. line.Insert (GetModelColFromWrappedLines (row, col), cell);
  97. if (line.Count > _frameWidth)
  98. {
  99. return true;
  100. }
  101. return false;
  102. }
  103. public bool RemoveAt (int row, int col)
  104. {
  105. int modelRow = GetModelLineFromWrappedLines (row);
  106. List<Cell> line = GetCurrentLine (modelRow);
  107. int modelCol = GetModelColFromWrappedLines (row, col);
  108. if (modelCol > line.Count)
  109. {
  110. Model.RemoveLine (modelRow);
  111. RemoveAt (row, 0);
  112. return false;
  113. }
  114. if (modelCol < line.Count)
  115. {
  116. line.RemoveAt (modelCol);
  117. }
  118. if (line.Count > _frameWidth || (row + 1 < _wrappedModelLines.Count && _wrappedModelLines [row + 1].ModelLine == modelRow))
  119. {
  120. return true;
  121. }
  122. return false;
  123. }
  124. public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = true)
  125. {
  126. lineRemoved = false;
  127. int modelRow = GetModelLineFromWrappedLines (row);
  128. List<Cell> line = GetCurrentLine (modelRow);
  129. int modelCol = GetModelColFromWrappedLines (row, col);
  130. if (modelCol == 0 && line.Count == 0)
  131. {
  132. Model.RemoveLine (modelRow);
  133. return false;
  134. }
  135. if (modelCol < line.Count)
  136. {
  137. if (forward)
  138. {
  139. line.RemoveAt (modelCol);
  140. return true;
  141. }
  142. if (modelCol - 1 > -1)
  143. {
  144. line.RemoveAt (modelCol - 1);
  145. return true;
  146. }
  147. }
  148. lineRemoved = true;
  149. if (forward)
  150. {
  151. if (modelRow + 1 == Model.Count)
  152. {
  153. return false;
  154. }
  155. List<Cell> nextLine = Model.GetLine (modelRow + 1);
  156. line.AddRange (nextLine);
  157. Model.RemoveLine (modelRow + 1);
  158. if (line.Count > _frameWidth)
  159. {
  160. return true;
  161. }
  162. }
  163. else
  164. {
  165. if (modelRow == 0)
  166. {
  167. return false;
  168. }
  169. List<Cell> prevLine = Model.GetLine (modelRow - 1);
  170. prevLine.AddRange (line);
  171. Model.RemoveLine (modelRow);
  172. if (prevLine.Count > _frameWidth)
  173. {
  174. return true;
  175. }
  176. }
  177. return false;
  178. }
  179. public bool RemoveRange (int row, int index, int count)
  180. {
  181. int modelRow = GetModelLineFromWrappedLines (row);
  182. List<Cell> line = GetCurrentLine (modelRow);
  183. int modelCol = GetModelColFromWrappedLines (row, index);
  184. try
  185. {
  186. line.RemoveRange (modelCol, count);
  187. }
  188. catch (Exception)
  189. {
  190. return false;
  191. }
  192. return true;
  193. }
  194. public List<List<Cell>> ToListRune (List<string> textList)
  195. {
  196. List<List<Cell>> runesList = new ();
  197. foreach (string text in textList)
  198. {
  199. runesList.Add (Cell.ToCellList (text));
  200. }
  201. return runesList;
  202. }
  203. public void UpdateModel (
  204. TextModel model,
  205. out int nRow,
  206. out int nCol,
  207. out int nStartRow,
  208. out int nStartCol,
  209. int row,
  210. int col,
  211. int startRow,
  212. int startCol,
  213. bool preserveTrailingSpaces
  214. )
  215. {
  216. _isWrapModelRefreshing = true;
  217. Model = model;
  218. WrapModel (
  219. _frameWidth,
  220. out nRow,
  221. out nCol,
  222. out nStartRow,
  223. out nStartCol,
  224. row,
  225. col,
  226. startRow,
  227. startCol,
  228. 0,
  229. preserveTrailingSpaces
  230. );
  231. _isWrapModelRefreshing = false;
  232. }
  233. public TextModel WrapModel (
  234. int width,
  235. out int nRow,
  236. out int nCol,
  237. out int nStartRow,
  238. out int nStartCol,
  239. int row = 0,
  240. int col = 0,
  241. int startRow = 0,
  242. int startCol = 0,
  243. int tabWidth = 0,
  244. bool preserveTrailingSpaces = true
  245. )
  246. {
  247. _frameWidth = width;
  248. int modelRow = _isWrapModelRefreshing ? row : GetModelLineFromWrappedLines (row);
  249. int modelCol = _isWrapModelRefreshing ? col : GetModelColFromWrappedLines (row, col);
  250. int modelStartRow = _isWrapModelRefreshing ? startRow : GetModelLineFromWrappedLines (startRow);
  251. int modelStartCol =
  252. _isWrapModelRefreshing ? startCol : GetModelColFromWrappedLines (startRow, startCol);
  253. var wrappedModel = new TextModel ();
  254. var lines = 0;
  255. nRow = 0;
  256. nCol = 0;
  257. nStartRow = 0;
  258. nStartCol = 0;
  259. bool isRowAndColSet = row == 0 && col == 0;
  260. bool isStartRowAndColSet = startRow == 0 && startCol == 0;
  261. List<WrappedLine> wModelLines = new ();
  262. for (var i = 0; i < Model.Count; i++)
  263. {
  264. List<Cell> line = Model.GetLine (i);
  265. List<List<Cell>> wrappedLines = ToListRune (
  266. TextFormatter.Format (
  267. Cell.ToString (line),
  268. width,
  269. Alignment.Start,
  270. true,
  271. preserveTrailingSpaces,
  272. tabWidth
  273. )
  274. );
  275. var sumColWidth = 0;
  276. for (var j = 0; j < wrappedLines.Count; j++)
  277. {
  278. List<Cell> wrapLine = wrappedLines [j];
  279. if (!isRowAndColSet && modelRow == i)
  280. {
  281. if (nCol + wrapLine.Count <= modelCol)
  282. {
  283. nCol += wrapLine.Count;
  284. nRow = lines;
  285. if (nCol == modelCol)
  286. {
  287. nCol = wrapLine.Count;
  288. isRowAndColSet = true;
  289. }
  290. else if (j == wrappedLines.Count - 1)
  291. {
  292. nCol = wrapLine.Count - j + modelCol - nCol;
  293. isRowAndColSet = true;
  294. }
  295. }
  296. else
  297. {
  298. int offset = nCol + wrapLine.Count - modelCol;
  299. nCol = wrapLine.Count - offset;
  300. nRow = lines;
  301. isRowAndColSet = true;
  302. }
  303. }
  304. if (!isStartRowAndColSet && modelStartRow == i)
  305. {
  306. if (nStartCol + wrapLine.Count <= modelStartCol)
  307. {
  308. nStartCol += wrapLine.Count;
  309. nStartRow = lines;
  310. if (nStartCol == modelStartCol)
  311. {
  312. nStartCol = wrapLine.Count;
  313. isStartRowAndColSet = true;
  314. }
  315. else if (j == wrappedLines.Count - 1)
  316. {
  317. nStartCol = wrapLine.Count - j + modelStartCol - nStartCol;
  318. isStartRowAndColSet = true;
  319. }
  320. }
  321. else
  322. {
  323. int offset = nStartCol + wrapLine.Count - modelStartCol;
  324. nStartCol = wrapLine.Count - offset;
  325. nStartRow = lines;
  326. isStartRowAndColSet = true;
  327. }
  328. }
  329. for (int k = j; k < wrapLine.Count; k++)
  330. {
  331. Cell cell = wrapLine [k];
  332. cell.Attribute = line [k].Attribute;
  333. wrapLine [k] = cell;
  334. }
  335. wrappedModel.AddLine (lines, wrapLine);
  336. sumColWidth += wrapLine.Count;
  337. var wrappedLine = new WrappedLine
  338. {
  339. ModelLine = i, Row = lines, RowIndex = j, ColWidth = wrapLine.Count
  340. };
  341. wModelLines.Add (wrappedLine);
  342. lines++;
  343. }
  344. }
  345. _wrappedModelLines = wModelLines;
  346. return wrappedModel;
  347. }
  348. private List<Cell> GetCurrentLine (int row) { return Model.GetLine (row); }
  349. private class WrappedLine
  350. {
  351. public int ColWidth;
  352. public int ModelLine;
  353. public int Row;
  354. public int RowIndex;
  355. }
  356. }