class Tailor::Rulers::IndentationSpacesRuler::IndentationManager

Used for managing the state of indentation for some file/text. An object of this type has no knowledge of the file/text itself, but rather just manages indentation expectations based on the object's user's input, somewhat like a state machine.

For the sake of talking about indentation expectations, the docs here make mention of 'levels' of indentation. A level here is simply 1 * (number of spaces to indent); so if you've set (number of spaces to indent) to 2, saying something should be indented 1 level, is simply saying that it should be indented 2 spaces.

Constants

ENCLOSERS

These are event names generated by the {Lexer} that signify indentation level should/could increase by 1.

OPEN_EVENT_FOR

Look-up table that allows for OPEN_EVENT_FOR[:on_rbrace].

Attributes

actual_indentation[R]

@return [Fixnum] The actual number of characters the current line is

indented.
indent_reasons[R]

@return [Array<Hash>] Each element represents a reason why code should

be indented.  Indent levels are not necessarily 1:1 relationship
to these reasons (hence the need for this class).

Public Class Methods

new(spaces) click to toggle source

@param [Fixnum] spaces The number of spaces each level of indentation

should move in & out.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 47
def initialize(spaces)
  @spaces = spaces
  @proper = { this_line: 0, next_line: 0 }
  @actual_indentation = 0
  @indent_reasons = []

  start
end

Public Instance Methods

add_indent_reason(event_type, token, lineno) click to toggle source

Adds to the list of reasons to indent the next line, then increases the expectation for the next line by +@spaces+.

@param [Symbol] event_type The event type that caused the reason for

indenting.

@param [Tailor::Token,String] token The token that caused the reason

for indenting.

@param [Fixnum] lineno The line number the reason for indenting was

discovered on.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 188
def add_indent_reason(event_type, token, lineno)
  @indent_reasons << {
    event_type: event_type,
    token: token,
    lineno: lineno,
    should_be_at: @proper[:this_line]
  }

  @proper[:next_line] = @indent_reasons.last[:should_be_at] + @spaces
  log "Added indent reason; it's now:"
  @indent_reasons.each { |r| log r.to_s }
end
decrease_this_line() click to toggle source

Decreases the indentation expectation for the current line by 1 level.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 63
def decrease_this_line
  if started?
    @proper[:this_line] -= @spaces

    if @proper[:this_line] < 0
      @proper[:this_line] = 0
    end

    log "@proper[:this_line] = #{@proper[:this_line]}"
    log "@proper[:next_line] = #{@proper[:next_line]}"
  else
    log '#decrease_this_line called, but checking is stopped.'
  end
end
in_an_enclosure?() click to toggle source

Determines if the current spot in the file is enclosed in braces, brackets, or parens.

@return [Boolean]

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 161
def in_an_enclosure?
  return false if @indent_reasons.empty?

  i_reasons = @indent_reasons.dup
  log "i reasons: #{i_reasons}"

  until ENCLOSERS.include? i_reasons.last[:event_type]
    i_reasons.pop
    break if i_reasons.empty?
  end

  return false if i_reasons.empty?

  i_reasons.last[:event_type] == :on_lbrace ||
    i_reasons.last[:event_type] == :on_lbracket ||
    i_reasons.last[:event_type] == :on_lparen
end
last_indent_reason_type() click to toggle source
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 342
def last_indent_reason_type
  return if @indent_reasons.empty?

  @indent_reasons.last[:event_type]
end
last_opening_event(closing_event_type) click to toggle source

Returns the last matching opening event that corresponds to the closing_event_type.

@param [Symbol] closing_event_type The closing event for which to

find its associated opening event.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 334
def last_opening_event(closing_event_type)
  return nil if @indent_reasons.empty?

  @indent_reasons.reverse.find do |r|
    r[:event_type] == OPEN_EVENT_FOR[closing_event_type]
  end
end
last_single_token_event() click to toggle source

A “single-token” event is one that that causes indentation expectations to increase. They don't have have a paired closing reason like opening reasons. Instead, they're determined to be done with their indenting when an :on_ignored_nl occurs. Single-token events are operators and commas (commas that aren't used as separators in {, [, ( events).

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 321
def last_single_token_event
  return nil if @indent_reasons.empty?

  @indent_reasons.reverse.find do |r|
    !ENCLOSERS.include?(r[:event_type]) && r[:event_type] != :on_kw
  end
end
line_ends_with_same_as_last(token_event) click to toggle source

Checks to see if the last token in @single_tokens is the same as the one in token_event.

@param [Array] token_event A single event (probably extracted from a

{LexedLine}).

@return [Boolean]

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 151
def line_ends_with_same_as_last(token_event)
  return false if @indent_reasons.empty?

  @indent_reasons.last[:event_type] == token_event[1]
end
line_ends_with_single_token_indenter?(lexed_line) click to toggle source

Checks if the current line ends with an operator, comma, or period.

@param [LexedLine] lexed_line @return [Boolean]

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 137
def line_ends_with_single_token_indenter?(lexed_line)
  lexed_line.ends_with_op? ||
    lexed_line.ends_with_comma? ||
    lexed_line.ends_with_period? ||
    lexed_line.ends_with_label? ||
    lexed_line.ends_with_modifier_kw?
end
method_missing(meth, *args, &blk) click to toggle source

Overriding to be able to call #multi_line_brackets?, #multi_line_braces?, and #multi_line_parens?, where each takes a single parameter, which is the lineno.

@return [Boolean]

Calls superclass method
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 363
def method_missing(meth, *args, &blk)
  if meth.to_s =~ /^multi_line_(.+)\?$/
    token = case $1
    when 'brackets' then '['
    when 'braces' then '{'
    when 'parens' then '('
    else
      super(meth, *args, &blk)
    end

    lineno = args.first

    tokens = @indent_reasons.find_all do |t|
      t[:token] == token
    end

    log "#{meth} called, but no #{$1} were found." if tokens.empty?
    return false if tokens.empty?

    token_on_this_line = tokens.find { |t| t[:lineno] == lineno }
    return true if token_on_this_line.nil?

    false
  else
    super(meth, *args, &blk)
  end
end
remove_appropriate_reason(closing_event_type) click to toggle source

Removes the last matching opening reason reason of event_type from the list of indent reasons.

@param [Symbol] closing_event_type The closing event for which to find

the matching opening event to remove from the list of indent
reasons.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 293
def remove_appropriate_reason(closing_event_type)
  if last_opening_event = last_opening_event(closing_event_type)
    r_index = @indent_reasons.reverse.index(last_opening_event)
    index = @indent_reasons.size - r_index - 1
    tmp_reasons = []

    @indent_reasons.each_with_index do |r, i|
      tmp_reasons << r unless i == index
    end

    @indent_reasons.replace(tmp_reasons)
  elsif last_single_token_event
    log "Just popped off reason: #{@indent_reasons.pop}"
  else
    log "Couldn't find a matching opening reason to pop off...'"
    return
  end

  log "Removed indent reason; it's now:"
  @indent_reasons.each { |r| log r.to_s }
end
remove_continuation_keywords() click to toggle source

Removes all continuation keywords from the list of indentation reasons.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 350
def remove_continuation_keywords
  return if @indent_reasons.empty?

  while CONTINUATION_KEYWORDS.include?(@indent_reasons.last[:token])
    log "Just popped off continuation reason: #{@indent_reasons.pop}"
  end
end
should_be_at() click to toggle source

@return [Fixnum] The indent level the file should currently be at.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 57
def should_be_at
  @proper[:this_line]
end
start() click to toggle source

Starts the process of increasing/decreasing line indentation expectations.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 94
def start
  log 'Starting indentation ruling.'
  log "Next check should be at #{should_be_at}"
  @do_measurement = true
end
started?() click to toggle source

Tells if the indentation checking process is on.

@return [Boolean] true if it's started; false if not.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 103
def started?
  @do_measurement
end
stop() click to toggle source

Stops the process of increasing/decreasing line indentation expectations.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 109
def stop
  if started?
    msg = "Stopping indentation ruling.  Should be: #{should_be_at}; "
    msg << "actual: #{@actual_indentation}"
    log msg
  end

  @do_measurement = false
end
transition_lines() click to toggle source

Should be called just before moving to the next line. This sets the expectation set in +@proper+ to +@proper+.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 81
def transition_lines
  if started?
    log 'Resetting change_this to 0.'
    log 'Setting @proper[:this_line] = that of :next_line'
    @proper[:this_line] = @proper[:next_line]
    log "Transitioning @proper[:this_line] to #{@proper[:this_line]}"
  else
    log 'Skipping #transition_lines; checking is stopped.'
  end
end
update_actual_indentation(lexed_line_output) click to toggle source

Updates +@actual_indentation+ based on the given lexed_line_output.

@param [Array] lexed_line_output The lexed output for the current line.

# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 122
def update_actual_indentation(lexed_line_output)
  if lexed_line_output.end_of_multi_line_string?
    log 'Found end of multi-line string.'
    return
  end

  first_non_space_element = lexed_line_output.first_non_space_element
  @actual_indentation = first_non_space_element.first.last
  log "Actual indentation: #{@actual_indentation}"
end
update_for_closing_reason(event_type, lexed_line) click to toggle source

A “closing reason” is a reason for indenting that also has an “opening reason”, such as a end, +}+, ], +)+.

@param [Symbol] event_type The event type that is the closing reason. @param [Tailor::LexedLine] lexed_line The line that contains the

closing reason.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 265
def update_for_closing_reason(event_type, lexed_line)
  remove_continuation_keywords
  remove_appropriate_reason(event_type)

  @proper[:next_line] = if @indent_reasons.empty?
    0
  else
    @indent_reasons.last[:should_be_at] + @spaces
  end

  log "Updated :next after closing; it's now #{@proper[:next_line]}"

  meth = "only_#{event_type.to_s.sub(/^on_/, '')}?"

  if lexed_line.send(meth.to_sym) || lexed_line.to_s =~ /^\s*end\n?$/
    @proper[:this_line] = [0, @proper[:this_line] - @spaces].max
    msg = 'End multi-line statement. '
    msg < "change_this -= 1 -> #{@proper[:this_line]}."
    log msg
  end
end
update_for_continuation_reason(token, lexed_line, lineno) click to toggle source

A “continuation reason” is a reason for indenting & outdenting that's not an opening or closing reason, such as elsif, rescue, when (in a case statement), etc.

@param [Symbol] token @param [Tailor::LexedLine] lexed_line @param [Fixnum] lineno The line number the opening reason was found

on.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 233
def update_for_continuation_reason(token, lexed_line, lineno)
  d_tokens = @indent_reasons.dup
  d_tokens.pop
  on_line_token = d_tokens.find { |t| t[:lineno] == lineno }
  log "online token: #{on_line_token}"

  if on_line_token.nil? && lexed_line.to_s =~ /^\s*#{token}/
    @proper[:this_line] -= @spaces unless @proper[:this_line].zero?
    msg = "Continuation keyword: '#{token}'.  "
    msg << "change_this -= 1 -> #{@proper[:this_line]}"
    log msg
  end

  last_reason_line = @indent_reasons.find { |r| r[:lineno] == lineno }

  @proper[:next_line] = if last_reason_line.nil?
    if @indent_reasons.empty?
      @spaces
    else
      @indent_reasons.last[:should_be_at] + @spaces
    end
  else
    @indent_reasons.last[:should_be_at] - @spaces
  end
end
update_for_opening_reason(event_type, token, lineno) click to toggle source

An “opening reason” is a reason for indenting that also has a “closing reason”, such as a def, +{+, [, +(+.

@param [Symbol] event_type The event type that is the opening reason. @param [Tailor::Token,String] token The token that is the opening

reasons.

@param [Fixnum] lineno The line number the opening reason was found

on.
# File lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb, line 209
def update_for_opening_reason(event_type, token, lineno)
  if token.modifier_keyword?
    log "Found modifier in line: '#{token}'"
    return
  end

  log "Token '#{token}' not used as a modifier."

  if token.do_is_for_a_loop?
    log "Found keyword loop using optional 'do'"
    return
  end

  add_indent_reason(event_type, token, lineno)
end