/ src / console.zig
console.zig
  1  const std = @import("std");
  2  const console = @This();
  3  
  4  const VGA_WIDTH = 80;
  5  const VGA_HEIGHT = 25;
  6  const VGA_SIZE = VGA_WIDTH * VGA_HEIGHT;
  7  
  8  var g_row: usize = 0;
  9  var g_column: usize = 0;
 10  var g_color: Color = .init(.light_gray, .black);
 11  var g_buffer = @as([*]volatile u16, @ptrFromInt(0xB8000));
 12  
 13  pub const ColorType = enum(u4) {
 14      black = 0,
 15      blue = 1,
 16      green = 2,
 17      cyan = 3,
 18      red = 4,
 19      magenta = 5,
 20      brown = 6,
 21      light_gray = 7,
 22      dark_gray = 8,
 23      light_blue = 9,
 24      light_green = 10,
 25      light_cyan = 11,
 26      light_red = 12,
 27      light_magenta = 13,
 28      light_brown = 14,
 29      white = 15,
 30  };
 31  
 32  const Color = packed struct(u8) {
 33      fg: ColorType,
 34      bg: ColorType,
 35  
 36      pub fn init(fg: ColorType, bg: ColorType) Color {
 37          return .{ .fg = fg, .bg = bg };
 38      }
 39  
 40      /// Combine vga color and char. The upper byte will be color and lower byte will be character
 41      pub fn getVgaChar(self: Color, char: u8) u16 {
 42          return @as(u16, @as(u8, @bitCast(self))) << 8 | char;
 43      }
 44  };
 45  
 46  /// Initialize VGA
 47  pub fn init() void {
 48      clear();
 49  }
 50  
 51  /// Set Color for VGA
 52  pub fn setColor(fg: Color, bg: Color) void {
 53      g_color = Color.init(fg, bg);
 54  }
 55  
 56  /// Clear the screen
 57  pub fn clear() void {
 58      @memset(g_buffer[0..VGA_SIZE], Color.getVgaChar(g_color, ' '));
 59  }
 60  
 61  /// Print character with color at specific position
 62  pub fn printCharAt(char: u8, color: Color, x: usize, y: usize) void {
 63      const index = y * VGA_WIDTH + x;
 64      g_buffer[index] = color.getVgaChar(char);
 65  }
 66  
 67  /// INFO: Scrolling is left as an exercise for the reader
 68  fn checkAndScroll() void {
 69      if (g_row == VGA_HEIGHT) {
 70          g_row = 0;
 71      }
 72  }
 73  
 74  /// Print character to the VGA
 75  pub fn printChar(char: u8) void {
 76      switch (char) {
 77          '\n' => {
 78              g_column = 0;
 79              g_row += 1;
 80              checkAndScroll();
 81          },
 82          else => {
 83              printCharAt(char, g_color, g_column, g_row);
 84              g_column += 1;
 85              if (g_column == VGA_WIDTH) {
 86                  g_column = 0;
 87                  g_row += 1;
 88                  checkAndScroll();
 89              }
 90          },
 91      }
 92  }
 93  
 94  /// Implementation of std.Io.Writer.vtable.drain function.
 95  /// When flush is called or the writer buffer is full this function is called.
 96  /// This function first writes all data of writer buffer after that it writes
 97  /// the argument data in which the last element is written splat times.
 98  fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
 99      // the length of data must not be zero
100      std.debug.assert(data.len != 0);
101  
102      var consumed: usize = 0;
103      const pattern = data[data.len - 1];
104      const splat_len = pattern.len * splat;
105  
106      // If buffer is not empty write it first
107      if (w.end != 0) {
108          printString(w.buffered());
109          w.end = 0;
110      }
111  
112      // Now write all data except last element
113      for (data[0 .. data.len - 1]) |bytes| {
114          printString(bytes);
115          consumed += bytes.len;
116      }
117  
118      // If out patter (i.e. last element of data) is non zero len then write splat times
119      switch (pattern.len) {
120          0 => {},
121          else => {
122              for (0..splat) |_| {
123                  printString(pattern);
124              }
125          },
126      }
127      // Now we have to return how many bytes we consumed from data
128      consumed += splat_len;
129      return consumed;
130  }
131  
132  /// Returns std.Io.Writer implementation for this console
133  pub fn writer(buffer: []u8) std.Io.Writer {
134      return .{
135          .buffer = buffer,
136          .end = 0,
137          .vtable = &.{
138              .drain = drain,
139          },
140      };
141  }
142  
143  /// Print string to VGA
144  pub fn printString(str: []const u8) void {
145      for (str) |char| {
146          printChar(char);
147      }
148  }
149  
150  /// Print with standard zig format to VGA
151  pub fn print(comptime fmt: []const u8, args: anytype) void {
152      var w = writer(&.{});
153      w.print(fmt, args) catch
154      return;
155  }