# JTextPane with line-numbers

What I wanted to do

As part of a larger project, I needed a simple text editor with line numbers. The editor should also be able to highlight the current line (line number in bold) and define the tab size.

The result looks like this:

My solution

After searching the internet for a while, I found out about the possibility of using a DocumentListener and adding a sepearate text-component with the line-numbers as RowHeader:

I also found this complete solution, but nevertheless implemented the class it on my own - for learning purposes.

My implementation of it looks like this:

import java.awt.Color;
import java.awt.Font;
import java.awt.Dimension;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

/**
*
* @author Philipp Wilhelm
* Provides a JScrollPane with line-numbers
*/
public class EditorScrollPane extends JScrollPane {

private static final long serialVersionUID = 1L;

private JTextPane inputArea;
private String indentation = "  ";
private JTextPane lineNumbers;

/*
* Here the constructor creates a TextPane as an editor-field and another TextPane for the
* line-numbers.
*/
public EditorScrollPane(int width, int height) {
// Editor-field
inputArea = new JTextPane();
inputArea.setPreferredSize(new Dimension(width, height));
Document doc = inputArea.getDocument();

// Replacing tabs with two spaces
((AbstractDocument) doc).setDocumentFilter(new DocumentFilter() {
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
super.insertString(fb, offset, text.replace("\t", indentation), attrs);
}
});

// Line-numbers
lineNumbers = new JTextPane();
lineNumbers.setBackground(Color.GRAY);
lineNumbers.setEditable(false);

// Line-numbers should be right-aligned
SimpleAttributeSet rightAlign = new SimpleAttributeSet();
StyleConstants.setAlignment(rightAlign, StyleConstants.ALIGN_RIGHT);
lineNumbers.setParagraphAttributes(rightAlign, true);

@Override
public void changedUpdate(DocumentEvent e) {
lineNumbers();
}

@Override
public void insertUpdate(DocumentEvent e) {
lineNumbers();
}

@Override
public void removeUpdate(DocumentEvent e) {
lineNumbers();
}
});
// Setting font
this.setFont(new Font("Monospaced", 12, Font.PLAIN));

// Sets the main-component in the JScrollPane. this.add(inputArea) wasn't
// enough in this case

// Adds lineNumbers as row header on the left side of the main JTextPane
}

private void lineNumbers() {
try {
String str = inputArea.getText();

// Plain Style
SimpleAttributeSet plain = new SimpleAttributeSet();
StyleConstants.setFontFamily(plain, "Monospaced");
StyleConstants.setFontSize(plain, 12);

// Bold style
SimpleAttributeSet bold = new SimpleAttributeSet();
StyleConstants.setBold(bold, true);

// Remove all from document
Document doc = lineNumbers.getDocument();
doc.remove(0, doc.getLength());

// Calculating the number of lines
int length = str.length() - str.replaceAll("\n", "").length() + 1;

for (int i = 1; i <= length; i++) {

// Non-bold line-numbers
if (i < length) {
doc.insertString(doc.getLength(), i + "\n", plain);

// Last line-number bold
} else {
doc.insertString(doc.getLength(), i + "\n", bold);
}
}
e.printStackTrace();
}
}

/*
* Setting indentation size in editor-field
*/
public void setIndentationSize(int size) {
String cache = indentation;
indentation = "";
for (int i = 0; i < size; i++) {
indentation += " ";
}
// Replace all previous indentations (at beginning of lines)
inputArea.setText(inputArea.getText().replaceAll(cache, indentation));
}

/*
* Overrides the method getText().
*/
public String getText() {
return inputArea.getText();
}

/*
* Overrides the method setText().
*/
public void setText(String str) {
inputArea.setText(str);
}
}


Minimal working example

If you want to test the class, you can use the following class to run the code:

// Just for testing purposes

import javax.swing.*;

public class Test {
public static void main(String[] args) {
// Frame
JFrame frame = new JFrame("Editor-Field");
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Panel
JPanel panel = new JPanel();
EditorScrollPane editor = new EditorScrollPane(400,400);
frame.pack();
frame.setVisible(true);
}
}


Questions

• How can this code be improved in general?
• Can the line-numbering be done in a more efficient way?
• Does this class miss anything important that you would expect from such a class?
• Lines (paragraphs) may vary in height (which would argue for a line number as part of an extra paragraph margin). You may use StyledDocument as using their attributes; the base class of HTML and RTF (Rich Text Format). Lighter background for line numbers, width based on log10(number of lines). Very nice beginning. – Joop Eggen Jul 8 '20 at 14:44

## Awkward Container

you create a composite component - the child items are

• JTextPane inputArea
• JTextPane lineNumbers

as container for these you use a JScrollPane - why is that so? why don't you use a simple container (like a JPanel) instead?

When you use a JScrollPane instead a mere JPanel you bring in more complexity than required. Additionally i would not expect any component to be a scrollPanel except a - well yes, except a JScrollPane itself...

## Inconsistent Font Handling

the font size of your LineNumberComponent should be equal to the EditorComponent font size - if they differ, the line numbers start to drift for each line a bit more

you set the font in the container (the JScrollPane) - that makes no sense in my opinion. (I think it's a typo and should be inputArea.setFont(...) )

// Setting font
this.setFont(new Font("Monospaced", 12, Font.PLAIN));


## method naming

• lineNumbers is not a verb but should be - maybe something like adjustLineNumbering?
• setIndentationSize does not, what a setter should do: set a value - instead rename it into what it really does: adjustIndentation

## Line Highlightning

you can get the cursors position by using the getCaretPosition to determine the selected line - even thou you'll have to do some math on your own. have a look at this answer on stackoverflow how you can make parts of a JTextArea in bold.