reline 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,177 @@
1
+ require 'io/console'
2
+
3
+ class Reline::ANSI
4
+ def self.encoding
5
+ Encoding.default_external
6
+ end
7
+
8
+ def self.win?
9
+ false
10
+ end
11
+
12
+ RAW_KEYSTROKE_CONFIG = {
13
+ # Console (80x25)
14
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
15
+ [27, 91, 52, 126] => :ed_move_to_end, # End
16
+ [27, 91, 51, 126] => :key_delete, # Del
17
+ [27, 91, 65] => :ed_prev_history, # ↑
18
+ [27, 91, 66] => :ed_next_history, # ↓
19
+ [27, 91, 67] => :ed_next_char, # →
20
+ [27, 91, 68] => :ed_prev_char, # ←
21
+
22
+ # KDE
23
+ [27, 91, 72] => :ed_move_to_beg, # Home
24
+ [27, 91, 70] => :ed_move_to_end, # End
25
+ # Del is 0x08
26
+ [27, 71, 65] => :ed_prev_history, # ↑
27
+ [27, 71, 66] => :ed_next_history, # ↓
28
+ [27, 71, 67] => :ed_next_char, # →
29
+ [27, 71, 68] => :ed_prev_char, # ←
30
+
31
+ # GNOME
32
+ [27, 79, 72] => :ed_move_to_beg, # Home
33
+ [27, 79, 70] => :ed_move_to_end, # End
34
+ # Del is 0x08
35
+ # Arrow keys are the same of KDE
36
+
37
+ # others
38
+ [27, 32] => :em_set_mark, # M-<space>
39
+ [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
40
+ [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
41
+ [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
42
+
43
+ [27, 79, 65] => :ed_prev_history, # ↑
44
+ [27, 79, 66] => :ed_next_history, # ↓
45
+ [27, 79, 67] => :ed_next_char, # →
46
+ [27, 79, 68] => :ed_prev_char, # ←
47
+ }
48
+
49
+ @@input = STDIN
50
+ def self.input=(val)
51
+ @@input = val
52
+ end
53
+
54
+ @@output = STDOUT
55
+ def self.output=(val)
56
+ @@output = val
57
+ end
58
+
59
+ @@buf = []
60
+ def self.getc
61
+ unless @@buf.empty?
62
+ return @@buf.shift
63
+ end
64
+ c = @@input.raw(intr: true, &:getbyte)
65
+ (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
66
+ end
67
+
68
+ def self.ungetc(c)
69
+ @@buf.unshift(c)
70
+ end
71
+
72
+ def self.retrieve_keybuffer
73
+ begin
74
+ result = select([@@input], [], [], 0.001)
75
+ return if result.nil?
76
+ str = @@input.read_nonblock(1024)
77
+ str.bytes.each do |c|
78
+ @@buf.push(c)
79
+ end
80
+ rescue EOFError
81
+ end
82
+ end
83
+
84
+ def self.get_screen_size
85
+ s = @@input.winsize
86
+ return s if s[0] > 0 && s[1] > 0
87
+ s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
88
+ return s if s[0] > 0 && s[1] > 0
89
+ [24, 80]
90
+ rescue Errno::ENOTTY
91
+ [24, 80]
92
+ end
93
+
94
+ def self.set_screen_size(rows, columns)
95
+ @@input.winsize = [rows, columns]
96
+ self
97
+ rescue Errno::ENOTTY
98
+ self
99
+ end
100
+
101
+ def self.cursor_pos
102
+ begin
103
+ res = ''
104
+ m = nil
105
+ @@input.raw do |stdin|
106
+ @@output << "\e[6n"
107
+ @@output.flush
108
+ while (c = stdin.getc) != 'R'
109
+ res << c if c
110
+ end
111
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)/)
112
+ (m.pre_match + m.post_match).chars.reverse_each do |ch|
113
+ stdin.ungetc ch
114
+ end
115
+ end
116
+ column = m[:column].to_i - 1
117
+ row = m[:row].to_i - 1
118
+ rescue Errno::ENOTTY
119
+ buf = @@output.pread(@@output.pos, 0)
120
+ row = buf.count("\n")
121
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
122
+ end
123
+ Reline::CursorPos.new(column, row)
124
+ end
125
+
126
+ def self.move_cursor_column(x)
127
+ @@output.write "\e[#{x + 1}G"
128
+ end
129
+
130
+ def self.move_cursor_up(x)
131
+ if x > 0
132
+ @@output.write "\e[#{x}A" if x > 0
133
+ elsif x < 0
134
+ move_cursor_down(-x)
135
+ end
136
+ end
137
+
138
+ def self.move_cursor_down(x)
139
+ if x > 0
140
+ @@output.write "\e[#{x}B" if x > 0
141
+ elsif x < 0
142
+ move_cursor_up(-x)
143
+ end
144
+ end
145
+
146
+ def self.erase_after_cursor
147
+ @@output.write "\e[K"
148
+ end
149
+
150
+ def self.scroll_down(x)
151
+ return if x.zero?
152
+ @@output.write "\e[#{x}S"
153
+ end
154
+
155
+ def self.clear_screen
156
+ @@output.write "\e[2J"
157
+ @@output.write "\e[1;1H"
158
+ end
159
+
160
+ @@old_winch_handler = nil
161
+ def self.set_winch_handler(&handler)
162
+ @@old_winch_handler = Signal.trap('WINCH', &handler)
163
+ end
164
+
165
+ def self.prep
166
+ retrieve_keybuffer
167
+ int_handle = Signal.trap('INT', 'IGNORE')
168
+ Signal.trap('INT', int_handle)
169
+ nil
170
+ end
171
+
172
+ def self.deprep(otio)
173
+ int_handle = Signal.trap('INT', 'IGNORE')
174
+ Signal.trap('INT', int_handle)
175
+ Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
176
+ end
177
+ end
@@ -0,0 +1,288 @@
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
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 'history-size'
188
+ @history_size = value.to_i
189
+ when 'bell-style'
190
+ @bell_style =
191
+ case value
192
+ when 'none', 'off'
193
+ :none
194
+ when 'audible', 'on'
195
+ :audible
196
+ when 'visible'
197
+ :visible
198
+ else
199
+ :audible
200
+ end
201
+ when 'comment-begin'
202
+ @comment_begin = value.dup
203
+ when 'completion-query-items'
204
+ @completion_query_items = value.to_i
205
+ when 'isearch-terminators'
206
+ @isearch_terminators = instance_eval(value)
207
+ when 'editing-mode'
208
+ case value
209
+ when 'emacs'
210
+ @editing_mode_label = :emacs
211
+ @keymap_label = :emacs
212
+ when 'vi'
213
+ @editing_mode_label = :vi_insert
214
+ @keymap_label = :vi_insert
215
+ end
216
+ when 'keymap'
217
+ case value
218
+ when 'emacs', 'emacs-standard', 'emacs-meta', 'emacs-ctlx'
219
+ @keymap_label = :emacs
220
+ when 'vi', 'vi-move', 'vi-command'
221
+ @keymap_label = :vi_command
222
+ when 'vi-insert'
223
+ @keymap_label = :vi_insert
224
+ end
225
+ when 'keyseq-timeout'
226
+ @keyseq_timeout = value.to_i
227
+ when *VARIABLE_NAMES then
228
+ variable_name = :"@#{name.tr(?-, ?_)}"
229
+ instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
230
+ end
231
+ end
232
+
233
+ def bind_key(key, func_name)
234
+ if key =~ /\A"(.*)"\z/
235
+ keyseq = parse_keyseq($1)
236
+ else
237
+ keyseq = nil
238
+ end
239
+ if func_name =~ /"(.*)"/
240
+ func = parse_keyseq($1)
241
+ else
242
+ func = func_name.tr(?-, ?_).to_sym # It must be macro.
243
+ end
244
+ [keyseq, func]
245
+ end
246
+
247
+ def key_notation_to_code(notation)
248
+ case notation
249
+ when /\\(?:C|Control)-([A-Za-z_])/
250
+ (1 + $1.downcase.ord - ?a.ord)
251
+ when /\\(?:M|Meta)-([0-9A-Za-z_])/
252
+ modified_key = $1
253
+ case $1
254
+ when /[0-9]/
255
+ ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
256
+ when /[A-Z]/
257
+ ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
258
+ when /[a-z]/
259
+ ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
260
+ end
261
+ when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
262
+ # 129 M-^A
263
+ when /\\(\d{1,3})/ then $1.to_i(8) # octal
264
+ when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
265
+ when "\\e" then ?\e.ord
266
+ when "\\\\" then ?\\.ord
267
+ when "\\\"" then ?".ord
268
+ when "\\'" then ?'.ord
269
+ when "\\a" then ?\a.ord
270
+ when "\\b" then ?\b.ord
271
+ when "\\d" then ?\d.ord
272
+ when "\\f" then ?\f.ord
273
+ when "\\n" then ?\n.ord
274
+ when "\\r" then ?\r.ord
275
+ when "\\t" then ?\t.ord
276
+ when "\\v" then ?\v.ord
277
+ else notation.ord
278
+ end
279
+ end
280
+
281
+ def parse_keyseq(str)
282
+ ret = []
283
+ str.scan(KEYSEQ_PATTERN) do
284
+ ret << key_notation_to_code($&)
285
+ end
286
+ ret
287
+ end
288
+ end