TextView.Utilities.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. namespace Terminal.Gui.Views;
  2. /// <summary>Utility and helper methods for TextView</summary>
  3. public partial class TextView
  4. {
  5. /// <summary>
  6. /// INTERNAL: Adjusts the scroll position and cursor to ensure the cursor is visible in the viewport.
  7. /// This method handles both horizontal and vertical scrolling, word wrap considerations, and syncs
  8. /// the internal scroll fields with the Viewport property.
  9. /// </summary>
  10. private void AdjustScrollPosition ()
  11. {
  12. (int width, int height) offB = GetViewportClipping ();
  13. List<Cell> line = GetCurrentLine ();
  14. bool need = NeedsDraw || _wrapNeeded || !Used;
  15. (int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
  16. (int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
  17. if (!_wordWrap && CurrentColumn < _leftColumn)
  18. {
  19. _leftColumn = CurrentColumn;
  20. need = true;
  21. }
  22. else if (!_wordWrap
  23. && (CurrentColumn - _leftColumn + 1 > Viewport.Width + offB.width || dSize.size + 1 >= Viewport.Width + offB.width))
  24. {
  25. _leftColumn = TextModel.CalculateLeftColumn (
  26. line,
  27. _leftColumn,
  28. CurrentColumn,
  29. Viewport.Width + offB.width,
  30. TabWidth
  31. );
  32. need = true;
  33. }
  34. else if ((_wordWrap && _leftColumn > 0) || (dSize.size < Viewport.Width + offB.width && tSize.size < Viewport.Width + offB.width))
  35. {
  36. if (_leftColumn > 0)
  37. {
  38. _leftColumn = 0;
  39. need = true;
  40. }
  41. }
  42. if (CurrentRow < _topRow)
  43. {
  44. _topRow = CurrentRow;
  45. need = true;
  46. }
  47. else if (CurrentRow - _topRow >= Viewport.Height + offB.height)
  48. {
  49. _topRow = Math.Min (Math.Max (CurrentRow - Viewport.Height + 1, 0), CurrentRow);
  50. need = true;
  51. }
  52. else if (_topRow > 0 && CurrentRow < _topRow)
  53. {
  54. _topRow = Math.Max (_topRow - 1, 0);
  55. need = true;
  56. }
  57. // Sync Viewport with the internal scroll position
  58. if (IsInitialized && (_leftColumn != Viewport.X || _topRow != Viewport.Y))
  59. {
  60. Viewport = new Rectangle (_leftColumn, _topRow, Viewport.Width, Viewport.Height);
  61. }
  62. if (need)
  63. {
  64. if (_wrapNeeded)
  65. {
  66. WrapTextModel ();
  67. _wrapNeeded = false;
  68. }
  69. SetNeedsDraw ();
  70. }
  71. else
  72. {
  73. if (IsInitialized)
  74. {
  75. PositionCursor ();
  76. }
  77. }
  78. OnUnwrappedCursorPosition ();
  79. }
  80. /// <summary>
  81. /// INTERNAL: Determines if a redraw is needed based on selection state, word wrap needs, and Used flag.
  82. /// If a redraw is needed, calls <see cref="AdjustScrollPosition"/>; otherwise positions the cursor and updates
  83. /// the unwrapped cursor position.
  84. /// </summary>
  85. private void DoNeededAction ()
  86. {
  87. if (!NeedsDraw && (IsSelecting || _wrapNeeded || !Used))
  88. {
  89. SetNeedsDraw ();
  90. }
  91. if (NeedsDraw)
  92. {
  93. AdjustScrollPosition ();
  94. }
  95. else
  96. {
  97. PositionCursor ();
  98. OnUnwrappedCursorPosition ();
  99. }
  100. }
  101. /// <summary>
  102. /// INTERNAL: Calculates the viewport clipping caused by the view extending beyond the SuperView's boundaries.
  103. /// Returns negative width and height offsets when the viewport extends beyond the SuperView, representing
  104. /// how much of the viewport is clipped.
  105. /// </summary>
  106. /// <returns>A tuple containing the width and height clipping offsets (negative when clipped).</returns>
  107. private (int width, int height) GetViewportClipping ()
  108. {
  109. var w = 0;
  110. var h = 0;
  111. if (SuperView?.Viewport.Right - Viewport.Right < 0)
  112. {
  113. w = SuperView!.Viewport.Right - Viewport.Right - 1;
  114. }
  115. if (SuperView?.Viewport.Bottom - Viewport.Bottom < 0)
  116. {
  117. h = SuperView!.Viewport.Bottom - Viewport.Bottom - 1;
  118. }
  119. return (w, h);
  120. }
  121. /// <summary>
  122. /// INTERNAL: Updates the content size based on the text model dimensions.
  123. /// When word wrap is enabled, content width equals viewport width.
  124. /// Otherwise, calculates the maximum line width from the entire text model.
  125. /// Content height is always the number of lines in the model.
  126. /// </summary>
  127. private void UpdateContentSize ()
  128. {
  129. int contentHeight = Math.Max (_model.Count, 1);
  130. // For horizontal size: if word wrap is enabled, content width equals viewport width
  131. // Otherwise, calculate the maximum line width (but only if we have a reasonable viewport)
  132. int contentWidth;
  133. if (_wordWrap)
  134. {
  135. // Word wrap: content width follows viewport width
  136. contentWidth = Math.Max (Viewport.Width, 1);
  137. }
  138. else
  139. {
  140. // No word wrap: calculate max line width
  141. // Cache the current value to avoid recalculating on every call
  142. contentWidth = Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);
  143. }
  144. SetContentSize (new Size (contentWidth, contentHeight));
  145. }
  146. /// <summary>
  147. /// INTERNAL: Resets the cursor position and scroll offsets to the beginning of the document (0,0)
  148. /// and stops any active text selection.
  149. /// </summary>
  150. private void ResetPosition ()
  151. {
  152. _topRow = _leftColumn = CurrentRow = CurrentColumn = 0;
  153. StopSelecting ();
  154. }
  155. /// <summary>
  156. /// INTERNAL: Resets the column tracking state and last kill operation flag.
  157. /// Column tracking is used to maintain the desired cursor column position when moving up/down
  158. /// through lines of different lengths.
  159. /// </summary>
  160. private void ResetColumnTrack ()
  161. {
  162. // Handle some state here - whether the last command was a kill
  163. // operation and the column tracking (up/down)
  164. _lastWasKill = false;
  165. _columnTrack = -1;
  166. }
  167. }