Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions examples/ted/TedApp.FileOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ internal void SetDocument (string text, string? filePath)
Editor.CaretOffset = 0;
CurrentFilePath = filePath;
UpdateFileNameShortcut ();
InstallFolding ();
Editor.SetNeedsDraw ();
}

Expand Down
31 changes: 31 additions & 0 deletions examples/ted/TedApp.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Terminal.Gui.App;
using Terminal.Gui.Configuration;
using Terminal.Gui.Document;
using Terminal.Gui.Document.Folding;
using Terminal.Gui.Drawing;
using Terminal.Gui.Input;
using Terminal.Gui.Resources;
Expand All @@ -17,6 +18,7 @@ namespace Ted;
/// </summary>
public sealed partial class TedApp : Window
{
private readonly BraceFoldingStrategy _braceFoldingStrategy;
private readonly Shortcut _fileNameShortcut;

/// <summary>Initializes a new <see cref="TedApp" />.</summary>
Expand All @@ -43,6 +45,10 @@ public TedApp (bool readOnly = false)
#pragma warning disable CS0618 // Type or member is obsolete
Editor.SyntaxHighlighter = new TextMateSyntaxHighlighter ();
#pragma warning restore CS0618 // Type or member is obsolete

// Enable brace-based folding. The strategy re-scans on each document change.
_braceFoldingStrategy = new BraceFoldingStrategy ();
InstallFolding ();
ShowOpenDialog = ShowDefaultOpenDialog;
ShowSaveDialog = ShowDefaultSaveDialog;
ShowSaveChangesDialog = ShowDefaultSaveChangesDialog;
Expand Down Expand Up @@ -331,4 +337,29 @@ private static string FormatLoc (int line, int column)
{
return $"Ln: {line}, Ch: {column}";
}

/// <summary>
/// Creates a <see cref="FoldingManager" /> for the current document and wires up
/// automatic fold updates on document changes.
/// </summary>
private void InstallFolding ()
{
if (Editor.Document is null)
{
return;
}

FoldingManager fm = new (Editor.Document);
Editor.FoldingManager = fm;
_braceFoldingStrategy.UpdateFoldings (fm, Editor.Document);
Editor.Document.Changed += (_, _) => UpdateFoldings ();
}

private void UpdateFoldings ()
{
if (Editor.FoldingManager is not null && Editor.Document is not null)
{
_braceFoldingStrategy.UpdateFoldings (Editor.FoldingManager, Editor.Document);
}
}
}
39 changes: 38 additions & 1 deletion src/Terminal.Gui.Editor/Editor.Commands.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Terminal.Gui.Document;
using Terminal.Gui.Document.Folding;
using Terminal.Gui.Input;
using Terminal.Gui.ViewBase;

Expand All @@ -25,7 +26,8 @@ public partial class Editor
[Command.DeleteCharLeft] = Bind.All (Key.Backspace),
[Command.DeleteCharRight] = Bind.All (Key.Delete),
[Command.Undo] = Bind.All (Key.Z.WithCtrl),
[Command.Redo] = Bind.All (Key.Y.WithCtrl, Key.Z.WithCtrl.WithShift)
[Command.Redo] = Bind.All (Key.Y.WithCtrl, Key.Z.WithCtrl.WithShift),
[Command.Collapse] = Bind.All (Key.M.WithCtrl)
Comment thread
tig marked this conversation as resolved.
};

private void CreateCommandsAndBindings ()
Expand Down Expand Up @@ -112,6 +114,9 @@ private void CreateCommandsAndBindings ()
return true;
});

// Folding
AddCommand (Command.Collapse, ToggleFoldUnderCaret);

ApplyKeyBindings (View.DefaultKeyBindings, DefaultKeyBindings);

// Reclaim Tab before the framework consumes it; the editor handles Tab / Shift+Tab
Expand Down Expand Up @@ -253,4 +258,36 @@ private void CreateCommandsAndBindings ()

return true;
}

private bool? ToggleFoldUnderCaret ()
{
if (FoldingManager is not { } fm || _document is null)
{
return true;
}

var caretOffset = CaretOffset;
DocumentLine caretLine = _document.GetLineByOffset (caretOffset);

// First, try to find a fold starting on this line.
FoldingSection? fold = fm.GetFoldingAtLine (caretLine.LineNumber);

// If none, try folds containing the caret.
if (fold is null)
{
foreach (FoldingSection fs in fm.GetFoldingsContaining (caretOffset))
{
fold = fs;

break;
}
}

if (fold is not null)
{
fold.IsFolded = !fold.IsFolded;
Comment thread
tig marked this conversation as resolved.
}

return true;
}
}
77 changes: 73 additions & 4 deletions src/Terminal.Gui.Editor/Editor.Drawing.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Drawing;
using Terminal.Gui.Document;
using Terminal.Gui.Document.Folding;
using Terminal.Gui.Drawing;
using Terminal.Gui.Drivers;
using Terminal.Gui.ViewBase;
Expand All @@ -11,6 +12,9 @@ namespace Terminal.Gui.Views;

