531 lines
15 KiB
Ruby
531 lines
15 KiB
Ruby
|
# 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 'rbreadline'
|
||
|
include RbReadline
|
||
|
|
||
|
@completion_proc = nil
|
||
|
@completion_case_fold = false
|
||
|
|
||
|
# 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
|
||
|
|
||
|
RbReadline.rl_instream = $stdin
|
||
|
RbReadline.rl_outstream = $stdout
|
||
|
|
||
|
status = 0
|
||
|
|
||
|
begin
|
||
|
buff = RbReadline.readline(prompt)
|
||
|
rescue Exception => e
|
||
|
buff = nil
|
||
|
RbReadline.rl_cleanup_after_signal()
|
||
|
RbReadline.rl_deprep_terminal()
|
||
|
raise e
|
||
|
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 <tt>$stdin</tt>.
|
||
|
#
|
||
|
def self.input=(input)
|
||
|
RbReadline.rl_instream = input
|
||
|
end
|
||
|
|
||
|
# Sets the output stream (an IO object) for readline interaction. The
|
||
|
# default is <tt>$stdout</tt>.
|
||
|
#
|
||
|
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
|
||
|
# <tt>.call</tt>, 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
|
||
|
|
||
|
case_fold = @completion_case_fold
|
||
|
ary = proc.call(text)
|
||
|
if ary.class != Array
|
||
|
ary = Array(ary)
|
||
|
else
|
||
|
ary.compact!
|
||
|
end
|
||
|
|
||
|
matches = ary.length
|
||
|
return nil if (matches == 0)
|
||
|
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
|