feat(vga): add and ESC (wip) support for VGA.puts
+ 119
- 47
@@ -26,11 +26,21 @@ fun kmain
     "[CrystalOS Kernel] Hello from Crystal.\n",
     color: VGA::Colors::BLACK_ON_CYAN,
   )
-  VGA.puts("weeeeeeee\n")
+  puts("weeeeeeee\n")
 
   VGA.puts("Hello world!\n \nWorks maybe?\n")
   VGA.puts("Can print on multiple lines\n", color: VGA::Colors::BLACK_ON_LIME)
-  VGA.puts("\n\nWe still don't have a heap, but now the puts stores the last row and col so it can call the putchar method with the right col / row instead of overlaying subsequent calls on-top one of another (this is super long line to test word wrap).\n")
+  VGA.puts("\nWe still don't have a heap, but now the puts stores the last row and col so it can call the putchar method with the \x1b[32mright\x1b[0m col / row instead of overlaying subsequent calls on-top one of another (this is super long line to test word wrap).\n")
+  VGA.puts "\nFeatures:\n"
+  VGA.puts("\tIt supports \\t char at start\taswell as in text.\n\tIt supports \\n char at end aswell as in text.\n")
+  VGA.puts "\t"
+  VGA.puts "It support writing on same line"
+  VGA.puts " "
+  VGA.puts "(YAY)", VGA::Colors::GREEN_ON_WHITE
+  VGA.puts "\n"
+  VGA.puts("\tCan use custom colors", VGA::Colors::RED_ON_BLACK)
+  VGA.puts("\tCan use another custom color\n", VGA::Colors::GREEN_ON_BLACK)
+  VGA.puts("\tCan use lot of custom color\n", VGA::Colors::CYAN_ON_BLACK)
 
   # VGA.set_cursor(0, 5)
   # VGA.hide_cursor

@@ -8,14 +8,6 @@ lib KernelShim
   )
 end
 
-struct VGACursor
-  @col : Int32 = 0
-  property col
-  
-  @row : Int32 = 0
-  property row
-end
-
 module VGA
   # VGA text mode buffer address (0xB8000)
   VGA_ADDRESS = 0xB8000_u64

...
@@ -25,17 +17,49 @@ module VGA
   VGA_WIDTH = 80
   VGA_HEIGHT = 25
   VGA_SIZE = VGA_WIDTH * VGA_HEIGHT
-  
-  # Cursor state
-  VGA_CURSOR = VGACursor.new
+
+  # Misc
+  TAB_SIZE = 2
 
   # Default color: white text on black background
   DEFAULT_COLOR = 0x07_u8
   
+  def self.color_code(
+    fg : VGA::Color,
+    bg : VGA::Color,
+    #char : UInt8
+  ) : UInt16
+    (bg.value << 4) | fg.value
+    # (attrib << 8) | char.to_u8
+  end
+  
+  enum Color : UInt16
+    Black      =  0
+    Blue       =  1
+    Green      =  2
+    Cyan       =  3
+    Red        =  4
+    Magenta    =  5
+    Brown      =  6
+    LightGray  =  7
+    DarkGray   =  8
+    LightBlue  =  9
+    LightGreen = 10
+    LightCyan  = 11
+    LightRed   = 12
+    Pink       = 13
+    Yellow     = 14
+    White      = 15
+  end
+  
   module Colors
-    DEFAULT       = 0x07_u8
-    BLACK_ON_CYAN = 0xB0_u8
-    BLACK_ON_LIME = 0xA0_u8
+    DEFAULT        = 0x07_u8
+    BLACK_ON_CYAN  = 0xB0_u8
+    BLACK_ON_LIME  = 0xA0_u8
+    RED_ON_BLACK   = 0x04_u8
+    GREEN_ON_BLACK = 0x02_u8
+    CYAN_ON_BLACK  = 0x03_u8
+    GREEN_ON_WHITE = 0xF2_u8
   end
 
   # Initialize VGA subsystem (clear screen)

...
@@ -67,12 +91,13 @@ module VGA
 
   # Set typing cursor position
   def self.set_cursor(
-    col : Int32,
+    next_col : Int32,
     next_row : Int32
   ) : Nil
     KernelShim
       .set_cursor_pos(next_row.to_u32, col.to_u32)
     @@row = next_row
+    @@col = next_col
   end
   
   @@puts_count = 0

...
@@ -94,15 +119,6 @@ module VGA
     @@col ||= i || 0
     @@row ||= line || 0
 
