# 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 # class Fixnum def ord; self; end end module RbReadline require 'etc' RL_LIBRARY_VERSION = "5.2" RL_READLINE_VERSION = 0x0502 RB_READLINE_VERSION = "0.4.0" EOF = "\xFF" ESC = "\C-[" PAGE = "\C-L" SPACE = "\x20" RETURN = "\C-M" ABORT_CHAR = "\C-G" TAB = "\t" RUBOUT = "\x7f" NEWLINE = "\n" DEFAULT_BUFFER_SIZE = 256 DEFAULT_MAX_KILLS = 10 MB_FIND_NONZERO = 1 MB_FIND_ANY = 0 MB_LEN_MAX = 4 DEFAULT_INPUTRC = "~/.inputrc" SYS_INPUTRC = "/etc/inputrc" UpCase = 1 DownCase = 2 CapCase = 3 # Possible history errors passed to hist_error. EVENT_NOT_FOUND = 0 BAD_WORD_SPEC = 1 SUBST_FAILED = 2 BAD_MODIFIER = 3 NO_PREV_SUBST = 4 # Possible definitions for history starting point specification. ANCHORED_SEARCH = 1 NON_ANCHORED_SEARCH = 0 # Possible definitions for what style of writing the history file we want. HISTORY_APPEND = 0 HISTORY_OVERWRITE = 1 # Input error; can be returned by (*rl_getc_function) if readline is reading # a top-level command (RL_ISSTATE (RL_STATE_READCMD)). READERR = 0xFE.chr # Definitions available for use by readline clients. RL_PROMPT_START_IGNORE = 1.chr RL_PROMPT_END_IGNORE = 2.chr # Possible values for do_replace argument to rl_filename_quoting_function, # called by rl_complete_internal. NO_MATCH = 0 SINGLE_MATCH = 1 MULT_MATCH = 2 # Callback data for reading numeric arguments NUM_SAWMINUS = 0x01 NUM_SAWDIGITS = 0x02 NUM_READONE = 0x04 # A context for reading key sequences longer than a single character when # using the callback interface. KSEQ_DISPATCHED = 0x01 KSEQ_SUBSEQ = 0x02 KSEQ_RECURSIVE = 0x04 # Possible state values for rl_readline_state RL_STATE_NONE = 0x000000 # no state before first call RL_STATE_INITIALIZING = 0x000001 # initializing RL_STATE_INITIALIZED = 0x000002 # initialization done RL_STATE_TERMPREPPED = 0x000004 # terminal is prepped RL_STATE_READCMD = 0x000008 # reading a command key RL_STATE_METANEXT = 0x000010 # reading input after ESC RL_STATE_DISPATCHING = 0x000020 # dispatching to a command RL_STATE_MOREINPUT = 0x000040 # reading more input in a command function RL_STATE_ISEARCH = 0x000080 # doing incremental search RL_STATE_NSEARCH = 0x000100 # doing non-inc search RL_STATE_SEARCH = 0x000200 # doing a history search RL_STATE_NUMERICARG = 0x000400 # reading numeric argument RL_STATE_MACROINPUT = 0x000800 # getting input from a macro RL_STATE_MACRODEF = 0x001000 # defining keyboard macro RL_STATE_OVERWRITE = 0x002000 # overwrite mode RL_STATE_COMPLETING = 0x004000 # doing completion RL_STATE_SIGHANDLER = 0x008000 # in readline sighandler RL_STATE_UNDOING = 0x010000 # doing an undo RL_STATE_INPUTPENDING = 0x020000 # rl_execute_next called RL_STATE_TTYCSAVED = 0x040000 # tty special chars saved RL_STATE_CALLBACK = 0x080000 # using the callback interface RL_STATE_VIMOTION = 0x100000 # reading vi motion arg RL_STATE_MULTIKEY = 0x200000 # reading multiple-key command RL_STATE_VICMDONCE = 0x400000 # entered vi command mode at least once RL_STATE_DONE = 0x800000 # done accepted line NO_BELL = 0 AUDIBLE_BELL = 1 VISIBLE_BELL = 2 # The actions that undo knows how to undo. Notice that UNDO_DELETE means # to insert some text, and UNDO_INSERT means to delete some text. I.e., # the code tells undo what to undo, not how to undo it. UNDO_DELETE, UNDO_INSERT, UNDO_BEGIN, UNDO_END = 0,1,2,3 # Definitions used when searching the line for characters. # NOTE: it is necessary that opposite directions are inverses FTO = 1 # forward to BTO = -1 # backward to FFIND = 2 # forward find BFIND = -2 # backward find # Possible values for the found_quote flags word used by the completion # functions. It says what kind of (shell-like) quoting we found anywhere # in the line. RL_QF_SINGLE_QUOTE = 0x01 RL_QF_DOUBLE_QUOTE = 0x02 RL_QF_BACKSLASH = 0x04 RL_QF_OTHER_QUOTE = 0x08 KEYMAP_SIZE = 257 ANYOTHERKEY = KEYMAP_SIZE-1 @hConsoleHandle = nil @MessageBeep = nil RL_IM_INSERT = 1 RL_IM_OVERWRITE = 0 RL_IM_DEFAULT = RL_IM_INSERT @no_mode = -1 @vi_mode = 0 @emacs_mode = 1 ISFUNC = 0 ISKMAP = 1 ISMACR = 2 HISTORY_WORD_DELIMITERS = " \t\n;&()|<>" HISTORY_QUOTE_CHARACTERS = "\"'`" RL_SEARCH_ISEARCH = 0x01 # incremental search RL_SEARCH_NSEARCH = 0x02 # non-incremental search RL_SEARCH_CSEARCH = 0x04 # intra-line char search # search flags SF_REVERSE = 0x01 SF_FOUND = 0x02 SF_FAILED = 0x04 @slashify_in_quotes = "\\`\"$" @sigint_proc = nil @sigint_blocked = false @rl_prep_term_function = :rl_prep_terminal @rl_deprep_term_function = :rl_deprep_terminal @_rl_history_saved_point = -1 @rl_max_kills = DEFAULT_MAX_KILLS @rl_kill_ring = nil @rl_kill_index = 0 @rl_kill_ring_length = 0 @pending_bytes = '' @stored_count = 0 @_rl_isearch_terminators = nil @_rl_iscxt = nil @last_isearch_string = nil @last_isearch_string_len = 0 @default_isearch_terminators = "\033\012" @_rl_history_preserve_point = false @terminal_prepped = false @otio = nil @msg_saved_prompt = false @_rl_nscxt = nil @noninc_search_string = nil @noninc_history_pos = 0 @prev_line_found = nil rl_history_search_len = 0 rl_history_search_pos = 0 history_search_string = nil history_string_size = 0 @_rl_tty_chars = Struct.new(:t_eol,:t_eol2,:t_erase,:t_werase,:t_kill,:t_reprint,:t_intr,:t_eof, :t_quit,:t_susp,:t_dsusp,:t_start,:t_stop,:t_lnext,:t_flush,:t_status).new @_rl_last_tty_chars = nil @_keyboard_input_timeout = 0.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 , "\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 = nil # Key sequence `contexts' @_rl_kscxt = nil # Non-zero means do not parse any lines other than comments and # parser directives. @_rl_parsing_conditionalized_out = false # Non-zero means to convert characters with the meta bit set to # escape-prefixed characters so we can indirect through # emacs_meta_keymap or vi_escape_keymap. @_rl_convert_meta_chars_to_ascii = true # Non-zero means to output characters with the meta bit set directly # rather than as a meta-prefixed escape sequence. @_rl_output_meta_chars = false # Non-zero means to look at the termios special characters and bind # them to equivalent readline functions at startup. @_rl_bind_stty_chars = true @rl_completion_display_matches_hook = nil XOK = 1 @_rl_term_clreol = nil @_rl_term_clrpag = nil @_rl_term_cr = nil @_rl_term_backspace = nil @_rl_term_goto = nil @_rl_term_pc = nil # Non-zero if we determine that the terminal can do character insertion. @_rl_terminal_can_insert = false # How to insert characters. @_rl_term_im = nil @_rl_term_ei = nil @_rl_term_ic = nil @_rl_term_ip = nil @_rl_term_IC = nil # How to delete characters. @_rl_term_dc = nil @_rl_term_DC = nil @_rl_term_forward_char = nil # How to go up a line. @_rl_term_up = nil # A visible bell; char if the terminal can be made to flash the screen. @_rl_visible_bell = nil # Non-zero means the terminal can auto-wrap lines. @_rl_term_autowrap = true # Non-zero means that this terminal has a meta key. @term_has_meta = 0 # The sequences to write to turn on and off the meta key, if this # terminal has one. @_rl_term_mm = nil @_rl_term_mo = nil # The key sequences output by the arrow keys, if this terminal has any. @_rl_term_ku = nil @_rl_term_kd = nil @_rl_term_kr = nil @_rl_term_kl = nil # How to initialize and reset the arrow keys, if this terminal has any. @_rl_term_ks = nil @_rl_term_ke = nil # The key sequences sent by the Home and End keys, if any. @_rl_term_kh = nil @_rl_term_kH = nil @_rl_term_at7 = nil # Delete key @_rl_term_kD = nil # Insert key @_rl_term_kI = nil # Cursor control @_rl_term_vs = nil # very visible @_rl_term_ve = nil # normal # Variables that hold the screen dimensions, used by the display code. @_rl_screenwidth = @_rl_screenheight = @_rl_screenchars = 0 # Non-zero means the user wants to enable the keypad. @_rl_enable_keypad = false # Non-zero means the user wants to enable a meta key. @_rl_enable_meta = true # **************************************************************** # # Completion matching, from readline's point of view. # # **************************************************************** # Variables known only to the readline library. # If non-zero, non-unique completions always show the list of matches. @_rl_complete_show_all = false # If non-zero, non-unique completions show the list of matches, unless it # is not possible to do partial completion and modify the line. @_rl_complete_show_unmodified = false # If non-zero, completed directory names have a slash appended. @_rl_complete_mark_directories = true # If non-zero, the symlinked directory completion behavior introduced in # readline-4.2a is disabled, and symlinks that point to directories have # a slash appended (subject to the value of _rl_complete_mark_directories). # This is user-settable via the mark-symlinked-directories variable. @_rl_complete_mark_symlink_dirs = false # If non-zero, completions are printed horizontally in alphabetical order, # like `ls -x'. @_rl_print_completions_horizontally = false @_rl_completion_case_fold = false # If non-zero, don't match hidden files (filenames beginning with a `.' on # Unix) when doing filename completion. @_rl_match_hidden_files = true # Global variables available to applications using readline. # Non-zero means add an additional character to each filename displayed # during listing completion iff rl_filename_completion_desired which helps # to indicate the type of file being listed. @rl_visible_stats = false # If non-zero, then this is the address of a function to call when # completing on a directory name. The function is called with # the address of a string (the current directory name) as an arg. @rl_directory_completion_hook = nil @rl_directory_rewrite_hook = nil # Non-zero means readline completion functions perform tilde expansion. @rl_complete_with_tilde_expansion = false # Pointer to the generator function for completion_matches (). # NULL means to use rl_filename_completion_function (), the default filename # completer. @rl_completion_entry_function = nil # Pointer to alternative function to create matches. # Function is called with TEXT, START, and END. # START and END are indices in RL_LINE_BUFFER saying what the boundaries # of TEXT are. # If this function exists and returns NULL then call the value of # rl_completion_entry_function to try to match, otherwise use the # array of strings returned. @rl_attempted_completion_function = nil # Non-zero means to suppress normal filename completion after the # user-specified completion function has been called. @rl_attempted_completion_over = false # Set to a character indicating the type of completion being performed # by rl_complete_internal, available for use by application completion # functions. @rl_completion_type = 0 # Up to this many items will be displayed in response to a # possible-completions call. After that, we ask the user if # she is sure she wants to see them all. A negative value means # don't ask. @rl_completion_query_items = 100 @_rl_page_completions = 1 # The basic list of characters that signal a break between words for the # completer routine. The contents of this variable is what breaks words # in the shell, i.e. " \t\n\"\\'`@$><=" @rl_basic_word_break_characters = " \t\n\"\\'`@$><=|&{(" # }) # List of basic quoting characters. @rl_basic_quote_characters = "\"'" # The list of characters that signal a break between words for # rl_complete_internal. The default list is the contents of # rl_basic_word_break_characters. @rl_completer_word_break_characters = nil # Hook function to allow an application to set the completion word # break characters before readline breaks up the line. Allows # position-dependent word break characters. @rl_completion_word_break_hook = nil # List of characters which can be used to quote a substring of the line. # Completion occurs on the entire substring, and within the substring # rl_completer_word_break_characters are treated as any other character, # unless they also appear within this list. @rl_completer_quote_characters = nil # List of characters that should be quoted in filenames by the completer. @rl_filename_quote_characters = nil # List of characters that are word break characters, but should be left # in TEXT when it is passed to the completion function. The shell uses # this to help determine what kind of completing to do. @rl_special_prefixes = nil # If non-zero, then disallow duplicates in the matches. @rl_ignore_completion_duplicates = true # Non-zero means that the results of the matches are to be treated # as filenames. This is ALWAYS zero on entry, and can only be changed # within a completion entry finder function. @rl_filename_completion_desired = false # Non-zero means that the results of the matches are to be quoted using # double quotes (or an application-specific quoting mechanism) if the # filename contains any characters in rl_filename_quote_chars. This is # ALWAYS non-zero on entry, and can only be changed within a completion # entry finder function. @rl_filename_quoting_desired = true # This function, if defined, is called by the completer when real # filename completion is done, after all the matching names have been # generated. It is passed a (char**) known as matches in the code below. # It consists of a NULL-terminated array of pointers to potential # matching strings. The 1st element (matches[0]) is the maximal # substring that is common to all matches. This function can re-arrange # the list of matches as required, but all elements of the array must be # free()'d if they are deleted. The main intent of this function is # to implement FIGNORE a la SunOS csh. @rl_ignore_some_completions_function = nil # Set to a function to quote a filename in an application-specific fashion. # Called with the text to quote, the type of match found (single or multiple) # and a pointer to the quoting character to be used, which the function can # reset if desired. #rl_filename_quoting_function = rl_quote_filename # Function to call to remove quoting characters from a filename. Called # before completion is attempted, so the embedded quotes do not interfere # with matching names in the file system. Readline doesn't do anything # with this it's set only by applications. @rl_filename_dequoting_function = nil # Function to call to decide whether or not a word break character is # quoted. If a character is quoted, it does not break words for the # completer. @rl_char_is_quoted_p = nil # If non-zero, the completion functions don't append anything except a # possible closing quote. This is set to 0 by rl_complete_internal and # may be changed by an application-specific completion function. @rl_completion_suppress_append = false # Character appended to completed words when at the end of the line. The # default is a space. @rl_completion_append_character = ' ' # If non-zero, the completion functions don't append any closing quote. # This is set to 0 by rl_complete_internal and may be changed by an # application-specific completion function. @rl_completion_suppress_quote = false # Set to any quote character readline thinks it finds before any application # completion function is called. @rl_completion_quote_character = 0 # Set to a non-zero value if readline found quoting anywhere in the word to # be completed set before any application completion function is called. @rl_completion_found_quote = false # If non-zero, a slash will be appended to completed filenames that are # symbolic links to directory names, subject to the value of the # mark-directories variable (which is user-settable). This exists so # that application completion functions can override the user's preference # (set via the mark-symlinked-directories variable) if appropriate. # It's set to the value of _rl_complete_mark_symlink_dirs in # rl_complete_internal before any application-specific completion # function is called, so without that function doing anything, the user's # preferences are honored. @rl_completion_mark_symlink_dirs = false # If non-zero, inhibit completion (temporarily). @rl_inhibit_completion = false # Variables local to this file. # Local variable states what happened during the last completion attempt. @completion_changed_buffer = nil # Non-zero means treat 0200 bit in terminal input as Meta bit. @_rl_meta_flag = false # Stack of previous values of parsing_conditionalized_out. @if_stack = [] @if_stack_depth = 0 # The last key bindings file read. @last_readline_init_file = nil # The file we're currently reading key bindings from. @current_readline_init_file = nil @current_readline_init_include_level = 0 @current_readline_init_lineno = 0 ENV["HOME"] ||= ENV["HOMEDRIVE"]+ENV["HOMEPATH"] @directory = nil @filename = nil @dirname = nil @users_dirname = nil @filename_len = 0 attr_accessor :rl_attempted_completion_function,:rl_deprep_term_function, :rl_event_hook,:rl_attempted_completion_over,:rl_basic_quote_characters, :rl_basic_word_break_characters,:rl_completer_quote_characters, :rl_completer_word_break_characters,:rl_completion_append_character, :rl_filename_quote_characters,:rl_instream,:rl_library_version,:rl_outstream, :rl_readline_name,:history_length,:history_base module_function # Okay, now we write the entry_function for filename completion. In the # general case. Note that completion in the shell is a little different # because of all the pathnames that must be followed when looking up the # completion for a command. def rl_filename_completion_function(text, state) # If we don't have any state, then do some initialization. if (state == 0) # If we were interrupted before closing the directory or reading #all of its contents, close it. if(@directory) @directory.close @directory = nil end text.delete!(0.chr) @filename = text.dup if text.length == 0 text = "." end @dirname = File.dirname(text) # 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 end @directory = Dir.new(@dirname) # Now dequote a non-null filename. if (@filename && @filename.length>0 && @rl_completion_found_quote && @rl_filename_dequoting_function) # delete single and double quotes temp = send(@rl_filename_dequoting_function,@filename, @rl_completion_quote_character) @filename = temp end @filename_len = @filename.length @rl_filename_completion_desired = true end # At this point we should entertain the possibility of hacking wildcarded # filenames, like /usr/man/man/te. If the directory name # contains globbing characters, then build an array of directories, and # then map over that list while completing. # *** UNIMPLEMENTED *** # Now that we have some state, we can read the directory. entry = nil while(@directory && (entry = @directory.read)) d_name = entry # Special case for no filename. If the user has disabled the # `match-hidden-files' variable, skip filenames beginning with `.'. #All other entries except "." and ".." match. if (@filename_len == 0) next if (!@_rl_match_hidden_files && d_name[0,1] == '.') break if (d_name != '.' && d_name != '..') else # Otherwise, if these match up to the length of filename, then # it is a match. if (@_rl_completion_case_fold) break if d_name =~ /^#{Regexp.escape(@filename)}/i else break if d_name =~ /^#{Regexp.escape(@filename)}/ end end end if entry.nil? if @directory @directory.close @directory = nil end @dirname = nil @filename = nil @users_dirname = nil return nil else if (@dirname != '.') if (@rl_complete_with_tilde_expansion && @users_dirname[0,1] == "~") temp = @dirname if(temp[-1,1] != '/') temp += '/' end else temp = @users_dirname if(temp[-1,1] != '/') temp += '/' 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_size = 10 match_list = [] match_list[1] = nil while (string = send(entry_function, text, matches)) match_list[matches+=1] = string match_list[matches+1] = nil end # If there were any matches, then look through them finding out the # lowest common denominator. That then becomes match_list[0]. if (matches!=0) compute_lcd_of_matches(match_list, matches, text) else # There were no matches. match_list = nil end return (match_list) end def _rl_to_lower(char) char.nil? ? nil : char.chr.downcase end # Find the common prefix of the list of matches, and put it into # matches[0]. def compute_lcd_of_matches(match_list, matches, text) # If only one match, just use that. Otherwise, compare each # member of the list with the next, finding out where they # stop matching. if (matches == 1) match_list[0] = match_list[1] match_list[1] = nil return 1 end i = 1 low = 100000 while(i 1) si += v - 1 end else break if (c1 != c2) end si += 1 end else si = 0 while((c1 = match_list[i][si]) && (c2 = match_list[i + 1][si])) if !@rl_byte_oriented if(!_rl_compare_chars(match_list[i],si,match_list[i+1],si)) break elsif ((v = _rl_get_char_len(match_list[i][si..-1])) > 1) si += v - 1 end else break if (c1 != c2) end si += 1 end end if (low > si) low = si end i += 1 end # If there were multiple matches, but none matched up to even the # first character, and the user typed something, use that as the # value of matches[0]. if (low == 0 && text && text.length>0 ) match_list[0] = text.dup else # XXX - this might need changes in the presence of multibyte chars # If we are ignoring case, try to preserve the case of the string # the user typed in the face of multiple matches differing in case. if (@_rl_completion_case_fold) # We're making an assumption here: # IF we're completing filenames AND # the application has defined a filename dequoting function AND # we found a quote character AND # the application has requested filename quoting # THEN # we assume that TEXT was dequoted before checking against # the file system and needs to be dequoted here before we # check against the list of matches # FI if (@rl_filename_completion_desired && @rl_filename_dequoting_function && @rl_completion_found_quote && @rl_filename_quoting_desired) dtext = send(@rl_filename_dequoting_function,text, @rl_completion_quote_character) text = dtext end # sort the list to get consistent answers. match_list = [match_list[0]] + match_list[1..-1].sort si = text.length if (si <= low) for i in 1 .. matches if match_list[i][0,si] == text match_list[0] = match_list[i][0,low] break end # no casematch, use first entry if (i > matches) match_list[0] = match_list[1][0,low] end end else # otherwise, just use the text the user typed. match_list[0] = text[0,low] end else match_list[0] = match_list[1][0,low] end end return matches end def rl_vi_editing_mode(count, key) _rl_set_insert_mode(RL_IM_INSERT, 1) # vi mode ignores insert mode @rl_editing_mode = @vi_mode rl_vi_insertion_mode(1, key) 0 end # Switching from one mode to the other really just involves # switching keymaps. def rl_vi_insertion_mode(count, key) @_rl_keymap = @vi_insertion_keymap @_rl_vi_last_key_before_insert = key 0 end def rl_emacs_editing_mode(count, key) @rl_editing_mode = @emacs_mode _rl_set_insert_mode(RL_IM_INSERT, 1) # emacs mode default is insert mode @_rl_keymap = @emacs_standard_keymap 0 end # Function for the rest of the library to use to set insert/overwrite mode. def _rl_set_insert_mode(im, force) @rl_insert_mode = im end # Toggle overwrite mode. A positive explicit argument selects overwrite # mode. A negative or zero explicit argument selects insert mode. def rl_overwrite_mode(count, key) if (!@rl_explicit_arg) _rl_set_insert_mode(@rl_insert_mode ^ 1, 0) elsif (count > 0) _rl_set_insert_mode(RL_IM_OVERWRITE, 0) else _rl_set_insert_mode(RL_IM_INSERT, 0) end 0 end # A function for simple tilde expansion. def rl_tilde_expand(ignore, key) _end = @rl_point start = _end - 1 if (@rl_point == @rl_end && @rl_line_buffer[@rl_point,1] == '~' ) homedir = File.expand_path("~") _rl_replace_text(homedir, start, _end) return (0) elsif (@rl_line_buffer[start,1] != '~') while(!whitespace(@rl_line_buffer[start,1]) && start >= 0) start -= 1 end start+=1 end _end = start begin _end+=1 end while(!whitespace(@rl_line_buffer[_end,1]) && _end < @rl_end) if (whitespace(@rl_line_buffer[_end,1]) || _end >= @rl_end) _end-=1 end # If the first character of the current word is a tilde, perform #tilde expansion and insert the result. If not a tilde, do # nothing. if (@rl_line_buffer[start,1] == '~') len = _end - start + 1 temp = @rl_line_buffer[start,len] homedir = File.expand_path(temp) temp = nil _rl_replace_text(homedir, start, _end) end 0 end # Clean up the terminal and readline state after catching a signal, before # resending it to the calling application. def rl_cleanup_after_signal() _rl_clean_up_for_exit() if (@rl_deprep_term_function) send(@rl_deprep_term_function) end rl_clear_pending_input() rl_clear_signals() end def _rl_clean_up_for_exit() if @readline_echoing_p _rl_move_vert(@_rl_vis_botlin) @_rl_vis_botlin = 0 @rl_outstream.flush rl_restart_output(1, 0) end end # Move the cursor from _rl_last_c_pos to NEW, which are buffer indices. # (Well, when we don't have multibyte characters, _rl_last_c_pos is a # buffer index.) # DATA is the contents of the screen line of interest; i.e., where # the movement is being done. def _rl_move_cursor_relative(new, data, start=0) woff = w_offset(@_rl_last_v_pos, @wrap_offset) cpos = @_rl_last_c_pos if !@rl_byte_oriented dpos = _rl_col_width(data, start, start+new) if (dpos > @prompt_last_invisible) # XXX - don't use woff here dpos -= woff # Since this will be assigned to _rl_last_c_pos at the end (more # precisely, _rl_last_c_pos == dpos when this function returns), # let the caller know. @cpos_adjusted = true end else dpos = new end # If we don't have to do anything, then return. if (cpos == dpos) return end if @hConsoleHandle csbi = 0.chr * 24 @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) x,y = csbi[4,4].unpack('SS') x = dpos @SetConsoleCursorPosition.Call(@hConsoleHandle,y*65536+x) @_rl_last_c_pos = dpos return end # It may be faster to output a CR, and then move forwards instead # of moving backwards. # i == current physical cursor position. if !@rl_byte_oriented i = @_rl_last_c_pos else i = @_rl_last_c_pos - woff end if (dpos == 0 || cr_faster(dpos, @_rl_last_c_pos) || (@_rl_term_autowrap && i == @_rl_screenwidth)) @rl_outstream.write(@_rl_term_cr) cpos = @_rl_last_c_pos = 0 end if (cpos < dpos) # Move the cursor forward. We do it by printing the command # to move the cursor forward if there is one, else print that # portion of the output buffer again. Which is cheaper? # The above comment is left here for posterity. It is faster # to print one character (non-control) than to print a control # sequence telling the terminal to move forward one character. # That kind of control is for people who don't know what the # data is underneath the cursor. # However, we need a handle on where the current display position is # in the buffer for the immediately preceding comment to be true. # In multibyte locales, we don't currently have that info available. # Without it, we don't know where the data we have to display begins # in the buffer and we have to go back to the beginning of the screen # line. In this case, we can use the terminal sequence to move forward # if it's available. if !@rl_byte_oriented if (@_rl_term_forward_char) @rl_outstream.write(@_rl_term_forward_char * (dpos-cpos)) else @rl_outstream.write(@_rl_term_cr) @rl_outstream.write(data[start,new]) end else @rl_outstream.write(data[start+cpos,new-cpos]) end elsif (cpos > dpos) _rl_backspace(cpos - dpos) end @_rl_last_c_pos = dpos end # PWP: move the cursor up or down. def _rl_move_vert(to) if (@_rl_last_v_pos == to || to > @_rl_screenheight) return end if ((delta = to - @_rl_last_v_pos) > 0) @rl_outstream.write("\n"*delta) @rl_outstream.write("\r") @_rl_last_c_pos = 0 else if(@_rl_term_up) @rl_outstream.write(@_rl_term_up*(-delta)) end end @_rl_last_v_pos = to # Now TO is here end def rl_setstate(x) (@rl_readline_state |= (x)) end def rl_unsetstate(x) (@rl_readline_state &= ~(x)) end def rl_isstate(x) (@rl_readline_state & (x))!=0 end # Clear any pending input pushed with rl_execute_next() def rl_clear_pending_input() @rl_pending_input = 0 rl_unsetstate(RL_STATE_INPUTPENDING) 0 end def rl_restart_output(count, key) 0 end def rl_clear_signals() if Signal.list['WINCH'] trap "WINCH",@def_proc end end def rl_set_signals() if Signal.list['WINCH'] @def_proc = trap "WINCH",Proc.new{rl_sigwinch_handler(0)} end end # Current implementation: # \001 (^A) start non-visible characters # \002 (^B) end non-visible characters # all characters except \001 and \002 (following a \001) are copied to # the returned string all characters except those between \001 and # \002 are assumed to be `visible'. def expand_prompt(pmt) # Short-circuit if we can. if (@rl_byte_oriented && pmt[RL_PROMPT_START_IGNORE].nil?) r = pmt.dup lp = r.length lip = 0 niflp = 0 vlp = lp return [r,lp,lip,niflp,vlp] end l = pmt.length ret = '' invfl = 0 # invisible chars in first line of prompt invflset = 0 # we only want to set invfl once igstart = 0 rl = 0 ignoring = false last = ninvis = physchars = 0 for pi in 0 ... pmt.length # This code strips the invisible character string markers #RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE if (!ignoring && pmt[pi,1] == RL_PROMPT_START_IGNORE) # XXX - check ignoring? ignoring = true igstart = pi next elsif (ignoring && pmt[pi,1] == RL_PROMPT_END_IGNORE) ignoring = false if (pi != (igstart + 1)) last = ret.length - 1 end next else if !@rl_byte_oriented pind = pi ind = _rl_find_next_mbchar(pmt, pind, 1, MB_FIND_NONZERO) l = ind - pind while (l>0) l-=1 ret << pmt[pi] pi += 1 end if (!ignoring) rl += ind - pind physchars += _rl_col_width(pmt, pind, ind) else ninvis += ind - pind end pi-=1 # compensate for later increment else ret << pmt[pi] if (!ignoring) rl+=1 # visible length byte counter physchars+=1 else ninvis+=1 # invisible chars byte counter end if (invflset == 0 && rl >= @_rl_screenwidth) invfl = ninvis invflset = 1 end end end end if (rl < @_rl_screenwidth) invfl = ninvis end lp = rl lip = last niflp = invfl vlp = physchars return [ret,lp,lip,niflp,vlp] end #* #* Expand the prompt string into the various display components, if #* necessary. #* #* local_prompt = expanded last line of string in rl_display_prompt #* (portion after the final newline) #* local_prompt_prefix = portion before last newline of rl_display_prompt, #* expanded via expand_prompt #* prompt_visible_length = number of visible characters in local_prompt #* prompt_prefix_length = number of visible characters in local_prompt_prefix #* #* This function is called once per call to readline(). It may also be #* called arbitrarily to expand the primary prompt. #* #* The return value is the number of visible characters on the last line #* of the (possibly multi-line) prompt. #* def rl_expand_prompt(prompt) @local_prompt = @local_prompt_prefix = nil @local_prompt_len = 0 @prompt_last_invisible = @prompt_invis_chars_first_line = 0 @prompt_visible_length = @prompt_physical_chars = 0 if (prompt.nil? || prompt == '') return (0) end pi = prompt.rindex("\n") if pi.nil? # The prompt is only one logical line, though it might wrap. @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = expand_prompt(prompt) @local_prompt_prefix = nil @local_prompt_len = @local_prompt ? @local_prompt.length : 0 return (@prompt_visible_length) else # The prompt spans multiple lines. pi += 1 if prompt.length!=pi+1 t = pi @local_prompt,@prompt_visible_length,@prompt_last_invisible,_,@prompt_physical_chars = expand_prompt(prompt[pi..-1]) c = prompt[t] prompt[t] = 0.chr # The portion of the prompt string up to and including the #final newline is now null-terminated. @local_prompt_prefix,@prompt_prefix_length,_,@prompt_invis_chars_first_line, = expand_prompt(prompt) prompt[t] = c @local_prompt_len = @local_prompt ? @local_prompt.length : 0 return (@prompt_prefix_length) end end # Set up the prompt and expand it. Called from readline() and # rl_callback_handler_install (). def rl_set_prompt(prompt) @rl_prompt = prompt ? prompt.dup : nil @rl_display_prompt = @rl_prompt ? @rl_prompt : "" @rl_visible_prompt_length = rl_expand_prompt(@rl_prompt) 0 end def get_term_capabilities(buffer) hash = {} `infocmp -C`.split(':').select{|x| x =~ /(.*)=(.*)/ and hash[$1]=$2.gsub('\\E',"\e").gsub(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} @_rl_term_at7 = hash["@7"] @_rl_term_DC = hash["DC"] @_rl_term_IC = hash["IC"] @_rl_term_clreol = hash["ce"] @_rl_term_clrpag = hash["cl"] @_rl_term_cr = hash["cr"] @_rl_term_dc = hash["dc"] @_rl_term_ei = hash["ei"] @_rl_term_ic = hash["ic"] @_rl_term_im = hash["im"] @_rl_term_kD = hash["kD"] @_rl_term_kH = hash["kH"] @_rl_term_kI = hash["kI"] @_rl_term_kd = hash["kd"] @_rl_term_ke = hash["ke"] @_rl_term_kh = hash["kh"] @_rl_term_kl = hash["kl"] @_rl_term_kr = hash["kr"] @_rl_term_ks = hash["ks"] @_rl_term_ku = hash["ku"] @_rl_term_backspace = hash["le"] @_rl_term_mm = hash["mm"] @_rl_term_mo = hash["mo"] @_rl_term_forward_char = hash["nd"] @_rl_term_pc = hash["pc"] @_rl_term_up = hash["up"] @_rl_visible_bell = hash["vb"] @_rl_term_vs = hash["vs"] @_rl_term_ve = hash["ve"] @tcap_initialized = true end # Set the environment variables LINES and COLUMNS to lines and cols, # respectively. def sh_set_lines_and_columns(lines, cols) ENV["LINES"] = lines.to_s ENV["COLUMNS"] = cols.to_s end # Get readline's idea of the screen size. TTY is a file descriptor open # to the terminal. If IGNORE_ENV is true, we do not pay attention to the # values of $LINES and $COLUMNS. The tests for TERM_STRING_BUFFER being # non-null serve to check whether or not we have initialized termcap. def _rl_get_screen_size(tty, ignore_env) if @hConsoleHandle csbi = 0.chr * 24 @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) wc,wr = csbi[0,4].unpack('SS') # wr,wc, = `mode con`.scan(/\d+\n/).map{|x| x.to_i} @_rl_screenwidth = wc @_rl_screenheight = wr else wr,wc = `stty size`.split(' ').map{|x| x.to_i} @_rl_screenwidth = wc @_rl_screenheight = wr if ignore_env==0 && ENV['LINES'] @_rl_screenheight = ENV['LINES'].to_i end if ignore_env==0 && ENV['COLUMNS'] @_rl_screenwidth = ENV['COLUMNS'].to_i end end # If all else fails, default to 80x24 terminal. if @_rl_screenwidth.nil? || @_rl_screenwidth <= 1 @_rl_screenwidth = 80 end if @_rl_screenheight.nil? || @_rl_screenheight <= 0 @_rl_screenheight = 24 end # If we're being compiled as part of bash, set the environment # variables $LINES and $COLUMNS to new values. Otherwise, just # do a pair of putenv () or setenv () calls. sh_set_lines_and_columns(@_rl_screenheight, @_rl_screenwidth) if !@_rl_term_autowrap @_rl_screenwidth-=1 end @_rl_screenchars = @_rl_screenwidth * @_rl_screenheight end def tgetflag(name) `infocmp -C -r`.scan(/\w{2}/).include?(name) end # Return the function (or macro) definition which would be invoked via # KEYSEQ if executed in MAP. If MAP is NULL, then the current keymap is # used. TYPE, if non-NULL, is a pointer to an int which will receive the # type of the object pointed to. One of ISFUNC (function), ISKMAP (keymap), # or ISMACR (macro). def rl_function_of_keyseq(keyseq, map, type) map ||= @_rl_keymap map[keyseq] end # Bind the key sequence represented by the string KEYSEQ to # the arbitrary pointer DATA. TYPE says what kind of data is # pointed to by DATA, right now this can be a function (ISFUNC), # a macro (ISMACR), or a keymap (ISKMAP). This makes new keymaps # as necessary. The initial place to do bindings is in MAP. def rl_generic_bind(type, keyseq, data, map) map[keyseq] = data 0 end # Bind the key sequence represented by the string KEYSEQ to # FUNCTION. This makes new keymaps as necessary. The initial # place to do bindings is in MAP. def rl_bind_keyseq_in_map(keyseq, function, map) rl_generic_bind(ISFUNC, keyseq, function, map) end # Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right # now, this is always used to attempt to bind the arrow keys, hence the # check for rl_vi_movement_mode. def rl_bind_keyseq_if_unbound_in_map(keyseq, default_func, kmap) if (keyseq) func = rl_function_of_keyseq(keyseq, kmap, nil) if (func.nil? || func == :rl_vi_movement_mode) return (rl_bind_keyseq_in_map(keyseq, default_func, kmap)) else return 1 end end 0 end def rl_bind_keyseq_if_unbound(keyseq, default_func) rl_bind_keyseq_if_unbound_in_map(keyseq, default_func, @_rl_keymap) end # Bind the arrow key sequences from the termcap description in MAP. def bind_termcap_arrow_keys(map) xkeymap = @_rl_keymap @_rl_keymap = map rl_bind_keyseq_if_unbound(@_rl_term_ku, :rl_get_previous_history) rl_bind_keyseq_if_unbound(@_rl_term_kd, :rl_get_next_history) rl_bind_keyseq_if_unbound(@_rl_term_kr, :rl_forward_char) rl_bind_keyseq_if_unbound(@_rl_term_kl, :rl_backward_char) rl_bind_keyseq_if_unbound(@_rl_term_kh, :rl_beg_of_line) # Home rl_bind_keyseq_if_unbound(@_rl_term_at7, :rl_end_of_line) # End rl_bind_keyseq_if_unbound(@_rl_term_kD, :rl_delete) rl_bind_keyseq_if_unbound(@_rl_term_kI, :rl_overwrite_mode) @_rl_keymap = xkeymap end def _rl_init_terminal_io(terminal_name) term = terminal_name ? terminal_name : ENV["TERM"] @_rl_term_clrpag = @_rl_term_cr = @_rl_term_clreol = nil tty = @rl_instream ? @rl_instream.fileno : 0 if no_terminal? term = "dumb" @_rl_bind_stty_chars = false end @term_string_buffer ||= 0.chr * 2032 @term_buffer ||= 0.chr * 4080 buffer = @term_string_buffer tgetent_ret = (term != "dumb") ? 1 : -1 if (tgetent_ret <= 0) buffer = @term_buffer = @term_string_buffer = nil @_rl_term_autowrap = false # used by _rl_get_screen_size # Allow calling application to set default height and width, using #rl_set_screen_size if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) _rl_get_screen_size(tty, 0) end # Defaults. if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) @_rl_screenwidth = 79 @_rl_screenheight = 24 end # Everything below here is used by the redisplay code (tputs). @_rl_screenchars = @_rl_screenwidth * @_rl_screenheight @_rl_term_cr = "\r" @_rl_term_im = @_rl_term_ei = @_rl_term_ic = @_rl_term_IC = nil @_rl_term_up = @_rl_term_dc = @_rl_term_DC = @_rl_visible_bell = nil @_rl_term_ku = @_rl_term_kd = @_rl_term_kl = @_rl_term_kr = nil @_rl_term_kh = @_rl_term_kH = @_rl_term_kI = @_rl_term_kD = nil @_rl_term_ks = @_rl_term_ke = @_rl_term_at7 = nil @_rl_term_mm = @_rl_term_mo = nil @_rl_term_ve = @_rl_term_vs = nil @_rl_term_forward_char = nil @_rl_terminal_can_insert = @term_has_meta = false # Reasonable defaults for tgoto(). Readline currently only uses # tgoto if _rl_term_IC or _rl_term_DC is defined, but just in case we # change that later... @_rl_term_backspace = "\b" return 0 end get_term_capabilities(buffer) @_rl_term_cr ||= "\r" @_rl_term_autowrap = !!(tgetflag("am") && tgetflag("xn")) # Allow calling application to set default height and width, using # rl_set_screen_size if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) _rl_get_screen_size(tty, 0) end # "An application program can assume that the terminal can do # character insertion if *any one of* the capabilities `IC', # `im', `ic' or `ip' is provided." But we can't do anything if # only `ip' is provided, so... @_rl_terminal_can_insert = !!(@_rl_term_IC || @_rl_term_im || @_rl_term_ic) # Check to see if this terminal has a meta key and clear the capability # variables if there is none. @term_has_meta = !!(tgetflag("km") || tgetflag("MT")) if !@term_has_meta @_rl_term_mm = @_rl_term_mo = nil end # Attempt to find and bind the arrow keys. Do not override already # bound keys in an overzealous attempt, however. bind_termcap_arrow_keys(@emacs_standard_keymap) bind_termcap_arrow_keys(@vi_movement_keymap) bind_termcap_arrow_keys(@vi_insertion_keymap) return 0 end # New public way to set the system default editing chars to their readline # equivalents. def rl_tty_set_default_bindings(kmap) h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten] h.each {|k,v| v.gsub!(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} kmap[h['erase']] = :rl_rubout kmap[h['kill']] = :rl_unix_line_discard kmap[h['werase']] = :rl_unix_word_rubout kmap[h['lnext']] = :rl_quoted_insert end # If this system allows us to look at the values of the regular # input editing characters, then bind them to their readline # equivalents, iff the characters are not bound to keymaps. def readline_default_bindings() if @_rl_bind_stty_chars rl_tty_set_default_bindings(@_rl_keymap) end end def _rl_init_eightbit() end # Do key bindings from a file. If FILENAME is NULL it defaults # to the first non-null filename from this list: # 1. the filename used for the previous call # 2. the value of the shell variable `INPUTRC' # 3. ~/.inputrc # 4. /etc/inputrc # If the file existed and could be opened and read, 0 is returned, # otherwise errno is returned. def rl_read_init_file(filename) # 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 = File.open(openname).read rescue return -1 end if (include_level == 0 && filename != @last_readline_init_file) @last_readline_init_file = filename.dup end @currently_reading_init_file = true # Loop over the lines in the file. Lines that start with `#' are # comments; all other lines are commands for readline initialization. @current_readline_init_lineno = 1 buffer.each_line do |line| line.strip! next if line =~ /^#/ next if line == '' rl_parse_and_bind(line) end return 0 end # Push _rl_parsing_conditionalized_out, and set parser state based # on ARGS. def parser_if(args) # Push parser state. @if_stack << @_rl_parsing_conditionalized_out # If parsing is turned off, then nothing can turn it back on except # for finding the matching endif. In that case, return right now. if @_rl_parsing_conditionalized_out return 0 end args.downcase! # Handle "$if term=foo" and "$if mode=emacs" constructs. If this # isn't term=foo, or mode=emacs, then check to see if the first # word in ARGS is the same as the value stored in rl_readline_name. if (@rl_terminal_name && args =~ /^term=/) # Terminals like "aaa-60" are equivalent to "aaa". tname = @rl_terminal_name.downcase.gsub(/-.*$/,'') # Test the `long' and `short' forms of the terminal name so that #if someone has a `sun-cmd' and does not want to have bindings #that will be executed if the terminal is a `sun', they can put #`$if term=sun-cmd' into their .inputrc. @_rl_parsing_conditionalized_out = (args[5..-1] != tname && args[5..-1] != @rl_terminal_name.downcase) elsif args =~ /^mode=/ if args[5..-1] == "emacs" mode = @emacs_mode elsif args[5..-1] == "vi" 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" @_rl_keymap = @vi_insertion_keymap @rl_editing_mode = @vi_mode when "emacs" @_rl_keymap = @emacs_standard_keymap @rl_editing_mode = @emacs_mode end when "isearch-terminators" @_rl_isearch_terminators = instance_eval(value) when "keymap" case value when "emacs","emacs-standard","emacs-meta","emacs-ctlx" @_rl_keymap = @emacs_standard_keymap when "vi","vi-move","vi-command" @_rl_keymap = @vi_movement_keymap when "vi-insert" @_rl_keymap = @vi_insertion_keymap end end end def rl_named_function(name) case name when "accept-line" return :rl_newline when "arrow-key-prefix" return :rl_arrow_keys when "backward-delete-char" return :rl_rubout when "character-search" return :rl_char_search when "character-search-backward" return :rl_backward_char_search when "copy-region-as-kill" return :rl_copy_region_to_kill when "delete-char" return :rl_delete when "delete-char-or-list" return :rl_delete_or_show_completions when "forward-backward-delete-char" return :rl_rubout_or_delete when "kill-whole-line" return :rl_kill_full_line when "non-incremental-forward-search-history" return :rl_noninc_forward_search when "non-incremental-reverse-search-history" return :rl_noninc_reverse_search when "non-incremental-forward-search-history-again" return :rl_noninc_forward_search_again when "non-incremental-reverse-search-history-again" return :rl_noninc_reverse_search_again when "redraw-current-line" return :rl_refresh_line when "self-insert" return :rl_insert when "undo" return :rl_undo_command when "beginning-of-line" return :rl_beg_of_line else if name =~ /^[-a-z]+$/ return ('rl_'+name.gsub('-','_')).to_sym end end nil end # Bind KEY to FUNCTION. Returns non-zero if KEY is out of range. def rl_bind_key(key, function) @_rl_keymap[key] = function @rl_binding_keymap = @_rl_keymap 0 end # Read the binding command from STRING and perform it. # A key binding command looks like: Keyname: function-name\0, # a variable binding command looks like: set variable value. # A new-style keybinding looks like "\C-x\C-x": exchange-point-and-mark. def rl_parse_and_bind(string) # If this is a parser directive, act on it. if (string[0,1] == "$") handle_parser_directive(string[1..-1]) return 0 end # If we aren't supposed to be parsing right now, then we're done. return 0 if @_rl_parsing_conditionalized_out if string =~ /^set/i _,var,value = string.downcase.split(' ') rl_variable_bind(var, value) return 0 end if string =~ /"(.*)"\s*:\s*(.*)$/ key, funname = $1, $2 rl_bind_key(key, rl_named_function(funname)) end 0 end def _rl_enable_meta_key() if(@term_has_meta && @_rl_term_mm) @_rl_out_stream.write(@_rl_term_mm) end end def rl_set_keymap_from_edit_mode() if (@rl_editing_mode == @emacs_mode) @_rl_keymap = @emacs_standard_keymap elsif (@rl_editing_mode == @vi_mode) @_rl_keymap = @vi_insertion_keymap end end def rl_get_keymap_name_from_edit_mode() if (@rl_editing_mode == @emacs_mode) "emacs" elsif (@rl_editing_mode == @vi_mode) "vi" else "none" end end # Bind some common arrow key sequences in MAP. def bind_arrow_keys_internal(map) xkeymap = @_rl_keymap @_rl_keymap = map if RUBY_PLATFORM =~ /mswin|mingw/ rl_bind_keyseq_if_unbound("\340H", :rl_get_previous_history) # Up rl_bind_keyseq_if_unbound("\340P", :rl_get_next_history) # Down rl_bind_keyseq_if_unbound("\340M", :rl_forward_char) # Right rl_bind_keyseq_if_unbound("\340K", :rl_backward_char) # Left rl_bind_keyseq_if_unbound("\340G", :rl_beg_of_line) # Home rl_bind_keyseq_if_unbound("\340O", :rl_end_of_line) # End rl_bind_keyseq_if_unbound("\340s", :rl_backward_word) # Ctrl-Left rl_bind_keyseq_if_unbound("\340t", :rl_forward_word) # Ctrl-Right rl_bind_keyseq_if_unbound("\340S", :rl_delete) # Delete rl_bind_keyseq_if_unbound("\340R", :rl_overwrite_mode) # Insert else rl_bind_keyseq_if_unbound("\033[A", :rl_get_previous_history) rl_bind_keyseq_if_unbound("\033[B", :rl_get_next_history) rl_bind_keyseq_if_unbound("\033[C", :rl_forward_char) rl_bind_keyseq_if_unbound("\033[D", :rl_backward_char) rl_bind_keyseq_if_unbound("\033[H", :rl_beg_of_line) rl_bind_keyseq_if_unbound("\033[F", :rl_end_of_line) rl_bind_keyseq_if_unbound("\033OA", :rl_get_previous_history) rl_bind_keyseq_if_unbound("\033OB", :rl_get_next_history) rl_bind_keyseq_if_unbound("\033OC", :rl_forward_char) rl_bind_keyseq_if_unbound("\033OD", :rl_backward_char) rl_bind_keyseq_if_unbound("\033OH", :rl_beg_of_line) rl_bind_keyseq_if_unbound("\033OF", :rl_end_of_line) end @_rl_keymap = xkeymap end # Try and bind the common arrow key prefixes after giving termcap and # the inputrc file a chance to bind them and create `real' keymaps # for the arrow key prefix. def bind_arrow_keys() bind_arrow_keys_internal(@emacs_standard_keymap) bind_arrow_keys_internal(@vi_movement_keymap) bind_arrow_keys_internal(@vi_insertion_keymap) end # Initialize the entire state of the world. def readline_initialize_everything() # Set up input and output if they are not already set up. @rl_instream ||= $stdin @rl_outstream ||= $stdout # Bind _rl_in_stream and _rl_out_stream immediately. These values # may change, but they may also be used before readline_internal () # is called. @_rl_in_stream = @rl_instream @_rl_out_stream = @rl_outstream # Allocate data structures. @rl_line_buffer = "" # Initialize the terminal interface. @rl_terminal_name ||= ENV["TERM"] _rl_init_terminal_io(@rl_terminal_name) # Bind tty characters to readline functions. readline_default_bindings() # Decide whether we should automatically go into eight-bit mode. _rl_init_eightbit() # Read in the init file. rl_read_init_file(nil) # XXX if (@_rl_horizontal_scroll_mode && @_rl_term_autowrap) @_rl_screenwidth -= 1 @_rl_screenchars -= @_rl_screenheight end # Override the effect of any `set keymap' assignments in the # inputrc file. rl_set_keymap_from_edit_mode() # Try to bind a common arrow key prefix, if not already bound. bind_arrow_keys() # Enable the meta key, if this terminal has one. if @_rl_enable_meta _rl_enable_meta_key() end # If the completion parser's default word break characters haven't # been set yet, then do so now. @rl_completer_word_break_characters ||= @rl_basic_word_break_characters end def _rl_init_line_state() @rl_point = @rl_end = @rl_mark = 0 @rl_line_buffer = "" end # Set the history pointer back to the last entry in the history. def _rl_start_using_history() using_history() @_rl_saved_line_for_history = nil end def cr_faster(new, cur) (new + 1) < (cur - new) end #* _rl_last_c_pos is an absolute cursor position in multibyte locales and a # buffer index in others. This macro is used when deciding whether the # current cursor position is in the middle of a prompt string containing # invisible characters. def prompt_ending_index() if !@rl_byte_oriented @prompt_physical_chars else (@prompt_last_invisible+1) end end # Initialize the VISIBLE_LINE and INVISIBLE_LINE arrays, and their associated # arrays of line break markers. MINSIZE is the minimum size of VISIBLE_LINE # and INVISIBLE_LINE; if it is greater than LINE_SIZE, LINE_SIZE is # increased. If the lines have already been allocated, this ensures that # they can hold at least MINSIZE characters. def init_line_structures(minsize) if @invisible_line.nil? # initialize it if (@line_size < minsize) @line_size = minsize end @visible_line = 0.chr * @line_size @invisible_line = 0.chr * @line_size # 1.chr elsif (@line_size < minsize) # ensure it can hold MINSIZE chars @line_size *= 2 if (@line_size < minsize) @line_size = minsize end @visible_line << 0.chr * (@line_size - @visible_line.length) @invisible_line << 1.chr * (@line_size - @invisible_line.length) end @visible_line[minsize,@line_size-minsize] = 0.chr * (@line_size-minsize) @invisible_line[minsize,@line_size-minsize] = 1.chr * (@line_size-minsize) if @vis_lbreaks.nil? @inv_lbreaks = [] @vis_lbreaks = [] @_rl_wrapped_line = [] @inv_lbreaks[0] = @vis_lbreaks[0] = 0 end end # Return the history entry at the current position, as determined by # history_offset. If there is no entry there, return a NULL pointer. def current_history() return ((@history_offset == @history_length) || @the_history.nil?) ? nil : @the_history[@history_offset] end def meta_char(c) c > "\x7f" && c <= "\xff" end def ctrl_char(c) c < "\x20" end def isprint(c) c >= "\x20" && c < "\x7f" end def whitespace(c) (c == ' ' || c == "\t") end def w_offset(line, offset) ((line) == 0 ? offset : 0) end def vis_llen(l) ((l) > @_rl_vis_botlin ? 0 : (@vis_lbreaks[l+1] - @vis_lbreaks[l])) end def inv_llen(l) (@inv_lbreaks[l+1] - @inv_lbreaks[l]) end def vis_chars(line) @visible_line[@vis_lbreaks[line] .. -1] end def vis_pos(line) @vis_lbreaks[line] end def vis_line(line) ((line) > @_rl_vis_botlin) ? "" : vis_chars(line) end def inv_line(line) @invisible_line[@inv_lbreaks[line] .. -1] end def m_offset(margin, offset) ((margin) == 0 ? offset : 0) end # PWP: update_line() is based on finding the middle difference of each # line on the screen; vis: # # /old first difference # /beginning of line | /old last same /old EOL # v v v v # old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as # new: eddie> Oh, my little buggy says to me, as lurgid as # ^ ^ ^ ^ # \beginning of line | \new last same \new end of line # \new first difference # # All are character pointers for the sake of speed. Special cases for # no differences, as well as for end of line additions must be handled. # # Could be made even smarter, but this works well enough def update_line(old, ostart, new, current_line, omax, nmax, inv_botlin) # If we're at the right edge of a terminal that supports xn, we're # ready to wrap around, so do so. This fixes problems with knowing # the exact cursor position and cut-and-paste with certain terminal # emulators. In this calculation, TEMP is the physical screen # position of the cursor. if @encoding == 'X' old.force_encoding('ASCII-8BIT') new.force_encoding('ASCII-8BIT') end if !@rl_byte_oriented temp = @_rl_last_c_pos else temp = @_rl_last_c_pos - w_offset(@_rl_last_v_pos, @visible_wrap_offset) end if (temp == @_rl_screenwidth && @_rl_term_autowrap && !@_rl_horizontal_scroll_mode && @_rl_last_v_pos == current_line - 1) if (!@rl_byte_oriented) # This fixes only double-column characters, but if the wrapped # character comsumes more than three columns, spaces will be # inserted in the string buffer. if (@_rl_wrapped_line[current_line] > 0) _rl_clear_to_eol(@_rl_wrapped_line[current_line]) end if new[0,1] != 0.chr case @encoding when 'E' wc = new.scan(/./me)[0] ret = wc.length tempwidth = wc.length when 'S' wc = new.scan(/./ms)[0] ret = wc.length tempwidth = wc.length when 'U' wc = new.scan(/./mu)[0] ret = wc.length tempwidth = wc.unpack('U').first >= 0x1000 ? 2 : 1 when 'X' wc = new[0..-1].force_encoding(@encoding_name)[0] ret = wc.bytesize tempwidth = wc.ord >= 0x1000 ? 2 : 1 else ret = 1 tempwidth = 1 end else tempwidth = 0 end if (tempwidth > 0) bytes = ret @rl_outstream.write(new[0,bytes]) @_rl_last_c_pos = tempwidth @_rl_last_v_pos+=1 if old[ostart,1] != 0.chr case @encoding when 'E' wc = old[ostart..-1].scan(/./me)[0] ret = wc.length when 'S' wc = old[ostart..-1].scan(/./ms)[0] ret = wc.length when 'U' wc = old[ostart..-1].scan(/./mu)[0] ret = wc.length when 'X' wc = old[ostart..-1].force_encoding(@encoding_name)[0] ret = wc.bytesize end else ret = 0 end if (ret != 0 && bytes != 0) if ret != bytes len = old[ostart..-1].index(0.chr,ret) old[ostart+bytes,len-ret] = old[ostart+ret,len-ret] end old[ostart,bytes] = new[0,bytes] end else @rl_outstream.write(' ') @_rl_last_c_pos = 1 @_rl_last_v_pos+=1 if (old[ostart,1] != 0.chr && new[0,1] != 0.chr) old[ostart,1] = new[0,1] end end else if (new[0,1] != 0.chr) @rl_outstream.write(new[0,1]) else @rl_outstream.write(' ') end @_rl_last_c_pos = 1 @_rl_last_v_pos+=1 if (old[ostart,1] != 0.chr && new[0,1] != 0.chr) old[ostart,1] = new[0,1] end end end # Find first difference. if (!@rl_byte_oriented) # See if the old line is a subset of the new line, so that the # only change is adding characters. if (index = old.index(0.chr)) && omax+ostart>index omax = index - ostart end if (index = new.index(0.chr)) && nmax>index nmax = index end temp = (omax < nmax) ? omax : nmax if old[ostart,temp]==new[0,temp] ofd = temp nfd = temp else if (omax == nmax && new[0,omax]==old[ostart,omax]) ofd = omax nfd = nmax else new_offset = 0 old_offset = ostart ofd = 0 nfd = 0 while(ofd < omax && old[ostart+ofd,1] != 0.chr && _rl_compare_chars(old, old_offset, new, new_offset)) old_offset = _rl_find_next_mbchar(old, old_offset, 1, MB_FIND_ANY) new_offset = _rl_find_next_mbchar(new, new_offset, 1, MB_FIND_ANY) ofd = old_offset - ostart nfd = new_offset end end end else ofd = 0 nfd = 0 while(ofd < omax && old[ostart+ofd,1] != 0.chr && old[ostart+ofd,1] == new[nfd,1]) ofd += 1 nfd += 1 end end # Move to the end of the screen line. ND and OD are used to keep track # of the distance between ne and new and oe and old, respectively, to # move a subtraction out of each loop. oe = old.index(0.chr,ostart+ofd) - ostart if oe.nil? || oe>omax oe = omax end ne = new.index(0.chr,nfd) if ne.nil? || ne>omax ne = nmax end # If no difference, continue to next line. if (ofd == oe && nfd == ne) return end wsatend = true # flag for trailing whitespace if (!@rl_byte_oriented) ols = _rl_find_prev_mbchar(old, ostart+oe, MB_FIND_ANY) - ostart nls = _rl_find_prev_mbchar(new, ne, MB_FIND_ANY) while ((ols > ofd) && (nls > nfd)) if (!_rl_compare_chars(old, ostart+ols, new, nls)) break end if (old[ostart+ols,1] == " ") wsatend = false end ols = _rl_find_prev_mbchar(old, ols+ostart, MB_FIND_ANY) - ostart nls = _rl_find_prev_mbchar(new, nls, MB_FIND_ANY) end else ols = oe - 1 # find last same nls = ne - 1 while ((ols > ofd) && (nls > nfd) && old[ostart+ols,1] == new[nls,1]) if (old[ostart+ols,1] != " ") wsatend = false end ols-=1 nls-=1 end end if (wsatend) ols = oe nls = ne elsif (!_rl_compare_chars(old, ostart+ols, new, nls)) if (old[ostart+ols,1] != 0.chr) # don't step past the NUL if !@rl_byte_oriented ols = _rl_find_next_mbchar(old, ostart+ols, 1, MB_FIND_ANY) - ostart else ols+=1 end end if (new[nls,1] != 0.chr ) if !@rl_byte_oriented nls = _rl_find_next_mbchar(new, nls, 1, MB_FIND_ANY) else nls+=1 end end end # count of invisible characters in the current invisible line. current_invis_chars = w_offset(current_line, @wrap_offset) if (@_rl_last_v_pos != current_line) _rl_move_vert(current_line) if (@rl_byte_oriented && current_line == 0 && @visible_wrap_offset!=0) @_rl_last_c_pos += @visible_wrap_offset end end # If this is the first line and there are invisible characters in the # prompt string, and the prompt string has not changed, and the current # cursor position is before the last invisible character in the prompt, # and the index of the character to move to is past the end of the prompt # string, then redraw the entire prompt string. We can only do this # reliably if the terminal supports a `cr' capability. # This is not an efficiency hack -- there is a problem with redrawing # portions of the prompt string if they contain terminal escape # sequences (like drawing the `unbold' sequence without a corresponding # `bold') that manifests itself on certain terminals. lendiff = @local_prompt_len if (current_line == 0 && !@_rl_horizontal_scroll_mode && @_rl_term_cr && lendiff > @prompt_visible_length && @_rl_last_c_pos > 0 && ofd >= lendiff && @_rl_last_c_pos < prompt_ending_index()) @rl_outstream.write(@_rl_term_cr) _rl_output_some_chars(@local_prompt,0,lendiff) if !@rl_byte_oriented # We take wrap_offset into account here so we can pass correct # information to _rl_move_cursor_relative. @_rl_last_c_pos = _rl_col_width(@local_prompt, 0, lendiff) - @wrap_offset @cpos_adjusted = true else @_rl_last_c_pos = lendiff end end # When this function returns, _rl_last_c_pos is correct, and an absolute # cursor postion in multibyte mode, but a buffer index when not in a # multibyte locale. _rl_move_cursor_relative(ofd, old, ostart) if (current_line == 0 && !@rl_byte_oriented && @_rl_last_c_pos == @prompt_physical_chars) @cpos_adjusted = true end # if (len (new) > len (old)) # lendiff == difference in buffer # col_lendiff == difference on screen # When not using multibyte characters, these are equal lendiff = (nls - nfd) - (ols - ofd) if !@rl_byte_oriented col_lendiff = _rl_col_width(new, nfd, nls) - _rl_col_width(old, ostart+ofd, ostart+ols) else col_lendiff = lendiff end # If we are changing the number of invisible characters in a line, and # the spot of first difference is before the end of the invisible chars, # lendiff needs to be adjusted. if (current_line == 0 && !@_rl_horizontal_scroll_mode && current_invis_chars != @visible_wrap_offset) if !@rl_byte_oriented lendiff += @visible_wrap_offset - current_invis_chars col_lendiff += @visible_wrap_offset - current_invis_chars else lendiff += @visible_wrap_offset - current_invis_chars col_lendiff = lendiff end end # Insert (diff (len (old), len (new)) ch. temp = ne - nfd if !@rl_byte_oriented col_temp = _rl_col_width(new,nfd,ne) else col_temp = temp end if (col_lendiff > 0) # XXX - was lendiff # Non-zero if we're increasing the number of lines. gl = current_line >= @_rl_vis_botlin && inv_botlin > @_rl_vis_botlin # Sometimes it is cheaper to print the characters rather than # use the terminal's capabilities. If we're growing the number # of lines, make sure we actually cause the new line to wrap # around on auto-wrapping terminals. if (@_rl_terminal_can_insert && ((2 * col_temp) >= col_lendiff || @_rl_term_IC) && (!@_rl_term_autowrap || !gl)) # If lendiff > prompt_visible_length and _rl_last_c_pos == 0 and # _rl_horizontal_scroll_mode == 1, inserting the characters with # _rl_term_IC or _rl_term_ic will screw up the screen because of the # invisible characters. We need to just draw them. if (old[ostart+ols,1] != 0.chr && (!@_rl_horizontal_scroll_mode || @_rl_last_c_pos > 0 || lendiff <= @prompt_visible_length || current_invis_chars==0)) insert_some_chars(new[nfd..-1], lendiff, col_lendiff) @_rl_last_c_pos += col_lendiff elsif ((@rl_byte_oriented) && old[ostart+ols,1] == 0.chr && lendiff > 0) # At the end of a line the characters do not have to # be "inserted". They can just be placed on the screen. # However, this screws up the rest of this block, which # assumes you've done the insert because you can. _rl_output_some_chars(new,nfd, lendiff) @_rl_last_c_pos += col_lendiff else # We have horizontal scrolling and we are not inserting at # the end. We have invisible characters in this line. This # is a dumb update. _rl_output_some_chars(new,nfd, temp) @_rl_last_c_pos += col_temp return end # Copy (new) chars to screen from first diff to last match. temp = nls - nfd if ((temp - lendiff) > 0) _rl_output_some_chars(new,(nfd + lendiff),temp - lendiff) # XXX -- this bears closer inspection. Fixes a redisplay bug # reported against bash-3.0-alpha by Andreas Schwab involving # multibyte characters and prompt strings with invisible # characters, but was previously disabled. @_rl_last_c_pos += _rl_col_width(new,nfd+lendiff, nfd+lendiff+temp-col_lendiff) end else # cannot insert chars, write to EOL _rl_output_some_chars(new,nfd, temp) @_rl_last_c_pos += col_temp # If we're in a multibyte locale and were before the last invisible # char in the current line (which implies we just output some invisible # characters) we need to adjust _rl_last_c_pos, since it represents # a physical character position. end else # Delete characters from line. # If possible and inexpensive to use terminal deletion, then do so. if (@_rl_term_dc && (2 * col_temp) >= -col_lendiff) # If all we're doing is erasing the invisible characters in the # prompt string, don't bother. It screws up the assumptions # about what's on the screen. if (@_rl_horizontal_scroll_mode && @_rl_last_c_pos == 0 && -lendiff == @visible_wrap_offset) col_lendiff = 0 end if (col_lendiff!=0) delete_chars(-col_lendiff) # delete (diff) characters end # Copy (new) chars to screen from first diff to last match temp = nls - nfd if (temp > 0) _rl_output_some_chars(new,nfd, temp) @_rl_last_c_pos += _rl_col_width(new,nfd,nfd+temp) end # Otherwise, print over the existing material. else if (temp > 0) _rl_output_some_chars(new,nfd, temp) @_rl_last_c_pos += col_temp # XXX end lendiff = (oe) - (ne) if !@rl_byte_oriented col_lendiff = _rl_col_width(old, ostart, ostart+oe) - _rl_col_width(new, 0, ne) else col_lendiff = lendiff end if (col_lendiff!=0) if (@_rl_term_autowrap && current_line < inv_botlin) space_to_eol(col_lendiff) else _rl_clear_to_eol(col_lendiff) end end end end end # Basic redisplay algorithm. def rl_redisplay() return if !@readline_echoing_p _rl_wrapped_multicolumn = 0 @rl_display_prompt ||= "" if (@invisible_line.nil? || @vis_lbreaks.nil?) init_line_structures(0) rl_on_new_line() end # Draw the line into the buffer. @cpos_buffer_position = -1 line = @invisible_line out = inv_botlin = 0 # Mark the line as modified or not. We only do this for history # lines. modmark = 0 if (@_rl_mark_modified_lines && current_history() && @rl_undo_list) line[out,1] = '*' out += 1 line[out,1] = 0.chr modmark = 1 end # If someone thought that the redisplay was handled, but the currently # visible line has a different modification state than the one about # to become visible, then correct the caller's misconception. if (@visible_line[0,1] != @invisible_line[0,1]) @rl_display_fixed = false end # If the prompt to be displayed is the `primary' readline prompt (the # one passed to readline()), use the values we have already expanded. # If not, use what's already in rl_display_prompt. WRAP_OFFSET is the # number of non-visible characters in the prompt string. if (@rl_display_prompt == @rl_prompt || @local_prompt) if (@local_prompt_prefix && @forced_display) _rl_output_some_chars(@local_prompt_prefix,0,@local_prompt_prefix.length) end if (@local_prompt_len > 0) temp = @local_prompt_len + out + 2 if (temp >= @line_size) @line_size = (temp + 1024) - (temp % 1024) if @visible_line.length >= @line_size @visible_line = @visible_line[0,@line_size] else @visible_line += 0.chr * (@line_size-@visible_line.length) end if @invisible_line.length >= @line_size @invisible_line = @invisible_line[0,@line_size] else @invisible_line += 0.chr * (@line_size-@invisible_line.length) end if @encoding=='X' @visible_line.force_encoding('ASCII-8BIT') @invisible_line.force_encoding('ASCII-8BIT') end line = @invisible_line end line[out,@local_prompt_len] = @local_prompt out += @local_prompt_len end line[out,1] = 0.chr @wrap_offset = @local_prompt_len - @prompt_visible_length else prompt_this_line = @rl_display_prompt.rindex("\n") if prompt_this_line.nil? prompt_this_line = 0 else prompt_this_line+=1 pmtlen = prompt_this_line # temp var if (@forced_display) _rl_output_some_chars(@rl_display_prompt,0,pmtlen) # Make sure we are at column zero even after a newline, #regardless of the state of terminal output processing. if (pmtlen < 2 || @rl_display_prompt[prompt_this_line-2,1] != "\r") cr() end end end @prompt_physical_chars = pmtlen = @rl_display_prompt.length - prompt_this_line temp = pmtlen + out + 2 if (temp >= @line_size) @line_size = (temp + 1024) - (temp % 1024) if @visible_line.length >= @line_size @visible_line = @visible_line[0,@line_size] else @visible_line += 0.chr * (@line_size-@visible_line.length) end if @invisible_line.length >= @line_size @invisible_line = @invisible_line[0,@line_size] else @invisible_line += 0.chr * (@line_size-@invisible_line.length) end if @encoding=='X' @visible_line.force_encoding('ASCII-8BIT') @invisible_line.force_encoding('ASCII-8BIT') end line = @invisible_line end line[out,pmtlen] = @rl_display_prompt[prompt_this_line,pmtlen] out += pmtlen line[out,1] = 0.chr @wrap_offset = @prompt_invis_chars_first_line = 0 end # inv_lbreaks[i] is where line i starts in the buffer. @inv_lbreaks[newlines = 0] = 0 lpos = @prompt_physical_chars + modmark @_rl_wrapped_line = Array.new(@visible_line.length,0) num = 0 # prompt_invis_chars_first_line is the number of invisible characters in # the first physical line of the prompt. # wrap_offset - prompt_invis_chars_first_line is the number of invis # chars on the second line. # what if lpos is already >= _rl_screenwidth before we start drawing the # contents of the command line? while (lpos >= @_rl_screenwidth) # fix from Darin Johnson for prompt string with # invisible characters that is longer than the screen width. The # prompt_invis_chars_first_line variable could be made into an array # saying how many invisible characters there are per line, but that's # probably too much work for the benefit gained. How many people have # prompts that exceed two physical lines? # Additional logic fix from Edward Catmur if (!@rl_byte_oriented) n0 = num temp = @local_prompt_len while (num < temp) z = _rl_col_width(@local_prompt, n0, num) if (z > @_rl_screenwidth) num = _rl_find_prev_mbchar(@local_prompt, num, MB_FIND_ANY) break elsif (z == @_rl_screenwidth) break end num+=1 end temp = num else temp = ((newlines + 1) * @_rl_screenwidth) end # Now account for invisible characters in the current line. temp += (@local_prompt_prefix.nil? ? ((newlines == 0) ? @prompt_invis_chars_first_line : ((newlines == 1) ? @wrap_offset : 0)) : ((newlines == 0) ? @wrap_offset : 0)) @inv_lbreaks[newlines+=1] = temp if !@rl_byte_oriented lpos -= _rl_col_width(@local_prompt, n0, num) else lpos -= @_rl_screenwidth end end @prompt_last_screen_line = newlines # Draw the rest of the line (after the prompt) into invisible_line, keeping # track of where the cursor is (cpos_buffer_position), the number of the line containing # the cursor (lb_linenum), the last line number (lb_botlin and inv_botlin). # It maintains an array of line breaks for display (inv_lbreaks). # This handles expanding tabs for display and displaying meta characters. lb_linenum = 0 _in = 0 if !@rl_byte_oriented && @rl_end>0 case @encoding when 'E' wc = @rl_line_buffer[0,@rl_end].scan(/./me)[0] wc_bytes = wc ? wc.length : 1 when 'S' wc = @rl_line_buffer[0,@rl_end].scan(/./ms)[0] wc_bytes = wc ? wc.length : 1 when 'U' wc = @rl_line_buffer[0,@rl_end].scan(/./mu)[0] wc_bytes = wc ? wc.length : 1 when 'X' wc = @rl_line_buffer[0,@rl_end].force_encoding(@encoding_name)[0] wc_bytes = wc ? wc.bytesize : 1 end else wc_bytes = 1 end while(_in < @rl_end) c = @rl_line_buffer[_in,1] if(c == 0.chr) @rl_end = _in break end if (!@rl_byte_oriented) case @encoding when 'U' wc_width = wc && wc.unpack('U').first >= 0x1000 ? 2 : 1 when 'X' wc_width = wc && wc.ord > 0x1000 ? 2 : 1 else wc_width = wc ? wc.length : 1 end end if (out + 8 >= @line_size) # XXX - 8 for \t @line_size *= 2 if @visible_line.length>=@line_size @visible_line = @visible_line[0,@line_size] else @visible_line += 0.chr * (@line_size-@visible_line.length) end if @invisible_line.length>=@line_size @invisible_line = @invisible_line[0,@line_size] else @invisible_line += 0.chr * (@line_size-@invisible_line.length) end line = @invisible_line end if (_in == @rl_point) @cpos_buffer_position = out lb_linenum = newlines end if (false && meta_char(c)) if (!@_rl_output_meta_chars && false) line[out,4] = "\\%03o" % c.ord if (lpos + 4 >= @_rl_screenwidth) temp = @_rl_screenwidth - lpos @inv_lbreaks[newlines+=1] = out + temp lpos = 4 - temp else lpos += 4 end out += 4 else line[out,1] = c out += 1 lpos+=1 if (lpos >= @_rl_screenwidth) @inv_lbreaks[newlines+=1] = out @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn lpos = 0 end end elsif (c == "\t") newout = out + 8 - lpos % 8 temp = newout - out if (lpos + temp >= @_rl_screenwidth) temp2 = @_rl_screenwidth - lpos @inv_lbreaks[newlines+=1] = out + temp2 lpos = temp - temp2 while (out < newout) line[out,1] = ' ' out += 1 end else while (out < newout) line[out,1] = ' ' out += 1 end lpos += temp end elsif (c == "\n" && !@_rl_horizontal_scroll_mode && @_rl_term_up) line[out,1] = 0.chr # XXX - sentinel out += 1 @inv_lbreaks[newlines+=1] = out lpos = 0 elsif (ctrl_char(c) || c == RUBOUT) line[out,1] = '^' out += 1 lpos+=1 if (lpos >= @_rl_screenwidth) @inv_lbreaks[newlines+=1] = out @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn lpos = 0 end # NOTE: c[0].ord works identically on both 1.8 and 1.9 line[out,1] = ctrl_char(c) ? (c[0].ord|0x40).chr.upcase : '?' out += 1 lpos+=1 if (lpos >= @_rl_screenwidth) @inv_lbreaks[newlines+=1] = out @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn lpos = 0 end else if (!@rl_byte_oriented) _rl_wrapped_multicolumn = 0 if (@_rl_screenwidth < lpos + wc_width) for i in lpos ... @_rl_screenwidth # The space will be removed in update_line() line[out,1] = ' ' out += 1 _rl_wrapped_multicolumn+=1 lpos+=1 if (lpos >= @_rl_screenwidth) @inv_lbreaks[newlines+=1] = out @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn lpos = 0 end end end if (_in == @rl_point) @cpos_buffer_position = out lb_linenum = newlines end line[out,wc_bytes] = @rl_line_buffer[_in,wc_bytes] out += wc_bytes for i in 0 ... wc_width lpos+=1 if (lpos >= @_rl_screenwidth) @inv_lbreaks[newlines+=1] = out @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn lpos = 0 end end else line[out,1] = c out += 1 lpos+=1 if (lpos >= @_rl_screenwidth) @inv_lbreaks[newlines+=1] = out @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn lpos = 0 end end end if (!@rl_byte_oriented) _in += wc_bytes case @encoding when 'E' wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./me)[0] wc_bytes = wc ? wc.length : 1 when 'S' wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./ms)[0] wc_bytes = wc ? wc.length : 1 when 'U' wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./mu)[0] wc_bytes = wc ? wc.length : 1 when 'X' wc = @rl_line_buffer[_in,@rl_end - _in].force_encoding(@encoding_name)[0] wc_bytes = wc ? wc.bytesize : 1 end else _in+=1 end end line[out,1] = 0.chr if (@cpos_buffer_position < 0) @cpos_buffer_position = out lb_linenum = newlines end inv_botlin = lb_botlin = newlines @inv_lbreaks[newlines+1] = out cursor_linenum = lb_linenum # CPOS_BUFFER_POSITION == position in buffer where cursor should be placed. # CURSOR_LINENUM == line number where the cursor should be placed. # PWP: now is when things get a bit hairy. The visible and invisible # line buffers are really multiple lines, which would wrap every # (screenwidth - 1) characters. Go through each in turn, finding # the changed region and updating it. The line order is top to bottom. # If we can move the cursor up and down, then use multiple lines, # otherwise, let long lines display in a single terminal line, and # horizontally scroll it. if (!@_rl_horizontal_scroll_mode && @_rl_term_up) if (!@rl_display_fixed || @forced_display) @forced_display = false # If we have more than a screenful of material to display, then # only display a screenful. We should display the last screen, # not the first. if (out >= @_rl_screenchars) if (!@rl_byte_oriented) out = _rl_find_prev_mbchar(line, @_rl_screenchars, MB_FIND_ANY) else out = @_rl_screenchars - 1 end end # The first line is at character position 0 in the buffer. The # second and subsequent lines start at inv_lbreaks[N], offset by # OFFSET (which has already been calculated above). # For each line in the buffer, do the updating display. for linenum in 0 .. inv_botlin # This can lead us astray if we execute a program that changes #the locale from a non-multibyte to a multibyte one. o_cpos = @_rl_last_c_pos @cpos_adjusted = false update_line(@visible_line,vis_pos(linenum), inv_line(linenum), linenum, vis_llen(linenum), inv_llen(linenum), inv_botlin) if (linenum == 0 && !@rl_byte_oriented && !@cpos_adjusted && @_rl_last_c_pos != o_cpos && @_rl_last_c_pos > @wrap_offset && o_cpos < @prompt_last_invisible) @_rl_last_c_pos -= @wrap_offset end # If this is the line with the prompt, we might need to # compensate for invisible characters in the new line. Do # this only if there is not more than one new line (which # implies that we completely overwrite the old visible line) # and the new line is shorter than the old. Make sure we are # at the end of the new line before clearing. if (linenum == 0 && inv_botlin == 0 && @_rl_last_c_pos == out && (@wrap_offset > @visible_wrap_offset) && (@_rl_last_c_pos < @visible_first_line_len)) if !@rl_byte_oriented nleft = @_rl_screenwidth - @_rl_last_c_pos else nleft = @_rl_screenwidth + @wrap_offset - @_rl_last_c_pos end if (nleft!=0) _rl_clear_to_eol(nleft) end end # Since the new first line is now visible, save its length. if (linenum == 0) @visible_first_line_len = (inv_botlin > 0) ? @inv_lbreaks[1] : out - @wrap_offset end end # We may have deleted some lines. If so, clear the left over # blank ones at the bottom out. if (@_rl_vis_botlin > inv_botlin) while(linenum <= @_rl_vis_botlin) tt = vis_chars(linenum) _rl_move_vert(linenum) _rl_move_cursor_relative(0, tt) _rl_clear_to_eol((linenum == @_rl_vis_botlin) ? tt.length : @_rl_screenwidth) linenum += 1 end end @_rl_vis_botlin = inv_botlin # CHANGED_SCREEN_LINE is set to 1 if we have moved to a # different screen line during this redisplay. changed_screen_line = @_rl_last_v_pos != cursor_linenum if (changed_screen_line) _rl_move_vert(cursor_linenum) # If we moved up to the line with the prompt using _rl_term_up, # the physical cursor position on the screen stays the same, # but the buffer position needs to be adjusted to account # for invisible characters. if (@rl_byte_oriented && cursor_linenum == 0 && @wrap_offset!=0) @_rl_last_c_pos += @wrap_offset end end # We have to reprint the prompt if it contains invisible # characters, since it's not generally OK to just reprint # the characters from the current cursor position. But we # only need to reprint it if the cursor is before the last # invisible character in the prompt string. nleft = @prompt_visible_length + @wrap_offset if (cursor_linenum == 0 && @wrap_offset > 0 && @_rl_last_c_pos > 0 && @_rl_last_c_pos < prompt_ending_index() && @local_prompt) if (@_rl_term_cr) @rl_outstream.write(@_rl_term_cr) end _rl_output_some_chars(@local_prompt,0,nleft) if !@rl_byte_oriented @_rl_last_c_pos = _rl_col_width(@local_prompt, 0, nleft) - @wrap_offset else @_rl_last_c_pos = nleft end end # Where on that line? And where does that line start # in the buffer? pos = @inv_lbreaks[cursor_linenum] # nleft == number of characters in the line buffer between the # start of the line and the desired cursor position. nleft = @cpos_buffer_position - pos # NLEFT is now a number of characters in a buffer. When in a # multibyte locale, however, _rl_last_c_pos is an absolute cursor # position that doesn't take invisible characters in the prompt # into account. We use a fudge factor to compensate. # Since _rl_backspace() doesn't know about invisible characters in the # prompt, and there's no good way to tell it, we compensate for # those characters here and call _rl_backspace() directly. if (@wrap_offset!=0 && cursor_linenum == 0 && nleft < @_rl_last_c_pos) # TX == new physical cursor position in multibyte locale. if !@rl_byte_oriented tx = _rl_col_width(@visible_line, pos, pos+nleft) - @visible_wrap_offset else tx = nleft end if (@_rl_last_c_pos > tx) _rl_backspace(@_rl_last_c_pos - tx) # XXX @_rl_last_c_pos = tx end end # We need to note that in a multibyte locale we are dealing with # _rl_last_c_pos as an absolute cursor position, but moving to a # point specified by a buffer position (NLEFT) that doesn't take # invisible characters into account. if !@rl_byte_oriented _rl_move_cursor_relative(nleft, @invisible_line,pos) elsif (nleft != @_rl_last_c_pos) _rl_move_cursor_relative(nleft, @invisible_line,pos) end end else # Do horizontal scrolling. # Always at top line. @_rl_last_v_pos = 0 # Compute where in the buffer the displayed line should start. This # will be LMARGIN. # The number of characters that will be displayed before the cursor. ndisp = @cpos_buffer_position - @wrap_offset nleft = @prompt_visible_length + @wrap_offset # Where the new cursor position will be on the screen. This can be # longer than SCREENWIDTH; if it is, lmargin will be adjusted. phys_c_pos = @cpos_buffer_position - (@last_lmargin!=0 ? @last_lmargin : @wrap_offset) t = @_rl_screenwidth / 3 # If the number of characters had already exceeded the screenwidth, #last_lmargin will be > 0. # If the number of characters to be displayed is more than the screen # width, compute the starting offset so that the cursor is about # two-thirds of the way across the screen. if (phys_c_pos > @_rl_screenwidth - 2) lmargin = @cpos_buffer_position - (2 * t) if (lmargin < 0) lmargin = 0 end # If the left margin would be in the middle of a prompt with # invisible characters, don't display the prompt at all. if (@wrap_offset!=0 && lmargin > 0 && lmargin < nleft) lmargin = nleft end elsif (ndisp < @_rl_screenwidth - 2) # XXX - was -1 lmargin = 0 elsif (phys_c_pos < 1) # If we are moving back towards the beginning of the line and # the last margin is no longer correct, compute a new one. lmargin = ((@cpos_buffer_position - 1) / t) * t # XXX if (@wrap_offset!=0 && lmargin > 0 && lmargin < nleft) lmargin = nleft end else lmargin = @last_lmargin end # If the first character on the screen isn't the first character #in the display line, indicate this with a special character. if (lmargin > 0) line[lmargin,1] = '<' end # If SCREENWIDTH characters starting at LMARGIN do not encompass # the whole line, indicate that with a special character at the # right edge of the screen. If LMARGIN is 0, we need to take the # wrap offset into account. t = lmargin + m_offset(lmargin, @wrap_offset) + @_rl_screenwidth if (t < out) line[t - 1,1] = '>' end if (!@rl_display_fixed || @forced_display || lmargin != @last_lmargin) @forced_display = false update_line(@visible_line,@last_lmargin,@invisible_line[lmargin..-1], 0, @_rl_screenwidth + @visible_wrap_offset, @_rl_screenwidth + (lmargin ? 0 : @wrap_offset), 0) # If the visible new line is shorter than the old, but the number # of invisible characters is greater, and we are at the end of # the new line, we need to clear to eol. t = @_rl_last_c_pos - m_offset(lmargin, @wrap_offset) if ((m_offset(lmargin, @wrap_offset) > @visible_wrap_offset) && (@_rl_last_c_pos == out) && t < @visible_first_line_len) nleft = @_rl_screenwidth - t _rl_clear_to_eol(nleft) end @visible_first_line_len = out - lmargin - m_offset(lmargin, @wrap_offset) if (@visible_first_line_len > @_rl_screenwidth) @visible_first_line_len = @_rl_screenwidth end _rl_move_cursor_relative(@cpos_buffer_position - lmargin, @invisible_line ,lmargin) @last_lmargin = lmargin end end @rl_outstream.flush # Swap visible and non-visible lines. @visible_line,@invisible_line = @invisible_line,@visible_line @vis_lbreaks,@inv_lbreaks = @inv_lbreaks,@vis_lbreaks @rl_display_fixed = false # If we are displaying on a single line, and last_lmargin is > 0, we # are not displaying any invisible characters, so set visible_wrap_offset # to 0. if (@_rl_horizontal_scroll_mode && @last_lmargin!=0) @visible_wrap_offset = 0 else @visible_wrap_offset = @wrap_offset end end 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 index = string.index(0.chr) str = index ? string[0,index] : string width = 0 case @encoding when 'N' return (_end - start) when 'U' str[start ... _end].scan(/./mu).each {|s| width += s.unpack('U').first >= 0x1000 ? 2 : 1 } when 'S' str[start ... _end].scan(/./ms).each {|s| width += s.length } when 'E' str[start ... _end].scan(/./me).each {|s| width += s.length } when 'X' 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) @_rl_out_stream.write(string[start,count]) end # Tell the update routines that we have moved onto a new line with the # prompt already displayed. Code originally from the version of readline # distributed with CLISP. rl_expand_prompt must have already been called # (explicitly or implicitly). This still doesn't work exactly right. def rl_on_new_line_with_prompt() # Initialize visible_line and invisible_line to ensure that they can hold # the already-displayed prompt. prompt_size = @rl_prompt.length + 1 init_line_structures(prompt_size) # Make sure the line structures hold the already-displayed prompt for # redisplay. lprompt = @local_prompt ? @local_prompt : @rl_prompt @visible_line[0,lprompt.length] = lprompt @invisible_line[0,lprompt.length] = lprompt # If the prompt contains newlines, take the last tail. prompt_last_line = rl_prompt.rindex("\n") if prompt_last_line.nil? prompt_last_line = @rl_prompt else prompt_last_line = @rl_prompt[prompt_last_line..-1] end l = prompt_last_line.length if !@rl_byte_oriented @_rl_last_c_pos = _rl_col_width(prompt_last_line, 0, l) else @_rl_last_c_pos = l end # Dissect prompt_last_line into screen lines. Note that here we have # to use the real screenwidth. Readline's notion of screenwidth might be # one less, see terminal.c. real_screenwidth = @_rl_screenwidth + (@_rl_term_autowrap ? 0 : 1) @_rl_last_v_pos = l / real_screenwidth # If the prompt length is a multiple of real_screenwidth, we don't know # whether the cursor is at the end of the last line, or already at the # beginning of the next line. Output a newline just to be safe. if (l > 0 && (l % real_screenwidth) == 0) _rl_output_some_chars("\n",0,1) end @last_lmargin = 0 newlines = 0 i = 0 while (i <= l) @_rl_vis_botlin = newlines @vis_lbreaks[newlines] = i newlines += 1 i += real_screenwidth end @vis_lbreaks[newlines] = l @visible_wrap_offset = 0 @rl_display_prompt = @rl_prompt # XXX - make sure it's set return 0 end def readline_internal_setup() @_rl_in_stream = @rl_instream @_rl_out_stream = @rl_outstream if (@rl_startup_hook) send(@rl_startup_hook) end # If we're not echoing, we still want to at least print a prompt, because # rl_redisplay will not do it for us. If the calling application has a # custom redisplay function, though, let that function handle it. if (!@readline_echoing_p && @rl_redisplay_function == :rl_redisplay) if (@rl_prompt && !@rl_already_prompted) nprompt = _rl_strip_prompt(@rl_prompt) @_rl_out_stream.write(nprompt) @_rl_out_stream.flush end else if (@rl_prompt && @rl_already_prompted) rl_on_new_line_with_prompt() else rl_on_new_line() end send(@rl_redisplay_function) end if (@rl_editing_mode == @vi_mode) rl_vi_insertion_mode(1, 'i') end if (@rl_pre_input_hook) send(@rl_pre_input_hook) end end # Create a default argument. def _rl_reset_argument() @rl_numeric_arg = @rl_arg_sign = 1 @rl_explicit_arg = false @_rl_argcxt = 0 end # Ring the terminal bell. def rl_ding() if @MessageBeep @MessageBeep.Call(0) elsif @readline_echoing_p if @_rl_bell_preference == VISIBLE_BELL if (@_rl_visible_bell) @_rl_out_stream.write(@_rl_visible_bell.chr) else $stderr.write("\007") $stderr.flush end elsif @_rl_bell_preference == AUDIBLE_BELL $stderr.write("\007") $stderr.flush end return 0 end return -1 end def _rl_search_getchar(cxt) # Read a key and decide how to proceed. rl_setstate(RL_STATE_MOREINPUT) c = cxt.lastc = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) if !@rl_byte_oriented cxt.mb = "" c = cxt.lastc = _rl_read_mbstring(cxt.lastc, cxt.mb, MB_LEN_MAX) end c end def endsrch_char(c) ((ctrl_char(c) || meta_char(c) || (c) == RUBOUT) && ((c) != "\C-G")) end def _rl_input_available IO.select([ $stdin ], nil, [ $stdin ], @_keyboard_input_timeout) end # Process just-read character C according to isearch context CXT. Return # -1 if the caller should just free the context and return, 0 if we should # break out of the loop, and 1 if we should continue to read characters. def _rl_isearch_dispatch(cxt, c) f = nil # Translate the keys we do something with to opcodes. if (c && @_rl_keymap[c]) f = @_rl_keymap[c] if (f == :rl_reverse_search_history) cxt.lastc = (cxt.sflags & SF_REVERSE)!=0 ? -1 : -2 elsif (f == :rl_forward_search_history) cxt.lastc = (cxt.sflags & SF_REVERSE)!=0 ? -2 : -1 elsif (f == :rl_rubout) cxt.lastc = -3 elsif (c == "\C-G") cxt.lastc = -4 elsif (c == "\C-W") # XXX cxt.lastc = -5 elsif (c == "\C-Y") # XXX cxt.lastc = -6 end end # The characters in isearch_terminators (set from the user-settable # variable isearch-terminators) are used to terminate the search but # not subsequently execute the character as a command. The default # value is "\033\012" (ESC and C-J). if (cxt.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 ctx = nil @_rl_iscxt = nil rl_unsetstate(RL_STATE_ISEARCH) r != 0 end # Do the command associated with KEY in MAP. # If the associated command is really a keymap, then read # another key, and dispatch into that map. def _rl_dispatch(key, map) @_rl_dispatching_keymap = map return _rl_dispatch_subseq(key, map, false) end def _rl_dispatch_subseq(key, map, got_subseq) func = map[key] if (func) @rl_executing_keymap = map @rl_dispatching = true rl_setstate(RL_STATE_DISPATCHING) send(map[key],@rl_numeric_arg * @rl_arg_sign, key) rl_unsetstate(RL_STATE_DISPATCHING) @rl_dispatching = false if (@rl_pending_input == 0 && map[key] != :rl_digit_argument) @rl_last_func = map[key] end else if(map.keys.detect{|x| x =~ /^#{Regexp.escape(key)}/}) key += _rl_subseq_getchar(key) return _rl_dispatch_subseq(key,map,got_subseq) elsif(key.length>1 && key[1].ord < 0x7f) _rl_abort_internal() return -1 else @rl_dispatching = true rl_setstate(RL_STATE_DISPATCHING) send(:rl_insert,@rl_numeric_arg * @rl_arg_sign, key) rl_unsetstate(RL_STATE_DISPATCHING) @rl_dispatching = false end end 0 end # Add KEY to the buffer of characters to be read. Returns 1 if the # character was stuffed correctly; 0 otherwise. def rl_stuff_char(key) return 0 if (ibuffer_space() == 0) if (key == EOF) key = NEWLINE @rl_pending_input = EOF rl_setstate(RL_STATE_INPUTPENDING) end @ibuffer[@push_index] = key @push_index += 1 if (@push_index >= @ibuffer_len) @push_index = 0 end return 1 end begin # Cygwin will look like Windows, but we want to treat it like a Posix OS: raise LoadError, "Cygwin is a Posix OS." if RUBY_PLATFORM =~ /\bcygwin\b/i raise LoadError, "Not Windows" if RUBY_PLATFORM !~ /mswin|mingw/ if RUBY_VERSION < '1.9.1' require 'Win32API' else require 'dl' class Win32API DLL = {} TYPEMAP = {"0" => DL::TYPE_VOID, "S" => DL::TYPE_VOIDP, "I" => DL::TYPE_LONG} def initialize(dllname, func, import, export = "0", calltype = :stdcall) @proto = [import].join.tr("VPpNnLlIi", "0SSI").sub(/^(.)0*$/, '\1') handle = DLL[dllname] ||= DL.dlopen(dllname) @func = DL::CFunc.new(handle[func], TYPEMAP[export.tr("VPpNnLlIi", "0SSI")], func, calltype) end def call(*args) import = @proto.split("") args.each_with_index do |x, i| args[i], = [x == 0 ? nil : x].pack("p").unpack("l!*") if import[i] == "S" args[i], = [x].pack("I").unpack("i") if import[i] == "I" end ret, = @func.call(args) return ret || 0 end alias Call call end end STD_OUTPUT_HANDLE = -11 STD_INPUT_HANDLE = -10 KEY_EVENT = 1 VK_SHIFT = 0x10 VK_MENU = 0x12 VK_LMENU = 0xA4 VK_RMENU = 0xA5 LEFT_CTRL_PRESSED = 0x0008 RIGHT_CTRL_PRESSED = 0x0004 LEFT_ALT_PRESSED = 0x0002 RIGHT_ALT_PRESSED = 0x0001 @getch = Win32API.new("msvcrt", "_getch", [], 'I') @kbhit = Win32API.new("msvcrt", "_kbhit", [], 'I') @GetStdHandle = Win32API.new("kernel32","GetStdHandle",['L'],'L') @SetConsoleCursorPosition = Win32API.new("kernel32","SetConsoleCursorPosition",['L','L'],'L') @GetConsoleScreenBufferInfo = Win32API.new("kernel32","GetConsoleScreenBufferInfo",['L','P'],'L') @FillConsoleOutputCharacter = Win32API.new("kernel32","FillConsoleOutputCharacter",['L','L','L','L','P'],'L') @ReadConsoleInput = Win32API.new( "kernel32", "ReadConsoleInput", ['L', 'P', 'L', 'P'], 'L' ) @MessageBeep = Win32API.new("user32","MessageBeep",['L'],'L') @GetKeyboardState = Win32API.new("user32","GetKeyboardState",['P'],'L') @GetKeyState = Win32API.new("user32","GetKeyState",['L'],'L') @hConsoleHandle = @GetStdHandle.Call(STD_OUTPUT_HANDLE) @hConsoleInput = @GetStdHandle.Call(STD_INPUT_HANDLE) @pending_count = 0 @pending_key = nil begin case `chcp`.scan(/\d+$/).first.to_i when 936,949,950,51932,51936,50225 @encoding = "E" when 932,50220,50221,20222 @encoding = "S" when 65001 @encoding = "U" else @encoding = "N" end rescue @encoding = "N" end def rl_getc(stream) while (@kbhit.Call == 0) # If there is no input, yield the processor for other threads sleep(@_keyboard_input_timeout) end c = @getch.Call alt = (@GetKeyState.call(VK_LMENU) & 0x80) != 0 if c==0 || c==0xE0 while (@kbhit.Call == 0) # If there is no input, yield the processor for other threads sleep(@_keyboard_input_timeout) end r = c.chr + @getch.Call.chr else r = c.chr end r = "\e"+r if alt r end def rl_gather_tyi() chars_avail = @kbhit.Call return 0 if(chars_avail<=0) k = send(@rl_getc_function,@rl_instream) rl_stuff_char(k) return 1 end rescue LoadError # If we're not on Windows try... if ENV["LANG"] =~ /\.UTF-8/ @encoding = "U" elsif ENV["LANG"] =~ /\.EUC/ @encoding = "E" elsif ENV["LANG"] =~ /\.SHIFT/ @encoding = "S" else @encoding = "N" end def rl_getc(stream) begin c = stream.read(1) rescue Errno::EINTR c = rl_getc(stream) end return c ? c : EOF end def rl_gather_tyi() chars_avail = 0 result = select([@rl_instream],nil,nil,0.1) return 0 if result.nil? k = send(@rl_getc_function,@rl_instream) rl_stuff_char(k) return 1 end end if (Object.const_defined?('Encoding') and Encoding.respond_to?('default_external')) @encoding = "X" # ruby 1.9.x or greater @encoding_name = Encoding.default_external.to_s end @rl_byte_oriented = @encoding == "N" # Read a key, including pending input. def rl_read_key() @rl_key_sequence_length+=1 if (@rl_pending_input!=0) c = @rl_pending_input rl_clear_pending_input() else # If the user has an event function, then call it periodically. if (@rl_event_hook) while (@rl_event_hook && (c=rl_get_char()).nil?) send(@rl_event_hook) if (@rl_done) # XXX - experimental return ("\n") end if (rl_gather_tyi() < 0) # XXX - EIO @rl_done = true return ("\n") end end else if (c=rl_get_char()).nil? c = send(@rl_getc_function,@rl_instream) end end end return (c) end # Return the amount of space available in the buffer for stuffing # characters. def ibuffer_space() if (@pop_index > @push_index) return (@pop_index - @push_index - 1) else return (@ibuffer_len - (@push_index - @pop_index)) end end # Get a key from the buffer of characters to be read. # Return the key in KEY. # Result is KEY if there was a key, or 0 if there wasn't. def rl_get_char() if (@push_index == @pop_index) return nil end key = @ibuffer[@pop_index] @pop_index += 1 if (@pop_index >= @ibuffer_len) @pop_index = 0 end return key end # Stuff KEY into the *front* of the input buffer. # Returns non-zero if successful, zero if there is # no space left in the buffer. def _rl_unget_char(key) if (ibuffer_space()!=0) @pop_index-=1 if (@pop_index < 0) @pop_index = @ibuffer_len - 1 end @ibuffer[@pop_index] = key return (1) end return (0) end def _rl_subseq_getchar(key) if (key == ESC) rl_setstate(RL_STATE_METANEXT) end rl_setstate(RL_STATE_MOREINPUT) k = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) if (key == ESC) rl_unsetstate(RL_STATE_METANEXT) end return k end # Clear to the end of the line. COUNT is the minimum # number of character spaces to clear, def _rl_clear_to_eol(count) if (@_rl_term_clreol) @rl_outstream.write(@_rl_term_clreol) elsif (count!=0) space_to_eol(count) end end # Clear to the end of the line using spaces. COUNT is the minimum # number of character spaces to clear, def space_to_eol(count) if @hConsoleHandle csbi = 0.chr * 24 @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) cursor_pos = csbi[4,4].unpack('L').first written = 0.chr * 4 @FillConsoleOutputCharacter.Call(@hConsoleHandle,0x20,count,cursor_pos,written) else @rl_outstream.write(' ' * count) end @_rl_last_c_pos += count end def _rl_clear_screen() if (@_rl_term_clrpag) @rl_outstream.write(@_rl_term_clrpag) else rl_crlf() end end # Move the cursor back. def _rl_backspace(count) if (@_rl_term_backspace) @_rl_out_stream.write(@_rl_term_backspace * count) else @_rl_out_stream.write("\b"*count) end 0 end # Move to the start of the next line. def rl_crlf() if (@_rl_term_cr) @_rl_out_stream.write(@_rl_term_cr) end @_rl_out_stream.write("\n") return 0 end # Move to the start of the current line. def cr() if (@_rl_term_cr) @_rl_out_stream.write(@_rl_term_cr) @_rl_last_c_pos = 0 end end def _rl_erase_entire_line() cr() _rl_clear_to_eol(0) cr() @rl_outstream.flush end def _rl_internal_char_cleanup() # In vi mode, when you exit insert mode, the cursor moves back # over the previous character. We explicitly check for that here. if (@rl_editing_mode == @vi_mode && @_rl_keymap == @vi_movement_keymap) rl_vi_check() end if (@rl_num_chars_to_read!=0 && @rl_end >= @rl_num_chars_to_read) send(@rl_redisplay_function) @_rl_want_redisplay = false rl_newline(1, "\n") end if (!@rl_done) send(@rl_redisplay_function) @_rl_want_redisplay = false end # If the application writer has told us to erase the entire line if # the only character typed was something bound to rl_newline, do so. if (@rl_erase_empty_line && @rl_done && @rl_last_func == :rl_newline && @rl_point == 0 && @rl_end == 0) _rl_erase_entire_line() end end def readline_internal_charloop() lastc = -1 eof_found = false while (!@rl_done) lk = @_rl_last_command_was_kill # send(rl_redisplay_function) # @_rl_want_redisplay = false if (@rl_pending_input == 0) # Then initialize the argument and number of keys read. _rl_reset_argument() @rl_key_sequence_length = 0 end rl_setstate(RL_STATE_READCMD) c = rl_read_key() rl_unsetstate(RL_STATE_READCMD) # look at input.c:rl_getc() for the circumstances under which this will #be returned; punt immediately on read error without converting it to #a newline. if (c == READERR) eof_found = true break end # EOF typed to a non-blank line is a . if (c == EOF && @rl_end!=0) c = NEWLINE end # The character _rl_eof_char typed to blank line, and not as the #previous character is interpreted as EOF. if (((c == @_rl_eof_char && lastc != c) || c == EOF) && @rl_end==0) eof_found = true break end lastc = c if _rl_dispatch(c, @_rl_keymap)== -1 next end # If there was no change in _rl_last_command_was_kill, then no kill #has taken place. Note that if input is pending we are reading #a prefix command, so nothing has changed yet. if (@rl_pending_input == 0 && lk == @_rl_last_command_was_kill) @_rl_last_command_was_kill = false end _rl_internal_char_cleanup() end eof_found end # How to abort things. def _rl_abort_internal() rl_ding() rl_clear_message() _rl_reset_argument() rl_clear_pending_input() rl_unsetstate(RL_STATE_MACRODEF) @rl_last_func = nil #throw :readline_top_level send(@rl_redisplay_function) @_rl_want_redisplay = false 0 end def rl_abort(count, key) _rl_abort_internal() end def rl_vi_check() if (@rl_point!=0 && @rl_point == @rl_end) @rl_point-=1 end 0 end def readline_internal_teardown(eof) # Restore the original of this history line, iff the line that we # are editing was originally in the history, AND the line has changed. entry = current_history() if (entry && @rl_undo_list) temp = @rl_line_buffer.delete(0.chr).dup rl_revert_line(1, 0) entry = replace_history_entry(where_history(), @rl_line_buffer, nil) entry = nil @rl_line_buffer = temp+0.chr temp = nil end # At any rate, it is highly likely that this line has an undo list. Get # rid of it now. if (@rl_undo_list) rl_free_undo_list() end # Restore normal cursor, if available. _rl_set_insert_mode(RL_IM_INSERT, 0) (eof ? nil : @rl_line_buffer.delete(0.chr)) end # Read a line of input from the global rl_instream, doing output on # the global rl_outstream. # If rl_prompt is non-null, then that is our prompt. def readline_internal() readline_internal_setup() eof = readline_internal_charloop() readline_internal_teardown(eof) end # Read a line of input. Prompt with PROMPT. An empty PROMPT means # none. A return value of NULL means that EOF was encountered. def readline(prompt) # If we are at EOF return a NULL string. if (@rl_pending_input == EOF) rl_clear_pending_input() return nil end rl_set_prompt(prompt) rl_initialize() @readline_echoing_p = true if (@rl_prep_term_function) send(@rl_prep_term_function,@_rl_meta_flag) end rl_set_signals() value = readline_internal() if(@rl_deprep_term_function) send(@rl_deprep_term_function) end rl_clear_signals() value end # Increase the size of RL_LINE_BUFFER until it has enough space to hold # LEN characters. def rl_extend_line_buffer(len) while (len >= @rl_line_buffer.length) @rl_line_buffer << 0.chr * DEFAULT_BUFFER_SIZE end @the_line = @rl_line_buffer end # Insert a string of text into the line at point. This is the only # way that you should do insertion. _rl_insert_char () calls this # function. Returns the number of characters inserted. def rl_insert_text(string) string.delete!(0.chr) l = string.length return 0 if (l == 0) if (@rl_end + l >= @rl_line_buffer.length) rl_extend_line_buffer(@rl_end + l) end @rl_line_buffer[@rl_point,0] = string # Remember how to undo this if we aren't undoing something. if (!@_rl_doing_an_undo) # If possible and desirable, concatenate the undos. if ((l == 1) && @rl_undo_list && (@rl_undo_list.what == UNDO_INSERT) && (@rl_undo_list.end == @rl_point) && (@rl_undo_list.end - @rl_undo_list.start < 20)) @rl_undo_list.end+=1 else rl_add_undo(UNDO_INSERT, @rl_point, @rl_point + l, nil) end end @rl_point += l @rl_end += l if @rl_line_buffer.length <= @rl_end @rl_line_buffer << 0.chr * (@rl_end - @rl_line_buffer.length + 1) else @rl_line_buffer[@rl_end] = "\0" end l end def alloc_undo_entry(what, start, _end, text) temp = Struct.new(:what,:start,:end,:text,:next).new temp.what = what temp.start = start temp.end = _end temp.text = text temp.next = nil temp end #* Remember how to undo something. Concatenate some undos if that # seems right. def rl_add_undo(what, start, _end, text) temp = alloc_undo_entry(what, start, _end, text) temp.next = @rl_undo_list @rl_undo_list = temp end # Delete the string between FROM and TO. FROM is inclusive, TO is not. # Returns the number of characters deleted. def rl_delete_text(from, to) # Fix it if the caller is confused. if (from > to) from,to = to,from end # fix boundaries if (to > @rl_end) to = @rl_end if (from > to) from = to end end if (from < 0) from = 0 end text = rl_copy_text(from, to) diff = to - from @rl_line_buffer[from...to] = '' @rl_line_buffer << 0.chr * diff # Remember how to undo this delete. if (!@_rl_doing_an_undo) rl_add_undo(UNDO_DELETE, from, to, text) else text = nil end @rl_end -= diff @rl_line_buffer[@rl_end] = 0.chr return (diff) end def rl_copy_text(from, to) return @rl_line_buffer[from...to] end # Fix up point so that it is within the line boundaries after killing # text. If FIX_MARK_TOO is non-zero, the mark is forced within line # boundaries also. def __rl_fix_point(x) if (x > @rl_end) @rl_end elsif (x < 0) 0 else x end end def _rl_fix_point(fix_mark_too) @rl_point = __rl_fix_point(@rl_point) if (fix_mark_too) @rl_mark = __rl_fix_point(@rl_mark) end end # Begin a group. Subsequent undos are undone as an atomic operation. def rl_begin_undo_group() rl_add_undo(UNDO_BEGIN, 0, 0, nil) @_rl_undo_group_level+=1 0 end # End an undo group started with rl_begin_undo_group (). def rl_end_undo_group() rl_add_undo(UNDO_END, 0, 0, nil) @_rl_undo_group_level-=1 0 end def rl_free_undo_list() replace_history_data(-1, @rl_undo_list, nil) @rl_undo_list = nil end # Replace the contents of the line buffer between START and END with # TEXT. The operation is undoable. To replace the entire line in an # undoable mode, use _rl_replace_text(text, 0, rl_end) def _rl_replace_text(text, start, _end) rl_begin_undo_group() rl_delete_text(start, _end + 1) @rl_point = start n = rl_insert_text(text) rl_end_undo_group() n end # Replace the current line buffer contents with TEXT. If CLEAR_UNDO is # non-zero, we free the current undo list. def rl_replace_line(text, clear_undo) len = text.delete(0.chr).length @rl_line_buffer = text.dup + 0.chr @rl_end = len if (clear_undo) rl_free_undo_list() end _rl_fix_point(true) end # Replace the DATA in the specified history entries, replacing OLD with # NEW. WHICH says which one(s) to replace: WHICH == -1 means to replace # all of the history entries where entry->data == OLD; WHICH == -2 means # to replace the `newest' history entry where entry->data == OLD; and # WHICH >= 0 means to replace that particular history entry's data, as # long as it matches OLD. def replace_history_data(which,old, new) new = new.dup if new if (which < -2 || which >= @history_length || @history_length == 0 || @the_history.nil?) return end if (which >= 0) entry = @the_history[which] if (entry && entry.data == old) entry.data = new end return end last = -1 for i in 0 ... @history_length entry = @the_history[i] if entry.nil? next end if (entry.data == old) last = i if (which == -1) entry.data = new end end end if (which == -2 && last >= 0) entry = @the_history[last] entry.data = new # XXX - we don't check entry->old end end # Move forward COUNT bytes. def rl_forward_byte(count, key) if (count < 0) return (rl_backward_byte(-count, key)) end if (count > 0) _end = @rl_point + count lend = @rl_end > 0 ? @rl_end - ((@rl_editing_mode == @vi_mode)?1:0) : @rl_end if (_end > lend) @rl_point = lend rl_ding() else @rl_point = _end end end if (@rl_end < 0) @rl_end = 0 end return 0 end # Move forward COUNT characters. def rl_forward_char(count, key) if @rl_byte_oriented return (rl_forward_byte(count, key)) end if (count < 0) return (rl_backward_char(-count, key)) end if (count > 0) point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, count, MB_FIND_NONZERO) if (@rl_end <= point && @rl_editing_mode == @vi_mode) point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_end, MB_FIND_NONZERO) end if (@rl_point == point) rl_ding() end @rl_point = point if (@rl_end < 0) @rl_end = 0 end end 0 end # Backwards compatibility. def rl_forward(count, key) rl_forward_char(count, key) end # Move backward COUNT bytes. def rl_backward_byte(count, key) if (count < 0) return (rl_forward_byte(-count, key)) end if (count > 0) if (@rl_point < count) @rl_point = 0 rl_ding() else @rl_point -= count end end if (@rl_point < 0) @rl_point = 0 end 0 end # Move backward COUNT characters. def rl_backward_char(count, key) if @rl_byte_oriented return (rl_backward_byte(count, key)) end if (count < 0) return (rl_forward_char(-count, key)) end if (count > 0) point = @rl_point while (count > 0 && point > 0) point = _rl_find_prev_mbchar(@rl_line_buffer, point, MB_FIND_NONZERO) count-=1 end if (count > 0) @rl_point = 0 rl_ding() else @rl_point = point end end 0 end # Backwards compatibility. def rl_backward(count, key) rl_backward_char(count, key) end # Move to the beginning of the line. def rl_beg_of_line(count, key) @rl_point = 0 0 end # Move to the end of the line. def rl_end_of_line(count, key) @rl_point = @rl_end 0 end def _rl_char_value(buf,ind) buf[ind,1] end @_rl_allow_pathname_alphabetic_chars = false @pathname_alphabetic_chars = '/-_=~.#$' def rl_alphabetic(c) if c =~ /\w/ return true end return !!(@_rl_allow_pathname_alphabetic_chars && @pathname_alphabetic_chars[c]) end def _rl_walphabetic(c) rl_alphabetic(c) end # Move forward a word. We do what Emacs does. Handles multibyte chars. def rl_forward_word(count, key) if (count < 0) return (rl_backward_word(-count, key)) end while (count>0) return 0 if (@rl_point == @rl_end) # If we are not in a word, move forward until we are in one. # Then, move forward until we hit a non-alphabetic character. c = _rl_char_value(@rl_line_buffer, @rl_point) if (!_rl_walphabetic(c)) if !@rl_byte_oriented @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) else @rl_point += 1 end while (@rl_point < @rl_end) c = _rl_char_value(@rl_line_buffer, @rl_point) if (_rl_walphabetic(c)) break end if !@rl_byte_oriented @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) else @rl_point += 1 end end end return 0 if (@rl_point == @rl_end) if !@rl_byte_oriented @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) else @rl_point += 1 end while (@rl_point < @rl_end) c = _rl_char_value(@rl_line_buffer, @rl_point) if (!_rl_walphabetic(c)) break end if !@rl_byte_oriented @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) else @rl_point += 1 end end count -= 1 end 0 end # Move backward a word. We do what Emacs does. Handles multibyte chars. def rl_backward_word(count, key) if (count < 0) return (rl_forward_word(-count, key)) end while (count>0) return 0 if (@rl_point == 0) # Like rl_forward_word (), except that we look at the characters # just before point. _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) c = _rl_char_value(@rl_line_buffer, _p) if (!_rl_walphabetic(c)) @rl_point = _p while (@rl_point > 0) _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) c = _rl_char_value(@rl_line_buffer, _p) if (_rl_walphabetic(c)) break end @rl_point = _p end end while (@rl_point>0) _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) c = _rl_char_value(@rl_line_buffer, _p) if (!_rl_walphabetic(c)) break else @rl_point = _p end end count -= 1 end 0 end # return the `current display line' of the cursor -- the number of lines to # move up to get to the first screen line of the current readline line. def _rl_current_display_line() # Find out whether or not there might be invisible characters in the # editing buffer. if (@rl_display_prompt == @rl_prompt) nleft = @_rl_last_c_pos - @_rl_screenwidth - @rl_visible_prompt_length else nleft = @_rl_last_c_pos - @_rl_screenwidth end if (nleft > 0) ret = 1 + nleft / @_rl_screenwidth else ret = 0 end ret end # Actually update the display, period. def rl_forced_update_display() if (@visible_line) @visible_line.gsub!(/[^\x00]/,0.chr) end rl_on_new_line() @forced_display=true if !@forced_display send(@rl_redisplay_function) 0 end # Clear the current line. Numeric argument to C-l does this. def rl_refresh_line(ignore1, ignore2) curr_line = _rl_current_display_line() _rl_move_vert(curr_line) _rl_move_cursor_relative(0, @rl_line_buffer) # XXX is this right _rl_clear_to_eol(0) # arg of 0 means to not use spaces rl_forced_update_display() @rl_display_fixed = true 0 end # C-l typed to a line without quoting clears the screen, and then reprints # the prompt and the current input line. Given a numeric arg, redraw only # the current line. def rl_clear_screen(count, key) if (@rl_explicit_arg) rl_refresh_line(count, key) return 0 end _rl_clear_screen() # calls termcap function to clear screen rl_forced_update_display() @rl_display_fixed = true 0 end # Restore the _rl_saved_line_for_history if there is one. def rl_maybe_unsave_line() if (@_rl_saved_line_for_history) # Can't call with `1' because rl_undo_list might point to an undo # list from a history entry, as in rl_replace_from_history() below. rl_replace_line(@_rl_saved_line_for_history.line, false) @rl_undo_list = @_rl_saved_line_for_history.data @_rl_saved_line_for_history = nil @rl_point = @rl_end # rl_replace_line sets rl_end else rl_ding() end 0 end # Save the current line in _rl_saved_line_for_history. def rl_maybe_save_line() if @_rl_saved_line_for_history.nil? @_rl_saved_line_for_history = Struct.new(:line,:timestamp,:data).new @_rl_saved_line_for_history.line = @rl_line_buffer.dup @_rl_saved_line_for_history.timestamp = nil @_rl_saved_line_for_history.data = @rl_undo_list end 0 end # Returns the magic number which says what history element we are # looking at now. In this implementation, it returns history_offset. def where_history() @history_offset end # Make the history entry at WHICH have LINE and DATA. This returns # the old entry so you can dispose of the data. In the case of an # invalid WHICH, a NULL pointer is returned. def replace_history_entry (which, line, data) if (which < 0 || which >= @history_length) return nil end temp = Struct.new(:line,:timestamp,:data).new old_value = @the_history[which] temp.line = line.delete(0.chr) temp.data = data temp.timestamp = old_value.timestamp.dup @the_history[which] = temp old_value end # Perhaps put back the current line if it has changed. def rl_maybe_replace_line() temp = current_history() # If the current line has changed, save the changes. if (temp && temp.data != @rl_undo_list) temp = replace_history_entry(where_history(), @rl_line_buffer, @rl_undo_list) end 0 end # Back up history_offset to the previous history entry, and return # a pointer to that entry. If there is no previous entry then return # a NULL pointer. def previous_history() @history_offset!=0 ? @the_history[@history_offset-=1] : nil end # Move history_offset forward to the next history entry, and return # a pointer to that entry. If there is no next entry then return a # NULL pointer. def next_history() (@history_offset == @history_length) ? nil : @the_history[@history_offset+=1] end # Get the previous item out of our interactive history, making it the current # line. If there is no previous history, just ding. def rl_get_previous_history(count, key) if (count < 0) return (rl_get_next_history(-count, key)) end if (count == 0) return 0 end # either not saved by rl_newline or at end of line, so set appropriately. if (@_rl_history_saved_point == -1 && (@rl_point!=0 || @rl_end!=0)) @_rl_history_saved_point = (@rl_point == @rl_end) ? -1 : @rl_point end # If we don't have a line saved, then save this one. rl_maybe_save_line() # If the current line has changed, save the changes. rl_maybe_replace_line() temp = old_temp = nil while (count>0) temp = previous_history() if temp.nil? break end old_temp = temp count -= 1 end # If there was a large argument, and we moved back to the start of the # history, that is not an error. So use the last value found. if (temp.nil? && old_temp) temp = old_temp end if temp.nil? rl_ding() else rl_replace_from_history(temp, 0) _rl_history_set_point() end 0 end def _rl_history_set_point () @rl_point = (@_rl_history_preserve_point && @_rl_history_saved_point != -1) ? @_rl_history_saved_point : @rl_end if (@rl_point > @rl_end) @rl_point = @rl_end end if (@rl_editing_mode == @vi_mode && @_rl_keymap != @vi_insertion_keymap) @rl_point = 0 end if (@rl_editing_mode == @emacs_mode) @rl_mark = (@rl_point == @rl_end ? 0 : @rl_end) end end # Move down to the next history line. def rl_get_next_history(count, key) if (count < 0) return (rl_get_previous_history(-count, key)) end if (count == 0) return 0 end rl_maybe_replace_line() # either not saved by rl_newline or at end of line, so set appropriately. if (@_rl_history_saved_point == -1 && (@rl_point!=0 || @rl_end!=0)) @_rl_history_saved_point = (@rl_point == @rl_end) ? -1 : @rl_point end temp = nil while (count>0) temp = next_history() if temp.nil? break end count -= 1 end if temp.nil? rl_maybe_unsave_line() else rl_replace_from_history(temp, 0) _rl_history_set_point() end 0 end def rl_arrow_keys(count, c) rl_setstate(RL_STATE_MOREINPUT) ch = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) case (ch.upcase) when 'A' rl_get_previous_history(count, ch) when 'B' rl_get_next_history(count, ch) when 'C' rl_forward_byte(count, ch) when 'D' rl_backward_byte(count, ch) else rl_ding() end 0 end def _rl_any_typein() return (@push_index != @pop_index) end def _rl_insert_typein(c) string = c while ((key = rl_get_char()) && @_rl_keymap[key] == :rl_insert) string << key end if (key) _rl_unget_char(key) end rl_insert_text(string) end # Insert the character C at the current location, moving point forward. # If C introduces a multibyte sequence, we read the whole sequence and # then insert the multibyte char into the line buffer. def _rl_insert_char(count, c) return 0 if (count <= 0) incoming = '' if @rl_byte_oriented incoming << c incoming_length = 1 else @pending_bytes << c if _rl_get_char_len(@pending_bytes) == -2 return 1 else incoming = @pending_bytes @pending_bytes = '' incoming_length = incoming.length end end if(count>1) string = incoming * count rl_insert_text(string) string = nil return 0 end if @rl_byte_oriented # We are inserting a single character. #If there is pending input, then make a string of all of the #pending characters that are bound to rl_insert, and insert #them all. if (_rl_any_typein()) _rl_insert_typein(c) else rl_insert_text(c) end else rl_insert_text(incoming) end return 0 end # Overwrite the character at point (or next COUNT characters) with C. # If C introduces a multibyte character sequence, read the entire sequence # before starting the overwrite loop. def _rl_overwrite_char(count, c) # Read an entire multibyte character sequence to insert COUNT times. if (count > 0 && !@rl_byte_oriented) mbkey = '' k = _rl_read_mbstring(c, mbkey, MB_LEN_MAX) end rl_begin_undo_group() count.times do if !@rl_byte_oriented rl_insert_text(mbkey) else _rl_insert_char(1, c) end if (@rl_point < @rl_end) rl_delete(1, c) end end rl_end_undo_group() return 0 end def rl_insert(count, c) ((@rl_insert_mode == RL_IM_INSERT) ? _rl_insert_char(count, c) : _rl_overwrite_char(count, c)) end # Insert the next typed character verbatim. def _rl_insert_next(count) rl_setstate(RL_STATE_MOREINPUT) c = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) _rl_insert_char(count, c) end def rl_quoted_insert(count, key) _rl_insert_next(count) end # Insert a tab character. def rl_tab_insert(count, key) _rl_insert_char(count, "\t") end def _rl_vi_save_insert(up) if (up.nil? || up.what != UNDO_INSERT) if (@vi_insert_buffer_size >= 1) @vi_insert_buffer[0] = 0.chr end return end start = up.start _end = up.end len = _end - start + 1 @vi_insert_buffer = @rl_line_buffer[start,len-1] end def _rl_vi_done_inserting() if (@_rl_vi_doing_insert) # The `C', `s', and `S' commands set this. rl_end_undo_group() # Now, the text between rl_undo_list->next->start and # rl_undo_list->next->end is what was inserted while in insert # mode. It gets copied to VI_INSERT_BUFFER because it depends # on absolute indices into the line which may change (though they # probably will not). @_rl_vi_doing_insert = 0 _rl_vi_save_insert(@rl_undo_list.next) @vi_continued_command = 1 else if ((@_rl_vi_last_key_before_insert == 'i' || @_rl_vi_last_key_before_insert == 'a') && @rl_undo_list) _rl_vi_save_insert(@rl_undo_list) # XXX - Other keys probably need to be checked. elsif (@_rl_vi_last_key_before_insert == 'C') rl_end_undo_group() end while (@_rl_undo_group_level > 0) rl_end_undo_group() end @vi_continued_command = 0 end end # Is the command C a VI mode text modification command? def _rl_vi_textmod_command(c) return @vi_textmod[c] end def _rl_vi_reset_last() @_rl_vi_last_command = 'i' @_rl_vi_last_repeat = 1 @_rl_vi_last_arg_sign = 1 @_rl_vi_last_motion = 0 end def _rl_update_final() full_lines = false # If the cursor is the only thing on an otherwise-blank last line, # compensate so we don't print an extra CRLF. if (@_rl_vis_botlin && @_rl_last_c_pos == 0 && @visible_line[@vis_lbreaks[@_rl_vis_botlin],1] == 0.chr ) @_rl_vis_botlin-=1 full_lines = true end _rl_move_vert(@_rl_vis_botlin) # If we've wrapped lines, remove the final xterm line-wrap flag. if (full_lines && @_rl_term_autowrap && (vis_llen(@_rl_vis_botlin) == @_rl_screenwidth)) last_line = @visible_line[@vis_lbreaks[@_rl_vis_botlin]..-1] @cpos_buffer_position = -1 # don't know where we are in buffer _rl_move_cursor_relative(@_rl_screenwidth - 1, last_line) # XXX _rl_clear_to_eol(0) @rl_outstream.write(last_line[@_rl_screenwidth - 1,1]) end @_rl_vis_botlin = 0 rl_crlf() @rl_outstream.flush @rl_display_fixed = true if !@rl_display_fixed end # What to do when a NEWLINE is pressed. We accept the whole line. # KEY is the key that invoked this command. I guess it could have # meaning in the future. def rl_newline(count, key) @rl_done = true if (@_rl_history_preserve_point) @_rl_history_saved_point = (@rl_point == @rl_end) ? 1 : @rl_point end rl_setstate(RL_STATE_DONE) if (@rl_editing_mode == @vi_mode) _rl_vi_done_inserting() if (_rl_vi_textmod_command(@_rl_vi_last_command).nil?) # XXX _rl_vi_reset_last() end end # If we've been asked to erase empty lines, suppress the final update, # since _rl_update_final calls rl_crlf(). if (@rl_erase_empty_line && @rl_point == 0 && @rl_end == 0) return 0 end if @readline_echoing_p _rl_update_final() end 0 end # What to do for some uppercase characters, like meta characters, # and some characters appearing in emacs_ctlx_keymap. This function # is just a stub, you bind keys to it and the code in _rl_dispatch () # is special cased. def rl_do_lowercase_version(ignore1, ignore2) 0 end def rl_character_len(c, pos) if (meta_char(c)) return ((!@_rl_output_meta_chars) ? 4 : 1) end if (c == "\t") return (((pos | 7) + 1) - pos) end if (ctrl_char(c) || c == RUBOUT) return (2) end return ((isprint(c)) ? 1 : 2) end # This is different from what vi does, so the code's not shared. Emacs # rubout in overwrite mode has one oddity: it replaces a control # character that's displayed as two characters (^X) with two spaces. def _rl_overwrite_rubout(count, key) if (@rl_point == 0) rl_ding() return 1 end opoint = @rl_point # L == number of spaces to insert l = 0 count.times do rl_backward_char(1, key) l += rl_character_len(@rl_line_buffer[@rl_point,1], @rl_point) # not exactly right end rl_begin_undo_group() if (count > 1 || @rl_explicit_arg) rl_kill_text(opoint, @rl_point) else rl_delete_text(opoint, @rl_point) end # Emacs puts point at the beginning of the sequence of spaces. if (@rl_point < @rl_end) opoint = @rl_point _rl_insert_char(l, ' ') @rl_point = opoint end rl_end_undo_group() 0 end # Rubout the character behind point. def rl_rubout(count, key) if (count < 0) return (rl_delete(-count, key)) end if (@rl_point==0) rl_ding() return -1 end if (@rl_insert_mode == RL_IM_OVERWRITE) return (_rl_overwrite_rubout(count, key)) end _rl_rubout_char(count, key) end # Quick redisplay hack when erasing characters at the end of the line. def _rl_erase_at_end_of_line(l) _rl_backspace(l) @rl_outstream.write(' '*l) _rl_backspace(l) @_rl_last_c_pos -= l @visible_line[@_rl_last_c_pos,l] = 0.chr * l @rl_display_fixed = true if !@rl_display_fixed end def _rl_rubout_char(count, key) # Duplicated code because this is called from other parts of the library. if (count < 0) return (rl_delete(-count, key)) end if (@rl_point == 0) rl_ding() return -1 end orig_point = @rl_point if (count > 1 || @rl_explicit_arg) rl_backward_char(count, key) rl_kill_text(orig_point, @rl_point) elsif (@rl_byte_oriented) c = @rl_line_buffer[@rl_point-=1,1] rl_delete_text(@rl_point, orig_point) # The erase-at-end-of-line hack is of questionable merit now. if (@rl_point == @rl_end && isprint(c) && @_rl_last_c_pos!=0) l = rl_character_len(c, @rl_point) _rl_erase_at_end_of_line(l) end else @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) rl_delete_text(@rl_point, orig_point) end 0 end # Delete the character under the cursor. Given a numeric argument, # kill that many characters instead. def rl_delete(count, key) if (count < 0) return (_rl_rubout_char(-count, key)) end if (@rl_point == @rl_end) rl_ding() return -1 end if (count > 1 || @rl_explicit_arg) xpoint = @rl_point rl_forward_byte(count, key) rl_kill_text(xpoint, @rl_point) @rl_point = xpoint else if !@rl_byte_oriented xpoint =_rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) else xpoint = @rl_point + 1 end rl_delete_text(@rl_point, xpoint) end 0 end # Add TEXT to the kill ring, allocating a new kill ring slot as necessary. # This uses TEXT directly, so the caller must not free it. If APPEND is # non-zero, and the last command was a kill, the text is appended to the # current kill ring slot, otherwise prepended. def _rl_copy_to_kill_ring(text, append) # First, find the slot to work with. if (!@_rl_last_command_was_kill) # Get a new slot. if @rl_kill_ring.nil? # If we don't have any defined, then make one. @rl_kill_ring_length = 1 @rl_kill_ring = Array.new(@rl_kill_ring_length+1) @rl_kill_ring[slot = 0] = nil else # We have to add a new slot on the end, unless we have # exceeded the max limit for remembering kills. slot = @rl_kill_ring_length if (slot == @rl_max_kills) @rl_kill_ring[0,slot] = @rl_kill_ring[1,slot] else slot = @rl_kill_ring_length += 1 end @rl_kill_ring[slot-=1] = nil end else slot = @rl_kill_ring_length - 1 end # If the last command was a kill, prepend or append. if (@_rl_last_command_was_kill && @rl_editing_mode != @vi_mode) old = @rl_kill_ring[slot] if (append) new = old + text else new = text + old end old = nil text = nil @rl_kill_ring[slot] = new else @rl_kill_ring[slot] = text end @rl_kill_index = slot 0 end # The way to kill something. This appends or prepends to the last # kill, if the last command was a kill command. if FROM is less # than TO, then the text is appended, otherwise prepended. If the # last command was not a kill command, then a new slot is made for # this kill. def rl_kill_text(from, to) # Is there anything to kill? if (from == to) @_rl_last_command_was_kill = true if !@_rl_last_command_was_kill return 0 end text = rl_copy_text(from, to) # Delete the copied text from the line. rl_delete_text(from, to) _rl_copy_to_kill_ring(text, from < to) @_rl_last_command_was_kill = true if !@_rl_last_command_was_kill 0 end # This does what C-w does in Unix. We can't prevent people from # using behaviour that they expect. def rl_unix_word_rubout(count, key) if (@rl_point == 0) rl_ding() else orig_point = @rl_point if (count <= 0) count = 1 end while (count>0) while (@rl_point>0 && whitespace(@rl_line_buffer[@rl_point - 1,1])) @rl_point-=1 end while (@rl_point>0 && !whitespace(@rl_line_buffer[@rl_point - 1,1])) @rl_point-=1 end count -= 1 end rl_kill_text(orig_point, @rl_point) if (@rl_editing_mode == @emacs_mode) @rl_mark = @rl_point end end 0 end # This deletes one filename component in a Unix pathname. That is, it # deletes backward to directory separator (`/') or whitespace. def rl_unix_filename_rubout(count, key) if (@rl_point == 0) rl_ding() else orig_point = @rl_point if (count <= 0) count = 1 end while (count>0) c = @rl_line_buffer[@rl_point - 1,1] while (@rl_point>0 && (whitespace(c) || c == '/')) @rl_point-=1 c = @rl_line_buffer[@rl_point - 1,1] end while (@rl_point>0 && !whitespace(c) && c != '/') @rl_point-=1 c = @rl_line_buffer[@rl_point - 1,1] end count -= 1 end rl_kill_text(orig_point, @rl_point) if (@rl_editing_mode == @emacs_mode) @rl_mark = @rl_point end end 0 end # Delete the character under the cursor, unless the insertion # point is at the end of the line, in which case the character # behind the cursor is deleted. COUNT is obeyed and may be used # to delete forward or backward that many characters. def rl_rubout_or_delete(count, key) if (@rl_end != 0 && @rl_point == @rl_end) return (_rl_rubout_char(count, key)) else return (rl_delete(count, key)) end end # Delete all spaces and tabs around point. def rl_delete_horizontal_space(count, ignore) start = @rl_point while (@rl_point!=0 && whitespace(@rl_line_buffer[@rl_point - 1])) @rl_point-=1 end start = @rl_point while (@rl_point < @rl_end && whitespace(@rl_line_buffer[@rl_point])) @rl_point+=1 end if (start != @rl_point) rl_delete_text(start, @rl_point) @rl_point = start end if (@rl_point < 0) @rl_point = 0 end 0 end # List the possible completions. See description of rl_complete (). def rl_possible_completions(ignore, invoking_key) rl_complete_internal('?') end # Like the tcsh editing function delete-char-or-list. The eof character # is caught before this is invoked, so this really does the same thing as # delete-char-or-list-or-eof, as long as it's bound to the eof character. def rl_delete_or_show_completions(count, key) if (@rl_end != 0 && @rl_point == @rl_end) return (rl_possible_completions(count, key)) else return (rl_delete(count, key)) end end # Turn the current line into a comment in shell history. # A K*rn shell style function. def rl_insert_comment(count, key) rl_beg_of_line(1, key) @rl_comment_text = @_rl_comment_begin ? @_rl_comment_begin : '#' if (!@rl_explicit_arg) rl_insert_text(@rl_comment_text) else @rl_comment_len = @rl_comment_text.length if @rl_comment_text[0,@rl_comment_len] == @rl_line_buffer[0,@rl_comment_len] rl_delete_text(@rl_point, @rl_point + @rl_comment_len) else rl_insert_text(@rl_comment_text) end end send(@rl_redisplay_function) rl_newline(1, "\n") 0 end def alloc_history_entry(string, ts) temp = Struct.new(:line,:data,:timestamp).new temp.line = string ? string.delete(0.chr) : string temp.data = nil temp.timestamp = ts return temp end def hist_inittime() t = Time.now.to_i ts = "X%u" % t ret = ts.dup ret[0,1] = @history_comment_char ret end # Place STRING at the end of the history list. The data field # is set to NULL. def add_history(string) if (@history_stifled && (@history_length == @history_max_entries)) # If the history is stifled, and history_length is zero, # and it equals history_max_entries, we don't save items. return if (@history_length == 0) @the_history.shift else if @the_history.nil? @the_history = [] @history_length = 1 else @history_length+=1 end end temp = alloc_history_entry(string, hist_inittime()) @the_history[@history_length] = nil @the_history[@history_length - 1] = temp end def using_history() @history_offset = @history_length end # Set default values for readline word completion. These are the variables # that application completion functions can change or inspect. def set_completion_defaults(what_to_do) # Only the completion entry function can change these. @rl_filename_completion_desired = false @rl_filename_quoting_desired = true @rl_completion_type = what_to_do @rl_completion_suppress_append = @rl_completion_suppress_quote = false # The completion entry function may optionally change this. @rl_completion_mark_symlink_dirs = @_rl_complete_mark_symlink_dirs end def _rl_find_completion_word() _end = @rl_point found_quote = 0 delimiter = 0.chr quote_char = 0.chr brkchars = nil if @rl_completion_word_break_hook brkchars = send(@rl_completion_word_break_hook) end if brkchars.nil? brkchars = @rl_completer_word_break_characters end if (@rl_completer_quote_characters) # We have a list of characters which can be used in pairs to # quote substrings for the completer. Try to find the start # of an unclosed quoted substring. # FOUND_QUOTE is set so we know what kind of quotes we found. scan = 0 pass_next = false while scan < _end if (pass_next) pass_next = false next end # Shell-like semantics for single quotes -- don't allow backslash # to quote anything in single quotes, especially not the closing # quote. If you don't like this, take out the check on the value # of quote_char. if (quote_char != "'" && @rl_line_buffer[scan,1] == "\\") pass_next = true found_quote |= RL_QF_BACKSLASH next end if (quote_char != 0.chr) # Ignore everything until the matching close quote char. if (@rl_line_buffer[scan,1] == quote_char) # Found matching close. Abandon this substring. quote_char = 0.chr @rl_point = _end end elsif (@rl_completer_quote_characters.include?(@rl_line_buffer[scan,1])) # Found start of a quoted substring. quote_char = @rl_line_buffer[scan,1] @rl_point = scan + 1 # Shell-like quoting conventions. if (quote_char == "'") found_quote |= RL_QF_SINGLE_QUOTE elsif (quote_char == '"') found_quote |= RL_QF_DOUBLE_QUOTE else found_quote |= RL_QF_OTHER_QUOTE end end if !@rl_byte_oriented scan = _rl_find_next_mbchar(@rl_line_buffer, scan, 1, MB_FIND_ANY) else scan += 1 end end end if (@rl_point == _end && quote_char == 0.chr) # We didn't find an unclosed quoted substring upon which to do # completion, so use the word break characters to find the # substring on which to complete. while (@rl_point = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_ANY):(@rl_point-1))>0 scan = @rl_line_buffer[@rl_point,1] if !brkchars.include?(scan) next end # Call the application-specific function to tell us whether # this word break character is quoted and should be skipped. if (@rl_char_is_quoted_p && found_quote!=0 && send(@rl_char_is_quoted_p,@rl_line_buffer, @rl_point)) next end # Convoluted code, but it avoids an n^2 algorithm with calls # to char_is_quoted. break end end # If we are at an unquoted word break, then advance past it. scan = @rl_line_buffer[@rl_point,1] # If there is an application-specific function to say whether or not # a character is quoted and we found a quote character, let that # function decide whether or not a character is a word break, even # if it is found in rl_completer_word_break_characters. Don't bother # if we're at the end of the line, though. if (scan != 0.chr) if (@rl_char_is_quoted_p) isbrk = (found_quote == 0 || !send(@rl_char_is_quoted_p,@rl_line_buffer, @rl_point)) && brkchars.include?(scan) else isbrk = brkchars.include?(scan) end if (isbrk) # If the character that caused the word break was a quoting # character, then remember it as the delimiter. if (@rl_basic_quote_characters && @rl_basic_quote_characters.include?(scan) && (_end - @rl_point) > 1) delimiter = scan end # If the character isn't needed to determine something special # about what kind of completion to perform, then advance past it. if (@rl_special_prefixes.nil? || !@rl_special_prefixes.include?(scan) ) @rl_point+=1 end end end return [quote_char,found_quote!=0,delimiter] end def gen_completion_matches(text, start, _end, our_func, found_quote, quote_char) @rl_completion_found_quote = found_quote @rl_completion_quote_character = quote_char # If the user wants to TRY to complete, but then wants to give # up and use the default completion function, they set the # variable rl_attempted_completion_function. if (@rl_attempted_completion_function) matches = Readline.send(@rl_attempted_completion_function,text, start, _end) if (matches || @rl_attempted_completion_over) @rl_attempted_completion_over = false return (matches) end end # XXX -- filename dequoting moved into rl_filename_completion_function matches = rl_completion_matches(text, our_func) matches end # Filter out duplicates in MATCHES. This frees up the strings in # MATCHES. def remove_duplicate_matches(matches) # Sort the items. # Sort the array without matches[0], since we need it to # stay in place no matter what. if matches.length>0 matches[1..-2] = matches[1..-2].sort.uniq end matches end def postprocess_matches(matchesp, matching_filenames) matches = matchesp return 0 if matches.nil? # It seems to me that in all the cases we handle we would like # to ignore duplicate possiblilities. Scan for the text to # insert being identical to the other completions. if (@rl_ignore_completion_duplicates) remove_duplicate_matches(matches) end # If we are matching filenames, then here is our chance to # do clever processing by re-examining the list. Call the # ignore function with the array as a parameter. It can # munge the array, deleting matches as it desires. if (@rl_ignore_some_completions_function && matching_filenames) nmatch = matches.length send(@rl_ignore_some_completions_function,matches) if (matches.nil? || matches[0].nil?) matches = nil return 0 else # If we removed some matches, recompute the common prefix. i = matches.length if (i > 1 && i < nmatch) t = matches[0] compute_lcd_of_matches(matches, i - 1, t) end end end matchesp = matches 1 end def insert_all_matches(matches, point, qc) rl_begin_undo_group() # remove any opening quote character; make_quoted_replacement will add # it back. if (qc && qc.length>0 && point>0 && @rl_line_buffer[point - 1,1] == qc) point-=1 end rl_delete_text(point, @rl_point) @rl_point = point if (matches[1]) i = 1 while(matches[i]) rp = make_quoted_replacement(matches[i], SINGLE_MATCH, qc) rl_insert_text(rp) rl_insert_text(" ") if (rp != matches[i]) rp = nil end i += 1 end else rp = make_quoted_replacement(matches[0], SINGLE_MATCH, qc) rl_insert_text(rp) rl_insert_text(" ") if (rp != matches[0]) rp = nil end end rl_end_undo_group() end def make_quoted_replacement(match, mtype, qc) # If we are doing completion on quoted substrings, and any matches # contain any of the completer_word_break_characters, then auto- # matically prepend the substring with a quote character (just pick # the first one from the list of such) if it does not already begin # with a quote string. FIXME: Need to remove any such automatically # inserted quote character when it no longer is necessary, such as # if we change the string we are completing on and the new set of # matches don't require a quoted substring. replacement = match should_quote = match && @rl_completer_quote_characters && @rl_filename_completion_desired && @rl_filename_quoting_desired if (should_quote) should_quote = should_quote && (qc.nil? || qc == 0.chr || (@rl_completer_quote_characters && @rl_completer_quote_characters.include?(qc))) end if (should_quote) # If there is a single match, see if we need to quote it. # This also checks whether the common prefix of several # matches needs to be quoted. should_quote = @rl_filename_quote_characters ? !!match[@rl_filename_quote_characters] : false do_replace = should_quote ? mtype : NO_MATCH # Quote the replacement, since we found an embedded # word break character in a potential match. if (do_replace != NO_MATCH && @rl_filename_quoting_function) replacement = send(@rl_filename_quoting_function,match, do_replace, qc) end end replacement end def insert_match(match, start, mtype, qc) oqc = qc replacement = make_quoted_replacement(match, mtype, qc) # Now insert the match. if (replacement) # Don't double an opening quote character. if (qc && qc.length>0 && start!=0 && @rl_line_buffer[start - 1,1] == qc && replacement[0,1] == qc) start-=1 # If make_quoted_replacement changed the quoting character, remove # the opening quote and insert the (fully-quoted) replacement. elsif (qc && (qc != oqc) && start!=0 && @rl_line_buffer[start - 1,1] == oqc && replacement[0,1] != oqc) start-=1 end _rl_replace_text(replacement, start, @rl_point - 1) if (replacement != match) replacement = nil end end end # Return the portion of PATHNAME that should be output when listing # possible completions. If we are hacking filename completion, we # are only interested in the basename, the portion following the # final slash. Otherwise, we return what we were passed. Since # printing empty strings is not very informative, if we're doing # filename completion, and the basename is the empty string, we look # for the previous slash and return the portion following that. If # there's no previous slash, we just return what we were passed. def printable_part(pathname) if (!@rl_filename_completion_desired) # don't need to do anything return (pathname) end temp = pathname.rindex('/') return pathname if temp.nil? File.basename(pathname) end def fnprint(to_print) printed_len = 0 case @encoding when 'E' arr = to_print.scan(/./me) when 'S' arr = to_print.scan(/./ms) when 'U' arr = to_print.scan(/./mu) when 'X' arr = to_print.dup.force_encoding(@encoding_name).chars else arr = to_print.scan(/./m) end arr.each do |s| if(ctrl_char(s)) @rl_outstream.write('^'+(s[0].ord|0x40).chr.upcase) printed_len += 2 elsif s == RUBOUT @rl_outstream.write('^?') printed_len += 2 else @rl_outstream.write(s) if @encoding=='U' printed_len += s.unpack('U').first >= 0x1000 ? 2 : 1 elsif @encoding=='X' printed_len += s.ord >= 0x1000 ? 2 : 1 else printed_len += s.length end end end printed_len end def _rl_internal_pager(lines) @rl_outstream.puts "--More--" @rl_outstream.flush i = get_y_or_n(1) _rl_erase_entire_line() if (i == 0) return -1 elsif (i == 2) return (lines - 1) else return 0 end end def path_isdir(filename) return File.directory?(filename) end # Return the character which best describes FILENAME. # `@' for symbolic links # `/' for directories # `*' for executables # `=' for sockets # `|' for FIFOs # `%' for character special devices # `#' for block special devices def stat_char(filename) return nil if !File.exists?(filename) return '/' if File.directory?(filename) return '%' if File.chardev?(filename) return '#' if File.blockdev?(filename) return '@' if File.symlink?(filename) return '=' if File.socket?(filename) return '|' if File.pipe?(filename) return '*' if File.executable?(filename) nil end # Output TO_PRINT to rl_outstream. If VISIBLE_STATS is defined and we # are using it, check for and output a single character for `special' # filenames. Return the number of characters we output. def print_filename(to_print, full_pathname) extension_char = 0.chr printed_len = fnprint(to_print) if (@rl_filename_completion_desired && (@rl_visible_stats || @_rl_complete_mark_directories)) # If to_print != full_pathname, to_print is the basename of the # path passed. In this case, we try to expand the directory # name before checking for the stat character. if (to_print != full_pathname) if full_pathname.nil? || full_pathname.length==0 dn = '/' else dn = File.dirname(full_pathname) end s = File.expand_path(dn) if (@rl_directory_completion_hook) send(@rl_directory_completion_hook,s) end slen = s.length tlen = to_print.length new_full_pathname = s.dup if (s[-1,1] == '/' ) slen-=1 else new_full_pathname[slen,1] = '/' end new_full_pathname[slen .. -1] = '/' + to_print if (@rl_visible_stats) extension_char = stat_char(new_full_pathname) else if (path_isdir(new_full_pathname)) extension_char = '/' end end new_full_pathname = nil else s = File.expand_path(full_pathname) if (@rl_visible_stats) extension_char = stat_char(s) else if (path_isdir(s)) extension_char = '/' end end end s = nil if (extension_char) @rl_outstream.write(extension_char) printed_len+=1 end end printed_len end # The user must press "y" or "n". Non-zero return means "y" pressed. def get_y_or_n(for_pager) while(true) rl_setstate(RL_STATE_MOREINPUT) c = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) if (c == 'y' || c == 'Y' || c == ' ') return (1) end if (c == 'n' || c == 'N' || c == RUBOUT) return (0) end if (c == ABORT_CHAR) _rl_abort_internal() end if (for_pager && (c == NEWLINE || c == RETURN)) return (2) end if (for_pager && (c == 'q' || c == 'Q')) return (0) end rl_ding() end end # Compute width of STRING when displayed on screen by print_filename def fnwidth(string) left = string.length + 1 width = pos = 0 while (string[pos] && string[pos,1] != 0.chr) if (ctrl_char(string[0,1]) || string[0,1] == RUBOUT) width += 2 pos+=1 else case @encoding when 'E' wc = string[pos,left-pos].scan(/./me)[0] bytes = wc.length tempwidth = wc.length when 'S' wc = string[pos,left-pos].scan(/./ms)[0] bytes = wc.length tempwidth = wc.length when 'U' wc = string[pos,left-pos].scan(/./mu)[0] bytes = wc.length tempwidth = wc.unpack('U').first >= 0x1000 ? 2 : 1 when 'X' wc = string[pos,left-pos].force_encoding(@encoding_name)[0] bytes = wc.bytesize tempwidth = wc.ord >= 0x1000 ? 2 : 1 else wc = string[pos,left-pos].scan(/./m)[0] bytes = wc.length tempwidth = wc.length end clen = bytes pos += clen w = tempwidth width += (w >= 0) ? w : 1 end end width end # Display MATCHES, a list of matching filenames in argv format. This # handles the simple case -- a single match -- first. If there is more # than one match, we compute the number of strings in the list and the # length of the longest string, which will be needed by the display # function. If the application wants to handle displaying the list of # matches itself, it sets RL_COMPLETION_DISPLAY_MATCHES_HOOK to the # address of a function, and we just call it. If we're handling the # display ourselves, we just call rl_display_match_list. We also check # that the list of matches doesn't exceed the user-settable threshold, # and ask the user if he wants to see the list if there are more matches # than RL_COMPLETION_QUERY_ITEMS. def display_matches(matches) # Move to the last visible line of a possibly-multiple-line command. _rl_move_vert(@_rl_vis_botlin) # Handle simple case first. What if there is only one answer? if matches[1].nil? temp = printable_part(matches[0]) rl_crlf() print_filename(temp, matches[0]) rl_crlf() rl_forced_update_display() @rl_display_fixed = true return end # There is more than one answer. Find out how many there are, # and find the maximum printed length of a single entry. max = 0 i = 1 while(matches[i]) temp = printable_part(matches[i]) len = fnwidth(temp) if (len > max) max = len end i += 1 end len = i - 1 # If the caller has defined a display hook, then call that now. if (@rl_completion_display_matches_hook) send(@rl_completion_display_matches_hook,matches, len, max) return end # If there are many items, then ask the user if she really wants to # see them all. if (@rl_completion_query_items > 0 && len >= @rl_completion_query_items) rl_crlf() @rl_outstream.write("Display all #{len} possibilities? (y or n)") @rl_outstream.flush if (get_y_or_n(false)==0) rl_crlf() rl_forced_update_display() @rl_display_fixed = true return end end rl_display_match_list(matches, len, max) rl_forced_update_display() @rl_display_fixed = true end # Complete the word at or before point. # WHAT_TO_DO says what to do with the completion. # `?' means list the possible completions. # TAB means do standard completion. # `*' means insert all of the possible completions. # `!' means to do standard completion, and list all possible completions if # there is more than one. # `@' means to do standard completion, and list all possible completions if # there is more than one and partial completion is not possible. def rl_complete_internal(what_to_do) rl_setstate(RL_STATE_COMPLETING) set_completion_defaults(what_to_do) saved_line_buffer = @rl_line_buffer ? @rl_line_buffer.delete(0.chr) : nil our_func = @rl_completion_entry_function ? @rl_completion_entry_function : :rl_filename_completion_function # We now look backwards for the start of a filename/variable word. _end = @rl_point found_quote = false delimiter = 0.chr quote_char = 0.chr if (@rl_point!=0) # This (possibly) changes rl_point. If it returns a non-zero char, # we know we have an open quote. quote_char,found_quote,delimiter = _rl_find_completion_word() end start = @rl_point @rl_point = _end text = rl_copy_text(start, _end) matches = gen_completion_matches(text, start, _end, our_func, found_quote, quote_char) # nontrivial_lcd is set if the common prefix adds something to the word # being completed. nontrivial_lcd = !!(matches && text != matches[0]) text = nil if matches.nil? rl_ding() saved_line_buffer = nil @completion_changed_buffer = false rl_unsetstate(RL_STATE_COMPLETING) return 0 end # If we are matching filenames, the attempted completion function will # have set rl_filename_completion_desired to a non-zero value. The basic # rl_filename_completion_function does this. i = @rl_filename_completion_desired if (postprocess_matches(matches, i) == 0) rl_ding() saved_line_buffer = nil @completion_changed_buffer = false rl_unsetstate(RL_STATE_COMPLETING) return 0 end case (what_to_do) when TAB,'!','@' # Insert the first match with proper quoting. if (matches[0]) insert_match(matches[0], start, matches[1] ? MULT_MATCH : SINGLE_MATCH, quote_char) end # If there are more matches, ring the bell to indicate. # If we are in vi mode, Posix.2 says to not ring the bell. # If the `show-all-if-ambiguous' variable is set, display # all the matches immediately. Otherwise, if this was the # only match, and we are hacking files, check the file to # see if it was a directory. If so, and the `mark-directories' # variable is set, add a '/' to the name. If not, and we # are at the end of the line, then add a space. if (matches[1]) if (what_to_do == '!') display_matches(matches) elsif (what_to_do == '@') if (!nontrivial_lcd) display_matches(matches) end elsif (@rl_editing_mode != @vi_mode) rl_ding() # There are other matches remaining. end else append_to_match(matches[0], delimiter, quote_char, nontrivial_lcd) end when '*' insert_all_matches(matches, start, quote_char) when '?' display_matches(matches) else $stderr.write("\r\nreadline: bad value #{what_to_do} for what_to_do in rl_complete\n") rl_ding() saved_line_buffer = nil rl_unsetstate(RL_STATE_COMPLETING) return 1 end matches = nil # Check to see if the line has changed through all of this manipulation. if (saved_line_buffer) @completion_changed_buffer = @rl_line_buffer.delete(0.chr) != saved_line_buffer saved_line_buffer = nil end rl_unsetstate(RL_STATE_COMPLETING) 0 end # Complete the word at or before point. You have supplied the function # that does the initial simple matching selection algorithm (see # rl_completion_matches ()). The default is to do filename completion. def rl_complete(ignore, invoking_key) if (@rl_inhibit_completion) return (_rl_insert_char(ignore, invoking_key)) elsif (@rl_last_func == :rl_complete && !@completion_changed_buffer) return (rl_complete_internal('?')) elsif (@_rl_complete_show_all) return (rl_complete_internal('!')) elsif (@_rl_complete_show_unmodified) return (rl_complete_internal('@')) else return (rl_complete_internal(TAB)) end end # Return the history entry which is logically at OFFSET in the history array. # OFFSET is relative to history_base. def history_get(offset) local_index = offset - @history_base return (local_index >= @history_length || local_index < 0 || @the_history.nil?) ? nil : @the_history[local_index] end def rl_replace_from_history(entry, flags) # Can't call with `1' because rl_undo_list might point to an undo list # from a history entry, just like we're setting up here. rl_replace_line(entry.line, false) @rl_undo_list = entry.data @rl_point = @rl_end @rl_mark = 0 if (@rl_editing_mode == @vi_mode) @rl_point = 0 @rl_mark = @rl_end end end # Remove history element WHICH from the history. The removed # element is returned to you so you can free the line, data, # and containing structure. def remove_history(which) if (which < 0 || which >= @history_length || @history_length == 0 || @the_history.nil?) return nil end return_value = @the_history[which] @the_history.delete_at(which) @history_length-=1 return_value end def block_sigint() return if @sigint_blocked #@sigint_proc = Signal.trap("INT","IGNORE") @sigint_blocked = true end def release_sigint() return if !@sigint_blocked #Signal.trap("INT",@sigint_proc) @sigint_blocked = false end def save_tty_chars() @_rl_last_tty_chars = @_rl_tty_chars h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten] h.each {|k,v| v.gsub!(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} @_rl_tty_chars.t_erase = h['erase'] @_rl_tty_chars.t_kill = h['kill'] @_rl_tty_chars.t_intr = h['intr'] @_rl_tty_chars.t_quit = h['quit'] @_rl_tty_chars.t_start = h['start'] @_rl_tty_chars.t_stop = h['stop'] @_rl_tty_chars.t_eof = h['eof'] @_rl_tty_chars.t_eol = "\n" @_rl_tty_chars.t_eol2 = h['eol2'] @_rl_tty_chars.t_susp = h['susp'] @_rl_tty_chars.t_dsusp = h['dsusp'] @_rl_tty_chars.t_reprint = h['rprnt'] @_rl_tty_chars.t_flush = h['flush'] @_rl_tty_chars.t_werase = h['werase'] @_rl_tty_chars.t_lnext = h['lnext'] @_rl_tty_chars.t_status = -1 @otio = `stty -g` end def _rl_bind_tty_special_chars(kmap) kmap[@_rl_tty_chars.t_erase] = :rl_rubout kmap[@_rl_tty_chars.t_kill] = :rl_unix_line_discard kmap[@_rl_tty_chars.t_werase] = :rl_unix_word_rubout kmap[@_rl_tty_chars.t_lnext] = :rl_quoted_insert end def prepare_terminal_settings(meta_flag) @readline_echoing_p = (`stty -a`.scan(/-*echo\b/).first == 'echo') # First, the basic settings to put us into character-at-a-time, no-echo # input mode. setting = " -echo -icrnl cbreak" # If this terminal doesn't care how the 8th bit is used, then we can # use it for the meta-key. If only one of even or odd parity is # specified, then the terminal is using parity, and we cannot. if (`stty -a`.scan(/-parenb\b/).first == '-parenb') setting << " pass8" end setting << " -ixoff" rl_bind_key(@_rl_tty_chars.t_start, :rl_restart_output) @_rl_eof_char = @_rl_tty_chars.t_eof #setting << " -isig" `stty #{setting}` end def _rl_control_keypad(on) if on && @_rl_term_ks @_rl_out_stream.write(@_rl_term_ks) elsif !on && @_rl_term_ke @_rl_out_stream.write(@_rl_term_ke) end end # Rebind all of the tty special chars that readline worries about back # to self-insert. Call this before saving the current terminal special # chars with save_tty_chars(). This only works on POSIX termios or termio # systems. def rl_tty_unset_default_bindings(kmap) # Don't bother before we've saved the tty special chars at least once. return if (!rl_isstate(RL_STATE_TTYCSAVED)) kmap[@_rl_tty_chars.t_erase] = :rl_insert kmap[@_rl_tty_chars.t_kill] = :rl_insert kmap[@_rl_tty_chars.t_lnext] = :rl_insert kmap[@_rl_tty_chars.t_werase] = :rl_insert end def rl_prep_terminal(meta_flag) if no_terminal? @readline_echoing_p = true return end return if (@terminal_prepped) # Try to keep this function from being INTerrupted. block_sigint() if (@_rl_bind_stty_chars) # If editing in vi mode, make sure we restore the bindings in the # insertion keymap no matter what keymap we ended up in. if (@rl_editing_mode == @vi_mode) rl_tty_unset_default_bindings(@vi_insertion_keymap) else rl_tty_unset_default_bindings(@_rl_keymap) end end save_tty_chars() rl_setstate(RL_STATE_TTYCSAVED) if (@_rl_bind_stty_chars) # If editing in vi mode, make sure we set the bindings in the # insertion keymap no matter what keymap we ended up in. if (@rl_editing_mode == @vi_mode) _rl_bind_tty_special_chars(@vi_insertion_keymap) else _rl_bind_tty_special_chars(@_rl_keymap) end end prepare_terminal_settings(meta_flag) if (@_rl_enable_keypad) _rl_control_keypad(true) end @rl_outstream.flush @terminal_prepped = true rl_setstate(RL_STATE_TERMPREPPED) release_sigint() end # Restore the terminal's normal settings and modes. def rl_deprep_terminal() return if ENV["TERM"].nil? return if (!@terminal_prepped) # Try to keep this function from being interrupted. block_sigint() if (@_rl_enable_keypad) _rl_control_keypad(false) end @rl_outstream.flush # restore terminal setting `stty #{@otio}` @terminal_prepped = false rl_unsetstate(RL_STATE_TERMPREPPED) release_sigint() end # Set the mark at POSITION. def _rl_set_mark_at_pos(position) return -1 if (position > @rl_end) @rl_mark = position 0 end # A bindable command to set the mark. def rl_set_mark(count, key) _rl_set_mark_at_pos(@rl_explicit_arg ? count : @rl_point) end # Kill from here to the end of the line. If DIRECTION is negative, kill # back to the line start instead. def rl_kill_line (direction, ignore) if (direction < 0) return (rl_backward_kill_line(1, ignore)) else orig_point = @rl_point rl_end_of_line(1, ignore) if (orig_point != @rl_point) rl_kill_text(orig_point, @rl_point) end @rl_point = orig_point if (@rl_editing_mode == @emacs_mode) @rl_mark = @rl_point end end 0 end # Kill backwards to the start of the line. If DIRECTION is negative, kill # forwards to the line end instead. def rl_backward_kill_line(direction, ignore) if (direction < 0) return (rl_kill_line(1, ignore)) else if (@rl_point==0) rl_ding() else orig_point = @rl_point rl_beg_of_line(1, ignore) if (@rl_point != orig_point) rl_kill_text(orig_point, @rl_point) end if (@rl_editing_mode == @emacs_mode) @rl_mark = @rl_point end end end 0 end # Kill the whole line, no matter where point is. def rl_kill_full_line(count, ignore) rl_begin_undo_group() @rl_point = 0 rl_kill_text(@rl_point, @rl_end) @rl_mark = 0 rl_end_undo_group() 0 end # Search backwards through the history looking for a string which is typed # interactively. Start with the current line. def rl_reverse_search_history(sign, key) rl_search_history(-sign, key) end # Search forwards through the history looking for a string which is typed # interactively. Start with the current line. def rl_forward_search_history(sign, key) rl_search_history(sign, key) end # Search through the history looking for an interactively typed string. # This is analogous to i-search. We start the search in the current line. # DIRECTION is which direction to search; >= 0 means forward, < 0 means # backwards. def rl_search_history(direction, invoking_key) rl_setstate(RL_STATE_ISEARCH) cxt = _rl_isearch_init(direction) rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, -1) # If we are using the callback interface, all we do is set up here and # return. The key is that we leave RL_STATE_ISEARCH set. if (rl_isstate(RL_STATE_CALLBACK)) return (0) end r = -1 while(true) c = _rl_search_getchar(cxt) # We might want to handle EOF here (c == 0) r = _rl_isearch_dispatch(cxt, cxt.lastc) break if (r <= 0) end # The searching is over. The user may have found the string that she # was looking for, or else she may have exited a failing search. If # LINE_INDEX is -1, then that shows that the string searched for was # not found. We use this to determine where to place rl_point. _rl_isearch_cleanup(cxt, r) end def _rl_scxt_alloc(type, flags) cxt = Struct.new(:type,:sflags,:search_string,:search_string_index,:search_string_size,:lines,:allocated_line, :hlen,:hindex,:save_point,:save_mark,:save_line,:last_found_line,:prev_line_found,:save_undo_list,:history_pos, :direction,:lastc,:sline,:sline_len,:sline_index,:search_terminators,:mb).new cxt.type = type cxt.sflags = flags cxt.search_string = nil cxt.search_string_size = cxt.search_string_index = 0 cxt.lines = nil cxt.allocated_line = nil cxt.hlen = cxt.hindex = 0 cxt.save_point = @rl_point cxt.save_mark = @rl_mark cxt.save_line = where_history() cxt.last_found_line = cxt.save_line cxt.prev_line_found = nil cxt.save_undo_list = nil cxt.history_pos = 0 cxt.direction = 0 cxt.lastc = 0 cxt.sline = nil cxt.sline_len = cxt.sline_index = 0 cxt.search_terminators = nil cxt end def history_list() @the_history end def _rl_isearch_init(direction) cxt = _rl_scxt_alloc(RL_SEARCH_ISEARCH, 0) if (direction < 0) cxt.sflags |= SF_REVERSE end cxt.search_terminators = @_rl_isearch_terminators ? @_rl_isearch_terminators : @default_isearch_terminators # Create an arrary of pointers to the lines that we want to search. hlist = history_list() rl_maybe_replace_line() i = 0 if (hlist) i += 1 while(hlist[i]) end # Allocate space for this many lines, +1 for the current input line, # and remember those lines. cxt.hlen = i cxt.lines = [] for i in 0 ... cxt.hlen cxt.lines[i] = hlist[i].line end if (@_rl_saved_line_for_history) cxt.lines[i] = @_rl_saved_line_for_history.line.dup else # Keep track of this so we can free it. cxt.allocated_line = @rl_line_buffer.dup cxt.lines << cxt.allocated_line end cxt.hlen+=1 # The line where we start the search. cxt.history_pos = cxt.save_line rl_save_prompt() # Initialize search parameters. cxt.search_string_size = 128 cxt.search_string_index = 0 cxt.search_string = "" # Normalize DIRECTION into 1 or -1. cxt.direction = (direction >= 0) ? 1 : -1 cxt.sline = @rl_line_buffer cxt.sline_len = cxt.sline.delete(0.chr).length cxt.sline_index = @rl_point @_rl_iscxt = cxt # save globally cxt end def rl_save_prompt() @saved_local_prompt = @local_prompt @saved_local_prefix = @local_prompt_prefix @saved_prefix_length = @prompt_prefix_length @saved_local_length = @local_prompt_len @saved_last_invisible = @prompt_last_invisible @saved_visible_length = @prompt_visible_length @saved_invis_chars_first_line = @prompt_invis_chars_first_line @saved_physical_chars = @prompt_physical_chars @local_prompt = @local_prompt_prefix = nil @local_prompt_len = 0 @prompt_last_invisible = @prompt_visible_length = @prompt_prefix_length = 0 @prompt_invis_chars_first_line = @prompt_physical_chars = 0 end def rl_restore_prompt() @local_prompt = nil @local_prompt_prefix = nil @local_prompt = @saved_local_prompt @local_prompt_prefix = @saved_local_prefix @local_prompt_len = @saved_local_length @prompt_prefix_length = @saved_prefix_length @prompt_last_invisible = @saved_last_invisible @prompt_visible_length = @saved_visible_length @prompt_invis_chars_first_line = @saved_invis_chars_first_line @prompt_physical_chars = @saved_physical_chars # can test saved_local_prompt to see if prompt info has been saved. @saved_local_prompt = @saved_local_prefix = nil @saved_local_length = 0 @saved_last_invisible = @saved_visible_length = @saved_prefix_length = 0 @saved_invis_chars_first_line = @saved_physical_chars = 0 end def rl_message(msg_buf) @rl_display_prompt = msg_buf if @saved_local_prompt.nil? rl_save_prompt() @msg_saved_prompt = true end @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = expand_prompt(msg_buf) @local_prompt_prefix = nil @local_prompt_len = @local_prompt ? @local_prompt.length : 0 send(@rl_redisplay_function) 0 end # Display the current state of the search in the echo-area. # SEARCH_STRING contains the string that is being searched for, # DIRECTION is zero for forward, or non-zero for reverse, # WHERE is the history list number of the current line. If it is # -1, then this line is the starting one. def rl_display_search(search_string, reverse_p, where) message = '(' if (reverse_p) message << "reverse-" end message << "i-search)`" if (search_string) message << search_string end message << "': " rl_message(message) message = nil send(@rl_redisplay_function) end # Transpose the characters at point. If point is at the end of the line, # then transpose the characters before point. def rl_transpose_chars(count, key) return 0 if (count == 0) if (@rl_point==0 || @rl_end < 2) rl_ding() return -1 end rl_begin_undo_group() if (@rl_point == @rl_end) if !@rl_byte_oriented @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) else @rl_point -= 1 end count = 1 end prev_point = @rl_point if !@rl_byte_oriented @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) else @rl_point -= 1 end char_length = prev_point - @rl_point dummy = @rl_line_buffer[@rl_point,char_length] rl_delete_text(@rl_point, @rl_point + char_length) @rl_point += count _rl_fix_point(0) rl_insert_text(dummy) rl_end_undo_group() dummy = nil 0 end # Here is C-u doing what Unix does. You don't *have* to use these # key-bindings. We have a choice of killing the entire line, or # killing from where we are to the start of the line. We choose the # latter, because if you are a Unix weenie, then you haven't backspaced # into the line at all, and if you aren't, then you know what you are # doing. def rl_unix_line_discard(count, key) if (@rl_point == 0) rl_ding() else rl_kill_text(@rl_point, 0) @rl_point = 0 if (@rl_editing_mode == @emacs_mode) @rl_mark = @rl_point end end 0 end # Yank back the last killed text. This ignores arguments. def rl_yank(count, ignore) if @rl_kill_ring.nil? _rl_abort_internal() return -1 end _rl_set_mark_at_pos(@rl_point) rl_insert_text(@rl_kill_ring[@rl_kill_index]) 0 end # If the last command was yank, or yank_pop, and the text just # before point is identical to the current kill item, then # delete that text from the line, rotate the index down, and # yank back some other text. def rl_yank_pop(count, key) if (((@rl_last_func != :rl_yank_pop) && (@rl_last_func != :rl_yank)) || @rl_kill_ring.nil?) _rl_abort_internal() return -1 end l = @rl_kill_ring[@rl_kill_index].length n = @rl_point - l if (n >= 0 && @rl_line_buffer[n,l] == @rl_kill_ring[@rl_kill_index][0,l]) rl_delete_text(n, @rl_point) @rl_point = n @rl_kill_index-=1 if (@rl_kill_index < 0) @rl_kill_index = @rl_kill_ring_length - 1 end rl_yank(1, 0) return 0 else _rl_abort_internal() return -1 end end # Yank the COUNTh argument from the previous history line, skipping # HISTORY_SKIP lines before looking for the `previous line'. def rl_yank_nth_arg_internal(count, ignore, history_skip) pos = where_history() if (history_skip>0) history_skip.times { entry = previous_history() } end entry = previous_history() history_set_pos(pos) if entry.nil? rl_ding() return -1 end arg = history_arg_extract(count, count, entry.line) if (arg.nil? || arg=='') rl_ding() arg = nil return -1 end rl_begin_undo_group() _rl_set_mark_at_pos(@rl_point) # Vi mode always inserts a space before yanking the argument, and it # inserts it right *after* rl_point. if (@rl_editing_mode == @vi_mode) rl_vi_append_mode(1, ignore) rl_insert_text(" ") end rl_insert_text(arg) arg = nil rl_end_undo_group() return 0 end # Yank the COUNTth argument from the previous history line. def rl_yank_nth_arg(count, ignore) rl_yank_nth_arg_internal(count, ignore, 0) end # Yank the last argument from the previous history line. This `knows' # how rl_yank_nth_arg treats a count of `$'. With an argument, this # behaves the same as rl_yank_nth_arg. @history_skip = 0 @explicit_arg_p = false @count_passed = 1 @direction = 1 @undo_needed = false def rl_yank_last_arg(count, key) if (@rl_last_func != :rl_yank_last_arg) @history_skip = 0 @explicit_arg_p = @rl_explicit_arg @count_passed = count @direction = 1 else if (@undo_needed) rl_do_undo() end if (count < 1) @direction = -@direction end @history_skip += @direction if (@history_skip < 0) @history_skip = 0 end end if (@explicit_arg_p) retval = rl_yank_nth_arg_internal(@count_passed, key, @history_skip) else retval = rl_yank_nth_arg_internal('$', key, @history_skip) end @undo_needed = retval == 0 retval end def _rl_char_search_internal(count, dir, smbchar, len) pos = @rl_point inc = (dir < 0) ? -1 : 1 while (count!=0) if ((dir < 0 && pos <= 0) || (dir > 0 && pos >= @rl_end)) rl_ding() return -1 end pos = (inc > 0) ? _rl_find_next_mbchar(@rl_line_buffer, pos, 1, MB_FIND_ANY) : _rl_find_prev_mbchar(@rl_line_buffer, pos, MB_FIND_ANY) begin if (_rl_is_mbchar_matched(@rl_line_buffer, pos, @rl_end, smbchar, len)!=0) count-=1 if (dir < 0) @rl_point = (dir == BTO) ? pos+1 : pos else @rl_point = (dir == FTO) ? pos-1 : pos end break end prepos = pos end while ((dir < 0) ? (pos = _rl_find_prev_mbchar(@rl_line_buffer, pos, MB_FIND_ANY)) != prepos : (pos = _rl_find_next_mbchar(@rl_line_buffer, pos, 1, MB_FIND_ANY)) != prepos) end 0 end def _rl_char_search(count, fdir, bdir) mbchar = '' mb_len = _rl_read_mbchar(mbchar, MB_LEN_MAX) if (count < 0) return (_rl_char_search_internal(-count, bdir, mbchar, mb_len)) else return (_rl_char_search_internal(count, fdir, mbchar, mb_len)) end end def rl_char_search(count, key) _rl_char_search(count, FFIND, BFIND) end # Undo the next thing in the list. Return 0 if there # is nothing to undo, or non-zero if there was. def trans(i) ((i) == -1 ? @rl_point : ((i) == -2 ? @rl_end : (i))) end def rl_do_undo() start = _end = waiting_for_begin = 0 begin return 0 if @rl_undo_list.nil? @_rl_doing_an_undo = true rl_setstate(RL_STATE_UNDOING) # To better support vi-mode, a start or end value of -1 means # rl_point, and a value of -2 means rl_end. if (@rl_undo_list.what == UNDO_DELETE || @rl_undo_list.what == UNDO_INSERT) start = trans(@rl_undo_list.start) _end = trans(@rl_undo_list.end) end case (@rl_undo_list.what) # Undoing deletes means inserting some text. when UNDO_DELETE @rl_point = start rl_insert_text(@rl_undo_list.text) @rl_undo_list.text = nil # Undoing inserts means deleting some text. when UNDO_INSERT rl_delete_text(start, _end) @rl_point = start # Undoing an END means undoing everything 'til we get to a BEGIN. when UNDO_END waiting_for_begin+=1 # Undoing a BEGIN means that we are done with this group. when UNDO_BEGIN if (waiting_for_begin!=0) waiting_for_begin-=1 else rl_ding() end end @_rl_doing_an_undo = false rl_unsetstate(RL_STATE_UNDOING) release = @rl_undo_list @rl_undo_list = @rl_undo_list.next replace_history_data(-1, release, @rl_undo_list) release = nil end while (waiting_for_begin!=0) 1 end # Do some undoing of things that were done. def rl_undo_command(count, key) if (count < 0) return 0 # Nothing to do. end while (count>0) if (rl_do_undo()) count-=1 else rl_ding() break end end 0 end # Delete the word at point, saving the text in the kill ring. def rl_kill_word(count, key) if (count < 0) return (rl_backward_kill_word(-count, key)) else orig_point = @rl_point rl_forward_word(count, key) if (@rl_point != orig_point) rl_kill_text(orig_point, @rl_point) end @rl_point = orig_point if (@rl_editing_mode == @emacs_mode) rl_mark = @rl_point end end 0 end # Rubout the word before point, placing it on the kill ring. def rl_backward_kill_word(count, ignore) if (count < 0) return (rl_kill_word(-count, ignore)) else orig_point = @rl_point rl_backward_word(count, ignore) if (@rl_point != orig_point) rl_kill_text(orig_point, @rl_point) end if (@rl_editing_mode == @emacs_mode) @rl_mark = @rl_point end end 0 end # Revert the current line to its previous state. def rl_revert_line(count, key) if @rl_undo_list.nil? rl_ding() else while (@rl_undo_list) rl_do_undo() end if (@rl_editing_mode == @vi_mode) @rl_point = @rl_mark = 0 # rl_end should be set correctly end end 0 end def rl_backward_char_search (count, key) _rl_char_search(count, BFIND, FFIND) end def rl_insert_completions(ignore, invoking_key) rl_complete_internal('*') end def _rl_arg_init() rl_save_prompt() @_rl_argcxt = 0 rl_setstate(RL_STATE_NUMERICARG) end def _rl_arg_getchar() rl_message("(arg: #{@rl_arg_sign * @rl_numeric_arg}) ") rl_setstate(RL_STATE_MOREINPUT) c = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) c end # Process C as part of the current numeric argument. Return -1 if the # argument should be aborted, 0 if we should not read any more chars, and # 1 if we should continue to read chars. def _rl_arg_dispatch(cxt, c) key = c # If we see a key bound to `universal-argument' after seeing digits, # it ends the argument but is otherwise ignored. if (@_rl_keymap[c] == :rl_universal_argument) if ((cxt & NUM_SAWDIGITS) == 0) @rl_numeric_arg *= 4 return 1 elsif (rl_isstate(RL_STATE_CALLBACK)) @_rl_argcxt |= NUM_READONE return 0 # XXX else rl_setstate(RL_STATE_MOREINPUT) key = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) rl_restore_prompt() rl_clear_message() rl_unsetstate(RL_STATE_NUMERICARG) return (_rl_dispatch(key, @_rl_keymap)) end end #c = (c[0].ord & ~0x80).chr r = c[1,1] if (r>='0' && r<='9') r = r.to_i @rl_numeric_arg = @rl_explicit_arg ? (@rl_numeric_arg * 10) + r : r @rl_explicit_arg = 1 @_rl_argcxt |= NUM_SAWDIGITS elsif (c == '-' && !@rl_explicit_arg) @rl_numeric_arg = 1 @_rl_argcxt |= NUM_SAWMINUS @rl_arg_sign = -1 else # Make M-- command equivalent to M--1 command. if ((@_rl_argcxt & NUM_SAWMINUS)!=0 && @rl_numeric_arg == 1 && !@rl_explicit_arg) @rl_explicit_arg = 1 end rl_restore_prompt() rl_clear_message() rl_unsetstate(RL_STATE_NUMERICARG) r = _rl_dispatch(key, @_rl_keymap) if (rl_isstate(RL_STATE_CALLBACK)) # At worst, this will cause an extra redisplay. Otherwise, # we have to wait until the next character comes in. if (!@rl_done) send(@rl_redisplay_function) end r = 0 end return r end 1 end def _rl_arg_overflow() if (@rl_numeric_arg > 1000000) @_rl_argcxt = 0 @rl_explicit_arg = @rl_numeric_arg = 0 rl_ding() rl_restore_prompt() rl_clear_message() rl_unsetstate(RL_STATE_NUMERICARG) return 1 end 0 end # Handle C-u style numeric args, as well as M--, and M-digits. def rl_digit_loop() while (true) return 1 if _rl_arg_overflow()!=0 c = _rl_arg_getchar() if (c >= "\xFE") _rl_abort_internal() return -1 end r = _rl_arg_dispatch(@_rl_argcxt, c) break if (r <= 0 || !rl_isstate(RL_STATE_NUMERICARG)) end return r end # Start a numeric argument with initial value KEY def rl_digit_argument(ignore, key) _rl_arg_init() if (rl_isstate(RL_STATE_CALLBACK)) _rl_arg_dispatch(@_rl_argcxt, key) rl_message("(arg: #{@rl_arg_sign * @rl_numeric_arg}) ") return 0 else rl_execute_next(key) return (rl_digit_loop()) end end # Make C be the next command to be executed. def rl_execute_next(c) @rl_pending_input = c rl_setstate(RL_STATE_INPUTPENDING) 0 end # Meta-< goes to the start of the history. def rl_beginning_of_history(count, key) rl_get_previous_history(1 + where_history(), key) end # Meta-> goes to the end of the history. (The current line). def rl_end_of_history(count, key) rl_maybe_replace_line() using_history() rl_maybe_unsave_line() 0 end # Uppercase the word at point. def rl_upcase_word(count, key) rl_change_case(count, UpCase) end # Lowercase the word at point. def rl_downcase_word(count, key) rl_change_case(count, DownCase) end # Upcase the first letter, downcase the rest. def rl_capitalize_word(count, key) rl_change_case(count, CapCase) end # Save an undo entry for the text from START to END. def rl_modifying(start, _end) if (start > _end) start,_end = _end,start end if (start != _end) temp = rl_copy_text(start, _end) rl_begin_undo_group() rl_add_undo(UNDO_DELETE, start, _end, temp) rl_add_undo(UNDO_INSERT, start, _end, nil) rl_end_undo_group() end 0 end # The meaty function. # Change the case of COUNT words, performing OP on them. # OP is one of UpCase, DownCase, or CapCase. # If a negative argument is given, leave point where it started, # otherwise, leave it where it moves to. def rl_change_case(count, op) start = @rl_point rl_forward_word(count, 0) _end = @rl_point if (op != UpCase && op != DownCase && op != CapCase) rl_ding() return -1 end if (count < 0) start,_end = _end,start end # We are going to modify some text, so let's prepare to undo it. rl_modifying(start, _end) inword = false while (start < _end) c = _rl_char_value(@rl_line_buffer, start) # This assumes that the upper and lower case versions are the same width. if !@rl_byte_oriented _next = _rl_find_next_mbchar(@rl_line_buffer, start, 1, MB_FIND_NONZERO) else _next = start + 1 end if (!_rl_walphabetic(c)) inword = false start = _next next end if (op == CapCase) nop = inword ? DownCase : UpCase inword = true else nop = op end if (isascii(c)) nc = (nop == UpCase) ? c.upcase : c.downcase @rl_line_buffer[start] = nc end start = _next end @rl_point = _end 0 end def isascii(c) int_val = c[0].to_i # 1.8 + 1.9 compat. return (int_val < 128 && int_val > 0) end # Search non-interactively through the history list. DIR < 0 means to # search backwards through the history of previous commands; otherwise # the search is for commands subsequent to the current position in the # history list. PCHAR is the character to use for prompting when reading # the search string; if not specified (0), it defaults to `:'. def noninc_search(dir, pchar) cxt = _rl_nsearch_init(dir, pchar) if (rl_isstate(RL_STATE_CALLBACK)) return (0) end # Read the search string. r = 0 while (true) c = _rl_search_getchar(cxt) if (c == 0.chr) break end r = _rl_nsearch_dispatch(cxt, c) if (r < 0) return 1 elsif (r == 0) break end end r = _rl_nsearch_dosearch(cxt) (r >= 0) ? _rl_nsearch_cleanup(cxt, r) : (r != 1) end # Search forward through the history list for a string. If the vi-mode # code calls this, KEY will be `?'. def rl_noninc_forward_search(count, key) noninc_search(1, (key == '?') ? '?' : nil) end # Reverse search the history list for a string. If the vi-mode code # calls this, KEY will be `/'. def rl_noninc_reverse_search(count, key) noninc_search(-1, (key == '/') ? '/' : nil) end # Make the data from the history entry ENTRY be the contents of the # current line. This doesn't do anything with rl_point; the caller # must set it. def make_history_line_current(entry) _rl_replace_text(entry.line, 0, @rl_end) _rl_fix_point(1) if (@rl_editing_mode == @vi_mode) # POSIX.2 says that the `U' command doesn't affect the copy of any # command lines to the edit line. We're going to implement that by # making the undo list start after the matching line is copied to the # current editing buffer. rl_free_undo_list() end if (@_rl_saved_line_for_history) @_rl_saved_line_for_history = nil end end # Make the current history item be the one at POS, an absolute index. # Returns zero if POS is out of range, else non-zero. def history_set_pos(pos) if (pos > @history_length || pos < 0 || @the_history.nil?) return (0) end @history_offset = pos 1 end # Do an anchored search for string through the history in DIRECTION. def history_search_prefix (string, direction) history_search_internal(string, direction, ANCHORED_SEARCH) end # Search for STRING in the history list. DIR is < 0 for searching # backwards. POS is an absolute index into the history list at # which point to begin searching. def history_search_pos(string, dir, pos) old = where_history() history_set_pos(pos) if (history_search(string, dir) == -1) history_set_pos(old) return (-1) end ret = where_history() history_set_pos(old) ret end # Search the history list for STRING starting at absolute history position # POS. If STRING begins with `^', the search must match STRING at the # beginning of a history line, otherwise a full substring match is performed # for STRING. DIR < 0 means to search backwards through the history list, # DIR >= 0 means to search forward. def noninc_search_from_pos(string, pos, dir) return 1 if (pos < 0) old = where_history() return -1 if (history_set_pos(pos) == 0) rl_setstate(RL_STATE_SEARCH) if (string[0,1] == '^') ret = history_search_prefix(string + 1, dir) else ret = history_search(string, dir) end rl_unsetstate(RL_STATE_SEARCH) if (ret != -1) ret = where_history() end history_set_pos(old) ret end # Search for a line in the history containing STRING. If DIR is < 0, the # search is backwards through previous entries, else through subsequent # entries. Returns 1 if the search was successful, 0 otherwise. def noninc_dosearch(string, dir) if (string.nil? || string == '' || @noninc_history_pos < 0) rl_ding() return 0 end pos = noninc_search_from_pos(string, @noninc_history_pos + dir, dir) if (pos == -1) # Search failed, current history position unchanged. rl_maybe_unsave_line() rl_clear_message() @rl_point = 0 rl_ding() return 0 end @noninc_history_pos = pos oldpos = where_history() history_set_pos(@noninc_history_pos) entry = current_history() if (@rl_editing_mode != @vi_mode) history_set_pos(oldpos) end make_history_line_current(entry) @rl_point = 0 @rl_mark = @rl_end rl_clear_message() 1 end def _rl_make_prompt_for_search(pchar) rl_save_prompt() # We've saved the prompt, and can do anything with the various prompt # strings we need before they're restored. We want the unexpanded # portion of the prompt string after any final newline. _p = @rl_prompt ? @rl_prompt.rindex("\n") : nil if _p.nil? len = (@rl_prompt && @rl_prompt.length>0 ) ? @rl_prompt.length : 0 if (len>0) pmt = @rl_prompt.dup else pmt = '' end pmt << pchar else _p+=1 pmt = @rl_prompt[_p..-1] pmt << pchar end # will be overwritten by expand_prompt, called from rl_message @prompt_physical_chars = @saved_physical_chars + 1 pmt end def _rl_nsearch_init(dir, pchar) cxt = _rl_scxt_alloc(RL_SEARCH_NSEARCH, 0) if (dir < 0) cxt.sflags |= SF_REVERSE # not strictly needed end cxt.direction = dir cxt.history_pos = cxt.save_line rl_maybe_save_line() # Clear the undo list, since reading the search string should create its # own undo list, and the whole list will end up being freed when we # finish reading the search string. @rl_undo_list = nil # Use the line buffer to read the search string. @rl_line_buffer[0,1] = 0.chr @rl_end = @rl_point = 0 _p = _rl_make_prompt_for_search(pchar ? pchar : ':') rl_message(_p) _p = nil rl_setstate(RL_STATE_NSEARCH) @_rl_nscxt = cxt cxt end def _rl_nsearch_cleanup(cxt, r) cxt = nil @_rl_nscxt = nil rl_unsetstate(RL_STATE_NSEARCH) r != 1 end def _rl_nsearch_abort(cxt) rl_maybe_unsave_line() rl_clear_message() @rl_point = cxt.save_point @rl_mark = cxt.save_mark rl_restore_prompt() rl_unsetstate(RL_STATE_NSEARCH) end # Process just-read character C according to search context CXT. Return -1 # if the caller should abort the search, 0 if we should break out of the # loop, and 1 if we should continue to read characters. def _rl_nsearch_dispatch(cxt, c) case (c) when "\C-W" rl_unix_word_rubout(1, c) when "\C-W" rl_unix_line_discard(1, c) when RETURN,NEWLINE return 0 when "\C-H",RUBOUT if (@rl_point == 0) _rl_nsearch_abort(cxt) return -1 end _rl_rubout_char(1, c) when "\C-C","\C-G" rl_ding() _rl_nsearch_abort(cxt) return -1 else if !@rl_byte_oriented rl_insert_text(cxt.mb) else _rl_insert_char(1, c) end end send(@rl_redisplay_function) 1 end # Perform one search according to CXT, using NONINC_SEARCH_STRING. Return # -1 if the search should be aborted, any other value means to clean up # using _rl_nsearch_cleanup (). Returns 1 if the search was successful, # 0 otherwise. def _rl_nsearch_dosearch(cxt) @rl_mark = cxt.save_mark # If rl_point == 0, we want to re-use the previous search string and # start from the saved history position. If there's no previous search # string, punt. if (@rl_point == 0) if @noninc_search_string.nil? rl_ding() rl_restore_prompt() rl_unsetstate(RL_STATE_NSEARCH) return -1 end else # We want to start the search from the current history position. @noninc_history_pos = cxt.save_line @noninc_search_string = @rl_line_buffer.dup # If we don't want the subsequent undo list generated by the search #matching a history line to include the contents of the search string, #we need to clear rl_line_buffer here. For now, we just clear the #undo list generated by reading the search string. (If the search #fails, the old undo list will be restored by rl_maybe_unsave_line.) rl_free_undo_list() end rl_restore_prompt() noninc_dosearch(@noninc_search_string, cxt.direction) end # Transpose the words at point. If point is at the end of the line, # transpose the two words before point. def rl_transpose_words(count, key) orig_point = @rl_point return if (count==0) # Find the two words. rl_forward_word(count, key) w2_end = @rl_point rl_backward_word(1, key) w2_beg = @rl_point rl_backward_word(count, key) w1_beg = @rl_point rl_forward_word(1, key) w1_end = @rl_point # Do some check to make sure that there really are two words. if ((w1_beg == w2_beg) || (w2_beg < w1_end)) rl_ding() @rl_point = orig_point return -1 end # Get the text of the words. word1 = rl_copy_text(w1_beg, w1_end) word2 = rl_copy_text(w2_beg, w2_end) # We are about to do many insertions and deletions. Remember them # as one operation. rl_begin_undo_group() # Do the stuff at word2 first, so that we don't have to worry # about word1 moving. @rl_point = w2_beg rl_delete_text(w2_beg, w2_end) rl_insert_text(word1) @rl_point = w1_beg rl_delete_text(w1_beg, w1_end) rl_insert_text(word2) # This is exactly correct since the text before this point has not # changed in length. @rl_point = w2_end # I think that does it. rl_end_undo_group() word1 = nil word2 = nil 0 end # Re-read the current keybindings file. def rl_re_read_init_file(count, ignore) r = rl_read_init_file(nil) rl_set_keymap_from_edit_mode() r end # Exchange the position of mark and point. def rl_exchange_point_and_mark(count, key) if (@rl_mark > @rl_end) @rl_mark = -1 end if (@rl_mark == -1) rl_ding() return -1 else @rl_point, @rl_mark = @rl_mark, @rl_point end 0 end # A convenience function for displaying a list of strings in # columnar format on readline's output stream. MATCHES is the list # of strings, in argv format, LEN is the number of strings in MATCHES, # and MAX is the length of the longest string in MATCHES. def rl_display_match_list(matches, len, max) # How many items of MAX length can we fit in the screen window? max += 2 limit = @_rl_screenwidth / max if (limit != 1 && (limit * max == @_rl_screenwidth)) limit-=1 end # Avoid a possible floating exception. If max > _rl_screenwidth, # limit will be 0 and a divide-by-zero fault will result. if (limit == 0) limit = 1 end # How many iterations of the printing loop? count = (len + (limit - 1)) / limit # Watch out for special case. If LEN is less than LIMIT, then # just do the inner printing loop. # 0 < len <= limit implies count = 1. # Sort the items if they are not already sorted. if (!@rl_ignore_completion_duplicates) matches[1,len] = matches[1,len].sort end rl_crlf() lines = 0 if (!@_rl_print_completions_horizontally) # Print the sorted items, up-and-down alphabetically, like ls. for i in 1 .. count l = i for j in 0 ... limit if (l > len || matches[l].nil?) break else temp = printable_part(matches[l]) printed_len = print_filename(temp, matches[l]) if (j + 1 < limit) @rl_outstream.write(' '*(max - printed_len)) end end l += count end rl_crlf() lines+=1 if (@_rl_page_completions && lines >= (@_rl_screenheight - 1) && i < count) lines = _rl_internal_pager(lines) return if (lines < 0) end end else # Print the sorted items, across alphabetically, like ls -x. i = 1 while(matches[i]) temp = printable_part(matches[i]) printed_len = print_filename(temp, matches[i]) # Have we reached the end of this line? if (matches[i+1]) if ((limit > 1) && (i % limit) == 0) rl_crlf() lines+=1 if (@_rl_page_completions && lines >= @_rl_screenheight - 1) lines = _rl_internal_pager(lines) return if (lines < 0) end else @rl_outstream.write(' '*(max - printed_len)) end end i += 1 end rl_crlf() end end # Append any necessary closing quote and a separator character to the # just-inserted match. If the user has specified that directories # should be marked by a trailing `/', append one of those instead. The # default trailing character is a space. Returns the number of characters # appended. If NONTRIVIAL_MATCH is set, we test for a symlink (if the OS # has them) and don't add a suffix for a symlink to a directory. A # nontrivial match is one that actually adds to the word being completed. # The variable rl_completion_mark_symlink_dirs controls this behavior # (it's initially set to the what the user has chosen, indicated by the # value of _rl_complete_mark_symlink_dirs, but may be modified by an # application's completion function). def append_to_match(text, delimiter, quote_char, nontrivial_match) temp_string = 0.chr * 4 temp_string_index = 0 if (quote_char && @rl_point>0 && !@rl_completion_suppress_quote && @rl_line_buffer[@rl_point - 1,1] != quote_char) temp_string[temp_string_index] = quote_char temp_string_index += 1 end if (delimiter != 0.chr) temp_string[temp_string_index] = delimiter temp_string_index += 1 elsif (!@rl_completion_suppress_append && @rl_completion_append_character) temp_string[temp_string_index] = @rl_completion_append_character temp_string_index += 1 end temp_string[temp_string_index] = 0.chr temp_string_index += 1 if (@rl_filename_completion_desired) filename = File.expand_path(text) s = (nontrivial_match && !@rl_completion_mark_symlink_dirs) ? File.lstat(filename) : File.stat(filename) if s.directory? if @_rl_complete_mark_directories # This is clumsy. Avoid putting in a double slash if point # is at the end of the line and the previous character is a # slash. if (@rl_point>0 && @rl_line_buffer[@rl_point,1] == 0.chr && @rl_line_buffer[@rl_point - 1,1] == '/' ) elsif (@rl_line_buffer[@rl_point,1] != '/') rl_insert_text('/') end end # Don't add anything if the filename is a symlink and resolves to a # directory. elsif s.symlink? && File.stat(filename).directory? else if (@rl_point == @rl_end && temp_string_index>0) rl_insert_text(temp_string) end end filename = nil else if (@rl_point == @rl_end && temp_string_index>0) rl_insert_text(temp_string) end end temp_string_index end # Stifle the history list, remembering only MAX number of lines. def stifle_history(max) max = 0 if (max < 0) if (@history_length > max) @the_history.slice!(0,(@history_length - max)) @history_length = max end @history_stifled = true @max_input_history = @history_max_entries = max end # Stop stifling the history. This returns the previous maximum # number of history entries. The value is positive if the history # was stifled, negative if it wasn't. def unstifle_history() if (@history_stifled) @history_stifled = false return (@history_max_entries) else return (-@history_max_entries) end end def history_is_stifled() return (@history_stifled) end def clear_history() @the_history = nil @history_offset = @history_length = 0 end # Insert COUNT characters from STRING to the output stream at column COL. def insert_some_chars(string, count, col) if @hConsoleHandle _rl_output_some_chars(string,0,count) else # DEBUGGING if (@rl_byte_oriented) if (count != col) $stderr.write("readline: debug: insert_some_chars: count (#{count}) != col (#{col})\n"); end end # If IC is defined, then we do not have to "enter" insert mode. #if (@_rl_term_IC) # buffer = tgoto(@_rl_term_IC, 0, col) # @_rl_out_stream.write(buffer) # _rl_output_some_chars(string,0,count) #else # If we have to turn on insert-mode, then do so. if (@_rl_term_im) @_rl_out_stream.write(@_rl_term_im) end # If there is a special command for inserting characters, then # use that first to open up the space. if (@_rl_term_ic) @_rl_out_stream.write(@_rl_term_ic * count) end # Print the text. _rl_output_some_chars(string,0, count) # If there is a string to turn off insert mode, we had best use # it now. if (@_rl_term_ei) @_rl_out_stream.write(@_rl_term_ei) end #end end end # Delete COUNT characters from the display line. def delete_chars(count) return if (count > @_rl_screenwidth) # XXX if @hConsoleHandle.nil? #if (@_rl_term_DC) # buffer = tgoto(_rl_term_DC, count, count); # @_rl_out_stream.write(buffer * count) #else if (@_rl_term_dc) @_rl_out_stream.write(@_rl_term_dc * count) end #end end end # adjust pointed byte and find mbstate of the point of string. # adjusted point will be point <= adjusted_point, and returns # differences of the byte(adjusted_point - point). # if point is invalied (point < 0 || more than string length), # it returns -1 def _rl_adjust_point(string, point) length = string.length return -1 if (point < 0) return -1 if (length < point) pos = 0 case @encoding when 'E' pos = string.scan(/./me).inject(0){|r,x| r= str.length end point end # Find previous character started byte point of the specified seed. # Returned point will be point <= seed. If flags is MB_FIND_NONZERO, # we look for non-zero-width multibyte characters. def _rl_find_prev_mbchar(string, seed, flags) if @encoding == 'N' return ((seed == 0) ? seed : seed - 1) end length = string.length if seed < 0 return 0 elsif length < seed return length end case @encoding when 'E' string[0,seed].scan(/./me)[0..-2].to_s.length when 'S' string[0,seed].scan(/./ms)[0..-2].to_s.length when 'U' string[0,seed].scan(/./mu)[0..-2].to_s.length when 'X' string[0,seed].force_encoding(@encoding_name)[0..-2].bytesize end end # compare the specified two characters. If the characters matched, # return true. Otherwise return false. def _rl_compare_chars(buf1, pos1, buf2, pos2) return false if buf1[pos1].nil? || buf2[pos2].nil? case @encoding when 'E' buf1[pos1..-1].scan(/./me)[0] == buf2[pos2..-1].scan(/./me)[0] when 'S' buf1[pos1..-1].scan(/./ms)[0] == buf2[pos2..-1].scan(/./ms)[0] when 'U' buf1[pos1..-1].scan(/./mu)[0] == buf2[pos2..-1].scan(/./mu)[0] when 'X' buf1[pos1..-1].force_encoding(@encoding_name)[0] == buf2[pos2..-1].force_encoding(@encoding_name)[0] else buf1[pos1] == buf2[pos2] end end # return the number of bytes parsed from the multibyte sequence starting # at src, if a non-L'\0' wide character was recognized. It returns 0, # if a L'\0' wide character was recognized. It returns (size_t)(-1), # if an invalid multibyte sequence was encountered. It returns (size_t)(-2) # if it couldn't parse a complete multibyte character. def _rl_get_char_len(src) return 0 if src[0,1] == 0.chr || src.length==0 case @encoding when 'E' len = src.scan(/./me)[0].to_s.length when 'S' len = src.scan(/./ms)[0].to_s.length when 'U' len = src.scan(/./mu)[0].to_s.length when 'X' src = src.dup.force_encoding(@encoding_name) len = src.valid_encoding? ? src[0].bytesize : 0 else len = 1 end len==0 ? -2 : len end # read multibyte char def _rl_read_mbchar(mbchar, size) mb_len = 0 while (mb_len < size) rl_setstate(RL_STATE_MOREINPUT) mbchar << rl_read_key() mb_len += 1 rl_unsetstate(RL_STATE_MOREINPUT) case @encoding when 'E' break unless mbchar.scan(/./me).empty? when 'S' break unless mbchar.scan(/./ms).empty? when 'U' break unless mbchar.scan(/./mu).empty? when 'X' break if mbchar.dup.force_encoding(@encoding_name).valid_encoding? end end mb_len end # Read a multibyte-character string whose first character is FIRST into # the buffer MB of length MLEN. Returns the last character read, which # may be FIRST. Used by the search functions, among others. Very similar # to _rl_read_mbchar. def _rl_read_mbstring(first, mb, mlen) c = first for i in 0 ... mlen mb << c if _rl_get_char_len(mb) == -2 # Read more for multibyte character rl_setstate(RL_STATE_MOREINPUT) c = rl_read_key() rl_unsetstate(RL_STATE_MOREINPUT) else break end end c end def _rl_is_mbchar_matched(string, seed, _end, mbchar, length) return 0 if ((_end - seed) < length) for i in 0 ... length if (string[seed + i] != mbchar[i]) return 0 end end 1 end # Redraw the last line of a multi-line prompt that may possibly contain # terminal escape sequences. Called with the cursor at column 0 of the # line to draw the prompt on. def redraw_prompt(t) oldp = @rl_display_prompt rl_save_prompt() @rl_display_prompt = t @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = expand_prompt(t) @local_prompt_prefix = nil @local_prompt_len = @local_prompt ? @local_prompt.length : 0 rl_forced_update_display() @rl_display_prompt = oldp rl_restore_prompt() end # Redisplay the current line after a SIGWINCH is received. def _rl_redisplay_after_sigwinch() # Clear the current line and put the cursor at column 0. Make sure # the right thing happens if we have wrapped to a new screen line. if @_rl_term_cr @rl_outstream.write(@_rl_term_cr) @_rl_last_c_pos = 0 if @_rl_term_clreol @rl_outstream.write(@_rl_term_clreol) else space_to_eol(@_rl_screenwidth) @rl_outstream.write(@_rl_term_cr) end if @_rl_last_v_pos > 0 _rl_move_vert(0) end else rl_crlf() end # Redraw only the last line of a multi-line prompt. t = @rl_display_prompt.index("\n") if t redraw_prompt(@rl_display_prompt[(t+1)..-1]) else rl_forced_update_display() end end def rl_resize_terminal() if @readline_echoing_p _rl_get_screen_size(@rl_instream.fileno, 1) if @rl_redisplay_function != :rl_redisplay rl_forced_update_display() else _rl_redisplay_after_sigwinch() end end end def rl_sigwinch_handler(sig) rl_setstate(RL_STATE_SIGHANDLER) rl_resize_terminal() rl_unsetstate(RL_STATE_SIGHANDLER) end module_function :rl_attempted_completion_function,:rl_deprep_term_function, :rl_event_hook,:rl_attempted_completion_over,:rl_basic_quote_characters, :rl_basic_word_break_characters,:rl_completer_quote_characters, :rl_completer_word_break_characters,:rl_completion_append_character, :rl_filename_quote_characters,:rl_instream,:rl_library_version,:rl_outstream, :rl_readline_name, :rl_attempted_completion_function=,:rl_deprep_term_function=, :rl_event_hook=,:rl_attempted_completion_over=,:rl_basic_quote_characters=, :rl_basic_word_break_characters=,:rl_completer_quote_characters=, :rl_completer_word_break_characters=,:rl_completion_append_character=, :rl_filename_quote_characters=,:rl_instream=,:rl_library_version=,:rl_outstream=, :rl_readline_name=,:history_length,:history_base def no_terminal? term = ENV["TERM"] term.nil? || (term == 'dumb') || (RUBY_PLATFORM =~ /mswin|mingw/) end private :no_terminal? end