class Textbringer::RubyMode

Constants

BLOCK_END
INDENT_BEG_RE

Public Class Methods

new(buffer) click to toggle source
Calls superclass method Textbringer::ProgrammingMode::new
# File lib/textbringer/modes/ruby_mode.rb, line 92
def initialize(buffer)
  super(buffer)
  @buffer[:indent_level] = CONFIG[:ruby_indent_level]
  @buffer[:indent_tabs_mode] = CONFIG[:ruby_indent_tabs_mode]
end

Public Instance Methods

backward_definition(n = number_prefix_arg || 1) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 119
def backward_definition(n = number_prefix_arg || 1)
  tokens = Ripper.lex(@buffer.to_s).reverse
  @buffer.beginning_of_line
  n.times do |i|
    tokens = tokens.drop_while { |(l, _), e, t|
      l >= @buffer.current_line ||
        e != :on_kw || /\A(?:class|module|def)\z/ !~ t
    }
    (line,), = tokens.first
    if line.nil?
      @buffer.beginning_of_buffer
      break
    end
    @buffer.goto_line(line)
    tokens = tokens.drop(1)
  end
  while /\s/ =~ @buffer.char_after
    @buffer.forward_char
  end
end
comment_start() click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 88
def comment_start
  "#"
end
compile(cmd = read_from_minibuffer("Compile: ", default: default_compile_command)) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 140
def compile(cmd = read_from_minibuffer("Compile: ",
                                       default: default_compile_command))
  shell_execute(cmd, buffer_name: "*Ruby compile result*",
                mode: BacktraceMode)
end
default_compile_command() click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 150
def default_compile_command
  @buffer[:ruby_compile_command] ||
    if File.exist?("Rakefile")
      prefix = File.exist?("Gemfile") ? "bundle exec " : ""
      prefix + "rake"
    elsif @buffer.file_name
      ruby_install_name + " " + @buffer.file_name
    else
      nil
    end
end
forward_definition(n = number_prefix_arg || 1) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 98
def forward_definition(n = number_prefix_arg || 1)
  tokens = Ripper.lex(@buffer.to_s)
  @buffer.forward_line
  n.times do |i|
    tokens = tokens.drop_while { |(l, _), e, t|
      l < @buffer.current_line ||
        e != :on_kw || /\A(?:class|module|def)\z/ !~ t
    }
    (line,), = tokens.first
    if line.nil?
      @buffer.end_of_buffer
      break
    end
    @buffer.goto_line(line)
    tokens = tokens.drop(1)
  end
  while /\s/ =~ @buffer.char_after
    @buffer.forward_char
  end
end
symbol_pattern() click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 146
def symbol_pattern
  /[\p{Letter}\p{Number}_$@!?]/
end
toggle_test() click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 162
def toggle_test
  case @buffer.file_name
  when %r'(.*)/test/(.*/)?test_(.*?)\.rb\z'
    path = find_test_target_path($1, $2, $3)
    find_file(path)
  when %r'(.*)/spec/(.*/)?(.*?)_spec\.rb\z'
    path = find_test_target_path($1, $2, $3)
    find_file(path)
  when %r'(.*)/(?:lib|app)/(.*/)?(.*?)\.rb\z'
    path = find_test_path($1, $2, $3)
    find_file(path)
  else
    raise EditorError, "Unknown file type"
  end
end

Private Instance Methods

beginning_of_indentation() click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 186
def beginning_of_indentation
  loop do
    @buffer.re_search_backward(INDENT_BEG_RE)
    space = @buffer.match_string(1)
    s = @buffer.substring(@buffer.point_min, @buffer.point)
    if PartialLiteralAnalyzer.in_literal?(s)
      next
    end
    return space_width(space)
  end
rescue SearchError
  @buffer.beginning_of_buffer
  0
end
calculate_indentation() click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 217
def calculate_indentation
  if @buffer.current_line == 1
    return 0
  end
  @buffer.save_excursion do
    @buffer.beginning_of_line
    start_with_period = @buffer.looking_at?(/[ \t]*\./)
    bol_pos = @buffer.point
    base_indentation = beginning_of_indentation
    start_pos = @buffer.point
    start_line = @buffer.current_line
    tokens = lex(@buffer.substring(start_pos, bol_pos))
    _, event, text = tokens.last
    if event == :on_nl
      _, event, text = tokens[-2]
    end
    if event == :on_tstring_beg ||
        event == :on_heredoc_beg ||
        event == :on_regexp_beg ||
        (event == :on_regexp_end && text.size > 1) ||
        event == :on_tstring_content
      return nil
    end
    i, extra_end_count = find_nearest_beginning_token(tokens)
    (line, column), event, = i ? tokens[i] : nil
    if event == :on_lparen && tokens.dig(i + 1, 1) != :on_ignored_nl
      return column + 1
    end
    if line
      @buffer.goto_line(start_line - 1 + line)
      while !@buffer.beginning_of_buffer?
        if @buffer.save_excursion {
          @buffer.backward_char
          @buffer.skip_re_backward(/\s/)
          @buffer.char_before == ?,
        }
          @buffer.backward_line
        else
          break
        end
      end
      @buffer.looking_at?(/[ \t]*/)
      base_indentation = space_width(@buffer.match_string(0))
    end
    @buffer.goto_char(bol_pos)
    if line.nil?
      indentation =
        base_indentation - extra_end_count * @buffer[:indent_level]
    else
      indentation = base_indentation + @buffer[:indent_level]
    end
    if @buffer.looking_at?(/[ \t]*([}\])]|(end|else|elsif|when|in|rescue|ensure)\b)/)
      indentation -= @buffer[:indent_level]
    end
    _, last_event, last_text = tokens.reverse_each.find { |_, e, _|
      e != :on_sp && e != :on_nl && e != :on_ignored_nl
    }
    if start_with_period ||
        (last_event == :on_op && last_text != "|") ||
        (last_event == :on_kw && /\A(and|or)\z/.match?(last_text)) ||
        last_event == :on_period ||
        (last_event == :on_comma && event != :on_lbrace &&
         event != :on_lparen && event != :on_lbracket)
      indentation += @buffer[:indent_level]
    end
    indentation
  end
end
endless_method_def?(tokens, i) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 343
def endless_method_def?(tokens, i)
  ts = tokens.drop(i + 1)
  ts.shift while ts[0][1] == :on_sp
  _, event = ts.shift
  return false if event != :on_ident
  ts.shift while ts[0][1] == :on_sp
  if ts[0][1] == :on_lparen
    ts.shift
    count = 1
    while count > 0
      _, event = ts.shift
      return false if event.nil?
      case event
      when :on_lparen
        count +=1
      when :on_rparen
        count -=1
      end
    end
    ts.shift while ts[0][1] == :on_sp
  end
  ts[0][1] == :on_op && ts[0][2] == "="
rescue NoMethodError # no token
  return false
end
find_first_path(patterns) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 389
def find_first_path(patterns)
  patterns.each do |pattern|
    paths = Dir.glob(pattern)
    return paths.first if !paths.empty?
  end
  nil
end
find_nearest_beginning_token(tokens) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 293
def find_nearest_beginning_token(tokens)
  stack = []
  (tokens.size - 1).downto(0) do |i|
    (line, ), event, text = tokens[i]
    case event
    when :on_kw
      _, prev_event, _ = tokens[i - 1]
      next if prev_event == :on_symbeg
      case text
      when "class", "module", "def", "if", "unless", "case",
        "do", "for", "while", "until", "begin"
        if /\A(if|unless|while|until)\z/.match?(text) &&
            modifier?(tokens, i)
          next
        end
        if text == "def" && endless_method_def?(tokens, i)
          next
        end
        if stack.empty?
          return i
        end
        if stack.last != "end"
          raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
        end
        stack.pop
      when "end"
        stack.push(text)
      end
    when :on_rbrace, :on_rparen, :on_rbracket, :on_embexpr_end
      stack.push(text)
    when :on_lbrace, :on_lparen, :on_lbracket, :on_tlambeg, :on_embexpr_beg
      if stack.empty?
        return i
      end
      if stack.last != BLOCK_END[text]
        raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
      end
      stack.pop
    end
  end
  return nil, stack.grep_v(/[)\]]/).size
end
find_test_path(base, namespace, name) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 378
def find_test_path(base, namespace, name)
  patterns = []
  if namespace
    patterns.push("#{base}/test/**/#{namespace}test_#{name}.rb")
    patterns.push("#{base}/spec/**/#{namespace}#{name}_spec.rb")
  end
  patterns.push("#{base}/test/**/test_#{name}.rb")
  patterns.push("#{base}/spec/**/#{name}_spec.rb")
  find_first_path(patterns) or raise EditorError, "Test not found"
end
find_test_target_path(base, namespace, name) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 369
def find_test_target_path(base, namespace, name)
  patterns = []
  if namespace
    patterns.push("#{base}/{lib,app}/**/#{namespace}#{name}.rb")
  end
  patterns.push("#{base}/{lib,app}/**/#{name}.rb")
  find_first_path(patterns) or raise EditorError, "Test target not found"
end
lex(source) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 201
def lex(source)
  line_count = source.count("\n")
  s = source
  lineno = 1
  tokens = []
  loop do
    lexer = Ripper::Lexer.new(s, "-", lineno)
    tokens.concat(lexer.lex)
    last_line = tokens.dig(-1, 0, 0)
    return tokens if last_line.nil? || last_line >= line_count
    s = source.sub(/(.*\n?){#{last_line}}/, "")
    return tokens if last_line + 1 <= lineno
    lineno = last_line + 1
  end
end
modifier?(tokens, i) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 336
def modifier?(tokens, i)
  (line,), = tokens[i]
  ts = tokens[0...i].reverse_each.take_while { |(l,_),| l == line }
  t = ts.find { |_, e| e != :on_sp }
  t && !(t[1] == :on_op && t[2] == "=")
end
space_width(s) click to toggle source
# File lib/textbringer/modes/ruby_mode.rb, line 182
def space_width(s)
  s.gsub(/\t/, " " * @buffer[:tab_width]).size
end