| /******************************************************************************* |
| * 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.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.IJobManager; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentPartitionerExtension; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITypedRegion; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.ui.console.ConsolePlugin; |
| import org.eclipse.ui.console.IConsoleDocumentPartitioner; |
| import org.eclipse.ui.console.IOConsole; |
| import org.eclipse.ui.console.IOConsoleInputStream; |
| import org.eclipse.ui.console.IOConsoleOutputStream; |
| import org.eclipse.ui.progress.UIJob; |
| import org.eclipse.ui.progress.WorkbenchJob; |
| |
| /** |
| * Partitions an IOConsole's document |
| * @since 3.1 |
| * |
| */ |
| public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocumentPartitionerExtension { |
| private PendingPartition consoleClosedPartition; |
| private IDocument document; |
| private ArrayList partitions; |
| /** |
| * Blocks of data that have not yet been appended to the document. |
| */ |
| private ArrayList pendingPartitions; |
| /** |
| * A list of PendingPartitions to be appended by the updateJob |
| */ |
| private ArrayList updatePartitions; |
| /** |
| * The last partition appended to the document |
| */ |
| private IOConsolePartition lastPartition; |
| /** |
| * Job that appends pending partitions to the document. |
| */ |
| private QueueProcessingJob queueJob; |
| /** |
| * The input stream attached to this document. |
| */ |
| private IOConsoleInputStream inputStream; |
| /** |
| * Flag to indicate that the updateJob is updating the document. |
| */ |
| private boolean updateInProgress; |
| /** |
| * A list of partitions containing input from the console, that have |
| * not been appended to the input stream yet. |
| */ |
| private ArrayList inputPartitions; |
| /** |
| * offset used by updateJob |
| */ |
| private int firstOffset; |
| /** |
| * An array of legal line delimiters |
| */ |
| private String[] lld; |
| private int highWaterMark = -1; |
| private int lowWaterMark = -1; |
| private boolean connected = false; |
| |
| private IOConsole console; |
| |
| private TrimJob trimJob = new TrimJob(); |
| /** |
| * Lock for appending to and removing from the document - used |
| * to synchronize addition of new text/partitions in the update |
| * job and handling buffer overflow/clearing of the console. |
| */ |
| private Object overflowLock = new Object(); |
| |
| |
| private int fBuffer; |
| |
| public IOConsolePartitioner(IOConsoleInputStream inputStream, IOConsole console) { |
| this.inputStream = inputStream; |
| this.console = console; |
| trimJob.setRule(console.getSchedulingRule()); |
| } |
| |
| public IDocument getDocument() { |
| return document; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#connect(org.eclipse.jface.text.IDocument) |
| */ |
| public void connect(IDocument doc) { |
| document = doc; |
| document.setDocumentPartitioner(this); |
| lld = document.getLegalLineDelimiters(); |
| partitions = new ArrayList(); |
| pendingPartitions = new ArrayList(); |
| inputPartitions = new ArrayList(); |
| queueJob = new QueueProcessingJob(); |
| queueJob.setSystem(true); |
| queueJob.setPriority(Job.INTERACTIVE); |
| queueJob.setRule(console.getSchedulingRule()); |
| connected = true; |
| } |
| |
| public int getHighWaterMark() { |
| return highWaterMark; |
| } |
| |
| public int getLowWaterMark() { |
| return lowWaterMark; |
| } |
| |
| public void setWaterMarks(int low, int high) { |
| lowWaterMark = low; |
| highWaterMark = high; |
| ConsolePlugin.getStandardDisplay().asyncExec(new Runnable() { |
| public void run() { |
| checkBufferSize(); |
| } |
| }); |
| } |
| |
| /** |
| * Notification from the console that all of its streams have been closed. |
| */ |
| public void streamsClosed() { |
| consoleClosedPartition = new PendingPartition(null, null); |
| synchronized (pendingPartitions) { |
| pendingPartitions.add(consoleClosedPartition); |
| } |
| queueJob.schedule(); //ensure that all pending partitions are processed. |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#disconnect() |
| */ |
| public void disconnect() { |
| synchronized (overflowLock) { |
| document = null; |
| partitions.clear(); |
| connected = false; |
| try { |
| inputStream.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) |
| */ |
| public void documentAboutToBeChanged(DocumentEvent event) { |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(org.eclipse.jface.text.DocumentEvent) |
| */ |
| public boolean documentChanged(DocumentEvent event) { |
| return documentChanged2(event) != null; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes() |
| */ |
| public String[] getLegalContentTypes() { |
| return new String[] { IOConsolePartition.OUTPUT_PARTITION_TYPE, IOConsolePartition.INPUT_PARTITION_TYPE }; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int) |
| */ |
| public String getContentType(int offset) { |
| return getPartition(offset).getType(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int) |
| */ |
| public ITypedRegion[] computePartitioning(int offset, int length) { |
| int rangeEnd = offset + length; |
| int left= 0; |
| int right= partitions.size() - 1; |
| int mid= 0; |
| IOConsolePartition position= null; |
| |
| if (left == right) { |
| return new IOConsolePartition[]{(IOConsolePartition) partitions.get(0)}; |
| } |
| while (left < right) { |
| |
| mid= (left + right) / 2; |
| |
| position= (IOConsolePartition) partitions.get(mid); |
| if (rangeEnd < position.getOffset()) { |
| if (left == mid) |
| right= left; |
| else |
| right= mid -1; |
| } else if (offset > (position.getOffset() + position.getLength() - 1)) { |
| if (right == mid) |
| left= right; |
| else |
| left= mid +1; |
| } else { |
| left= right= mid; |
| } |
| } |
| |
| |
| List list = new ArrayList(); |
| int index = left - 1; |
| if (index >= 0) { |
| position= (IOConsolePartition) partitions.get(index); |
| while (index >= 0 && (position.getOffset() + position.getLength()) > offset) { |
| index--; |
| if (index >= 0) { |
| position= (IOConsolePartition) partitions.get(index); |
| } |
| } |
| } |
| index++; |
| position= (IOConsolePartition) partitions.get(index); |
| while (index < partitions.size() && (position.getOffset() < rangeEnd)) { |
| list.add(position); |
| index++; |
| if (index < partitions.size()) { |
| position= (IOConsolePartition) partitions.get(index); |
| } |
| } |
| |
| return (ITypedRegion[]) list.toArray(new IOConsolePartition[list.size()]); |
| } |
| |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int) |
| */ |
| public ITypedRegion getPartition(int offset) { |
| for (int i = 0; i < partitions.size(); i++) { |
| ITypedRegion partition = (ITypedRegion) partitions.get(i); |
| int start = partition.getOffset(); |
| int end = start + partition.getLength(); |
| if (offset >= start && offset < end) { |
| return partition; |
| } |
| } |
| |
| if (lastPartition == null) { |
| synchronized(partitions) { |
| lastPartition = new IOConsolePartition(inputStream, ""); //$NON-NLS-1$ |
| lastPartition.setOffset(offset); |
| partitions.add(lastPartition); |
| inputPartitions.add(lastPartition); |
| } |
| } |
| return lastPartition; |
| } |
| |
| /** |
| * Enforces the buffer size. |
| * When the number of lines in the document exceeds the high water mark, the |
| * beginning of the document is trimmed until the number of lines equals the |
| * low water mark. |
| */ |
| private void checkBufferSize() { |
| if (document != null && highWaterMark > 0) { |
| int length = document.getLength(); |
| if (length > highWaterMark) { |
| if (trimJob.getState() == Job.NONE) { //if the job isn't already running |
| trimJob.setOffset(length - lowWaterMark); |
| trimJob.schedule(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clears the console |
| */ |
| public void clearBuffer() { |
| synchronized (overflowLock) { |
| trimJob.setOffset(-1); |
| trimJob.schedule(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(org.eclipse.jface.text.DocumentEvent) |
| */ |
| public IRegion documentChanged2(DocumentEvent event) { |
| if (document == null) { |
| return null; //another thread disconnected the partitioner |
| } |
| if (document.getLength() == 0) { //document cleared |
| lastPartition = null; |
| return new Region(0, 0); |
| } |
| |
| |
| if (updateInProgress) { |
| synchronized(partitions) { |
| if (updatePartitions != null) { |
| for (Iterator i = updatePartitions.iterator(); i.hasNext(); ) { |
| PendingPartition pp = (PendingPartition) i.next(); |
| if (pp == consoleClosedPartition) { |
| continue; |
| } |
| |
| int ppLen = pp.text.length(); |
| if (lastPartition != null && lastPartition.getStream() == pp.stream) { |
| int len = lastPartition.getLength(); |
| lastPartition.setLength(len + ppLen); |
| } else { |
| IOConsolePartition partition = new IOConsolePartition(pp.stream, ppLen); |
| partition.setOffset(firstOffset); |
| lastPartition = partition; |
| partitions.add(partition); |
| } |
| firstOffset += ppLen; |
| } |
| } |
| } |
| } else {// user input. |
| int amountDeleted = event.getLength() ; |
| |
| if (amountDeleted > 0) { |
| int offset = event.fOffset; |
| IOConsolePartition partition = (IOConsolePartition) getPartition(offset); |
| if(partition == lastPartition) { |
| partition.delete(event.fOffset-partition.getOffset(), amountDeleted); |
| } |
| } |
| |
| synchronized(partitions) { |
| if (lastPartition == null || lastPartition.isReadOnly()) { |
| lastPartition = new IOConsolePartition(inputStream, event.fText); |
| lastPartition.setOffset(event.fOffset); |
| partitions.add(lastPartition); |
| inputPartitions.add(lastPartition); |
| } else { |
| lastPartition.insert(event.fText, (event.fOffset-lastPartition.getOffset())); |
| } |
| |
| int lastLineDelimiter = -1; |
| String partitionText = lastPartition.getString(); |
| for (int i = 0; i < lld.length; i++) { |
| String ld = lld[i]; |
| int index = partitionText.lastIndexOf(ld); |
| if (index != -1) { |
| index += ld.length(); |
| } |
| if (index > lastLineDelimiter) { |
| lastLineDelimiter = index; |
| } |
| } |
| if (lastLineDelimiter != -1) { |
| StringBuffer input = new StringBuffer(); |
| Iterator it = inputPartitions.iterator(); |
| while (it.hasNext()) { |
| IOConsolePartition partition = (IOConsolePartition) it.next(); |
| if (partition.getOffset() + partition.getLength() <= event.fOffset + lastLineDelimiter) { |
| if (partition == lastPartition) { |
| lastPartition = null; |
| } |
| input.append(partition.getString()); |
| partition.clearBuffer(); |
| partition.setReadOnly(); |
| it.remove(); |
| } else { |
| //create a new partition containing everything up to the line delimiter |
| //and append that to the string buffer. |
| String contentBefore = partitionText.substring(0, lastLineDelimiter); |
| IOConsolePartition newPartition = new IOConsolePartition(inputStream, contentBefore); |
| newPartition.setOffset(partition.getOffset()); |
| newPartition.setReadOnly(); |
| newPartition.clearBuffer(); |
| int index = partitions.indexOf(partition); |
| partitions.add(index, newPartition); |
| input.append(contentBefore); |
| //delete everything that has been appended to the buffer. |
| partition.delete(0, lastLineDelimiter); |
| partition.setOffset(lastLineDelimiter + partition.getOffset()); |
| lastLineDelimiter = 0; |
| } |
| } |
| if (input.length() > 0) { |
| inputStream.appendData(input.toString()); |
| } |
| |
| } |
| } |
| } |
| |
| return new Region(event.fOffset, event.fText.length()); |
| } |
| |
| private void setUpdateInProgress(boolean b) { |
| updateInProgress = b; |
| } |
| |
| /** |
| * A stream has been appended, add to pendingPartions list and schedule updateJob. |
| * updateJob is scheduled with a slight delay, this allows the console to run the job |
| * less frequently and update the document with a greater amount of data each time |
| * the job is run |
| * @param stream The stream that was written to. |
| * @param s The string that should be appended to the document. |
| */ |
| public void streamAppended(IOConsoleOutputStream stream, String s) throws IOException { |
| if (document == null) { |
| throw new IOException("Document is closed"); //$NON-NLS-1$ |
| } |
| synchronized(pendingPartitions) { |
| PendingPartition last = (PendingPartition) (pendingPartitions.size() > 0 ? pendingPartitions.get(pendingPartitions.size()-1) : null); |
| if (last != null && last.stream == stream) { |
| last.append(s); |
| } else { |
| pendingPartitions.add(new PendingPartition(stream, s)); |
| if (fBuffer > 1000) { |
| queueJob.schedule(); |
| } else { |
| queueJob.schedule(50); |
| } |
| } |
| |
| if (fBuffer > 160000) { |
| try { |
| pendingPartitions.wait(); |
| } catch (InterruptedException e) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Holds data until updateJob can be run and the document can be updated. |
| */ |
| private class PendingPartition { |
| StringBuffer text = new StringBuffer(8192); |
| IOConsoleOutputStream stream; |
| |
| PendingPartition(IOConsoleOutputStream stream, String text) { |
| this.stream = stream; |
| if (text != null) { |
| append(text); |
| } |
| } |
| |
| void append(String moreText) { |
| text.append(moreText); |
| fBuffer += moreText.length(); |
| } |
| } |
| |
| /** |
| * Updates the document. Will append everything that is available before |
| * finishing. |
| */ |
| private class QueueProcessingJob extends UIJob { |
| |
| QueueProcessingJob() { |
| super("IOConsole Updater"); //$NON-NLS-1$ |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| synchronized (overflowLock) { |
| ArrayList pendingCopy = new ArrayList(); |
| StringBuffer buffer = null; |
| boolean consoleClosed = false; |
| while (pendingPartitions.size() > 0) { |
| synchronized(pendingPartitions) { |
| pendingCopy.addAll(pendingPartitions); |
| pendingPartitions.clear(); |
| fBuffer = 0; |
| pendingPartitions.notifyAll(); |
| } |
| |
| buffer = new StringBuffer(); |
| for (Iterator i = pendingCopy.iterator(); i.hasNext(); ) { |
| PendingPartition pp = (PendingPartition) i.next(); |
| if (pp != consoleClosedPartition) { |
| buffer.append(pp.text); |
| } else { |
| consoleClosed = true; |
| } |
| } |
| } |
| |
| if (connected) { |
| setUpdateInProgress(true); |
| updatePartitions = pendingCopy; |
| firstOffset = document.getLength(); |
| try { |
| document.replace(firstOffset, 0, buffer.toString()); |
| } catch (BadLocationException e) { |
| } |
| updatePartitions = null; |
| setUpdateInProgress(false); |
| } |
| if (consoleClosed) { |
| console.partitionerFinished(); |
| } |
| checkBufferSize(); |
| |
| } |
| |
| return Status.OK_STATUS; |
| } |
| |
| /* |
| * Job will process as much as it can each time it's run, but it gets |
| * scheduled everytime a PendingPartition is added to the list, meaning |
| * that this job could get scheduled unnecessarily in cases of heavy output. |
| * Note however, that schedule() will only reschedule a running/scheduled Job |
| * once even if it's called many times. |
| */ |
| public boolean shouldRun() { |
| boolean shouldRun = connected && pendingPartitions != null && pendingPartitions.size() > 0; |
| return shouldRun; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| /** |
| * Job to trim the console document, runs in the UI thread. |
| */ |
| private class TrimJob extends WorkbenchJob { |
| |
| /** |
| * trims output up to the line containing the given offset, |
| * or all output if -1. |
| */ |
| private int truncateOffset; |
| |
| /** |
| * Creates a new job to trim the buffer. |
| */ |
| TrimJob() { |
| super("Trim Job"); //$NON-NLS-1$ |
| setSystem(true); |
| } |
| |
| /** |
| * Sets the trim offset. |
| * |
| * @param offset trims output up to the line containing the given offset |
| */ |
| public void setOffset(int offset) { |
| truncateOffset = offset; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| IJobManager jobManager = Job.getJobManager(); |
| try { |
| jobManager.join(console, monitor); |
| } catch (OperationCanceledException e1) { |
| return Status.CANCEL_STATUS; |
| } catch (InterruptedException e1) { |
| return Status.CANCEL_STATUS; |
| } |
| if (document == null) { |
| return Status.OK_STATUS; |
| } |
| |
| int length = document.getLength(); |
| if (truncateOffset < length) { |
| synchronized (overflowLock) { |
| try { |
| if (truncateOffset < 0) { |
| // clear |
| setUpdateInProgress(true); |
| document.set(""); //$NON-NLS-1$ |
| setUpdateInProgress(false); |
| partitions.clear(); |
| } else { |
| // overflow |
| int cutoffLine = document.getLineOfOffset(truncateOffset); |
| int cutOffset = document.getLineOffset(cutoffLine); |
| |
| |
| // set the new length of the first partition |
| IOConsolePartition partition = (IOConsolePartition) getPartition(cutOffset); |
| partition.setLength(partition.getOffset() + partition.getLength() - cutOffset); |
| |
| setUpdateInProgress(true); |
| document.replace(0, cutOffset, ""); //$NON-NLS-1$ |
| setUpdateInProgress(false); |
| |
| //remove partitions and reset Partition offsets |
| int index = partitions.indexOf(partition); |
| for (int i = 0; i < index; i++) { |
| partitions.remove(0); |
| } |
| |
| int offset = 0; |
| for (Iterator i = partitions.iterator(); i.hasNext(); ) { |
| IOConsolePartition p = (IOConsolePartition) i.next(); |
| p.setOffset(offset); |
| offset += p.getLength(); |
| } |
| } |
| } catch (BadLocationException e) { |
| } |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.console.IConsoleDocumentPartitioner#isReadOnly(int) |
| */ |
| public boolean isReadOnly(int offset) { |
| return ((IOConsolePartition)getPartition(offset)).isReadOnly(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.console.IConsoleDocumentPartitioner#computeStyleRange(int, int) |
| */ |
| public StyleRange[] getStyleRanges(int offset, int length) { |
| if (!connected) { |
| return new StyleRange[0]; |
| } |
| IOConsolePartition[] computedPartitions = (IOConsolePartition[])computePartitioning(offset, length); |
| StyleRange[] styles = new StyleRange[computedPartitions.length]; |
| for (int i = 0; i < computedPartitions.length; i++) { |
| int rangeStart = Math.max(computedPartitions[i].getOffset(), offset); |
| int rangeLength = computedPartitions[i].getLength(); |
| styles[i] = computedPartitions[i].getStyleRange(rangeStart, rangeLength); |
| } |
| return styles; |
| } |
| |
| |
| } |