Class: Textbringer::RubyMode

Inherits:
ProgrammingMode show all
Defined in:
lib/textbringer/modes/ruby_mode.rb,
sig/lib/textbringer/mode.rbs,
sig/lib/textbringer/modes/ruby_mode.rbs

Defined Under Namespace

Classes: PartialLiteralAnalyzer

Constant Summary

Constants inherited from Mode

Mode::DEFAULT_SYNTAX_TABLE

Constants included from Commands

Commands::CLIPBOARD_AVAILABLE, Commands::COMPLETION_POPUP_STATUS, Commands::CTAGS, Commands::EMAIL_REGEXP, Commands::HELP_RING, Commands::ISEARCH_STATUS, Commands::ISPELL_STATUS, Commands::ISPELL_WORD_REGEXP, Commands::KEYBOARD_MACROS, Commands::LSP_DOCUMENT_VERSIONS, Commands::LSP_STATUS, Commands::REGISTERS, Commands::RE_SEARCH_STATUS, Commands::URI_REGEXP

Constants included from Utils

Utils::COMPLETION, Utils::EXPRESSION_COMPLETOR, Utils::EXPRESSION_COMPLETOR_OPTIONS, Utils::HOOKS

Instance Attribute Summary

Attributes inherited from Mode

#buffer

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ProgrammingMode

define_generic_command, #indent_line, #indent_new_comment_line, #indent_region, inherited, #reindent_then_newline_and_indent

Methods inherited from FundamentalMode

inherited

Methods inherited from Mode

define_generic_command, define_local_command, inherited, list, #name, #syntax_table

Methods included from Commands

[], #buffer_uri, #command_help, command_table, #completion_popup_done, completion_popup_mode_active?, #completion_popup_pre_command_hook, #completion_popup_start, #current_prefix_arg, define_command, #ensure_ispell_active, #execute_keyboard_macro, #get_tags, #insert_completion, #isearch_done, #isearch_mode, #isearch_mode?, #isearch_pre_command_hook, #isearch_prompt, #isearch_repeat, #isearch_repeat_backward, #isearch_repeat_forward, #isearch_search, #ispell_done, #ispell_forward, #ispell_mode, #keymap_bindings, list, #lsp_after_set_visited_file_name_hook, #lsp_close_signature_window, #lsp_completion_context, #lsp_find_file_hook, #lsp_open_document, #lsp_position, #lsp_setup_buffer_hooks, #lsp_show_signature_window, #lsp_signature_pre_command_hook, #lsp_text_document_sync_kind, #lsp_utf16_length, #match_beginning, #match_end, #match_string, #message_misspelled, #number_prefix_arg, #prefix_numeric_value, #read_input_method_name, #read_keyboard_macro, #read_register, #read_theme_name, #replace_match, undefine_command, #universal_argument_mode

Methods included from Utils

add_hook, background, complete_for_minibuffer, delete_completions_window, foreground, foreground!, get_hooks, message, read_buffer, read_char, read_command_name, read_encoding, read_event, read_expression, read_file_name, read_from_minibuffer, read_key_sequence, read_object, read_single_char, received_keyboard_quit?, remove_hook, ruby_install_name, run_hooks, run_hooks_in, self_insert_and_exit_minibuffer, set_transient_map, show_exception, sit_for, sleep_for, with_clean_env, y_or_n?, yes_or_no?

Constructor Details

#initialize(buffer) ⇒ RubyMode

Returns a new instance of RubyMode.

Parameters:



18
19
20
21
22
23
24
25
26
27
28
# File 'lib/textbringer/modes/ruby_mode.rb', line 18

def initialize(buffer)
  super(buffer)
  @buffer[:indent_level] = CONFIG[:ruby_indent_level]
  @buffer[:indent_tabs_mode] = CONFIG[:ruby_indent_tabs_mode]
  @prism_version = nil
  @prism_tokens = nil
  @prism_ast = nil
  @prism_method_name_locs = nil
  @literal_levels = nil
  @literal_levels_version = nil
end

Class Method Details

.define_syntaxRegexp

Parameters:

  • (Symbol)
  • (Regexp)

Returns:

  • (Regexp)


24
# File 'sig/lib/textbringer/mode.rbs', line 24

def self.define_syntax: (Symbol, Regexp) -> Regexp

Instance Method Details

#backward_definition(n = number_prefix_arg || 1) ⇒ nil

Parameters:

  • (Integer)

Returns:

  • (nil)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/textbringer/modes/ruby_mode.rb', line 51

def backward_definition(n = number_prefix_arg || 1)
  tokens = filter_prism_tokens_line_and_type.reverse
  @buffer.beginning_of_line
  n.times do |i|
    tokens = tokens.drop_while { |l, type|
      l >= @buffer.current_line ||
        !DEFINITION_KEYWORDS.include?(type)
    }
    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

#beginning_of_indentationInteger

Returns:

  • (Integer)


277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/textbringer/modes/ruby_mode.rb', line 277

def beginning_of_indentation
  loop do
    @buffer.re_search_backward(INDENT_BEG_RE)
    space = @buffer.match_string(1)
    if in_literal?(@buffer.point)
      next
    end
    return space_width(space)
  end
rescue SearchError
  @buffer.beginning_of_buffer
  0
end

#calculate_indentationInteger #calculate_indentationnil

Overloads:

  • #calculate_indentationInteger

    Returns:

    • (Integer)
  • #calculate_indentationnil

    Returns:

    • (nil)


300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/textbringer/modes/ruby_mode.rb', line 300

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 == :NEWLINE || event == :IGNORED_NEWLINE
      _, event, text = tokens[-2]
    end
    if event == :STRING_BEGIN ||
        event == :HEREDOC_START ||
        (event == :HEREDOC_END && text.empty?) ||
        event == :REGEXP_BEGIN ||
        event == :STRING_CONTENT ||
        event == :HEREDOC_CONTENT
      return nil
    end
    i, extra_end_count = find_nearest_beginning_token(tokens)
    (line, column), event, = i ? tokens[i] : nil
    if event == :PARENTHESIS_LEFT && tokens.dig(i + 1, 1) != :IGNORED_NEWLINE
      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, = tokens.reverse_each.find { |_, e, _|
      e != :NEWLINE && e != :IGNORED_NEWLINE
    }
    if start_with_period ||
        CONTINUATION_OPERATOR_TYPES.include?(last_event) ||
        last_event == :KEYWORD_AND || last_event == :KEYWORD_OR ||
        last_event == :DOT ||
        (last_event == :COMMA && event != :BRACE_LEFT &&
         event != :PARENTHESIS_LEFT && event != :BRACKET_LEFT &&
         event != :BRACKET_LEFT_ARRAY) ||
        last_event == :LABEL
      indentation += @buffer[:indent_level]
    end
    indentation
  end
end

#comment_startString

Returns:

  • (String)


14
15
16
# File 'lib/textbringer/modes/ruby_mode.rb', line 14

def comment_start
  "#"
end

#compile(cmd = read_from_minibuffer("Compile: ", default: default_compile_command)) ⇒ nil

Parameters:

  • (String)

Returns:

  • (nil)


72
73
74
75
76
# File 'lib/textbringer/modes/ruby_mode.rb', line 72

def compile(cmd = read_from_minibuffer("Compile: ",
                                       default: default_compile_command))
  shell_execute(cmd, buffer_name: "*Ruby compile result*",
                mode: BacktraceMode)
end

#default_compile_commandnil #default_compile_commandString

Overloads:

  • #default_compile_commandnil

    Returns:

    • (nil)
  • #default_compile_commandString

    Returns:

    • (String)


82
83
84
85
86
87
88
89
90
91
92
# File 'lib/textbringer/modes/ruby_mode.rb', line 82

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

#endless_method_def?(tokens, i) ⇒ Boolean

Parameters:

  • (Array[untyped])
  • (Integer)

Returns:

  • (Boolean)


417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/textbringer/modes/ruby_mode.rb', line 417

def endless_method_def?(tokens, i)
  ts = tokens.drop(i + 1)
  _, event = ts.shift
  return false if event != :IDENTIFIER && event != :METHOD_NAME
  if ts[0][1] == :PARENTHESIS_LEFT
    ts.shift
    count = 1
    while count > 0
      _, event = ts.shift
      return false if event.nil?
      case event
      when :PARENTHESIS_LEFT
        count += 1
      when :PARENTHESIS_RIGHT
        count -= 1
      end
    end
  end
  ts[0][1] == :EQUAL
rescue NoMethodError # no token
  return false
end

#find_first_path(arg0) ⇒ String #find_first_path(arg0) ⇒ nil

Overloads:

  • #find_first_path(arg0) ⇒ String

    Parameters:

    • arg0 (Array[untyped])

    Returns:

    • (String)
  • #find_first_path(arg0) ⇒ nil

    Parameters:

    • arg0 (Array[untyped])

    Returns:

    • (nil)


460
461
462
463
464
465
466
# File 'lib/textbringer/modes/ruby_mode.rb', line 460

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(arg0) ⇒ Array[untyped] #find_nearest_beginning_token(arg0) ⇒ Integer #find_nearest_beginning_token(arg0) ⇒ nil

Overloads:

  • #find_nearest_beginning_token(arg0) ⇒ Array[untyped]

    Parameters:

    • arg0 (Array[untyped])

    Returns:

    • (Array[untyped])
  • #find_nearest_beginning_token(arg0) ⇒ Integer

    Parameters:

    • arg0 (Array[untyped])

    Returns:

    • (Integer)
  • #find_nearest_beginning_token(arg0) ⇒ nil

    Parameters:

    • arg0 (Array[untyped])

    Returns:

    • (nil)


372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/textbringer/modes/ruby_mode.rb', line 372

def find_nearest_beginning_token(tokens)
  stack = []
  (tokens.size - 1).downto(0) do |i|
    (line, ), event, text = tokens[i]
    case event
    when :KEYWORD_CLASS, :KEYWORD_MODULE, :KEYWORD_DEF,
      :KEYWORD_IF, :KEYWORD_UNLESS, :KEYWORD_CASE,
      :KEYWORD_DO, :KEYWORD_DO_LOOP, :KEYWORD_FOR,
      :KEYWORD_WHILE, :KEYWORD_UNTIL, :KEYWORD_BEGIN
      if i > 0
        _, prev_event, _ = tokens[i - 1]
        next if prev_event == :SYMBOL_BEGIN
      end
      if event == :KEYWORD_DEF && endless_method_def?(tokens, i)
        next
      end
      if stack.empty?
        return i
      end
      if stack.last != :KEYWORD_END
        raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
      end
      stack.pop
    when :KEYWORD_END
      if i > 0
        _, prev_event, _ = tokens[i - 1]
        next if prev_event == :SYMBOL_BEGIN
      end
      stack.push(:KEYWORD_END)
    when :BRACE_RIGHT, :PARENTHESIS_RIGHT, :BRACKET_RIGHT, :EMBEXPR_END
      stack.push(event)
    when :BRACE_LEFT, :PARENTHESIS_LEFT, :BRACKET_LEFT,
      :BRACKET_LEFT_ARRAY, :LAMBDA_BEGIN, :EMBEXPR_BEGIN
      if stack.empty?
        return i
      end
      if stack.last != BLOCK_END[event]
        raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
      end
      stack.pop
    end
  end
  return nil, stack.count { |t| t != :PARENTHESIS_RIGHT && t != :BRACKET_RIGHT }
end

#find_test_path(arg0, arg1, arg2) ⇒ String #find_test_path(arg0, arg1, arg2) ⇒ nil

Overloads:

  • #find_test_path(arg0, arg1, arg2) ⇒ String

    Parameters:

    • arg0 (String)
    • arg1 (String)
    • arg2 (String)

    Returns:

    • (String)
  • #find_test_path(arg0, arg1, arg2) ⇒ nil

    Parameters:

    • arg0 (String)
    • arg1 (String)
    • arg2 (String)

    Returns:

    • (nil)


449
450
451
452
453
454
455
456
457
458
# File 'lib/textbringer/modes/ruby_mode.rb', line 449

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(arg0, arg1, arg2) ⇒ String #find_test_target_path(arg0, arg1, arg2) ⇒ String #find_test_target_path(arg0, arg1, arg2) ⇒ nil

Overloads:

  • #find_test_target_path(arg0, arg1, arg2) ⇒ String

    Parameters:

    • arg0 (String)
    • arg1 (String)
    • arg2 (String)

    Returns:

    • (String)
  • #find_test_target_path(arg0, arg1, arg2) ⇒ String

    Parameters:

    • arg0 (String)
    • arg1 (nil)
    • arg2 (String)

    Returns:

    • (String)
  • #find_test_target_path(arg0, arg1, arg2) ⇒ nil

    Parameters:

    • arg0 (String)
    • arg1 (String)
    • arg2 (String)

    Returns:

    • (nil)


440
441
442
443
444
445
446
447
# File 'lib/textbringer/modes/ruby_mode.rb', line 440

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

#forward_definition(n = number_prefix_arg || 1) ⇒ nil

Parameters:

  • (Integer)

Returns:

  • (nil)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/textbringer/modes/ruby_mode.rb', line 30

def forward_definition(n = number_prefix_arg || 1)
  tokens = filter_prism_tokens_line_and_type
  @buffer.forward_line
  n.times do |i|
    tokens = tokens.drop_while { |l, type|
      l < @buffer.current_line ||
        !DEFINITION_KEYWORDS.include?(type)
    }
    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

#highlight(ctx) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/textbringer/modes/ruby_mode.rb', line 110

def highlight(ctx)
  ensure_prism_tokens
  return unless @prism_tokens
  ensure_method_name_locs
  base_pos = ctx.buffer.point_min
  hl_start = ctx.highlight_start
  hl_end = ctx.highlight_end
  in_symbol = false
  after_class_or_module = false
  @prism_tokens.each do |token_info|
    token = token_info[0]
    type = token.type
    offset = token.location.start_offset
    length = token.location.length
    pos = base_pos + offset
    pos_end = pos + length
    break if pos >= hl_end
    if pos_end > hl_start
      face_name = PRISM_TOKEN_FACES[type]
      if in_symbol
        face_name = :string if face_name.nil? || face_name == :constant ||
          face_name == :keyword || face_name == :operator
      elsif @prism_method_name_locs.key?(offset)
        face_name = :function_name
      elsif face_name == :constant &&
          (after_class_or_module || token.location.slice.match?(/\p{Lower}/))
        face_name = :type
      end
      if face_name && (face = Face[face_name])
        ctx.highlight(pos, pos_end, face)
      end
    end
    in_symbol = type == :SYMBOL_BEGIN
    after_class_or_module = (type == :KEYWORD_CLASS || type == :KEYWORD_MODULE) ||
      (after_class_or_module && !(type == :NEWLINE || type == :SEMICOLON))
  end
end

#lex(source) ⇒ Array[untyped]

Parameters:

  • (String)

Returns:

  • (Array[untyped])


291
292
293
294
295
296
297
298
# File 'lib/textbringer/modes/ruby_mode.rb', line 291

def lex(source)
  Prism.lex(source).value.filter_map { |token, _state|
    type = token.type
    next if type == :EOF
    loc = token.location
    [[loc.start_line, loc.start_column], type, token.value]
  }
end

#modifier?(arg0, arg1) ⇒ nil #modifier?(arg0, arg1) ⇒ Boolean

Overloads:

  • #modifier?(arg0, arg1) ⇒ nil

    Parameters:

    • arg0 (Array[untyped])
    • arg1 (Integer)

    Returns:

    • (nil)
  • #modifier?(arg0, arg1) ⇒ Boolean

    Parameters:

    • arg0 (Array[untyped])
    • arg1 (Integer)

    Returns:

    • (Boolean)


30
31
# File 'sig/lib/textbringer/modes/ruby_mode.rbs', line 30

def modifier?: (Array[untyped], Integer) -> nil
| (Array[untyped], Integer) -> bool

#space_width(s) ⇒ Integer

Parameters:

  • (String)

Returns:

  • (Integer)


273
274
275
# File 'lib/textbringer/modes/ruby_mode.rb', line 273

def space_width(s)
  s.gsub(/\t/, " " * @buffer[:tab_width]).size
end

#symbol_patternRegexp

Returns:

  • (Regexp)


78
79
80
# File 'lib/textbringer/modes/ruby_mode.rb', line 78

def symbol_pattern
  /[\p{Letter}\p{Number}_$@!?]/
end

#toggle_testArray[untyped] #toggle_testnil

Overloads:

  • #toggle_testArray[untyped]

    Returns:

    • (Array[untyped])
  • #toggle_testnil

    Returns:

    • (nil)


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/textbringer/modes/ruby_mode.rb', line 94

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