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
71 changes: 71 additions & 0 deletions Terminal.Gui/Views/Markdown/Markdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,58 @@ public Markdown ()
SetupBindingsAndCommands ();
}

/// <inheritdoc/>
/// <remarks>
/// If <see cref="View.CanFocus"/> is <see langword="false"/> and a valid <see cref="View.HotKey"/>
/// is set, the hotkey is forwarded to the next peer in <see cref="View.SuperView"/>'s
/// <see cref="View.SubViews"/> — mirroring <see cref="Label"/> so that a non-focusable
/// <see cref="Markdown"/> describing a focusable view (e.g. a <see cref="TextField"/>) moves
/// focus to that view when its hotkey is pressed.
/// </remarks>
protected override bool OnActivating (CommandEventArgs args)
{
// If Markdown can't focus, forward HotKey to the next peer in the SubView list
if (CanFocus || !HotKey.IsValid)
{
return base.OnActivating (args);
}
int me = SuperView?.SubViews.IndexOf (this) ?? -1;

if (me == -1 || !(me < SuperView?.SubViews.Count - 1))
{
return base.OnActivating (args);
}
bool handled = SuperView?.SubViews.ElementAt (me + 1).InvokeCommand (Command.HotKey) == true;

if (!handled)
{
return base.OnActivating (args);
}
args.Handled = true;

return true;
}

/// <summary>Gets or sets the Markdown-formatted text displayed by this view.</summary>
/// <value>The raw Markdown string. Setting this property triggers reparsing, re-layout, and a redraw.</value>
public override string Text { get => _markdown; set => SetMarkdown (value); }

/// <inheritdoc/>
/// <remarks>
/// Unlike <see cref="Label"/>, <see cref="Markdown"/> derives <see cref="View.HotKey"/>
/// from <see cref="Text"/> (the raw markdown) rather than <see cref="View.Title"/>,
/// because <see cref="Text"/> does not flow through <c>Title</c>.
/// </remarks>
public override Rune HotKeySpecifier
{
get => base.HotKeySpecifier;
set
{
TitleTextFormatter.HotKeySpecifier = TextFormatter.HotKeySpecifier = value;
UpdateHotKeyFromMarkdown ();
}
}

/// <summary>Gets or sets the Markdig <see cref="Markdig.MarkdownPipeline"/> used for parsing.</summary>
/// <value>
/// A custom pipeline, or <see langword="null"/> to use the default pipeline
Expand Down Expand Up @@ -266,13 +314,36 @@ private void SetMarkdown (string value)
}

_markdown = value;
UpdateHotKeyFromMarkdown ();
_scrollToTopPending = true;
InvalidateParsedAndLayout ();

OnMarkdownChanged ();
MarkdownChanged?.Invoke (this, EventArgs.Empty);
}

private void UpdateHotKeyFromMarkdown ()
{
if (HotKeySpecifier == new Rune ('\xFFFF'))
{
HotKey = Key.Empty;

return;
}

if (TextFormatter.FindHotKey (_markdown, HotKeySpecifier, out _, out Key hotKey))
{
if (HotKey != hotKey)
{
HotKey = hotKey;
}

return;
}

HotKey = Key.Empty;
}

private void InvalidateParsedAndLayout ()
{
_parsed = false;
Expand Down
142 changes: 141 additions & 1 deletion Tests/UnitTestsParallelizable/Views/Markdown/MarkdownViewTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
using UnitTests;

Expand Down Expand Up @@ -1903,5 +1904,144 @@ public void GetContentHeight_Table_With_Wide_Code_Block_Does_Not_Overestimate ()
hostCode.Dispose ();
hostNoCode.Dispose ();
}
}

// Copilot
[Fact]
public void CanFocus_False_Text_HotKeySpecifier_SetsFocus_Next ()
{
using IApplication app = Application.Create ();
Runnable<bool> runnable = new ();
View otherView = new () { CanFocus = true };
Terminal.Gui.Views.Markdown markdown = new ()
{
CanFocus = false,
Width = 20,
Height = 1,
Text = "_Markdown"
};
View nextView = new () { CanFocus = true };

markdown.HotKeySpecifier = (Rune)'_';

app.Begin (runnable);
runnable.Add (otherView, markdown, nextView);
otherView.SetFocus ();

Assert.Equal (Key.M, markdown.HotKey);
Assert.True (otherView.HasFocus);
Assert.False (markdown.HasFocus);
Assert.False (nextView.HasFocus);

app.Keyboard.RaiseKeyDownEvent (markdown.HotKey);

Assert.False (otherView.HasFocus);
Assert.False (markdown.HasFocus);
Assert.True (nextView.HasFocus);
}

// Copilot
[Fact]
public void CanFocus_False_LeftButtonClicked_SetsFocus_Next ()
{
using IApplication app = Application.Create ();
Runnable<bool> runnable = new ();
View otherView = new ()
{
X = 0,
Y = 0,
Width = 1,
Height = 1,
CanFocus = true
};
Terminal.Gui.Views.Markdown markdown = new ()
{
X = 0,
Y = 1,
Width = 20,
Height = 1,
CanFocus = false,
Text = "_Markdown"
};
View nextView = new ()
{
X = Pos.Right (markdown),
Y = Pos.Top (markdown),
Width = 1,
Height = 1,
CanFocus = true
};

markdown.HotKeySpecifier = (Rune)'_';

app.Begin (runnable);
runnable.Add (otherView, markdown, nextView);
otherView.SetFocus ();

Assert.Equal (Key.M, markdown.HotKey);
Assert.True (otherView.HasFocus);
Assert.False (markdown.HasFocus);
Assert.False (nextView.HasFocus);

app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = markdown.Frame.Location, Flags = MouseFlags.LeftButtonClicked });

Assert.False (markdown.HasFocus);
Assert.True (nextView.HasFocus);
}

// Copilot
[Fact]
public void CanFocus_True_Text_HotKeySpecifier_SetsFocus_OnMarkdown ()
{
using IApplication app = Application.Create ();
Runnable<bool> runnable = new ();
View otherView = new () { CanFocus = true };
Terminal.Gui.Views.Markdown markdown = new ()
{
CanFocus = true,
Width = 20,
Height = 1,
Text = "_Markdown"
};
View nextView = new () { CanFocus = true };

markdown.HotKeySpecifier = (Rune)'_';

app.Begin (runnable);
runnable.Add (otherView, markdown, nextView);
otherView.SetFocus ();

Assert.Equal (Key.M, markdown.HotKey);

app.Keyboard.RaiseKeyDownEvent (markdown.HotKey);

Assert.False (otherView.HasFocus);
Assert.True (markdown.HasFocus);
Assert.False (nextView.HasFocus);
}

// Copilot
[Fact]
public void CanFocus_False_Text_HotKeySpecifier_NoNextPeer_DoesNotFocusMarkdown ()
{
using IApplication app = Application.Create ();
Runnable<bool> runnable = new ();
Terminal.Gui.Views.Markdown markdown = new ()
{
CanFocus = false,
Width = 20,
Height = 1,
Text = "_Markdown"
};

markdown.HotKeySpecifier = (Rune)'_';

app.Begin (runnable);
runnable.Add (markdown);

Assert.Equal (Key.M, markdown.HotKey);

app.Keyboard.RaiseKeyDownEvent (markdown.HotKey);

Assert.False (markdown.HasFocus);
}
}
Loading