TextModel.cs 30 KB

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