Chris@909: module CodeRay Chris@909: module Scanners Chris@909: Chris@909: # Scanner for output of the diff command. Chris@909: # Chris@909: # Alias: +patch+ Chris@909: class Diff < Scanner Chris@909: Chris@909: register_for :diff Chris@909: title 'diff output' Chris@909: Chris@909: DEFAULT_OPTIONS = { Chris@909: :highlight_code => true, Chris@909: :inline_diff => true, Chris@909: } Chris@909: Chris@909: protected Chris@909: Chris@909: require 'coderay/helpers/file_type' Chris@909: Chris@909: def scan_tokens encoder, options Chris@909: Chris@909: line_kind = nil Chris@909: state = :initial Chris@909: deleted_lines = 0 Chris@909: scanners = Hash.new do |h, lang| Chris@909: h[lang] = Scanners[lang].new '', :keep_tokens => true, :keep_state => true Chris@909: end Chris@909: content_scanner = scanners[:plain] Chris@909: content_scanner_entry_state = nil Chris@909: Chris@909: until eos? Chris@909: Chris@909: if match = scan(/\n/) Chris@909: deleted_lines = 0 unless line_kind == :delete Chris@909: if line_kind Chris@909: encoder.end_line line_kind Chris@909: line_kind = nil Chris@909: end Chris@909: encoder.text_token match, :space Chris@909: next Chris@909: end Chris@909: Chris@909: case state Chris@909: Chris@909: when :initial Chris@909: if match = scan(/--- |\+\+\+ |=+|_+/) Chris@909: encoder.begin_line line_kind = :head Chris@909: encoder.text_token match, :head Chris@909: if match = scan(/.*?(?=$|[\t\n\x00]| \(revision)/) Chris@909: encoder.text_token match, :filename Chris@909: if options[:highlight_code] Chris@909: file_type = FileType.fetch(match, :text) Chris@909: file_type = :text if file_type == :diff Chris@909: content_scanner = scanners[file_type] Chris@909: content_scanner_entry_state = nil Chris@909: end Chris@909: end Chris@909: next unless match = scan(/.+/) Chris@909: encoder.text_token match, :plain Chris@909: elsif match = scan(/Index: |Property changes on: /) Chris@909: encoder.begin_line line_kind = :head Chris@909: encoder.text_token match, :head Chris@909: next unless match = scan(/.+/) Chris@909: encoder.text_token match, :plain Chris@909: elsif match = scan(/Added: /) Chris@909: encoder.begin_line line_kind = :head Chris@909: encoder.text_token match, :head Chris@909: next unless match = scan(/.+/) Chris@909: encoder.text_token match, :plain Chris@909: state = :added Chris@909: elsif match = scan(/\\ .*/) Chris@909: encoder.text_token match, :comment Chris@909: elsif match = scan(/@@(?>[^@\n]*)@@/) Chris@909: content_scanner.state = :initial unless match?(/\n\+/) Chris@909: content_scanner_entry_state = nil Chris@909: if check(/\n|$/) Chris@909: encoder.begin_line line_kind = :change Chris@909: else Chris@909: encoder.begin_group :change Chris@909: end Chris@909: encoder.text_token match[0,2], :change Chris@909: encoder.text_token match[2...-2], :plain Chris@909: encoder.text_token match[-2,2], :change Chris@909: encoder.end_group :change unless line_kind Chris@909: next unless match = scan(/.+/) Chris@909: if options[:highlight_code] Chris@909: content_scanner.tokenize match, :tokens => encoder Chris@909: else Chris@909: encoder.text_token match, :plain Chris@909: end Chris@909: next Chris@909: elsif match = scan(/\+/) Chris@909: encoder.begin_line line_kind = :insert Chris@909: encoder.text_token match, :insert Chris@909: next unless match = scan(/.+/) Chris@909: if options[:highlight_code] Chris@909: content_scanner.tokenize match, :tokens => encoder Chris@909: else Chris@909: encoder.text_token match, :plain Chris@909: end Chris@909: next Chris@909: elsif match = scan(/-/) Chris@909: deleted_lines += 1 Chris@909: encoder.begin_line line_kind = :delete Chris@909: encoder.text_token match, :delete Chris@909: if options[:inline_diff] && deleted_lines == 1 && check(/(?>.*)\n\+(?>.*)$(?!\n\+)/) Chris@909: content_scanner_entry_state = content_scanner.state Chris@909: skip(/(.*)\n\+(.*)$/) Chris@909: head, deletion, insertion, tail = diff self[1], self[2] Chris@909: pre, deleted, post = content_scanner.tokenize [head, deletion, tail], :tokens => Tokens.new Chris@909: encoder.tokens pre Chris@909: unless deleted.empty? Chris@909: encoder.begin_group :eyecatcher Chris@909: encoder.tokens deleted Chris@909: encoder.end_group :eyecatcher Chris@909: end Chris@909: encoder.tokens post Chris@909: encoder.end_line line_kind Chris@909: encoder.text_token "\n", :space Chris@909: encoder.begin_line line_kind = :insert Chris@909: encoder.text_token '+', :insert Chris@909: content_scanner.state = content_scanner_entry_state || :initial Chris@909: pre, inserted, post = content_scanner.tokenize [head, insertion, tail], :tokens => Tokens.new Chris@909: encoder.tokens pre Chris@909: unless inserted.empty? Chris@909: encoder.begin_group :eyecatcher Chris@909: encoder.tokens inserted Chris@909: encoder.end_group :eyecatcher Chris@909: end Chris@909: encoder.tokens post Chris@909: elsif match = scan(/.*/) Chris@909: if options[:highlight_code] Chris@909: if deleted_lines == 1 Chris@909: content_scanner_entry_state = content_scanner.state Chris@909: end Chris@909: content_scanner.tokenize match, :tokens => encoder unless match.empty? Chris@909: if !match?(/\n-/) Chris@909: if match?(/\n\+/) Chris@909: content_scanner.state = content_scanner_entry_state || :initial Chris@909: end Chris@909: content_scanner_entry_state = nil Chris@909: end Chris@909: else Chris@909: encoder.text_token match, :plain Chris@909: end Chris@909: end Chris@909: next Chris@909: elsif match = scan(/ .*/) Chris@909: if options[:highlight_code] Chris@909: content_scanner.tokenize match, :tokens => encoder Chris@909: else Chris@909: encoder.text_token match, :plain Chris@909: end Chris@909: next Chris@909: elsif match = scan(/.+/) Chris@909: encoder.begin_line line_kind = :comment Chris@909: encoder.text_token match, :plain Chris@909: else Chris@909: raise_inspect 'else case rached' Chris@909: end Chris@909: Chris@909: when :added Chris@909: if match = scan(/ \+/) Chris@909: encoder.begin_line line_kind = :insert Chris@909: encoder.text_token match, :insert Chris@909: next unless match = scan(/.+/) Chris@909: encoder.text_token match, :plain Chris@909: else Chris@909: state = :initial Chris@909: next Chris@909: end Chris@909: end Chris@909: Chris@909: end Chris@909: Chris@909: encoder.end_line line_kind if line_kind Chris@909: Chris@909: encoder Chris@909: end Chris@909: Chris@909: private Chris@909: Chris@909: def diff a, b Chris@909: # i will be the index of the leftmost difference from the left. Chris@909: i_max = [a.size, b.size].min Chris@909: i = 0 Chris@909: i += 1 while i < i_max && a[i] == b[i] Chris@909: # j_min will be the index of the leftmost difference from the right. Chris@909: j_min = i - i_max Chris@909: # j will be the index of the rightmost difference from the right which Chris@909: # does not precede the leftmost one from the left. Chris@909: j = -1 Chris@909: j -= 1 while j >= j_min && a[j] == b[j] Chris@909: return a[0...i], a[i..j], b[i..j], (j < -1) ? a[j+1..-1] : '' Chris@909: end Chris@909: Chris@909: end Chris@909: Chris@909: end Chris@909: end