001: package workbench.gui.editor;
002:
003: import javax.swing.event.DocumentEvent;
004: import javax.swing.event.UndoableEditEvent;
005: import javax.swing.event.UndoableEditListener;
006: import javax.swing.text.AbstractDocument;
007: import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
008: import javax.swing.text.BadLocationException;
009: import javax.swing.text.Element;
010: import javax.swing.text.PlainDocument;
011: import javax.swing.text.Segment;
012: import javax.swing.undo.CannotRedoException;
013: import javax.swing.undo.CannotUndoException;
014: import javax.swing.undo.UndoManager;
015: import javax.swing.undo.UndoableEdit;
016: import workbench.log.LogMgr;
017: import workbench.resource.Settings;
018:
019: /**
020: * A document implementation that can be tokenized by the syntax highlighting
021: * system.
022: *
023: * @author Slava Pestov
024: */
025: public class SyntaxDocument extends PlainDocument implements
026: UndoableEditListener {
027: private UndoManager undoManager = new UndoManager();
028: protected TokenMarker tokenMarker;
029: private int compoundLevelCounter = 0;
030: private WbCompoundEdit compoundEditItem = null;
031: private boolean undoSuspended = false;
032: private int maxLineLength = 0;
033:
034: public SyntaxDocument() {
035: super ();
036: this .addUndoableEditListener(this );
037: this .initDefaultProperties();
038: }
039:
040: public SyntaxDocument(AbstractDocument.Content aContent) {
041: super (aContent);
042: this .addUndoableEditListener(this );
043: this .initDefaultProperties();
044: }
045:
046: public DocumentEvent createChangedEvent() {
047: DefaultDocumentEvent evt = new DefaultDocumentEvent(0, this
048: .getLength(), DocumentEvent.EventType.CHANGE);
049: return evt;
050: }
051:
052: protected void initDefaultProperties() {
053: this .putProperty("filterNewlines", Boolean.FALSE);
054: this .putProperty(PlainDocument.tabSizeAttribute, new Integer(
055: Settings.getInstance().getEditorTabWidth()));
056: }
057:
058: /**
059: * Returns the token marker that is to be used to split lines
060: * of this document up into tokens. May return null if this
061: * document is not to be colorized.
062: */
063: public TokenMarker getTokenMarker() {
064: return tokenMarker;
065: }
066:
067: /**
068: * Sets the token marker that is to be used to split lines of
069: * this document up into tokens. May throw an exception if
070: * this is not supported for this type of document.
071: * @param tm The new token marker
072: */
073: public void setTokenMarker(TokenMarker tm) {
074: tokenMarker = tm;
075: if (tm == null)
076: return;
077: tokenMarker.insertLines(0, getDefaultRootElement()
078: .getElementCount());
079: tokenizeLines();
080: }
081:
082: public void dispose() {
083: this .clearUndoBuffer();
084: if (this .compoundEditItem != null) {
085: this .compoundEditItem.clear();
086: this .compoundEditItem = null;
087: }
088: if (tokenMarker != null)
089: tokenMarker.dispose();
090: try {
091: this .remove(0, this .getLength());
092: } catch (Throwable th) {
093: }
094: }
095:
096: public void suspendUndo() {
097: this .undoSuspended = true;
098: }
099:
100: public void resumeUndo() {
101: this .undoSuspended = false;
102: }
103:
104: public void clearUndoBuffer() {
105: this .undoManager.discardAllEdits();
106: }
107:
108: public void redo() {
109: if (!undoManager.canRedo())
110: return;
111: try {
112: undoManager.redo();
113: } catch (CannotRedoException cre) {
114: cre.printStackTrace();
115: }
116: }
117:
118: public void undo() {
119: if (!undoManager.canUndo())
120: return;
121: try {
122: undoManager.undo();
123: } catch (CannotUndoException cre) {
124: cre.printStackTrace();
125: }
126: }
127:
128: /**
129: * Reparses the document, by passing all lines to the token
130: * marker. This should be called after the document is first
131: * loaded.
132: */
133: public void tokenizeLines() {
134: tokenizeLines(0, getDefaultRootElement().getElementCount());
135: }
136:
137: /**
138: * Reparses the document, by passing the specified lines to the
139: * token marker. This should be called after a large quantity of
140: * text is first inserted.
141: *
142: * @param start The first line to parse
143: * @param len The number of lines, after the first one to parse
144: */
145: public void tokenizeLines(int start, int len) {
146: if (tokenMarker == null)
147: return;
148:
149: Segment lineSegment = new Segment();
150: Element map = getDefaultRootElement();
151:
152: len += start;
153:
154: try {
155: for (int i = start; i < len; i++) {
156: Element lineElement = map.getElement(i);
157: if (lineElement == null)
158: break;
159: int lineStart = lineElement.getStartOffset();
160: getText(lineStart, lineElement.getEndOffset()
161: - lineStart - 1, lineSegment);
162: tokenMarker.markTokens(lineSegment, i);
163: }
164: } catch (BadLocationException bl) {
165: bl.printStackTrace();
166: }
167: }
168:
169: private void calcMaxLineLength() {
170: Segment lineSegment = new Segment();
171: Element map = getDefaultRootElement();
172:
173: int len = getDefaultRootElement().getElementCount();
174:
175: this .maxLineLength = 0;
176:
177: try {
178: for (int i = 0; i < len; i++) {
179: Element lineElement = map.getElement(i);
180: int lineStart = lineElement.getStartOffset();
181: getText(lineStart, lineElement.getEndOffset()
182: - lineStart - 1, lineSegment);
183: if (lineSegment.count > this .maxLineLength)
184: this .maxLineLength = lineSegment.count;
185: }
186: } catch (BadLocationException bl) {
187: // Ignore
188: }
189: }
190:
191: public int getMaxLineLength() {
192: return this .maxLineLength;
193: }
194:
195: public synchronized void undoableEditHappened(UndoableEditEvent e) {
196: if (undoSuspended)
197: return;
198:
199: if (this .compoundEditItem != null) {
200: this .compoundEditItem.addEdit(e.getEdit());
201: } else {
202: undoManager.addEdit(e.getEdit());
203: }
204: }
205:
206: /**
207: * Starts a compound edit that can be undone in one operation.
208: */
209: public synchronized void beginCompoundEdit() {
210: if (undoSuspended)
211: return;
212: if (compoundLevelCounter == 0) {
213: this .compoundEditItem = new WbCompoundEdit();
214: }
215: this .compoundLevelCounter++;
216: }
217:
218: /**
219: * Ends a compound edit that can be undone in one operation.
220: */
221: public synchronized void endCompoundEdit() {
222: this .calcMaxLineLength();
223: if (undoSuspended)
224: return;
225: if (compoundLevelCounter == 1) {
226: // it's important to finish the current compound undo
227: // because the UndoManager asks the last UndoableEdit whether
228: // it accepts more elements. If the compound wasn't finished
229: // the single item added would be added to the compound rather
230: // than to the UndoManager's item list
231: compoundEditItem.finished();
232:
233: if (compoundEditItem.getSize() == 1) {
234: UndoableEdit single = compoundEditItem.getLast();
235: undoManager.addEdit(single);
236: compoundEditItem.clear();
237: } else if (compoundEditItem.getSize() > 1) {
238: undoManager.addEdit(compoundEditItem);
239: }
240: compoundEditItem = null;
241: compoundLevelCounter = 0;
242: } else if (compoundLevelCounter == 0) {
243: Exception e = new IllegalStateException();
244: LogMgr.logError("SyntaxDocument.endCompoundEdit",
245: "Unbalanced endCompoundEdit()", e);
246: } else {
247: this .compoundLevelCounter--;
248: }
249: }
250:
251: public void addUndoableEdit(UndoableEdit edit) {
252: }
253:
254: public int getPositionOfLastChange() {
255: return lastChangePosition;
256: }
257:
258: private int lastChangePosition = -1;
259:
260: /**
261: * We overwrite this method to update the token marker
262: * state immediately so that any event listeners get a
263: * consistent token marker.
264: */
265: protected void fireInsertUpdate(DocumentEvent evt) {
266: if (tokenMarker != null) {
267: DocumentEvent.ElementChange ch = evt
268: .getChange(getDefaultRootElement());
269: if (ch != null) {
270: int index = ch.getIndex() + 1;
271: int lines = ch.getChildrenAdded().length
272: - ch.getChildrenRemoved().length;
273: tokenMarker.insertLines(index, lines);
274: }
275: }
276: lastChangePosition = evt.getOffset();
277: super .fireInsertUpdate(evt);
278: }
279:
280: /**
281: * We overwrite this method to update the token marker
282: * state immediately so that any event listeners get a
283: * consistent token marker.
284: */
285: protected void fireRemoveUpdate(DocumentEvent evt) {
286: if (tokenMarker != null) {
287: DocumentEvent.ElementChange ch = evt
288: .getChange(getDefaultRootElement());
289: if (ch != null) {
290: tokenMarker.deleteLines(ch.getIndex() + 1, ch
291: .getChildrenRemoved().length
292: - ch.getChildrenAdded().length);
293: }
294: }
295: lastChangePosition = evt.getOffset();
296: super.fireRemoveUpdate(evt);
297: }
298: }
|