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;