TextField.cs 55 KB

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