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
5 changes: 4 additions & 1 deletion Terminal.Gui/FileServices/FileSystemTreeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ internal static bool IsReparsePoint (IFileSystemInfo entry)
}
catch (Exception)
{
return false; // treat an unreadable entry as not a reparse point / not expandable
// Fail closed: if the reparse status cannot be read, treat the entry as a reparse point so
// callers do not traverse it — an unreadable symlink/junction could otherwise recreate the
// directory-cycle risk this guard exists to prevent.
return true;
}
}
}
4 changes: 3 additions & 1 deletion Terminal.Gui/Views/CharMap/CharMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ public CharMap ()
KeyBindings.Add (Key.PageDown, Command.PageDown);
KeyBindings.Add (Key.Home, Command.Start);
KeyBindings.Add (Key.End, Command.End);
KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context);
// ReplaceCommands, not Add: PopoverMenu.DefaultKey is configurable and may collide with a key
// that is already bound (e.g. Ctrl+P); the user's context-menu key wins.
KeyBindings.ReplaceCommands (PopoverMenu.DefaultKey, Command.Context);

MouseBindings.ReplaceCommands (MouseFlags.LeftButtonClicked, Command.Activate);
MouseBindings.Add (MouseFlags.LeftButtonDoubleClicked, Command.Accept);
Expand Down
5 changes: 4 additions & 1 deletion Terminal.Gui/Views/FileDialogs/FileDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ internal FileDialog (IFileSystem? fileSystem)
// by default, Runnable doesn't bind to Command.Context, so
// we can take advantage of the CommandNotBound event to handle it
_tableView.CommandNotBound += TableViewHandleCommandNotBound;
_tableView.KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context);

// ReplaceCommands, not Add: PopoverMenu.DefaultKey is configurable and may collide with a key
// TableView already binds (e.g. Ctrl+P -> Command.Up); the user's context-menu key wins.
_tableView.KeyBindings.ReplaceCommands (PopoverMenu.DefaultKey, Command.Context);
_tableView.MouseBindings.Add (MouseFlags.RightButtonClicked, Command.Context);

_tbPath.TextChanged += (_, _) => PathChanged ();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Claude - Opus 4.8

using System.IO.Abstractions.TestingHelpers;
using Terminal.Gui.Configuration;

namespace ConfigurationTests;

/// <summary>
/// Verifies that views binding <see cref="PopoverMenu.DefaultKey"/> to <see cref="Command.Context"/>
/// do not throw when the configured key collides with a key the view already binds
/// (e.g. Ctrl+P, which <see cref="TableView"/> binds to <see cref="Command.Up"/> by default).
/// </summary>
[Collection ("StaticSettingsTests")]
public class PopoverMenuDefaultKeyCollisionTests
{
[Fact]
public void FileDialog_Ctor_DoesNotThrow_WhenDefaultKeyCollidesWithTableViewBinding ()
{
PopoverMenuSettings original = PopoverMenuSettings.Defaults;

try
{
// TableView binds Ctrl+P -> Command.Up by default; the context-menu key must win without throwing.
PopoverMenuSettings.Defaults = new () { DefaultKey = Key.P.WithCtrl };

MockFileSystem fs = new ();
fs.AddDirectory ("/testdir");

using FileDialog fd = new (fs);

Assert.NotNull (fd);
}
finally
{
PopoverMenuSettings.Defaults = original;
}
}

[Fact]
public void CharMap_Ctor_DoesNotThrow_WhenDefaultKeyCollidesWithExistingBinding ()
{
PopoverMenuSettings original = PopoverMenuSettings.Defaults;

try
{
// CharMap binds Key.End -> Command.End before binding the context-menu key.
PopoverMenuSettings.Defaults = new () { DefaultKey = Key.End };

using CharMap charMap = new ();

Assert.NotNull (charMap);
}
finally
{
PopoverMenuSettings.Defaults = original;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace ConfigurationTests;

[CollectionDefinition ("StaticSettingsTests", DisableParallelization = true)]
public class StaticSettingsTestCollection
{
// Marker collection for tests that mutate process-wide static settings facades
// (e.g. DriverSettings.Defaults, PopoverMenuSettings.Defaults). Without
// DisableParallelization, these tests race views constructed in parallel tests —
// e.g. temporarily making PopoverMenu.DefaultKey read Ctrl+P while a FileDialog
// is being constructed elsewhere.
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,16 @@ public void GetChildren_DirectoryWithUnreadableAttributes_DoesNotThrowAndReturns

Assert.Empty (children);
}

// Claude - Opus 4.8
[Fact]
public void IsReparsePoint_WhenAttributesUnreadable_FailsClosed ()
{
Mock<IDirectoryInfo> directory = new ();
directory.SetupGet (d => d.Attributes).Throws (new UnauthorizedAccessException ("Access denied"));

// Fail closed: an entry whose reparse status cannot be read must be treated as a reparse
// point so callers (CanExpand, GetChildren, FileDialog search) do not traverse it.
Assert.True (FileSystemTreeBuilder.IsReparsePoint (directory.Object));
}
}
Loading