Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 106 additions & 2 deletions Terminal.Gui/Views/TextInput/TextView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,13 @@ public int LeftColumn
return;
}

_leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0);
int clampedValue = Math.Max (Math.Min (value, Maxlength - 1), 0);
_leftColumn = clampedValue;

if (IsInitialized && Viewport.X != _leftColumn)
{
Viewport = Viewport with { X = _leftColumn };
}
}
}

Expand Down Expand Up @@ -966,7 +972,16 @@ public override string Text
public int TopRow
{
get => _topRow;
set => _topRow = Math.Max (Math.Min (value, Lines - 1), 0);
set
{
int clampedValue = Math.Max (Math.Min (value, Lines - 1), 0);
_topRow = clampedValue;

if (IsInitialized && Viewport.Y != _topRow)
{
Viewport = Viewport with { Y = _topRow };
}
}
}

/// <summary>
Expand Down Expand Up @@ -1004,6 +1019,13 @@ public bool WordWrap
_model = _wrapManager.Model;
}

// Update horizontal scrollbar AutoShow based on WordWrap
if (IsInitialized)
{
HorizontalScrollBar.AutoShow = !_wordWrap;
UpdateContentSize ();
}

SetNeedsDraw ();
}
}
Expand Down Expand Up @@ -1777,6 +1799,12 @@ public virtual void OnContentsChanged ()

ProcessInheritsPreviousScheme (CurrentRow, CurrentColumn);
ProcessAutocomplete ();

// Update content size when content changes
if (IsInitialized)
{
UpdateContentSize ();
}
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance concern: UpdateContentSize() is called on every content change (each keystroke), which triggers _model.GetMaxVisibleLine(0, _model.Count, TabWidth) - a potentially expensive O(n) operation that scans all lines. For large documents, this could cause noticeable lag during typing. Consider throttling or debouncing this call, or caching the max line width and only updating it when necessary.

Copilot uses AI. Check for mistakes.
}

/// <inheritdoc/>
Expand Down Expand Up @@ -2139,12 +2167,22 @@ public void ScrollTo (int idx, bool isRow = true)
if (isRow)
{
_topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0);

if (IsInitialized && Viewport.Y != _topRow)
{
Viewport = Viewport with { Y = _topRow };
}
}
else if (!_wordWrap)
{
int maxlength =
_model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
_leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);

if (IsInitialized && Viewport.X != _leftColumn)
{
Viewport = Viewport with { X = _leftColumn };
}
}

SetNeedsDraw ();
Expand Down Expand Up @@ -2342,6 +2380,12 @@ private void Adjust ()
need = true;
}

// Sync Viewport with the internal scroll position
if (IsInitialized && (_leftColumn != Viewport.X || _topRow != Viewport.Y))
{
Viewport = new Rectangle (_leftColumn, _topRow, Viewport.Width, Viewport.Height);
}

if (need)
{
if (_wrapNeeded)
Expand Down Expand Up @@ -4652,12 +4696,72 @@ private void TextView_Initialized (object sender, EventArgs e)
App?.Popover?.Register (ContextMenu);
KeyBindings.Add (ContextMenu.Key, Command.Context);

// Configure ScrollBars to use modern View scrolling infrastructure
ConfigureScrollBars ();

OnContentsChanged ();
}

/// <summary>
/// Configures the ScrollBars to work with the modern View scrolling system.
/// </summary>
private void ConfigureScrollBars ()
{
// Subscribe to ViewportChanged to sync internal scroll fields
ViewportChanged += TextView_ViewportChanged;

// Vertical ScrollBar: AutoShow enabled by default as per requirements
VerticalScrollBar.AutoShow = true;

// Horizontal ScrollBar: AutoShow tracks WordWrap as per requirements
HorizontalScrollBar.AutoShow = !WordWrap;
}

private void TextView_ViewportChanged (object? sender, DrawEventArgs e)
{
// Sync internal scroll position fields with Viewport
// Only update if values actually changed to prevent infinite loops
if (_topRow != Viewport.Y)
{
_topRow = Viewport.Y;
}

if (_leftColumn != Viewport.X)
{
_leftColumn = Viewport.X;
}
}

/// <summary>
/// Updates the content size based on the text model dimensions.
/// </summary>
private void UpdateContentSize ()
{
int contentHeight = Math.Max (_model.Count, 1);

// For horizontal size: if word wrap is enabled, content width equals viewport width
// Otherwise, calculate the maximum line width (but only if we have a reasonable viewport)
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete or outdated comment: the comment mentions "(but only if we have a reasonable viewport)" but there's no conditional check for a "reasonable viewport" in the code. Remove this misleading phrase from the comment.

Suggested change
// Otherwise, calculate the maximum line width (but only if we have a reasonable viewport)
// Otherwise, calculate the maximum line width

Copilot uses AI. Check for mistakes.
int contentWidth;

if (_wordWrap)
{
// Word wrap: content width follows viewport width
contentWidth = Math.Max (Viewport.Width, 1);
}
else
{
// No word wrap: calculate max line width
// Cache the current value to avoid recalculating on every call
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading comment: the comment claims to "cache the current value to avoid recalculating on every call" but no caching is actually implemented. The method calls _model.GetMaxVisibleLine() on every invocation. Either implement caching or remove the misleading comment.

Suggested change
// Cache the current value to avoid recalculating on every call

Copilot uses AI. Check for mistakes.
contentWidth = Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);
}
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Suggested change
if (_wordWrap)
{
// Word wrap: content width follows viewport width
contentWidth = Math.Max (Viewport.Width, 1);
}
else
{
// No word wrap: calculate max line width
// Cache the current value to avoid recalculating on every call
contentWidth = Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);
}
contentWidth = _wordWrap
? Math.Max (Viewport.Width, 1)
: Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);

Copilot uses AI. Check for mistakes.

SetContentSize (new Size (contentWidth, contentHeight));
}

private void TextView_LayoutComplete (object? sender, LayoutEventArgs e)
{
WrapTextModel ();
UpdateContentSize ();
Adjust ();
}

Expand Down
Loading
Loading