class RawLine::Editor

The Editor class defines methods to:

Note that the following default key bindings are provided:

Attributes

basic_word_break_characters[RW]
basic_word_break_characters=[RW]
char[RW]
completer_word_break_characters[RW]
completer_word_break_characters=[RW]
completion_append_character[RW]
completion_append_character=[RW]
completion_append_string[RW]
completion_matches[RW]
completion_proc[RW]
history[RW]
history_size[RW]
keys[RW]
line[RW]
line_history_size[RW]
match_hidden_files[RW]
mode[RW]
terminal[RW]
word_break_characters[RW]

Public Class Methods

new(input=STDIN, output=STDOUT) { |self| ... } click to toggle source

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.

# 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

Public Instance Methods

add_to_history() click to toggle source

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

# File lib/rawline/editor.rb, line 538
def add_to_history
        @history << @line.text.dup if @add_history && @line.text != ""
end
add_to_line_history() click to toggle source

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

# File lib/rawline/editor.rb, line 531
def add_to_line_history
        @line.history << @line.text.dup unless @line.text == ""
end
bind(key, &block) click to toggle source

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.

# 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_history() click to toggle source

Clear the editor history.

# File lib/rawline/editor.rb, line 427
def clear_history
        @history.empty
end
clear_line() click to toggle source

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

# 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
complete() click to toggle source

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.

# 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_line() click to toggle source

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

# 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_action() click to toggle source

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.

# File lib/rawline/editor.rb, line 261
def default_action
        print_character
end
delete_character(no_line_history=false) click to toggle source

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.

# 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) click to toggle source

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.

# 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) click to toggle source
# File lib/rawline/editor.rb, line 671
def escape(string)
        string.each_byte { |c| @win32_io.putc c }
end
filename_completion_proc() click to toggle source

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

# 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_back() click to toggle source

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.

# 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_forward() click to toggle source

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.

# File lib/rawline/editor.rb, line 521
def history_forward
        generic_history_forward(@history)
        add_to_line_history
end
key_bound?() click to toggle source

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

# File lib/rawline/editor.rb, line 244
def key_bound?
        @keys[@char] ? true : false
end
library_version() click to toggle source

Return the current RawLine version

# File lib/rawline/editor.rb, line 94
def library_version
        "RawLine v#{RawLine.rawline_version}"
end
move_left() click to toggle source

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.

# 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_right() click to toggle source

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.

# 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) click to toggle source

Move the cursor to pos.

# 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
newline() click to toggle source

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

# File lib/rawline/editor.rb, line 359
def newline
        add_to_history
end
overwrite_line(new_line, position=nil) click to toggle source

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

# 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) click to toggle source
Parse a key or key sequence into the corresponding codes.

This method is called automatically by read_character

# 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_key() click to toggle source

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

# File lib/rawline/editor.rb, line 252
def press_key
        @keys[@char].call
end
print_character(char=@char, no_line_history = false) click to toggle source

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.

process_character() click to toggle source

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

# 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) click to toggle source

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)

# 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
Also aliased as: readline
read_character() click to toggle source

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

# 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
readline(prompt="", add_history=false)

Readline compatibility aliases

Alias for: read
redo() click to toggle source

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

# File lib/rawline/editor.rb, line 492
def redo
        generic_history_forward(@line.history)
end
show_history() click to toggle source

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

# 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_mode() click to toggle source

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

# File lib/rawline/editor.rb, line 545
def toggle_mode
        case @mode
        when :insert then @mode = :replace
        when :replace then @mode = :insert
        end
end
undo() click to toggle source

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

# 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) click to toggle source

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.

# 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) click to toggle source

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

# 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

Private Instance Methods

bind_hash(key, block) click to toggle source
# File lib/rawline/editor.rb, line 600
def bind_hash(key, block)
        key.each_pair do |j,k|
                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].ord)
                code = []
                case k.class.to_s
                when 'Fixnum' then
                        code = [k]
                when 'String' then
                        k.each_byte { |b| code << b }
                when 'Array' then
                        code = k
                else
                        raise BindingException, "Unable to bind '#{k.to_s}' (#{k.class.to_s})"
                end
                @terminal.keys[j] = code
                @keys[code] = block
        end
end
generic_history_back(history) click to toggle source
# File lib/rawline/editor.rb, line 627
def generic_history_back(history)
        unless history.empty?
                history.back
                line = history.get
                overwrite_line(line)
        end
end
generic_history_forward(history) click to toggle source
# File lib/rawline/editor.rb, line 635
def generic_history_forward(history)
        if history.forward then
                overwrite_line(history.get)
        end
end
raw_print(string) click to toggle source
# File lib/rawline/editor.rb, line 623
def raw_print(string)
        string.each_byte { |c| @output.putc c }
end
select_characters(direction, n, offset=0) click to toggle source
# File lib/rawline/editor.rb, line 641
def select_characters(direction, n, offset=0)
        if direction == :right then
                @line.text[@line.position+offset..@line.position+offset+n]
        elsif direction == :left then
                @line.text[@line.position-offset-n..@line.position-offset]
        end
end
select_characters_from_cursor(offset=0) click to toggle source
# File lib/rawline/editor.rb, line 619
def select_characters_from_cursor(offset=0)
        select_characters(:right, @line.length-@line.position, offset)
end
set_default_keys() click to toggle source
# File lib/rawline/editor.rb, line 649
def set_default_keys          
        bind(:enter) { newline }
        bind(:tab) { complete }
        bind(:backspace) { delete_left_character }
        bind(:ctrl_k) { clear_line }
        bind(:ctrl_u) { undo }
        bind(:ctrl_r) { self.redo }
        bind(:left_arrow) { move_left }
        bind(:right_arrow) { move_right }
        bind(:up_arrow) { history_back }
        bind(:down_arrow) { history_forward }
        bind(:delete) { delete_character }
        bind(:insert) { toggle_mode }
end
update_word_separator() click to toggle source
# File lib/rawline/editor.rb, line 589
def update_word_separator
        return @word_separator = "" if @word_break_characters.to_s == ""
        chars = []
        @word_break_characters.each_byte do |c|
                ch = (c.is_a? Fixnum) ? c : c.ord
                value = (ch == ?\s.ord) ? ' ' : Regexp.escape(ch.chr).to_s
                chars << value
        end
        @word_separator = /#{chars.join('|')}/
end