From 92c422096e5b6e6c9a3c7a675db87847ff2b5188 Mon Sep 17 00:00:00 2001 From: xntrik Date: Sat, 20 Aug 2011 02:45:14 +0000 Subject: [PATCH] (Fixes issue 386) Updated 'console' extension to incorporate MSFs Rex Library for an interactive shell git-svn-id: https://beef.googlecode.com/svn/trunk@1221 b87d56ec-f9c0-11de-8c8a-61c5e9addfc9 --- beef | 15 +- config.yaml | 3 + core/loader.rb | 2 + extensions/console/config.yaml | 4 + extensions/console/extension.rb | 1 + extensions/console/lib/command_dispatcher.rb | 37 + .../console/lib/command_dispatcher/command.rb | 169 + .../console/lib/command_dispatcher/core.rb | 339 + .../console/lib/command_dispatcher/target.rb | 182 + extensions/console/lib/rbreadline.rb | 8738 +++++++++++++++++ extensions/console/lib/readline_compatible.rb | 549 ++ extensions/console/lib/shellinterface.rb | 576 ++ extensions/console/shell.rb | 78 + install | 2 +- 14 files changed, 10692 insertions(+), 3 deletions(-) create mode 100644 extensions/console/lib/command_dispatcher.rb create mode 100644 extensions/console/lib/command_dispatcher/command.rb create mode 100644 extensions/console/lib/command_dispatcher/core.rb create mode 100644 extensions/console/lib/command_dispatcher/target.rb create mode 100644 extensions/console/lib/rbreadline.rb create mode 100644 extensions/console/lib/readline_compatible.rb create mode 100644 extensions/console/lib/shellinterface.rb create mode 100644 extensions/console/shell.rb diff --git a/beef b/beef index 2c6d41531..a21cf4adb 100755 --- a/beef +++ b/beef @@ -101,5 +101,16 @@ BeEF::Extension::Console::Banners.print_network_interfaces_routes # We dynamically get the list of all browser hook handler using the API and register them BeEF::API.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) -# starts the web server -http_hook_server.start +# We check now for whether we load the Console Shell or not +if config.get("beef.extension.console.shell.enable") == true + puts "" + begin + FileUtils.mkdir_p(File.expand_path(config.get("beef.extension.console.shell.historyfolder"))) + BeEF::Extension::Console::Shell.new(BeEF::Extension::Console::Shell::DefaultPrompt, + BeEF::Extension::Console::Shell::DefaultPromptChar,{'config' => config, 'http_hook_server' => http_hook_server}).run + rescue Interrupt + end +else + # starts the web server + http_hook_server.start +end diff --git a/config.yaml b/config.yaml index ab9b3491a..a5f0ff959 100644 --- a/config.yaml +++ b/config.yaml @@ -64,3 +64,6 @@ beef: enable: true metasploit: enable: false + console: + shell: + enable: false diff --git a/core/loader.rb b/core/loader.rb index cbee48a22..7c2741a25 100644 --- a/core/loader.rb +++ b/core/loader.rb @@ -33,6 +33,8 @@ require 'xmlrpc/client' require 'erubis' require 'openssl' require 'term/ansicolor' +require 'rex' +require 'rex/ui' # Include the filters require 'core/filters' diff --git a/extensions/console/config.yaml b/extensions/console/config.yaml index 3e1268b07..22ad1952b 100644 --- a/extensions/console/config.yaml +++ b/extensions/console/config.yaml @@ -18,4 +18,8 @@ beef: console: enable: true name: 'Console' + shell: + enable: true + historyfolder: '~/.beef/' + historyfile: 'history' diff --git a/extensions/console/extension.rb b/extensions/console/extension.rb index 0f0b390e1..bcc38faf8 100644 --- a/extensions/console/extension.rb +++ b/extensions/console/extension.rb @@ -60,3 +60,4 @@ end require 'extensions/console/banners' require 'extensions/console/commandline' +require 'extensions/console/shell' diff --git a/extensions/console/lib/command_dispatcher.rb b/extensions/console/lib/command_dispatcher.rb new file mode 100644 index 000000000..ac93c3129 --- /dev/null +++ b/extensions/console/lib/command_dispatcher.rb @@ -0,0 +1,37 @@ +# +# Copyright 2011 Wade Alcorn wade@bindshell.net +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +module BeEF +module Extension +module Console + +module CommandDispatcher + include Rex::Ui::Text::DispatcherShell::CommandDispatcher + + def initialize(driver) + super + + self.driver = driver + end + + attr_accessor :driver + +end + +end end end + +require 'extensions/console/lib/command_dispatcher/core' +require 'extensions/console/lib/command_dispatcher/target' +require 'extensions/console/lib/command_dispatcher/command' \ No newline at end of file diff --git a/extensions/console/lib/command_dispatcher/command.rb b/extensions/console/lib/command_dispatcher/command.rb new file mode 100644 index 000000000..6420a0588 --- /dev/null +++ b/extensions/console/lib/command_dispatcher/command.rb @@ -0,0 +1,169 @@ +# +# Copyright 2011 Wade Alcorn wade@bindshell.net +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +module BeEF +module Extension +module Console +module CommandDispatcher + +class Command + include BeEF::Extension::Console::CommandDispatcher + + def initialize(driver) + super + end + + def commands + { + "execute" => "Go! Execute the command module", + "param" => "Set parameters for this module", + "response" => "Get previous responses to this command module", + "cmdinfo" => "See information about this particular command module" + } + end + + def name + "Command" + end + + @@bare_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help." ]) + + def cmd_cmdinfo(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_cmdinfo_help + return false + end + } + + print_line("Module name: " + driver.interface.cmd['Name']) + print_line("Module category: " + driver.interface.cmd['Category']) + print_line("Module description: " + driver.interface.cmd['Description']) + print_line("Module parameters:") + + driver.interface.cmd['Data'].each{|data| + print_line(data['name'] + " => \"" + data['value'] + "\" # this is the " + data['ui_label'] + " parameter") + } if not driver.interface.cmd['Data'].nil? + end + + def cmd_cmdinfo_help(*args) + print_status("Displays information about the current command module") + end + + def cmd_param(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_param_help + return false + end + } + + if (args[0] == nil || args[1] == nil) + cmd_param_help + return + else + p = "" + (1..args.length-1).each do |x| + p << args[x] << " " + end + p.chop! + driver.interface.setparam(args[0],p) + end + end + + def cmd_param_help(*args) + print_status("Sets parameters for the current modules. Run \"cmdinfo\" to see the parameter values") + print_status(" Usage: param ") + end + + def cmd_execute(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_execute_help + return false + end + } + + if driver.interface.executecommand == true + print_status("Command successfully queued") + else + print_status("Something went wrong") + end + end + + def cmd_execute_help(*args) + print_status("Execute this module... go on!") + end + + def cmd_response(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_response_help + return false + end + } + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'Executed Time', + 'Response Time' + ]) + + if args[0] == nil + driver.interface.getcommandresponses.each do |resp| + indiresp = driver.interface.getindividualresponse(resp['object_id']) + respout = "" + if indiresp.nil? or indiresp[0].nil? + respout = "No response yet" + else + respout = Time.at(indiresp[0]['date'].to_i).to_s + end + tbl << [resp['object_id'].to_s, resp['creationdate'], respout] + end + + puts "\n" + puts "List of responses for this command module:\n" + puts tbl.to_s + "\n" + else + output = driver.interface.getindividualresponse(args[0]) + if output.nil? + print_line("Invalid response ID") + elsif output[0].nil? + print_line("No response yet from the hooked browser or perhaps an invalid response ID") + else + print_line("Results retrieved: " + Time.at(output[0]['date'].to_i).to_s) + print_line("") + print_line("Response:") + print_line(output[0]['data']['data'].to_s) + end + end + end + + def cmd_response_help(*args) + print_status("List and review particular responses to this command") + print_status(" Usage: response (id)") + print_status(" If you omit id you'll see a list of all responses for the currently active command module") + end + +end + +end end end end \ No newline at end of file diff --git a/extensions/console/lib/command_dispatcher/core.rb b/extensions/console/lib/command_dispatcher/core.rb new file mode 100644 index 000000000..5614631de --- /dev/null +++ b/extensions/console/lib/command_dispatcher/core.rb @@ -0,0 +1,339 @@ +# +# Copyright 2011 Wade Alcorn wade@bindshell.net +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +module BeEF +module Extension +module Console +module CommandDispatcher + +class Core + include BeEF::Extension::Console::CommandDispatcher + + def initialize(driver) + super + end + + def commands + { + "?" => "Help menu", + "back" => "Move back from the current context", + "exit" => "Exit the console", + "help" => "Help menu", + "jobs" => "Print jobs", + "online" => "List online hooked browsers", + "offline" => "List previously hooked browsers", + "quit" => "Exit the console", + "review" => "Target a particular previously hooked (offline) hooked browser", + "show" => "Displays 'zombies' or 'browsers' or 'commands'. (For those who prefer the MSF way)", + "target" => "Target a particular online hooked browser", + } + end + + def name + "Core" + end + + def cmd_back(*args) + if (driver.current_dispatcher.name == 'Command') + driver.remove_dispatcher('Command') + driver.interface.clearcommand #TODO: TIDY THIS UP + driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.to_s+"] ") + elsif (driver.current_dispatcher.name == 'Target') + driver.remove_dispatcher('Target') + driver.interface.cleartarget + driver.update_prompt('') + elsif (driver.dispatcher_stack.size > 1 and + driver.current_dispatcher.name != 'Core') + + driver.destack_dispatcher + + driver.update_prompt('') + end + end + + def cmd_back_help(*args) + print_status("Move back one step") + end + + def cmd_exit + driver.stop + end + + alias cmd_quit cmd_exit + + @@jobs_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help." ], + "-l" => [ false, "List jobs." ], + "-k" => [ true, "Terminate the job." ]) + + def cmd_jobs(*args) + if (args[0] == nil) + cmd_jobs_list + print_line "Try: jobs -h" + return + end + + @@jobs_opts.parse(args) {|opt, idx, val| + case opt + when "-k" + if (not driver.jobs.has_key?(val)) + print_error("no such job") + else + #This is a special job, that has to be terminated different prior to cleanup + driver.http_hook_server.stop if driver.jobs[val].name == "http_hook_server" + + print_line("Stopping job: #{val}...") + driver.jobs.stop_job(val) + end + when "-l" + cmd_jobs_list + when "-h" + cmd_jobs_help + return false + end + } + end + + def cmd_jobs_help(*args) + print_line "Usage: jobs [options]" + print_line + print @@jobs_opts.usage() + end + + def cmd_jobs_list + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'Job Name' + ]) + driver.jobs.keys.each{|k| + tbl << [driver.jobs[k].jid.to_s, driver.jobs[k].name] + } + puts "\n" + puts tbl.to_s + "\n" + end + + @@bare_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help." ]) + + def cmd_online(*args) + + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_online_help + return false + end + } + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'IP', + 'OS' + ]) + + BeEF::Core::Models::HookedBrowser.all(:lastseen.gte => (Time.new.to_i - 30)).each do |zombie| + tbl << [zombie.id,zombie.ip,beef_logo_to_os(BeEF::Extension::Initialization::Models::BrowserDetails.os_icon(zombie.session))] + end + + puts "\n" + puts "Currently hooked browsers within BeEF" + puts "\n" + puts tbl.to_s + "\n" + end + + def cmd_online_help(*args) + print_status("Show currently hooked browsers within BeEF") + end + + def cmd_offline(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_offline_help + return false + end + } + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'IP', + 'OS' + ]) + + BeEF::Core::Models::HookedBrowser.all(:lastseen.lt => (Time.new.to_i - 30)).each do |zombie| + tbl << [zombie.id,zombie.ip,beef_logo_to_os(BeEF::Extension::Initialization::Models::BrowserDetails.os_icon(zombie.session))] + end + + puts "\n" + puts "Previously hooked browsers within BeEF" + puts "\n" + puts tbl.to_s + "\n" + end + + def cmd_offline_help(*args) + print_status("Show previously hooked browsers") + end + + def cmd_target(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_target_help + return false + end + } + + if args[0] == nil + cmd_target_help + return + end + + if not driver.interface.settarget(args[0]).nil? + + if (driver.dispatcher_stack.size > 1 and + driver.current_dispatcher.name != 'Core') + + driver.destack_dispatcher + driver.update_prompt('') + end + + driver.enstack_dispatcher(Target) + driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.to_s+"] ") + end + end + + def cmd_target_help(*args) + print_status("Target a particular online, hooked browser") + print_status(" Usage: target ") + end + + def cmd_review(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_review_help + return false + end + } + + if args[0] == nil + cmd_review_help + return + end + + if not driver.interface.setofflinetarget(args[0]).nil? + if (driver.dispatcher_stack.size > 1 and + driver.current_dispatcher.name != 'Core') + + driver.destack_dispatcher + driver.update_prompt('') + end + + driver.enstack_dispatcher(Target) + driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.to_s+"] ") + end + + end + + def cmd_review_help(*args) + print_status("Review an offline, previously hooked browser") + print_status(" Usage: review ") + end + + def cmd_show(*args) + args << "-h" if (args.length == 0) + + args.each { |type| + case type + when '-h' + cmd_show_help + when 'zombies' + driver.run_single("online") + when 'browsers' + driver.run_single("online") + when 'online' + driver.run_single("online") + when 'offline' + driver.run_single("offline") + when 'commands' + if driver.dispatched_enstacked(Target) + driver.run_single("commands") + else + print_error("You aren't targeting a zombie yet") + end + when 'info' + if driver.dispatched_enstacked(Target) + driver.run_single("info") + else + print_error("You aren't targeting a zombie yet") + end + when 'cmdinfo' + if driver.dispatched_enstacked(Command) + driver.run_single("cmdinfo") + else + print_error("You haven't selected a command module yet") + end + else + print_error("Invalid parameter, try show -h for more information.") + end + } + end + + def cmd_show_tabs(str, words) + return [] if words.length > 1 + + res = %w{zombies browsers online offline} + + if driver.dispatched_enstacked(Target) + res.concat(%w{commands info}) + end + + if driver.dispatched_enstacked(Command) + res.concat(%w{cmdinfo}) + end + + return res + end + + def cmd_show_help + global_opts = %w{zombies browsers} + print_status("Valid parameters for the \"show\" command are: #{global_opts.join(", ")}") + + target_opts = %w{commands} + print_status("If you're targeting a module, you can also specify: #{target_opts.join(", ")}") + end + + def beef_logo_to_os(logo) + case logo + when "mac.png" + hbos = "Mac OS X" + when "linux.png" + hbos = "Linux" + when "win.png" + hbos = "Microsoft Windows" + when "unknown.png" + hbos = "Unknown" + end + end + +end + +end end end end \ No newline at end of file diff --git a/extensions/console/lib/command_dispatcher/target.rb b/extensions/console/lib/command_dispatcher/target.rb new file mode 100644 index 000000000..9c4ae091d --- /dev/null +++ b/extensions/console/lib/command_dispatcher/target.rb @@ -0,0 +1,182 @@ +# +# Copyright 2011 Wade Alcorn wade@bindshell.net +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +module BeEF +module Extension +module Console +module CommandDispatcher + +class Target + include BeEF::Extension::Console::CommandDispatcher + + @@commands = [] + + def initialize(driver) + super + begin + driver.interface.getcommands.each { |folder| + folder['children'].each { |command| + @@commands << folder['text'] + "/" + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_") + } + } + rescue + return + end + end + + def commands + { + "commands" => "List available commands against this particular target", + "info" => "Info about the target", + "select" => "Prepare the command module for execution against this target" + } + end + + def name + "Target" + end + + @@bare_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help." ]) + + def cmd_commands(*args) + + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_commands_help + return false + end + } + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Id', + 'Command', + 'Status', + 'Execute Count' + ]) + + + driver.interface.getcommands.each { |folder| + folder['children'].each { |command| + tbl << [command['id'].to_s, + folder['text'] + "/" + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_"), + command['status'], + driver.interface.getcommandresponses(command['id']).length] #TODO + } + } + + puts "\n" + puts "List command modules for this target\n" + puts tbl.to_s + "\n" + + end + + def cmd_commands_help(*args) + print_status("List command modules for this target") + end + + def cmd_info(*args) + + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_info_help + return false + end + } + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ + 'Param', + 'Value' + ]) + + driver.interface.select_zombie_summary['results'].each { |x| + x['data'].each { |k,v| + tbl << [k,v] + } + } + + puts "\nHooked Browser Info:\n" + puts tbl.to_s + "\n" + + end + + def cmd_info_help(*args) + print_status("Display initialisation information about the hooked browser.") + end + + def cmd_select(*args) + @@bare_opts.parse(args) {|opt, idx, val| + case opt + when "-h" + cmd_select_help + return false + end + } + + if args[0] == nil + cmd_select_help + return false + end + + modid = nil + + if args[0] =~ /[0-9]+/ + modid = args[0] + else + driver.interface.getcommands.each { |x| + x['children'].each { |y| + if args[0].chomp == x['text']+"/"+y['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_") + modid = y['id'] + end + } + } + end + + if modid.nil? + print_status("Could not find command module") + return false + end + + driver.interface.setcommand(modid) + + driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false + + driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.to_s+"] / "+driver.interface.cmd['Name']+" ") + end + + def cmd_select_help(*args) + print_status("Select a command module to use against the current target") + print_status(" Usage: module OR ") + end + + def cmd_select_tabs(str,words) + return if words.length > 1 + + if @@commands == "" + #nothing prepopulated? + else + return @@commands + end + end + +end + +end end end end \ No newline at end of file diff --git a/extensions/console/lib/rbreadline.rb b/extensions/console/lib/rbreadline.rb new file mode 100644 index 000000000..f0cfa9818 --- /dev/null +++ b/extensions/console/lib/rbreadline.rb @@ -0,0 +1,8738 @@ +# rbreadline.rb -- a general facility for reading lines of input +# with emacs style editing and completion. + +# +# Inspired by GNU Readline, translation to Ruby +# Copyright (C) 2009 by Park Heesob phasis@gmail.com +# + +=begin +Copyright (c) 2009, Park Heesob +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Park Heesob nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=end + + +class Fixnum + def ord; self; end +end + +module RbReadline + require 'etc' + + RL_LIBRARY_VERSION = "5.2" + RL_READLINE_VERSION = 0x0502 + RB_READLINE_VERSION = "0.2.2" + + EOF = "\xFF" + ESC = "\C-[" + PAGE = "\C-L" + SPACE = "\x20" + RETURN = "\C-M" + ABORT_CHAR = "\C-G" + TAB = "\t" + RUBOUT = "\x7f" + NEWLINE = "\n" + + DEFAULT_BUFFER_SIZE = 256 + DEFAULT_MAX_KILLS = 10 + + MB_FIND_NONZERO = 1 + MB_FIND_ANY = 0 + MB_LEN_MAX = 4 + + DEFAULT_INPUTRC = "~/.inputrc" + SYS_INPUTRC = "/etc/inputrc" + + UpCase = 1 + DownCase = 2 + CapCase = 3 + + # Possible history errors passed to hist_error. + EVENT_NOT_FOUND = 0 + BAD_WORD_SPEC = 1 + SUBST_FAILED = 2 + BAD_MODIFIER = 3 + NO_PREV_SUBST = 4 + + # Possible definitions for history starting point specification. + ANCHORED_SEARCH = 1 + NON_ANCHORED_SEARCH = 0 + + # Possible definitions for what style of writing the history file we want. + HISTORY_APPEND = 0 + HISTORY_OVERWRITE = 1 + + # Input error; can be returned by (*rl_getc_function) if readline is reading + # a top-level command (RL_ISSTATE (RL_STATE_READCMD)). + READERR = 0xFE.chr + + # Definitions available for use by readline clients. + RL_PROMPT_START_IGNORE = 1.chr + RL_PROMPT_END_IGNORE = 2.chr + + # Possible values for do_replace argument to rl_filename_quoting_function, + # called by rl_complete_internal. + NO_MATCH = 0 + SINGLE_MATCH = 1 + MULT_MATCH = 2 + + # Callback data for reading numeric arguments + NUM_SAWMINUS = 0x01 + NUM_SAWDIGITS = 0x02 + NUM_READONE = 0x04 + + # A context for reading key sequences longer than a single character when + # using the callback interface. + KSEQ_DISPATCHED = 0x01 + KSEQ_SUBSEQ = 0x02 + KSEQ_RECURSIVE = 0x04 + + # Possible state values for rl_readline_state + RL_STATE_NONE = 0x000000 # no state before first call + + RL_STATE_INITIALIZING = 0x000001 # initializing + RL_STATE_INITIALIZED = 0x000002 # initialization done + RL_STATE_TERMPREPPED = 0x000004 # terminal is prepped + RL_STATE_READCMD = 0x000008 # reading a command key + RL_STATE_METANEXT = 0x000010 # reading input after ESC + RL_STATE_DISPATCHING = 0x000020 # dispatching to a command + RL_STATE_MOREINPUT = 0x000040 # reading more input in a command function + RL_STATE_ISEARCH = 0x000080 # doing incremental search + RL_STATE_NSEARCH = 0x000100 # doing non-inc search + RL_STATE_SEARCH = 0x000200 # doing a history search + RL_STATE_NUMERICARG = 0x000400 # reading numeric argument + RL_STATE_MACROINPUT = 0x000800 # getting input from a macro + RL_STATE_MACRODEF = 0x001000 # defining keyboard macro + RL_STATE_OVERWRITE = 0x002000 # overwrite mode + RL_STATE_COMPLETING = 0x004000 # doing completion + RL_STATE_SIGHANDLER = 0x008000 # in readline sighandler + RL_STATE_UNDOING = 0x010000 # doing an undo + RL_STATE_INPUTPENDING = 0x020000 # rl_execute_next called + RL_STATE_TTYCSAVED = 0x040000 # tty special chars saved + RL_STATE_CALLBACK = 0x080000 # using the callback interface + RL_STATE_VIMOTION = 0x100000 # reading vi motion arg + RL_STATE_MULTIKEY = 0x200000 # reading multiple-key command + RL_STATE_VICMDONCE = 0x400000 # entered vi command mode at least once + + RL_STATE_DONE = 0x800000 # done accepted line + + NO_BELL = 0 + AUDIBLE_BELL = 1 + VISIBLE_BELL = 2 + # The actions that undo knows how to undo. Notice that UNDO_DELETE means + # to insert some text, and UNDO_INSERT means to delete some text. I.e., + # the code tells undo what to undo, not how to undo it. + UNDO_DELETE, UNDO_INSERT, UNDO_BEGIN, UNDO_END = 0,1,2,3 + + # Definitions used when searching the line for characters. + # NOTE: it is necessary that opposite directions are inverses + FTO = 1 # forward to + BTO = -1 # backward to + FFIND = 2 # forward find + BFIND = -2 # backward find + + # Possible values for the found_quote flags word used by the completion + # functions. It says what kind of (shell-like) quoting we found anywhere + # in the line. + RL_QF_SINGLE_QUOTE = 0x01 + RL_QF_DOUBLE_QUOTE = 0x02 + RL_QF_BACKSLASH = 0x04 + RL_QF_OTHER_QUOTE = 0x08 + + KEYMAP_SIZE = 257 + ANYOTHERKEY = KEYMAP_SIZE-1 + + @hConsoleHandle = nil + @MessageBeep = nil + + RL_IM_INSERT = 1 + RL_IM_OVERWRITE = 0 + RL_IM_DEFAULT = RL_IM_INSERT + @no_mode = -1 + @vi_mode = 0 + @emacs_mode = 1 + ISFUNC = 0 + ISKMAP = 1 + ISMACR = 2 + + HISTORY_WORD_DELIMITERS = " \t\n;&()|<>" + HISTORY_QUOTE_CHARACTERS = "\"'`" + + RL_SEARCH_ISEARCH = 0x01 # incremental search + RL_SEARCH_NSEARCH = 0x02 # non-incremental search + RL_SEARCH_CSEARCH = 0x04 # intra-line char search + + # search flags + SF_REVERSE = 0x01 + SF_FOUND = 0x02 + SF_FAILED = 0x04 + + @slashify_in_quotes = "\\`\"$" + + @sigint_proc = nil + @sigint_blocked = false + + @rl_prep_term_function = :rl_prep_terminal + @rl_deprep_term_function = :rl_deprep_terminal + + @_rl_history_saved_point = -1 + + @rl_max_kills = DEFAULT_MAX_KILLS + @rl_kill_ring = nil + @rl_kill_index = 0 + @rl_kill_ring_length = 0 + + @pending_bytes = '' + @stored_count = 0 + + @_rl_isearch_terminators = nil + @_rl_iscxt = nil + @last_isearch_string = nil + @last_isearch_string_len = 0 + @default_isearch_terminators = "\033\012" + @_rl_history_preserve_point = false + @terminal_prepped = false + @otio = nil + + @msg_saved_prompt = false + + @_rl_nscxt = nil + @noninc_search_string = nil + @noninc_history_pos = 0 + @prev_line_found = nil + + rl_history_search_len = 0 + rl_history_search_pos = 0 + history_search_string = nil + history_string_size = 0 + + @_rl_tty_chars = Struct.new(:t_eol,:t_eol2,:t_erase,:t_werase,:t_kill,:t_reprint,:t_intr,:t_eof, + :t_quit,:t_susp,:t_dsusp,:t_start,:t_stop,:t_lnext,:t_flush,:t_status).new + @_rl_last_tty_chars = nil + + @_keyboard_input_timeout = 0.01 + + # Variables exported by this file. + # The character that represents the start of a history expansion + # request. This is usually `!'. + @history_expansion_char = "!" + + # The character that invokes word substitution if found at the start of + # a line. This is usually `^'. + @history_subst_char = "^" + + # During tokenization, if this character is seen as the first character + # of a word, then it, and all subsequent characters upto a newline are + # ignored. For a Bourne shell, this should be '#'. Bash special cases + # the interactive comment character to not be a comment delimiter. + @history_comment_char = 0.chr + + # The list of characters which inhibit the expansion of text if found + # immediately following history_expansion_char. + @history_no_expand_chars = " \t\n\r=" + + # If set to a non-zero value, single quotes inhibit history expansion. + # The default is 0. + @history_quotes_inhibit_expansion = 0 + + # Used to split words by history_tokenize_internal. + @history_word_delimiters = HISTORY_WORD_DELIMITERS + + # If set, this points to a function that is called to verify that a + # particular history expansion should be performed. + @history_inhibit_expansion_function = nil + + @rl_event_hook = nil + # The visible cursor position. If you print some text, adjust this. + # NOTE: _rl_last_c_pos is used as a buffer index when not in a locale + # supporting multibyte characters, and an absolute cursor position when + # in such a locale. This is an artifact of the donated multibyte support. + # Care must be taken when modifying its value. + @_rl_last_c_pos = 0 + @_rl_last_v_pos = 0 + + @cpos_adjusted = false + @cpos_buffer_position = 0 + + # Number of lines currently on screen minus 1. + @_rl_vis_botlin = 0 + + # Variables used only in this file. + # The last left edge of text that was displayed. This is used when + # doing horizontal scrolling. It shifts in thirds of a screenwidth. + @last_lmargin = 0 + + # The line display buffers. One is the line currently displayed on + # the screen. The other is the line about to be displayed. + @visible_line = nil + @invisible_line = nil + + # A buffer for `modeline' messages. + @msg_buf = 0.chr * 128 + + # Non-zero forces the redisplay even if we thought it was unnecessary. + @forced_display = false + + # Default and initial buffer size. Can grow. + @line_size = 1024 + + # Variables to keep track of the expanded prompt string, which may + # include invisible characters. + + @local_prompt = nil + @local_prompt_prefix = nil + @local_prompt_len = 0 + @prompt_visible_length = 0 + @prompt_prefix_length = 0 + + # The number of invisible characters in the line currently being + # displayed on the screen. + @visible_wrap_offset = 0 + + # The number of invisible characters in the prompt string. Static so it + # can be shared between rl_redisplay and update_line + @wrap_offset = 0 + + @prompt_last_invisible = 0 + + # The length (buffer offset) of the first line of the last (possibly + # multi-line) buffer displayed on the screen. + @visible_first_line_len = 0 + + # Number of invisible characters on the first physical line of the prompt. + # Only valid when the number of physical characters in the prompt exceeds + # (or is equal to) _rl_screenwidth. + @prompt_invis_chars_first_line = 0 + + @prompt_last_screen_line = 0 + + @prompt_physical_chars = 0 + + # Variables to save and restore prompt and display information. + + # These are getting numerous enough that it's time to create a struct. + + @saved_local_prompt = nil + @saved_local_prefix = nil + @saved_last_invisible = 0 + @saved_visible_length = 0 + @saved_prefix_length = 0 + @saved_local_length = 0 + @saved_invis_chars_first_line = 0 + @saved_physical_chars = 0 + + @inv_lbreaks = nil + @vis_lbreaks = nil + @_rl_wrapped_line = nil + + @term_buffer = nil + @term_string_buffer = nil + + @tcap_initialized = false + + # While we are editing the history, this is the saved + # version of the original line. + @_rl_saved_line_for_history = nil + + # An array of HIST_ENTRY. This is where we store the history. + @the_history = nil + @history_base = 1 + + # Non-zero means that we have enforced a limit on the amount of + # history that we save. + @history_stifled = false + + # If HISTORY_STIFLED is non-zero, then this is the maximum number of + # entries to remember. + @history_max_entries = 0 + @max_input_history = 0 # backwards compatibility + + # The current location of the interactive history pointer. Just makes + # life easier for outside callers. + @history_offset = 0 + + # The number of strings currently stored in the history list. + @history_length = 0 + + @_rl_vi_last_command = 'i' # default `.' puts you in insert mode + + # Non-zero means enter insertion mode. + @_rl_vi_doing_insert = 0 + + # Command keys which do movement for xxx_to commands. + @vi_motion = " hl^$0ftFT;,%wbeWBE|" + + # Keymap used for vi replace characters. Created dynamically since + # rarely used. + @vi_replace_map = nil + + # The number of characters inserted in the last replace operation. + @vi_replace_count = 0 + + # If non-zero, we have text inserted after a c[motion] command that put + # us implicitly into insert mode. Some people want this text to be + # attached to the command so that it is `redoable' with `.'. + @vi_continued_command = false + @vi_insert_buffer = nil + @vi_insert_buffer_size = 0 + + @_rl_vi_last_repeat = 1 + @_rl_vi_last_arg_sign = 1 + @_rl_vi_last_motion = 0 + @_rl_vi_last_search_char = 0 + @_rl_vi_last_replacement = 0 + + @_rl_vi_last_key_before_insert = 0 + + @vi_redoing = 0 + + # Text modification commands. These are the `redoable' commands. + @vi_textmod = "_*\\AaIiCcDdPpYyRrSsXx~" + + # Arrays for the saved marks. + @vi_mark_chars = Array.new(26,-1) + + @emacs_standard_keymap = { + "\C-@" => :rl_set_mark , + "\C-a" => :rl_beg_of_line , + "\C-b" => :rl_backward_char , + "\C-d" => :rl_delete , + "\C-e" => :rl_end_of_line , + "\C-f" => :rl_forward_char , + "\C-g" => :rl_abort , + "\C-h" => :rl_rubout , + "\C-i" => :rl_complete , + "\C-j" => :rl_newline , + "\C-k" => :rl_kill_line , + "\C-l" => :rl_clear_screen , + "\C-m" => :rl_newline , + "\C-n" => :rl_get_next_history , + "\C-p" => :rl_get_previous_history , + "\C-q" => :rl_quoted_insert , + "\C-r" => :rl_reverse_search_history , + "\C-s" => :rl_forward_search_history , + "\C-t" => :rl_transpose_chars , + "\C-u" => :rl_unix_line_discard , + "\C-v" => :rl_quoted_insert , + "\C-w" => :rl_unix_word_rubout , + "\C-y" => :rl_yank , + "\C-]" => :rl_char_search , + "\C-_" => :rl_undo_command , + "\x7F" => :rl_rubout , + "\e\C-g" => :rl_abort , + "\e\C-h" => :rl_backward_kill_word , + "\e\C-i" => :rl_tab_insert , + "\e\C-j" => :rl_vi_editing_mode , + "\e\C-m" => :rl_vi_editing_mode , + "\e\C-r" => :rl_revert_line , + "\e\C-y" => :rl_yank_nth_arg , + "\e\C-[" => :rl_complete , + "\e\C-]" => :rl_backward_char_search , + "\e " => :rl_set_mark , + "\e#" => :rl_insert_comment , + "\e&" => :rl_tilde_expand , + "\e*" => :rl_insert_completions , + "\e-" => :rl_digit_argument , + "\e." => :rl_yank_last_arg , + "\e0" => :rl_digit_argument , + "\e1" => :rl_digit_argument , + "\e2" => :rl_digit_argument , + "\e3" => :rl_digit_argument , + "\e4" => :rl_digit_argument , + "\e5" => :rl_digit_argument , + "\e6" => :rl_digit_argument , + "\e7" => :rl_digit_argument , + "\e8" => :rl_digit_argument , + "\e9" => :rl_digit_argument , + "\e<" => :rl_beginning_of_history , + "\e=" => :rl_possible_completions , + "\e>" => :rl_end_of_history , + "\e?" => :rl_possible_completions , + "\eB" => :rl_backward_word , + "\eC" => :rl_capitalize_word , + "\eD" => :rl_kill_word , + "\eF" => :rl_forward_word , + "\eL" => :rl_downcase_word , + "\eN" => :rl_noninc_forward_search , + "\eP" => :rl_noninc_reverse_search , + "\eR" => :rl_revert_line , + "\eT" => :rl_transpose_words , + "\eU" => :rl_upcase_word , + "\eY" => :rl_yank_pop , + "\e\\" => :rl_delete_horizontal_space , + "\e_" => :rl_yank_last_arg , + "\eb" => :rl_backward_word , + "\ec" => :rl_capitalize_word , + "\ed" => :rl_kill_word , + "\ef" => :rl_forward_word , + "\el" => :rl_downcase_word , + "\en" => :rl_noninc_forward_search , + "\ep" => :rl_noninc_reverse_search , + "\er" => :rl_revert_line , + "\et" => :rl_transpose_words , + "\eu" => :rl_upcase_word , + "\ey" => :rl_yank_pop , + "\e~" => :rl_tilde_expand , + "\377" => :rl_backward_kill_word , + + "\C-x\C-g" => :rl_abort , + "\C-x\C-r" => :rl_re_read_init_file , + "\C-x\C-u" => :rl_undo_command , + "\C-x\C-x" => :rl_exchange_point_and_mark , + "\C-x(" => :rl_start_kbd_macro , + "\C-x)" => :rl_end_kbd_macro , + "\C-xE" => :rl_call_last_kbd_macro , + "\C-xe" => :rl_call_last_kbd_macro , + "\C-x\x7F" => :rl_backward_kill_line + } + + @vi_movement_keymap = { + "\C-d" => :rl_vi_eof_maybe , + "\C-e" => :rl_emacs_editing_mode , + "\C-g" => :rl_abort , + "\C-h" => :rl_backward_char , + "\C-j" => :rl_newline , + "\C-k" => :rl_kill_line , + "\C-l" => :rl_clear_screen , + "\C-m" => :rl_newline , + "\C-n" => :rl_get_next_history , + "\C-p" => :rl_get_previous_history , + "\C-q" => :rl_quoted_insert , + "\C-r" => :rl_reverse_search_history , + "\C-s" => :rl_forward_search_history , + "\C-t" => :rl_transpose_chars , + "\C-u" => :rl_unix_line_discard , + "\C-v" => :rl_quoted_insert , + "\C-w" => :rl_unix_word_rubout , + "\C-y" => :rl_yank , + "\C-_" => :rl_vi_undo , + " " => :rl_forward_char , + "#" => :rl_insert_comment , + "$" => :rl_end_of_line , + "%" => :rl_vi_match , + "&" => :rl_vi_tilde_expand , + "*" => :rl_vi_complete , + "+" => :rl_get_next_history , + "," => :rl_vi_char_search , + "-" => :rl_get_previous_history , + "." => :rl_vi_redo , + "/" => :rl_vi_search , + "0" => :rl_beg_of_line , + "1" => :rl_vi_arg_digit , + "2" => :rl_vi_arg_digit , + "3" => :rl_vi_arg_digit , + "4" => :rl_vi_arg_digit , + "5" => :rl_vi_arg_digit , + "6" => :rl_vi_arg_digit , + "7" => :rl_vi_arg_digit , + "8" => :rl_vi_arg_digit , + "9" => :rl_vi_arg_digit , + "" => :rl_vi_char_search , + "=" => :rl_vi_complete , + "?" => :rl_vi_search , + "A" => :rl_vi_append_eol , + "B" => :rl_vi_prev_word , + "C" => :rl_vi_change_to , + "D" => :rl_vi_delete_to , + "E" => :rl_vi_end_word , + "F" => :rl_vi_char_search , + "G" => :rl_vi_fetch_history , + "I" => :rl_vi_insert_beg , + "N" => :rl_vi_search_again , + "P" => :rl_vi_put , + "R" => :rl_vi_replace , + "S" => :rl_vi_subst , + "T" => :rl_vi_char_search , + "U" => :rl_revert_line , + "W" => :rl_vi_next_word , + "X" => :rl_vi_rubout , + "Y" => :rl_vi_yank_to , + "\\" => :rl_vi_complete , + "^" => :rl_vi_first_print , + "_" => :rl_vi_yank_arg , + "`" => :rl_vi_goto_mark , + "a" => :rl_vi_append_mode , + "b" => :rl_vi_prev_word , + "c" => :rl_vi_change_to , + "d" => :rl_vi_delete_to , + "e" => :rl_vi_end_word , + "f" => :rl_vi_char_search , + "h" => :rl_backward_char , + "i" => :rl_vi_insertion_mode , + "j" => :rl_get_next_history , + "k" => :rl_get_previous_history , + "l" => :rl_forward_char , + "m" => :rl_vi_set_mark , + "n" => :rl_vi_search_again , + "p" => :rl_vi_put , + "r" => :rl_vi_change_char , + "s" => :rl_vi_subst , + "t" => :rl_vi_char_search , + "u" => :rl_vi_undo , + "w" => :rl_vi_next_word , + "x" => :rl_vi_delete , + "y" => :rl_vi_yank_to , + "|" => :rl_vi_column , + "~" => :rl_vi_change_case + } + + @vi_insertion_keymap = { + "\C-a" => :rl_insert , + "\C-b" => :rl_insert , + "\C-c" => :rl_insert , + "\C-d" => :rl_vi_eof_maybe , + "\C-e" => :rl_insert , + "\C-f" => :rl_insert , + "\C-g" => :rl_insert , + "\C-h" => :rl_rubout , + "\C-i" => :rl_complete , + "\C-j" => :rl_newline , + "\C-k" => :rl_insert , + "\C-l" => :rl_insert , + "\C-m" => :rl_newline , + "\C-n" => :rl_insert , + "\C-o" => :rl_insert , + "\C-p" => :rl_insert , + "\C-q" => :rl_insert , + "\C-r" => :rl_reverse_search_history , + "\C-s" => :rl_forward_search_history , + "\C-t" => :rl_transpose_chars , + "\C-u" => :rl_unix_line_discard , + "\C-v" => :rl_quoted_insert , + "\C-w" => :rl_unix_word_rubout , + "\C-x" => :rl_insert , + "\C-y" => :rl_yank , + "\C-z" => :rl_insert , +# "\C-[" => :rl_vi_movement_mode, +# XXX: NOT IMPLEMENTED + "\C-\\" => :rl_insert , + "\C-]" => :rl_insert , + "\C-^" => :rl_insert , + "\C-_" => :rl_vi_undo , + "\x7F" => :rl_rubout + } + + @rl_library_version = RL_LIBRARY_VERSION + + @rl_readline_version = RL_READLINE_VERSION + + @rl_readline_name = "other" + + @rl_getc_function = :rl_getc + + # Non-zero tells rl_delete_text and rl_insert_text to not add to + # the undo list. + @_rl_doing_an_undo = false + + # How many unclosed undo groups we currently have. + @_rl_undo_group_level = 0 + + # The current undo list for THE_LINE. + @rl_undo_list = nil + + # Application-specific redisplay function. + @rl_redisplay_function = :rl_redisplay + + # Global variables declared here. + # What YOU turn on when you have handled all redisplay yourself. + @rl_display_fixed = false + + @_rl_suppress_redisplay = 0 + @_rl_want_redisplay = false + + # The stuff that gets printed out before the actual text of the line. + # This is usually pointing to rl_prompt. + @rl_display_prompt = nil + + # True if this is `real' readline as opposed to some stub substitute. + @rl_gnu_readline_p = true + + for i in 32 .. 255 + @emacs_standard_keymap[i.chr] = :rl_insert unless @emacs_standard_keymap[i.chr] + @vi_insertion_keymap[i.chr] = :rl_insert unless @vi_insertion_keymap[i.chr] + end + # A pointer to the keymap that is currently in use. + # By default, it is the standard emacs keymap. + @_rl_keymap = @emacs_standard_keymap + + + # The current style of editing. + @rl_editing_mode = @emacs_mode + + # The current insert mode: input (the default) or overwrite + @rl_insert_mode = RL_IM_DEFAULT + + # Non-zero if we called this function from _rl_dispatch(). It's present + # so functions can find out whether they were called from a key binding + # or directly from an application. + @rl_dispatching = false + + # Non-zero if the previous command was a kill command. + @_rl_last_command_was_kill = false + + # The current value of the numeric argument specified by the user. + @rl_numeric_arg = 1 + + # Non-zero if an argument was typed. + @rl_explicit_arg = false + + # Temporary value used while generating the argument. + @rl_arg_sign = 1 + + # Non-zero means we have been called at least once before. + @rl_initialized = false + + # Flags word encapsulating the current readline state. + @rl_readline_state = RL_STATE_NONE + + # The current offset in the current input line. + @rl_point = 0 + + # Mark in the current input line. + @rl_mark = 0 + + # Length of the current input line. + @rl_end = 0 + + # Make this non-zero to return the current input_line. + @rl_done = false + + # The last function executed by readline. + @rl_last_func = nil + + # Top level environment for readline_internal (). + @readline_top_level = nil + + # The streams we interact with. + @_rl_in_stream = nil + @_rl_out_stream = nil + + # The names of the streams that we do input and output to. + @rl_instream = nil + @rl_outstream = nil + + @pop_index = 0 + @push_index = 0 + @ibuffer = 0.chr * 512 + @ibuffer_len = @ibuffer.length - 1 + + + # Non-zero means echo characters as they are read. Defaults to no echo + # set to 1 if there is a controlling terminal, we can get its attributes, + # and the attributes include `echo'. Look at rltty.c:prepare_terminal_settings + # for the code that sets it. + @readline_echoing_p = false + + # Current prompt. + @rl_prompt = nil + @rl_visible_prompt_length = 0 + + # Set to non-zero by calling application if it has already printed rl_prompt + # and does not want readline to do it the first time. + @rl_already_prompted = false + + # The number of characters read in order to type this complete command. + @rl_key_sequence_length = 0 + + # If non-zero, then this is the address of a function to call just + # before readline_internal_setup () prints the first prompt. + @rl_startup_hook = nil + + # If non-zero, this is the address of a function to call just before + # readline_internal_setup () returns and readline_internal starts + # reading input characters. + @rl_pre_input_hook = nil + + # The character that can generate an EOF. Really read from + # the terminal driver... just defaulted here. + @_rl_eof_char = "\cD" + + # Non-zero makes this the next keystroke to read. + @rl_pending_input = 0 + + # Pointer to a useful terminal name. + @rl_terminal_name = nil + + # Non-zero means to always use horizontal scrolling in line display. + @_rl_horizontal_scroll_mode = false + + # Non-zero means to display an asterisk at the starts of history lines + # which have been modified. + @_rl_mark_modified_lines = false + + # The style of `bell' notification preferred. This can be set to NO_BELL, + # AUDIBLE_BELL, or VISIBLE_BELL. + @_rl_bell_preference = AUDIBLE_BELL + + # String inserted into the line by rl_insert_comment (). + @_rl_comment_begin = nil + + # Keymap holding the function currently being executed. + @rl_executing_keymap = nil + + # Keymap we're currently using to dispatch. + @_rl_dispatching_keymap = nil + + # Non-zero means to erase entire line, including prompt, on empty input lines. + @rl_erase_empty_line = false + + # Non-zero means to read only this many characters rather than up to a + # character bound to accept-line. + @rl_num_chars_to_read = 0 + + # Line buffer and maintenence. + @rl_line_buffer = nil + + # Key sequence `contexts' + @_rl_kscxt = nil + + # Non-zero means do not parse any lines other than comments and + # parser directives. + @_rl_parsing_conditionalized_out = false + + # Non-zero means to convert characters with the meta bit set to + # escape-prefixed characters so we can indirect through + # emacs_meta_keymap or vi_escape_keymap. + @_rl_convert_meta_chars_to_ascii = true + + # Non-zero means to output characters with the meta bit set directly + # rather than as a meta-prefixed escape sequence. + @_rl_output_meta_chars = false + + # Non-zero means to look at the termios special characters and bind + # them to equivalent readline functions at startup. + @_rl_bind_stty_chars = true + + @rl_completion_display_matches_hook = nil + + XOK = 1 + + @_rl_term_clreol = nil + @_rl_term_clrpag = nil + @_rl_term_cr = nil + @_rl_term_backspace = nil + @_rl_term_goto = nil + @_rl_term_pc = nil + + # Non-zero if we determine that the terminal can do character insertion. + @_rl_terminal_can_insert = false + + # How to insert characters. + @_rl_term_im = nil + @_rl_term_ei = nil + @_rl_term_ic = nil + @_rl_term_ip = nil + @_rl_term_IC = nil + + # How to delete characters. + @_rl_term_dc = nil + @_rl_term_DC = nil + + @_rl_term_forward_char = nil + + # How to go up a line. + @_rl_term_up = nil + + # A visible bell; char if the terminal can be made to flash the screen. + @_rl_visible_bell = nil + + # Non-zero means the terminal can auto-wrap lines. + @_rl_term_autowrap = true + + # Non-zero means that this terminal has a meta key. + @term_has_meta = 0 + + # The sequences to write to turn on and off the meta key, if this + # terminal has one. + @_rl_term_mm = nil + @_rl_term_mo = nil + + # The key sequences output by the arrow keys, if this terminal has any. + @_rl_term_ku = nil + @_rl_term_kd = nil + @_rl_term_kr = nil + @_rl_term_kl = nil + + # How to initialize and reset the arrow keys, if this terminal has any. + @_rl_term_ks = nil + @_rl_term_ke = nil + + # The key sequences sent by the Home and End keys, if any. + @_rl_term_kh = nil + @_rl_term_kH = nil + @_rl_term_at7 = nil + + # Delete key + @_rl_term_kD = nil + + # Insert key + @_rl_term_kI = nil + + # Cursor control + @_rl_term_vs = nil # very visible + @_rl_term_ve = nil # normal + + # Variables that hold the screen dimensions, used by the display code. + @_rl_screenwidth = @_rl_screenheight = @_rl_screenchars = 0 + + # Non-zero means the user wants to enable the keypad. + @_rl_enable_keypad = false + + # Non-zero means the user wants to enable a meta key. + @_rl_enable_meta = true + + # **************************************************************** + # + # Completion matching, from readline's point of view. + # + # **************************************************************** + + # Variables known only to the readline library. + + # If non-zero, non-unique completions always show the list of matches. + @_rl_complete_show_all = false + + # If non-zero, non-unique completions show the list of matches, unless it + # is not possible to do partial completion and modify the line. + @_rl_complete_show_unmodified = false + + # If non-zero, completed directory names have a slash appended. + @_rl_complete_mark_directories = true + + # If non-zero, the symlinked directory completion behavior introduced in + # readline-4.2a is disabled, and symlinks that point to directories have + # a slash appended (subject to the value of _rl_complete_mark_directories). + # This is user-settable via the mark-symlinked-directories variable. + @_rl_complete_mark_symlink_dirs = false + + # If non-zero, completions are printed horizontally in alphabetical order, + # like `ls -x'. + @_rl_print_completions_horizontally = false + + @_rl_completion_case_fold = false + + # If non-zero, don't match hidden files (filenames beginning with a `.' on + # Unix) when doing filename completion. + @_rl_match_hidden_files = true + + # Global variables available to applications using readline. + + # Non-zero means add an additional character to each filename displayed + # during listing completion iff rl_filename_completion_desired which helps + # to indicate the type of file being listed. + @rl_visible_stats = false + + # If non-zero, then this is the address of a function to call when + # completing on a directory name. The function is called with + # the address of a string (the current directory name) as an arg. + @rl_directory_completion_hook = nil + + @rl_directory_rewrite_hook = nil + + # Non-zero means readline completion functions perform tilde expansion. + @rl_complete_with_tilde_expansion = false + + # Pointer to the generator function for completion_matches (). + # NULL means to use rl_filename_completion_function (), the default filename + # completer. + @rl_completion_entry_function = nil + + # Pointer to alternative function to create matches. + # Function is called with TEXT, START, and END. + # START and END are indices in RL_LINE_BUFFER saying what the boundaries + # of TEXT are. + # If this function exists and returns NULL then call the value of + # rl_completion_entry_function to try to match, otherwise use the + # array of strings returned. + @rl_attempted_completion_function = nil + + # Non-zero means to suppress normal filename completion after the + # user-specified completion function has been called. + @rl_attempted_completion_over = false + + # Set to a character indicating the type of completion being performed + # by rl_complete_internal, available for use by application completion + # functions. + @rl_completion_type = 0 + + # Up to this many items will be displayed in response to a + # possible-completions call. After that, we ask the user if + # she is sure she wants to see them all. A negative value means + # don't ask. + @rl_completion_query_items = 100 + + @_rl_page_completions = 1 + + # The basic list of characters that signal a break between words for the + # completer routine. The contents of this variable is what breaks words + # in the shell, i.e. " \t\n\"\\'`@$><=" + @rl_basic_word_break_characters = " \t\n\"\\'`@$><=|&{(" # }) + + # List of basic quoting characters. + @rl_basic_quote_characters = "\"'" + + # The list of characters that signal a break between words for + # rl_complete_internal. The default list is the contents of + # rl_basic_word_break_characters. + @rl_completer_word_break_characters = nil + + # Hook function to allow an application to set the completion word + # break characters before readline breaks up the line. Allows + # position-dependent word break characters. + @rl_completion_word_break_hook = nil + + # List of characters which can be used to quote a substring of the line. + # Completion occurs on the entire substring, and within the substring + # rl_completer_word_break_characters are treated as any other character, + # unless they also appear within this list. + @rl_completer_quote_characters = nil + + # List of characters that should be quoted in filenames by the completer. + @rl_filename_quote_characters = nil + + # List of characters that are word break characters, but should be left + # in TEXT when it is passed to the completion function. The shell uses + # this to help determine what kind of completing to do. + @rl_special_prefixes = nil + + # If non-zero, then disallow duplicates in the matches. + @rl_ignore_completion_duplicates = true + + # Non-zero means that the results of the matches are to be treated + # as filenames. This is ALWAYS zero on entry, and can only be changed + # within a completion entry finder function. + @rl_filename_completion_desired = false + + # Non-zero means that the results of the matches are to be quoted using + # double quotes (or an application-specific quoting mechanism) if the + # filename contains any characters in rl_filename_quote_chars. This is + # ALWAYS non-zero on entry, and can only be changed within a completion + # entry finder function. + @rl_filename_quoting_desired = true + + # This function, if defined, is called by the completer when real + # filename completion is done, after all the matching names have been + # generated. It is passed a (char**) known as matches in the code below. + # It consists of a NULL-terminated array of pointers to potential + # matching strings. The 1st element (matches[0]) is the maximal + # substring that is common to all matches. This function can re-arrange + # the list of matches as required, but all elements of the array must be + # free()'d if they are deleted. The main intent of this function is + # to implement FIGNORE a la SunOS csh. + @rl_ignore_some_completions_function = nil + + # Set to a function to quote a filename in an application-specific fashion. + # Called with the text to quote, the type of match found (single or multiple) + # and a pointer to the quoting character to be used, which the function can + # reset if desired. + #rl_filename_quoting_function = rl_quote_filename + + # Function to call to remove quoting characters from a filename. Called + # before completion is attempted, so the embedded quotes do not interfere + # with matching names in the file system. Readline doesn't do anything + # with this it's set only by applications. + @rl_filename_dequoting_function = nil + + # Function to call to decide whether or not a word break character is + # quoted. If a character is quoted, it does not break words for the + # completer. + @rl_char_is_quoted_p = nil + + # If non-zero, the completion functions don't append anything except a + # possible closing quote. This is set to 0 by rl_complete_internal and + # may be changed by an application-specific completion function. + @rl_completion_suppress_append = false + + # Character appended to completed words when at the end of the line. The + # default is a space. + @rl_completion_append_character = ' ' + + # If non-zero, the completion functions don't append any closing quote. + # This is set to 0 by rl_complete_internal and may be changed by an + # application-specific completion function. + @rl_completion_suppress_quote = false + + # Set to any quote character readline thinks it finds before any application + # completion function is called. + @rl_completion_quote_character = 0 + + # Set to a non-zero value if readline found quoting anywhere in the word to + # be completed set before any application completion function is called. + @rl_completion_found_quote = false + + # If non-zero, a slash will be appended to completed filenames that are + # symbolic links to directory names, subject to the value of the + # mark-directories variable (which is user-settable). This exists so + # that application completion functions can override the user's preference + # (set via the mark-symlinked-directories variable) if appropriate. + # It's set to the value of _rl_complete_mark_symlink_dirs in + # rl_complete_internal before any application-specific completion + # function is called, so without that function doing anything, the user's + # preferences are honored. + @rl_completion_mark_symlink_dirs = false + + # If non-zero, inhibit completion (temporarily). + @rl_inhibit_completion = false + + # Variables local to this file. + + # Local variable states what happened during the last completion attempt. + @completion_changed_buffer = nil + + # Non-zero means treat 0200 bit in terminal input as Meta bit. + @_rl_meta_flag = false + + # Stack of previous values of parsing_conditionalized_out. + @if_stack = [] + @if_stack_depth = 0 + + + # The last key bindings file read. + @last_readline_init_file = nil + + # The file we're currently reading key bindings from. + @current_readline_init_file = nil + @current_readline_init_include_level = 0 + @current_readline_init_lineno = 0 + + ENV["HOME"] ||= ENV["HOMEDRIVE"]+ENV["HOMEPATH"] + + @directory = nil + @filename = nil + @dirname = nil + @users_dirname = nil + @filename_len = 0 + + attr_accessor :rl_attempted_completion_function,:rl_deprep_term_function, + :rl_event_hook,:rl_attempted_completion_over,:rl_basic_quote_characters, + :rl_basic_word_break_characters,:rl_completer_quote_characters, + :rl_completer_word_break_characters,:rl_completion_append_character, + :rl_filename_quote_characters,:rl_instream,:rl_library_version,:rl_outstream, + :rl_readline_name,:history_length,:history_base + + module_function + # Okay, now we write the entry_function for filename completion. In the + # general case. Note that completion in the shell is a little different + # because of all the pathnames that must be followed when looking up the + # completion for a command. + def rl_filename_completion_function(text, state) + # If we don't have any state, then do some initialization. + if (state == 0) + # If we were interrupted before closing the directory or reading + #all of its contents, close it. + if(@directory) + @directory.close + @directory = nil + end + text.delete!(0.chr) + if text.length == 0 + text = "." + end + + @filename = '' + @directory = nil + dir = text.dup + # We also support "~user" and "~/" syntax. + if (dir[0,1] == '~') + @users_dirname = dir.dup + dir = File.expand_path(dir) + end + + # Try the whole thing as a directory and if that fails, try dirname + # and basename. If that doesn't work either, then it wasn't meant to + # be; we don't have a directory to open or a filename to complete. + if File.directory?(dir) and File.readable?(dir) + @directory = Dir.new(dir) + @dirname = dir.dup + elsif File.directory?(File.dirname(dir)) and File.readable?(File.dirname(dir)) + @dirname = File.dirname(dir) + @directory = Dir.new(@dirname) + @filename = File.basename(dir) + end + + # Save the version of the directory that the user typed if we didn't + # have a reason to do so before + @users_dirname ||= @dirname.dup + + # The directory completion hook should perform any necessary + # dequoting. + if (@rl_directory_completion_hook) + send(rl_directory_completion_hook,@dirname) + elsif (@rl_completion_found_quote && @rl_filename_dequoting_function) + # delete single and double quotes + temp = send(@rl_filename_dequoting_function,@users_dirname, @rl_completion_quote_character) + @users_dirname = temp + end + + # Now dequote a non-null filename. + if (@filename && @filename.length>0 && @rl_completion_found_quote && @rl_filename_dequoting_function) + # delete single and double quotes + temp = send(@rl_filename_dequoting_function,@filename, @rl_completion_quote_character) + @filename = temp + end + + @filename_len = @filename.length + @rl_filename_completion_desired = true + end + + # At this point we should entertain the possibility of hacking wildcarded + # filenames, like /usr/man/man/te. If the directory name + # contains globbing characters, then build an array of directories, and + # then map over that list while completing. + # *** UNIMPLEMENTED *** + + # Now that we have some state, we can read the directory. + entry = nil + while(@directory && (entry = @directory.read)) + d_name = entry + # Special case for no filename. If the user has disabled the + # `match-hidden-files' variable, skip filenames beginning with `.'. + #All other entries except "." and ".." match. + if (@filename_len == 0) + next if (!@_rl_match_hidden_files && d_name[0,1] == '.') + break if (d_name != '.' && d_name != '..') + else + # Otherwise, if these match up to the length of filename, then + # it is a match. + + if (@_rl_completion_case_fold) + break if d_name =~ /^#{Regexp.escape(@filename)}/i + else + break if d_name =~ /^#{Regexp.escape(@filename)}/ + end + end + end + + if entry.nil? + if @directory + @directory.close + @directory = nil + end + @dirname = nil + @filename = nil + @users_dirname = nil + + return nil + else + if (@dirname != '.') + if (@rl_complete_with_tilde_expansion && @users_dirname[0,1] == "~") + temp = @dirname + else + temp = @users_dirname + end + + # make sure the directory name has a trailing slash before + # appending the file name + temp += '/' if (temp[-1,1] != '/') + temp += entry + else + temp = entry.dup + end + return (temp) + end + end + + # A completion function for usernames. + # TEXT contains a partial username preceded by a random + # character (usually `~'). + def rl_username_completion_function(text, state) + return nil if RUBY_PLATFORM =~ /mswin|mingw/ + + if (state == 0) + first_char = text[0,1] + first_char_loc = (first_char == '~' ? 1 : 0) + + username = text[first_char_loc..-1] + namelen = username.length + Etc.setpwent() + end + + while (entry = Etc.getpwent()) + # Null usernames should result in all users as possible completions. + break if (namelen == 0 || entry.name =~ /^#{username}/ ) + end + + if entry.nil? + Etc.endpwent() + return nil + else + value = text.dup + value[first_char_loc..-1] = entry.name + + if (first_char == '~') + @rl_filename_completion_desired = true + end + + return (value) + end + end + + #************************************************************* + # + # Application-callable completion match generator functions + # + #************************************************************* + + # Return an array of (char *) which is a list of completions for TEXT. + # If there are no completions, return a NULL pointer. + # The first entry in the returned array is the substitution for TEXT. + # The remaining entries are the possible completions. + # The array is terminated with a NULL pointer. + # + # ENTRY_FUNCTION is a function of two args, and returns a (char *). + # The first argument is TEXT. + # The second is a state argument it should be zero on the first call, and + # non-zero on subsequent calls. It returns a NULL pointer to the caller + # when there are no more matches. + # + def rl_completion_matches(text, entry_function) + matches = 0 + match_list_size = 10 + match_list = [] + match_list[1] = nil + while (string = send(entry_function, text, matches)) + match_list[matches+=1] = string + match_list[matches+1] = nil + end + + # If there were any matches, then look through them finding out the + # lowest common denominator. That then becomes match_list[0]. + if (matches!=0) + compute_lcd_of_matches(match_list, matches, text) + else # There were no matches. + match_list = nil + end + return (match_list) + end + + def _rl_to_lower(char) + char.nil? ? nil : char.chr.downcase + end + + # Find the common prefix of the list of matches, and put it into + # matches[0]. + def compute_lcd_of_matches(match_list, matches, text) + # If only one match, just use that. Otherwise, compare each + # member of the list with the next, finding out where they + # stop matching. + if (matches == 1) + match_list[0] = match_list[1] + match_list[1] = nil + return 1 + end + + i = 1 + low = 100000 + while(i 1) + si += v - 1 + end + else + break if (c1 != c2) + end + si += 1 + end + else + si = 0 + while((c1 = match_list[i][si]) && + (c2 = match_list[i + 1][si])) + if !@rl_byte_oriented + if(!_rl_compare_chars(match_list[i],si,match_list[i+1],si)) + break + elsif ((v = _rl_get_char_len(match_list[i][si..-1])) > 1) + si += v - 1 + end + else + break if (c1 != c2) + end + si += 1 + end + end + + if (low > si) + low = si + end + i += 1 + end + + # If there were multiple matches, but none matched up to even the + # first character, and the user typed something, use that as the + # value of matches[0]. + if (low == 0 && text && text.length>0 ) + match_list[0] = text.dup + else + # XXX - this might need changes in the presence of multibyte chars + + # If we are ignoring case, try to preserve the case of the string + # the user typed in the face of multiple matches differing in case. + if (@_rl_completion_case_fold) + + # We're making an assumption here: + # IF we're completing filenames AND + # the application has defined a filename dequoting function AND + # we found a quote character AND + # the application has requested filename quoting + # THEN + # we assume that TEXT was dequoted before checking against + # the file system and needs to be dequoted here before we + # check against the list of matches + # FI + if (@rl_filename_completion_desired && + @rl_filename_dequoting_function && + @rl_completion_found_quote && + @rl_filename_quoting_desired) + + dtext = send(@rl_filename_dequoting_function,text, @rl_completion_quote_character) + text = dtext + end + + # sort the list to get consistent answers. + match_list = [match_list[0]] + match_list[1..-1].sort + + si = text.length + if (si <= low) + for i in 1 .. matches + if match_list[i][0,si] == text + match_list[0] = match_list[i][0,low] + break + end + # no casematch, use first entry + if (i > matches) + match_list[0] = match_list[1][0,low] + end + end + else + # otherwise, just use the text the user typed. + match_list[0] = text[0,low] + end + else + match_list[0] = match_list[1][0,low] + end + end + + return matches + end + + + def rl_vi_editing_mode(count, key) + _rl_set_insert_mode(RL_IM_INSERT, 1) # vi mode ignores insert mode + @rl_editing_mode = @vi_mode + rl_vi_insertion_mode(1, key) + 0 + end + + # Switching from one mode to the other really just involves + # switching keymaps. + def rl_vi_insertion_mode(count, key) + @_rl_keymap = @vi_insertion_keymap + @_rl_vi_last_key_before_insert = key + 0 + end + + def rl_emacs_editing_mode(count, key) + @rl_editing_mode = @emacs_mode + _rl_set_insert_mode(RL_IM_INSERT, 1) # emacs mode default is insert mode + @_rl_keymap = @emacs_standard_keymap + 0 + end + + # Function for the rest of the library to use to set insert/overwrite mode. + def _rl_set_insert_mode(im, force) + @rl_insert_mode = im + end + + # Toggle overwrite mode. A positive explicit argument selects overwrite + # mode. A negative or zero explicit argument selects insert mode. + def rl_overwrite_mode(count, key) + if (!@rl_explicit_arg) + _rl_set_insert_mode(@rl_insert_mode ^ 1, 0) + elsif (count > 0) + _rl_set_insert_mode(RL_IM_OVERWRITE, 0) + else + _rl_set_insert_mode(RL_IM_INSERT, 0) + end + 0 + end + + + # A function for simple tilde expansion. + def rl_tilde_expand(ignore, key) + _end = @rl_point + start = _end - 1 + + if (@rl_point == @rl_end && @rl_line_buffer[@rl_point,1] == '~' ) + homedir = File.expand_path("~") + _rl_replace_text(homedir, start, _end) + return (0) + elsif (@rl_line_buffer[start,1] != '~') + while(!whitespace(@rl_line_buffer[start,1]) && start >= 0) + start -= 1 + end + start+=1 + end + + _end = start + begin + _end+=1 + end while(!whitespace(@rl_line_buffer[_end,1]) && _end < @rl_end) + + if (whitespace(@rl_line_buffer[_end,1]) || _end >= @rl_end) + _end-=1 + end + + # If the first character of the current word is a tilde, perform + #tilde expansion and insert the result. If not a tilde, do + # nothing. + if (@rl_line_buffer[start,1] == '~') + + len = _end - start + 1 + temp = @rl_line_buffer[start,len] + homedir = File.expand_path(temp) + temp = nil + + _rl_replace_text(homedir, start, _end) + end + 0 + end + + # Clean up the terminal and readline state after catching a signal, before + # resending it to the calling application. + def rl_cleanup_after_signal() + _rl_clean_up_for_exit() + if (@rl_deprep_term_function) + send(@rl_deprep_term_function) + end + rl_clear_pending_input() + rl_clear_signals() + end + + def _rl_clean_up_for_exit() + if @readline_echoing_p + _rl_move_vert(@_rl_vis_botlin) + @_rl_vis_botlin = 0 + @rl_outstream.flush + rl_restart_output(1, 0) + end + end + + # Move the cursor from _rl_last_c_pos to NEW, which are buffer indices. + # (Well, when we don't have multibyte characters, _rl_last_c_pos is a + # buffer index.) + # DATA is the contents of the screen line of interest; i.e., where + # the movement is being done. + def _rl_move_cursor_relative(new, data, start=0) + + woff = w_offset(@_rl_last_v_pos, @wrap_offset) + cpos = @_rl_last_c_pos + + if !@rl_byte_oriented + dpos = _rl_col_width(data, start, start+new) + + if (dpos > @prompt_last_invisible) # XXX - don't use woff here + dpos -= woff + # Since this will be assigned to _rl_last_c_pos at the end (more + # precisely, _rl_last_c_pos == dpos when this function returns), + # let the caller know. + @cpos_adjusted = true + end + else + dpos = new + end + # If we don't have to do anything, then return. + if (cpos == dpos) + return + end + + if @hConsoleHandle + csbi = 0.chr * 24 + @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) + x,y = csbi[4,4].unpack('SS') + x = dpos + @SetConsoleCursorPosition.Call(@hConsoleHandle,y*65536+x) + @_rl_last_c_pos = dpos + return + end + + # It may be faster to output a CR, and then move forwards instead + # of moving backwards. + # i == current physical cursor position. + if !@rl_byte_oriented + i = @_rl_last_c_pos + else + i = @_rl_last_c_pos - woff + end + + if (dpos == 0 || cr_faster(dpos, @_rl_last_c_pos) || + (@_rl_term_autowrap && i == @_rl_screenwidth)) + @rl_outstream.write(@_rl_term_cr) + cpos = @_rl_last_c_pos = 0 + end + + if (cpos < dpos) + # Move the cursor forward. We do it by printing the command + # to move the cursor forward if there is one, else print that + # portion of the output buffer again. Which is cheaper? + + # The above comment is left here for posterity. It is faster + # to print one character (non-control) than to print a control + # sequence telling the terminal to move forward one character. + # That kind of control is for people who don't know what the + # data is underneath the cursor. + + # However, we need a handle on where the current display position is + # in the buffer for the immediately preceding comment to be true. + # In multibyte locales, we don't currently have that info available. + # Without it, we don't know where the data we have to display begins + # in the buffer and we have to go back to the beginning of the screen + # line. In this case, we can use the terminal sequence to move forward + # if it's available. + if !@rl_byte_oriented + if (@_rl_term_forward_char) + @rl_outstream.write(@_rl_term_forward_char * (dpos-cpos)) + else + @rl_outstream.write(@_rl_term_cr) + @rl_outstream.write(data[start,new]) + end + else + @rl_outstream.write(data[start+cpos,new-cpos]) + end + elsif (cpos > dpos) + _rl_backspace(cpos - dpos) + end + @_rl_last_c_pos = dpos + end + + + # PWP: move the cursor up or down. + def _rl_move_vert(to) + if (@_rl_last_v_pos == to || to > @_rl_screenheight) + return + end + + if ((delta = to - @_rl_last_v_pos) > 0) + @rl_outstream.write("\n"*delta) + @rl_outstream.write("\r") + @_rl_last_c_pos = 0 + else + if(@_rl_term_up) + @rl_outstream.write(@_rl_term_up*(-delta)) + end + end + @_rl_last_v_pos = to # Now TO is here + end + + def rl_setstate(x) + (@rl_readline_state |= (x)) + end + + def rl_unsetstate(x) + (@rl_readline_state &= ~(x)) + end + + def rl_isstate(x) + (@rl_readline_state & (x))!=0 + end + + # Clear any pending input pushed with rl_execute_next() + def rl_clear_pending_input() + @rl_pending_input = 0 + rl_unsetstate(RL_STATE_INPUTPENDING) + 0 + end + + def rl_restart_output(count, key) + 0 + end + + def rl_clear_signals() + if Signal.list['WINCH'] + trap "WINCH",@def_proc + end + end + + def rl_set_signals() + if Signal.list['WINCH'] + @def_proc = trap "WINCH",Proc.new{rl_sigwinch_handler(0)} + end + end + + # Current implementation: + # \001 (^A) start non-visible characters + # \002 (^B) end non-visible characters + # all characters except \001 and \002 (following a \001) are copied to + # the returned string all characters except those between \001 and + # \002 are assumed to be `visible'. + def expand_prompt(pmt) + # Short-circuit if we can. + if (@rl_byte_oriented && pmt[RL_PROMPT_START_IGNORE].nil?) + r = pmt.dup + lp = r.length + lip = 0 + niflp = 0 + vlp = lp + return [r,lp,lip,niflp,vlp] + end + + l = pmt.length + ret = '' + invfl = 0 # invisible chars in first line of prompt + invflset = 0 # we only want to set invfl once + + igstart = 0 + rl = 0 + ignoring = false + last = ninvis = physchars = 0 + for pi in 0 ... pmt.length + # This code strips the invisible character string markers + #RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE + if (!ignoring && pmt[pi,1] == RL_PROMPT_START_IGNORE) # XXX - check ignoring? + ignoring = true + igstart = pi + next + elsif (ignoring && pmt[pi,1] == RL_PROMPT_END_IGNORE) + ignoring = false + if (pi != (igstart + 1)) + last = ret.length - 1 + end + next + else + if !@rl_byte_oriented + pind = pi + ind = _rl_find_next_mbchar(pmt, pind, 1, MB_FIND_NONZERO) + l = ind - pind + while (l>0) + l-=1 + ret << pmt[pi] + pi += 1 + end + if (!ignoring) + rl += ind - pind + physchars += _rl_col_width(pmt, pind, ind) + else + ninvis += ind - pind + end + pi-=1 # compensate for later increment + else + ret << pmt[pi] + if (!ignoring) + rl+=1 # visible length byte counter + physchars+=1 + else + ninvis+=1 # invisible chars byte counter + end + + if (invflset == 0 && rl >= @_rl_screenwidth) + invfl = ninvis + invflset = 1 + end + end + end + end + + if (rl < @_rl_screenwidth) + invfl = ninvis + end + lp = rl + lip = last + niflp = invfl + vlp = physchars + return [ret,lp,lip,niflp,vlp] + end + + + #* + #* Expand the prompt string into the various display components, if + #* necessary. + #* + #* local_prompt = expanded last line of string in rl_display_prompt + #* (portion after the final newline) + #* local_prompt_prefix = portion before last newline of rl_display_prompt, + #* expanded via expand_prompt + #* prompt_visible_length = number of visible characters in local_prompt + #* prompt_prefix_length = number of visible characters in local_prompt_prefix + #* + #* This function is called once per call to readline(). It may also be + #* called arbitrarily to expand the primary prompt. + #* + #* The return value is the number of visible characters on the last line + #* of the (possibly multi-line) prompt. + #* + def rl_expand_prompt(prompt) + @local_prompt = @local_prompt_prefix = nil + @local_prompt_len = 0 + @prompt_last_invisible = @prompt_invis_chars_first_line = 0 + @prompt_visible_length = @prompt_physical_chars = 0 + + if (prompt.nil? || prompt == '') + return (0) + end + + pi = prompt.rindex("\n") + if pi.nil? + # The prompt is only one logical line, though it might wrap. + @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = + expand_prompt(prompt) + @local_prompt_prefix = nil + @local_prompt_len = @local_prompt ? @local_prompt.length : 0 + return (@prompt_visible_length) + else + # The prompt spans multiple lines. + pi += 1 if prompt.length!=pi+1 + t = pi + @local_prompt,@prompt_visible_length,@prompt_last_invisible,_,@prompt_physical_chars = expand_prompt(prompt[pi..-1]) + c = prompt[t] + prompt[t] = 0.chr + # The portion of the prompt string up to and including the + #final newline is now null-terminated. + @local_prompt_prefix,@prompt_prefix_length,_,@prompt_invis_chars_first_line, = expand_prompt(prompt) + prompt[t] = c + @local_prompt_len = @local_prompt ? @local_prompt.length : 0 + return (@prompt_prefix_length) + end + end + + # Set up the prompt and expand it. Called from readline() and + # rl_callback_handler_install (). + def rl_set_prompt(prompt) + @rl_prompt = prompt ? prompt.dup : nil + @rl_display_prompt = @rl_prompt ? @rl_prompt : "" + @rl_visible_prompt_length = rl_expand_prompt(@rl_prompt) + 0 + end + + def get_term_capabilities(buffer) + hash = {} + `infocmp -C`.split(':').select{|x| x =~ /(.*)=(.*)/ and hash[$1]=$2.gsub('\\E',"\e").gsub(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} + @_rl_term_at7 = hash["@7"] + @_rl_term_DC = hash["DC"] + @_rl_term_IC = hash["IC"] + @_rl_term_clreol = hash["ce"] + @_rl_term_clrpag = hash["cl"] + @_rl_term_cr = hash["cr"] + @_rl_term_dc = hash["dc"] + @_rl_term_ei = hash["ei"] + @_rl_term_ic = hash["ic"] + @_rl_term_im = hash["im"] + @_rl_term_kD = hash["kD"] + @_rl_term_kH = hash["kH"] + @_rl_term_kI = hash["kI"] + @_rl_term_kd = hash["kd"] + @_rl_term_ke = hash["ke"] + @_rl_term_kh = hash["kh"] + @_rl_term_kl = hash["kl"] + @_rl_term_kr = hash["kr"] + @_rl_term_ks = hash["ks"] + @_rl_term_ku = hash["ku"] + @_rl_term_backspace = hash["le"] + @_rl_term_mm = hash["mm"] + @_rl_term_mo = hash["mo"] + @_rl_term_forward_char = hash["nd"] + @_rl_term_pc = hash["pc"] + @_rl_term_up = hash["up"] + @_rl_visible_bell = hash["vb"] + @_rl_term_vs = hash["vs"] + @_rl_term_ve = hash["ve"] + @tcap_initialized = true + end + + # Set the environment variables LINES and COLUMNS to lines and cols, + # respectively. + def sh_set_lines_and_columns(lines, cols) + ENV["LINES"] = lines.to_s + ENV["COLUMNS"] = cols.to_s + end + + # Get readline's idea of the screen size. TTY is a file descriptor open + # to the terminal. If IGNORE_ENV is true, we do not pay attention to the + # values of $LINES and $COLUMNS. The tests for TERM_STRING_BUFFER being + # non-null serve to check whether or not we have initialized termcap. + def _rl_get_screen_size(tty, ignore_env) + + if @hConsoleHandle + csbi = 0.chr * 24 + @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) + wc,wr = csbi[0,4].unpack('SS') + # wr,wc, = `mode con`.scan(/\d+\n/).map{|x| x.to_i} + @_rl_screenwidth = wc + @_rl_screenheight = wr + else + wr,wc = `stty size`.split(' ').map{|x| x.to_i} + @_rl_screenwidth = wc + @_rl_screenheight = wr + if ignore_env==0 && ENV['LINES'] + @_rl_screenheight = ENV['LINES'].to_i + end + if ignore_env==0 && ENV['COLUMNS'] + @_rl_screenwidth = ENV['COLUMNS'].to_i + end + end + + # If all else fails, default to 80x24 terminal. + if @_rl_screenwidth.nil? || @_rl_screenwidth <= 1 + @_rl_screenwidth = 80 + end + if @_rl_screenheight.nil? || @_rl_screenheight <= 0 + @_rl_screenheight = 24 + end + # If we're being compiled as part of bash, set the environment + # variables $LINES and $COLUMNS to new values. Otherwise, just + # do a pair of putenv () or setenv () calls. + sh_set_lines_and_columns(@_rl_screenheight, @_rl_screenwidth) + + if !@_rl_term_autowrap + @_rl_screenwidth-=1 + end + @_rl_screenchars = @_rl_screenwidth * @_rl_screenheight + end + + def tgetflag(name) + `infocmp -C -r`.scan(/\w{2}/).include?(name) + end + + # Return the function (or macro) definition which would be invoked via + # KEYSEQ if executed in MAP. If MAP is NULL, then the current keymap is + # used. TYPE, if non-NULL, is a pointer to an int which will receive the + # type of the object pointed to. One of ISFUNC (function), ISKMAP (keymap), + # or ISMACR (macro). + def rl_function_of_keyseq(keyseq, map, type) + map ||= @_rl_keymap + map[keyseq] + end + + # Bind the key sequence represented by the string KEYSEQ to + # the arbitrary pointer DATA. TYPE says what kind of data is + # pointed to by DATA, right now this can be a function (ISFUNC), + # a macro (ISMACR), or a keymap (ISKMAP). This makes new keymaps + # as necessary. The initial place to do bindings is in MAP. + def rl_generic_bind(type, keyseq, data, map) + map[keyseq] = data + 0 + end + + # Bind the key sequence represented by the string KEYSEQ to + # FUNCTION. This makes new keymaps as necessary. The initial + # place to do bindings is in MAP. + def rl_bind_keyseq_in_map(keyseq, function, map) + rl_generic_bind(ISFUNC, keyseq, function, map) + end + + + # Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right + # now, this is always used to attempt to bind the arrow keys, hence the + # check for rl_vi_movement_mode. + def rl_bind_keyseq_if_unbound_in_map(keyseq, default_func, kmap) + if (keyseq) + func = rl_function_of_keyseq(keyseq, kmap, nil) + if (func.nil? || func == :rl_vi_movement_mode) + return (rl_bind_keyseq_in_map(keyseq, default_func, kmap)) + else + return 1 + end + end + 0 + end + + def rl_bind_keyseq_if_unbound(keyseq, default_func) + rl_bind_keyseq_if_unbound_in_map(keyseq, default_func, @_rl_keymap) + end + + # Bind the arrow key sequences from the termcap description in MAP. + def bind_termcap_arrow_keys(map) + xkeymap = @_rl_keymap + @_rl_keymap = map + + rl_bind_keyseq_if_unbound(@_rl_term_ku, :rl_get_previous_history) + rl_bind_keyseq_if_unbound(@_rl_term_kd, :rl_get_next_history) + rl_bind_keyseq_if_unbound(@_rl_term_kr, :rl_forward_char) + rl_bind_keyseq_if_unbound(@_rl_term_kl, :rl_backward_char) + + rl_bind_keyseq_if_unbound(@_rl_term_kh, :rl_beg_of_line) # Home + rl_bind_keyseq_if_unbound(@_rl_term_at7, :rl_end_of_line) # End + + rl_bind_keyseq_if_unbound(@_rl_term_kD, :rl_delete) + rl_bind_keyseq_if_unbound(@_rl_term_kI, :rl_overwrite_mode) + + @_rl_keymap = xkeymap + end + + def _rl_init_terminal_io(terminal_name) + term = terminal_name ? terminal_name : ENV["TERM"] + @_rl_term_clrpag = @_rl_term_cr = @_rl_term_clreol = nil + tty = @rl_instream ? @rl_instream.fileno : 0 + + if no_terminal? + term = "dumb" + @_rl_bind_stty_chars = false + end + + @term_string_buffer ||= 0.chr * 2032 + + @term_buffer ||= 0.chr * 4080 + + buffer = @term_string_buffer + + tgetent_ret = (term != "dumb") ? 1 : -1 + + if (tgetent_ret <= 0) + buffer = @term_buffer = @term_string_buffer = nil + + @_rl_term_autowrap = false # used by _rl_get_screen_size + + # Allow calling application to set default height and width, using + #rl_set_screen_size + if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) + _rl_get_screen_size(tty, 0) + end + + # Defaults. + if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) + @_rl_screenwidth = 79 + @_rl_screenheight = 24 + end + + # Everything below here is used by the redisplay code (tputs). + @_rl_screenchars = @_rl_screenwidth * @_rl_screenheight + @_rl_term_cr = "\r" + @_rl_term_im = @_rl_term_ei = @_rl_term_ic = @_rl_term_IC = nil + @_rl_term_up = @_rl_term_dc = @_rl_term_DC = @_rl_visible_bell = nil + @_rl_term_ku = @_rl_term_kd = @_rl_term_kl = @_rl_term_kr = nil + @_rl_term_kh = @_rl_term_kH = @_rl_term_kI = @_rl_term_kD = nil + @_rl_term_ks = @_rl_term_ke = @_rl_term_at7 = nil + @_rl_term_mm = @_rl_term_mo = nil + @_rl_term_ve = @_rl_term_vs = nil + @_rl_term_forward_char = nil + @_rl_terminal_can_insert = @term_has_meta = false + + # Reasonable defaults for tgoto(). Readline currently only uses + # tgoto if _rl_term_IC or _rl_term_DC is defined, but just in case we + # change that later... + @_rl_term_backspace = "\b" + + return 0 + end + + get_term_capabilities(buffer) + + @_rl_term_cr ||= "\r" + @_rl_term_autowrap = !!(tgetflag("am") && tgetflag("xn")) + + # Allow calling application to set default height and width, using + # rl_set_screen_size + if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) + _rl_get_screen_size(tty, 0) + end + + # "An application program can assume that the terminal can do + # character insertion if *any one of* the capabilities `IC', + # `im', `ic' or `ip' is provided." But we can't do anything if + # only `ip' is provided, so... + @_rl_terminal_can_insert = !!(@_rl_term_IC || @_rl_term_im || @_rl_term_ic) + + # Check to see if this terminal has a meta key and clear the capability + # variables if there is none. + @term_has_meta = !!(tgetflag("km") || tgetflag("MT")) + if !@term_has_meta + @_rl_term_mm = @_rl_term_mo = nil + end + + # Attempt to find and bind the arrow keys. Do not override already + # bound keys in an overzealous attempt, however. + + bind_termcap_arrow_keys(@emacs_standard_keymap) + + bind_termcap_arrow_keys(@vi_movement_keymap) + bind_termcap_arrow_keys(@vi_insertion_keymap) + + return 0 + end + + # New public way to set the system default editing chars to their readline + # equivalents. + def rl_tty_set_default_bindings(kmap) + h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten] + h.each {|k,v| v.gsub!(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} + kmap[h['erase']] = :rl_rubout + kmap[h['kill']] = :rl_unix_line_discard + kmap[h['werase']] = :rl_unix_word_rubout + kmap[h['lnext']] = :rl_quoted_insert + end + + # If this system allows us to look at the values of the regular + # input editing characters, then bind them to their readline + # equivalents, iff the characters are not bound to keymaps. + def readline_default_bindings() + if @_rl_bind_stty_chars + rl_tty_set_default_bindings(@_rl_keymap) + end + end + + def _rl_init_eightbit() + + end + + + # Do key bindings from a file. If FILENAME is NULL it defaults + # to the first non-null filename from this list: + # 1. the filename used for the previous call + # 2. the value of the shell variable `INPUTRC' + # 3. ~/.inputrc + # 4. /etc/inputrc + # If the file existed and could be opened and read, 0 is returned, + # otherwise errno is returned. + def rl_read_init_file(filename) + + +=begin + # Default the filename. + filename ||= @last_readline_init_file + filename ||= ENV["INPUTRC"] + if (filename.nil? || filename == '') + filename = DEFAULT_INPUTRC + # Try to read DEFAULT_INPUTRC; fall back to SYS_INPUTRC on failure + if (_rl_read_init_file(filename, 0) == 0) + return 0 + end + filename = SYS_INPUTRC + end + + if RUBY_PLATFORM =~ /mswin|mingw/ + return 0 if (_rl_read_init_file(filename, 0) == 0) + filename = "~/_inputrc" + end + return (_rl_read_init_file(filename, 0)) +=end + # + # This code is too problematic at the moment + # Just hardcode things and move on + # + return 0 + end + + def _rl_read_init_file(filename, include_level) + @current_readline_init_file = filename + @current_readline_init_include_level = include_level + + openname = File.expand_path(filename) + begin + buffer = File.open(openname).read + rescue + return -1 + end + + if (include_level == 0 && filename != @last_readline_init_file) + @last_readline_init_file = filename.dup + end + + @currently_reading_init_file = true + + # Loop over the lines in the file. Lines that start with `#' are + # comments; all other lines are commands for readline initialization. + @current_readline_init_lineno = 1 + + buffer.each_line do |line| + line.strip! + next if line =~ /^#/ + next if line == '' + rl_parse_and_bind(line) + end + + return 0 + end + + # Push _rl_parsing_conditionalized_out, and set parser state based + # on ARGS. + def parser_if(args) + # Push parser state. + @if_stack << @_rl_parsing_conditionalized_out + + # If parsing is turned off, then nothing can turn it back on except + # for finding the matching endif. In that case, return right now. + if @_rl_parsing_conditionalized_out + return 0 + end + + args.downcase! + # Handle "$if term=foo" and "$if mode=emacs" constructs. If this + # isn't term=foo, or mode=emacs, then check to see if the first + # word in ARGS is the same as the value stored in rl_readline_name. + if (@rl_terminal_name && args =~ /^term=/) + # Terminals like "aaa-60" are equivalent to "aaa". + tname = @rl_terminal_name.downcase.gsub(/-.*$/,'') + + # Test the `long' and `short' forms of the terminal name so that + #if someone has a `sun-cmd' and does not want to have bindings + #that will be executed if the terminal is a `sun', they can put + #`$if term=sun-cmd' into their .inputrc. + @_rl_parsing_conditionalized_out = (args[5..-1] != tname && args[5..-1] != @rl_terminal_name.downcase) + elsif args =~ /^mode=/ + if args[5..-1] == "emacs" + mode = @emacs_mode + elsif args[5..-1] == "vi" + $stderr.puts "*** Warning: vi-mode not supported, switching back to emacs mode" + mode = @emacs_mode + else + mode = @no_mode + end + @_rl_parsing_conditionalized_out = (mode != @rl_editing_mode) + # Check to see if the first word in ARGS is the same as the + # value stored in rl_readline_name. + elsif (args == @rl_readline_name) + @_rl_parsing_conditionalized_out = false + else + @_rl_parsing_conditionalized_out = true + end + return 0 + end + + # Invert the current parser state if there is anything on the stack. + def parser_else(args) + if @if_stack.empty? + #_rl_init_file_error ("$else found without matching $if") + return 0 + end + + # Check the previous (n) levels of the stack to make sure that + # we haven't previously turned off parsing. + return 0 if @if_stack.detect {|x| x } + + # Invert the state of parsing if at top level. + @_rl_parsing_conditionalized_out = !@_rl_parsing_conditionalized_out + return 0 + end + + # Terminate a conditional, popping the value of + # _rl_parsing_conditionalized_out from the stack. + def parser_endif(args) + if (@if_stack.length>0) + @_rl_parsing_conditionalized_out = @if_stack.pop + else + #_rl_init_file_error ("$endif without matching $if") + end + 0 + end + + def parser_include(args) + return 0 if (@_rl_parsing_conditionalized_out) + + old_init_file = @current_readline_init_file + old_line_number = @current_readline_init_lineno + old_include_level = @current_readline_init_include_level + + r = _rl_read_init_file(args, old_include_level + 1) + + @current_readline_init_file = old_init_file + @current_readline_init_lineno = old_line_number + @current_readline_init_include_level = old_include_level + + return r + end + + # Handle a parser directive. STATEMENT is the line of the directive + # without any leading `$'. + def handle_parser_directive(statement) + + directive,args = statement.split(' ') + + case directive.downcase + when "if" + parser_if(args) + return 0 + when "endif" + parser_endif(args) + return 0 + when "else" + parser_else(args) + return 0 + when "include" + parser_include(args) + return 0 + end + + #_rl_init_file_error("unknown parser directive") + return 1 + end + + + def rl_variable_bind(name,value) + case name + when "bind-tty-special-chars" + @_rl_bind_stty_chars = value.nil? || value=='1' || value == 'on' + when "blink-matching-paren" + @rl_blink_matching_paren = value.nil? || value=='1' || value == 'on' + when "byte-oriented" + @rl_byte_oriented = value.nil? || value=='1' || value == 'on' + when "completion-ignore-case" + @_rl_completion_case_fold = value.nil? || value=='1' || value == 'on' + when "convert-meta" + @_rl_convert_meta_chars_to_ascii = value.nil? || value=='1' || value == 'on' + when "disable-completion" + @rl_inhibit_completion = value.nil? || value=='1' || value == 'on' + when "enable-keypad" + @_rl_enable_keypad = value.nil? || value=='1' || value == 'on' + when "expand-tilde" + @rl_complete_with_tilde_expansion = value.nil? || value=='1' || value == 'on' + when "history-preserve-point" + @_rl_history_preserve_point = value.nil? || value=='1' || value == 'on' + when "horizontal-scroll-mode" + @_rl_horizontal_scroll_mode = value.nil? || value=='1' || value == 'on' + when "input-meta" + @_rl_meta_flag = value.nil? || value=='1' || value == 'on' + when "mark-directories" + @_rl_complete_mark_directories = value.nil? || value=='1' || value == 'on' + when "mark-modified-lines" + @_rl_mark_modified_lines = value.nil? || value=='1' || value == 'on' + when "mark-symlinked-directories" + @_rl_complete_mark_symlink_dirs = value.nil? || value=='1' || value == 'on' + when "match-hidden-files" + @_rl_match_hidden_files = value.nil? || value=='1' || value == 'on' + when "meta-flag" + @_rl_meta_flag = value.nil? || value=='1' || value == 'on' + when "output-meta" + @_rl_output_meta_chars = value.nil? || value=='1' || value == 'on' + when "page-completions" + @_rl_page_completions = value.nil? || value=='1' || value == 'on' + when "prefer-visible-bell" + @_rl_prefer_visible_bell = value.nil? || value=='1' || value == 'on' + when "print-completions-horizontally" + @_rl_print_completions_horizontally = value.nil? || value=='1' || value == 'on' + when "show-all-if-ambiguous" + @_rl_complete_show_all = value.nil? || value=='1' || value == 'on' + when "show-all-if-unmodified" + @_rl_complete_show_unmodified = value.nil? || value=='1' || value == 'on' + when "visible-stats" + @rl_visible_stats = value.nil? || value=='1' || value == 'on' + when "bell-style" + case value + when "none","off" + @_rl_bell_preference = NO_BELL + when "audible", "on" + @_rl_bell_preference = AUDIBLE_BELL + when "visible" + @_rl_bell_preference = VISIBLE_BELL + else + @_rl_bell_preference = AUDIBLE_BELL + end + when "comment-begin" + @_rl_comment_begin = value.dup + when "completion-query-items" + @rl_completion_query_items = value.to_i + when "editing-mode" + case value + when "vi" + $stderr.puts "*** Warning: vi editing-mode not supported, switching back to emacs" + #@_rl_keymap = @vi_insertion_keymap + #@rl_editing_mode = @vi_mode + @_rl_keymap = @emacs_standard_keymap + @rl_editing_mode = @emacs_mode + when "emacs" + @_rl_keymap = @emacs_standard_keymap + @rl_editing_mode = @emacs_mode + end + when "isearch-terminators" + @_rl_isearch_terminators = instance_eval(value) + when "keymap" + case value + when "emacs","emacs-standard","emacs-meta","emacs-ctlx" + @_rl_keymap = @emacs_standard_keymap + when "vi","vi-move","vi-command" + @_rl_keymap = @vi_movement_keymap + when "vi-insert" + @_rl_keymap = @vi_insertion_keymap + end + end + end + + def rl_named_function(name) + case name + when "accept-line" + return :rl_newline + when "arrow-key-prefix" + return :rl_arrow_keys + when "backward-delete-char" + return :rl_rubout + when "character-search" + return :rl_char_search + when "character-search-backward" + return :rl_backward_char_search + when "copy-region-as-kill" + return :rl_copy_region_to_kill + when "delete-char" + return :rl_delete + when "delete-char-or-list" + return :rl_delete_or_show_completions + when "forward-backward-delete-char" + return :rl_rubout_or_delete + when "kill-whole-line" + return :rl_kill_full_line + when "non-incremental-forward-search-history" + return :rl_noninc_forward_search + when "non-incremental-reverse-search-history" + return :rl_noninc_reverse_search + when "non-incremental-forward-search-history-again" + return :rl_noninc_forward_search_again + when "non-incremental-reverse-search-history-again" + return :rl_noninc_reverse_search_again + when "redraw-current-line" + return :rl_refresh_line + when "self-insert" + return :rl_insert + when "undo" + return :rl_undo_command + when "beginning-of-line" + return :rl_beg_of_line + else + if name =~ /^[-a-z]+$/ + return ('rl_'+name.gsub('-','_')).to_sym + end + end + nil + end + + # Bind KEY to FUNCTION. Returns non-zero if KEY is out of range. + def rl_bind_key(key, function) + @_rl_keymap[key] = function + @rl_binding_keymap = @_rl_keymap + 0 + end + + # Read the binding command from STRING and perform it. + # A key binding command looks like: Keyname: function-name\0, + # a variable binding command looks like: set variable value. + # A new-style keybinding looks like "\C-x\C-x": exchange-point-and-mark. + def rl_parse_and_bind(string) + + # If this is a parser directive, act on it. + if (string[0,1] == "$") + handle_parser_directive(string[1..-1]) + return 0 + end + + # If we aren't supposed to be parsing right now, then we're done. + return 0 if @_rl_parsing_conditionalized_out + + if string =~ /^set/i + _,var,value = string.downcase.split(' ') + rl_variable_bind(var, value) + return 0 + end + + if string =~ /"(.*)"\s*:\s*(.*)$/ + key, funname = $1, $2 + + rl_bind_key(key, rl_named_function(funname)) + end + + 0 + end + + + def _rl_enable_meta_key() + if(@term_has_meta && @_rl_term_mm) + @_rl_out_stream.write(@_rl_term_mm) + end + end + + def rl_set_keymap_from_edit_mode() + if (@rl_editing_mode == @emacs_mode) + @_rl_keymap = @emacs_standard_keymap + elsif (@rl_editing_mode == @vi_mode) + @_rl_keymap = @vi_insertion_keymap + end + end + + def rl_get_keymap_name_from_edit_mode() + if (@rl_editing_mode == @emacs_mode) + "emacs" + elsif (@rl_editing_mode == @vi_mode) + "vi" + else + "none" + end + end + + # Bind some common arrow key sequences in MAP. + def bind_arrow_keys_internal(map) + xkeymap = @_rl_keymap + @_rl_keymap = map + + if RUBY_PLATFORM =~ /mswin|mingw/ + rl_bind_keyseq_if_unbound("\340H", :rl_get_previous_history) # Up + rl_bind_keyseq_if_unbound("\340P", :rl_get_next_history) # Down + rl_bind_keyseq_if_unbound("\340M", :rl_forward_char) # Right + rl_bind_keyseq_if_unbound("\340K", :rl_backward_char) # Left + rl_bind_keyseq_if_unbound("\340G", :rl_beg_of_line) # Home + rl_bind_keyseq_if_unbound("\340O", :rl_end_of_line) # End + rl_bind_keyseq_if_unbound("\340s", :rl_backward_word) # Ctrl-Left + rl_bind_keyseq_if_unbound("\340t", :rl_forward_word) # Ctrl-Right + rl_bind_keyseq_if_unbound("\340S", :rl_delete) # Delete + rl_bind_keyseq_if_unbound("\340R", :rl_overwrite_mode) # Insert + else + rl_bind_keyseq_if_unbound("\033[A", :rl_get_previous_history) + rl_bind_keyseq_if_unbound("\033[B", :rl_get_next_history) + rl_bind_keyseq_if_unbound("\033[C", :rl_forward_char) + rl_bind_keyseq_if_unbound("\033[D", :rl_backward_char) + rl_bind_keyseq_if_unbound("\033[H", :rl_beg_of_line) + rl_bind_keyseq_if_unbound("\033[F", :rl_end_of_line) + + rl_bind_keyseq_if_unbound("\033OA", :rl_get_previous_history) + rl_bind_keyseq_if_unbound("\033OB", :rl_get_next_history) + rl_bind_keyseq_if_unbound("\033OC", :rl_forward_char) + rl_bind_keyseq_if_unbound("\033OD", :rl_backward_char) + rl_bind_keyseq_if_unbound("\033OH", :rl_beg_of_line) + rl_bind_keyseq_if_unbound("\033OF", :rl_end_of_line) + end + + @_rl_keymap = xkeymap + end + + # Try and bind the common arrow key prefixes after giving termcap and + # the inputrc file a chance to bind them and create `real' keymaps + # for the arrow key prefix. + def bind_arrow_keys() + bind_arrow_keys_internal(@emacs_standard_keymap) + bind_arrow_keys_internal(@vi_movement_keymap) + bind_arrow_keys_internal(@vi_insertion_keymap) + end + + # Initialize the entire state of the world. + def readline_initialize_everything() + # Set up input and output if they are not already set up. + @rl_instream ||= $stdin + + @rl_outstream ||= $stdout + + # Bind _rl_in_stream and _rl_out_stream immediately. These values + # may change, but they may also be used before readline_internal () + # is called. + @_rl_in_stream = @rl_instream + @_rl_out_stream = @rl_outstream + + # Allocate data structures. + @rl_line_buffer = "" + + # Initialize the terminal interface. + @rl_terminal_name ||= ENV["TERM"] + _rl_init_terminal_io(@rl_terminal_name) + + # Bind tty characters to readline functions. + readline_default_bindings() + + # Decide whether we should automatically go into eight-bit mode. + _rl_init_eightbit() + + # Read in the init file. + rl_read_init_file(nil) + + # XXX + if (@_rl_horizontal_scroll_mode && @_rl_term_autowrap) + @_rl_screenwidth -= 1 + @_rl_screenchars -= @_rl_screenheight + end + + # Override the effect of any `set keymap' assignments in the + # inputrc file. + rl_set_keymap_from_edit_mode() + + # Try to bind a common arrow key prefix, if not already bound. + bind_arrow_keys() + + # Enable the meta key, if this terminal has one. + if @_rl_enable_meta + _rl_enable_meta_key() + end + + # If the completion parser's default word break characters haven't + # been set yet, then do so now. + @rl_completer_word_break_characters ||= @rl_basic_word_break_characters + end + + def _rl_init_line_state() + @rl_point = @rl_end = @rl_mark = 0 + @rl_line_buffer = "" + end + + # Set the history pointer back to the last entry in the history. + def _rl_start_using_history() + using_history() + @_rl_saved_line_for_history = nil + end + + + def cr_faster(new, cur) + (new + 1) < (cur - new) + end + + #* _rl_last_c_pos is an absolute cursor position in multibyte locales and a + # buffer index in others. This macro is used when deciding whether the + # current cursor position is in the middle of a prompt string containing + # invisible characters. + def prompt_ending_index() + if !@rl_byte_oriented + @prompt_physical_chars + else + (@prompt_last_invisible+1) + end + end + + # Initialize the VISIBLE_LINE and INVISIBLE_LINE arrays, and their associated + # arrays of line break markers. MINSIZE is the minimum size of VISIBLE_LINE + # and INVISIBLE_LINE; if it is greater than LINE_SIZE, LINE_SIZE is + # increased. If the lines have already been allocated, this ensures that + # they can hold at least MINSIZE characters. + def init_line_structures(minsize) + if @invisible_line.nil? # initialize it + if (@line_size < minsize) + @line_size = minsize + end + @visible_line = 0.chr * @line_size + @invisible_line = 0.chr * @line_size # 1.chr + elsif (@line_size < minsize) # ensure it can hold MINSIZE chars + @line_size *= 2 + if (@line_size < minsize) + @line_size = minsize + end + @visible_line << 0.chr * (@line_size - @visible_line.length) + @invisible_line << 1.chr * (@line_size - @invisible_line.length) + end + @visible_line[minsize,@line_size-minsize] = 0.chr * (@line_size-minsize) + @invisible_line[minsize,@line_size-minsize] = 1.chr * (@line_size-minsize) + + if @vis_lbreaks.nil? + @inv_lbreaks = [] + @vis_lbreaks = [] + @_rl_wrapped_line = [] + @inv_lbreaks[0] = @vis_lbreaks[0] = 0 + end + end + + # Return the history entry at the current position, as determined by + # history_offset. If there is no entry there, return a NULL pointer. + def current_history() + return ((@history_offset == @history_length) || @the_history.nil?) ? + nil : @the_history[@history_offset] + end + + def meta_char(c) + c > "\x7f" && c <= "\xff" + end + + def ctrl_char(c) + c < "\x20" + end + + def isprint(c) + c >= "\x20" && c < "\x7f" + end + + def whitespace(c) + (c == ' ' || c == "\t") + end + + def w_offset(line, offset) + ((line) == 0 ? offset : 0) + end + + def vis_llen(l) + ((l) > @_rl_vis_botlin ? 0 : (@vis_lbreaks[l+1] - @vis_lbreaks[l])) + end + + def inv_llen(l) + (@inv_lbreaks[l+1] - @inv_lbreaks[l]) + end + + def vis_chars(line) + @visible_line[@vis_lbreaks[line] .. -1] + end + + def vis_pos(line) + @vis_lbreaks[line] + end + + def vis_line(line) + ((line) > @_rl_vis_botlin) ? "" : vis_chars(line) + end + + def inv_line(line) + @invisible_line[@inv_lbreaks[line] .. -1] + end + + def m_offset(margin, offset) + ((margin) == 0 ? offset : 0) + end + + + # PWP: update_line() is based on finding the middle difference of each + # line on the screen; vis: + # + # /old first difference + # /beginning of line | /old last same /old EOL + # v v v v + # old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as + # new: eddie> Oh, my little buggy says to me, as lurgid as + # ^ ^ ^ ^ + # \beginning of line | \new last same \new end of line + # \new first difference + # + # All are character pointers for the sake of speed. Special cases for + # no differences, as well as for end of line additions must be handled. + # + # Could be made even smarter, but this works well enough + def update_line(old, ostart, new, current_line, omax, nmax, inv_botlin) + # If we're at the right edge of a terminal that supports xn, we're + # ready to wrap around, so do so. This fixes problems with knowing + # the exact cursor position and cut-and-paste with certain terminal + # emulators. In this calculation, TEMP is the physical screen + # position of the cursor. + if @encoding == 'X' + old.force_encoding('ASCII-8BIT') + new.force_encoding('ASCII-8BIT') + end + + if !@rl_byte_oriented + temp = @_rl_last_c_pos + else + temp = @_rl_last_c_pos - w_offset(@_rl_last_v_pos, @visible_wrap_offset) + end + if (temp == @_rl_screenwidth && @_rl_term_autowrap && !@_rl_horizontal_scroll_mode && + @_rl_last_v_pos == current_line - 1) + + if (!@rl_byte_oriented) + # This fixes only double-column characters, but if the wrapped + # character comsumes more than three columns, spaces will be + # inserted in the string buffer. + if (@_rl_wrapped_line[current_line] > 0) + _rl_clear_to_eol(@_rl_wrapped_line[current_line]) + end + + if new[0,1] != 0.chr + case @encoding + when 'E' + wc = new.scan(/./me)[0] + ret = wc.length + tempwidth = wc.length + when 'S' + wc = new.scan(/./ms)[0] + ret = wc.length + tempwidth = wc.length + when 'U' + wc = new.scan(/./mu)[0] + ret = wc.length + tempwidth = wc.unpack('U').first >= 0x1000 ? 2 : 1 + when 'X' + wc = new[0..-1].force_encoding(@encoding_name)[0] + ret = wc.bytesize + tempwidth = wc.ord >= 0x1000 ? 2 : 1 + else + ret = 1 + tempwidth = 1 + end + else + tempwidth = 0 + end + + if (tempwidth > 0) + bytes = ret + @rl_outstream.write(new[0,bytes]) + @_rl_last_c_pos = tempwidth + @_rl_last_v_pos+=1 + + if old[ostart,1] != 0.chr + case @encoding + when 'E' + wc = old[ostart..-1].scan(/./me)[0] + ret = wc.length + when 'S' + wc = old[ostart..-1].scan(/./ms)[0] + ret = wc.length + when 'U' + wc = old[ostart..-1].scan(/./mu)[0] + ret = wc.length + when 'X' + wc = old[ostart..-1].force_encoding(@encoding_name)[0] + ret = wc.bytesize + end + else + ret = 0 + end + if (ret != 0 && bytes != 0) + if ret != bytes + len = old[ostart..-1].index(0.chr,ret) + old[ostart+bytes,len-ret] = old[ostart+ret,len-ret] + end + old[ostart,bytes] = new[0,bytes] + end + else + @rl_outstream.write(' ') + @_rl_last_c_pos = 1 + @_rl_last_v_pos+=1 + if (old[ostart,1] != 0.chr && new[0,1] != 0.chr) + old[ostart,1] = new[0,1] + end + end + + else + if (new[0,1] != 0.chr) + @rl_outstream.write(new[0,1]) + else + @rl_outstream.write(' ') + end + @_rl_last_c_pos = 1 + @_rl_last_v_pos+=1 + if (old[ostart,1] != 0.chr && new[0,1] != 0.chr) + old[ostart,1] = new[0,1] + end + end + end + + # Find first difference. + if (!@rl_byte_oriented) + # See if the old line is a subset of the new line, so that the + # only change is adding characters. + if (index = old.index(0.chr)) && omax+ostart>index + omax = index - ostart + end + if (index = new.index(0.chr)) && nmax>index + nmax = index + end + + temp = (omax < nmax) ? omax : nmax + if old[ostart,temp]==new[0,temp] + ofd = temp + nfd = temp + else + if (omax == nmax && new[0,omax]==old[ostart,omax]) + ofd = omax + nfd = nmax + else + new_offset = 0 + old_offset = ostart + ofd = 0 + nfd = 0 + while(ofd < omax && old[ostart+ofd,1] != 0.chr && + _rl_compare_chars(old, old_offset, new, new_offset)) + + old_offset = _rl_find_next_mbchar(old, old_offset, 1, MB_FIND_ANY) + new_offset = _rl_find_next_mbchar(new, new_offset, 1, MB_FIND_ANY) + ofd = old_offset - ostart + nfd = new_offset + end + end + end + else + ofd = 0 + nfd = 0 + while(ofd < omax && old[ostart+ofd,1] != 0.chr && old[ostart+ofd,1] == new[nfd,1]) + ofd += 1 + nfd += 1 + end + end + + + # Move to the end of the screen line. ND and OD are used to keep track + # of the distance between ne and new and oe and old, respectively, to + # move a subtraction out of each loop. + oe = old.index(0.chr,ostart+ofd) - ostart + if oe.nil? || oe>omax + oe = omax + end + + ne = new.index(0.chr,nfd) + if ne.nil? || ne>omax + ne = nmax + end + + # If no difference, continue to next line. + if (ofd == oe && nfd == ne) + return + end + + + wsatend = true # flag for trailing whitespace + + if (!@rl_byte_oriented) + + ols = _rl_find_prev_mbchar(old, ostart+oe, MB_FIND_ANY) - ostart + nls = _rl_find_prev_mbchar(new, ne, MB_FIND_ANY) + while ((ols > ofd) && (nls > nfd)) + + if (!_rl_compare_chars(old, ostart+ols, new, nls)) + break + end + if (old[ostart+ols,1] == " ") + wsatend = false + end + + ols = _rl_find_prev_mbchar(old, ols+ostart, MB_FIND_ANY) - ostart + nls = _rl_find_prev_mbchar(new, nls, MB_FIND_ANY) + end + else + ols = oe - 1 # find last same + nls = ne - 1 + while ((ols > ofd) && (nls > nfd) && old[ostart+ols,1] == new[nls,1]) + if (old[ostart+ols,1] != " ") + wsatend = false + end + ols-=1 + nls-=1 + end + end + + if (wsatend) + ols = oe + nls = ne + elsif (!_rl_compare_chars(old, ostart+ols, new, nls)) + if (old[ostart+ols,1] != 0.chr) # don't step past the NUL + if !@rl_byte_oriented + ols = _rl_find_next_mbchar(old, ostart+ols, 1, MB_FIND_ANY) - ostart + else + ols+=1 + end + end + if (new[nls,1] != 0.chr ) + if !@rl_byte_oriented + nls = _rl_find_next_mbchar(new, nls, 1, MB_FIND_ANY) + else + nls+=1 + end + end + end + + # count of invisible characters in the current invisible line. + current_invis_chars = w_offset(current_line, @wrap_offset) + if (@_rl_last_v_pos != current_line) + _rl_move_vert(current_line) + if (@rl_byte_oriented && current_line == 0 && @visible_wrap_offset!=0) + @_rl_last_c_pos += @visible_wrap_offset + end + end + + # If this is the first line and there are invisible characters in the + # prompt string, and the prompt string has not changed, and the current + # cursor position is before the last invisible character in the prompt, + # and the index of the character to move to is past the end of the prompt + # string, then redraw the entire prompt string. We can only do this + # reliably if the terminal supports a `cr' capability. + + # This is not an efficiency hack -- there is a problem with redrawing + # portions of the prompt string if they contain terminal escape + # sequences (like drawing the `unbold' sequence without a corresponding + # `bold') that manifests itself on certain terminals. + + lendiff = @local_prompt_len + + if (current_line == 0 && !@_rl_horizontal_scroll_mode && + @_rl_term_cr && lendiff > @prompt_visible_length && @_rl_last_c_pos > 0 && + ofd >= lendiff && @_rl_last_c_pos < prompt_ending_index()) + @rl_outstream.write(@_rl_term_cr) + _rl_output_some_chars(@local_prompt,0,lendiff) + if !@rl_byte_oriented + # We take wrap_offset into account here so we can pass correct + # information to _rl_move_cursor_relative. + @_rl_last_c_pos = _rl_col_width(@local_prompt, 0, lendiff) - @wrap_offset + @cpos_adjusted = true + else + @_rl_last_c_pos = lendiff + end + end + + # When this function returns, _rl_last_c_pos is correct, and an absolute + # cursor postion in multibyte mode, but a buffer index when not in a + # multibyte locale. + _rl_move_cursor_relative(ofd, old, ostart) + + if (current_line == 0 && !@rl_byte_oriented && @_rl_last_c_pos == @prompt_physical_chars) + @cpos_adjusted = true + end + + # if (len (new) > len (old)) + # lendiff == difference in buffer + # col_lendiff == difference on screen + # When not using multibyte characters, these are equal + lendiff = (nls - nfd) - (ols - ofd) + if !@rl_byte_oriented + col_lendiff = _rl_col_width(new, nfd, nls) - _rl_col_width(old, ostart+ofd, ostart+ols) + else + col_lendiff = lendiff + end + + # If we are changing the number of invisible characters in a line, and + # the spot of first difference is before the end of the invisible chars, + # lendiff needs to be adjusted. + if (current_line == 0 && !@_rl_horizontal_scroll_mode && + current_invis_chars != @visible_wrap_offset) + if !@rl_byte_oriented + lendiff += @visible_wrap_offset - current_invis_chars + col_lendiff += @visible_wrap_offset - current_invis_chars + else + lendiff += @visible_wrap_offset - current_invis_chars + col_lendiff = lendiff + end + end + + # Insert (diff (len (old), len (new)) ch. + temp = ne - nfd + if !@rl_byte_oriented + col_temp = _rl_col_width(new,nfd,ne) + else + col_temp = temp + end + if (col_lendiff > 0) # XXX - was lendiff + + # Non-zero if we're increasing the number of lines. + gl = current_line >= @_rl_vis_botlin && inv_botlin > @_rl_vis_botlin + # Sometimes it is cheaper to print the characters rather than + # use the terminal's capabilities. If we're growing the number + # of lines, make sure we actually cause the new line to wrap + # around on auto-wrapping terminals. + if (@_rl_terminal_can_insert && ((2 * col_temp) >= col_lendiff || @_rl_term_IC) && (!@_rl_term_autowrap || !gl)) + + # If lendiff > prompt_visible_length and _rl_last_c_pos == 0 and + # _rl_horizontal_scroll_mode == 1, inserting the characters with + # _rl_term_IC or _rl_term_ic will screw up the screen because of the + # invisible characters. We need to just draw them. + if (old[ostart+ols,1] != 0.chr && (!@_rl_horizontal_scroll_mode || @_rl_last_c_pos > 0 || + lendiff <= @prompt_visible_length || current_invis_chars==0)) + + insert_some_chars(new[nfd..-1], lendiff, col_lendiff) + @_rl_last_c_pos += col_lendiff + elsif ((@rl_byte_oriented) && old[ostart+ols,1] == 0.chr && lendiff > 0) + # At the end of a line the characters do not have to + # be "inserted". They can just be placed on the screen. + # However, this screws up the rest of this block, which + # assumes you've done the insert because you can. + _rl_output_some_chars(new,nfd, lendiff) + @_rl_last_c_pos += col_lendiff + else + # We have horizontal scrolling and we are not inserting at + # the end. We have invisible characters in this line. This + # is a dumb update. + _rl_output_some_chars(new,nfd, temp) + @_rl_last_c_pos += col_temp + return + end + # Copy (new) chars to screen from first diff to last match. + temp = nls - nfd + if ((temp - lendiff) > 0) + _rl_output_some_chars(new,(nfd + lendiff),temp - lendiff) + # XXX -- this bears closer inspection. Fixes a redisplay bug + # reported against bash-3.0-alpha by Andreas Schwab involving + # multibyte characters and prompt strings with invisible + # characters, but was previously disabled. + @_rl_last_c_pos += _rl_col_width(new,nfd+lendiff, nfd+lendiff+temp-col_lendiff) + end + else + # cannot insert chars, write to EOL + _rl_output_some_chars(new,nfd, temp) + @_rl_last_c_pos += col_temp + # If we're in a multibyte locale and were before the last invisible + # char in the current line (which implies we just output some invisible + # characters) we need to adjust _rl_last_c_pos, since it represents + # a physical character position. + end + else # Delete characters from line. + # If possible and inexpensive to use terminal deletion, then do so. + if (@_rl_term_dc && (2 * col_temp) >= -col_lendiff) + + # If all we're doing is erasing the invisible characters in the + # prompt string, don't bother. It screws up the assumptions + # about what's on the screen. + if (@_rl_horizontal_scroll_mode && @_rl_last_c_pos == 0 && + -lendiff == @visible_wrap_offset) + col_lendiff = 0 + end + + if (col_lendiff!=0) + delete_chars(-col_lendiff) # delete (diff) characters + end + + # Copy (new) chars to screen from first diff to last match + temp = nls - nfd + if (temp > 0) + _rl_output_some_chars(new,nfd, temp) + @_rl_last_c_pos += _rl_col_width(new,nfd,nfd+temp) + end + + # Otherwise, print over the existing material. + else + if (temp > 0) + _rl_output_some_chars(new,nfd, temp) + @_rl_last_c_pos += col_temp # XXX + end + + lendiff = (oe) - (ne) + if !@rl_byte_oriented + col_lendiff = _rl_col_width(old, ostart, ostart+oe) - _rl_col_width(new, 0, ne) + else + col_lendiff = lendiff + end + + if (col_lendiff!=0) + if (@_rl_term_autowrap && current_line < inv_botlin) + space_to_eol(col_lendiff) + else + _rl_clear_to_eol(col_lendiff) + end + end + end + end + end + + # Basic redisplay algorithm. + def rl_redisplay() + return if !@readline_echoing_p + + _rl_wrapped_multicolumn = 0 + + @rl_display_prompt ||= "" + + if (@invisible_line.nil? || @vis_lbreaks.nil?) + init_line_structures(0) + rl_on_new_line() + end + + # Draw the line into the buffer. + @cpos_buffer_position = -1 + + line = @invisible_line + out = inv_botlin = 0 + + # Mark the line as modified or not. We only do this for history + # lines. + modmark = 0 + if (@_rl_mark_modified_lines && current_history() && @rl_undo_list) + line[out,1] = '*' + out += 1 + line[out,1] = 0.chr + modmark = 1 + end + + # If someone thought that the redisplay was handled, but the currently + # visible line has a different modification state than the one about + # to become visible, then correct the caller's misconception. + if (@visible_line[0,1] != @invisible_line[0,1]) + @rl_display_fixed = false + end + + # If the prompt to be displayed is the `primary' readline prompt (the + # one passed to readline()), use the values we have already expanded. + # If not, use what's already in rl_display_prompt. WRAP_OFFSET is the + # number of non-visible characters in the prompt string. + if (@rl_display_prompt == @rl_prompt || @local_prompt) + + if (@local_prompt_prefix && @forced_display) + _rl_output_some_chars(@local_prompt_prefix,0,@local_prompt_prefix.length) + end + if (@local_prompt_len > 0) + + temp = @local_prompt_len + out + 2 + if (temp >= @line_size) + @line_size = (temp + 1024) - (temp % 1024) + if @visible_line.length >= @line_size + @visible_line = @visible_line[0,@line_size] + else + @visible_line += 0.chr * (@line_size-@visible_line.length) + end + + if @invisible_line.length >= @line_size + @invisible_line = @invisible_line[0,@line_size] + else + @invisible_line += 0.chr * (@line_size-@invisible_line.length) + end + if @encoding=='X' + @visible_line.force_encoding('ASCII-8BIT') + @invisible_line.force_encoding('ASCII-8BIT') + end + line = @invisible_line + end + line[out,@local_prompt_len] = @local_prompt + out += @local_prompt_len + end + line[out,1] = 0.chr + @wrap_offset = @local_prompt_len - @prompt_visible_length + else + prompt_this_line = @rl_display_prompt.rindex("\n") + if prompt_this_line.nil? + prompt_this_line = 0 + else + prompt_this_line+=1 + + pmtlen = prompt_this_line # temp var + if (@forced_display) + _rl_output_some_chars(@rl_display_prompt,0,pmtlen) + # Make sure we are at column zero even after a newline, + #regardless of the state of terminal output processing. + if (pmtlen < 2 || @rl_display_prompt[prompt_this_line-2,1] != "\r") + cr() + end + end + end + + @prompt_physical_chars = pmtlen = @rl_display_prompt.length - prompt_this_line + temp = pmtlen + out + 2 + if (temp >= @line_size) + @line_size = (temp + 1024) - (temp % 1024) + if @visible_line.length >= @line_size + @visible_line = @visible_line[0,@line_size] + else + @visible_line += 0.chr * (@line_size-@visible_line.length) + end + + if @invisible_line.length >= @line_size + @invisible_line = @invisible_line[0,@line_size] + else + @invisible_line += 0.chr * (@line_size-@invisible_line.length) + end + if @encoding=='X' + @visible_line.force_encoding('ASCII-8BIT') + @invisible_line.force_encoding('ASCII-8BIT') + end + + line = @invisible_line + end + line[out,pmtlen] = @rl_display_prompt[prompt_this_line,pmtlen] + out += pmtlen + line[out,1] = 0.chr + @wrap_offset = @prompt_invis_chars_first_line = 0 + end + # inv_lbreaks[i] is where line i starts in the buffer. + @inv_lbreaks[newlines = 0] = 0 + lpos = @prompt_physical_chars + modmark + + @_rl_wrapped_line = Array.new(@visible_line.length,0) + num = 0 + + # prompt_invis_chars_first_line is the number of invisible characters in + # the first physical line of the prompt. + # wrap_offset - prompt_invis_chars_first_line is the number of invis + # chars on the second line. + + # what if lpos is already >= _rl_screenwidth before we start drawing the + # contents of the command line? + while (lpos >= @_rl_screenwidth) + # fix from Darin Johnson for prompt string with + # invisible characters that is longer than the screen width. The + # prompt_invis_chars_first_line variable could be made into an array + # saying how many invisible characters there are per line, but that's + # probably too much work for the benefit gained. How many people have + # prompts that exceed two physical lines? + # Additional logic fix from Edward Catmur + if (!@rl_byte_oriented) + n0 = num + temp = @local_prompt_len + while (num < temp) + z = _rl_col_width(@local_prompt, n0, num) + if (z > @_rl_screenwidth) + num = _rl_find_prev_mbchar(@local_prompt, num, MB_FIND_ANY) + break + elsif (z == @_rl_screenwidth) + break + end + num+=1 + end + temp = num + else + temp = ((newlines + 1) * @_rl_screenwidth) + end + + # Now account for invisible characters in the current line. + temp += (@local_prompt_prefix.nil? ? ((newlines == 0) ? @prompt_invis_chars_first_line : + ((newlines == 1) ? @wrap_offset : 0)) : + ((newlines == 0) ? @wrap_offset : 0)) + + @inv_lbreaks[newlines+=1] = temp + if !@rl_byte_oriented + lpos -= _rl_col_width(@local_prompt, n0, num) + else + lpos -= @_rl_screenwidth + end + end + @prompt_last_screen_line = newlines + + # Draw the rest of the line (after the prompt) into invisible_line, keeping + # track of where the cursor is (cpos_buffer_position), the number of the line containing + # the cursor (lb_linenum), the last line number (lb_botlin and inv_botlin). + # It maintains an array of line breaks for display (inv_lbreaks). + # This handles expanding tabs for display and displaying meta characters. + lb_linenum = 0 + _in = 0 + if !@rl_byte_oriented && @rl_end>0 + case @encoding + when 'E' + wc = @rl_line_buffer[0,@rl_end].scan(/./me)[0] + wc_bytes = wc ? wc.length : 1 + when 'S' + wc = @rl_line_buffer[0,@rl_end].scan(/./ms)[0] + wc_bytes = wc ? wc.length : 1 + when 'U' + wc = @rl_line_buffer[0,@rl_end].scan(/./mu)[0] + wc_bytes = wc ? wc.length : 1 + when 'X' + wc = @rl_line_buffer[0,@rl_end].force_encoding(@encoding_name)[0] + wc_bytes = wc ? wc.bytesize : 1 + end + else + wc_bytes = 1 + end + + while(_in < @rl_end) + + c = @rl_line_buffer[_in,1] + if(c == 0.chr) + @rl_end = _in + break + end + if (!@rl_byte_oriented) + case @encoding + when 'U' + wc_width = wc && wc.unpack('U').first >= 0x1000 ? 2 : 1 + when 'X' + wc_width = wc && wc.ord > 0x1000 ? 2 : 1 + else + wc_width = wc ? wc.length : 1 + end + end + if (out + 8 >= @line_size) # XXX - 8 for \t + @line_size *= 2 + if @visible_line.length>=@line_size + @visible_line = @visible_line[0,@line_size] + else + @visible_line += 0.chr * (@line_size-@visible_line.length) + end + if @invisible_line.length>=@line_size + @invisible_line = @invisible_line[0,@line_size] + else + @invisible_line += 0.chr * (@line_size-@invisible_line.length) + end + line = @invisible_line + end + + if (_in == @rl_point) + @cpos_buffer_position = out + lb_linenum = newlines + end + if (false && meta_char(c)) + if (!@_rl_output_meta_chars && false) + line[out,4] = "\\%03o" % c.ord + + if (lpos + 4 >= @_rl_screenwidth) + temp = @_rl_screenwidth - lpos + @inv_lbreaks[newlines+=1] = out + temp + lpos = 4 - temp + else + lpos += 4 + end + out += 4 + else + line[out,1] = c + out += 1 + lpos+=1 + if (lpos >= @_rl_screenwidth) + @inv_lbreaks[newlines+=1] = out + @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn + lpos = 0 + end + end + + elsif (c == "\t") + + newout = out + 8 - lpos % 8 + temp = newout - out + if (lpos + temp >= @_rl_screenwidth) + temp2 = @_rl_screenwidth - lpos + @inv_lbreaks[newlines+=1] = out + temp2 + lpos = temp - temp2 + while (out < newout) + line[out,1] = ' ' + out += 1 + end + + else + + while (out < newout) + line[out,1] = ' ' + out += 1 + end + lpos += temp + end + + elsif (c == "\n" && !@_rl_horizontal_scroll_mode && @_rl_term_up) + line[out,1] = 0.chr # XXX - sentinel + out += 1 + @inv_lbreaks[newlines+=1] = out + lpos = 0 + elsif (ctrl_char(c) || c == RUBOUT) + line[out,1] = '^' + out += 1 + lpos+=1 + if (lpos >= @_rl_screenwidth) + @inv_lbreaks[newlines+=1] = out + @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn + lpos = 0 + end + # NOTE: c[0].ord works identically on both 1.8 and 1.9 + line[out,1] = ctrl_char(c) ? (c[0].ord|0x40).chr.upcase : '?' + out += 1 + lpos+=1 + if (lpos >= @_rl_screenwidth) + @inv_lbreaks[newlines+=1] = out + @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn + lpos = 0 + end + else + + if (!@rl_byte_oriented) + _rl_wrapped_multicolumn = 0 + if (@_rl_screenwidth < lpos + wc_width) + for i in lpos ... @_rl_screenwidth + # The space will be removed in update_line() + line[out,1] = ' ' + out += 1 + _rl_wrapped_multicolumn+=1 + lpos+=1 + if (lpos >= @_rl_screenwidth) + @inv_lbreaks[newlines+=1] = out + @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn + lpos = 0 + end + end + end + if (_in == @rl_point) + @cpos_buffer_position = out + lb_linenum = newlines + end + line[out,wc_bytes] = @rl_line_buffer[_in,wc_bytes] + out += wc_bytes + for i in 0 ... wc_width + lpos+=1 + if (lpos >= @_rl_screenwidth) + @inv_lbreaks[newlines+=1] = out + @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn + lpos = 0 + end + end + else + line[out,1] = c + out += 1 + lpos+=1 + if (lpos >= @_rl_screenwidth) + @inv_lbreaks[newlines+=1] = out + @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn + lpos = 0 + end + end + + end + + if (!@rl_byte_oriented) + _in += wc_bytes + case @encoding + when 'E' + wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./me)[0] + wc_bytes = wc ? wc.length : 1 + when 'S' + wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./ms)[0] + wc_bytes = wc ? wc.length : 1 + when 'U' + wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./mu)[0] + wc_bytes = wc ? wc.length : 1 + when 'X' + wc = @rl_line_buffer[_in,@rl_end - _in].force_encoding(@encoding_name)[0] + wc_bytes = wc ? wc.bytesize : 1 + end + + else + _in+=1 + end + end + + line[out,1] = 0.chr + + if (@cpos_buffer_position < 0) + @cpos_buffer_position = out + lb_linenum = newlines + end + + inv_botlin = lb_botlin = newlines + @inv_lbreaks[newlines+1] = out + cursor_linenum = lb_linenum + + # CPOS_BUFFER_POSITION == position in buffer where cursor should be placed. + # CURSOR_LINENUM == line number where the cursor should be placed. + + # PWP: now is when things get a bit hairy. The visible and invisible + # line buffers are really multiple lines, which would wrap every + # (screenwidth - 1) characters. Go through each in turn, finding + # the changed region and updating it. The line order is top to bottom. + + # If we can move the cursor up and down, then use multiple lines, + # otherwise, let long lines display in a single terminal line, and + # horizontally scroll it. + + if (!@_rl_horizontal_scroll_mode && @_rl_term_up) + if (!@rl_display_fixed || @forced_display) + @forced_display = false + # If we have more than a screenful of material to display, then + # only display a screenful. We should display the last screen, + # not the first. + if (out >= @_rl_screenchars) + if (!@rl_byte_oriented) + out = _rl_find_prev_mbchar(line, @_rl_screenchars, MB_FIND_ANY) + else + out = @_rl_screenchars - 1 + end + end + + # The first line is at character position 0 in the buffer. The + # second and subsequent lines start at inv_lbreaks[N], offset by + # OFFSET (which has already been calculated above). + + # For each line in the buffer, do the updating display. + for linenum in 0 .. inv_botlin + # This can lead us astray if we execute a program that changes + #the locale from a non-multibyte to a multibyte one. + o_cpos = @_rl_last_c_pos + @cpos_adjusted = false + update_line(@visible_line,vis_pos(linenum), inv_line(linenum), linenum, + vis_llen(linenum), inv_llen(linenum), inv_botlin) + + if (linenum == 0 && !@rl_byte_oriented && + !@cpos_adjusted && + @_rl_last_c_pos != o_cpos && + @_rl_last_c_pos > @wrap_offset && + o_cpos < @prompt_last_invisible) + @_rl_last_c_pos -= @wrap_offset + end + + # If this is the line with the prompt, we might need to + # compensate for invisible characters in the new line. Do + # this only if there is not more than one new line (which + # implies that we completely overwrite the old visible line) + # and the new line is shorter than the old. Make sure we are + # at the end of the new line before clearing. + if (linenum == 0 && + inv_botlin == 0 && @_rl_last_c_pos == out && + (@wrap_offset > @visible_wrap_offset) && + (@_rl_last_c_pos < @visible_first_line_len)) + if !@rl_byte_oriented + nleft = @_rl_screenwidth - @_rl_last_c_pos + else + nleft = @_rl_screenwidth + @wrap_offset - @_rl_last_c_pos + end + if (nleft!=0) + _rl_clear_to_eol(nleft) + end + end + + # Since the new first line is now visible, save its length. + if (linenum == 0) + @visible_first_line_len = (inv_botlin > 0) ? @inv_lbreaks[1] : out - @wrap_offset + end + end + + # We may have deleted some lines. If so, clear the left over + # blank ones at the bottom out. + if (@_rl_vis_botlin > inv_botlin) + while(linenum <= @_rl_vis_botlin) + tt = vis_chars(linenum) + _rl_move_vert(linenum) + _rl_move_cursor_relative(0, tt) + _rl_clear_to_eol((linenum == @_rl_vis_botlin) ? tt.length : @_rl_screenwidth) + linenum += 1 + end + end + @_rl_vis_botlin = inv_botlin + + # CHANGED_SCREEN_LINE is set to 1 if we have moved to a + # different screen line during this redisplay. + changed_screen_line = @_rl_last_v_pos != cursor_linenum + if (changed_screen_line) + _rl_move_vert(cursor_linenum) + # If we moved up to the line with the prompt using _rl_term_up, + # the physical cursor position on the screen stays the same, + # but the buffer position needs to be adjusted to account + # for invisible characters. + if (@rl_byte_oriented && cursor_linenum == 0 && @wrap_offset!=0) + @_rl_last_c_pos += @wrap_offset + end + end + # We have to reprint the prompt if it contains invisible + # characters, since it's not generally OK to just reprint + # the characters from the current cursor position. But we + # only need to reprint it if the cursor is before the last + # invisible character in the prompt string. + nleft = @prompt_visible_length + @wrap_offset + if (cursor_linenum == 0 && @wrap_offset > 0 && @_rl_last_c_pos > 0 && + @_rl_last_c_pos < prompt_ending_index() && @local_prompt) + if (@_rl_term_cr) + @rl_outstream.write(@_rl_term_cr) + end + _rl_output_some_chars(@local_prompt,0,nleft) + if !@rl_byte_oriented + @_rl_last_c_pos = _rl_col_width(@local_prompt, 0, nleft) - @wrap_offset + else + @_rl_last_c_pos = nleft + end + end + + # Where on that line? And where does that line start + # in the buffer? + pos = @inv_lbreaks[cursor_linenum] + # nleft == number of characters in the line buffer between the + # start of the line and the desired cursor position. + nleft = @cpos_buffer_position - pos + + # NLEFT is now a number of characters in a buffer. When in a + # multibyte locale, however, _rl_last_c_pos is an absolute cursor + # position that doesn't take invisible characters in the prompt + # into account. We use a fudge factor to compensate. + + # Since _rl_backspace() doesn't know about invisible characters in the + # prompt, and there's no good way to tell it, we compensate for + # those characters here and call _rl_backspace() directly. + + if (@wrap_offset!=0 && cursor_linenum == 0 && nleft < @_rl_last_c_pos) + # TX == new physical cursor position in multibyte locale. + if !@rl_byte_oriented + tx = _rl_col_width(@visible_line, pos, pos+nleft) - @visible_wrap_offset + else + tx = nleft + end + if (@_rl_last_c_pos > tx) + _rl_backspace(@_rl_last_c_pos - tx) # XXX + @_rl_last_c_pos = tx + end + end + # We need to note that in a multibyte locale we are dealing with + # _rl_last_c_pos as an absolute cursor position, but moving to a + # point specified by a buffer position (NLEFT) that doesn't take + # invisible characters into account. + if !@rl_byte_oriented + _rl_move_cursor_relative(nleft, @invisible_line,pos) + elsif (nleft != @_rl_last_c_pos) + _rl_move_cursor_relative(nleft, @invisible_line,pos) + end + end + + else # Do horizontal scrolling. + + # Always at top line. + @_rl_last_v_pos = 0 + + # Compute where in the buffer the displayed line should start. This + # will be LMARGIN. + + # The number of characters that will be displayed before the cursor. + ndisp = @cpos_buffer_position - @wrap_offset + nleft = @prompt_visible_length + @wrap_offset + # Where the new cursor position will be on the screen. This can be + # longer than SCREENWIDTH; if it is, lmargin will be adjusted. + phys_c_pos = @cpos_buffer_position - (@last_lmargin!=0 ? @last_lmargin : @wrap_offset) + t = @_rl_screenwidth / 3 + + # If the number of characters had already exceeded the screenwidth, + #last_lmargin will be > 0. + + # If the number of characters to be displayed is more than the screen + # width, compute the starting offset so that the cursor is about + # two-thirds of the way across the screen. + if (phys_c_pos > @_rl_screenwidth - 2) + lmargin = @cpos_buffer_position - (2 * t) + if (lmargin < 0) + lmargin = 0 + end + # If the left margin would be in the middle of a prompt with + # invisible characters, don't display the prompt at all. + if (@wrap_offset!=0 && lmargin > 0 && lmargin < nleft) + lmargin = nleft + end + elsif (ndisp < @_rl_screenwidth - 2) # XXX - was -1 + lmargin = 0 + elsif (phys_c_pos < 1) + # If we are moving back towards the beginning of the line and + # the last margin is no longer correct, compute a new one. + lmargin = ((@cpos_buffer_position - 1) / t) * t # XXX + if (@wrap_offset!=0 && lmargin > 0 && lmargin < nleft) + lmargin = nleft + end + else + lmargin = @last_lmargin + end + + # If the first character on the screen isn't the first character + #in the display line, indicate this with a special character. + if (lmargin > 0) + line[lmargin,1] = '<' + end + + # If SCREENWIDTH characters starting at LMARGIN do not encompass + # the whole line, indicate that with a special character at the + # right edge of the screen. If LMARGIN is 0, we need to take the + # wrap offset into account. + t = lmargin + m_offset(lmargin, @wrap_offset) + @_rl_screenwidth + if (t < out) + line[t - 1,1] = '>' + end + if (!@rl_display_fixed || @forced_display || lmargin != @last_lmargin) + + @forced_display = false + update_line(@visible_line,@last_lmargin,@invisible_line[lmargin..-1], + 0, + @_rl_screenwidth + @visible_wrap_offset, + @_rl_screenwidth + (lmargin ? 0 : @wrap_offset), + 0) + # If the visible new line is shorter than the old, but the number + # of invisible characters is greater, and we are at the end of + # the new line, we need to clear to eol. + t = @_rl_last_c_pos - m_offset(lmargin, @wrap_offset) + if ((m_offset(lmargin, @wrap_offset) > @visible_wrap_offset) && + (@_rl_last_c_pos == out) && + t < @visible_first_line_len) + + nleft = @_rl_screenwidth - t + _rl_clear_to_eol(nleft) + end + @visible_first_line_len = out - lmargin - m_offset(lmargin, @wrap_offset) + if (@visible_first_line_len > @_rl_screenwidth) + @visible_first_line_len = @_rl_screenwidth + end + _rl_move_cursor_relative(@cpos_buffer_position - lmargin, @invisible_line ,lmargin) + @last_lmargin = lmargin + end + end + @rl_outstream.flush + + # Swap visible and non-visible lines. + @visible_line,@invisible_line = @invisible_line,@visible_line + @vis_lbreaks,@inv_lbreaks = @inv_lbreaks,@vis_lbreaks + + @rl_display_fixed = false + # If we are displaying on a single line, and last_lmargin is > 0, we + # are not displaying any invisible characters, so set visible_wrap_offset + # to 0. + if (@_rl_horizontal_scroll_mode && @last_lmargin!=0) + @visible_wrap_offset = 0 + else + @visible_wrap_offset = @wrap_offset + end + end + + # Tell the update routines that we have moved onto a new (empty) line. + def rl_on_new_line() + if (@visible_line) + @visible_line[0,1] = 0.chr + end + @_rl_last_c_pos = @_rl_last_v_pos = 0 + @_rl_vis_botlin = @last_lmargin = 0 + if (@vis_lbreaks) + @vis_lbreaks[0] = @vis_lbreaks[1] = 0 + end + @visible_wrap_offset = 0 + 0 + end + + def rl_reset_line_state() + rl_on_new_line() + + @rl_display_prompt = @rl_prompt ? @rl_prompt : "" + @forced_display = true + 0 + end + + def _rl_vi_initialize_line + rl_unsetstate(RL_STATE_VICMDONCE) + end + + # Initialize readline (and terminal if not already). + def rl_initialize() + # If we have never been called before, initialize the + # terminal and data structures. + if (!@rl_initialized) + rl_setstate(RL_STATE_INITIALIZING) + readline_initialize_everything() + rl_unsetstate(RL_STATE_INITIALIZING) + @rl_initialized = true + rl_setstate(RL_STATE_INITIALIZED) + end + + # Initalize the current line information. + _rl_init_line_state() + + # We aren't done yet. We haven't even gotten started yet! + @rl_done = false + rl_unsetstate(RL_STATE_DONE) + + # Tell the history routines what is going on. + _rl_start_using_history() + + # Make the display buffer match the state of the line. + rl_reset_line_state() + + # No such function typed yet. + @rl_last_func = nil + + # Parsing of key-bindings begins in an enabled state. + @_rl_parsing_conditionalized_out = 0 + + if (@rl_editing_mode == @vi_mode) + _rl_vi_initialize_line() + end + # Each line starts in insert mode (the default). + _rl_set_insert_mode(RL_IM_DEFAULT, 1) + + return 0 + end + + def _rl_strip_prompt(pmt) + return expand_prompt(pmt).first + end + + def _rl_col_width(string,start,_end) + return 0 if _end <= start + + index = string.index(0.chr) + str = index ? string[0,index] : string + width = 0 + + case @encoding + when 'N' + return (_end - start) + when 'U' + str[start ... _end].scan(/./mu).each {|s| width += s.unpack('U').first >= 0x1000 ? 2 : 1 } + when 'S' + str[start ... _end].scan(/./ms).each {|s| width += s.length } + when 'E' + str[start ... _end].scan(/./me).each {|s| width += s.length } + when 'X' + tmp = str[start ... _end] + return 0 if not tmp + tmp.force_encoding(@encoding_name).codepoints.each {|s| width += s > 0x1000 ? 2 : 1 } + end + width + end + + # Write COUNT characters from STRING to the output stream. + def _rl_output_some_chars(string,start,count) + @_rl_out_stream.write(string[start,count]) + end + + # Tell the update routines that we have moved onto a new line with the + # prompt already displayed. Code originally from the version of readline + # distributed with CLISP. rl_expand_prompt must have already been called + # (explicitly or implicitly). This still doesn't work exactly right. + def rl_on_new_line_with_prompt() + # Initialize visible_line and invisible_line to ensure that they can hold + # the already-displayed prompt. + prompt_size = @rl_prompt.length + 1 + init_line_structures(prompt_size) + + # Make sure the line structures hold the already-displayed prompt for + # redisplay. + lprompt = @local_prompt ? @local_prompt : @rl_prompt + @visible_line[0,lprompt.length] = lprompt + @invisible_line[0,lprompt.length] = lprompt + + # If the prompt contains newlines, take the last tail. + prompt_last_line = rl_prompt.rindex("\n") + if prompt_last_line.nil? + prompt_last_line = @rl_prompt + else + prompt_last_line = @rl_prompt[prompt_last_line..-1] + end + l = prompt_last_line.length + if !@rl_byte_oriented + @_rl_last_c_pos = _rl_col_width(prompt_last_line, 0, l) + else + @_rl_last_c_pos = l + end + + # Dissect prompt_last_line into screen lines. Note that here we have + # to use the real screenwidth. Readline's notion of screenwidth might be + # one less, see terminal.c. + real_screenwidth = @_rl_screenwidth + (@_rl_term_autowrap ? 0 : 1) + @_rl_last_v_pos = l / real_screenwidth + # If the prompt length is a multiple of real_screenwidth, we don't know + # whether the cursor is at the end of the last line, or already at the + # beginning of the next line. Output a newline just to be safe. + if (l > 0 && (l % real_screenwidth) == 0) + _rl_output_some_chars("\n",0,1) + end + @last_lmargin = 0 + + newlines = 0 + i = 0 + while (i <= l) + @_rl_vis_botlin = newlines + @vis_lbreaks[newlines] = i + newlines += 1 + i += real_screenwidth + end + @vis_lbreaks[newlines] = l + @visible_wrap_offset = 0 + + @rl_display_prompt = @rl_prompt # XXX - make sure it's set + + return 0 + end + + def readline_internal_setup() + @_rl_in_stream = @rl_instream + @_rl_out_stream = @rl_outstream + + if (@rl_startup_hook) + send(@rl_startup_hook) + end + + # If we're not echoing, we still want to at least print a prompt, because + # rl_redisplay will not do it for us. If the calling application has a + # custom redisplay function, though, let that function handle it. + if (!@readline_echoing_p && @rl_redisplay_function == :rl_redisplay) + if (@rl_prompt && !@rl_already_prompted) + nprompt = _rl_strip_prompt(@rl_prompt) + @_rl_out_stream.write(nprompt) + @_rl_out_stream.flush + end + else + if (@rl_prompt && @rl_already_prompted) + rl_on_new_line_with_prompt() + else + rl_on_new_line() + end + send(@rl_redisplay_function) + end + + if (@rl_editing_mode == @vi_mode) + rl_vi_insertion_mode(1, 'i') + end + if (@rl_pre_input_hook) + send(@rl_pre_input_hook) + end + end + + # Create a default argument. + def _rl_reset_argument() + @rl_numeric_arg = @rl_arg_sign = 1 + @rl_explicit_arg = false + @_rl_argcxt = 0 + end + + # Ring the terminal bell. + def rl_ding() + if @MessageBeep + @MessageBeep.Call(0) + elsif @readline_echoing_p + if @_rl_bell_preference == VISIBLE_BELL + if (@_rl_visible_bell) + @_rl_out_stream.write(@_rl_visible_bell.chr) + else + $stderr.write("\007") + $stderr.flush + end + elsif @_rl_bell_preference == AUDIBLE_BELL + $stderr.write("\007") + $stderr.flush + end + return 0 + end + return -1 + end + + def _rl_search_getchar(cxt) + # Read a key and decide how to proceed. + rl_setstate(RL_STATE_MOREINPUT) + c = cxt.lastc = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + if !@rl_byte_oriented + cxt.mb = "" + c = cxt.lastc = _rl_read_mbstring(cxt.lastc, cxt.mb, MB_LEN_MAX) + end + c + end + + def endsrch_char(c) + ((ctrl_char(c) || meta_char(c) || (c) == RUBOUT) && ((c) != "\C-G")) + end + + def _rl_input_available + IO.select([ $stdin ], nil, [ $stdin ], @_keyboard_input_timeout) + end + + # Process just-read character C according to isearch context CXT. Return + # -1 if the caller should just free the context and return, 0 if we should + # break out of the loop, and 1 if we should continue to read characters. + def _rl_isearch_dispatch(cxt, c) + f = nil + + # Translate the keys we do something with to opcodes. + if (c && @_rl_keymap[c]) + f = @_rl_keymap[c] + if (f == :rl_reverse_search_history) + cxt.lastc = (cxt.sflags & SF_REVERSE)!=0 ? -1 : -2 + elsif (f == :rl_forward_search_history) + cxt.lastc = (cxt.sflags & SF_REVERSE)!=0 ? -2 : -1 + elsif (f == :rl_rubout) + cxt.lastc = -3 + elsif (c == "\C-G") + cxt.lastc = -4 + elsif (c == "\C-W") # XXX + cxt.lastc = -5 + elsif (c == "\C-Y") # XXX + cxt.lastc = -6 + end + end + + # The characters in isearch_terminators (set from the user-settable + # variable isearch-terminators) are used to terminate the search but + # not subsequently execute the character as a command. The default + # value is "\033\012" (ESC and C-J). + if (cxt.lastc.class == ::String and cxt.search_terminators.include?(cxt.lastc)) + # ESC still terminates the search, but if there is pending + #input or if input arrives within 0.1 seconds (on systems + #with select(2)) it is used as a prefix character + #with rl_execute_next. WATCH OUT FOR THIS! This is intended + #to allow the arrow keys to be used like ^F and ^B are used + #to terminate the search and execute the movement command. + #XXX - since _rl_input_available depends on the application- + #settable keyboard timeout value, this could alternatively + #use _rl_input_queued(100000) + if (cxt.lastc == ESC && _rl_input_available()) + rl_execute_next(ESC) + end + return (0) + end + + if !@rl_byte_oriented + if (cxt.lastc.class == String && (cxt.mb.length == 1) && endsrch_char(cxt.lastc)) + # This sets rl_pending_input to c; it will be picked up the next + # time rl_read_key is called. + rl_execute_next(cxt.lastc) + return (0) + end + elsif (cxt.lastc.class == String && endsrch_char(cxt.lastc)) + # This sets rl_pending_input to LASTC; it will be picked up the next + # time rl_read_key is called. + rl_execute_next(cxt.lastc) + return (0) + end + + # Now dispatch on the character. `Opcodes' affect the search string or + # state. Other characters are added to the string. + case (cxt.lastc) + + # search again + when -1 + if (cxt.search_string_index == 0) + if (@last_isearch_string) + cxt.search_string_size = 64 + @last_isearch_string_len + cxt.search_string = @last_isearch_string.dup + cxt.search_string_index = @last_isearch_string_len + rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, -1) + else + return (1) + end + elsif (cxt.sflags & SF_REVERSE)!=0 + cxt.sline_index-=1 + elsif (cxt.sline_index != cxt.sline_len) + cxt.sline_index+=1 + else + rl_ding() + end + + # switch directions + when -2 + cxt.direction = -cxt.direction + if (cxt.direction < 0) + cxt.sflags |= SF_REVERSE + else + cxt.sflags &= ~SF_REVERSE + end + # delete character from search string. + when -3 # C-H, DEL + # This is tricky. To do this right, we need to keep a + # stack of search positions for the current search, with + # sentinels marking the beginning and end. But this will + # do until we have a real isearch-undo. + if (cxt.search_string_index == 0) + rl_ding() + else + cxt.search_string_index -= 1 + cxt.search_string.chop! + end + when -4 # C-G, abort + rl_replace_line(cxt.lines[cxt.save_line], false) + @rl_point = cxt.save_point + @rl_mark = cxt.save_mark + rl_restore_prompt() + rl_clear_message() + return -1 + when -5 # C-W + # skip over portion of line we already matched and yank word + wstart = @rl_point + cxt.search_string_index + if (wstart >= @rl_end) + rl_ding() + else + # if not in a word, move to one. + cval = _rl_char_value(@rl_line_buffer, wstart) + if (!_rl_walphabetic(cval)) + rl_ding() + else + if !@rl_byte_oriented + n = _rl_find_next_mbchar(@rl_line_buffer, wstart, 1, MB_FIND_NONZERO) + else + n = wstart+1 + end + while (n < @rl_end) + cval = _rl_char_value(@rl_line_buffer, n) + break if !_rl_walphabetic(cval) + if !@rl_byte_oriented + n = _rl_find_next_mbchar(@rl_line_buffer, n, 1, MB_FIND_NONZERO) + else + n = n+1 + end + end + wlen = n - wstart + 1 + if (cxt.search_string_index + wlen + 1 >= cxt.search_string_size) + cxt.search_string_size += wlen + 1 + end + cxt.search_string[cxt.search_string_index..-1] = @rl_line_buffer[wstart,wlen] + cxt.search_string_index += wlen + end + end + + when -6 # C-Y + # skip over portion of line we already matched and yank rest + wstart = @rl_point + cxt.search_string_index + if (wstart >= @rl_end) + rl_ding() + else + n = @rl_end - wstart + 1 + if (cxt.search_string_index + n + 1 >= cxt.search_string_size) + cxt.search_string_size += n + 1 + end + cxt.search_string[cxt.search_string_index..-1] = @rl_line_buffer[wstart,n] + end + + # Add character to search string and continue search. + else + if (cxt.search_string_index + 2 >= cxt.search_string_size) + cxt.search_string_size += 128 + end + if !@rl_byte_oriented + for j in 0 ... cxt.mb.length + cxt.search_string << cxt.mb[j,1] + cxt.search_string_index += 1 + end + else + cxt.search_string << c + cxt.search_string_index += 1 + end + end + + while (cxt.sflags &= ~(SF_FOUND|SF_FAILED))!=0 + limit = cxt.sline_len - cxt.search_string_index + 1 + # Search the current line. + while ((cxt.sflags & SF_REVERSE)!=0 ? (cxt.sline_index >= 0) : (cxt.sline_index < limit)) + + if (cxt.search_string[0,cxt.search_string_index] == cxt.sline[cxt.sline_index,cxt.search_string_index]) + cxt.sflags |= SF_FOUND + break + else + cxt.sline_index += cxt.direction + end + end + break if (cxt.sflags & SF_FOUND)!=0 + + # Move to the next line, but skip new copies of the line + # we just found and lines shorter than the string we're + # searching for. + begin + # Move to the next line. + cxt.history_pos += cxt.direction + + # At limit for direction? + if ((cxt.sflags & SF_REVERSE)!=0 ? (cxt.history_pos < 0) : (cxt.history_pos == cxt.hlen)) + cxt.sflags |= SF_FAILED + break + end + + # We will need these later. + cxt.sline = cxt.lines[cxt.history_pos] + cxt.sline_len = cxt.sline.length + end while ((cxt.prev_line_found && cxt.prev_line_found == cxt.lines[cxt.history_pos]) || + (cxt.search_string_index > cxt.sline_len)) + + break if (cxt.sflags & SF_FAILED)!=0 + + # Now set up the line for searching... + cxt.sline_index = (cxt.sflags & SF_REVERSE)!=0 ? cxt.sline_len - cxt.search_string_index : 0 + end + + if (cxt.sflags & SF_FAILED)!=0 + # We cannot find the search string. Ding the bell. + rl_ding() + cxt.history_pos = cxt.last_found_line + return 1 + end + + # We have found the search string. Just display it. But don't + # actually move there in the history list until the user accepts + # the location. + if (cxt.sflags & SF_FOUND)!=0 + cxt.prev_line_found = cxt.lines[cxt.history_pos] + if (cxt.prev_line_found) + rl_replace_line(cxt.lines[cxt.history_pos], false) + end + @rl_point = cxt.sline_index + cxt.last_found_line = cxt.history_pos + rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, (cxt.history_pos == cxt.save_line) ? -1 : cxt.history_pos) + end + 1 + end + + # How to clear things from the "echo-area". + def rl_clear_message() + @rl_display_prompt = @rl_prompt + if (@msg_saved_prompt) + rl_restore_prompt() + @msg_saved_prompt = nil + end + send(@rl_redisplay_function) + 0 + end + + def _rl_isearch_fini(cxt) + # First put back the original state. + @rl_line_buffer = cxt.lines[cxt.save_line].dup + rl_restore_prompt() + + # Save the search string for possible later use. + @last_isearch_string = cxt.search_string + @last_isearch_string_len = cxt.search_string_index + cxt.search_string = nil + + if (cxt.last_found_line < cxt.save_line) + rl_get_previous_history(cxt.save_line - cxt.last_found_line, 0) + else + rl_get_next_history(cxt.last_found_line - cxt.save_line, 0) + end + + # If the string was not found, put point at the end of the last matching + # line. If last_found_line == orig_line, we didn't find any matching + # history lines at all, so put point back in its original position. + if (cxt.sline_index < 0) + + if (cxt.last_found_line == cxt.save_line) + cxt.sline_index = cxt.save_point + else + cxt.sline_index = @rl_line_buffer.delete(0.chr).length + end + @rl_mark = cxt.save_mark + end + + @rl_point = cxt.sline_index + # Don't worry about where to put the mark here; rl_get_previous_history + # and rl_get_next_history take care of it. + rl_clear_message() + end + + + def _rl_isearch_cleanup(cxt, r) + if (r >= 0) + _rl_isearch_fini(cxt) + end + ctx = nil + @_rl_iscxt = nil + + rl_unsetstate(RL_STATE_ISEARCH) + + r != 0 + end + + # Do the command associated with KEY in MAP. + # If the associated command is really a keymap, then read + # another key, and dispatch into that map. + def _rl_dispatch(key, map) + @_rl_dispatching_keymap = map + return _rl_dispatch_subseq(key, map, false) + end + + + def _rl_dispatch_subseq(key, map, got_subseq) + func = map[key] + if (func) + @rl_executing_keymap = map + + @rl_dispatching = true + rl_setstate(RL_STATE_DISPATCHING) + send(map[key],@rl_numeric_arg * @rl_arg_sign, key) + rl_unsetstate(RL_STATE_DISPATCHING) + @rl_dispatching = false + if (@rl_pending_input == 0 && map[key] != :rl_digit_argument) + @rl_last_func = map[key] + end + else + if(map.keys.detect{|x| x =~ /^#{Regexp.escape(key)}/}) + key += _rl_subseq_getchar(key) + return _rl_dispatch_subseq(key,map,got_subseq) + elsif(key.length>1 && key[1].ord < 0x7f) + _rl_abort_internal() + return -1 + else + @rl_dispatching = true + rl_setstate(RL_STATE_DISPATCHING) + send(:rl_insert,@rl_numeric_arg * @rl_arg_sign, key) + rl_unsetstate(RL_STATE_DISPATCHING) + @rl_dispatching = false + end + end + 0 + end + + # Add KEY to the buffer of characters to be read. Returns 1 if the + # character was stuffed correctly; 0 otherwise. + def rl_stuff_char(key) + return 0 if (ibuffer_space() == 0) + + if (key == EOF) + key = NEWLINE + @rl_pending_input = EOF + rl_setstate(RL_STATE_INPUTPENDING) + end + @ibuffer[@push_index] = key + @push_index += 1 + if (@push_index >= @ibuffer_len) + @push_index = 0 + end + + return 1 + end + + begin + # Cygwin will look like Windows, but we want to treat it like a Posix OS: + raise LoadError, "Cygwin is a Posix OS." if RUBY_PLATFORM =~ /\bcygwin\b/i + + # Try to use win32api regardless of version. This allows us to correctly + # fall back to unixy stuff when not on Windows. Requires some testing on + # 1.8 for Windows, but msf ships 1.9, so don't worry about it for now. + #if RUBY_VERSION < '1.9.1' + require 'Win32API' + #else + # require 'dl' + # class Win32API + # DLL = {} + # TYPEMAP = {"0" => DL::TYPE_VOID, "S" => DL::TYPE_VOIDP, "I" => DL::TYPE_LONG} + # + # def initialize(dllname, func, import, export = "0", calltype = :stdcall) + # @proto = [import].join.tr("VPpNnLlIi", "0SSI").sub(/^(.)0*$/, '\1') + # handle = DLL[dllname] ||= DL.dlopen(dllname) + # @func = DL::CFunc.new(handle[func], TYPEMAP[export.tr("VPpNnLlIi", "0SSI")], func, calltype) + # end + # + # def call(*args) + # import = @proto.split("") + # args.each_with_index do |x, i| + # args[i], = [x == 0 ? nil : x].pack("p").unpack("l!*") if import[i] == "S" + # args[i], = [x].pack("I").unpack("i") if import[i] == "I" + # end + # ret, = @func.call(args) + # return ret || 0 + # end + # + # alias Call call + # end + #end + + STD_OUTPUT_HANDLE = -11 + STD_INPUT_HANDLE = -10 + KEY_EVENT = 1 + VK_SHIFT = 0x10 + VK_MENU = 0x12 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + + LEFT_CTRL_PRESSED = 0x0008 + RIGHT_CTRL_PRESSED = 0x0004 + LEFT_ALT_PRESSED = 0x0002 + RIGHT_ALT_PRESSED = 0x0001 + + @getch = Win32API.new("msvcrt", "_getch", [], 'I') + @kbhit = Win32API.new("msvcrt", "_kbhit", [], 'I') + @GetStdHandle = Win32API.new("kernel32","GetStdHandle",['L'],'L') + @SetConsoleCursorPosition = Win32API.new("kernel32","SetConsoleCursorPosition",['L','L'],'L') + @GetConsoleScreenBufferInfo = Win32API.new("kernel32","GetConsoleScreenBufferInfo",['L','P'],'L') + @FillConsoleOutputCharacter = Win32API.new("kernel32","FillConsoleOutputCharacter",['L','L','L','L','P'],'L') + @ReadConsoleInput = Win32API.new( "kernel32", "ReadConsoleInput", ['L', 'P', 'L', 'P'], 'L' ) + @MessageBeep = Win32API.new("user32","MessageBeep",['L'],'L') + @GetKeyboardState = Win32API.new("user32","GetKeyboardState",['P'],'L') + @GetKeyState = Win32API.new("user32","GetKeyState",['L'],'L') + @hConsoleHandle = @GetStdHandle.Call(STD_OUTPUT_HANDLE) + @hConsoleInput = @GetStdHandle.Call(STD_INPUT_HANDLE) + @pending_count = 0 + @pending_key = nil + + begin + case `chcp`.scan(/\d+$/).first.to_i + when 936,949,950,51932,51936,50225 + @encoding = "E" + when 932,50220,50221,20222 + @encoding = "S" + when 65001 + @encoding = "U" + else + @encoding = "N" + end + rescue + @encoding = "N" + end + + def rl_getc(stream) + while (@kbhit.Call == 0) + # If there is no input, yield the processor for other threads + sleep(@_keyboard_input_timeout) + end + c = @getch.Call + alt = (@GetKeyState.call(VK_LMENU) & 0x80) != 0 + if c==0 || c==0xE0 + while (@kbhit.Call == 0) + # If there is no input, yield the processor for other threads + sleep(@_keyboard_input_timeout) + end + r = c.chr + @getch.Call.chr + else + r = c.chr + end + r = "\e"+r if alt + r + end + + def rl_gather_tyi() + chars_avail = @kbhit.Call + return 0 if(chars_avail<=0) + k = send(@rl_getc_function,@rl_instream) + rl_stuff_char(k) + return 1 + end + + rescue LoadError # If we're not on Windows try... + + if ENV["LANG"] =~ /\.UTF-8/ + @encoding = "U" + elsif ENV["LANG"] =~ /\.EUC/ + @encoding = "E" + elsif ENV["LANG"] =~ /\.SHIFT/ + @encoding = "S" + else + @encoding = "N" + end + + def rl_getc(stream) + begin + c = stream.read(1) + rescue Errno::EINTR + c = rl_getc(stream) + end + return c ? c : EOF + end + + def rl_gather_tyi() + chars_avail = 0 + result = select([@rl_instream],nil,nil,0.1) + return 0 if result.nil? + k = send(@rl_getc_function,@rl_instream) + rl_stuff_char(k) + return 1 + end + end + + if (Object.const_defined?('Encoding') and Encoding.respond_to?('default_external')) + @encoding = "X" # ruby 1.9.x or greater + @encoding_name = Encoding.default_external.to_s + end + + @rl_byte_oriented = @encoding == "N" + + # Read a key, including pending input. + def rl_read_key() + @rl_key_sequence_length+=1 + + if (@rl_pending_input!=0) + c = @rl_pending_input + rl_clear_pending_input() + else + # If the user has an event function, then call it periodically. + if (@rl_event_hook) + while (@rl_event_hook && (c=rl_get_char()).nil?) + + send(@rl_event_hook) + if (@rl_done) # XXX - experimental + return ("\n") + end + if (rl_gather_tyi() < 0) # XXX - EIO + @rl_done = true + return ("\n") + end + end + + else + + if (c=rl_get_char()).nil? + c = send(@rl_getc_function,@rl_instream) + end + end + end + + return (c) + end + + + # Return the amount of space available in the buffer for stuffing + # characters. + def ibuffer_space() + if (@pop_index > @push_index) + return (@pop_index - @push_index - 1) + else + return (@ibuffer_len - (@push_index - @pop_index)) + end + end + + # Get a key from the buffer of characters to be read. + # Return the key in KEY. + # Result is KEY if there was a key, or 0 if there wasn't. + def rl_get_char() + if (@push_index == @pop_index) + return nil + end + key = @ibuffer[@pop_index] + @pop_index += 1 + + if (@pop_index >= @ibuffer_len) + @pop_index = 0 + end + + return key + end + + # Stuff KEY into the *front* of the input buffer. + # Returns non-zero if successful, zero if there is + # no space left in the buffer. + def _rl_unget_char(key) + if (ibuffer_space()!=0) + @pop_index-=1 + if (@pop_index < 0) + @pop_index = @ibuffer_len - 1 + end + @ibuffer[@pop_index] = key + return (1) + end + return (0) + end + + def _rl_subseq_getchar(key) + if (key == ESC) + rl_setstate(RL_STATE_METANEXT) + end + rl_setstate(RL_STATE_MOREINPUT) + k = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + if (key == ESC) + rl_unsetstate(RL_STATE_METANEXT) + end + + return k + end + + # Clear to the end of the line. COUNT is the minimum + # number of character spaces to clear, + def _rl_clear_to_eol(count) + if (@_rl_term_clreol) + @rl_outstream.write(@_rl_term_clreol) + elsif (count!=0) + space_to_eol(count) + end + end + + # Clear to the end of the line using spaces. COUNT is the minimum + # number of character spaces to clear, + def space_to_eol(count) + if @hConsoleHandle + csbi = 0.chr * 24 + @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) + cursor_pos = csbi[4,4].unpack('L').first + written = 0.chr * 4 + @FillConsoleOutputCharacter.Call(@hConsoleHandle,0x20,count,cursor_pos,written) + else + @rl_outstream.write(' ' * count) + end + @_rl_last_c_pos += count + end + + def _rl_clear_screen() + if (@_rl_term_clrpag) + @rl_outstream.write(@_rl_term_clrpag) + else + rl_crlf() + end + end + + # Move the cursor back. + def _rl_backspace(count) + if (@_rl_term_backspace) + @_rl_out_stream.write(@_rl_term_backspace * count) + else + @_rl_out_stream.write("\b"*count) + end + 0 + end + + # Move to the start of the next line. + def rl_crlf() + if (@_rl_term_cr) + @_rl_out_stream.write(@_rl_term_cr) + end + @_rl_out_stream.write("\n") + return 0 + end + + # Move to the start of the current line. + def cr() + if (@_rl_term_cr) + @_rl_out_stream.write(@_rl_term_cr) + @_rl_last_c_pos = 0 + end + end + + def _rl_erase_entire_line() + cr() + _rl_clear_to_eol(0) + cr() + @rl_outstream.flush + end + + def _rl_internal_char_cleanup() + # In vi mode, when you exit insert mode, the cursor moves back + # over the previous character. We explicitly check for that here. + if (@rl_editing_mode == @vi_mode && @_rl_keymap == @vi_movement_keymap) + rl_vi_check() + end + + if (@rl_num_chars_to_read!=0 && @rl_end >= @rl_num_chars_to_read) + send(@rl_redisplay_function) + @_rl_want_redisplay = false + rl_newline(1, "\n") + end + + if (!@rl_done) + send(@rl_redisplay_function) + @_rl_want_redisplay = false + end + + # If the application writer has told us to erase the entire line if + # the only character typed was something bound to rl_newline, do so. + if (@rl_erase_empty_line && @rl_done && @rl_last_func == :rl_newline && + @rl_point == 0 && @rl_end == 0) + _rl_erase_entire_line() + end + end + + def readline_internal_charloop() + lastc = -1 + eof_found = false + + while (!@rl_done) + lk = @_rl_last_command_was_kill + + # send(rl_redisplay_function) + # @_rl_want_redisplay = false + + if (@rl_pending_input == 0) + # Then initialize the argument and number of keys read. + _rl_reset_argument() + @rl_key_sequence_length = 0 + end + + rl_setstate(RL_STATE_READCMD) + c = rl_read_key() + rl_unsetstate(RL_STATE_READCMD) + # look at input.c:rl_getc() for the circumstances under which this will + #be returned; punt immediately on read error without converting it to + #a newline. + if (c == READERR) + eof_found = true + break + end + + # EOF typed to a non-blank line is a . + if (c == EOF && @rl_end!=0) + c = NEWLINE + end + + # The character _rl_eof_char typed to blank line, and not as the + #previous character is interpreted as EOF. + if (((c == @_rl_eof_char && lastc != c) || c == EOF) && @rl_end==0) + eof_found = true + break + end + lastc = c + if _rl_dispatch(c, @_rl_keymap)== -1 + next + end + + # If there was no change in _rl_last_command_was_kill, then no kill + #has taken place. Note that if input is pending we are reading + #a prefix command, so nothing has changed yet. + if (@rl_pending_input == 0 && lk == @_rl_last_command_was_kill) + @_rl_last_command_was_kill = false + end + _rl_internal_char_cleanup() + end + + eof_found + end + + # How to abort things. + def _rl_abort_internal() + rl_ding() + rl_clear_message() + _rl_reset_argument() + rl_clear_pending_input() + + rl_unsetstate(RL_STATE_MACRODEF) + + @rl_last_func = nil + #throw :readline_top_level + send(@rl_redisplay_function) + @_rl_want_redisplay = false + 0 + end + + def rl_abort(count, key) + _rl_abort_internal() + end + + def rl_vi_check() + if (@rl_point!=0 && @rl_point == @rl_end) + @rl_point-=1 + end + 0 + end + + def readline_internal_teardown(eof) + # Restore the original of this history line, iff the line that we + # are editing was originally in the history, AND the line has changed. + entry = current_history() + + if (entry && @rl_undo_list) + temp = @rl_line_buffer.delete(0.chr).dup + rl_revert_line(1, 0) + entry = replace_history_entry(where_history(), @rl_line_buffer, nil) + entry = nil + + @rl_line_buffer = temp+0.chr + temp = nil + end + + # At any rate, it is highly likely that this line has an undo list. Get + # rid of it now. + if (@rl_undo_list) + rl_free_undo_list() + end + # Restore normal cursor, if available. + _rl_set_insert_mode(RL_IM_INSERT, 0) + + (eof ? nil : @rl_line_buffer.delete(0.chr)) + end + + # Read a line of input from the global rl_instream, doing output on + # the global rl_outstream. + # If rl_prompt is non-null, then that is our prompt. + def readline_internal() + readline_internal_setup() + eof = readline_internal_charloop() + readline_internal_teardown(eof) + end + + # Read a line of input. Prompt with PROMPT. An empty PROMPT means + # none. A return value of NULL means that EOF was encountered. + def readline(prompt) + # If we are at EOF return a NULL string. + if (@rl_pending_input == EOF) + rl_clear_pending_input() + return nil + end + + rl_set_prompt(prompt) + + rl_initialize() + @readline_echoing_p = true + if (@rl_prep_term_function) + send(@rl_prep_term_function,@_rl_meta_flag) + end + rl_set_signals() + + value = readline_internal() + if(@rl_deprep_term_function) + send(@rl_deprep_term_function) + end + + rl_clear_signals() + + value + end + + # Increase the size of RL_LINE_BUFFER until it has enough space to hold + # LEN characters. + def rl_extend_line_buffer(len) + while (len >= @rl_line_buffer.length) + @rl_line_buffer << 0.chr * DEFAULT_BUFFER_SIZE + end + @the_line = @rl_line_buffer + end + + # Insert a string of text into the line at point. This is the only + # way that you should do insertion. _rl_insert_char () calls this + # function. Returns the number of characters inserted. + def rl_insert_text(string) + string.delete!(0.chr) + l = string.length + return 0 if (l == 0) + + if (@rl_end + l >= @rl_line_buffer.length) + rl_extend_line_buffer(@rl_end + l) + end + @rl_line_buffer[@rl_point,0] = string + + # Remember how to undo this if we aren't undoing something. + if (!@_rl_doing_an_undo) + # If possible and desirable, concatenate the undos. + if ((l == 1) && + @rl_undo_list && + (@rl_undo_list.what == UNDO_INSERT) && + (@rl_undo_list.end == @rl_point) && + (@rl_undo_list.end - @rl_undo_list.start < 20)) + @rl_undo_list.end+=1 + else + rl_add_undo(UNDO_INSERT, @rl_point, @rl_point + l, nil) + end + end + @rl_point += l + @rl_end += l + if @rl_line_buffer.length <= @rl_end + @rl_line_buffer << 0.chr * (@rl_end - @rl_line_buffer.length + 1) + else + @rl_line_buffer[@rl_end] = "\0" + end + l + end + + def alloc_undo_entry(what, start, _end, text) + temp = Struct.new(:what,:start,:end,:text,:next).new + temp.what = what + temp.start = start + temp.end = _end + temp.text = text + temp.next = nil + temp + end + + #* Remember how to undo something. Concatenate some undos if that + # seems right. + def rl_add_undo(what, start, _end, text) + temp = alloc_undo_entry(what, start, _end, text) + temp.next = @rl_undo_list + @rl_undo_list = temp + end + + + # Delete the string between FROM and TO. FROM is inclusive, TO is not. + # Returns the number of characters deleted. + def rl_delete_text(from, to) + + # Fix it if the caller is confused. + if (from > to) + from,to = to,from + end + + # fix boundaries + if (to > @rl_end) + to = @rl_end + if (from > to) + from = to + end + end + if (from < 0) + from = 0 + end + text = rl_copy_text(from, to) + diff = to - from + @rl_line_buffer[from...to] = '' + @rl_line_buffer << 0.chr * diff + # Remember how to undo this delete. + if (!@_rl_doing_an_undo) + rl_add_undo(UNDO_DELETE, from, to, text) + else + text = nil + end + @rl_end -= diff + @rl_line_buffer[@rl_end] = 0.chr + return (diff) + end + + def rl_copy_text(from, to) + return @rl_line_buffer[from...to] + end + + # Fix up point so that it is within the line boundaries after killing + # text. If FIX_MARK_TOO is non-zero, the mark is forced within line + # boundaries also. + + def __rl_fix_point(x) + if (x > @rl_end) + @rl_end + elsif (x < 0) + 0 + else + x + end + end + + def _rl_fix_point(fix_mark_too) + @rl_point = __rl_fix_point(@rl_point) + if (fix_mark_too) + @rl_mark = __rl_fix_point(@rl_mark) + end + end + + # Begin a group. Subsequent undos are undone as an atomic operation. + def rl_begin_undo_group() + rl_add_undo(UNDO_BEGIN, 0, 0, nil) + @_rl_undo_group_level+=1 + 0 + end + + # End an undo group started with rl_begin_undo_group (). + def rl_end_undo_group() + rl_add_undo(UNDO_END, 0, 0, nil) + @_rl_undo_group_level-=1 + 0 + end + + def rl_free_undo_list() + replace_history_data(-1, @rl_undo_list, nil) + @rl_undo_list = nil + end + + # Replace the contents of the line buffer between START and END with + # TEXT. The operation is undoable. To replace the entire line in an + # undoable mode, use _rl_replace_text(text, 0, rl_end) + def _rl_replace_text(text, start, _end) + rl_begin_undo_group() + rl_delete_text(start, _end + 1) + @rl_point = start + n = rl_insert_text(text) + rl_end_undo_group() + n + end + + # Replace the current line buffer contents with TEXT. If CLEAR_UNDO is + # non-zero, we free the current undo list. + def rl_replace_line(text, clear_undo) + len = text.delete(0.chr).length + @rl_line_buffer = text.dup + 0.chr + @rl_end = len + if (clear_undo) + rl_free_undo_list() + end + _rl_fix_point(true) + end + + + # Replace the DATA in the specified history entries, replacing OLD with + # NEW. WHICH says which one(s) to replace: WHICH == -1 means to replace + # all of the history entries where entry->data == OLD; WHICH == -2 means + # to replace the `newest' history entry where entry->data == OLD; and + # WHICH >= 0 means to replace that particular history entry's data, as + # long as it matches OLD. + def replace_history_data(which,old, new) + new = new.dup if new + if (which < -2 || which >= @history_length || @history_length == 0 || @the_history.nil?) + return + end + if (which >= 0) + entry = @the_history[which] + if (entry && entry.data == old) + entry.data = new + end + return + end + + last = -1 + for i in 0 ... @history_length + entry = @the_history[i] + if entry.nil? + next + end + if (entry.data == old) + last = i + if (which == -1) + entry.data = new + end + end + end + if (which == -2 && last >= 0) + entry = @the_history[last] + entry.data = new # XXX - we don't check entry->old + end + end + + # Move forward COUNT bytes. + def rl_forward_byte(count, key) + if (count < 0) + return (rl_backward_byte(-count, key)) + end + if (count > 0) + _end = @rl_point + count + lend = @rl_end > 0 ? @rl_end - ((@rl_editing_mode == @vi_mode)?1:0) : @rl_end + if (_end > lend) + @rl_point = lend + rl_ding() + else + @rl_point = _end + end + end + + if (@rl_end < 0) + @rl_end = 0 + end + return 0 + end + + # Move forward COUNT characters. + def rl_forward_char(count, key) + if @rl_byte_oriented + return (rl_forward_byte(count, key)) + end + if (count < 0) + return (rl_backward_char(-count, key)) + end + if (count > 0) + point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, count, MB_FIND_NONZERO) + if (@rl_end <= point && @rl_editing_mode == @vi_mode) + point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_end, MB_FIND_NONZERO) + end + if (@rl_point == point) + rl_ding() + end + @rl_point = point + if (@rl_end < 0) + @rl_end = 0 + end + end + 0 + end + + # Backwards compatibility. + def rl_forward(count, key) + rl_forward_char(count, key) + end + + # Move backward COUNT bytes. + def rl_backward_byte(count, key) + if (count < 0) + return (rl_forward_byte(-count, key)) + end + if (count > 0) + if (@rl_point < count) + @rl_point = 0 + rl_ding() + else + @rl_point -= count + end + end + + if (@rl_point < 0) + @rl_point = 0 + end + 0 + end + + # Move backward COUNT characters. + def rl_backward_char(count, key) + if @rl_byte_oriented + return (rl_backward_byte(count, key)) + end + if (count < 0) + return (rl_forward_char(-count, key)) + end + + if (count > 0) + point = @rl_point + while (count > 0 && point > 0) + point = _rl_find_prev_mbchar(@rl_line_buffer, point, MB_FIND_NONZERO) + count-=1 + end + if (count > 0) + @rl_point = 0 + rl_ding() + else + @rl_point = point + end + end + 0 + end + + # Backwards compatibility. + def rl_backward(count, key) + rl_backward_char(count, key) + end + + # Move to the beginning of the line. + def rl_beg_of_line(count, key) + @rl_point = 0 + 0 + end + + # Move to the end of the line. + def rl_end_of_line(count, key) + @rl_point = @rl_end + 0 + end + + def _rl_char_value(buf,ind) + buf[ind,1] + end + + @_rl_allow_pathname_alphabetic_chars = false + @pathname_alphabetic_chars = '/-_=~.#$' + + def rl_alphabetic(c) + if c =~ /\w/ + return true + end + + return !!(@_rl_allow_pathname_alphabetic_chars && + @pathname_alphabetic_chars[c]) + end + + def _rl_walphabetic(c) + rl_alphabetic(c) + end + + # Move forward a word. We do what Emacs does. Handles multibyte chars. + def rl_forward_word(count, key) + if (count < 0) + return (rl_backward_word(-count, key)) + end + + while (count>0) + return 0 if (@rl_point == @rl_end) + + # If we are not in a word, move forward until we are in one. + # Then, move forward until we hit a non-alphabetic character. + c = _rl_char_value(@rl_line_buffer, @rl_point) + + if (!_rl_walphabetic(c)) + if !@rl_byte_oriented + @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) + else + @rl_point += 1 + end + while (@rl_point < @rl_end) + c = _rl_char_value(@rl_line_buffer, @rl_point) + if (_rl_walphabetic(c)) + break + end + if !@rl_byte_oriented + @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) + else + @rl_point += 1 + end + end + end + + return 0 if (@rl_point == @rl_end) + + if !@rl_byte_oriented + @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) + else + @rl_point += 1 + end + while (@rl_point < @rl_end) + c = _rl_char_value(@rl_line_buffer, @rl_point) + if (!_rl_walphabetic(c)) + break + end + if !@rl_byte_oriented + @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) + else + @rl_point += 1 + end + end + count -= 1 + end + 0 + end + + # Move backward a word. We do what Emacs does. Handles multibyte chars. + def rl_backward_word(count, key) + if (count < 0) + return (rl_forward_word(-count, key)) + end + while (count>0) + return 0 if (@rl_point == 0) + + # Like rl_forward_word (), except that we look at the characters + # just before point. + _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) + c = _rl_char_value(@rl_line_buffer, _p) + if (!_rl_walphabetic(c)) + @rl_point = _p + while (@rl_point > 0) + _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) + c = _rl_char_value(@rl_line_buffer, _p) + if (_rl_walphabetic(c)) + break + end + @rl_point = _p + end + end + while (@rl_point>0) + _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) + c = _rl_char_value(@rl_line_buffer, _p) + if (!_rl_walphabetic(c)) + break + else + @rl_point = _p + end + end + count -= 1 + end + 0 + end + + # return the `current display line' of the cursor -- the number of lines to + # move up to get to the first screen line of the current readline line. + def _rl_current_display_line() + # Find out whether or not there might be invisible characters in the + # editing buffer. + if (@rl_display_prompt == @rl_prompt) + nleft = @_rl_last_c_pos - @_rl_screenwidth - @rl_visible_prompt_length + else + nleft = @_rl_last_c_pos - @_rl_screenwidth + end + + if (nleft > 0) + ret = 1 + nleft / @_rl_screenwidth + else + ret = 0 + end + + ret + end + + # Actually update the display, period. + def rl_forced_update_display() + if (@visible_line) + @visible_line.gsub!(/[^\x00]/,0.chr) + end + rl_on_new_line() + @forced_display=true if !@forced_display + send(@rl_redisplay_function) + 0 + end + + + # Clear the current line. Numeric argument to C-l does this. + def rl_refresh_line(ignore1, ignore2) + curr_line = _rl_current_display_line() + + _rl_move_vert(curr_line) + _rl_move_cursor_relative(0, @rl_line_buffer) # XXX is this right + + _rl_clear_to_eol(0) # arg of 0 means to not use spaces + + rl_forced_update_display() + @rl_display_fixed = true + + 0 + end + + # C-l typed to a line without quoting clears the screen, and then reprints + # the prompt and the current input line. Given a numeric arg, redraw only + # the current line. + def rl_clear_screen(count, key) + if (@rl_explicit_arg) + rl_refresh_line(count, key) + return 0 + end + + _rl_clear_screen() # calls termcap function to clear screen + rl_forced_update_display() + @rl_display_fixed = true + 0 + end + + # Restore the _rl_saved_line_for_history if there is one. + def rl_maybe_unsave_line() + if (@_rl_saved_line_for_history) + # Can't call with `1' because rl_undo_list might point to an undo + # list from a history entry, as in rl_replace_from_history() below. + rl_replace_line(@_rl_saved_line_for_history.line, false) + @rl_undo_list = @_rl_saved_line_for_history.data + @_rl_saved_line_for_history = nil + @rl_point = @rl_end # rl_replace_line sets rl_end + else + rl_ding() + end + 0 + end + + # Save the current line in _rl_saved_line_for_history. + def rl_maybe_save_line() + if @_rl_saved_line_for_history.nil? + @_rl_saved_line_for_history = Struct.new(:line,:timestamp,:data).new + @_rl_saved_line_for_history.line = @rl_line_buffer.dup + @_rl_saved_line_for_history.timestamp = nil + @_rl_saved_line_for_history.data = @rl_undo_list + end + 0 + end + + # Returns the magic number which says what history element we are + # looking at now. In this implementation, it returns history_offset. + def where_history() + @history_offset + end + + # Make the history entry at WHICH have LINE and DATA. This returns + # the old entry so you can dispose of the data. In the case of an + # invalid WHICH, a NULL pointer is returned. + def replace_history_entry (which, line, data) + if (which < 0 || which >= @history_length) + return nil + end + temp = Struct.new(:line,:timestamp,:data).new + old_value = @the_history[which] + temp.line = line.delete(0.chr) + temp.data = data + temp.timestamp = old_value.timestamp.dup + @the_history[which] = temp + old_value + end + + # Perhaps put back the current line if it has changed. + def rl_maybe_replace_line() + temp = current_history() + # If the current line has changed, save the changes. + if (temp && temp.data != @rl_undo_list) + temp = replace_history_entry(where_history(), @rl_line_buffer, @rl_undo_list) + end + 0 + end + + # Back up history_offset to the previous history entry, and return + # a pointer to that entry. If there is no previous entry then return + # a NULL pointer. + def previous_history() + @history_offset!=0 ? @the_history[@history_offset-=1] : nil + end + + # Move history_offset forward to the next history entry, and return + # a pointer to that entry. If there is no next entry then return a + # NULL pointer. + def next_history() + (@history_offset == @history_length) ? nil : @the_history[@history_offset+=1] + end + + # Get the previous item out of our interactive history, making it the current + # line. If there is no previous history, just ding. + def rl_get_previous_history(count, key) + if (count < 0) + return (rl_get_next_history(-count, key)) + end + if (count == 0) + return 0 + end + # either not saved by rl_newline or at end of line, so set appropriately. + if (@_rl_history_saved_point == -1 && (@rl_point!=0 || @rl_end!=0)) + @_rl_history_saved_point = (@rl_point == @rl_end) ? -1 : @rl_point + end + + # If we don't have a line saved, then save this one. + rl_maybe_save_line() + + # If the current line has changed, save the changes. + rl_maybe_replace_line() + + temp = old_temp = nil + while (count>0) + temp = previous_history() + if temp.nil? + break + end + old_temp = temp + count -= 1 + end + + # If there was a large argument, and we moved back to the start of the + # history, that is not an error. So use the last value found. + if (temp.nil? && old_temp) + temp = old_temp + end + + if temp.nil? + rl_ding() + else + rl_replace_from_history(temp, 0) + _rl_history_set_point() + end + + 0 + end + + def _rl_history_set_point () + @rl_point = (@_rl_history_preserve_point && @_rl_history_saved_point != -1) ? + @_rl_history_saved_point : @rl_end + if (@rl_point > @rl_end) + @rl_point = @rl_end + end + if (@rl_editing_mode == @vi_mode && @_rl_keymap != @vi_insertion_keymap) + @rl_point = 0 + end + if (@rl_editing_mode == @emacs_mode) + @rl_mark = (@rl_point == @rl_end ? 0 : @rl_end) + end + end + + # Move down to the next history line. + def rl_get_next_history(count, key) + if (count < 0) + return (rl_get_previous_history(-count, key)) + end + if (count == 0) + return 0 + end + rl_maybe_replace_line() + + # either not saved by rl_newline or at end of line, so set appropriately. + if (@_rl_history_saved_point == -1 && (@rl_point!=0 || @rl_end!=0)) + @_rl_history_saved_point = (@rl_point == @rl_end) ? -1 : @rl_point + end + temp = nil + while (count>0) + temp = next_history() + if temp.nil? + break + end + count -= 1 + end + + if temp.nil? + rl_maybe_unsave_line() + else + rl_replace_from_history(temp, 0) + _rl_history_set_point() + end + 0 + end + + def rl_arrow_keys(count, c) + rl_setstate(RL_STATE_MOREINPUT) + ch = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + + case (ch.upcase) + when 'A' + rl_get_previous_history(count, ch) + when 'B' + rl_get_next_history(count, ch) + when 'C' + rl_forward_byte(count, ch) + when 'D' + rl_backward_byte(count, ch) + else + rl_ding() + end + 0 + end + + def _rl_any_typein() + return (@push_index != @pop_index) + end + + def _rl_insert_typein(c) + string = c + + while ((key = rl_get_char()) && + @_rl_keymap[key] == :rl_insert) + string << key + end + if (key) + _rl_unget_char(key) + end + rl_insert_text(string) + end + + # Insert the character C at the current location, moving point forward. + # If C introduces a multibyte sequence, we read the whole sequence and + # then insert the multibyte char into the line buffer. + def _rl_insert_char(count, c) + return 0 if (count <= 0) + + incoming = '' + + if @rl_byte_oriented + incoming << c + incoming_length = 1 + else + @pending_bytes << c + if _rl_get_char_len(@pending_bytes) == -2 + return 1 + else + incoming = @pending_bytes + @pending_bytes = '' + incoming_length = incoming.length + end + end + + if(count>1) + string = incoming * count + rl_insert_text(string) + string = nil + return 0 + end + + if @rl_byte_oriented + # We are inserting a single character. + #If there is pending input, then make a string of all of the + #pending characters that are bound to rl_insert, and insert + #them all. + if (_rl_any_typein()) + _rl_insert_typein(c) + else + rl_insert_text(c) + end + else + rl_insert_text(incoming) + end + + return 0 + end + + # Overwrite the character at point (or next COUNT characters) with C. + # If C introduces a multibyte character sequence, read the entire sequence + # before starting the overwrite loop. + def _rl_overwrite_char(count, c) + + # Read an entire multibyte character sequence to insert COUNT times. + if (count > 0 && !@rl_byte_oriented) + mbkey = '' + k = _rl_read_mbstring(c, mbkey, MB_LEN_MAX) + end + rl_begin_undo_group() + + count.times do + if !@rl_byte_oriented + rl_insert_text(mbkey) + else + _rl_insert_char(1, c) + end + if (@rl_point < @rl_end) + rl_delete(1, c) + end + end + + rl_end_undo_group() + + return 0 + end + + def rl_insert(count, c) + ((@rl_insert_mode == RL_IM_INSERT) ? _rl_insert_char(count, c) : + _rl_overwrite_char(count, c)) + end + + + # Insert the next typed character verbatim. + def _rl_insert_next(count) + rl_setstate(RL_STATE_MOREINPUT) + c = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + + _rl_insert_char(count, c) + end + + def rl_quoted_insert(count, key) + _rl_insert_next(count) + end + + # Insert a tab character. + def rl_tab_insert(count, key) + _rl_insert_char(count, "\t") + end + + def _rl_vi_save_insert(up) + if (up.nil? || up.what != UNDO_INSERT) + if (@vi_insert_buffer_size >= 1) + @vi_insert_buffer[0] = 0.chr + end + return + end + start = up.start + _end = up.end + len = _end - start + 1 + @vi_insert_buffer = @rl_line_buffer[start,len-1] + end + + def _rl_vi_done_inserting() + if (@_rl_vi_doing_insert) + + # The `C', `s', and `S' commands set this. + rl_end_undo_group() + # Now, the text between rl_undo_list->next->start and + # rl_undo_list->next->end is what was inserted while in insert + # mode. It gets copied to VI_INSERT_BUFFER because it depends + # on absolute indices into the line which may change (though they + # probably will not). + @_rl_vi_doing_insert = 0 + _rl_vi_save_insert(@rl_undo_list.next) + @vi_continued_command = 1 + else + if ((@_rl_vi_last_key_before_insert == 'i' || @_rl_vi_last_key_before_insert == 'a') && @rl_undo_list) + _rl_vi_save_insert(@rl_undo_list) + + # XXX - Other keys probably need to be checked. + elsif (@_rl_vi_last_key_before_insert == 'C') + rl_end_undo_group() + end + while (@_rl_undo_group_level > 0) + rl_end_undo_group() + end + @vi_continued_command = 0 + end + end + + # Is the command C a VI mode text modification command? + def _rl_vi_textmod_command(c) + return @vi_textmod[c] + end + + def _rl_vi_reset_last() + @_rl_vi_last_command = 'i' + @_rl_vi_last_repeat = 1 + @_rl_vi_last_arg_sign = 1 + @_rl_vi_last_motion = 0 + end + + def _rl_update_final() + full_lines = false + # If the cursor is the only thing on an otherwise-blank last line, + # compensate so we don't print an extra CRLF. + if (@_rl_vis_botlin && @_rl_last_c_pos == 0 && + @visible_line[@vis_lbreaks[@_rl_vis_botlin],1] == 0.chr ) + @_rl_vis_botlin-=1 + full_lines = true + end + _rl_move_vert(@_rl_vis_botlin) + # If we've wrapped lines, remove the final xterm line-wrap flag. + if (full_lines && @_rl_term_autowrap && (vis_llen(@_rl_vis_botlin) == @_rl_screenwidth)) + last_line = @visible_line[@vis_lbreaks[@_rl_vis_botlin]..-1] + @cpos_buffer_position = -1 # don't know where we are in buffer + _rl_move_cursor_relative(@_rl_screenwidth - 1, last_line) # XXX + _rl_clear_to_eol(0) + @rl_outstream.write(last_line[@_rl_screenwidth - 1,1]) + end + @_rl_vis_botlin = 0 + rl_crlf() + @rl_outstream.flush + @rl_display_fixed = true if !@rl_display_fixed + end + + + # What to do when a NEWLINE is pressed. We accept the whole line. + # KEY is the key that invoked this command. I guess it could have + # meaning in the future. + def rl_newline(count, key) + @rl_done = true + + if (@_rl_history_preserve_point) + @_rl_history_saved_point = (@rl_point == @rl_end) ? 1 : @rl_point + end + rl_setstate(RL_STATE_DONE) + + if (@rl_editing_mode == @vi_mode) + _rl_vi_done_inserting() + if (_rl_vi_textmod_command(@_rl_vi_last_command).nil?) # XXX + _rl_vi_reset_last() + end + end + # If we've been asked to erase empty lines, suppress the final update, + # since _rl_update_final calls rl_crlf(). + if (@rl_erase_empty_line && @rl_point == 0 && @rl_end == 0) + return 0 + end + if @readline_echoing_p + _rl_update_final() + end + 0 + end + + # What to do for some uppercase characters, like meta characters, + # and some characters appearing in emacs_ctlx_keymap. This function + # is just a stub, you bind keys to it and the code in _rl_dispatch () + # is special cased. + def rl_do_lowercase_version(ignore1, ignore2) + 0 + end + + def rl_character_len(c, pos) + if (meta_char(c)) + return ((!@_rl_output_meta_chars) ? 4 : 1) + end + if (c == "\t") + return (((pos | 7) + 1) - pos) + end + if (ctrl_char(c) || c == RUBOUT) + return (2) + end + + return ((isprint(c)) ? 1 : 2) + end + + # This is different from what vi does, so the code's not shared. Emacs + # rubout in overwrite mode has one oddity: it replaces a control + # character that's displayed as two characters (^X) with two spaces. + def _rl_overwrite_rubout(count, key) + if (@rl_point == 0) + rl_ding() + return 1 + end + + opoint = @rl_point + + # L == number of spaces to insert + l = 0 + count.times do + rl_backward_char(1, key) + l += rl_character_len(@rl_line_buffer[@rl_point,1], @rl_point) # not exactly right + end + + rl_begin_undo_group() + + if (count > 1 || @rl_explicit_arg) + rl_kill_text(opoint, @rl_point) + else + rl_delete_text(opoint, @rl_point) + end + # Emacs puts point at the beginning of the sequence of spaces. + if (@rl_point < @rl_end) + opoint = @rl_point + _rl_insert_char(l, ' ') + @rl_point = opoint + end + + rl_end_undo_group() + + 0 + end + + # Rubout the character behind point. + def rl_rubout(count, key) + if (count < 0) + return (rl_delete(-count, key)) + end + if (@rl_point==0) + rl_ding() + return -1 + end + + if (@rl_insert_mode == RL_IM_OVERWRITE) + return (_rl_overwrite_rubout(count, key)) + end + _rl_rubout_char(count, key) + end + + + # Quick redisplay hack when erasing characters at the end of the line. + def _rl_erase_at_end_of_line(l) + _rl_backspace(l) + @rl_outstream.write(' '*l) + _rl_backspace(l) + @_rl_last_c_pos -= l + @visible_line[@_rl_last_c_pos,l] = 0.chr * l + @rl_display_fixed = true if !@rl_display_fixed + end + + def _rl_rubout_char(count, key) + # Duplicated code because this is called from other parts of the library. + if (count < 0) + return (rl_delete(-count, key)) + end + if (@rl_point == 0) + rl_ding() + return -1 + end + + orig_point = @rl_point + if (count > 1 || @rl_explicit_arg) + rl_backward_char(count, key) + rl_kill_text(orig_point, @rl_point) + elsif (@rl_byte_oriented) + c = @rl_line_buffer[@rl_point-=1,1] + rl_delete_text(@rl_point, orig_point) + # The erase-at-end-of-line hack is of questionable merit now. + if (@rl_point == @rl_end && isprint(c) && @_rl_last_c_pos!=0) + l = rl_character_len(c, @rl_point) + _rl_erase_at_end_of_line(l) + end + else + @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) + rl_delete_text(@rl_point, orig_point) + end + + 0 + end + + # Delete the character under the cursor. Given a numeric argument, + # kill that many characters instead. + def rl_delete(count, key) + if (count < 0) + return (_rl_rubout_char(-count, key)) + end + if (@rl_point == @rl_end) + rl_ding() + return -1 + end + + if (count > 1 || @rl_explicit_arg) + xpoint = @rl_point + rl_forward_byte(count, key) + + rl_kill_text(xpoint, @rl_point) + @rl_point = xpoint + else + if !@rl_byte_oriented + xpoint =_rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) + else + xpoint = @rl_point + 1 + end + + rl_delete_text(@rl_point, xpoint) + end + 0 + end + + # Add TEXT to the kill ring, allocating a new kill ring slot as necessary. + # This uses TEXT directly, so the caller must not free it. If APPEND is + # non-zero, and the last command was a kill, the text is appended to the + # current kill ring slot, otherwise prepended. + def _rl_copy_to_kill_ring(text, append) + # First, find the slot to work with. + if (!@_rl_last_command_was_kill) + # Get a new slot. + if @rl_kill_ring.nil? + # If we don't have any defined, then make one. + @rl_kill_ring_length = 1 + @rl_kill_ring = Array.new(@rl_kill_ring_length+1) + @rl_kill_ring[slot = 0] = nil + else + # We have to add a new slot on the end, unless we have + # exceeded the max limit for remembering kills. + slot = @rl_kill_ring_length + if (slot == @rl_max_kills) + @rl_kill_ring[0,slot] = @rl_kill_ring[1,slot] + else + slot = @rl_kill_ring_length += 1 + end + @rl_kill_ring[slot-=1] = nil + end + else + slot = @rl_kill_ring_length - 1 + end + + # If the last command was a kill, prepend or append. + if (@_rl_last_command_was_kill && @rl_editing_mode != @vi_mode) + old = @rl_kill_ring[slot] + + if (append) + new = old + text + else + new = text + old + end + old = nil + text = nil + @rl_kill_ring[slot] = new + else + @rl_kill_ring[slot] = text + end + + @rl_kill_index = slot + 0 + end + + # The way to kill something. This appends or prepends to the last + # kill, if the last command was a kill command. if FROM is less + # than TO, then the text is appended, otherwise prepended. If the + # last command was not a kill command, then a new slot is made for + # this kill. + def rl_kill_text(from, to) + # Is there anything to kill? + if (from == to) + @_rl_last_command_was_kill = true if !@_rl_last_command_was_kill + return 0 + end + text = rl_copy_text(from, to) + + # Delete the copied text from the line. + rl_delete_text(from, to) + _rl_copy_to_kill_ring(text, from < to) + @_rl_last_command_was_kill = true if !@_rl_last_command_was_kill + 0 + end + + # This does what C-w does in Unix. We can't prevent people from + # using behaviour that they expect. + def rl_unix_word_rubout(count, key) + if (@rl_point == 0) + rl_ding() + else + orig_point = @rl_point + if (count <= 0) + count = 1 + end + + while (count>0) + while (@rl_point>0 && whitespace(@rl_line_buffer[@rl_point - 1,1])) + @rl_point-=1 + end + + while (@rl_point>0 && !whitespace(@rl_line_buffer[@rl_point - 1,1])) + @rl_point-=1 + end + count -= 1 + end + + rl_kill_text(orig_point, @rl_point) + if (@rl_editing_mode == @emacs_mode) + @rl_mark = @rl_point + end + end + 0 + end + + # This deletes one filename component in a Unix pathname. That is, it + # deletes backward to directory separator (`/') or whitespace. + def rl_unix_filename_rubout(count, key) + if (@rl_point == 0) + rl_ding() + else + orig_point = @rl_point + if (count <= 0) + count = 1 + end + + while (count>0) + + c = @rl_line_buffer[@rl_point - 1,1] + while (@rl_point>0 && (whitespace(c) || c == '/')) + @rl_point-=1 + c = @rl_line_buffer[@rl_point - 1,1] + end + + while (@rl_point>0 && !whitespace(c) && c != '/') + @rl_point-=1 + c = @rl_line_buffer[@rl_point - 1,1] + end + count -= 1 + end + + rl_kill_text(orig_point, @rl_point) + if (@rl_editing_mode == @emacs_mode) + @rl_mark = @rl_point + end + end + 0 + end + + # Delete the character under the cursor, unless the insertion + # point is at the end of the line, in which case the character + # behind the cursor is deleted. COUNT is obeyed and may be used + # to delete forward or backward that many characters. + def rl_rubout_or_delete(count, key) + if (@rl_end != 0 && @rl_point == @rl_end) + return (_rl_rubout_char(count, key)) + else + return (rl_delete(count, key)) + end + end + + # Delete all spaces and tabs around point. + def rl_delete_horizontal_space(count, ignore) + start = @rl_point + + while (@rl_point!=0 && whitespace(@rl_line_buffer[@rl_point - 1])) + @rl_point-=1 + end + start = @rl_point + while (@rl_point < @rl_end && whitespace(@rl_line_buffer[@rl_point])) + @rl_point+=1 + end + if (start != @rl_point) + rl_delete_text(start, @rl_point) + @rl_point = start + end + if (@rl_point < 0) + @rl_point = 0 + end + 0 + end + + # List the possible completions. See description of rl_complete (). + def rl_possible_completions(ignore, invoking_key) + rl_complete_internal('?') + end + + # Like the tcsh editing function delete-char-or-list. The eof character + # is caught before this is invoked, so this really does the same thing as + # delete-char-or-list-or-eof, as long as it's bound to the eof character. + def rl_delete_or_show_completions(count, key) + if (@rl_end != 0 && @rl_point == @rl_end) + return (rl_possible_completions(count, key)) + else + return (rl_delete(count, key)) + end + end + + # Turn the current line into a comment in shell history. + # A K*rn shell style function. + def rl_insert_comment(count, key) + rl_beg_of_line(1, key) + @rl_comment_text = @_rl_comment_begin ? @_rl_comment_begin : '#' + + if (!@rl_explicit_arg) + rl_insert_text(@rl_comment_text) + else + @rl_comment_len = @rl_comment_text.length + if @rl_comment_text[0,@rl_comment_len] == @rl_line_buffer[0,@rl_comment_len] + rl_delete_text(@rl_point, @rl_point + @rl_comment_len) + else + rl_insert_text(@rl_comment_text) + end + end + + send(@rl_redisplay_function) + rl_newline(1, "\n") + 0 + end + + def alloc_history_entry(string, ts) + temp = Struct.new(:line,:data,:timestamp).new + temp.line = string ? string.delete(0.chr) : string + temp.data = nil + temp.timestamp = ts + + return temp + end + + def hist_inittime() + t = Time.now.to_i + ts = "X%u" % t + ret = ts.dup + ret[0,1] = @history_comment_char + + ret + end + + # Place STRING at the end of the history list. The data field + # is set to NULL. + def add_history(string) + if (@history_stifled && (@history_length == @history_max_entries)) + # If the history is stifled, and history_length is zero, + # and it equals history_max_entries, we don't save items. + return if (@history_length == 0) + @the_history.shift + else + if @the_history.nil? + @the_history = [] + @history_length = 1 + else + @history_length+=1 + end + end + + temp = alloc_history_entry(string, hist_inittime()) + @the_history[@history_length] = nil + @the_history[@history_length - 1] = temp + end + + def using_history() + @history_offset = @history_length + end + + # Set default values for readline word completion. These are the variables + # that application completion functions can change or inspect. + def set_completion_defaults(what_to_do) + # Only the completion entry function can change these. + @rl_filename_completion_desired = false + @rl_filename_quoting_desired = true + @rl_completion_type = what_to_do + @rl_completion_suppress_append = @rl_completion_suppress_quote = false + + # The completion entry function may optionally change this. + @rl_completion_mark_symlink_dirs = @_rl_complete_mark_symlink_dirs + end + + def _rl_find_completion_word() + _end = @rl_point + found_quote = 0 + delimiter = 0.chr + quote_char = 0.chr + + brkchars = nil + if @rl_completion_word_break_hook + brkchars = send(@rl_completion_word_break_hook) + end + if brkchars.nil? + brkchars = @rl_completer_word_break_characters + end + if (@rl_completer_quote_characters) + # We have a list of characters which can be used in pairs to + # quote substrings for the completer. Try to find the start + # of an unclosed quoted substring. + # FOUND_QUOTE is set so we know what kind of quotes we found. + scan = 0 + pass_next = false + while scan < _end + if (pass_next) + pass_next = false + next + end + + # Shell-like semantics for single quotes -- don't allow backslash + # to quote anything in single quotes, especially not the closing + # quote. If you don't like this, take out the check on the value + # of quote_char. + if (quote_char != "'" && @rl_line_buffer[scan,1] == "\\") + pass_next = true + found_quote |= RL_QF_BACKSLASH + next + end + + if (quote_char != 0.chr) + # Ignore everything until the matching close quote char. + if (@rl_line_buffer[scan,1] == quote_char) + # Found matching close. Abandon this substring. + quote_char = 0.chr + @rl_point = _end + end + + elsif (@rl_completer_quote_characters.include?(@rl_line_buffer[scan,1])) + + # Found start of a quoted substring. + quote_char = @rl_line_buffer[scan,1] + @rl_point = scan + 1 + # Shell-like quoting conventions. + if (quote_char == "'") + found_quote |= RL_QF_SINGLE_QUOTE + elsif (quote_char == '"') + found_quote |= RL_QF_DOUBLE_QUOTE + else + found_quote |= RL_QF_OTHER_QUOTE + end + end + if !@rl_byte_oriented + scan = _rl_find_next_mbchar(@rl_line_buffer, scan, 1, MB_FIND_ANY) + else + scan += 1 + end + end + end + + if (@rl_point == _end && quote_char == 0.chr) + + # We didn't find an unclosed quoted substring upon which to do + # completion, so use the word break characters to find the + # substring on which to complete. + + + while (@rl_point = !@rl_byte_oriented ? + _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_ANY):(@rl_point-1))>0 + + scan = @rl_line_buffer[@rl_point,1] + if !brkchars.include?(scan) + next + end + # Call the application-specific function to tell us whether + # this word break character is quoted and should be skipped. + if (@rl_char_is_quoted_p && found_quote!=0 && + send(@rl_char_is_quoted_p,@rl_line_buffer, @rl_point)) + next + end + + # Convoluted code, but it avoids an n^2 algorithm with calls + # to char_is_quoted. + break + end + end + + # If we are at an unquoted word break, then advance past it. + scan = @rl_line_buffer[@rl_point,1] + + # If there is an application-specific function to say whether or not + # a character is quoted and we found a quote character, let that + # function decide whether or not a character is a word break, even + # if it is found in rl_completer_word_break_characters. Don't bother + # if we're at the end of the line, though. + if (scan != 0.chr) + if (@rl_char_is_quoted_p) + isbrk = (found_quote == 0 || + !send(@rl_char_is_quoted_p,@rl_line_buffer, @rl_point)) && + brkchars.include?(scan) + else + isbrk = brkchars.include?(scan) + end + + if (isbrk) + # If the character that caused the word break was a quoting + # character, then remember it as the delimiter. + if (@rl_basic_quote_characters && + @rl_basic_quote_characters.include?(scan) && + (_end - @rl_point) > 1) + delimiter = scan + end + + # If the character isn't needed to determine something special + # about what kind of completion to perform, then advance past it. + if (@rl_special_prefixes.nil? || !@rl_special_prefixes.include?(scan) ) + @rl_point+=1 + end + end + end + + return [quote_char,found_quote!=0,delimiter] + end + + def gen_completion_matches(text, start, _end, our_func, found_quote, quote_char) + @rl_completion_found_quote = found_quote + @rl_completion_quote_character = quote_char + + # If the user wants to TRY to complete, but then wants to give + # up and use the default completion function, they set the + # variable rl_attempted_completion_function. + if (@rl_attempted_completion_function) + matches = Readline.send(@rl_attempted_completion_function,text, start, _end) + if (matches || @rl_attempted_completion_over) + @rl_attempted_completion_over = false + return (matches) + end + end + # XXX -- filename dequoting moved into rl_filename_completion_function + + matches = rl_completion_matches(text, our_func) + matches + end + + # Filter out duplicates in MATCHES. This frees up the strings in + # MATCHES. + def remove_duplicate_matches(matches) + # Sort the items. + # Sort the array without matches[0], since we need it to + # stay in place no matter what. + if matches.length>0 + matches[1..-2] = matches[1..-2].sort.uniq + end + matches + end + + def postprocess_matches(matchesp, matching_filenames) + matches = matchesp + + return 0 if matches.nil? + + # It seems to me that in all the cases we handle we would like + # to ignore duplicate possiblilities. Scan for the text to + # insert being identical to the other completions. + if (@rl_ignore_completion_duplicates) + remove_duplicate_matches(matches) + end + + # If we are matching filenames, then here is our chance to + # do clever processing by re-examining the list. Call the + # ignore function with the array as a parameter. It can + # munge the array, deleting matches as it desires. + if (@rl_ignore_some_completions_function && matching_filenames) + nmatch = matches.length + send(@rl_ignore_some_completions_function,matches) + if (matches.nil? || matches[0].nil?) + matches = nil + return 0 + else + # If we removed some matches, recompute the common prefix. + i = matches.length + if (i > 1 && i < nmatch) + t = matches[0] + compute_lcd_of_matches(matches, i - 1, t) + end + end + end + + matchesp = matches + 1 + end + + def insert_all_matches(matches, point, qc) + rl_begin_undo_group() + # remove any opening quote character; make_quoted_replacement will add + # it back. + if (qc && qc.length>0 && point>0 && @rl_line_buffer[point - 1,1] == qc) + point-=1 + end + rl_delete_text(point, @rl_point) + @rl_point = point + if (matches[1]) + i = 1 + while(matches[i]) + rp = make_quoted_replacement(matches[i], SINGLE_MATCH, qc) + rl_insert_text(rp) + rl_insert_text(" ") + if (rp != matches[i]) + rp = nil + end + i += 1 + end + else + rp = make_quoted_replacement(matches[0], SINGLE_MATCH, qc) + rl_insert_text(rp) + rl_insert_text(" ") + if (rp != matches[0]) + rp = nil + end + end + rl_end_undo_group() + end + + def make_quoted_replacement(match, mtype, qc) + # If we are doing completion on quoted substrings, and any matches + # contain any of the completer_word_break_characters, then auto- + # matically prepend the substring with a quote character (just pick + # the first one from the list of such) if it does not already begin + # with a quote string. FIXME: Need to remove any such automatically + # inserted quote character when it no longer is necessary, such as + # if we change the string we are completing on and the new set of + # matches don't require a quoted substring. + replacement = match + + should_quote = match && @rl_completer_quote_characters && + @rl_filename_completion_desired && + @rl_filename_quoting_desired + + if (should_quote) + should_quote = should_quote && (qc.nil? || qc == 0.chr || + (@rl_completer_quote_characters && @rl_completer_quote_characters.include?(qc))) + end + + if (should_quote) + + # If there is a single match, see if we need to quote it. + # This also checks whether the common prefix of several + # matches needs to be quoted. + should_quote = @rl_filename_quote_characters ? + !!match[@rl_filename_quote_characters] : + false + + do_replace = should_quote ? mtype : NO_MATCH + # Quote the replacement, since we found an embedded + # word break character in a potential match. + if (do_replace != NO_MATCH && @rl_filename_quoting_function) + replacement = send(@rl_filename_quoting_function,match, do_replace, qc) + end + end + replacement + end + + + def insert_match(match, start, mtype, qc) + oqc = qc + replacement = make_quoted_replacement(match, mtype, qc) + + # Now insert the match. + if (replacement) + # Don't double an opening quote character. + if (qc && qc.length>0 && start!=0 && @rl_line_buffer[start - 1,1] == qc && + replacement[0,1] == qc) + start-=1 + # If make_quoted_replacement changed the quoting character, remove + # the opening quote and insert the (fully-quoted) replacement. + elsif (qc && (qc != oqc) && start!=0 && @rl_line_buffer[start - 1,1] == oqc && + replacement[0,1] != oqc) + start-=1 + end + _rl_replace_text(replacement, start, @rl_point - 1) + if (replacement != match) + replacement = nil + end + end + end + + # Return the portion of PATHNAME that should be output when listing + # possible completions. If we are hacking filename completion, we + # are only interested in the basename, the portion following the + # final slash. Otherwise, we return what we were passed. Since + # printing empty strings is not very informative, if we're doing + # filename completion, and the basename is the empty string, we look + # for the previous slash and return the portion following that. If + # there's no previous slash, we just return what we were passed. + def printable_part(pathname) + if (!@rl_filename_completion_desired) # don't need to do anything + return (pathname) + end + + temp = pathname.rindex('/') + return pathname if temp.nil? + File.basename(pathname) + end + + def fnprint(to_print) + printed_len = 0 + + case @encoding + when 'E' + arr = to_print.scan(/./me) + when 'S' + arr = to_print.scan(/./ms) + when 'U' + arr = to_print.scan(/./mu) + when 'X' + arr = to_print.dup.force_encoding(@encoding_name).chars + else + arr = to_print.scan(/./m) + end + + arr.each do |s| + if(ctrl_char(s)) + @rl_outstream.write('^'+(s[0].ord|0x40).chr.upcase) + printed_len += 2 + elsif s == RUBOUT + @rl_outstream.write('^?') + printed_len += 2 + else + @rl_outstream.write(s) + if @encoding=='U' + printed_len += s.unpack('U').first >= 0x1000 ? 2 : 1 + elsif @encoding=='X' + printed_len += s.ord >= 0x1000 ? 2 : 1 + else + printed_len += s.length + end + end + + end + + printed_len + end + + def _rl_internal_pager(lines) + @rl_outstream.puts "--More--" + @rl_outstream.flush + i = get_y_or_n(1) + _rl_erase_entire_line() + if (i == 0) + return -1 + elsif (i == 2) + return (lines - 1) + else + return 0 + end + end + + def path_isdir(filename) + return File.directory?(filename) + end + + # Return the character which best describes FILENAME. + # `@' for symbolic links + # `/' for directories + # `*' for executables + # `=' for sockets + # `|' for FIFOs + # `%' for character special devices + # `#' for block special devices + def stat_char(filename) + return nil if !File.exists?(filename) + + return '/' if File.directory?(filename) + return '%' if File.chardev?(filename) + return '#' if File.blockdev?(filename) + return '@' if File.symlink?(filename) + return '=' if File.socket?(filename) + return '|' if File.pipe?(filename) + return '*' if File.executable?(filename) + nil + end + + + # Output TO_PRINT to rl_outstream. If VISIBLE_STATS is defined and we + # are using it, check for and output a single character for `special' + # filenames. Return the number of characters we output. + def print_filename(to_print, full_pathname) + extension_char = 0.chr + printed_len = fnprint(to_print) + + if (@rl_filename_completion_desired && (@rl_visible_stats || @_rl_complete_mark_directories)) + + # If to_print != full_pathname, to_print is the basename of the + # path passed. In this case, we try to expand the directory + # name before checking for the stat character. + if (to_print != full_pathname) + + if full_pathname.nil? || full_pathname.length==0 + dn = '/' + else + dn = File.dirname(full_pathname) + end + s = File.expand_path(dn) + if (@rl_directory_completion_hook) + send(@rl_directory_completion_hook,s) + end + + slen = s.length + tlen = to_print.length + new_full_pathname = s.dup + if (s[-1,1] == '/' ) + slen-=1 + else + new_full_pathname[slen,1] = '/' + end + new_full_pathname[slen .. -1] = '/' + to_print + + if (@rl_visible_stats) + extension_char = stat_char(new_full_pathname) + else + if (path_isdir(new_full_pathname)) + extension_char = '/' + end + end + + new_full_pathname = nil + + else + + s = File.expand_path(full_pathname) + if (@rl_visible_stats) + extension_char = stat_char(s) + else + if (path_isdir(s)) + extension_char = '/' + end + end + end + s = nil + if (extension_char) + @rl_outstream.write(extension_char) + printed_len+=1 + end + end + + printed_len + end + + # The user must press "y" or "n". Non-zero return means "y" pressed. + def get_y_or_n(for_pager) + while(true) + + rl_setstate(RL_STATE_MOREINPUT) + c = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + + if (c == 'y' || c == 'Y' || c == ' ') + return (1) + end + if (c == 'n' || c == 'N' || c == RUBOUT) + return (0) + end + if (c == ABORT_CHAR) + _rl_abort_internal() + end + if (for_pager && (c == NEWLINE || c == RETURN)) + return (2) + end + if (for_pager && (c == 'q' || c == 'Q')) + return (0) + end + rl_ding() + end + end + + # Compute width of STRING when displayed on screen by print_filename + def fnwidth(string) + left = string.length + 1 + width = pos = 0 + while (string[pos] && string[pos,1] != 0.chr) + if (ctrl_char(string[0,1]) || string[0,1] == RUBOUT) + width += 2 + pos+=1 + else + case @encoding + when 'E' + wc = string[pos,left-pos].scan(/./me)[0] + bytes = wc.length + tempwidth = wc.length + when 'S' + wc = string[pos,left-pos].scan(/./ms)[0] + bytes = wc.length + tempwidth = wc.length + when 'U' + wc = string[pos,left-pos].scan(/./mu)[0] + bytes = wc.length + tempwidth = wc.unpack('U').first >= 0x1000 ? 2 : 1 + when 'X' + wc = string[pos,left-pos].force_encoding(@encoding_name)[0] + bytes = wc.bytesize + tempwidth = wc.ord >= 0x1000 ? 2 : 1 + else + wc = string[pos,left-pos].scan(/./m)[0] + bytes = wc.length + tempwidth = wc.length + end + clen = bytes + pos += clen + w = tempwidth + width += (w >= 0) ? w : 1 + end + end + width + end + + # Display MATCHES, a list of matching filenames in argv format. This + # handles the simple case -- a single match -- first. If there is more + # than one match, we compute the number of strings in the list and the + # length of the longest string, which will be needed by the display + # function. If the application wants to handle displaying the list of + # matches itself, it sets RL_COMPLETION_DISPLAY_MATCHES_HOOK to the + # address of a function, and we just call it. If we're handling the + # display ourselves, we just call rl_display_match_list. We also check + # that the list of matches doesn't exceed the user-settable threshold, + # and ask the user if he wants to see the list if there are more matches + # than RL_COMPLETION_QUERY_ITEMS. + def display_matches(matches) + # Move to the last visible line of a possibly-multiple-line command. + _rl_move_vert(@_rl_vis_botlin) + + # Handle simple case first. What if there is only one answer? + if matches[1].nil? + temp = printable_part(matches[0]) + rl_crlf() + print_filename(temp, matches[0]) + rl_crlf() + rl_forced_update_display() + @rl_display_fixed = true + return + end + + # There is more than one answer. Find out how many there are, + # and find the maximum printed length of a single entry. + max = 0 + i = 1 + while(matches[i]) + temp = printable_part(matches[i]) + len = fnwidth(temp) + + if (len > max) + max = len + end + i += 1 + end + len = i - 1 + + # If the caller has defined a display hook, then call that now. + if (@rl_completion_display_matches_hook) + send(@rl_completion_display_matches_hook,matches, len, max) + return + end + + # If there are many items, then ask the user if she really wants to + # see them all. + if (@rl_completion_query_items > 0 && len >= @rl_completion_query_items) + + rl_crlf() + @rl_outstream.write("Display all #{len} possibilities? (y or n)") + @rl_outstream.flush + if (get_y_or_n(false)==0) + rl_crlf() + + rl_forced_update_display() + @rl_display_fixed = true + + return + end + end + + rl_display_match_list(matches, len, max) + + rl_forced_update_display() + @rl_display_fixed = true + end + + # Complete the word at or before point. + # WHAT_TO_DO says what to do with the completion. + # `?' means list the possible completions. + # TAB means do standard completion. + # `*' means insert all of the possible completions. + # `!' means to do standard completion, and list all possible completions if + # there is more than one. + # `@' means to do standard completion, and list all possible completions if + # there is more than one and partial completion is not possible. + def rl_complete_internal(what_to_do) + rl_setstate(RL_STATE_COMPLETING) + set_completion_defaults(what_to_do) + + saved_line_buffer = @rl_line_buffer ? @rl_line_buffer.delete(0.chr) : nil + our_func = @rl_completion_entry_function ? + @rl_completion_entry_function : :rl_filename_completion_function + # We now look backwards for the start of a filename/variable word. + _end = @rl_point + found_quote = false + delimiter = 0.chr + quote_char = 0.chr + + if (@rl_point!=0) + # This (possibly) changes rl_point. If it returns a non-zero char, + # we know we have an open quote. + quote_char,found_quote,delimiter = _rl_find_completion_word() + end + + start = @rl_point + @rl_point = _end + + text = rl_copy_text(start, _end) + matches = gen_completion_matches(text, start, _end, our_func, found_quote, quote_char) + # nontrivial_lcd is set if the common prefix adds something to the word + # being completed. + nontrivial_lcd = !!(matches && text != matches[0]) + text = nil + if matches.nil? + rl_ding() + saved_line_buffer = nil + @completion_changed_buffer = false + rl_unsetstate(RL_STATE_COMPLETING) + return 0 + end + + # If we are matching filenames, the attempted completion function will + # have set rl_filename_completion_desired to a non-zero value. The basic + # rl_filename_completion_function does this. + i = @rl_filename_completion_desired + if (postprocess_matches(matches, i) == 0) + rl_ding() + saved_line_buffer = nil + @completion_changed_buffer = false + rl_unsetstate(RL_STATE_COMPLETING) + return 0 + end + + case (what_to_do) + + when TAB,'!','@' + # Insert the first match with proper quoting. + if (matches[0]) + insert_match(matches[0], start, matches[1] ? MULT_MATCH : SINGLE_MATCH, quote_char) + end + # If there are more matches, ring the bell to indicate. + # If we are in vi mode, Posix.2 says to not ring the bell. + # If the `show-all-if-ambiguous' variable is set, display + # all the matches immediately. Otherwise, if this was the + # only match, and we are hacking files, check the file to + # see if it was a directory. If so, and the `mark-directories' + # variable is set, add a '/' to the name. If not, and we + # are at the end of the line, then add a space. + if (matches[1]) + if (what_to_do == '!') + display_matches(matches) + elsif (what_to_do == '@') + if (!nontrivial_lcd) + display_matches(matches) + end + elsif (@rl_editing_mode != @vi_mode) + rl_ding() # There are other matches remaining. + end + else + append_to_match(matches[0], delimiter, quote_char, nontrivial_lcd) + end + when '*' + insert_all_matches(matches, start, quote_char) + when '?' + display_matches(matches) + else + $stderr.write("\r\nreadline: bad value #{what_to_do} for what_to_do in rl_complete\n") + rl_ding() + saved_line_buffer = nil + rl_unsetstate(RL_STATE_COMPLETING) + return 1 + end + + matches = nil + + # Check to see if the line has changed through all of this manipulation. + if (saved_line_buffer) + @completion_changed_buffer = @rl_line_buffer.delete(0.chr) != saved_line_buffer + saved_line_buffer = nil + end + + rl_unsetstate(RL_STATE_COMPLETING) + 0 + end + + # Complete the word at or before point. You have supplied the function + # that does the initial simple matching selection algorithm (see + # rl_completion_matches ()). The default is to do filename completion. + def rl_complete(ignore, invoking_key) + if (@rl_inhibit_completion) + return (_rl_insert_char(ignore, invoking_key)) + elsif (@rl_last_func == :rl_complete && !@completion_changed_buffer) + return (rl_complete_internal('?')) + elsif (@_rl_complete_show_all) + return (rl_complete_internal('!')) + elsif (@_rl_complete_show_unmodified) + return (rl_complete_internal('@')) + else + return (rl_complete_internal(TAB)) + end + end + + # Return the history entry which is logically at OFFSET in the history array. + # OFFSET is relative to history_base. + def history_get(offset) + local_index = offset - @history_base + return (local_index >= @history_length || local_index < 0 || @the_history.nil?) ? + nil : @the_history[local_index] + end + + def rl_replace_from_history(entry, flags) + # Can't call with `1' because rl_undo_list might point to an undo list + # from a history entry, just like we're setting up here. + rl_replace_line(entry.line, false) + @rl_undo_list = entry.data + @rl_point = @rl_end + @rl_mark = 0 + + if (@rl_editing_mode == @vi_mode) + @rl_point = 0 + @rl_mark = @rl_end + end + end + + # Remove history element WHICH from the history. The removed + # element is returned to you so you can free the line, data, + # and containing structure. + def remove_history(which) + if (which < 0 || which >= @history_length || @history_length == 0 || @the_history.nil?) + return nil + end + return_value = @the_history[which] + @the_history.delete_at(which) + @history_length-=1 + return_value + end + + def block_sigint() + return if @sigint_blocked + #@sigint_proc = Signal.trap("INT","IGNORE") + @sigint_blocked = true + end + + def release_sigint() + return if !@sigint_blocked + #Signal.trap("INT",@sigint_proc) + @sigint_blocked = false + end + + def save_tty_chars() + @_rl_last_tty_chars = @_rl_tty_chars + h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten] + h.each {|k,v| v.gsub!(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} + @_rl_tty_chars.t_erase = h['erase'] + @_rl_tty_chars.t_kill = h['kill'] + @_rl_tty_chars.t_intr = h['intr'] + @_rl_tty_chars.t_quit = h['quit'] + @_rl_tty_chars.t_start = h['start'] + @_rl_tty_chars.t_stop = h['stop'] + @_rl_tty_chars.t_eof = h['eof'] + @_rl_tty_chars.t_eol = "\n" + @_rl_tty_chars.t_eol2 = h['eol2'] + @_rl_tty_chars.t_susp = h['susp'] + @_rl_tty_chars.t_dsusp = h['dsusp'] + @_rl_tty_chars.t_reprint = h['rprnt'] + @_rl_tty_chars.t_flush = h['flush'] + @_rl_tty_chars.t_werase = h['werase'] + @_rl_tty_chars.t_lnext = h['lnext'] + @_rl_tty_chars.t_status = -1 + @otio = `stty -g` + end + + def _rl_bind_tty_special_chars(kmap) + kmap[@_rl_tty_chars.t_erase] = :rl_rubout + kmap[@_rl_tty_chars.t_kill] = :rl_unix_line_discard + kmap[@_rl_tty_chars.t_werase] = :rl_unix_word_rubout + kmap[@_rl_tty_chars.t_lnext] = :rl_quoted_insert + end + + def prepare_terminal_settings(meta_flag) + @readline_echoing_p = (`stty -a`.scan(/-*echo\b/).first == 'echo') + + # First, the basic settings to put us into character-at-a-time, no-echo + # input mode. + setting = " -echo -icrnl cbreak" + + # If this terminal doesn't care how the 8th bit is used, then we can + # use it for the meta-key. If only one of even or odd parity is + # specified, then the terminal is using parity, and we cannot. + if (`stty -a`.scan(/-parenb\b/).first == '-parenb') + setting << " pass8" + end + + setting << " -ixoff" + + rl_bind_key(@_rl_tty_chars.t_start, :rl_restart_output) + @_rl_eof_char = @_rl_tty_chars.t_eof + + #setting << " -isig" + + `stty #{setting}` + end + + def _rl_control_keypad(on) + if on && @_rl_term_ks + @_rl_out_stream.write(@_rl_term_ks) + elsif !on && @_rl_term_ke + @_rl_out_stream.write(@_rl_term_ke) + end + end + + # Rebind all of the tty special chars that readline worries about back + # to self-insert. Call this before saving the current terminal special + # chars with save_tty_chars(). This only works on POSIX termios or termio + # systems. + def rl_tty_unset_default_bindings(kmap) + # Don't bother before we've saved the tty special chars at least once. + return if (!rl_isstate(RL_STATE_TTYCSAVED)) + + kmap[@_rl_tty_chars.t_erase] = :rl_insert + kmap[@_rl_tty_chars.t_kill] = :rl_insert + kmap[@_rl_tty_chars.t_lnext] = :rl_insert + kmap[@_rl_tty_chars.t_werase] = :rl_insert + end + + def rl_prep_terminal(meta_flag) + if no_terminal? + @readline_echoing_p = true + return + end + + return if (@terminal_prepped) + + # Try to keep this function from being INTerrupted. + block_sigint() + + if (@_rl_bind_stty_chars) + # If editing in vi mode, make sure we restore the bindings in the + # insertion keymap no matter what keymap we ended up in. + if (@rl_editing_mode == @vi_mode) + rl_tty_unset_default_bindings(@vi_insertion_keymap) + else + rl_tty_unset_default_bindings(@_rl_keymap) + end + end + + save_tty_chars() + + rl_setstate(RL_STATE_TTYCSAVED) + if (@_rl_bind_stty_chars) + + # If editing in vi mode, make sure we set the bindings in the + # insertion keymap no matter what keymap we ended up in. + if (@rl_editing_mode == @vi_mode) + _rl_bind_tty_special_chars(@vi_insertion_keymap) + else + _rl_bind_tty_special_chars(@_rl_keymap) + end + end + + prepare_terminal_settings(meta_flag) + + if (@_rl_enable_keypad) + _rl_control_keypad(true) + end + @rl_outstream.flush + @terminal_prepped = true + rl_setstate(RL_STATE_TERMPREPPED) + + release_sigint() + end + + # Restore the terminal's normal settings and modes. + def rl_deprep_terminal() + return if ENV["TERM"].nil? + return if (!@terminal_prepped) + + # Try to keep this function from being interrupted. + block_sigint() + + if (@_rl_enable_keypad) + _rl_control_keypad(false) + end + + @rl_outstream.flush + + # restore terminal setting + `stty #{@otio}` + + @terminal_prepped = false + rl_unsetstate(RL_STATE_TERMPREPPED) + + release_sigint() + end + + # Set the mark at POSITION. + def _rl_set_mark_at_pos(position) + return -1 if (position > @rl_end) + @rl_mark = position + 0 + end + + # A bindable command to set the mark. + def rl_set_mark(count, key) + _rl_set_mark_at_pos(@rl_explicit_arg ? count : @rl_point) + end + + # Kill from here to the end of the line. If DIRECTION is negative, kill + # back to the line start instead. + def rl_kill_line (direction, ignore) + if (direction < 0) + return (rl_backward_kill_line(1, ignore)) + else + orig_point = @rl_point + rl_end_of_line(1, ignore) + if (orig_point != @rl_point) + rl_kill_text(orig_point, @rl_point) + end + @rl_point = orig_point + if (@rl_editing_mode == @emacs_mode) + @rl_mark = @rl_point + end + end + 0 + end + + # Kill backwards to the start of the line. If DIRECTION is negative, kill + # forwards to the line end instead. + def rl_backward_kill_line(direction, ignore) + if (direction < 0) + return (rl_kill_line(1, ignore)) + else + if (@rl_point==0) + rl_ding() + else + orig_point = @rl_point + rl_beg_of_line(1, ignore) + if (@rl_point != orig_point) + rl_kill_text(orig_point, @rl_point) + end + if (@rl_editing_mode == @emacs_mode) + @rl_mark = @rl_point + end + end + end + 0 + end + + # Kill the whole line, no matter where point is. + def rl_kill_full_line(count, ignore) + rl_begin_undo_group() + @rl_point = 0 + rl_kill_text(@rl_point, @rl_end) + @rl_mark = 0 + rl_end_undo_group() + 0 + end + + # Search backwards through the history looking for a string which is typed + # interactively. Start with the current line. + def rl_reverse_search_history(sign, key) + rl_search_history(-sign, key) + end + + # Search forwards through the history looking for a string which is typed + # interactively. Start with the current line. + def rl_forward_search_history(sign, key) + rl_search_history(sign, key) + end + + # Search through the history looking for an interactively typed string. + # This is analogous to i-search. We start the search in the current line. + # DIRECTION is which direction to search; >= 0 means forward, < 0 means + # backwards. + def rl_search_history(direction, invoking_key) + rl_setstate(RL_STATE_ISEARCH) + cxt = _rl_isearch_init(direction) + + rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, -1) + + # If we are using the callback interface, all we do is set up here and + # return. The key is that we leave RL_STATE_ISEARCH set. + if (rl_isstate(RL_STATE_CALLBACK)) + return (0) + end + + r = -1 + while(true) + c = _rl_search_getchar(cxt) + # We might want to handle EOF here (c == 0) + r = _rl_isearch_dispatch(cxt, cxt.lastc) + break if (r <= 0) + end + + # The searching is over. The user may have found the string that she + # was looking for, or else she may have exited a failing search. If + # LINE_INDEX is -1, then that shows that the string searched for was + # not found. We use this to determine where to place rl_point. + _rl_isearch_cleanup(cxt, r) + end + + def _rl_scxt_alloc(type, flags) + cxt = Struct.new(:type,:sflags,:search_string,:search_string_index,:search_string_size,:lines,:allocated_line, + :hlen,:hindex,:save_point,:save_mark,:save_line,:last_found_line,:prev_line_found,:save_undo_list,:history_pos, + :direction,:lastc,:sline,:sline_len,:sline_index,:search_terminators,:mb).new + + cxt.type = type + cxt.sflags = flags + + cxt.search_string = nil + cxt.search_string_size = cxt.search_string_index = 0 + + cxt.lines = nil + cxt.allocated_line = nil + cxt.hlen = cxt.hindex = 0 + + cxt.save_point = @rl_point + cxt.save_mark = @rl_mark + cxt.save_line = where_history() + cxt.last_found_line = cxt.save_line + cxt.prev_line_found = nil + + cxt.save_undo_list = nil + + cxt.history_pos = 0 + cxt.direction = 0 + + cxt.lastc = 0 + + cxt.sline = nil + cxt.sline_len = cxt.sline_index = 0 + + cxt.search_terminators = nil + + cxt + end + + def history_list() + @the_history + end + + def _rl_isearch_init(direction) + cxt = _rl_scxt_alloc(RL_SEARCH_ISEARCH, 0) + if (direction < 0) + cxt.sflags |= SF_REVERSE + end + + cxt.search_terminators = @_rl_isearch_terminators ? @_rl_isearch_terminators : + @default_isearch_terminators + + # Create an arrary of pointers to the lines that we want to search. + hlist = history_list() + rl_maybe_replace_line() + i = 0 + if (hlist) + i += 1 while(hlist[i]) + end + + # Allocate space for this many lines, +1 for the current input line, + # and remember those lines. + cxt.hlen = i + cxt.lines = [] + for i in 0 ... cxt.hlen + cxt.lines[i] = hlist[i].line + end + + if (@_rl_saved_line_for_history) + cxt.lines[i] = @_rl_saved_line_for_history.line.dup + else + # Keep track of this so we can free it. + cxt.allocated_line = @rl_line_buffer.dup + cxt.lines << cxt.allocated_line + end + + cxt.hlen+=1 + + # The line where we start the search. + cxt.history_pos = cxt.save_line + + rl_save_prompt() + + # Initialize search parameters. + cxt.search_string_size = 128 + cxt.search_string_index = 0 + cxt.search_string = "" + + # Normalize DIRECTION into 1 or -1. + cxt.direction = (direction >= 0) ? 1 : -1 + + cxt.sline = @rl_line_buffer + cxt.sline_len = cxt.sline.delete(0.chr).length + cxt.sline_index = @rl_point + + @_rl_iscxt = cxt # save globally + cxt + end + + def rl_save_prompt() + @saved_local_prompt = @local_prompt + @saved_local_prefix = @local_prompt_prefix + @saved_prefix_length = @prompt_prefix_length + @saved_local_length = @local_prompt_len + @saved_last_invisible = @prompt_last_invisible + @saved_visible_length = @prompt_visible_length + @saved_invis_chars_first_line = @prompt_invis_chars_first_line + @saved_physical_chars = @prompt_physical_chars + + @local_prompt = @local_prompt_prefix = nil + @local_prompt_len = 0 + @prompt_last_invisible = @prompt_visible_length = @prompt_prefix_length = 0 + @prompt_invis_chars_first_line = @prompt_physical_chars = 0 + end + + def rl_restore_prompt() + @local_prompt = nil + @local_prompt_prefix = nil + + @local_prompt = @saved_local_prompt + @local_prompt_prefix = @saved_local_prefix + @local_prompt_len = @saved_local_length + @prompt_prefix_length = @saved_prefix_length + @prompt_last_invisible = @saved_last_invisible + @prompt_visible_length = @saved_visible_length + @prompt_invis_chars_first_line = @saved_invis_chars_first_line + @prompt_physical_chars = @saved_physical_chars + + # can test saved_local_prompt to see if prompt info has been saved. + @saved_local_prompt = @saved_local_prefix = nil + @saved_local_length = 0 + @saved_last_invisible = @saved_visible_length = @saved_prefix_length = 0 + @saved_invis_chars_first_line = @saved_physical_chars = 0 + end + + def rl_message(msg_buf) + @rl_display_prompt = msg_buf + if @saved_local_prompt.nil? + rl_save_prompt() + @msg_saved_prompt = true + end + @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = + expand_prompt(msg_buf) + @local_prompt_prefix = nil + @local_prompt_len = @local_prompt ? @local_prompt.length : 0 + send(@rl_redisplay_function) + 0 + end + + # Display the current state of the search in the echo-area. + # SEARCH_STRING contains the string that is being searched for, + # DIRECTION is zero for forward, or non-zero for reverse, + # WHERE is the history list number of the current line. If it is + # -1, then this line is the starting one. + def rl_display_search(search_string, reverse_p, where) + message = '(' + if (reverse_p) + message << "reverse-" + end + message << "i-search)`" + + if (search_string) + message << search_string + end + message << "': " + + rl_message(message) + message = nil + send(@rl_redisplay_function) + end + + # Transpose the characters at point. If point is at the end of the line, + # then transpose the characters before point. + def rl_transpose_chars(count, key) + return 0 if (count == 0) + + if (@rl_point==0 || @rl_end < 2) + rl_ding() + return -1 + end + + rl_begin_undo_group() + + if (@rl_point == @rl_end) + if !@rl_byte_oriented + @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) + else + @rl_point -= 1 + end + count = 1 + end + + prev_point = @rl_point + if !@rl_byte_oriented + @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) + else + @rl_point -= 1 + end + + char_length = prev_point - @rl_point + dummy = @rl_line_buffer[@rl_point,char_length] + + rl_delete_text(@rl_point, @rl_point + char_length) + + @rl_point += count + _rl_fix_point(0) + rl_insert_text(dummy) + rl_end_undo_group() + dummy = nil + 0 + end + + # Here is C-u doing what Unix does. You don't *have* to use these + # key-bindings. We have a choice of killing the entire line, or + # killing from where we are to the start of the line. We choose the + # latter, because if you are a Unix weenie, then you haven't backspaced + # into the line at all, and if you aren't, then you know what you are + # doing. + def rl_unix_line_discard(count, key) + if (@rl_point == 0) + rl_ding() + else + rl_kill_text(@rl_point, 0) + @rl_point = 0 + if (@rl_editing_mode == @emacs_mode) + @rl_mark = @rl_point + end + end + 0 + end + + # Yank back the last killed text. This ignores arguments. + def rl_yank(count, ignore) + if @rl_kill_ring.nil? + _rl_abort_internal() + return -1 + end + _rl_set_mark_at_pos(@rl_point) + rl_insert_text(@rl_kill_ring[@rl_kill_index]) + 0 + end + + # If the last command was yank, or yank_pop, and the text just + # before point is identical to the current kill item, then + # delete that text from the line, rotate the index down, and + # yank back some other text. + def rl_yank_pop(count, key) + if (((@rl_last_func != :rl_yank_pop) && (@rl_last_func != :rl_yank)) || + @rl_kill_ring.nil?) + _rl_abort_internal() + return -1 + end + + l = @rl_kill_ring[@rl_kill_index].length + n = @rl_point - l + if (n >= 0 && @rl_line_buffer[n,l] == @rl_kill_ring[@rl_kill_index][0,l]) + rl_delete_text(n, @rl_point) + @rl_point = n + @rl_kill_index-=1 + if (@rl_kill_index < 0) + @rl_kill_index = @rl_kill_ring_length - 1 + end + rl_yank(1, 0) + return 0 + else + _rl_abort_internal() + return -1 + end + end + + # Yank the COUNTh argument from the previous history line, skipping + # HISTORY_SKIP lines before looking for the `previous line'. + def rl_yank_nth_arg_internal(count, ignore, history_skip) + pos = where_history() + if (history_skip>0) + history_skip.times { entry = previous_history() } + end + entry = previous_history() + history_set_pos(pos) + if entry.nil? + rl_ding() + return -1 + end + + arg = history_arg_extract(count, count, entry.line) + if (arg.nil? || arg=='') + rl_ding() + arg = nil + return -1 + end + + rl_begin_undo_group() + + _rl_set_mark_at_pos(@rl_point) + + # Vi mode always inserts a space before yanking the argument, and it + # inserts it right *after* rl_point. + if (@rl_editing_mode == @vi_mode) + rl_vi_append_mode(1, ignore) + rl_insert_text(" ") + end + + rl_insert_text(arg) + arg = nil + rl_end_undo_group() + return 0 + end + + # Yank the COUNTth argument from the previous history line. + def rl_yank_nth_arg(count, ignore) + rl_yank_nth_arg_internal(count, ignore, 0) + end + + # Yank the last argument from the previous history line. This `knows' + # how rl_yank_nth_arg treats a count of `$'. With an argument, this + # behaves the same as rl_yank_nth_arg. + @history_skip = 0 + @explicit_arg_p = false + @count_passed = 1 + @direction = 1 + @undo_needed = false + + def rl_yank_last_arg(count, key) + if (@rl_last_func != :rl_yank_last_arg) + @history_skip = 0 + @explicit_arg_p = @rl_explicit_arg + @count_passed = count + @direction = 1 + else + if (@undo_needed) + rl_do_undo() + end + if (count < 1) + @direction = -@direction + end + @history_skip += @direction + if (@history_skip < 0) + @history_skip = 0 + end + end + + if (@explicit_arg_p) + retval = rl_yank_nth_arg_internal(@count_passed, key, @history_skip) + else + retval = rl_yank_nth_arg_internal('$', key, @history_skip) + end + @undo_needed = retval == 0 + retval + end + + def _rl_char_search_internal(count, dir, smbchar, len) + pos = @rl_point + inc = (dir < 0) ? -1 : 1 + while (count!=0) + if ((dir < 0 && pos <= 0) || (dir > 0 && pos >= @rl_end)) + rl_ding() + return -1 + end + pos = (inc > 0) ? _rl_find_next_mbchar(@rl_line_buffer, pos, 1, MB_FIND_ANY) : + _rl_find_prev_mbchar(@rl_line_buffer, pos, MB_FIND_ANY) + begin + if (_rl_is_mbchar_matched(@rl_line_buffer, pos, @rl_end, smbchar, len)!=0) + count-=1 + if (dir < 0) + @rl_point = (dir == BTO) ? pos+1 : pos + else + @rl_point = (dir == FTO) ? pos-1 : pos + end + break + end + prepos = pos + end while ((dir < 0) ? (pos = _rl_find_prev_mbchar(@rl_line_buffer, pos, MB_FIND_ANY)) != prepos : + (pos = _rl_find_next_mbchar(@rl_line_buffer, pos, 1, MB_FIND_ANY)) != prepos) + end + 0 + end + + def _rl_char_search(count, fdir, bdir) + mbchar = '' + mb_len = _rl_read_mbchar(mbchar, MB_LEN_MAX) + + if (count < 0) + return (_rl_char_search_internal(-count, bdir, mbchar, mb_len)) + else + return (_rl_char_search_internal(count, fdir, mbchar, mb_len)) + end + end + + def rl_char_search(count, key) + _rl_char_search(count, FFIND, BFIND) + end + + # Undo the next thing in the list. Return 0 if there + # is nothing to undo, or non-zero if there was. + def trans(i) + ((i) == -1 ? @rl_point : ((i) == -2 ? @rl_end : (i))) + end + + def rl_do_undo() + start = _end = waiting_for_begin = 0 + begin + return 0 if @rl_undo_list.nil? + + @_rl_doing_an_undo = true + rl_setstate(RL_STATE_UNDOING) + + # To better support vi-mode, a start or end value of -1 means + # rl_point, and a value of -2 means rl_end. + if (@rl_undo_list.what == UNDO_DELETE || @rl_undo_list.what == UNDO_INSERT) + start = trans(@rl_undo_list.start) + _end = trans(@rl_undo_list.end) + end + + case (@rl_undo_list.what) + # Undoing deletes means inserting some text. + when UNDO_DELETE + @rl_point = start + rl_insert_text(@rl_undo_list.text) + @rl_undo_list.text = nil + + # Undoing inserts means deleting some text. + when UNDO_INSERT + rl_delete_text(start, _end) + @rl_point = start + # Undoing an END means undoing everything 'til we get to a BEGIN. + when UNDO_END + waiting_for_begin+=1 + # Undoing a BEGIN means that we are done with this group. + when UNDO_BEGIN + if (waiting_for_begin!=0) + waiting_for_begin-=1 + else + rl_ding() + end + end + + @_rl_doing_an_undo = false + rl_unsetstate(RL_STATE_UNDOING) + + release = @rl_undo_list + @rl_undo_list = @rl_undo_list.next + replace_history_data(-1, release, @rl_undo_list) + release = nil + end while (waiting_for_begin!=0) + + 1 + end + + + + # Do some undoing of things that were done. + def rl_undo_command(count, key) + if (count < 0) + return 0 # Nothing to do. + end + while (count>0) + if (rl_do_undo()) + count-=1 + else + rl_ding() + break + end + end + 0 + end + + # Delete the word at point, saving the text in the kill ring. + def rl_kill_word(count, key) + if (count < 0) + return (rl_backward_kill_word(-count, key)) + else + orig_point = @rl_point + rl_forward_word(count, key) + + if (@rl_point != orig_point) + rl_kill_text(orig_point, @rl_point) + end + + @rl_point = orig_point + if (@rl_editing_mode == @emacs_mode) + rl_mark = @rl_point + end + end + 0 + end + + # Rubout the word before point, placing it on the kill ring. + def rl_backward_kill_word(count, ignore) + if (count < 0) + return (rl_kill_word(-count, ignore)) + else + orig_point = @rl_point + rl_backward_word(count, ignore) + if (@rl_point != orig_point) + rl_kill_text(orig_point, @rl_point) + end + if (@rl_editing_mode == @emacs_mode) + @rl_mark = @rl_point + end + end + 0 + end + + # Revert the current line to its previous state. + def rl_revert_line(count, key) + if @rl_undo_list.nil? + rl_ding() + else + while (@rl_undo_list) + rl_do_undo() + end + if (@rl_editing_mode == @vi_mode) + @rl_point = @rl_mark = 0 # rl_end should be set correctly + end + end + 0 + end + + def rl_backward_char_search (count, key) + _rl_char_search(count, BFIND, FFIND) + end + + def rl_insert_completions(ignore, invoking_key) + rl_complete_internal('*') + end + + def _rl_arg_init() + rl_save_prompt() + @_rl_argcxt = 0 + rl_setstate(RL_STATE_NUMERICARG) + end + + def _rl_arg_getchar() + rl_message("(arg: #{@rl_arg_sign * @rl_numeric_arg}) ") + rl_setstate(RL_STATE_MOREINPUT) + c = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + c + end + + # Process C as part of the current numeric argument. Return -1 if the + # argument should be aborted, 0 if we should not read any more chars, and + # 1 if we should continue to read chars. + def _rl_arg_dispatch(cxt, c) + key = c + + # If we see a key bound to `universal-argument' after seeing digits, + # it ends the argument but is otherwise ignored. + if (@_rl_keymap[c] == :rl_universal_argument) + if ((cxt & NUM_SAWDIGITS) == 0) + @rl_numeric_arg *= 4 + return 1 + elsif (rl_isstate(RL_STATE_CALLBACK)) + @_rl_argcxt |= NUM_READONE + return 0 # XXX + else + rl_setstate(RL_STATE_MOREINPUT) + key = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + rl_restore_prompt() + rl_clear_message() + rl_unsetstate(RL_STATE_NUMERICARG) + return (_rl_dispatch(key, @_rl_keymap)) + end + end + + #c = (c[0].ord & ~0x80).chr + r = c[1,1] + if (r>='0' && r<='9') + r = r.to_i + @rl_numeric_arg = @rl_explicit_arg ? (@rl_numeric_arg * 10) + r : r + @rl_explicit_arg = 1 + @_rl_argcxt |= NUM_SAWDIGITS + elsif (c == '-' && !@rl_explicit_arg) + @rl_numeric_arg = 1 + @_rl_argcxt |= NUM_SAWMINUS + @rl_arg_sign = -1 + else + # Make M-- command equivalent to M--1 command. + if ((@_rl_argcxt & NUM_SAWMINUS)!=0 && @rl_numeric_arg == 1 && !@rl_explicit_arg) + @rl_explicit_arg = 1 + end + rl_restore_prompt() + rl_clear_message() + rl_unsetstate(RL_STATE_NUMERICARG) + + r = _rl_dispatch(key, @_rl_keymap) + if (rl_isstate(RL_STATE_CALLBACK)) + # At worst, this will cause an extra redisplay. Otherwise, + # we have to wait until the next character comes in. + if (!@rl_done) + send(@rl_redisplay_function) + end + r = 0 + end + return r + end + 1 + end + + def _rl_arg_overflow() + if (@rl_numeric_arg > 1000000) + @_rl_argcxt = 0 + @rl_explicit_arg = @rl_numeric_arg = 0 + rl_ding() + rl_restore_prompt() + rl_clear_message() + rl_unsetstate(RL_STATE_NUMERICARG) + return 1 + end + 0 + end + + # Handle C-u style numeric args, as well as M--, and M-digits. + def rl_digit_loop() + while (true) + return 1 if _rl_arg_overflow()!=0 + c = _rl_arg_getchar() + if (c >= "\xFE") + _rl_abort_internal() + return -1 + end + r = _rl_arg_dispatch(@_rl_argcxt, c) + break if (r <= 0 || !rl_isstate(RL_STATE_NUMERICARG)) + end + + return r + end + + # Start a numeric argument with initial value KEY + def rl_digit_argument(ignore, key) + _rl_arg_init() + if (rl_isstate(RL_STATE_CALLBACK)) + _rl_arg_dispatch(@_rl_argcxt, key) + rl_message("(arg: #{@rl_arg_sign * @rl_numeric_arg}) ") + return 0 + else + rl_execute_next(key) + return (rl_digit_loop()) + end + end + + # Make C be the next command to be executed. + def rl_execute_next(c) + @rl_pending_input = c + rl_setstate(RL_STATE_INPUTPENDING) + 0 + end + + # Meta-< goes to the start of the history. + def rl_beginning_of_history(count, key) + rl_get_previous_history(1 + where_history(), key) + end + + # Meta-> goes to the end of the history. (The current line). + def rl_end_of_history(count, key) + rl_maybe_replace_line() + using_history() + rl_maybe_unsave_line() + 0 + end + + # Uppercase the word at point. + def rl_upcase_word(count, key) + rl_change_case(count, UpCase) + end + + # Lowercase the word at point. + def rl_downcase_word(count, key) + rl_change_case(count, DownCase) + end + + # Upcase the first letter, downcase the rest. + def rl_capitalize_word(count, key) + rl_change_case(count, CapCase) + end + + # Save an undo entry for the text from START to END. + def rl_modifying(start, _end) + if (start > _end) + start,_end = _end,start + end + + if (start != _end) + temp = rl_copy_text(start, _end) + rl_begin_undo_group() + rl_add_undo(UNDO_DELETE, start, _end, temp) + rl_add_undo(UNDO_INSERT, start, _end, nil) + rl_end_undo_group() + end + 0 + end + + # The meaty function. + # Change the case of COUNT words, performing OP on them. + # OP is one of UpCase, DownCase, or CapCase. + # If a negative argument is given, leave point where it started, + # otherwise, leave it where it moves to. + def rl_change_case(count, op) + start = @rl_point + rl_forward_word(count, 0) + _end = @rl_point + + if (op != UpCase && op != DownCase && op != CapCase) + rl_ding() + return -1 + end + + if (count < 0) + start,_end = _end,start + end + + # We are going to modify some text, so let's prepare to undo it. + rl_modifying(start, _end) + + inword = false + while (start < _end) + c = _rl_char_value(@rl_line_buffer, start) + # This assumes that the upper and lower case versions are the same width. + if !@rl_byte_oriented + _next = _rl_find_next_mbchar(@rl_line_buffer, start, 1, MB_FIND_NONZERO) + else + _next = start + 1 + end + + if (!_rl_walphabetic(c)) + inword = false + start = _next + next + end + + if (op == CapCase) + nop = inword ? DownCase : UpCase + inword = true + else + nop = op + end + if (isascii(c)) + nc = (nop == UpCase) ? c.upcase : c.downcase + @rl_line_buffer[start] = nc + end + + start = _next + end + + @rl_point = _end + 0 + end + + def isascii(c) + int_val = c[0].to_i # 1.8 + 1.9 compat. + return (int_val < 128 && int_val > 0) + end + + # Search non-interactively through the history list. DIR < 0 means to + # search backwards through the history of previous commands; otherwise + # the search is for commands subsequent to the current position in the + # history list. PCHAR is the character to use for prompting when reading + # the search string; if not specified (0), it defaults to `:'. + def noninc_search(dir, pchar) + cxt = _rl_nsearch_init(dir, pchar) + if (rl_isstate(RL_STATE_CALLBACK)) + return (0) + end + # Read the search string. + r = 0 + while (true) + c = _rl_search_getchar(cxt) + if (c == 0.chr) + break + end + r = _rl_nsearch_dispatch(cxt, c) + if (r < 0) + return 1 + elsif (r == 0) + break + end + end + + r = _rl_nsearch_dosearch(cxt) + (r >= 0) ? _rl_nsearch_cleanup(cxt, r) : (r != 1) + end + + # Search forward through the history list for a string. If the vi-mode + # code calls this, KEY will be `?'. + def rl_noninc_forward_search(count, key) + noninc_search(1, (key == '?') ? '?' : nil) + end + + # Reverse search the history list for a string. If the vi-mode code + # calls this, KEY will be `/'. + def rl_noninc_reverse_search(count, key) + noninc_search(-1, (key == '/') ? '/' : nil) + end + + # Make the data from the history entry ENTRY be the contents of the + # current line. This doesn't do anything with rl_point; the caller + # must set it. + def make_history_line_current(entry) + _rl_replace_text(entry.line, 0, @rl_end) + _rl_fix_point(1) + if (@rl_editing_mode == @vi_mode) + # POSIX.2 says that the `U' command doesn't affect the copy of any + # command lines to the edit line. We're going to implement that by + # making the undo list start after the matching line is copied to the + # current editing buffer. + rl_free_undo_list() + end + if (@_rl_saved_line_for_history) + @_rl_saved_line_for_history = nil + end + end + + # Make the current history item be the one at POS, an absolute index. + # Returns zero if POS is out of range, else non-zero. + def history_set_pos(pos) + if (pos > @history_length || pos < 0 || @the_history.nil?) + return (0) + end + @history_offset = pos + 1 + end + + # Do an anchored search for string through the history in DIRECTION. + def history_search_prefix (string, direction) + history_search_internal(string, direction, ANCHORED_SEARCH) + end + + # Search for STRING in the history list. DIR is < 0 for searching + # backwards. POS is an absolute index into the history list at + # which point to begin searching. + def history_search_pos(string, dir, pos) + old = where_history() + history_set_pos(pos) + if (history_search(string, dir) == -1) + history_set_pos(old) + return (-1) + end + ret = where_history() + history_set_pos(old) + ret + end + + # Search the history list for STRING starting at absolute history position + # POS. If STRING begins with `^', the search must match STRING at the + # beginning of a history line, otherwise a full substring match is performed + # for STRING. DIR < 0 means to search backwards through the history list, + # DIR >= 0 means to search forward. + def noninc_search_from_pos(string, pos, dir) + return 1 if (pos < 0) + + old = where_history() + return -1 if (history_set_pos(pos) == 0) + + rl_setstate(RL_STATE_SEARCH) + if (string[0,1] == '^') + ret = history_search_prefix(string + 1, dir) + else + ret = history_search(string, dir) + end + rl_unsetstate(RL_STATE_SEARCH) + + if (ret != -1) + ret = where_history() + end + history_set_pos(old) + ret + end + + # Search for a line in the history containing STRING. If DIR is < 0, the + # search is backwards through previous entries, else through subsequent + # entries. Returns 1 if the search was successful, 0 otherwise. + def noninc_dosearch(string, dir) + if (string.nil? || string == '' || @noninc_history_pos < 0) + rl_ding() + return 0 + end + + pos = noninc_search_from_pos(string, @noninc_history_pos + dir, dir) + if (pos == -1) + # Search failed, current history position unchanged. + rl_maybe_unsave_line() + rl_clear_message() + @rl_point = 0 + rl_ding() + return 0 + end + + @noninc_history_pos = pos + + oldpos = where_history() + history_set_pos(@noninc_history_pos) + entry = current_history() + if (@rl_editing_mode != @vi_mode) + history_set_pos(oldpos) + end + make_history_line_current(entry) + @rl_point = 0 + @rl_mark = @rl_end + rl_clear_message() + 1 + end + + def _rl_make_prompt_for_search(pchar) + rl_save_prompt() + + # We've saved the prompt, and can do anything with the various prompt + # strings we need before they're restored. We want the unexpanded + # portion of the prompt string after any final newline. + _p = @rl_prompt ? @rl_prompt.rindex("\n") : nil + if _p.nil? + len = (@rl_prompt && @rl_prompt.length>0 ) ? @rl_prompt.length : 0 + if (len>0) + pmt = @rl_prompt.dup + else + pmt = '' + end + pmt << pchar + else + _p+=1 + pmt = @rl_prompt[_p..-1] + pmt << pchar + end + + # will be overwritten by expand_prompt, called from rl_message + @prompt_physical_chars = @saved_physical_chars + 1 + pmt + end + + def _rl_nsearch_init(dir, pchar) + cxt = _rl_scxt_alloc(RL_SEARCH_NSEARCH, 0) + if (dir < 0) + cxt.sflags |= SF_REVERSE # not strictly needed + end + cxt.direction = dir + cxt.history_pos = cxt.save_line + rl_maybe_save_line() + # Clear the undo list, since reading the search string should create its + # own undo list, and the whole list will end up being freed when we + # finish reading the search string. + @rl_undo_list = nil + + # Use the line buffer to read the search string. + @rl_line_buffer[0,1] = 0.chr + @rl_end = @rl_point = 0 + + _p = _rl_make_prompt_for_search(pchar ? pchar : ':') + rl_message(_p) + _p = nil + + rl_setstate(RL_STATE_NSEARCH) + @_rl_nscxt = cxt + cxt + end + + def _rl_nsearch_cleanup(cxt, r) + cxt = nil + @_rl_nscxt = nil + rl_unsetstate(RL_STATE_NSEARCH) + r != 1 + end + + def _rl_nsearch_abort(cxt) + rl_maybe_unsave_line() + rl_clear_message() + @rl_point = cxt.save_point + @rl_mark = cxt.save_mark + rl_restore_prompt() + rl_unsetstate(RL_STATE_NSEARCH) + end + + # Process just-read character C according to search context CXT. Return -1 + # if the caller should abort the search, 0 if we should break out of the + # loop, and 1 if we should continue to read characters. + def _rl_nsearch_dispatch(cxt, c) + case (c) + when "\C-W" + rl_unix_word_rubout(1, c) + when "\C-W" + rl_unix_line_discard(1, c) + when RETURN,NEWLINE + return 0 + when "\C-H",RUBOUT + if (@rl_point == 0) + _rl_nsearch_abort(cxt) + return -1 + end + _rl_rubout_char(1, c) + when "\C-C","\C-G" + rl_ding() + _rl_nsearch_abort(cxt) + return -1 + else + if !@rl_byte_oriented + rl_insert_text(cxt.mb) + else + _rl_insert_char(1, c) + end + end + + send(@rl_redisplay_function) + 1 + end + + # Perform one search according to CXT, using NONINC_SEARCH_STRING. Return + # -1 if the search should be aborted, any other value means to clean up + # using _rl_nsearch_cleanup (). Returns 1 if the search was successful, + # 0 otherwise. + def _rl_nsearch_dosearch(cxt) + @rl_mark = cxt.save_mark + + # If rl_point == 0, we want to re-use the previous search string and + # start from the saved history position. If there's no previous search + # string, punt. + if (@rl_point == 0) + if @noninc_search_string.nil? + rl_ding() + rl_restore_prompt() + rl_unsetstate(RL_STATE_NSEARCH) + return -1 + end + else + # We want to start the search from the current history position. + @noninc_history_pos = cxt.save_line + @noninc_search_string = @rl_line_buffer.dup + + # If we don't want the subsequent undo list generated by the search + #matching a history line to include the contents of the search string, + #we need to clear rl_line_buffer here. For now, we just clear the + #undo list generated by reading the search string. (If the search + #fails, the old undo list will be restored by rl_maybe_unsave_line.) + rl_free_undo_list() + end + + rl_restore_prompt() + noninc_dosearch(@noninc_search_string, cxt.direction) + end + + # Transpose the words at point. If point is at the end of the line, + # transpose the two words before point. + def rl_transpose_words(count, key) + orig_point = @rl_point + + return if (count==0) + + # Find the two words. + rl_forward_word(count, key) + w2_end = @rl_point + rl_backward_word(1, key) + w2_beg = @rl_point + rl_backward_word(count, key) + w1_beg = @rl_point + rl_forward_word(1, key) + w1_end = @rl_point + + # Do some check to make sure that there really are two words. + if ((w1_beg == w2_beg) || (w2_beg < w1_end)) + rl_ding() + @rl_point = orig_point + return -1 + end + + # Get the text of the words. + word1 = rl_copy_text(w1_beg, w1_end) + word2 = rl_copy_text(w2_beg, w2_end) + + # We are about to do many insertions and deletions. Remember them + # as one operation. + rl_begin_undo_group() + + # Do the stuff at word2 first, so that we don't have to worry + # about word1 moving. + @rl_point = w2_beg + rl_delete_text(w2_beg, w2_end) + rl_insert_text(word1) + + @rl_point = w1_beg + rl_delete_text(w1_beg, w1_end) + rl_insert_text(word2) + + # This is exactly correct since the text before this point has not + # changed in length. + @rl_point = w2_end + + # I think that does it. + rl_end_undo_group() + word1 = nil + word2 = nil + + 0 + end + + # Re-read the current keybindings file. + def rl_re_read_init_file(count, ignore) + r = rl_read_init_file(nil) + rl_set_keymap_from_edit_mode() + r + end + + # Exchange the position of mark and point. + def rl_exchange_point_and_mark(count, key) + if (@rl_mark > @rl_end) + @rl_mark = -1 + end + if (@rl_mark == -1) + rl_ding() + return -1 + else + @rl_point, @rl_mark = @rl_mark, @rl_point + end + 0 + end + + # A convenience function for displaying a list of strings in + # columnar format on readline's output stream. MATCHES is the list + # of strings, in argv format, LEN is the number of strings in MATCHES, + # and MAX is the length of the longest string in MATCHES. + def rl_display_match_list(matches, len, max) + # How many items of MAX length can we fit in the screen window? + max += 2 + limit = @_rl_screenwidth / max + if (limit != 1 && (limit * max == @_rl_screenwidth)) + limit-=1 + end + # Avoid a possible floating exception. If max > _rl_screenwidth, + # limit will be 0 and a divide-by-zero fault will result. + if (limit == 0) + limit = 1 + end + # How many iterations of the printing loop? + count = (len + (limit - 1)) / limit + + # Watch out for special case. If LEN is less than LIMIT, then + # just do the inner printing loop. + # 0 < len <= limit implies count = 1. + + # Sort the items if they are not already sorted. + if (!@rl_ignore_completion_duplicates) + matches[1,len] = matches[1,len].sort + end + rl_crlf() + + lines = 0 + if (!@_rl_print_completions_horizontally) + # Print the sorted items, up-and-down alphabetically, like ls. + for i in 1 .. count + l = i + for j in 0 ... limit + if (l > len || matches[l].nil?) + break + else + temp = printable_part(matches[l]) + printed_len = print_filename(temp, matches[l]) + + if (j + 1 < limit) + @rl_outstream.write(' '*(max - printed_len)) + end + end + l += count + end + rl_crlf() + lines+=1 + if (@_rl_page_completions && lines >= (@_rl_screenheight - 1) && i < count) + lines = _rl_internal_pager(lines) + return if (lines < 0) + end + end + else + # Print the sorted items, across alphabetically, like ls -x. + i = 1 + while(matches[i]) + temp = printable_part(matches[i]) + printed_len = print_filename(temp, matches[i]) + # Have we reached the end of this line? + if (matches[i+1]) + if ((limit > 1) && (i % limit) == 0) + rl_crlf() + lines+=1 + if (@_rl_page_completions && lines >= @_rl_screenheight - 1) + lines = _rl_internal_pager(lines) + return if (lines < 0) + end + else + @rl_outstream.write(' '*(max - printed_len)) + end + end + i += 1 + end + rl_crlf() + end + end + + # Append any necessary closing quote and a separator character to the + # just-inserted match. If the user has specified that directories + # should be marked by a trailing `/', append one of those instead. The + # default trailing character is a space. Returns the number of characters + # appended. If NONTRIVIAL_MATCH is set, we test for a symlink (if the OS + # has them) and don't add a suffix for a symlink to a directory. A + # nontrivial match is one that actually adds to the word being completed. + # The variable rl_completion_mark_symlink_dirs controls this behavior + # (it's initially set to the what the user has chosen, indicated by the + # value of _rl_complete_mark_symlink_dirs, but may be modified by an + # application's completion function). + def append_to_match(text, delimiter, quote_char, nontrivial_match) + temp_string = 0.chr * 4 + temp_string_index = 0 + if (quote_char && @rl_point>0 && !@rl_completion_suppress_quote && + @rl_line_buffer[@rl_point - 1,1] != quote_char) + temp_string[temp_string_index] = quote_char + temp_string_index += 1 + end + if (delimiter != 0.chr) + temp_string[temp_string_index] = delimiter + temp_string_index += 1 + elsif (!@rl_completion_suppress_append && @rl_completion_append_character) + temp_string[temp_string_index] = @rl_completion_append_character + temp_string_index += 1 + end + temp_string[temp_string_index] = 0.chr + temp_string_index += 1 + + if (@rl_filename_completion_desired) + filename = File.expand_path(text) + s = (nontrivial_match && !@rl_completion_mark_symlink_dirs) ? + File.lstat(filename) : File.stat(filename) + if s.directory? + if @_rl_complete_mark_directories + # This is clumsy. Avoid putting in a double slash if point + # is at the end of the line and the previous character is a + # slash. + if (@rl_point>0 && @rl_line_buffer[@rl_point,1] == 0.chr && @rl_line_buffer[@rl_point - 1,1] == '/' ) + + elsif (@rl_line_buffer[@rl_point,1] != '/') + rl_insert_text('/') + end + end + # Don't add anything if the filename is a symlink and resolves to a + # directory. + elsif s.symlink? && File.stat(filename).directory? + + else + if (@rl_point == @rl_end && temp_string_index>0) + rl_insert_text(temp_string) + end + end + filename = nil + else + if (@rl_point == @rl_end && temp_string_index>0) + rl_insert_text(temp_string) + end + end + temp_string_index + end + + # Stifle the history list, remembering only MAX number of lines. + def stifle_history(max) + max = 0 if (max < 0) + + if (@history_length > max) + @the_history.slice!(0,(@history_length - max)) + @history_length = max + end + + @history_stifled = true + @max_input_history = @history_max_entries = max + end + + # Stop stifling the history. This returns the previous maximum + # number of history entries. The value is positive if the history + # was stifled, negative if it wasn't. + def unstifle_history() + if (@history_stifled) + @history_stifled = false + return (@history_max_entries) + else + return (-@history_max_entries) + end + end + + def history_is_stifled() + return (@history_stifled) + end + + def clear_history() + @the_history = nil + @history_offset = @history_length = 0 + end + + # Insert COUNT characters from STRING to the output stream at column COL. + def insert_some_chars(string, count, col) + if @hConsoleHandle + _rl_output_some_chars(string,0,count) + else + # DEBUGGING + if (@rl_byte_oriented) + if (count != col) + $stderr.write("readline: debug: insert_some_chars: count (#{count}) != col (#{col})\n"); + end + end + # If IC is defined, then we do not have to "enter" insert mode. + #if (@_rl_term_IC) + # buffer = tgoto(@_rl_term_IC, 0, col) + # @_rl_out_stream.write(buffer) + # _rl_output_some_chars(string,0,count) + #else + # If we have to turn on insert-mode, then do so. + if (@_rl_term_im) + @_rl_out_stream.write(@_rl_term_im) + end + # If there is a special command for inserting characters, then + # use that first to open up the space. + if (@_rl_term_ic) + @_rl_out_stream.write(@_rl_term_ic * count) + end + + # Print the text. + _rl_output_some_chars(string,0, count) + + # If there is a string to turn off insert mode, we had best use + # it now. + if (@_rl_term_ei) + @_rl_out_stream.write(@_rl_term_ei) + end + #end + end + end + + # Delete COUNT characters from the display line. + def delete_chars(count) + return if (count > @_rl_screenwidth) # XXX + + if @hConsoleHandle.nil? + #if (@_rl_term_DC) + # buffer = tgoto(_rl_term_DC, count, count); + # @_rl_out_stream.write(buffer * count) + #else + if (@_rl_term_dc) + @_rl_out_stream.write(@_rl_term_dc * count) + end + #end + end + end + + # adjust pointed byte and find mbstate of the point of string. + # adjusted point will be point <= adjusted_point, and returns + # differences of the byte(adjusted_point - point). + # if point is invalied (point < 0 || more than string length), + # it returns -1 + def _rl_adjust_point(string, point) + + length = string.length + return -1 if (point < 0) + return -1 if (length < point) + + pos = 0 + + case @encoding + when 'E' + pos = string.scan(/./me).inject(0){|r,x| r= str.length + end + point + end + + # Find previous character started byte point of the specified seed. + # Returned point will be point <= seed. If flags is MB_FIND_NONZERO, + # we look for non-zero-width multibyte characters. + def _rl_find_prev_mbchar(string, seed, flags) + if @encoding == 'N' + return ((seed == 0) ? seed : seed - 1) + end + + length = string.length + if seed < 0 + return 0 + elsif length < seed + return length + end + + case @encoding + when 'E' + string[0,seed].scan(/./me)[0..-2].to_s.length + when 'S' + string[0,seed].scan(/./ms)[0..-2].to_s.length + when 'U' + string[0,seed].scan(/./mu)[0..-2].to_s.length + when 'X' + string[0,seed].force_encoding(@encoding_name)[0..-2].bytesize + end + end + + # compare the specified two characters. If the characters matched, + # return true. Otherwise return false. + def _rl_compare_chars(buf1, pos1, buf2, pos2) + return false if buf1[pos1].nil? || buf2[pos2].nil? + case @encoding + when 'E' + buf1[pos1..-1].scan(/./me)[0] == buf2[pos2..-1].scan(/./me)[0] + when 'S' + buf1[pos1..-1].scan(/./ms)[0] == buf2[pos2..-1].scan(/./ms)[0] + when 'U' + buf1[pos1..-1].scan(/./mu)[0] == buf2[pos2..-1].scan(/./mu)[0] + when 'X' + buf1[pos1..-1].force_encoding(@encoding_name)[0] == buf2[pos2..-1].force_encoding(@encoding_name)[0] + else + buf1[pos1] == buf2[pos2] + end + end + + # return the number of bytes parsed from the multibyte sequence starting + # at src, if a non-L'\0' wide character was recognized. It returns 0, + # if a L'\0' wide character was recognized. It returns (size_t)(-1), + # if an invalid multibyte sequence was encountered. It returns (size_t)(-2) + # if it couldn't parse a complete multibyte character. + def _rl_get_char_len(src) + return 0 if src[0,1] == 0.chr || src.length==0 + case @encoding + when 'E' + len = src.scan(/./me)[0].to_s.length + when 'S' + len = src.scan(/./ms)[0].to_s.length + when 'U' + len = src.scan(/./mu)[0].to_s.length + when 'X' + src = src.dup.force_encoding(@encoding_name) + len = src.valid_encoding? ? src[0].bytesize : 0 + else + len = 1 + end + len==0 ? -2 : len + end + + # read multibyte char + def _rl_read_mbchar(mbchar, size) + mb_len = 0 + while (mb_len < size) + rl_setstate(RL_STATE_MOREINPUT) + mbchar << rl_read_key() + mb_len += 1 + rl_unsetstate(RL_STATE_MOREINPUT) + case @encoding + when 'E' + break unless mbchar.scan(/./me).empty? + when 'S' + break unless mbchar.scan(/./ms).empty? + when 'U' + break unless mbchar.scan(/./mu).empty? + when 'X' + break if mbchar.dup.force_encoding(@encoding_name).valid_encoding? + end + end + mb_len + end + + # Read a multibyte-character string whose first character is FIRST into + # the buffer MB of length MLEN. Returns the last character read, which + # may be FIRST. Used by the search functions, among others. Very similar + # to _rl_read_mbchar. + def _rl_read_mbstring(first, mb, mlen) + c = first + for i in 0 ... mlen + mb << c + if _rl_get_char_len(mb) == -2 + # Read more for multibyte character + rl_setstate(RL_STATE_MOREINPUT) + c = rl_read_key() + rl_unsetstate(RL_STATE_MOREINPUT) + else + break + end + end + c + end + + def _rl_is_mbchar_matched(string, seed, _end, mbchar, length) + return 0 if ((_end - seed) < length) + + for i in 0 ... length + if (string[seed + i] != mbchar[i]) + return 0 + end + end + 1 + end + + # Redraw the last line of a multi-line prompt that may possibly contain + # terminal escape sequences. Called with the cursor at column 0 of the + # line to draw the prompt on. + def redraw_prompt(t) + oldp = @rl_display_prompt + rl_save_prompt() + + @rl_display_prompt = t + @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = + expand_prompt(t) + @local_prompt_prefix = nil + @local_prompt_len = @local_prompt ? @local_prompt.length : 0 + + rl_forced_update_display() + + @rl_display_prompt = oldp + rl_restore_prompt() + end + + # Redisplay the current line after a SIGWINCH is received. + def _rl_redisplay_after_sigwinch() + # Clear the current line and put the cursor at column 0. Make sure + # the right thing happens if we have wrapped to a new screen line. + if @_rl_term_cr + @rl_outstream.write(@_rl_term_cr) + @_rl_last_c_pos = 0 + if @_rl_term_clreol + @rl_outstream.write(@_rl_term_clreol) + else + space_to_eol(@_rl_screenwidth) + @rl_outstream.write(@_rl_term_cr) + end + + if @_rl_last_v_pos > 0 + _rl_move_vert(0) + end + else + rl_crlf() + end + + # Redraw only the last line of a multi-line prompt. + t = @rl_display_prompt.index("\n") + if t + redraw_prompt(@rl_display_prompt[(t+1)..-1]) + else + rl_forced_update_display() + end + end + + def rl_resize_terminal() + if @readline_echoing_p + _rl_get_screen_size(@rl_instream.fileno, 1) + if @rl_redisplay_function != :rl_redisplay + rl_forced_update_display() + else + _rl_redisplay_after_sigwinch() + end + end + end + + def rl_sigwinch_handler(sig) + rl_setstate(RL_STATE_SIGHANDLER) + rl_resize_terminal() + rl_unsetstate(RL_STATE_SIGHANDLER) + end + + + + module_function :rl_attempted_completion_function,:rl_deprep_term_function, + :rl_event_hook,:rl_attempted_completion_over,:rl_basic_quote_characters, + :rl_basic_word_break_characters,:rl_completer_quote_characters, + :rl_completer_word_break_characters,:rl_completion_append_character, + :rl_filename_quote_characters,:rl_instream,:rl_library_version,:rl_outstream, + :rl_readline_name, + :rl_attempted_completion_function=,:rl_deprep_term_function=, + :rl_event_hook=,:rl_attempted_completion_over=,:rl_basic_quote_characters=, + :rl_basic_word_break_characters=,:rl_completer_quote_characters=, + :rl_completer_word_break_characters=,:rl_completion_append_character=, + :rl_filename_quote_characters=,:rl_instream=,:rl_library_version=,:rl_outstream=, + :rl_readline_name=,:history_length,:history_base + + def no_terminal? + term = ENV["TERM"] + term.nil? || (term == 'dumb') || (term == 'cygwin' && RUBY_PLATFORM =~ /mswin|mingw/) + end + private :no_terminal? + +end diff --git a/extensions/console/lib/readline_compatible.rb b/extensions/console/lib/readline_compatible.rb new file mode 100644 index 000000000..fa3e305c9 --- /dev/null +++ b/extensions/console/lib/readline_compatible.rb @@ -0,0 +1,549 @@ +# readline.rb -- GNU Readline module +# Copyright (C) 1997-2001 Shugo Maed +# +# Ruby translation by Park Heesob phasis@gmail.com + +=begin +Copyright (c) 2009, Park Heesob +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Park Heesob nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=end + +module Readline + + require 'extensions/console/lib/rbreadline' + include RbReadline + + @completion_proc = nil + @completion_case_fold = false + + # + # A sneaky way to prevent the real Readline from loading after us + # + $LOADED_FEATURES.unshift("readline.rb") + + # Begins an interactive terminal process using +prompt+ as the command + # prompt that users see when they type commands. The method returns the + # line entered whenever a carriage return is encountered. + # + # If an +add_history+ argument is provided, commands entered by users are + # stored in a history buffer that can be recalled for later use. + # + # Note that this method depends on $stdin and $stdout both being open. + # Because this is meant as an interactive console interface, they should + # generally not be redirected. + # + # Example: + # + # loop{ Readline.readline('> ') } + # + def readline(prompt, add_history=nil) + if $stdin.closed? + raise IOError, "stdin closed" + end + + status = 0 + + begin + RbReadline.rl_instream = $stdin + RbReadline.rl_outstream = $stdout + buff = RbReadline.readline(prompt) + rescue ::Interrupt + raise $! + rescue Exception => e + buff = nil + RbReadline.rl_cleanup_after_signal() + RbReadline.rl_deprep_terminal() + $stderr.puts "[-] RbReadline Error: #{e.class} #{e} #{e.backtrace}" + retry + end + + if add_history && buff + RbReadline.add_history(buff) + end + + return buff ? buff.dup : nil + end + + # Sets the input stream (an IO object) for readline interaction. The + # default is $stdin. + # + def self.input=(input) + RbReadline.rl_instream = input + end + + # Sets the output stream (an IO object) for readline interaction. The + # default is $stdout. + # + def self.output=(output) + RbReadline.rl_outstream = output + end + + # Sets the auto-completion procedure (i.e. tab auto-complete). + # + # The +proc+ argument is typically a Proc object. It must respond to + # .call, take a single String argument and return an Array of + # candidates for completion. + # + # Example: + # + # list = ['search', 'next', 'clear'] + # Readline.completion_proc = proc{ |s| list.grep( /^#{Regexp.escape(s)}/) } + # + def self.completion_proc=(proc) + unless defined? proc.call + raise ArgumentError,"argument must respond to `call'" + end + @completion_proc = proc + end + + # Returns the current auto-completion procedure. + # + def self.completion_proc() + @completion_proc + end + + # Sets whether or not the completion proc should ignore case sensitivity. + # The default is false, i.e. completion procs are case sensitive. + # + def self.completion_case_fold=(bool) + @completion_case_fold = bool + end + + # Returns whether or not the completion proc is case sensitive. The + # default is false, i.e. completion procs are case sensitive. + # + def self.completion_case_fold() + @completion_case_fold + end + + def self.readline_attempted_completion_function(text,start,_end) + proc = @completion_proc + return nil if proc.nil? + + RbReadline.rl_attempted_completion_over = true + + # Remove leading spaces + text.gsub!(/^\s+/, '') + + case_fold = @completion_case_fold + ary = proc.call(text) + if ary.class != Array + ary = Array(ary) + else + ary.compact! + ary.uniq! + end + + ary.delete('') + + matches = ary.length + return nil if (matches == 0) + + if(matches == 1) + ary[0] = ary[0].strip + " " + end + + result = Array.new(matches+2) + for i in 0 ... matches + result[i+1] = ary[i].dup + end + result[matches+1] = nil + + if(matches==1) + result[0] = result[1].dup + else + i = 1 + low = 100000 + + while (i < matches) + if (case_fold) + si = 0 + while ((c1 = result[i][si,1].downcase) && + (c2 = result[i + 1][si,1].downcase)) + break if (c1 != c2) + si += 1 + end + else + si = 0 + while ((c1 = result[i][si,1]) && + (c2 = result[i + 1][si,1])) + break if (c1 != c2) + si += 1 + end + end + if (low > si) + low = si + end + i+=1 + end + result[0] = result[1][0,low] + end + + result + end + + # Sets vi editing mode. + # + def self.vi_editing_mode() + RbReadline.rl_vi_editing_mode(1,0) + nil + end + + # Sets emacs editing mode + # + def self.emacs_editing_mode() + RbReadline.rl_emacs_editing_mode(1,0) + nil + end + + # Sets the character that is automatically appended after the + # Readline.completion_proc method is called. + # + # If +char+ is nil or empty, then a null character is used. + # + def self.completion_append_character=(char) + if char.nil? + RbReadline.rl_completion_append_character = ?\0 + elsif char.length==0 + RbReadline.rl_completion_append_character = ?\0 + else + RbReadline.rl_completion_append_character = char[0] + end + end + + # Returns the character that is automatically appended after the + # Readline.completion_proc method is called. + # + def self.completion_append_character() + if RbReadline.rl_completion_append_character == ?\0 + nil + end + return RbReadline.rl_completion_append_character + end + + # Sets the character string that signal a break between words for the + # completion proc. + # + def self.basic_word_break_characters=(str) + RbReadline.rl_basic_word_break_characters = str.dup + end + + # Returns the character string that signal a break between words for the + # completion proc. The default is " \t\n\"\\'`@$><=|&{(". + # + def self.basic_word_break_characters() + if RbReadline.rl_basic_word_break_characters.nil? + nil + else + RbReadline.rl_basic_word_break_characters.dup + end + end + + # Sets the character string that signal the start or end of a word for + # the completion proc. + # + def self.completer_word_break_characters=(str) + RbReadline.rl_completer_word_break_characters = str.dup + end + + # Returns the character string that signal the start or end of a word for + # the completion proc. + # + def self.completer_word_break_characters() + if RbReadline.rl_completer_word_break_characters.nil? + nil + else + RbReadline.rl_completer_word_break_characters.dup + end + end + + # Sets the list of quote characters that can cause a word break. + # + def self.basic_quote_characters=(str) + RbReadline.rl_basic_quote_characters = str.dup + end + + # Returns the list of quote characters that can cause a word break. + # The default is "'\"" (single and double quote characters). + # + def self.basic_quote_characters() + if RbReadline.rl_basic_quote_characters.nil? + nil + else + RbReadline.rl_basic_quote_characters.dup + end + end + + # Sets the list of characters that can be used to quote a substring of + # the line, i.e. a group of characters within quotes. + # + def self.completer_quote_characters=(str) + RbReadline.rl_completer_quote_characters = str.dup + end + + # Returns the list of characters that can be used to quote a substring + # of the line, i.e. a group of characters inside quotes. + # + def self.completer_quote_characters() + if RbReadline.rl_completer_quote_characters.nil? + nil + else + RbReadline.rl_completer_quote_characters.dup + end + end + + # Sets the character string of one or more characters that indicate quotes + # for the filename completion of user input. + # + def self.filename_quote_characters=(str) + RbReadline.rl_filename_quote_characters = str.dup + end + + # Returns the character string used to indicate quotes for the filename + # completion of user input. + # + def self.filename_quote_characters() + if RbReadline.rl_filename_quote_characters.nil? + nil + else + RbReadline.rl_filename_quote_characters.dup + end + end + + # The History class encapsulates a history of all commands entered by + # users at the prompt, providing an interface for inspection and retrieval + # of all commands. + class History + extend Enumerable + + # The History class, stringified in all caps. + #-- + # Why? + # + def self.to_s + "HISTORY" + end + + # Returns the command that was entered at the specified +index+ + # in the history buffer. + # + # Raises an IndexError if the entry is nil. + # + def self.[](index) + if index < 0 + index += RbReadline.history_length + end + entry = RbReadline.history_get(RbReadline.history_base+index) + if entry.nil? + raise IndexError,"invalid index" + end + entry.line.dup + end + + # Sets the command +str+ at the given index in the history buffer. + # + # You can only replace an existing entry. Attempting to create a new + # entry will result in an IndexError. + # + def self.[]=(index,str) + if index<0 + index += RbReadline.history_length + end + entry = RbReadline.replace_history_entry(index,str,nil) + if entry.nil? + raise IndexError,"invalid index" + end + str + end + + # Synonym for Readline.add_history. + # + def self.<<(str) + RbReadline.add_history(str) + end + + # Pushes a list of +args+ onto the history buffer. + # + def self.push(*args) + args.each do |str| + RbReadline.add_history(str) + end + end + + # Internal function that removes the item at +index+ from the history + # buffer, performing necessary duplication in the process. + #-- + # TODO: mark private? + # + def self.rb_remove_history(index) + entry = RbReadline.remove_history(index) + if (entry) + val = entry.line.dup + entry = nil + return val + end + nil + end + + # Removes and returns the last element from the history buffer. + # + def self.pop() + if RbReadline.history_length>0 + rb_remove_history(RbReadline.history_length-1) + else + nil + end + end + + # Removes and returns the first element from the history buffer. + # + def self.shift() + if RbReadline.history_length>0 + rb_remove_history(0) + else + nil + end + end + + # Iterates over each entry in the history buffer. + # + def self.each() + for i in 0 ... RbReadline.history_length + entry = RbReadline.history_get(RbReadline.history_base + i) + break if entry.nil? + yield entry.line.dup + end + self + end + + # Returns the length of the history buffer. + # + def self.length() + RbReadline.history_length + end + + # Synonym for Readline.length. + # + def self.size() + RbReadline.history_length + end + + # Returns a bolean value indicating whether or not the history buffer + # is empty. + # + def self.empty?() + RbReadline.history_length == 0 + end + + # Deletes an entry from the histoyr buffer at the specified +index+. + # + def self.delete_at(index) + if index < 0 + i += RbReadline.history_length + end + if index < 0 || index > RbReadline.history_length - 1 + raise IndexError, "invalid index" + end + rb_remove_history(index) + end + + end + + HISTORY = History + + # The Fcomp class provided to encapsulate typical filename completion + # procedure. You will not typically use this directly, but will instead + # use the Readline::FILENAME_COMPLETION_PROC. + # + class Fcomp + def self.call(str) + matches = RbReadline.rl_completion_matches(str, + :rl_filename_completion_function) + if (matches) + result = [] + i = 0 + while(matches[i]) + result << matches[i].dup + matches[i] = nil + i += 1 + end + matches = nil + if (result.length >= 2) + result.shift + end + else + result = nil + end + return result + end + end + + FILENAME_COMPLETION_PROC = Fcomp + + # The Ucomp class provided to encapsulate typical filename completion + # procedure. You will not typically use this directly, but will instead + # use the Readline::USERNAME_COMPLETION_PROC. + # + # Note that this feature currently only works on Unix systems since it + # ultimately uses the Etc module to iterate over a list of users. + # + class Ucomp + def self.call(str) + matches = RbReadline.rl_completion_matches(str, + :rl_username_completion_function) + if (matches) + result = [] + i = 0 + while(matches[i]) + result << matches[i].dup + matches[i] = nil + i += 1 + end + matches = nil + if (result.length >= 2) + result.shift + end + else + result = nil + end + return result + end + end + + USERNAME_COMPLETION_PROC = Ucomp + + RbReadline.rl_readline_name = "Ruby" + + RbReadline.using_history() + + VERSION = RbReadline.rl_library_version + + module_function :readline + + RbReadline.rl_attempted_completion_function = :readline_attempted_completion_function + +end + diff --git a/extensions/console/lib/shellinterface.rb b/extensions/console/lib/shellinterface.rb new file mode 100644 index 000000000..ae95cee00 --- /dev/null +++ b/extensions/console/lib/shellinterface.rb @@ -0,0 +1,576 @@ +# +# Copyright 2011 Wade Alcorn wade@bindshell.net +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +module BeEF +module Extension +module Console + +class ShellInterface + + BD = BeEF::Extension::Initialization::Models::BrowserDetails + + def initialize(config) + self.config = config + self.cmd = {} + end + + def settarget(id) + begin + self.targetsession = BeEF::Core::Models::HookedBrowser.first(:id => id).session + self.targetip = BeEF::Core::Models::HookedBrowser.first(:id => id).ip + self.targetid = id + rescue + return nil + end + end + + def setofflinetarget(id) + begin + self.targetsession = BeEF::Core::Models::HookedBrowser.first(:id => id).session + self.targetip = "(OFFLINE) " + BeEF::Core::Models::HookedBrowser.first(:id => id).ip + self.targetid = id + rescue + return nil + end + end + + def cleartarget + self.targetsession = nil + self.targetip = nil + self.targetid = nil + self.cmd = {} + end + + # This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb + def getcommands + + return if self.targetid.nil? + + tree = [] + BeEF::Modules.get_categories.each { |c| + tree.push({ + 'text' => c, + 'cls' => 'folder', + 'children' => [] + }) + } + + BeEF::Modules.get_enabled.each{|k, mod| + update_command_module_tree(tree, mod['category'], get_command_module_status(k), mod['name'],mod['db']['id']) + } + + # if dynamic modules are found in the DB, then we don't have yaml config for them + # and loading must proceed in a different way. + dynamic_modules = BeEF::Core::Models::CommandModule.all(:path.like => "Dynamic/") + + if(dynamic_modules != nil) + all_modules = BeEF::Core::Models::CommandModule.all(:order => [:id.asc]) + all_modules.each{|dyn_mod| + next if !dyn_mod.path.split('/').first.match(/^Dynamic/) + + dyn_mod_name = dyn_mod.path.split('/').last + dyn_mod_category = nil + if(dyn_mod_name == "Msf") + dyn_mod_category = "Metasploit" + else + # future dynamic modules... + end + + #print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]") + command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new + command_mod.session_id = hook_session_id + command_mod.update_info(dyn_mod.id) + command_mod_name = command_mod.info['Name'].downcase + + update_command_module_tree(tree, dyn_mod_category, "Verified Unknown", command_mod_name,dyn_mod.id) + } + end + + # sort the parent array nodes + tree.sort! {|a,b| a['text'] <=> b['text']} + + # sort the children nodes by status + tree.each {|x| x['children'] = + x['children'].sort_by {|a| a['status']} + } + + # append the number of command modules so the branch name results in: " (num)" + #tree.each {|command_module_branch| + # num_of_command_modules = command_module_branch['children'].length + # command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")" + #} + + # return a JSON array of hashes + tree + end + + def setcommand(id) + key = BeEF::Module.get_key_by_database_id(id.to_i) + + self.cmd['id'] = id + self.cmd['Name'] = self.config.get("beef.module.#{key}.name") + self.cmd['Description'] = self.config.get("beef.module.#{key}.description") + self.cmd['Category'] = self.config.get("beef.module.#{key}.category") + self.cmd['Data'] = BeEF::Module.get_options(key) + end + + def clearcommand + self.cmd = {} + end + + def setparam(param,value) + self.cmd['Data'].each do |data| + if data['name'] == param + data['value'] = value + return + end + end + end + + def getcommandresponses(cmdid = self.cmd['id']) + + commands = [] + i = 0 + + BeEF::Core::Models::Command.all(:command_module_id => cmdid, :hooked_browser_id => self.targetid).each do |command| + commands.push({ + 'id' => i, + 'object_id' => command.id, + 'creationdate' => Time.at(command.creationdate.to_i).strftime("%Y-%m-%d %H:%M").to_s, + 'label' => command.label + }) + i+=1 + end + + commands + end + + def getindividualresponse(cmdid) + results = [] + begin + BeEF::Core::Models::Result.all(:command_id => cmdid).each { |result| + results.push({'date' => result.date, 'data' => JSON.parse(result.data)}) + } + rescue + return nil + end + results + end + + def executecommand + definition = {} + options = {} + options.store("zombie_session", self.targetsession.to_s) + options.store("command_module_id", self.cmd['id']) + + if not self.cmd['Data'].nil? + self.cmd['Data'].each do |key| + options.store("txt_"+key['name'].to_s,key['value']) + end + end + + options.keys.each {|param| + definition[param[4..-1]] = options[param] + oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1]) + oc.value = options[param] + oc.save + } + + mod_key = BeEF::Module.get_key_by_database_id(self.cmd['id']) + # Hack to rework the old option system into the new option system + def2 = [] + definition.each{|k,v| + def2.push({'name' => k, 'value' => v}) + } + # End hack + if BeEF::Module.execute(mod_key, self.targetsession.to_s, def2) == true + return true + else + return false + end + + #Old method + #begin + # BeEF::Core::Models::Command.new( :data => definition.to_json, + # :hooked_browser_id => self.targetid, + # :command_module_id => self.cmd['id'], + # :creationdate => Time.new.to_i + # ).save + #rescue + # return false + #end + + #return true + end + + def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id) + + # construct leaf node for the command module tree + leaf_node = { + 'text' => cmd_name, + 'leaf' => true, + 'status' => cmd_status, + 'id' => cmd_id + } + + # add the node to the branch in the command module tree + tree.each {|x| + if x['text'].eql? cmd_category + x['children'].push( leaf_node ) + break + end + } + end + + def get_command_module_status(mod) + hook_session_id = self.targetsession + if hook_session_id == nil + return "Verified Unknown" + end + case BeEF::Module.support(mod, { + 'browser' => BD.get(hook_session_id, 'BrowserName'), + 'ver' => BD.get(hook_session_id, 'BrowserVersion'), + 'os' => [BD.get(hook_session_id, 'OsName')]}) + + when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING + return "Verfied Not Working" + when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY + return "Verified User Notify" + when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING + return "Verified Working" + when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN + return "Verified Unknown" + else + return "Verified Unknown" + end + end + + #Yoinked from the UI panel - we really need to centralise all this stuff and encapsulate it away?? + def select_zombie_summary + + return if self.targetsession.nil? + + # init the summary grid + summary_grid_hash = { + 'success' => 'true', + 'results' => [] + } + + # set and add the return values for the page title + page_title = BD.get(self.targetsession, 'PageTitle') + if not page_title.nil? + encoded_page_title = CGI.escapeHTML(page_title) + encoded_page_hash = { 'Page Title' => encoded_page_title } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_page_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the return values for the host name + host_name = BD.get(self.targetsession, 'HostName') + if not host_name.nil? + encoded_host_name = CGI.escapeHTML(host_name) + encoded_host_name_hash = { 'Hostname/IP' => encoded_host_name } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_host_name_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the return values for the os name + os_name = BD.get(self.targetsession, 'OsName') + if not os_name.nil? + encoded_os_name = CGI.escapeHTML(os_name) + encoded_os_name_hash = { 'OS Name' => encoded_os_name } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_os_name_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the return values for the browser name + browser_name = BD.get(self.targetsession, 'BrowserName') + if not browser_name.nil? + friendly_browser_name = BeEF::Core::Constants::Browsers.friendly_name(browser_name) + browser_name_hash = { 'Browser Name' => friendly_browser_name } + + browser_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => browser_name_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(browser_name_row) # add the row + end + + # set and add the return values for the browser version + browser_version = BD.get(self.targetsession, 'BrowserVersion') + if not browser_version.nil? + encoded_browser_version = CGI.escapeHTML(browser_version) + browser_version_hash = { 'Browser Version' => encoded_browser_version } + + browser_version_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => browser_version_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(browser_version_row) # add the row + end + + # set and add the return values for the browser ua string + browser_uastring = BD.get(self.targetsession, 'BrowserReportedName') + if not browser_uastring.nil? + browser_uastring_hash = { 'Browser UA String' => browser_uastring } + + browser_uastring_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => browser_uastring_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(browser_uastring_row) # add the row + end + + # set and add the list of cookies + cookies = BD.get(self.targetsession, 'Cookies') + if not cookies.nil? and not cookies.empty? + encoded_cookies = CGI.escapeHTML(cookies) + encoded_cookies_hash = { 'Cookies' => encoded_cookies } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_cookies_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the list of plugins installed in the browser + browser_plugins = BD.get(self.targetsession, 'BrowserPlugins') + if not browser_plugins.nil? and not browser_plugins.empty? + encoded_browser_plugins = CGI.escapeHTML(browser_plugins) + encoded_browser_plugins_hash = { 'Browser Plugins' => encoded_browser_plugins } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_browser_plugins_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the internal ip address + internal_ip = BD.get(self.targetsession, 'InternalIP') + if not internal_ip.nil? + encoded_internal_ip = CGI.escapeHTML(internal_ip) + encoded_internal_ip_hash = { 'Internal IP' => encoded_internal_ip } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_internal_ip_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the internal hostname + internal_hostname = BD.get(self.targetsession, 'InternalHostname') + if not internal_hostname.nil? + encoded_internal_hostname = CGI.escapeHTML(internal_hostname) + encoded_internal_hostname_hash = { 'Internal Hostname' => encoded_internal_hostname } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_internal_hostname_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the zombie screen size and color depth + screen_params = BD.get(self.targetsession, 'ScreenParams') + if not screen_params.nil? + + screen_params_hash = JSON.parse(screen_params.gsub(/\"\=\>/, '":')) # tidy up the string for JSON + width = screen_params_hash['width'] + #raise WEBrick::HTTPStatus::BadRequest, "width is wrong type" if not width.is_a?(Fixnum) + height = screen_params_hash['height'] + #raise WEBrick::HTTPStatus::BadRequest, "height is wrong type" if not height.is_a?(Fixnum) + colordepth = screen_params_hash['colordepth'] + #raise WEBrick::HTTPStatus::BadRequest, "colordepth is wrong type" if not colordepth.is_a?(Fixnum) + + # construct the string to be displayed in the details tab + encoded_screen_params = CGI.escapeHTML("Width: "+width.to_s + ", Height: " + height.to_s + ", Colour Depth: " + colordepth.to_s) + encoded_screen_params_hash = { 'Screen Params' => encoded_screen_params } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_screen_params_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the zombie browser window size + window_size = BD.get(self.targetsession, 'WindowSize') + if not window_size.nil? + + window_size_hash = JSON.parse(window_size.gsub(/\"\=\>/, '":')) # tidy up the string for JSON + width = window_size_hash['width'] + #raise WEBrick::HTTPStatus::BadRequest, "width is wrong type" if not width.is_a?(Fixnum) + height = window_size_hash['height'] + #raise WEBrick::HTTPStatus::BadRequest, "height is wrong type" if not height.is_a?(Fixnum) + + # construct the string to be displayed in the details tab + encoded_window_size = CGI.escapeHTML("Width: "+width.to_s + ", Height: " + height.to_s) + encoded_window_size_hash = { 'Window Size' => encoded_window_size } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_window_size_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the yes|no value for JavaEnabled + java_enabled = BD.get(self.targetsession, 'JavaEnabled') + if not java_enabled.nil? + encoded_java_enabled = CGI.escapeHTML(java_enabled) + encoded_java_enabled_hash = { 'Java Enabled' => encoded_java_enabled } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_java_enabled_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the yes|no value for VBScriptEnabled + vbscript_enabled = BD.get(self.targetsession, 'VBScriptEnabled') + if not vbscript_enabled.nil? + encoded_vbscript_enabled = CGI.escapeHTML(vbscript_enabled) + encoded_vbscript_enabled_hash = { 'VBScript Enabled' => encoded_vbscript_enabled } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_vbscript_enabled_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the yes|no value for HasFlash + has_flash = BD.get(self.targetsession, 'HasFlash') + if not has_flash.nil? + encoded_has_flash = CGI.escapeHTML(has_flash) + encoded_has_flash_hash = { 'Has Flash' => encoded_has_flash } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_has_flash_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the yes|no value for HasGoogleGears + has_googlegears = BD.get(self.targetsession, 'HasGoogleGears') + if not has_googlegears.nil? + encoded_has_googlegears = CGI.escapeHTML(has_googlegears) + encoded_has_googlegears_hash = { 'Has GoogleGears' => encoded_has_googlegears } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_has_googlegears_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the return values for hasSessionCookies + has_session_cookies = BD.get(self.targetsession, 'hasSessionCookies') + if not has_session_cookies.nil? + encoded_has_session_cookies = CGI.escapeHTML(has_session_cookies) + encoded_has_session_cookies_hash = { 'Session Cookies' => encoded_has_session_cookies } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_has_session_cookies_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + # set and add the return values for hasPersistentCookies + has_persistent_cookies = BD.get(self.targetsession, 'hasPersistentCookies') + if not has_persistent_cookies.nil? + encoded_has_persistent_cookies = CGI.escapeHTML(has_persistent_cookies) + encoded_has_persistent_cookies_hash = { 'Persistent Cookies' => encoded_has_persistent_cookies } + + page_name_row = { + 'category' => 'Browser Hook Initialisation', + 'data' => encoded_has_persistent_cookies_hash, + 'from' => 'Initialisation' + } + + summary_grid_hash['results'].push(page_name_row) # add the row + end + + summary_grid_hash + end + + attr_reader :targetsession + attr_reader :targetid + attr_reader :targetip + attr_reader :cmd + + protected + + attr_writer :targetsession + attr_writer :targetid + attr_writer :targetip + attr_writer :cmd + attr_accessor :config + +end + +end end end \ No newline at end of file diff --git a/extensions/console/shell.rb b/extensions/console/shell.rb new file mode 100644 index 000000000..688b1ede6 --- /dev/null +++ b/extensions/console/shell.rb @@ -0,0 +1,78 @@ +# +# Copyright 2011 Wade Alcorn wade@bindshell.net +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +module BeEF +module Extension +module Console + +class Shell + + DefaultPrompt = "%undBeEF%clr" + DefaultPromptChar = "%clr>" + + include Rex::Ui::Text::DispatcherShell + + def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) + + require 'extensions/console/lib/readline_compatible' + require 'extensions/console/lib/command_dispatcher' + require 'extensions/console/lib/shellinterface' + + self.http_hook_server = opts['http_hook_server'] + self.config = opts['config'] + self.jobs = Rex::JobContainer.new + self.interface = BeEF::Extension::Console::ShellInterface.new(self.config) + + super(prompt, prompt_char, File.expand_path(self.config.get("beef.extension.console.shell.historyfolder").to_s + self.config.get("beef.extension.console.shell.historyfile").to_s)) + + input = Rex::Ui::Text::Input::Stdio.new + output = Rex::Ui::Text::Output::Stdio.new + + init_ui(input,output) + + enstack_dispatcher(CommandDispatcher::Core) + + #To prevent http_hook_server from blocking, we kick it off as a background job here. + self.jobs.start_bg_job( + "http_hook_server", + self, + Proc.new { |ctx_| self.http_hook_server.start } + ) + + end + + def stop + super + end + + #New method to determine if a particular command dispatcher it already .. enstacked .. gooood + def dispatched_enstacked(dispatcher) + inst = dispatcher.new(self) + self.dispatcher_stack.each { |disp| + if (disp.name == inst.name) + return true + end + } + return false + end + + attr_accessor :http_hook_server + attr_accessor :config + attr_accessor :jobs + attr_accessor :interface + +end + +end end end \ No newline at end of file diff --git a/install b/install index 1571d8170..ca9dba662 100644 --- a/install +++ b/install @@ -34,7 +34,7 @@ puts "\nPlease make sure you have installed SQLite before proceeding. For instr # array of required gems - add to as needed (specify a version if needed eg "gem_name, =x.x.x") $gems_required = ["ansi", "term-ansicolor", "dm-core", "json", "data_objects", "do_sqlite3", "sqlite3", "dm-sqlite-adapter", - "parseconfig", "erubis", "dm-migrations"] + "parseconfig", "erubis", "dm-migrations", "librex"] # array of missing non-version specific gems installed $gems_missing = Array.new