TextView.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. //
  2. // TextView.cs: multi-line text editing
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Text;
  12. using NStack;
  13. namespace Terminal.Gui {
  14. class TextModel {
  15. List<List<Rune>> lines;
  16. List<int> lineLength;
  17. public bool LoadFile (string file)
  18. {
  19. if (file == null)
  20. throw new ArgumentNullException (nameof (file));
  21. try {
  22. var stream = File.OpenRead (file);
  23. if (stream == null)
  24. return false;
  25. } catch {
  26. return false;
  27. }
  28. LoadStream (File.OpenRead (file));
  29. return true;
  30. }
  31. List<Rune> ToRunes (ustring str)
  32. {
  33. List<Rune> runes = new List<Rune> ();
  34. foreach (var x in str.ToRunes ()) {
  35. runes.Add (x);
  36. }
  37. return runes;
  38. }
  39. void Append (List<byte> line)
  40. {
  41. var str = ustring.Make (line.ToArray ());
  42. lines.Add (ToRunes (str));
  43. }
  44. public void LoadStream (Stream input)
  45. {
  46. if (input == null)
  47. throw new ArgumentNullException (nameof (input));
  48. lines = new List<List<Rune>> ();
  49. var buff = new BufferedStream (input);
  50. int v;
  51. var line = new List<byte> ();
  52. while ((v = buff.ReadByte ()) != -1) {
  53. if (v == 10) {
  54. Append (line);
  55. line.Clear ();
  56. continue;
  57. }
  58. line.Add ((byte)v);
  59. }
  60. if (line.Count > 0)
  61. Append (line);
  62. }
  63. public void LoadString (ustring content)
  64. {
  65. lines = new List<List<Rune>> ();
  66. int start = 0, i = 0;
  67. for (; i < content.Length; i++) {
  68. if (content [i] == 10) {
  69. if (i - start > 0)
  70. lines.Add (ToRunes (content [start, i]));
  71. else
  72. lines.Add (ToRunes (ustring.Empty));
  73. start = i + 1;
  74. }
  75. }
  76. if (i - start > 0)
  77. lines.Add (ToRunes (content [start, null]));
  78. }
  79. public override string ToString ()
  80. {
  81. var sb = new StringBuilder ();
  82. foreach (var line in lines) {
  83. sb.Append (line);
  84. sb.AppendLine ();
  85. }
  86. return sb.ToString ();
  87. }
  88. public int Count => lines.Count;
  89. public List<Rune> GetLine (int line) => lines [line];
  90. }
  91. /// <summary>
  92. /// Text data entry widget
  93. /// </summary>
  94. /// <remarks>
  95. /// The Entry widget provides Emacs-like editing
  96. /// functionality, and mouse support.
  97. /// </remarks>
  98. public class TextView : View {
  99. TextModel model = new TextModel ();
  100. int topRow;
  101. int leftColumn;
  102. int currentRow;
  103. int currentColumn;
  104. bool used;
  105. /// <summary>
  106. /// Changed event, raised when the text has clicked.
  107. /// </summary>
  108. /// <remarks>
  109. /// Client code can hook up to this event, it is
  110. /// raised when the text in the entry changes.
  111. /// </remarks>
  112. public event EventHandler Changed;
  113. /// <summary>
  114. /// Public constructor.
  115. /// </summary>
  116. /// <remarks>
  117. /// </remarks>
  118. public TextView (Rect frame) : base (frame)
  119. {
  120. CanFocus = true;
  121. }
  122. void ResetPosition ()
  123. {
  124. topRow = leftColumn = currentRow = currentColumn = 0;
  125. }
  126. /// <summary>
  127. /// Sets or gets the text in the entry.
  128. /// </summary>
  129. /// <remarks>
  130. /// </remarks>
  131. public ustring Text {
  132. get {
  133. return model.ToString ();
  134. }
  135. set {
  136. ResetPosition ();
  137. model.LoadString (value);
  138. SetNeedsDisplay ();
  139. }
  140. }
  141. /// <summary>
  142. /// The current cursor row.
  143. /// </summary>
  144. public int CurrentRow => currentRow;
  145. /// <summary>
  146. /// Gets the cursor column.
  147. /// </summary>
  148. /// <value>The cursor column.</value>
  149. public int CurrentColumn => currentColumn;
  150. /// <summary>
  151. /// Sets the cursor position.
  152. /// </summary>
  153. public override void PositionCursor ()
  154. {
  155. Move (CurrentColumn - leftColumn, CurrentRow - topRow);
  156. }
  157. void ClearRegion (int left, int top, int right, int bottom)
  158. {
  159. for (int row = top; row < bottom; row++) {
  160. Move (left, row);
  161. for (int col = left; col < right; col++)
  162. AddRune (col, row, ' ');
  163. }
  164. }
  165. public override void Redraw (Rect region)
  166. {
  167. Driver.SetAttribute (ColorScheme.Focus);
  168. Move (0, 0);
  169. int bottom = region.Bottom;
  170. int right = region.Right;
  171. for (int row = region.Top; row < bottom; row++) {
  172. int textLine = topRow + row;
  173. if (textLine >= model.Count) {
  174. ClearRegion (region.Left, row, region.Right, row + 1);
  175. continue;
  176. }
  177. var line = model.GetLine (textLine);
  178. int lineRuneCount = line.Count;
  179. if (line.Count < region.Left){
  180. ClearRegion (region.Left, row, region.Right, row + 1);
  181. continue;
  182. }
  183. Move (region.Left, row);
  184. for (int col = region.Left; col < right; col++) {
  185. var lineCol = leftColumn + col;
  186. var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
  187. AddRune (col, row, rune);
  188. }
  189. }
  190. PositionCursor ();
  191. }
  192. public override bool CanFocus {
  193. get => true;
  194. set { base.CanFocus = value; }
  195. }
  196. void SetClipboard (ustring text)
  197. {
  198. Clipboard.Contents = text;
  199. }
  200. public void Insert (Rune rune)
  201. {
  202. var line = model.GetLine (currentRow);
  203. line.Insert (currentColumn, rune);
  204. var prow = currentRow - topRow;
  205. SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
  206. }
  207. public override bool ProcessKey (KeyEvent kb)
  208. {
  209. switch (kb.Key) {
  210. case Key.ControlN:
  211. case Key.CursorDown:
  212. if (currentRow + 1 < model.Count) {
  213. currentRow++;
  214. if (currentRow >= topRow + Frame.Height) {
  215. topRow++;
  216. SetNeedsDisplay ();
  217. }
  218. PositionCursor ();
  219. }
  220. break;
  221. case Key.ControlP:
  222. case Key.CursorUp:
  223. if (currentRow > 0) {
  224. currentRow--;
  225. if (currentRow < topRow) {
  226. topRow--;
  227. SetNeedsDisplay ();
  228. }
  229. PositionCursor ();
  230. }
  231. break;
  232. case Key.ControlF:
  233. case Key.CursorRight:
  234. var currentLine = model.GetLine (currentRow);
  235. if (currentColumn < currentLine.Count) {
  236. currentColumn++;
  237. if (currentColumn >= leftColumn + Frame.Width) {
  238. leftColumn++;
  239. SetNeedsDisplay ();
  240. }
  241. PositionCursor ();
  242. } else {
  243. if (currentRow + 1 < model.Count) {
  244. currentRow++;
  245. currentColumn = 0;
  246. leftColumn = 0;
  247. if (currentRow >= topRow + Frame.Height) {
  248. topRow++;
  249. }
  250. SetNeedsDisplay ();
  251. PositionCursor ();
  252. }
  253. break;
  254. }
  255. break;
  256. case Key.ControlB:
  257. case Key.CursorLeft:
  258. if (currentColumn > 0) {
  259. currentColumn--;
  260. if (currentColumn < leftColumn) {
  261. leftColumn--;
  262. SetNeedsDisplay ();
  263. }
  264. PositionCursor ();
  265. } else {
  266. if (currentRow > 0) {
  267. currentRow--;
  268. if (currentRow < topRow) {
  269. topRow--;
  270. }
  271. currentLine = model.GetLine (currentRow);
  272. currentColumn = currentLine.Count;
  273. int prev = leftColumn;
  274. leftColumn = currentColumn - Frame.Width + 1;
  275. if (leftColumn < 0)
  276. leftColumn = 0;
  277. if (prev != leftColumn)
  278. SetNeedsDisplay ();
  279. PositionCursor ();
  280. }
  281. }
  282. break;
  283. case Key.Delete:
  284. case Key.Backspace:
  285. break;
  286. // Home, C-A
  287. case Key.Home:
  288. case Key.ControlA:
  289. currentColumn = 0;
  290. if (currentColumn < leftColumn) {
  291. leftColumn = 0;
  292. SetNeedsDisplay ();
  293. } else
  294. PositionCursor ();
  295. break;
  296. case Key.ControlD: // Delete
  297. break;
  298. case Key.ControlE: // End
  299. currentLine = model.GetLine (currentRow);
  300. currentColumn = currentLine.Count;
  301. int pcol = leftColumn;
  302. leftColumn = currentColumn - Frame.Width + 1;
  303. if (leftColumn < 0)
  304. leftColumn = 0;
  305. if (pcol != leftColumn)
  306. SetNeedsDisplay ();
  307. PositionCursor ();
  308. break;
  309. case Key.ControlK: // kill-to-end
  310. break;
  311. case Key.ControlY: // Control-y, yank
  312. case (Key)((int)'b' + Key.AltMask):
  313. break;
  314. case (Key)((int)'f' + Key.AltMask):
  315. break;
  316. default:
  317. // Ignore control characters and other special keys
  318. if (kb.Key < Key.Space || kb.Key > Key.CharMask)
  319. return false;
  320. Insert ((uint)kb.Key);
  321. currentColumn++;
  322. if (currentColumn >= leftColumn + Frame.Width) {
  323. leftColumn++;
  324. SetNeedsDisplay ();
  325. }
  326. PositionCursor ();
  327. return true;
  328. }
  329. return true;
  330. }
  331. #if false
  332. int WordForward (int p)
  333. {
  334. if (p >= text.Length)
  335. return -1;
  336. int i = p;
  337. if (Rune.IsPunctuation (text [p]) || Rune.IsWhiteSpace (text [p])) {
  338. for (; i < text.Length; i++) {
  339. var r = text [i];
  340. if (Rune.IsLetterOrDigit (r))
  341. break;
  342. }
  343. for (; i < text.Length; i++) {
  344. var r = text [i];
  345. if (!Rune.IsLetterOrDigit (r))
  346. break;
  347. }
  348. } else {
  349. for (; i < text.Length; i++) {
  350. var r = text [i];
  351. if (!Rune.IsLetterOrDigit (r))
  352. break;
  353. }
  354. }
  355. if (i != p)
  356. return i;
  357. return -1;
  358. }
  359. int WordBackward (int p)
  360. {
  361. if (p == 0)
  362. return -1;
  363. int i = p - 1;
  364. if (i == 0)
  365. return 0;
  366. var ti = text [i];
  367. if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
  368. for (; i >= 0; i--) {
  369. if (Rune.IsLetterOrDigit (text [i]))
  370. break;
  371. }
  372. for (; i >= 0; i--) {
  373. if (!Rune.IsLetterOrDigit (text [i]))
  374. break;
  375. }
  376. } else {
  377. for (; i >= 0; i--) {
  378. if (!Rune.IsLetterOrDigit (text [i]))
  379. break;
  380. }
  381. }
  382. i++;
  383. if (i != p)
  384. return i;
  385. return -1;
  386. }
  387. public override bool MouseEvent (MouseEvent ev)
  388. {
  389. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  390. return false;
  391. if (!HasFocus)
  392. SuperView.SetFocus (this);
  393. // We could also set the cursor position.
  394. point = first + ev.X;
  395. if (point > text.Length)
  396. point = text.Length;
  397. if (point < first)
  398. point = 0;
  399. SetNeedsDisplay ();
  400. return true;
  401. }
  402. #endif
  403. }
  404. }