TextModel.cs 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. namespace Terminal.Gui.Views;
  2. /// <summary>
  3. /// Represents the underlying data model for managing and manipulating multi-line text in a <see cref="TextView"/>.
  4. /// </summary>
  5. /// <remarks>
  6. /// The <see cref="TextModel"/> class provides functionality for storing, retrieving, and modifying lines of text,
  7. /// as well as supporting operations like word navigation, text search, and file loading. It is used internally
  8. /// by text input controls such as <see cref="TextView"/> to manage text content.
  9. /// </remarks>
  10. internal class TextModel
  11. {
  12. private List<List<Cell>> _lines = new ();
  13. private (Point startPointToFind, Point currentPointToFind, bool found) _toFind;
  14. /// <summary>The number of text lines in the model</summary>
  15. public int Count => _lines.Count;
  16. public string? FilePath { get; set; }
  17. /// <summary>Adds a line to the model at the specified position.</summary>
  18. /// <param name="pos">Line number where the line will be inserted.</param>
  19. /// <param name="cells">The line of text and color, as a List of Cell.</param>
  20. public void AddLine (int pos, List<Cell> cells) { _lines.Insert (pos, cells); }
  21. public bool CloseFile ()
  22. {
  23. if (FilePath is null)
  24. {
  25. throw new ArgumentNullException (nameof (FilePath));
  26. }
  27. FilePath = null;
  28. _lines = new ();
  29. return true;
  30. }
  31. public List<List<Cell>> GetAllLines () { return _lines; }
  32. /// <summary>Returns the specified line as a List of Rune</summary>
  33. /// <returns>The line.</returns>
  34. /// <param name="line">Line number to retrieve.</param>
  35. public List<Cell> GetLine (int line)
  36. {
  37. if (_lines.Count > 0)
  38. {
  39. if (line < Count)
  40. {
  41. return _lines [line];
  42. }
  43. return _lines [Count - 1];
  44. }
  45. _lines.Add (new ());
  46. return _lines [0];
  47. }
  48. /// <summary>Returns the maximum line length of the visible lines.</summary>
  49. /// <param name="first">The first line.</param>
  50. /// <param name="last">The last line.</param>
  51. /// <param name="tabWidth">The tab width.</param>
  52. public int GetMaxVisibleLine (int first, int last, int tabWidth)
  53. {
  54. var maxLength = 0;
  55. last = last < _lines.Count ? last : _lines.Count;
  56. for (int i = first; i < last; i++)
  57. {
  58. List<Cell> line = GetLine (i);
  59. int tabSum = line.Sum (c => c.Grapheme == "\t" ? Math.Max (tabWidth - 1, 0) : 0);
  60. int l = line.Count + tabSum;
  61. if (l > maxLength)
  62. {
  63. maxLength = l;
  64. }
  65. }
  66. return maxLength;
  67. }
  68. public event EventHandler? LinesLoaded;
  69. public bool LoadFile (string file)
  70. {
  71. FilePath = file ?? throw new ArgumentNullException (nameof (file));
  72. using (FileStream stream = File.OpenRead (file))
  73. {
  74. LoadStream (stream);
  75. return true;
  76. }
  77. }
  78. public void LoadListCells (List<List<Cell>> cellsList, Attribute? attribute)
  79. {
  80. _lines = cellsList;
  81. SetAttributes (attribute);
  82. OnLinesLoaded ();
  83. }
  84. public void LoadCells (List<Cell> cells, Attribute? attribute)
  85. {
  86. _lines = Cell.ToCells (cells);
  87. SetAttributes (attribute);
  88. OnLinesLoaded ();
  89. }
  90. public void LoadStream (Stream input)
  91. {
  92. if (input is null)
  93. {
  94. throw new ArgumentNullException (nameof (input));
  95. }
  96. _lines = new ();
  97. var buff = new BufferedStream (input);
  98. int v;
  99. List<byte> line = new ();
  100. var wasNewLine = false;
  101. while ((v = buff.ReadByte ()) != -1)
  102. {
  103. if (v == 13)
  104. {
  105. continue;
  106. }
  107. if (v == 10)
  108. {
  109. Append (line);
  110. line.Clear ();
  111. wasNewLine = true;
  112. continue;
  113. }
  114. line.Add ((byte)v);
  115. wasNewLine = false;
  116. }
  117. if (line.Count > 0 || wasNewLine)
  118. {
  119. Append (line);
  120. }
  121. buff.Dispose ();
  122. OnLinesLoaded ();
  123. }
  124. public void LoadString (string content)
  125. {
  126. _lines = Cell.StringToLinesOfCells (content);
  127. OnLinesLoaded ();
  128. }
  129. /// <summary>Removes the line at the specified position</summary>
  130. /// <param name="pos">Position.</param>
  131. public void RemoveLine (int pos)
  132. {
  133. if (_lines.Count > 0)
  134. {
  135. if (_lines.Count == 1 && _lines [0].Count == 0)
  136. {
  137. return;
  138. }
  139. _lines.RemoveAt (pos);
  140. }
  141. }
  142. public void ReplaceLine (int pos, List<Cell> runes)
  143. {
  144. if (_lines.Count > 0 && pos < _lines.Count)
  145. {
  146. _lines [pos] = [.. runes];
  147. }
  148. else if (_lines.Count == 0 || (_lines.Count > 0 && pos >= _lines.Count))
  149. {
  150. _lines.Add (runes);
  151. }
  152. }
  153. public override string ToString ()
  154. {
  155. var sb = new StringBuilder ();
  156. for (var i = 0; i < _lines.Count; i++)
  157. {
  158. sb.Append (Cell.ToString (_lines [i]));
  159. if (i + 1 < _lines.Count)
  160. {
  161. sb.AppendLine ();
  162. }
  163. }
  164. return sb.ToString ();
  165. }
  166. public (int col, int row)? WordBackward (int fromCol, int fromRow, bool useSameRuneType)
  167. {
  168. if (fromRow == 0 && fromCol == 0)
  169. {
  170. return null;
  171. }
  172. int col = Math.Max (fromCol - 1, 0);
  173. int row = fromRow;
  174. try
  175. {
  176. Cell? cell = RuneAt (col, row);
  177. Rune rune;
  178. if (cell is { })
  179. {
  180. rune = Rune.GetRuneAt (cell.Value.Grapheme, 0);
  181. }
  182. else
  183. {
  184. if (col > 0)
  185. {
  186. return (col, row);
  187. }
  188. if (col == 0 && row > 0)
  189. {
  190. row--;
  191. List<Cell> line = GetLine (row);
  192. return (line.Count, row);
  193. }
  194. return null;
  195. }
  196. RuneType runeType = GetRuneType (rune);
  197. int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
  198. ? col
  199. : -1;
  200. void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune)
  201. {
  202. if (Rune.IsWhiteSpace (nRune))
  203. {
  204. while (MovePrev (ref nCol, ref nRow, out nRune, useSameRuneType))
  205. {
  206. lastValidCol = nCol;
  207. if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
  208. {
  209. rune = nRune;
  210. runeType = GetRuneType (nRune);
  211. }
  212. }
  213. if (lastValidCol > -1)
  214. {
  215. nCol = lastValidCol;
  216. nRow = fromRow;
  217. }
  218. if ((!Rune.IsWhiteSpace (nRune) && Rune.IsWhiteSpace (rune))
  219. || (Rune.IsWhiteSpace (nRune) && !Rune.IsWhiteSpace (rune)))
  220. {
  221. return;
  222. }
  223. if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
  224. {
  225. List<Cell> line = GetLine (nRow);
  226. if (lastValidCol > -1)
  227. {
  228. nCol = lastValidCol + Math.Max (lastValidCol, line.Count);
  229. }
  230. }
  231. }
  232. else
  233. {
  234. if (!MovePrev (ref nCol, ref nRow, out nRune, useSameRuneType))
  235. {
  236. if (lastValidCol > -1)
  237. {
  238. nCol = lastValidCol;
  239. nRow = fromRow;
  240. }
  241. return;
  242. }
  243. List<Cell> line = GetLine (nRow);
  244. Rune firstRune = Rune.GetRuneAt (line [0].Grapheme, 0);
  245. if (nCol == 0
  246. && nRow == fromRow
  247. && (Rune.IsLetterOrDigit (firstRune) || Rune.IsPunctuation (firstRune) || Rune.IsSymbol (firstRune)))
  248. {
  249. return;
  250. }
  251. lastValidCol =
  252. (IsSameRuneType (nRune, runeType, useSameRuneType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
  253. ? nCol
  254. : lastValidCol;
  255. if (lastValidCol > -1 && Rune.IsWhiteSpace (nRune))
  256. {
  257. nCol = lastValidCol;
  258. return;
  259. }
  260. if (fromRow != nRow)
  261. {
  262. nCol = line.Count;
  263. return;
  264. }
  265. ProcMovePrev (ref nCol, ref nRow, nRune);
  266. }
  267. }
  268. ProcMovePrev (ref col, ref row, rune);
  269. if (fromCol != col || fromRow != row)
  270. {
  271. return (col, row);
  272. }
  273. if (fromCol == col && fromRow == row && row > 0)
  274. {
  275. row--;
  276. List<Cell> line = GetLine (row);
  277. col = line.Count;
  278. return (col, row);
  279. }
  280. return null;
  281. }
  282. catch (Exception)
  283. {
  284. return null;
  285. }
  286. }
  287. public (int col, int row)? WordForward (int fromCol, int fromRow, bool useSameRuneType)
  288. {
  289. if (fromRow == _lines.Count - 1 && fromCol == GetLine (_lines.Count - 1).Count)
  290. {
  291. return null;
  292. }
  293. int col = fromCol;
  294. int row = fromRow;
  295. try
  296. {
  297. Rune rune = _lines [row].Count > 0 ? Rune.GetRuneAt (RuneAt (col, row)!.Value.Grapheme, 0) : default (Rune);
  298. RuneType runeType = GetRuneType (rune);
  299. int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
  300. ? col
  301. : -1;
  302. void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune)
  303. {
  304. if (Rune.IsWhiteSpace (nRune))
  305. {
  306. while (MoveNext (ref nCol, ref nRow, out nRune, useSameRuneType))
  307. {
  308. lastValidCol = nCol;
  309. if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
  310. {
  311. return;
  312. }
  313. }
  314. lastValidCol = nCol;
  315. if (!Rune.IsWhiteSpace (nRune) && Rune.IsWhiteSpace (rune))
  316. {
  317. return;
  318. }
  319. if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
  320. {
  321. if (lastValidCol > -1)
  322. {
  323. nCol = lastValidCol;
  324. }
  325. return;
  326. }
  327. if (lastValidCol > -1)
  328. {
  329. nCol = lastValidCol;
  330. nRow = fromRow;
  331. }
  332. }
  333. else
  334. {
  335. if (!MoveNext (ref nCol, ref nRow, out nRune, useSameRuneType))
  336. {
  337. return;
  338. }
  339. lastValidCol = nCol;
  340. if (!IsSameRuneType (nRune, runeType, useSameRuneType) && !Rune.IsWhiteSpace (nRune))
  341. {
  342. return;
  343. }
  344. List<Cell> line = GetLine (nRow);
  345. Rune firstRune = Rune.GetRuneAt (line [0].Grapheme, 0);
  346. if (nCol == line.Count
  347. && nRow == fromRow
  348. && (Rune.IsLetterOrDigit (firstRune) || Rune.IsPunctuation (firstRune) || Rune.IsSymbol (firstRune)))
  349. {
  350. return;
  351. }
  352. if (fromRow != nRow)
  353. {
  354. nCol = 0;
  355. return;
  356. }
  357. ProcMoveNext (ref nCol, ref nRow, nRune);
  358. }
  359. }
  360. ProcMoveNext (ref col, ref row, rune);
  361. if (fromCol != col || fromRow != row)
  362. {
  363. return (col, row);
  364. }
  365. return null;
  366. }
  367. catch (Exception)
  368. {
  369. return null;
  370. }
  371. }
  372. public (int startCol, int col, int row)? ProcessDoubleClickSelection (int fromStartCol, int fromCol, int fromRow, bool useSameRuneType, bool selectWordOnly)
  373. {
  374. List<Cell> line = GetLine (fromRow);
  375. int startCol = fromStartCol;
  376. int col = fromCol;
  377. int row = fromRow;
  378. (int col, int row)? newPos = WordForward (col, row, useSameRuneType);
  379. if (newPos.HasValue)
  380. {
  381. col = row == newPos.Value.row ? newPos.Value.col : 0;
  382. }
  383. if (startCol > 0
  384. && StringExtensions.ToString (line.GetRange (startCol, col - startCol).Select (c => c.Grapheme).ToList ()).Trim () == ""
  385. && (col - startCol > 1 || (col - startCol > 0 && line [startCol - 1].Grapheme == " ")))
  386. {
  387. while (startCol > 0 && line [startCol - 1].Grapheme == " ")
  388. {
  389. startCol--;
  390. }
  391. }
  392. else
  393. {
  394. newPos = WordBackward (col, row, useSameRuneType);
  395. if (newPos is { })
  396. {
  397. startCol = row == newPos.Value.row ? newPos.Value.col : line.Count;
  398. }
  399. }
  400. if (selectWordOnly)
  401. {
  402. List<string> selText = line.GetRange (startCol, col - startCol).Select (c => c.Grapheme).ToList ();
  403. if (StringExtensions.ToString (selText).Trim () != "")
  404. {
  405. for (int i = selText.Count - 1; i > -1; i--)
  406. {
  407. if (selText [i] == " ")
  408. {
  409. col--;
  410. }
  411. }
  412. }
  413. }
  414. if (fromStartCol != startCol || fromCol != col || fromRow != row)
  415. {
  416. return (startCol, col, row);
  417. }
  418. return null;
  419. }
  420. internal static int CalculateLeftColumn (List<Cell> t, int start, int end, int width, int tabWidth = 0)
  421. {
  422. List<string> strings = new ();
  423. foreach (Cell cell in t)
  424. {
  425. strings.Add (cell.Grapheme);
  426. }
  427. return CalculateLeftColumn (strings, start, end, width, tabWidth);
  428. }
  429. // Returns the left column in a range of the string.
  430. internal static int CalculateLeftColumn (List<string> t, int start, int end, int width, int tabWidth = 0)
  431. {
  432. if (t is null || t.Count == 0)
  433. {
  434. return 0;
  435. }
  436. var size = 0;
  437. int tCount = end > t.Count - 1 ? t.Count - 1 : end;
  438. var col = 0;
  439. for (int i = tCount; i >= 0; i--)
  440. {
  441. string text = t [i];
  442. size += text.GetColumns (false);
  443. if (text == "\t")
  444. {
  445. size += tabWidth + 1;
  446. }
  447. if (size > width)
  448. {
  449. if (col + width == end)
  450. {
  451. col++;
  452. }
  453. break;
  454. }
  455. if ((end < t.Count && col > 0 && start < end && col == start) || end - col == width - 1)
  456. {
  457. break;
  458. }
  459. col = i;
  460. }
  461. return col;
  462. }
  463. internal static (int size, int length) DisplaySize (
  464. List<Cell> t,
  465. int start = -1,
  466. int end = -1,
  467. bool checkNextText = true,
  468. int tabWidth = 0
  469. )
  470. {
  471. List<string> strings = new ();
  472. foreach (Cell cell in t)
  473. {
  474. strings.Add (cell.Grapheme);
  475. }
  476. return DisplaySize (strings, start, end, checkNextText, tabWidth);
  477. }
  478. // Returns the size and length in a range of the string.
  479. internal static (int size, int length) DisplaySize (
  480. List<string> t,
  481. int start = -1,
  482. int end = -1,
  483. bool checkNextRune = true,
  484. int tabWidth = 0
  485. )
  486. {
  487. if (t is null || t.Count == 0)
  488. {
  489. return (0, 0);
  490. }
  491. var size = 0;
  492. var len = 0;
  493. int tCount = end == -1 ? t.Count :
  494. end > t.Count ? t.Count : end;
  495. int i = start == -1 ? 0 : start;
  496. for (; i < tCount; i++)
  497. {
  498. string text = t [i];
  499. int colWidth = text.GetColumns (false);
  500. size += colWidth;
  501. len += text.Length;
  502. if (text == "\t")
  503. {
  504. size += tabWidth + 1;
  505. len += tabWidth - 1;
  506. }
  507. else if (colWidth == -1)
  508. {
  509. size += 2; // -1+2=1
  510. }
  511. if (checkNextRune && i == tCount - 1 && t.Count > tCount && IsWideText (t [i + 1], tabWidth, out int s, out int l))
  512. {
  513. size += s;
  514. len += l;
  515. }
  516. }
  517. bool IsWideText (string s1, int tWidth, out int s, out int l)
  518. {
  519. s = s1.GetColumns ();
  520. l = Encoding.Unicode.GetByteCount (s1);
  521. if (s1 == "\t")
  522. {
  523. s += tWidth + 1;
  524. l += tWidth - 1;
  525. }
  526. return s > 1;
  527. }
  528. return (size, len);
  529. }
  530. internal Size GetDisplaySize ()
  531. {
  532. var size = Size.Empty;
  533. return size;
  534. }
  535. internal (Point current, bool found) FindNextText (
  536. string text,
  537. out bool gaveFullTurn,
  538. bool matchCase = false,
  539. bool matchWholeWord = false
  540. )
  541. {
  542. if (text is null || _lines.Count == 0)
  543. {
  544. gaveFullTurn = false;
  545. return (Point.Empty, false);
  546. }
  547. if (_toFind.found)
  548. {
  549. _toFind.currentPointToFind.X++;
  550. }
  551. (Point current, bool found) foundPos = GetFoundNextTextPoint (
  552. text,
  553. _lines.Count,
  554. matchCase,
  555. matchWholeWord,
  556. _toFind.currentPointToFind
  557. );
  558. if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind)
  559. {
  560. foundPos = GetFoundNextTextPoint (
  561. text,
  562. _toFind.startPointToFind.Y + 1,
  563. matchCase,
  564. matchWholeWord,
  565. Point.Empty
  566. );
  567. }
  568. gaveFullTurn = ApplyToFind (foundPos);
  569. return foundPos;
  570. }
  571. internal (Point current, bool found) FindPreviousText (
  572. string text,
  573. out bool gaveFullTurn,
  574. bool matchCase = false,
  575. bool matchWholeWord = false
  576. )
  577. {
  578. if (text is null || _lines.Count == 0)
  579. {
  580. gaveFullTurn = false;
  581. return (Point.Empty, false);
  582. }
  583. if (_toFind.found)
  584. {
  585. _toFind.currentPointToFind.X++;
  586. }
  587. int linesCount = _toFind.currentPointToFind.IsEmpty ? _lines.Count - 1 : _toFind.currentPointToFind.Y;
  588. (Point current, bool found) foundPos = GetFoundPreviousTextPoint (
  589. text,
  590. linesCount,
  591. matchCase,
  592. matchWholeWord,
  593. _toFind.currentPointToFind
  594. );
  595. if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind)
  596. {
  597. foundPos = GetFoundPreviousTextPoint (
  598. text,
  599. _lines.Count - 1,
  600. matchCase,
  601. matchWholeWord,
  602. new (_lines [_lines.Count - 1].Count, _lines.Count)
  603. );
  604. }
  605. gaveFullTurn = ApplyToFind (foundPos);
  606. return foundPos;
  607. }
  608. internal static int GetColFromX (List<Cell> t, int start, int x, int tabWidth = 0)
  609. {
  610. List<string> strings = new ();
  611. foreach (Cell cell in t)
  612. {
  613. strings.Add (cell.Grapheme);
  614. }
  615. return GetColFromX (strings, start, x, tabWidth);
  616. }
  617. internal static int GetColFromX (List<string> t, int start, int x, int tabWidth = 0)
  618. {
  619. if (x < 0)
  620. {
  621. return x;
  622. }
  623. int size = start;
  624. int pX = x + start;
  625. for (int i = start; i < t.Count; i++)
  626. {
  627. string s = t [i];
  628. size += s.GetColumns ();
  629. if (s == "\t")
  630. {
  631. size += tabWidth + 1;
  632. }
  633. if (i == pX || size > pX)
  634. {
  635. return i - start;
  636. }
  637. }
  638. return t.Count - start;
  639. }
  640. internal (Point current, bool found) ReplaceAllText (
  641. string text,
  642. bool matchCase = false,
  643. bool matchWholeWord = false,
  644. string? textToReplace = null
  645. )
  646. {
  647. var found = false;
  648. var pos = Point.Empty;
  649. for (var i = 0; i < _lines.Count; i++)
  650. {
  651. List<Cell> x = _lines [i];
  652. string txt = GetText (x);
  653. string matchText = !matchCase ? text.ToUpper () : text;
  654. int col = txt.IndexOf (matchText);
  655. while (col > -1)
  656. {
  657. if (matchWholeWord && !MatchWholeWord (txt, matchText, col))
  658. {
  659. if (col + 1 > txt.Length)
  660. {
  661. break;
  662. }
  663. col = txt.IndexOf (matchText, col + 1);
  664. continue;
  665. }
  666. if (col > -1)
  667. {
  668. if (!found)
  669. {
  670. found = true;
  671. }
  672. _lines [i] = Cell.ToCellList (ReplaceText (x, textToReplace!, matchText, col));
  673. x = _lines [i];
  674. txt = GetText (x);
  675. pos = new (col, i);
  676. col += textToReplace!.Length - matchText.Length;
  677. }
  678. if (col < 0 || col + 1 > txt.Length)
  679. {
  680. break;
  681. }
  682. col = txt.IndexOf (matchText, col + 1);
  683. }
  684. }
  685. string GetText (List<Cell> x)
  686. {
  687. var txt = Cell.ToString (x);
  688. if (!matchCase)
  689. {
  690. txt = txt.ToUpper ();
  691. }
  692. return txt;
  693. }
  694. return (pos, found);
  695. }
  696. /// <summary>Redefine column and line tracking.</summary>
  697. /// <param name="point">Contains the column and line.</param>
  698. internal void ResetContinuousFind (Point point)
  699. {
  700. _toFind.startPointToFind = _toFind.currentPointToFind = point;
  701. _toFind.found = false;
  702. }
  703. internal static bool SetCol (ref int col, int width, int cols)
  704. {
  705. if (col + cols <= width)
  706. {
  707. col += cols;
  708. return true;
  709. }
  710. return false;
  711. }
  712. private void Append (List<byte> line)
  713. {
  714. var str = StringExtensions.ToString (line.ToArray ());
  715. _lines.Add (Cell.StringToCells (str));
  716. }
  717. private bool ApplyToFind ((Point current, bool found) foundPos)
  718. {
  719. var gaveFullTurn = false;
  720. if (foundPos.found)
  721. {
  722. _toFind.currentPointToFind = foundPos.current;
  723. if (_toFind.found && _toFind.currentPointToFind == _toFind.startPointToFind)
  724. {
  725. gaveFullTurn = true;
  726. }
  727. if (!_toFind.found)
  728. {
  729. _toFind.startPointToFind = _toFind.currentPointToFind = foundPos.current;
  730. _toFind.found = foundPos.found;
  731. }
  732. }
  733. return gaveFullTurn;
  734. }
  735. private (Point current, bool found) GetFoundNextTextPoint (
  736. string text,
  737. int linesCount,
  738. bool matchCase,
  739. bool matchWholeWord,
  740. Point start
  741. )
  742. {
  743. for (int i = start.Y; i < linesCount; i++)
  744. {
  745. List<Cell> x = _lines [i];
  746. var txt = Cell.ToString (x);
  747. if (!matchCase)
  748. {
  749. txt = txt.ToUpper ();
  750. }
  751. string matchText = !matchCase ? text.ToUpper () : text;
  752. int col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length));
  753. if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col))
  754. {
  755. continue;
  756. }
  757. if (col > -1 && ((i == start.Y && col >= start.X) || i > start.Y) && txt.Contains (matchText))
  758. {
  759. return (new (col, i), true);
  760. }
  761. if (col == -1 && start.X > 0)
  762. {
  763. start.X = 0;
  764. }
  765. }
  766. return (Point.Empty, false);
  767. }
  768. private (Point current, bool found) GetFoundPreviousTextPoint (
  769. string text,
  770. int linesCount,
  771. bool matchCase,
  772. bool matchWholeWord,
  773. Point start
  774. )
  775. {
  776. for (int i = linesCount; i >= 0; i--)
  777. {
  778. List<Cell> x = _lines [i];
  779. var txt = Cell.ToString (x);
  780. if (!matchCase)
  781. {
  782. txt = txt.ToUpper ();
  783. }
  784. if (start.Y != i)
  785. {
  786. start.X = Math.Max (x.Count - 1, 0);
  787. }
  788. string matchText = !matchCase ? text.ToUpper () : text;
  789. int col = txt.LastIndexOf (matchText, _toFind.found ? start.X - 1 : start.X);
  790. if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col))
  791. {
  792. continue;
  793. }
  794. if (col > -1 && ((i <= linesCount && col <= start.X) || i < start.Y) && txt.Contains (matchText))
  795. {
  796. return (new (col, i), true);
  797. }
  798. }
  799. return (Point.Empty, false);
  800. }
  801. private RuneType GetRuneType (Rune rune)
  802. {
  803. if (Rune.IsSymbol (rune))
  804. {
  805. return RuneType.IsSymbol;
  806. }
  807. if (Rune.IsWhiteSpace (rune))
  808. {
  809. return RuneType.IsWhiteSpace;
  810. }
  811. if (Rune.IsLetterOrDigit (rune))
  812. {
  813. return RuneType.IsLetterOrDigit;
  814. }
  815. if (Rune.IsPunctuation (rune))
  816. {
  817. return RuneType.IsPunctuation;
  818. }
  819. return RuneType.IsUnknown;
  820. }
  821. private bool IsSameRuneType (Rune newRune, RuneType runeType, bool useSameRuneType)
  822. {
  823. RuneType rt = GetRuneType (newRune);
  824. if (useSameRuneType)
  825. {
  826. return rt == runeType;
  827. }
  828. switch (runeType)
  829. {
  830. case RuneType.IsSymbol:
  831. case RuneType.IsPunctuation:
  832. return rt is RuneType.IsSymbol or RuneType.IsPunctuation;
  833. case RuneType.IsWhiteSpace:
  834. case RuneType.IsLetterOrDigit:
  835. case RuneType.IsUnknown:
  836. return rt == runeType;
  837. default:
  838. throw new ArgumentOutOfRangeException (nameof (runeType), runeType, null);
  839. }
  840. }
  841. private bool MatchWholeWord (string source, string matchText, int index = 0)
  842. {
  843. if (string.IsNullOrEmpty (source) || string.IsNullOrEmpty (matchText))
  844. {
  845. return false;
  846. }
  847. string txt = matchText.Trim ();
  848. int start = index > 0 ? index - 1 : 0;
  849. int end = index + txt.Length;
  850. if ((start == 0 || Rune.IsWhiteSpace ((Rune)source [start])) && (end == source.Length || Rune.IsWhiteSpace ((Rune)source [end])))
  851. {
  852. return true;
  853. }
  854. return false;
  855. }
  856. private bool MoveNext (ref int col, ref int row, out Rune rune, bool useSameRuneType)
  857. {
  858. List<Cell> line = GetLine (row);
  859. if (col + 1 < line.Count)
  860. {
  861. col++;
  862. rune = Rune.GetRuneAt (line [col].Grapheme, 0);
  863. Rune prevRune = Rune.GetRuneAt (line [col - 1].Grapheme, 0);
  864. if (col + 1 == line.Count
  865. && !Rune.IsLetterOrDigit (rune)
  866. && !Rune.IsWhiteSpace (prevRune)
  867. && IsSameRuneType (prevRune, GetRuneType (rune), useSameRuneType))
  868. {
  869. col++;
  870. }
  871. prevRune = Rune.GetRuneAt (line [col - 1].Grapheme, 0);
  872. if (!Rune.IsWhiteSpace (rune)
  873. && (Rune.IsWhiteSpace (prevRune) || !IsSameRuneType (prevRune, GetRuneType (rune), useSameRuneType)))
  874. {
  875. return false;
  876. }
  877. return true;
  878. }
  879. if (col + 1 == line.Count)
  880. {
  881. col++;
  882. rune = default (Rune);
  883. return false;
  884. }
  885. // End of line
  886. col = 0;
  887. row++;
  888. rune = default (Rune);
  889. return false;
  890. }
  891. private bool MovePrev (ref int col, ref int row, out Rune rune, bool useSameRuneType)
  892. {
  893. List<Cell> line = GetLine (row);
  894. if (col > 0)
  895. {
  896. col--;
  897. rune = Rune.GetRuneAt (line [col].Grapheme, 0);
  898. Rune nextRune = Rune.GetRuneAt (line [col + 1].Grapheme, 0);
  899. if ((!Rune.IsWhiteSpace (rune)
  900. && !Rune.IsWhiteSpace (nextRune)
  901. && !IsSameRuneType (nextRune, GetRuneType (rune), useSameRuneType))
  902. || (Rune.IsWhiteSpace (rune) && !Rune.IsWhiteSpace (nextRune)))
  903. {
  904. return false;
  905. }
  906. return true;
  907. }
  908. rune = default (Rune);
  909. return false;
  910. }
  911. private void OnLinesLoaded () { LinesLoaded?.Invoke (this, EventArgs.Empty); }
  912. private string ReplaceText (List<Cell> source, string textToReplace, string matchText, int col)
  913. {
  914. var origTxt = Cell.ToString (source);
  915. (_, int len) = DisplaySize (source, 0, col, false);
  916. (_, int len2) = DisplaySize (source, col, col + matchText.Length, false);
  917. (_, int len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false);
  918. return origTxt [..len] + textToReplace + origTxt.Substring (len + len2, len3);
  919. }
  920. private Cell? RuneAt (int col, int row)
  921. {
  922. List<Cell> line = GetLine (row);
  923. if (line.Count > 0)
  924. {
  925. return line [col > line.Count - 1 ? line.Count - 1 : col];
  926. }
  927. return null;
  928. }
  929. private void SetAttributes (Attribute? attribute)
  930. {
  931. foreach (List<Cell> line in _lines)
  932. {
  933. for (var i = 0; i < line.Count; i++)
  934. {
  935. Cell cell = line [i];
  936. cell.Attribute ??= attribute;
  937. line [i] = cell;
  938. }
  939. }
  940. }
  941. private enum RuneType
  942. {
  943. IsSymbol,
  944. IsWhiteSpace,
  945. IsLetterOrDigit,
  946. IsPunctuation,
  947. IsUnknown
  948. }
  949. }