class Diakonos::Buffer
Constants
- CHARACTER_PAIRS
- DONT_DISPLAY
- DONT_PAD_END
- DONT_PITCH_CURSOR
- DONT_STRIP_LINE
- DONT_USE_INDENT_IGNORE
- DONT_USE_MD5
- DO_DISPLAY
- DO_PITCH_CURSOR
- DO_USE_MD5
- MATCH_ANY
- MATCH_CLOSE
- NO_SNAPSHOT
- OPPOSITE_DIRECTION
- PAD_END
- READ_ONLY
- READ_WRITE
- ROUND_DOWN
- ROUND_UP
- START_FROM_BEGINNING
- STILL_TYPING
- STOPPED_TYPING
- STRIP_LINE
- TYPING
- USE_INDENT_IGNORE
- WORD_REGEXP
Attributes
Public Class Methods
Set name to nil to create a buffer that is not associated with a file. @param [Hash] options @option options [String] ‘filepath’
A file path (which is expanded internally)
@option options [Boolean] ‘read_only’ (READ_WRITE
)
Whether the buffer should be protected from modification
@option options [Hash] ‘cursor’
A Hash containing 'row' and/or 'col' indicating where the cursor should initially be placed. Defaults: 0 and 0
@option options [Hash] ‘display’
A Hash containing 'top_line' and 'left_column' indicating where the view should be positioned in the file. Defaults: 0 and 0
@see READ_WRITE
@see READ_ONLY
# File lib/diakonos/buffer.rb, line 46 def initialize( options = {} ) @name = options[ 'filepath' ] @modified = false @last_modification_check = Time.now @buffer_states = Array.new @cursor_states = Array.new if @name.nil? @lines = Array.new @lines[ 0 ] = "" else @name = File.expand_path( @name ) if FileTest.exists? @name @lines = IO.readlines( @name ) if ( @lines.length == 0 ) || ( @lines[ -1 ][ -1..-1 ] == "\n" ) @lines.push "" end @lines = @lines.collect do |line| line.chomp end else @lines = Array.new @lines[ 0 ] = "" end end @current_buffer_state = 0 options[ 'display' ] ||= Hash.new @top_line = options[ 'display' ][ 'top_line' ] || 0 @left_column = options[ 'display' ][ 'left_column' ] || 0 @desired_column = @left_column @mark_anchor = nil @text_marks = Hash.new @selection_mode = :normal @last_search_regexps = Array( options['last_search_regexps'] ).map { |r| Regexp.new(r) } @highlight_regexp = nil @last_search = nil @changing_selection = false @typing = false options[ 'cursor' ] ||= Hash.new @last_col = options[ 'cursor' ][ 'col' ] || 0 @last_row = options[ 'cursor' ][ 'row' ] || 0 @last_screen_y = @last_row - @top_line @last_screen_x = @last_col - @left_column @last_screen_col = @last_screen_x @read_only = options[ 'read_only' ] || READ_WRITE @bookmarks = Array.new @lang_stack = Array.new configure if @settings[ "convert_tabs" ] tabs_subbed = false @lines.collect! do |line| new_line = line.expand_tabs( @tab_size ) tabs_subbed = ( tabs_subbed || new_line != line ) # Return value for collect: new_line end @modified = ( @modified || tabs_subbed ) if tabs_subbed $diakonos.set_iline "(spaces substituted for tab characters)" end end @buffer_states[ @current_buffer_state ] = @lines @cursor_states[ @current_buffer_state ] = [ @last_row, @last_col ] end
Public Instance Methods
# File lib/diakonos/buffer.rb, line 159 def == (other) return false if other.nil? @name == other.name end
# File lib/diakonos/buffer.rb, line 155 def [] ( arg ) @lines[ arg ] end
# File lib/diakonos/buffer/searching.rb, line 67 def _find_down(regexps, regexp, from_row, from_col, search_area) # Check the current row first. index = @lines[ from_row ].index( regexp, ( @last_finding ? @last_finding.start_col : from_col ) + 1 ) if index finding = confirm_finding( regexps, search_area, from_row, index, Regexp.last_match ) return finding if finding end # Check below the cursor. ( (from_row + 1)..search_area.end_row ).each do |i| line = @lines[ i ] if i == search_area.end_row line = line[ 0...search_area.end_col ] end index = line.index( regexp ) if index finding = confirm_finding( regexps, search_area, i, index, Regexp.last_match ) return finding if finding end end if index finding = confirm_finding( regexps, search_area, search_area.end_row, index, Regexp.last_match ) return finding if finding end # Wrap around. @wrapped = true index = @lines[ search_area.start_row ].index( regexp, search_area.start_col ) if index finding = confirm_finding( regexps, search_area, search_area.start_row, index, Regexp.last_match ) return finding if finding end ( search_area.start_row+1...from_row ).each do |i| index = @lines[ i ].index( regexp ) if index finding = confirm_finding( regexps, search_area, i, index, Regexp.last_match ) return finding if finding end end # And finally, the other side of the current row. if from_row == search_area.start_row index_col = search_area.start_col else index_col = 0 end if index = @lines[ from_row ].index( regexp, index_col ) if index <= ( @last_finding ? @last_finding.start_col : from_col ) finding = confirm_finding( regexps, search_area, from_row, index, Regexp.last_match ) return finding if finding end end end
# File lib/diakonos/buffer/searching.rb, line 131 def _find_up(regexps, regexp, search_area, from_row, from_col) # Check the current row first. col_to_check = ( @last_finding ? @last_finding.end_col : from_col ) - 1 if ( col_to_check >= 0 ) && ( index = @lines[ from_row ][ 0...col_to_check ].rindex( regexp ) ) finding = confirm_finding( regexps, search_area, from_row, index, Regexp.last_match ) return finding if finding end # Check above the cursor. (from_row - 1).downto( 0 ) do |i| if index = @lines[ i ].rindex( regexp ) finding = confirm_finding( regexps, search_area, i, index, Regexp.last_match ) return finding if finding end end # Wrap around. @wrapped = true (@lines.length - 1).downto(from_row + 1) do |i| if index = @lines[ i ].rindex( regexp ) finding = confirm_finding( regexps, search_area, i, index, Regexp.last_match ) return finding if finding end end # And finally, the other side of the current row. search_col = ( @last_finding ? @last_finding.start_col : from_col ) + 1 if index = @lines[ from_row ].rindex( regexp ) if index > search_col finding = confirm_finding( regexps, search_area, from_row, index, Regexp.last_match ) return finding if finding end end end
# File lib/diakonos/buffer/selection.rb, line 179 def anchor_selection( row = @last_row, col = @last_col, do_display = DO_DISPLAY ) @mark_anchor = ( @mark_anchor || Hash.new ) @mark_anchor[ "row" ] = row @mark_anchor[ "col" ] = col record_mark_start_and_end @changing_selection = true @auto_anchored = false display if do_display end
# File lib/diakonos/buffer/selection.rb, line 189 def anchor_unanchored_selection(*args) if @mark_anchor.nil? anchor_selection *args @changing_selection = false end @auto_anchored = true end
# File lib/diakonos/buffer.rb, line 385 def carriage_return take_snapshot row = @last_row col = @last_col @lines = @lines[ 0...row ] + [ @lines[ row ][ 0...col ] ] + [ @lines[ row ][ col..-1 ] ] + @lines[ (row+1)..-1 ] cursor_to( row + 1, 0 ) if @auto_indent parsed_indent undoable: false end set_modified end
# File lib/diakonos/buffer/searching.rb, line 365 def clear_matches( do_display = DONT_DISPLAY ) @text_marks[ :found ] = [] @highlight_regexp = nil display if do_display end
# File lib/diakonos/buffer/searching.rb, line 451 def clear_pair_highlight @text_marks[ :pair ] = nil end
# File lib/diakonos/buffer/searching.rb, line 27 def clear_search_area @search_area = nil @text_marks.delete :search_area_pre @text_marks.delete :search_area_post end
# File lib/diakonos/buffer.rb, line 259 def close_code line = @lines[ @last_row ] @closers.each_value do |h| h[ :regexp ] =~ line lm = Regexp.last_match if lm str = case h[ :closer ] when String if lm[ 1 ].nil? h[ :closer ] else lm[ 1 ].gsub( Regexp.new( "(#{ Regexp.escape( lm[1] ) })" ), h[ :closer ] ) end when Proc h[ :closer ].call( lm ).to_s end r, c = @last_row, @last_col paste str, !TYPING, @indent_closers cursor_to r, c if /%_/ === str find [/%_/], direction: :down, replacement: '', auto_choice: CHOICE_YES_AND_STOP end end end end
# File lib/diakonos/buffer.rb, line 288 def collapse_whitespace if selection_mark remove_selection DONT_DISPLAY end line = @lines[ @last_row ] head = line[ 0...@last_col ] tail = line[ @last_col..-1 ] new_head = head.sub( /\s+$/, '' ) new_line = new_head + tail.sub( /^\s+/, ' ' ) if new_line != line take_snapshot( TYPING ) @lines[ @last_row ] = new_line cursor_to( @last_row, @last_col - ( head.length - new_head.length ) ) set_modified end end
Translates the window column, x, to a buffer-relative column index.
# File lib/diakonos/buffer.rb, line 419 def column_of( x ) @left_column + x end
Returns nil if the column is off-screen.
# File lib/diakonos/buffer.rb, line 437 def column_to_x( col ) return nil if col.nil? x = col - @left_column x = nil if ( x < 0 ) || ( x > @left_column + Curses::cols - 1 ) x end
# File lib/diakonos/buffer.rb, line 306 def columnize( delimiter = /=>?|:|,/, num_spaces_padding = 1 ) take_snapshot lines = self.selected_lines column_width = 0 lines.each do |line| pos = ( line =~ delimiter ) if pos column_width = [ pos, column_width ].max end end padding = ' ' * num_spaces_padding one_modified = false lines.each do |line| old_line = line.dup if line =~ /^(.+?)(#{delimiter.source})(.*)$/ pre = $1 del = $2 post = $3 if pre !~ /\s$/ del = " #{del}" end if post !~ /^\s/ del = "#{del} " end del.sub!( /^\s+/, ' ' * num_spaces_padding ) del.sub!( /\s+$/, ' ' * num_spaces_padding ) line.replace( ( "%-#{column_width}s" % pre ) + del + post ) end one_modified ||= ( line != old_line ) end if one_modified set_modified end end
# File lib/diakonos/buffer.rb, line 345 def comment_out take_snapshot one_modified = false closer = @settings[ "lang.#{@language}.comment_close_string" ].to_s self.selected_lines.each do |line| next if line.strip.empty? old_line = line.dup line.gsub!( /^(\s*)/, "\\1" + @settings[ "lang.#{@language}.comment_string" ].to_s ) if ! closer.empty? && line !~ /#{Regexp.escape(closer)}$/ line << closer end one_modified ||= ( line != old_line ) end if one_modified set_modified end end
# File lib/diakonos/buffer.rb, line 116 def configure( language = ( $diakonos.get_language_from_shabang( @lines[ 0 ] ) || $diakonos.get_language_from_name( @name ) || LANG_TEXT ) ) reset_display set_language language @original_language = @language end
# File lib/diakonos/buffer/searching.rb, line 50 def confirm_finding( regexps, search_area, from_row, from_col, match ) found_text = match[0] range = ::Diakonos::Range.new(from_row, from_col, from_row, from_col + found_text.length) Finding.confirm(range, regexps, @lines, search_area, match) end
# File lib/diakonos/buffer.rb, line 576 def context retval = Array.new row = @last_row clevel = indentation_level( row ) while row > 0 && clevel < 0 row = row - 1 clevel = indentation_level( row ) end clevel = 0 if clevel < 0 while row > 0 row = row - 1 line = @lines[ row ] if ! line.strip.empty? && ( line !~ @settings[ "lang.#{@language}.context.ignore" ] ) level = indentation_level( row ) if level < clevel && level > -1 retval.unshift line clevel = level break if clevel == 0 end end end retval end
# File lib/diakonos/buffer/selection.rb, line 214 def copy_selection selected_text end
# File lib/diakonos/buffer.rb, line 448 def current_column @last_col end
# File lib/diakonos/buffer.rb, line 409 def current_line @lines[ @last_row ] end
# File lib/diakonos/buffer.rb, line 444 def current_row @last_row end
Returns true iff the cursor changed positions in the buffer.
# File lib/diakonos/buffer/cursor.rb, line 8 def cursor_to( row, col, do_display = DONT_DISPLAY, stopped_typing = STOPPED_TYPING, adjust_row = ADJUST_ROW ) old_last_row = @last_row old_last_col = @last_col row = NumberFitter.fit( number: row, min: 0, max: @lines.length - 1, ) if col < 0 if adjust_row if row > 0 row = row - 1 col = @lines[ row ].length else col = 0 end else col = 0 end elsif col > @lines[ row ].length if adjust_row if row < @lines.length - 1 row = row + 1 col = 0 else col = @lines[ row ].length end else col = @lines[ row ].length end end if adjust_row @desired_column = col else goto_col = [ @desired_column, @lines[ row ].length ].min if col < goto_col col = goto_col end end new_col = tab_expanded_column( col, row ) view_changed = show_character( row, new_col ) @last_screen_y = row - @top_line @last_screen_x = new_col - @left_column @typing = false if stopped_typing @last_row = row @last_col = col @last_screen_col = new_col record_mark_start_and_end selection_removed = false if ! @auto_anchored && ! @changing_selection && selecting? remove_selection( DONT_DISPLAY ) selection_removed = true end @auto_anchored = false old_pair = @text_marks[ :pair ] if @settings[ 'view.pairs.highlight' ] highlight_pair elsif old_pair clear_pair_highlight end highlight_changed = old_pair != @text_marks[ :pair ] if selection_removed || ( do_display && ( selecting? || view_changed || highlight_changed ) ) display else $diakonos.display_mutex.synchronize do @win_main.setpos( @last_screen_y, @last_screen_x ) end end $diakonos.update_status_line $diakonos.update_context_line ( @last_row != old_last_row || @last_col != old_last_col ) end
# File lib/diakonos/buffer/cursor.rb, line 95 def cursor_to_bol row = @last_row case @settings[ "bol_behaviour" ] when BOL_ZERO col = 0 when BOL_FIRST_CHAR col = ( ( @lines[ row ] =~ /\S/ ) || 0 ) when BOL_ALT_ZERO if @last_col == 0 col = ( @lines[ row ] =~ /\S/ ) else col = 0 end #when BOL_ALT_FIRST_CHAR else first_char_col = ( ( @lines[ row ] =~ /\S/ ) || 0 ) if @last_col == first_char_col col = 0 else col = first_char_col end end cursor_to( row, col, DO_DISPLAY ) end
Bottom of view
# File lib/diakonos/buffer/cursor.rb, line 150 def cursor_to_bov cursor_to( row_of( 0 + $diakonos.main_window_height - 1 ), @last_col, DO_DISPLAY ) end
# File lib/diakonos/buffer/cursor.rb, line 91 def cursor_to_eof cursor_to( @lines.length - 1, @lines[ -1 ].length, DO_DISPLAY ) end
# File lib/diakonos/buffer/cursor.rb, line 120 def cursor_to_eol y = @win_main.cury end_col = line_at( y ).length last_char_col = line_at( y ).rstrip.length case @settings[ 'eol_behaviour' ] when EOL_END col = end_col when EOL_LAST_CHAR col = last_char_col when EOL_ALT_LAST_CHAR if @last_col == last_char_col col = end_col else col = last_char_col end else if @last_col == end_col col = last_char_col else col = end_col end end cursor_to( @last_row, col, DO_DISPLAY ) end
Top of view
# File lib/diakonos/buffer/cursor.rb, line 146 def cursor_to_tov cursor_to( row_of( 0 ), @last_col, DO_DISPLAY ) end
x and y are given window-relative, not buffer-relative.
# File lib/diakonos/buffer/delete.rb, line 6 def delete if selection_mark delete_selection else row = @last_row col = @last_col if ( row >= 0 ) && ( col >= 0 ) line = @lines[ row ] if col == line.length if row < @lines.length - 1 # Delete newline, and concat next line join_lines( row ) cursor_to( @last_row, @last_col ) end else take_snapshot( TYPING ) @lines[ row ] = line[ 0...col ] + line[ (col + 1)..-1 ] set_modified end end end end
# File lib/diakonos/buffer/delete.rb, line 100 def delete_from( char ) remove_selection( DONT_DISPLAY ) if selection_mark first_row = row = @last_row index = @lines[ @last_row ].rindex( char, @last_col-1 ) while row > 0 && index.nil? row -= 1 index = @lines[ row ].rindex( char ) end if index deleted_text = delete_from_to( row, index+1, first_row, @last_col ) cursor_to( row, index+1 ) deleted_text end end
# File lib/diakonos/buffer/delete.rb, line 68 def delete_from_to( row_from, col_from, row_to, col_to ) take_snapshot if row_to == row_from retval = [ @lines[ row_to ].slice!( col_from, col_to - col_from ) ] else pre_head = @lines[ row_from ][ 0...col_from ] post_tail = @lines[ row_to ][ col_to..-1 ] head = @lines[ row_from ].slice!( col_from..-1 ) tail = @lines[ row_to ].slice!( 0...col_to ) retval = [ head ] + @lines.slice!( row_from + 1, row_to - row_from ) + [ tail ] @lines[ row_from ] = pre_head + post_tail end set_modified retval end
# File lib/diakonos/buffer/delete.rb, line 29 def delete_line remove_selection( DONT_DISPLAY ) if selection_mark row = @last_row take_snapshot retval = nil if @lines.length == 1 retval = @lines[ 0 ] @lines[ 0 ] = "" else retval = @lines[ row ] @lines.delete_at row end cursor_to( row, 0 ) set_modified retval end
# File lib/diakonos/buffer/selection.rb, line 268 def delete_selection( do_display = DO_DISPLAY ) return if @text_marks[ :selection ].nil? take_snapshot selection = @text_marks[ :selection ] start_row = selection.start_row start_col = selection.start_col end_row = selection.end_row end_col = selection.end_col start_line = @lines[ start_row ] if end_row == selection.start_row @lines[ start_row ] = start_line[ 0...start_col ] + start_line[ end_col..-1 ] else case @selection_mode when :normal end_line = @lines[ end_row ] @lines[ start_row ] = start_line[ 0...start_col ] + end_line[ end_col..-1 ] @lines = @lines[ 0..start_row ] + @lines[ (end_row + 1)..-1 ] when :block @lines[ start_row..end_row ] = @lines[ start_row..end_row ].collect { |line| line[ 0...start_col ] + ( line[ end_col..-1 ] || '' ) } end end cursor_to start_row, start_col remove_selection DONT_DISPLAY set_modified do_display end
# File lib/diakonos/buffer/delete.rb, line 84 def delete_to( char ) remove_selection( DONT_DISPLAY ) if selection_mark first_row = row = @last_row index = @lines[ @last_row ].index( char, @last_col+1 ) while row < @lines.length - 1 && index.nil? row += 1 index = @lines[ row ].index( char ) end if index delete_from_to( first_row, @last_col, row, index ) end end
# File lib/diakonos/buffer/delete.rb, line 118 def delete_to_and_from( char, inclusive = NOT_INCLUSIVE ) remove_selection( DONT_DISPLAY ) if selection_mark start_char = end_char = char case char when '(' end_char = ')' when '{' end_char = '}' when '[' end_char = ']' when '<' end_char = '>' when ')' end_char = '(' when '}' end_char = '{' when ']' end_char = '[' when '>' end_char = '<' end row = @last_row start_index = @lines[ @last_row ].rindex( start_char, @last_col ) while row > 0 && start_index.nil? row -= 1 start_index = @lines[ row ].rindex( start_char ) end start_row = row row = @last_row end_index = @lines[ row ].index( end_char, @last_col+1 ) while row < @lines.length - 1 && end_index.nil? row += 1 end_index = @lines[ row ].index( end_char ) end end_row = row if start_index && end_index if inclusive end_index += 1 else start_index += 1 end cursor_to( start_row, start_index ) delete_from_to( start_row, start_index, end_row, end_index ) end end
# File lib/diakonos/buffer/delete.rb, line 48 def delete_to_eol remove_selection( DONT_DISPLAY ) if selection_mark row = @last_row col = @last_col take_snapshot if @settings[ 'delete_newline_on_delete_to_eol' ] && col == @lines[ row ].size next_line = @lines.delete_at( row + 1 ) @lines[ row ] << next_line retval = [ "\n" ] else retval = [ @lines[ row ][ col..-1 ] ] @lines[ row ] = @lines[ row ][ 0...col ] end set_modified retval end
# File lib/diakonos/buffer/display.rb, line 272 def display @continued_format_class = nil @pen_down = true @lang_stack = [] # First, we have to "draw" off-screen, in order to check for opening of # multi-line highlights. # So, first look backwards from the @top_line to find the first opening # regexp match, if any. index = @top_line - 1 @lines[ [ 0, @top_line - @settings[ "view.lookback" ] ].max...@top_line ].reverse_each do |line| open_index = -1 open_token_class = nil open_match_text = nil open_index, open_token_class, open_match_text = find_opening_match( line ) if open_token_class @pen_down = false @lines[ index...@top_line ].each do |line| print_line line end @pen_down = true break end index = index - 1 end # Draw each on-screen line. y = 0 @lines[ @top_line...($diakonos.main_window_height + @top_line) ].each_with_index do |line, row| if @win_line_numbers @win_line_numbers.setpos( y, 0 ) @win_line_numbers.attrset @settings[ 'view.line_numbers.format' ] n = ( @top_line+row+1 ).to_s @win_line_numbers.addstr( @settings[ 'view.line_numbers.number_format' ] % [ n[ -[ @settings[ 'view.line_numbers.width' ], n.length ].min..-1 ] ] ) end @win_main.setpos( y, 0 ) print_line line.expand_tabs( @tab_size ) @win_main.setpos( y, 0 ) paint_marks @top_line + row y += 1 end # Paint the empty space below the file if the file is too short to fit in one screen. ( y...$diakonos.main_window_height ).each do |y| if @win_line_numbers @win_line_numbers.setpos( y, 0 ) @win_line_numbers.attrset @settings[ 'view.line_numbers.format' ] @win_line_numbers.addstr( ' ' * @settings[ 'view.line_numbers.width' ] ) end @win_main.setpos( y, 0 ) @win_main.attrset @default_formatting linestr = " " * Curses::cols if @settings[ "view.nonfilelines.visible" ] linestr[ 0 ] = ( @settings[ "view.nonfilelines.character" ] || "~" ) end @win_main.addstr linestr end paint_column_markers if @win_line_numbers @win_line_numbers.refresh end @win_main.setpos( @last_screen_y , @last_screen_x ) @win_main.refresh if @language != @original_language set_language( @original_language ) end end
Compares MD5 sums of buffer and actual file on disk. Returns true if there is no file on disk.
# File lib/diakonos/buffer/file.rb, line 130 def file_different? if @name && File.exist?( @name ) Digest::MD5.hexdigest( @lines.join( "\n" ) ) != Digest::MD5.hexdigest( File.read( @name ) ) else true end end
Check if the file which is being edited has been modified since the last time we checked it. @return true if file has been modified @return false if file has not been modified
# File lib/diakonos/buffer/file.rb, line 109 def file_modified? modified = false if @name begin mtime = File.mtime( @name ) if mtime > @last_modification_check modified = true @last_modification_check = mtime end rescue Errno::ENOENT # Ignore if file doesn't exist end end modified end
@param regexps [Array] Regexps which represent a user-provided regexp, split across newline characters. Once the first element is found, each successive element must match against lines following the first element. @return [Integer] the number of replacements made
# File lib/diakonos/buffer/searching.rb, line 176 def find( regexps, options = {} ) return if regexps.nil? regexp = regexps[ 0 ] return if regexp.nil? || regexp == // direction = options[ :direction ] replacement = options[ :replacement ] auto_choice = options[ :auto_choice ] from_row = options[ :starting_row ] || @last_row from_col = options[ :starting_col ] || @last_col show_context_after = options[ :show_context_after ] if options[:starting] @num_matches_found = nil end num_replacements = 0 # TODO: Just use Range here instead of TextMark? search_area = @search_area || TextMark.new( ::Diakonos::Range.new(0, 0, @lines.size - 1, @lines[ -1 ].size), nil ) if ! search_area.contains?( from_row, from_col ) from_row, from_col = search_area.start_row, search_area.start_col end if direction == :opposite direction = OPPOSITE_DIRECTION[@last_search_direction] end @last_search_regexps = regexps @last_search_direction = direction @wrapped = false if direction == :down finding = _find_down(regexps, regexp, from_row, from_col, search_area) elsif direction == :up finding = _find_up(regexps, regexp, search_area, from_row, from_col) end if ! finding remove_selection DONT_DISPLAY clear_matches DO_DISPLAY if ! options[ :quiet ] && ( replacement.nil? || num_replacements == 0 ) $diakonos.set_iline "/#{regexp.source}/ not found." end else if @wrapped && ! options[ :quiet ] if @search_area $diakonos.set_iline( "(search wrapped around to start of search area)" ) else $diakonos.set_iline( "(search wrapped around BOF/EOF)" ) end end remove_selection( DONT_DISPLAY ) @last_finding = finding if @settings[ "found_cursor_start" ] anchor_selection( finding.end_row, finding.end_col, DONT_DISPLAY ) cursor_to( finding.start_row, finding.start_col ) else anchor_selection( finding.start_row, finding.start_col, DONT_DISPLAY ) cursor_to( finding.end_row, finding.end_col ) end if show_context_after watermark = Curses::lines / 6 if @last_row - @top_line > watermark pitch_view( @last_row - @top_line - watermark ) end end @changing_selection = false if regexps.length == 1 @highlight_regexp = regexp highlight_matches else clear_matches end display if replacement # Substitute placeholders (e.g. \1) in str for the group matches of the last match. actual_replacement = replacement.dup actual_replacement.gsub!( /\\(\\|\d+)/ ) { |m| ref = $1 if ref == "\\" "\\" else finding.captured_group(ref.to_i) end } choices = [ CHOICE_YES, CHOICE_NO, CHOICE_ALL, CHOICE_CANCEL, CHOICE_YES_AND_STOP, ] if @search_area choices << CHOICE_WITHIN_SELECTION end choice = auto_choice || $diakonos.get_choice( "#{@num_matches_found} match#{ @num_matches_found != 1 ? 'es' : '' } - Replace this one?", choices, CHOICE_YES ) case choice when CHOICE_YES paste [ actual_replacement ] num_replacements += 1 + find( regexps, direction: direction, replacement: replacement ) when CHOICE_ALL num_replacements += replace_all(regexp, replacement) when CHOICE_WITHIN_SELECTION num_replacements += replace_all(regexp, replacement, :within_search_area) when CHOICE_NO num_replacements += find( regexps, direction: direction, replacement: replacement ) when CHOICE_CANCEL # Do nothing further. when CHOICE_YES_AND_STOP paste [ actual_replacement ] num_replacements += 1 # Do nothing further. end end end num_replacements end
# File lib/diakonos/buffer/searching.rb, line 455 def find_again( last_search_regexps, direction = @last_search_direction ) if @last_search_regexps.nil? || @last_search_regexps.empty? @last_search_regexps = last_search_regexps end if @last_search_regexps find @last_search_regexps, direction: direction end end
# File lib/diakonos/buffer/display.rb, line 7 def find_opening_match( line, match_close = true, bos_allowed = true ) open_index = line.length open_token_class = nil open_match_text = nil match = nil match_text = nil @token_regexps.each do |token_class,regexp| if match = regexp.match( line ) if match.length > 1 index = match.begin 1 match_text = match[ 1 ] whole_match_index = match.begin 0 else whole_match_index = index = match.begin( 0 ) match_text = match[ 0 ] end if ( ! regexp.uses_bos ) || ( bos_allowed && ( whole_match_index == 0 ) ) if index < open_index if ( ( ! match_close ) || @close_token_regexps[ token_class ] ) open_index = index open_token_class = token_class open_match_text = match_text end end end end end [ open_index, open_token_class, open_match_text ] end
# File lib/diakonos/buffer/cursor.rb, line 208 def go_block_inner initial_level = indentation_level( @last_row ) new_row = @lines.length ( @last_row...@lines.length ).each do |row| next if @lines[ row ].strip.empty? level = indentation_level( row ) if level > initial_level new_row = row break elsif level < initial_level new_row = @last_row break end end go_to_line( new_row, @lines[ new_row ].index( /\S/ ) ) end
# File lib/diakonos/buffer/cursor.rb, line 225 def go_block_next initial_level = indentation_level( @last_row ) new_row = @last_row passed = false ( @last_row+1...@lines.length ).each do |row| next if @lines[ row ].strip.empty? level = indentation_level( row ) if ! passed if level < initial_level passed = true end else if level == initial_level new_row = row break elsif level < initial_level - 1 break end end end go_to_line( new_row, @lines[ new_row ].index( /\S/ ) ) end
# File lib/diakonos/buffer/cursor.rb, line 185 def go_block_outer initial_level = indentation_level( @last_row ) new_row = @last_row passed = false new_level = initial_level ( 0...@last_row ).reverse_each do |row| next if @lines[ row ].strip.empty? level = indentation_level( row ) if ! passed passed = ( level < initial_level ) new_level = level else if level < new_level new_row = ( row+1..@last_row ).find { |r| ! @lines[ r ].strip.empty? } break end end end go_to_line( new_row, @lines[ new_row ].index( /\S/ ) ) end
# File lib/diakonos/buffer/cursor.rb, line 248 def go_block_previous initial_level = indentation_level( @last_row ) new_row = @last_row passed = false # search for unindent passed2 = false # search for reindent ( 0...@last_row ).reverse_each do |row| next if @lines[ row ].strip.empty? level = indentation_level( row ) if ! passed if level < initial_level passed = true end else if ! passed2 if level >= initial_level new_row = row passed2 = true elsif level <= initial_level - 2 # No previous block break end else if level < initial_level new_row = ( row+1..@last_row ).find { |r| ! @lines[ r ].strip.empty? } break end end end end go_to_line( new_row, @lines[ new_row ].index( /\S/ ) ) end
# File lib/diakonos/buffer/cursor.rb, line 282 def go_to_char( char, after = ON_CHAR ) r = @last_row i = @lines[ r ].index( char, @last_col + 1 ) if i if after i += 1 end return cursor_to r, i, DO_DISPLAY end loop do r += 1 break if r >= @lines.size i = @lines[ r ].index( char ) if i if after i += 1 end return cursor_to r, i, DO_DISPLAY end end end
# File lib/diakonos/buffer/cursor.rb, line 306 def go_to_char_previous( char, after = ON_CHAR ) r = @last_row search_from = @last_col - 1 if search_from >= 0 i = @lines[ r ].rindex( char, search_from ) if i if after i += 1 end return cursor_to r, i, DO_DISPLAY end end loop do r -= 1 break if r < 0 i = @lines[ r ].rindex( char ) if i if after i += 1 end return cursor_to r, i, DO_DISPLAY end end end
# File lib/diakonos/buffer/cursor.rb, line 181 def go_to_line( line = nil, column = nil, do_display = DO_DISPLAY ) cursor_to( line || @last_row, column || 0, do_display ) end
# File lib/diakonos/buffer/bookmarking.rb, line 5 def go_to_next_bookmark cur_pos = Bookmark.new( self, @last_row, @last_col ) next_bm = @bookmarks.find do |bm| bm > cur_pos end if next_bm cursor_to( next_bm.row, next_bm.col, DO_DISPLAY ) end end
# File lib/diakonos/buffer/searching.rb, line 429 def go_to_pair_match row, col = pos_of_pair_match if row && col if cursor_to( row, col ) highlight_pair display end end end
# File lib/diakonos/buffer/bookmarking.rb, line 15 def go_to_previous_bookmark cur_pos = Bookmark.new( self, @last_row, @last_col ) # There's no reverse_find method, so, we have to do this manually. prev = nil @bookmarks.reverse_each do |bm| if bm < cur_pos prev = bm break end end if prev cursor_to( prev.row, prev.col, DO_DISPLAY ) end end
Returns an Array of results, where each result is a String
usually containing n‘s due to context
# File lib/diakonos/buffer/searching.rb, line 532 def grep( regexp_source ) if @name filename = File.basename( @name ) filepath = @name else filename = "(unnamed buffer)" filepath = "(unnamed buffer #{object_id})" end ::Diakonos.grep_array( Regexp.new( regexp_source ), @lines, $diakonos.settings[ 'grep.context' ], "#{filename}:", filepath ) end
# File lib/diakonos/buffer/searching.rb, line 334 def highlight_matches( regexp = @highlight_regexp ) @highlight_regexp = regexp return if @highlight_regexp.nil? if @search_area lines = self.search_area_lines line_index_offset = @search_area.start_row col_offset = @search_area.start_col else lines = @lines line_index_offset = 0 col_offset = 0 end grepped_lines = lines.grep_indices( @highlight_regexp ) n = grepped_lines.count found_marks = grepped_lines.collect do |line_index, start_col, end_col| TextMark.new( ::Diakonos::Range.new( line_index + line_index_offset, start_col + ( line_index == 0 ? col_offset : 0 ), line_index + line_index_offset, end_col, ), @settings["lang.#{@language}.format.found"] ) end @text_marks[:found] = found_marks @num_matches_found ||= found_marks.size end
# File lib/diakonos/buffer/searching.rb, line 439 def highlight_pair match_row, match_col = pos_of_pair_match( @last_row, @last_col ) if match_col.nil? @text_marks[ :pair ] = nil else @text_marks[ :pair ] = TextMark.new( ::Diakonos::Range.new(match_row, match_col, match_row, match_col + 1), @settings[ "lang.#{@language}.format.pair" ] || @settings[ "lang.shared.format.pair" ] ) end end
Returns true iff the given column, x, is less than the length of the given line, y.
# File lib/diakonos/buffer.rb, line 414 def in_line( x, y ) x + @left_column < line_at( y ).length end
# File lib/diakonos/buffer/indentation.rb, line 163 def indent( row = @last_row, do_display = DO_DISPLAY ) level = indentation_level( row, DONT_USE_INDENT_IGNORE ) set_indent row, level + 1, do_display: do_display end
# File lib/diakonos/buffer/indentation.rb, line 51 def indentation_level( row, use_indent_ignore = USE_INDENT_IGNORE ) line = @lines[ row ] if use_indent_ignore if line =~ /^[\s#{@indent_ignore_charset}]*$/ || line == "" level = 0 elsif line =~ /^([\s#{@indent_ignore_charset}]+)[^\s#{@indent_ignore_charset}]/ whitespace = $1.expand_tabs( @tab_size ) level = whitespace.length / @indent_size if @indent_roundup && ( whitespace.length % @indent_size > 0 ) level += 1 end else level = 0 end else level = 0 if line =~ /^([\s]+)/ whitespace = $1.expand_tabs( @tab_size ) level = whitespace.length / @indent_size if @indent_roundup && ( whitespace.length % @indent_size > 0 ) level += 1 end end end level end
# File lib/diakonos/buffer.rb, line 188 def insert_char( c ) row = @last_row col = @last_col take_snapshot( TYPING ) line = @lines[ row ] @lines[ row ] = line[ 0...col ] + c.chr + line[ col..-1 ] set_modified end
# File lib/diakonos/buffer.rb, line 197 def insert_string( str ) row = @last_row col = @last_col take_snapshot( TYPING ) line = @lines[ row ] @lines[ row ] = line[ 0...col ] + str + line[ col..-1 ] set_modified end
# File lib/diakonos/buffer.rb, line 247 def join_lines( row = @last_row, strip = DONT_STRIP_LINE ) take_snapshot( TYPING ) next_line = @lines.delete_at( row + 1 ) return false if next_line.nil? if strip next_line = ' ' + next_line.strip end @lines[ row ] << next_line set_modified end
# File lib/diakonos/buffer.rb, line 220 def join_lines_upward( row = @last_row, strip = DONT_STRIP_LINE ) return false if row == 0 take_snapshot line = @lines.delete_at( row ) old_line = @lines[ row-1 ] new_x_pos = old_line.length if strip line.strip! # Only prepend a space if the line above isn't empty. if ! old_line.strip.empty? line = ' ' + line new_x_pos += 1 end end @lines[ row-1 ] << line cursor_to( row-1, new_x_pos ) set_modified end
# File lib/diakonos/buffer.rb, line 164 def length @lines.length end
# File lib/diakonos/buffer.rb, line 400 def line_at( y ) row = @top_line + y if row < 0 nil else @lines[ row ] end end
# File lib/diakonos/buffer.rb, line 172 def modified? @modified end
@param starting_row [Integer] @param next_line_check [Boolean] @return [Integer]
# File lib/diakonos/buffer/indentation.rb, line 83 def nearest_basis_row_from(starting_row, next_line_check = true) row = starting_row-1 if @lines[row] =~ @indenters_next_line || @lines[row] =~ @indenters return row end loop do return nil if row.nil? || row < 0 while ( @lines[row] =~ /^[\s#{@indent_ignore_charset}]*$/ || @lines[row] =~ @settings[ "lang.#{@language}.indent.ignore" ] || @lines[row] =~ @settings[ "lang.#{@language}.indent.not_indented" ] ) row = nearest_basis_row_from(row) return nil if row.nil? end if next_line_check row_before = nearest_basis_row_from(row, false) if row_before && @lines[row_before] =~ @indenters_next_line row = row_before next end end break end row end
# File lib/diakonos/buffer.rb, line 176 def nice_name @name || @settings[ "status.unnamed_str" ] end
# File lib/diakonos/buffer/display.rb, line 145 def paint_column_markers $diakonos.column_markers.each_value do |data| column = data[ :column ] next if column.nil? next if column > Curses::cols - @left_column || column - @left_column < 0 num_lines_to_paint = [ $diakonos.main_window_height, @lines.size - @top_line ].min ( 0...num_lines_to_paint ).each do |row| @win_main.setpos( row, column - @left_column ) @win_main.attrset data[ :format ] @win_main.addstr( @lines[ @top_line + row ][ column + @left_column ] || ' ' ) end end end
# File lib/diakonos/buffer/display.rb, line 101 def paint_marks( row ) string = @lines[ row ][ @left_column ... @left_column + Curses::cols ] return if string.nil? || string == "" string = string.expand_tabs( @tab_size ) cury = @win_main.cury curx = @win_main.curx @text_marks.values.flatten.reverse_each do |text_mark| next if text_mark.nil? @win_main.attrset text_mark.formatting case @selection_mode when :normal if ( (text_mark.start_row + 1) .. (text_mark.end_row - 1) ) === row @win_main.setpos( cury, curx ) @win_main.addstr string elsif row == text_mark.start_row && row == text_mark.end_row paint_single_row_mark( row, text_mark, string, curx, cury ) elsif row == text_mark.start_row expanded_col = tab_expanded_column( text_mark.start_col, row ) if expanded_col < @left_column + Curses::cols left = [ expanded_col - @left_column, 0 ].max @win_main.setpos( cury, curx + left ) @win_main.addstr string[ left..-1 ] end elsif row == text_mark.end_row right = tab_expanded_column( text_mark.end_col, row ) - @left_column @win_main.setpos( cury, curx ) @win_main.addstr string[ 0...right ] else # This row not in selection. end when :block if( text_mark.start_row <= row && row <= text_mark.end_row || text_mark.end_row <= row && row <= text_mark.start_row ) paint_single_row_mark( row, text_mark, string, curx, cury ) end end end end
Worker function for painting only part of a row.
# File lib/diakonos/buffer/display.rb, line 89 def paint_single_row_mark( row, text_mark, string, curx, cury ) expanded_col = tab_expanded_column( text_mark.start_col, row ) if expanded_col < @left_column + Curses::cols left = [ expanded_col - @left_column, 0 ].max right = tab_expanded_column( text_mark.end_col, row ) - @left_column if left < right @win_main.setpos( cury, curx + left ) @win_main.addstr string[ left...right ] end end end
Returns the amount the view was actually panned.
# File lib/diakonos/buffer.rb, line 459 def pan_view( x = 1, do_display = DO_DISPLAY ) old_left_column = @left_column pan_view_to( @left_column + x, do_display ) @left_column - old_left_column end
# File lib/diakonos/buffer.rb, line 452 def pan_view_to( left_column, do_display = DO_DISPLAY ) @left_column = [ left_column, 0 ].max record_mark_start_and_end display if do_display end
Returns an array of lines of the current paragraph.
# File lib/diakonos/buffer.rb, line 650 def paragraph_under_cursor ( first, _ ), ( last, _ ) = paragraph_under_cursor_pos @lines[ first..last ] end
Returns the coordinates of the first and last line of the current paragraph.
# File lib/diakonos/buffer.rb, line 657 def paragraph_under_cursor_pos if @lines[ @last_row ] =~ /^\s*$/ return [ [ @last_row, 0 ], [ @last_row, @lines[ @last_row ].length - 1 ] ] end upper_boundary = 0 lower_boundary = @lines.size - 1 @last_row.downto( 0 ) do |i| line = @lines[ i ] if line =~ /^\s*$/ upper_boundary = i + 1 break end end @last_row.upto( @lines.size - 1 ) do |i| line = @lines[ i ] if line =~ /^\s*$/ lower_boundary = i - 1 break end end [ [ upper_boundary, 0 ], [ lower_boundary, @lines[ lower_boundary ].length - 1 ] ] end
# File lib/diakonos/buffer/indentation.rb, line 116 def parsed_indent( opts = {} ) row = opts.fetch( :row, @last_row ) do_display = opts.fetch( :do_display, true ) undoable = opts.fetch( :undoable, true ) cursor_eol = opts.fetch( :cursor_eol, false ) if row == 0 || @lines[ row ] =~ @settings[ "lang.#{@language}.indent.not_indented" ] level = 0 else basis_row = nearest_basis_row_from(row) if basis_row.nil? level = 0 else # @lines[basis_row] += " // x" level = indentation_level(basis_row) prev_line = @lines[basis_row] line = @lines[row] if @preventers prev_line = prev_line.gsub( @preventers, "" ) line = line.gsub( @preventers, "" ) end indenter_index = (prev_line =~ @indenters) nl_indenter_index = (prev_line =~ @indenters_next_line) if nl_indenter_index && basis_row == row-1 level += 1 elsif indenter_index level += 1 unindenter_index = (prev_line =~ @unindenters) if unindenter_index && unindenter_index != indenter_index level -= 1 end end if line =~ @unindenters level -= 1 end end end set_indent row, level, do_display: do_display, undoable: undoable, cursor_eol: cursor_eol end
text is an array of Strings, or a String
with zero or more newlines (“n”)
# File lib/diakonos/buffer/selection.rb, line 301 def paste( text, typing = ! TYPING, do_parsed_indent = false ) return if text.nil? if ! text.kind_of?(Array) s = text.to_s if s.include?( "\n" ) text = s.split( "\n", -1 ) else text = [ s ] end end take_snapshot typing delete_selection DONT_DISPLAY row = @last_row col = @last_col new_col = nil line = @lines[ row ] if text.length == 1 @lines[ row ] = line[ 0...col ] + text[ 0 ] + line[ col..-1 ] if do_parsed_indent parsed_indent row: row, do_display: false end cursor_to( @last_row, @last_col + text[ 0 ].length, DONT_DISPLAY, ! typing ) elsif text.length > 1 case @selection_mode when :normal @lines[ row ] = line[ 0...col ] + text[ 0 ] @lines[ row + 1, 0 ] = text[ -1 ] + line[ col..-1 ] @lines[ row + 1, 0 ] = text[ 1..-2 ] new_col = column_of( text[ -1 ].length ) when :block @lines += [ '' ] * [ 0, ( row + text.length - @lines.length ) ].max @lines[ row...( row + text.length ) ] = @lines[ row...( row + text.length ) ].collect.with_index { |line,index| pre = line[ 0...col ].ljust( col ) post = line[ col..-1 ] "#{pre}#{text[ index ]}#{post}" } new_col = col + text[ -1 ].length end new_row = @last_row + text.length - 1 if do_parsed_indent ( row..new_row ).each do |r| parsed_indent row: r, do_display: false end end cursor_to( new_row, new_col ) end set_modified end
Returns the amount the view was actually pitched.
# File lib/diakonos/buffer.rb, line 537 def pitch_view( y = 1, do_pitch_cursor = DONT_PITCH_CURSOR, do_display = DO_DISPLAY ) pitch_view_to( @top_line + y, do_pitch_cursor, do_display ) end
# File lib/diakonos/buffer.rb, line 465 def pitch_view_to( new_top_line, do_pitch_cursor = DONT_PITCH_CURSOR, do_display = DO_DISPLAY ) old_top_line = @top_line if new_top_line < 0 @top_line = 0 elsif new_top_line + $diakonos.main_window_height > @lines.length @top_line = [ @lines.length - $diakonos.main_window_height, 0 ].max else @top_line = new_top_line end old_row = @last_row old_col = @last_col changed = ( @top_line - old_top_line ) if changed != 0 && do_pitch_cursor @last_row += changed end height = [ $diakonos.main_window_height, @lines.length ].min @last_row = NumberFitter.fit( number: @last_row, min: @top_line, max: @top_line + height - 1, ) if @last_row - @top_line < @settings["view.margin.y"] @last_row = NumberFitter.fit( number: @top_line + @settings["view.margin.y"], min: @top_line, max: @top_line + height - 1, ) elsif @top_line + height - 1 - @last_row < @settings[ "view.margin.y" ] @last_row = NumberFitter.fit( number: @top_line + height - 1 - @settings[ "view.margin.y" ], min: @top_line, max: @top_line + height - 1, ) end @last_col = NumberFitter.fit( number: @last_col, min: @left_column, max: [ @left_column + Curses::cols - 1, @lines[@last_row].length ].min ) @last_screen_y = @last_row - @top_line @last_screen_x = tab_expanded_column( @last_col, @last_row ) - @left_column record_mark_start_and_end if changed != 0 if ! @auto_anchored && ! @changing_selection && selecting? remove_selection DONT_DISPLAY end highlight_matches if $diakonos.there_was_non_movement $diakonos.push_cursor_state( old_top_line, old_row, old_col ) end end display if do_display changed end
# File lib/diakonos/buffer/searching.rb, line 371 def pos_of_next( regexp, start_row, start_col ) row, col = start_row, start_col col = @lines[ row ].index( regexp, col ) while col.nil? && row < @lines.length - 1 row += 1 col = @lines[ row ].index( regexp ) end if col [ row, col, Regexp.last_match( 0 ) ] end end
# File lib/diakonos/buffer/searching.rb, line 401 def pos_of_pair_match( row = @last_row, col = @last_col ) c = @lines[ row ][ col ] data = CHARACTER_PAIRS[ c ] return if data.nil? d = data[ :partner ] c_ = Regexp.escape c d_ = Regexp.escape d target = /(?:#{c_}|#{d_})/ case data[ :direction ] when :forward row, col, char = pos_of_next( target, row, col + 1 ) while char == c # Take care of nested pairs row, col = pos_of_pair_match( row, col ) break if col.nil? row, col, char = pos_of_next( target, row, col + 1 ) end when :backward row, col, char = pos_of_prev( target, row, col - 1 ) while char == c # Take care of nested pairs row, col = pos_of_pair_match( row, col ) break if col.nil? row, col, char = pos_of_prev( target, row, col - 1 ) end end [ row, col ] end
# File lib/diakonos/buffer/searching.rb, line 383 def pos_of_prev( regexp, start_row, start_col ) row, col = start_row, start_col if col < 0 row -= 1 col = -1 end return if row < 0 col = @lines[ row ].rindex( regexp, col ) while col.nil? && row > 0 row -= 1 col = @lines[ row ].rindex( regexp ) end if col [ row, col, Regexp.last_match( 0 ) ] end end
This method assumes that the cursor has been set up already at the left-most column of the correct on-screen row. It merely unintelligently prints the characters on the current curses line, refusing to print characters of the in-buffer line which are offscreen.
# File lib/diakonos/buffer/display.rb, line 172 def print_line( line ) i = 0 substr = nil index = nil while i < line.length substr = line[ i..-1 ] if @continued_format_class close_index, close_match_text = find_closing_match( substr, @close_token_regexps[@continued_format_class], i == 0 ) if close_match_text.nil? print_string truncate_off_screen( substr, i ) print_padding_from( line.length ) i = line.length else end_index = close_index + close_match_text.length print_string truncate_off_screen( substr[ 0...end_index ], i ) @continued_format_class = nil i += end_index end else first_index, first_token_class, first_word = find_opening_match( substr, MATCH_ANY, i == 0 ) if @lang_stack.length > 0 prev_lang, close_token_class = @lang_stack[-1] close_index, close_match_text = find_closing_match( substr, $diakonos.close_token_regexps[prev_lang][close_token_class], i == 0 ) if close_match_text && close_index <= first_index # Print any remaining text in the embedded language s = substr[0...close_index] print_string( truncate_off_screen(s, i) ) i += s.length @lang_stack.pop set_language prev_lang print_string( truncate_off_screen( substr[ close_index...(close_index + close_match_text.length) ], i ), @token_formats[close_token_class] ) i += close_match_text.length # Continue printing from here. next end end if first_word if first_index > 0 # Print any preceding text in the default format print_string truncate_off_screen( substr[ 0...first_index ], i ) i += substr[ 0...first_index ].length end print_string( truncate_off_screen( first_word, i ), @token_formats[ first_token_class ] ) i += first_word.length if @close_token_regexps[ first_token_class ] if change_to = @settings[ "lang.#{@language}.tokens.#{first_token_class}.change_to" ] @lang_stack.push [ @language, first_token_class ] set_language change_to else @continued_format_class = first_token_class end end else print_string truncate_off_screen( substr, i ) i += substr.length break end end end print_padding_from i end
# File lib/diakonos/buffer/display.rb, line 258 def print_padding_from( col ) return if ! @pen_down if col < @left_column remainder = Curses::cols else remainder = @left_column + Curses::cols - col end if remainder > 0 print_string( " " * remainder ) end end
# File lib/diakonos/buffer/display.rb, line 160 def print_string( string, formatting = ( @token_formats[ @continued_format_class ] || @default_formatting ) ) return if ! @pen_down return if string.nil? @win_main.attrset formatting @win_main.addstr string end
@mark_start[ “col” ] is inclusive, @mark_end[ “col” ] is exclusive.
# File lib/diakonos/buffer/selection.rb, line 7 def record_mark_start_and_end if @mark_anchor.nil? @text_marks[ :selection ] = nil return end crow = @last_row ccol = @last_col arow = @mark_anchor[ 'row' ] acol = @mark_anchor[ 'col' ] case @selection_mode when :normal anchor_first = true if crow < arow anchor_first = false elsif crow > arow anchor_first = true else if ccol < acol anchor_first = false end end if anchor_first @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(arow, acol, crow, ccol), @selection_formatting ) else @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(crow, ccol, arow, acol), @selection_formatting ) end when :block if crow < arow if ccol < acol # Northwest @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(crow, ccol, arow, acol), @selection_formatting ) else # Northeast @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(crow, acol, arow, ccol), @selection_formatting ) end else if ccol < acol # Southwest @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(arow, ccol, crow, acol), @selection_formatting ) else # Southeast @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(arow, acol, crow, ccol), @selection_formatting ) end end end end
# File lib/diakonos/buffer/selection.rb, line 197 def remove_selection( do_display = DO_DISPLAY ) return if selection_mark.nil? @mark_anchor = nil record_mark_start_and_end @changing_selection = false @last_finding = nil display if do_display end
@return [Integer] the number of replacements made
# File lib/diakonos/buffer/searching.rb, line 300 def replace_all( regexp, replacement, within_search_area = false ) return if( regexp.nil? || replacement.nil? ) num_replacements = 0 take_snapshot if within_search_area && @search_area lines = self.search_area_lines else lines = @lines end lines_modified = lines.collect { |line| num_replacements += line.scan(regexp).size line.gsub( regexp, replacement ) } if within_search_area && @search_area @lines[@search_area.start_row][@search_area.start_col..-1] = lines_modified[0] if @search_area.end_row - @search_area.start_row > 1 @lines[@search_area.start_row+1..@search_area.end_row-1] = lines_modified[1..-2] end @lines[@search_area.end_row][0..@search_area.end_col] = lines_modified[-1] else @lines = lines_modified end set_modified clear_matches display num_replacements end
# File lib/diakonos/buffer.rb, line 180 def replace_char( c ) row = @last_row col = @last_col take_snapshot TYPING @lines[ row ][ col ] = c set_modified end
# File lib/diakonos/buffer.rb, line 128 def reset_display @win_main = $diakonos.win_main @win_line_numbers = $diakonos.win_line_numbers end
Translates the window row, y, to a buffer-relative row index.
# File lib/diakonos/buffer.rb, line 424 def row_of( y ) @top_line + y end
Returns nil if the row is off-screen.
# File lib/diakonos/buffer.rb, line 429 def row_to_y( row ) return nil if row.nil? y = row - @top_line y = nil if ( y < 0 ) || ( y > @top_line + $diakonos.main_window_height - 1 ) y end
# File lib/diakonos/buffer/file.rb, line 5 def save( filename = nil, prompt_overwrite = DONT_PROMPT_OVERWRITE ) if filename name = File.expand_path( filename ) else name = @name end if @read_only && FileTest.exists?( @name ) && FileTest.exists?( name ) && ( File.stat( @name ).ino == File.stat( name ).ino ) $diakonos.set_iline "#{name} cannot be saved since it is read-only." else @read_only = false if name.nil? $diakonos.save_file_as else proceed = true if prompt_overwrite && FileTest.exists?( name ) proceed = false choice = $diakonos.get_choice( "Overwrite existing '#{name}'?", [ CHOICE_YES, CHOICE_NO ], CHOICE_NO ) case choice when CHOICE_YES proceed = true when CHOICE_NO proceed = false end end if file_modified? proceed = ! $diakonos.revert( "File has been altered externally. Load on-disk version?" ) end if proceed save_copy name @name = name @last_modification_check = File.mtime( @name ) saved = true if @name =~ /#{$diakonos.diakonos_home}\/.*\.conf/ $diakonos.load_configuration $diakonos.initialize_display end @modified = false display $diakonos.update_status_line end end end saved end
Returns true on successful write.
# File lib/diakonos/buffer/file.rb, line 63 def save_copy( filename ) return false if filename.nil? name = File.expand_path( filename ) if @settings['save_backup_files'] begin FileUtils.cp name, name+'~', preserve: true rescue Errno::ENOENT # Do nothing if file didn't exist yet end end File.open( name, "w" ) do |f| @lines[ 0..-2 ].each do |line| if @settings[ 'strip_trailing_whitespace_on_save' ] line.rstrip! end f.puts line end line = @lines[ -1 ] if @settings[ 'strip_trailing_whitespace_on_save' ] line.rstrip! end if line != "" # No final newline character if @settings[ "eof_newline" ] line << "\n" @lines << '' end f.print line end if @settings[ 'strip_trailing_whitespace_on_save' ] if @last_col > @lines[ @last_row ].size cursor_to @last_row, @lines[ @last_row ].size end end end end
# File lib/diakonos/buffer/searching.rb, line 23 def search_area? @search_area end
@return [Array<String>]
# File lib/diakonos/buffer/searching.rb, line 57 def search_area_lines return [] if @search_area.nil? lines = @lines[@search_area.start_row..@search_area.end_row] lines[0] = lines[0][@search_area.start_col..-1] lines[-1] = lines[-1][0..@search_area.end_col] lines end
# File lib/diakonos/buffer/searching.rb, line 464 def seek( regexp, direction = :down ) return if regexp.nil? || regexp == // found_row = nil found_col = nil found_text = nil catch :found do if direction == :down # Check the current row first. index, match_text = @lines[ @last_row ].group_index( regexp, @last_col + 1 ) if index found_row = @last_row found_col = index found_text = match_text throw :found end # Check below the cursor. ( (@last_row + 1)...@lines.length ).each do |i| index, match_text = @lines[ i ].group_index( regexp ) if index found_row = i found_col = index found_text = match_text throw :found end end else # Check the current row first. col_to_check = @last_col - 1 if col_to_check >= 0 index, match_text = @lines[ @last_row ].group_rindex( regexp, col_to_check ) if index found_row = @last_row found_col = index found_text = match_text throw :found end end # Check above the cursor. (@last_row - 1).downto( 0 ) do |i| index, match_text = @lines[ i ].group_rindex( regexp ) if index found_row = i found_col = index found_text = match_text throw :found end end end end if found_text cursor_to( found_row, found_col ) display true end end
# File lib/diakonos/buffer/selection.rb, line 146 def select( from_regexp, to_regexp, include_ending = true ) start_row = nil @lines[ 0..@last_row ].reverse.each_with_index do |line,index| if line =~ from_regexp start_row = @last_row - index break end end if start_row end_row = nil @lines[ start_row..-1 ].each_with_index do |line,index| if line =~ to_regexp end_row = start_row + index break end end if end_row if include_ending end_row += 1 end anchor_selection( start_row, 0, DONT_DISPLAY ) cursor_to( end_row, 0 ) display end end end
# File lib/diakonos/buffer/selection.rb, line 83 def select_all selection_mode_normal anchor_selection( 0, 0, DONT_DISPLAY ) cursor_to( @lines.length - 1, @lines[ -1 ].length, DO_DISPLAY ) end
# File lib/diakonos/buffer/selection.rb, line 62 def select_current_line selection_mode_normal anchor_selection( @last_row, 0, DONT_DISPLAY ) if @last_row == @lines.size - 1 row = @last_row col = @lines[ @last_row ].size else row = @last_row + 1 col = 0 end cursor_to( row, col, DO_DISPLAY ) end
# File lib/diakonos/buffer/selection.rb, line 122 def select_word coords = word_under_cursor_pos( or_after: true ) if coords.nil? remove_selection else cursor_to *coords[0] anchor_selection cursor_to *coords[1] display end end
# File lib/diakonos/buffer/selection.rb, line 134 def select_word_another m = selection_mark if m.nil? select_word else row, col, _ = pos_of_next( /\w\b/, @last_row, @last_col ) if row && col cursor_to row, col+1, DO_DISPLAY end end end
# File lib/diakonos/buffer/selection.rb, line 89 def select_wrapping_block block_level = indentation_level( @last_row ) if selecting? block_level -= 1 end if block_level <= 0 return select_all end end_row = start_row = @last_row # Find block end ( @last_row...@lines.size ).each do |row| next if @lines[ row ].strip.empty? if indentation_level( row ) < block_level end_row = row break end end # Go to block beginning ( 0...@last_row ).reverse_each do |row| next if @lines[ row ].strip.empty? if indentation_level( row ) < block_level start_row = row + 1 break end end anchor_selection( end_row, 0 ) cursor_to( start_row, 0, DO_DISPLAY ) end
# File lib/diakonos/buffer/selection.rb, line 247 def selected_lines selection = selection_mark if selection if selection.end_col == 0 end_row = selection.end_row - 1 else end_row = selection.end_row end @lines[ selection.start_row..end_row ] else [ @lines[ @last_row ] ] end end
# File lib/diakonos/buffer/selection.rb, line 238 def selected_string lines = selected_text if lines lines.join( "\n" ) else nil end end
@return [Array<String>, nil]
# File lib/diakonos/buffer/selection.rb, line 219 def selected_text selection = selection_mark if selection.nil? nil elsif selection.start_row == selection.end_row [ @lines[ selection.start_row ][ selection.start_col...selection.end_col ] ] else if @selection_mode == :block @lines[ selection.start_row .. selection.end_row ].collect { |line| line[ selection.start_col ... selection.end_col ] } else [ @lines[ selection.start_row ][ selection.start_col..-1 ] ] + ( @lines[ (selection.start_row + 1) .. (selection.end_row - 1) ] || [] ) + [ @lines[ selection.end_row ][ 0...selection.end_col ] ] end end end
# File lib/diakonos/buffer/selection.rb, line 58 def selecting? !! selection_mark end
# File lib/diakonos/buffer/selection.rb, line 54 def selection_mark @text_marks[:selection] end
# File lib/diakonos/buffer/selection.rb, line 261 def selection_mode_block @selection_mode = :block end
# File lib/diakonos/buffer/selection.rb, line 264 def selection_mode_normal @selection_mode = :normal end
# File lib/diakonos/buffer/indentation.rb, line 16 def set_indent( row, level, opts = {} ) do_display = opts.fetch( :do_display, true ) undoable = opts.fetch( :undoable, true ) cursor_eol = opts.fetch( :cursor_eol, false ) @lines[ row ] =~ /^([\s#{@indent_ignore_charset}]*)(.*)$/ current_indent_text = ( $1 || "" ) rest = ( $2 || "" ) current_indent_text.gsub!( /\t/, ' ' * @tab_size ) indentation = @indent_size * [ level, 0 ].max if current_indent_text.length >= indentation indent_text = current_indent_text[ 0...indentation ] else indent_text = current_indent_text + " " * ( indentation - current_indent_text.length ) end if @settings[ "lang.#{@language}.indent.using_tabs" ] num_tabs = 0 indent_text.gsub!( / {#{@tab_size}}/ ) { |match| num_tabs += 1 "\t" } indentation -= num_tabs * ( @tab_size - 1 ) end take_snapshot( TYPING ) if do_display && undoable @lines[ row ] = indent_text + rest if do_display cursor_to( row, cursor_eol ? @lines[row].length : indentation ) end set_modified do_display end
# File lib/diakonos/buffer.rb, line 133 def set_language( language ) @settings = $diakonos.settings @language = language @surround_pairs = $diakonos.surround_pairs[ @language ] @token_regexps = $diakonos.token_regexps[ @language ] @close_token_regexps = $diakonos.close_token_regexps[ @language ] @token_formats = $diakonos.token_formats[ @language ] @indenters = $diakonos.indenters[ @language ] @indenters_next_line = $diakonos.indenters_next_line[ @language ] @unindenters = $diakonos.unindenters[ @language ] @preventers = @settings[ "lang.#{@language}.indent.preventers" ] @closers = $diakonos.closers[ @language ] || Hash.new @auto_indent = @settings[ "lang.#{@language}.indent.auto" ] @indent_size = ( @settings[ "lang.#{@language}.indent.size" ] || 4 ) @indent_roundup = @settings[ "lang.#{@language}.indent.roundup" ].nil? ? true : @settings[ "lang.#{@language}.indent.roundup" ] @indent_closers = @settings[ "lang.#{@language}.indent.closers" ].nil? ? false : @settings[ "lang.#{@language}.indent.closers" ] @default_formatting = ( @settings[ "lang.#{@language}.format.default" ] || Curses::A_NORMAL ) @selection_formatting = ( @settings[ "lang.#{@language}.format.selection" ] || Curses::A_REVERSE ) @indent_ignore_charset = ( @settings[ "lang.#{@language}.indent.ignore.charset" ] || "" ) @tab_size = ( @settings[ "lang.#{@language}.tabsize" ] || DEFAULT_TAB_SIZE ) end
# File lib/diakonos/buffer/file.rb, line 142 def set_modified( do_display = DO_DISPLAY, use_md5 = DONT_USE_MD5 ) if @read_only $diakonos.set_iline "Warning: Modifying a read-only file." end @modified = use_md5 ? file_different? : true clear_matches if do_display $diakonos.update_status_line display end end
# File lib/diakonos/buffer/searching.rb, line 33 def set_search_area(mark) if mark.nil? raise 'Call Diakonos::Buffer#clear_search_area instead' return end @search_area = mark @text_marks[ :search_area_pre ] = TextMark.new( ::Diakonos::Range.new(0, 0, mark.start_row, mark.start_col), @settings[ 'view.non_search_area.format' ] ) @text_marks[ :search_area_post ] = TextMark.new( ::Diakonos::Range.new(mark.end_row, mark.end_col, @lines.length - 1, @lines[ -1 ].length), @settings[ 'view.non_search_area.format' ] ) end
# File lib/diakonos/buffer/selection.rb, line 174 def set_selection( start_row, start_col, end_row, end_col ) @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(start_row, start_col, end_row, end_col), @selection_formatting ) @changing_selection = false end
# File lib/diakonos/buffer/selection.rb, line 75 def set_selection_current_line @text_marks[ :selection ] = TextMark.new( ::Diakonos::Range.new(@last_row, 0, @last_row, @lines[ @last_row ].size), @selection_formatting ) @lines[ @last_row ] end
# File lib/diakonos/buffer.rb, line 600 def set_type( type ) return false if type.nil? configure( type ) display true end
col and row are given relative to the buffer, not any window or screen. Returns true if the view changed positions.
# File lib/diakonos/buffer/cursor.rb, line 156 def show_character( row, col ) old_top_line = @top_line old_left_column = @left_column while row < @top_line + @settings[ "view.margin.y" ] amount = (-1) * @settings[ "view.jump.y" ] break if( pitch_view( amount, DONT_PITCH_CURSOR, DONT_DISPLAY ) != amount ) end while row > @top_line + $diakonos.main_window_height - 1 - @settings[ "view.margin.y" ] amount = @settings[ "view.jump.y" ] break if( pitch_view( amount, DONT_PITCH_CURSOR, DONT_DISPLAY ) != amount ) end while col < @left_column + @settings[ "view.margin.x" ] amount = (-1) * @settings[ "view.jump.x" ] break if( pan_view( amount, DONT_DISPLAY ) != amount ) end while col > @left_column + $diakonos.main_window_width - @settings[ "view.margin.x" ] - 2 amount = @settings[ "view.jump.x" ] break if( pan_view( amount, DONT_DISPLAY ) != amount ) end @top_line != old_top_line || @left_column != old_left_column end
# File lib/diakonos/buffer.rb, line 206 def surround( text, parenthesis ) pattern, pair = @surround_pairs.select { |r, p| parenthesis =~ r }.to_a[ 0 ] if pair.nil? $diakonos.set_iline "No matching parentheses pair found." nil else pair.map! do |paren| parenthesis.gsub( pattern, paren ) end pair[ 0 ] + text.join( "\n" ) + pair[ 1 ] end end
# File lib/diakonos/buffer/indentation.rb, line 5 def tab_expanded_column( col, row ) delta = 0 line = @lines[ row ] for i in 0...col if line[ i ] == "\t" delta += ( @tab_size - ( (i+delta) % @tab_size ) ) - 1 end end col + delta end
# File lib/diakonos/buffer/undo.rb, line 8 def take_snapshot( typing = false ) do_it = false if ! @modified && file_modified? && file_different? return if $diakonos.revert( "File has been altered externally. Load on-disk version?" ) end if @typing != typing @typing = typing # If we just started typing, take a snapshot, but don't continue # taking snapshots for every keystroke if typing do_it = true end end if ! @typing do_it = true end if do_it undo_size = 0 @buffer_states[ 1..-1 ].each do |state| undo_size += state.length end while ( ( undo_size + @lines.length ) >= @settings[ "max_undo_lines" ] ) && @buffer_states.length > 1 @cursor_states.pop popped_state = @buffer_states.pop undo_size = undo_size - popped_state.length end if @current_buffer_state > 0 @buffer_states.unshift @lines.deep_clone @cursor_states.unshift [ @last_row, @last_col ] end @buffer_states.unshift @lines.deep_clone @cursor_states.unshift [ @last_row, @last_col ] @current_buffer_state = 0 @lines = @buffer_states[ @current_buffer_state ] end end
# File lib/diakonos/buffer.rb, line 168 def to_a @lines.dup end
# File lib/diakonos/buffer/bookmarking.rb, line 30 def toggle_bookmark bookmark = Bookmark.new( self, @last_row, @last_col ) existing = @bookmarks.find do |bm| bm == bookmark end if existing @bookmarks.delete existing $diakonos.set_iline "Bookmark #{existing.to_s} deleted." else @bookmarks.push bookmark @bookmarks.sort $diakonos.set_iline "Bookmark #{bookmark.to_s} set." end end
# File lib/diakonos/buffer/selection.rb, line 206 def toggle_selection if @changing_selection remove_selection else anchor_selection end end
Prints text to the screen, truncating where necessary. Returns nil if the string is completely off-screen. write_cursor_col is buffer-relative, not screen-relative
# File lib/diakonos/buffer/display.rb, line 64 def truncate_off_screen( string, write_cursor_col ) retval = string # Truncate based on left edge of display area if write_cursor_col < @left_column retval = retval[ (@left_column - write_cursor_col)..-1 ] write_cursor_col = @left_column end if retval # Truncate based on right edge of display area if write_cursor_col + retval.length > @left_column + Curses::cols - 1 new_length = ( @left_column + Curses::cols - write_cursor_col ) if new_length <= 0 retval = nil else retval = retval[ 0...new_length ] end end end retval == "" ? nil : retval end
# File lib/diakonos/buffer.rb, line 366 def uncomment take_snapshot comment_string = Regexp.escape( @settings[ "lang.#{@language}.comment_string" ].to_s ) comment_close_string = Regexp.escape( @settings[ "lang.#{@language}.comment_close_string" ].to_s ) one_modified = false self.selected_lines.each do |line| old_line = line.dup comment_regexp = /^(\s*)#{comment_string}/ line.gsub!( comment_regexp, "\\1" ) if line !~ comment_regexp line.gsub!( /#{comment_close_string}$/, '' ) end one_modified ||= ( line != old_line ) end if one_modified set_modified end end
# File lib/diakonos/buffer/undo.rb, line 50 def undo return if @current_buffer_state >= @buffer_states.length - 1 @current_buffer_state += 1 @lines = @buffer_states[ @current_buffer_state ] cursor_to( @cursor_states[ @current_buffer_state - 1 ][ 0 ], @cursor_states[ @current_buffer_state - 1 ][ 1 ] ) $diakonos.set_iline "Undo level: #{@current_buffer_state} of #{@buffer_states.length - 1}" set_modified DO_DISPLAY, DO_USE_MD5 end
# File lib/diakonos/buffer/indentation.rb, line 168 def unindent( row = @last_row, do_display = DO_DISPLAY ) level = indentation_level( row, DONT_USE_INDENT_IGNORE ) set_indent row, level - 1, do_display: do_display end
Since redo is a Ruby keyword…
# File lib/diakonos/buffer/undo.rb, line 61 def unundo return if @current_buffer_state <= 0 @current_buffer_state += -1 @lines = @buffer_states[ @current_buffer_state ] cursor_to( @cursor_states[ @current_buffer_state ][ 0 ], @cursor_states[ @current_buffer_state ][ 1 ] ) $diakonos.set_iline "Undo level: #{@current_buffer_state} of #{@buffer_states.length - 1}" set_modified DO_DISPLAY, DO_USE_MD5 end
# File lib/diakonos/buffer.rb, line 634 def word_before_cursor word = nil @lines[ @last_row ].scan( WORD_REGEXP ) do |match_text| last_match = Regexp.last_match if last_match.begin( 0 ) <= @last_col && @last_col <= last_match.end( 0 ) word = match_text break end end word end
# File lib/diakonos/buffer.rb, line 607 def word_under_cursor pos = word_under_cursor_pos return if pos.nil? col1 = pos[ 0 ][ 1 ] col2 = pos[ 1 ][ 1 ] @lines[ @last_row ][ col1...col2 ] end
# File lib/diakonos/buffer.rb, line 616 def word_under_cursor_pos( options = {} ) or_after = options[:or_after] @lines[ @last_row ].scan( WORD_REGEXP ) do |match_text| last_match = Regexp.last_match if ( last_match.begin( 0 ) <= @last_col && @last_col < last_match.end( 0 ) || or_after && last_match.begin(0) > @last_col ) return [ [ @last_row, last_match.begin( 0 ) ], [ @last_row, last_match.end( 0 ) ], ] end end nil end
TODO paragraph_before_cursor(_pos)?
# File lib/diakonos/buffer.rb, line 691 def words( filter_regexp = nil ) w = @lines.join( ' ' ).scan( WORD_REGEXP ) filter_regexp ? w.grep( filter_regexp ) : w end
# File lib/diakonos/buffer.rb, line 541 def wrap_paragraph start_row = end_row = cursor_row = @last_row cursor_col = @last_col until start_row == 0 || @lines[ start_row - 1 ].strip == '' start_row -= 1 end until end_row == @lines.size || @lines[ end_row ].strip == '' end_row += 1 end lines = [] line = '' words = @lines[ start_row...end_row ].join( ' ' ).scan( /\S+/ ) words.each do |word| if word =~ /^[a-z']+[.!?]$/ word = "#{word} " end if line.length + word.length + 1 > ( @settings[ "lang.#{@language}.wrap_margin" ] || 80 ) lines << line.strip line = '' end line << " #{word}" end line.strip! if ! line.empty? lines << line end if @lines[ start_row...end_row ] != lines take_snapshot @lines[ start_row...end_row ] = lines set_modified cursor_to start_row + lines.length, lines[-1].length end end
Private Instance Methods
# File lib/diakonos/buffer/display.rb, line 38 def find_closing_match(line_segment, regexp, bos_allowed = true) close_match_text = nil close_index = nil line_segment.scan(regexp) do |m| match = Regexp.last_match if match.length > 1 index = match.begin 1 match_text = match[1] else index = match.begin 0 match_text = match[0] end if ( ! regexp.uses_bos ) || ( bos_allowed && ( index == 0 ) ) close_index = index close_match_text = match_text break end end [close_index, close_match_text] end