Last active
August 24, 2022 12:55
-
-
Save pjambet/660b18a3f6b5a056325cc414e8f55f48 to your computer and use it in GitHub Desktop.
A very basic text editor in ruby, copied from Kilo and adapted in Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'termios' | |
s = [0, 0, 0, 0].pack("S_S_S_S_") | |
STDOUT.ioctl(Termios::TIOCGWINSZ, s) | |
HEIGHT, WIDTH, _, _ = s.unpack("S_S_S_S_") | |
# Raw mode | |
current = Termios.tcgetattr(STDIN) | |
t = current.dup | |
t.c_iflag &= ~(Termios::BRKINT | Termios::ICRNL | Termios::INPCK | Termios::ISTRIP | Termios::IXON) | |
t.c_oflag &= ~(Termios::OPOST) | |
t.c_cflag |= (Termios::CS8) | |
t.c_lflag &= ~(Termios::ECHO | Termios::ICANON | Termios::IEXTEN | Termios::ISIG) | |
t.c_cc[Termios::VMIN] = 1 # Setting 0 as in Kilo raises EOF errors | |
Termios.tcsetattr(STDIN, Termios::TCSANOW, t) | |
trap("INT") { | |
Termios.tcsetattr(STDIN, Termios::TCSANOW, current) | |
exit(0) | |
} | |
TEXT_CONTENT = [""] | |
CURSOR_POSITION = [1, 1] | |
def refresh | |
append_buffer = "" | |
append_buffer << "\x1b[?25l" # Hide cursor | |
append_buffer << "\x1b[H" | |
# stderr_log TEXT_CONTENT | |
HEIGHT.times do |row_index| | |
if row_index >= TEXT_CONTENT.count | |
append_buffer << "~\x1b[0K\r\n" | |
# stderr_log("Row index: #{row_index}") | |
# stderr_log("Line count: #{TEXT_CONTENT.count}") | |
next | |
end | |
row = TEXT_CONTENT[row_index] || "" | |
# stderr_log "'#{row}'" | |
append_buffer << row | |
append_buffer << "\x1b[39m" | |
append_buffer << "\x1b[0K" | |
append_buffer << "\r\n" | |
end | |
append_buffer.strip! | |
append_buffer << "\x1b[H" | |
x, y = CURSOR_POSITION | |
# x += 1 if x > 0 | |
append_buffer << "\x1b[#{y};#{x}H" | |
# append_buffer << "\x1b[;1H" | |
append_buffer << "\x1b[?25h" # Show cursor | |
stderr_log("'#{append_buffer}'".inspect) | |
stderr_log("Cursor postition: x: #{CURSOR_POSITION[0]}, y: #{CURSOR_POSITION[1]}: #{y};#{x}H") | |
STDOUT.write(append_buffer) | |
end | |
def current_row | |
TEXT_CONTENT[CURSOR_POSITION[1] - 1] | |
end | |
def stderr_log(message) | |
unless STDERR.tty? # true when not redirecting to a file, a little janky but works for what I want | |
STDERR.puts(message) | |
end | |
end | |
loop do | |
refresh | |
c = STDIN.readpartial(1) | |
if c == "q" | |
Process.kill("INT", Process.pid) | |
end | |
# stderr_log("ord: #{c.ord}") | |
if c.ord == 13 # enter | |
if current_row.length > (CURSOR_POSITION[0] - 1) | |
carry = current_row[(CURSOR_POSITION[0] - 1)..-1] | |
current_row.slice!((CURSOR_POSITION[0] - 1)..-1) | |
else | |
carry = "" | |
end | |
TEXT_CONTENT.insert(CURSOR_POSITION[1], carry) | |
CURSOR_POSITION[0] = 1 | |
CURSOR_POSITION[1] += 1 | |
elsif c.ord == 127 # backspace | |
next if CURSOR_POSITION[0] == 1 && CURSOR_POSITION[1] == 1 | |
if CURSOR_POSITION[0] == 1 | |
if current_row.nil? | |
TEXT_CONTENT.delete_at(CURSOR_POSITION[1] - 1) | |
CURSOR_POSITION[1] -= 1 | |
CURSOR_POSITION[0] = current_row.length + 1 | |
elsif current_row.empty? | |
TEXT_CONTENT.delete_at(CURSOR_POSITION[1] - 1) | |
CURSOR_POSITION[1] -= 1 | |
CURSOR_POSITION[0] = current_row.length + 1 | |
else | |
previous_row = TEXT_CONTENT[CURSOR_POSITION[1] - 2] | |
CURSOR_POSITION[0] = previous_row.length + 1 | |
TEXT_CONTENT[CURSOR_POSITION[1] - 2] = previous_row + current_row | |
TEXT_CONTENT.delete_at(CURSOR_POSITION[1] - 1) | |
CURSOR_POSITION[1] -= 1 | |
end | |
else | |
deletion_index = CURSOR_POSITION[0] - 2 | |
current_row.slice!(deletion_index) | |
CURSOR_POSITION[0] -= 1 | |
end | |
elsif c.ord == 27 # ESC | |
second_char = STDIN.read_nonblock(1, exception: false) | |
next if second_char == :wait_readable | |
third_char = STDIN.read_nonblock(1, exception: false) | |
next if third_char == :wait_readable | |
if second_char == "[" | |
case third_char | |
when "A" # Up | |
CURSOR_POSITION[1] -= 1 unless CURSOR_POSITION[1] == 1 | |
if current_row && CURSOR_POSITION[0] > current_row.length + 1 | |
CURSOR_POSITION[0] = current_row.length + 1 | |
end | |
when "B" # Down | |
if CURSOR_POSITION[1] == TEXT_CONTENT.length | |
CURSOR_POSITION[0] = 1 | |
end | |
CURSOR_POSITION[1] += 1 unless CURSOR_POSITION[1] == TEXT_CONTENT.length + 1 | |
if current_row && CURSOR_POSITION[0] > current_row.length + 1 | |
CURSOR_POSITION[0] = current_row.length + 1 | |
end | |
when "C" # Right | |
# stderr_log("Current row: #{current_row}\n") | |
if current_row && CURSOR_POSITION[0] > current_row.length | |
if CURSOR_POSITION[1] <= TEXT_CONTENT.length + 1 | |
CURSOR_POSITION[0] = 1 | |
CURSOR_POSITION[1] += 1 | |
end | |
elsif current_row | |
CURSOR_POSITION[0] += 1 | |
end | |
when "D" # Left | |
if CURSOR_POSITION[0] == 1 | |
if CURSOR_POSITION[1] > 1 | |
CURSOR_POSITION[1] -= 1 | |
CURSOR_POSITION[0] = current_row.length + 1 | |
end | |
else | |
CURSOR_POSITION[0] -= 1 | |
end | |
when "H" then "H" # Home | |
when "F" then "F" # End | |
end | |
end | |
# TEXT_CONTENT.last << third_char | |
elsif c.ord >= 32 && c.ord <= 126 | |
if current_row.nil? | |
TEXT_CONTENT << "" | |
end | |
current_row.insert(CURSOR_POSITION[0] - 1, c) | |
CURSOR_POSITION[0] += 1 | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'termios' | |
s = [0, 0, 0, 0].pack("S_S_S_S_") | |
STDOUT.ioctl(Termios::TIOCGWINSZ, s) | |
HEIGHT, WIDTH, _, _ = s.unpack("S_S_S_S_") | |
TEXT_CONTENT = [""] | |
current = Termios.tcgetattr(STDIN) | |
t = current.dup | |
# https://man7.org/linux/man-pages/man3/tcflush.3.html | |
t.c_lflag &= ~(Termios::ICANON); | |
Termios.tcsetattr(STDIN, Termios::TCSANOW, t) | |
def refresh | |
append_buffer = "" | |
append_buffer << "\x1b[?25l" # Hide cursor | |
append_buffer << "\x1b[H" | |
HEIGHT.times do |row_index| | |
if row_index >= TEXT_CONTENT.count | |
append_buffer << "~\x1b[0K\r\n" | |
next | |
end | |
end | |
append_buffer << "\x1b[H" | |
append_buffer << TEXT_CONTENT.join("") | |
STDOUT.write(append_buffer) | |
end | |
loop do | |
refresh | |
c = STDIN.readpartial(1) | |
exit(0) if c == "q" | |
if c.ord >= 32 && c.ord <= 126 | |
TEXT_CONTENT.last << c | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment