[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / lib / reline / config.rb
blobd44c2675abace0a3eab4fbbec9ec51222dfe1eac
1 class Reline::Config
2   attr_reader :test_mode
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
8   end
10   VARIABLE_NAMES = %w{
11     completion-ignore-case
12     convert-meta
13     disable-completion
14     history-size
15     keyseq-timeout
16     show-all-if-ambiguous
17     show-mode-in-prompt
18     vi-cmd-mode-string
19     vi-ins-mode-string
20     emacs-mode-string
21     enable-bracketed-paste
22     isearch-terminators
23   }
24   VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
25   VARIABLE_NAME_SYMBOLS.each do |v|
26     attr_accessor v
27   end
29   attr_accessor :autocompletion
31   def initialize
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
39     @keymap_prefix = []
40     @key_actors = {}
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
49     @keyseq_timeout = 500
50     @test_mode = false
51     @autocompletion = false
52     @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
53     @loaded = false
54     @enable_bracketed_paste = true
55   end
57   def reset
58     if editing_mode_is?(:vi_command)
59       @editing_mode_label = :vi_insert
60     end
61     @oneshot_key_bindings.clear
62   end
64   def editing_mode
65     @key_actors[@editing_mode_label]
66   end
68   def editing_mode=(val)
69     @editing_mode_label = val
70   end
72   def editing_mode_is?(*val)
73     val.any?(@editing_mode_label)
74   end
76   def keymap
77     @key_actors[@keymap_label]
78   end
80   def loaded?
81     @loaded
82   end
84   def inputrc_path
85     case ENV['INPUTRC']
86     when nil, ''
87     else
88       return File.expand_path(ENV['INPUTRC'])
89     end
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']
98     when nil, ''
99     else
100       path = File.join(path, 'readline/inputrc')
101       return path if File.exist?(path) and path == File.expand_path(path)
102     end
104     path = File.expand_path('~/.config/readline/inputrc')
105     return path if File.exist?(path)
107     return home_rc_path
108   end
110   private def default_inputrc_path
111     @default_inputrc_path ||= inputrc_path
112   end
114   def read(file = nil)
115     @loaded = true
116     file ||= default_inputrc_path
117     begin
118       if file.respond_to?(:readlines)
119         lines = file.readlines
120       else
121         lines = File.readlines(file)
122       end
123     rescue Errno::ENOENT
124       return nil
125     end
127     read_lines(lines, file)
128     self
129   rescue InvalidInputrc => e
130     warn e.message
131     nil
132   end
134   def key_bindings
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)
139     kb
140   end
142   def add_oneshot_key_binding(keystroke, target)
143     @oneshot_key_bindings[keystroke] = target
144   end
146   def reset_oneshot_key_bindings
147     @oneshot_key_bindings.clear
148   end
150   def add_default_key_binding_by_keymap(keymap, keystroke, target)
151     @key_actors[keymap].default_key_bindings[keystroke] = target
152   end
154   def add_default_key_binding(keystroke, target)
155     @key_actors[@keymap_label].default_key_bindings[keystroke] = target
156   end
158   def read_lines(lines, file = nil)
159     if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
160       begin
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)
166         end
167       end
168     end
169     if_stack = []
171     lines.each_with_index do |line, no|
172       next if line.match(/\A\s*#/)
174       no += 1
176       line = line.chomp.lstrip
177       if line.start_with?('$')
178         handle_directive(line[1..-1], file, no, if_stack)
179         next
180       end
182       next if if_stack.any? { |_no, skip| skip }
184       case line
185       when /^set +([^ ]+) +([^ ]+)/i
186         var, value = $1.downcase, $2
187         bind_variable(var, value)
188         next
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
195       end
196     end
197     unless if_stack.empty?
198       raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
199     end
200   end
202   def handle_directive(directive, file, no, if_stack)
203     directive, args = directive.split(' ')
204     case directive
205     when 'if'
206       condition = false
207       case args
208       when /^mode=(vi|emacs)$/i
209         mode = $1.downcase
210         # NOTE: mode=vi means vi-insert mode
211         mode = 'vi_insert' if mode == 'vi'
212         if @editing_mode_label == mode.to_sym
213           condition = true
214         end
215       when 'term'
216       when 'version'
217       else # application name
218         condition = true if args == 'Ruby'
219         condition = true if args == 'Reline'
220       end
221       if_stack << [no, !condition]
222     when 'else'
223       if if_stack.empty?
224         raise InvalidInputrc, "#{file}:#{no}: unmatched else"
225       end
226       if_stack.last[1] = !if_stack.last[1]
227     when 'endif'
228       if if_stack.empty?
229         raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
230       end
231       if_stack.pop
232     when 'include'
233       read(File.expand_path(args))
234     end
235   end
237   def bind_variable(name, value)
238     case name
239     when 'history-size'
240       begin
241         @history_size = Integer(value)
242       rescue ArgumentError
243         @history_size = 500
244       end
245     when 'bell-style'
246       @bell_style =
247         case value
248         when 'none', 'off'
249           :none
250         when 'audible', 'on'
251           :audible
252         when 'visible'
253           :visible
254         else
255           :audible
256         end
257     when 'comment-begin'
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)
263     when 'editing-mode'
264       case value
265       when 'emacs'
266         @editing_mode_label = :emacs
267         @keymap_label = :emacs
268         @keymap_prefix = []
269       when 'vi'
270         @editing_mode_label = :vi_insert
271         @keymap_label = :vi_insert
272         @keymap_prefix = []
273       end
274     when 'keymap'
275       case value
276       when 'emacs', 'emacs-standard'
277         @keymap_label = :emacs
278         @keymap_prefix = []
279       when 'emacs-ctlx'
280         @keymap_label = :emacs
281         @keymap_prefix = [?\C-x.ord]
282       when 'emacs-meta'
283         @keymap_label = :emacs
284         @keymap_prefix = [?\e.ord]
285       when 'vi', 'vi-move', 'vi-command'
286         @keymap_label = :vi_command
287         @keymap_prefix = []
288       when 'vi-insert'
289         @keymap_label = :vi_insert
290         @keymap_prefix = []
291       end
292     when 'keyseq-timeout'
293       @keyseq_timeout = value.to_i
294     when 'show-mode-in-prompt'
295       case value
296       when 'off'
297         @show_mode_in_prompt = false
298       when 'on'
299         @show_mode_in_prompt = true
300       else
301         @show_mode_in_prompt = false
302       end
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')
312     end
313   end
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
318   end
320   def bind_key(key, func_name)
321     if key =~ /\A"(.*)"\z/
322       keyseq = parse_keyseq($1)
323     else
324       keyseq = nil
325     end
326     if func_name =~ /"(.*)"/
327       func = parse_keyseq($1)
328     else
329       func = func_name.tr(?-, ?_).to_sym # It must be macro.
330     end
331     [keyseq, func]
332   end
334   def key_notation_to_code(notation)
335     case notation
336     when /\\(?:C|Control)-([A-Za-z_])/
337       (1 + $1.downcase.ord - ?a.ord)
338     when /\\(?:M|Meta)-([0-9A-Za-z_])/
339       modified_key = $1
340       case $1
341       when /[0-9]/
342         ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
343       when /[A-Z]/
344         ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
345       when /[a-z]/
346         ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
347       end
348     when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
349     # 129 M-^A
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
364     else notation.ord
365     end
366   end
368   def parse_keyseq(str)
369     ret = []
370     str.scan(KEYSEQ_PATTERN) do
371       ret << key_notation_to_code($&)
372     end
373     ret
374   end
376   private def seven_bit_encoding?(encoding)
377     encoding == Encoding::US_ASCII
378   end