reline 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,75 @@
1
- module Reline
2
- def getc
1
+ class Reline::ANSI
2
+ RAW_KEYSTROKE_CONFIG = {
3
+ [27, 91, 65] => :ed_prev_history, # ↑
4
+ [27, 91, 66] => :ed_next_history, # ↓
5
+ [27, 91, 67] => :ed_next_char, # →
6
+ [27, 91, 68] => :ed_prev_char, # ←
7
+ [27, 91, 51, 126] => :key_delete, # Del
8
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
9
+ [27, 91, 52, 126] => :ed_move_to_end, # End
10
+ }.each_key(&:freeze).freeze
11
+
12
+ @@input = STDIN
13
+ def self.input=(val)
14
+ @@input = val
15
+ end
16
+
17
+ @@output = STDOUT
18
+ def self.output=(val)
19
+ @@output = val
20
+ end
21
+
22
+ @@buf = []
23
+ def self.getc
24
+ unless @@buf.empty?
25
+ return @@buf.shift
26
+ end
3
27
  c = nil
4
- until c
5
- return nil if @line_editor.finished?
6
- result = select([$stdin], [], [], 0.1)
28
+ loop do
29
+ result = select([@@input], [], [], 0.1)
7
30
  next if result.nil?
8
- c = $stdin.read(1)
31
+ c = @@input.read(1)
32
+ break
9
33
  end
10
- c.ord
34
+ c&.ord
35
+ end
36
+
37
+ def self.ungetc(c)
38
+ @@buf.unshift(c)
11
39
  end
12
40
 
13
41
  def self.get_screen_size
14
- $stdin.winsize
42
+ @@input.winsize
43
+ rescue Errno::ENOTTY
44
+ [24, 80]
15
45
  end
16
46
 
17
47
  def self.set_screen_size(rows, columns)
18
- $stdin.winsize = [rows, columns]
48
+ @@input.winsize = [rows, columns]
49
+ self
50
+ rescue Errno::ENOTTY
19
51
  self
20
52
  end
21
53
 
22
54
  def self.cursor_pos
23
- res = ''
24
- $stdin.raw do |stdin|
25
- $stdout << "\e[6n"
26
- $stdout.flush
27
- while (c = stdin.getc) != 'R'
28
- res << c if c
55
+ begin
56
+ res = ''
57
+ @@input.raw do |stdin|
58
+ @@output << "\e[6n"
59
+ @@output.flush
60
+ while (c = stdin.getc) != 'R'
61
+ res << c if c
62
+ end
29
63
  end
64
+ m = res.match(/(?<row>\d+);(?<column>\d+)/)
65
+ column = m[:column].to_i - 1
66
+ row = m[:row].to_i - 1
67
+ rescue Errno::ENOTTY
68
+ buf = @@output.pread(@@output.pos, 0)
69
+ row = buf.count("\n")
70
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
30
71
  end
31
- m = res.match(/(?<row>\d+);(?<column>\d+)/)
32
- CursorPos.new(m[:column].to_i - 1, m[:row].to_i - 1)
72
+ Reline::CursorPos.new(column, row)
33
73
  end
34
74
 
35
75
  def self.move_cursor_column(x)
@@ -66,11 +106,11 @@ module Reline
66
106
  print "\e[1;1H"
67
107
  end
68
108
 
69
- def prep
109
+ def self.prep
70
110
  int_handle = Signal.trap('INT', 'IGNORE')
71
111
  otio = `stty -g`.chomp
72
112
  setting = ' -echo -icrnl cbreak'
73
- if (`stty -a`.scan(/-parenb\b/).first == '-parenb')
113
+ if /-parenb\b/ =~ `stty -a`
74
114
  setting << ' pass8'
75
115
  end
76
116
  setting << ' -ixoff'
@@ -79,7 +119,7 @@ module Reline
79
119
  otio
80
120
  end
81
121
 
82
- def deprep(otio)
122
+ def self.deprep(otio)
83
123
  int_handle = Signal.trap('INT', 'IGNORE')
84
124
  `stty #{otio}`
85
125
  Signal.trap('INT', int_handle)
@@ -1,23 +1,70 @@
1
1
  require 'pathname'
2
2
 