-    # somehow doesn't work...
-    if ch == 0x0A_u8 # \n handler
-      @@col = 0
-      @@row += 1
-      if @@row >= VGA_HEIGHT
-        @@row = VGA_HEIGHT - 1
-      end
-    end
-
     offset = (@@row * VGA_WIDTH + @@col) * 2
 
     (VGA_BUFFER + offset).value = ch

...
@@ -126,21 +142,66 @@ module VGA
     color : UInt8? = Colors::DEFAULT,
     line : Int32? = nil,
   ) : Nil
-    i = 0
+    @@col ||= 0
     @@row ||= (line || 0)
+    i = 0
     while i < str.size
       ch = (str.to_unsafe + i).value
-      if ch == 0x0A_u8
+
+      # todo: support more control chars
+      # - \b - 0x08 : Backspace (col - 1)
+      # - \a - 0x07 : Bell (ring sound) ?
+      # - \0 - 0x00 : NUL string terminator
+      # - \r - 0x0D : Cariage Return (col = 0)
+      # - \f - 0x0C : Form Feed (row += VGA_HEIGHT)
+      # - ESC - 0x1B : Escape (colors, etc)
+      if ch == 0x0A_u8 # \n
         @@row += 1
         @@col = 0
         i += 1
         next
+      elsif ch == 0x09_u8 # \t
+        @@col += TAB_SIZE
+        i += 1
+        next
+      elsif ch == 0x1B_u8 # \ESC
+        # i.e. reset all: \x1b[0m
+        # i.e. fg green: \x1b[32m
+        # i.e. bg green: \x1b[42m
+        # read next chars til' "m"
+        # convert args to color
+        # set cell color (as default)
+        # skip args (don't print)
+        j = 0
+        # args = [] of String
+        is_reset = false
+        while ((str.to_unsafe + i + j).value) != 0x6D_u8 # m letter
+          # do something with args
+          # i + 1    = [ start
+          # i + 2..; = fg color
+          # i + ;..; = bg color (optional)
+          # i + ;..; = style bit (optional)
+          # i + ;..m = end
+          if str[i+j+1] == '[' && str[i+j+2] == '0' && str[i+j+3] == 'm'
+            is_reset = true
+          else
+            is_reset = false
+          end
+          j += 1
+        end
+        #(str.to_unsafe + i + j).value = 0xB0_u8
+        i += j # skip ESC sequence length chars
+        puts "[" if is_reset == false
+        puts "]" if is_reset
+        i += 1
+        next
       end
-      putchar(ch, i, @@row, color)
+
+      putchar(ch, @@col, @@row, color)
       i += 1
     end
     
-    VGA.set_cursor(0, @@row)
+    VGA.set_cursor(@@col, @@row)
   end
 
 end

@@ -23,5 +23,8 @@ require "./prelude/crystal_core/string"
 require "./prelude/crystal_core/reference"
 require "./prelude/crystal_core/static_array"
 
+# needs support for pmm first
+# require "./prelude/crystal_core/array"
+
 # crystal api
 require "./prelude/puts"

prelude/crystal_core/array.cr
@@ -44,7 +44,7 @@ class Array(T)
 
   def initialize(initial_capacity : Int = 0)
     if initial_capacity > 0
-      @buffer = Pointer(T).malloc_atomic(initial_capacity.to_u64)
+      @buffer = Pointer(T).new(initial_capacity.to_u64)
       recalculate_capacity
     end
   end

...
@@ -127,6 +127,15 @@ class Array(T)
       @size = 0
     end
   end
+  
+  def join(sep : String? = "")
+    str = ""
+    @size.times do |i|
+      str += @buffer[i]
+      str += sep
+    end
+    str
+  end
 
   @[NoInline]
   def mark(&block : Void* ->)

@@ -1,19 +1,8 @@
-fun print_string(str : UInt8*)
-end
-
-VGA_ADDRESS = 0xb8000_u64
-
+# Global `puts` method
 def puts(str : String)
-  _str : Pointer(UInt8) = str.to_unsafe
-  video_memory = Pointer(UInt8).new(VGA_ADDRESS)
-  i = 0
-  ch = _str.value
-  while ch > 0
-    ch = (_str + i).value
-    break if ch == 0
-    (video_memory + (i * 2)).value = ch
-    (video_memory + (i * 2) + 1).value = 0x07_u8
-    i += 1
-  end
+  VGA.puts str
 end
 
+def p!(str : String)
+  VGA.puts str
+end