Vou postar aqui a classe daquele editor que faz recursos como "find/replace". Como eu te falei, não é um recurso trivial:
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JScrollPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import TextUtils;
public class SearchMachine
{
class Index
{
int p1;
int p2;
public Index(int p1, int p2)
{
this.p1 = p1;
this.p2 = p2;
}
}
private HighlighterManager hilighter;
private JTextComponent txtComponent;
private JScrollPane scrlPane;
private String word;
private Pattern pattern;
private boolean isPattern;
private boolean caseSensitive;
public SearchMachine(JTextComponent txtComponent, String word)
{
setTxtComponent(txtComponent);
hilighter = new HighlighterManager(txtComponent);
this.word = word;
this.isPattern = false;
this.caseSensitive = false;
}
public SearchMachine(JTextComponent txtComponent, Pattern pattern)
{
setTxtComponent(txtComponent);
hilighter = new HighlighterManager(txtComponent);
this.word = pattern.pattern();
this.pattern = pattern;
this.isPattern = true;
this.caseSensitive = false;
}
public void setScrollPane(JScrollPane scrl)
{
this.scrlPane = scrl;
}
public void setCaseSensitive(boolean status)
{
this.caseSensitive = status;
}
public boolean isCaseSensitive()
{
return caseSensitive;
}
private void setTxtComponent(JTextComponent txtComponent2)
{
if (txtComponent2 == null)
throw new IllegalArgumentException("Text component cannot be null!");
this.txtComponent = txtComponent2;
}
private void addInList(List<Integer> list, int line)
{
if (!list.contains(line))
list.add(line);
}
public List<Integer> find()
{
return find(0);
}
public List<Integer> find(int start)
{
if (isPattern)
return findPattern(start, false);
return findForward(start);
}
private List<Integer> findForward(int start)
{
if (word == null)
throw new IllegalStateException("word is null!");
String targetWord;
if (isCaseSensitive())
targetWord = word;
else
targetWord = word.toUpperCase();
// First remove all old highlights
hilighter.removeHighlights();
List<Integer> lines = new ArrayList<Integer>();
List<Index> indexes = new ArrayList<Index>();
String text = isCaseSensitive() ? getText(0) : getText(0).toUpperCase();
boolean firstCicle = true;
int pos = text.indexOf(targetWord, start);
// if didn't find from start, search from the begining
if (pos == -1)
pos = text.indexOf(targetWord, 0);
while (pos != -1)
{
// Create highlighter using private painter and apply around
Index i = new Index(pos, pos + word.length());
hilighter.highlight(i.p1, i.p2);
indexes.add(i);
addInList(lines, getLine(i.p1));
pos = text.indexOf(targetWord, i.p2);
// if end file has been reached
if (pos == -1)
{
if (firstCicle)
{
// restart in the begin
firstCicle = false;
pos = text.indexOf(word, 0);
}
}
else if (!firstCicle)
{
// when reached the end and so reach where had started.
if (pos > start)
pos = -1;
}
}
if (!indexes.isEmpty())
{
select(indexes.get(0));
}
return lines;
}
private List<Integer> findBackward(int start)
{
if (word == null)
throw new IllegalStateException();
String targetWord;
if (isCaseSensitive())
targetWord = word;
else
targetWord = word.toUpperCase();
final int realOffset = start - word.length() - 1;
// First remove all old highlights
hilighter.removeHighlights();
List<Integer> lines = new ArrayList<Integer>();
List<Index> indexes = new ArrayList<Index>();
String text = isCaseSensitive() ? getText(0) : getText(0).toUpperCase();
boolean firstCicle = true;
int pos = text.lastIndexOf(targetWord, realOffset);
// if didn't find from start, search from the begining
if (pos == -1)
pos = text.lastIndexOf(targetWord, text.length());
while (pos != -1)
{
// Create highlighter using private painter and apply around
Index i = new Index(pos, pos + targetWord.length());
hilighter.highlight(i.p1, i.p2);
indexes.add(i);
addInList(lines, getLine(i.p1));
pos = text.lastIndexOf(targetWord, pos - targetWord.length() - 1);
// if end file has been reached
if (pos == -1)
{
if (firstCicle)
{
// restart in the begining
firstCicle = false;
pos = text.lastIndexOf(targetWord, text.length());
}
}
else if (!firstCicle)
{
// when reached the end and so reach where had started.
if (pos < realOffset)
pos = -1;
}
}
if (!indexes.isEmpty())
select(indexes.get(0));
return lines;
}
public List<Integer> findPattern(int start, boolean back)
{
if (pattern == null)
throw new IllegalStateException();
// First remove all old highlights
hilighter.removeHighlights();
List<Integer> lines = new ArrayList<Integer>();
List<Index> indexes = new ArrayList<Index>();
Matcher matcher = pattern.matcher(getText(0));
while (matcher.find())
{
Index i = new Index(matcher.start(), matcher.end());
hilighter.highlight(i.p1, i.p2);
indexes.add(i);
addInList(lines, getLine(i.p1));
}
if (!indexes.isEmpty())
{
if (back)
select(getPreviousIndex(indexes, start));
else
select(getNextIndex(indexes, start));
}
return lines;
}
private Index getNextIndex(List<Index> indexes, int start)
{
for (Index i : indexes)
{
if (i.p1 > start)
return i;
}
return indexes.get(0);
}
private Index getPreviousIndex(List<Index> indexes, int start)
{
for (int i = indexes.size() - 1; i >= 0; i--)
{
Index index = indexes.get(i);
if (index.p1 < start)
return index;
}
return indexes.get(0);
}
protected int getCaretPosition()
{
return txtComponent.getCaretPosition();
}
public void next()
{
if (isPattern)
findPattern(getCaretPosition(), false);
else
findForward(getCaretPosition());
}
public void previous()
{
if (isPattern)
findPattern(getCaretPosition(), true);
else
findBackward(getCaretPosition());
}
private String getText(int start)
{
try
{
Document doc = txtComponent.getDocument();
return doc.getText(start, doc.getLength() - start);
}
catch (BadLocationException e)
{
EditorLog.thrown(getClass(), "getText", e);
}
return "";
}
private synchronized void select(Index index)
{
select(index.p1, index.p2);
}
private synchronized void select(int posStart, int posEnd)
{
txtComponent.setCaretPosition(posStart);
txtComponent.moveCaretPosition(posEnd);
txtComponent.setSelectedTextColor(Color.RED);
if (scrlPane != null)
TextUtils.scrollToVisible(scrlPane, posStart);
}
private void replaceSelected(final String newString)
{
if (isPattern)
{
replacePattern(newString);
}
else
{
txtComponent.replaceSelection(newString);
}
}
public void replaceNext(String newString)
{
replaceSelected(newString);
find(getCaretPosition());
}
public void replaceAll(String newString)
{
if (isPattern)
replaceAllPattern(newString);
else
replaceAllSimple(newString);
}
private void replaceAllSimple(String newString)
{
int pos, end = 0;
String text = getText(0);
while ((pos = text.indexOf(word, end)) != -1)
{
end = pos + word.length();
select(pos, end);
replaceSelected(newString);
text = getText(0);
}
}
private void replaceAllPattern(String newString)
{
Matcher matcher = pattern.matcher(getText(0));
while (matcher.find())
{
select(matcher.start(), matcher.end());
replacePattern(newString);
matcher = pattern.matcher(getText(0));
}
}
private void replacePattern(String newString)
{
String strNewText = newString;
String selected = txtComponent.getSelectedText();
Matcher matcher = pattern.matcher(selected);
if (matcher.find())
{
Matcher m = Pattern.compile("\\$[0-9]+").matcher(newString);
while (m.find())
{
int group = Integer.parseInt(m.group().substring(1));
String text = matcher.group(group);
if (text != null)
strNewText = strNewText.replaceAll("\\$" + group, text);
}
}
txtComponent.replaceSelection(strNewText);
}
private int getLine(int offset)
{
return TextUtils.getLine(txtComponent, offset);
}
}
Ele também usa essa classe:
/**
* Util static methods to becomes easy operation with Text Components. All
* functionalities are available to JTextComponent as well as a Document.
*
* @author Farina, Andre
*/
public class TextUtils
{
/**
* Returns the line number of the caret in a text component.
*
* @param txtCmp Text Component
* @return Line number
*/
public static int getCaretLine(JTextComponent txtCmp)
{
return getLine(txtCmp.getDocument(), txtCmp.getCaretPosition());
}
/**
* Given an offset, returns the line number of a text component.
*
* @param txtCmp Text Component
* @param offset Offset position
* @return Line number starting in 0.
*/
public static int getLine(JTextComponent txtCmp, int offset)
{
return getLine(txtCmp.getDocument(), offset);
}
/**
* Given an offset, returns the line number in a text component.
*
* @param doc JTextComponent document
* @param offset Offset position
* @return Line number starting in 0.
*/
public static int getLine(Document doc, int offset)
{
Element root = doc.getDefaultRootElement();
return root.getElementIndex(offset);
}
/**
* Given an offset, returns the column number in a text component.
*
* @param offset Offset position
* @return Column number starting in 0.
*/
public static int getCaretColumn(JTextComponent txtCmp)
{
return getColumn(txtCmp, txtCmp.getCaretPosition());
}
/**
* Given an offset, returns the column number in a text component.
*
* @param offset Offset position
* @return Column number starting in 0.
*/
public static int getColumn(JTextComponent txtCmp, int offset)
{
return txtCmp.getCaretPosition() - getOffsetStartLine(txtCmp, offset);
}
/**
* Given an offset, returns the offset where the line begins.
*
* @param offset Offset position.
* @return Offset where the line begins.
*/
public static int getOffsetStartLine(JTextComponent txtCmp, int offset)
{
return getOffsetStartLine(txtCmp.getDocument(), offset);
}
/**
* Given an offset, returns the offset where the line begins.
*
* @param doc JTextComponent document
* @param offset Offset position.
* @return Offset where the line begins.
*/
public static int getOffsetStartLine(Document doc, int offset)
{
int line = getLine(doc, offset);
return getWhereLineStarts(doc, line);
}
/**
* Given an offset, returns the offset where the line ends.
*
* @param offset Offset position.
* @return Offset where the line ends.
*/
public static int getOffsetEndLine(JTextComponent txtCmp, int offset)
{
return getOffsetEndLine(txtCmp.getDocument(), offset);
}
/**
* Given an offset, returns the offset where the line ends.
*
* @param doc JTextComponent document
* @param offset Offset position.
* @return Offset where the line ends.
*/
public static int getOffsetEndLine(Document doc, int offset)
{
int line = getLine(doc, offset);
return getWhereLineEnds(doc, line);
}
/**
* Given a line returns the offset number where the line starts.
*
* @param line Line number
* @return Offset number
*/
public static int getWhereLineStarts(JTextComponent txtCmp, int line)
{
return getWhereLineStarts(txtCmp.getDocument(), line);
}
/**
* Given a line returns the offset number where the line starts.
*
* @param doc JTextComponent document
* @param line Line number
* @return Offset number
*/
public static int getWhereLineStarts(Document doc, int line)
{
Element el = doc.getDefaultRootElement().getElement(line);
if (el != null)
return el.getStartOffset();
return doc.getStartPosition().getOffset();
}
/**
* Given a line returns the offset number where the line ends.
*
* @param line Line number
* @return Offset number
*/
public static int getWhereLineEnds(JTextComponent txtCmp, int line)
{
return getWhereLineEnds(txtCmp.getDocument(), line);
}
/**
* Given a line returns the offset number where the line ends.
*
* @param doc JTextComponent document
* @param line Line number
* @return Offset number
*/
public static int getWhereLineEnds(Document doc, int line)
{
Element el = doc.getDefaultRootElement().getElement(line);
if (el != null)
return el.getEndOffset();
return doc.getEndPosition().getOffset();
}
/**
* Count the line number.
*
* @return Amount of lines.
*/
public static int countLines(JTextComponent txtCmp)
{
return countLines(txtCmp.getDocument());
}
/**
* Count the line number.
*
* @param doc JTextComponent document
* @return Amount of lines.
*/
public static int countLines(Document doc)
{
return doc.getDefaultRootElement().getElementCount();
}
/**
* Get number of rows in the content being shown. Independent of the text's
* size.
*
* @return Amount of rows.
*/
public static int getRows(JTextComponent txtCmp)
{
FontMetrics fm = txtCmp.getFontMetrics(txtCmp.getFont());
int fontHeight = fm.getHeight();
int ybaseline = txtCmp.getY() + fm.getAscent();
int yend = ybaseline + txtCmp.getHeight();
int rows = 1;
while (ybaseline < yend)
{
ybaseline += fontHeight;
rows++;
}
return rows;
}
/**
* Get first visible line of the text component in a JScrollPane.
*
* @param txtCmp Text component.
* @return The line.
*/
public static int getTopLine(JTextComponent txtCmp)
{
FontMetrics fm = txtCmp.getFontMetrics(txtCmp.getFont());
int fontHeight = fm.getHeight();
return (Math.abs(txtCmp.getY()) + fm.getAscent()) / fontHeight + 1;
}
/**
* Get last visible line of the text component in a JScrollPane.
*
* @param txtCmp Text component.
* @return The line.
*/
public static int getBottomLine(JTextComponent txtCmp)
{
FontMetrics fm = txtCmp.getFontMetrics(txtCmp.getFont());
int fontHeight = fm.getHeight();
View view = txtCmp.getUI().getRootView(txtCmp).getView(0);
int height = view.getContainer().getParent().getHeight();
return (Math.abs(txtCmp.getY()) + fm.getAscent() + height) / fontHeight;
}
/**
* Scroll the pane until offset becomes visible.
*
* @param scroll
* @param offset
*/
public static void scrollToVisible(final JScrollPane scroll, int offset)
{
if (!(scroll.getViewport().getView() instanceof JTextComponent))
return;
JTextComponent txtCmp = (JTextComponent) scroll.getViewport().getView();
Element root = txtCmp.getDocument().getDefaultRootElement();
int line = root.getElementIndex(offset);
scrollLineToVisible(scroll, line);
}
/**
* Scroll the pane until line becomes visible.
*
* @param scroll
* @param line
*/
public static void scrollLineToVisible(final JScrollPane scroll, int line)
{
if (!(scroll.getViewport().getView() instanceof JTextComponent))
return;
JTextComponent txtCmp = (JTextComponent) scroll.getViewport().getView();
FontMetrics fm = txtCmp.getFontMetrics(txtCmp.getFont());
int fontHeight = fm.getHeight();
scroll.getViewport().setViewPosition(new Point(0, fontHeight*line));
}
public static String generateEscapeRegex(String text)
{
text = text.replaceAll("\\\\", "\\\\\\\\");
text = text.replaceAll("\\.", "\\\\.");
text = text.replaceAll("\\*", "\\\\*");
text = text.replaceAll("\\?", "\\\\?");
text = text.replaceAll("\\(", "\\\\(");
text = text.replaceAll("\\)", "\\\\)");
text = text.replaceAll("\\[", "\\\\[");
text = text.replaceAll("\\]", "\\\\]");
text = text.replaceAll("\\{", "\\\\{");
text = text.replaceAll("\\}", "\\\\}");
text = text.replaceAll("\\^", "\\\\^");
text = text.replaceAll("\\$", "\\\\\\$");
text = text.replaceAll("\\+", "\\\\+");
text = text.replaceAll("\\|", "\\\\|");
return text;
}
}
Manipular documentos com formatação é uma tarefa longe de ser trivial. Se você quer fazer isso, estude o StyledDocument, e entenda exatamente como funciona.