Class: Tailor::Rulers::IndentationSpacesRuler::IndentationManager

Inherits:
Object
  • Object
show all
Includes:
LexerConstants, Logger::Mixin
Defined in:
lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb

Overview

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.

Constant Summary collapse

ENCLOSERS =

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

Set.new [:on_lbrace, :on_lbracket, :on_lparen]
OPEN_EVENT_FOR =

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

{
  on_kw: :on_kw,
  on_rbrace: :on_lbrace,
  on_rbracket: :on_lbracket,
  on_rparen: :on_lparen
}

Constants included from LexerConstants

LexerConstants::CONTINUATION_KEYWORDS, LexerConstants::KEYWORDS_AND_MODIFIERS, LexerConstants::KEYWORDS_TO_INDENT, LexerConstants::LOOP_KEYWORDS, LexerConstants::MODIFIERS, LexerConstants::MULTILINE_OPERATORS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logger::Mixin

included

Constructor Details

#initialize(spaces) ⇒ IndentationManager

Returns a new instance of IndentationManager.

Parameters:

  • spaces (Fixnum)

    The number of spaces each level of indentation should move in & out.



49
50
51
52
53
54
55
56
57
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 49

def initialize(spaces)
  @spaces = spaces
  @proper = { this_line: 0, next_line: 0 }
  @actual_indentation = 0
  @indent_reasons = []
  @amount_to_change_this = 0

  start
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &blk) ⇒ Boolean

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.

Returns:

  • (Boolean)


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
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 375

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

Instance Attribute Details

#actual_indentationFixnum (readonly)

Returns The actual number of characters the current line is indented.

Returns:

  • (Fixnum)

    The actual number of characters the current line is indented.



40
41
42
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 40

def actual_indentation
  @actual_indentation
end

#amount_to_change_thisObject

Allows for updating the indent expectation for the current line.



36
37
38
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 36

def amount_to_change_this
  @amount_to_change_this
end

#indent_reasonsArray<Hash> (readonly)

Returns 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).

Returns:

  • (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).



45
46
47
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 45

def indent_reasons
  @indent_reasons
end

Instance Method Details

#add_indent_reason(event_type, token, lineno) ⇒ Object

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

Parameters:

  • event_type (Symbol)

    The event type that caused the reason for indenting.

  • token (Tailor::Token, String)

    The token that caused the reason for indenting.

  • lineno (Fixnum)

    The line number the reason for indenting was discovered on.



199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 199

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_lineObject

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



66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 66

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?Boolean

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

Returns:

  • (Boolean)


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 172

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_typeObject



354
355
356
357
358
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 354

def last_indent_reason_type
  return if @indent_reasons.empty?

  @indent_reasons.last[:event_type]
end

#last_opening_event(closing_event_type) ⇒ Object

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

Parameters:

  • closing_event_type (Symbol)

    The closing event for which to find its associated opening event.



346
347
348
349
350
351
352
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 346

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_eventObject

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).



332
333
334
335
336
337
338
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 332

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) ⇒ Boolean

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

Parameters:

  • token_event (Array)

    A single event (probably extracted from a LexedLine).

Returns:

  • (Boolean)


162
163
164
165
166
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 162

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) ⇒ Boolean

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

Parameters:

Returns:

  • (Boolean)


148
149
150
151
152
153
154
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 148

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

#remove_appropriate_reason(closing_event_type) ⇒ Object

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

Parameters:

  • closing_event_type (Symbol)

    The closing event for which to find the matching opening event to remove from the list of indent reasons.



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 304

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_keywordsObject

Removes all continuation keywords from the list of indentation reasons.



362
363
364
365
366
367
368
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 362

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

#set_up_line_transitionObject

Sets up expectations in @proper based on the number of /- reasons to change this and next lines, given in @amount_to_change_this+.



83
84
85
86
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 83

def set_up_line_transition
  log "Amount to change this line: #{@amount_to_change_this}"
  decrease_this_line if @amount_to_change_this < 0
end

#should_be_atFixnum

Returns The indent level the file should currently be at.

Returns:

  • (Fixnum)

    The indent level the file should currently be at.



60
61
62
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 60

def should_be_at
  @proper[:this_line]
end

#startObject

Starts the process of increasing/decreasing line indentation expectations.



105
106
107
108
109
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 105

def start
  log "Starting indentation ruling."
  log "Next check should be at #{should_be_at}"
  @do_measurement = true
end

#started?Boolean

Tells if the indentation checking process is on.

Returns:

  • (Boolean)

    true if it’s started; false if not.



114
115
116
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 114

def started?
  @do_measurement
end

#stopObject

Stops the process of increasing/decreasing line indentation expectations.



120
121
122
123
124
125
126
127
128
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 120

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_linesObject

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



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 91

def transition_lines
  if started?
    log "Resetting change_this to 0."
    @amount_to_change_this = 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) ⇒ Object

Updates @actual_indentation based on the given lexed_line_output.

Parameters:

  • lexed_line_output (Array)

    The lexed output for the current line.



133
134
135
136
137
138
139
140
141
142
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 133

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) ⇒ Object

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

Parameters:

  • event_type (Symbol)

    The event type that is the closing reason.

  • token (Tailor::Token, String)

    The token that is the closing reason.



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 276

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] = @proper[:this_line] - @spaces
    msg = "End multi-line statement. "
    msg < "change_this -= 1 -> #{@proper[:this_line]}."
    log msg
  end
end

#update_for_continuation_reason(token, lexed_line, lineno) ⇒ Object

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.

Parameters:

  • event_type (Symbol)

    The event type that is the opening reason.

  • lexed_line (Tailor::LexedLine)
  • lineno (Fixnum)

    The line number the opening reason was found on.



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 244

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) ⇒ Object

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

Parameters:

  • event_type (Symbol)

    The event type that is the opening reason.

  • token (Tailor::Token, String)

    The token that is the opening reasons.

  • lineno (Fixnum)

    The line number the opening reason was found on.



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 220

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