require 'pathname' class Reline::Config DEFAULT_PATH = Pathname.new(Dir.home).join('.inputrc') def initialize @skip_section = nil @if_stack = [] @editing_mode_label = :emacs @keymap_label = :emacs @key_actors = {} @key_actors[:emacs] = Reline::KeyActor::Emacs.new @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new end def reset if editing_mode_is?(:vi_command) @editing_mode_label = :vi_insert end end def editing_mode @key_actors[@editing_mode_label] end def editing_mode=(val) @editing_mode_label = val end def editing_mode_is?(*val) (val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label) end def keymap @key_actors[@keymap_label] end def read(file = DEFAULT_PATH) begin if file.respond_to?(:readlines) lines = file.readlines else File.open(file, 'rt') do |f| lines = f.readlines end end rescue Errno::ENOENT $stderr.puts "no such file #{file}" return nil end read_lines(lines) self end def read_lines(lines) lines.each do |line| line = line.chomp.gsub(/^\s*/, '') if line[0, 1] == '$' handle_directive(line[1..-1]) next end next if @skip_section if line.match(/^set +([^ ]+) +([^ ]+)/i) var, value = $1.downcase, $2.downcase bind_variable(var, value) next end if line =~ /\s*(.*)\s*:\s*(.*)\s*$/ key, func_name = $1, $2 bind_key(key, func_name) end end end def handle_directive(directive) directive, args = directive.split(' ') case directive when 'if' condition = false case args # TODO: variables when 'mode' when 'term' when 'version' else # application name condition = true if args == 'Ruby' end unless @skip_section.nil? @if_stack << @skip_section end @skip_section = !condition when 'else' @skip_section = !@skip_section when 'endif' @skip_section = nil unless @if_stack.empty? @skip_section = @if_stack.pop end when 'include' read(args) end end def bind_variable(name, value) case name when %w{ bind-tty-special-chars blink-matching-paren byte-oriented completion-ignore-case convert-meta disable-completion enable-keypad expand-tilde history-preserve-point horizontal-scroll-mode input-meta mark-directories mark-modified-lines mark-symlinked-directories match-hidden-files meta-flag output-meta page-completions prefer-visible-bell print-completions-horizontally show-all-if-ambiguous show-all-if-unmodified visible-stats } then variable_name = :"@#{name.tr(?-, ?_)}" instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on') when 'bell-style' @bell_style = case value when 'none', 'off' :none when 'audible', 'on' :audible when 'visible' :visible else :audible end when 'comment-begin' @comment_begin = value.dup when 'completion-query-items' @completion_query_items = value.to_i when 'isearch-terminators' @isearch_terminators = instance_eval(value) when 'editing-mode' case value when 'emacs' @editing_mode_label = :emacs @keymap_label = :emacs when 'vi' @editing_mode_label = :vi_insert @keymap_label = :vi_insert end when 'keymap' case value when 'emacs', 'emacs-standard', 'emacs-meta', 'emacs-ctlx' @keymap_label = :emacs when 'vi', 'vi-move', 'vi-command' @keymap_label = :vi_command when 'vi-insert' @keymap_label = :vi_insert end end end def bind_key(key, func_name) if key =~ /"(.*)"/ keyseq = parse_keyseq($1).force_encoding('ASCII-8BIT') else keyseq = nil end if func_name =~ /"(.*)"/ func = parse_keyseq($1).force_encoding('ASCII-8BIT') else func = func_name.to_sym # It must be macro. end [keyseq, func] end def key_notation_to_char(notation) case notation when /\\C-([A-Za-z_])/ (1 + $1.downcase.ord - ?a.ord).chr('ASCII-8BIT') when /\\M-([0-9A-Za-z_])/ modified_key = $1 code = case $1 when /[0-9]/ ?\M-0.bytes.first + (modified_key.ord - ?0.ord) when /[A-Z]/ ?\M-A.bytes.first + (modified_key.ord - ?A.ord) when /[a-z]/ ?\M-a.bytes.first + (modified_key.ord - ?a.ord) end code.chr('ASCII-8BIT') when /\\C-M-[A-Za-z_]/, /\\M-C-[A-Za-z_]/ # 129 M-^A when /\\(\d{1,3})/ then $1.to_i(8).chr # octal when /\\x(\h{1,2})/ then $1.to_i(16).chr # hexadecimal when "\\e" then ?\e when "\\\\" then ?\\ when "\\\"" then ?" when "\\'" then ?' when "\\a" then ?\a when "\\b" then ?\b when "\\d" then ?\d when "\\f" then ?\f when "\\n" then ?\n when "\\r" then ?\r when "\\t" then ?\t when "\\v" then ?\v else notation end end def parse_keyseq(str) # TODO: Control- and Meta- ret = String.new(encoding: 'ASCII-8BIT') while str =~ /(\\C-[A-Za-z_]|\\M-[0-9A-Za-z_]|\\C-M-[A-Za-z_]|\\M-C-[A-Za-z_]|\\e|\\\\|\\"|\\'|\\a|\\b|\\d|\\f|\\n|\\r|\\t|\\v|\\\d{1,3}|\\x\h{1,2}|.)/ ret << key_notation_to_char($&) str = $' end ret end end