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
17 changes: 15 additions & 2 deletions Terminal.Gui/Views/FileDialogs/FileDialog.Navigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,21 @@ private IDirectoryInfo StringToDirectoryInfo (string path)

private static void SuppressIfBadChar (Key k)
{
// don't let user type bad letters
var ch = (char)k;
// Only suppress actual typed printable characters.
// Navigation keys (Home/End/etc.) must pass through to key bindings.
if (k.IsAlt || k.IsCtrl)
{
return;
}

string grapheme = k.AsGrapheme;

if (grapheme.Length != 1)
{
return;
}

char ch = grapheme [0];

if (_badChars.Contains (ch))
{
Expand Down
43 changes: 43 additions & 0 deletions Tests/UnitTestsParallelizable/Views/AppendAutocompleteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,49 @@ public void ProcessKey_CursorUp_CyclesSuggestions ()
Assert.Equal (initialIdx, ac.SelectedIdx);
}

[Fact]
public void ProcessKey_End_ReturnsFalse_WhenSuggestionExists ()
{
// Copilot - End should not be consumed by AppendAutocomplete.
// This lets TextField's normal End command move the cursor.
// Arrange: focused text field with "f" at end and suggestion "fish"
TextField tf = new () { Text = "f" };
tf.SetFocus ();
tf.MoveEnd ();

AppendAutocomplete ac = new (tf);
((SingleWordSuggestionGenerator)ac.SuggestionGenerator).AllSuggestions = ["fish"];

AutocompleteContext context = new ([new Cell (Grapheme: "f")], cursorPosition: 1);
ac.GenerateSuggestions (context);

Assert.NotEmpty (ac.Suggestions);

// Act
bool result = ac.ProcessKey (Key.End);

// Assert: End was not consumed by autocomplete
Assert.False (result);
Assert.Equal ("f", tf.Text);
Assert.NotEmpty (ac.Suggestions);
}

[Fact]
public void ProcessKey_End_ReturnsFalse_WhenNoSuggestions ()
{
// Copilot - End key should NOT consume the event when no suggestion is showing,
// allowing the normal MoveEnd command to run.
// Arrange: text field without suggestions
TextField tf = new () { Text = "f" };
AppendAutocomplete ac = new (tf);

// Act
bool result = ac.ProcessKey (Key.End);

// Assert: End was not consumed
Assert.False (result);
}

[Fact]
public void AcceptSelectionIfAny_AcceptsSuggestionWhenFocused ()
{
Expand Down
51 changes: 51 additions & 0 deletions Tests/UnitTestsParallelizable/Views/FileDialogResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,57 @@ public void OpenDialog_UsesInnerTableSeparatorsWithoutOuterBorders ()
Assert.False (tableView.Style.ShowVerticalCellLineForLastColumn);
}

[Fact]
public void FileDialog_PathField_End_MovesInsertionPointToEnd ()
{
// Copilot
MockFileSystem fs = new ();
fs.AddDirectory ("/testdir");
using FileDialog fd = new TestableFileDialog (fs);

FieldInfo? tbPathField = typeof (FileDialog).GetField ("_tbPath", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull (tbPathField);

TextField tbPath = Assert.IsType<TextField> (tbPathField!.GetValue (fd));
tbPath.Text = "/testdir/example.txt";

tbPath.NewKeyDownEvent (Key.Home);
Assert.Equal (0, tbPath.InsertionPoint);

tbPath.NewKeyDownEvent (Key.End);
Assert.Equal (tbPath.Text.Length, tbPath.InsertionPoint);
}

[Theory]
[InlineData ('"')]
[InlineData ('<')]
[InlineData ('>')]
[InlineData ('|')]
[InlineData ('*')]
[InlineData ('?')]
public void FileDialog_PathField_BadChars_AreSuppressed (char badChar)
{
// Copilot
MockFileSystem fs = new ();
fs.AddDirectory ("/testdir");
using FileDialog fd = new TestableFileDialog (fs);

FieldInfo? tbPathField = typeof (FileDialog).GetField ("_tbPath", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull (tbPathField);

TextField tbPath = Assert.IsType<TextField> (tbPathField!.GetValue (fd));
tbPath.Text = "/testdir/";
tbPath.MoveEnd ();

int insertionPointBefore = tbPath.InsertionPoint;
string textBefore = tbPath.Text;

tbPath.NewKeyDownEvent (new Key (badChar));

Assert.Equal (textBefore, tbPath.Text);
Assert.Equal (insertionPointBefore, tbPath.InsertionPoint);
}

/// <summary>Testable subclass that exposes the internal file-system constructor.</summary>
private sealed class TestableFileDialog : FileDialog
{
Expand Down
Loading