public partial class Editor
{
/// <summary>Cached visible-line mapping; cleared when folds change or the document changes.</summary>
private List<int>? _cachedVisibleLineNumbers;

private ISyntaxHighlighter? _highlighterPreparedInstance;

// Syntax highlighter state optimization: tracks how far we've prepared so incremental
Expand Down Expand Up @@ -84,18 +88,29 @@ private void DrawVisibleLines (Rectangle viewport, Attribute normal, Attribute s
var visibleStart = viewport.X;
var visibleEnd = viewport.X + viewport.Width;

PrepareSyntaxHighlighter (syntaxHighlighter, viewport.Y);
// Build a mapping from viewport row → document line number (1-based),
// skipping lines hidden by collapsed folds.
List<int> visibleLineNumbers = GetVisibleLineNumbers ();

// Prime the highlighter from the first visible *document* line, not the viewport row index,
// so that folded regions above the viewport don't leave the highlighter in stale state.
var firstVisibleIndex = viewport.Y;
var firstDocLine = firstVisibleIndex >= 0 && firstVisibleIndex < visibleLineNumbers.Count
? visibleLineNumbers[firstVisibleIndex] - 1
: viewport.Y;
PrepareSyntaxHighlighter (syntaxHighlighter, firstDocLine);

for (var row = 0; row < viewport.Height; row++)
{
var lineIndex = viewport.Y + row;
var visibleIndex = viewport.Y + row;

Comment thread
tig marked this conversation as resolved.
if (lineIndex < 0 || lineIndex >= _document!.LineCount)
if (visibleIndex < 0 || visibleIndex >= visibleLineNumbers.Count)
{
break;
}

DocumentLine line = _document.GetLineByNumber (lineIndex + 1);
var lineNumber = visibleLineNumbers[visibleIndex];
DocumentLine line = _document!.GetLineByNumber (lineNumber);
#pragma warning disable CS0618 // Type or member is obsolete — see PrepareSyntaxHighlighter.
IReadOnlyList<StyledSegment>? segments =
syntaxHighlighter?.Highlight (_document.GetText (line), SyntaxLanguage);
Expand All @@ -105,6 +120,60 @@ private void DrawVisibleLines (Rectangle viewport, Attribute normal, Attribute s
}
}

/// <summary>
/// Returns a list of 1-based document line numbers that are visible (not hidden by folds),
/// in order. Cached until folds change.
/// </summary>
internal List<int> GetVisibleLineNumbers ()
{
if (_cachedVisibleLineNumbers is not null)
{
return _cachedVisibleLineNumbers;
Comment thread
tig marked this conversation as resolved.
}

List<int> result = new ();

if (_document is null)
{
_cachedVisibleLineNumbers = result;

return result;
}

FoldingManager? fm = FoldingManager;
var lineNumber = 1;

while (lineNumber <= _document.LineCount)
{
result.Add (lineNumber);

if (fm is not null)
{
// If there's a folded section starting on this line, skip its hidden lines.
FoldingSection? fold = fm.GetFoldingAtLine (lineNumber);

if (fold is { IsFolded: true })
{
Comment thread
tig marked this conversation as resolved.
Outdated
DocumentLine endLine =
fm.Document.GetLineByOffset (Math.Clamp (fold.EndOffset, 0, fm.Document.TextLength));

if (endLine.LineNumber > lineNumber)
{
lineNumber = endLine.LineNumber + 1;

continue;
}
}
}

lineNumber++;
}

_cachedVisibleLineNumbers = result;

return result;
}

private void PrepareSyntaxHighlighter (ISyntaxHighlighter? syntaxHighlighter, int firstVisibleLineIndex)
{
if (syntaxHighlighter is null || _document is null)
Expand Down
17 changes: 15 additions & 2 deletions src/Terminal.Gui.Editor/Editor.Selection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ internal void SelectLineAtViewRow (int row)
return;
}

var lineNumber = Math.Clamp (Viewport.Y + row + 1, 1, _document.LineCount);
var lineNumber = ViewRowToLineNumber (row);
SelectLines (lineNumber, lineNumber);
}

Expand All @@ -268,7 +268,20 @@ internal int ViewRowToLineNumber (int row)
return 1;
}

return Math.Clamp (Viewport.Y + row + 1, 1, _document.LineCount);
List<int> visibleLines = GetVisibleLineNumbers ();
var visibleIndex = Viewport.Y + row;

if (visibleIndex < 0 || visibleLines.Count == 0)
{
return 1;
}

if (visibleIndex >= visibleLines.Count)
{
return visibleLines[^1];
}

return visibleLines[visibleIndex];
}

private bool IsIdentifierWordCharAt (int offset)
Expand Down
Loading
Loading