2
0

TextView.cs 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328
  1. using System.Globalization;
  2. using System.Runtime.CompilerServices;
  3. namespace Terminal.Gui.Views;
  4. /// <summary>Fully featured multi-line text editor</summary>
  5. /// <remarks>
  6. /// <list type="table">
  7. /// <listheader>
  8. /// <term>Shortcut</term> <description>Action performed</description>
  9. /// </listheader>
  10. /// <item>
  11. /// <term>Left cursor, Control-b</term> <description>Moves the editing point left.</description>
  12. /// </item>
  13. /// <item>
  14. /// <term>Right cursor, Control-f</term> <description>Moves the editing point right.</description>
  15. /// </item>
  16. /// <item>
  17. /// <term>Alt-b</term> <description>Moves one word back.</description>
  18. /// </item>
  19. /// <item>
  20. /// <term>Alt-f</term> <description>Moves one word forward.</description>
  21. /// </item>
  22. /// <item>
  23. /// <term>Up cursor, Control-p</term> <description>Moves the editing point one line up.</description>
  24. /// </item>
  25. /// <item>
  26. /// <term>Down cursor, Control-n</term> <description>Moves the editing point one line down</description>
  27. /// </item>
  28. /// <item>
  29. /// <term>Home key, Control-a</term> <description>Moves the cursor to the beginning of the line.</description>
  30. /// </item>
  31. /// <item>
  32. /// <term>End key, Control-e</term> <description>Moves the cursor to the end of the line.</description>
  33. /// </item>
  34. /// <item>
  35. /// <term>Control-Home</term> <description>Scrolls to the first line and moves the cursor there.</description>
  36. /// </item>
  37. /// <item>
  38. /// <term>Control-End</term> <description>Scrolls to the last line and moves the cursor there.</description>
  39. /// </item>
  40. /// <item>
  41. /// <term>Delete, Control-d</term> <description>Deletes the character in front of the cursor.</description>
  42. /// </item>
  43. /// <item>
  44. /// <term>Backspace</term> <description>Deletes the character behind the cursor.</description>
  45. /// </item>
  46. /// <item>
  47. /// <term>Control-k</term>
  48. /// <description>
  49. /// Deletes the text until the end of the line and replaces the kill buffer with the deleted text.
  50. /// You can paste this text in a different place by using Control-y.
  51. /// </description>
  52. /// </item>
  53. /// <item>
  54. /// <term>Control-y</term>
  55. /// <description>Pastes the content of the kill ring into the current position.</description>
  56. /// </item>
  57. /// <item>
  58. /// <term>Alt-d</term>
  59. /// <description>
  60. /// Deletes the word above the cursor and adds it to the kill ring. You can paste the contents of
  61. /// the kill ring with Control-y.
  62. /// </description>
  63. /// </item>
  64. /// <item>
  65. /// <term>Control-q</term>
  66. /// <description>
  67. /// Quotes the next input character, to prevent the normal processing of key handling to take
  68. /// place.
  69. /// </description>
  70. /// </item>
  71. /// </list>
  72. /// </remarks>
  73. public partial class TextView : View, IDesignable
  74. {
  75. // BUGBUG: AllowsReturn is mis-named. It should be EnterKeyAccepts.
  76. /// <summary>
  77. /// Gets or sets whether pressing ENTER in a <see cref="TextView"/> creates a new line of text
  78. /// in the view or invokes the <see cref="View.Accepting"/> event.
  79. /// </summary>
  80. /// <remarks>
  81. /// <para>
  82. /// Setting this property alters <see cref="Multiline"/>.
  83. /// If <see cref="AllowsReturn"/> is set to <see langword="true"/>, then <see cref="Multiline"/> is also set to
  84. /// `true` and
  85. /// vice-versa.
  86. /// </para>
  87. /// <para>
  88. /// If <see cref="AllowsReturn"/> is set to <see langword="false"/>, then <see cref="AllowsTab"/> gets set to
  89. /// <see langword="false"/>.
  90. /// </para>
  91. /// </remarks>
  92. public bool AllowsReturn
  93. {
  94. get => _allowsReturn;
  95. set
  96. {
  97. _allowsReturn = value;
  98. if (_allowsReturn && !_multiline)
  99. {
  100. // BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsReturn should be independent.
  101. Multiline = true;
  102. }
  103. if (!_allowsReturn && _multiline)
  104. {
  105. Multiline = false;
  106. // BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsTab should be independent.
  107. AllowsTab = false;
  108. }
  109. SetNeedsDraw ();
  110. }
  111. }
  112. /// <summary>
  113. /// Gets or sets whether the <see cref="TextView"/> inserts a tab character into the text or ignores tab input. If
  114. /// set to `false` and the user presses the tab key (or shift-tab) the focus will move to the next view (or previous
  115. /// with shift-tab). The default is `true`; if the user presses the tab key, a tab character will be inserted into the
  116. /// text.
  117. /// </summary>
  118. public bool AllowsTab
  119. {
  120. get => _allowsTab;
  121. set
  122. {
  123. _allowsTab = value;
  124. if (_allowsTab && _tabWidth == 0)
  125. {
  126. _tabWidth = 4;
  127. }
  128. if (_allowsTab && !_multiline)
  129. {
  130. Multiline = true;
  131. }
  132. if (!_allowsTab && _tabWidth > 0)
  133. {
  134. _tabWidth = 0;
  135. }
  136. SetNeedsDraw ();
  137. }
  138. }
  139. /// <summary>
  140. /// Provides autocomplete context menu based on suggestions at the current cursor position. Configure
  141. /// <see cref="IAutocomplete.SuggestionGenerator"/> to enable this feature
  142. /// </summary>
  143. public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
  144. /// <summary>Get the Context Menu.</summary>
  145. public PopoverMenu? ContextMenu { get; private set; }
  146. /// <summary>Gets the cursor column.</summary>
  147. /// <value>The cursor column.</value>
  148. public int CurrentColumn { get; private set; }
  149. /// <summary>Gets the current cursor row.</summary>
  150. public int CurrentRow { get; private set; }
  151. /// <summary>Sets or gets the current cursor position.</summary>
  152. public Point CursorPosition
  153. {
  154. get => new (CurrentColumn, CurrentRow);
  155. set
  156. {
  157. List<Cell> line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0));
  158. CurrentColumn = value.X < 0 ? 0 :
  159. value.X > line.Count ? line.Count : value.X;
  160. CurrentRow = value.Y < 0 ? 0 :
  161. value.Y > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value.Y;
  162. SetNeedsDraw ();
  163. AdjustScrollPosition ();
  164. }
  165. }
  166. /// <summary>
  167. /// Indicates whatever the text has history changes or not. <see langword="true"/> if the text has history changes
  168. /// <see langword="false"/> otherwise.
  169. /// </summary>
  170. public bool HasHistoryChanges => _historyText.HasHistoryChanges;
  171. /// <summary>
  172. /// If <see langword="true"/> and the current <see cref="Cell.Attribute"/> is null will inherit from the
  173. /// previous, otherwise if <see langword="false"/> (default) do nothing. If the text is load with
  174. /// <see cref="Load(List{Cell})"/> this property is automatically sets to <see langword="true"/>.
  175. /// </summary>
  176. public bool InheritsPreviousAttribute { get; set; }
  177. /// <summary>
  178. /// Indicates whatever the text was changed or not. <see langword="true"/> if the text was changed
  179. /// <see langword="false"/> otherwise.
  180. /// </summary>
  181. public bool IsDirty
  182. {
  183. get => _historyText.IsDirty (_model.GetAllLines ());
  184. set => _historyText.Clear (_model.GetAllLines ());
  185. }
  186. /// <summary>Gets or sets the left column.</summary>
  187. public int LeftColumn
  188. {
  189. get => _leftColumn;
  190. set
  191. {
  192. if (value > 0 && _wordWrap)
  193. {
  194. return;
  195. }
  196. int clampedValue = Math.Max (Math.Min (value, Maxlength - 1), 0);
  197. _leftColumn = clampedValue;
  198. if (IsInitialized && Viewport.X != _leftColumn)
  199. {
  200. Viewport = Viewport with { X = _leftColumn };
  201. }
  202. }
  203. }
  204. /// <summary>Gets the number of lines.</summary>
  205. public int Lines => _model.Count;
  206. /// <summary>Gets the maximum visible length line.</summary>
  207. public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
  208. /// <summary>Gets or sets a value indicating whether this <see cref="TextView"/> is a multiline text view.</summary>
  209. public bool Multiline
  210. {
  211. get => _multiline;
  212. set
  213. {
  214. _multiline = value;
  215. if (_multiline && !_allowsTab)
  216. {
  217. AllowsTab = true;
  218. }
  219. if (_multiline && !_allowsReturn)
  220. {
  221. AllowsReturn = true;
  222. }
  223. if (!_multiline)
  224. {
  225. AllowsReturn = false;
  226. AllowsTab = false;
  227. WordWrap = false;
  228. CurrentColumn = 0;
  229. CurrentRow = 0;
  230. _savedHeight = Height;
  231. Height = Dim.Auto (DimAutoStyle.Text, 1);
  232. if (!IsInitialized)
  233. {
  234. _model.LoadString (Text);
  235. }
  236. SetNeedsDraw ();
  237. }
  238. else if (_multiline && _savedHeight is { })
  239. {
  240. Height = _savedHeight;
  241. SetNeedsDraw ();
  242. }
  243. KeyBindings.Remove (Key.Enter);
  244. KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
  245. }
  246. }
  247. /// <summary>Gets or sets whether the <see cref="TextView"/> is in read-only mode or not</summary>
  248. /// <value>Boolean value(Default false)</value>
  249. public bool ReadOnly
  250. {
  251. get => _isReadOnly;
  252. set
  253. {
  254. if (value != _isReadOnly)
  255. {
  256. _isReadOnly = value;
  257. SetNeedsDraw ();
  258. WrapTextModel ();
  259. AdjustScrollPosition ();
  260. }
  261. }
  262. }
  263. /// <summary>Gets or sets a value indicating the number of whitespace when pressing the TAB key.</summary>
  264. public int TabWidth
  265. {
  266. get => _tabWidth;
  267. set
  268. {
  269. _tabWidth = Math.Max (value, 0);
  270. if (_tabWidth > 0 && !AllowsTab)
  271. {
  272. AllowsTab = true;
  273. }
  274. SetNeedsDraw ();
  275. }
  276. }
  277. /// <summary>Sets or gets the text in the <see cref="TextView"/>.</summary>
  278. /// <remarks>
  279. /// The <see cref="View.TextChanged"/> event is fired whenever this property is set. Note, however, that Text is not
  280. /// set by <see cref="TextView"/> as the user types.
  281. /// </remarks>
  282. public override string Text
  283. {
  284. get
  285. {
  286. if (_wordWrap)
  287. {
  288. return _wrapManager!.Model.ToString ();
  289. }
  290. return _model.ToString ();
  291. }
  292. set
  293. {
  294. ResetPosition ();
  295. _model.LoadString (value);
  296. if (_wordWrap)
  297. {
  298. _wrapManager = new (_model);
  299. _model = _wrapManager.WrapModel (Viewport.Width, out _, out _, out _, out _);
  300. }
  301. OnTextChanged ();
  302. SetNeedsDraw ();
  303. _historyText.Clear (_model.GetAllLines ());
  304. }
  305. }
  306. /// <summary>Gets or sets the top row.</summary>
  307. public int TopRow
  308. {
  309. get => _topRow;
  310. set
  311. {
  312. int clampedValue = Math.Max (Math.Min (value, Lines - 1), 0);
  313. _topRow = clampedValue;
  314. if (IsInitialized && Viewport.Y != _topRow)
  315. {
  316. Viewport = Viewport with { Y = _topRow };
  317. }
  318. }
  319. }
  320. /// <summary>
  321. /// Tracks whether the text view should be considered "used", that is, that the user has moved in the entry, so
  322. /// new input should be appended at the cursor position, rather than clearing the entry
  323. /// </summary>
  324. public bool Used { get; set; }
  325. /// <summary>Allows word wrap the to fit the available container width.</summary>
  326. public bool WordWrap
  327. {
  328. get => _wordWrap;
  329. set
  330. {
  331. if (value == _wordWrap)
  332. {
  333. return;
  334. }
  335. if (value && !_multiline)
  336. {
  337. return;
  338. }
  339. _wordWrap = value;
  340. ResetPosition ();
  341. if (_wordWrap)
  342. {
  343. _wrapManager = new (_model);
  344. WrapTextModel ();
  345. }
  346. else if (!_wordWrap && _wrapManager is { })
  347. {
  348. _model = _wrapManager.Model;
  349. }
  350. // Update horizontal scrollbar AutoShow based on WordWrap
  351. if (IsInitialized)
  352. {
  353. HorizontalScrollBar.AutoShow = !_wordWrap;
  354. UpdateContentSize ();
  355. }
  356. SetNeedsDraw ();
  357. }
  358. }
  359. /// <summary>
  360. /// Gets or sets whether the word forward and word backward navigation should use the same or equivalent rune type.
  361. /// Default is <c>false</c> meaning using equivalent rune type.
  362. /// </summary>
  363. public bool UseSameRuneTypeForWords { get; set; }
  364. /// <summary>Allows clearing the <see cref="HistoryTextItemEventArgs"/> items updating the original text.</summary>
  365. public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
  366. /// <summary>Closes the contents of the stream into the <see cref="TextView"/>.</summary>
  367. /// <returns><c>true</c>, if stream was closed, <c>false</c> otherwise.</returns>
  368. public bool CloseFile ()
  369. {
  370. SetWrapModel ();
  371. bool res = _model.CloseFile ();
  372. ResetPosition ();
  373. SetNeedsDraw ();
  374. UpdateWrapModel ();
  375. return res;
  376. }
  377. /// <summary>Raised when the contents of the <see cref="TextView"/> are changed.</summary>
  378. /// <remarks>
  379. /// Unlike the <see cref="View.TextChanged"/> event, this event is raised whenever the user types or otherwise changes
  380. /// the contents of the <see cref="TextView"/>.
  381. /// </remarks>
  382. public event EventHandler<ContentsChangedEventArgs>? ContentsChanged;
  383. /// <summary>
  384. /// Open a dialog to set the foreground and background colors.
  385. /// </summary>
  386. public void PromptForColors ()
  387. {
  388. if (!ColorPicker.Prompt (
  389. "Colors",
  390. GetSelectedCellAttribute (),
  391. out Attribute newAttribute
  392. ))
  393. {
  394. return;
  395. }
  396. var attribute = new Attribute (
  397. newAttribute.Foreground,
  398. newAttribute.Background,
  399. newAttribute.Style
  400. );
  401. ApplyCellsAttribute (attribute);
  402. }
  403. /// <summary>Gets all lines of characters.</summary>
  404. /// <returns></returns>
  405. public List<List<Cell>> GetAllLines () { return _model.GetAllLines (); }
  406. /// <summary>
  407. /// Returns the characters on the current line (where the cursor is positioned). Use <see cref="CurrentColumn"/>
  408. /// to determine the position of the cursor within that line
  409. /// </summary>
  410. /// <returns></returns>
  411. public List<Cell> GetCurrentLine () { return _model.GetLine (CurrentRow); }
  412. /// <summary>Returns the characters on the <paramref name="line"/>.</summary>
  413. /// <param name="line">The intended line.</param>
  414. /// <returns></returns>
  415. public List<Cell> GetLine (int line) { return _model.GetLine (line); }
  416. /// <summary>Loads the contents of the file into the <see cref="TextView"/>.</summary>
  417. /// <returns><c>true</c>, if file was loaded, <c>false</c> otherwise.</returns>
  418. /// <param name="path">Path to the file to load.</param>
  419. public bool Load (string path)
  420. {
  421. SetWrapModel ();
  422. bool res;
  423. try
  424. {
  425. SetWrapModel ();
  426. res = _model.LoadFile (path);
  427. _historyText.Clear (_model.GetAllLines ());
  428. ResetPosition ();
  429. }
  430. finally
  431. {
  432. UpdateWrapModel ();
  433. SetNeedsDraw ();
  434. AdjustScrollPosition ();
  435. }
  436. UpdateWrapModel ();
  437. return res;
  438. }
  439. /// <summary>Loads the contents of the stream into the <see cref="TextView"/>.</summary>
  440. /// <returns><c>true</c>, if stream was loaded, <c>false</c> otherwise.</returns>
  441. /// <param name="stream">Stream to load the contents from.</param>
  442. public void Load (Stream stream)
  443. {
  444. SetWrapModel ();
  445. _model.LoadStream (stream);
  446. _historyText.Clear (_model.GetAllLines ());
  447. ResetPosition ();
  448. SetNeedsDraw ();
  449. UpdateWrapModel ();
  450. }
  451. /// <summary>Loads the contents of the <see cref="Cell"/> list into the <see cref="TextView"/>.</summary>
  452. /// <param name="cells">Text cells list to load the contents from.</param>
  453. public void Load (List<Cell> cells)
  454. {
  455. SetWrapModel ();
  456. _model.LoadCells (cells, GetAttributeForRole (VisualRole.Focus));
  457. _historyText.Clear (_model.GetAllLines ());
  458. ResetPosition ();
  459. SetNeedsDraw ();
  460. UpdateWrapModel ();
  461. InheritsPreviousAttribute = true;
  462. }
  463. /// <summary>Loads the contents of the list of <see cref="Cell"/> list into the <see cref="TextView"/>.</summary>
  464. /// <param name="cellsList">List of rune cells list to load the contents from.</param>
  465. public void Load (List<List<Cell>> cellsList)
  466. {
  467. SetWrapModel ();
  468. InheritsPreviousAttribute = true;
  469. _model.LoadListCells (cellsList, GetAttributeForRole (VisualRole.Focus));
  470. _historyText.Clear (_model.GetAllLines ());
  471. ResetPosition ();
  472. SetNeedsDraw ();
  473. UpdateWrapModel ();
  474. }
  475. /// <inheritdoc/>
  476. protected override bool OnMouseEvent (MouseEventArgs ev)
  477. {
  478. if (ev is { IsSingleDoubleOrTripleClicked: false, IsPressed: false, IsReleased: false, IsWheel: false }
  479. && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)
  480. && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)
  481. && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift)
  482. && !ev.Flags.HasFlag (ContextMenu!.MouseFlags))
  483. {
  484. return false;
  485. }
  486. if (!CanFocus)
  487. {
  488. return true;
  489. }
  490. if (!HasFocus)
  491. {
  492. SetFocus ();
  493. }
  494. _continuousFind = false;
  495. // Give autocomplete first opportunity to respond to mouse clicks
  496. if (SelectedLength == 0 && Autocomplete.OnMouseEvent (ev, true))
  497. {
  498. return true;
  499. }
  500. if (ev.Flags == MouseFlags.Button1Clicked)
  501. {
  502. if (_isButtonReleased)
  503. {
  504. _isButtonReleased = false;
  505. if (SelectedLength == 0)
  506. {
  507. StopSelecting ();
  508. }
  509. return true;
  510. }
  511. if (_shiftSelecting && !_isButtonShift)
  512. {
  513. StopSelecting ();
  514. }
  515. ProcessMouseClick (ev, out _);
  516. if (Used)
  517. {
  518. PositionCursor ();
  519. }
  520. else
  521. {
  522. SetNeedsDraw ();
  523. }
  524. _lastWasKill = false;
  525. _columnTrack = CurrentColumn;
  526. }
  527. else if (ev.Flags == MouseFlags.WheeledDown)
  528. {
  529. _lastWasKill = false;
  530. _columnTrack = CurrentColumn;
  531. ScrollTo (_topRow + 1);
  532. }
  533. else if (ev.Flags == MouseFlags.WheeledUp)
  534. {
  535. _lastWasKill = false;
  536. _columnTrack = CurrentColumn;
  537. ScrollTo (_topRow - 1);
  538. }
  539. else if (ev.Flags == MouseFlags.WheeledRight)
  540. {
  541. _lastWasKill = false;
  542. _columnTrack = CurrentColumn;
  543. ScrollTo (_leftColumn + 1, false);
  544. }
  545. else if (ev.Flags == MouseFlags.WheeledLeft)
  546. {
  547. _lastWasKill = false;
  548. _columnTrack = CurrentColumn;
  549. ScrollTo (_leftColumn - 1, false);
  550. }
  551. else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
  552. {
  553. ProcessMouseClick (ev, out List<Cell> line);
  554. PositionCursor ();
  555. if (_model.Count > 0 && _shiftSelecting && IsSelecting)
  556. {
  557. if (CurrentRow - _topRow >= Viewport.Height - 1 && _model.Count > _topRow + CurrentRow)
  558. {
  559. ScrollTo (_topRow + Viewport.Height);
  560. }
  561. else if (_topRow > 0 && CurrentRow <= _topRow)
  562. {
  563. ScrollTo (_topRow - Viewport.Height);
  564. }
  565. else if (ev.Position.Y >= Viewport.Height)
  566. {
  567. ScrollTo (_model.Count);
  568. }
  569. else if (ev.Position.Y < 0 && _topRow > 0)
  570. {
  571. ScrollTo (0);
  572. }
  573. if (CurrentColumn - _leftColumn >= Viewport.Width - 1 && line.Count > _leftColumn + CurrentColumn)
  574. {
  575. ScrollTo (_leftColumn + Viewport.Width, false);
  576. }
  577. else if (_leftColumn > 0 && CurrentColumn <= _leftColumn)
  578. {
  579. ScrollTo (_leftColumn - Viewport.Width, false);
  580. }
  581. else if (ev.Position.X >= Viewport.Width)
  582. {
  583. ScrollTo (line.Count, false);
  584. }
  585. else if (ev.Position.X < 0 && _leftColumn > 0)
  586. {
  587. ScrollTo (0, false);
  588. }
  589. }
  590. _lastWasKill = false;
  591. _columnTrack = CurrentColumn;
  592. }
  593. else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift))
  594. {
  595. if (!_shiftSelecting)
  596. {
  597. _isButtonShift = true;
  598. StartSelecting ();
  599. }
  600. ProcessMouseClick (ev, out _);
  601. PositionCursor ();
  602. _lastWasKill = false;
  603. _columnTrack = CurrentColumn;
  604. }
  605. else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed))
  606. {
  607. if (_shiftSelecting)
  608. {
  609. _clickWithSelecting = true;
  610. StopSelecting ();
  611. }
  612. ProcessMouseClick (ev, out _);
  613. PositionCursor ();
  614. if (!IsSelecting)
  615. {
  616. StartSelecting ();
  617. }
  618. _lastWasKill = false;
  619. _columnTrack = CurrentColumn;
  620. if (App?.Mouse.MouseGrabView is null)
  621. {
  622. App?.Mouse.GrabMouse (this);
  623. }
  624. }
  625. else if (ev.Flags.HasFlag (MouseFlags.Button1Released))
  626. {
  627. _isButtonReleased = true;
  628. App?.Mouse.UngrabMouse ();
  629. }
  630. else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
  631. {
  632. if (ev.Flags.HasFlag (MouseFlags.ButtonShift))
  633. {
  634. if (!IsSelecting)
  635. {
  636. StartSelecting ();
  637. }
  638. }
  639. else if (IsSelecting)
  640. {
  641. StopSelecting ();
  642. }
  643. ProcessMouseClick (ev, out List<Cell> line);
  644. if (!IsSelecting)
  645. {
  646. StartSelecting ();
  647. }
  648. (int startCol, int col, int row)? newPos = _model.ProcessDoubleClickSelection (SelectionStartColumn, CurrentColumn, CurrentRow, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
  649. if (newPos.HasValue)
  650. {
  651. SelectionStartColumn = newPos.Value.startCol;
  652. CurrentColumn = newPos.Value.col;
  653. CurrentRow = newPos.Value.row;
  654. }
  655. PositionCursor ();
  656. _lastWasKill = false;
  657. _columnTrack = CurrentColumn;
  658. SetNeedsDraw ();
  659. }
  660. else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked))
  661. {
  662. if (IsSelecting)
  663. {
  664. StopSelecting ();
  665. }
  666. ProcessMouseClick (ev, out List<Cell> line);
  667. CurrentColumn = 0;
  668. if (!IsSelecting)
  669. {
  670. StartSelecting ();
  671. }
  672. CurrentColumn = line.Count;
  673. PositionCursor ();
  674. _lastWasKill = false;
  675. _columnTrack = CurrentColumn;
  676. SetNeedsDraw ();
  677. }
  678. else if (ev.Flags == ContextMenu!.MouseFlags)
  679. {
  680. ShowContextMenu (ev.ScreenPosition);
  681. }
  682. OnUnwrappedCursorPosition ();
  683. return true;
  684. }
  685. /// <summary>
  686. /// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises the
  687. /// <see cref="ContentsChanged"/> event.
  688. /// </summary>
  689. public virtual void OnContentsChanged ()
  690. {
  691. ContentsChanged?.Invoke (this, new (CurrentRow, CurrentColumn));
  692. ProcessInheritsPreviousScheme (CurrentRow, CurrentColumn);
  693. ProcessAutocomplete ();
  694. // Update content size when content changes
  695. if (IsInitialized)
  696. {
  697. UpdateContentSize ();
  698. }
  699. }
  700. /// <inheritdoc/>
  701. protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view)
  702. {
  703. if (App?.Mouse.MouseGrabView is { } && App?.Mouse.MouseGrabView == this)
  704. {
  705. App?.Mouse.UngrabMouse ();
  706. }
  707. }
  708. /// <inheritdoc/>
  709. protected override bool OnKeyDown (Key key)
  710. {
  711. if (!key.IsValid)
  712. {
  713. return false;
  714. }
  715. // Give autocomplete first opportunity to respond to key presses
  716. if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (key))
  717. {
  718. return true;
  719. }
  720. return false;
  721. }
  722. /// <inheritdoc/>
  723. protected override bool OnKeyDownNotHandled (Key a)
  724. {
  725. if (!CanFocus)
  726. {
  727. return true;
  728. }
  729. ResetColumnTrack ();
  730. // Ignore control characters and other special keys
  731. if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask))
  732. {
  733. return false;
  734. }
  735. InsertText (a);
  736. DoNeededAction ();
  737. return true;
  738. }
  739. /// <inheritdoc/>
  740. public override bool OnKeyUp (Key key)
  741. {
  742. if (key == Key.Space.WithCtrl)
  743. {
  744. return true;
  745. }
  746. return false;
  747. }
  748. /// <summary>Positions the cursor on the current row and column</summary>
  749. public override Point? PositionCursor ()
  750. {
  751. ProcessAutocomplete ();
  752. if (!CanFocus || !Enabled || Driver is null)
  753. {
  754. return null;
  755. }
  756. if (App?.Mouse.MouseGrabView == this && IsSelecting)
  757. {
  758. // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
  759. //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
  760. //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
  761. //SetNeedsDraw (new (0, minRow, Viewport.Width, maxRow));
  762. SetNeedsDraw ();
  763. }
  764. List<Cell> line = _model.GetLine (CurrentRow);
  765. var col = 0;
  766. if (line.Count > 0)
  767. {
  768. for (int idx = _leftColumn; idx < line.Count; idx++)
  769. {
  770. if (idx >= CurrentColumn)
  771. {
  772. break;
  773. }
  774. int cols = line [idx].Grapheme.GetColumns ();
  775. if (line [idx].Grapheme == "\t")
  776. {
  777. cols += TabWidth + 1;
  778. }
  779. else
  780. {
  781. // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
  782. cols = Math.Max (cols, 1);
  783. }
  784. if (!TextModel.SetCol (ref col, Viewport.Width, cols))
  785. {
  786. col = CurrentColumn;
  787. break;
  788. }
  789. }
  790. }
  791. int posX = CurrentColumn - _leftColumn;
  792. int posY = CurrentRow - _topRow;
  793. if (posX > -1 && col >= posX && posX < Viewport.Width && _topRow <= CurrentRow && posY < Viewport.Height)
  794. {
  795. Move (col, CurrentRow - _topRow);
  796. return new (col, CurrentRow - _topRow);
  797. }
  798. return null; // Hide cursor
  799. }
  800. /// <summary>Redoes the latest changes.</summary>
  801. public void Redo ()
  802. {
  803. if (ReadOnly)
  804. {
  805. return;
  806. }
  807. _historyText.Redo ();
  808. }
  809. ///// <summary>Raised when the <see cref="Text"/> property of the <see cref="TextView"/> changes.</summary>
  810. ///// <remarks>
  811. ///// The <see cref="Text"/> property of <see cref="TextView"/> only changes when it is explicitly set, not as the
  812. ///// user types. To be notified as the user changes the contents of the TextView see <see cref="IsDirty"/>.
  813. ///// </remarks>
  814. //public event EventHandler? TextChanged;
  815. /// <summary>Undoes the latest changes.</summary>
  816. public void Undo ()
  817. {
  818. if (ReadOnly)
  819. {
  820. return;
  821. }
  822. _historyText.Undo ();
  823. }
  824. private void ClearRegion (int left, int top, int right, int bottom)
  825. {
  826. for (int row = top; row < bottom; row++)
  827. {
  828. Move (left, row);
  829. for (int col = left; col < right; col++)
  830. {
  831. AddRune (col, row, (Rune)' ');
  832. }
  833. }
  834. }
  835. private void GenerateSuggestions ()
  836. {
  837. List<Cell> currentLine = GetCurrentLine ();
  838. int cursorPosition = Math.Min (CurrentColumn, currentLine.Count);
  839. Autocomplete.Context = new (
  840. currentLine,
  841. cursorPosition,
  842. Autocomplete.Context != null
  843. ? Autocomplete.Context.Canceled
  844. : false
  845. );
  846. Autocomplete.GenerateSuggestions (
  847. Autocomplete.Context
  848. );
  849. }
  850. private void ProcessAutocomplete ()
  851. {
  852. if (_isDrawing)
  853. {
  854. return;
  855. }
  856. if (_clickWithSelecting)
  857. {
  858. _clickWithSelecting = false;
  859. return;
  860. }
  861. if (SelectedLength > 0)
  862. {
  863. return;
  864. }
  865. // draw autocomplete
  866. GenerateSuggestions ();
  867. var renderAt = new Point (
  868. Autocomplete.Context.CursorPosition,
  869. Autocomplete.PopupInsideContainer
  870. ? CursorPosition.Y + 1 - TopRow
  871. : 0
  872. );
  873. Autocomplete.RenderOverlay (renderAt);
  874. }
  875. private bool ProcessBackTab ()
  876. {
  877. ResetColumnTrack ();
  878. if (!AllowsTab || _isReadOnly)
  879. {
  880. return false;
  881. }
  882. if (CurrentColumn > 0)
  883. {
  884. SetWrapModel ();
  885. List<Cell> currentLine = GetCurrentLine ();
  886. if (currentLine.Count > 0 && currentLine [CurrentColumn - 1].Grapheme == "\t")
  887. {
  888. _historyText.Add (new () { new (currentLine) }, CursorPosition);
  889. currentLine.RemoveAt (CurrentColumn - 1);
  890. CurrentColumn--;
  891. _historyText.Add (
  892. new () { new (GetCurrentLine ()) },
  893. CursorPosition,
  894. TextEditingLineStatus.Replaced
  895. );
  896. }
  897. SetNeedsDraw ();
  898. UpdateWrapModel ();
  899. }
  900. DoNeededAction ();
  901. return true;
  902. }
  903. // If InheritsPreviousScheme is enabled this method will check if the rune cell on
  904. // the row and col location and around has a not null scheme. If it's null will set it with
  905. // the very most previous valid scheme.
  906. private void ProcessInheritsPreviousScheme (int row, int col)
  907. {
  908. if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0))
  909. {
  910. return;
  911. }
  912. List<Cell> line = GetLine (row);
  913. List<Cell> lineToSet = line;
  914. while (line.Count == 0)
  915. {
  916. if (row == 0 && line.Count == 0)
  917. {
  918. return;
  919. }
  920. row--;
  921. line = GetLine (row);
  922. lineToSet = line;
  923. }
  924. int colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0);
  925. Cell cell = line [colWithColor];
  926. int colWithoutColor = Math.Max (col - 1, 0);
  927. Cell lineTo = lineToSet [colWithoutColor];
  928. if (cell.Attribute is { } && colWithColor == 0 && lineTo.Attribute is { })
  929. {
  930. for (int r = row - 1; r > -1; r--)
  931. {
  932. List<Cell> l = GetLine (r);
  933. for (int c = l.Count - 1; c > -1; c--)
  934. {
  935. Cell cell1 = l [c];
  936. if (cell1.Attribute is null)
  937. {
  938. cell1.Attribute = cell.Attribute;
  939. l [c] = cell1;
  940. }
  941. else
  942. {
  943. return;
  944. }
  945. }
  946. }
  947. return;
  948. }
  949. if (cell.Attribute is null)
  950. {
  951. for (int r = row; r > -1; r--)
  952. {
  953. List<Cell> l = GetLine (r);
  954. colWithColor = l.FindLastIndex (
  955. colWithColor > -1 ? colWithColor : l.Count - 1,
  956. c => c.Attribute != null
  957. );
  958. if (colWithColor > -1 && l [colWithColor].Attribute is { })
  959. {
  960. cell = l [colWithColor];
  961. break;
  962. }
  963. }
  964. }
  965. else
  966. {
  967. int cRow = row;
  968. while (cell.Attribute is null)
  969. {
  970. if ((colWithColor == 0 || cell.Attribute is null) && cRow > 0)
  971. {
  972. line = GetLine (--cRow);
  973. colWithColor = line.Count - 1;
  974. cell = line [colWithColor];
  975. }
  976. else if (cRow == 0 && colWithColor < line.Count)
  977. {
  978. cell = line [colWithColor + 1];
  979. }
  980. }
  981. }
  982. if (cell.Attribute is { } && colWithColor > -1 && colWithoutColor < lineToSet.Count && lineTo.Attribute is null)
  983. {
  984. while (lineTo.Attribute is null)
  985. {
  986. lineTo.Attribute = cell.Attribute;
  987. lineToSet [colWithoutColor] = lineTo;
  988. colWithoutColor--;
  989. if (colWithoutColor == -1 && row > 0)
  990. {
  991. lineToSet = GetLine (--row);
  992. colWithoutColor = lineToSet.Count - 1;
  993. }
  994. }
  995. }
  996. }
  997. private void ProcessMouseClick (MouseEventArgs ev, out List<Cell> line)
  998. {
  999. List<Cell>? r = null;
  1000. if (_model.Count > 0)
  1001. {
  1002. int maxCursorPositionableLine = Math.Max (_model.Count - 1 - _topRow, 0);
  1003. if (Math.Max (ev.Position.Y, 0) > maxCursorPositionableLine)
  1004. {
  1005. CurrentRow = maxCursorPositionableLine + _topRow;
  1006. }
  1007. else
  1008. {
  1009. CurrentRow = Math.Max (ev.Position.Y + _topRow, 0);
  1010. }
  1011. r = GetCurrentLine ();
  1012. int idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.Position.X, 0), TabWidth);
  1013. if (idx - _leftColumn >= r.Count)
  1014. {
  1015. CurrentColumn = Math.Max (r.Count - _leftColumn - (ReadOnly ? 1 : 0), 0);
  1016. }
  1017. else
  1018. {
  1019. CurrentColumn = idx + _leftColumn;
  1020. }
  1021. }
  1022. line = r!;
  1023. }
  1024. private bool ProcessEnterKey (ICommandContext? commandContext)
  1025. {
  1026. ResetColumnTrack ();
  1027. if (_isReadOnly)
  1028. {
  1029. return false;
  1030. }
  1031. if (!AllowsReturn)
  1032. {
  1033. // By Default pressing ENTER should be ignored (OnAccept will return false or null). Only cancel if the
  1034. // event was fired and set Cancel = true.
  1035. return RaiseAccepting (commandContext) is null or false;
  1036. }
  1037. SetWrapModel ();
  1038. List<Cell> currentLine = GetCurrentLine ();
  1039. _historyText.Add (new () { new (currentLine) }, CursorPosition);
  1040. if (IsSelecting)
  1041. {
  1042. ClearSelectedRegion ();
  1043. currentLine = GetCurrentLine ();
  1044. }
  1045. int restCount = currentLine.Count - CurrentColumn;
  1046. List<Cell> rest = currentLine.GetRange (CurrentColumn, restCount);
  1047. currentLine.RemoveRange (CurrentColumn, restCount);
  1048. List<List<Cell>> addedLines = new () { new (currentLine) };
  1049. _model.AddLine (CurrentRow + 1, rest);
  1050. addedLines.Add (new (_model.GetLine (CurrentRow + 1)));
  1051. _historyText.Add (addedLines, CursorPosition, TextEditingLineStatus.Added);
  1052. CurrentRow++;
  1053. var fullNeedsDraw = false;
  1054. if (CurrentRow >= _topRow + Viewport.Height)
  1055. {
  1056. _topRow++;
  1057. fullNeedsDraw = true;
  1058. }
  1059. CurrentColumn = 0;
  1060. _historyText.Add (
  1061. new () { new (GetCurrentLine ()) },
  1062. CursorPosition,
  1063. TextEditingLineStatus.Replaced
  1064. );
  1065. if (!_wordWrap && CurrentColumn < _leftColumn)
  1066. {
  1067. fullNeedsDraw = true;
  1068. _leftColumn = 0;
  1069. }
  1070. if (fullNeedsDraw)
  1071. {
  1072. SetNeedsDraw ();
  1073. }
  1074. else
  1075. {
  1076. // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
  1077. //SetNeedsDraw (new (0, currentRow - topRow, 2, Viewport.Height));
  1078. SetNeedsDraw ();
  1079. }
  1080. UpdateWrapModel ();
  1081. DoNeededAction ();
  1082. OnContentsChanged ();
  1083. return true;
  1084. }
  1085. private void ProcessSetOverwrite ()
  1086. {
  1087. ResetColumnTrack ();
  1088. SetOverwrite (!Used);
  1089. }
  1090. private bool ProcessTab ()
  1091. {
  1092. ResetColumnTrack ();
  1093. if (!AllowsTab || _isReadOnly)
  1094. {
  1095. return false;
  1096. }
  1097. InsertText (new Key ((KeyCode)'\t'));
  1098. DoNeededAction ();
  1099. return true;
  1100. }
  1101. private void SetOverwrite (bool overwrite)
  1102. {
  1103. Used = overwrite;
  1104. SetNeedsDraw ();
  1105. DoNeededAction ();
  1106. }
  1107. private void SetValidUsedColor (Attribute? attribute)
  1108. {
  1109. // BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now
  1110. //if ((scheme!.HotNormal.Foreground & scheme.Focus.Background) == scheme.Focus.Foreground) {
  1111. SetAttribute (new (attribute!.Value.Background, attribute!.Value.Foreground, attribute!.Value.Style));
  1112. }
  1113. }