lib/reline/config.rb in reline-0.0.0 vs lib/reline/config.rb in reline-0.0.1
- old
+ new
@@ -1,25 +1,72 @@
require 'pathname'
class Reline::Config
- DEFAULT_PATH = Pathname.new(Dir.home).join('.inputrc')
+ attr_reader :test_mode
+ DEFAULT_PATH = '~/.inputrc'
+
+ KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
+
+ class InvalidInputrc < RuntimeError
+ attr_accessor :file, :lineno
+ end
+
+ VARIABLE_NAMES = %w{
+ bind-tty-special-chars
+ blink-matching-paren
+ byte-oriented
+ completion-ignore-case
+ convert-meta
+ disable-completion
+ enable-keypad
+ expand-tilde
+ history-preserve-point
+ history-size
+ horizontal-scroll-mode
+ input-meta
+ keyseq-timeout
+ 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
+ }
+ VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
+ VARIABLE_NAME_SYMBOLS.each do |v|
+ attr_accessor v
+ end
+
def initialize
+ @additional_key_bindings = {} # from inputrc
+ @default_key_bindings = {} # environment-dependent
@skip_section = nil
- @if_stack = []
+ @if_stack = nil
@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
+ @history_size = 500
+ @keyseq_timeout = 500
+ @test_mode = false
end
def reset
if editing_mode_is?(:vi_command)
@editing_mode_label = :vi_insert
end
+ @additional_key_bindings = {}
+ @default_key_bindings = {}
end
def editing_mode
@key_actors[@editing_mode_label]
end
@@ -34,33 +81,55 @@
def keymap
@key_actors[@keymap_label]
end
- def read(file = DEFAULT_PATH)
+ def read(file = nil)
+ file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH)
begin
if file.respond_to?(:readlines)
lines = file.readlines
else
- File.open(file, 'rt') do |f|
- lines = f.readlines
- end
+ lines = File.readlines(file)
end
rescue Errno::ENOENT
- $stderr.puts "no such file #{file}"
return nil
end
- read_lines(lines)
+ read_lines(lines, file)
self
+ rescue InvalidInputrc => e
+ warn e.message
+ nil
end
- def read_lines(lines)
- lines.each do |line|
- line = line.chomp.gsub(/^\s*/, '')
+ def key_bindings
+ # override @default_key_bindings with @additional_key_bindings
+ @default_key_bindings.merge(@additional_key_bindings)
+ end
+
+ def add_default_key_binding(keystroke, target)
+ @default_key_bindings[keystroke] = target
+ end
+
+ def reset_default_key_bindings
+ @default_key_bindings = {}
+ end
+
+ def read_lines(lines, file = nil)
+ conditions = [@skip_section, @if_stack]
+ @skip_section = nil
+ @if_stack = []
+
+ lines.each_with_index do |line, no|
+ next if line.match(/\A\s*#/)
+
+ no += 1
+
+ line = line.chomp.lstrip
if line[0, 1] == '$'
- handle_directive(line[1..-1])
+ handle_directive(line[1..-1], file, no)
next
end
next if @skip_section
@@ -68,72 +137,57 @@
var, value = $1.downcase, $2.downcase
bind_variable(var, value)
next
end
- if line =~ /\s*(.*)\s*:\s*(.*)\s*$/
+ if line =~ /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/
key, func_name = $1, $2
- bind_key(key, func_name)
+ keystroke, func = bind_key(key, func_name)
+ next unless keystroke
+ @additional_key_bindings[keystroke] = func
end
end
+ unless @if_stack.empty?
+ raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
+ end
+ ensure
+ @skip_section, @if_stack = conditions
end
- def handle_directive(directive)
+ def handle_directive(directive, file, no)
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'
+ condition = true if args == 'Reline'
end
- unless @skip_section.nil?
- @if_stack << @skip_section
- end
+ @if_stack << [file, no, @skip_section]
@skip_section = !condition
when 'else'
+ if @if_stack.empty?
+ raise InvalidInputrc, "#{file}:#{no}: unmatched else"
+ end
@skip_section = !@skip_section
when 'endif'
- @skip_section = nil
- unless @if_stack.empty?
- @skip_section = @if_stack.pop
+ if @if_stack.empty?
+ raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
end
+ @skip_section = @if_stack.pop
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
+ when VARIABLE_NAMES then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
when 'bell-style'
@bell_style =
case value
@@ -168,68 +222,66 @@
when 'vi', 'vi-move', 'vi-command'
@keymap_label = :vi_command
when 'vi-insert'
@keymap_label = :vi_insert
end
+ when 'keyseq-timeout'
+ @keyseq_timeout = value.to_i
end
end
def bind_key(key, func_name)
- if key =~ /"(.*)"/
- keyseq = parse_keyseq($1).force_encoding('ASCII-8BIT')
+ if key =~ /\A"(.*)"\z/
+ keyseq = parse_keyseq($1)
else
keyseq = nil
end
if func_name =~ /"(.*)"/
- func = parse_keyseq($1).force_encoding('ASCII-8BIT')
+ func = parse_keyseq($1)
else
- func = func_name.to_sym # It must be macro.
+ func = func_name.tr(?-, ?_).to_sym # It must be macro.
end
[keyseq, func]
end
- def key_notation_to_char(notation)
+ def key_notation_to_code(notation)
case notation
- when /\\C-([A-Za-z_])/
- (1 + $1.downcase.ord - ?a.ord).chr('ASCII-8BIT')
- when /\\M-([0-9A-Za-z_])/
+ when /\\(?:C|Control)-([A-Za-z_])/
+ (1 + $1.downcase.ord - ?a.ord)
+ when /\\(?:M|Meta)-([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_]/
+ 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
+ when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[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
+ when /\\(\d{1,3})/ then $1.to_i(8) # octal
+ when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
+ when "\\e" then ?\e.ord
+ when "\\\\" then ?\\.ord
+ when "\\\"" then ?".ord
+ when "\\'" then ?'.ord
+ when "\\a" then ?\a.ord
+ when "\\b" then ?\b.ord
+ when "\\d" then ?\d.ord
+ when "\\f" then ?\f.ord
+ when "\\n" then ?\n.ord
+ when "\\r" then ?\r.ord
+ when "\\t" then ?\t.ord
+ when "\\v" then ?\v.ord
+ else notation.ord
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 = $'
+ ret = []
+ str.scan(KEYSEQ_PATTERN) do
+ ret << key_notation_to_code($&)
end
ret
end
end