#!/usr/bin/lua local ttysettings_file function setup_screen() ttysettings_file = os.tmpname() os.execute("stty -g > "..ttysettings_file) -- hope no weird chars in filename! os.execute("stty cbreak -echo -ixon") end function restore_screen() os.execute("stty $(cat "..ttysettings_file..")") os.remove(ttysettings_file) end function termout(s) io.stdout:write(s) end function termin() return io.stdin:read(1) end function reset_terminal() termout("\27c") end function home_cursor() termout("\27[H") end function read_file(filename) return { search_forward = search_forward , data = file_contents(filename) , filename = filename , eobp = eobp , kill_line = kill_line , redraw = redraw_buf , execute = execute , save = save , backup = backup_buf , pos = 0 , forward = forward_buf , delete = delete_in_buf , insert = insert_in_buf } end function file_contents(filename) local fd = io.open(filename, "r") local data = fd:read("*all") fd:close() return data end function redraw_buf(self) reset_terminal() termout(string.sub(self.data, 1, self.pos - 1 ) )--  ) termout("|") termout(string.sub(self.data, self.pos)) end function execute(self, command) if command == "\2" then self:backup() -- control B elseif command == "\6" then self:forward() -- control F elseif command == "\127" then self:delete() -- delete character elseif command == "\11" then self:kill_line() -- control K elseif command == "\19" then self:search_forward() -- control S else self:insert(command) end end function kill_line(self) while not self:eobp() and string.sub(self.data, self.pos, 1) ~= "\n" do self:forward() self:delete() end if not self:eobp() then self:forward(); self:delete() end end function eobp(self) return self.pos == #(self.data) + 1 end function save(self) local fd = io.open(self.filename, "w") fd:write(self.data) fd:close() end function backup_buf(self) if self.pos < 3 then self.pos = 1 else self.pos = self.pos - 1 end end function forward_buf(self) if self.pos > #(self.data) then self.pos = #(self.data) + 1 else self.pos = self.pos + 1 end end function delete_in_buf(self) if self.pos > 1 then self.data = string.sub(self.data, 1, self.pos - 2) .. string.sub(self.data, self.pos) self.pos = self.pos - 1 end end function insert_in_buf(self, character) self.data = string.sub(self.data, 1, self.pos - 1) .. character .. string.sub(self.data, self.pos) self.pos = self.pos + 1 end function search_forward(self) -- really, really simple approach: read a string and go for it termout("/") local needle = "" while 1 do local char = termin() if char == "\10" then break end -- Return key, icrnl makes it newline character if char == "\8" then needle = string.sub(needle, 1, #needle - 1) termout("\8 \8") else needle = needle..char termout(char) end end local newpos = string.find(self.data, needle, self.pos + 1) if newpos == nil then newpos = string.find(self.data, needle) end if newpos == nil then newpos = self.pos end self.pos = newpos end function editormain(filename) local buf = read_file(filename) setup_editor() while 1 do buf:redraw() local command = read_command() if command == exit_command then break end buf:execute(command) end buf:save() end function read_file(filename) return { search_forward = search_forward , data = file_contents(filename) , filename = filename , eobp = eobp , kill_line = kill_line , redraw = redraw_buf , execute = execute , save = save , backup = backup_buf , pos = 0 , forward = forward_buf , delete = delete_in_buf , insert = insert_in_buf } end function setup_editor() setup_screen() reset_terminal() end function read_command() return io.stdin:read(1) end exit_command = "\24" local filename = ... assert(filename) editormain(filename) restore_screen()