#! /usr/bin/env ruby
# TODO
# - handle user input redirection
# - readline
# Credits:
# - Initial linux version: Gilles Filippini
# - Initial windows port : Marco Frailis
# - Currently maintained and developed by
# Martin DeMello <martindemello@gmail.com>
require "fox12"
require "irb"
require "singleton"
require "English"
include Fox
STDOUT.sync = true
class FXIRBInputMethod < IRB::StdioInputMethod
attr_accessor :print_prompt
def initialize
super
@history = 1
@begin = nil
@print_prompt = true
@end = nil
@continued_from = nil
end
def gets
if (a = @prompt.match(/(\d+)[>*]/))
level = a[1].to_i
else
level = 0
end
if level > 0
@continued_from ||= @line_no
elsif @continued_from
merge_last(@line_no-@continued_from+1)
@continued_from = nil
end
if @print_prompt
print @prompt
#indentation
print " "*level
end
str = FXIrb.instance.get_line
@line_no += 1
@history = @line_no + 1
@line[@line_no] = str
str
end
# merge a block spanning several lines into one \n-separated line
def merge_last(i)
return unless i > 1
range = -i..-1
@line[range] = @line[range].map {|l| l.chomp}.join("\n")
@line_no -= (i-1)
@history -= (i-1)
end
def prevCmd
if @line_no > 0
@history -= 1 unless @history <= 1
return line(@history)
end
return ""
end
def nextCmd
if (@line_no > 0) && (@history < @line_no)
@history += 1
return line(@history)
end
return ""
end
end
module IRB
def IRB.start_in_fxirb(im)
if RUBY_VERSION < "1.7.3"
IRB.initialize(nil)
IRB.parse_opts
IRB.load_modules
else
IRB.setup(nil)
end
irb = Irb.new(nil, im)
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
@CONF[:MAIN_CONTEXT] = irb.context
trap("SIGINT") do
irb.signal_handle
end
catch(:IRB_EXIT) do
irb.eval_input
end
print "\n"
end
end
class FXIrb < FXText
include Singleton
include Responder
attr_reader :input
def FXIrb.init(p, tgt, sel, opts)
unless @__instance__
Thread.critical = true
begin
@__instance__ ||= new(p, tgt, sel, opts)
ensure
Thread.critical = false
end
end
return @__instance__
end
def initialize(p, tgt, sel, opts)
FXMAPFUNC(SEL_KEYRELEASE, 0, "onKeyRelease")
FXMAPFUNC(SEL_KEYPRESS, 0, "onKeyPress")
FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,"onLeftBtnPress")
FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,"onMiddleBtnPress")
FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,"onLeftBtnRelease")
super
setFont(FXFont.new(FXApp.instance, "lucida console", 9))
@anchor = 0
end
def create
super
setFocus
# IRB initialization
@inputAdded = 0
@input = IO.pipe
$DEFAULT_OUTPUT = self
@im = FXIRBInputMethod.new
@irb = Thread.new {
while true do
ret = IRB.start_in_fxirb(@im); STDOUT.flush; STDERR.flush
self.crash(ret)
end
}
@multiline = false
@exit_proc = lambda {exit}
end
def on_exit(&block)
@exit_proc = block
end
def crash(ret)
#appendText("\nIRB exited abnormally. Restarting...\n\n")
# For some reason the call below crashes when the call
# is passed from FreeRIDE. So insert the code here
# TODO: fix the problem (thread related??) and find a way
# to make a distinction between normal "exit" and exit on
# a syntax error (didn't find how to catch and print error msg)
# see FR bug #4574
#instance_eval(&@exit_proc)
appendText("#{$!}\nIRB exited. Restarting...\n\n")
end
def onKeyRelease(sender, sel, event)
case event.code
when Fox::KEY_Return, Fox::KEY_KP_Enter
newLineEntered
end
return 1
end
def onKeyPress(sender,sel,event)
case event.code
when Fox::KEY_Return, Fox::KEY_KP_Enter
setCursorPos(getLength)
super
when Fox::KEY_Up,Fox::KEY_KP_Up
str = extractText(@anchor, getCursorPos-@anchor)
if str =~ /\n/
@multiline = true
super
setCursorPos(@anchor+1) if getCursorPos < @anchor
else
history(:prev) unless @multiline
end
when Fox::KEY_Down,Fox::KEY_KP_Down
str = extractText(getCursorPos, getLength - getCursorPos)
if str =~ /\n/
@multiline = true
super
else
history(:next) unless @multiline
end
when Fox::KEY_Left,Fox::KEY_KP_Left
if getCursorPos > @anchor
super
end
when Fox::KEY_Delete,Fox::KEY_KP_Delete,Fox::KEY_BackSpace
if getCursorPos > @anchor
super
end
when Fox::KEY_Home, Fox::KEY_KP_Home
setCursorPos(@anchor)
when Fox::KEY_End, Fox::KEY_KP_End
setCursorPos(getLength)
when Fox::KEY_Page_Up, Fox::KEY_KP_Page_Up
history(:prev)
when Fox::KEY_Page_Down, Fox::KEY_KP_Page_Down
history(:next)
when Fox::KEY_bracketright
#auto-dedent if the } or ] is on a line by itself
if (emptyline? or (getline == "en")) and indented?
dedent
end
super
when Fox::KEY_u
if (event.state & CONTROLMASK) != 0
str = extractText(getCursorPos, getLength - getCursorPos)
rmline
appendText(str)
setCursorPos(@anchor)
end
super
when Fox::KEY_k
if (event.state & CONTROLMASK) != 0
str = extractText(@anchor, getCursorPos-@anchor)
rmline
appendText(str)
setCursorPos(getLength)
end
super
when Fox::KEY_d
if (event.state & CONTROLMASK) != 0
#Ctrl - D
rmline
appendText("exit")
newLineEntered
else
# test for 'end' so we can dedent
if (getline == "en") and indented?
dedent
end
end
super
else
super
end
end
def dedent
str = getline
@anchor -= 2
rmline
appendText(str)
setCursorPos(getLength)
end
def history(dir)
str = (dir == :prev) ? @im.prevCmd.chomp : @im.nextCmd.chomp
if str != ""
removeText(@anchor, getLength-@anchor)
write(str)
end
end
def getline
extractText(@anchor, getLength-@anchor)
end
def rmline
str = getline
removeText(@anchor, getLength-@anchor)
str
end
def emptyline?
getline == ""
end
def indented?
extractText(@anchor-2, 2) == " "
end
def onLeftBtnPress(sender,sel,event)
@store_anchor = @anchor
setFocus
super
end
def onLeftBtnRelease(sender,sel,event)
super
@anchor = @store_anchor
setCursorPos(@anchor)
setCursorPos(getLength)
end
def onMiddleBtnPress(sender,sel,event)
pos=getPosAt(event.win_x,event.win_y)
if pos >= @anchor
super
end
end
def newLineEntered
processCommandLine(extractText(@anchor, getLength-@anchor))
end
def processCommandLine(cmd)
@multiline = false
lines = cmd.split(/\n/)
lines.each {|i|
@input[1].puts i
@inputAdded += 1
}
@im.print_prompt = false
while (@inputAdded > 0) do
@irb.run
end
@im.merge_last(lines.length)
@im.print_prompt = true
end
def sendCommand(cmd)
setCursorPos(getLength)
makePositionVisible(getLength) unless isPosVisible(getLength)
cmd += "\n"
appendText(cmd)
processCommandLine(cmd)
end
def write(obj)
str = obj.to_s
appendText(str)
setCursorPos(getLength)
makePositionVisible(getLength) unless isPosVisible(getLength)
return str.length
end
def get_line
@anchor = getLength
if @inputAdded == 0
Thread.stop
end
@inputAdded -= 1
return @input[0].gets
end
end
# Stand alone run
if __FILE__ == $0
application = FXApp.new("FXIrb", "ruby")
application.threadsEnabled = true
Thread.abort_on_exception = true
application.init(ARGV)
window = FXMainWindow.new(application, "FXIrb", nil, nil, DECOR_ALL, 0, 0, 580, 500)
fxirb = FXIrb.init(window, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y|TEXT_WORDWRAP|TEXT_SHOWACTIVE)
application.create
window.show(PLACEMENT_SCREEN)
fxirb.on_exit {exit}
application.run
end
syntax highlighted by Code2HTML, v. 0.9.1