Package org.nlogo.editor

Source Code of org.nlogo.editor.BracketMatcher$BracketHighlightPainter

// (C) Uri Wilensky. https://github.com/NetLogo/NetLogo

package org.nlogo.editor;

import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import java.util.List;

/**
* Highlights two corresponding parentheses/brackets and identifies if they are
* of the same type.  Two different colors are used to indicate "good"
* matches and "bad" matches (where bad means that the parentheses/brackets
* don't balance correctly).
* <p/>
* Created by EditorArea class.  Some methods are static because they are
* also used by DoubleClickCaret.
*/

strictfp class BracketMatcher<TokenType>
    implements javax.swing.event.CaretListener {

  private static final java.awt.Color GOOD_COLOR = java.awt.Color.GRAY;
  private static final java.awt.Color BAD_COLOR = java.awt.Color.RED;

  // this object breaks the text up into tokens for us
  private final Colorizer<TokenType> colorizer;

  // remember what the whole document text was so we only retokenize when it changes
  private String oldText = "";
  private List<TokenType> tokenTypes = null;
  // remember what the text on current line was so we only retokenize when it changes
  private String oldLineText = "";
  private List<TokenType> lineTokenTypes = null;

  private final BracketHighlightPainter goodPainter;
  private final BracketHighlightPainter badPainter;

  BracketMatcher(Colorizer<TokenType> colorizer) {
    this.colorizer = colorizer;
    goodPainter = new BracketHighlightPainter(GOOD_COLOR);
    badPainter = new BracketHighlightPainter(BAD_COLOR);
  }

  ///

  /**
   * This is the single method we provide in order to implement CaretListener.
   * Listens for changes in caret position of text.
   * Checks current location of caret for parenthesis. If found, tries to find
   * matching parenthesis.
   */
  // The code gets complicated because of the need to be efficient.
  // We don't want to call the tokenizer unless necessary.  We especially
  // don't want to retokenize the whole document unless necessary.
  public void caretUpdate(javax.swing.event.CaretEvent e) {
    EditorArea<?> source = (EditorArea<?>) e.getSource();
    Highlighter highlighter = source.getHighlighter();
    removeOldHighlights(highlighter);
    int dot = e.getDot();
    // only highlight if there is no selection and there is
    // a character to the left
    if (dot != e.getMark() || dot == 0) {
      return;
    }
    try {
      // first we tokenize only the current line (if it changed,
      // otherwise use cached info) in order to find out
      // whether the cursor is to the right of an opener or closer
      String lineText = source.getLineText(dot);
      if (!lineText.equals(oldLineText)) {
        // line text changed, must retokenize
        lineTokenTypes = colorizer.getCharacterTokenTypes(lineText);
        oldLineText = lineText;
      }
      javax.swing.text.PlainDocument doc =
          (javax.swing.text.PlainDocument) source.getDocument();
      int lineNumber = source.offsetToLine(doc, dot);
      int lineDot = dot - source.lineToStartOffset(doc, lineNumber);
      if (lineDot == 0 ||
          // look left of cursor for opening paren
          !colorizer.isOpener(lineTokenTypes.get(lineDot - 1)) &&
              // look left of cursor for closing paren
              !colorizer.isCloser(lineTokenTypes.get(lineDot - 1))) {
        return;
      }
      String text = source.getText();
      if (!text.equals(oldText)) {
        // Text changed, so retokenize the whole document.
        // It'd be better if we could find a way to retokenize
        // only as much as needed to match!  This would be possible
        // to arrange, but tricky. - ST 10/29/04
        tokenTypes = colorizer.getCharacterTokenTypes(text);
        oldText = text;
      }
      doHighlighting(highlighter,
          colorizer.isOpener(lineTokenTypes.get(lineDot - 1)),
          tokenTypes, dot);
    } catch (BadLocationException ex) {
      throw new IllegalStateException(ex);
    }
  }

  void focusLost(EditorArea<?> source) {
    removeOldHighlights(source.getHighlighter());
    oldText = "";
    tokenTypes = null;
    oldLineText = "";
    lineTokenTypes = null;
  }

  /// now for the actual matching logic

  private void doHighlighting(Highlighter highlighter, boolean isOpener,
                              List<TokenType> tokens, int dot)
      throws BadLocationException {
    if (isOpener) {
      int closer = findCloser(colorizer, tokens, dot - 1);
      if (closer != -1 &&
          // candidate found, but is it a good match?
          colorizer.isMatch(tokens.get(dot - 1),
              tokens.get(closer))) {
        highlightGood(highlighter, closer);
      }
      // don't call highlightBad here because they
      // probably just haven't typed the closer yet
    } else {
      int opener = findOpener(colorizer, tokens, dot - 1);
      if (opener != -1) // success!
      {
        // candidate found, but is it a good match?
        if (colorizer.isMatch(tokens.get(opener),
            tokens.get(dot - 1))) {
          highlightGood(highlighter, opener);
        } else {
          highlightBad(highlighter, opener);
        }
      } else {
        highlightBad(highlighter, dot - 1);
      }
    }
  }

  /**
   * Given location of an open parenthesis, will search subsequent tokens
   * for corresponding close parenthesis. Will account for other open parentheses
   * that it encounters and their corresponding close parentheses.
   * Declared static so it can be used from DoubleClickCaret.
   *
   * @return location of close paren in document, or -1 if none found
   */
  int findCloser(Colorizer<TokenType> colorizer, List<TokenType> tokens, int opener) {
    int parenCount = 1;
    for (int i = opener + 1; i < tokens.size(); i++) {
      if (colorizer.isOpener(tokens.get(i))) {
        parenCount++;
      } else if (colorizer.isCloser(tokens.get(i))) {
        parenCount--;
      }
      if (parenCount == 0) {
        return i;
      }
    }
    return -1; // failure
  }

  /**
   * Given location of a close parenthesis, will search previous tokens
   * for corresponding open parenthesis. Will account for other close parentheses
   * that it encounters and their corresponding open parentheses.
   * Declared static so it can be used from DoubleClickCaret.
   *
   * @return location of open paren in document, or -1 if none found
   */
  int findOpener(Colorizer<TokenType> colorizer, List<TokenType> tokens, int closer) {
    int parenCount = 1;
    for (int i = closer - 1; i >= 0; i--) {
      if (colorizer.isCloser(tokens.get(i))) {
        parenCount++;
      } else if (colorizer.isOpener(tokens.get(i))) {
        parenCount--;
      }
      if (parenCount == 0) {
        return i;
      }
    }
    return -1; // failure
  }

  /// methods for creating & removing highlights

  private void removeOldHighlights(Highlighter highlighter) {
    // we can't just remove all highlights because when you have a
    // selection, that's considered a highlight as well, so we need
    // to look for our own highlights
    Highlighter.Highlight[] highlights = highlighter.getHighlights();
    for (int i = 0; i < highlights.length; i++) {
      if (highlights[i].getPainter() == goodPainter ||
          highlights[i].getPainter() == badPainter) {
        highlighter.removeHighlight(highlights[i]);
      }
    }
  }

  private void highlightGood(Highlighter highlighter, int pos)
      throws BadLocationException {
    highlighter.addHighlight
        (pos, pos + 1, goodPainter);
  }

  private void highlightBad(Highlighter highlighter, int pos)
      throws BadLocationException {
    highlighter.addHighlight(pos, pos + 1, badPainter);
  }

  ///

  private class BracketHighlightPainter
      implements Highlighter.HighlightPainter {
    private final java.awt.Color color;

    BracketHighlightPainter(java.awt.Color color) {
      this.color = color;
    }

    public void paint(java.awt.Graphics g, int p0, int p1,
                      java.awt.Shape bounds, JTextComponent c) {
      try {
        g.setColor(color);
        java.awt.Rectangle rect =
            c.getUI().modelToView(c, p0)
                .union
                    (c.getUI().modelToView(c, p1));
        g.drawRect(rect.x, rect.y,
            rect.width - 1, rect.height - 1);
      } catch (javax.swing.text.BadLocationException ex) {
        throw new IllegalStateException(ex);
      }
    }
  }

}
TOP

Related Classes of org.nlogo.editor.BracketMatcher$BracketHighlightPainter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.