2
0

TextView.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326
  1. //
  2. // TextView.cs: multi-line text editing
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. //
  8. // TODO:
  9. // In ReadOnly mode backspace/space behave like pageup/pagedown
  10. // Attributed text on spans
  11. // Replace insertion with Insert method
  12. // String accumulation (Control-k, control-k is not preserving the last new line, see StringToRunes
  13. // Alt-D, Alt-Backspace
  14. // API to set the cursor position
  15. // API to scroll to a particular place
  16. // keybindings to go to top/bottom
  17. // public API to insert, remove ranges
  18. // Add word forward/word backwards commands
  19. // Save buffer API
  20. // Mouse
  21. //
  22. // Desirable:
  23. // Move all the text manipulation into the TextModel
  24. using System;
  25. using System.Collections.Generic;
  26. using System.IO;
  27. using System.Linq;
  28. using System.Text;
  29. using NStack;
  30. using Rune = System.Rune;
  31. namespace Terminal.Gui {
  32. class TextModel {
  33. List<List<Rune>> lines = new List<List<Rune>> ();
  34. public bool LoadFile (string file)
  35. {
  36. if (file == null)
  37. throw new ArgumentNullException (nameof (file));
  38. try {
  39. FilePath = file;
  40. var stream = File.OpenRead (file);
  41. } catch {
  42. return false;
  43. }
  44. LoadStream (File.OpenRead (file));
  45. return true;
  46. }
  47. public bool CloseFile ()
  48. {
  49. if (FilePath == null)
  50. throw new ArgumentNullException (nameof (FilePath));
  51. try {
  52. FilePath = null;
  53. lines = new List<List<Rune>> ();
  54. } catch {
  55. return false;
  56. }
  57. return true;
  58. }
  59. // Turns the ustring into runes, this does not split the
  60. // contents on a newline if it is present.
  61. internal static List<Rune> ToRunes (ustring str)
  62. {
  63. List<Rune> runes = new List<Rune> ();
  64. foreach (var x in str.ToRunes ()) {
  65. runes.Add (x);
  66. }
  67. return runes;
  68. }
  69. // Splits a string into a List that contains a List<Rune> for each line
  70. public static List<List<Rune>> StringToRunes (ustring content)
  71. {
  72. var lines = new List<List<Rune>> ();
  73. int start = 0, i = 0;
  74. // BUGBUG: I think this is buggy w.r.t Unicode. content.Length is bytes, and content[i] is bytes
  75. // and content[i] == 10 may be the middle of a Rune.
  76. for (; i < content.Length; i++) {
  77. if (content [i] == 10) {
  78. if (i - start > 0)
  79. lines.Add (ToRunes (content [start, i]));
  80. else
  81. lines.Add (ToRunes (ustring.Empty));
  82. start = i + 1;
  83. }
  84. }
  85. if (i - start >= 0)
  86. lines.Add (ToRunes (content [start, null]));
  87. return lines;
  88. }
  89. void Append (List<byte> line)
  90. {
  91. var str = ustring.Make (line.ToArray ());
  92. lines.Add (ToRunes (str));
  93. }
  94. public void LoadStream (Stream input)
  95. {
  96. if (input == null)
  97. throw new ArgumentNullException (nameof (input));
  98. lines = new List<List<Rune>> ();
  99. var buff = new BufferedStream (input);
  100. int v;
  101. var line = new List<byte> ();
  102. while ((v = buff.ReadByte ()) != -1) {
  103. if (v == 10) {
  104. Append (line);
  105. line.Clear ();
  106. continue;
  107. }
  108. line.Add ((byte)v);
  109. }
  110. if (line.Count > 0)
  111. Append (line);
  112. }
  113. public void LoadString (ustring content)
  114. {
  115. lines = StringToRunes (content);
  116. }
  117. public override string ToString ()
  118. {
  119. var sb = new StringBuilder ();
  120. for (int i = 0; i < lines.Count; i++) {
  121. sb.Append (ustring.Make (lines[i]));
  122. if ((i + 1) < lines.Count) {
  123. sb.AppendLine ();
  124. }
  125. }
  126. return sb.ToString ();
  127. }
  128. public string FilePath { get; set; }
  129. /// <summary>
  130. /// The number of text lines in the model
  131. /// </summary>
  132. public int Count => lines.Count;
  133. /// <summary>
  134. /// Returns the specified line as a List of Rune
  135. /// </summary>
  136. /// <returns>The line.</returns>
  137. /// <param name="line">Line number to retrieve.</param>
  138. public List<Rune> GetLine (int line) => line < Count ? lines [line] : lines [Count - 1];
  139. /// <summary>
  140. /// Adds a line to the model at the specified position.
  141. /// </summary>
  142. /// <param name="pos">Line number where the line will be inserted.</param>
  143. /// <param name="runes">The line of text, as a List of Rune.</param>
  144. public void AddLine (int pos, List<Rune> runes)
  145. {
  146. lines.Insert (pos, runes);
  147. }
  148. /// <summary>
  149. /// Removes the line at the specified position
  150. /// </summary>
  151. /// <param name="pos">Position.</param>
  152. public void RemoveLine (int pos)
  153. {
  154. lines.RemoveAt (pos);
  155. }
  156. internal static int SetCol (int col, int width, int cols)
  157. {
  158. if (col + cols <= width) {
  159. col += cols;
  160. }
  161. return col;
  162. }
  163. internal static int GetColFromX (List<Rune> t, int start, int x)
  164. {
  165. if (x < 0) {
  166. return x;
  167. }
  168. int size = start;
  169. var pX = x + start;
  170. for (int i = start; i < t.Count; i++) {
  171. var r = t [i];
  172. size += Rune.ColumnWidth (r);
  173. if (i == pX || (size > pX)) {
  174. return i - start;
  175. }
  176. }
  177. return t.Count - start;
  178. }
  179. }
  180. /// <summary>
  181. /// Multi-line text editing <see cref="View"/>
  182. /// </summary>
  183. /// <remarks>
  184. /// <para>
  185. /// <see cref="TextView"/> provides a multi-line text editor. Users interact
  186. /// with it with the standard Emacs commands for movement or the arrow
  187. /// keys.
  188. /// </para>
  189. /// <list type="table">
  190. /// <listheader>
  191. /// <term>Shortcut</term>
  192. /// <description>Action performed</description>
  193. /// </listheader>
  194. /// <item>
  195. /// <term>Left cursor, Control-b</term>
  196. /// <description>
  197. /// Moves the editing point left.
  198. /// </description>
  199. /// </item>
  200. /// <item>
  201. /// <term>Right cursor, Control-f</term>
  202. /// <description>
  203. /// Moves the editing point right.
  204. /// </description>
  205. /// </item>
  206. /// <item>
  207. /// <term>Alt-b</term>
  208. /// <description>
  209. /// Moves one word back.
  210. /// </description>
  211. /// </item>
  212. /// <item>
  213. /// <term>Alt-f</term>
  214. /// <description>
  215. /// Moves one word forward.
  216. /// </description>
  217. /// </item>
  218. /// <item>
  219. /// <term>Up cursor, Control-p</term>
  220. /// <description>
  221. /// Moves the editing point one line up.
  222. /// </description>
  223. /// </item>
  224. /// <item>
  225. /// <term>Down cursor, Control-n</term>
  226. /// <description>
  227. /// Moves the editing point one line down
  228. /// </description>
  229. /// </item>
  230. /// <item>
  231. /// <term>Home key, Control-a</term>
  232. /// <description>
  233. /// Moves the cursor to the beginning of the line.
  234. /// </description>
  235. /// </item>
  236. /// <item>
  237. /// <term>End key, Control-e</term>
  238. /// <description>
  239. /// Moves the cursor to the end of the line.
  240. /// </description>
  241. /// </item>
  242. /// <item>
  243. /// <term>Control-Home</term>
  244. /// <description>
  245. /// Scrolls to the first line and moves the cursor there.
  246. /// </description>
  247. /// </item>
  248. /// <item>
  249. /// <term>Control-End</term>
  250. /// <description>
  251. /// Scrolls to the last line and moves the cursor there.
  252. /// </description>
  253. /// </item>
  254. /// <item>
  255. /// <term>Delete, Control-d</term>
  256. /// <description>
  257. /// Deletes the character in front of the cursor.
  258. /// </description>
  259. /// </item>
  260. /// <item>
  261. /// <term>Backspace</term>
  262. /// <description>
  263. /// Deletes the character behind the cursor.
  264. /// </description>
  265. /// </item>
  266. /// <item>
  267. /// <term>Control-k</term>
  268. /// <description>
  269. /// Deletes the text until the end of the line and replaces the kill buffer
  270. /// with the deleted text. You can paste this text in a different place by
  271. /// using Control-y.
  272. /// </description>
  273. /// </item>
  274. /// <item>
  275. /// <term>Control-y</term>
  276. /// <description>
  277. /// Pastes the content of the kill ring into the current position.
  278. /// </description>
  279. /// </item>
  280. /// <item>
  281. /// <term>Alt-d</term>
  282. /// <description>
  283. /// Deletes the word above the cursor and adds it to the kill ring. You
  284. /// can paste the contents of the kill ring with Control-y.
  285. /// </description>
  286. /// </item>
  287. /// <item>
  288. /// <term>Control-q</term>
  289. /// <description>
  290. /// Quotes the next input character, to prevent the normal processing of
  291. /// key handling to take place.
  292. /// </description>
  293. /// </item>
  294. /// </list>
  295. /// </remarks>
  296. public class TextView : View {
  297. TextModel model = new TextModel ();
  298. int topRow;
  299. int leftColumn;
  300. int currentRow;
  301. int currentColumn;
  302. int selectionStartColumn, selectionStartRow;
  303. bool selecting;
  304. //bool used;
  305. /// <summary>
  306. /// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes.
  307. /// </summary>
  308. public event Action TextChanged;
  309. #if false
  310. /// <summary>
  311. /// Changed event, raised when the text has clicked.
  312. /// </summary>
  313. /// <remarks>
  314. /// Client code can hook up to this event, it is
  315. /// raised when the text in the entry changes.
  316. /// </remarks>
  317. public Action Changed;
  318. #endif
  319. /// <summary>
  320. /// Initializes a <see cref="TextView"/> on the specified area, with absolute position and size.
  321. /// </summary>
  322. /// <remarks>
  323. /// </remarks>
  324. public TextView (Rect frame) : base (frame)
  325. {
  326. CanFocus = true;
  327. }
  328. /// <summary>
  329. /// Initializes a <see cref="TextView"/> on the specified area,
  330. /// with dimensions controlled with the X, Y, Width and Height properties.
  331. /// </summary>
  332. public TextView () : base ()
  333. {
  334. CanFocus = true;
  335. }
  336. void ResetPosition ()
  337. {
  338. topRow = leftColumn = currentRow = currentColumn = 0;
  339. }
  340. /// <summary>
  341. /// Sets or gets the text in the <see cref="TextView"/>.
  342. /// </summary>
  343. /// <remarks>
  344. /// </remarks>
  345. public override ustring Text {
  346. get {
  347. return model.ToString ();
  348. }
  349. set {
  350. ResetPosition ();
  351. model.LoadString (value);
  352. TextChanged?.Invoke ();
  353. SetNeedsDisplay ();
  354. }
  355. }
  356. /// <summary>
  357. /// Loads the contents of the file into the <see cref="TextView"/>.
  358. /// </summary>
  359. /// <returns><c>true</c>, if file was loaded, <c>false</c> otherwise.</returns>
  360. /// <param name="path">Path to the file to load.</param>
  361. public bool LoadFile (string path)
  362. {
  363. if (path == null)
  364. throw new ArgumentNullException (nameof (path));
  365. ResetPosition ();
  366. var res = model.LoadFile (path);
  367. SetNeedsDisplay ();
  368. return res;
  369. }
  370. /// <summary>
  371. /// Loads the contents of the stream into the <see cref="TextView"/>.
  372. /// </summary>
  373. /// <returns><c>true</c>, if stream was loaded, <c>false</c> otherwise.</returns>
  374. /// <param name="stream">Stream to load the contents from.</param>
  375. public void LoadStream (Stream stream)
  376. {
  377. if (stream == null)
  378. throw new ArgumentNullException (nameof (stream));
  379. ResetPosition ();
  380. model.LoadStream (stream);
  381. SetNeedsDisplay ();
  382. }
  383. /// <summary>
  384. /// Closes the contents of the stream into the <see cref="TextView"/>.
  385. /// </summary>
  386. /// <returns><c>true</c>, if stream was closed, <c>false</c> otherwise.</returns>
  387. public bool CloseFile ()
  388. {
  389. ResetPosition ();
  390. var res = model.CloseFile ();
  391. SetNeedsDisplay ();
  392. return res;
  393. }
  394. /// <summary>
  395. /// Gets the current cursor row.
  396. /// </summary>
  397. public int CurrentRow => currentRow;
  398. /// <summary>
  399. /// Gets the cursor column.
  400. /// </summary>
  401. /// <value>The cursor column.</value>
  402. public int CurrentColumn => currentColumn;
  403. /// <summary>
  404. /// Positions the cursor on the current row and column
  405. /// </summary>
  406. public override void PositionCursor ()
  407. {
  408. if (selecting) {
  409. var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
  410. var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
  411. SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow));
  412. }
  413. var line = model.GetLine (currentRow);
  414. var retreat = 0;
  415. var col = 0;
  416. if (line.Count > 0) {
  417. retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
  418. ? 1 : 0), 0);
  419. for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) {
  420. if (idx == CurrentColumn)
  421. break;
  422. var cols = Rune.ColumnWidth (line [idx]);
  423. col += cols - 1;
  424. }
  425. }
  426. Move (CurrentColumn - leftColumn - retreat + col, CurrentRow - topRow);
  427. }
  428. void ClearRegion (int left, int top, int right, int bottom)
  429. {
  430. for (int row = top; row < bottom; row++) {
  431. Move (left, row);
  432. for (int col = left; col < right; col++)
  433. AddRune (col, row, ' ');
  434. }
  435. }
  436. void ColorNormal ()
  437. {
  438. Driver.SetAttribute (ColorScheme.Normal);
  439. }
  440. void ColorSelection ()
  441. {
  442. if (HasFocus)
  443. Driver.SetAttribute (ColorScheme.Focus);
  444. else
  445. Driver.SetAttribute (ColorScheme.Normal);
  446. }
  447. bool isReadOnly = false;
  448. /// <summary>
  449. /// Gets or sets whether the <see cref="TextView"/> is in read-only mode or not
  450. /// </summary>
  451. /// <value>Boolean value(Default false)</value>
  452. public bool ReadOnly {
  453. get => isReadOnly;
  454. set {
  455. isReadOnly = value;
  456. }
  457. }
  458. // Returns an encoded region start..end (top 32 bits are the row, low32 the column)
  459. void GetEncodedRegionBounds (out long start, out long end)
  460. {
  461. long selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn;
  462. long point = ((long)(uint)currentRow << 32) | (uint)currentColumn;
  463. if (selection > point) {
  464. start = point;
  465. end = selection;
  466. } else {
  467. start = selection;
  468. end = point;
  469. }
  470. }
  471. bool PointInSelection (int col, int row)
  472. {
  473. long start, end;
  474. GetEncodedRegionBounds (out start, out end);
  475. var q = ((long)(uint)row << 32) | (uint)col;
  476. return q >= start && q <= end;
  477. }
  478. //
  479. // Returns a ustring with the text in the selected
  480. // region.
  481. //
  482. ustring GetRegion ()
  483. {
  484. long start, end;
  485. GetEncodedRegionBounds (out start, out end);
  486. int startRow = (int)(start >> 32);
  487. var maxrow = ((int)(end >> 32));
  488. int startCol = (int)(start & 0xffffffff);
  489. var endCol = (int)(end & 0xffffffff);
  490. var line = model.GetLine (startRow);
  491. if (startRow == maxrow)
  492. return StringFromRunes (line.GetRange (startCol, endCol));
  493. ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));
  494. for (int row = startRow + 1; row < maxrow; row++) {
  495. res = res + ustring.Make ((Rune)10) + StringFromRunes (model.GetLine (row));
  496. }
  497. line = model.GetLine (maxrow);
  498. res = res + ustring.Make ((Rune)10) + StringFromRunes (line.GetRange (0, endCol));
  499. return res;
  500. }
  501. //
  502. // Clears the contents of the selected region
  503. //
  504. void ClearRegion ()
  505. {
  506. long start, end;
  507. long currentEncoded = ((long)(uint)currentRow << 32) | (uint)currentColumn;
  508. GetEncodedRegionBounds (out start, out end);
  509. int startRow = (int)(start >> 32);
  510. var maxrow = ((int)(end >> 32));
  511. int startCol = (int)(start & 0xffffffff);
  512. var endCol = (int)(end & 0xffffffff);
  513. var line = model.GetLine (startRow);
  514. if (startRow == maxrow) {
  515. line.RemoveRange (startCol, endCol - startCol);
  516. currentColumn = startCol;
  517. SetNeedsDisplay (new Rect (0, startRow - topRow, Frame.Width, startRow - topRow + 1));
  518. return;
  519. }
  520. line.RemoveRange (startCol, line.Count - startCol);
  521. var line2 = model.GetLine (maxrow);
  522. line.AddRange (line2.Skip (endCol));
  523. for (int row = startRow + 1; row <= maxrow; row++) {
  524. model.RemoveLine (startRow + 1);
  525. }
  526. if (currentEncoded == end) {
  527. currentRow -= maxrow - (startRow);
  528. }
  529. currentColumn = startCol;
  530. SetNeedsDisplay ();
  531. }
  532. ///<inheritdoc/>
  533. public override void Redraw (Rect bounds)
  534. {
  535. ColorNormal ();
  536. int bottom = bounds.Bottom;
  537. int right = bounds.Right;
  538. for (int row = bounds.Top; row < bottom; row++) {
  539. int textLine = topRow + row;
  540. if (textLine >= model.Count) {
  541. ColorNormal ();
  542. ClearRegion (bounds.Left, row, bounds.Right, row + 1);
  543. continue;
  544. }
  545. var line = model.GetLine (textLine);
  546. int lineRuneCount = line.Count;
  547. if (line.Count < bounds.Left) {
  548. ClearRegion (bounds.Left, row, bounds.Right, row + 1);
  549. continue;
  550. }
  551. Move (bounds.Left, row);
  552. var col = 0;
  553. for (int idx = bounds.Left; idx < right; idx++) {
  554. var lineCol = leftColumn + idx;
  555. var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
  556. var cols = Rune.ColumnWidth (rune);
  557. if (selecting && PointInSelection (idx, row)) {
  558. ColorSelection ();
  559. } else {
  560. ColorNormal ();
  561. }
  562. if (!SpecialRune (rune)) {
  563. AddRune (col, row, rune);
  564. }
  565. col = TextModel.SetCol (col, bounds.Right, cols);
  566. }
  567. }
  568. PositionCursor ();
  569. }
  570. bool SpecialRune (Rune rune)
  571. {
  572. switch (rune) {
  573. case (uint)Key.Enter:
  574. case 0xd:
  575. return true;
  576. default:
  577. return false; }
  578. }
  579. ///<inheritdoc/>
  580. public override bool CanFocus {
  581. get => base.CanFocus;
  582. set { base.CanFocus = value; }
  583. }
  584. void SetClipboard (ustring text)
  585. {
  586. Clipboard.Contents = text;
  587. }
  588. void AppendClipboard (ustring text)
  589. {
  590. Clipboard.Contents = Clipboard.Contents + text;
  591. }
  592. void Insert (Rune rune)
  593. {
  594. var line = GetCurrentLine ();
  595. line.Insert (currentColumn, rune);
  596. var prow = currentRow - topRow;
  597. SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
  598. }
  599. ustring StringFromRunes (List<Rune> runes)
  600. {
  601. if (runes == null)
  602. throw new ArgumentNullException (nameof (runes));
  603. int size = 0;
  604. foreach (var rune in runes) {
  605. size += Utf8.RuneLen (rune);
  606. }
  607. var encoded = new byte [size];
  608. int offset = 0;
  609. foreach (var rune in runes) {
  610. offset += Utf8.EncodeRune (rune, encoded, offset);
  611. }
  612. return ustring.Make (encoded);
  613. }
  614. List<Rune> GetCurrentLine () => model.GetLine (currentRow);
  615. void InsertText (ustring text)
  616. {
  617. var lines = TextModel.StringToRunes (text);
  618. if (lines.Count == 0)
  619. return;
  620. var line = GetCurrentLine ();
  621. // Optimize single line
  622. if (lines.Count == 1) {
  623. line.InsertRange (currentColumn, lines [0]);
  624. currentColumn += lines [0].Count;
  625. if (currentColumn - leftColumn > Frame.Width)
  626. leftColumn = currentColumn - Frame.Width + 1;
  627. SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
  628. return;
  629. }
  630. // Keep a copy of the rest of the line
  631. var restCount = line.Count - currentColumn;
  632. var rest = line.GetRange (currentColumn, restCount);
  633. line.RemoveRange (currentColumn, restCount);
  634. // First line is inserted at the current location, the rest is appended
  635. line.InsertRange (currentColumn, lines [0]);
  636. for (int i = 1; i < lines.Count; i++)
  637. model.AddLine (currentRow + i, lines [i]);
  638. var last = model.GetLine (currentRow + lines.Count - 1);
  639. var lastp = last.Count;
  640. last.InsertRange (last.Count, rest);
  641. // Now adjust column and row positions
  642. currentRow += lines.Count - 1;
  643. currentColumn = lastp;
  644. if (currentRow - topRow > Frame.Height) {
  645. topRow = currentRow - Frame.Height + 1;
  646. if (topRow < 0)
  647. topRow = 0;
  648. }
  649. if (currentColumn < leftColumn)
  650. leftColumn = currentColumn;
  651. if (currentColumn - leftColumn >= Frame.Width)
  652. leftColumn = currentColumn - Frame.Width + 1;
  653. SetNeedsDisplay ();
  654. }
  655. // The column we are tracking, or -1 if we are not tracking any column
  656. int columnTrack = -1;
  657. // Tries to snap the cursor to the tracking column
  658. void TrackColumn ()
  659. {
  660. // Now track the column
  661. var line = GetCurrentLine ();
  662. if (line.Count < columnTrack)
  663. currentColumn = line.Count;
  664. else if (columnTrack != -1)
  665. currentColumn = columnTrack;
  666. else if (currentColumn > line.Count)
  667. currentColumn = line.Count;
  668. Adjust ();
  669. }
  670. void Adjust ()
  671. {
  672. bool need = false;
  673. if (currentColumn < leftColumn) {
  674. currentColumn = leftColumn;
  675. need = true;
  676. }
  677. if (currentColumn - leftColumn > Frame.Width) {
  678. leftColumn = currentColumn - Frame.Width + 1;
  679. need = true;
  680. }
  681. if (currentRow < topRow) {
  682. topRow = currentRow;
  683. need = true;
  684. }
  685. if (currentRow - topRow > Frame.Height) {
  686. topRow = currentRow - Frame.Height + 1;
  687. need = true;
  688. }
  689. if (need)
  690. SetNeedsDisplay ();
  691. else
  692. PositionCursor ();
  693. }
  694. /// <summary>
  695. /// Will scroll the <see cref="TextView"/> to display the specified row at the top
  696. /// </summary>
  697. /// <param name="row">Row that should be displayed at the top, if the value is negative it will be reset to zero</param>
  698. public void ScrollTo (int row)
  699. {
  700. if (row < 0)
  701. row = 0;
  702. topRow = row > model.Count ? model.Count - 1 : row;
  703. SetNeedsDisplay ();
  704. }
  705. bool lastWasKill;
  706. ///<inheritdoc/>
  707. public override bool ProcessKey (KeyEvent kb)
  708. {
  709. int restCount;
  710. List<Rune> rest;
  711. // Handle some state here - whether the last command was a kill
  712. // operation and the column tracking (up/down)
  713. switch (kb.Key) {
  714. case Key.N | Key.CtrlMask:
  715. case Key.CursorDown:
  716. case Key.P | Key.CtrlMask:
  717. case Key.CursorUp:
  718. lastWasKill = false;
  719. break;
  720. case Key.K | Key.CtrlMask:
  721. break;
  722. default:
  723. lastWasKill = false;
  724. columnTrack = -1;
  725. break;
  726. }
  727. // Dispatch the command.
  728. switch (kb.Key) {
  729. case Key.PageDown:
  730. case Key.V | Key.CtrlMask:
  731. int nPageDnShift = Frame.Height - 1;
  732. if (currentRow < model.Count) {
  733. if (columnTrack == -1)
  734. columnTrack = currentColumn;
  735. currentRow = (currentRow + nPageDnShift) > model.Count ? model.Count : currentRow + nPageDnShift;
  736. if (topRow < currentRow - nPageDnShift) {
  737. topRow = currentRow >= model.Count ? currentRow - nPageDnShift : topRow + nPageDnShift;
  738. SetNeedsDisplay ();
  739. }
  740. TrackColumn ();
  741. PositionCursor ();
  742. }
  743. break;
  744. case Key.PageUp:
  745. case ((int)'v' + Key.AltMask):
  746. int nPageUpShift = Frame.Height - 1;
  747. if (currentRow > 0) {
  748. if (columnTrack == -1)
  749. columnTrack = currentColumn;
  750. currentRow = currentRow - nPageUpShift < 0 ? 0 : currentRow - nPageUpShift;
  751. if (currentRow < topRow) {
  752. topRow = topRow - nPageUpShift < 0 ? 0 : topRow - nPageUpShift;
  753. SetNeedsDisplay ();
  754. }
  755. TrackColumn ();
  756. PositionCursor ();
  757. }
  758. break;
  759. case Key.N | Key.CtrlMask:
  760. case Key.CursorDown:
  761. MoveDown ();
  762. break;
  763. case Key.P | Key.CtrlMask:
  764. case Key.CursorUp:
  765. MoveUp ();
  766. break;
  767. case Key.F | Key.CtrlMask:
  768. case Key.CursorRight:
  769. var currentLine = GetCurrentLine ();
  770. if (currentColumn < currentLine.Count) {
  771. currentColumn++;
  772. if (currentColumn >= leftColumn + Frame.Width) {
  773. leftColumn++;
  774. SetNeedsDisplay ();
  775. }
  776. PositionCursor ();
  777. } else {
  778. if (currentRow + 1 < model.Count) {
  779. currentRow++;
  780. currentColumn = 0;
  781. leftColumn = 0;
  782. if (currentRow >= topRow + Frame.Height) {
  783. topRow++;
  784. }
  785. SetNeedsDisplay ();
  786. PositionCursor ();
  787. }
  788. break;
  789. }
  790. break;
  791. case Key.B | Key.CtrlMask:
  792. case Key.CursorLeft:
  793. if (currentColumn > 0) {
  794. currentColumn--;
  795. if (currentColumn < leftColumn) {
  796. leftColumn--;
  797. SetNeedsDisplay ();
  798. }
  799. PositionCursor ();
  800. } else {
  801. if (currentRow > 0) {
  802. currentRow--;
  803. if (currentRow < topRow) {
  804. topRow--;
  805. SetNeedsDisplay ();
  806. }
  807. currentLine = GetCurrentLine ();
  808. currentColumn = currentLine.Count;
  809. int prev = leftColumn;
  810. leftColumn = currentColumn - Frame.Width + 1;
  811. if (leftColumn < 0)
  812. leftColumn = 0;
  813. if (prev != leftColumn)
  814. SetNeedsDisplay ();
  815. PositionCursor ();
  816. }
  817. }
  818. break;
  819. case Key.Delete:
  820. case Key.Backspace:
  821. if (isReadOnly)
  822. break;
  823. if (currentColumn > 0) {
  824. // Delete backwards
  825. currentLine = GetCurrentLine ();
  826. currentLine.RemoveAt (currentColumn - 1);
  827. currentColumn--;
  828. if (currentColumn < leftColumn) {
  829. leftColumn--;
  830. SetNeedsDisplay ();
  831. } else
  832. SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width));
  833. } else {
  834. // Merges the current line with the previous one.
  835. if (currentRow == 0)
  836. return true;
  837. var prowIdx = currentRow - 1;
  838. var prevRow = model.GetLine (prowIdx);
  839. var prevCount = prevRow.Count;
  840. model.GetLine (prowIdx).AddRange (GetCurrentLine ());
  841. model.RemoveLine (currentRow);
  842. currentRow--;
  843. currentColumn = prevCount;
  844. leftColumn = currentColumn - Frame.Width + 1;
  845. if (leftColumn < 0)
  846. leftColumn = 0;
  847. SetNeedsDisplay ();
  848. }
  849. break;
  850. // Home, C-A
  851. case Key.Home:
  852. case Key.A | Key.CtrlMask:
  853. currentColumn = 0;
  854. if (currentColumn < leftColumn) {
  855. leftColumn = 0;
  856. SetNeedsDisplay ();
  857. } else
  858. PositionCursor ();
  859. break;
  860. case Key.DeleteChar:
  861. case Key.D | Key.CtrlMask: // Delete
  862. if (isReadOnly)
  863. break;
  864. currentLine = GetCurrentLine ();
  865. if (currentColumn == currentLine.Count) {
  866. if (currentRow + 1 == model.Count)
  867. break;
  868. var nextLine = model.GetLine (currentRow + 1);
  869. currentLine.AddRange (nextLine);
  870. model.RemoveLine (currentRow + 1);
  871. var sr = currentRow - topRow;
  872. SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
  873. } else {
  874. currentLine.RemoveAt (currentColumn);
  875. var r = currentRow - topRow;
  876. SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1));
  877. }
  878. break;
  879. case Key.End:
  880. case Key.E | Key.CtrlMask: // End
  881. currentLine = GetCurrentLine ();
  882. currentColumn = currentLine.Count;
  883. int pcol = leftColumn;
  884. leftColumn = currentColumn - Frame.Width + 1;
  885. if (leftColumn < 0)
  886. leftColumn = 0;
  887. if (pcol != leftColumn)
  888. SetNeedsDisplay ();
  889. PositionCursor ();
  890. break;
  891. case Key.K | Key.CtrlMask: // kill-to-end
  892. if (isReadOnly)
  893. break;
  894. currentLine = GetCurrentLine ();
  895. if (currentLine.Count == 0) {
  896. model.RemoveLine (currentRow);
  897. var val = ustring.Make ((Rune)'\n');
  898. if (lastWasKill)
  899. AppendClipboard (val);
  900. else
  901. SetClipboard (val);
  902. } else {
  903. restCount = currentLine.Count - currentColumn;
  904. rest = currentLine.GetRange (currentColumn, restCount);
  905. var val = StringFromRunes (rest);
  906. if (lastWasKill)
  907. AppendClipboard (val);
  908. else
  909. SetClipboard (val);
  910. currentLine.RemoveRange (currentColumn, restCount);
  911. }
  912. SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
  913. lastWasKill = true;
  914. break;
  915. case Key.Y | Key.CtrlMask: // Control-y, yank
  916. if (isReadOnly)
  917. break;
  918. InsertText (Clipboard.Contents);
  919. selecting = false;
  920. break;
  921. case Key.Space | Key.CtrlMask:
  922. selecting = true;
  923. selectionStartColumn = currentColumn;
  924. selectionStartRow = currentRow;
  925. break;
  926. case ((int)'w' + Key.AltMask):
  927. SetClipboard (GetRegion ());
  928. selecting = false;
  929. break;
  930. case Key.W | Key.CtrlMask:
  931. SetClipboard (GetRegion ());
  932. if (!isReadOnly)
  933. ClearRegion ();
  934. selecting = false;
  935. break;
  936. case (Key)((int)'b' + Key.AltMask):
  937. var newPos = WordBackward (currentColumn, currentRow);
  938. if (newPos.HasValue) {
  939. currentColumn = newPos.Value.col;
  940. currentRow = newPos.Value.row;
  941. }
  942. Adjust ();
  943. break;
  944. case (Key)((int)'f' + Key.AltMask):
  945. newPos = WordForward (currentColumn, currentRow);
  946. if (newPos.HasValue) {
  947. currentColumn = newPos.Value.col;
  948. currentRow = newPos.Value.row;
  949. }
  950. Adjust ();
  951. break;
  952. case Key.Enter:
  953. if (isReadOnly)
  954. break;
  955. var orow = currentRow;
  956. currentLine = GetCurrentLine ();
  957. restCount = currentLine.Count - currentColumn;
  958. rest = currentLine.GetRange (currentColumn, restCount);
  959. currentLine.RemoveRange (currentColumn, restCount);
  960. model.AddLine (currentRow + 1, rest);
  961. currentRow++;
  962. bool fullNeedsDisplay = false;
  963. if (currentRow >= topRow + Frame.Height) {
  964. topRow++;
  965. fullNeedsDisplay = true;
  966. }
  967. currentColumn = 0;
  968. if (currentColumn < leftColumn) {
  969. fullNeedsDisplay = true;
  970. leftColumn = 0;
  971. }
  972. if (fullNeedsDisplay)
  973. SetNeedsDisplay ();
  974. else
  975. SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height));
  976. break;
  977. case Key.CtrlMask | Key.End:
  978. MoveEnd ();
  979. break;
  980. case Key.CtrlMask | Key.Home:
  981. MoveHome ();
  982. break;
  983. default:
  984. // Ignore control characters and other special keys
  985. if (kb.Key < Key.Space || kb.Key > Key.CharMask)
  986. return false;
  987. //So that special keys like tab can be processed
  988. if (isReadOnly)
  989. return true;
  990. Insert ((uint)kb.Key);
  991. currentColumn++;
  992. if (currentColumn >= leftColumn + Frame.Width) {
  993. leftColumn++;
  994. SetNeedsDisplay ();
  995. }
  996. PositionCursor ();
  997. return true;
  998. }
  999. return true;
  1000. }
  1001. private void MoveUp ()
  1002. {
  1003. if (currentRow > 0) {
  1004. if (columnTrack == -1)
  1005. columnTrack = currentColumn;
  1006. currentRow--;
  1007. if (currentRow < topRow) {
  1008. topRow--;
  1009. SetNeedsDisplay ();
  1010. }
  1011. TrackColumn ();
  1012. PositionCursor ();
  1013. }
  1014. }
  1015. private void MoveDown ()
  1016. {
  1017. if (currentRow + 1 < model.Count) {
  1018. if (columnTrack == -1)
  1019. columnTrack = currentColumn;
  1020. currentRow++;
  1021. if (currentRow >= topRow + Frame.Height) {
  1022. topRow++;
  1023. SetNeedsDisplay ();
  1024. }
  1025. TrackColumn ();
  1026. PositionCursor ();
  1027. }
  1028. }
  1029. IEnumerable<(int col, int row, Rune rune)> ForwardIterator (int col, int row)
  1030. {
  1031. if (col < 0 || row < 0)
  1032. yield break;
  1033. if (row >= model.Count)
  1034. yield break;
  1035. var line = GetCurrentLine ();
  1036. if (col >= line.Count)
  1037. yield break;
  1038. while (row < model.Count) {
  1039. for (int c = col; c < line.Count; c++) {
  1040. yield return (c, row, line [c]);
  1041. }
  1042. col = 0;
  1043. row++;
  1044. line = GetCurrentLine ();
  1045. }
  1046. }
  1047. Rune RuneAt (int col, int row) => model.GetLine (row) [col];
  1048. /// <summary>
  1049. /// Will scroll the <see cref="TextView"/> to the last line and position the cursor there.
  1050. /// </summary>
  1051. public void MoveEnd ()
  1052. {
  1053. currentRow = model.Count - 1;
  1054. TrackColumn ();
  1055. PositionCursor ();
  1056. }
  1057. /// <summary>
  1058. /// Will scroll the <see cref="TextView"/> to the first line and position the cursor there.
  1059. /// </summary>
  1060. public void MoveHome ()
  1061. {
  1062. currentRow = 0;
  1063. TrackColumn ();
  1064. PositionCursor ();
  1065. }
  1066. bool MoveNext (ref int col, ref int row, out Rune rune)
  1067. {
  1068. var line = model.GetLine (row);
  1069. if (col + 1 < line.Count) {
  1070. col++;
  1071. rune = line [col];
  1072. return true;
  1073. }
  1074. while (row + 1 < model.Count) {
  1075. col = 0;
  1076. row++;
  1077. line = model.GetLine (row);
  1078. if (line.Count > 0) {
  1079. rune = line [0];
  1080. return true;
  1081. }
  1082. }
  1083. rune = 0;
  1084. return false;
  1085. }
  1086. bool MovePrev (ref int col, ref int row, out Rune rune)
  1087. {
  1088. var line = model.GetLine (row);
  1089. if (col > 0) {
  1090. col--;
  1091. rune = line [col];
  1092. return true;
  1093. }
  1094. if (row == 0) {
  1095. rune = 0;
  1096. return false;
  1097. }
  1098. while (row > 0) {
  1099. row--;
  1100. line = model.GetLine (row);
  1101. col = line.Count - 1;
  1102. if (col >= 0) {
  1103. rune = line [col];
  1104. return true;
  1105. }
  1106. }
  1107. rune = 0;
  1108. return false;
  1109. }
  1110. (int col, int row)? WordForward (int fromCol, int fromRow)
  1111. {
  1112. var col = fromCol;
  1113. var row = fromRow;
  1114. var line = GetCurrentLine ();
  1115. var rune = RuneAt (col, row);
  1116. var srow = row;
  1117. if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
  1118. while (MoveNext (ref col, ref row, out rune)) {
  1119. if (Rune.IsLetterOrDigit (rune))
  1120. break;
  1121. }
  1122. while (MoveNext (ref col, ref row, out rune)) {
  1123. if (!Rune.IsLetterOrDigit (rune))
  1124. break;
  1125. }
  1126. } else {
  1127. while (MoveNext (ref col, ref row, out rune)) {
  1128. if (!Rune.IsLetterOrDigit (rune))
  1129. break;
  1130. }
  1131. }
  1132. if (fromCol != col || fromRow != row)
  1133. return (col, row);
  1134. return null;
  1135. }
  1136. (int col, int row)? WordBackward (int fromCol, int fromRow)
  1137. {
  1138. if (fromRow == 0 && fromCol == 0)
  1139. return null;
  1140. var col = fromCol;
  1141. var row = fromRow;
  1142. var line = GetCurrentLine ();
  1143. var rune = RuneAt (col, row);
  1144. if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
  1145. while (MovePrev (ref col, ref row, out rune)) {
  1146. if (Rune.IsLetterOrDigit (rune))
  1147. break;
  1148. }
  1149. while (MovePrev (ref col, ref row, out rune)) {
  1150. if (!Rune.IsLetterOrDigit (rune))
  1151. break;
  1152. }
  1153. } else {
  1154. while (MovePrev (ref col, ref row, out rune)) {
  1155. if (!Rune.IsLetterOrDigit (rune))
  1156. break;
  1157. }
  1158. }
  1159. if (fromCol != col || fromRow != row)
  1160. return (col, row);
  1161. return null;
  1162. }
  1163. ///<inheritdoc/>
  1164. public override bool MouseEvent (MouseEvent ev)
  1165. {
  1166. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) &&
  1167. !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)) {
  1168. return false;
  1169. }
  1170. if (!CanFocus) {
  1171. return true;
  1172. }
  1173. if (!HasFocus) {
  1174. SetFocus ();
  1175. }
  1176. if (ev.Flags == MouseFlags.Button1Clicked) {
  1177. if (model.Count > 0) {
  1178. var maxCursorPositionableLine = (model.Count - 1) - topRow;
  1179. if (ev.Y > maxCursorPositionableLine) {
  1180. currentRow = maxCursorPositionableLine;
  1181. } else {
  1182. currentRow = ev.Y + topRow;
  1183. }
  1184. var r = GetCurrentLine ();
  1185. var idx = TextModel.GetColFromX (r, leftColumn, ev.X);
  1186. if (idx - leftColumn >= r.Count) {
  1187. currentColumn = r.Count - leftColumn;
  1188. } else {
  1189. currentColumn = idx - leftColumn;
  1190. }
  1191. }
  1192. PositionCursor ();
  1193. } else if (ev.Flags == MouseFlags.WheeledDown) {
  1194. lastWasKill = false;
  1195. MoveDown ();
  1196. } else if (ev.Flags == MouseFlags.WheeledUp) {
  1197. lastWasKill = false;
  1198. MoveUp ();
  1199. }
  1200. return true;
  1201. }
  1202. }
  1203. }