Skip to content

Instantly share code, notes, and snippets.

@frankdugan3
Created June 17, 2025 18:43
Show Gist options
  • Save frankdugan3/5efd044e2390bb13912ffadef42f74d9 to your computer and use it in GitHub Desktop.
Save frankdugan3/5efd044e2390bb13912ffadef42f74d9 to your computer and use it in GitHub Desktop.
OTP 28 - RawModeVim
defmodule RawModeVim do
@moduledoc """
This is a quick experiment with OTP 28's raw mode.
"""
def run do
:shell.start_interactive({:noshell, :raw})
cursor_start()
mode = normal_mode()
loop(nil, mode)
end
def loop("q", :normal) do
IO.write([IO.ANSI.clear(), "\r", IO.ANSI.cursor(0, 0)])
System.halt(0)
end
def loop("\e", mode) when mode != :normal do
mode = normal_mode()
loop(IO.getn("", 1), mode)
end
def loop("\r", mode) when mode in [:insert, :replace, :overwrite] do
IO.write(["\n\r"])
loop(IO.getn("", 1), mode)
end
def loop(last, :replace) do
IO.write([last])
mode = normal_mode()
loop(IO.getn("", 1), mode)
end
def loop(last, mode) when mode in [:insert, :overwrite] do
IO.write([last])
loop(IO.getn("", 1), mode)
end
def loop("j", :normal) do
IO.write([IO.ANSI.cursor_down(1)])
loop(IO.getn("", 1), :normal)
end
def loop("k", :normal) do
IO.write([IO.ANSI.cursor_up(1)])
loop(IO.getn("", 1), :normal)
end
def loop("h", :normal) do
IO.write([IO.ANSI.cursor_left(1)])
loop(IO.getn("", 1), :normal)
end
def loop("l", :normal) do
IO.write([IO.ANSI.cursor_right(1)])
loop(IO.getn("", 1), :normal)
end
def loop("R", :normal) do
mode = overwrite_mode()
loop(IO.getn("", 1), mode)
end
def loop("r", :normal) do
mode = replace_mode()
loop(IO.getn("", 1), mode)
end
def loop("i", :normal) do
mode = insert_mode()
loop(IO.getn("", 1), mode)
end
def loop("o", :normal) do
IO.write("\e[E\e[L")
mode = insert_mode()
loop(IO.getn("", 1), mode)
end
def loop("O", :normal) do
IO.write("\e[L")
mode = insert_mode()
loop(IO.getn("", 1), mode)
end
def loop("d", :normal) do
# We can easily append keysequences -- nice!
loop("d" <> IO.getn("", 1), :normal)
end
def loop("dd", :normal) do
delete_line()
loop(IO.getn("", 1), :normal)
end
def loop(_last, :normal) do
loop(IO.getn("", 1), :normal)
end
defp cursor_start() do
IO.write([IO.ANSI.cursor(0, 0)])
end
defp delete_line() do
# This causes the modeline to move out of correct position, since there's no distinction between the screen and a "buffer"
IO.write("\e[M")
end
defp set_modeline(terminal) do
mode = terminal.mode |> Atom.to_string() |> String.upcase()
{:ok, row} = :io.rows()
{bg, fg} =
case terminal.mode do
:insert -> {IO.ANSI.green_background(), IO.ANSI.black()}
:replace -> {IO.ANSI.yellow_background(), IO.ANSI.black()}
:overwrite -> {IO.ANSI.red_background(), IO.ANSI.black()}
_ -> {IO.ANSI.cyan_background(), IO.ANSI.black()}
end
write_preserving(
[bg, fg, "\e[2K#{mode}", IO.ANSI.reset()],
row,
0
)
end
defp write_preserving(text, row, column) do
IO.write("\e[s\e[#{row};#{column}H")
IO.write(text)
IO.write("\e[u")
end
defp insert_mode do
set_modeline(%{mode: :insert})
IO.write(["\e[4h", "\e[5 q"])
:insert
end
defp overwrite_mode do
set_modeline(%{mode: :overwrite})
IO.write(["\e[4l", "\e[1 q"])
:overwrite
end
defp replace_mode do
set_modeline(%{mode: :replace})
IO.write(["\e[4l", "\e[1 q"])
:replace
end
defp normal_mode do
set_modeline(%{mode: :normal})
IO.write(["\e[4l", "\e[2 q"])
:normal
end
end
RawModeVim.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment