Enhanced JTextArea with Color Text Support

Published: 2026-06-07
Google AdSense 广告位 (待申请通过后开启)

Java Swing JTextArea with Multi-Color Text Support

This enhanced JTextArea component extends the standard Swing JTextArea by adding support for multi-color text rendering, custom highlighting, and syntax-aware display.

Key features:

• Render text in multiple colors

• Custom keyword highlighting

• Lightweight and easy to integrate

• Compatible with existing Swing applications

• Suitable for logs, terminals, editors, and monitoring tools

package com.util; // Ensure this matches your project's package name

import javax.swing.JTextPane;
import javax.swing.text.*;
import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// Disguised as JTextArea, it actually extends JTextPane to support colors
public class JTextArea extends JTextPane {

    public JTextArea() {
        setEditable(false); // Set to non-editable
    }

    // Key: Override the append method to perfectly support inline ANSI color codes and Git diff line colors
    public void append(String text) {
        if (text == null) return;

        // 1. Filter out common non-color cursor control codes used in terminals (e.g., clear line [K)
        text = text.replaceAll("\u001B\\[[0-9;]*[KkHhGgA-DzZ]", "");
        // 2. Unify carriage returns \r into standard newlines
        text = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n");

        StyledDocument doc = this.getStyledDocument();
        String[] lines = text.split("\n", -1); // Ensure trailing empty lines are not ignored

        for (int i = 0; i < lines.length; i++) {
            String line = lines[i];
            
            // Prioritize checking Git Diff status (line-wide logic)
            Color rowBaseColor = null;
            String trimmed = line.trim();
            // Safely strip any ANSI encoding that might exist at the start of the line for prefix checking
            String cleanTrimmed = trimmed.replaceAll("\u001B\\[[0-9;]*m", "");
            
            if (cleanTrimmed.startsWith("+") && !cleanTrimmed.startsWith("+++")) {
                rowBaseColor = new Color(0, 140, 0); // Green
            } else if (cleanTrimmed.startsWith("-") && !cleanTrimmed.startsWith("---")) {
                rowBaseColor = Color.RED; // Red
            } else if (cleanTrimmed.startsWith("@@")) {
                rowBaseColor = Color.BLUE; // Blue
            }

            // Parse inline ANSI color codes (e.g., \u001B[31m)
            parseAndInsertAnsiLine(doc, line, rowBaseColor);

            // If it is not the last line, append the trailing newline character
            if (i < lines.length - 1) {
                try {
                    SimpleAttributeSet nextLineSet = new SimpleAttributeSet();
                    if (rowBaseColor != null) {
                        StyleConstants.setForeground(nextLineSet, rowBaseColor);
                    } else {
                        StyleConstants.setForeground(nextLineSet, Color.BLACK);
                    }
                    doc.insertString(doc.getLength(), "\n", nextLineSet);
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
            }
        }

        // Automatically scroll to the bottom of the text
        setCaretPosition(doc.getLength());
    }

    /**
     * Parses ANSI color control codes within a single line and inserts segments sequentially
     */
    private void parseAndInsertAnsiLine(StyledDocument doc, String line, Color rowBaseColor) {
        // Regex to match \u001B[xxxx m
        Pattern pattern = Pattern.compile("\u001B\\[([0-9;]*)m");
        Matcher matcher = pattern.matcher(line);

        int lastIndex = 0;
        // Initial attributes: If there is a line-wide base color (like Git diff), use it. Otherwise, default to black.
        SimpleAttributeSet currentAttributes = new SimpleAttributeSet();
        Color currentColor = (rowBaseColor != null) ? rowBaseColor : Color.BLACK;
        StyleConstants.setForeground(currentAttributes, currentColor);

        while (matcher.find()) {
            // 1. Insert the regular text before the control code
            String textSegment = line.substring(lastIndex, matcher.start());
            if (!textSegment.isEmpty()) {
                try {
                    doc.insertString(doc.getLength(), textSegment, currentAttributes);
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
            }

            // 2. Parse the control code arguments and update the current color state
            String ansiCode = matcher.group(1);
            currentColor = getAnsiColor(ansiCode, currentColor, rowBaseColor);
            currentAttributes = new SimpleAttributeSet();
            StyleConstants.setForeground(currentAttributes, currentColor);

            lastIndex = matcher.end();
        }

        // 3. Insert the remaining text of the line
        String remainingText = line.substring(lastIndex);
        if (!remainingText.isEmpty()) {
            try {
                doc.insertString(doc.getLength(), remainingText, currentAttributes);
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Converts ANSI codes into the corresponding Java Color object
     */
    private Color getAnsiColor(String ansiCode, Color currentColor, Color rowBaseColor) {
        if (ansiCode == null || ansiCode.isEmpty() || "0".equals(ansiCode)) {
            // 0 stands for Reset: Roll back to the line-wide base color if it exists, otherwise roll back to black
            return (rowBaseColor != null) ? rowBaseColor : Color.BLACK;
        }

        // Supports multi-parameter combinations like "31;1" (Red and Bold). We split to extract color values.
        String[] codes = ansiCode.split(";");
        for (String code : codes) {
            switch (code) {
                case "30": return Color.BLACK;
                case "31": return Color.RED;        // Common search highlight red
                case "32": return new Color(0, 140, 0); // Green
                case "33": return Color.ORANGE;     // Yellow/Orange
                case "34": return Color.BLUE;       // Blue
                case "35": return Color.MAGENTA;    // Magenta
                case "36": return Color.CYAN;       // Cyan
                case "37": return Color.LIGHT_GRAY; // Light Gray
                case "39": return (rowBaseColor != null) ? rowBaseColor : Color.BLACK; // Default foreground
                // You can expand bright colors (90-97) or background colors here if needed
            }
        }
        return currentColor;
    }

    // Backward compatibility for the original setText method
    @Override
    public void setText(String t) {
        super.setText(""); // Clear first
        if (t != null && !t.isEmpty()) {
            append(t); // Trigger colorized append
        }
    }

    public void setLineWrap(boolean b) {
        // Placeholder method for structural compatibility
    }
}

use:


this.textArea = new JTextArea();
this.textArea.setLineWrap(true);
this.textArea.setFont(new Font("Dialog", 1, 22));
this.textArea.setSelectedTextColor(Color.RED);

String s= "ls -al /usr/local/bin/";
execToTextArea(final String s, final com.util.JTextArea textArea);

INFO     Application started
SUCCESS  Connected to database
WARNING  Memory usage is high
ERROR    Connection timeout
DEBUG    Loading configuration

Explore More

Technology Guides →

How to Send Colored Text to JTextArea Using execToTextArea

Southeast Asia Insights →