Module: RuVim::Lang::Ruby

Defined in:
lib/ruvim/lang/ruby.rb

Constant Summary collapse

PRISM_KEYWORD_TYPES =
%i[
  KEYWORD_ALIAS
  KEYWORD_AND
  KEYWORD_BEGIN
  KEYWORD_BEGIN_UPCASE
  KEYWORD_BREAK
  KEYWORD_CASE
  KEYWORD_CLASS
  KEYWORD_DEF
  KEYWORD_DEFINED
  KEYWORD_DO
  KEYWORD_ELSE
  KEYWORD_ELSIF
  KEYWORD_END
  KEYWORD_ENSURE
  KEYWORD_FALSE
  KEYWORD_FOR
  KEYWORD_IF
  KEYWORD_IF_MODIFIER
  KEYWORD_IN
  KEYWORD_MODULE
  KEYWORD_NEXT
  KEYWORD_NIL
  KEYWORD_NOT
  KEYWORD_OR
  KEYWORD_REDO
  KEYWORD_RESCUE
  KEYWORD_RESCUE_MODIFIER
  KEYWORD_RETRY
  KEYWORD_RETURN
  KEYWORD_SELF
  KEYWORD_SUPER
  KEYWORD_THEN
  KEYWORD_TRUE
  KEYWORD_UNDEF
  KEYWORD_UNLESS
  KEYWORD_UNLESS_MODIFIER
  KEYWORD_UNTIL
  KEYWORD_UNTIL_MODIFIER
  KEYWORD_WHEN
  KEYWORD_WHILE
  KEYWORD_WHILE_MODIFIER
  KEYWORD_YIELD
  MISSING
].freeze
PRISM_STRING_TYPES =
%i[
  STRING_BEGIN
  STRING_CONTENT
  STRING_END
  SYMBOL_BEGIN
  REGEXP_BEGIN
  REGEXP_CONTENT
  REGEXP_END
  XSTRING_BEGIN
  XSTRING_CONTENT
  XSTRING_END
  WORDS_BEGIN
  QWORDS_BEGIN
  WORDS_SEPARATOR
  STRING
  CHARACTER_LITERAL
].freeze
PRISM_NUMBER_TYPES =
%i[
  INTEGER
  FLOAT
  RATIONAL_NUMBER
  IMAGINARY_NUMBER
  UINTEGER
].freeze
PRISM_COMMENT_TYPES =
%i[
  COMMENT
  EMBDOC_BEGIN
  EMBDOC_LINE
  EMBDOC_END
].freeze
PRISM_VARIABLE_TYPES =
%i[
  INSTANCE_VARIABLE
  CLASS_VARIABLE
  GLOBAL_VARIABLE
].freeze
PRISM_CONSTANT_TYPES =
%i[
  CONSTANT
].freeze
INDENT_OPEN_RE =

Keywords that open a new indentation level

/\A\s*(?:def|class|module|if|unless|while|until|for|begin|case)\b/
INDENT_DO_RE =

do at end of line (block form)

/\bdo\s*(\|[^|]*\|)?\s*$/
INDENT_BRACKET_OPEN_RE =

Opening brackets at end of line

/[\[({]\s*$/
INDENT_CLOSE_RE =

Keywords that close an indentation level

/\A\s*(?:end|[}\])])/
INDENT_MID_RE =

Keywords at same level as their opening keyword (dedent for the line itself)

/\A\s*(?:else|elsif|when|rescue|ensure|in)\b/
MODIFIER_RE =

Modifier keywords that do NOT open indentation

/\b(?:if|unless|while|until|rescue)\b/
DEDENT_TRIGGERS =

Dedent trigger patterns keyed by the last character typed

{
  "d" => /\A(\s*)end\z/,
  "e" => /\A(\s*)(?:else|rescue|ensure)\z/,
  "f" => /\A(\s*)elsif\z/,
  "n" => /\A(\s*)(?:when|in)\z/
}.freeze

Class Method Summary collapse

Class Method Details

.calculate_indent(lines, target_row, shiftwidth) ⇒ Object



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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ruvim/lang/ruby.rb', line 113

def calculate_indent(lines, target_row, shiftwidth)
  depth = 0
  (0...target_row).each do |row|
    line = lines[row]
    stripped = line.to_s.lstrip

    # Skip blank and comment lines for indent computation
    next if stripped.empty? || stripped.start_with?("#")

    # Check if the line opens a new level
    if stripped.match?(INDENT_OPEN_RE)
      # Check for modifier form: something before the keyword on the same line
      # e.g. "return if true" — if keyword is not at start, it's a modifier
      first_word = stripped[/\A(\w+)/, 1]
      if %w[if unless while until rescue].include?(first_word)
        depth += 1
      elsif %w[def class module begin case for].include?(first_word)
        depth += 1
      end
    elsif stripped.match?(INDENT_DO_RE)
      depth += 1
    end

    # Count opening brackets (not at end-of-line pattern, but individual)
    stripped.each_char do |ch|
      case ch
      when "{", "[", "("
        depth += 1
      when "}", "]", ")"
        depth -= 1
      end
    end if !stripped.match?(INDENT_OPEN_RE) && !stripped.match?(INDENT_DO_RE)

    # Handle closing keywords
    if stripped.match?(/\A\s*end\b/)
      depth -= 1
    end

    # Handle mid keywords (else/elsif/when/rescue/ensure) — they don't change depth for following lines
  end

  # Now compute indent for target_row
  target_line = lines[target_row].to_s.lstrip

  # If target line is a closing keyword, dedent
  if target_line.match?(INDENT_CLOSE_RE)
    depth -= 1
  elsif target_line.match?(INDENT_MID_RE)
    depth -= 1
  end

  depth = 0 if depth < 0
  depth * shiftwidth
end

.color_columns(text) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/ruvim/lang/ruby.rb', line 213

def color_columns(text)
  cols = {}
  Prism.lex(text).value.each do |entry|
    token = entry[0]
    type = token.type
    range = token.location.start_offset...token.location.end_offset
    if PRISM_STRING_TYPES.include?(type)
      range.each { |idx| cols[idx] = Highlighter::STRING_COLOR unless cols.key?(idx) }
    elsif PRISM_KEYWORD_TYPES.include?(type)
      range.each { |idx| cols[idx] = Highlighter::KEYWORD_COLOR unless cols.key?(idx) }
    elsif PRISM_NUMBER_TYPES.include?(type)
      range.each { |idx| cols[idx] = Highlighter::NUMBER_COLOR unless cols.key?(idx) }
    elsif PRISM_VARIABLE_TYPES.include?(type)
      range.each { |idx| cols[idx] = Highlighter::VARIABLE_COLOR unless cols.key?(idx) }
    elsif PRISM_CONSTANT_TYPES.include?(type)
      range.each { |idx| cols[idx] = Highlighter::CONSTANT_COLOR unless cols.key?(idx) }
    elsif PRISM_COMMENT_TYPES.include?(type)
      range.each { |idx| cols[idx] = Highlighter::COMMENT_COLOR }
    end
  end
  cols
end

.dedent_trigger(char) ⇒ Object

Returns the dedent pattern for the given character, or nil



186
187
188
# File 'lib/ruvim/lang/ruby.rb', line 186

def dedent_trigger(char)
  DEDENT_TRIGGERS[char]
end

.indent_trigger?(line) ⇒ Boolean

Returns true if the line should increase indent for the next line

Returns:

  • (Boolean)


169
170
171
172
173
174
175
# File 'lib/ruvim/lang/ruby.rb', line 169

def indent_trigger?(line)
  stripped = line.to_s.rstrip.lstrip
  first_word = stripped[/\A(\w+)/, 1].to_s
  return true if %w[def class module if unless while until for begin case].include?(first_word)
  return true if stripped.match?(/\bdo\s*(\|[^|]*\|)?\s*$/)
  false
end

.on_save(ctx, path) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/ruvim/lang/ruby.rb', line 190

def on_save(ctx, path)
  return unless path && File.exist?(path)
  return if ctx.editor.respond_to?(:restricted_mode?) && ctx.editor.restricted_mode?
  output, status = Open3.capture2e("ruby", "-wc", path)
  message = output.sub(/^Syntax OK\n?\z/m, "").strip

  if !status.success? || !message.empty?
    buffer_id = ctx.buffer.id
    items = message.lines.filter_map { |line|
      if line =~ /\A.+?:(\d+):/
        { buffer_id: buffer_id, row: $1.to_i - 1, col: 0, text: line.strip }
      end
    }
    items = [{ buffer_id: buffer_id, row: 0, col: 0, text: message }] if items.empty?
    ctx.editor.set_quickfix_list(items)
    first = message.lines.first.to_s.strip
    hint = items.size > 1 ? " (Q to see all, #{items.size} total)" : ""
    ctx.editor.echo_error("#{first}#{hint}")
  else
    ctx.editor.set_quickfix_list([])
  end
end