Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 16 additions & 2 deletions Terminal.Gui/Views/Markdown/IntermediateBlock.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
namespace Terminal.Gui.Views;

internal sealed class IntermediateBlock (IReadOnlyList<InlineRun> runs, bool wrap, string prefix = "", string continuationPrefix = "", bool isCodeBlock = false, string? anchor = null, bool isThematicBreak = false, TableData? tableData = null)
internal sealed class IntermediateBlock (IReadOnlyList<InlineRun> runs,
bool wrap,
string prefix = "",
string continuationPrefix = "",
bool isCodeBlock = false,
string? anchor = null,
bool isThematicBreak = false,
TableData? tableData = null,
string? language = null)
{
public IReadOnlyList<InlineRun> Runs { get; } = runs;
public bool Wrap { get; } = wrap;
Expand All @@ -13,8 +21,14 @@ internal sealed class IntermediateBlock (IReadOnlyList<InlineRun> runs, bool wra
public TableData? TableData { get; } = tableData;

/// <summary>Gets whether this block represents a Markdown table.</summary>
public bool IsTable => TableData is not null;
public bool IsTable => TableData is { };

/// <summary>The GitHub-style anchor slug for heading blocks, or <see langword="null"/> for non-heading blocks.</summary>
public string? Anchor { get; } = anchor;

/// <summary>
/// The fenced code block language specifier (e.g. <c>"cs"</c>, <c>"python"</c>), or
/// <see langword="null"/> when this is not a code block or no language was given.
/// </summary>
public string? Language { get; } = language;
}
38 changes: 38 additions & 0 deletions Terminal.Gui/Views/Markdown/Markdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,43 @@ namespace Terminal.Gui.Views;
/// Hyperlinks raise the <see cref="LinkClicked"/> event. Anchor links (URLs beginning with
/// <c>#</c>) are handled automatically by scrolling to the matching heading.
/// </para>
/// <para>Default key bindings:</para>
/// <list type="table">
/// <listheader>
/// <term>Key</term> <description>Action</description>
/// </listheader>
/// <item>
/// <term>Ctrl+A</term>
/// <description>Selects all rendered content (<see cref="Command.SelectAll"/>).</description>
/// </item>
/// <item>
/// <term>Ctrl+C</term>
/// <description>
/// Copies the current selection to the clipboard, or the entire markdown source if nothing is selected
/// (<see cref="Command.Copy"/>).
/// </description>
/// </item>
/// <item>
/// <term>Shift+F10 / Right-click</term>
/// <description>Opens a context menu with <b>Select All</b> and <b>Copy</b> items.</description>
/// </item>
/// </list>
/// <para>Default mouse bindings:</para>
/// <list type="table">
/// <listheader>
/// <term>Mouse Event</term> <description>Action</description>
/// </listheader>
/// <item>
/// <term>Left-button drag</term> <description>Selects text by dragging the mouse.</description>
/// </item>
/// <item>
/// <term>Left-button click</term>
/// <description>Clears the selection and activates a hyperlink if one is under the cursor.</description>
/// </item>
/// <item>
/// <term>Right-button click</term> <description>Opens the context menu.</description>
/// </item>
/// </list>
/// </remarks>
public partial class Markdown : View, IDesignable
{
Expand Down Expand Up @@ -249,6 +286,7 @@ private void InvalidateParsedAndLayout ()
RemoveTableViews ();
RemoveThematicBreakViews ();
_maxLineWidth = 0;
_isSelecting = false;

SetNeedsLayout ();
SetNeedsDraw ();
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Views/Markdown/MarkdownCodeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ protected override bool OnMouseEvent (Mouse mouse)

int copyGlyphX = Viewport.Width - 2;

if (pos.X != copyGlyphX && pos.X != copyGlyphX + 1 || pos.Y != 0)
if ((pos.X != copyGlyphX && pos.X != copyGlyphX + 1) || pos.Y != 0)
{
return false;
}
Expand Down
105 changes: 105 additions & 0 deletions Terminal.Gui/Views/Markdown/MarkdownView.Drawing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,107 @@ protected override bool OnDrawingContent (DrawContext? context)
// All visible content was drawn in OnDrawingSubViews; just register the drawn region.
context?.AddDrawnRegion (new Region (new Rectangle (ContentToScreen (Point.Empty), Viewport.Size)));

// OnDrawingContent is called AFTER SubViews have drawn. Plain rendered lines are
// highlighted during DrawRenderedLine (in OnDrawingSubViews), but table and code-block
// rows are owned by their SubViews and don't receive that pass. Draw the selection
// overlay for those rows here, on top of what the SubViews rendered.
if (_isSelecting)
{
DrawSelectionOverlayOnSubViewRows ();
}

return true;
}

/// <summary>
/// Draws the selection highlight over table and fenced-code-block rows.
/// Those rows are owned by SubViews (<see cref="MarkdownTable"/> /
/// <see cref="MarkdownCodeBlock"/>) that draw after
/// <see cref="OnDrawingSubViews"/> returns. This pass reads the graphemes
/// that the SubViews already placed in the screen buffer and re-draws them
/// with the selection attribute, preserving the rendered characters while
/// applying the selection background.
/// </summary>
private void DrawSelectionOverlayOnSubViewRows ()
{
Cell [,]? contents = ScreenContents;

if (contents is null)
{
return;
}

Attribute selAttr = GetAttributeForRole (VisualRole.Focus);
(Point start, Point end) = GetNormalizedSelection ();

int startRow = Math.Max (start.Y, Viewport.Y);
int endRow = Math.Min (end.Y, Viewport.Y + Viewport.Height - 1);

bool anySubViewRows = false;

for (int lineIdx = startRow; lineIdx <= Math.Min (endRow, _renderedLines.Count - 1); lineIdx++)
{
if (_renderedLines [lineIdx].IsTable || _renderedLines [lineIdx].IsCodeBlock)
{
anySubViewRows = true;

break;
}
}

if (!anySubViewRows)
{
return;
}

// After DoDrawSubViews each SubView calls DoDrawComplete which excludes its screen
// area from Driver.Clip. DoDrawContent (OnDrawingContent) runs with those exclusions
// still active, so drawing would silently no-op on SubView areas.
// Reset the clip to the raw viewport rectangle to allow the overlay to appear.
Region? savedClip = GetClip ();
Rectangle viewportScreen = ViewportToScreen (new Rectangle (Point.Empty, Viewport.Size));
SetClip (new Region (viewportScreen));

SetAttribute (selAttr);

for (int lineIdx = startRow; lineIdx <= Math.Min (endRow, _renderedLines.Count - 1); lineIdx++)
{
RenderedLine line = _renderedLines [lineIdx];

if (!line.IsTable && !line.IsCodeBlock)
{
continue;
}

int drawRow = lineIdx - Viewport.Y;
Point screenOrigin = ContentToScreen (new Point (0, drawRow));
int screenRow = screenOrigin.Y;
int screenStartCol = screenOrigin.X;
int cols = Viewport.Width;

for (int col = 0; col < cols; col++)
{
int sc = screenStartCol + col;

if (screenRow < 0 || screenRow >= contents.GetLength (0) || sc < 0 || sc >= contents.GetLength (1))
{
continue;
}

string grapheme = contents [screenRow, sc].Grapheme;

if (string.IsNullOrEmpty (grapheme))
{
grapheme = " ";
}

AddStr (col, drawRow, grapheme);
}
}

SetClip (savedClip);
}

private void DrawRenderedLine (RenderedLine line, int contentRow, int drawRow)
{
// Thematic breaks are drawn by Line SubViews
Expand Down Expand Up @@ -112,6 +210,13 @@ private void DrawRenderedLine (RenderedLine line, int contentRow, int drawRow)
AddStr (drawCol, drawRow, grapheme);
}
}
else if (IsInSelection (contentRow, contentX))
{
// Use the scheme's Focus attribute for selection highlight — it provides
// reliable contrast regardless of per-segment colours.
SetAttribute (GetAttributeForRole (VisualRole.Focus));
AddStr (drawCol, drawRow, grapheme);
Comment thread
tig marked this conversation as resolved.
}
else
{
DrawGrapheme (segment, grapheme, drawCol, drawRow);
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Views/Markdown/MarkdownView.Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private void BuildRenderedLines ()
// Reserve placeholder lines so content height is correct
for (var i = 0; i < tableHeight; i++)
{
_renderedLines.Add (new RenderedLine ([new StyledSegment ("", MarkdownStyleRole.Table)], false, 0, isTable: true));
_renderedLines.Add (new RenderedLine ([new StyledSegment ("", MarkdownStyleRole.Table)], false, 0, isTable: true, tableData: tableData));
}

continue;
Expand Down Expand Up @@ -188,7 +188,7 @@ private static RenderedLine CreateUnwrappedLine (IntermediateBlock block)

int width = CalculateWidth (segments);

return new RenderedLine (segments, false, width, block.IsCodeBlock, block.IsThematicBreak);
return new RenderedLine (segments, false, width, block.IsCodeBlock, block.IsThematicBreak, codeLanguage: block.Language);
}

private static List<RenderedLine> WrapBlock (IntermediateBlock block, int viewportWidth)
Expand Down
Loading
Loading