Skip to content

Instantly share code, notes, and snippets.

@nroose
Last active June 5, 2022 19:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nroose/32997db80a45b570bc53174dbe7a5e66 to your computer and use it in GitHub Desktop.
Save nroose/32997db80a45b570bc53174dbe7a5e66 to your computer and use it in GitHub Desktop.
#! /usr/bin/env ruby
# adapted from https://gist.github.com/gabehollombe/064a90351c204381832d749ca6b569e0
# but it actually edits a file!
# `editor.rb [filename]`
# arrows, letters, backspace... Just the basics.
require 'curses'
include Curses
include Curses::Key
class Integer
def clamp(low, high)
v = self
v = low if self < low
v = high if self > high
v
end
end
class Cursor
attr_reader :row, :col
def initialize(row = 0, col = 0)
@row = row
@col = col
end
def right
Cursor.new @row, @col + 1
end
def left
Cursor.new @row, @col - 1
end
def down
Cursor.new @row + 1, @col
end
def up
Cursor.new @row - 1, @col
end
end
class Buffer
attr_reader :lines, :cursor
def initialize(lines = [], cursor = Cursor.new)
@lines = lines
@cursor = cursor
end
def cur_row
@cursor.row
end
def cur_col
@cursor.col
end
def current_line
@lines[cur_row]
end
def add_char(char)
lines = @lines.dup
line = lines[cur_row] || []
line.insert cur_col, char
lines[cur_row] = line
Buffer.new lines, @cursor.right
end
def remove_char_before_cursor
return Buffer.new @lines, @cursor if cur_col == 0
lines = @lines.dup
line = lines[cur_row] || []
line.delete_at(cur_col - 1)
lines[@cursor.row] = line
Buffer.new lines, @cursor.left
end
def new_line
lines = @lines.dup
line, next_line = current_line.partition.with_index { |_, i| i < cur_col }
lines[cur_row] = line
lines = lines.insert cur_row + 1, next_line
Buffer.new lines, Cursor.new(cur_row + 1, 0)
end
def beginning_of_line
Buffer.new lines, Cursor.new(cur_row, 0)
end
def end_of_line
Buffer.new lines, Cursor.new(cur_row, current_line.length)
end
def cursor_left
Buffer.new lines, clamp(@cursor.left)
end
def cursor_right
Buffer.new lines, clamp(@cursor.right)
end
def cursor_up
Buffer.new lines, clamp(@cursor.up)
end
def cursor_down
Buffer.new lines, clamp(@cursor.down)
end
def clamp(cursor)
r = cursor.row.clamp(0, @lines.count - 1)
c = cursor.col.clamp(0, @lines[r].length)
Cursor.new r, c
end
end
class Editor
BACKSPACE = 127
CTRLA = 1
CTRLC = 3
CTRLE = 5
CTRLX = 24
ENTER = 13
def main(buffer = nil)
raw
noecho
nonl
stdscr.keypad = true
init_screen
buffer ||= Buffer.new
render buffer
begin
loop do
char = getch
buffer =
case char
when CTRLX then break
when CTRLC then exit
when CTRLA then buffer.beginning_of_line
when CTRLE then buffer.end_of_line
when KEY_UP then buffer.cursor_up
when KEY_DOWN then buffer.cursor_down
when KEY_LEFT then buffer.cursor_left
when KEY_RIGHT then buffer.cursor_right
when BACKSPACE then buffer.remove_char_before_cursor
when ENTER then buffer.new_line
when /[[:print:]]/ then buffer.add_char(char)
else buffer
end
render buffer
end
ensure
close_screen
end
buffer
end
def render(buffer)
clear
buffer.lines.each_with_index do |line, index|
setpos index, 0
addstr(line.join)
end
setpos buffer.cursor.row, buffer.cursor.col
refresh
end
end
buf = Buffer.new File.read(ARGV[0]).lines.to_a.map { |r| r.scan(/[[:print:]]/) }, Cursor.new(0, 0) unless ARGV[0].nil?
buf = Editor.new.main buf
if ARGV[0].nil?
print "file name? ('q' to exit without save): "
filename = gets.strip
else
filename = ARGV[0]
end
File.write(filename, buf.lines.map { |line| line.join('') }.join("\n")) unless filename == 'q'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment