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

dev.robocode.tankroyale.gui.ansi.AnsiEditorKit.kt Maven / Gradle / Ivy

package dev.robocode.tankroyale.gui.ansi

import dev.robocode.tankroyale.gui.ansi.AnsiAttributesExt.updateAnsi
import dev.robocode.tankroyale.gui.ansi.esc_code.CommandCode
import dev.robocode.tankroyale.gui.ansi.esc_code.EscapeSequence
import java.awt.Color
import java.io.*
import javax.swing.text.*

/**
 * The AnsiEditorKit is a specialized [StyledEditorKit] that is able to create [StyledDocument]s based on text
 * containing ANSI escape sequences for styling the text.
 * The documents are created with the Monospaced font to simulate an old-fashioned text console for displaying ANSI
 * graphics.
 * @param fontSize is the monospaced font size to use across an entire document. Default is 14.
 * @param ansiColors is the [IAnsiColors] to use for the ANSI color schema. Default is the [DefaultAnsiColors] color
 * scheme.
 */
class AnsiEditorKit(
    private val fontSize: Int = 14,
    private val ansiColors: IAnsiColors = DefaultAnsiColors
) : StyledEditorKit() {

    private val ansiEscCodeRegex = Regex("\u001b\\[\\d+(;\\d+)*m")

    override fun getContentType() = "text/x-ansi"

    /** {@inheritDoc} */
    override fun read(inputStream: InputStream, doc: Document, pos: Int) {
        read(BufferedReader(InputStreamReader(inputStream)), doc, pos)
    }

    /** {@inheritDoc} */
    override fun read(reader: Reader, doc: Document, pos: Int) {
        require(doc is StyledDocument) { "The document must be a StyledDocument for this kit" }
        insertAnsi(doc, reader.readText(), pos)
    }

    /** {@inheritDoc} */
    override fun write(outputStream: OutputStream, doc: Document, pos: Int, len: Int) {
        write(BufferedWriter(OutputStreamWriter(outputStream)), doc, pos, len)
    }

    /** {@inheritDoc} */
    override fun write(writer: Writer, doc: Document, pos: Int, len: Int) {
        writer.write(doc.getText(pos, len))
    }

    /**
     * Inserts an ANSI text into a specific text position of the document.
     * ANSI escape codes are converted into [AttributeSet]s to style the inserted text.
     * @param doc is a [StyledDocument] the ANSI text is inserted into.
     * @param ansiText is the ANSI text to insert into the document.
     * @param offset is the offset into the document where the text will be inserted.
     */
    fun insertAnsi(doc: StyledDocument, ansiText: String, offset: Int = doc.length) {
        require(offset >= 0) { "Offset cannot be negative. Was: $offset" }

        var attributes: MutableAttributeSet = SimpleAttributeSet(doc.getCharacterElement(offset).attributes)
        StyleConstants.setFontFamily(attributes, "Monospaced")
        StyleConstants.setFontSize(attributes, fontSize)

        // Set the foreground color to the default ANSI color if no foreground color has been set previously
        if (StyleConstants.getForeground(attributes) == Color.black) { // if no foreground color is set, black is returned?!
            attributes = attributes.updateAnsi(EscapeSequence(CommandCode.DEFAULT), ansiColors)
        }

        val match = ansiEscCodeRegex.find(ansiText, 0)
        if (match == null) {
            doc.insertString(offset, ansiText, attributes) // no ansi codes found
            return
        }

        var codeStart = match.range.first

        var text = ansiText.substring(0, codeStart)
        if (text.isNotEmpty()) {
            doc.insertString(0, text, attributes) // no ansi codes found
        }

        ansiEscCodeRegex.findAll(ansiText, codeStart).forEach { m ->
            val ansiCode = m.value
            codeStart = m.range.first
            val codeEnd = m.range.last + 1

            attributes = attributes.updateAnsi(EscapeSequence.parse(ansiCode), ansiColors)

            val endMatch = ansiEscCodeRegex.find(ansiText, codeEnd)

            text = if (endMatch == null) {
                ansiText.substring(codeEnd)
            } else {
                ansiText.substring(codeEnd, endMatch.range.first)
            }
            if (text.isNotEmpty()) {
                doc.insertString(doc.length, text, attributes)
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy