TextField.cs 55 KB

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