Skip to content

Instantly share code, notes, and snippets.

@gabehollombe
Last active June 27, 2023 23:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gabehollombe/064a90351c204381832d749ca6b569e0 to your computer and use it in GitHub Desktop.
Save gabehollombe/064a90351c204381832d749ca6b569e0 to your computer and use it in GitHub Desktop.
#! /usr/bin/env ruby
require 'curses'
include Curses
include Curses::Key
class Fixnum
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
ENTER = 13
def main
raw
noecho
nonl
stdscr.keypad = true
init_screen
buffer = Buffer.new
begin
loop do
char = getch
buffer = case char
when CTRLC then break
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
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
Editor.new.main
@nroose
Copy link

nroose commented Feb 26, 2021

I modified it a little to make it edit a file. https://gist.github.com/nroose/32997db80a45b570bc53174dbe7a5e66

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment