###########################################################################
#
#             Script: cfilt.pl
#
#            Creator: alex@phred.org (Alex Wetmore)
#               Date: Sept 3, 1998
#
#     Basic Overview: A C color filter for C/C++ for use by Vile's perl
#                     mode.  Implements two functions, ColorC (does the
#                     coloring) and ClearColorC (removes coloring).
#
#  Comments/Concerns: Its not very fast.  Takes about 4 seconds to color
#                     a 90k C file on a P2-300.
#
###########################################################################

#
# Suggested bindings for your .vilerc
#
# store-procedure cfilt
# 	perl "require 'cfilt.pl'"
# 	perl ColorC
# ~endm
# bind-key cfilt +
# 
# store-procedure hi
# 	perl "require 'cfilt.pl'"
# 	perl ColorWords
# ~endm
# 
# store-procedure uncfilt
# 	perl "require 'cfilt.pl'"
# 	perl ClearColorC
# ~endm
# 
# store-procedure clear-all-colors
# 	uncfilt
# 	clear-visual-matches
# ~endm
# 
# bind-key clear-all-colors =
#
# This makes + turn on colors, = turn off all coloring (visual matching
# plus what is done by these functions) and :hi highlight regular
# expressions for you
# 

# to enable keyword coloring set this to 1
$fColorKeywords = 0;
if ($fColorKeywords) {
	# this list of keywords is taken from vile-cfilt.pl, written by 
	#            Creator: cavanaug@elwom4.ecn.purdue.edu (John P Cavanaugh)
	$rgKeywords= "void break register short enum extern int for if while struct "
	    . "static long continue switch case char unsigned double float "
	    . "return else default goto do pascal Boolean typedef volatile union "
	    . "auto sizeof size_t new delete class friend protected private "
	    . "public template try catch throw operator const mutable virtual "
	    . "asm inline this and and_eq bitand bitor compl not or or_eq xor "
	    . "xor_eq not_eq wchar_t bool true false static_cast dynamic_cast "
	    . "reinterpret_cast typeid using namespace inherited";
	# mangle the two lists into a portion of a regexp
	$rgKeywords =~ s/ /\\b\|\\b/g;
	# a list of C preprocessor words to highlight
	$rgCPPWords= "#ifdef #ifndef #if #endif #define #include #blah";
	$rgCPPWords =~ s/ /\|/g;
	$rgKeywords = "\\b" . $rgKeywords . "\\b|" . $rgCPPWords;
} else {
	$rgKeywords = "";
}

# regular expressions for the front and end of comment blocks
$rgCommentsPlus = "#if\\s*0|#if[def\\s]*BUGBUG";
$rgCommentsMinus = "#endif";

#
# the colors that we use for highlighting.  If your terminal doesn't support
# color then try the alternates below
#
@attrComments = ("color" => 8);
@attrStrings = ("color" => 14);
@attrKeywords = ("color" => 15);
@attrHighlight = ("color" => 11);

# monochrome alternates
#@attrComments = ("underline");
#@attrStrings = ("reverse");
#@attrKeywords = ("bold");
#@attrHighlight = ("bold");

#
# Remove all attributes on a buffer
#
sub ClearColorC {
    $Vile::current_buffer->set_region(1,'$');
    $Vile::current_buffer->attribute("normal");
}

