# encoding: US-ASCII
#
# 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
#

require "rbreadline/version"

class Integer
  def ord; self; end
end

module RbReadline
  require 'etc'

  RL_LIBRARY_VERSION = "5.2"
  RL_READLINE_VERSION  = 0x0502

  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_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.001

  # 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  ,
      "\e\x7F" => :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  ,
      "\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 = ""

  # 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

  # "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...
  #
  # Currently rb-readline can't tgoto(). Setting this to false means that
  # insert_some_chars doesn't get called and some other method is used.
  @_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"]}"
  if !File.directory? ENV["HOME"]
    raise RuntimeError.new("HOME environment variable (or HOMEDRIVE and HOMEPATH) must be set and point to a directory")
  end

  @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,:rl_point

  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
        @dirname = "."
        @filename = ""
      elsif text.rindex(File::SEPARATOR) == text.length-1
        @dirname = text
        @filename = ""
      else
        @dirname, @filename = File.split(text)

        # This preserves the "./" when the user types "./dirname<tab>".
        if @dirname == "." && text[0,2] == ".#{File::SEPARATOR}"
          @dirname += File::SEPARATOR
        end
      end

      # We aren't done yet.  We also support the "~user" syntax.

      # Save the version of the directory that the user typed.
      @users_dirname = @dirname.dup

      if (@dirname[0,1] == '~')
        @dirname = File.expand_path(@dirname)
      end

      # The directory completion hook should perform any necessary
      #   dequoting.
      if (@rl_directory_completion_hook && send(rl_directory_completion_hook,@dirname))
        @users_dirname = @dirname.dup
      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
        @dirname = @users_dirname.dup
      end

      begin
        @directory = Dir.new(@dirname)
      rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EACCES
      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<WILD>/te<TAB>.  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
          if(temp[-1,1] != File::SEPARATOR)
            temp += File::SEPARATOR
          end
        else
          temp = @users_dirname
          if(temp[-1,1] != File::SEPARATOR)
            temp += File::SEPARATOR
          end
        end
        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 = []
    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<matches)
      if (@_rl_completion_case_fold)
        si = 0
        while((c1 = _rl_to_lower(match_list[i][si])) &&
              (c2 = _rl_to_lower(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
      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


  # This is a NOOP until the rest of Vi-mode is working.
  def rl_vi_editing_mode(count, 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)

      # Use NEW when comparing against the last invisible character in the
      # prompt string, since they're both buffer indices and DPOS is a desired
      # display position.
      if (new > @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 = Fiddle::Pointer.malloc(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_invis_chars_first_line,@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,_,_, = 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("\\r", "\r").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 = Fiddle::Pointer.malloc(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 = 0
      retry_if_interrupted do
        wr, wc = `stty size`.split(' ').map { |x| x.to_i }
      end
      @_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

    # 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 = {}
    retry_if_interrupted do
      h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten]
    end
    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)
    # 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

  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 = nil
      File.open(openname) do |file|
        buffer = file.read
      end
    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"
        mode = @vi_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"
        # This is a NOOP until the rest of Vi-mode is working.
      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"
        # This is a NOOP until the rest of Vi-mode is working.
      when "vi-insert"
        # This is a NOOP until the rest of Vi-mode is working.
      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 "next-history"
      return :rl_get_next_history
    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 "previous-history"
      return :rl_get_previous_history
    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]+$/
        method = ('rl_' + name.gsub('-', '_')).to_sym
        return method if respond_to?(method)
      end
    end
    nil
  end

  def rl_translate_keyseq(seq)
    require 'strscan'

    ss = StringScanner.new(seq)
    new_seq = ''

    until ss.eos?
      char = ss.getch
      next new_seq << char unless char == '\\'

      char = ss.getch
      new_seq << case char
                 when 'a'
                   "\007"
                 when 'b'
                   "\b"
                 when 'd'
                   RUBOUT
                 when 'e'
                   ESC
                 when 'f'
                   "\f"
                 when 'n'
                   NEWLINE
                 when 'r'
                   RETURN
                 when 't'
                   TAB
                 when 'v'
                   0x0B
                 when '\\'
                   '\\'
                 when 'x'
                   ss.scan(/\d\d/).to_i(16).chr
                 when '0'..'7'
                   ss.pos -= 1
                   ss.scan(/\d\d\d/).to_i(8).chr
                 else
                   char
                 end
    end

    new_seq
  end

  # Bind KEY to FUNCTION.  Returns non-zero if KEY is out of range.
  def rl_bind_key(key, function)
    @_rl_keymap[rl_translate_keyseq(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
      func = rl_named_function(funname)
      rl_bind_key(key, func) if func
    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] || 0
  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.
      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

    o_cpos = @_rl_last_c_pos

    # 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)

    # We need to indicate that the cursor position is correct in the presence
    # of invisible characters in the prompt string.  Let's see if setting this
    # when we make sure we're at the end of the drawn prompt string works.
    if (current_line == 0 && !@rl_byte_oriented &&
        (@_rl_last_c_pos > 0 || o_cpos > 0) &&
        @_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

      # If col_lendiff is > 0, implying that the new string takes up more
      # screen real estate than the old, but lendiff is < 0, meaning that it
      # takes fewer bytes, we need to just output the characters starting from
      # the first difference.  These will overwrite what is on the display, so
      # there's no reason to do a smart update.  This can really only happen in
      # a multibyte environment.
      if lendiff < 0
        _rl_output_some_chars(new, nfd, temp)
        @_rl_last_c_pos += _rl_col_width(new, nfd, nfd+temp)

        # If nfd begins before any invisible characters in the prompt, adjust
        # _rl_last_c_pos to account for wrap_offset and set cpos_adjusted to
        # let the caller know.
        if current_line == 0 && @wrap_offset && nfd <= @prompt_last_invisible
          @_rl_last_c_pos -= @wrap_offset
          @cpos_adjusted = true
        end
        return
      # 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.
      elsif (@_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
          _rl_output_some_chars(new,nfd, temp)
          @_rl_last_c_pos += col_temp
          # If nfd begins before any invisible characters in the prompt, adjust
          # _rl_last_c_pos to account for wrap_offset and set cpos_adjusted to
          # let the caller know.
          if current_line == 0 && @wrap_offset && nfd <= @prompt_last_invisible
            @_rl_last_c_pos -= @wrap_offset
            @cpos_adjusted = true
          end
          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)
          # If nfd begins at the prompt, or before the invisible characters in
          # the prompt, we need to adjust _rl_last_c_pos in a multibyte locale
          # to account for the wrap offset and set cpos_adjusted accordingly.
          _rl_output_some_chars(new,nfd, temp)
          if !@rl_byte_oriented
            @_rl_last_c_pos += _rl_col_width(new,nfd,nfd+temp)
            if current_line == 0 && @wrap_offset && nfd <= @prompt_last_invisible
              @_rl_last_c_pos -= @wrap_offset
              @cpos_adjusted = true
            end
          else
            @_rl_last_c_pos += temp
          end
        end

        # Otherwise, print over the existing material.
      else
        if (temp > 0)
          # If nfd begins at the prompt, or before the invisible characters in
          # the prompt, we need to adjust _rl_last_c_pos in a multibyte locale
          # to account for the wrap offset and set cpos_adjusted accordingly.
          _rl_output_some_chars(new,nfd, temp)
          @_rl_last_c_pos += col_temp      # XXX
          if !@rl_byte_oriented
            if current_line == 0 && @wrap_offset && nfd <= @prompt_last_invisible
              @_rl_last_c_pos -= @wrap_offset
              @cpos_adjusted = true
            end
          end
        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 <darin@acuson.com> 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 <ed@catmur.co.uk>
      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 (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 = 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.
        linenum = 0
        while linenum <= 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

          linenum += 1
        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 tx >= 0 && @_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

  def rl_line_buffer
    @rl_line_buffer.tr(0.chr, '')
  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

    # Find the first occurance of 0.chr, which marks the end of the string.
    # Because newlines are also in the string as 0.chrs (they are tracked
    # seperately), we need to ignore any 0.chrs that lie before _end.
    index = string[_end...string.length].index(0.chr)

    str = index ? string[0,index+_end] : 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'
      str[start ... _end].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)
    case @encoding
    when 'X'
      @_rl_out_stream.write(string[start, count].force_encoding(@encoding_name))
    else
      @_rl_out_stream.write(string[start, count])
    end
  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

    if c.is_a?(Integer) && c < 0
      cxt.sflags |= SF_FAILED
      cxt.history_pos = cxt.last_found_line
      return -1
    end

    # 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 && 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]
      rl_replace_line(cxt.lines[cxt.history_pos], false)
      @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
    @_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
    raise LoadError, "Not Windows" if RUBY_PLATFORM !~ /mswin|mingw/

    if RUBY_VERSION < '1.9.1'
      require 'Win32API'
    else
      require 'fiddle'
      class Win32API
        DLL = {}
        TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
        CALL_TYPE_TO_ABI = {:stdcall => 1, :cdecl => 1, nil => 1} #Taken from Fiddle::Importer

        def initialize(dllname, func, import, export = "0", calltype = :stdcall)
          @proto = import.join.tr("VPpNnLlIi", "0SSI").chomp('0').split('')
          handle = DLL[dllname] ||= Fiddle.dlopen(dllname)
          @func = Fiddle::Function.new(handle[func], TYPEMAP.values_at(*@proto), CALL_TYPE_TO_ABI[calltype])
        end

        def call(*args)
          args.each_with_index do |x, i|
            args[i], = [x == 0 ? nil : x].pack("p").unpack("l!*") if @proto[i] == "S" && !x.is_a?(Fiddle::Pointer)
            args[i], = [x].pack("I").unpack("i") if @proto[i] == "I"
          end
          @func.call(*args).to_i || 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()
      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
  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 = Fiddle::Pointer.malloc(24)
      @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi)
      cursor_pos = csbi[4,4].unpack('L').first
      written = Fiddle::Pointer.malloc(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 <NL>.
      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,1] = 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 = ''
      _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)

    if c.is_a?(Integer) && c < 0
      return -1
    end

    _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.encode('UTF-8', invalid: :replace, undef: :replace, replace: '').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 !@rl_byte_oriented
          scan = _rl_find_next_mbchar(@rl_line_buffer, scan, 1, MB_FIND_ANY)
        else
          scan += 1
        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
      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.write("--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)
    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
        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 || (c.is_a?(Integer) && c < 0))
        _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 retry_if_interrupted(&block)
    tries = 0
    begin
      yield block
    rescue Errno::EINTR
      tries += 1
      retry if tries <= 10
    end
  end

  def save_tty_chars()
    @_rl_last_tty_chars = @_rl_tty_chars
    h = {}
    retry_if_interrupted do
      h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten]
    end
    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
    retry_if_interrupted do
      @otio = `stty -g`
    end
  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)
    retry_if_interrupted do
      @readline_echoing_p = (`stty -a`.scan(/-*echo\b/).first == 'echo')
    end

    # 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.
    retry_if_interrupted do
      if (`stty -a`.scan(/-parenb\b/).first == '-parenb')
        setting << " pass8"
      end
    end

    setting << " -ixoff"

    rl_bind_key(@_rl_tty_chars.t_start, :rl_restart_output) unless @_rl_tty_chars.t_start.nil?
    @_rl_eof_char = @_rl_tty_chars.t_eof

    #setting << " -isig"

    retry_if_interrupted do
      `stty #{setting}`
    end
  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
    retry_if_interrupted do
      `stty #{@otio}`
    end

    @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)
      _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 { 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 history_arg_extract(first, last, string)
    if first != "$" || last != "$"
      fail "RbReadline.history_arg_extract called with currently unsupported args."
    end

    # Find the last index of an unescaped quote character.
    last_unescaped_quote_char = -1
    RbReadline::HISTORY_QUOTE_CHARACTERS.each_char do |quote_char|
      quote_char = Regexp.escape(quote_char)
      if index = string =~ /(?:\\.|[^#{quote_char}\\])#{quote_char} *$/
        last_unescaped_quote_char = index if index > last_unescaped_quote_char
      end
    end
    last_unescaped_quote_char += 1 # Because of the regex used above.

    # Find the last index of an unescaped word delimiter.
    delimiters = RbReadline::HISTORY_WORD_DELIMITERS.chars.to_a.map { |d| Regexp.escape(d) }
    unless last_unescaped_delimiter = string =~ /(?:\\.|[^#{delimiters.join}])+? *$/
      last_unescaped_delimiter = 0
    end

    if last_unescaped_quote_char >= last_unescaped_delimiter
      quoted_arg = _extract_last_quote(string, string[last_unescaped_quote_char,1])
    end
    quoted_arg or string[last_unescaped_delimiter...string.length]
  end

  def _extract_last_quote(string, quote_char)
    quote_char = Regexp.escape(quote_char)
    string =~ /(#{quote_char}(?:\\.|[^#{quote_char}])+?#{quote_char}) *$/
    $1
  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 (mbchar.is_a?(Integer) && c < 0) || mbchar == 0.chr
      return -1
    end

    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)
        if key.is_a?(Integer) && key < 0
          return -1
        end
        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-U"
      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)
      return temp_string_index unless File.exists? filename

      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'
      x = string.scan(/./me)
      i, len = 0, x.length
      while (pos < point && i < len)
        pos += x[i].length
        i += 1
      end
    when 'S'
      x = string.scan(/./ms)
      i, len = 0, x.length
      while (pos < point && i < len)
        pos += x[i].length
        i += 1
      end
    when 'U'
      x = string.scan(/./mu)
      i, len = 0, x.length
      while (pos < point && i < len)
        pos += x[i].length
        i += 1
      end
    when 'X'
      enc = string.encoding
      str = string.force_encoding(@encoding_name)
      len = str.length
      if point <= length / 2
        # count byte size from head
        i = 0
        while (pos < point && i < len)
          pos += str[i].bytesize
          i += 1
        end
      else
        # count byte size from tail
        pos = str.bytesize
        i = len - 1
        while (pos > point && i >= 0)
          pos -= str[i].bytesize
          i -= 1
        end
        pos += str[i + 1].bytesize if pos < point
      end
      string.force_encoding(enc)
    else
      pos = point
    end
    pos - point
  end

  # Find next `count' characters started byte point of the specified seed.
  #   If flags is MB_FIND_NONZERO, we look for non-zero-width multibyte
  #   characters.
  def _rl_find_next_mbchar(string, seed, count, flags)
    if @encoding == 'N'
      return (seed + count)
    end
    seed = 0 if seed < 0
    return seed if count <= 0

    point = seed + _rl_adjust_point(string,seed)
    if (seed < point)
      count -= 1
    end

    str = (flags == MB_FIND_NONZERO) ? string.sub(/\x00+$/,'') : string

    case @encoding
    when 'E'
      point += str[point..-1].scan(/./me)[0,count].to_s.length
    when 'S'
      point += str[point..-1].scan(/./ms)[0,count].to_s.length
    when 'U'
      point += str[point..-1].scan(/./mu)[0,count].to_s.length
    when 'X'
      point += str[point..-1].force_encoding(@encoding_name)[0,count].bytesize
    else
      point += count
      point = str.length if point >= 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)
      c = rl_read_key()
      rl_unsetstate(RL_STATE_MOREINPUT)

      break if c.is_a?(Integer) && c < 0

      mbchar << c
      mb_len += 1
      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
    (0...mlen).each do
      mb << c
      if _rl_get_char_len(mb) == -2
        # Read more for multibyte character
        rl_setstate(RL_STATE_MOREINPUT)
        c = rl_read_key()
        break if c.is_a?(Integer) && c < 0
        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,:rl_point

  def no_terminal?
    term = ENV["TERM"]
    term.nil? || (term == 'dumb') || (RUBY_PLATFORM =~ /mswin|mingw/)
  end
  private :no_terminal?

end
