diff --git a/org.eclipse.tm.terminal.serial/src/org/eclipse/tm/internal/terminal/serial/SerialPortHandler.java b/org.eclipse.tm.terminal.serial/src/org/eclipse/tm/internal/terminal/serial/SerialPortHandler.java index a34e182617a..bd3dfd519d2 100644 --- a/org.eclipse.tm.terminal.serial/src/org/eclipse/tm/internal/terminal/serial/SerialPortHandler.java +++ b/org.eclipse.tm.terminal.serial/src/org/eclipse/tm/internal/terminal/serial/SerialPortHandler.java @@ -58,7 +58,7 @@ public class SerialPortHandler implements try { while (fConn.getInputStream() != null && fConn.getInputStream().available() > 0) { int nBytes = fConn.getInputStream().read(bytes); - fControl.writeToTerminal(new String(bytes, 0, nBytes)); + fControl.getRemoteToTerminalOutputStream().write(bytes, 0, nBytes); } } catch (IOException ex) { fControl.displayTextInTerminal(ex.getMessage()); diff --git a/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/internal/terminal/ssh/SshConnection.java b/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/internal/terminal/ssh/SshConnection.java index 968007d39c3..ca0ba1434bc 100644 --- a/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/internal/terminal/ssh/SshConnection.java +++ b/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/internal/terminal/ssh/SshConnection.java @@ -148,8 +148,7 @@ class SshConnection extends Thread { int n; // read until the thread gets interrupted.... while( (n=in.read(bytes))!=-1) { - // we assume we get ASCII UTF8 bytes - fControl.writeToTerminal(new String(bytes,0,n,"UTF8")); //$NON-NLS-1$ + fControl.getRemoteToTerminalOutputStream().write(bytes,0,n); } } diff --git a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java index 45d91169301..17baf916ebc 100644 --- a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java +++ b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java @@ -18,6 +18,7 @@ package org.eclipse.tm.internal.terminal.control.impl; import java.io.IOException; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.net.SocketException; import org.eclipse.jface.dialogs.MessageDialog; @@ -449,24 +450,24 @@ public class TerminalControl implements ITerminalControlForText, ITerminalContro * @see org.eclipse.tm.terminal.ITerminalControl#displayTextInTerminal(java.lang.String) */ public void displayTextInTerminal(String text) { - writeToTerminal(text+"\r\n"); //$NON-NLS-1$ + writeToTerminal("\r\n"+text+"\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ } - - public void writeToTerminal(String txt) { - - // Do _not_ use asyncExec() here. Class TerminalText requires that - // its run() and setNewText() methods be called in strictly - // alternating order. If we were to call asyncExec() here, this - // loop might race around and call setNewText() twice in a row, - // which would lose data. - getTerminalText().setNewText(new StringBuffer(txt)); - if(Display.getDefault().getThread()==Thread.currentThread()) - getTerminalText().run(); - else - fDisplay.syncExec(getTerminalText()); + private void writeToTerminal(String text) { + try { + getRemoteToTerminalOutputStream().write(text.getBytes("ISO-8859-1")); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + // should never happen! + e.printStackTrace(); + } catch (IOException e) { + // should never happen! + e.printStackTrace(); + } } - + + public OutputStream getRemoteToTerminalOutputStream() { + return getTerminalText().getOutputStream(); + } protected boolean isLogCharEnabled() { return TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_CHAR); } diff --git a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalInputStream.java b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalInputStream.java new file mode 100644 index 00000000000..8ec3f10501b --- /dev/null +++ b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalInputStream.java @@ -0,0 +1,321 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Michael Scharf (Wind River) - initial implementation +*/ + +package org.eclipse.tm.internal.terminal.control.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.swt.widgets.Display; + +/** + * The main purpose of this class is to start a runnable in the + * display thread when data is available and to pretend no data + * is available after a given amount of time the runnable is running. + * + */ +public class TerminalInputStream extends InputStream { + /** + * The maximum time in milli seconds the {@link #fNotifyChange} runs until + * {@link #ready()} returns false. + */ + private final int fUITimeout; + /** + * The output stream used by the terminal backend to write to the terminal + */ + protected final OutputStream fOutputStream; + /** + * This runnable is called every time some characters are available from... + */ + private final Runnable fNotifyChange; + /** + * A shared timer for all terminals. This times is used to limit the + * time used in the display thread.... + */ + static Timer fgTimer=new Timer(false); + /** + * A blocking byte queue. + */ + private final BoundedByteBuffer fQueue; + + /** + * The maximum amount of data read and written in one shot. + * The timer cannot interrupt reading this amount of data. + * {@link #available()} and {@link #read(byte[], int, int)} + * This is used as optimization, because reading single characters + * can be very inefficient, because each call is synchronized. + */ + // block size must be smaller than the Queue capacity! + final int BLOCK_SIZE=64; + + + /** + * The runnable that si scheduled in the display tread. Takes care of + * the timeout management. It calls the {@link #fNotifyChange} + */ + // synchronized with fQueue! + private Runnable fRunnable; + + /** + * Used as flag to indicate that the current runnable + * has used enough time in the display thread. + * This variable is set by a timer thread after the + * Runnable starts to run in the Display thread after + * {@link #fUITimeout}. + */ + // synchronized with fQueue! + private boolean fEnoughDisplayTime; + + /** + * A byte bounded buffer used to synchronize the input and the output stream. + *
+ * Adapted from BoundedBufferWithStateTracking + * http://gee.cs.oswego.edu/dl/cpj/allcode.java + * http://gee.cs.oswego.edu/dl/cpj/ + *
+ * For some reasons a solution based on + * PipedOutputStream/PipedIntputStream + * does work *very* slowly: + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4404700 + *
+ *
+ */
+ class BoundedByteBuffer {
+ protected final byte[] fBuffer; // the elements
+ protected int fPutPos = 0; // circular indices
+ protected int fTakePos = 0;
+ protected int fUsedSlots = 0; // the count
+ public BoundedByteBuffer(int capacity) throws IllegalArgumentException {
+ // make sure we don't deadlock on too small capacity
+ if(capacity
+ */
+ public void close() throws IOException {
+ }
+
+ public int read(byte[] cbuf, int off, int len) throws IOException {
+ int n=0;
+ // read as much as we can using a single synchronized statement
+ synchronized (fQueue) {
+ try {
+ // The assumption is that the caller has used available to
+ // check if bytes are available! That's why we don't check
+ // for fEnoughDisplayTime!
+ // Make sure that not more than BLOCK_SIZE is read in one call
+ while(fQueue.size()>0 && n
- *
- * IMPORTANT: This method must be called in strict alternation with method
- * {@link #run()}.
- *
- *
- * @param newBuffer
- * The new buffer containing characters received from the remote
- * host.
+ * This method erases all text from the Terminal view.
*/
- public synchronized void setNewText(StringBuffer newBuffer) {
- if (Logger.isLogEnabled()) {
- Logger.log("new text: '" + Logger.encode(newBuffer.toString()) + "'"); //$NON-NLS-1$ //$NON-NLS-2$
- }
- newText = newBuffer;
-
- // When continuous output is being processed by the Terminal view code, it
- // consumes nearly 100% of the CPU. This fixes that. If this method is called
- // too frequently, we explicitly sleep for a short time so that the thread
- // executing this function (which is the thread reading from the socket or
- // serial port) doesn't consume 100% of the CPU. Without this code, the
- // Workbench GUI is practically hung when there is continuous output in the
- // Terminal view.
-
- long CurrentTime = System.currentTimeMillis();
-
- if (CurrentTime - LastNewOutputTime < 250 && newBuffer.length() > 10) {
- try {
- Thread.sleep(50);
- } catch (InterruptedException ex) {
- // Ignore.
- }
- }
-
- LastNewOutputTime = CurrentTime;
- }
-
- /**
- * This method erases all text from the Terminal view. This method is called
- * when the user chooses "Clear all" from the Terminal view context menu, so
- * we need to serialize this method with methods {@link #run()} and {@link
- * #setNewText(StringBuffer)}.
- */
- public synchronized void clearTerminal() {
+ public void clearTerminal() {
Logger.log("entered"); //$NON-NLS-1$
text.setText(""); //$NON-NLS-1$
cursorColumn = 0;
@@ -412,12 +365,9 @@ public class TerminalText implements Runnable, ControlListener {
/**
* This method is called when the user changes the Terminal view's font. We
* attempt to recompute the pixel width of the new font's characters and fix
- * the terminal's dimensions. This method must be synchronized to prevent it
- * from executing at the same time as run(), which displays new text. We
- * can't have the fields that represent the dimensions of the terminal
- * changing while we are rendering text.
+ * the terminal's dimensions.
*/
- public synchronized void fontChanged() {
+ public void fontChanged() {
Logger.log("entered"); //$NON-NLS-1$
characterPixelWidth = 0;
@@ -425,7 +375,6 @@ public class TerminalText implements Runnable, ControlListener {
if (text != null)
adjustTerminalDimensions();
}
-
/**
* This method executes in the Display thread to process data received from
* the remote host by class {@link TelnetConnection} and
@@ -434,7 +383,7 @@ public class TerminalText implements Runnable, ControlListener {
*
* These connectors write text to the terminal's buffer through
* {@link TerminalControl#writeToTerminal(String)} and then have
- * this run method exectued in the display thread. This method
+ * this run method executed in the display thread. This method
* must not execute at the same time as methods
* {@link #setNewText(StringBuffer)} and {@link #clearTerminal()}.
*
@@ -442,9 +391,7 @@ public class TerminalText implements Runnable, ControlListener {
* {@link #setNewText(StringBuffer)}.
*
*/
- public synchronized void run() {
- Logger.log("entered"); //$NON-NLS-1$
-
+ void processText() {
try {
// This method can be called just after the user closes the view, so we
// make sure not to cause a widget-disposed exception.
@@ -471,20 +418,23 @@ public class TerminalText implements Runnable, ControlListener {
// ISSUE: Is this causing the scroll-to-bottom-on-output behavior?
text.setCaretOffset(caretOffset);
-
- processNewText();
+ try {
+ processNewText();
+ } catch (IOException e) {
+ Logger.logException(e);
+ }
caretOffset = text.getCaretOffset();
} catch (Exception ex) {
Logger.logException(ex);
}
}
-
/**
* This method scans the newly received text, processing ANSI control
* characters and escape sequences and displaying normal text.
+ * @throws IOException
*/
- protected void processNewText() {
+ protected void processNewText() throws IOException {
Logger.log("entered"); //$NON-NLS-1$
// Stop the StyledText widget from redrawing while we manipulate its contents.
@@ -494,10 +444,8 @@ public class TerminalText implements Runnable, ControlListener {
// Scan the newly received text.
- characterIndex = 0;
-
- while (characterIndex < newText.length()) {
- char character = newText.charAt(characterIndex);
+ while (hasNextChar()) {
+ char character = getNextChar();
switch (ansiState) {
case ANSISTATE_INITIAL:
@@ -530,7 +478,7 @@ public class TerminalText implements Runnable, ControlListener {
break;
default:
- processNonControlCharacters();
+ processNonControlCharacters(character);
break;
}
break;
@@ -614,15 +562,12 @@ public class TerminalText implements Runnable, ControlListener {
ansiState = ANSISTATE_INITIAL;
break;
}
-
- ++characterIndex;
}
// Allow the StyledText widget to redraw itself.
text.setRedraw(true);
}
-
/**
* This method is called when we have parsed an OS Command escape sequence.
* The only one we support is "\e]0;...\u0007", which sets the terminal
@@ -1093,7 +1038,7 @@ public class TerminalText implements Runnable, ControlListener {
/**
* Delete one or more lines of text. Any lines below the deleted lines move
- * up, which we implmement by appending newlines to the end of the text.
+ * up, which we implement by appending newlines to the end of the text.
*/
protected void processAnsiCommand_M() {
int totalLines = text.getLineCount();
@@ -1355,9 +1300,9 @@ public class TerminalText implements Runnable, ControlListener {
/**
* This method processes a single parameter character in an ANSI escape
- * sequence. Paramters are the (optional) characters between the leading
+ * sequence. Parameters are the (optional) characters between the leading
* "\e[" and the command character in an escape sequence (e.g., in the
- * escape sequence "\e[20;10H", the paramter characters are "20;10").
+ * escape sequence "\e[20;10H", the parameter characters are "20;10").
* Parameters are integers separated by one or more ';'s.
*/
protected void processAnsiParameterCharacter(char ch) {
@@ -1368,44 +1313,35 @@ public class TerminalText implements Runnable, ControlListener {
ansiParameters[nextAnsiParameter].append(ch);
}
}
-
/**
* This method processes a contiguous sequence of non-control characters.
* This is a performance optimization, so that we don't have to insert or
* append each non-control character individually to the StyledText widget.
* A non-control character is any character that passes the condition in the
* below while loop.
+ * @throws IOException
*/
- protected void processNonControlCharacters() {
- int firstNonControlCharacterIndex = characterIndex;
- int newTextLength = newText.length();
- char character = newText.charAt(characterIndex);
-
+ protected void processNonControlCharacters(char character) throws IOException {
+ StringBuffer buffer=new StringBuffer();
+ buffer.append(character);
// Identify a contiguous sequence of non-control characters, starting at
// firstNonControlCharacterIndex in newText.
-
- while (character != '\u0000' && character != '\b' && character != '\t'
- && character != '\u0007' && character != '\n'
- && character != '\r' && character != '\u001b') {
- ++characterIndex;
-
- if (characterIndex >= newTextLength)
+ while(hasNextChar()) {
+ character=getNextChar();
+ if(character == '\u0000' || character == '\b' || character == '\t'
+ || character == '\u0007' || character == '\n'
+ || character == '\r' || character == '\u001b') {
+ pushBackChar(character);
break;
-
- character = newText.charAt(characterIndex);
+ }
+ buffer.append(character);
}
-
- // Move characterIndex back by one character because it gets incremented at the
- // bottom of the loop in processNewText().
-
- --characterIndex;
-
int preDisplayCaretOffset = text.getCaretOffset();
// Now insert the sequence of non-control characters in the StyledText widget
// at the location of the cursor.
- displayNewText(firstNonControlCharacterIndex, characterIndex);
+ displayNewText(buffer.toString());
// If any one of the current font style, foreground color or background color
// differs from the defaults, apply the current style to the newly displayed
@@ -1438,23 +1374,18 @@ public class TerminalText implements Runnable, ControlListener {
* text being displayed by this method (this includes newlines, carriage
* returns, and tabs).
*
- *
- * @param first
- * The index (within newText) of the first character to display.
- * @param last
- * The index (within newText) of the last character to display.
*/
- protected void displayNewText(int first, int last) {
+ protected void displayNewText(String buffer) {
if (text.getCaretOffset() == text.getCharCount()) {
// The cursor is at the very end of the terminal's text, so we append the
// new text to the StyledText widget.
- displayNewTextByAppending(first, last);
+ displayNewTextByAppending(buffer);
} else {
// The cursor is not at the end of the screen's text, so we have to
// overwrite existing text.
- displayNewTextByOverwriting(first, last);
+ displayNewTextByOverwriting(buffer);
}
}
@@ -1467,14 +1398,11 @@ public class TerminalText implements Runnable, ControlListener {
* text being displayed by this method (this includes newlines, carriage
* returns, and tabs).
*
- *
- * @param first
- * The index (within newText) of the first character to display.
- * @param last
- * The index (within newText) of the last character to display.
*/
- protected void displayNewTextByAppending(int first, int last) {
- int numCharsToOutput = last - first + 1;
+ protected void displayNewTextByAppending(String newText) {
+ int first=0;
+ int last=newText.length()-1;
+ int numCharsToOutput = newText.length();
int availableSpaceOnLine = widthInColumns - cursorColumn;
if (numCharsToOutput >= availableSpaceOnLine) {
@@ -1525,16 +1453,13 @@ public class TerminalText implements Runnable, ControlListener {
* text being displayed by this method (this includes newlines, carriage
* returns, and tabs).
*
- *
- * @param first
- * The index (within newText) of the first character to display.
- * @param last
- * The index (within newText) of the last character to display.
*/
- protected void displayNewTextByOverwriting(int first, int last) {
+ protected void displayNewTextByOverwriting(String newText) {
// First, break new text into segments, based on where it needs to line wrap,
// so that each segment contains text that will appear on a separate
// line.
+ int first=0;
+ int last=newText.length()-1;
List textSegments = new ArrayList(100);
@@ -1569,7 +1494,7 @@ public class TerminalText implements Runnable, ControlListener {
if (caretOffset == text.getCharCount()) {
// The cursor is at the end of the text, so just append the current
- // segement along with a newline.
+ // segment along with a newline.
text.append(segment);
@@ -1823,7 +1748,7 @@ public class TerminalText implements Runnable, ControlListener {
// terminal.
ITerminalConnector telnetConnection = terminal.getTerminalConnection();
-
+ // TODO MSA: send only if dimensions have really changed!
if (telnetConnection != null && widthInColumns != 0 && heightInLines != 0) {
telnetConnection.setTerminalSize(widthInColumns, heightInLines);
}
@@ -1898,7 +1823,7 @@ public class TerminalText implements Runnable, ControlListener {
}
/**
- * This method returns the relative line number of the line comtaining the
+ * This method returns the relative line number of the line containing the
* cursor. The returned line number is relative to the topmost visible line,
* which has relative line number 0.
*
@@ -2077,4 +2002,43 @@ public class TerminalText implements Runnable, ControlListener {
protected void setLimitOutput(boolean limitOutput) {
fLimitOutput = limitOutput;
}
+
+ public OutputStream getOutputStream() {
+ return fTerminalInputStream.getOutputStream();
+ }
+
+ /**
+ * Buffer for {@link #pushBackChar(char)}.
+ */
+ private int fNextChar=-1;
+ private char getNextChar() throws IOException {
+ int c=-1;
+ if(fNextChar!=-1) {
+ c= fNextChar;
+ fNextChar=-1;
+ } else {
+ c = fReader.read();
+ }
+ // TODO: better end of file handling
+ if(c==-1)
+ c=0;
+ return (char)c;
+ }
+
+ private boolean hasNextChar() throws IOException {
+ if(fNextChar>=0)
+ return true;
+ return fReader.ready();
+ }
+
+ /**
+ * Put back one character to the stream. This method can push
+ * back exactly one character. The character is the next character
+ * returned by {@link #getNextChar}
+ * @param c the character to be pushed back.
+ */
+ void pushBackChar(char c) {
+ //assert fNextChar!=-1: "Already a character waiting:"+fNextChar; //$NON-NLS-1$
+ fNextChar=c;
+ }
}
diff --git a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnection.java b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnection.java
index 44ebd2f1dc3..bdb5655bac4 100644
--- a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnection.java
+++ b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnection.java
@@ -280,8 +280,8 @@ public class TelnetConnection extends Thread implements TelnetCodes {
return localEcho;
}
- private void writeToTerminal(String string) {
- terminalControl.writeToTerminal(string);
+ private void displayTextInTerminal(String string) {
+ terminalControl.displayTextInTerminal(string);
}
/**
@@ -304,7 +304,7 @@ public class TelnetConnection extends Thread implements TelnetCodes {
// Announce to the user that the remote endpoint has closed the
// connection.
- writeToTerminal("\r"+TelnetMessages.CONNECTION_CLOSED_BY_FOREIGN_HOST+"\r\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ displayTextInTerminal(TelnetMessages.CONNECTION_CLOSED_BY_FOREIGN_HOST);
// Tell the ITerminalControl object that the connection is
// closed.
@@ -321,7 +321,7 @@ public class TelnetConnection extends Thread implements TelnetCodes {
int nProcessedBytes = processTelnetProtocol(nRawBytes);
if (nProcessedBytes > 0) {
- writeToTerminal(new String(processedBytes, 0, nProcessedBytes));
+ terminalControl.getRemoteToTerminalOutputStream().write(processedBytes, 0, nProcessedBytes);
}
}
}
diff --git a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnector.java b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnector.java
index 91081a203e7..03fc62c9ef2 100644
--- a/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnector.java
+++ b/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/telnet/TelnetConnector.java
@@ -135,8 +135,11 @@ public class TelnetConnector implements ITerminalConnector {
public void setTelnetConnection(TelnetConnection connection) {
fTelnetConnection=connection;
}
- public void writeToTerminal(String txt) {
- fControl.writeToTerminal(txt);
+ public void displayTextInTerminal(String text) {
+ fControl.displayTextInTerminal(text);
+ }
+ public OutputStream getRemoteToTerminalOutputStream () {
+ return fControl.getRemoteToTerminalOutputStream();
}
public void setState(TerminalState state) {
diff --git a/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/ITerminalControl.java b/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/ITerminalControl.java
index 53f9460b7e7..a34830b3e3a 100644
--- a/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/ITerminalControl.java
+++ b/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/ITerminalControl.java
@@ -11,6 +11,8 @@
*******************************************************************************/
package org.eclipse.tm.terminal;
+import java.io.OutputStream;
+
import org.eclipse.swt.widgets.Shell;
/**
@@ -46,10 +48,11 @@ public interface ITerminalControl {
void displayTextInTerminal(String text);
/**
- * Write a string directly to the terminal.
- * @param txt
+ * @return a stream used to write to the terminal. Any bytes written to this
+ * stream appear in the terminal or are interpreted by the emulator as
+ * control sequences.
*/
- void writeToTerminal(String txt);
+ OutputStream getRemoteToTerminalOutputStream();
/**
* Set the title of the terminal view.