reline 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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