inline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == Version 0.1.0
2
+
3
+ First preview release of InLine, implementing some of the functionality provided by the ReadLine library such as basic line editing, history and word completion.
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2008, Fabio Cevasco
2
+ All rights reserved.
3
+
4
+ - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+ - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+ - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7
+ Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8
+
9
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10
+
11
+
data/README ADDED
@@ -0,0 +1,43 @@
1
+ = InLine
2
+
3
+ InLine was created to provide a 100% Ruby alternative to the ReadLine library, providing some of its most popular features such as:
4
+
5
+ * Basic line editing operations
6
+ * Word completion
7
+ * History Management
8
+ * Custom key/key sequences bindings
9
+
10
+ == Installation
11
+
12
+ The simplest method to install InLine is to install the gem:
13
+
14
+ gem install -r inline
15
+
16
+ == Usage
17
+
18
+ Editor initialization:
19
+
20
+ require 'inline'
21
+ editor = InLine::Editor.new
22
+
23
+ Key binding:
24
+
25
+ editor.bind(:ctrl_z) { editor.undo }
26
+ editor.bind(:up_arrow) { editor.history_back }
27
+ editor.bind(:ctrl_x) { puts "Exiting..."; exit }
28
+
29
+ Setup word completion
30
+
31
+ editor.completion_proc = lambda do |word|
32
+ if word
33
+ ['select', 'update', 'delete', 'debug', 'destroy'].find_all { |e| e.match(/^#{Regexp.escape(word)}/) }
34
+ end
35
+ end
36
+ editor.completion_append_string = " "
37
+
38
+ Read input:
39
+
40
+ editor.read("=> ")
41
+
42
+
43
+
@@ -0,0 +1,26 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # inline.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module InLine
13
+ HOME = File.dirname(File.expand_path(__FILE__))
14
+ class BindingException < Exception; end
15
+ end
16
+
17
+ require "rubygems"
18
+ require "highline"
19
+ require "#{InLine::HOME}/inline/terminal"
20
+ require "#{InLine::HOME}/inline/terminal/windows_terminal"
21
+ require "#{InLine::HOME}/inline/terminal/vt220_terminal"
22
+ require "#{InLine::HOME}/inline/history_buffer"
23
+ require "#{InLine::HOME}/inline/line"
24
+ require "#{InLine::HOME}/inline/editor"
25
+
26
+
@@ -0,0 +1,594 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # editor.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module InLine
13
+
14
+ #
15
+ # The Editor class defines methods to:
16
+ #
17
+ # * Read characters from STDIN or any type of input
18
+ # * Write characters to STDOUT or any type of output
19
+ # * Bind keys to specific actions
20
+ # * Perform line-related operations like moving, navigating through history, etc.
21
+ #
22
+ # Note that the following default key bindings are provided:
23
+ #
24
+ # * TAB: word completion defined via completion_proc
25
+ # * LEFT/RIGHT ARROWS: cursor movement (left/right)
26
+ # * UP/DOWN ARROWS: history navigation
27
+ # * DEL: Delete character under cursor
28
+ # * BACKSPACE: Delete character before cursor
29
+ # * INSERT: Toggle insert/replace mode (default: insert)
30
+ # * CTRL+K: Clear the whole line
31
+ # * CTRL+Z: undo (unless already registered by the OS)
32
+ # * CTRL+Y: redo (unless already registered by the OS)
33
+ #
34
+ class Editor
35
+
36
+ include HighLine::SystemExtensions
37
+
38
+ attr_accessor :char, :history_size, :line_history_size, :terminal, :keys, :word_separator, :mode, :completion_proc, :line, :history, :completion_append_string
39
+
40
+ #
41
+ # Create an instance of InLine::Editor which can be used
42
+ # to read from input and perform line-editing operations.
43
+ # This method takes an optional block used to override the
44
+ # following instance attributes:
45
+ # * <tt>@history_size</tt> - the size of the editor history buffer (30).
46
+ # * <tt>@line_history_size</tt> - the size of the editor line history
47
+ # buffer (50).
48
+ # * <tt>@keys</tt> - the keys (arrays of character codes)
49
+ # bound to specific actions.
50
+ # * <tt>@word_separator</tt> - a string used as word separator (' ').
51
+ # * <tt>@mode</tt> - The editor's character insertion mode (:insert).
52
+ # * <tt>@completion_proc</tt> - a Proc object used to perform word completion.
53
+ # * <tt>@completion_append_string</tt> - a string to append to completed words.
54
+ # * <tt>@terminal</tt> - an InLine::Terminal containing character key codes.
55
+ #
56
+ def initialize(input=STDIN, output=STDOUT)
57
+ @input = input
58
+ @output = output
59
+ case PLATFORM
60
+ when /win32/i then
61
+ @terminal = WindowsTerminal.new
62
+ else
63
+ @terminal = VT220Terminal.new
64
+ end
65
+ @history_size = 30
66
+ @line_history_size = 50
67
+ @keys = {}
68
+ @word_separator = ' '
69
+ @mode = :insert
70
+ @completion_proc = []
71
+ @completion_append_string = ' '
72
+ @completion_matches = HistoryBuffer.new(0) { |h| h.duplicates = false; h.cycle = true }
73
+ set_default_keys
74
+ yield self if block_given?
75
+ @history = HistoryBuffer.new(@history_size) do |h|
76
+ h.duplicates = false;
77
+ h.exclude = lambda { |item| item.strip == "" }
78
+ end
79
+ @char = nil
80
+ @newline = true
81
+ end
82
+
83
+ #
84
+ # Read characters from <tt>@input</tt> until the user presses ENTER
85
+ # (use it in the same way as you'd use IO#gets)
86
+ # An optional prompt can be specified to be printed at the beginning of the line.
87
+ #
88
+ def read(prompt="")
89
+ @newline = true
90
+ @line = Line.new(@line_history_size) do |l|
91
+ l.prompt = prompt
92
+ l.word_separator = @word_separator
93
+ end
94
+ add_to_line_history
95
+ loop do
96
+ print prompt if @newline
97
+ @newline = false
98
+ read_character
99
+ process_character
100
+ break if @char == @terminal.keys[:enter] || !@char
101
+ end
102
+ puts
103
+ "#{@line.text}\n"
104
+ end
105
+
106
+ #
107
+ # Read and parse a character from <tt>@input</tt>.
108
+ # This method is called automatically by <tt>read</tt>
109
+ #
110
+ def read_character
111
+ c = get_character(@input)
112
+ @char = parse_key_code(c) || c
113
+ end
114
+
115
+ #
116
+ # Parse a key or key sequence into the corresponding codes.
117
+ # This method is called automatically by <tt>read_character</tt>
118
+ #
119
+ def parse_key_code(code)
120
+ if @terminal.escape_codes.include? code then
121
+ sequence = [code]
122
+ seqs = []
123
+ loop do
124
+ c = get_character(@input)
125
+ sequence << c
126
+ seqs = @terminal.escape_sequences.select { |e| e[0..sequence.length-1] == sequence }
127
+ break if seqs.empty?
128
+ return sequence if [sequence] == seqs
129
+ end
130
+ else
131
+ return (@terminal.keys.has_value? [code]) ? [code] : nil
132
+ end
133
+ end
134
+
135
+ #
136
+ # Write a string to <tt>@output</tt> starting from the cursor position.
137
+ # Characters at the right of the cursor are shifted to the right if
138
+ # <tt>@mode == :insert</tt>, deleted otherwise.
139
+ #
140
+ def write(string)
141
+ string.each_byte { |c| print_character c, true }
142
+ add_to_line_history
143
+ end
144
+
145
+ #
146
+ # Process a character. If the key corresponding to the inputted character
147
+ # is bound to an action, call <tt>press_key</tt>, otherwise call <tt>default_action</tt>.
148
+ # This method is called automatically by <tt>read</tt>
149
+ #
150
+ def process_character
151
+ case @char.class.to_s
152
+ when 'Fixnum' then
153
+ default_action
154
+ when 'Array'
155
+ press_key if key_bound?
156
+ end
157
+ end
158
+
159
+ #
160
+ # Bind a key to an action specified via <tt>block</tt>.
161
+ # <tt>key</tt> can be:
162
+ #
163
+ # * A Symbol identifying a character or character sequence defined for the current terminal
164
+ # * A Fixnum identifying a character defined for the current terminal
165
+ # * An Array identifying a character or character sequence defined for the current terminal
166
+ # * A String identifying a character or character sequence defined for the current terminal
167
+ # * An Hash identifying a character or character sequence, even if it is not defined for the current terminal
168
+ #
169
+ # If <tt>key</tt> is a has, then:
170
+ #
171
+ # * It must contain only one key/value pair
172
+ # * The key identifies the name of the character or character sequence
173
+ # * The value identifies the code(s) corresponding to the character or character sequence
174
+ # * The value can be a Fixnum, a String or an Array.
175
+ #
176
+ def bind(key, &block)
177
+ case key.class.to_s
178
+ when 'Symbol' then
179
+ raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys[key]
180
+ @keys[@terminal.keys[key]] = block
181
+ when 'Array' then
182
+ raise BindingException, "Unknown key or key sequence '#{key.join(", ")}' (#{key.class.to_s})" unless @terminal.keys.has_value? key
183
+ @keys[key] = block
184
+ when 'Fixnum' then
185
+ raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys.has_value? [key]
186
+ @keys[[key]] = block
187
+ when 'String' then
188
+ raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys.has_value? key_array
189
+ key_array = []
190
+ key.each_byte { |b| key_array << b }
191
+ @keys[key_array] = block
192
+ when 'Hash' then
193
+ raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
194
+ key.each_pair do |j,k|
195
+ raise BindingException, "'#{k[0].chr}' is not a legal escape code for '#{@terminal.class.to_s}'." unless k.length > 1 && @terminal.escape_codes.include?(k[0])
196
+ code = []
197
+ case k.class.to_s
198
+ when 'Fixnum' then
199
+ code = [k]
200
+ when 'String' then
201
+ k.each_byte { |b| code << b }
202
+ when 'Array' then
203
+ code = k
204
+ else
205
+ raise BindingException, "Unable to bind '#{k.to_s}' (#{k.class.to_s})"
206
+ end
207
+ @terminal.keys[j] = code
208
+ @keys[code] = block
209
+ end
210
+ else
211
+ raise BindingException, "Unable to bind '#{key.to_s}' (#{key.class.to_s})"
212
+ end
213
+ @terminal.update
214
+ end
215
+
216
+ #
217
+ # Return true if the last character read via <tt>read</tt> is bound to an action.
218
+ #
219
+ def key_bound?
220
+ @keys[@char] ? true : false
221
+ end
222
+
223
+ #
224
+ # Call the action bound to the last character read via <tt>read</tt>.
225
+ # This method is called automatically by <tt>process_character</tt>.
226
+ #
227
+ def press_key
228
+ @keys[@char].call
229
+ end
230
+
231
+ #
232
+ # Execute the default action for the last character read via <tt>read</tt>.
233
+ # By default it prints the character to the screen via <tt>print_character</tt>.
234
+ # This method is called automatically by <tt>process_character</tt>.
235
+ #
236
+ def default_action
237
+ print_character
238
+ end
239
+
240
+ #
241
+ # Write a character to <tt>@output</tt> at cursor position,
242
+ # shifting characters as appropriate.
243
+ # If <tt>no_line_history</tt> is set to <tt>true</tt>, the updated
244
+ # won't be saved in the history of the current line.
245
+ #
246
+ def print_character(char=@char, no_line_history = false)
247
+ unless @line.length >= @line.max_length-2
248
+ case
249
+ when @line.position < @line.length then
250
+ chars = select_characters_from_cursor if @mode == :insert
251
+ @output.putc char
252
+ @line.text[@line.position] = (@mode == :insert) ? "#{char.chr}#{@line.text[@line.position].chr}" : "#{char.chr}"
253
+ @line.right
254
+ if @mode == :insert then
255
+ raw_print chars
256
+ chars.length.times { putc ?\b } # move cursor back
257
+ end
258
+ else
259
+ @output.putc char
260
+ @line.right
261
+ @line << char
262
+ end
263
+ add_to_line_history unless no_line_history
264
+ end
265
+ end
266
+
267
+ #
268
+ # Complete the current word according to what returned by
269
+ # <tt>@completion_proc</tt>. Characters can be appended to the
270
+ # completed word via <tt>@completion_append_character</tt> and word
271
+ # separators can be defined via <tt>@word_separator</tt>.
272
+ #
273
+ # This action is bound to the tab key by default, so the first
274
+ # match is displayed the first time the user presses tab, and all
275
+ # the possible messages will be displayed (cyclically) when tab is
276
+ # pressed again.
277
+ #
278
+ def complete
279
+ completion_char = @char
280
+ @completion_matches.empty
281
+ word_start = @line.word[:start]
282
+ sub_word = @line.text[@line.word[:start]..@line.position-1] || ""
283
+ matches = @completion_proc.call(sub_word)
284
+ matches = (matches.is_a?(Array)) ? matches.sort.reverse : []
285
+ complete_word = lambda do |match|
286
+ unless @line.word[:text].length == 0
287
+ # If not in a word, print the match, otherwise continue existing word
288
+ move_to_position(@line.word[:end]+@completion_append_string.length+1)
289
+ end
290
+ (@line.position-word_start).times { delete_left_character(true) }
291
+ write match+@completion_append_string
292
+ end
293
+ unless matches.empty? then
294
+ @completion_matches.resize(matches.length)
295
+ matches.each { |w| @completion_matches << w }
296
+ # Get first match
297
+ @completion_matches.back
298
+ match = @completion_matches.get
299
+ complete_word.call(match)
300
+ read_character
301
+ while @char == completion_char do
302
+ move_to_position(word_start)
303
+ @completion_matches.back
304
+ match = @completion_matches.get
305
+ complete_word.call(match)
306
+ read_character
307
+ end
308
+ process_character
309
+ end
310
+ end
311
+
312
+ #
313
+ # Adds <tt>@line.text</tt> to the editor history. This action is
314
+ # bound to the enter key by default.
315
+ #
316
+ def newline
317
+ add_to_history
318
+ end
319
+
320
+ #
321
+ # Move the cursor left (if possible) by printing a
322
+ # backspace, updating <tt>@line.position</tt> accordingly.
323
+ # This action is bound to the left arrow key by default.
324
+ #
325
+ def move_left
326
+ unless @line.bol?:
327
+ @output.putc ?\b
328
+ @line.left
329
+ return true
330
+ end
331
+ false
332
+ end
333
+
334
+ #
335
+ # Move the cursor right (if possible) by re-printing the
336
+ # character at the right of the cursor, if any, and updating
337
+ # <tt>@line.position</tt> accordingly.
338
+ # This action is bound to the right arrow key by default.
339
+ #
340
+ def move_right
341
+ unless @line.position > @line.eol:
342
+ @line.right
343
+ @output.putc @line.text[@line.position-1]
344
+ return true
345
+ end
346
+ false
347
+ end
348
+
349
+ #
350
+ # Print debug information about the current line. Note that after
351
+ # the message is displayed, the line text and position will be restored.
352
+ #
353
+ def debug_line
354
+ pos = @line.position
355
+ text = @line.text
356
+ word = @line.word
357
+ @output.puts
358
+ @output.puts "Text: [#{text}]"
359
+ @output.puts "Length: #{@line.length}"
360
+ @output.puts "Position: #{pos}"
361
+ @output.puts "Character at Position: [#{text[pos].chr}] (#{text[pos]})" unless pos >= @line.length
362
+ @output.puts "Current Word: [#{word[:text]}] (#{word[:start]} -- #{word[:end]})"
363
+ clear_line
364
+ raw_print text
365
+ overwrite_line(text, pos)
366
+ end
367
+
368
+ #
369
+ # Print the content of the editor history. Note that after
370
+ # the message is displayed, the line text and position will be restored.
371
+ #
372
+ def show_history
373
+ pos = @line.position
374
+ text = @line.text
375
+ @output.puts
376
+ @output.puts "History:"
377
+ @history.each {|l| puts "- [#{l}]"}
378
+ overwrite_line(text, pos)
379
+ end
380
+
381
+ #
382
+ # Clear the editor history.
383
+ #
384
+ def clear_history
385
+ @history.empty
386
+ end
387
+
388
+ #
389
+ # Delete the character at the left of the cursor.
390
+ # If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
391
+ # recorded in the line history.
392
+ # This action is bound to the backspace key by default.
393
+ #
394
+ def delete_left_character(no_line_history=false)
395
+ if move_left then
396
+ delete_character(no_line_history)
397
+ end
398
+ end
399
+
400
+ #
401
+ # Delete the character under the cursor.
402
+ # If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
403
+ # recorded in the line history.
404
+ # This action is bound to the delete key by default.
405
+ #
406
+ def delete_character(no_line_history=false)
407
+ unless @line.position > @line.eol
408
+ # save characters to shift
409
+ chars = (@line.eol?) ? ' ' : select_characters_from_cursor(1)
410
+ # remove character from console and shift characters
411
+ raw_print chars
412
+ putc ?\s
413
+ (chars.length+1).times { putc ?\b }
414
+ #remove character from line
415
+ @line[@line.position] = ''
416
+ add_to_line_history unless no_line_history
417
+ end
418
+ end
419
+
420
+ #
421
+ # Clear the current line, i.e.
422
+ # <tt>@line.text</tt> and <tt>@line.position</tt>.
423
+ # This action is bound to ctrl+k by default.
424
+ #
425
+ def clear_line
426
+ @output.putc ?\r
427
+ raw_print @line.prompt
428
+ @line.length.times { putc ?\s }
429
+ @line.length.times { putc ?\b }
430
+ add_to_line_history
431
+ @line.text = ""
432
+ @line.position = 0
433
+ end
434
+
435
+ #
436
+ # Undo the last modification to the current line (<tt>@line.text</tt>).
437
+ # This action is bound to ctrl+z by default.
438
+ #
439
+ def undo
440
+ generic_history_back(@line.history) if @line.history.position == nil
441
+ generic_history_back(@line.history)
442
+ end
443
+
444
+ #
445
+ # Redo a previously-undone modification to the
446
+ # current line (<tt>@line.text</tt>).
447
+ # This action is bound to ctrl+y by default.
448
+ #
449
+ def redo
450
+ generic_history_forward(@line.history)
451
+ end
452
+
453
+ #
454
+ # Load the previous entry of the editor in place of the
455
+ # current line (<tt>@line.text</tt>).
456
+ # This action is bound to the up arrow key by default.
457
+ #
458
+ def history_back
459
+ unless @history.position
460
+ current_line = @line.text.dup
461
+ # Temporarily override exclusion rules
462
+ exclude = @history.exclude.dup
463
+ @history.exclude = lambda { nil }
464
+ # Add current line
465
+ @history << current_line
466
+ @history.exclude = exclude
467
+ @history.back
468
+ end
469
+ generic_history_back(@history)
470
+ add_to_line_history
471
+ end
472
+
473
+ #
474
+ # Load the next entry of the editor history in place of the
475
+ # current line (<tt>@line.text</tt>).
476
+ # This action is bound to down arrow key by default.
477
+ #
478
+ def history_forward
479
+ generic_history_forward(@history)
480
+ add_to_line_history
481
+ end
482
+
483
+ #
484
+ # Add the current line (<tt>@line.text</tt>) to the
485
+ # line history, to allow undo/redo
486
+ # operations.
487
+ #
488
+ def add_to_line_history
489
+ @line.history << @line.text.dup unless @line.text == ""
490
+ end
491
+
492
+ #
493
+ # Add the current line (<tt>@line.text</tt>) to the editor history.
494
+ #
495
+ def add_to_history
496
+ @history << @line.text.dup unless @line.text == ""
497
+ end
498
+
499
+ #
500
+ # Toggle the editor <tt>@mode</tt> to :replace or :insert (default).
501
+ #
502
+ def toggle_mode
503
+ case @mode
504
+ when :insert then @mode = :replace
505
+ when :replace then @mode = :insert
506
+ end
507
+ end
508
+
509
+ #
510
+ # Overwrite the current line (<tt>@line.text</tt>)
511
+ # with <tt>new_line</tt>, and optionally reset the cursor position to
512
+ # <tt>position</tt>.
513
+ #
514
+ def overwrite_line(new_line, position=nil)
515
+ pos = position || new_line.length
516
+ text = @line.text
517
+ putc ?\r
518
+ raw_print @line.prompt
519
+ raw_print new_line
520
+ n = text.length-new_line.length+1
521
+ if n > 0
522
+ n.times { putc ?\s }
523
+ n.times { putc ?\b }
524
+ end
525
+ @line.position = new_line.length
526
+ move_to_position(pos)
527
+ @line.text = new_line
528
+ end
529
+
530
+ #
531
+ # Move the cursor to <tt>pos</tt>.
532
+ #
533
+ def move_to_position(pos)
534
+ n = pos-@line.position
535
+ case
536
+ when n > 0 then
537
+ n.times { move_right }
538
+ when n < 0 then
539
+ n.abs.times {move_left}
540
+ when n == 0 then
541
+ end
542
+ end
543
+
544
+ private
545
+
546
+ def select_characters_from_cursor(offset=0)
547
+ select_characters(:right, @line.length-@line.position, offset)
548
+ end
549
+
550
+ def raw_print(string)
551
+ string.each_byte { |c| @output.putc c }
552
+ end
553
+
554
+ def generic_history_back(history)
555
+ unless history.empty?
556
+ history.back
557
+ line = history.get
558
+ overwrite_line(line)
559
+ end
560
+ end
561
+
562
+ def generic_history_forward(history)
563
+ if history.forward then
564
+ overwrite_line(history.get)
565
+ end
566
+ end
567
+
568
+ def select_characters(direction, n, offset=0)
569
+ if direction == :right then
570
+ @line.text[@line.position+offset..@line.position+offset+n]
571
+ elsif direction == :left then
572
+ @line.text[@line.position-offset-n..@line.position-offset]
573
+ end
574
+ end
575
+
576
+ def set_default_keys
577
+ bind(:return) { newline }
578
+ bind(:tab) { complete }
579
+ bind(:backspace) { delete_left_character }
580
+ bind(:ctrl_k) { clear_line }
581
+ bind(:ctrl_z) { undo }
582
+ bind(:ctrl_y) { self.redo }
583
+ bind(:left_arrow) { move_left }
584
+ bind(:right_arrow) { move_right }
585
+ bind(:up_arrow) { history_back }
586
+ bind(:down_arrow) { history_forward }
587
+ bind(:delete) { delete_character }
588
+ bind(:insert) { toggle_mode }
589
+ end
590
+
591
+ end
592
+ end
593
+
594
+
@@ -0,0 +1,130 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # history_buffer.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+ #
12
+ #
13
+ module InLine
14
+
15
+ #
16
+ # The HistoryBuffer class is used to hold the editor and line histories, as well
17
+ # as word completion matches.
18
+ #
19
+ class HistoryBuffer < Array
20
+
21
+ attr_reader :position, :size
22
+ attr_accessor :duplicates, :exclude, :cycle
23
+
24
+ undef <<
25
+
26
+ #
27
+ # Create an instance of InLine::HistoryBuffer.
28
+ # This method takes an optional block used to override the
29
+ # following instance attributes:
30
+ # * <tt>@duplicates</tt> - whether or not duplicate items will be stored in the
31
+ # buffer.
32
+ # * <tt>@exclude</tt> - a Proc object defining exclusion rules to prevent items
33
+ # from being added to the buffer.
34
+ # * <tt>@cycle</tt> - Whether or not the buffer is cyclic.
35
+ #
36
+ def initialize(size)
37
+ @duplicates = true
38
+ @exclude = lambda { nil }
39
+ @cycle = false
40
+ yield self if block_given?
41
+ @size = size
42
+ @position = nil
43
+ end
44
+
45
+ #
46
+ # Resize the buffer, resetting <tt>@position</tt> to nil.
47
+ #
48
+ def resize(new_size)
49
+ if new_size < @size
50
+ @size-new_size.times { pop }
51
+ end
52
+ @size = new_size
53
+ @position = nil
54
+ end
55
+
56
+ #
57
+ # Clear the content of the buffer and reset <tt>@position</tt> to nil.
58
+ #
59
+ def empty
60
+ @position = nil
61
+ clear
62
+ end
63
+
64
+ #
65
+ # Retrieve the element at <tt>@position</tt>.
66
+ #
67
+ def get
68
+ return nil unless length > 0
69
+ @position = length-1 unless @position
70
+ at @position
71
+ end
72
+
73
+ #
74
+ # Return true if <tt>@position</tt> is at the end of the buffer.
75
+ #
76
+ def end?
77
+ @position == length-1
78
+ end
79
+
80
+ #
81
+ # Return true if <tt>@position</tt> is at the start of the buffer.
82
+ #
83
+ def start?
84
+ @position == 0
85
+ end
86
+
87
+ #
88
+ # Decrement <tt>@position</tt>.
89
+ #
90
+ def back
91
+ return nil unless length > 0
92
+ case @position
93
+ when nil: @position = length-1
94
+ when 0: @position = length-1 if @cycle
95
+ else @position -= 1
96
+ end
97
+ end
98
+
99
+ #
100
+ # Increment <tt>@position</tt>.
101
+ #
102
+ def forward
103
+ return nil unless length > 0
104
+ case @position
105
+ when nil: @position = length-1
106
+ when length-1: @position = 0 if @cycle
107
+ else @position += 1
108
+ end
109
+ end
110
+
111
+ #
112
+ # Add a new item to the buffer.
113
+ #
114
+ def <<(item)
115
+ delete(item) unless @duplicates
116
+ unless @exclude.call(item)
117
+ # Remove the oldest element if size is exceeded
118
+ if @size <= length
119
+ reverse!.pop
120
+ reverse!
121
+ end
122
+ # Add the new item and reset the position
123
+ push(item)
124
+ @position = nil
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,158 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # line.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module InLine
13
+
14
+ #
15
+ # The Line class is used to represent the current line being processed and edited
16
+ # by InLine::Editor. It keeps track of the characters typed, the cursor position,
17
+ # the current word and maintains an internal history to allow undos and redos.
18
+ #
19
+ class Line
20
+
21
+ attr_accessor :text, :position, :history, :prompt, :history_size, :word_separator
22
+ attr_reader :offset
23
+
24
+ include HighLine::SystemExtensions
25
+
26
+ #
27
+ # Create an instance of InLine::Line.
28
+ # This method takes an optional block used to override the
29
+ # following instance attributes:
30
+ # * <tt>@text</tt> - the line text.
31
+ # * <tt>@history_size</tt> - the size of the line history buffer.
32
+ # * <tt>@position</tt> - the current cursor position within the line.
33
+ # * <tt>@prompt</tt> - a prompt to prepend to the line text.
34
+ #
35
+ def initialize(history_size)
36
+ @text = ""
37
+ @history_size = history_size
38
+ @position = 0
39
+ @prompt = ""
40
+ @word_separator = ' '
41
+ yield self if block_given?
42
+ @words = []
43
+ @history = InLine::HistoryBuffer.new(@history_size)
44
+ @history << "" # Add empty line for complete undo...
45
+ @offset = @prompt.length
46
+ end
47
+
48
+ #
49
+ # Return the maximum line length. By default, it corresponds to the terminal's
50
+ # width minus the length of the line prompt.
51
+ #
52
+ def max_length
53
+ terminal_size[0]-@offset
54
+ end
55
+
56
+ #
57
+ # Return information about the current word, as a Hash composed by the following
58
+ # elements:
59
+ # * <tt>:start</tt>: The position in the line corresponding to the word start
60
+ # * <tt>:end</tt>: The position in the line corresponding to the word end
61
+ # * <tt>:text</tt>: The word text.
62
+ def word
63
+ last = @text.index(@word_separator, @position)
64
+ first = @text.rindex(@word_separator, @position)
65
+ # Trim word separators and handle EOL and BOL
66
+ if first: first +=1
67
+ else first = bol
68
+ end
69
+ if last: last -=1
70
+ else last = eol+1 unless last
71
+ end
72
+ # Swap if overlapping
73
+ last, first = first, last if last < first
74
+ text = @text[first..last]
75
+ # Repeat the search if within word separator
76
+ if text.match @word_separator then
77
+ last = first
78
+ first = @text.rindex(@word_separator, first)
79
+ if first then first+=1
80
+ else first = bol
81
+ end
82
+ text = @text[first..last]
83
+ end
84
+ {:start => first, :end => last, :text => text}
85
+ end
86
+
87
+ #
88
+ # Return the position corresponding to the beginning of the line.
89
+ #
90
+ def bol
91
+ 0
92
+ end
93
+
94
+ #
95
+ # Return true if the cursor is at the beginning of the line.
96
+ #
97
+ def bol?
98
+ @position<=bol
99
+ end
100
+
101
+ #
102
+ # Return the position corresponding to the end of the line.
103
+ #
104
+ def eol
105
+ @text.length-1
106
+ end
107
+
108
+ #
109
+ # Return true if the cursor is at the end of the line.
110
+ #
111
+ def eol?
112
+ @position>=eol
113
+ end
114
+
115
+ #
116
+ # Decrement the line position by <tt>offset</tt>
117
+ #
118
+ def left(offset=1)
119
+ @position = (@position-offset <= 0) ? 0 : @position-offset
120
+ end
121
+
122
+ #
123
+ # Increment the line position by <tt>offset</tt>
124
+ #
125
+ def right(offset=1)
126
+ @position = (@position+offset >= max_length) ? max_length : @position+offset
127
+ end
128
+
129
+ #
130
+ # Add a character (expressed as a character code) to the line text.
131
+ #
132
+ def <<(char)
133
+ @text << char.chr
134
+ end
135
+
136
+ #
137
+ # Access the line text at <tt>@index</tt>
138
+ #
139
+ def [](index)
140
+ @text[index]
141
+ end
142
+
143
+ #
144
+ # Modify the character(s) in the line text at <tt>@index</tt>
145
+ #
146
+ def []=(index, chars)
147
+ @text[index] = chars
148
+ end
149
+
150
+ #
151
+ # Return the length of the line text.
152
+ #
153
+ def length
154
+ @text.length
155
+ end
156
+
157
+ end
158
+ end
@@ -0,0 +1,87 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # terminal.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+ #
12
+ #
13
+ module InLine
14
+
15
+ #
16
+ # The Terminal class defines character codes and code sequences which can be
17
+ # bound to actions by editors.
18
+ # An OS-dependent subclass of InLine::Terminal is automatically instantiated by
19
+ # InLine::Editor.
20
+ #
21
+ class Terminal
22
+
23
+ include HighLine::SystemExtensions
24
+
25
+ attr_accessor :escape_codes
26
+ attr_reader :keys, :escape_sequences
27
+
28
+ #
29
+ # Create an instance of InLine::Terminal.
30
+ #
31
+ def initialize
32
+ @keys =
33
+ {
34
+ :tab => [?\t],
35
+ :return => [?\r],
36
+ :newline => [?\n],
37
+ :escape => [?\e],
38
+
39
+ :ctrl_a => [?\C-a],
40
+ :ctrl_b => [?\C-b],
41
+ :ctrl_c => [?\C-c],
42
+ :ctrl_d => [?\C-d],
43
+ :ctrl_e => [?\C-e],
44
+ :ctrl_f => [?\C-f],
45
+ :ctrl_g => [?\C-g],
46
+ :ctrl_h => [?\C-h],
47
+ :ctrl_i => [?\C-i],
48
+ :ctrl_j => [?\C-j],
49
+ :ctrl_k => [?\C-k],
50
+ :ctrl_l => [?\C-l],
51
+ :ctrl_m => [?\C-m],
52
+ :ctrl_n => [?\C-n],
53
+ :ctrl_o => [?\C-o],
54
+ :ctrl_p => [?\C-p],
55
+ :ctrl_q => [?\C-q],
56
+ :ctrl_r => [?\C-r],
57
+ :ctrl_s => [?\C-s],
58
+ :ctrl_t => [?\C-t],
59
+ :ctrl_u => [?\C-u],
60
+ :ctrl_v => [?\C-v],
61
+ :ctrl_w => [?\C-w],
62
+ :ctrl_x => [?\C-x],
63
+ :ctrl_y => [?\C-y],
64
+ :ctrl_z => [?\C-z]
65
+ }
66
+ @escape_codes = []
67
+ @escape_sequences = []
68
+ update
69
+ end
70
+
71
+ #
72
+ # Update the terminal escape sequences. This method is called automatically
73
+ # by InLine::Editor#bind().
74
+ #
75
+ def update
76
+ @keys.each_value do |k|
77
+ l = k.length
78
+ if l > 1 then
79
+ @escape_sequences << k unless @escape_sequences.include? k
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+
87
+ end
@@ -0,0 +1,66 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # vt220_terminal.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module InLine
13
+
14
+ #
15
+ # This class is used to define all the most common character codes and
16
+ # escape sequences used on *nix systems.
17
+ #
18
+ class VT220Terminal < Terminal
19
+
20
+ def initialize
21
+ super
22
+ @escape_codes = [?\e]
23
+ @keys.merge!(
24
+ {
25
+ :up_arrow => [?\e, ?[, ?A],
26
+ :down_arrow => [?\e, ?[, ?B],
27
+ :right_arrow => [?\e, ?[, ?C],
28
+ :left_arrow => [?\e, ?[, ?D],
29
+ :insert => [?\e, ?[, ?2, ?~],
30
+ :delete => [?\e, ?[, ?3, ?~],
31
+ :backspace => [?\C-?],
32
+ :enter => [?\n],
33
+
34
+ :ctrl_alt_a => [?\e, ?\C-a],
35
+ :ctrl_alt_b => [?\e, ?\C-b],
36
+ :ctrl_alt_c => [?\e, ?\C-c],
37
+ :ctrl_alt_d => [?\e, ?\C-d],
38
+ :ctrl_alt_e => [?\e, ?\C-e],
39
+ :ctrl_alt_f => [?\e, ?\C-f],
40
+ :ctrl_alt_g => [?\e, ?\C-g],
41
+ :ctrl_alt_h => [?\e, ?\C-h],
42
+ :ctrl_alt_i => [?\e, ?\C-i],
43
+ :ctrl_alt_j => [?\e, ?\C-j],
44
+ :ctrl_alt_k => [?\e, ?\C-k],
45
+ :ctrl_alt_l => [?\e, ?\C-l],
46
+ :ctrl_alt_m => [?\e, ?\C-m],
47
+ :ctrl_alt_n => [?\e, ?\C-n],
48
+ :ctrl_alt_o => [?\e, ?\C-o],
49
+ :ctrl_alt_p => [?\e, ?\C-p],
50
+ :ctrl_alt_q => [?\e, ?\C-q],
51
+ :ctrl_alt_r => [?\e, ?\C-r],
52
+ :ctrl_alt_s => [?\e, ?\C-s],
53
+ :ctrl_alt_t => [?\e, ?\C-t],
54
+ :ctrl_alt_u => [?\e, ?\C-u],
55
+ :ctrl_alt_v => [?\e, ?\C-v],
56
+ :ctrl_alt_w => [?\e, ?\C-w],
57
+ :ctrl_alt_x => [?\e, ?\C-x],
58
+ :ctrl_alt_y => [?\e, ?\C-y],
59
+ :ctrl_alt_z => [?\e, ?\C-z]
60
+ })
61
+ end
62
+
63
+ end
64
+
65
+
66
+ end
@@ -0,0 +1,66 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ #
4
+ # windows_terminal.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module InLine
13
+
14
+ #
15
+ # This class is used to define all the most common character codes and
16
+ # escape sequences used on Windows systems.
17
+ #
18
+ class WindowsTerminal < Terminal
19
+
20
+ def initialize
21
+ super
22
+ @escape_codes = [0, 224]
23
+ @keys.merge!(
24
+ {
25
+ :left_arrow => [224, 75],
26
+ :right_arrow => [224, 77],
27
+ :up_arrow => [224, 72],
28
+ :down_arrow => [224, 80],
29
+ :insert => [224, 82],
30
+ :delete => [224, 83],
31
+ :backspace => [8],
32
+ :enter => [13],
33
+
34
+ :ctrl_alt_a => [0, 30],
35
+ :ctrl_alt_b => [0, 48],
36
+ :ctrl_alt_c => [0, 46],
37
+ :ctrl_alt_d => [0, 32],
38
+ :ctrl_alt_e => [0, 63],
39
+ :ctrl_alt_f => [0, 33],
40
+ :ctrl_alt_g => [0, 34],
41
+ :ctrl_alt_h => [0, 35],
42
+ :ctrl_alt_i => [0, 23],
43
+ :ctrl_alt_j => [0, 36],
44
+ :ctrl_alt_k => [0, 37],
45
+ :ctrl_alt_l => [0, 26],
46
+ :ctrl_alt_m => [0, 32],
47
+ :ctrl_alt_n => [0, 31],
48
+ :ctrl_alt_o => [0, 24],
49
+ :ctrl_alt_p => [0, 25],
50
+ :ctrl_alt_q => [0, 16],
51
+ :ctrl_alt_r => [0, 19],
52
+ :ctrl_alt_s => [0, 31],
53
+ :ctrl_alt_t => [0, 20],
54
+ :ctrl_alt_u => [0, 22],
55
+ :ctrl_alt_v => [0, 47],
56
+ :ctrl_alt_w => [0, 17],
57
+ :ctrl_alt_x => [0, 45],
58
+ :ctrl_alt_y => [0, 21],
59
+ :ctrl_alt_z => [0, 44]
60
+ })
61
+ end
62
+
63
+ end
64
+
65
+
66
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ module InLine
4
+ TEST_HOME = File.dirname(File.expand_path(__FILE__))+'/..' unless const_defined?(:TEST_HOME)
5
+ end
6
+
7
+ require "test_history_buffer"
8
+ require "test_line"
9
+ require "test_editor"
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: inline
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2008-03-15 00:00:00 +01:00
8
+ summary: A library for definign custom key bindings and perform line editing operations
9
+ require_paths:
10
+ - lib
11
+ email: h3rald@h3rald.com
12
+ homepage: http://rubyforge.org/projects/inline
13
+ rubyforge_project: inline
14
+ description: InLine can be used to define custom key bindings, perform common line editing operations, manage command history and define custom command completion rules.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Fabio Cevasco
31
+ files:
32
+ - lib/inline
33
+ - lib/inline/editor.rb
34
+ - lib/inline/history_buffer.rb
35
+ - lib/inline/line.rb
36
+ - lib/inline/terminal
37
+ - lib/inline/terminal/vt220_terminal.rb
38
+ - lib/inline/terminal/windows_terminal.rb
39
+ - lib/inline/terminal.rb
40
+ - lib/inline.rb
41
+ - README
42
+ - LICENSE
43
+ - CHANGELOG
44
+ test_files:
45
+ - test/test_all.rb
46
+ rdoc_options:
47
+ - --main
48
+ - README
49
+ - --exclude
50
+ - test
51
+ extra_rdoc_files:
52
+ - README
53
+ - LICENSE
54
+ - CHANGELOG
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ requirements: []
60
+
61
+ dependencies:
62
+ - !ruby/object:Gem::Dependency
63
+ name: highline
64
+ version_requirement:
65
+ version_requirements: !ruby/object:Gem::Version::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 1.4.0
70
+ version: