Class: RawLine::Editor

Inherits:
Object
  • Object
show all
Includes:
HighLine::SystemExtensions
Defined in:
lib/rawline/editor.rb,
lib/rawline/editor.rb

Overview

The Editor class defines methods to:

  • Read characters from STDIN or any type of input

  • Write characters to STDOUT or any type of output

  • Bind keys to specific actions

  • Perform line-related operations like moving, navigating through history, etc.

Note that the following default key bindings are provided:

  • TAB: word completion defined via completion_proc

  • LEFT/RIGHT ARROWS: cursor movement (left/right)

  • UP/DOWN ARROWS: history navigation

  • DEL: Delete character under cursor

  • BACKSPACE: Delete character before cursor

  • INSERT: Toggle insert/replace mode (default: insert)

  • CTRL+K: Clear the whole line

  • CTRL+Z: undo (unless already registered by the OS)

  • CTRL+Y: redo (unless already registered by the OS)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input = STDIN, output = STDOUT) {|_self| ... } ⇒ Editor

Create an instance of RawLine::Editor which can be used to read from input and perform line-editing operations. This method takes an optional block used to override the following instance attributes:

  • @history_size - the size of the editor history buffer (30).

  • @line_history_size - the size of the editor line history buffer (50).

  • @keys - the keys (arrays of character codes) bound to specific actions.

  • @word_break_characters - a string listing all characters which can be used as word separators (“ tn"\‘`@$><=;|&{(/”).

  • @mode - The editor’s character insertion mode (:insert).

  • @completion_proc - a Proc object used to perform word completion.

  • @completion_append_string - a string to append to completed words (”).

  • @completion_matches - word completion candidates.

  • @terminal - a RawLine::Terminal containing character key codes.

Yields:

  • (_self)

Yield Parameters:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/rawline/editor.rb', line 59

def initialize(input=STDIN, output=STDOUT)
	@input = input
	@output = output
	case RUBY_PLATFORM
	when /mswin/i then
		@terminal = WindowsTerminal.new
		if RawLine.win32console? then
			@win32_io = Win32::Console::ANSI::IO.new
		end
	else
		@terminal = VT220Terminal.new
	end
	@history_size = 30
	@line_history_size = 50
	@keys = {}
	@word_break_characters = " \t\n\"\\'`@$><=;|&{(/"
	@mode = :insert
	@completion_proc = filename_completion_proc
	@completion_append_string = ''
	@match_hidden_files = false
	@completion_matches = HistoryBuffer.new(0) { |h| h.duplicates = false; h.cycle = true }
	set_default_keys
	yield self if block_given?
	update_word_separator
	@add_history = false 
	@history = HistoryBuffer.new(@history_size) do |h| 
		h.duplicates = false; 
		h.exclude = lambda { |item| item.strip == "" }
	end
	@char = nil
end

Instance Attribute Details

#charObject

Returns the value of attribute char.



38
39
40
# File 'lib/rawline/editor.rb', line 38

def char
  @char
end

#completion_append_stringObject Also known as: completion_append_character

Returns the value of attribute completion_append_string.



40
41
42
# File 'lib/rawline/editor.rb', line 40

def completion_append_string
  @completion_append_string
end

#completion_matchesObject

Returns the value of attribute completion_matches.



41
42
43
# File 'lib/rawline/editor.rb', line 41

def completion_matches
  @completion_matches
end

#completion_procObject

Returns the value of attribute completion_proc.



40
41
42
# File 'lib/rawline/editor.rb', line 40

def completion_proc
  @completion_proc
end

#historyObject

Returns the value of attribute history.



40
41
42
# File 'lib/rawline/editor.rb', line 40

def history
  @history
end

#history_sizeObject

Returns the value of attribute history_size.



38
39
40
# File 'lib/rawline/editor.rb', line 38

def history_size
  @history_size
end

#keysObject

Returns the value of attribute keys.



39
40
41
# File 'lib/rawline/editor.rb', line 39

def keys
  @keys
end

#lineObject

Returns the value of attribute line.



40
41
42
# File 'lib/rawline/editor.rb', line 40

def line
  @line
end

#line_history_sizeObject

Returns the value of attribute line_history_size.



38
39
40
# File 'lib/rawline/editor.rb', line 38

def line_history_size
  @line_history_size
end

#match_hidden_filesObject

Returns the value of attribute match_hidden_files.



41
42
43
# File 'lib/rawline/editor.rb', line 41

def match_hidden_files
  @match_hidden_files
end

#modeObject

Returns the value of attribute mode.



39
40
41
# File 'lib/rawline/editor.rb', line 39

def mode
  @mode
end

#terminalObject

Returns the value of attribute terminal.



39
40
41
# File 'lib/rawline/editor.rb', line 39

def terminal
  @terminal
end

#word_break_charactersObject Also known as: basic_word_break_characters, completer_word_break_characters

Returns the value of attribute word_break_characters.



42
43
44
# File 'lib/rawline/editor.rb', line 42

def word_break_characters
  @word_break_characters
end

Instance Method Details

#add_to_historyObject

Add the current line (@line.text) to the editor history.



538
539
540
# File 'lib/rawline/editor.rb', line 538

def add_to_history
	@history << @line.text.dup if @add_history && @line.text != ""
end

#add_to_line_historyObject

Add the current line (@line.text) to the line history, to allow undo/redo operations.



531
532
533
# File 'lib/rawline/editor.rb', line 531

def add_to_line_history
	@line.history << @line.text.dup unless @line.text == ""
end

#bind(key, &block) ⇒ Object

Bind a key to an action specified via block. key can be:

  • A Symbol identifying a character or character sequence defined for the current terminal

  • A Fixnum identifying a character defined for the current terminal

  • An Array identifying a character or character sequence defined for the current terminal

  • A String identifying a character or character sequence, even if it is not defined for the current terminal

  • An Hash identifying a character or character sequence, even if it is not defined for the current terminal

If key is a hash, then:

  • It must contain only one key/value pair

  • The key identifies the name of the character or character sequence

  • The value identifies the code(s) corresponding to the character or character sequence

  • The value can be a Fixnum, a String or an Array.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/rawline/editor.rb', line 215

def bind(key, &block)
	case key.class.to_s
	when 'Symbol' then
		raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys[key]
		@keys[@terminal.keys[key]] = block
	when 'Array' then
		raise BindingException, "Unknown key or key sequence '#{key.join(", ")}' (#{key.class.to_s})" unless @terminal.keys.has_value? key
		@keys[key] = block
	when 'Fixnum' then
		raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys.has_value? [key]
		@keys[[key]] = block
	when 'String' then
		if key.length == 1 then
			@keys[[key.ord]] = block
		else
			bind_hash({:"#{key}" => key}, block)
		end
	when 'Hash' then
		raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
		bind_hash(key, block)
	else
		raise BindingException, "Unable to bind '#{key.to_s}' (#{key.class.to_s})"
	end
	@terminal.update
end

#clear_historyObject

Clear the editor history.



427
428
429
# File 'lib/rawline/editor.rb', line 427

def clear_history
	@history.empty
end

#clear_lineObject

Clear the current line, i.e. @line.text and @line.position. This action is bound to ctrl+k by default.



468
469
470
471
472
473
474
475
476
# File 'lib/rawline/editor.rb', line 468

def clear_line
	@output.putc ?\r
	print @line.prompt
	@line.length.times { @output.putc ?\s.ord }
	@line.length.times { @output.putc ?\b.ord }
	add_to_line_history
	@line.text = ""
	@line.position = 0
end

#completeObject

Complete the current word according to what returned by @completion_proc. Characters can be appended to the completed word via @completion_append_character and word separators can be defined via @word_separator.

This action is bound to the tab key by default, so the first match is displayed the first time the user presses tab, and all the possible messages will be displayed (cyclically) when tab is pressed again.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/rawline/editor.rb', line 302

def complete
	completion_char = @char
	@completion_matches.empty
	word_start = @line.word[:start]
	sub_word = @line.text[@line.word[:start]..@line.position-1] || ""
	matches  = @completion_proc.call(sub_word) unless !completion_proc || @completion_proc == []
	matches = matches.to_a.compact.sort.reverse
	complete_word = lambda do |match|
		unless @line.word[:text].length == 0
			# If not in a word, print the match, otherwise continue existing word
			move_to_position(@line.word[:end]+@completion_append_string.to_s.length+1)
		end
		(@line.position-word_start).times { delete_left_character(true) }
		write match+@completion_append_string.to_s
	end
	unless matches.empty? then
		@completion_matches.resize(matches.length) 
		matches.each { |w| @completion_matches << w }
		# Get first match
		@completion_matches.back
		match = @completion_matches.get
		complete_word.call(match)
		read_character
		while @char == completion_char do
			move_to_position(word_start)
			@completion_matches.back
			match = @completion_matches.get
			complete_word.call(match)
			read_character
		end
		process_character
	end
end

#debug_lineObject

Print debug information about the current line. Note that after the message is displayed, the line text and position will be restored.



396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/rawline/editor.rb', line 396

def debug_line
	pos = @line.position
	text = @line.text
	word = @line.word
	@output.puts 
	@output.puts "Text: [#{text}]"
	@output.puts "Length: #{@line.length}"
	@output.puts "Position: #{pos}"
	@output.puts "Character at Position: [#{text[pos].chr}] (#{text[pos]})" unless pos >= @line.length
	@output.puts "Current Word: [#{word[:text]}] (#{word[:start]} -- #{word[:end]})"
	clear_line
	raw_print text
	overwrite_line(text, pos)
end

#default_actionObject

Execute the default action for the last character read via read. By default it prints the character to the screen via print_character. This method is called automatically by process_character.



261
262
263
# File 'lib/rawline/editor.rb', line 261

def default_action
	print_character
end

#delete_character(no_line_history = false) ⇒ Object

Delete the character under the cursor. If no_line_hisytory is set to true, the deletion won’t be recorded in the line history. This action is bound to the delete key by default.



449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/rawline/editor.rb', line 449

def delete_character(no_line_history=false)
	unless @line.position > @line.eol
		# save characters to shift
		chars = (@line.eol?) ? ' ' : select_characters_from_cursor(1)
		# remove character from console and shift characters
		raw_print chars
		@output.putc ?\s.ord
		(chars.length+1).times { @output.putc ?\b.ord }
		#remove character from line
		@line[@line.position] = ''
		add_to_line_history unless no_line_history
	end
end

#delete_left_character(no_line_history = false) ⇒ Object

Delete the character at the left of the cursor. If no_line_hisytory is set to true, the deletion won’t be recorded in the line history. This action is bound to the backspace key by default.



437
438
439
440
441
# File 'lib/rawline/editor.rb', line 437

def delete_left_character(no_line_history=false)
	if move_left then
		delete_character(no_line_history)
	end
end

#escape(string) ⇒ Object



671
672
673
# File 'lib/rawline/editor.rb', line 671

def escape(string)
	string.each_byte { |c| @win32_io.putc c }
end

#filename_completion_procObject

Complete file and directory names. Hidden files and directories are matched only if @match_hidden_files is true.



340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/rawline/editor.rb', line 340

def filename_completion_proc
	lambda do |word|
		dirs = @line.text.split('/')
			path = @line.text.match(/^\/|[a-zA-Z]:\//) ? "/" : Dir.pwd+"/"
		if dirs.length == 0 then # starting directory
			dir = path
		else
			dirs.delete(dirs.last) unless File.directory?(path+dirs.join('/'))
			dir = path+dirs.join('/')
		end
		Dir.entries(dir).select { |e| (e =~ /^\./ && @match_hidden_files && word == '') || (e =~ /^#{word}/ && e !~ /^\./) }
	end
end

#history_backObject

Load the previous entry of the editor in place of the current line (@line.text). This action is bound to the up arrow key by default.



501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/rawline/editor.rb', line 501

def history_back
	unless @history.position
		current_line = @line.text.dup
		# Temporarily override exclusion rules
		exclude = @history.exclude.dup
		@history.exclude = lambda{|a|}
		# Add current line
		@history << current_line
		@history.exclude = exclude
		@history.back
	end
	generic_history_back(@history)
	add_to_line_history
end

#history_forwardObject

Load the next entry of the editor history in place of the current line (@line.text). This action is bound to down arrow key by default.



521
522
523
524
# File 'lib/rawline/editor.rb', line 521

def history_forward
	generic_history_forward(@history)
	add_to_line_history
end

#key_bound?Boolean

Return true if the last character read via read is bound to an action.

Returns:

  • (Boolean)


244
245
246
# File 'lib/rawline/editor.rb', line 244

def key_bound?
	@keys[@char] ? true : false
end

#library_versionObject

Return the current RawLine version



94
95
96
# File 'lib/rawline/editor.rb', line 94

def library_version
	"RawLine v#{RawLine.rawline_version}"
end

#move_leftObject

Move the cursor left (if possible) by printing a backspace, updating @line.position accordingly. This action is bound to the left arrow key by default.



368
369
370
371
372
373
374
375
# File 'lib/rawline/editor.rb', line 368

def move_left
	unless @line.bol? then
		@output.putc ?\b.ord
		@line.left
		return true
	end
	false
end

#move_rightObject

Move the cursor right (if possible) by re-printing the character at the right of the cursor, if any, and updating @line.position accordingly. This action is bound to the right arrow key by default.



383
384
385
386
387
388
389
390
# File 'lib/rawline/editor.rb', line 383

def move_right
	unless @line.position > @line.eol then
		@line.right
		@output.putc @line.text[@line.position-1]
		return true
	end
	false
end

#move_to_position(pos) ⇒ Object

Move the cursor to pos.



576
577
578
579
580
581
582
583
584
585
# File 'lib/rawline/editor.rb', line 576

def move_to_position(pos)
	n = pos-@line.position
	case
	when n > 0 then
		n.times { move_right }
	when n < 0 then
		n.abs.times {move_left}
	when n == 0 then
	end	
end

#newlineObject

Adds @line.text to the editor history. This action is bound to the enter key by default.



359
360
361
# File 'lib/rawline/editor.rb', line 359

def newline
	add_to_history
end

#overwrite_line(new_line, position = nil) ⇒ Object

Overwrite the current line (@line.text) with new_line, and optionally reset the cursor position to position.



557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/rawline/editor.rb', line 557

def overwrite_line(new_line, position=nil)
	pos = position || new_line.length
	text = @line.text
	@output.putc ?\r.ord
	print @line.prompt
	raw_print new_line
	n = text.length-new_line.length+1
	if n > 0
		n.times { @output.putc ?\s.ord } 
		n.times { @output.putc ?\b.ord }
	end
	@line.position = new_line.length
	move_to_position(pos)		
	@line.text = new_line
end

#parse_key_code(code) ⇒ Object

Parse a key or key sequence into the corresponding codes. This method is called automatically by read_character



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rawline/editor.rb', line 145

def parse_key_code(code)
	if @terminal.escape_codes.include? code then
		sequence = [code]
		seqs = []
		loop do
			c = get_character(@input).ord rescue nil
			sequence << c
			seqs = @terminal.escape_sequences.select { |e| e[0..sequence.length-1] == sequence }
			break if seqs.empty?
			return sequence if [sequence] == seqs
		end
	else
		return (@terminal.keys.has_value? [code]) ? [code] : nil
	end
end

#press_keyObject

Call the action bound to the last character read via read. This method is called automatically by process_character.



252
253
254
# File 'lib/rawline/editor.rb', line 252

def press_key
	@keys[@char].call
end

Write a character to @output at cursor position, shifting characters as appropriate. If no_line_history is set to true, the updated won’t be saved in the history of the current line.



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/rawline/editor.rb', line 271

def print_character(char=@char, no_line_history = false)
	unless @line.length >= @line.max_length-2 then
		if @line.position < @line.length then
			chars = select_characters_from_cursor if @mode == :insert
			@output.putc char
			@line.text[@line.position] = (@mode == :insert) ? "#{char.chr}#{@line.text[@line.position].chr}" : "#{char.chr}"
			@line.right
			if @mode == :insert then
				raw_print chars
				chars.length.times { @output.putc ?\b.ord } # move cursor back
			end
		else
			@output.putc char
			@line.right
			@line << char 
		end
		add_to_line_history unless no_line_history
	end
end

#process_characterObject

Process a character. If the key corresponding to the inputted character is bound to an action, call press_key, otherwise call default_action. This method is called automatically by read



189
190
191
192
193
194
195
196
# File 'lib/rawline/editor.rb', line 189

def process_character
	case @char.class.to_s
	when 'Fixnum' then
		default_action
	when 'Array'
		press_key if key_bound?
	end
end

#read(prompt = "", add_history = false) ⇒ Object Also known as: readline

Read characters from @input until the user presses ENTER (use it in the same way as you’d use IO#gets)

  • An optional prompt can be specified to be printed at the beginning of the line (“”).

  • An optional flag can be specified to enable/disable editor history (false)



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/rawline/editor.rb', line 104

def read(prompt="", add_history=false)
	update_word_separator
	@output.print prompt if prompt != ""
	@add_history = add_history
	@line = Line.new(@line_history_size) do |l| 
		l.prompt = prompt
		l.word_separator = @word_separator
	end
	add_to_line_history
	loop do
		read_character
		process_character
		break if @char == @terminal.keys[:enter] || !@char
	end
	@output.print "\n"
	@line.text
end

#read_characterObject

Read and parse a character from @input. This method is called automatically by read



135
136
137
138
139
# File 'lib/rawline/editor.rb', line 135

def read_character
	@output.flush
	c = get_character(@input).ord rescue nil
	@char = parse_key_code(c) || c
end

#redoObject

Redo a previously-undone modification to the current line (@line.text). This action is bound to ctrl+y by default.



492
493
494
# File 'lib/rawline/editor.rb', line 492

def redo
	generic_history_forward(@line.history)
end

#show_historyObject

Print the content of the editor history. Note that after the message is displayed, the line text and position will be restored.



415
416
417
418
419
420
421
422
# File 'lib/rawline/editor.rb', line 415

def show_history
	pos = @line.position
	text = @line.text
	@output.puts
	@output.puts "History:"
	@history.each {|l| puts "- [#{l}]"}
	overwrite_line(text, pos)
end

#toggle_modeObject

Toggle the editor @mode to :replace or :insert (default).



545
546
547
548
549
550
# File 'lib/rawline/editor.rb', line 545

def toggle_mode
	case @mode
	when :insert then @mode = :replace
	when :replace then @mode = :insert
	end
end

#undoObject

Undo the last modification to the current line (@line.text). This action is bound to ctrl+z by default.



482
483
484
485
# File 'lib/rawline/editor.rb', line 482

def undo
	generic_history_back(@line.history) if @line.history.position == nil
	generic_history_back(@line.history)
end

#write(string) ⇒ Object

Write a string to @output starting from the cursor position. Characters at the right of the cursor are shifted to the right if @mode == :insert, deleted otherwise.



166
167
168
169
# File 'lib/rawline/editor.rb', line 166

def write(string)
	string.each_byte { |c| print_character c, true }
	add_to_line_history
end

#write_line(string) ⇒ Object

Write a new line to @output, overwriting any existing text and printing an end of line character.



175
176
177
178
179
180
181
182
# File 'lib/rawline/editor.rb', line 175

def write_line(string)
	clear_line
	@output.print string
	@line.text = string
	add_to_line_history
	add_to_history
	@char = nil
end