From fdc6d813480c946e0be355cfded0f66f156023d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 09:51:49 +0000 Subject: [PATCH 1/5] Initial plan From c2c7490ddaa2f661b79d4937d2767e9ea8892614 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:01:56 +0000 Subject: [PATCH 2/5] Make IConsoleDriver public and add ConsoleDriverFactory for extensibility Co-authored-by: shibayan <1356444+shibayan@users.noreply.github.com> --- Sharprompt/Drivers/DefaultConsoleDriver.cs | 2 +- Sharprompt/Drivers/IConsoleDriver.cs | 2 +- Sharprompt/Forms/FormBase.cs | 7 +++---- Sharprompt/Prompt.Configuration.cs | 9 +++++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Sharprompt/Drivers/DefaultConsoleDriver.cs b/Sharprompt/Drivers/DefaultConsoleDriver.cs index 3da5577d..3aeee009 100644 --- a/Sharprompt/Drivers/DefaultConsoleDriver.cs +++ b/Sharprompt/Drivers/DefaultConsoleDriver.cs @@ -7,7 +7,7 @@ namespace Sharprompt.Drivers; -internal sealed class DefaultConsoleDriver : IConsoleDriver +public class DefaultConsoleDriver : IConsoleDriver { static DefaultConsoleDriver() { diff --git a/Sharprompt/Drivers/IConsoleDriver.cs b/Sharprompt/Drivers/IConsoleDriver.cs index 695a192f..dcf85d6e 100644 --- a/Sharprompt/Drivers/IConsoleDriver.cs +++ b/Sharprompt/Drivers/IConsoleDriver.cs @@ -2,7 +2,7 @@ namespace Sharprompt.Drivers; -internal interface IConsoleDriver : IDisposable +public interface IConsoleDriver : IDisposable { void Beep(); void Reset(); diff --git a/Sharprompt/Forms/FormBase.cs b/Sharprompt/Forms/FormBase.cs index 16d9a58d..b47f2631 100644 --- a/Sharprompt/Forms/FormBase.cs +++ b/Sharprompt/Forms/FormBase.cs @@ -13,10 +13,9 @@ internal abstract class FormBase : IDisposable { protected FormBase() { - _consoleDriver = new DefaultConsoleDriver - { - CancellationCallback = CancellationHandler - }; + _consoleDriver = Prompt.ConsoleDriverFactory() ?? throw new InvalidOperationException("ConsoleDriverFactory must return a non-null IConsoleDriver instance."); + + _consoleDriver.CancellationCallback = CancellationHandler; _formRenderer = new FormRenderer(_consoleDriver); } diff --git a/Sharprompt/Prompt.Configuration.cs b/Sharprompt/Prompt.Configuration.cs index 07e2aa13..29d22f33 100644 --- a/Sharprompt/Prompt.Configuration.cs +++ b/Sharprompt/Prompt.Configuration.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; +using Sharprompt.Drivers; using Sharprompt.Strings; namespace Sharprompt; @@ -9,6 +10,14 @@ public static partial class Prompt { public static bool ThrowExceptionOnCancel { get; set; } = false; + private static Func s_consoleDriverFactory = () => new DefaultConsoleDriver(); + + public static Func ConsoleDriverFactory + { + get => s_consoleDriverFactory; + set => s_consoleDriverFactory = value ?? throw new ArgumentNullException(nameof(value)); + } + public static CultureInfo Culture { get => Resource.Culture; From 975d9f16c331708b93cb63081593e97541115b7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:08:34 +0000 Subject: [PATCH 3/5] Add test cases for ConsoleDriverFactory and IConsoleDriver extensibility Co-authored-by: shibayan <1356444+shibayan@users.noreply.github.com> --- Sharprompt.Tests/ConsoleDriverFactoryTests.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Sharprompt.Tests/ConsoleDriverFactoryTests.cs diff --git a/Sharprompt.Tests/ConsoleDriverFactoryTests.cs b/Sharprompt.Tests/ConsoleDriverFactoryTests.cs new file mode 100644 index 00000000..c3934157 --- /dev/null +++ b/Sharprompt.Tests/ConsoleDriverFactoryTests.cs @@ -0,0 +1,103 @@ +using System; + +using Sharprompt.Drivers; + +using Xunit; + +namespace Sharprompt.Tests; + +public class ConsoleDriverFactoryTests +{ + [Fact] + public void ConsoleDriverFactory_DefaultFactory_IsNotNull() + { + Assert.NotNull(Prompt.ConsoleDriverFactory); + } + + [Fact] + public void ConsoleDriverFactory_SetCustomFactory_ReturnsCustomFactory() + { + var originalFactory = Prompt.ConsoleDriverFactory; + + try + { + var customDriver = new StubConsoleDriver(); + Prompt.ConsoleDriverFactory = () => customDriver; + + var driver = Prompt.ConsoleDriverFactory(); + + Assert.Same(customDriver, driver); + } + finally + { + Prompt.ConsoleDriverFactory = originalFactory; + } + } + + [Fact] + public void ConsoleDriverFactory_SetNull_ThrowsArgumentNullException() + { + Assert.Throws(() => Prompt.ConsoleDriverFactory = null!); + } + + [Fact] + public void ConsoleDriverFactory_AfterReset_ReturnsPreviousFactory() + { + var originalFactory = Prompt.ConsoleDriverFactory; + + try + { + Prompt.ConsoleDriverFactory = () => new StubConsoleDriver(); + + Prompt.ConsoleDriverFactory = originalFactory; + + Assert.Same(originalFactory, Prompt.ConsoleDriverFactory); + } + finally + { + Prompt.ConsoleDriverFactory = originalFactory; + } + } + + [Fact] + public void IConsoleDriver_CustomImplementation_CanBeAssigned() + { + var originalFactory = Prompt.ConsoleDriverFactory; + + try + { + IConsoleDriver driver = new StubConsoleDriver(); + Prompt.ConsoleDriverFactory = () => driver; + + var result = Prompt.ConsoleDriverFactory(); + + Assert.IsAssignableFrom(result); + Assert.Same(driver, result); + } + finally + { + Prompt.ConsoleDriverFactory = originalFactory; + } + } + + private sealed class StubConsoleDriver : IConsoleDriver + { + public void Dispose() { } + public void Beep() { } + public void Reset() { } + public void ClearLine(int top) { } + public ConsoleKeyInfo ReadKey() => new('\0', ConsoleKey.Enter, false, false, false); + public void Write(string value, ConsoleColor color) { } + public void WriteLine() { } + public void SetCursorPosition(int left, int top) { } + public bool KeyAvailable => false; + public bool CursorVisible { set { } } + public int CursorLeft => 0; + public int CursorTop => 0; + public int BufferWidth => 80; + public int BufferHeight => 24; + public int WindowWidth => 80; + public int WindowHeight => 24; + public Action CancellationCallback { get; set; } = () => { }; + } +} From 93afb8c8511c11b56a64913a684b57553841a6fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:17:49 +0000 Subject: [PATCH 4/5] Add IConsoleDriver test cases covering all interface members Co-authored-by: shibayan <1356444+shibayan@users.noreply.github.com> --- Sharprompt.Tests/IConsoleDriverTests.cs | 226 ++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 Sharprompt.Tests/IConsoleDriverTests.cs diff --git a/Sharprompt.Tests/IConsoleDriverTests.cs b/Sharprompt.Tests/IConsoleDriverTests.cs new file mode 100644 index 00000000..96824e20 --- /dev/null +++ b/Sharprompt.Tests/IConsoleDriverTests.cs @@ -0,0 +1,226 @@ +using System; + +using Sharprompt.Drivers; + +using Xunit; + +namespace Sharprompt.Tests; + +public class IConsoleDriverTests +{ + [Fact] + public void Beep_CanBeCalled() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).Beep(); + + Assert.True(driver.BeepCalled); + } + + [Fact] + public void Reset_CanBeCalled() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).Reset(); + + Assert.True(driver.ResetCalled); + } + + [Fact] + public void ClearLine_PassesCorrectArgument() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).ClearLine(5); + + Assert.Equal(5, driver.ClearLineTop); + } + + [Fact] + public void ReadKey_ReturnsConfiguredKeyInfo() + { + var expected = new ConsoleKeyInfo('A', ConsoleKey.A, false, false, false); + var driver = new RecordingConsoleDriver { ReadKeyResult = expected }; + + var result = ((IConsoleDriver)driver).ReadKey(); + + Assert.Equal(expected, result); + } + + [Fact] + public void Write_PassesCorrectArguments() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).Write("hello", ConsoleColor.Cyan); + + Assert.Equal("hello", driver.LastWriteValue); + Assert.Equal(ConsoleColor.Cyan, driver.LastWriteColor); + } + + [Fact] + public void WriteLine_CanBeCalled() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).WriteLine(); + + Assert.True(driver.WriteLineCalled); + } + + [Fact] + public void SetCursorPosition_PassesCorrectCoordinates() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).SetCursorPosition(10, 20); + + Assert.Equal(10, driver.SetCursorLeft); + Assert.Equal(20, driver.SetCursorTop); + } + + [Fact] + public void KeyAvailable_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { KeyAvailableValue = true }; + + var result = ((IConsoleDriver)driver).KeyAvailable; + + Assert.True(result); + } + + [Fact] + public void CursorVisible_CanBeSet() + { + var driver = new RecordingConsoleDriver(); + + ((IConsoleDriver)driver).CursorVisible = false; + + Assert.False(driver.CursorVisibleValue); + } + + [Fact] + public void CursorLeft_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { CursorLeftValue = 42 }; + + var result = ((IConsoleDriver)driver).CursorLeft; + + Assert.Equal(42, result); + } + + [Fact] + public void CursorTop_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { CursorTopValue = 7 }; + + var result = ((IConsoleDriver)driver).CursorTop; + + Assert.Equal(7, result); + } + + [Fact] + public void BufferWidth_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { BufferWidthValue = 120 }; + + var result = ((IConsoleDriver)driver).BufferWidth; + + Assert.Equal(120, result); + } + + [Fact] + public void BufferHeight_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { BufferHeightValue = 50 }; + + var result = ((IConsoleDriver)driver).BufferHeight; + + Assert.Equal(50, result); + } + + [Fact] + public void WindowWidth_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { WindowWidthValue = 200 }; + + var result = ((IConsoleDriver)driver).WindowWidth; + + Assert.Equal(200, result); + } + + [Fact] + public void WindowHeight_ReturnsConfiguredValue() + { + var driver = new RecordingConsoleDriver { WindowHeightValue = 60 }; + + var result = ((IConsoleDriver)driver).WindowHeight; + + Assert.Equal(60, result); + } + + [Fact] + public void CancellationCallback_CanBeSetAndInvoked() + { + var driver = new RecordingConsoleDriver(); + var invoked = false; + Action callback = () => invoked = true; + + ((IConsoleDriver)driver).CancellationCallback = callback; + ((IConsoleDriver)driver).CancellationCallback.Invoke(); + + Assert.True(invoked); + } + + [Fact] + public void Dispose_CanBeCalled() + { + var driver = new RecordingConsoleDriver(); + + ((IDisposable)driver).Dispose(); + + Assert.True(driver.DisposeCalled); + } + + private sealed class RecordingConsoleDriver : IConsoleDriver + { + public bool BeepCalled { get; private set; } + public bool ResetCalled { get; private set; } + public int ClearLineTop { get; private set; } = -1; + public ConsoleKeyInfo ReadKeyResult { get; set; } = new('\0', ConsoleKey.Enter, false, false, false); + public string? LastWriteValue { get; private set; } + public ConsoleColor LastWriteColor { get; private set; } + public bool WriteLineCalled { get; private set; } + public int SetCursorLeft { get; private set; } = -1; + public int SetCursorTop { get; private set; } = -1; + public bool KeyAvailableValue { get; set; } + public bool? CursorVisibleValue { get; private set; } + public int CursorLeftValue { get; set; } + public int CursorTopValue { get; set; } + public int BufferWidthValue { get; set; } = 80; + public int BufferHeightValue { get; set; } = 24; + public int WindowWidthValue { get; set; } = 80; + public int WindowHeightValue { get; set; } = 24; + public bool DisposeCalled { get; private set; } + + public void Dispose() => DisposeCalled = true; + public void Beep() => BeepCalled = true; + public void Reset() => ResetCalled = true; + public void ClearLine(int top) => ClearLineTop = top; + public ConsoleKeyInfo ReadKey() => ReadKeyResult; + public void Write(string value, ConsoleColor color) { LastWriteValue = value; LastWriteColor = color; } + public void WriteLine() => WriteLineCalled = true; + public void SetCursorPosition(int left, int top) { SetCursorLeft = left; SetCursorTop = top; } + public bool KeyAvailable => KeyAvailableValue; + public bool CursorVisible { set => CursorVisibleValue = value; } + public int CursorLeft => CursorLeftValue; + public int CursorTop => CursorTopValue; + public int BufferWidth => BufferWidthValue; + public int BufferHeight => BufferHeightValue; + public int WindowWidth => WindowWidthValue; + public int WindowHeight => WindowHeightValue; + public Action CancellationCallback { get; set; } = () => { }; + } +} From 903e8b6432c26fa21c8fae5277c929d490db0cb1 Mon Sep 17 00:00:00 2001 From: Tatsuro Shibamura Date: Thu, 5 Mar 2026 11:34:48 +0900 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Sharprompt/Drivers/DefaultConsoleDriver.cs | 2 +- Sharprompt/Prompt.Configuration.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sharprompt/Drivers/DefaultConsoleDriver.cs b/Sharprompt/Drivers/DefaultConsoleDriver.cs index 3aeee009..ca50869b 100644 --- a/Sharprompt/Drivers/DefaultConsoleDriver.cs +++ b/Sharprompt/Drivers/DefaultConsoleDriver.cs @@ -7,7 +7,7 @@ namespace Sharprompt.Drivers; -public class DefaultConsoleDriver : IConsoleDriver +public sealed class DefaultConsoleDriver : IConsoleDriver { static DefaultConsoleDriver() { diff --git a/Sharprompt/Prompt.Configuration.cs b/Sharprompt/Prompt.Configuration.cs index 29d22f33..d8734940 100644 --- a/Sharprompt/Prompt.Configuration.cs +++ b/Sharprompt/Prompt.Configuration.cs @@ -15,7 +15,11 @@ public static partial class Prompt public static Func ConsoleDriverFactory { get => s_consoleDriverFactory; - set => s_consoleDriverFactory = value ?? throw new ArgumentNullException(nameof(value)); + set + { + ArgumentNullException.ThrowIfNull(value); + s_consoleDriverFactory = value; + } } public static CultureInfo Culture