All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.common.highlight.SyntaxHighlighter Maven / Gradle / Ivy

package org.bidib.wizard.common.highlight;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.io.IOException;
import java.io.Reader;

import javax.swing.JTextPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Segment;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

// Public domain, no restrictions, Ian Holyer, University of Bristol.

/**
 * Display text with syntax highlighting. Highlighting is done with full accuracy, using a given language scanner. Large
 * amounts of re-highlighting are done in small bursts to make sure the user interface doesn't freeze.
 */
public class SyntaxHighlighter extends JTextPane implements DocumentListener, TokenTypes {
    /**
     * serial version uid
     */
    private static final long serialVersionUID = 1L;

    private StyledDocument doc;

    private final Scanner scanner;

    private final int height;

    private final int width;

    private boolean stopHighlighting;

    /**
     * Create a graphics component which displays text with syntax highlighting. Provide a width and height, in
     * characters, and a language scanner.
     */
    public SyntaxHighlighter(int height, int width, Scanner scanner) {
        super(new DefaultStyledDocument());
        doc = (StyledDocument) getDocument();
        this.height = height;
        this.width = width;
        this.scanner = scanner;
        doc.addDocumentListener(this);
        Font font = new Font("Monospaced", Font.PLAIN, getFont().getSize());
        changeFont(font);
        initStyles();
    }

    /**
     * @param stopHighlighting
     *            the stopHighlighting to set
     */
    public void setStopHighlighting(boolean stopHighlighting) {
        if (this.stopHighlighting != stopHighlighting) {
            int length = doc.getLength();
            firstRehighlightToken = scanner.change(0, 0, length);
            repaint();
        }
        this.stopHighlighting = stopHighlighting;
    }

    /**
     * @return the stopHighlighting flag
     */
    public boolean isStopHighlighting() {
        return this.stopHighlighting;
    }

    /**
     * Change the component's font, and change the size of the component to match.
     */
    public void changeFont(Font font) {
        int borderOfJTextPane = 3;
        setFont(font);
        FontMetrics metrics = getFontMetrics(font);
        int paneWidth = width * metrics.charWidth('m') + 2 * borderOfJTextPane;
        int paneHeight = height * metrics.getHeight() + 2 * borderOfJTextPane;
        Dimension size = new Dimension(paneWidth, paneHeight);
        setMinimumSize(size);
        setPreferredSize(size);
        invalidate();
    }

    /**
     * Read new text into the component from a Reader. Overrides read in
     * JTextComponent in order to highlight the new text.
     */
    @Override
    public void read(Reader in, Object desc) throws IOException {
        int oldLength = getDocument().getLength();
        doc.removeDocumentListener(this);
        super.read(in, desc);
        doc = (StyledDocument) getDocument();
        doc.addDocumentListener(this);
        int newLength = getDocument().getLength();
        firstRehighlightToken = scanner.change(0, oldLength, newLength);
        repaint();
    }

    // An array of styles, indexed by token type. Default styles are set up,
    // which can be used for any languages.

    Style[] styles;

    private void initStyles() {
        styles = new Style[TYPENAMES.length];
        changeStyle(UNRECOGNIZED, Color.red);
        changeStyle(WHITESPACE, Color.black);
        changeStyle(WORD, Color.black);
        changeStyle(NUMBER, Color.orange.darker());
        changeStyle(PUNCTUATION, Color.orange.darker());
        changeStyle(COMMENT, Color.green.darker());
        changeStyle(START_COMMENT, Color.green.darker());
        changeStyle(MID_COMMENT, Color.green.darker());
        changeStyle(END_COMMENT, Color.green.darker());
        changeStyle(TAG, Color.blue, Font.BOLD);
        changeStyle(END_TAG, Color.blue, Font.BOLD);
        changeStyle(KEYWORD, Color.blue);
        changeStyle(KEYWORD2, Color.gray);
        changeStyle(IDENTIFIER, Color.black);
        changeStyle(LITERAL, Color.magenta);
        changeStyle(STRING, Color.magenta);
        changeStyle(CHARACTER, Color.magenta);
        changeStyle(OPERATOR, Color.black, Font.BOLD);
        changeStyle(BRACKET, Color.orange.darker());
        changeStyle(SEPARATOR, Color.orange.darker());
        changeStyle(URL, Color.blue.darker());
        changeStyle(VARIABLE, Color.magenta.darker());

        for (int i = 0; i < styles.length; i++) {
            if (styles[i] == null) {
                styles[i] = styles[WHITESPACE];
            }
        }
    }

    /**
     * Change the style of a particular type of token.
     */
    public void changeStyle(int type, Color color) {
        Style style = addStyle(TYPENAMES[type], null);
        StyleConstants.setForeground(style, color);
        styles[type] = style;
    }

    /**
     * Change the style of a particular type of token, including adding bold or italic using a third argument of
     * Font.BOLD or Font.ITALIC or the bitwise union Font.BOLD|Font.ITALIC.
     */
    public void changeStyle(int type, Color color, int fontStyle) {
        Style style = addStyle(TYPENAMES[type], null);
        StyleConstants.setForeground(style, color);
        if ((fontStyle & Font.BOLD) != 0) {
            StyleConstants.setBold(style, true);
        }
        if ((fontStyle & Font.ITALIC) != 0) {
            StyleConstants.setItalic(style, true);
        }
        styles[type] = style;
    }

    /**
     * Ignore this method. Responds to the underlying document changes by
     * re-highlighting.
     * */
    public void insertUpdate(DocumentEvent e) {
        int offset = e.getOffset();
        int length = e.getLength();
        firstRehighlightToken = scanner.change(offset, 0, length);
        repaint();
    }

    /**
     * Ignore this method. Responds to the underlying document changes by
     * re-highlighting.
     * */
    public void removeUpdate(DocumentEvent e) {
        int offset = e.getOffset();
        int length = e.getLength();
        firstRehighlightToken = scanner.change(offset, length, 0);
        repaint();
    }

    /**
     * Ignore this method. Responds to the underlying document changes by
     * re-highlighting.
     * */
    public void changedUpdate(DocumentEvent e) {
        // Do nothing.
    }

    // Scan a small portion of the document. If more is needed, call repaint()
    // so the GUI gets a go and doesn't freeze, but calls this again later.

    Segment text = new Segment();

    int firstRehighlightToken;

    int smallAmount = 100;

    /**
     * Ignore this method. Carries out a small amount of re-highlighting for each call to
     * repaint.
     */
    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent(g);

        int offset = scanner.position();
        if (offset < 0) {
            return;
        }

        int tokensToRedo = 0;
        int amount = smallAmount;
        while (tokensToRedo == 0 && offset >= 0) {
            int length = doc.getLength() - offset;
            if (length > amount) {
                length = amount;
            }
            try {
                doc.getText(offset, length, text);
            }
            catch (BadLocationException e) { // NOSONAR
                return;
            }
            tokensToRedo = scanner.scan(text.array, text.offset, text.count);
            offset = scanner.position();
            amount = 2 * amount;
        }
        for (int i = 0; i < tokensToRedo; i++) {
            Token t = scanner.getToken(firstRehighlightToken + i);
            int length = t.symbol.name.length();
            int type = t.symbol.type;
            if (type < 0) {
                type = UNRECOGNIZED;
            }
            if (stopHighlighting) {
                doc.setCharacterAttributes(t.position, length, styles[WHITESPACE], false);
            }
            else {
                doc.setCharacterAttributes(t.position, length, styles[type], false);
            }
        }
        firstRehighlightToken += tokensToRedo;
        if (offset >= 0) {
            repaint(2);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy