diff --git a/src/PatienceOS.Kernel.Tests/ConsoleTests.cs b/src/PatienceOS.Kernel.Tests/ConsoleTests.cs index a5f4ca5..5489012 100644 --- a/src/PatienceOS.Kernel.Tests/ConsoleTests.cs +++ b/src/PatienceOS.Kernel.Tests/ConsoleTests.cs @@ -1,26 +1,228 @@ -using Xunit; - -namespace PatienceOS.Kernel.Tests -{ - unsafe public class ConsoleTests - { - [Fact] - public void Console_Should_Write_Hello() - { - // Given - byte* buffer = stackalloc byte[80 * 25 * 2]; - var frameBuffer = new FrameBuffer(buffer); - var console = new Console(80, 25, frameBuffer); - - // When - console.Print("Hello"); - - // Then - Assert.Equal((byte)'H', frameBuffer.Fetch(0)); - Assert.Equal((byte)'e', frameBuffer.Fetch(2)); - Assert.Equal((byte)'l', frameBuffer.Fetch(4)); - Assert.Equal((byte)'l', frameBuffer.Fetch(6)); - Assert.Equal((byte)'o', frameBuffer.Fetch(8)); - } - } -} +using Xunit; + +namespace PatienceOS.Kernel.Tests +{ + unsafe public class ConsoleTests + { + [Fact] + public void Should_Write_AB() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("AB"); + + // Then + Assert.Equal((byte)'A', buffer[0]); + Assert.Equal((byte)'B', buffer[2]); + } + + [Fact] + public void Should_Clear_AB() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("AB"); + console.Clear(); + + // Then + Assert.Equal((byte)' ', buffer[0]); + Assert.Equal((byte)' ', buffer[2]); + } + + [Fact] + public void Should_Write_A_EOL_B() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("A\nB"); + + // Then + Assert.Equal((byte)'A', buffer[0]); + Assert.Equal((byte)'B', buffer[80 * 2 + 0]); + } + + [Fact] + public void Should_Clear_A_EOL_B() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("A\nB"); + console.Clear(); + + // Then + Assert.Equal((byte)' ', buffer[0]); + Assert.Equal((byte)' ', buffer[80 * 2 + 0]); + } + + unsafe public class ThreeXThree + { + [Fact] + public void Should_Write_ABC_EOL_DEF_EOL_GHI() + { + // Given + byte* buffer = stackalloc byte[3 * 3 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(3, 3, frameBuffer); + + // When + console.Print("ABCDEFGHI"); + + // Then + Assert.Equal((byte)'A', buffer[0]); + Assert.Equal((byte)'B', buffer[2]); + Assert.Equal((byte)'C', buffer[4]); + Assert.Equal((byte)'D', buffer[6]); + Assert.Equal((byte)'E', buffer[8]); + Assert.Equal((byte)'F', buffer[10]); + Assert.Equal((byte)'G', buffer[12]); + Assert.Equal((byte)'H', buffer[14]); + Assert.Equal((byte)'I', buffer[16]); + } + } + + unsafe public class HelloWorld + { + [Fact] + public void Should_Write_Hello_Space_World_With_Print_Statement() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("Hello World"); + + // Then + Assert.Equal((byte)'H', buffer[0]); + Assert.Equal((byte)'e', buffer[2]); + Assert.Equal((byte)'l', buffer[4]); + Assert.Equal((byte)'l', buffer[6]); + Assert.Equal((byte)'o', buffer[8]); + Assert.Equal((byte)' ', buffer[10]); + Assert.Equal((byte)'W', buffer[12]); + Assert.Equal((byte)'o', buffer[14]); + Assert.Equal((byte)'r', buffer[16]); + Assert.Equal((byte)'l', buffer[18]); + Assert.Equal((byte)'d', buffer[20]); + } + + [Fact] + public void Should_Write_Hello_Space_World_With_Print_Statements() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("Hello"); + console.Print(" "); + console.Print("World"); + + // Then + Assert.Equal((byte)'H', buffer[0]); + Assert.Equal((byte)'e', buffer[2]); + Assert.Equal((byte)'l', buffer[4]); + Assert.Equal((byte)'l', buffer[6]); + Assert.Equal((byte)'o', buffer[8]); + Assert.Equal((byte)' ', buffer[10]); + Assert.Equal((byte)'W', buffer[12]); + Assert.Equal((byte)'o', buffer[14]); + Assert.Equal((byte)'r', buffer[16]); + Assert.Equal((byte)'l', buffer[18]); + Assert.Equal((byte)'d', buffer[20]); + } + + [Fact] + public void Should_Write_Hello_EOL_World_EOL_With_Print_Statement() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("Hello\nWorld\n"); + + // Then + Assert.Equal((byte)'H', buffer[0]); + Assert.Equal((byte)'e', buffer[2]); + Assert.Equal((byte)'l', buffer[4]); + Assert.Equal((byte)'l', buffer[6]); + Assert.Equal((byte)'o', buffer[8]); + Assert.Equal((byte)'W', buffer[80 * 2 + 0]); + Assert.Equal((byte)'o', buffer[80 * 2 + 2]); + Assert.Equal((byte)'r', buffer[80 * 2 + 4]); + Assert.Equal((byte)'l', buffer[80 * 2 + 6]); + Assert.Equal((byte)'d', buffer[80 * 2 + 8]); + } + + [Fact] + public void Should_Write_Hello_EOL_World_EOL_With_Print_Statements() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.Print("Hello\n"); + console.Print("World\n"); + + // Then + Assert.Equal((byte)'H', buffer[0]); + Assert.Equal((byte)'e', buffer[2]); + Assert.Equal((byte)'l', buffer[4]); + Assert.Equal((byte)'l', buffer[6]); + Assert.Equal((byte)'o', buffer[8]); + Assert.Equal((byte)'W', buffer[80 * 2 + 0]); + Assert.Equal((byte)'o', buffer[80 * 2 + 2]); + Assert.Equal((byte)'r', buffer[80 * 2 + 4]); + Assert.Equal((byte)'l', buffer[80 * 2 + 6]); + Assert.Equal((byte)'d', buffer[80 * 2 + 8]); + } + + [Fact] + public void Should_Write_Hello_EOL_World_EOL_With_PrintLine_Statements() + { + // Given + byte* buffer = stackalloc byte[80 * 25 * 2]; + var frameBuffer = new FrameBuffer(buffer); + var console = new Console(80, 25, frameBuffer); + + // When + console.PrintLine("Hello"); + console.PrintLine("World"); + + // Then + Assert.Equal((byte)'H', buffer[0]); + Assert.Equal((byte)'e', buffer[2]); + Assert.Equal((byte)'l', buffer[4]); + Assert.Equal((byte)'l', buffer[6]); + Assert.Equal((byte)'o', buffer[8]); + Assert.Equal((byte)'W', buffer[80 * 2 + 0]); + Assert.Equal((byte)'o', buffer[80 * 2 + 2]); + Assert.Equal((byte)'r', buffer[80 * 2 + 4]); + Assert.Equal((byte)'l', buffer[80 * 2 + 6]); + Assert.Equal((byte)'d', buffer[80 * 2 + 8]); + } + } + } +} diff --git a/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs b/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs index 0809de4..dea9620 100644 --- a/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs +++ b/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs @@ -19,11 +19,11 @@ public void FrameBuffer_Should_Contain_Hello() frameBuffer.Write(4, (byte)'o'); // Then - Assert.Equal((byte)'H', frameBuffer.Fetch(0)); - Assert.Equal((byte)'e', frameBuffer.Fetch(1)); - Assert.Equal((byte)'l', frameBuffer.Fetch(2)); - Assert.Equal((byte)'l', frameBuffer.Fetch(3)); - Assert.Equal((byte)'o', frameBuffer.Fetch(4)); + Assert.Equal((byte)'H', buffer[0]); + Assert.Equal((byte)'e', buffer[1]); + Assert.Equal((byte)'l', buffer[2]); + Assert.Equal((byte)'l', buffer[3]); + Assert.Equal((byte)'o', buffer[4]); } } } \ No newline at end of file diff --git a/src/PatienceOS.Kernel/Console.cs b/src/PatienceOS.Kernel/Console.cs index cb37a37..e305cb6 100644 --- a/src/PatienceOS.Kernel/Console.cs +++ b/src/PatienceOS.Kernel/Console.cs @@ -10,7 +10,9 @@ unsafe public struct Console private FrameBuffer frameBuffer; - private int pos = 0; + private int column = 0; // nb. Incremented by two for each write, to account for the text colouring + private int row = 0; + private byte foregroundColor = 0x0F; // White public Console(int width, int height, FrameBuffer frameBuffer) @@ -25,27 +27,66 @@ public Console(int width, int height, FrameBuffer frameBuffer) /// public void Clear() { - for (int i = 0; i < width * height * 2; i++) + for (int i = 0; i < width * height * 2; i += 2) { - frameBuffer.Write(i, 0); + // Write directly to the video memory + frameBuffer.Write(i, (byte)' '); + frameBuffer.Write(i + 1, foregroundColor); } + + // Reset the cursor position + column = 0; + row = 0; } /// /// Print a string to the current cursor position /// + /// + /// Assumes each screen character is represented by two bytes aligned as a 16-bit word, + /// see + /// public void Print(string s) { fixed (char* ps = s) { for (int i = 0; i < s.Length; i++) { - frameBuffer.Write(pos, (byte)ps[i]); - frameBuffer.Write(pos + 1, foregroundColor); + // Perform a CRLF if we encounter a Newline character + if (ps[i] == '\n') + { + column = 0; + row++; + + continue; + } + + // Perform a CRLF when the cursor reaches the end of the terminal line + // eg.column is 0 to 79, width = 80 + if (column >= width * 2) + { + column = 0; + row++; + } + + // Write directly to the video memory + frameBuffer.Write(row * width * 2 + column, (byte)ps[i]); + frameBuffer.Write(row * width * 2 + column + 1, foregroundColor); - pos += 2; + // Move the cursor right by one + column += 2; } } } + + /// + /// Prints a string to the current cursor position + /// and then moves the cursor to the next line + /// + public void PrintLine(string s) + { + Print(s); + Print("\n"); + } } } diff --git a/src/PatienceOS.Kernel/FrameBuffer.cs b/src/PatienceOS.Kernel/FrameBuffer.cs index 890d7cc..bee0396 100644 --- a/src/PatienceOS.Kernel/FrameBuffer.cs +++ b/src/PatienceOS.Kernel/FrameBuffer.cs @@ -12,11 +12,6 @@ public FrameBuffer(byte* buffer) this.buffer = buffer; } - public byte Fetch(int position) - { - return buffer[position]; - } - public void Write(int position, byte value) { buffer[position] = value;