But Matz, my Ruby is real bad. It looks like my Pascal did. I never
did manage to get my head around this new fangled stuff. I'm 41, and
i learned to program when i was about 10, on a pdp 11. Anyway, I'm
getting off topic. Did I thank you for this wonderful language.
Anyway, here goes... these are the two main files...
···
On Tue, May 25, 2010 at 2:23 PM, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:
I feel sympathy, but above code snippet help us nothing. If you need
help from the list, you'd better disclose everything, to reproduce the
problem, no matter how bad your code is right now.
#
# Full Screen EDitor (FSED) for QUARKware QBBS.
#
# Copyright (C) 2002, Dossy <dossy@panoptic.com>
# All rights reserved.
#
# $Id: fsed.rb,v 1.1 2002/09/12 12:27:16 dossy Exp $
#
module Editors
module FSED
VERSION = "0.75"
ESC = 27.chr
RETURN = 10.chr
class Buffer
def initialize(max_lines,in_file)
@buffer = getfile(in_file)
@max_lines = max_lines
end
def =(x, y, value)
if @buffer[y - 1] == nil
@buffer[y - 1] =
end
@buffer[y - 1][x - 1] = value
end
def (x, y)
if @buffer[y - 1].nil?
nil
else
@buffer[y - 1][x - 1]
end
end
def clear
@buffer =
end
def length
@buffer.length
end
def delete_at(x, y)
unless @buffer[y - 1].nil?
@buffer[y - 1].slice!(x - 1) # used to use delete_at but that
didn't always work.
end
end
def insert_at_line(y,x,in_str)
inthing = nil
if !in_str.nil? then
in_str.each_with_index {|c,i| @buffer[y-1].insert(x+ i,c)}
end
end
def insert_line_at(y,ln)
@buffer[y] = if @buffer[y] == nil
@buffer.insert(y,ln)
end
def delete_line_at(y)
@buffer.delete_at(y - 1)
end
def buffer_length
return @buffer.length
end
def length_y(y)
if !@buffer[y - 1].nil?
return @buffer[y - 1].length
else
return 0
end
end
def del_range(y,x1,x2)
total = x2 - x1
str = @buffer[y-1].slice!(-(total),total)
return str
end
def insert_char(x, y, value)
@buffer[y - 1] = if @buffer[y - 1] == nil
if (x-1) > @buffer[y-1].length then
@buffer[y-1] << value
else
@buffer[y - 1].insert(x-1,value)
end
end
def find_first_space(y)
return @buffer[y-1].index(" ")
end
def find_nearest_space(y,space)
result = nil
@buffer[y - 1] = if @buffer[y - 1].nil?
highest = 0
@buffer[y-1].each_with_index {|c,i|
highest = i
result = i if (c == " ") and (i <= space)
}
result= highest if highest <= space
return result
end
def paragraph_up(start_y,width)
for i in start_y..@buffer.length #- 1
break if @buffer[i,0].nil?
pos = length_y(i-1)
space = width - pos #how much space on the line above?
unwrap_space = find_nearest_space(i,space)
break if unwrap_space.nil? or unwrap_space == 0
unwrap_str = del_range(i,0,unwrap_space+1)
insert_at_line(i - 1,pos,unwrap_str)
end
end
def detect_and_wrap(y,x,width)
l_space = 0; wrap = nil
@buffer[y - 1] = if @buffer[y - 1].nil?
l = @buffer[y-1].length - 1
test = @buffer[y-1]
if !@buffer[y-1][width].nil? then #is there now a character past max_width
test.slice!(-1) if test.last == " "
l_space = test.rindex(' ')
wrap = @buffer[y-1][l_space..l]
del_range(y,l_space+1,l+1)
return [l_space,wrap]
end
end
def line(line)
if !@buffer[line - 1].nil? then #protect against backspace on
an empty line -- produces a nil
@buffer[line - 1].collect { |char|
if char.nil?
" "
else
char
end
}.to_s.chomp
end
end
def buff_out
@buffer.collect { |line|
if line.nil?
"\n"
else
[ line.collect { |char|
if char.nil?
" "
else
char
end
}, "\n" ].to_s
end
}
end
def dump
@buffer.collect { |line|
if line.nil?
"\n"
else
[ line.collect { |char|
if char.nil?
" "
else
char
end
}, "\n" ].to_s
end
}.to_s.chomp
end
# def display
# i = 0
# @buffer.collect { |line| i = i + 1
# if line.nil?
# "#{i}: \n"
# else
# "#{i}: " << [ line.collect { |char|
# if char.nil?
# " "
# else
# char
# end
# }, "\n" ].to_s
# end
# }.to_s.chomp
#end
def to_s(top_start,vp_height)
out = String.new
top_stop = @buffer.length - 1
top_stop = vp_height + top_start - 1 if @buffer.length - 1 >=
vp_height + top_start
for i in top_start..top_stop
one_line = @buffer[i].to_s
out = out+ one_line + "\n"
end
out
end
def room_on_line(y,str,width)
room = false
if !@buffer[y].nil?
if !str.nil? then
room = true if (@buffer[y].length - 1) + str.length < width
end
end
return room
end
def getfile(filename)
file_array =
if !filename.nil? then
#this used to use chars.to_a but it didn't always work right
if File.exists?(filename)
IO.foreach(filename) { |line|
line.gsub!("\n","")
line.gsub!("\r","")
build =
for i in 0..line.length-1
build << line[i].chr
end
file_array << build}
end
end
puts
file_array
end
end
class EditorState
attr_reader :current_cursor_position, :previous_cursor_position
attr_reader :screen_width, :screen_height
attr_reader :viewport_width, :viewport_height
attr_reader :header_height
attr_reader :buffer
def initialize(width, height,in_file)
@dirty = true
@current_cursor_position = [1, 1]
@previous_cursor_position = [1, 1]
@screen_width = width
@screen_height = height
@buffer = Buffer.new(500,in_file)
@buffer_top = 0
@header_height = 2
@viewport_width = @screen_width
@viewport_height = @screen_height - @header_height
@wrapped = false
@insert = true
open_error_log
end
require "windows.rb"
def open_error_log
$lf = File.new("debug.txt", File::CREAT|File::TRUNC|File::RDWR, 0644)
end
def current_x
@current_cursor_position[0]
end
def current_y
@current_cursor_position[1]
end
def place_cursor(x, y)
@previous_cursor_position = @current_cursor_position
@current_cursor_position = [x, y]
@dirty = true
end
def move_cursor_up(x)
result = NO_REDRAW
new_y = current_y - x
if new_y < 1 then
new_y = 1
@buffer_top -=x if @buffer_top > 0
result = REDRAW
end
place_cursor(current_x, new_y)
return result
end
def page_up
redraw = move_cursor_up(@viewport_height)
return redraw
end
def move_cursor_down(x)
result = NO_REDRAW
new_y = current_y + x
@wrapped = false
if new_y >=@viewport_height then
new_y = (current_y)
@buffer_top += x
result = REDRAW
end
place_cursor(current_x, new_y)
return result
end
def page_down
down = @buffer.buffer_length - (current_y+ @buffer_top)
if down > @viewport_height then
redraw = move_cursor_down(@viewport_height)
return redraw
else
return NO_REDRAW
end
end
def move_cursor_left(x)
new_x = current_x - x
new_x = 1 if new_x < 1
place_cursor(new_x, current_y)
end
def home_cursor
place_cursor(1,current_y)
end
def move_cursor_right(x)
new_x = current_x + x
new_x = @viewport_width if new_x > @viewport_width
place_cursor(new_x, current_y)
end
def end_cursor
end_line = @buffer.length_y(current_y)+1
end_line = @viewport_width if end_line > @viewport_width
place_cursor(end_line,current_y)
end
def clear_screen
"#{ESC}[2J#{ESC}[H#{ESC}[00m"
end
def toggle_ins
@insert = !@insert
end
def parse_c(line)
COLORTABLE.each_pair {|color, result| line.gsub!(color,result) }
return line
end
def header
out_str = "INS"
out_str = "OVR" if !@insert
out = String.new
out << parse_c("%WQuark%YEDIT #{VERSION}%W".fit(79)) +"\n"
out << parse_c("%YCTRL + e%YX%Wit %Y|%W %YG%W Help %Y|%W %YS%Wave
%Y|%W %YN%Wewline %Y|%W %YY%W Delete %Y|%W #{out_str} %Y|%W Line:
#{current_y + @buffer_top}".fit(79)) << "\n"
out << bg("black") << fg("WHITE")
return out
end
def redraw(force)
@dirty = true if force
if @dirty
@dirty = false
if force
[clear_screen,
header,
buffer.to_s(@buffer_top,@viewport_height),
update_cursor_position].to_s
else
""
end
else
""
end
end
def clear
@buffer.clear
end
# this is complicated... too complicated...
def input_char_at_cursor(c)
if @insert then #we are
in insert mode
@buffer.insert_char(current_x,(current_y) + @buffer_top,c)
l_space,wrap = @buffer.detect_and_wrap(current_y,current_x,@screen_width - 1)
if !wrap.nil? then
if !(current_x < @buffer.length_y(current_y) - 1) then
#no wrap... insert a character
if (current_x + wrap.length) < @screen_width then
move_cursor_right(1)
return [c,NO_REDRAW]
else
#wrap at insert at end of line
@buffer.insert_line_at(current_y,wrap.to_s.strip!)
home_cursor
move_cursor_right(wrap.length - 1)
move_cursor_down(1)
end
$lf.print "I'm here...wrap line\n"
return [nil,REDRAW] #we don't want a character
printed because we are in overflow
end
$lf.print "@buffer.length + wrap.length:
#{@buffer.length_y(current_y) +(wrap.length + 2)}\n"
$lf.print "@screen_width: #{@screen_width}\n"
$lf.print "room on line: #{@buffer.room_on_line(current_y +
1,wrap,@screen_width)}\n"
$lf.print "@buffer.length: #{@buffer.length}\n"
if (@buffer.length_y(current_y) + (wrap.length + 2)) >=
@screen_width then #wrap for insert not at end of line
$lf.print "in insert not at end of line...\n"
if @buffer.room_on_line(current_y + 1,wrap,@screen_width) then
@buffer.insert_at_line(current_y+1,0,wrap) #subsequent words go
to next line
$lf.print "on next existing line...\n"
else
@buffer.insert_line_at(current_y,wrap) #out of room so make a new line
$lf.print "on a new line...\n"
end
move_cursor_right(1)
$lf.print "done with insert before line\n"
return [c,REDRAW]
end
end
move_cursor_right(1) #redraw because we are inserting...
if (@buffer.length_y(current_y)+1) == current_x then
return [c,NO_REDRAW] #insert mode at eol so no redraw
else
return [c,REDRAW] #insert mode not at eol, so redraw
end
else
if current_x < @screen_width then
@buffer[current_x,(current_y) + @buffer_top] = c #we are in
over-write mode....
move_cursor_right(1)
$lf.print "Overwrite Mode\n"
return [c,NO_REDRAW]
else
$lf.print "I'm here...sixth return\n"
return [nil,NO_REDRAW]
end
end
end
def newline
if @buffer.length_y(current_y) == 0 then
@buffer.insert_line_at(current_y,nil)
move_cursor_down(1)
else
str = @buffer.del_range(current_y,current_x-1,@buffer.length_y(current_y))
@buffer.insert_line_at(current_y,str)
move_cursor_left(current_x)
move_cursor_down(1)
end
@dirty = true
end
def deleteline
@buffer.delete_line_at(current_y)
end
def backspace
if current_x > 1 then #normal delete, not at BOL
move_cursor_left(1)
@buffer.delete_at(current_x, current_y)
else
if current_y > 1 then # delete at BOL
if @buffer.length_y(current_y-1) == 0 then #blank line above
@buffer.delete_line_at(current_y-1)
move_cursor_up(1)
else
@buffer.paragraph_up(current_y,@screen_width) #move up until you
hit a blank line...
move_cursor_up(1)
home_cursor
move_cursor_right(@buffer.length_y(current_y))
end
return REDRAW
end
end
return REDRAW
end
def update_cursor_position
"#{ESC}[#{current_y + @header_height};#{current_x}H"
end
def w_update_cursor(x,y)
"#{ESC}[#{y + @header_height};#{x}H"
end
def w_clear
return @c.reset
end
def fg(forground)
out = String.new
case forground
when "red"
out = "e[31m"
when "RED"
out = "e[;1;31m"
when "green"
out << "e[32m"
when "GREEN"
out = "e[;1;32m"
when "blue"
out = "e[34m"
when "BLUE"
out = "e[;1;34m"
when "cyan"
out = "e[36m"
when "CYAN"
out = "e[;1;36m"
when "magenta"
out = "e[35m"
when "MAGENTA"
out = "e[;1;35m"
when "yellow"
out = "e[33m"
when "YELLOW"
out = "e[;1;33m"
when "black"
out = "e[30m"
when "BLACK"
out = "e[;1;30m"
when "hide"
out = "e[?25l"
when "show"
out = "e[?25h"
when "reset"
out = "e[0m"
end
return out
end
def bg(background)
out = String.new
case background
when "red"
out = "e[41m"
when "green"
out = "e[42m"
when "blue"
out = "e[44m"
when "cyan"
out = "e[46m"
when "magenta"
out = "e[45m"
when "yellow"
out = "e[43m"
when "black"
out = "e[40m"
when "white"
out = "e[47m"
end
return out
end
def center(string,width,color)
result = String.new
outdash = ((width / 2 ) - (string.length / 2))
outdash.times {result << " "}
result << color if !color.nil?
result << string
(width - (outdash + string.length)).times {result << " "}
return result
end
def make_window(startx,starty,width,height,forground,background,border,title)
f_color = fg(forground)
b_color = bg(background)
bdr_color = bg(border)
window = String.new
window << w_update_cursor(startx,starty)
window << bdr_color
window << center(title,width,nil)
for i in 1..height do
window << w_update_cursor(startx,starty+i)
window << bdr_color << " " << b_color
(width - 2).times {window << " "}
window << bdr_color << " "
window << bg("white") << " "
end
window << w_update_cursor(startx,starty+height) << bdr_color
width.times {window << " "}
window << w_update_cursor(startx+1,starty+height+1) << bg("white")
width.times {window << " "}
window << w_update_cursor(startx,starty)
return window
end
def help_window
idt = 16
str = 5
width=58
out = make_window(str,2,60,12,"BLACK","cyan","blue","Help Window")
out << w_update_cursor(idt,str) << fg("yellow") << bg("cyan")
out << "CTRL-A" << fg("white") << " Abort Message"
out << w_update_cursor(idt,str+1) << fg("yellow")
out << "CTRL-L" << fg("white") << " Refresh Screen"
out << w_update_cursor(idt,str+2) << fg("yellow")
out << "CTRL-N" << fg("white") << " New Line"
out << w_update_cursor(idt,str+3) << fg("yellow")
out << "CTRL-X" << fg("white") << " Save (Post) Message"
out << w_update_cursor(idt,str+4) << fg("yellow")
out << "CTRL-Y" << fg("white") << " Delete Line"
out << w_update_cursor(idt,str+6) << fg("yellow")
out << "INSERT" << fg("white") << " Toggle Insert/Overwrite"
out << w_update_cursor(idt,str+8)
out << "ESC to exit this window." << fg("white")
end
def splash_window
idt = 15
str = 8
width = 38
out = make_window(idt-1,str-1,40,8,"WHITE","magenta","cyan","About")
out << w_update_cursor(idt,str+1) <<fg("yellow") << bg("magenta")
out << center("QUARKedit #{VERSION}",width,fg("white"))
out << w_update_cursor(idt,str+3)
out << center("By Dossy and Mark Firestone",width,fg("white"))
out <<w_update_cursor(idt+36,str+7)
#out << fg("hide")
end
def yes_no_window(question)
idt = 11
str = 10
w_width = question.length + 14
width = w_width - 2
out = make_window(idt-2,str-1,w_width,4,"WHITE","red","yellow","Confirm")
out << w_update_cursor(idt+1,str+1)
out << fg("WHITE") << bg("red")
out << question << "(Y,n): "
end
def screen_clear # I don't know why. Needs two redraws or the
background color is wrong....
out = redraw(true)
out << redraw(true)
return out
end
end #of class
class Editor
#Window Constants
MESSAGE = 1
ABORT = 2
SAVE = 3
SPELL = 4
def initialize(width, height, in_io, out_io,in_file,bbs_mode)
@state = EditorState.new(width, height,in_file)
@in_io = in_io
@out_io = out_io
@w_mode = false
@w_type = MESSAGE
@supress = false
@bbs_mode = bbs_mode
end
def run
@out_io.sync = true
@in_io.sync = false
@out_io.print @state.screen_clear
@out_io.print @state.redraw(true)
@out_io.print @state.splash_window
sleep(1)
@out_io.print @state.redraw(true)
@out_io.print @state.redraw(true)
buf = nil
while true
if select([@in_io], nil, nil, 1)
$lf.print "I made it"
c = @in_io.sysread(1)
$lf.print "after sysread"
#c = @in_io.getc
# $lf.print"c: #{c}\n"
# $lf.print"c-chr: #{c[0]}\n"
# if @supress then # if we are suppressing the mysterious
extra linefeed... we do that here.
# @supress = false
# c = 0.chr if c.bytes.to_a[0] = 10
# end
if @w_mode then #We are in window mode, not edit mode...
# $lf.print "in wmode\n"
#$lf.print "c: #{c.upcase}"
case c
when "\e" #effectively, esc this is cancel for everything
@w_mode = false
@out_io.print @state.screen_clear
else
case @w_type
when ABORT,SAVE
if c.upcase == "Y" or c == RETURN then
print "Y"
@state.clear if @w_type == ABORT
@state.clear_screen
sleep(2)
break
else
@out_io.print @state.screen_clear
@w_mode = false
end
end
end
else
case c
when "\cX" # exit
@out_io.print @state.yes_no_window("Post message... Are you sure?")
@w_type = SAVE
@w_mode = true
when "\cG","\eOP"
@out_io.print @state.help_window
@w_type = MESSAGE
@w_mode = true
when "\cA"
@out_io.print @state.yes_no_window("Abort message... Are you sure?")
@w_type = ABORT
@w_mode = true
when "\cN" #insert line
@state.newline
@out_io.print @state.redraw(true)
when "\cY" #delete line
@state.deleteline
@out_io.print @state.redraw(true)
when "\cL" # refresh
@out_io.print @state.redraw(true)
when "\r","\n"
@state.newline
@supress = true if @bbs_mode #telnet seems to like to echo
linefeeds. lets supress this ...
@out_io.print @state.redraw(true)
when "\010", "\177"
redraw = @state.backspace
@out_io.print "\e[#{@state.current_y + @state.header_height};1H\e[K"
@out_io.print @state.buffer.line(@state.current_y)
@out_io.print @state.update_cursor_position
@out_io.print @state.redraw(true) if redraw
when "\e" # escape
buf = c
else
if buf.nil?
chr = c.unpack("c")[0]
if (chr >= 32 && chr <= 127)
out_c,redraw = @state.input_char_at_cursor(c)
@out_io.putc(out_c) if !out_c.nil?
@out_io.print @state.redraw(true) if redraw
end
else
buf << c
# $lf.print "buf: #{buf}\n"
case buf
when "\e[H","\e[1"
@state.home_cursor
@out_io.print @state.update_cursor_position
when "\e[F","\e[4"
@state.end_cursor
@out_io.print @state.update_cursor_position
when "\e[6"
redraw = @state.page_down
@out_io.print @state.redraw(true) if redraw
when "\e[5"
redraw = @state.page_up
@out_io.print @state.redraw(true) if redraw
when "\e[2"
@state.toggle_ins
@out_io.print @state.redraw(true)
when "\e[A"
redraw = @state.move_cursor_up(1)
if redraw
@out_io.print @state.redraw(true)
else
@out_io.print @state.update_cursor_position
end
buf = nil
when "\e[B"
redraw = @state.move_cursor_down(1)
if redraw
@out_io.print @state.redraw(true)
else
@out_io.print @state.update_cursor_position
end
buf = nil
when "\e[D"
@state.move_cursor_left(1)
@out_io.print @state.update_cursor_position
buf = nil
when "\e[C"
@state.move_cursor_right(1)
@out_io.print @state.update_cursor_position
buf = nil
else
if buf.size >= 3
buf = nil
end
end
end
end
end
end
end
@state.buffer
end
end
end
end
file edit.rb:
require 'fsed'
require "tools.rb"
#require "raspell"
#Consts
SPELL_CHECK = true #if you don't want raspell, comment out the
require above.
COLORTABLE = {
'%R' => "\e[;1;31;44m", '%G' => "\e[;1;32;44m",
'%Y' => "\e[;1;33;44m", '%B' => "\e[;1;34;44m",
'%M' => "\e[;1;35;44m", '%C' => "\e[;1;36;44m",
'%W' => "\e[;1;37;44m", '%r' => "\e[;31;44m",
'%g' => "\e[;32;44m", '%y' => "\e[;33;44m",
'%b' => "\e[;34;44m", '%m' => "\e[;35;44m",
'%c' => "\e[;36;44m", '%w' => "\e[;31;44m"
}
REDRAW = true
NO_REDRAW = false
def tcgetattr(io)
_TCGETA = 0x5405
attr = [0, 0, 0, 0].pack("SSSS")
io.ioctl(_TCGETA, attr)
attr
end
def tcsetattr(io, attr)
_TCSETA = 0x5406
io.ioctl(_TCSETA, attr)
end
def writefile (filename,array)
lf = File.new(filename, File::WRONLY|File::TRUNC|File::CREAT, 0644)
array.each {|x|
print "."
lf.puts x}
lf.close
puts
end
def pull_apart_args(args)
bbs_mode = false
filename = nil
if !args.nil then
filename = args.last
args.each {|arg| bbs_mode = true if arg == "-L"
#put more switches here
}
end
return [bbs_mode,filename]
end
begin
unless RUBY_PLATFORM =~ /mswin32/
# turn off stdin buffering and echo
# c_iflag bits
INLCR = "0000100".to_i
IGNCR = "0000200".to_i
ICRNL = "0000400".to_i
# c_oflag bits
OPOST = "0000001".to_i
# c_lflag bits
ISIG = "0000001".to_i
ICANON = "0000002".to_i
ECHO = "0000010".to_i
old_attr = tcgetattr($stdin)
input, output, control, local = old_attr.unpack("SSSS")
# input &= ~(INLCR | IGNCR | ICRNL)
# output &= ~OPOST
#local &= ~(ECHO | ICANON | ISIG)
local &= ~(ECHO)
new_attr = [input, output, control, local].pack("SSSS")
tcsetattr($stdin, new_attr)
end
bbs_mode,in_file = pull_apart_args(ARGV)
puts "bbsmode: #{bbs_mode}"
sleep(2)
editor = Editors::FSED::Editor.new(80, 23, STDIN.to_io,
$>.to_io,in_file,bbs_mode)
buffer = editor.run
# if bbs_mode then
$lf.print "writing file...\n"
writefile(in_file,buffer.buff_out)
$lf.print "file written...\n"
#end
ensure
unless RUBY_PLATFORM =~ /mswin32/
tcsetattr($stdin, old_attr)
end
end
--
I know a mouse
And he hasn't got a house
I don't know why
I call him Gerald
He's getting rather old
But he's a good mouse
- Syd Barrett