Undo/Redo Java?

  • Thread starter Andreas Wachhold
  • Start date
A

Andreas Wachhold

Hello, i wrote this little editor and it supports syntaxhighlighting.
Of course I want to add support for undo/redo via the undo package. So
far so good. Now i have 2 questions:

1. If you compile the code and run it, you will notice that undoing
and redoing is a bit slow (strg z or y). Can you see why?

2. If you write a line like "for(int i=0;i<10;i++)" and press strg z
the keywords are highlighted only a few times. Can you see why?

Thanks:)

Here is the code:

--------------
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import javax.swing.event.*;
import javax.swing.event.*;
import java.awt.event.*;


class jpe extends DefaultStyledDocument {

private DefaultStyledDocument doc;
private Element rootElement;

int index;

private boolean multiLineComment;
private MutableAttributeSet normal;
private MutableAttributeSet keyword;
private MutableAttributeSet comment;
private MutableAttributeSet quote;
private MutableAttributeSet method;
private MutableAttributeSet variable;


private Hashtable keywords;
private Hashtable methodhash;

public jpe() {
doc = this;
rootElement = doc.getDefaultRootElement();
putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");

normal = new SimpleAttributeSet();
StyleConstants.setForeground(normal, Color.black);

comment = new SimpleAttributeSet();
StyleConstants.setForeground(comment, Color.gray);
StyleConstants.setItalic(comment, true);

keyword = new SimpleAttributeSet();
StyleConstants.setForeground(keyword, Color.blue);

method = new SimpleAttributeSet();
StyleConstants.setForeground(method, Color.magenta);

variable = new SimpleAttributeSet();
StyleConstants.setForeground(method, Color.magenta);

quote = new SimpleAttributeSet();
StyleConstants.setForeground(quote, Color.red);

//Keywords like double, for, if ....
Object keywordObject = new Object();
keywords = new Hashtable();
keywords.put("abstract", keywordObject);
keywords.put("boolean", keywordObject);
keywords.put("break", keywordObject);
keywords.put("byte", keywordObject);
keywords.put("byvalue", keywordObject);
keywords.put("case", keywordObject);
keywords.put("cast", keywordObject);
keywords.put("catch", keywordObject);
keywords.put("char", keywordObject);
keywords.put("class", keywordObject);
keywords.put("const", keywordObject);
keywords.put("continue", keywordObject);
keywords.put("default", keywordObject);
keywords.put("do", keywordObject);
keywords.put("double", keywordObject);
keywords.put("else", keywordObject);
keywords.put("extends", keywordObject);
keywords.put("false", keywordObject);
keywords.put("final", keywordObject);
keywords.put("finally", keywordObject);
keywords.put("float", keywordObject);
keywords.put("for", keywordObject);
keywords.put("future", keywordObject);
keywords.put("generic", keywordObject);
keywords.put("goto", keywordObject);
keywords.put("if", keywordObject);
keywords.put("implements", keywordObject);
keywords.put("import", keywordObject);
keywords.put("inner", keywordObject);
keywords.put("instanceof", keywordObject);
keywords.put("int", keywordObject);
keywords.put("interface", keywordObject);
keywords.put("long", keywordObject);
keywords.put("native", keywordObject);
keywords.put("new", keywordObject);
keywords.put("null", keywordObject);
keywords.put("operator", keywordObject);
keywords.put("outer", keywordObject);
keywords.put("package", keywordObject);
keywords.put("private", keywordObject);
keywords.put("protected", keywordObject);
keywords.put("public", keywordObject);
keywords.put("rest", keywordObject);
keywords.put("return", keywordObject);
keywords.put("short", keywordObject);
keywords.put("static", keywordObject);
keywords.put("String", keywordObject);
keywords.put("super", keywordObject);
keywords.put("switch", keywordObject);
keywords.put("synchronized", keywordObject);
keywords.put("this", keywordObject);
keywords.put("throw", keywordObject);
keywords.put("throws", keywordObject);
keywords.put("transient", keywordObject);
keywords.put("true", keywordObject);
keywords.put("try", keywordObject);
keywords.put("var", keywordObject);
keywords.put("virtual", keywordObject);
keywords.put("void", keywordObject);
keywords.put("volatile", keywordObject);
keywords.put("while", keywordObject);

//"Methods" like procedure in Pascal or sub in Perl
Object methodObject = new Object();
methodhash = new Hashtable();
methodhash.put("sub", methodObject);
methodhash.put("procedure", methodObject);
methodhash.put("function", methodObject);
methodhash.put("union", methodObject);
}

public void insertString(int offset, String str,
AttributeSet a)
throws BadLocationException {
if (str.equals("{"))
str = addMatchingBrace(offset);

super.insertString(offset, str, a);
processChangedLines(offset, str.length());
}
public void remove(int offset, int length) throws
BadLocationException {
super.remove(offset, length);
processChangedLines(offset, 0);
}

/*
* Determine how many lines have been changed,
* then apply highlighting to each line
*/
private void processChangedLines(int offset, int length)
throws BadLocationException {
String content = doc.getText(0, doc.getLength());

// The lines affected by the latest document update
int startLine = rootElement.getElementIndex(offset);
int endLine = rootElement.getElementIndex(offset + length);


// Do the actual highlighting
for (int i = startLine; i <= endLine; i++) {
applyHighlighting(content, i);
}

// Resolve highlighting to the next end multi line delimiter
if (isMultiLineComment())
commentLinesAfter(content, endLine);
else
highlightLinesAfter(content, endLine);
}

/*
* Highlight lines when a multi line comment is still 'open'
* (ie. matching end delimiter has not yet been encountered)
*/
private boolean commentLinesBefore(String content, int line) {
int offset = rootElement.getElement(line).getStartOffset();

// Start of comment not found, nothing to do
int startDelimiter =
lastIndexOf(content, getStartDelimiter(), offset - 2);

if (startDelimiter < 0)
return false;

// Matching start/end of comment found, nothing to do
int endDelimiter =
indexOf(content, getEndDelimiter(), startDelimiter);

if (endDelimiter < offset & endDelimiter != -1)
return false;

// End of comment not found, highlight the lines
doc.setCharacterAttributes(
startDelimiter,
offset - startDelimiter + 1,
comment,
false);
return true;
}

/*
* Highlight comment lines to matching end delimiter
*/
private void commentLinesAfter(String content, int line) {
int offset = rootElement.getElement(line).getEndOffset();

// End of comment not found, nothing to do
int endDelimiter = indexOf(content, getEndDelimiter(), offset);

if (endDelimiter < 0)
return;

// Matching start/end of comment found, comment the lines
int startDelimiter =
lastIndexOf(content, getStartDelimiter(), endDelimiter);

if (startDelimiter < 0 || startDelimiter <= offset) {
doc.setCharacterAttributes(
offset,
endDelimiter - offset + 1,
comment,
false);
}
}

/*
* Highlight lines to start or end delimiter
*/
private void highlightLinesAfter(String content, int line)
throws BadLocationException {
int offset = rootElement.getElement(line).getEndOffset();

// Start/End delimiter not found, nothing to do

int startDelimiter =
indexOf(content, getStartDelimiter(),
offset);
int endDelimiter = indexOf(content, getEndDelimiter(), offset);

if (startDelimiter < 0)
startDelimiter = content.length();

if (endDelimiter < 0)
endDelimiter = content.length();

int delimiter = Math.min(startDelimiter, endDelimiter);

if (delimiter < offset)
return;

// Start/End delimiter found, reapply highlighting
int endLine = rootElement.getElementIndex(delimiter);

for (int i = line + 1; i < endLine; i++) {
Element branch = rootElement.getElement(i);
Element leaf =

doc.getCharacterElement(branch.getStartOffset());
AttributeSet as = leaf.getAttributes();

if (as.isEqual(comment))
applyHighlighting(content, i);
}
}

/*
* Parse the line to determine the appropriate highlighting
*/
private void applyHighlighting(String content, int line)
throws BadLocationException {

int startOffset = rootElement.getElement(line).getStartOffset();
int endOffset = rootElement.getElement(line).getEndOffset() - 1;

int lineLength = endOffset - startOffset;
int contentLength = content.length();

if (endOffset >= contentLength)
endOffset = contentLength - 1;

// check for multi line comments
// (always set the comment attribute for the entire line)
if (endingMultiLineComment(content, startOffset, endOffset)
|| isMultiLineComment()
|| startingMultiLineComment(content, startOffset, endOffset)) {
doc.setCharacterAttributes(
startOffset,
endOffset - startOffset + 1,
comment,
false);
return;
}

// set normal attributes for the line
doc.setCharacterAttributes(startOffset,
lineLength, normal, true);

// check for single line comment
int index =
content.indexOf(getSingleLineDelimiter(),
startOffset);

if ((index > -1) && (index < endOffset)) {
doc.setCharacterAttributes(
index,
endOffset - index + 1,
comment,
false);
endOffset = index - 1;
}

// check for tokens
checkForTokens(content, startOffset, endOffset);
}

/*
* Does this line contain the start delimiter
*/
private boolean startingMultiLineComment(
String content,
int startOffset,
int endOffset)
throws BadLocationException {
int index = indexOf(content, getStartDelimiter(), startOffset);

if ((index < 0) || (index > endOffset))
return false;
else {
setMultiLineComment(true);
return true;
}
}

/*
* Does this line contain the end delimiter
*/
private boolean endingMultiLineComment(
String content,
int startOffset,
int endOffset)
throws BadLocationException {
int index = indexOf(content, getEndDelimiter(), startOffset);

if ((index < 0) || (index > endOffset))
return false;
else {
setMultiLineComment(false);
return true;
}
}

/*
* We have found a start delimiter
* and are still searching for the end delimiter
*/
private boolean isMultiLineComment() {
return multiLineComment;
}

private void setMultiLineComment(boolean value) {
multiLineComment = value;
}

/*
* Parse the line for tokens to highlight
*/
private void checkForTokens(
String content,
int startOffset,
int endOffset) {
while (startOffset <= endOffset) {

while (isDelimiter(content
.substring(startOffset, startOffset + 1))) {
if (startOffset < endOffset)
startOffset++;
else
return;
}

// Extract and process the entire token
if (isQuoteDelimiter(content
.substring(startOffset, startOffset + 1)))
startOffset =
getQuoteToken(content, startOffset,
endOffset);
else
startOffset =
getOtherToken(content, startOffset,
endOffset);
}
}

private int getQuoteToken(String content,
int startOffset, int endOffset) {
String quoteDelimiter =
content.substring(startOffset, startOffset + 1);
String escapeString = getEscapeString(quoteDelimiter);

int index;
int endOfQuote = startOffset;

// skip over the escape quotes in this quote
index = content.indexOf(escapeString, endOfQuote + 1);

while ((index > -1) && (index < endOffset)) {
endOfQuote = index + 1;
index = content.indexOf(escapeString, endOfQuote);
}

// now find the matching delimiter
index = content.indexOf(quoteDelimiter, endOfQuote + 1);

if ((index < 0) || (index > endOffset))
endOfQuote = endOffset;
else
endOfQuote = index;

doc.setCharacterAttributes(
startOffset,
endOfQuote - startOffset + 1,
quote,
false);

return endOfQuote + 1;
}


private int getOtherToken(String content,
int startOffset, int endOffset) {
int endOfToken = startOffset + 1;

while (endOfToken <= endOffset) {
if (isDelimiter(content.substring(endOfToken, endOfToken + 1)))
break;

endOfToken++;
}

String token = content.substring(startOffset, endOfToken);


//Keyword? --> see keywordhash for supported keywords
if (isKeyword(token))
doc.setCharacterAttributes(
startOffset,
endOfToken - startOffset,
keyword,
false);

//Method? --> see methodhash for supported names
if (isMethod(token)||token.startsWith("@")

||token.startsWith("$#")||token.startsWith("$"))
doc.setCharacterAttributes(
startOffset,
endOfToken - startOffset,
method,
false);

return endOfToken + 1;
}

/*
* Assume the needle will the found at the start/end of the line
*/
private int indexOf(String content, String needle, int offset) {
int index;

while ((index = content.indexOf(needle, offset)) != -1) {
String text = getLine(content, index).trim();

if (text.startsWith(needle) || text.endsWith(needle))
break;
else
offset = index + 1;
}

return index;
}

/*
* Assume the needle will the found at the start/end of the line
*/
private int lastIndexOf(String content, String needle, int offset) {
int index;

while ((index = content.lastIndexOf(needle, offset)) != -1) {
String text = getLine(content, index).trim();

if (text.startsWith(needle) || text.endsWith(needle))
break;
else
offset = index - 1;
}

return index;
}

private String getLine(String content, int offset) {
int line = rootElement.getElementIndex(offset);
Element lineElement = rootElement.getElement(line);
int start = lineElement.getStartOffset();
int end = lineElement.getEndOffset();
return content.substring(start, end - 1);
}

/*
* Override for other languages
*/
protected boolean isDelimiter(String character) {
String operands = ";:{}()[]+-/%<=>!&|^~*";

if (Character.isWhitespace(character.charAt(0))
|| operands.indexOf(character) != -1)
return true;
else
return false;
}

/*
* Override for other languages
*/
protected boolean isQuoteDelimiter(String character) {
String quoteDelimiters = "\"'";

if (quoteDelimiters.indexOf(character) < 0)
return false;
else
return true;
}


protected boolean isKeyword(String token) {
Object o = keywords.get(token);

return o == null ? false : true;
}

protected boolean isMethod(String token) {
Object o1 = methodhash.get(token);

return o1 == null ? false : true;
}

/*
* Override for other languages
*/
protected String getStartDelimiter() {
return "/*";
}

/*
* Override for other languages
*/
protected String getEndDelimiter() {
return "*/";
}

/*
* Override for other languages
*/
protected String getSingleLineDelimiter() {
return "//";
}

/*
* Override for other languages
*/
protected String getEscapeString(String quoteDelimiter) {
return "\\" + quoteDelimiter;
}

/*
*
*/
protected String addMatchingBrace(int offset)
throws BadLocationException {
StringBuffer whiteSpace = new StringBuffer();
int line = rootElement.getElementIndex(offset);
int i = rootElement.getElement(line).getStartOffset();

while (true) {
String temp = doc.getText(i, 1);

if (temp.equals(" ") || temp.equals("\t")) {
whiteSpace.append(temp);
i++;
} else
break;
}

return "{\n"
+ whiteSpace.toString()
+ "\t\n"
+ whiteSpace.toString()
+ "}";
}

public static void main(String a[]) {


JFrame mjf = new JFrame();
JEditorPane textcomp = new JEditorPane();



final UndoManager undo = new UndoManager();


textcomp.setEditorKit(new StyledEditorKit());
textcomp.setDocument(new SyntaxDocument());

JScrollPane scroll = new JScrollPane(textcomp);

Document doc1 = textcomp.getDocument();



doc1.addUndoableEditListener(new UndoableEditListener() {
public void undoableEditHappened(UndoableEditEvent evt) {

undo.addEdit(evt.getEdit());
}
});

textcomp.getActionMap().put("Undo",
new AbstractAction("Undo") {
public void actionPerformed(ActionEvent evt) {
try {
if (undo.canUndo()) {
undo.undo();
}
} catch (CannotUndoException e) {
}
}
});

textcomp.getInputMap().put(KeyStroke.getKeyStroke("control Z"),
"Undo");

textcomp.getActionMap().put("Redo",
new AbstractAction("Redo") {
public void actionPerformed(ActionEvent evt) {
try {
if (undo.canRedo()) {
undo.redo();
}
} catch (CannotRedoException e) {
}
}
});


textcomp.getInputMap().put(KeyStroke.getKeyStroke("control Y"),
"Redo");



mjf.getContentPane().add(scroll);
mjf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mjf.setSize(600, 300);
mjf.setVisible(true);
}
}

--------------
 
A

Andrew Thompson

Hello, i wrote this little editor

662 lines of code is a huge amount of
code to post to a group. Please put it
up at a site and link to it in future, so
the scores of people in the group who
do not know the answer do not have to
suffer the download.
...and it supports syntaxhighlighting.
Of course I want to add support for undo/redo via the undo package. So
far so good. Now i have 2 questions:

1. If you compile the code and run it,

Nuh. It does not compile.

SyntaxDocument must (I guess) be a customized
JEditorPane? It is missing. There are 5 other
errors that seem related to that.

...but do _not_ post it to the group!
Put the complete source at Geocities
or somewhere and link to it.

As an aside, you might also check the
naming conventions for classes, attributes
and methods. I notice you do not follow them.
[ Your classname should start with uppercase ]
 
M

Mykola Rabchevskiy

Andreas said:
Hello, i wrote this little editor and it supports syntaxhighlighting.
Of course I want to add support for undo/redo via the undo package. So
far so good. Now i have 2 questions:

1. If you compile the code and run it, you will notice that undoing
and redoing is a bit slow (strg z or y). Can you see why?

2. If you write a line like "for(int i=0;i<10;i++)" and press strg z
the keywords are highlighted only a few times. Can you see why?

Thanks:)

Here is the code:
[...]

What is SyntaxDocument?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,702
Latest member
LukasConde

Latest Threads

Top