3
3
  class Reline::Config
4
- DEFAULT_PATH = Pathname.new(Dir.home).join('.inputrc')
4
+ attr_reader :test_mode
5
+
6
+ DEFAULT_PATH = '~/.inputrc'
7
+
8
+ 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}|./
9
+
10
+ class InvalidInputrc < RuntimeError
11
+ attr_accessor :file, :lineno
12
+ end
13
+
14
+ VARIABLE_NAMES = %w{
15
+ bind-tty-special-chars
16
+ blink-matching-paren
17
+ byte-oriented
18
+ completion-ignore-case
19
+ convert-meta
20
+ disable-completion
21
+ enable-keypad
22
+ expand-tilde
23
+ history-preserve-point
24
+ history-size
25
+ horizontal-scroll-mode
26
+ input-meta
27
+ keyseq-timeout
28
+ mark-directories
29
+ mark-modified-lines
30
+ mark-symlinked-directories
31
+ match-hidden-files
32
+ meta-flag
33
+ output-meta
34
+ page-completions
35
+ prefer-visible-bell
36
+ print-completions-horizontally
37
+ show-all-if-ambiguous
38
+ show-all-if-unmodified
39
+ visible-stats
40
+ }
41
+ VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
42
+ VARIABLE_NAME_SYMBOLS.each do |v|
43
+ attr_accessor v
44
+ end
5
45
 
6
46
  def initialize
47
+ @additional_key_bindings = {} # from inputrc
48
+ @default_key_bindings = {} # environment-dependent
7
49
  @skip_section = nil
8
- @if_stack = []
50
+ @if_stack = nil
9
51
  @editing_mode_label = :emacs
10
52
  @keymap_label = :emacs
11
53
  @key_actors = {}
12
54
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
13
55
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
14
56
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
+ @history_size = 500
58
+ @keyseq_timeout = 500
59
+ @test_mode = false
15
60
  end
16
61
 
17
62
  def reset
18
63
  if editing_mode_is?(:vi_command)
19
64
  @editing_mode_label = :vi_insert
20
65
  end
66
+ @additional_key_bindings = {}
67
+ @default_key_bindings = {}
21
68
  end
22
69
 
23
70
  def editing_mode
@@ -36,29 +83,51 @@ class Reline::Config
36
83
  @key_actors[@keymap_label]
37
84
  end
38
85
 
39
- def read(file = DEFAULT_PATH)
86
+ def read(file = nil)
87
+ file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH)
40
88
  begin
41
89
  if file.respond_to?(:readlines)
42
90
  lines = file.readlines
43
91
  else
44
- File.open(file, 'rt') do |f|
45
- lines = f.readlines
46
- end
92
+ lines = File.readlines(file)
47
93
  end
48
94
  rescue Errno::ENOENT
49
- $stderr.puts "no such file #{file}"
50
95
  return nil
51
96
  end
52
97
 
53
- read_lines(lines)
98
+ read_lines(lines, file)
54
99
  self
100
+ rescue InvalidInputrc => e
101
+ warn e.message
102
+ nil
103
+ end
104
+
105
+ def key_bindings
106
+ # override @default_key_bindings with @additional_key_bindings
107
+ @default_key_bindings.merge(@additional_key_bindings)
55
108
  end
56
109
 
57
- def read_lines(lines)
58
- lines.each do |line|
59
- line = line.chomp.gsub(/^\s*/, '')
110
+ def add_default_key_binding(keystroke, target)
111
+ @default_key_bindings[keystroke] = target
112
+ end
113
+
114
+ def reset_default_key_bindings
115
+ @default_key_bindings = {}
116
+ end
117
+
118
+ def read_lines(lines, file = nil)
119
+ conditions = [@skip_section, @if_stack]
120
+ @skip_section = nil
121
+ @if_stack = []
122
+
123
+ lines.each_with_index do |line, no|
124
+ next if line.match(/\A\s*#/)
125
+
126
+ no += 1
127
+
128
+ line = line.chomp.lstrip
60
129
  if line[0, 1] == '$'
61
- handle_directive(line[1..-1])
130
+ handle_directive(line[1..-1], file, no)
62
131
  next
63
132
  end
64
133
 
@@ -70,14 +139,21 @@ class Reline::Config
70
139
  next
71
140
  end
72
141
 
73
- if line =~ /\s*(.*)\s*:\s*(.*)\s*$/
142
+ if line =~ /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/
74
143
  key, func_name = $1, $2
75
- bind_key(key, func_name)
144
+ keystroke, func = bind_key(key, func_name)
145
+ next unless keystroke
146
+ @additional_key_bindings[keystroke] = func
76
147
  end
77
148
  end
149
+ unless @if_stack.empty?
150
+ raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
151
+ end
152
+ ensure
153
+ @skip_section, @if_stack = conditions
78
154
  end
79
155
 
80
- def handle_directive(directive)
156
+ def handle_directive(directive, file, no)
81
157
  directive, args = directive.split(' ')
82
158
  case directive
83
159
  when 'if'
@@ -88,18 +164,20 @@ class Reline::Config
88
164
  when 'version'
89
165
  else # application name
90
166
  condition = true if args == 'Ruby'
167
+ condition = true if args == 'Reline'
91
168
  end
92
- unless @skip_section.nil?
93
- @if_stack << @skip_section
94
- end
169
+ @if_stack << [file, no, @skip_section]
95
170
  @skip_section = !condition
96
171
  when 'else'
172
+ if @if_stack.empty?
173
+ raise InvalidInputrc, "#{file}:#{no}: unmatched else"
174
+ end
97
175
  @skip_section = !@skip_section
98
176
  when 'endif'
99
- @skip_section = nil
100
- unless @if_stack.empty?
101
- @skip_section = @if_stack.pop
177
+ if @if_stack.empty?
178
+ raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
102
179
  end
180
+ @skip_section = @if_stack.pop
103
181
  when 'include'
104
182
  read(args)
105
183
  end
@@ -107,31 +185,7 @@ class Reline::Config
107
185
 
108
186
  def bind_variable(name, value)
109
187
  case name
110
- when %w{
111
- bind-tty-special-chars
112
- blink-matching-paren
113
- byte-oriented
114
- completion-ignore-case
115
- convert-meta
116
- disable-completion
117
- enable-keypad
118
- expand-tilde
119
- history-preserve-point
120
- horizontal-scroll-mode
121
- input-meta
122
- mark-directories
123
- mark-modified-lines
124
- mark-symlinked-directories
125
- match-hidden-files
126
- meta-flag
127
- output-meta
128
- page-completions
129
- prefer-visible-bell
130
- print-completions-horizontally
131
- show-all-if-ambiguous
132
- show-all-if-unmodified
133
- visible-stats
134
- } then
188
+ when VARIABLE_NAMES then
135
189
  variable_name = :"@#{name.tr(?-, ?_)}"
136
190
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
137
191
  when 'bell-style'
@@ -170,65 +224,63 @@ class Reline::Config
170
224
  when 'vi-insert'
171
225
  @keymap_label = :vi_insert
172
226
  end
227
+ when 'keyseq-timeout'
228
+ @keyseq_timeout = value.to_i
173
229
  end
174
230
  end
175
231
 
176
232
  def bind_key(key, func_name)
177
- if key =~ /"(.*)"/
178
- keyseq = parse_keyseq($1).force_encoding('ASCII-8BIT')
233
+ if key =~ /\A"(.*)"\z/
234
+ keyseq = parse_keyseq($1)
179
235
  else
180
236
  keyseq = nil
181
237
  end
182
238
  if func_name =~ /"(.*)"/
183
- func = parse_keyseq($1).force_encoding('ASCII-8BIT')
239
+ func = parse_keyseq($1)
184
240
  else
185
- func = func_name.to_sym # It must be macro.
241
+ func = func_name.tr(?-, ?_).to_sym # It must be macro.
186
242
  end
187
243
  [keyseq, func]
188
244
  end
189
245
 
190
- def key_notation_to_char(notation)
246
+ def key_notation_to_code(notation)
191
247
  case notation
192
- when /\\C-([A-Za-z_])/
193
- (1 + $1.downcase.ord - ?a.ord).chr('ASCII-8BIT')
194
- when /\\M-([0-9A-Za-z_])/
248
+ when /\\(?:C|Control)-([A-Za-z_])/
249
+ (1 + $1.downcase.ord - ?a.ord)
250
+ when /\\(?:M|Meta)-([0-9A-Za-z_])/
195
251
  modified_key = $1
196
- code =
197
- case $1
198
- when /[0-9]/
199
- ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
200
- when /[A-Z]/
201
- ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
202
- when /[a-z]/
203
- ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
204
- end
205
- code.chr('ASCII-8BIT')
206
- when /\\C-M-[A-Za-z_]/, /\\M-C-[A-Za-z_]/
252
+ case $1
253
+ when /[0-9]/
254
+ ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
255
+ when /[A-Z]/
256
+ ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
257
+ when /[a-z]/
258
+ ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
259
+ end
260
+ when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
207
261
  # 129 M-^A
208
- when /\\(\d{1,3})/ then $1.to_i(8).chr # octal
209
- when /\\x(\h{1,2})/ then $1.to_i(16).chr # hexadecimal
210
- when "\\e" then ?\e
211
- when "\\\\" then ?\\
212
- when "\\\"" then ?"
213
- when "\\'" then ?'
214
- when "\\a" then ?\a
215
- when "\\b" then ?\b
216
- when "\\d" then ?\d
217
- when "\\f" then ?\f
218
- when "\\n" then ?\n
219
- when "\\r" then ?\r
220
- when "\\t" then ?\t
221
- when "\\v" then ?\v
222
- else notation
262
+ when /\\(\d{1,3})/ then $1.to_i(8) # octal
263
+ when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
264
+ when "\\e" then ?\e.ord
265
+ when "\\\\" then ?\\.ord
266
+ when "\\\"" then ?".ord
267
+ when "\\'" then ?'.ord
268
+ when "\\a" then ?\a.ord
269
+ when "\\b" then ?\b.ord
270
+ when "\\d" then ?\d.ord
271
+ when "\\f" then ?\f.ord
272
+ when "\\n" then ?\n.ord
273
+ when "\\r" then ?\r.ord
274
+ when "\\t" then ?\t.ord
275
+ when "\\v" then ?\v.ord
276
+ else notation.ord
223
277
  end
224
278
  end
225
279
 
226
280
  def parse_keyseq(str)
227
- # TODO: Control- and Meta-
228
- ret = String.new(encoding: 'ASCII-8BIT')
229
- 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}|.)/
230
- ret << key_notation_to_char($&)
231
- str = $'
281
+ ret = []
282
+ str.scan(KEYSEQ_PATTERN) do
283
+ ret << key_notation_to_code($&)
232
284
  end
