001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.console;
011:
012: import java.util.ArrayList;
013: import java.util.Iterator;
014: import java.util.List;
015: import java.util.regex.Matcher;
016: import java.util.regex.Pattern;
017:
018: import org.eclipse.core.runtime.Assert;
019: import org.eclipse.jface.text.BadLocationException;
020: import org.eclipse.jface.text.DocumentEvent;
021: import org.eclipse.jface.text.IDocument;
022: import org.eclipse.jface.text.IDocumentAdapter;
023: import org.eclipse.jface.text.IDocumentListener;
024: import org.eclipse.swt.custom.TextChangeListener;
025: import org.eclipse.swt.custom.TextChangedEvent;
026: import org.eclipse.swt.custom.TextChangingEvent;
027:
028: /**
029: * Adapts a Console's document to the viewer StyledText widget. Allows proper line
030: * wrapping of fixed width consoles without having to add line delimiters to the StyledText.
031: *
032: * By using this adapter, the offset of any character is the same in both the widget and the
033: * document.
034: *
035: * @since 3.1
036: */
037: public class ConsoleDocumentAdapter implements IDocumentAdapter,
038: IDocumentListener {
039:
040: private int consoleWidth = -1;
041: private List textChangeListeners;
042: private IDocument document;
043:
044: int[] offsets = new int[5000];
045: int[] lengths = new int[5000];
046: private int regionCount = 1;
047: private Pattern pattern = Pattern.compile("$", Pattern.MULTILINE); //$NON-NLS-1$
048:
049: public ConsoleDocumentAdapter(int width) {
050: textChangeListeners = new ArrayList();
051: consoleWidth = width;
052: }
053:
054: /*
055: * repairs lines list from the beginning of the line containing the offset of any
056: * DocumentEvent, to the end of the Document.
057: */
058: private void repairLines(int eventOffset) {
059: if (document == null) {
060: return;
061: }
062: try {
063: int docLine = document.getLineOfOffset(eventOffset);
064: int docLineOffset = document.getLineOffset(docLine);
065: int widgetLine = getLineAtOffset(docLineOffset);
066:
067: for (int i = regionCount - 1; i >= widgetLine; i--) {
068: regionCount--;
069: }
070:
071: int numLinesInDoc = document.getNumberOfLines();
072:
073: int nextOffset = document.getLineOffset(docLine);
074: for (int i = docLine; i < numLinesInDoc; i++) {
075: int offset = nextOffset;
076: int length = document.getLineLength(i);
077: nextOffset += length;
078:
079: if (length == 0) {
080: addRegion(offset, 0);
081: } else {
082: while (length > 0) {
083: int trimmedLength = length;
084: String lineDelimiter = document
085: .getLineDelimiter(i);
086: int lineDelimiterLength = 0;
087: if (lineDelimiter != null) {
088: lineDelimiterLength = lineDelimiter
089: .length();
090: trimmedLength -= lineDelimiterLength;
091: }
092:
093: if (consoleWidth > 0
094: && consoleWidth < trimmedLength) {
095: addRegion(offset, consoleWidth);
096: offset += consoleWidth;
097: length -= consoleWidth;
098: } else {
099: addRegion(offset, length);
100: offset += length;
101: length -= length;
102: }
103: }
104: }
105: }
106: } catch (BadLocationException e) {
107: }
108:
109: if (regionCount == 0) {
110: addRegion(0, document.getLength());
111: }
112: }
113:
114: private void addRegion(int offset, int length) {
115: if (regionCount == 0) {
116: offsets[0] = offset;
117: lengths[0] = length;
118: } else {
119: if (regionCount == offsets.length) {
120: growRegionArray(regionCount * 2);
121: }
122: offsets[regionCount] = offset;
123: lengths[regionCount] = length;
124: }
125: regionCount++;
126: }
127:
128: /* (non-Javadoc)
129: * @see org.eclipse.jface.text.IDocumentAdapter#setDocument(org.eclipse.jface.text.IDocument)
130: */
131: public void setDocument(IDocument doc) {
132: if (document != null) {
133: document.removeDocumentListener(this );
134: }
135:
136: document = doc;
137:
138: if (document != null) {
139: document.addDocumentListener(this );
140: repairLines(0);
141: }
142: }
143:
144: /* (non-Javadoc)
145: * @see org.eclipse.swt.custom.StyledTextContent#addTextChangeListener(org.eclipse.swt.custom.TextChangeListener)
146: */
147: public synchronized void addTextChangeListener(
148: TextChangeListener listener) {
149: Assert.isNotNull(listener);
150: if (!textChangeListeners.contains(listener)) {
151: textChangeListeners.add(listener);
152: }
153: }
154:
155: /* (non-Javadoc)
156: * @see org.eclipse.swt.custom.StyledTextContent#removeTextChangeListener(org.eclipse.swt.custom.TextChangeListener)
157: */
158: public synchronized void removeTextChangeListener(
159: TextChangeListener listener) {
160: if (textChangeListeners != null) {
161: Assert.isNotNull(listener);
162: textChangeListeners.remove(listener);
163: }
164: }
165:
166: /* (non-Javadoc)
167: * @see org.eclipse.swt.custom.StyledTextContent#getCharCount()
168: */
169: public int getCharCount() {
170: return document.getLength();
171: }
172:
173: /* (non-Javadoc)
174: * @see org.eclipse.swt.custom.StyledTextContent#getLine(int)
175: */
176: public String getLine(int lineIndex) {
177: try {
178: StringBuffer line = new StringBuffer(document.get(
179: offsets[lineIndex], lengths[lineIndex]));
180: int index = line.length() - 1;
181: while (index > -1
182: && (line.charAt(index) == '\n' || line
183: .charAt(index) == '\r')) {
184: index--;
185: }
186: return new String(line.substring(0, index + 1));
187: } catch (BadLocationException e) {
188: }
189: return ""; //$NON-NLS-1$
190: }
191:
192: /* (non-Javadoc)
193: * @see org.eclipse.swt.custom.StyledTextContent#getLineAtOffset(int)
194: */
195: public int getLineAtOffset(int offset) {
196: if (offset == 0 || regionCount <= 1) {
197: return 0;
198: }
199:
200: if (offset == document.getLength()) {
201: return regionCount - 1;
202: }
203:
204: int left = 0;
205: int right = regionCount - 1;
206: int midIndex = 0;
207:
208: while (left <= right) {
209: if (left == right) {
210: return right;
211: }
212: midIndex = (left + right) / 2;
213:
214: if (offset < offsets[midIndex]) {
215: right = midIndex;
216: } else if (offset >= offsets[midIndex] + lengths[midIndex]) {
217: left = midIndex + 1;
218: } else {
219: return midIndex;
220: }
221: }
222:
223: return midIndex;
224: }
225:
226: /* (non-Javadoc)
227: * @see org.eclipse.swt.custom.StyledTextContent#getLineCount()
228: */
229: public int getLineCount() {
230: return regionCount;
231: }
232:
233: /* (non-Javadoc)
234: * @see org.eclipse.swt.custom.StyledTextContent#getLineDelimiter()
235: */
236: public String getLineDelimiter() {
237: return System.getProperty("line.separator"); //$NON-NLS-1$
238: }
239:
240: /* (non-Javadoc)
241: * @see org.eclipse.swt.custom.StyledTextContent#getOffsetAtLine(int)
242: */
243: public int getOffsetAtLine(int lineIndex) {
244: return offsets[lineIndex];
245: }
246:
247: /* (non-Javadoc)
248: * @see org.eclipse.swt.custom.StyledTextContent#getTextRange(int, int)
249: */
250: public String getTextRange(int start, int length) {
251: try {
252: return document.get(start, length);
253: } catch (BadLocationException e) {
254: }
255: return null;
256: }
257:
258: /* (non-Javadoc)
259: * @see org.eclipse.swt.custom.StyledTextContent#replaceTextRange(int, int, java.lang.String)
260: */
261: public void replaceTextRange(int start, int replaceLength,
262: String text) {
263: try {
264: document.replace(start, replaceLength, text);
265: } catch (BadLocationException e) {
266: }
267: }
268:
269: /* (non-Javadoc)
270: * @see org.eclipse.swt.custom.StyledTextContent#setText(java.lang.String)
271: */
272: public synchronized void setText(String text) {
273: TextChangedEvent changeEvent = new TextChangedEvent(this );
274: for (Iterator iter = textChangeListeners.iterator(); iter
275: .hasNext();) {
276: TextChangeListener element = (TextChangeListener) iter
277: .next();
278: element.textSet(changeEvent);
279: }
280: }
281:
282: /* (non-Javadoc)
283: * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
284: */
285: public synchronized void documentAboutToBeChanged(
286: DocumentEvent event) {
287: if (document == null) {
288: return;
289: }
290:
291: TextChangingEvent changeEvent = new TextChangingEvent(this );
292: changeEvent.start = event.fOffset;
293: changeEvent.newText = (event.fText == null ? "" : event.fText); //$NON-NLS-1$
294: changeEvent.replaceCharCount = event.fLength;
295: changeEvent.newCharCount = (event.fText == null ? 0
296: : event.fText.length());
297:
298: int first = getLineAtOffset(event.fOffset);
299: int lOffset = Math.max(event.fOffset + event.fLength - 1, 0);
300: int last = getLineAtOffset(lOffset);
301: changeEvent.replaceLineCount = Math.max(last - first, 0);
302:
303: int newLineCount = countNewLines(event.fText);
304: changeEvent.newLineCount = newLineCount >= 0 ? newLineCount : 0;
305:
306: if (changeEvent.newLineCount > offsets.length - regionCount) {
307: growRegionArray(changeEvent.newLineCount);
308: }
309:
310: for (Iterator iter = textChangeListeners.iterator(); iter
311: .hasNext();) {
312: TextChangeListener element = (TextChangeListener) iter
313: .next();
314: element.textChanging(changeEvent);
315: }
316: }
317:
318: private void growRegionArray(int minSize) {
319: int size = Math.max(offsets.length * 2, minSize * 2);
320: int[] newOffsets = new int[size];
321: System.arraycopy(offsets, 0, newOffsets, 0, regionCount);
322: offsets = newOffsets;
323: int[] newLengths = new int[size];
324: System.arraycopy(lengths, 0, newLengths, 0, regionCount);
325: lengths = newLengths;
326: }
327:
328: private int countNewLines(String string) {
329: int count = 0;
330:
331: if (string.length() == 0)
332: return 0;
333:
334: // work around to
335: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4994840
336: // see bug 84641
337: int offset = string.length() - 1;
338: while (string.charAt(offset) == '\r') {
339: offset--;
340: count++;
341: }
342: if (offset < (string.length() - 1)) {
343: string = string.substring(0, offset);
344: }
345:
346: int lastIndex = 0;
347: int index = 0;
348:
349: Matcher matcher = pattern.matcher(string);
350:
351: while (matcher.find()) {
352: index = matcher.start();
353:
354: if (index == 0)
355: count++;
356: else if (index != string.length())
357: count++;
358:
359: if (consoleWidth > 0) {
360: int lineLen = index - lastIndex + 1;
361: if (index == 0)
362: lineLen += lengths[regionCount - 1];
363: count += lineLen / consoleWidth;
364: }
365:
366: lastIndex = index;
367: }
368: return count;
369: }
370:
371: /* (non-Javadoc)
372: * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
373: */
374: public synchronized void documentChanged(DocumentEvent event) {
375: if (document == null) {
376: return;
377: }
378:
379: repairLines(event.fOffset);
380:
381: TextChangedEvent changeEvent = new TextChangedEvent(this );
382:
383: for (Iterator iter = textChangeListeners.iterator(); iter
384: .hasNext();) {
385: TextChangeListener element = (TextChangeListener) iter
386: .next();
387: element.textChanged(changeEvent);
388: }
389: }
390:
391: /**
392: * sets consoleWidth, repairs line information, then fires event to the viewer text widget.
393: * @param width The console's width
394: */
395: public void setWidth(int width) {
396: if (width != consoleWidth) {
397: consoleWidth = width;
398: repairLines(0);
399: TextChangedEvent changeEvent = new TextChangedEvent(this );
400: for (Iterator iter = textChangeListeners.iterator(); iter
401: .hasNext();) {
402: TextChangeListener element = (TextChangeListener) iter
403: .next();
404: element.textSet(changeEvent);
405: }
406: }
407: }
408: }
|