reline 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,140 @@
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
+ }
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
27
+ @@input.getbyte
28
+ end
29
+
30
+ def self.ungetc(c)
31
+ @@buf.unshift(c)
32
+ end
33
+
34
+ def self.retrieve_keybuffer
35
+ result = select([@@input], [], [], 0.001)
36
+ return if result.nil?
37
+ str = @@input.read_nonblock(1024)
38
+ str.bytes.each do |c|
39
+ @@buf.push(c)
40
+ end
41
+ end
42
+
43
+ def self.get_screen_size
44
+ @@input.winsize
45
+ rescue Errno::ENOTTY
46
+ [24, 80]
47
+ end
48
+
49
+ def self.set_screen_size(rows, columns)
50
+ @@input.winsize = [rows, columns]
51
+ self
52
+ rescue Errno::ENOTTY
53
+ self
54
+ end
55
+
56
+ def self.cursor_pos
57
+ begin
58
+ res = ''
59
+ @@input.raw do |stdin|
60
+ @@output << "\e[6n"
61
+ @@output.flush
62
+ while (c = stdin.getc) != 'R'
63
+ res << c if c
64
+ end
65
+ end
66
+ m = res.match(/(?<row>\d+);(?<column>\d+)/)
67
+ column = m[:column].to_i - 1
68
+ row = m[:row].to_i - 1
69
+ rescue Errno::ENOTTY
70
+ buf = @@output.pread(@@output.pos, 0)
71
+ row = buf.count("\n")
72
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
73
+ end
74
+ Reline::CursorPos.new(column, row)
75
+ end
76
+
77
+ def self.move_cursor_column(x)
78
+ print "\e[#{x + 1}G"
79
+ end
80
+
81
+ def self.move_cursor_up(x)
82
+ if x > 0
83
+ print "\e[#{x}A" if x > 0
84
+ elsif x < 0
85
+ move_cursor_down(-x)
86
+ end
87
+ end
88
+
89
+ def self.move_cursor_down(x)
90
+ if x > 0
91
+ print "\e[#{x}B" if x > 0
92
+ elsif x < 0
93
+ move_cursor_up(-x)
94
+ end
95
+ end
96
+
97
+ def self.erase_after_cursor
98
+ print "\e[K"
99
+ end
100
+
101
+ def self.scroll_down(x)
102
+ return if x.zero?
103
+ print "\e[#{x}S"
104
+ end
105
+
106
+ def self.clear_screen
107
+ print "\e[2J"
108
+ print "\e[1;1H"
109
+ end
110
+
111
+ @@old_winch_handler = nil
112
+ def self.set_winch_handler(&handler)
113
+ @@old_winch_handler = Signal.trap('WINCH', &handler)
114
+ end
115
+
116
+ def self.prep
117
+ retrieve_keybuffer
118
+ int_handle = Signal.trap('INT', 'IGNORE')
119
+ otio = `stty -g`.chomp
120
+ setting = ' -echo -icrnl cbreak'
121
+ stty = `stty -a`
122
+ if /-parenb\b/ =~ stty
123
+ setting << ' pass8'
124
+ end
125
+ if /\bdsusp *=/ =~ stty
126
+ setting << ' dsusp undef'
127
+ end
128
+ setting << ' -ixoff'
129
+ `stty #{setting}`
130
+ Signal.trap('INT', int_handle)
131
+ otio
132
+ end
133
+
134
+ def self.deprep(otio)
135
+ int_handle = Signal.trap('INT', 'IGNORE')
136
+ `stty #{otio}`
137
+ Signal.trap('INT', int_handle)
138
+ Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
139
+ end
140
+ end
@@ -0,0 +1,286 @@
1
+ require 'pathname'
2
+
3
+ class Reline::Config
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
45
+
46
+ def initialize
47
+ @additional_key_bindings = {} # from inputrc
48
+ @default_key_bindings = {} # environment-dependent
49
+ @skip_section = nil
50
+ @if_stack = nil
51
+ @editing_mode_label = :emacs
52
+ @keymap_label = :emacs
53
+ @key_actors = {}
54
+ @key_actors[:emacs] = Reline::KeyActor::Emacs.new
55
+ @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
56
+ @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
+ @history_size = 500
58
+ @keyseq_timeout = 500
59
+ @test_mode = false
60
+ end
61
+
62
+ def reset
63
+ if editing_mode_is?(:vi_command)
64
+ @editing_mode_label = :vi_insert
65
+ end
66
+ @additional_key_bindings = {}
67
+ @default_key_bindings = {}
68
+ end
69
+
70
+ def editing_mode
71
+ @key_actors[@editing_mode_label]
72
+ end
73
+
74
+ def editing_mode=(val)
75
+ @editing_mode_label = val
76
+ end
77
+
78
+ def editing_mode_is?(*val)
79
+ (val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
80
+ end
81
+
82
+ def keymap
83
+ @key_actors[@keymap_label]
84
+ end
85
+
86
+ def read(file = nil)
87
+ file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH)
88
+ begin
89
+ if file.respond_to?(:readlines)
90
+ lines = file.readlines
91
+ else
92
+ lines = File.readlines(file)
93
+ end
94
+ rescue Errno::ENOENT
95
+ return nil
96
+ end
97
+
98
+ read_lines(lines, file)
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)
108
+ end
109
+
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
129
+ if line.start_with?('$')
130
+ handle_directive(line[1..-1], file, no)
131
+ next
132
+ end
133
+
134
+ next if @skip_section
135
+
136
+ case line
137
+ when /^set +([^ ]+) +([^ ]+)/i
138
+ var, value = $1.downcase, $2.downcase
139
+ bind_variable(var, value)
140
+ next
141
+ when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
142
+ key, func_name = $1, $2
143
+ keystroke, func = bind_key(key, func_name)
144
+ next unless keystroke
145
+ @additional_key_bindings[keystroke] = func
146
+ end
147
+ end
148
+ unless @if_stack.empty?
149
+ raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
150
+ end
151
+ ensure
152
+ @skip_section, @if_stack = conditions
153
+ end
154
+
155
+ def handle_directive(directive, file, no)
156
+ directive, args = directive.split(' ')
157
+ case directive
158
+ when 'if'
159
+ condition = false
160
+ case args # TODO: variables
161
+ when 'mode'
162
+ when 'term'
163
+ when 'version'
164
+ else # application name
165
+ condition = true if args == 'Ruby'
166
+ condition = true if args == 'Reline'
167
+ end
168
+ @if_stack << [file, no, @skip_section]
169
+ @skip_section = !condition
170
+ when 'else'
171
+ if @if_stack.empty?
172
+ raise InvalidInputrc, "#{file}:#{no}: unmatched else"
173
+ end
174
+ @skip_section = !@skip_section
175
+ when 'endif'
176
+ if @if_stack.empty?
177
+ raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
178
+ end
179
+ @skip_section = @if_stack.pop
180
+ when 'include'
181
+ read(args)
182
+ end
183
+ end
184
+
185
+ def bind_variable(name, value)
186
+ case name
187
+ when VARIABLE_NAMES then
188
+ variable_name = :"@#{name.tr(?-, ?_)}"
189
+ instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
190
+ when 'bell-style'
191
+ @bell_style =
192
+ case value
193
+ when 'none', 'off'
194
+ :none
195
+ when 'audible', 'on'
196
+ :audible
197
+ when 'visible'
198
+ :visible
199
+ else
200
+ :audible
201
+ end
202
+ when 'comment-begin'
203
+ @comment_begin = value.dup
204
+ when 'completion-query-items'
205
+ @completion_query_items = value.to_i
206
+ when 'isearch-terminators'
207
+ @isearch_terminators = instance_eval(value)
208
+ when 'editing-mode'
209
+ case value
210
+ when 'emacs'
211
+ @editing_mode_label = :emacs
212
+ @keymap_label = :emacs
213
+ when 'vi'
214
+ @editing_mode_label = :vi_insert
215
+ @keymap_label = :vi_insert
216
+ end
217
+ when 'keymap'
218
+ case value
219
+ when 'emacs', 'emacs-standard', 'emacs-meta', 'emacs-ctlx'
220
+ @keymap_label = :emacs
221
+ when 'vi', 'vi-move', 'vi-command'
222
+ @keymap_label = :vi_command
223
+ when 'vi-insert'
224
+ @keymap_label = :vi_insert
225
+ end
226
+ when 'keyseq-timeout'
227
+ @keyseq_timeout = value.to_i
228
+ end
229
+ end
230
+
231
+ def bind_key(key, func_name)
232
+ if key =~ /\A"(.*)"\z/
233
+ keyseq = parse_keyseq($1)
234
+ else
235
+ keyseq = nil
236
+ end
237
+ if func_name =~ /"(.*)"/
238
+ func = parse_keyseq($1)
239
+ else
240
+ func = func_name.tr(?-, ?_).to_sym # It must be macro.
241
+ end
242
+ [keyseq, func]
243
+ end
244
+
245
+ def key_notation_to_code(notation)
246
+ case notation
247
+ when /\\(?:C|Control)-([A-Za-z_])/
248
+ (1 + $1.downcase.ord - ?a.ord)
249
+ when /\\(?:M|Meta)-([0-9A-Za-z_])/
250
+ modified_key = $1
251
+ case $1
252
+ when /[0-9]/
253
+ ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
254
+ when /[A-Z]/
255
+ ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
256
+ when /[a-z]/
257
+ ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
258
+ end
259
+ when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
260
+ # 129 M-^A
261
+ when /\\(\d{1,3})/ then $1.to_i(8) # octal
262
+ when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
263
+ when "\\e" then ?\e.ord
264
+ when "\\\\" then ?\\.ord
265
+ when "\\\"" then ?".ord
266
+ when "\\'" then ?'.ord
267
+ when "\\a" then ?\a.ord
268
+ when "\\b" then ?\b.ord
269
+ when "\\d" then ?\d.ord
270
+ when "\\f" then ?\f.ord
271
+ when "\\n" then ?\n.ord
272
+ when "\\r" then ?\r.ord
273
+ when "\\t" then ?\t.ord
274
+ when "\\v" then ?\v.ord
275
+ else notation.ord
276
+ end
277
+ end
278
+
279
+ def parse_keyseq(str)
280
+ ret = []
281
+ str.scan(KEYSEQ_PATTERN) do
282
+ ret << key_notation_to_code($&)
283
+ end
284
+ ret
285
+ end
286
+ end
@@ -0,0 +1,67 @@
1
+ require 'timeout'
2
+
3
+ class Reline::GeneralIO
4
+ RAW_KEYSTROKE_CONFIG = {}
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.set_winch_handler(&handler)
60
+ end
61
+
62
+ def self.prep
63
+ end
64
+
65
+ def self.deprep(otio)
66
+ end
67
+ end