This is both simple and complicated based on what you want to achieve, first however, some "do nots":
- Don't use
KeyListener
or key bindings with text components if you intend to modify the text of the field, neither of these are notified when the user pastes test
- Don't use a
DocumentListener
as this can generate an infinite loop and cause mutation exceptions in the Document
Okay, so what should you use? A DocumentFilter
, which allows you to modify the text been passed into the underlying Document
of the text component. See Implementing a Document Filter for more details.
The next problem is, generally speaking as a user, I'd prefer if you didn't modify the position of my cursor (unless I want you to ;)). Now, in this case, it would be rather rude to insert a }
and have the cursor move to the right hand side of it, because now I have to move the cursor back and that just breaks the work flow.
This is a conundrum, as the DocumentFilter
has no idea about the text component (and nor should it), so we need some way for the filter to tell us that it's about to apply a change and has applied a change.
To this end, we can create a custom DocumentFilter
which has a listener call back functionality, which can notify us that it's about to apply a "auto complete" change and that is has completed a "auto complete" change. This gives us two points in time, before and after the change, so we can grab the information we need in order to control the cursor...
public interface AutoCompleteListener {
public void willAutoComplete(SyntaxFilter filter);
public void didAutoComplete(SyntaxFilter filter);
}
public class SyntaxFilter extends DocumentFilter {
private List<AutoCompleteListener> listeners;
public SyntaxFilter() {
listeners = new ArrayList<>(25);
}
public void addAutoCompleteListener(AutoCompleteListener listener) {
listeners.add(listener);
}
public void removeAutoCompleteListener(AutoCompleteListener listener) {
listeners.add(listener);
}
protected void fireWillAutoComplete() {
for (AutoCompleteListener listener : listeners) {
listener.willAutoComplete(this);
}
}
protected void fireDidAutoComplete() {
for (AutoCompleteListener listener : listeners) {
listener.didAutoComplete(this);
}
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, text, attrs); //To change body of generated methods, choose Tools | Templates.
if (text.endsWith("{")) {
fireWillAutoComplete();
fb.insertString(fb.getDocument().getLength(), "}", attrs);
fireDidAutoComplete();
}
}
}
Now, there are a couple of ways I can think of to control the cursor position, you could simply grab the current position before the change and reset it after the change, this will work, but you might find it generates a small rendering flick as the cursor is flung about the place. A alternative is simply to stop the cursor from been moved when an update occurs, for example...

JTextArea editor = new JTextArea(10, 20);
SyntaxFilter filter = new SyntaxFilter();
filter.addAutoCompleteListener(new AutoCompleteListener() {
private int updatePolicy = DefaultCaret.UPDATE_WHEN_ON_EDT;
@Override
public void willAutoComplete(SyntaxFilter filter) {
updatePolicy = ((DefaultCaret) editor.getCaret()).getUpdatePolicy();
((DefaultCaret) editor.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
@Override
public void didAutoComplete(SyntaxFilter filter) {
((DefaultCaret) editor.getCaret()).setUpdatePolicy(updatePolicy);
}
});
((AbstractDocument) editor.getDocument()).setDocumentFilter(filter);