#! /usr/bin/env ruby # This application is free software; you can redistribute it and/or # modify it under the terms of the Ruby license defined in the # COPYING file. # # Copyright (C) 2005 Martin DeMello. All rights reserved. # TODO # - handle user input redirection # - readline # Credits: # - Initial linux version: Gilles Filippini # - Initial windows port : Marco Frailis # - Currently maintained and developed by # Martin DeMello require "fox12" require "irb" require "singleton" require "English" include Fox STDOUT.sync = true class FXIRBInputMethod < IRB::StdioInputMethod attr_accessor :print_prompt, :gets_mode def initialize super @history = 1 @begin = nil @print_prompt = true @end = nil @continued_from = nil @gets_mode = false end def gets if @gets_mode return FXIrb.instance.get_line end 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 return "" if @gets_mode if @line_no > 0 @history -= 1 unless @history <= 1 return line(@history) end return "" end def nextCmd return "" if @gets_mode 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.restart(im) end def IRB.restart(im) 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 class << irb.context.workspace.main def gets inp = IRB.conf[:MAIN_CONTEXT].io inp.gets_mode = true retval = IRB.conf[:MAIN_CONTEXT].io.gets inp.gets_mode = false retval end 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) @parent = p 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 restart end def restart setFocus setText("") # IRB initialization @inputAdded = 0 @input = IO.pipe $DEFAULT_OUTPUT = self @im = FXIRBInputMethod.new if @irb @irb.kill @irb = Thread.new { IRB.restart(@im) } else @irb = Thread.new { IRB.start_in_fxirb(@im) } end 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 restart =begin #Ctrl - D rmline appendText(exit) sendCommand("exit") newLineEntered =end else # test for 'end' so we can dedent if (getline == "en") and indented? str = getline @anchor -= 2 rmline appendText(str) setCursorPos(getLength) 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) application.run end