233
285
  ret
234
286
  end
@@ -0,0 +1,64 @@
1
+ require 'timeout'
2
+
3
+ class Reline::GeneralIO
4
+ RAW_KEYSTROKE_CONFIG = {}.freeze
5
+
6
+ @@buf = []
7
+
8
+ def self.input=(val)
9
+ @@input = val
10
+ end
11
+
12
+ def self.getc
13
+ unless @@buf.empty?
14
+ return @@buf.shift
15
+ end
16
+ c = nil
17
+ loop do
18
+ result = select([@@input], [], [], 0.1)
19
+ next if result.nil?
20
+ c = @@input.read(1)
21
+ break
22
+ end
23
+ c&.ord
24
+ end
25
+
26
+ def self.ungetc(c)
27
+ @@buf.unshift(c)
28
+ end
29
+
30
+ def self.get_screen_size
31
+ [1, 1]
32
+ end
33
+
34
+ def self.cursor_pos
35
+ Reline::CursorPos.new(1, 1)
36
+ end
37
+
38
+ def self.move_cursor_column(val)
39
+ end
40
+
41
+ def self.move_cursor_up(val)
42
+ end
43
+
44
+ def self.move_cursor_down(val)
45
+ end
46
+
47
+ def self.erase_after_cursor
48
+ end
49
+
50
+ def self.scroll_down(val)
51
+ end
52
+
53
+ def self.clear_screen
54
+ end
55
+
56
+ def self.set_screen_size(rows, columns)
57
+ end
58
+
59
+ def self.prep
60
+ end
61
+
62
+ def self.deprep(otio)
63
+ end
64
+ end