blob: 0c5a0220896f4bdee4e8928e25311ad9a7a41469 [file] [log] [blame]
package org.eclipse.debug.internal.ui.views.console;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.ITextStore;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
public class ConsoleDocument extends AbstractDocument implements IDebugEventSetListener {
private boolean fClosed= false;
private boolean fKilled= false;
protected IProcess fProcess;
private IStreamsProxy fProxy;
private int fLastStreamWriteEnd= 0;
private int fLastWritePosition= 0;
private int fNewStreamWriteEnd= 0;
protected boolean fNeedsToStartReading= true;
class StreamEntry {
/**
* Whether written to std out or std err - one of OUT/ERR
*/
private int fKind = -1;
/**
* The text written
*/
private String fText = null;
StreamEntry(String text, int kind) {
fText = text;
fKind = kind;
}
/**
* Returns the kind of entry - OUT or ERR
*/
public int getKind() {
return fKind;
}
/**
* Returns the text written
*/
public String getText() {
return fText;
}
}
/**
* A queue of stream entries written to standard out and standard err.
* Entries appended to the end of the queue and removed from the front.
* Intentionally a vector to obtain synchronization as entries are
* added and removed.
*/
private Vector fQueue = new Vector(10);
/**
* Thread that polls the queue for new output
*/
private Thread fPollingThread = null;
/**
* Whether an append is still in progress or to be run
*/
private boolean fAppending = false;
/**
* Whether associated process has terminated
*/
private boolean fTerminated = false;
/**
* Whether to keep polling
*/
private boolean fPoll = false;
/**
* The base number of milliseconds to pause
* between polls.
*/
private static final long BASE_DELAY= 50L;
public static final int OUT= 0;
public static final int ERR= 1;
protected List fStyleRanges= new ArrayList(2);
protected ConsoleViewer fConsoleViewer= null;
protected IStreamListener fSystemOutListener= new IStreamListener() {
public void streamAppended(String newText, IStreamMonitor monitor) {
DebugUIPlugin.getConsoleDocumentManager().aboutToWriteSystemOut(ConsoleDocument.this);
systemOutAppended(newText);
}
};
protected IStreamListener fSystemErrListener= new IStreamListener() {
public void streamAppended(String newText, IStreamMonitor monitor) {
DebugUIPlugin.getConsoleDocumentManager().aboutToWriteSystemErr(ConsoleDocument.this);
systemErrAppended(newText);
}
};
public ConsoleDocument(IProcess process) {
super();
fProcess= process;
if (process != null) {
fProxy= process.getStreamsProxy();
fTerminated = process.isTerminated();
DebugPlugin.getDefault().addDebugEventListener(this);
setTextStore(new ConsoleOutputTextStore(2500));
} else {
fClosed= true;
fTerminated = true;
setTextStore(new ConsoleOutputTextStore(0));
}
setLineTracker(new DefaultLineTracker());
completeInitialization();
}
public void close() {
if (!fClosed) {
fClosed= true;
stopReading();
DebugPlugin.getDefault().removeDebugEventListener(this);
fStyleRanges= Collections.EMPTY_LIST;
set(""); //$NON-NLS-1$
}
}
/**
* Stops reading/polling immediately
*/
public void kill() {
fKilled = true;
if (fPollingThread != null && fPollingThread.isAlive()) {
fPollingThread.interrupt();
}
close();
}
public boolean isClosed() {
return fClosed;
}
/**
* The user has typed into the console.
*
* @see IDocument#replace(int, int, String)
*/
public void replace(int pos, int replaceLength, String text) {
if (isReadOnly() || pos < getStartOfEditableContent()) {
return;
}
replace0(pos, replaceLength, text);
if (!isClosed()) {
// echo the data to the std in of the associated process
String[] lineDelimiters= getLegalLineDelimiters();
for (int i= 0; i < lineDelimiters.length; i++) {
if (lineDelimiters[i].equals(text)) {
try {
String inText= get();
fLastWritePosition = fLastStreamWriteEnd;
inText= inText.substring(fLastWritePosition, inText.length());
if (inText.length() == 0) {
break;
}
fProxy.write(inText);
fLastStreamWriteEnd= getLength();
break;
} catch (IOException ioe) {
DebugUIPlugin.log(ioe);
}
}
}
}
int docLength= getLength();
if (docLength == fNewStreamWriteEnd) {
//removed all of the user input text
fStyleRanges.remove(fStyleRanges.size() - 1);
} else {
updateInputStyleRange(docLength, fNewStreamWriteEnd);
//notify the viewer that the style ranges have changed.
fireDocumentChanged(new DocumentEvent(this, 0, 0, "")); //$NON-NLS-1$
}
}
/**
* Replace text used to add content from streams even though
* the process is terminated (and therefore the doc is "read only")
*/
protected void replace0(int pos, int replaceLength, String text) {
try {
super.replace(pos, replaceLength, text);
} catch (BadLocationException ble) {
DebugUIPlugin.log(ble);
}
}
/**
* @see IDocument#set(String)
*/
public void set(String text) {
fNewStreamWriteEnd= text.length();
super.set(text);
fLastStreamWriteEnd= fNewStreamWriteEnd;
}
public void startReading() {
if (fProxy == null) {
return;
}
if (!fNeedsToStartReading) {
return;
}
fNeedsToStartReading= false;
IStreamMonitor monitor= fProxy.getOutputStreamMonitor();
if (monitor != null) {
monitor.addListener(fSystemOutListener);
String contents= monitor.getContents();
if (contents.length() > 0) {
systemOutAppended(contents);
}
}
monitor= fProxy.getErrorStreamMonitor();
if (monitor != null) {
monitor.addListener(fSystemErrListener);
String contents= monitor.getContents();
if (contents.length() > 0) {
systemErrAppended(contents);
}
}
Runnable r = new Runnable() {
public void run() {
pollAndSleep();
}
};
fPoll = true;
fPollingThread = new Thread(r, "Console Polling Thread"); //$NON-NLS-1$
fPollingThread.start();
}
/**
* Polls and sleeps until closed or the associated
* process terminates
*/
protected void pollAndSleep() {
while (!fKilled && fPoll && (!fTerminated || !fQueue.isEmpty())) {
poll();
try {
Thread.sleep(BASE_DELAY);
} catch (InterruptedException e) {
}
}
}
/**
* Polls the queue for new output and updates this document
*/
protected void poll() {
if (isAppendInProgress()) {
return;
}
synchronized(fQueue) {
StringBuffer buffer = null;
StreamEntry prev = null;
int processed = 0;
int amount = 0;
while (!fKilled && processed < fQueue.size() && amount < 8096) {
StreamEntry entry = (StreamEntry)fQueue.get(processed);
if (prev == null) {
buffer = new StringBuffer(entry.getText());
} else {
if (prev.getKind() == entry.getKind()) {
buffer.append(entry.getText());
} else {
// only do one append per poll
break;
}
}
prev = entry;
processed++;
amount+= entry.getText().length();
}
if (buffer != null) {
appendToDocument(buffer.toString(), prev.getKind());
}
for (int i = 0; i < processed; i++) {
fQueue.remove(0);
}
}
}
protected void stopReading() {
fPoll = false;
if (fProxy == null) {
return;
}
fNeedsToStartReading= true;
IStreamMonitor monitor= fProxy.getOutputStreamMonitor();
monitor.removeListener(fSystemOutListener);
monitor= fProxy.getErrorStreamMonitor();
monitor.removeListener(fSystemErrListener);
}
/**
* System out or System error has had text append to it.
* Adds the new text to the document.
*
* @see IStreamListener#streamAppended(String, IStreamMonitor)
*/
protected void appendToDocument(final String text, final int source) {
setAppendInProgress(true);
update(new Runnable() {
public void run() {
int appendedLength= text.length();
fNewStreamWriteEnd= fLastStreamWriteEnd + appendedLength;
updateOutputStyleRanges(source, getLength() + appendedLength, fLastStreamWriteEnd, fNewStreamWriteEnd);
replace0(fLastStreamWriteEnd, 0, text);
fLastStreamWriteEnd= fNewStreamWriteEnd;
setAppendInProgress(false);
}
});
}
/**
* System out or System error has had text append to it.
* Adds a new entry to the queue.
*/
protected void streamAppended(String text, int source) {
fQueue.add(new StreamEntry(text, source));
}
/**
* @see IStreamListener#streamAppended(String, IStreamMonitor)
*/
protected void systemErrAppended(String text) {
streamAppended(text, ERR);
}
/**
* @see IStreamListener#streamAppended(String, IStreamMonitor)
*/
protected void systemOutAppended(String text) {
streamAppended(text, OUT);
}
/**
* @see Object#equals(Object)
*/
public boolean equals(Object obj) {
boolean correctInstance= obj instanceof ConsoleDocument;
if (fProcess != null) {
return correctInstance && fProcess.equals(((ConsoleDocument)obj).fProcess);
} else {
return correctInstance && ((ConsoleDocument)obj).fProcess == null;
}
}
/**
* @see Object#hashCode()
*/
public int hashCode() {
return (fProcess != null) ? fProcess.hashCode() : super.hashCode();
}
protected StyleRange[] getStyleRanges() {
if (fStyleRanges.isEmpty()) {
return new StyleRange[]{};
}
StyleRange[] sRanges= new StyleRange[fStyleRanges.size()];
return (StyleRange[])fStyleRanges.toArray(sRanges);
}
/**
* Coalese that last two style ranges if they are similar
*/
protected void coaleseRanges() {
int size= fStyleRanges.size();
if (size > 1) {
StyleRange last= (StyleRange) fStyleRanges.get(size - 1);
StyleRange nextToLast= (StyleRange) fStyleRanges.get(size - 2);
if (last.similarTo(nextToLast)) {//same color?
StyleRange newRange= new StyleRange(nextToLast.start, last.length + nextToLast.length, last.foreground, null);
fStyleRanges.remove(size - 1);
fStyleRanges.remove(size - 2);
addNewStyleRange(newRange);
}
}
}
/**
* Returns whether the document's underlying process is
* terminated.
*/
protected boolean isReadOnly() {
return (fProcess != null) ? fProcess.isTerminated() : true;
}
/**
* Updates the current input style range.
*/
protected void updateInputStyleRange(int docLength, int newWriteEnd) {
if (docLength != newWriteEnd) {
StyleRange input=
new StyleRange(newWriteEnd, docLength - newWriteEnd,
DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_IN_RGB),
null);
if (!fStyleRanges.isEmpty()) {
if (((StyleRange)fStyleRanges.get(fStyleRanges.size() - 1)).similarTo(input)) {
//remove the top "input" range...continuing input
fStyleRanges.remove(fStyleRanges.size() - 1);
}
}
addNewStyleRange(input);
}
}
protected void updateOutputStyleRanges(int sourceStream, int docLength, int prevWriteEnd, int newWriteEnd) {
if (docLength == 0) {
return;
}
if ((newWriteEnd == 0) && (0 == prevWriteEnd)) {
return;
}
if (newWriteEnd == prevWriteEnd) {
return;
}
Color newRangeColor=
(sourceStream == ConsoleDocument.OUT) ? DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_OUT_RGB) : DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_ERR_RGB);
StyleRange newRange= new StyleRange(prevWriteEnd, newWriteEnd - prevWriteEnd, newRangeColor, null);
if (!fStyleRanges.isEmpty()) {
if ((docLength != newWriteEnd) &&
((StyleRange)fStyleRanges.get(fStyleRanges.size() - 1)).foreground ==
DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_IN_RGB)) {
//remove the top "input" range..it will get recalculated in updateInputStyleRanges
fStyleRanges.remove(fStyleRanges.size() - 1);
}
}
addNewStyleRange(newRange);
coaleseRanges();
updateInputStyleRange(docLength, newWriteEnd);
}
/**
* Adds a new style range if the document is not closed.
* Note that the document can be closed by a separate thread.
* This is the reason for the copy of the style ranges.
*/
protected void addNewStyleRange(StyleRange newRange) {
List tempRanges= fStyleRanges;
if (fClosed) {
return;
}
tempRanges.add(newRange);
}
protected void setStyleRanges(List ranges) {
fStyleRanges= ranges;
}
protected void clearDocument() {
fQueue.clear();
fStyleRanges= new ArrayList(2);
set(""); //$NON-NLS-1$
}
/**
* Returns the position after which editing of the
* content is allowable.
*/
protected int getStartOfEditableContent() {
return fLastStreamWriteEnd;
}
/**
* Make visible to the ConsoleViewer
*/
protected ITextStore getStore() {
return super.getStore();
}
/**
* @see IDebugEventSetListener#handleDebugEvents(DebugEvent[])
*/
public void handleDebugEvents(DebugEvent[] events) {
if (fProcess == null) {
return;
}
for (int i = 0; i < events.length; i++) {
DebugEvent event = events[i];
if (event.getKind() == DebugEvent.TERMINATE) {
Object element= event.getSource();
if (element != null && element.equals(fProcess)) {
fTerminated = true;
update( new Runnable() {
public void run() {
fireDocumentChanged(new DocumentEvent(ConsoleDocument.this, 0, 0, null));
}
});
}
}
}
}
/**
* Posts the update code "behind" the running operation if the
* UI will be updated.
*/
protected void update(Runnable runnable) {
if (fConsoleViewer != null && fConsoleViewer.getControl() != null && !fConsoleViewer.getControl().isDisposed()) {
fConsoleViewer.getControl().getDisplay().asyncExec(runnable);
} else {
Display display= DebugUIPlugin.getDefault().getStandardDisplay();
if (display != null && !display.isDisposed()) {
display.asyncExec(runnable);
}
}
}
/**
* Sets the console viewer that this document is viewed within.
* Can be set to <code>null</code> if no longer currently being
* viewed.
*/
protected void setConsoleViewer(ConsoleViewer viewer) {
fConsoleViewer = viewer;
}
/**
* Sets whether a runnable has been submitted to update the console
* document.
*/
protected void setAppendInProgress(boolean appending) {
fAppending = appending;
}
/**
* Sets whether a runnable has been submitted to update the console
* document.
*/
protected boolean isAppendInProgress() {
return fAppending;
}
}