hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-08 11:50:02 +0200
committerhachem <im@hachem.wtf>2026-10-08 11:50:02 +0200
commitef104148057ab98f80b1560405b5c02768457096 (patch)
tree47e75bc81aacb2e035e7cd43be9616b677b01d04
parent5f6deca073728d0dafa2cd47d841b28d54af3aaf (diff)
feat: colors + better ps1
-rw-r--r--gils/cat.rv2
-rw-r--r--gils/cp.rv4
-rw-r--r--gils/gils.rv2
-rw-r--r--gils/head.rv4
-rw-r--r--gils/mkdir.rv2
-rw-r--r--gils/mv.rv4
-rw-r--r--gils/rm.rv2
-rw-r--r--gils/tail.rv4
-rw-r--r--gils/touch.rv4
-rw-r--r--gils/tree.rv4
-rw-r--r--gils/wc.rv4
-rw-r--r--kernel/console.rv94
-rw-r--r--kernel/fs.rv22
-rw-r--r--kernel/shell.rv8
14 files changed, 122 insertions, 38 deletions
diff --git a/gils/cat.rv b/gils/cat.rv
index 220ce93..0666304 100644
--- a/gils/cat.rv
+++ b/gils/cat.rv
@@ -2,7 +2,7 @@
const context = env.ctx
const parsed = arg_name(context.rest, 0)
-if parsed.name:len() == 0 do puts("usage: cat <path>\n") end
+if parsed.name:len() == 0 do fail("usage: cat <path>\n") end
else do
const path = expand_path(parsed.name)
if vfs_owns(path) do vfs_cat(path) end
diff --git a/gils/cp.rv b/gils/cp.rv
index 5006acb..20d4ae3 100644
--- a/gils/cp.rv
+++ b/gils/cp.rv
@@ -2,12 +2,12 @@
# HAAAAAANNNNKKKK
const context = env.ctx
-if context.argv:len() < 3 do puts("usage: cp <source> <destination>\n") end
+if context.argv:len() < 3 do fail("usage: cp <source> <destination>\n") end
else do
const source = expand_path(context.argv[1])
const destination = expand_path(context.argv[2])
const size = fs:size(source)
- if size < 0 do puts("cp: #{source}: no such file\n") end
+ if size < 0 do fail("cp: #{source}: no such file\n") end
else do
const contents = if size > 0 fs:read_bytes(source, 0, size) else ""
if fs:store(destination, contents) == 1 do puts("#{source} -> #{destination}\n") end
diff --git a/gils/gils.rv b/gils/gils.rv
index 08c97cd..a500322 100644
--- a/gils/gils.rv
+++ b/gils/gils.rv
@@ -39,7 +39,7 @@ fn run_script_path(path, label, ctx) do
if size < 0 return :false
env.ctx = ctx
const error_msg = eval_script(fs:read_bytes(path, 0, size))
- if error_msg != :nil do puts("#{label}: #{error_msg}\n") end
+ if error_msg != :nil do fail("#{label}: #{error_msg}\n") end
return :true
end
diff --git a/gils/head.rv b/gils/head.rv
index addd11a..2c2a82f 100644
--- a/gils/head.rv
+++ b/gils/head.rv
@@ -13,13 +13,13 @@ fn file_lines(path) do
end
const context = env.ctx
-if context.argv:len() < 2 do puts("usage: head [n] <path>\n") end
+if context.argv:len() < 2 do fail("usage: head [n] <path>\n") end
else do
let limit = 10
let path_arg = context.argv[1]
if (context.argv:len() >= 3 and parse_pid(context.argv[1]) >= 0) do limit = parse_pid(context.argv[1]) path_arg = context.argv[2] end
const lines = file_lines(expand_path(path_arg))
- if lines == :nil do puts("head: no such file\n") end
+ if lines == :nil do fail("head: no such file\n") end
else do
let index = 0
while (index < lines:len() and index < limit) do puts("#{lines[index]}\n") index += 1 end
diff --git a/gils/mkdir.rv b/gils/mkdir.rv
index 759544e..c7d12f7 100644
--- a/gils/mkdir.rv
+++ b/gils/mkdir.rv
@@ -1,5 +1,5 @@
# make a directory
const context = env.ctx
-if context.argv:len() < 2 do puts("usage: mkdir <path>\n") end
+if context.argv:len() < 2 do fail("usage: mkdir <path>\n") end
else do fs:mkdir(expand_path(context.argv[1])) end
diff --git a/gils/mv.rv b/gils/mv.rv
index d3ecf47..b44995b 100644
--- a/gils/mv.rv
+++ b/gils/mv.rv
@@ -1,12 +1,12 @@
# move or rename a file
const context = env.ctx
-if context.argv:len() < 3 do puts("usage: mv <source> <destination>\n") end
+if context.argv:len() < 3 do fail("usage: mv <source> <destination>\n") end
else do
const source = expand_path(context.argv[1])
const destination = expand_path(context.argv[2])
const size = fs:size(source)
- if size < 0 do puts("mv: #{source}: no such file\n") end
+ if size < 0 do fail("mv: #{source}: no such file\n") end
else do
const contents = if size > 0 fs:read_bytes(source, 0, size) else ""
if fs:store(destination, contents) == 1 do fs:unlink(source) puts("#{source} -> #{destination}\n") end
diff --git a/gils/rm.rv b/gils/rm.rv
index 891dc27..236d2ba 100644
--- a/gils/rm.rv
+++ b/gils/rm.rv
@@ -2,5 +2,5 @@
const context = env.ctx
const parsed = arg_name(context.rest, 0)
-if parsed.name:len() == 0 do puts("usage: rm <path>\n") end
+if parsed.name:len() == 0 do fail("usage: rm <path>\n") end
else do fs:rm(expand_path(parsed.name)) end
diff --git a/gils/tail.rv b/gils/tail.rv
index 4a661b4..c1ca124 100644
--- a/gils/tail.rv
+++ b/gils/tail.rv
@@ -14,13 +14,13 @@ fn file_lines(path) do
end
const context = env.ctx
-if context.argv:len() < 2 do puts("usage: tail [n] <path>\n") end
+if context.argv:len() < 2 do fail("usage: tail [n] <path>\n") end
else do
let limit = 10
let path_arg = context.argv[1]
if (context.argv:len() >= 3 and parse_pid(context.argv[1]) >= 0) do limit = parse_pid(context.argv[1]) path_arg = context.argv[2] end
const lines = file_lines(expand_path(path_arg))
- if lines == :nil do puts("tail: no such file\n") end
+ if lines == :nil do fail("tail: no such file\n") end
else do
let start = lines:len() - limit
if start < 0 do start = 0 end
diff --git a/gils/touch.rv b/gils/touch.rv
index 84ecca8..f8e3e18 100644
--- a/gils/touch.rv
+++ b/gils/touch.rv
@@ -1,9 +1,9 @@
# dont touch my nono square
const context = env.ctx
-if context.argv:len() < 2 do puts("usage: touch <path>\n") end
+if context.argv:len() < 2 do fail("usage: touch <path>\n") end
else do
const path = expand_path(context.argv[1])
- if fs:is_dir(path) do puts("touch: #{path}: is a directory\n") end
+ if fs:is_dir(path) do fail("touch: #{path}: is a directory\n") end
else if fs:size(path) < 0 do fs:store(path, "") end
end
diff --git a/gils/tree.rv b/gils/tree.rv
index cc41601..d1a6af6 100644
--- a/gils/tree.rv
+++ b/gils/tree.rv
@@ -21,8 +21,8 @@ end
const context = env.ctx
const target = if context.argv:len() > 1 expand_path(context.argv[1]) else wd.path
-if (vfs_owns(target) and target != "/") do puts("tree: #{target}: virtual dir\n") end
-else if (target != "/" and fs:is_dir(target) == :false) do puts("tree: #{target}: not a directory\n") end
+if (vfs_owns(target) and target != "/") do fail("tree: #{target}: virtual dir\n") end
+else if (target != "/" and fs:is_dir(target) == :false) do fail("tree: #{target}: not a directory\n") end
else do
puts("#{target}\n")
tree_walk(target, "")
diff --git a/gils/wc.rv b/gils/wc.rv
index 9a184ca..1127299 100644
--- a/gils/wc.rv
+++ b/gils/wc.rv
@@ -1,11 +1,11 @@
# word count
const context = env.ctx
-if context.argv:len() < 2 do puts("usage: wc <path>\n") end
+if context.argv:len() < 2 do fail("usage: wc <path>\n") end
else do
const path = expand_path(context.argv[1])
const size = fs:size(path)
- if size < 0 do puts("wc: #{path}: no such file\n") end
+ if size < 0 do fail("wc: #{path}: no such file\n") end
else do
const contents = if size > 0 fs:read_bytes(path, 0, size) else ""
let line_count = 0
diff --git a/kernel/console.rv b/kernel/console.rv
index 3bff6de..9447860 100644
--- a/kernel/console.rv
+++ b/kernel/console.rv
@@ -1,11 +1,54 @@
const FONT_WIDTH = 8 # glyph width in pixels
const FONT_HEIGHT = 16 # glyph height in pixels (8x16 vga bitmap font)
-const FG_COLOR = 0xd0d0d0
-const BG_COLOR = 0x101018
-const CURSOR_COLOR = 0x50fa7b
+
+# base16 from lung
+const BASE00 = 0x101010 # background
+const BASE03 = 0x505050 # bright black
+const BASE05 = 0xd0d0d0 # default foreground
+const BASE07 = 0xfafafa # bright white
+const BASE08 = 0xab4642 # red
+const BASE09 = 0xdc9656 # orange
+const BASE0A = 0xf7ca88 # yellow
+const BASE0B = 0xa1b56c # green
+const BASE0C = 0x86c1b9 # cyan
+const BASE0D = 0x7cafc2 # blue
+const BASE0E = 0xba8baf # magenta
+const FG_COLOR = BASE05
+const BG_COLOR = BASE00
+const CURSOR_COLOR = BASE0B
global wd = { path = "/" }
-global prompt = fn() "revel:#{wd.path}$ "
+global shell_status = { code = 0 }
+
+global prompt = fn() do
+ let mid = ""
+ unless (shell_status.code == 0) do mid = "\x1b[31m#{shell_status.code} " end
+ return "\x1b[33m#{wd.path} #{mid}\x1b[97m$\x1b[0m "
+end
+
+fn parse_int(text) do
+ let value = 0
+ for index in 0..text:len() do
+ const digit = text[index]:ascii()
+ if (digit >= 48 and digit <= 57) do value = value * 10 + (digit - 48) end
+ end
+ return value
+end
+
+fn sgr_color(code, current) do
+ if (code == 0 or code == 39) return BASE05
+ if code == 30 return BASE00
+ if code == 90 return BASE03
+ if (code == 31 or code == 91) return BASE08
+ if (code == 32 or code == 92) return BASE0B
+ if (code == 33 or code == 93) return BASE0A
+ if (code == 34 or code == 94) return BASE0D
+ if (code == 35 or code == 95) return BASE0E
+ if (code == 36 or code == 96) return BASE0C
+ if code == 37 return BASE05
+ if code == 97 return BASE07
+ return current
+end
# runtime font bs
fn embolden(byte, row) do return (byte bor (byte shl 1)) band 0xFF end
@@ -58,7 +101,23 @@ add_font("italic", remap_glyphs(slant), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil,
add_font("bolditalic", remap_glyphs(fn(byte, row) do return slant(embolden(byte, row), row) end), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0)
global console = {
- column = 0, row = 0, scale = 1, shown = :false, cursor_x = 0, cursor_y = 0, blink = 0, font = FONT, font_width = FONT_WIDTH, font_height = FONT_HEIGHT, font_entry = find_font("vga"),
+ column = 0,
+ row = 0,
+ scale = 1,
+
+ shown = :false,
+ cursor_x = 0,
+ cursor_y = 0,
+ blink = 0,
+
+ font = FONT,
+ font_width = FONT_WIDTH,
+ font_height = FONT_HEIGHT,
+ font_entry = find_font("vga"),
+
+ fg = FG_COLOR,
+ ansi = 0,
+ ansi_param = "",
# this is now font dependent
cell_width = fn(self) self.font_width * self.scale,
@@ -87,7 +146,7 @@ global console = {
for glyph_row in 0..self.font_height do
const bits = glyph[glyph_row]
for glyph_col in 0..self.font_width do
- if (bits shr glyph_col) band 1 == 1 fill_rect(origin_x + glyph_col * self.scale, origin_y + glyph_row * self.scale, self.scale, self.scale, FG_COLOR)
+ if (bits shr glyph_col) band 1 == 1 fill_rect(origin_x + glyph_col * self.scale, origin_y + glyph_row * self.scale, self.scale, self.scale, self.fg)
end
end
end,
@@ -103,8 +162,26 @@ global console = {
# we dont do shift/cntrl, thats a job for future me lmao
# NOTE: future me kinda delt with it, i was mad drunk
+ apply_sgr = fn(self, param) do
+ if param:len() == 0 do self.fg = BASE05 return :nil end
+ const parts = param:split(";")
+ for index in 0..parts:len() do self.fg = sgr_color(parse_int(parts[index]), self.fg) end
+ end,
+
putc = fn(self, character) do
const code = character:ascii()
+ if self.ansi == 1 do
+ if code == 91 do self.ansi = 2 self.ansi_param = "" end
+ else do self.ansi = 0 end
+ return :nil
+ end
+ if self.ansi == 2 do
+ if (code >= 48 and code <= 57) or code == 59 do self.ansi_param = self.ansi_param ~ character return :nil end
+ if code == 109 do self:apply_sgr(self.ansi_param) end
+ self.ansi = 0
+ return :nil
+ end
+ if code == 27 do self.ansi = 1 return :nil end # ESC, start a sequence
if code == 10 do self:newline() return :nil end # \n
if code == 13 do self.column = 0 return :nil end # \r
if code == 8 do # backspace: move left
@@ -146,6 +223,11 @@ global console = {
global puts = fn(text) serial_puts(text)
+global fail = fn(message) do
+ puts(message)
+ shell_status.code = 1
+end
+
global flush_console = fn() do
console:cursor_clear()
console:drain()
diff --git a/kernel/fs.rv b/kernel/fs.rv
index 255797f..23ccd78 100644
--- a/kernel/fs.rv
+++ b/kernel/fs.rv
@@ -249,11 +249,11 @@ global fs = {
mkdir = fn(self, path) do
if self:ensure() == :false return :nil
const parent = self:resolve_parent(path)
- if parent == :nil do puts("mkdir: #{path}: bad path\n") return :nil end
+ if parent == :nil do fail("mkdir: #{path}: bad path\n") return :nil end
if parent.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return :nil end
- if self:dir_lookup(parent.dir, parent.name) != :nil do puts("mkdir: #{parent.name}: already exists\n") return :nil end
+ if self:dir_lookup(parent.dir, parent.name) != :nil do fail("mkdir: #{parent.name}: already exists\n") return :nil end
const new_block = self:fat_alloc()
- if new_block == 0 do puts("disk full\n") return :nil end
+ if new_block == 0 do fail("disk full\n") return :nil end
self:fat_set(new_block, END)
mem_zero(self.scratch, BLOCK)
disk_write(new_block, self.scratch)
@@ -267,7 +267,7 @@ global fs = {
if parent.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return 0 end
const existing = self:dir_lookup(parent.dir, parent.name)
if existing != :nil do
- if existing.type == T_DIR do puts("#{parent.name}: is a directory\n") return 0 end
+ if existing.type == T_DIR do fail("#{parent.name}: is a directory\n") return 0 end
self:free_chain(existing.first)
end
@@ -277,7 +277,7 @@ global fs = {
let written = 0
while written < size do
const block = self:fat_alloc()
- if block == 0 do puts("disk full\n") return 0 end
+ if block == 0 do fail("disk full\n") return 0 end
self:fat_set(block, END)
if first == 0 do first = block end
else do self:fat_set(previous, block) end
@@ -349,8 +349,8 @@ global fs = {
cat = fn(self, path) do
if self:ensure() == :false return :nil
const entry = self:resolve(path)
- if entry == :nil do puts("cat: #{path}: no such file\n") return :nil end
- if entry.type == T_DIR do puts("cat: #{path}: is a directory\n") return :nil end
+ if entry == :nil do fail("cat: #{path}: no such file\n") return :nil end
+ if entry.type == T_DIR do fail("cat: #{path}: is a directory\n") return :nil end
let block = entry.first
let remaining = entry.size
let result = ""
@@ -369,7 +369,7 @@ global fs = {
ls = fn(self, path) do
if self:ensure() == :false return -1
const entry = self:resolve(path)
- if entry == :nil do puts("ls: #{path}: no such file\n") return -1 end
+ if entry == :nil do fail("ls: #{path}: no such file\n") return -1 end
if entry.type != T_DIR do puts(" #{path} #{entry.size} bytes\n") return -1 end
let count = 0
let block = entry.first
@@ -394,11 +394,11 @@ global fs = {
rm = fn(self, path) do
if self:ensure() == :false return :nil
const parent = self:resolve_parent(path)
- if parent == :nil do puts("rm: #{path}: no such file\n") return :nil end
+ if parent == :nil do fail("rm: #{path}: no such file\n") return :nil end
const entry = self:dir_lookup(parent.dir, parent.name)
- if entry == :nil do puts("rm: #{path}: no such file\n") return :nil end
+ if entry == :nil do fail("rm: #{path}: no such file\n") return :nil end
if entry.type == T_DIR and self:dir_empty(entry.first) == :false do
- puts("rm: #{parent.name}: directory not empty\n")
+ fail("rm: #{parent.name}: directory not empty\n")
return :nil
end
self:free_chain(entry.first)
diff --git a/kernel/shell.rv b/kernel/shell.rv
index f112889..31cc70e 100644
--- a/kernel/shell.rv
+++ b/kernel/shell.rv
@@ -88,7 +88,7 @@ fn cmd_cd(context) do
const arg = if context.argv:len() > 1 context.argv[1] else "/"
const target = if arg == ".." parent_of(wd.path) else expand_path(arg)
if target == "/proc" or target == "/bin" or fs:is_dir(target) do wd.path = target end
- else do puts("cd: #{arg}: not a directory\n") end
+ else do puts("cd: #{arg}: not a directory\n") shell_status.code = 1 end
end
fn cmd_pwd(context) puts("#{wd.path}\n")
@@ -309,7 +309,7 @@ fn cmd_run(context) do
if context.argv:len() <= 1 do list_progs() return :nil end
const target = context.argv[1]
const info = exec_file(target)
- if info.kind == :none do puts("run: #{target}: not found\n") return :nil end
+ if info.kind == :none do fail("run: #{target}: not found\n") return :nil end
const sub_argv = context.rest:split(" ")
const sub_ctx = make_ctx(context.rest, sub_argv)
if info.kind == :script do
@@ -317,7 +317,7 @@ fn cmd_run(context) do
return :nil
end
if load_file_elf(info.path, sub_ctx.rest) == :true return :hold
- puts("run: #{target}: not executable\n")
+ fail("run: #{target}: not executable\n")
return :nil
end
@@ -328,6 +328,7 @@ fn run(line) do
if line:trim():len() == 0 do puts(prompt()) return :nil end
const argv = line:split(" ")
const context = make_ctx(line, argv)
+ shell_status.code = 0
const command = lookup(argv[0])
if command != :nil do
# :hold means the command took over (modal editor / launched a program) so
@@ -339,6 +340,7 @@ fn run(line) do
if run_script(argv[0], context) == :true do puts(prompt()) return :nil end
if run_prog(argv[0], context.rest) == :true return :nil
+ shell_status.code = 127
puts("#{argv[0]}: command not found\n")
puts(prompt())
end