4 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}|./
6 class InvalidInputrc < RuntimeError
7 attr_accessor :file, :lineno
11 completion-ignore-case
21 enable-bracketed-paste
24 VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
25 VARIABLE_NAME_SYMBOLS.each do |v|
29 attr_accessor :autocompletion
32 @additional_key_bindings = {} # from inputrc
33 @additional_key_bindings[:emacs] = {}
34 @additional_key_bindings[:vi_insert] = {}
35 @additional_key_bindings[:vi_command] = {}
36 @oneshot_key_bindings = {}
37 @editing_mode_label = :emacs
38 @keymap_label = :emacs
41 @key_actors[:emacs] = Reline::KeyActor::Emacs.new
42 @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
43 @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
44 @vi_cmd_mode_string = '(cmd)'
45 @vi_ins_mode_string = '(ins)'
46 @emacs_mode_string = '@'
47 # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
48 @history_size = -1 # unlimited
51 @autocompletion = false
52 @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
54 @enable_bracketed_paste = true
58 if editing_mode_is?(:vi_command)
59 @editing_mode_label = :vi_insert
61 @oneshot_key_bindings.clear
65 @key_actors[@editing_mode_label]
68 def editing_mode=(val)
69 @editing_mode_label = val
72 def editing_mode_is?(*val)
73 val.any?(@editing_mode_label)
77 @key_actors[@keymap_label]
88 return File.expand_path(ENV['INPUTRC'])
91 # In the XDG Specification, if ~/.config/readline/inputrc exists, then
92 # ~/.inputrc should not be read, but for compatibility with GNU Readline,
93 # if ~/.inputrc exists, then it is given priority.
94 home_rc_path = File.expand_path('~/.inputrc')
95 return home_rc_path if File.exist?(home_rc_path)
97 case path = ENV['XDG_CONFIG_HOME']
100 path = File.join(path, 'readline/inputrc')
101 return path if File.exist?(path) and path == File.expand_path(path)
104 path = File.expand_path('~/.config/readline/inputrc')
105 return path if File.exist?(path)
110 private def default_inputrc_path
111 @default_inputrc_path ||= inputrc_path
116 file ||= default_inputrc_path
118 if file.respond_to?(:readlines)
119 lines = file.readlines
121 lines = File.readlines(file)
127 read_lines(lines, file)
129 rescue InvalidInputrc => e
135 # The key bindings for each editing mode will be overwritten by the user-defined ones.
136 kb = @key_actors[@editing_mode_label].default_key_bindings.dup
137 kb.merge!(@additional_key_bindings[@editing_mode_label])
138 kb.merge!(@oneshot_key_bindings)
142 def add_oneshot_key_binding(keystroke, target)
143 @oneshot_key_bindings[keystroke] = target
146 def reset_oneshot_key_bindings
147 @oneshot_key_bindings.clear
150 def add_default_key_binding_by_keymap(keymap, keystroke, target)
151 @key_actors[keymap].default_key_bindings[keystroke] = target
154 def add_default_key_binding(keystroke, target)
155 @key_actors[@keymap_label].default_key_bindings[keystroke] = target
158 def read_lines(lines, file = nil)
159 if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
161 lines = lines.map do |l|
162 l.encode(Reline.encoding_system_needs)
163 rescue Encoding::UndefinedConversionError
164 mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
165 raise Reline::ConfigEncodingConversionError.new(mes)
171 lines.each_with_index do |line, no|
172 next if line.match(/\A\s*#/)
176 line = line.chomp.lstrip
177 if line.start_with?('$')
178 handle_directive(line[1..-1], file, no, if_stack)
182 next if if_stack.any? { |_no, skip| skip }
185 when /^set +([^ ]+) +([^ ]+)/i
186 var, value = $1.downcase, $2
187 bind_variable(var, value)
189 when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
190 key, func_name = $1, $2
191 func_name = func_name.split.first
192 keystroke, func = bind_key(key, func_name)
193 next unless keystroke
194 @additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
197 unless if_stack.empty?
198 raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
202 def handle_directive(directive, file, no, if_stack)
203 directive, args = directive.split(' ')
208 when /^mode=(vi|emacs)$/i
210 # NOTE: mode=vi means vi-insert mode
211 mode = 'vi_insert' if mode == 'vi'
212 if @editing_mode_label == mode.to_sym
217 else # application name
218 condition = true if args == 'Ruby'
219 condition = true if args == 'Reline'
221 if_stack << [no, !condition]
224 raise InvalidInputrc, "#{file}:#{no}: unmatched else"
226 if_stack.last[1] = !if_stack.last[1]
229 raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
233 read(File.expand_path(args))
237 def bind_variable(name, value)
241 @history_size = Integer(value)
258 @comment_begin = value.dup
259 when 'completion-query-items'
260 @completion_query_items = value.to_i
261 when 'isearch-terminators'
262 @isearch_terminators = retrieve_string(value)
266 @editing_mode_label = :emacs
267 @keymap_label = :emacs
270 @editing_mode_label = :vi_insert
271 @keymap_label = :vi_insert
276 when 'emacs', 'emacs-standard'
277 @keymap_label = :emacs
280 @keymap_label = :emacs
281 @keymap_prefix = [?\C-x.ord]
283 @keymap_label = :emacs
284 @keymap_prefix = [?\e.ord]
285 when 'vi', 'vi-move', 'vi-command'
286 @keymap_label = :vi_command
289 @keymap_label = :vi_insert
292 when 'keyseq-timeout'
293 @keyseq_timeout = value.to_i
294 when 'show-mode-in-prompt'
297 @show_mode_in_prompt = false
299 @show_mode_in_prompt = true
301 @show_mode_in_prompt = false
303 when 'vi-cmd-mode-string'
304 @vi_cmd_mode_string = retrieve_string(value)
305 when 'vi-ins-mode-string'
306 @vi_ins_mode_string = retrieve_string(value)
307 when 'emacs-mode-string'
308 @emacs_mode_string = retrieve_string(value)
309 when *VARIABLE_NAMES then
310 variable_name = :"@#{name.tr(?-, ?_)}"
311 instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
315 def retrieve_string(str)
316 str = $1 if str =~ /\A"(.*)"\z/
317 parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
320 def bind_key(key, func_name)
321 if key =~ /\A"(.*)"\z/
322 keyseq = parse_keyseq($1)
326 if func_name =~ /"(.*)"/
327 func = parse_keyseq($1)
329 func = func_name.tr(?-, ?_).to_sym # It must be macro.
334 def key_notation_to_code(notation)
336 when /\\(?:C|Control)-([A-Za-z_])/
337 (1 + $1.downcase.ord - ?a.ord)
338 when /\\(?:M|Meta)-([0-9A-Za-z_])/
342 ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
344 ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
346 ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
348 when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
350 when /\\(\d{1,3})/ then $1.to_i(8) # octal
351 when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
352 when "\\e" then ?\e.ord
353 when "\\\\" then ?\\.ord
354 when "\\\"" then ?".ord
355 when "\\'" then ?'.ord
356 when "\\a" then ?\a.ord
357 when "\\b" then ?\b.ord
358 when "\\d" then ?\d.ord
359 when "\\f" then ?\f.ord
360 when "\\n" then ?\n.ord
361 when "\\r" then ?\r.ord
362 when "\\t" then ?\t.ord
363 when "\\v" then ?\v.ord
368 def parse_keyseq(str)
370 str.scan(KEYSEQ_PATTERN) do
371 ret << key_notation_to_code($&)
376 private def seven_bit_encoding?(encoding)
377 encoding == Encoding::US_ASCII