#
# Colorize C code.  This colors strings, comments, and keywords defined 
# above.
#
sub ColorC {
    my $bufCurrent;                 # the current buffer
    my $fInCommentBlock = 0;        # are we in a comment block?
    my $fInCommentLine = 0;         # are we on a comment line?
    my $fInString = 0;              # are we in a string?
	my $cIfNesting = 0;				# nesting of comment ifdefs
    my @region;                     # the current region
    my $fQuote;                     # is the current region a quote?
    my @rgLines;                    # all of the lines in the current buffer
    my $iLine;                      # index of the current line in @rgLines
    my $cLines;                     # count of lines in @rgLines
    my $szLine;                     # $rgLines[$iLine]
    my $cLine;                      # length($szLine)
    my $cMatch;                     # length($&) (or 1 if the match is a quote)
    my $offset;                     # offset of the match into the line

    $bufCurrent = $Vile::current_buffer;

    # set the working flag
    Vile::working(1);

    # clear all attributes.  this also sets the region to include the 
    # entire file
    ClearColorC;

    # load the entire buffer into an array.  we do this to avoid setting
    # the region to each line that we want, and using <> operator to get
    # each line in turn
    @rgLines = <$Vile::current_buffer>;
    $cLines = scalar(@rgLines);

    # walk each line in turn
    for ($iLine = 0; $iLine < $cLines; $iLine++) {
        $szLine = $rgLines[$iLine];
        $fInCommentLine = 0;
        $cLine = length($szLine);

        @region = ($iLine + 1, 0, $iLine + 1, 0);

        # search for: ", /*, */, //, or a keyword
        while ($szLine =~ m!\\\\\"|[^\\]\"|//|/\*|\*/|\b#if[ndef]\b|\b#endif\b|$rgCommentsPlus|$rgCommentsMinus|$rgKeywords/!go) {
			my $fSetPos = 0;

            # find the offset to it
            $offset = pos($szLine);
            # quotes are a bit of a special case, so see if this is one
            if ($& eq "\\\\\"") {
                $fQuote = 1;
                $cMatch = 1;
	            pos($szLine) = pos($szLine) - 2;
				$fSetPos = 1;
			} elsif (substr($&, 1) eq "\"") {
                $fQuote = 1;
                $cMatch = 1;
	            pos($szLine) = pos($szLine) - 1;
				$fSetPos = 1;
            } else {
                $fQuote = 0;
                $cMatch = length $&;
            }

            # update our region to have everything up to and including the
            # string that our regexp found and set it
            $region[3] = $offset;
            $bufCurrent->set_region(@region);

            # colorize comments and strings
            if ($fInCommentLine || $fInCommentBlock || $cIfNesting > 0) {
                $bufCurrent->attribute(@attrComments);
            } elsif ($fInString) {
                $bufCurrent->attribute(("color" => $colorStrings));
            }

			if (!$fInString) {
	            # check for start of comment
	            if ($& eq "/*") {
	                $fInCommentBlock = 1;
	            } elsif ($& eq "//") {
	                $fInCommentLine = 1;
	                pos($szLine) = $cLine;
					$fSetPos = 1;
	            } elsif ($& =~ /$rgCommentsPlus/o) {
					$cIfNesting++;
				}
			}

            # do other checks
            if (!($fInCommentBlock || $fInCommentLine || $cIfNesting > 0)) {
                # check for start/end of string and for keywords
                if ($fQuote) { 
                    $fInString = !$fInString; 
                } elsif (!$fInString) {
                    $region[3] = $offset;
                    $region[1] = length($`);
                    $bufCurrent->set_region(@region);
                    $bufCurrent->attribute(("color" => $colorKeywords));
                }
            }

            # check for end of comments
			if (!$fInString) {
	            if ($& eq "*/") {
	                $fInCommentBlock = 0; 
	            } elsif ($& =~ /$rgCommentsMinus/o && $cIfNesting > 0) {
					$cIfNesting--;
				}
			}

			if (!$fSetPos) {
	            pos($szLine) = pos($szLine) - $cMatch + 1;
			}

            # our next region will start where this one left off
            $region[1] = $region[3] - $cMatch;
        }

        # if necessary colorize the rest of the line
        if ($fInCommentLine || $fInCommentBlock || $cIfNesting > 0 || $fInString) {
            $region[3] = $cLine;
            $bufCurrent->set_region(@region);
            if ($fInString) {
                $bufCurrent->attribute(("color" => $colorStrings));
            } else {
                $bufCurrent->attribute(@attrComments);
            }
        }   
    }

    # clear the working flag
    Vile::working(0);
}

#
# a function (based on code from the above function) which will color 
# a regular expression throughout the file.   this can be useful when
# reading through code and trying to figure out how one variable is
# used.
#
sub ColorWords {
	$szWord = Vile::mlreply("regexp to highlight: ");

    my $bufCurrent;                 # the current buffer
    my @region;                     # the current region
    my @rgLines;                    # all of the lines in the current buffer
    my $iLine = 0;                  # index of the current line in @rgLines
    my $cLines;                     # count of lines in @rgLines
    my $szLine;                     # $rgLines[$iLine]
    my $offset;                     # offset of the match into the line

    $bufCurrent = $Vile::current_buffer;

    # set the working flag
    Vile::working(1);

    # load the entire buffer into an array.  we do this to avoid setting
    # the region to each line that we want, and using <> operator to get
    # each line in turn
    $bufCurrent->set_region(1,'$');
    @rgLines = <$bufCurrent>;
    $cLines = scalar(@rgLines);

    # walk each line in turn
    for ($iLine = 0; $iLine < $cLines; $iLine++) {
        $szLine = $rgLines[$iLine];

        @region = ($iLine + 1, 0, $iLine + 1, 0);

        # search for our word
        while ($szLine =~ /$szWord/og) {
            # find the offset to it
            $offset = pos($szLine);

			# update the region to contain the word
            $region[3] = $offset;
            $region[1] = length($`);
            $bufCurrent->set_region(@region);
            $bufCurrent->attribute(("color" => $colorHighlight));
        }
    }

    # clear the working flag
    Vile::working(0);
}

1;

