# frozen_string_literal: true module Textbringer CONFIG[:c_indent_level] = 4 CONFIG[:c_indent_tabs_mode] = true CONFIG[:c_continued_statement_offset] = 4 CONFIG[:c_case_label_offset] = -4 CONFIG[:c_label_offset] = -2 class CMode < ProgrammingMode self.file_name_pattern = /\A.*\.[ch]\z/i KEYWORDS = %w( auto break case char const continue default double do else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while _Bool _Complex _Imaginary ) define_syntax :comment, / (?: \/\* (?> (?:.|\n)*? \*\/ ) ) | (?: \/\/ .*(?:\\\n.*)*(?:\z|(? ^[ \t\f\v]*(?:\#|%:).*(?:\\\n.*)*[^\\]\n ) | (? (? \/\* (?> (?:.|\n)*? \*\/ ) ) | (? \/\/ .*(?:\\\n.*)*(? (? \/\* (?:.|\n)* ) | (? \/\/ .*? \\\n (?:.|\n)* ) ) | (? (?: #{KEYWORDS.join("|")} ) \b ) | (? (? (? (? (? [0-9]+ )? \. \g | \g \. ) (? [eE] [+\-]? \g )? (? [flFL] )? ) | (? (? 0x | 0X ) (? (? [0-9a-fA-F]+ )? \. \g | \g \. ) (? [pP] [+\-]? \g ) \g? | \g \g \g \g? ) ) | (? (? [1-9][0-9]* ) (? (? [uU] ) (? [lL] )? \g (? ll | LL )? \g \g? \g \g? )? | (? \g \g ) \g? | (? 0 (? [0-7] )* ) \g? ) | (? ' (? (? [^'\\\r\n] | (? (? \\ ['"?\\abfnrtv] ) | (? \\ \g{1,3} ) | (? \\x \g ) | (? \\u[0-9a-fA-F]{4} | \\U[0-9a-fA-F]{8} ) ) )+ ) ' | L' \g ' ) ) | (? " (? (? [^"\\\r\n] | \g )+ ) " | L" \g? " ) | (? (? [_a-zA-Z] | \g ) (?: \g | [0-9] )* ) | (? \[ | \] | \( | \) | \{ | \} | \.\.\. | \. | \+\+ | \+= | \+ | -> | -- | -= | - | \*= | \* | \/= | \/ | && | &= | & | \|\| | \|= | \| | != | ! | ~ | == | = | \^= | \^ | <: | <% | <<= | << | <= | < | >>= | >> | >= | > | \? | ; | :> | : | , | \#\# | \# | %> | %:%: | %: | %= | % ) | (? \s+ ) | (?.) )/x def lex(s) tokens = [] pos = 0 line = 1 column = 0 while pos < s.size && s.index(TOKEN_REGEXP, pos) text = $& token_name = TOKEN_NAMES.find { |name| $~[name] } if text.empty? raise EditorError, "Empty token: (#{line},#{column}) #{$~.inspect}" end tokens.push([[line, column], token_name, text]) lf_count = text.count("\n") if lf_count > 0 line += lf_count column = text.slice(/[^\n]*\z/).size else column += text.size end pos += text.size end tokens end private def calculate_indentation if @buffer.current_line == 1 return 0 end @buffer.save_excursion do @buffer.beginning_of_line if @buffer.looking_at?(/[ \t]*(?:#|%:)/) return 0 end bol_pos = @buffer.point s = @buffer.substring(@buffer.point_min, @buffer.point).b tokens = lex(s) _, event, = tokens.last if event == :partial_comment return nil end line, column, event, text = find_nearest_beginning_token(tokens) if event == :punctuator && text == "(" return column + 1 end if line @buffer.goto_line(line) else (ln, _), ev = tokens.reverse_each.drop_while { |(l, _), e, t| l >= @buffer.current_line - 1 || e == :space }.first if ev == :comment @buffer.goto_line(ln) else @buffer.backward_line end end @buffer.looking_at?(/[ \t]*/) base_indentation = @buffer.match_string(0). gsub(/\t/, " " * @buffer[:tab_width]).size @buffer.goto_char(bol_pos) if line.nil? || @buffer.looking_at?(/[ \t]*([}\])]|:>|%>)/) indentation = base_indentation else indentation = base_indentation + @buffer[:indent_level] end if @buffer.looking_at?(/[ \t]*(?:case.*|default):/) indentation += @buffer[:c_case_label_offset] elsif @buffer.looking_at?(/[ \t]*[_a-zA-Z0-9\\]+:/) indentation += @buffer[:c_label_offset] end indent_continued_statement(indentation, tokens, line) end end def indent_continued_statement(indentation, tokens ,line) if line && !@buffer.looking_at?(/[ \t]*\{/) _, last_event, last_text = tokens.reverse_each.drop_while { |(l, _), e, t| l == @buffer.current_line || e == :space || e == :comment }.first if last_event != :preprocessing_directive && /[:;{}]/ !~ last_text indentation + @buffer[:c_continued_statement_offset] else indentation end else indentation end end CANONICAL_PUNCTUATORS = Hash.new { |h, k| k } CANONICAL_PUNCTUATORS["<:"] = "[" CANONICAL_PUNCTUATORS[":>"] = "]" CANONICAL_PUNCTUATORS["<%"] = "{" CANONICAL_PUNCTUATORS["%>"] = "}" BLOCK_END = { "{" => "}", "(" => ")", "[" => "]" } BLOCK_BEG = BLOCK_END.invert def find_nearest_beginning_token(tokens) stack = [] (tokens.size - 1).downto(0) do |i| (line, column), event, raw_text = tokens[i] text = CANONICAL_PUNCTUATORS[raw_text] case event when :punctuator if BLOCK_BEG.key?(text) stack.push(text) elsif BLOCK_END.key?(text) if stack.empty? return line, column, event, text end if stack.last != BLOCK_END[text] raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}" end stack.pop end end end return nil end end end