TextField.cs 39 KB

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