| /******************************************************************************* |
| * Copyright (c) 2000, 2007 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.console; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentAdapter; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.swt.custom.TextChangeListener; |
| import org.eclipse.swt.custom.TextChangedEvent; |
| import org.eclipse.swt.custom.TextChangingEvent; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| /** |
| * Adapts a Console's document to the viewer StyledText widget. Allows proper line |
| * wrapping of fixed width consoles without having to add line delimiters to the StyledText. |
| * |
| * By using this adapter, the offset of any character is the same in both the widget and the |
| * document. |
| * |
| * @since 3.1 |
| */ |
| public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListener { |
| |
| private int consoleWidth = -1; |
| private List textChangeListeners; |
| private IDocument document; |
| |
| int[] offsets = new int[5000]; |
| int[] lengths = new int[5000]; |
| private int regionCount = 1; |
| private Pattern pattern = Pattern.compile("$", Pattern.MULTILINE); //$NON-NLS-1$ |
| |
| |
| public ConsoleDocumentAdapter(int width) { |
| textChangeListeners = new ArrayList(); |
| consoleWidth = width; |
| } |
| |
| /* |
| * repairs lines list from the beginning of the line containing the offset of any |
| * DocumentEvent, to the end of the Document. |
| */ |
| private void repairLines(int eventOffset) { |
| if (document == null) { |
| return; |
| } |
| try { |
| int docLine = document.getLineOfOffset(eventOffset); |
| int docLineOffset = document.getLineOffset(docLine); |
| int widgetLine = getLineAtOffset(docLineOffset); |
| |
| for (int i=regionCount-1; i>=widgetLine; i--) { |
| regionCount--; |
| } |
| |
| int numLinesInDoc = document.getNumberOfLines(); |
| |
| int nextOffset = document.getLineOffset(docLine); |
| for (int i = docLine; i<numLinesInDoc; i++) { |
| int offset = nextOffset; |
| int length = document.getLineLength(i); |
| nextOffset += length; |
| |
| if (length == 0) { |
| addRegion(offset, 0); |
| } else { |
| while (length > 0) { |
| int trimmedLength = length; |
| String lineDelimiter = document.getLineDelimiter(i); |
| int lineDelimiterLength = 0; |
| if (lineDelimiter != null) { |
| lineDelimiterLength = lineDelimiter.length(); |
| trimmedLength -= lineDelimiterLength; |
| } |
| |
| if (consoleWidth > 0 && consoleWidth < trimmedLength) { |
| addRegion(offset, consoleWidth); |
| offset += consoleWidth; |
| length -= consoleWidth; |
| } else { |
| addRegion(offset, length); |
| offset += length; |
| length -= length; |
| } |
| } |
| } |
| } |
| } catch (BadLocationException e) { |
| } |
| |
| if (regionCount == 0) { |
| addRegion(0, document.getLength()); |
| } |
| } |
| |
| private void addRegion(int offset, int length) { |
| if (regionCount == 0) { |
| offsets[0] = offset; |
| lengths[0] = length; |
| } else { |
| if (regionCount == offsets.length) { |
| growRegionArray(regionCount * 2); |
| } |
| offsets[regionCount] = offset; |
| lengths[regionCount] = length; |
| } |
| regionCount++; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentAdapter#setDocument(org.eclipse.jface.text.IDocument) |
| */ |
| public void setDocument(IDocument doc) { |
| if (document != null) { |
| document.removeDocumentListener(this); |
| } |
| |
| document = doc; |
| |
| if (document != null) { |
| document.addDocumentListener(this); |
| repairLines(0); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#addTextChangeListener(org.eclipse.swt.custom.TextChangeListener) |
| */ |
| public synchronized void addTextChangeListener(TextChangeListener listener) { |
| Assert.isNotNull(listener); |
| if (!textChangeListeners.contains(listener)) { |
| textChangeListeners.add(listener); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#removeTextChangeListener(org.eclipse.swt.custom.TextChangeListener) |
| */ |
| public synchronized void removeTextChangeListener(TextChangeListener listener) { |
| if(textChangeListeners != null) { |
| Assert.isNotNull(listener); |
| textChangeListeners.remove(listener); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getCharCount() |
| */ |
| public int getCharCount() { |
| return document.getLength(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getLine(int) |
| */ |
| public String getLine(int lineIndex) { |
| try { |
| StringBuffer line = new StringBuffer(document.get(offsets[lineIndex], lengths[lineIndex])); |
| int index = line.length() - 1; |
| while(index > -1 && (line.charAt(index)=='\n' || line.charAt(index)=='\r')) { |
| index--; |
| } |
| return new String(line.substring(0, index+1)); |
| } catch (BadLocationException e) { |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getLineAtOffset(int) |
| */ |
| public int getLineAtOffset(int offset) { |
| if (offset == 0 || regionCount <= 1) { |
| return 0; |
| } |
| |
| if (offset == document.getLength()) { |
| return regionCount-1; |
| } |
| |
| int left= 0; |
| int right= regionCount-1; |
| int midIndex = 0; |
| |
| while (left <= right) { |
| if(left == right) { |
| return right; |
| } |
| midIndex = (left + right) / 2; |
| |
| if (offset < offsets[midIndex]) { |
| right = midIndex; |
| } else if (offset >= offsets[midIndex] + lengths[midIndex]) { |
| left = midIndex + 1; |
| } else { |
| return midIndex; |
| } |
| } |
| |
| return midIndex; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getLineCount() |
| */ |
| public int getLineCount() { |
| return regionCount; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getLineDelimiter() |
| */ |
| public String getLineDelimiter() { |
| return System.getProperty("line.separator"); //$NON-NLS-1$ |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getOffsetAtLine(int) |
| */ |
| public int getOffsetAtLine(int lineIndex) { |
| return offsets[lineIndex]; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#getTextRange(int, int) |
| */ |
| public String getTextRange(int start, int length) { |
| try { |
| return document.get(start, length); |
| } catch (BadLocationException e) { |
| } |
| return null; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#replaceTextRange(int, int, java.lang.String) |
| */ |
| public void replaceTextRange(int start, int replaceLength, String text) { |
| try { |
| document.replace(start, replaceLength, text); |
| } catch (BadLocationException e) { |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.swt.custom.StyledTextContent#setText(java.lang.String) |
| */ |
| public synchronized void setText(String text) { |
| TextChangedEvent changeEvent = new TextChangedEvent(this); |
| for (Iterator iter = textChangeListeners.iterator(); iter.hasNext();) { |
| TextChangeListener element = (TextChangeListener) iter.next(); |
| element.textSet(changeEvent); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) |
| */ |
| public synchronized void documentAboutToBeChanged(DocumentEvent event) { |
| if (document == null) { |
| return; |
| } |
| |
| TextChangingEvent changeEvent = new TextChangingEvent(this); |
| changeEvent.start = event.fOffset; |
| changeEvent.newText = (event.fText == null ? "" : event.fText); //$NON-NLS-1$ |
| changeEvent.replaceCharCount = event.fLength; |
| changeEvent.newCharCount = (event.fText == null ? 0 : event.fText.length()); |
| |
| int first = getLineAtOffset(event.fOffset); |
| int lOffset = Math.max(event.fOffset + event.fLength - 1, 0); |
| int last = getLineAtOffset(lOffset); |
| changeEvent.replaceLineCount = Math.max(last - first, 0); |
| |
| int newLineCount = countNewLines(event.fText); |
| changeEvent.newLineCount = newLineCount >= 0 ? newLineCount : 0; |
| |
| if (changeEvent.newLineCount > offsets.length-regionCount) { |
| growRegionArray(changeEvent.newLineCount); |
| } |
| |
| for (Iterator iter = textChangeListeners.iterator(); iter.hasNext();) { |
| TextChangeListener element = (TextChangeListener) iter.next(); |
| element.textChanging(changeEvent); |
| } |
| } |
| |
| private void growRegionArray(int minSize) { |
| int size = Math.max(offsets.length*2, minSize*2); |
| int[] newOffsets = new int[size]; |
| System.arraycopy(offsets, 0, newOffsets, 0, regionCount); |
| offsets = newOffsets; |
| int[] newLengths = new int[size]; |
| System.arraycopy(lengths, 0, newLengths, 0, regionCount); |
| lengths = newLengths; |
| } |
| |
| private int countNewLines(String string) { |
| int count = 0; |
| |
| if (string.length() == 0) return 0; |
| |
| // work around to |
| // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4994840 |
| // see bug 84641 |
| if (string.endsWith("\r")) { //$NON-NLS-1$ |
| int len = string.length(); |
| int index = len >= 2 ? len - 2 : 0; |
| string = string.substring(0, index); |
| count++; |
| } |
| |
| int lastIndex = 0; |
| int index = 0; |
| |
| Matcher matcher = pattern.matcher(string); |
| |
| while (matcher.find()) { |
| index = matcher.start(); |
| |
| if (index == 0) |
| count++; |
| else if (index!=string.length()) |
| count++; |
| |
| if (consoleWidth > 0) { |
| int lineLen = index - lastIndex + 1; |
| if (index == 0) lineLen += lengths[regionCount-1]; |
| count += lineLen/consoleWidth; |
| } |
| |
| lastIndex = index; |
| } |
| return count; |
| } |
| |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent) |
| */ |
| public synchronized void documentChanged(DocumentEvent event) { |
| if (document == null) { |
| return; |
| } |
| |
| repairLines(event.fOffset); |
| |
| TextChangedEvent changeEvent = new TextChangedEvent(this); |
| |
| for (Iterator iter = textChangeListeners.iterator(); iter.hasNext();) { |
| TextChangeListener element = (TextChangeListener) iter.next(); |
| element.textChanged(changeEvent); |
| } |
| } |
| |
| /** |
| * sets consoleWidth, repairs line information, then fires event to the viewer text widget. |
| * @param width The console's width |
| */ |
| public void setWidth(int width) { |
| if (width != consoleWidth) { |
| consoleWidth = width; |
| repairLines(0); |
| TextChangedEvent changeEvent = new TextChangedEvent(this); |
| for (Iterator iter = textChangeListeners.iterator(); iter.hasNext();) { |
| TextChangeListener element = (TextChangeListener) iter.next(); |
| element.textSet(changeEvent); |
| } |
| } |
| } |
| } |