TextField.cs 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881
  1. using System.Data;
  2. using System.Globalization;
  3. using Terminal.Gui.Resources;
  4. namespace Terminal.Gui;
  5. /// <summary>Single-line text entry <see cref="View"/></summary>
  6. /// <remarks>The <see cref="TextField"/> <see cref="View"/> provides editing functionality and mouse support.</remarks>
  7. public class TextField : View
  8. {
  9. private readonly HistoryText _historyText;
  10. private CultureInfo _currentCulture;
  11. private int _cursorPosition;
  12. private bool _isButtonPressed;
  13. private bool _isButtonReleased;
  14. private bool _isDrawing;
  15. private int _preTextChangedCursorPos;
  16. private int _selectedStart; // -1 represents there is no text selection.
  17. private string _selectedText;
  18. private int _start;
  19. private List<Rune> _text;
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="TextField"/> class.
  22. /// </summary>
  23. public TextField ()
  24. {
  25. _historyText = new HistoryText ();
  26. _isButtonReleased = true;
  27. _selectedStart = -1;
  28. _text = new List<Rune> ();
  29. CaptionColor = new Color (Color.DarkGray);
  30. ReadOnly = false;
  31. Autocomplete = new TextFieldAutocomplete ();
  32. Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
  33. CanFocus = true;
  34. CursorVisibility = CursorVisibility.Default;
  35. Used = true;
  36. WantMousePositionReports = true;
  37. _historyText.ChangeText += HistoryText_ChangeText;
  38. Initialized += TextField_Initialized;
  39. // Things this view knows how to do
  40. AddCommand (
  41. Command.DeleteCharRight,
  42. () =>
  43. {
  44. DeleteCharRight ();
  45. return true;
  46. }
  47. );
  48. AddCommand (
  49. Command.DeleteCharLeft,
  50. () =>
  51. {
  52. DeleteCharLeft (false);
  53. return true;
  54. }
  55. );
  56. AddCommand (
  57. Command.LeftHomeExtend,
  58. () =>
  59. {
  60. MoveHomeExtend ();
  61. return true;
  62. }
  63. );
  64. AddCommand (
  65. Command.RightEndExtend,
  66. () =>
  67. {
  68. MoveEndExtend ();
  69. return true;
  70. }
  71. );
  72. AddCommand (
  73. Command.LeftHome,
  74. () =>
  75. {
  76. MoveHome ();
  77. return true;
  78. }
  79. );
  80. AddCommand (
  81. Command.LeftExtend,
  82. () =>
  83. {
  84. MoveLeftExtend ();
  85. return true;
  86. }
  87. );
  88. AddCommand (
  89. Command.RightExtend,
  90. () =>
  91. {
  92. MoveRightExtend ();
  93. return true;
  94. }
  95. );
  96. AddCommand (
  97. Command.WordLeftExtend,
  98. () =>
  99. {
  100. MoveWordLeftExtend ();
  101. return true;
  102. }
  103. );
  104. AddCommand (
  105. Command.WordRightExtend,
  106. () =>
  107. {
  108. MoveWordRightExtend ();
  109. return true;
  110. }
  111. );
  112. AddCommand (
  113. Command.Left,
  114. () =>
  115. {
  116. MoveLeft ();
  117. return true;
  118. }
  119. );
  120. AddCommand (
  121. Command.RightEnd,
  122. () =>
  123. {
  124. MoveEnd ();
  125. return true;
  126. }
  127. );
  128. AddCommand (
  129. Command.Right,
  130. () =>
  131. {
  132. MoveRight ();
  133. return true;
  134. }
  135. );
  136. AddCommand (
  137. Command.CutToEndLine,
  138. () =>
  139. {
  140. KillToEnd ();
  141. return true;
  142. }
  143. );
  144. AddCommand (
  145. Command.CutToStartLine,
  146. () =>
  147. {
  148. KillToStart ();
  149. return true;
  150. }
  151. );
  152. AddCommand (
  153. Command.Undo,
  154. () =>
  155. {
  156. Undo ();
  157. return true;
  158. }
  159. );
  160. AddCommand (
  161. Command.Redo,
  162. () =>
  163. {
  164. Redo ();
  165. return true;
  166. }
  167. );
  168. AddCommand (
  169. Command.WordLeft,
  170. () =>
  171. {
  172. MoveWordLeft ();
  173. return true;
  174. }
  175. );
  176. AddCommand (
  177. Command.WordRight,
  178. () =>
  179. {
  180. MoveWordRight ();
  181. return true;
  182. }
  183. );
  184. AddCommand (
  185. Command.KillWordForwards,
  186. () =>
  187. {
  188. KillWordForwards ();
  189. return true;
  190. }
  191. );
  192. AddCommand (
  193. Command.KillWordBackwards,
  194. () =>
  195. {
  196. KillWordBackwards ();
  197. return true;
  198. }
  199. );
  200. AddCommand (
  201. Command.ToggleOverwrite,
  202. () =>
  203. {
  204. SetOverwrite (!Used);
  205. return true;
  206. }
  207. );
  208. AddCommand (
  209. Command.EnableOverwrite,
  210. () =>
  211. {
  212. SetOverwrite (true);
  213. return true;
  214. }
  215. );
  216. AddCommand (
  217. Command.DisableOverwrite,
  218. () =>
  219. {
  220. SetOverwrite (false);
  221. return true;
  222. }
  223. );
  224. AddCommand (
  225. Command.Copy,
  226. () =>
  227. {
  228. Copy ();
  229. return true;
  230. }
  231. );
  232. AddCommand (
  233. Command.Cut,
  234. () =>
  235. {
  236. Cut ();
  237. return true;
  238. }
  239. );
  240. AddCommand (
  241. Command.Paste,
  242. () =>
  243. {
  244. Paste ();
  245. return true;
  246. }
  247. );
  248. AddCommand (
  249. Command.SelectAll,
  250. () =>
  251. {
  252. SelectAll ();
  253. return true;
  254. }
  255. );
  256. AddCommand (
  257. Command.DeleteAll,
  258. () =>
  259. {
  260. DeleteAll ();
  261. return true;
  262. }
  263. );
  264. AddCommand (
  265. Command.ShowContextMenu,
  266. () =>
  267. {
  268. ShowContextMenu ();
  269. return true;
  270. }
  271. );
  272. // By Default pressing ENTER should be ignored (OnAccept will return false or null). Only cancel if the
  273. // event was fired and set Cancel = true.
  274. AddCommand (Command.Accept, () => OnAccept () == false);
  275. // Default keybindings for this view
  276. // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
  277. KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
  278. KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
  279. KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
  280. KeyBindings.Add (Key.Home.WithShift, Command.LeftHomeExtend);
  281. KeyBindings.Add (Key.Home.WithShift.WithCtrl, Command.LeftHomeExtend);
  282. KeyBindings.Add (Key.A.WithShift.WithCtrl, Command.LeftHomeExtend);
  283. KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
  284. KeyBindings.Add (Key.End.WithShift.WithCtrl, Command.RightEndExtend);
  285. KeyBindings.Add (Key.E.WithShift.WithCtrl, Command.RightEndExtend);
  286. KeyBindings.Add (Key.Home, Command.LeftHome);
  287. KeyBindings.Add (Key.Home.WithCtrl, Command.LeftHome);
  288. KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
  289. KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
  290. KeyBindings.Add (Key.CursorUp.WithShift, Command.LeftExtend);
  291. KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
  292. KeyBindings.Add (Key.CursorDown.WithShift, Command.RightExtend);
  293. KeyBindings.Add (Key.CursorLeft.WithShift.WithCtrl, Command.WordLeftExtend);
  294. KeyBindings.Add (Key.CursorUp.WithShift.WithCtrl, Command.WordLeftExtend);
  295. KeyBindings.Add (Key.CursorRight.WithShift.WithCtrl, Command.WordRightExtend);
  296. KeyBindings.Add (Key.CursorDown.WithShift.WithCtrl, Command.WordRightExtend);
  297. KeyBindings.Add (Key.CursorLeft, Command.Left);
  298. KeyBindings.Add (Key.B.WithCtrl, Command.Left);
  299. KeyBindings.Add (Key.End, Command.RightEnd);
  300. KeyBindings.Add (Key.End.WithCtrl, Command.RightEnd);
  301. KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
  302. KeyBindings.Add (Key.CursorRight, Command.Right);
  303. KeyBindings.Add (Key.F.WithCtrl, Command.Right);
  304. KeyBindings.Add (Key.K.WithCtrl, Command.CutToEndLine);
  305. KeyBindings.Add (Key.K.WithCtrl.WithShift, Command.CutToStartLine);
  306. KeyBindings.Add (Key.Z.WithCtrl, Command.Undo);
  307. KeyBindings.Add (Key.Y.WithCtrl, Command.Redo);
  308. KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.WordLeft);
  309. KeyBindings.Add (Key.CursorUp.WithCtrl, Command.WordLeft);
  310. KeyBindings.Add (Key.CursorRight.WithCtrl, Command.WordRight);
  311. KeyBindings.Add (Key.CursorDown.WithCtrl, Command.WordRight);
  312. #if UNIX_KEY_BINDINGS
  313. KeyBindings.Add (Key.F.WithShift.WithAlt, Command.WordRightExtend);
  314. KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine);
  315. KeyBindings.Add (Key.B.WithShift.WithAlt, Command.WordLeftExtend);
  316. KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
  317. KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
  318. KeyBindings.Add (Key.Backspace.WithAlt, Command.Undo);
  319. #endif
  320. KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards);
  321. KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards);
  322. KeyBindings.Add (Key.InsertChar, Command.ToggleOverwrite);
  323. KeyBindings.Add (Key.C.WithCtrl, Command.Copy);
  324. KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
  325. KeyBindings.Add (Key.V.WithCtrl, Command.Paste);
  326. KeyBindings.Add (Key.T.WithCtrl, Command.SelectAll);
  327. KeyBindings.Add (Key.R.WithCtrl, Command.DeleteAll);
  328. KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
  329. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  330. ContextMenu = new ContextMenu { Host = this, MenuItems = BuildContextMenuBarItem () };
  331. ContextMenu.KeyChanged += ContextMenu_KeyChanged;
  332. KeyBindings.Add (ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu);
  333. KeyBindings.Add (Key.Enter, Command.Accept);
  334. }
  335. /// <summary>
  336. /// Provides autocomplete context menu based on suggestions at the current cursor position. Configure
  337. /// <see cref="ISuggestionGenerator"/> to enable this feature.
  338. /// </summary>
  339. public IAutocomplete Autocomplete { get; set; }
  340. /// <summary>
  341. /// Gets or sets the text to render in control when no value has been entered yet and the <see cref="View"/> does
  342. /// not yet have input focus.
  343. /// </summary>
  344. public string Caption { get; set; }
  345. /// <summary>Gets or sets the foreground <see cref="Color"/> to use when rendering <see cref="Caption"/>.</summary>
  346. public Color CaptionColor { get; set; }
  347. /// <summary>Get the <see cref="ContextMenu"/> for this view.</summary>
  348. public ContextMenu ContextMenu { get; }
  349. /// <summary>Sets or gets the current cursor position.</summary>
  350. public virtual int CursorPosition
  351. {
  352. get => _cursorPosition;
  353. set
  354. {
  355. if (value < 0)
  356. {
  357. _cursorPosition = 0;
  358. }
  359. else if (value > _text.Count)
  360. {
  361. _cursorPosition = _text.Count;
  362. }
  363. else
  364. {
  365. _cursorPosition = value;
  366. }
  367. PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
  368. }
  369. }
  370. /// <summary>
  371. /// Indicates whatever the text has history changes or not. <see langword="true"/> if the text has history changes
  372. /// <see langword="false"/> otherwise.
  373. /// </summary>
  374. public bool HasHistoryChanges => _historyText.HasHistoryChanges;
  375. /// <summary>
  376. /// Indicates whatever the text was changed or not. <see langword="true"/> if the text was changed
  377. /// <see langword="false"/> otherwise.
  378. /// </summary>
  379. public bool IsDirty => _historyText.IsDirty (Text);
  380. /// <summary>If set to true its not allow any changes in the text.</summary>
  381. public bool ReadOnly { get; set; }
  382. /// <summary>Gets the left offset position.</summary>
  383. public int ScrollOffset { get; private set; }
  384. /// <summary>
  385. /// Sets the secret property.
  386. /// <remarks>This makes the text entry suitable for entering passwords.</remarks>
  387. /// </summary>
  388. public bool Secret { get; set; }
  389. /// <summary>Length of the selected text.</summary>
  390. public int SelectedLength { get; private set; }
  391. /// <summary>Start position of the selected text.</summary>
  392. public int SelectedStart
  393. {
  394. get => _selectedStart;
  395. set
  396. {
  397. if (value < -1)
  398. {
  399. _selectedStart = -1;
  400. }
  401. else if (value > _text.Count)
  402. {
  403. _selectedStart = _text.Count;
  404. }
  405. else
  406. {
  407. _selectedStart = value;
  408. }
  409. PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
  410. }
  411. }
  412. /// <summary>The selected text.</summary>
  413. public string SelectedText
  414. {
  415. get => Secret ? null : _selectedText;
  416. private set => _selectedText = value;
  417. }
  418. /// <summary>Sets or gets the text held by the view.</summary>
  419. public new string Text
  420. {
  421. get => StringExtensions.ToString (_text);
  422. set
  423. {
  424. var oldText = StringExtensions.ToString (_text);
  425. if (oldText == value)
  426. {
  427. return;
  428. }
  429. string newText = value.Replace ("\t", "").Split ("\n") [0];
  430. CancelEventArgs<string> args = new (ref oldText, ref newText);
  431. OnTextChanging (args);
  432. if (args.Cancel)
  433. {
  434. if (_cursorPosition > _text.Count)
  435. {
  436. _cursorPosition = _text.Count;
  437. }
  438. return;
  439. }
  440. ClearAllSelection ();
  441. // Note we use NewValue here; TextChanging subscribers may have changed it
  442. _text = args.NewValue.EnumerateRunes ().ToList ();
  443. if (!Secret && !_historyText.IsFromHistory)
  444. {
  445. _historyText.Add (
  446. new List<List<RuneCell>> { TextModel.ToRuneCellList (oldText) },
  447. new Point (_cursorPosition, 0)
  448. );
  449. _historyText.Add (
  450. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  451. new Point (_cursorPosition, 0),
  452. HistoryText.LineStatus.Replaced
  453. );
  454. }
  455. OnTextChanged ();
  456. ProcessAutocomplete ();
  457. if (_cursorPosition > _text.Count)
  458. {
  459. _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
  460. }
  461. Adjust ();
  462. SetNeedsDisplay ();
  463. }
  464. }
  465. /// <summary>
  466. /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so
  467. /// new input should be appended at the cursor position, rather than clearing the entry
  468. /// </summary>
  469. public bool Used { get; set; }
  470. /// <summary>Clear the selected text.</summary>
  471. public void ClearAllSelection ()
  472. {
  473. if (_selectedStart == -1 && SelectedLength == 0 && string.IsNullOrEmpty (_selectedText))
  474. {
  475. return;
  476. }
  477. _selectedStart = -1;
  478. SelectedLength = 0;
  479. _selectedText = null;
  480. _start = 0;
  481. SelectedLength = 0;
  482. SetNeedsDisplay ();
  483. }
  484. /// <summary>Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.</summary>
  485. public void ClearHistoryChanges () { _historyText.Clear (Text); }
  486. /// <summary>Copy the selected text to the clipboard.</summary>
  487. public virtual void Copy ()
  488. {
  489. if (Secret || SelectedLength == 0)
  490. {
  491. return;
  492. }
  493. Clipboard.Contents = SelectedText;
  494. }
  495. /// <summary>Cut the selected text to the clipboard.</summary>
  496. public virtual void Cut ()
  497. {
  498. if (ReadOnly || Secret || SelectedLength == 0)
  499. {
  500. return;
  501. }
  502. Clipboard.Contents = SelectedText;
  503. List<Rune> newText = DeleteSelectedText ();
  504. Text = StringExtensions.ToString (newText);
  505. Adjust ();
  506. }
  507. /// <summary>Deletes all text.</summary>
  508. public void DeleteAll ()
  509. {
  510. if (_text.Count == 0)
  511. {
  512. return;
  513. }
  514. _selectedStart = 0;
  515. MoveEndExtend ();
  516. DeleteCharLeft (false);
  517. SetNeedsDisplay ();
  518. }
  519. /// <summary>Deletes the character to the left.</summary>
  520. /// <param name="usePreTextChangedCursorPos">
  521. /// If set to <see langword="true">true</see> use the cursor position cached ;
  522. /// otherwise use <see cref="CursorPosition"/>. use .
  523. /// </param>
  524. public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
  525. {
  526. if (ReadOnly)
  527. {
  528. return;
  529. }
  530. _historyText.Add (
  531. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  532. new Point (_cursorPosition, 0)
  533. );
  534. if (SelectedLength == 0)
  535. {
  536. if (_cursorPosition == 0)
  537. {
  538. return;
  539. }
  540. if (!usePreTextChangedCursorPos)
  541. {
  542. _preTextChangedCursorPos = _cursorPosition;
  543. }
  544. _cursorPosition--;
  545. if (_preTextChangedCursorPos < _text.Count)
  546. {
  547. SetText (
  548. _text.GetRange (0, _preTextChangedCursorPos - 1)
  549. .Concat (
  550. _text.GetRange (
  551. _preTextChangedCursorPos,
  552. _text.Count - _preTextChangedCursorPos
  553. )
  554. )
  555. );
  556. }
  557. else
  558. {
  559. SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
  560. }
  561. Adjust ();
  562. }
  563. else
  564. {
  565. List<Rune> newText = DeleteSelectedText ();
  566. Text = StringExtensions.ToString (newText);
  567. Adjust ();
  568. }
  569. }
  570. /// <summary>Deletes the character to the right.</summary>
  571. public virtual void DeleteCharRight ()
  572. {
  573. if (ReadOnly)
  574. {
  575. return;
  576. }
  577. _historyText.Add (
  578. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  579. new Point (_cursorPosition, 0)
  580. );
  581. if (SelectedLength == 0)
  582. {
  583. if (_text.Count == 0 || _text.Count == _cursorPosition)
  584. {
  585. return;
  586. }
  587. SetText (
  588. _text.GetRange (0, _cursorPosition)
  589. .Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1)))
  590. );
  591. Adjust ();
  592. }
  593. else
  594. {
  595. List<Rune> newText = DeleteSelectedText ();
  596. Text = StringExtensions.ToString (newText);
  597. Adjust ();
  598. }
  599. }
  600. /// <inheritdoc/>
  601. public override Attribute GetNormalColor ()
  602. {
  603. ColorScheme cs = ColorScheme;
  604. if (ColorScheme is null)
  605. {
  606. cs = new ColorScheme ();
  607. }
  608. return Enabled ? cs.Focus : cs.Disabled;
  609. }
  610. /// <summary>
  611. /// Inserts the given <paramref name="toAdd"/> text at the current cursor position exactly as if the user had just
  612. /// typed it
  613. /// </summary>
  614. /// <param name="toAdd">Text to add</param>
  615. /// <param name="useOldCursorPos">Use the previous cursor position.</param>
  616. public void InsertText (string toAdd, bool useOldCursorPos = true)
  617. {
  618. foreach (char ch in toAdd)
  619. {
  620. Key key;
  621. try
  622. {
  623. key = ch;
  624. }
  625. catch (Exception)
  626. {
  627. throw new ArgumentException (
  628. $"Cannot insert character '{ch}' because it does not map to a Key"
  629. );
  630. }
  631. InsertText (key, useOldCursorPos);
  632. }
  633. }
  634. /// <summary>Deletes word backwards.</summary>
  635. public virtual void KillWordBackwards ()
  636. {
  637. ClearAllSelection ();
  638. (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
  639. if (newPos is null)
  640. {
  641. return;
  642. }
  643. if (newPos.Value.col != -1)
  644. {
  645. SetText (
  646. _text.GetRange (0, newPos.Value.col)
  647. .Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition))
  648. );
  649. _cursorPosition = newPos.Value.col;
  650. }
  651. Adjust ();
  652. }
  653. /// <summary>Deletes word forwards.</summary>
  654. public virtual void KillWordForwards ()
  655. {
  656. ClearAllSelection ();
  657. (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
  658. if (newPos is null)
  659. {
  660. return;
  661. }
  662. if (newPos.Value.col != -1)
  663. {
  664. SetText (
  665. _text.GetRange (0, _cursorPosition)
  666. .Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col))
  667. );
  668. }
  669. Adjust ();
  670. }
  671. /// <inheritdoc/>
  672. protected internal override bool OnMouseEvent (MouseEvent ev)
  673. {
  674. if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed)
  675. && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition)
  676. && !ev.Flags.HasFlag (MouseFlags.Button1Released)
  677. && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
  678. && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)
  679. && !ev.Flags.HasFlag (ContextMenu.MouseFlags))
  680. {
  681. return base.OnMouseEvent (ev);
  682. }
  683. if (!CanFocus)
  684. {
  685. return true;
  686. }
  687. if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition)
  688. {
  689. SetFocus ();
  690. }
  691. // Give autocomplete first opportunity to respond to mouse clicks
  692. if (SelectedLength == 0 && Autocomplete.OnMouseEvent (ev, true))
  693. {
  694. return true;
  695. }
  696. if (ev.Flags == MouseFlags.Button1Pressed)
  697. {
  698. EnsureHasFocus ();
  699. PositionCursor (ev);
  700. if (_isButtonReleased)
  701. {
  702. ClearAllSelection ();
  703. }
  704. _isButtonReleased = true;
  705. _isButtonPressed = true;
  706. }
  707. else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed)
  708. {
  709. int x = PositionCursor (ev);
  710. _isButtonReleased = false;
  711. PrepareSelection (x);
  712. if (Application.MouseGrabView is null)
  713. {
  714. Application.GrabMouse (this);
  715. }
  716. }
  717. else if (ev.Flags == MouseFlags.Button1Released)
  718. {
  719. _isButtonReleased = true;
  720. _isButtonPressed = false;
  721. Application.UngrabMouse ();
  722. }
  723. else if (ev.Flags == MouseFlags.Button1DoubleClicked)
  724. {
  725. EnsureHasFocus ();
  726. int x = PositionCursor (ev);
  727. int sbw = x;
  728. if (x == _text.Count
  729. || (x > 0 && (char)_text [x - 1].Value != ' ')
  730. || (x > 0 && (char)_text [x].Value == ' '))
  731. {
  732. (int col, int row)? newPosBw = GetModel ().WordBackward (x, 0);
  733. if (newPosBw is null)
  734. {
  735. return true;
  736. }
  737. sbw = newPosBw.Value.col;
  738. }
  739. if (sbw != -1)
  740. {
  741. x = sbw;
  742. PositionCursor (x);
  743. }
  744. (int col, int row)? newPosFw = GetModel ().WordForward (x, 0);
  745. if (newPosFw is null)
  746. {
  747. return true;
  748. }
  749. ClearAllSelection ();
  750. if (newPosFw.Value.col != -1 && sbw != -1)
  751. {
  752. _cursorPosition = newPosFw.Value.col;
  753. }
  754. PrepareSelection (sbw, newPosFw.Value.col - sbw);
  755. }
  756. else if (ev.Flags == MouseFlags.Button1TripleClicked)
  757. {
  758. EnsureHasFocus ();
  759. PositionCursor (0);
  760. ClearAllSelection ();
  761. PrepareSelection (0, _text.Count);
  762. }
  763. else if (ev.Flags == ContextMenu.MouseFlags)
  764. {
  765. ShowContextMenu ();
  766. }
  767. //SetNeedsDisplay ();
  768. return true;
  769. void EnsureHasFocus ()
  770. {
  771. if (!HasFocus)
  772. {
  773. SetFocus ();
  774. }
  775. }
  776. }
  777. /// <summary>Moves cursor to the end of the typed text.</summary>
  778. public void MoveEnd ()
  779. {
  780. ClearAllSelection ();
  781. _cursorPosition = _text.Count;
  782. Adjust ();
  783. }
  784. /// <inheritdoc/>
  785. public override void OnDrawContent (Rectangle viewport)
  786. {
  787. _isDrawing = true;
  788. var selColor = new Attribute (GetFocusColor ().Background, GetFocusColor ().Foreground);
  789. SetSelectedStartSelectedLength ();
  790. Driver?.SetAttribute (GetNormalColor ());
  791. Move (0, 0);
  792. int p = ScrollOffset;
  793. var col = 0;
  794. int width = Frame.Width + OffSetBackground ();
  795. int tcount = _text.Count;
  796. Attribute roc = GetReadOnlyColor ();
  797. for (int idx = p; idx < tcount; idx++)
  798. {
  799. Rune rune = _text [idx];
  800. int cols = rune.GetColumns ();
  801. if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly)
  802. {
  803. Driver?.SetAttribute (selColor);
  804. }
  805. else if (ReadOnly)
  806. {
  807. Driver?.SetAttribute (
  808. idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
  809. ? selColor
  810. : roc
  811. );
  812. }
  813. else if (!HasFocus && Enabled)
  814. {
  815. Driver?.SetAttribute (GetFocusColor ());
  816. }
  817. else if (!Enabled)
  818. {
  819. Driver?.SetAttribute (roc);
  820. }
  821. else
  822. {
  823. Driver?.SetAttribute (
  824. idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
  825. ? selColor
  826. : ColorScheme.Focus
  827. );
  828. }
  829. if (col + cols <= width)
  830. {
  831. Driver?.AddRune (Secret ? Glyphs.Dot : rune);
  832. }
  833. if (!TextModel.SetCol (ref col, width, cols))
  834. {
  835. break;
  836. }
  837. if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width)
  838. {
  839. break;
  840. }
  841. }
  842. Driver.SetAttribute (GetFocusColor ());
  843. for (int i = col; i < width; i++)
  844. {
  845. Driver.AddRune ((Rune)' ');
  846. }
  847. PositionCursor ();
  848. RenderCaption ();
  849. ProcessAutocomplete ();
  850. _isDrawing = false;
  851. }
  852. /// <inheritdoc/>
  853. public override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope)
  854. {
  855. // Give autocomplete first opportunity to respond to key presses
  856. if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a))
  857. {
  858. return true;
  859. }
  860. return base.OnInvokingKeyBindings (a, scope);
  861. }
  862. /// <inheritdoc/>
  863. public override bool OnLeave (View view)
  864. {
  865. if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
  866. {
  867. Application.UngrabMouse ();
  868. }
  869. //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
  870. // ClearAllSelection ();
  871. return base.OnLeave (view);
  872. }
  873. /// TODO: Flush out these docs
  874. /// <summary>
  875. /// Processes key presses for the <see cref="TextField"/>.
  876. /// <remarks>
  877. /// The <see cref="TextField"/> control responds to the following keys:
  878. /// <list type="table">
  879. /// <listheader>
  880. /// <term>Keys</term> <description>Function</description>
  881. /// </listheader>
  882. /// <item>
  883. /// <term><see cref="Key.Delete"/>, <see cref="Key.Backspace"/></term>
  884. /// <description>Deletes the character before cursor.</description>
  885. /// </item>
  886. /// </list>
  887. /// </remarks>
  888. /// </summary>
  889. /// <param name="a"></param>
  890. /// <returns></returns>
  891. public override bool OnProcessKeyDown (Key a)
  892. {
  893. // Remember the cursor position because the new calculated cursor position is needed
  894. // to be set BEFORE the TextChanged event is triggered.
  895. // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
  896. _preTextChangedCursorPos = _cursorPosition;
  897. // Ignore other control characters.
  898. if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask))
  899. {
  900. return false;
  901. }
  902. if (ReadOnly)
  903. {
  904. return true;
  905. }
  906. InsertText (a, true);
  907. return true;
  908. }
  909. /// <summary>Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.</summary>
  910. /// <param name="args">The event arguments..</param>
  911. /// <returns><see langword="true"/> if the event was cancelled.</returns>
  912. public bool OnTextChanging (CancelEventArgs<string> args)
  913. {
  914. TextChanging?.Invoke (this, args);
  915. return args.Cancel;
  916. }
  917. /// <summary>Paste the selected text from the clipboard.</summary>
  918. public virtual void Paste ()
  919. {
  920. if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents))
  921. {
  922. return;
  923. }
  924. SetSelectedStartSelectedLength ();
  925. int selStart = _start == -1 ? CursorPosition : _start;
  926. string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
  927. Text = StringExtensions.ToString (_text.GetRange (0, selStart))
  928. + cbTxt
  929. + StringExtensions.ToString (
  930. _text.GetRange (
  931. selStart + SelectedLength,
  932. _text.Count - (selStart + SelectedLength)
  933. )
  934. );
  935. _cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
  936. ClearAllSelection ();
  937. SetNeedsDisplay ();
  938. Adjust ();
  939. }
  940. /// <summary>Sets the cursor position.</summary>
  941. public override Point? PositionCursor ()
  942. {
  943. ProcessAutocomplete ();
  944. var col = 0;
  945. for (int idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++)
  946. {
  947. if (idx == _cursorPosition)
  948. {
  949. break;
  950. }
  951. int cols = _text [idx].GetColumns ();
  952. TextModel.SetCol (ref col, Frame.Width - 1, cols);
  953. }
  954. int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
  955. Move (pos, 0);
  956. return new Point (pos, 0);
  957. }
  958. /// <summary>Redoes the latest changes.</summary>
  959. public void Redo ()
  960. {
  961. if (ReadOnly)
  962. {
  963. return;
  964. }
  965. _historyText.Redo ();
  966. }
  967. /// <summary>Selects all text.</summary>
  968. public void SelectAll ()
  969. {
  970. if (_text.Count == 0)
  971. {
  972. return;
  973. }
  974. _selectedStart = 0;
  975. MoveEndExtend ();
  976. SetNeedsDisplay ();
  977. }
  978. ///// <summary>
  979. ///// Changed event, raised when the text has changed.
  980. ///// <remarks>
  981. ///// This event is raised when the <see cref="Text"/> changes. The passed <see cref="EventArgs"/> is a
  982. ///// <see cref="string"/> containing the old value.
  983. ///// </remarks>
  984. ///// </summary>
  985. //public event EventHandler<StateEventArgs<string>> TextChanged;
  986. /// <summary>Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.</summary>
  987. public event EventHandler<CancelEventArgs<string>> TextChanging;
  988. /// <summary>Undoes the latest changes.</summary>
  989. public void Undo ()
  990. {
  991. if (ReadOnly)
  992. {
  993. return;
  994. }
  995. _historyText.Undo ();
  996. }
  997. /// <summary>
  998. /// Returns <see langword="true"/> if the current cursor position is at the end of the <see cref="Text"/>. This
  999. /// includes when it is empty.
  1000. /// </summary>
  1001. /// <returns></returns>
  1002. internal bool CursorIsAtEnd () { return CursorPosition == Text.Length; }
  1003. /// <summary>Returns <see langword="true"/> if the current cursor position is at the start of the <see cref="TextField"/>.</summary>
  1004. /// <returns></returns>
  1005. internal bool CursorIsAtStart () { return CursorPosition <= 0; }
  1006. private void Adjust ()
  1007. {
  1008. if (!IsAdded)
  1009. {
  1010. return;
  1011. }
  1012. // TODO: This is a lame prototype proving it should be easy for TextField to
  1013. // TODO: support Width = Dim.Auto (DimAutoStyle: Content).
  1014. //SetContentSize(new (TextModel.DisplaySize (_text).size, 1));
  1015. int offB = OffSetBackground ();
  1016. bool need = NeedsDisplay || !Used;
  1017. if (_cursorPosition < ScrollOffset)
  1018. {
  1019. ScrollOffset = _cursorPosition;
  1020. need = true;
  1021. }
  1022. else if (Frame.Width > 0
  1023. && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0
  1024. || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB))
  1025. {
  1026. ScrollOffset = Math.Max (
  1027. TextModel.CalculateLeftColumn (
  1028. _text,
  1029. ScrollOffset,
  1030. _cursorPosition,
  1031. Frame.Width + offB
  1032. ),
  1033. 0
  1034. );
  1035. need = true;
  1036. }
  1037. if (need)
  1038. {
  1039. SetNeedsDisplay ();
  1040. }
  1041. else
  1042. {
  1043. PositionCursor ();
  1044. }
  1045. }
  1046. private MenuBarItem BuildContextMenuBarItem ()
  1047. {
  1048. return new MenuBarItem (
  1049. new MenuItem []
  1050. {
  1051. new (
  1052. Strings.ctxSelectAll,
  1053. "",
  1054. () => SelectAll (),
  1055. null,
  1056. null,
  1057. (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)
  1058. ),
  1059. new (
  1060. Strings.ctxDeleteAll,
  1061. "",
  1062. () => DeleteAll (),
  1063. null,
  1064. null,
  1065. (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)
  1066. ),
  1067. new (
  1068. Strings.ctxCopy,
  1069. "",
  1070. () => Copy (),
  1071. null,
  1072. null,
  1073. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)
  1074. ),
  1075. new (
  1076. Strings.ctxCut,
  1077. "",
  1078. () => Cut (),
  1079. null,
  1080. null,
  1081. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)
  1082. ),
  1083. new (
  1084. Strings.ctxPaste,
  1085. "",
  1086. () => Paste (),
  1087. null,
  1088. null,
  1089. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)
  1090. ),
  1091. new (
  1092. Strings.ctxUndo,
  1093. "",
  1094. () => Undo (),
  1095. null,
  1096. null,
  1097. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)
  1098. ),
  1099. new (
  1100. Strings.ctxRedo,
  1101. "",
  1102. () => Redo (),
  1103. null,
  1104. null,
  1105. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)
  1106. )
  1107. }
  1108. );
  1109. }
  1110. private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); }
  1111. private List<Rune> DeleteSelectedText ()
  1112. {
  1113. SetSelectedStartSelectedLength ();
  1114. int selStart = SelectedStart > -1 ? _start : _cursorPosition;
  1115. string newText = StringExtensions.ToString (_text.GetRange (0, selStart))
  1116. + StringExtensions.ToString (
  1117. _text.GetRange (
  1118. selStart + SelectedLength,
  1119. _text.Count - (selStart + SelectedLength)
  1120. )
  1121. );
  1122. ClearAllSelection ();
  1123. _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
  1124. return newText.ToRuneList ();
  1125. }
  1126. private void GenerateSuggestions ()
  1127. {
  1128. List<RuneCell> currentLine = TextModel.ToRuneCellList (Text);
  1129. int cursorPosition = Math.Min (CursorPosition, currentLine.Count);
  1130. Autocomplete.Context = new AutocompleteContext (
  1131. currentLine,
  1132. cursorPosition,
  1133. Autocomplete.Context != null
  1134. ? Autocomplete.Context.Canceled
  1135. : false
  1136. );
  1137. Autocomplete.GenerateSuggestions (
  1138. Autocomplete.Context
  1139. );
  1140. }
  1141. private TextModel GetModel ()
  1142. {
  1143. var model = new TextModel ();
  1144. model.LoadString (Text);
  1145. return model;
  1146. }
  1147. private Attribute GetReadOnlyColor ()
  1148. {
  1149. ColorScheme cs = ColorScheme;
  1150. if (ColorScheme is null)
  1151. {
  1152. cs = new ColorScheme ();
  1153. }
  1154. if (cs.Disabled.Foreground == cs.Focus.Background)
  1155. {
  1156. return new Attribute (cs.Focus.Foreground, cs.Focus.Background);
  1157. }
  1158. return new Attribute (cs.Disabled.Foreground, cs.Focus.Background);
  1159. }
  1160. private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
  1161. {
  1162. if (obj is null)
  1163. {
  1164. return;
  1165. }
  1166. Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
  1167. CursorPosition = obj.CursorPosition.X;
  1168. Adjust ();
  1169. }
  1170. private void InsertText (Key a, bool usePreTextChangedCursorPos)
  1171. {
  1172. _historyText.Add (
  1173. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  1174. new Point (_cursorPosition, 0)
  1175. );
  1176. List<Rune> newText = _text;
  1177. if (SelectedLength > 0)
  1178. {
  1179. newText = DeleteSelectedText ();
  1180. _preTextChangedCursorPos = _cursorPosition;
  1181. }
  1182. if (!usePreTextChangedCursorPos)
  1183. {
  1184. _preTextChangedCursorPos = _cursorPosition;
  1185. }
  1186. StringRuneEnumerator kbstr = a.AsRune.ToString ().EnumerateRunes ();
  1187. if (Used)
  1188. {
  1189. _cursorPosition++;
  1190. if (_cursorPosition == newText.Count + 1)
  1191. {
  1192. SetText (newText.Concat (kbstr).ToList ());
  1193. }
  1194. else
  1195. {
  1196. if (_preTextChangedCursorPos > newText.Count)
  1197. {
  1198. _preTextChangedCursorPos = newText.Count;
  1199. }
  1200. SetText (
  1201. newText.GetRange (0, _preTextChangedCursorPos)
  1202. .Concat (kbstr)
  1203. .Concat (
  1204. newText.GetRange (
  1205. _preTextChangedCursorPos,
  1206. Math.Min (
  1207. newText.Count - _preTextChangedCursorPos,
  1208. newText.Count
  1209. )
  1210. )
  1211. )
  1212. );
  1213. }
  1214. }
  1215. else
  1216. {
  1217. SetText (
  1218. newText.GetRange (0, _preTextChangedCursorPos)
  1219. .Concat (kbstr)
  1220. .Concat (
  1221. newText.GetRange (
  1222. Math.Min (_preTextChangedCursorPos + 1, newText.Count),
  1223. Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0)
  1224. )
  1225. )
  1226. );
  1227. _cursorPosition++;
  1228. }
  1229. Adjust ();
  1230. }
  1231. private void KillToEnd ()
  1232. {
  1233. if (ReadOnly)
  1234. {
  1235. return;
  1236. }
  1237. ClearAllSelection ();
  1238. if (_cursorPosition >= _text.Count)
  1239. {
  1240. return;
  1241. }
  1242. SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  1243. SetText (_text.GetRange (0, _cursorPosition));
  1244. Adjust ();
  1245. }
  1246. private void KillToStart ()
  1247. {
  1248. if (ReadOnly)
  1249. {
  1250. return;
  1251. }
  1252. ClearAllSelection ();
  1253. if (_cursorPosition == 0)
  1254. {
  1255. return;
  1256. }
  1257. SetClipboard (_text.GetRange (0, _cursorPosition));
  1258. SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  1259. _cursorPosition = 0;
  1260. Adjust ();
  1261. }
  1262. private void MoveEndExtend ()
  1263. {
  1264. if (_cursorPosition <= _text.Count)
  1265. {
  1266. int x = _cursorPosition;
  1267. _cursorPosition = _text.Count;
  1268. PrepareSelection (x, _cursorPosition - x);
  1269. }
  1270. }
  1271. private void MoveHome ()
  1272. {
  1273. ClearAllSelection ();
  1274. _cursorPosition = 0;
  1275. Adjust ();
  1276. }
  1277. private void MoveHomeExtend ()
  1278. {
  1279. if (_cursorPosition > 0)
  1280. {
  1281. int x = _cursorPosition;
  1282. _cursorPosition = 0;
  1283. PrepareSelection (x, _cursorPosition - x);
  1284. }
  1285. }
  1286. private void MoveLeft ()
  1287. {
  1288. ClearAllSelection ();
  1289. if (_cursorPosition > 0)
  1290. {
  1291. _cursorPosition--;
  1292. Adjust ();
  1293. }
  1294. }
  1295. private void MoveLeftExtend ()
  1296. {
  1297. if (_cursorPosition > 0)
  1298. {
  1299. PrepareSelection (_cursorPosition--, -1);
  1300. }
  1301. }
  1302. private void MoveRight ()
  1303. {
  1304. ClearAllSelection ();
  1305. if (_cursorPosition == _text.Count)
  1306. {
  1307. return;
  1308. }
  1309. _cursorPosition++;
  1310. Adjust ();
  1311. }
  1312. private void MoveRightExtend ()
  1313. {
  1314. if (_cursorPosition < _text.Count)
  1315. {
  1316. PrepareSelection (_cursorPosition++, 1);
  1317. }
  1318. }
  1319. private void MoveWordLeft ()
  1320. {
  1321. ClearAllSelection ();
  1322. (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
  1323. if (newPos is null)
  1324. {
  1325. return;
  1326. }
  1327. if (newPos.Value.col != -1)
  1328. {
  1329. _cursorPosition = newPos.Value.col;
  1330. }
  1331. Adjust ();
  1332. }
  1333. private void MoveWordLeftExtend ()
  1334. {
  1335. if (_cursorPosition > 0)
  1336. {
  1337. int x = Math.Min (
  1338. _start > -1 && _start > _cursorPosition ? _start : _cursorPosition,
  1339. _text.Count
  1340. );
  1341. if (x > 0)
  1342. {
  1343. (int col, int row)? newPos = GetModel ().WordBackward (x, 0);
  1344. if (newPos is null)
  1345. {
  1346. return;
  1347. }
  1348. if (newPos.Value.col != -1)
  1349. {
  1350. _cursorPosition = newPos.Value.col;
  1351. }
  1352. PrepareSelection (x, newPos.Value.col - x);
  1353. }
  1354. }
  1355. }
  1356. private void MoveWordRight ()
  1357. {
  1358. ClearAllSelection ();
  1359. (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
  1360. if (newPos is null)
  1361. {
  1362. return;
  1363. }
  1364. if (newPos.Value.col != -1)
  1365. {
  1366. _cursorPosition = newPos.Value.col;
  1367. }
  1368. Adjust ();
  1369. }
  1370. private void MoveWordRightExtend ()
  1371. {
  1372. if (_cursorPosition < _text.Count)
  1373. {
  1374. int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
  1375. (int col, int row)? newPos = GetModel ().WordForward (x, 0);
  1376. if (newPos is null)
  1377. {
  1378. return;
  1379. }
  1380. if (newPos.Value.col != -1)
  1381. {
  1382. _cursorPosition = newPos.Value.col;
  1383. }
  1384. PrepareSelection (x, newPos.Value.col - x);
  1385. }
  1386. }
  1387. // BUGBUG: This assumes Frame == Viewport. It's also not clear what the intention is. For now, changed to always return 0.
  1388. private int OffSetBackground ()
  1389. {
  1390. var offB = 0;
  1391. if (SuperView?.Frame.Right - Frame.Right < 0)
  1392. {
  1393. offB = SuperView.Frame.Right - Frame.Right - 1;
  1394. }
  1395. return 0;//offB;
  1396. }
  1397. private int PositionCursor (MouseEvent ev)
  1398. {
  1399. return PositionCursor (TextModel.GetColFromX (_text, ScrollOffset, ev.Position.X), false);
  1400. }
  1401. private int PositionCursor (int x, bool getX = true)
  1402. {
  1403. int pX = x;
  1404. if (getX)
  1405. {
  1406. pX = TextModel.GetColFromX (_text, ScrollOffset, x);
  1407. }
  1408. if (ScrollOffset + pX > _text.Count)
  1409. {
  1410. _cursorPosition = _text.Count;
  1411. }
  1412. else if (ScrollOffset + pX < ScrollOffset)
  1413. {
  1414. _cursorPosition = 0;
  1415. }
  1416. else
  1417. {
  1418. _cursorPosition = ScrollOffset + pX;
  1419. }
  1420. return _cursorPosition;
  1421. }
  1422. private void PrepareSelection (int x, int direction = 0)
  1423. {
  1424. x = x + ScrollOffset < -1 ? 0 : x;
  1425. _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count
  1426. ? x
  1427. : _selectedStart;
  1428. if (_selectedStart > -1)
  1429. {
  1430. SelectedLength = Math.Abs (
  1431. x + direction <= _text.Count
  1432. ? x + direction - _selectedStart
  1433. : _text.Count - _selectedStart
  1434. );
  1435. SetSelectedStartSelectedLength ();
  1436. if (_start > -1 && SelectedLength > 0)
  1437. {
  1438. _selectedText = SelectedLength > 0
  1439. ? StringExtensions.ToString (
  1440. _text.GetRange (
  1441. _start < 0 ? 0 : _start,
  1442. SelectedLength > _text.Count
  1443. ? _text.Count
  1444. : SelectedLength
  1445. )
  1446. )
  1447. : "";
  1448. if (ScrollOffset > _start)
  1449. {
  1450. ScrollOffset = _start;
  1451. }
  1452. }
  1453. else if (_start > -1 && SelectedLength == 0)
  1454. {
  1455. _selectedText = null;
  1456. }
  1457. SetNeedsDisplay ();
  1458. }
  1459. else if (SelectedLength > 0 || _selectedText is { })
  1460. {
  1461. ClearAllSelection ();
  1462. }
  1463. Adjust ();
  1464. }
  1465. private void ProcessAutocomplete ()
  1466. {
  1467. if (_isDrawing)
  1468. {
  1469. return;
  1470. }
  1471. if (SelectedLength > 0)
  1472. {
  1473. return;
  1474. }
  1475. // draw autocomplete
  1476. GenerateSuggestions ();
  1477. var renderAt = new Point (
  1478. Autocomplete.Context.CursorPosition,
  1479. 0
  1480. );
  1481. Autocomplete.RenderOverlay (renderAt);
  1482. }
  1483. private void RenderCaption ()
  1484. {
  1485. if (HasFocus
  1486. || Caption == null
  1487. || Caption.Length == 0
  1488. || Text?.Length > 0)
  1489. {
  1490. return;
  1491. }
  1492. var color = new Attribute (CaptionColor, GetNormalColor ().Background);
  1493. Driver.SetAttribute (color);
  1494. Move (0, 0);
  1495. string render = Caption;
  1496. if (render.GetColumns () > Viewport.Width)
  1497. {
  1498. render = render [..Viewport.Width];
  1499. }
  1500. Driver.AddStr (render);
  1501. }
  1502. private void SetClipboard (IEnumerable<Rune> text)
  1503. {
  1504. if (!Secret)
  1505. {
  1506. Clipboard.Contents = StringExtensions.ToString (text.ToList ());
  1507. }
  1508. }
  1509. private void SetOverwrite (bool overwrite)
  1510. {
  1511. Used = overwrite;
  1512. SetNeedsDisplay ();
  1513. }
  1514. private void SetSelectedStartSelectedLength ()
  1515. {
  1516. if (SelectedStart > -1 && _cursorPosition < SelectedStart)
  1517. {
  1518. _start = _cursorPosition;
  1519. }
  1520. else
  1521. {
  1522. _start = SelectedStart;
  1523. }
  1524. }
  1525. private void SetText (List<Rune> newText) { Text = StringExtensions.ToString (newText); }
  1526. private void SetText (IEnumerable<Rune> newText) { SetText (newText.ToList ()); }
  1527. private void ShowContextMenu ()
  1528. {
  1529. if (_currentCulture != Thread.CurrentThread.CurrentUICulture)
  1530. {
  1531. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  1532. ContextMenu.MenuItems = BuildContextMenuBarItem ();
  1533. }
  1534. ContextMenu.Show ();
  1535. }
  1536. private void TextField_Initialized (object sender, EventArgs e)
  1537. {
  1538. _cursorPosition = Text.GetRuneCount ();
  1539. if (Viewport.Width > 0)
  1540. {
  1541. ScrollOffset = _cursorPosition > Viewport.Width + 1 ? _cursorPosition - Viewport.Width + 1 : 0;
  1542. }
  1543. Autocomplete.HostControl = this;
  1544. Autocomplete.PopupInsideContainer = false;
  1545. }
  1546. }
  1547. /// <summary>
  1548. /// Renders an overlay on another view at a given point that allows selecting from a range of 'autocomplete'
  1549. /// options. An implementation on a TextField.
  1550. /// </summary>
  1551. public class TextFieldAutocomplete : PopupAutocomplete
  1552. {
  1553. /// <inheritdoc/>
  1554. protected override void DeleteTextBackwards () { ((TextField)HostControl).DeleteCharLeft (false); }
  1555. /// <inheritdoc/>
  1556. protected override void InsertText (string accepted) { ((TextField)HostControl).InsertText (accepted, false); }
  1557. /// <inheritdoc/>
  1558. protected override void SetCursorPosition (int column) { ((TextField)HostControl).CursorPosition = column; }
  1559. }