hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/gils/ed.rv
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-06 12:49:46 +0200
committerhachem <im@hachem.wtf>2026-10-06 12:49:46 +0200
commit5d4a723a5f157c0ed38a348099a600d1d21c3c2b (patch)
tree70e5fdf560a64cd0eb3a13d88ce046ddd29fd077 /gils/ed.rv
parent52dab7fc85d877e80e3032f0afabae906adebad8 (diff)
feat: gils core utils and other shit
Diffstat (limited to 'gils/ed.rv')
-rw-r--r--gils/ed.rv190
1 files changed, 190 insertions, 0 deletions
diff --git a/gils/ed.rv b/gils/ed.rv
new file mode 100644
index 0000000..00ff50a
--- /dev/null
+++ b/gils/ed.rv
@@ -0,0 +1,190 @@
+# simple ass modal ass line ass editor
+
+fn list_insert(items, index, value) do
+ let result = {}
+ for position in 0..index do result:push(items[position]) end
+ result:push(value)
+ for position in index..items:len() do result:push(items[position]) end
+ return result
+end
+
+fn list_delete(items, low, high) do
+ let result = {}
+ for position in 0..items:len() do if (position < low or position > high) result:push(items[position]) end
+ return result
+end
+
+fn ed_is_digit(character) do
+ const code = character:ascii()
+ return code >= 48 and code <= 57
+end
+
+# parse one address at index
+fn ed_addr(state, line, index) do
+ if index >= line:len() return { value = -1, next = index }
+ if line[index] == "." return { value = state.dot, next = index + 1 }
+ if line[index] == "$" return { value = state.lines:len() - 1, next = index + 1 }
+ if ed_is_digit(line[index]) do
+ let number = 0
+ let scan = index
+ while (scan < line:len() and ed_is_digit(line[scan])) do number = number * 10 + (line[scan]:ascii() - 48) scan += 1 end
+ return { value = number - 1, next = scan }
+ end
+ return { value = -1, next = index }
+end
+
+# split a command line into low, high and command
+fn ed_parse(state, line) do
+ let index = 0
+ let low = -1
+ let high = -1
+ if (line:len() > 0 and (line[0] == "," or line[0] == "%")) do
+ low = 0
+ high = state.lines:len() - 1
+ index = 1
+ end
+ else do
+ const first_addr = ed_addr(state, line, 0)
+ if first_addr.value >= 0 do low = first_addr.value index = first_addr.next end
+ if (index < line:len() and line[index] == ",") do
+ index += 1
+ const second_addr = ed_addr(state, line, index)
+ if second_addr.value >= 0 do high = second_addr.value index = second_addr.next end
+ else do high = state.lines:len() - 1 end
+ end
+ end
+ if low < 0 do low = state.dot end
+ if high < 0 do high = low end
+ const command = if index < line:len() line[index..] else ""
+ return { low = low, high = high, command = command }
+end
+
+fn ed_print(state, low, high, numbered) do
+ let index = low
+ while index <= high do
+ if (index >= 0 and index < state.lines:len()) do
+ if numbered do puts("#{index + 1}\t#{state.lines[index]}\n") end
+ else do puts("#{state.lines[index]}\n") end
+ end
+ index += 1
+ end
+ if (high >= 0 and high < state.lines:len()) do state.dot = high end
+end
+
+fn ed_goto(state, low) do
+ if state.lines:len() == 0 do puts("?\n") return :nil end
+ let target = low
+ if target < 0 do target = 0 end
+ if target >= state.lines:len() do target = state.lines:len() - 1 end
+ state.dot = target
+ puts("#{state.lines[target]}\n")
+end
+
+fn ed_begin_input(state, position) do
+ let clamped = position
+ if clamped < 0 do clamped = 0 end
+ if clamped > state.lines:len() do clamped = state.lines:len() end
+ state.input = :true
+ state.insert_at = clamped
+end
+
+fn ed_delete(state, low, high) do
+ state.lines = list_delete(state.lines, low, high)
+ state.dirty = :true
+ let new_dot = low
+ if new_dot >= state.lines:len() do new_dot = state.lines:len() - 1 end
+ if new_dot < 0 do new_dot = 0 end
+ state.dot = new_dot
+end
+
+fn ed_change(state, low, high) do
+ ed_delete(state, low, high)
+ ed_begin_input(state, low)
+end
+
+fn ed_join(items) do
+ let result = ""
+ for index in 0..items:len() do result = result ~ items[index] ~ "\n" end
+ return result
+end
+
+fn ed_write(state, command) do
+ let path = state.path
+ if command:len() > 1 do
+ const filename = command[1..]:trim()
+ if filename:len() > 0 do path = abspath(filename) end
+ end
+ if path:len() == 0 do puts("?\n") return :nil end
+ const contents = ed_join(state.lines)
+ fs:store(path, contents)
+ state.path = path
+ state.dirty = :false
+ puts("#{contents:len()}\n")
+end
+
+fn ed_quit(state) do
+ state.active = :false
+ puts("% ")
+end
+
+global ed_state = { active = :false, input = :false, path = "", lines = {}, dot = 0, insert_at = 0, dirty = :false }
+
+fn gils_ed(context) do
+ ed_state.active = :true
+ ed_state.input = :false
+ ed_state.dirty = :false
+ ed_state.lines = {}
+ ed_state.path = if context.argv:len() > 1 abspath(context.argv[1]) else ""
+ if ed_state.path:len() > 0 do
+ const size = fs:size(ed_state.path)
+ if size > 0 do
+ const contents = fs:read_bytes(ed_state.path, 0, size)
+ const raw_lines = contents:split("\n")
+ for index in 0..raw_lines:len() do
+ if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) do ed_state.lines:push(raw_lines[index]) end
+ end
+ puts("#{size}\n")
+ end
+ else do puts("#{ed_state.path}: new file\n") end
+ end
+ ed_state.dot = if ed_state.lines:len() > 0 ed_state.lines:len() - 1 else 0
+ return :hold
+end
+
+fn gils_ed_feed(line) do
+ const state = ed_state
+ if state.input do
+ if line == "." do state.input = :false end
+ else do
+ state.lines = list_insert(state.lines, state.insert_at, line)
+ state.insert_at += 1
+ state.dot = state.insert_at - 1
+ state.dirty = :true
+ end
+ return :nil
+ end
+ if line:len() == 0 do
+ if state.lines:len() == 0 do puts("?\n") return :nil end
+ if state.dot < state.lines:len() - 1 do state.dot += 1 end
+ puts("#{state.lines[state.dot]}\n")
+ return :nil
+ end
+ const parsed = ed_parse(state, line)
+ const command_char = if parsed.command:len() > 0 parsed.command[0] else ""
+ match command_char
+ | "p" => ed_print(state, parsed.low, parsed.high, :false)
+ | "n" => ed_print(state, parsed.low, parsed.high, :true)
+ | "a" => ed_begin_input(state, parsed.low + 1)
+ | "i" => ed_begin_input(state, parsed.low)
+ | "c" => ed_change(state, parsed.low, parsed.high)
+ | "d" => ed_delete(state, parsed.low, parsed.high)
+ | "w" => ed_write(state, parsed.command)
+ | "q" => ed_quit(state)
+ | "=" => puts("#{state.dot + 1}\n")
+ | "" => ed_goto(state, parsed.low)
+ | _ => puts("?\n")
+end
+
+fn gils_ed_active() ed_state.active
+
+register("ed [path]", "gils", "the standard editor (a i d p w q)", gils_ed)