// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)JavaText.java, 1.9, 12/05/96





package marimba.text.editor;





import marimba.util.*;


import java.awt.*;


import marimba.text.*;





/**


 * Text subclass which knows about font coloring for Java.


 *


 * @author	Jonathan Payne


 * @version 	1.9, 12/05/96


 */


public class JavaText extends Text {


    static StyleChange keywordStyle;


    static StyleChange stringStyle;


    static StyleChange typeStyle;


    static StyleChange storageStyle;


    static StyleChange commentStyle;


    static StyleChange plainStyle;


    static StyleChange slashComment;


    static StyleChange chconst;


    static {


	keywordStyle = new StyleChange().makePlain().makeBold().setColor(Color.blue);


	stringStyle = new StyleChange().makePlain().makeItalic().setColor(Color.red);


	typeStyle = new StyleChange().makePlain().makeBold().setColor(Color.black);


	storageStyle = typeStyle;


	commentStyle = new StyleChange().makePlain().makeItalic().setColor(Color.blue);


	slashComment = new StyleChange().makePlain().makeItalic().setColor(Color.magenta);


	chconst = new StyleChange().makePlain().makeItalic().setColor(Color.red.darker());


	plainStyle = new StyleChange().makePlain().setColor(Color.black);


    }





    /** Fontify a region from pos0 to pos1.  To make this more


     * efficient, we figure out what fontification is in effect at


     * pos0, back up to the beginning of that region and assume that


     * that's a syntactic boundery suitable for beginning a scan.  And


     * then we scan until we reach pos1 AND the style we just applied


     * is the same as the style that was already there.


     * 


     * So, for example, if you're in the middle of a comment, the scan


     * will start back at the beginning of the comment, and go until


     * the end of the comment is reached.  It will reapply the already


     * existing style at that point and realize it has nothing to do.


     *


     * But another example is, a comment is started.  Then what


     * happens is the scan starts at the beginning of the current


     * line, scans until the end of comment is reached (or the end of


     * the buffer) or the end of some other comment that already


     * existed.  When the close comment is hit, the scan (which


     * started at the beginning of the comment) closes off the comment


     * but continues because it keeps applying styles which are


     * different from what was already there.


     */


    public void fontifyRegion(int pos0, int pos1) {


	/* round region to whole line bounderies */


	pos0 = scan('\n', pos0, -1) + 1;


	pos1 = scan('\n', pos1, 1);


	if (pos1 == -1) {


	    pos1 = length();


	}


	int index = styles.findStyleIndexFor(pos0);


	Style s = styles.getStyleAt(index);


	if (!s.equals(styles.getStyleAt(0))) {


	    //System.out.println(s + " not plain!");


	    pos0 = styles.getStylePosAt(index);


	    //System.out.println("Backing up to " + pos0);


	}





	Scanner scanner = new Scanner(this, pos0, length());


	ByteString token = scanner.token;





	int tok;


	int lastpos = pos0;


	while ((tok = scanner.scan()) != -1) {


	    StyleChange sc;





	    switch (tok) {


	      case Scanner.KEYWORD:


		sc = keywordStyle;


		break;





	      case Scanner.TYPE:


		sc = typeStyle;


		break;





	      case Scanner.STORAGE:


		sc = storageStyle;


		break;





	      case Scanner.COMMENT:


		sc = commentStyle;


		break;





	      case Scanner.STRING:


		sc = stringStyle;


		break;





	      case Scanner.SLASH_COMMENT:


		sc = slashComment;


		break;





	      case Scanner.CHAR_CONSTANT:


		sc = chconst;


		break;





	      default:


		continue;


	    }


	    if (token.off > lastpos) {


		applyStyleChangeCheck(plainStyle, lastpos, token.off);


	    }


	    //System.out.println("Apply " + sc + " [ " + token.off + ", " +


	    //(token.off + token.length));


	    boolean changed = applyStyleChangeCheck(sc, token.off,


						    token.off + token.length);


	    lastpos = token.off + token.length;


	    if (lastpos > pos1 && !changed) {


		index = styles.findStyleIndexFor(lastpos);


		if (styles.getStylePosAt(index) == lastpos) {


		    break;


		}


	    }


	}


	if (tok == -1) {


	    applyStyleChange(plainStyle, lastpos, length());


	}


    }





    protected void notifyInsert(int pos, int count) {


	super.notifyInsert(pos, count);


//	fontifyRegion(0, length());


//	make it correct for now, not fast


	fontifyRegion(pos, pos + count);


    }





    protected void notifyDeleteAfter(int pos, int count) {


	super.notifyDeleteAfter(pos, count);


//	fontifyRegion(0, length());


//	make it correct for now, not fast


	fontifyRegion(pos - 1, pos);


    }





    final boolean isBackSlashed(int pos) {


	byte data[] = this.data;


	int cnt = 0;





	while (pos > 0 && data[--pos] == '\\') {


	    cnt += 1;


	}


	return (cnt & 1) != 0;


    }





    public int getMatchingParen(int pos, int direction) {


	int count = 0;


	byte data[] = this.data;


	int limit = this.count;


	boolean in_comment = false;


	int quotec = 0;


	int countAtLastNewline = 0;


	int matchFound = -1;


	boolean hitNewline = false;





	pos += direction;


	while (pos >= 0 && pos < limit) {


	    int c = data[pos];


	    if (c == '/' && !isBackSlashed(pos)) {


		boolean new_ic = in_comment;





		if (pos > 0 && data[pos - 1] == '*') {


		    new_ic = (direction < 0);


		    if (!new_ic && !in_comment) {


			count = 0;


			quotec = 0;


		    }


		} else if (pos + 1 < limit && data[pos + 1] == '*') {


		    new_ic = (direction > 0);


		    if (!new_ic && !in_comment) {


			count = 0;


			quotec = 0;


		    }


		} else if (pos + 1 < limit && data[pos + 1] == '/') {


		    new_ic = in_comment;


		    if (direction > 1) {


			while (pos < limit && data[pos] != '\n') {


			    pos += 1;


			}


		    } else {


			count = countAtLastNewline;


			matchFound = -1;


			quotec = 0;


		    }


		}


		if (quotec == 0) {


		    in_comment = new_ic;


		}


	    }


	    if (!in_comment) {


		switch (c) {


		  case '(':


		  case '{':


		  case '[':


		    if (quotec == 0 && !isBackSlashed(pos)) {


			count += direction;


		    }


		    break;





		  case ')':


		  case '}':


		  case ']':


		    if (quotec == 0 && !isBackSlashed(pos)) {


			count -= direction;


		    }


		    break;





		  case '"':


		  case '\'':


		    if (!isBackSlashed(pos)) {


			if (quotec == c) {


			    quotec = 0;


			} else {


			    quotec = c;


			}


		    }


		    break;		





		  case '\n':


		    if (matchFound >= 0) {


			return matchFound;


		    }


		    if (direction < 0) {


			countAtLastNewline = count;


		    }


		    hitNewline = true;


		    break;


		}


	    }


	    if (count < 0 && matchFound == -1) {


		/* We found the match.  If we're going forward, then


		   we're done, but if we're going backward, we have to


		   make sure we're not in the middle of a single-line


		   comment.  So we set a flag that indicates we're


		   done, and then we check that flag when we reach the


		   newline. */


		if (direction < 0 && hitNewline) {


		    matchFound = pos;


		} else {


		    break;


		}


	    }


	    pos += direction;


	}


	if (pos < 0 || pos >= limit) {


	    return -1;


	}


	return pos;


    }


}


