TextView.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. //
  2. // TextView.cs: multi-line text editing
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. //
  8. // TODO:
  9. // Attributed text on spans
  10. // Replace insertion with Insert method
  11. // String accumulation (Control-k, control-k is not preserving the last new line, see StringToRunes
  12. //
  13. using System;
  14. using System.Collections.Generic;
  15. using System.IO;
  16. using System.Linq;
  17. using System.Text;
  18. using NStack;
  19. namespace Terminal.Gui {
  20. class TextModel {
  21. List<List<Rune>> lines;
  22. public bool LoadFile (string file)
  23. {
  24. if (file == null)
  25. throw new ArgumentNullException (nameof (file));
  26. try {
  27. var stream = File.OpenRead (file);
  28. if (stream == null)
  29. return false;
  30. } catch {
  31. return false;
  32. }
  33. LoadStream (File.OpenRead (file));
  34. return true;
  35. }
  36. // Turns the ustring into runes, this does not split the
  37. // contents on a newline if it is present.
  38. static List<Rune> ToRunes (ustring str)
  39. {
  40. List<Rune> runes = new List<Rune> ();
  41. foreach (var x in str.ToRunes ()) {
  42. runes.Add (x);
  43. }
  44. return runes;
  45. }
  46. // Splits a string into a List that contains a List<Rune> for each line
  47. public static List<List<Rune>> StringToRunes (ustring content)
  48. {
  49. var lines = new List<List<Rune>> ();
  50. int start = 0, i = 0;
  51. for (; i < content.Length; i++) {
  52. if (content [i] == 10) {
  53. if (i - start > 0)
  54. lines.Add (ToRunes (content [start, i]));
  55. else
  56. lines.Add (ToRunes (ustring.Empty));
  57. start = i + 1;
  58. }
  59. }
  60. if (i - start >= 0)
  61. lines.Add (ToRunes (content [start, null]));
  62. return lines;
  63. }
  64. void Append (List<byte> line)
  65. {
  66. var str = ustring.Make (line.ToArray ());
  67. lines.Add (ToRunes (str));
  68. }
  69. public void LoadStream (Stream input)
  70. {
  71. if (input == null)
  72. throw new ArgumentNullException (nameof (input));
  73. lines = new List<List<Rune>> ();
  74. var buff = new BufferedStream (input);
  75. int v;
  76. var line = new List<byte> ();
  77. while ((v = buff.ReadByte ()) != -1) {
  78. if (v == 10) {
  79. Append (line);
  80. line.Clear ();
  81. continue;
  82. }
  83. line.Add ((byte)v);
  84. }
  85. if (line.Count > 0)
  86. Append (line);
  87. }
  88. public void LoadString (ustring content)
  89. {
  90. lines = StringToRunes (content);
  91. }
  92. public override string ToString ()
  93. {
  94. var sb = new StringBuilder ();
  95. foreach (var line in lines) {
  96. sb.Append (line);
  97. sb.AppendLine ();
  98. }
  99. return sb.ToString ();
  100. }
  101. public int Count => lines.Count;
  102. public List<Rune> GetLine (int line) => lines [line];
  103. public void AddLine (int pos, List<Rune> runes)
  104. {
  105. lines.Insert (pos, runes);
  106. }
  107. public void RemoveLine (int pos)
  108. {
  109. lines.RemoveAt (pos);
  110. }
  111. }
  112. /// <summary>
  113. /// Text data entry widget
  114. /// </summary>
  115. /// <remarks>
  116. /// The Entry widget provides Emacs-like editing
  117. /// functionality, and mouse support.
  118. /// </remarks>
  119. public class TextView : View {
  120. TextModel model = new TextModel ();
  121. int topRow;
  122. int leftColumn;
  123. int currentRow;
  124. int currentColumn;
  125. int selectionStartColumn, selectionStartRow;
  126. bool selecting;
  127. //bool used;
  128. #if false
  129. /// <summary>
  130. /// Changed event, raised when the text has clicked.
  131. /// </summary>
  132. /// <remarks>
  133. /// Client code can hook up to this event, it is
  134. /// raised when the text in the entry changes.
  135. /// </remarks>
  136. public event EventHandler Changed;
  137. #endif
  138. /// <summary>
  139. /// Public constructor.
  140. /// </summary>
  141. /// <remarks>
  142. /// </remarks>
  143. public TextView (Rect frame) : base (frame)
  144. {
  145. CanFocus = true;
  146. }
  147. void ResetPosition ()
  148. {
  149. topRow = leftColumn = currentRow = currentColumn = 0;
  150. }
  151. /// <summary>
  152. /// Sets or gets the text in the entry.
  153. /// </summary>
  154. /// <remarks>
  155. /// </remarks>
  156. public ustring Text {
  157. get {
  158. return model.ToString ();
  159. }
  160. set {
  161. ResetPosition ();
  162. model.LoadString (value);
  163. SetNeedsDisplay ();
  164. }
  165. }
  166. /// <summary>
  167. /// The current cursor row.
  168. /// </summary>
  169. public int CurrentRow => currentRow;
  170. /// <summary>
  171. /// Gets the cursor column.
  172. /// </summary>
  173. /// <value>The cursor column.</value>
  174. public int CurrentColumn => currentColumn;
  175. /// <summary>
  176. /// Sets the cursor position.
  177. /// </summary>
  178. public override void PositionCursor ()
  179. {
  180. if (selecting) {
  181. var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow)-topRow, 0), Frame.Height);
  182. var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
  183. SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow));
  184. }
  185. Move (CurrentColumn - leftColumn, CurrentRow - topRow);
  186. }
  187. void ClearRegion (int left, int top, int right, int bottom)
  188. {
  189. for (int row = top; row < bottom; row++) {
  190. Move (left, row);
  191. for (int col = left; col < right; col++)
  192. AddRune (col, row, ' ');
  193. }
  194. }
  195. void ColorNormal ()
  196. {
  197. Driver.SetAttribute (ColorScheme.Normal);
  198. }
  199. void ColorSelection ()
  200. {
  201. if (HasFocus)
  202. Driver.SetAttribute (ColorScheme.Focus);
  203. else
  204. Driver.SetAttribute (ColorScheme.Normal);
  205. }
  206. // Returns an encoded region start..end (top 32 bits are the row, low32 the column)
  207. void GetEncodedRegionBounds (out long start, out long end)
  208. {
  209. long selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn;
  210. long point = ((long)(uint)currentRow << 32) | (uint)currentColumn;
  211. if (selection > point) {
  212. start = point;
  213. end = selection;
  214. } else {
  215. start = selection;
  216. end = point;
  217. }
  218. }
  219. bool PointInSelection (int col, int row)
  220. {
  221. long start, end;
  222. GetEncodedRegionBounds (out start, out end);
  223. var q = ((long)(uint)row << 32) | (uint)col;
  224. return q >= start && q <= end;
  225. }
  226. //
  227. // Returns a ustring with the text in the selected
  228. // region.
  229. //
  230. public ustring GetRegion ()
  231. {
  232. long start, end;
  233. GetEncodedRegionBounds (out start, out end);
  234. int startRow = (int)(start >> 32);
  235. var maxrow = ((int)(end >> 32));
  236. int startCol = (int)(start & 0xffffffff);
  237. var endCol = (int)(end & 0xffffffff);
  238. var line = model.GetLine (startRow);
  239. if (startRow == maxrow)
  240. return StringFromRunes (line.GetRange (startCol, endCol));
  241. ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));
  242. for (int row = startRow+1; row < maxrow; row++) {
  243. res = res + ustring.Make (10) + StringFromRunes (model.GetLine (row));
  244. }
  245. line = model.GetLine (maxrow);
  246. res = res + ustring.Make (10) + StringFromRunes (line.GetRange (0, endCol));
  247. return res;
  248. }
  249. //
  250. // Clears the contents of the selected region
  251. //
  252. public void ClearRegion ()
  253. {
  254. long start, end;
  255. long currentEncoded = ((long)(uint)currentRow << 32) | (uint)currentColumn;
  256. GetEncodedRegionBounds (out start, out end);
  257. int startRow = (int)(start >> 32);
  258. var maxrow = ((int)(end >> 32));
  259. int startCol = (int)(start & 0xffffffff);
  260. var endCol = (int)(end & 0xffffffff);
  261. var line = model.GetLine (startRow);
  262. if (startRow == maxrow) {
  263. line.RemoveRange (startCol, endCol - startCol);
  264. currentColumn = startCol;
  265. SetNeedsDisplay (new Rect (0, startRow - topRow, Frame.Width, startRow - topRow + 1));
  266. return;
  267. }
  268. line.RemoveRange (startCol, line.Count - startCol);
  269. var line2 = model.GetLine (maxrow);
  270. line.AddRange (line2.Skip (endCol));
  271. for (int row = startRow + 1; row <= maxrow; row++) {
  272. model.RemoveLine (startRow+1);
  273. }
  274. if (currentEncoded == end) {
  275. currentRow -= maxrow - (startRow);
  276. }
  277. currentColumn = startCol;
  278. SetNeedsDisplay ();
  279. }
  280. /// <summary>
  281. /// Redraw the text editor region
  282. /// </summary>
  283. /// <param name="region">The region to redraw.</param>
  284. public override void Redraw (Rect region)
  285. {
  286. ColorNormal ();
  287. int bottom = region.Bottom;
  288. int right = region.Right;
  289. for (int row = region.Top; row < bottom; row++) {
  290. int textLine = topRow + row;
  291. if (textLine >= model.Count) {
  292. ColorNormal ();
  293. ClearRegion (region.Left, row, region.Right, row + 1);
  294. continue;
  295. }
  296. var line = model.GetLine (textLine);
  297. int lineRuneCount = line.Count;
  298. if (line.Count < region.Left){
  299. ClearRegion (region.Left, row, region.Right, row + 1);
  300. continue;
  301. }
  302. Move (region.Left, row);
  303. for (int col = region.Left; col < right; col++) {
  304. var lineCol = leftColumn + col;
  305. var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
  306. if (selecting && PointInSelection (col, row))
  307. ColorSelection ();
  308. else
  309. ColorNormal ();
  310. AddRune (col, row, rune);
  311. }
  312. }
  313. PositionCursor ();
  314. }
  315. public override bool CanFocus {
  316. get => true;
  317. set { base.CanFocus = value; }
  318. }
  319. void SetClipboard (ustring text)
  320. {
  321. Clipboard.Contents = text;
  322. }
  323. void AppendClipboard (ustring text)
  324. {
  325. Clipboard.Contents = Clipboard.Contents + text;
  326. }
  327. void Insert (Rune rune)
  328. {
  329. var line = GetCurrentLine ();
  330. line.Insert (currentColumn, rune);
  331. var prow = currentRow - topRow;
  332. SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
  333. }
  334. ustring StringFromRunes (List<Rune> runes)
  335. {
  336. if (runes == null)
  337. throw new ArgumentNullException (nameof (runes));
  338. int size = 0;
  339. foreach (var rune in runes) {
  340. size += Utf8.RuneLen (rune);
  341. }
  342. var encoded = new byte [size];
  343. int offset = 0;
  344. foreach (var rune in runes) {
  345. offset += Utf8.EncodeRune (rune, encoded, offset);
  346. }
  347. return ustring.Make (encoded);
  348. }
  349. List<Rune> GetCurrentLine () => model.GetLine (currentRow);
  350. void InsertText (ustring text)
  351. {
  352. var lines = TextModel.StringToRunes (text);
  353. if (lines.Count == 0)
  354. return;
  355. var line = GetCurrentLine ();
  356. // Optmize single line
  357. if (lines.Count == 1) {
  358. line.InsertRange (currentColumn, lines [0]);
  359. currentColumn += lines [0].Count;
  360. if (currentColumn - leftColumn > Frame.Width)
  361. leftColumn = currentColumn - Frame.Width + 1;
  362. SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
  363. return;
  364. }
  365. // Keep a copy of the rest of the line
  366. var restCount = line.Count - currentColumn;
  367. var rest = line.GetRange (currentColumn, restCount);
  368. line.RemoveRange (currentColumn, restCount);
  369. // First line is inserted at the current location, the rest is appended
  370. line.InsertRange (currentColumn, lines [0]);
  371. for (int i = 1; i < lines.Count; i++)
  372. model.AddLine (currentRow + i, lines [i]);
  373. var last = model.GetLine (currentRow + lines.Count-1);
  374. var lastp = last.Count;
  375. last.InsertRange (last.Count, rest);
  376. // Now adjjust column and row positions
  377. currentRow += lines.Count-1;
  378. currentColumn = lastp;
  379. if (currentRow - topRow > Frame.Height) {
  380. topRow = currentRow - Frame.Height + 1;
  381. if (topRow < 0)
  382. topRow = 0;
  383. }
  384. if (currentColumn < leftColumn)
  385. leftColumn = currentColumn;
  386. if (currentColumn-leftColumn >= Frame.Width)
  387. leftColumn = currentColumn - Frame.Width + 1;
  388. SetNeedsDisplay ();
  389. }
  390. // The column we are tracking, or -1 if we are not tracking any column
  391. int columnTrack = -1;
  392. // Tries to snap the cursor to the tracking column
  393. void TrackColumn ()
  394. {
  395. // Now track the column
  396. var line = GetCurrentLine ();
  397. if (line.Count < columnTrack)
  398. currentColumn = line.Count;
  399. else if (columnTrack != -1)
  400. currentColumn = columnTrack;
  401. else if (currentColumn > line.Count)
  402. currentColumn = line.Count;
  403. if (currentColumn < leftColumn) {
  404. leftColumn = currentColumn;
  405. SetNeedsDisplay ();
  406. }
  407. if (currentColumn - leftColumn > Frame.Width) {
  408. leftColumn = currentColumn - Frame.Width + 1;
  409. SetNeedsDisplay ();
  410. }
  411. }
  412. bool lastWasKill;
  413. public override bool ProcessKey (KeyEvent kb)
  414. {
  415. int restCount;
  416. List<Rune> rest;
  417. // Handle some state here - whether the last command was a kill
  418. // operation and the column tracking (up/down)
  419. switch (kb.Key) {
  420. case Key.ControlN:
  421. case Key.CursorDown:
  422. case Key.ControlP:
  423. case Key.CursorUp:
  424. lastWasKill = false;
  425. break;
  426. case Key.ControlK:
  427. break;
  428. default:
  429. lastWasKill = false;
  430. columnTrack = -1;
  431. break;
  432. }
  433. // Dispatch the command.
  434. switch (kb.Key) {
  435. case Key.ControlN:
  436. case Key.CursorDown:
  437. if (currentRow + 1 < model.Count) {
  438. if (columnTrack == -1)
  439. columnTrack = currentColumn;
  440. currentRow++;
  441. if (currentRow >= topRow + Frame.Height) {
  442. topRow++;
  443. SetNeedsDisplay ();
  444. }
  445. TrackColumn ();
  446. PositionCursor ();
  447. }
  448. break;
  449. case Key.ControlP:
  450. case Key.CursorUp:
  451. if (currentRow > 0) {
  452. if (columnTrack == -1)
  453. columnTrack = currentColumn;
  454. currentRow--;
  455. if (currentRow < topRow) {
  456. topRow--;
  457. SetNeedsDisplay ();
  458. }
  459. TrackColumn ();
  460. PositionCursor ();
  461. }
  462. break;
  463. case Key.ControlF:
  464. case Key.CursorRight:
  465. var currentLine = GetCurrentLine ();
  466. if (currentColumn < currentLine.Count) {
  467. currentColumn++;
  468. if (currentColumn >= leftColumn + Frame.Width) {
  469. leftColumn++;
  470. SetNeedsDisplay ();
  471. }
  472. PositionCursor ();
  473. } else {
  474. if (currentRow + 1 < model.Count) {
  475. currentRow++;
  476. currentColumn = 0;
  477. leftColumn = 0;
  478. if (currentRow >= topRow + Frame.Height) {
  479. topRow++;
  480. }
  481. SetNeedsDisplay ();
  482. PositionCursor ();
  483. }
  484. break;
  485. }
  486. break;
  487. case Key.ControlB:
  488. case Key.CursorLeft:
  489. if (currentColumn > 0) {
  490. currentColumn--;
  491. if (currentColumn < leftColumn) {
  492. leftColumn--;
  493. SetNeedsDisplay ();
  494. }
  495. PositionCursor ();
  496. } else {
  497. if (currentRow > 0) {
  498. currentRow--;
  499. if (currentRow < topRow) {
  500. topRow--;
  501. }
  502. currentLine = GetCurrentLine ();
  503. currentColumn = currentLine.Count;
  504. int prev = leftColumn;
  505. leftColumn = currentColumn - Frame.Width + 1;
  506. if (leftColumn < 0)
  507. leftColumn = 0;
  508. if (prev != leftColumn)
  509. SetNeedsDisplay ();
  510. PositionCursor ();
  511. }
  512. }
  513. break;
  514. case Key.Delete:
  515. case Key.Backspace:
  516. if (currentColumn > 0) {
  517. // Delete backwards
  518. currentLine = GetCurrentLine ();
  519. currentLine.RemoveAt (currentColumn - 1);
  520. currentColumn--;
  521. if (currentColumn < leftColumn) {
  522. leftColumn--;
  523. SetNeedsDisplay ();
  524. } else
  525. SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width));
  526. } else {
  527. // Merges the current line with the previous one.
  528. if (currentRow == 0)
  529. return true;
  530. var prowIdx = currentRow - 1;
  531. var prevRow = model.GetLine (prowIdx);
  532. var prevCount = prevRow.Count;
  533. model.GetLine (prowIdx).AddRange (GetCurrentLine ());
  534. currentRow--;
  535. currentColumn = prevCount;
  536. leftColumn = currentColumn - Frame.Width + 1;
  537. if (leftColumn < 0)
  538. leftColumn = 0;
  539. SetNeedsDisplay ();
  540. }
  541. break;
  542. // Home, C-A
  543. case Key.Home:
  544. case Key.ControlA:
  545. currentColumn = 0;
  546. if (currentColumn < leftColumn) {
  547. leftColumn = 0;
  548. SetNeedsDisplay ();
  549. } else
  550. PositionCursor ();
  551. break;
  552. case Key.ControlD: // Delete
  553. currentLine = GetCurrentLine ();
  554. if (currentColumn == currentLine.Count) {
  555. if (currentRow + 1 == model.Count)
  556. break;
  557. var nextLine = model.GetLine (currentRow + 1);
  558. currentLine.AddRange (nextLine);
  559. model.RemoveLine (currentRow + 1);
  560. var sr = currentRow - topRow;
  561. SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
  562. } else {
  563. currentLine.RemoveAt (currentColumn);
  564. var r = currentRow - topRow;
  565. SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1));
  566. }
  567. break;
  568. case Key.ControlE: // End
  569. currentLine = GetCurrentLine ();
  570. currentColumn = currentLine.Count;
  571. int pcol = leftColumn;
  572. leftColumn = currentColumn - Frame.Width + 1;
  573. if (leftColumn < 0)
  574. leftColumn = 0;
  575. if (pcol != leftColumn)
  576. SetNeedsDisplay ();
  577. PositionCursor ();
  578. break;
  579. case Key.ControlK: // kill-to-end
  580. currentLine = GetCurrentLine ();
  581. if (currentLine.Count == 0) {
  582. model.RemoveLine (currentRow);
  583. var val = ustring.Make ('\n');
  584. if (lastWasKill)
  585. AppendClipboard (val);
  586. else
  587. SetClipboard (val);
  588. } else {
  589. restCount = currentLine.Count - currentColumn;
  590. rest = currentLine.GetRange (currentColumn, restCount);
  591. var val = StringFromRunes (rest);
  592. if (lastWasKill)
  593. AppendClipboard (val);
  594. else
  595. SetClipboard (val);
  596. currentLine.RemoveRange (currentColumn, restCount);
  597. }
  598. SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
  599. lastWasKill = true;
  600. break;
  601. case Key.ControlY: // Control-y, yank
  602. InsertText (Clipboard.Contents);
  603. selecting = false;
  604. break;
  605. case Key.ControlSpace:
  606. selecting = true;
  607. selectionStartColumn = currentColumn;
  608. selectionStartRow = currentRow;
  609. break;
  610. case (Key)((int)'w' + Key.AltMask):
  611. SetClipboard (GetRegion ());
  612. selecting = false;
  613. break;
  614. case Key.ControlW:
  615. SetClipboard (GetRegion ());
  616. ClearRegion ();
  617. selecting = false;
  618. break;
  619. case (Key)((int)'b' + Key.AltMask):
  620. break;
  621. case (Key)((int)'f' + Key.AltMask):
  622. break;
  623. case Key.Enter:
  624. var orow = currentRow;
  625. currentLine = GetCurrentLine ();
  626. restCount = currentLine.Count - currentColumn;
  627. rest = currentLine.GetRange (currentColumn, restCount);
  628. currentLine.RemoveRange (currentColumn, restCount);
  629. model.AddLine (currentRow + 1, rest);
  630. currentRow++;
  631. bool fullNeedsDisplay = false;
  632. if (currentRow >= topRow + Frame.Height) {
  633. topRow++;
  634. fullNeedsDisplay = true;
  635. }
  636. currentColumn = 0;
  637. if (currentColumn < leftColumn) {
  638. fullNeedsDisplay = true;
  639. leftColumn = 0;
  640. }
  641. if (fullNeedsDisplay)
  642. SetNeedsDisplay ();
  643. else
  644. SetNeedsDisplay (new Rect (0, currentRow - topRow, 0, Frame.Height));
  645. break;
  646. default:
  647. // Ignore control characters and other special keys
  648. if (kb.Key < Key.Space || kb.Key > Key.CharMask)
  649. return false;
  650. Insert ((uint)kb.Key);
  651. currentColumn++;
  652. if (currentColumn >= leftColumn + Frame.Width) {
  653. leftColumn++;
  654. SetNeedsDisplay ();
  655. }
  656. PositionCursor ();
  657. return true;
  658. }
  659. return true;
  660. }
  661. #if false
  662. int WordForward (int p)
  663. {
  664. if (p >= text.Length)
  665. return -1;
  666. int i = p;
  667. if (Rune.IsPunctuation (text [p]) || Rune.IsWhiteSpace (text [p])) {
  668. for (; i < text.Length; i++) {
  669. var r = text [i];
  670. if (Rune.IsLetterOrDigit (r))
  671. break;
  672. }
  673. for (; i < text.Length; i++) {
  674. var r = text [i];
  675. if (!Rune.IsLetterOrDigit (r))
  676. break;
  677. }
  678. } else {
  679. for (; i < text.Length; i++) {
  680. var r = text [i];
  681. if (!Rune.IsLetterOrDigit (r))
  682. break;
  683. }
  684. }
  685. if (i != p)
  686. return i;
  687. return -1;
  688. }
  689. int WordBackward (int p)
  690. {
  691. if (p == 0)
  692. return -1;
  693. int i = p - 1;
  694. if (i == 0)
  695. return 0;
  696. var ti = text [i];
  697. if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
  698. for (; i >= 0; i--) {
  699. if (Rune.IsLetterOrDigit (text [i]))
  700. break;
  701. }
  702. for (; i >= 0; i--) {
  703. if (!Rune.IsLetterOrDigit (text [i]))
  704. break;
  705. }
  706. } else {
  707. for (; i >= 0; i--) {
  708. if (!Rune.IsLetterOrDigit (text [i]))
  709. break;
  710. }
  711. }
  712. i++;
  713. if (i != p)
  714. return i;
  715. return -1;
  716. }
  717. public override bool MouseEvent (MouseEvent ev)
  718. {
  719. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  720. return false;
  721. if (!HasFocus)
  722. SuperView.SetFocus (this);
  723. // We could also set the cursor position.
  724. point = first + ev.X;
  725. if (point > text.Length)
  726. point = text.Length;
  727. if (point < first)
  728. point = 0;
  729. SetNeedsDisplay ();
  730. return true;
  731. }
  732. #endif
  733. }
  734. }