diff --git a/terminal/org.eclipse.tm.terminal/.project b/terminal/org.eclipse.tm.terminal/.project
new file mode 100644
index 00000000000..621a3a690cf
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/.project
@@ -0,0 +1,28 @@
+
+
+ * + *
+ * Logger.log("something has happened"); + * Logger.log("counter is " + counter); + *+ * + * @author Fran Litterio
+ * + * IMPORTANT: Understanding this code requires understanding the TELNET protocol and + * TELNET option processing, as defined in the RFCs listed below.
+ * + * @author Fran Litterio (francis.litterio@windriver.com) + * + * @see RFC 854 + * @see RFC 855 + * @see RFC 856 + * @see RFC 857 + * @see RFC 858 + * @see RFC 859 + * @see RFC 860 + * @see RFC 861 + * @see RFC 1091 + * @see RFC 1096 + * @see RFC 1073 + * @see RFC 1079 + * @see RFC 1143 + * @see RFC 1572 + */ +class TelnetConnection extends Thread implements TelnetCodes, TerminalMsg +{ + /** + * TELNET connection state: Initial state. + */ + protected static final int STATE_INITIAL = 0; + + /** + * TELNET connection state: Last byte processed was IAC code. + * code. + */ + protected static final int STATE_IAC_RECEIVED = 1; + + /** + * TELNET connection state: Last byte processed was WILL code. + * code. + */ + protected static final int STATE_WILL_RECEIVED = 2; + + /** + * TELNET connection state: Last byte processed was WONT code. + */ + protected static final int STATE_WONT_RECEIVED = 3; + + /** + * TELNET connection state: Last byte processed was DO code. + */ + protected static final int STATE_DO_RECEIVED = 4; + + /** + * TELNET connection state: Last byte processed was DONT code. + */ + protected static final int STATE_DONT_RECEIVED = 5; + + /** + * TELNET connection state: Last byte processed was SB. + */ + protected static final int STATE_SUBNEGOTIATION_STARTED = 6; + + /** + * TELNET connection state: Currently receiving sub-negotiation data. + */ + protected static final int STATE_RECEIVING_SUBNEGOTIATION = 7; + + /** + * Size of buffer for processing data received from remote endpoint. + */ + protected static final int BUFFER_SIZE = 2048; + + /** + * Holds raw bytes received from the remote endpoint, prior to any TELNET protocol + * processing. + */ + protected byte[] rawBytes = new byte[BUFFER_SIZE]; + + /** + * Holds incoming network data after the TELNET protocol bytes have been + * processed and removed. + */ + protected byte[] processedBytes = new byte[BUFFER_SIZE]; + + /** + * This field holds a StringBuffer containing text recently received from the + * remote endpoint (after all TELNET protocol bytes have been processed and + * removed). + */ + protected StringBuffer processedStringBuffer = new StringBuffer(BUFFER_SIZE); + + /** + * Holds the current state of the TELNET protocol processor. + */ + protected int telnetState = STATE_INITIAL; + + /** + * This field is true if the remote endpoint is a TELNET server, false if not. We + * set this to true if and only if the remote endpoint sends recognizable TELNET + * protocol data. We do not assume that the remote endpoint is a TELNET server + * just because it is listening on port 23. This allows us to successfully connect + * to a TELNET server listening on a port other than 23.
+ * + * When this field first changes from false to true, we send all WILL or DO + * commands to the remote endpoint.
+ *
+ * @see #telnetServerDetected()
+ */
+ protected boolean remoteIsTelnetServer = false;
+
+ /**
+ * An array of TelnetOption objects representing the local endpoint's TELNET
+ * options. The array is indexed by the numeric TELNET option code.
+ */
+ protected TelnetOption[] localOptions = new TelnetOption[256];
+
+ /**
+ * An array of TelnetOption objects representing the remote endpoint's TELNET
+ * options. The array is indexed by the numeric TELNET option code.
+ */
+ protected TelnetOption[] remoteOptions = new TelnetOption[256];
+
+ /**
+ * An array of bytes that holds the TELNET subnegotiation command most recently
+ * received from the remote endpoint. This array does _not_ include the leading
+ * IAC SB bytes, nor does it include the trailing IAC SE bytes. The first byte of
+ * this array is always a TELNET option code.
+ */
+ protected byte[] receivedSubnegotiation = new byte[128];
+
+ /**
+ * This field holds the index into array {@link receivedSubnegotiation} of the next
+ * unused byte. This is used by method {@link #processTelnetProtocol(int)} when
+ * the state machine is in states {@link #STATE_SUBNEGOTIATION_STARTED} and {@link
+ * STATE_RECEIVING_SUBNEGOTIATION}.
+ */
+ protected int nextSubnegotiationByteIndex = 0;
+
+ /**
+ * This field is true if an error occurs while processing a subnegotiation
+ * command.
+ *
+ * @see #processTelnetProtocol(int)
+ */
+ protected boolean ignoreSubnegotiation = false;
+
+ /**
+ * This field holds the width of the Terminal screen in columns.
+ */
+ protected int width = 0;
+
+ /**
+ * This field holds the height of the Terminal screen in rows.
+ */
+ protected int height = 0;
+
+ /**
+ * This field holds a reference to the {@link TerminalCtrl} singleton.
+ */
+ protected TerminalCtrl terminalControl;
+
+ /**
+ * This method holds the Socket object for the TELNET connection.
+ */
+ protected Socket socket;
+
+ /**
+ * This field holds a reference to a {@link TerminalText} object, which displays
+ * text to the user.
+ */
+ protected TerminalText terminalText;
+
+ /**
+ * This field holds a reference to an {@link InputStream} object used to receive
+ * data from the remote endpoint.
+ */
+ protected InputStream inputStream;
+
+ /**
+ * This field holds a reference to an {@link OutputStream} object used to send data
+ * to the remote endpoint.
+ */
+ protected OutputStream outputStream;
+
+ /**
+ * This field holds the SWT Display object for the GUI. We use this to execute a
+ * TerminalText method on the display thread, so that it can draw text in the view.
+ */
+ protected Display display;
+
+ /**
+ * UNDER CONSTRUCTION
+ */
+ protected boolean localEcho = true;
+
+ /**
+ * This constructor just initializes some internal object state from its
+ * arguments.
+ */
+ public TelnetConnection(TerminalCtrl terminalControl, Socket socket,
+ TerminalText terminalText)
+ throws IOException
+ {
+ super();
+
+ Logger.log("entered"); //$NON-NLS-1$
+
+ this.terminalControl = terminalControl;
+ this.socket = socket;
+ this.terminalText = terminalText;
+
+ inputStream = socket.getInputStream();
+ outputStream = socket.getOutputStream();
+ display = terminalControl.getTextWidget().getDisplay();
+
+ initializeOptions();
+ }
+
+ /**
+ * Returns true if the TCP connection represented by this object is connected,
+ * false otherwise.
+ */
+ public boolean isConnected()
+ {
+ return socket != null && socket.isConnected();
+ }
+
+ /**
+ * Returns true if the TCP connection represented by this object is connected and
+ * the remote endpoint is a TELNET server, false otherwise.
+ */
+ public boolean isRemoteTelnetServer()
+ {
+ return remoteIsTelnetServer;
+ }
+
+ /**
+ * This method sets the terminal width and height to the supplied values. If
+ * either new value differs from the corresponding old value, we initiate a NAWS
+ * subnegotiation to inform the remote endpoint of the new terminal size.
+ */
+ public void setTerminalSize(int newWidth, int newHeight)
+ {
+ Logger.log("Setting new size: width = " + newWidth + ", height = " + newHeight); //$NON-NLS-1$ //$NON-NLS-2$
+
+ boolean sizeChanged = false;
+
+ if (newWidth != width || newHeight != height)
+ sizeChanged = true;
+
+ width = newWidth;
+ height = newHeight;
+
+ if (sizeChanged && remoteIsTelnetServer && localOptions[TELNET_OPTION_NAWS].isEnabled())
+ {
+ Integer[] sizeData = { new Integer(width), new Integer(height) };
+
+ localOptions[TELNET_OPTION_NAWS].sendSubnegotiation(sizeData);
+ }
+ }
+
+ /**
+ * Returns true if local echoing is enabled for this TCP connection, false otherwise.
+ */
+ public boolean localEcho()
+ {
+ return localEcho;
+ }
+
+ /**
+ * This method runs in its own thread. It reads raw bytes from the TELNET
+ * connection socket, processes any TELNET protocol bytes (and removes them), and
+ * passes the remaining bytes to a TerminalDisplay object for display.
+ */
+ public void run()
+ {
+ Logger.log("Entered"); //$NON-NLS-1$
+
+ try
+ {
+ while (socket.isConnected())
+ {
+ int nRawBytes = inputStream.read(rawBytes);
+
+ if (nRawBytes == -1)
+ {
+ // End of input on inputStream.
+ Logger.log("End of input reading from socket!"); //$NON-NLS-1$
+
+ // Announce to the user that the remote endpoint has closed the
+ // connection.
+
+ processedStringBuffer.replace(0, processedStringBuffer.length(),
+ "\rConnection closed by foreign host.\r\n"); //$NON-NLS-1$
+
+ terminalText.setNewText(processedStringBuffer);
+
+ // See the large comment below for an explaination of why we must
+ // call Display.syncExec() instead of Display.asyncExec().
+
+ display.syncExec(terminalText);
+
+ // Tell the TerminalCtrl object that the connection is closed.
+
+ terminalControl.setOpened(false);
+ terminalControl.setConnected(false);
+
+ // Update the Terminal view UI to show a disconnected state. This
+ // ugliness involving Display.asyncExec() is forced on us by the
+ // bad design of class TerminalCtrl, which requires in certain
+ // cases (maybe all?) that TerminalCtrl.execute() is called only
+ // from the display thread.
+
+ Runnable disconnectNotifier = new Thread()
+ {
+ public void run()
+ {
+ terminalControl.execute(ON_TERMINAL_DISCONNECT, null);
+ }
+ };
+
+ display.asyncExec(disconnectNotifier);
+ break;
+ }
+ else
+ {
+ Logger.log("Received " + nRawBytes + " bytes: '" + //$NON-NLS-1$ //$NON-NLS-2$
+ new String(rawBytes, 0, nRawBytes) + "'"); //$NON-NLS-1$
+
+ // Process any TELNET protocol data that we receive. Don't send
+ // any TELNET protocol data until we are sure the remote endpoint
+ // is a TELNET server.
+
+ int nProcessedBytes = processTelnetProtocol(nRawBytes);
+
+ if (nProcessedBytes > 0)
+ {
+ String strBuffer = new String(processedBytes, 0, nProcessedBytes);
+
+ // An earlier version of this code created a new StringBuffer
+ // object here, but that was unnecessary. Instead, we reuse an
+ // existing object (processedStringBuffer).
+
+ processedStringBuffer.replace(0, processedStringBuffer.length(),
+ strBuffer);
+
+ // Use the TerminalText object to display the text to the
+ // user.
+
+ terminalText.setNewText(processedStringBuffer);
+
+ // Now make the TerminalText object display the processesed
+ // bytes. Its code has to run in the display thread, so we
+ // call syncExec() to do that. We do _not_ call asyncExec()
+ // because asyncExec() does not wait for the Runnable
+ // (TerminalText) to finish. If we were to call asynchExec(),
+ // this loop might race around and call setNewText() on the
+ // terminalText object again before the Display thread gets to
+ // display the previous buffer held by that object. By
+ // blocking here, we avoid that race and also delay the next
+ // call to read(), which keeps the unread data in the kernel,
+ // where it belongs (i.e., we don't have to manage it).
+ //
+ //
+ * + * This method does not negotiate options that we do not desire to be enabled, + * because all options are initially disabled.
+ */ + protected void telnetServerDetected() + { + if (!remoteIsTelnetServer) + { + // This block only executes once per TelnetConnection instance. + + localEcho = false; + + Logger.log("Detected TELNET server."); //$NON-NLS-1$ + + remoteIsTelnetServer = true; + + for (int i = 0; i < localOptions.length; ++i) + { + if (localOptions[i].isDesired()) + { + localOptions[i].negotiate(); + } + } + + for (int i = 0; i < remoteOptions.length; ++i) + { + if (remoteOptions[i].isDesired()) + { + remoteOptions[i].negotiate(); + } + } + } + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TelnetOption.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TelnetOption.java new file mode 100644 index 00000000000..60712d6eeef --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TelnetOption.java @@ -0,0 +1,756 @@ +/******************************************************************************* + * Copyright (c) 2006 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: + * Wind River Systems, Inc. - initial implementation + * + *******************************************************************************/ + + +package org.eclipse.tm.terminal; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; + +/** + * This class represents a single TELNET protocol option at one endpoint of a TELNET + * connection. This class encapsulates the endpoint associated with the option (local + * or remote), the current state of the option (enabled or disabled), the desired state + * of the option, the current state of the negotiation, an OutputStream that allows + * communication with the remote endpoint, and the number of negotiations that have + * started within this connection.
+ * + * In addition to encapsulating the above state, this class performs option negotiation + * to attempt to achieve the desired option state. For some options, this class also + * performs option sub-negotiation.
+ * + * IMPORTANT: Understanding this code requires understanding the TELNET protocol and + * TELNET option processing.
+ * + * @author Fran Litterio (francis.litterio@windriver.com) + */ +class TelnetOption implements TelnetCodes +{ + /** + * This array of Strings maps an integer TELNET option code value to the symbolic + * name of the option. Array elements of the form "?" represent unassigned option + * values. + */ + protected static final String[] optionNames = + { + "BINARY", // 0 //$NON-NLS-1$ + "ECHO", // 1 //$NON-NLS-1$ + "RECONNECTION", // 2 //$NON-NLS-1$ + "SUPPRESS GO AHEAD", // 3 //$NON-NLS-1$ + "MSG SIZE NEGOTIATION", // 4 //$NON-NLS-1$ + "STATUS", // 5 //$NON-NLS-1$ + "TIMING MARK", // 6 //$NON-NLS-1$ + "REMOTE CTRL TRANS+ECHO", // 7 //$NON-NLS-1$ + "OUTPUT LINE WIDTH", // 8 //$NON-NLS-1$ + "OUTPUT PAGE SIZE", // 9 //$NON-NLS-1$ + "OUTPUT CR DISPOSITION", // 10 //$NON-NLS-1$ + "OUTPUT HORIZ TABSTOPS", // 11 //$NON-NLS-1$ + "OUTPUT HORIZ TAB DISPOSITION", // 12 //$NON-NLS-1$ + "OUTPUT FORMFEED DISPOSITION", // 13 //$NON-NLS-1$ + "OUTPUT VERTICAL TABSTOPS", // 14 //$NON-NLS-1$ + "OUTPUT VT DISPOSITION", // 15 //$NON-NLS-1$ + "OUTPUT LF DISPOSITION", // 16 //$NON-NLS-1$ + "EXTENDED ASCII", // 17 //$NON-NLS-1$ + "LOGOUT", // 18 //$NON-NLS-1$ + "BYTE MACRO", // 19 //$NON-NLS-1$ + "DATA ENTRY TERMINAL", // 20 //$NON-NLS-1$ + "SUPDUP", // 21 //$NON-NLS-1$ + "SUPDUP OUTPUT", // 22 //$NON-NLS-1$ + "SEND LOCATION", // 23 //$NON-NLS-1$ + "TERMINAL TYPE", // 24 //$NON-NLS-1$ + "END OF RECORD", // 25 //$NON-NLS-1$ + "TACACS USER IDENTIFICATION", // 26 //$NON-NLS-1$ + "OUTPUT MARKING", // 27 //$NON-NLS-1$ + "TERMINAL LOCATION NUMBER", // 28 //$NON-NLS-1$ + "3270 REGIME", // 29 //$NON-NLS-1$ + "X.3 PAD", // 30 //$NON-NLS-1$ + "NEGOTIATE ABOUT WINDOW SIZE", // 31 //$NON-NLS-1$ + "TERMINAL SPEED", // 32 //$NON-NLS-1$ + "REMOTE FLOW CONTROL", // 33 //$NON-NLS-1$ + "LINEMODE", // 34 //$NON-NLS-1$ + "X DISPLAY LOCATION", // 35 //$NON-NLS-1$ + "ENVIRONMENT OPTION", // 36 //$NON-NLS-1$ + "AUTHENTICATION OPTION", // 37 //$NON-NLS-1$ + "ENCRYPTION OPTION", // 38 //$NON-NLS-1$ + "NEW ENVIRONMENT OPTION", // 39 //$NON-NLS-1$ + "TN3270E", // 40 //$NON-NLS-1$ + "XAUTH", // 41 //$NON-NLS-1$ + "CHARSET", // 42 //$NON-NLS-1$ + "REMOTE SERIAL PORT", // 43 //$NON-NLS-1$ + "COM PORT CONTROL OPTION", // 44 //$NON-NLS-1$ + "SUPPRESS LOCAL ECHO", // 45 //$NON-NLS-1$ + "START TLS", // 46 //$NON-NLS-1$ + "KERMIT", // 47 //$NON-NLS-1$ + "SEND URL", // 48 //$NON-NLS-1$ + "FORWARD X", // 49 //$NON-NLS-1$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 50 ... //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", // ... 137 //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ + "TELOPT PRAGMA LOGON", // 138 //$NON-NLS-1$ + "TELOPT SSPI LOGON", // 139 //$NON-NLS-1$ + "TELOPT PRAGMA HEARTBEAT", // 140 //$NON-NLS-1$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 141 ... //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ + "?", "?", "?", "?", // ... 254 //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + "EXTENDED OPTIONS LIST" // 255 //$NON-NLS-1$ + }; + + /** + * Negotiation state: Negotiation not yet started for this option.
+ * + * This constant and the others having similar names represent the states of a + * finite state automaton (FSA) that tracks the negotiation state of this option. + * The initial state is NEGOTIATION_NOT_STARTED. The state machine is as follows + * (with transitions labelled with letters in parentheses):
+ * + *
+ * NEGOTIATION_NOT_STARTED -----> {@link #NEGOTIATION_IN_PROGRESS} + * | (A) | ^ + * (C)| (B)| |(D) + * | V | + * +--------> {@link #NEGOTIATION_DONE} + *
+ * + * Once the FSA leaves state NEGOTIATION_NOT_STARTED, it never returns to that + * state. Transition A happens when the local endpoint sends an option command + * before receiving a command for the same option from the remote endpoint.
+ * + * Transition B happens when the local endpoint receives a reply to an option + * command sent earlier by the local endpoint. Receipt of that reply terminates + * the negotiation.
+ * + * Transition D happens after negotiation is done and "something changes" (see the + * RFCs for the definition of "something changes"). Either endpoint can + * re-negotiate an option after a previous negotiation, but only if some external + * influence (such as the user or the OS) causes it to do so. Re-negotiation must + * start more than {@link #NEGOTIATION_IGNORE_DURATION} milliseconds after the FSA + * enters state NEGOTIATION_DONE or it will be ignored. This is how this client + * prevents negotiation loops.
+ * + * Transition C happens when the local endpoint receives an option command from the + * remote endpoint before sending a command for the same option. In that case, the + * local endpoint replies immediately with an option command and the negotitation + * terminates.
+ * + * Some TELNET servers (e.g., the Solaris server), after sending WILL and receiving + * DONT, will reply with a superfluous WONT. Any such superfluous option command + * received from the remote endpoint while the option's FSA is in state + * {@link #NEGOTIATION_DONE} will be ignored by the local endpoint. + */ + protected static final int NEGOTIATION_NOT_STARTED = 0; + + /** Negotiation state: Negotiation is in progress for this option. */ + protected static final int NEGOTIATION_IN_PROGRESS = 1; + + /** Negotiation state: Negotiation has terminated for this option. */ + protected static final int NEGOTIATION_DONE = 2; + + /** + * The number of milliseconds following the end of negotiation of this option + * before which the remote endpoint can re-negotiate the option. Any option + * command received from the remote endpoint before this time passes is ignored. + * This is used to prevent option negotiation loops. + * + * @see #ignoreNegotiation() + * @see #negotiationCompletionTime + */ + protected static final int NEGOTIATION_IGNORE_DURATION = 30000; + + /** + * This field holds the current negotiation state for this option. + */ + protected int negotiationState = NEGOTIATION_NOT_STARTED; + + /** + * This field holds the time when negotiation of this option most recently + * terminated (i.e., entered state {@link #NEGOTIATION_DONE}). This is used to + * determine whether an option command received from the remote endpoint after + * negotiation has terminated for this option is to be ignored or interpreted as + * the start of a new negotiation. + * + * @see #NEGOTIATION_IGNORE_DURATION + */ + protected Date negotiationCompletionTime = new Date(0); + + /** + * Holds the total number of negotiations that have completed for this option. + */ + protected int negotiationCount = 0; + + /** + * Holds the integer code representing the option. + */ + protected byte option = 0; + + /** + * Holds the OutputStream object that allows data to be sent to the remote endpoint + * of the TELNET connection. + */ + protected OutputStream outputStream; + + /** + * True if this option is for the local endpoint, false for the remote endpoint. + */ + protected boolean local = true; + + /** + * This field is true if the option is enabled, false if it is disabled. All + * options are initially disabled until they are negotiated to be enabled.
+ */ + protected boolean enabled = false; + + /** + * This field is true if the client desires the option to be enabled, false if the + * client desires the option to be disabled. This field does not represent the + * remote's endpoints desire (as expressed via WILL and WONT commands) -- it + * represnet the local endpoint's desire.
+ * + * @see #setDesired(boolean) + */ + protected boolean desired = false; + + /** + * Constructor.
+ * + * @param option The integer code of this option. + * @param desired Whether we desire this option to be enabled. + * @param local Whether this option is for the local or remote endpoint. + * @param outputStream A stream used to negotiate with the remote endpoint. + */ + TelnetOption(byte option, boolean desired, boolean local, OutputStream outputStream) + { + this.option = option; + this.desired = desired; + this.local = local; + this.outputStream = outputStream; + } + + /** + * @return Returns a String containing the name of the TELNET option specified in + * parameter option. + */ + public String optionName() + { + return optionNames[option]; + } + + /** + * Returns true if this option is enabled, false if it is disabled.
+ * + * @return Returns true if this option is enabled, false if it is disabled. + */ + public boolean isEnabled() + { + return enabled; + } + + /** + * Enables this option if newValue is true, otherwise disables this + * option.
+ * + * @param newValue True if this option is to be enabled, false otherwise. + */ + public void setEnabled(boolean newValue) + { + Logger.log("Enabling " + (local ? "local" : "remote") + " option " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + optionName()); + enabled = newValue; + } + + /** + * Returns true if the local endpoint desires this option to be enabled, false if + * not. It is not an error for the value returned by this method to differ from + * the value returned by isEnabled(). The value returned by this method can change + * over time, reflecting the local endpoint's changing desire regarding the + * option.
+ * + * NOTE: Even if this option represents a remote endpoint option, the return value + * of this method represents the local endpint's desire regarding the remote + * option.
+ * + * @return Returns true if the local endpoint desires this option to be enabled, + * false if not. + */ + public boolean isDesired() + { + return desired; + } + + /** + * Sets our desired value for this option. Note that the option can be desired + * when enabled is false, and the option can be undesired when + * enabled is true, though the latter state should not persist, since either + * endpoint can disable any option at any time.
+ *
+ * @param newValue True if we desire this option to be enabled, false if
+ * we desire this option to be disabled.
+ */
+ public void setDesired(boolean newValue)
+ {
+ if (newValue)
+ Logger.log("Setting " + (local ? "local" : "remote") + " option " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ optionName() + " as desired."); //$NON-NLS-1$
+
+ desired = newValue;
+ }
+
+ /**
+ * Call this method to request that negotiation begin for this option. This method
+ * does nothing if negotiation for this option has already started or is already
+ * complete. If negotiation has not yet started for this option and the local
+ * endpoint desires this option to be enabled, then we send a WILL or DO command to
+ * the remote endpoint.
+ */
+ public void negotiate()
+ {
+ if (negotiationState == NEGOTIATION_NOT_STARTED && desired)
+ {
+ if (local)
+ {
+ Logger.log("Starting negotiation for local option " + optionName()); //$NON-NLS-1$
+ sendWill();
+ }
+ else
+ {
+ Logger.log("Starting negotiation for remote option " + optionName()); //$NON-NLS-1$
+ sendDo();
+ }
+
+ negotiationState = NEGOTIATION_IN_PROGRESS;
+ }
+ }
+
+ /**
+ * This method is called whenever we receive a WILL command from the remote
+ * endpoint.
+ */
+ public void handleWill()
+ {
+ if (negotiationState == NEGOTIATION_DONE && ignoreNegotiation())
+ {
+ Logger.log("Ignoring superfluous WILL command from remote endpoint."); //$NON-NLS-1$
+ return;
+ }
+
+ if (negotiationState == NEGOTIATION_IN_PROGRESS)
+ {
+ if (desired)
+ {
+ // We sent DO and server replied with WILL. Enable the option, and end
+ // this negotiation.
+
+ enabled = true;
+ Logger.log("Enabling remote option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ else
+ {
+ // This should never happen! We sent DONT and the server replied with
+ // WILL. Bad server. No soup for you. Disable the option, and end
+ // this negotiation.
+
+ Logger.log("Server answered DONT with WILL!"); //$NON-NLS-1$
+ enabled = false;
+ Logger.log("Disabling remote option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ }
+ else
+ {
+ if (desired)
+ {
+ // Server sent WILL, so we reply with DO. Enable the option, and end
+ // this negotiation.
+
+ sendDo();
+ enabled = true;
+ Logger.log("Enabling remote option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ else
+ {
+ // Server sent WILL, so we reply with DONT. Disable the option, and
+ // end this negotiation.
+
+ sendDont();
+ enabled = false;
+ Logger.log("Disabling remote option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ }
+ }
+
+ /**
+ * Handles a WONT command sent by the remote endpoint for this option. The value
+ * of desired doesn't matter in this method, because the remote endpoint is
+ * forcing the option to be disabled.
+ */
+ public void handleWont()
+ {
+ if (negotiationState == NEGOTIATION_DONE && ignoreNegotiation())
+ {
+ Logger.log("Ignoring superfluous WONT command from remote endpoint."); //$NON-NLS-1$
+ return;
+ }
+
+ if (negotiationState == NEGOTIATION_IN_PROGRESS)
+ {
+ // We sent DO or DONT and server replied with WONT. Disable the
+ // option, and end this negotiation.
+
+ enabled = false;
+ Logger.log("Disabling remote option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ else
+ {
+ // Server sent WONT, so we reply with DONT. Disable the option, and
+ // end this negotiation.
+
+ sendDont();
+ enabled = false;
+ Logger.log("Disabling remote option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ }
+
+ /**
+ * Handles a DO command sent by the remote endpoint for this option.
+ */
+ public void handleDo()
+ {
+ if (negotiationState == NEGOTIATION_DONE && ignoreNegotiation())
+ {
+ Logger.log("Ignoring superfluous DO command from remote endpoint."); //$NON-NLS-1$
+ return;
+ }
+
+ if (negotiationState == NEGOTIATION_IN_PROGRESS)
+ {
+ if (desired)
+ {
+ // We sent WILL and server replied with DO. Enable the option, and end
+ // this negotiation.
+
+ enabled = true;
+ Logger.log("Enabling local option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ else
+ {
+ // We sent WONT and server replied with DO. This should never happen!
+ // Bad server. No soup for you. Disable the option, and end this
+ // negotiation.
+
+ Logger.log("Server answered WONT with DO!"); //$NON-NLS-1$
+ enabled = false;
+ Logger.log("Disabling local option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ }
+ else
+ {
+ if (desired)
+ {
+ // Server sent DO, so we reply with WILL. Enable the option, and end
+ // this negotiation.
+
+ sendWill();
+ enabled = true;
+ Logger.log("Enabling local option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ else
+ {
+ // Server sent DO, so we reply with WONT. Disable the option, and end
+ // this negotiation.
+
+ sendWont();
+ enabled = false;
+ Logger.log("Disabling local option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ }
+ }
+
+ /**
+ * Handles a DONT command sent by the remote endpoint for this option. The value
+ * of desired doesn't matter in this method, because the remote endpoint is
+ * forcing the option to be disabled.
+ */
+ public void handleDont()
+ {
+ if (negotiationState == NEGOTIATION_DONE && ignoreNegotiation())
+ {
+ Logger.log("Ignoring superfluous DONT command from remote endpoint."); //$NON-NLS-1$
+ return;
+ }
+
+ if (negotiationState == NEGOTIATION_IN_PROGRESS)
+ {
+ // We sent WILL or WONT and server replied with DONT. Disable the
+ // option, and end this negotiation.
+
+ enabled = false;
+ Logger.log("Disabling local option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ else
+ {
+ // Server sent DONT, so we reply with WONT. Disable the option, and end
+ // this negotiation.
+
+ sendWont();
+ enabled = false;
+ Logger.log("Disabling local option " + optionName() + "."); //$NON-NLS-1$ //$NON-NLS-2$
+ endNegotiation();
+ }
+ }
+
+ /**
+ * This method handles a subnegotiation command received from the remote endpoint.
+ * Currently, the only subnegotiation we handle is when the remote endpoint
+ * commands us to send our terminal type (which is "ansi").
+ *
+ * @param subnegotiationData An array of bytes containing a TELNET
+ * subnegotiation command received from the
+ * remote endpoint.
+ * @param count The number of bytes in array
+ * subnegotiationData to examine.
+ */
+ public void handleSubnegotiation(byte[] subnegotiationData, int count)
+ {
+ switch (option)
+ {
+ case TELNET_OPTION_TERMINAL_TYPE:
+ if (subnegotiationData[1] != TELNET_SEND)
+ {
+ // This should never happen!
+ Logger.log("Invalid TERMINAL-TYPE subnegotiation command from remote endpoint: " + //$NON-NLS-1$
+ (subnegotiationData[1] & 0xff));
+ break;
+ }
+
+ // Tell the remote endpoint our terminal type is "ansi" using this sequence
+ // of TELNET protocol bytes:
+ //
+ // IAC SB TERMINAL-TYPE IS a n s i IAC SE
+
+ byte[] terminalTypeData = { TELNET_IAC, TELNET_SB, TELNET_OPTION_TERMINAL_TYPE,
+ TELNET_IS, (byte)'a', (byte)'n', (byte)'s', (byte)'i',
+ TELNET_IAC, TELNET_SE };
+
+ try
+ {
+ outputStream.write(terminalTypeData);
+ }
+ catch (IOException ex)
+ {
+ Logger.log("IOException sending TERMINAL-TYPE subnegotiation!"); //$NON-NLS-1$
+ Logger.logException(ex);
+ }
+ break;
+
+ default:
+ // This should never happen!
+ Logger.log("SHOULD NOT BE REACHED: Called for option " + optionName()); //$NON-NLS-1$
+ assert false;
+ break;
+ }
+ }
+
+ /**
+ * This method sends a subnegotiation command to the remote endpoint.
+ *
+ * @param subnegotiationData An array of Objects holding data to be used
+ * when generating the outbound subnegotiation
+ * command.
+ */
+ public void sendSubnegotiation(Object[] subnegotiationData)
+ {
+ switch (option)
+ {
+ case TELNET_OPTION_NAWS:
+ // Get the width and height of the view and send it to the remote
+ // endpoint using this sequence of TELNET protocol bytes:
+ //
+ // IAC SB NAWS
+ *
+ * The current implementation of this method returns true if the new negotiation
+ * starts within NEGOTIATION_IGNORE_DURATION seconds of the end of the previous
+ * negotiation of this option.
+ *
+ * @return Returns true if the new negotiation should be ignored, false if not.
+ */
+ protected boolean ignoreNegotiation()
+ {
+ return (System.currentTimeMillis() - negotiationCompletionTime.getTime()) >
+ NEGOTIATION_IGNORE_DURATION;
+ }
+
+ /**
+ * Sends a DO command to the remote endpoint for this option.
+ */
+ protected void sendDo()
+ {
+ Logger.log("Sending DO " + optionName()); //$NON-NLS-1$
+ sendCommand(TELNET_DO);
+ }
+
+ /**
+ * Sends a DONT command to the remote endpoint for this option.
+ */
+ protected void sendDont()
+ {
+ Logger.log("Sending DONT " + optionName()); //$NON-NLS-1$
+ sendCommand(TELNET_DONT);
+ }
+
+ /**
+ * Sends a WILL command to the remote endpoint for this option.
+ */
+ protected void sendWill()
+ {
+ Logger.log("Sending WILL " + optionName()); //$NON-NLS-1$
+ sendCommand(TELNET_WILL);
+ }
+
+ /**
+ * Sends a WONT command to the remote endpoint for this option.
+ */
+ protected void sendWont()
+ {
+ Logger.log("Sending WONT " + optionName()); //$NON-NLS-1$
+ sendCommand(TELNET_WONT);
+ }
+
+ /**
+ * This method sends a WILL/WONT/DO/DONT command to the remote endpoint for this
+ * option.
+ */
+ protected void sendCommand(byte command)
+ {
+ byte[] data = { TELNET_IAC, 0, 0 };
+
+ data[1] = command;
+ data[2] = option;
+
+ try
+ {
+ outputStream.write(data);
+ }
+ catch (IOException ex)
+ {
+ Logger.log("IOException sending command " + command); //$NON-NLS-1$
+ Logger.logException(ex);
+ }
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalAction.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalAction.java
new file mode 100644
index 00000000000..e1b786d4867
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalAction.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+
+package org.eclipse.tm.terminal;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.ImageRegistry;
+
+public class TerminalAction extends Action
+ implements TerminalMsg, TerminalConsts
+{
+ /**
+ *
+ */
+ protected TerminalTarget m_Target;
+ protected String m_strMsg;
+
+ /**
+ *
+ */
+ public TerminalAction(TerminalTarget target,
+ String strMsg,
+ String strId)
+ {
+ super(""); //$NON-NLS-1$
+
+ m_Target = target;
+ m_strMsg = strMsg;
+
+ setId(strId);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Action interface
+ //
+
+ /**
+ *
+ */
+ public void run()
+ {
+ m_Target.execute(m_strMsg,this);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Operations
+ //
+
+ /**
+ *
+ */
+ protected void setupAction(String strText,
+ String strToolTip,
+ String strImage,
+ String strEnabledImage,
+ String strDisabledImage,
+ boolean bEnabled)
+ {
+ TerminalPlugin plugin;
+ ImageRegistry imageRegistry;
+
+ plugin = TerminalPlugin.getDefault();
+ imageRegistry = plugin.getImageRegistry();
+ setupAction(strText,
+ strToolTip,
+ strImage,
+ strEnabledImage,
+ strDisabledImage,
+ bEnabled,
+ imageRegistry);
+ }
+
+ /**
+ *
+ */
+ protected void setupAction(String strText,
+ String strToolTip,
+ String strImage,
+ String strEnabledImage,
+ String strDisabledImage,
+ boolean bEnabled,
+ ImageRegistry imageRegistry)
+ {
+ ImageDescriptor imageDescriptor;
+
+ setText(strText);
+ setToolTipText(strToolTip);
+ setEnabled(bEnabled);
+
+ imageDescriptor = imageRegistry.getDescriptor(strEnabledImage);
+ if (imageDescriptor != null)
+ {
+ setImageDescriptor(imageDescriptor);
+ }
+
+ imageDescriptor = imageRegistry.getDescriptor(strDisabledImage);
+ if (imageDescriptor != null)
+ {
+ setDisabledImageDescriptor(imageDescriptor);
+ }
+
+ imageDescriptor = imageRegistry.getDescriptor(strImage);
+ if (imageDescriptor != null)
+ {
+ setHoverImageDescriptor(imageDescriptor);
+ }
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionClearAll.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionClearAll.java
new file mode 100644
index 00000000000..889715198e7
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionClearAll.java
@@ -0,0 +1,34 @@
+
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+
+package org.eclipse.tm.terminal;
+
+public class TerminalActionClearAll extends TerminalAction
+{
+ protected TerminalActionClearAll(TerminalTarget target)
+ {
+ super(target,
+ ON_EDIT_CLEARALL,
+ TerminalActionClearAll.class.getName());
+
+ setupAction(TERMINAL_TEXT_CLEARALL,
+ TERMINAL_TEXT_CLEARALL,
+ null,
+ null,
+ null,
+ false);
+ }
+}
+
+
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionConnect.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionConnect.java
new file mode 100644
index 00000000000..078338e1c34
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionConnect.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+
+package org.eclipse.tm.terminal;
+
+public class TerminalActionConnect extends TerminalAction
+{
+ protected TerminalActionConnect(TerminalTarget target)
+ {
+ super(target,
+ ON_TERMINAL_CONNECT,
+ TerminalActionConnect.class.getName());
+
+ setupAction(TERMINAL_TEXT_CONNECT,
+ TERMINAL_TEXT_CONNECT,
+ TERMINAL_IMAGE_CLCL_CONNECT,
+ TERMINAL_IMAGE_ELCL_CONNECT,
+ TERMINAL_IMAGE_DLCL_CONNECT,
+ true);
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionCopy.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionCopy.java
new file mode 100644
index 00000000000..47c900f8a11
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionCopy.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.tm.terminal;
+
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.internal.WorkbenchImages;
+
+public class TerminalActionCopy extends TerminalAction
+{
+ protected TerminalActionCopy(TerminalTarget target)
+ {
+ super(target,
+ ON_EDIT_COPY,
+ TerminalActionCopy.class.getName());
+
+ ImageRegistry imageRegistry;
+
+ imageRegistry = WorkbenchImages.getImageRegistry();
+ setupAction(TERMINAL_TEXT_COPY,
+ TERMINAL_TEXT_COPY,
+ ISharedImages.IMG_TOOL_COPY,
+ ISharedImages.IMG_TOOL_COPY,
+ ISharedImages.IMG_TOOL_COPY_DISABLED,
+ true,
+ imageRegistry);
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionCut.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionCut.java
new file mode 100644
index 00000000000..2a06c87748d
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionCut.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.tm.terminal;
+
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.internal.WorkbenchImages;
+
+public class TerminalActionCut extends TerminalAction
+{
+ protected TerminalActionCut(TerminalTarget target)
+ {
+ super(target,
+ ON_EDIT_CUT,
+ TerminalActionCut.class.getName());
+
+ ImageRegistry imageRegistry;
+
+ imageRegistry = WorkbenchImages.getImageRegistry();
+ setupAction(TERMINAL_TEXT_CUT,
+ TERMINAL_TEXT_CUT,
+ ISharedImages.IMG_TOOL_CUT,
+ ISharedImages.IMG_TOOL_CUT,
+ ISharedImages.IMG_TOOL_CUT_DISABLED,
+ true,
+ imageRegistry);
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionDisconnect.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionDisconnect.java
new file mode 100644
index 00000000000..e1f294da96e
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionDisconnect.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.tm.terminal;
+
+public class TerminalActionDisconnect extends TerminalAction
+{
+ /**
+ *
+ */
+ protected TerminalActionDisconnect(TerminalTarget target)
+ {
+ super(target,
+ ON_TERMINAL_DISCONNECT,
+ TerminalActionDisconnect.class.getName());
+
+ setupAction(TERMINAL_TEXT_DISCONNECT,
+ TERMINAL_TEXT_DISCONNECT,
+ TERMINAL_IMAGE_CLCL_DISCONNECT,
+ TERMINAL_IMAGE_ELCL_DISCONNECT,
+ TERMINAL_IMAGE_DLCL_DISCONNECT,
+ false);
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionNewTerminal.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionNewTerminal.java
new file mode 100644
index 00000000000..80bfdccadb6
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalActionNewTerminal.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.tm.terminal;
+
+/**
+ * UNDER CONSTRUCTION
+ *
+ * @author Fran Litterio
+ *
+ * The StyledText widget that displays text has a vertical bar (called the "caret")
+ * that appears _between_ character cells, but ANSI terminals have the concept of a
+ * cursor that appears _in_ a character cell, so we need a convention for which
+ * character cell the cursor logically occupies when the caret is physically
+ * between two cells. The convention used in this class is that the cursor is
+ * logically in column N when the caret is physically positioned immediately to the
+ * _left_ of column N.
+ *
+ * When cursorColumn is N, the next character output to the terminal appears in
+ * column N. When a character is output to the rightmost column on a given line
+ * (column widthInColumns - 1), the cursor moves to column 0 on the next line after
+ * the character is drawn (this is how line wrapping is implemented). If the
+ * cursor is in the bottommost line when line wrapping occurs, the topmost visible
+ * line is scrolled off the top edge of the screen.
+ */
+ protected int cursorColumn = 0;
+
+ /**
+ * This field holds the caret offset when we last moved it or wrote text to the
+ * terminal. The reason we need to remember this value is because, unlike with a
+ * normal terminal emulator, the user can move the caret by clicking anywhere in
+ * the terminal view. In a normal terminal emulator, the cursor only moves as the
+ * result of character output (i.e., escape sequences or normal characters). We
+ * use the value stored in this field to restore the position of the caret
+ * immediately before processing each chunk of output from the remote endpoint.
+ */
+ protected int caretOffset = 0;
+
+ /**
+ * This field hold the saved absolute line number of the cursor when processing the
+ * "ESC 7" and "ESC 8" command sequences.
+ */
+ protected int savedCursorLine = 0;
+
+ /**
+ * This field hold the saved column number of the cursor when processing the "ESC
+ * 7" and "ESC 8" command sequences.
+ */
+ protected int savedCursorColumn = 0;
+
+ /**
+ * This field holds an array of StringBuffer objects, each of which is one
+ * parameter from the current ANSI escape sequence. For example, when parsing the
+ * escape sequence "\e[20;10H", this array holds the strings "20" and "10".
+ */
+ protected StringBuffer[] ansiParameters = new StringBuffer[16];
+
+ /**
+ * This field holds the OS-specific command found in an escape sequence of the form
+ * "\e]...\u0007".
+ */
+ protected StringBuffer ansiOsCommand = new StringBuffer(128);
+
+ /**
+ * This field holds the index of the next unused element of the array stored in
+ * field {@link #ansiParameters}.
+ */
+ protected int nextAnsiParameter = 0;
+
+ /**
+ * This field holds the Color object representing the current foreground color as
+ * set by the ANSI escape sequence "\e[m".
+ */
+ protected Color currentForegroundColor;
+
+ /**
+ * This field holds the Color object representing the current background color as
+ * set by the ANSI escape sequence "\e[m".
+ */
+ protected Color currentBackgroundColor;
+
+ /**
+ * This field holds an integer representing the current font style as set by the
+ * ANSI escape sequence "\e[m".
+ */
+ protected int currentFontStyle = SWT.NORMAL;
+
+ /**
+ * This field is true if we are currently outputing text in reverse video mode,
+ * false otherwise.
+ */
+ protected boolean reverseVideo = false;
+
+ /**
+ * This field holds the time (in milliseconds) of the previous call to method
+ * {@link #SetNewText()}.
+ */
+ static long LastNewOutputTime = 0;
+
+ /**
+ * Color object representing the color black. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color BLACK = new Color(Display.getCurrent(), 0, 0, 0);
+
+ /**
+ * Color object representing the color red. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color RED = new Color(Display.getCurrent(), 255, 0, 0);
+
+ /**
+ * Color object representing the color green. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color GREEN = new Color(Display.getCurrent(), 0, 255, 0);
+
+ /**
+ * Color object representing the color yellow. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color YELLOW = new Color(Display.getCurrent(), 255, 255, 0);
+
+ /**
+ * Color object representing the color blue. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color BLUE = new Color(Display.getCurrent(), 0, 0, 255);
+
+ /**
+ * Color object representing the color magenta. The Color class requires us to
+ * call dispose() on this object when we no longer need it. We do that in method
+ * {@link #dispose()}.
+ */
+ protected final Color MAGENTA = new Color(Display.getCurrent(), 255, 0, 255);
+
+ /**
+ * Color object representing the color cyan. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color CYAN = new Color(Display.getCurrent(), 0, 255, 255);
+
+ /**
+ * Color object representing the color white. The Color class requires us to call
+ * dispose() on this object when we no longer need it. We do that in method {@link
+ * #dispose()}.
+ */
+ protected final Color WHITE = new Color(Display.getCurrent(), 255, 255, 255);
+
+ /**
+ * The constructor.
+ */
+ public TerminalText(TerminalCtrl terminal)
+ {
+ super();
+
+ Logger.log("entered"); //$NON-NLS-1$
+
+ this.terminal = terminal;
+
+ for (int i = 0; i < ansiParameters.length; ++i)
+ {
+ ansiParameters[i] = new StringBuffer();
+ }
+ }
+
+ /**
+ * This method performs clean up when this TerminalText object is no longer
+ * needed. After calling this method, no other method on this object should be
+ * called.
+ */
+ public void dispose()
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+
+ // Call dispose() on the Color objects we created.
+
+ BLACK.dispose();
+ RED.dispose();
+ GREEN.dispose();
+ YELLOW.dispose();
+ BLUE.dispose();
+ MAGENTA.dispose();
+ CYAN.dispose();
+ WHITE.dispose();
+ }
+
+ /**
+ * This method is required by interface ControlListener. It allows us to know when
+ * the StyledText widget is moved.
+ */
+ public void controlMoved(ControlEvent event)
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+ // Empty.
+ }
+
+ /**
+ * This method is required by interface ControlListener. It allows us to know when
+ * the StyledText widget is resized. 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.
+ */
+ public synchronized void controlResized(ControlEvent event)
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+ adjustTerminalDimensions();
+ }
+
+ /**
+ * This method sets field {@link #newText} to a new value. This method must
+ * not execute at the same time as methods {@link #run()} and {@link
+ * #clearTerminal()}.
+ *
+ * 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.
+ */
+ public synchronized void setNewText(StringBuffer newBuffer)
+ {
+ Logger.log("new text: '" + newBuffer + "'"); //$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()
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+ text.setText(""); //$NON-NLS-1$
+ cursorColumn = 0;
+ }
+
+ /**
+ * 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.
+ */
+ public synchronized void fontChanged()
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+
+ characterPixelWidth = 0;
+
+ if (text != null)
+ adjustTerminalDimensions();
+ }
+
+ /**
+ * This method executes in the Display thread to process data received from the
+ * remote host by classes {@link TelnetConnection} and {@link
+ * TerminalSerialPortHandler}. This method must not execute at the same time as
+ * methods {@link #setNewText(StringBuffer)} and {@link #clearTerminal()}.
+ *
+ * IMPORTANT: This method must be called in strict alternation with method
+ * {@link #setNewText(StringBuffer)}.
+ */
+ public synchronized void run()
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+
+ try
+ {
+ if (text == null)
+ {
+ // We defer initialization of these fields until execution reaches
+ // here, because the StyledText widget doesn't exist when this class is
+ // first instantiated.
+
+ text = terminal.getTextWidget();
+
+ // Register this class instance as a ControlListener so we can learn
+ // when the StyledText widget is resized.
+
+ text.addControlListener(this);
+
+ currentForegroundColor = text.getForeground();
+ currentBackgroundColor = text.getBackground();
+ currentFontStyle = SWT.NORMAL;
+ reverseVideo = false;
+ }
+
+ // This method can be called just after the user closes the view, so we
+ // make sure not to cause a widget-disposed exception.
+
+ if (text != null && text.isDisposed())
+ return;
+
+ // If the status bar is showing "OPENED", change it to "CONNECTED".
+
+ if (terminal.isOpened())
+ {
+ terminal.setOpened(false);
+ terminal.execute(TerminalMsg.ON_TERMINAL_STATUS, null);
+ }
+
+ // Find the width and height of the terminal, and resize it to display an
+ // integral number of lines and columns.
+
+ adjustTerminalDimensions();
+
+ // Restore the caret offset, process and display the new text, then save
+ // the caret offset. See the documentation for field caretOffset for
+ // details.
+
+ // ISSUE: Is this causing the scroll-to-bottom-on-output behavior?
+
+ text.setCaretOffset(caretOffset);
+
+ processNewText();
+
+ 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.
+ */
+ protected void processNewText()
+ {
+ Logger.log("entered"); //$NON-NLS-1$
+
+ // Stop the StyledText widget from redrawing while we manipulate its contents.
+ // This helps display performance.
+
+ text.setRedraw(false);
+
+ // Scan the newly received text.
+
+ characterIndex = 0;
+
+ while (characterIndex < newText.length())
+ {
+ char character = newText.charAt(characterIndex);
+
+ switch (ansiState)
+ {
+ case ANSISTATE_INITIAL:
+ switch (character)
+ {
+ case '\u0000':
+ break; // NUL character. Ignore it.
+
+ case '\u0007':
+ processBEL(); // BEL (Ctrl-G)
+ break;
+
+ case '\b':
+ processBackspace(); // Backspace
+ break;
+
+ case '\t':
+ processTab(); // Tab.
+ break;
+
+ case '\n':
+ processNewline(); // Newline (Ctrl-J)
+ break;
+
+ case '\r':
+ processCarriageReturn(); // Carriage Return (Ctrl-M)
+ break;
+
+ case '\u001b':
+ ansiState = ANSISTATE_ESCAPE; // Escape.
+ break;
+
+ default:
+ processNonControlCharacters();
+ break;
+ }
+ break;
+
+ case ANSISTATE_ESCAPE:
+ // We've seen an escape character. Here, we process the character
+ // immediately following the escape.
+
+ switch (character)
+ {
+ case '[':
+ ansiState = ANSISTATE_EXPECTING_PARAMETER_OR_COMMAND;
+ nextAnsiParameter = 0;
+
+ // Erase the parameter strings in preparation for optional
+ // parameter characters.
+
+ for (int i = 0; i < ansiParameters.length; ++i)
+ {
+ ansiParameters[i].delete(0, ansiParameters[i].length());
+ }
+ break;
+
+ case ']':
+ ansiState = ANSISTATE_EXPECTING_OS_COMMAND;
+ ansiOsCommand.delete(0, ansiOsCommand.length());
+ break;
+
+ case '7':
+ // Save cursor position and character attributes
+
+ ansiState = ANSISTATE_INITIAL;
+ savedCursorLine = absoluteCursorLine();
+ savedCursorColumn = cursorColumn;
+ break;
+
+ case '8':
+ // Restore cursor and attributes to previously saved position
+
+ ansiState = ANSISTATE_INITIAL;
+ moveCursor(savedCursorLine, savedCursorColumn);
+ break;
+
+ default:
+ Logger.log("Unsupported escape sequence: escape '" + character + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+ ansiState = ANSISTATE_INITIAL;
+ break;
+ }
+ break;
+
+ case ANSISTATE_EXPECTING_PARAMETER_OR_COMMAND:
+ // Parameters can appear after the '[' in an escape sequence, but they
+ // are optional.
+
+ if (character == '@' ||
+ (character >= 'A' && character <= 'Z') ||
+ (character >= 'a' && character <= 'z'))
+ {
+ ansiState = ANSISTATE_INITIAL;
+ processAnsiCommandCharacter(character);
+ }
+ else
+ {
+ processAnsiParameterCharacter(character);
+ }
+ break;
+
+ case ANSISTATE_EXPECTING_OS_COMMAND:
+ // A BEL (\u0007) character marks the end of the OSC sequence.
+
+ if (character == '\u0007')
+ {
+ ansiState = ANSISTATE_INITIAL;
+ processAnsiOsCommand();
+ }
+ else
+ {
+ ansiOsCommand.append(character);
+ }
+ break;
+
+ default:
+ // This should never happen! If it does happen, it means there is a
+ // bug in the FSA. For robustness, we return to the initial state.
+
+ Logger.log("INVALID ANSI FSA STATE: " + ansiState); //$NON-NLS-1$
+ ansiState = ANSISTATE_INITIAL;
+ assert false;
+ 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 title.
+ */
+ protected void processAnsiOsCommand()
+ {
+ if (ansiOsCommand.charAt(0) != '0' || ansiOsCommand.charAt(1) != ';')
+ {
+ Logger.log("Ignoring unsupported ANSI OSC sequence: '" + ansiOsCommand + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+
+ terminal.execute(TerminalMsg.ON_TERMINAL_STATUS, ansiOsCommand.substring(2));
+ }
+
+ /**
+ * This method dispatches control to various processing methods based on the
+ * command character found in the most recently received ANSI escape sequence.
+ * This method only handles command characters that follow the ANSI standard
+ * Control Sequence Introducer (CSI), which is "\e[...", where "..." is an optional
+ * ';'-separated sequence of numeric parameters.
+ */
+ protected void processAnsiCommandCharacter(char ansiCommandCharacter)
+ {
+ // If the width or height of the terminal is ridiculously small (one line or
+ // column or less), don't even try to process the escape sequence. This avoids
+ // throwing an exception (SPR 107450). The display will be messed up, but what
+ // did you user expect by making the terminal so small?
+
+ if (heightInLines <= 1 || widthInColumns <= 1)
+ return;
+
+ switch (ansiCommandCharacter)
+ {
+ case '@':
+ // Insert character(s).
+ processAnsiCommand_atsign();
+ break;
+
+ case 'A':
+ // Move cursor up N lines (default 1).
+ processAnsiCommand_A();
+ break;
+
+ case 'B':
+ // Move cursor down N lines (default 1).
+ processAnsiCommand_B();
+ break;
+
+ case 'C':
+ // Move cursor forward N columns (default 1).
+ processAnsiCommand_C();
+ break;
+
+ case 'D':
+ // Move cursor backward N columns (default 1).
+ processAnsiCommand_D();
+ break;
+
+ case 'E':
+ // Move cursor to first column of Nth next line (default 1).
+ processAnsiCommand_E();
+ break;
+
+ case 'F':
+ // Move cursor to first column of Nth previous line (default 1).
+ processAnsiCommand_F();
+ break;
+
+ case 'G':
+ // Move to column N of current line (default 1).
+ processAnsiCommand_G();
+ break;
+
+ case 'H':
+ // Set cursor Position.
+ processAnsiCommand_H();
+ break;
+
+ case 'J':
+ // Erase part or all of display. Cursor does not move.
+ processAnsiCommand_J();
+ break;
+
+ case 'K':
+ // Erase in line (cursor does not move).
+ processAnsiCommand_K();
+ break;
+
+ case 'L':
+ // Insert line(s) (current line moves down).
+ processAnsiCommand_L();
+ break;
+
+ case 'M':
+ // Delete line(s).
+ processAnsiCommand_M();
+ break;
+
+ case 'm':
+ // Set Graphics Rendition (SGR).
+ processAnsiCommand_m();
+ break;
+
+ case 'n':
+ // Device Status Report (DSR).
+ processAnsiCommand_n();
+ break;
+
+ case 'P':
+ // Delete character(s).
+ processAnsiCommand_P();
+ break;
+
+ case 'S':
+ // Scroll up.
+ // Emacs, vi, and GNU readline don't seem to use this command, so we ignore
+ // it for now.
+ break;
+
+ case 'T':
+ // Scroll down.
+ // Emacs, vi, and GNU readline don't seem to use this command, so we ignore
+ // it for now.
+ break;
+
+ case 'X':
+ // Erase character.
+ // Emacs, vi, and GNU readline don't seem to use this command, so we ignore
+ // it for now.
+ break;
+
+ case 'Z':
+ // Cursor back tab.
+ // Emacs, vi, and GNU readline don't seem to use this command, so we ignore
+ // it for now.
+ break;
+
+ default:
+ Logger.log("Ignoring unsupported ANSI command character: '" + //$NON-NLS-1$
+ ansiCommandCharacter + "'"); //$NON-NLS-1$
+ break;
+ }
+ }
+
+ /**
+ * This method makes room for N characters on the current line at the cursor
+ * position. Text under the cursor moves right without wrapping at the end of hte
+ * line.
+ */
+ protected void processAnsiCommand_atsign()
+ {
+ int charactersToInsert = getAnsiParameter(0);
+ int caretOffset = text.getCaretOffset();
+
+ text.replaceTextRange(caretOffset, 0, generateString(' ', charactersToInsert));
+
+ // If the current line extends past the right edge of the screen, delete the
+ // characters beyond the rightmost visible column.
+
+ int currentLineAbsolute = absoluteCursorLine();
+ int currentLineStartOffset = text.getOffsetAtLine(currentLineAbsolute);
+ int currentLineEndOffset;
+
+ if (currentLineAbsolute == text.getLineCount() - 1)
+ {
+ // The cursor is on the bottommost line of text.
+
+ currentLineEndOffset = text.getCharCount();
+ }
+ else
+ {
+ // The cursor is not on the bottommost line of text.
+
+ currentLineEndOffset = text.getOffsetAtLine(currentLineAbsolute + 1) - 1;
+ }
+
+ if (currentLineEndOffset - currentLineStartOffset > widthInColumns)
+ {
+ int charactersToDelete =
+ currentLineEndOffset - currentLineStartOffset - widthInColumns;
+
+ text.replaceTextRange(currentLineStartOffset + widthInColumns,
+ charactersToDelete, ""); //$NON-NLS-1$
+ }
+
+ // Is this necessary?
+
+ text.setCaretOffset(caretOffset);
+ }
+
+ /**
+ * This method moves the cursor up by the number of lines specified by the escape
+ * sequence parameter (default 1).
+ */
+ protected void processAnsiCommand_A()
+ {
+ moveCursorUp(getAnsiParameter(0));
+ }
+
+ /**
+ * This method moves the cursor down by the number of lines specified by the escape
+ * sequence parameter (default 1).
+ */
+ protected void processAnsiCommand_B()
+ {
+ moveCursorDown(getAnsiParameter(0));
+ }
+
+ /**
+ * This method moves the cursor forward by the number of columns specified by the
+ * escape sequence parameter (default 1).
+ */
+ protected void processAnsiCommand_C()
+ {
+ moveCursorForward(getAnsiParameter(0));
+ }
+
+ /**
+ * This method moves the cursor backward by the number of columns specified by the
+ * escape sequence parameter (default 1).
+ */
+ protected void processAnsiCommand_D()
+ {
+ moveCursorBackward(getAnsiParameter(0));
+ }
+
+ /**
+ * This method moves the cursor to the first column of the Nth next line, where N
+ * is specified by the ANSI parameter (default 1).
+ */
+ protected void processAnsiCommand_E()
+ {
+ int linesToMove = getAnsiParameter(0);
+
+ moveCursor(relativeCursorLine() + linesToMove, 0);
+ }
+
+ /**
+ * This method moves the cursor to the first column of the Nth previous line, where
+ * N is specified by the ANSI parameter (default 1).
+ */
+ protected void processAnsiCommand_F()
+ {
+ int linesToMove = getAnsiParameter(0);
+
+ moveCursor(relativeCursorLine() - linesToMove, 0);
+ }
+
+ /**
+ * This method moves the cursor within the current line to the column specified by
+ * the ANSI parameter (default is column 1).
+ */
+ protected void processAnsiCommand_G()
+ {
+ int targetColumn = 1;
+
+ if (ansiParameters[0].length() > 0)
+ targetColumn = getAnsiParameter(0) - 1;
+
+ moveCursor(relativeCursorLine(), targetColumn);
+ }
+
+ /**
+ * This method sets the cursor to a position specified by the escape sequence
+ * parameters (default is the upper left corner of the screen).
+ */
+ protected void processAnsiCommand_H()
+ {
+ moveCursor(getAnsiParameter(0) - 1, getAnsiParameter(1) - 1);
+ }
+
+ /**
+ * This method deletes some (or all) of the text on the screen without moving the
+ * cursor.
+ */
+ protected void processAnsiCommand_J()
+ {
+ int ansiParameter;
+
+ if (ansiParameters[0].length() == 0)
+ ansiParameter = 0;
+ else
+ ansiParameter = getAnsiParameter(0);
+
+ switch (ansiParameter)
+ {
+ case 0:
+ // Erase from current position to end of screen (inclusive).
+
+ int caretOffset = text.getCaretOffset();
+
+ text.replaceTextRange(caretOffset,
+ text.getCharCount() - caretOffset,
+ generateString('\n', heightInLines - relativeCursorLine() - 1));
+
+ // The above call moves the caret to the end of the text, so restore its
+ // position.
+
+ text.setCaretOffset(caretOffset);
+ break;
+
+ case 1:
+ // Erase from beginning to current position (inclusive).
+
+ int currentRelativeLineNumber = relativeCursorLine();
+ int topmostScreenLineStartOffset = text.getOffsetAtLine(absoluteLine(0));
+
+ text.replaceTextRange(topmostScreenLineStartOffset,
+ text.getCaretOffset() - topmostScreenLineStartOffset,
+ generateString('\n', currentRelativeLineNumber) +
+ generateString(' ', cursorColumn));
+
+ text.setCaretOffset(topmostScreenLineStartOffset + currentRelativeLineNumber +
+ cursorColumn);
+ break;
+
+ case 2:
+ // Erase entire display.
+
+ int currentLineNumber = relativeCursorLine();
+ topmostScreenLineStartOffset = text.getOffsetAtLine(absoluteLine(0));
+
+ text.replaceTextRange(topmostScreenLineStartOffset,
+ text.getCharCount() - topmostScreenLineStartOffset,
+ generateString('\n', heightInLines - 1));
+
+ moveCursor(currentLineNumber, cursorColumn);
+ break;
+
+ default:
+ Logger.log("Unexpected J-command parameter: " + ansiParameter); //$NON-NLS-1$
+ assert false;
+ break;
+ }
+ }
+
+ /**
+ * This method deletes some (or all) of the text in the current line without moving
+ * the cursor.
+ */
+ protected void processAnsiCommand_K()
+ {
+ int ansiParameter = getAnsiParameter(0);
+ int originalCaretOffset = text.getCaretOffset();
+
+ switch (ansiParameter)
+ {
+ case 0:
+ // Erase from beginning to current position (inclusive).
+
+ int currentLineStartOffset = text.getOffsetAtLine(absoluteCursorLine());
+
+ text.replaceTextRange(currentLineStartOffset,
+ cursorColumn,
+ generateString(' ', cursorColumn));
+ break;
+
+ case 1:
+ // Erase from current position to end (inclusive).
+
+ int caretOffset = text.getCaretOffset();
+
+ if (absoluteCursorLine() == text.getLineCount() - 1)
+ {
+ text.replaceTextRange(caretOffset, text.getCharCount() - caretOffset, ""); //$NON-NLS-1$
+ }
+ else
+ {
+ int nextLineStartOffset = text.getOffsetAtLine(absoluteCursorLine() + 1);
+
+ text.replaceTextRange(caretOffset, nextLineStartOffset - caretOffset - 1, ""); //$NON-NLS-1$
+ }
+ break;
+
+ case 2:
+ // Erase entire line.
+
+ currentLineStartOffset = text.getOffsetAtLine(absoluteCursorLine());
+
+ if (absoluteCursorLine() == text.getLineCount() - 1)
+ {
+ // The cursor is on the bottommost line of text. Replace its contents
+ // with enough spaces to leave the cursor in the current column.
+
+ text.replaceTextRange(currentLineStartOffset,
+ text.getCharCount() - currentLineStartOffset,
+ generateString(' ', cursorColumn));
+ }
+ else
+ {
+ // The cursor is not on the bottommost line of text. Replace the
+ // current line's contents with enough spaces to leave the cursor in
+ // the current column.
+
+ int nextLineStartOffset = text.getOffsetAtLine(absoluteCursorLine() + 1);
+
+ text.replaceTextRange(currentLineStartOffset,
+ nextLineStartOffset - currentLineStartOffset - 1,
+ generateString(' ', cursorColumn));
+ }
+ break;
+
+ default:
+ Logger.log("Unexpected K-command parameter: " + ansiParameter); //$NON-NLS-1$
+ assert false;
+ break;
+ }
+
+ // There is some undocumented strangeness with method
+ // StyledText.replaceTextRange() that requires us to manually reposition the
+ // caret after calling that method. If we don't do this, the caret sometimes
+ // moves to the very end of the text when deleting text within a line.
+
+ text.setCaretOffset(originalCaretOffset);
+ }
+
+ /**
+ * Insert one or more blank lines. The current line of text moves down. Text that
+ * falls off the bottom of the screen is deleted.
+ */
+ protected void processAnsiCommand_L()
+ {
+ int linesToInsert = getAnsiParameter(0);
+
+ int currentLineStartOffset = text.getOffsetAtLine(absoluteCursorLine());
+
+ // Compute how many of the bottommost lines of text to delete. This is
+ // necessary if those lines are being pushed off the bottom of the screen by
+ // the insertion of the blank lines.
+
+ int totalLines = text.getLineCount();
+ int linesToDelete = -1;
+
+ if (heightInLines <= totalLines)
+ {
+ // There are more lines of text than are displayed, so delete as many lines
+ // at the end as we insert in the middle.
+
+ linesToDelete = linesToInsert;
+ }
+ else
+ {
+ // There are fewer lines of text than the size of the terminal window, so
+ // compute how many lines will be pushed off the end of the screen by the
+ // insertion. NOTE: It is possible that we may not have to delete any
+ // lines at all, which will leave linesToDelete set to -1.
+
+ if (totalLines + linesToInsert > heightInLines)
+ {
+ linesToDelete = (totalLines + linesToInsert) - heightInLines;
+ }
+ }
+
+ if (linesToDelete != -1)
+ {
+ // Delete the bottomost linesToInsert lines plus the newline on the line
+ // immediately above the first line to be deleted.
+
+ int firstLineToDeleteStartOffset = text.getOffsetAtLine(totalLines - linesToDelete);
+
+ text.replaceTextRange(firstLineToDeleteStartOffset - 1,
+ text.getCharCount() - firstLineToDeleteStartOffset + 1,
+ ""); //$NON-NLS-1$
+ }
+
+ // Insert the new blank lines, leaving the cursor on the topmost of the new
+ // blank lines.
+
+ int totalCharacters = text.getCharCount();
+
+ if (currentLineStartOffset > totalCharacters)
+ {
+ // We are inserting the blank lines at the very end of the text, so
+ // currentLineStartOffset is now out of range. It will be be in range
+ // again after these newlines are appended.
+
+ text.replaceTextRange(totalCharacters, 0, generateString('\n', linesToInsert));
+ }
+ else
+ {
+ // We are inserting the blank lines in the middle of the text, so
+ // currentLineStartOffset is not out of range.
+
+ text.replaceTextRange(currentLineStartOffset, 0, generateString('\n', linesToInsert));
+ }
+
+ text.setCaretOffset(currentLineStartOffset);
+ }
+
+ /**
+ * 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.
+ */
+ protected void processAnsiCommand_M()
+ {
+ int totalLines = text.getLineCount();
+ int linesToDelete = getAnsiParameter(0);
+ int currentLineAbsolute = absoluteCursorLine();
+ int currentLineStartOffset = text.getOffsetAtLine(currentLineAbsolute);
+
+ // Compute the offset of the character after the lines to be deleted. This
+ // might be the end of the text.
+
+ if (linesToDelete >= totalLines - currentLineAbsolute)
+ {
+ // We are deleting all the lines to the bottom of the text. Replace them
+ // with blank lines.
+
+ text.replaceTextRange(currentLineStartOffset,
+ text.getCharCount() - currentLineStartOffset,
+ generateString('\n', totalLines - currentLineAbsolute - 1));
+ }
+ else
+ {
+ // Delete the next linesToDelete lines.
+
+ int firstUndeletedLineStartOffset =
+ text.getOffsetAtLine(currentLineAbsolute + linesToDelete);
+
+ text.replaceTextRange(currentLineStartOffset,
+ firstUndeletedLineStartOffset - currentLineStartOffset,
+ ""); //$NON-NLS-1$
+
+ // Add an equal number of blank lines to the end of the text.
+
+ text.replaceTextRange(text.getCharCount(), 0, generateString('\n', linesToDelete));
+ }
+
+ text.setCaretOffset(currentLineStartOffset);
+ }
+
+ /**
+ * This method sets a new graphics rendition mode, such as foreground/background
+ * color, bold/normal text, and reverse video.
+ */
+ protected void processAnsiCommand_m()
+ {
+ if (ansiParameters[0].length() == 0)
+ {
+ // This a special case: when no ANSI parameter is specified, act like a
+ // single parameter equal to 0 was specified.
+
+ ansiParameters[0].append('0');
+ }
+
+ // There are a non-zero number of ANSI parameters. Process each one in order.
+
+ int totalParameters = ansiParameters.length;
+ int parameterIndex = 0;
+
+ while (parameterIndex < totalParameters && ansiParameters[parameterIndex].length() > 0)
+ {
+ int ansiParameter = getAnsiParameter(parameterIndex);
+
+ switch (ansiParameter)
+ {
+ case 0:
+ // Reset all graphics modes.
+ currentForegroundColor = text.getForeground();
+ currentBackgroundColor = text.getBackground();
+ currentFontStyle = SWT.NORMAL;
+ reverseVideo = false;
+ break;
+
+ case 1:
+ currentFontStyle = SWT.BOLD; // Turn on bold.
+ break;
+
+ case 7:
+ reverseVideo = true; // Reverse video.
+ break;
+
+ case 10: // Set primary font. Ignored.
+ break;
+
+ case 22:
+ currentFontStyle = SWT.NORMAL; // Cancel bold or dim attributes only.
+ break;
+
+ case 27:
+ reverseVideo = false; // Cancel reverse video attribute only.
+ break;
+
+ case 30:
+ currentForegroundColor = BLACK; // Foreground is black.
+ break;
+
+ case 31:
+ currentForegroundColor = RED; // Foreground is red.
+ break;
+
+ case 32:
+ currentForegroundColor = GREEN; // Foreground is green.
+ break;
+
+ case 33:
+ currentForegroundColor = YELLOW; // Foreground is yellow.
+ break;
+
+ case 34:
+ currentForegroundColor = BLUE; // Foreground is blue.
+ break;
+
+ case 35:
+ currentForegroundColor = MAGENTA; // Foreground is magenta.
+ break;
+
+ case 36:
+ currentForegroundColor = CYAN; // Foreground is cyan.
+ break;
+
+ case 37:
+ currentForegroundColor = text.getForeground(); // Foreground is white.
+ break;
+
+ case 40:
+ currentBackgroundColor = text.getBackground(); // Background is black.
+ break;
+
+ case 41:
+ currentBackgroundColor = RED; // Background is red.
+ break;
+
+ case 42:
+ currentBackgroundColor = GREEN; // Background is green.
+ break;
+
+ case 43:
+ currentBackgroundColor = YELLOW; // Background is yellow.
+ break;
+
+ case 44:
+ currentBackgroundColor = BLUE; // Background is blue.
+ break;
+
+ case 45:
+ currentBackgroundColor = MAGENTA; // Background is magenta.
+ break;
+
+ case 46:
+ currentBackgroundColor = CYAN; // Background is cyan.
+ break;
+
+ case 47:
+ currentBackgroundColor = WHITE; // Background is white.
+ break;
+
+ default:
+ Logger.log("Unsupported graphics rendition parameter: " + ansiParameter); //$NON-NLS-1$
+ break;
+ }
+
+ ++ parameterIndex;
+ }
+ }
+
+ /**
+ * This method responds to an ANSI Device Status Report (DSR) command from the
+ * remote endpoint requesting the cursor position. Requests for other kinds of
+ * status are ignored.
+ */
+ protected void processAnsiCommand_n()
+ {
+ // Do nothing if the numeric parameter was not 6 (which means report cursor
+ // position).
+
+ if (getAnsiParameter(0) != 6)
+ return;
+
+ // Send the ANSI cursor position (which is 1-based) to the remote endpoint.
+
+ String positionReport = "\u001b[" + (relativeCursorLine() + 1) + ";" + //$NON-NLS-1$ //$NON-NLS-2$
+ (cursorColumn + 1) + "R"; //$NON-NLS-1$
+
+ OutputStreamWriter streamWriter =
+ new OutputStreamWriter(terminal.getOutputStream(), Charset.forName("ISO-8859-1")); //$NON-NLS-1$
+
+ try
+ {
+ streamWriter.write(positionReport, 0, positionReport.length());
+ streamWriter.flush();
+ }
+ catch (IOException ex)
+ {
+ Logger.log("Caught IOException!"); //$NON-NLS-1$
+ assert false;
+ }
+ }
+
+ /**
+ * Deletes one or more characters starting at the current cursor position.
+ * Characters on the same line and to the right of the deleted characters move
+ * left. If there are no characters on the current line at or to the right of the
+ * cursor column, no text is deleted.
+ */
+ protected void processAnsiCommand_P()
+ {
+ int currentLineEndOffset;
+ int currentLineAbsolute = absoluteCursorLine();
+
+ if (currentLineAbsolute == text.getLineCount() - 1)
+ {
+ // The cursor is on the bottommost line of text.
+
+ currentLineEndOffset = text.getCharCount();
+ }
+ else
+ {
+ // The cursor is not on the bottommost line of text.
+
+ currentLineEndOffset = text.getOffsetAtLine(currentLineAbsolute + 1) - 1;
+ }
+
+ int caretOffset = text.getCaretOffset();
+ int remainingCharactersOnLine = currentLineEndOffset - caretOffset;
+
+ if (remainingCharactersOnLine > 0)
+ {
+ // There are characters that can be deleted.
+
+ int charactersToDelete = getAnsiParameter(0);
+
+ if (charactersToDelete > remainingCharactersOnLine)
+ charactersToDelete = remainingCharactersOnLine;
+
+ text.replaceTextRange(caretOffset, charactersToDelete, ""); //$NON-NLS-1$
+ text.setCaretOffset(caretOffset);
+ }
+ }
+
+ /**
+ * This method returns one of the numeric ANSI parameters received in the most
+ * recent escape sequence.
+ *
+ * @return The parameterIndexth numeric ANSI parameter or -1 if the index
+ * is out of range.
+ */
+ protected int getAnsiParameter(int parameterIndex)
+ {
+ if (parameterIndex < 0 || parameterIndex >= ansiParameters.length)
+ {
+ // This should never happen.
+ assert false;
+ return -1;
+ }
+
+ String parameter = ansiParameters[parameterIndex].toString();
+
+ if (parameter.length() == 0)
+ return 1;
+
+ int parameterValue = 1;
+
+ // Don't trust the remote endpoint to send well formed numeric parameters.
+
+ try
+ {
+ parameterValue = Integer.parseInt(parameter);
+ }
+ catch (NumberFormatException ex)
+ {
+ parameterValue = 1;
+ }
+
+ return parameterValue;
+ }
+
+ /**
+ * This method processes a single parameter character in an ANSI escape sequence.
+ * Paramters 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"). Parameters are integers
+ * separated by one or more ';'s.
+ */
+ protected void processAnsiParameterCharacter(char ch)
+ {
+ if (ch == ';')
+ {
+ ++nextAnsiParameter;
+ }
+ else
+ {
+ if (nextAnsiParameter < ansiParameters.length)
+ 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.
+ */
+ protected void processNonControlCharacters()
+ {
+ int firstNonControlCharacterIndex = characterIndex;
+ int newTextLength = newText.length();
+ char character = newText.charAt(characterIndex);
+
+ // 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)
+ break;
+
+ character = newText.charAt(characterIndex);
+ }
+
+ // 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);
+
+ // 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
+ // text. Since this method is only called for a contiguous sequence of
+ // non-control characters, the current style applies to the entire sequence of
+ // characters.
+
+ if (!currentForegroundColor.equals(text.getForeground()) ||
+ !currentBackgroundColor.equals(text.getBackground()) ||
+ currentFontStyle != SWT.NORMAL ||
+ reverseVideo == true)
+ {
+ StyleRange style =
+ new StyleRange(preDisplayCaretOffset,
+ text.getCaretOffset() - preDisplayCaretOffset,
+ reverseVideo ? currentBackgroundColor : currentForegroundColor,
+ reverseVideo ? currentForegroundColor : currentBackgroundColor,
+ currentFontStyle);
+
+ text.setStyleRange(style);
+ }
+ }
+
+ /**
+ * This method displays a subset of the newly-received text in the Terminal view,
+ * wrapping text at the right edge of the screen and overwriting text when the
+ * cursor is not at the very end of the screen's text.
+ *
+ * There are never any ANSI control characters or escape sequences in the 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)
+ {
+ 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);
+ }
+ else
+ {
+ // The cursor is not at the end of the screen's text, so we have to
+ // overwrite existing text.
+
+ displayNewTextByOverwriting(first, last);
+ }
+ }
+
+ /**
+ * This method displays new text by appending it to the end of the existing text,
+ * wrapping text that extends past the right edge of the screen.
+ *
+ * There are never any ANSI control characters or escape sequences in the 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;
+ int availableSpaceOnLine = widthInColumns - cursorColumn;
+
+ if (numCharsToOutput >= availableSpaceOnLine)
+ {
+ // We need to wrap the text, because it's longer than the available
+ // space on the current line. First, appends as many characters as
+ // will fit in the space remaining on the current line.
+ //
+ // NOTE: We don't line wrap the text in this method the same way we line
+ // wrap the text in method displayNewTextByOverwriting(), but this is by far
+ // the most common case, and it has to run the fastest.
+
+ text.append(newText.substring(first, first + availableSpaceOnLine));
+ first += availableSpaceOnLine;
+
+ processCarriageReturn();
+ processNewline();
+
+ while (first <= last)
+ {
+ availableSpaceOnLine = widthInColumns;
+
+ if (availableSpaceOnLine > last - first + 1)
+ {
+ text.append(newText.substring(first, last + 1));
+ cursorColumn = last - first + 1;
+ break;
+ }
+ else
+ {
+ text.append(newText.substring(first, first + availableSpaceOnLine));
+ first += availableSpaceOnLine;
+
+ processCarriageReturn();
+ processNewline();
+ }
+ }
+ }
+ else
+ {
+ // We don't need to wrap the text.
+
+ text.append(newText.substring(first, last + 1));
+ cursorColumn += last - first + 1;
+ }
+ }
+
+ /**
+ * This method displays new text by overwriting existing text, wrapping text that
+ * extends past the right edge of the screen.
+ *
+ * There are never any ANSI control characters or escape sequences in the 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)
+ {
+ // 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.
+
+ List textSegments = new ArrayList(100);
+
+ int availableSpaceOnLine = widthInColumns - cursorColumn;
+
+ while (first <= last)
+ {
+ String segment;
+
+ if (last - first + 1 > availableSpaceOnLine)
+ segment = newText.substring(first, first + availableSpaceOnLine);
+ else
+ segment = newText.substring(first, last + 1);
+
+ textSegments.add(segment);
+
+ first += availableSpaceOnLine;
+ availableSpaceOnLine = widthInColumns;
+ }
+
+ // Next, for each segment, if the cursor is at the end of the text, append the
+ // segment along with a newline character. If the cursor is not at the end of
+ // the text, replace the next N characters starting at the cursor position with
+ // the segment, where N is the minimum of the length of the segment or the
+ // length of the rest of the current line.
+
+ Iterator iter = textSegments.iterator();
+
+ while (iter.hasNext())
+ {
+ String segment = (String)iter.next();
+ int caretOffset = text.getCaretOffset();
+
+ if (caretOffset == text.getCharCount())
+ {
+ // The cursor is at the end of the text, so just append the current
+ // segement along with a newline.
+
+ text.append(segment);
+
+ // If there is another segment to display, move the cursor to a new
+ // line.
+
+ if (iter.hasNext())
+ {
+ processCarriageReturn();
+ processNewline();
+ }
+ }
+ else
+ {
+ // The cursor is not at the end of the text, so replace some or all of
+ // the text following the cursor on the current line with the current
+ // segment.
+
+ int numCharactersAfterCursorOnLine;
+
+ if (absoluteCursorLine() == text.getLineCount() - 1)
+ {
+ // The cursor is on the last line of text.
+ numCharactersAfterCursorOnLine = text.getCharCount() - caretOffset;
+ }
+ else
+ {
+ // The cursor is not on the last line of text.
+ numCharactersAfterCursorOnLine =
+ text.getOffsetAtLine(absoluteCursorLine() + 1) - caretOffset - 1;
+ }
+
+ int segmentLength = segment.length();
+ int numCharactersToReplace;
+
+ if (segmentLength < numCharactersAfterCursorOnLine)
+ numCharactersToReplace = segmentLength;
+ else
+ numCharactersToReplace = numCharactersAfterCursorOnLine;
+
+ text.replaceTextRange(caretOffset, numCharactersToReplace, segment);
+ text.setCaretOffset(caretOffset + segmentLength);
+ cursorColumn += segmentLength;
+
+ // If there is another segment, move the cursor to the start of the
+ // next line.
+
+ if (iter.hasNext())
+ {
+ cursorColumn = 0;
+ text.setCaretOffset(caretOffset + segmentLength + 1);
+ }
+ else
+ {
+ // We just inserted the last segment. If the current line is full,
+ // wrap the cursor onto a new line.
+
+ if (cursorColumn == widthInColumns)
+ {
+ processCarriageReturn();
+ processNewline();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Process a BEL (Ctrl-G) character.
+ */
+ protected void processBEL()
+ {
+ // ISSUE: Is there a better way to make a sound? This is not guaranteed to
+ // work on all platforms.
+
+ java.awt.Toolkit.getDefaultToolkit().beep();
+ }
+
+ /**
+ * Process a backspace (Ctrl-H) character.
+ */
+ protected void processBackspace()
+ {
+ moveCursorBackward(1);
+ }
+
+ /**
+ * Process a tab (Ctrl-I) character. We don't insert a tab character into the
+ * StyledText widget. Instead, we move the cursor forward to the next tab stop,
+ * without altering any of the text. Tab stops are every 8 columns. The cursor
+ * will never move past the rightmost column.
+ */
+ protected void processTab()
+ {
+ moveCursorForward(8 - (cursorColumn % 8));
+ }
+
+ /**
+ * Process a newline (Control-J) character. A newline (NL) character just moves
+ * the cursor to the same column on the next line, creating new lines when the
+ * cursor reaches the bottom edge of the terminal. This is counter-intuitive,
+ * especially to UNIX programmers who are taught that writing a single NL to a
+ * terminal is sufficient to move the cursor to the first column of the next line,
+ * as if a carriage return (CR) and a NL were written.
+ *
+ * UNIX terminals typically display a NL character as a CR followed by a NL because
+ * the terminal device typically has the ONLCR attribute bit set (see the
+ * termios(4) man page for details), which causes the terminal device driver to
+ * translate NL to CR + NL on output. The terminal itself (i.e., a hardware
+ * terminal or a terminal emulator, like xterm or this code) _always_ interprets a
+ * CR to mean "move the cursor to the beginning of the current line" and a NL to
+ * mean "move the cursor to the same column on the next line".
+ */
+ protected void processNewline()
+ {
+ int totalLines = text.getLineCount();
+ int currentLineAbsolute = absoluteCursorLine();
+
+ if (currentLineAbsolute < totalLines - 1)
+ {
+ // The cursor is not on the bottommost line of text, so we move the cursor
+ // to the same column on the next line.
+
+ // TODO: If we can verify that the next character is a carriage return, we
+ // can optimize out the insertion of spaces that moveCursorDown() will do.
+
+ moveCursorDown(1);
+ }
+ else if (currentLineAbsolute == totalLines - 1)
+ {
+ // The cursor is on the bottommost line of text, so we append a newline
+ // character to the end of the terminal's text (creating a new line on the
+ // screen) and insert cursorColumn spaces.
+
+ text.append("\n"); //$NON-NLS-1$
+ text.append(generateString(' ', cursorColumn));
+ text.setCaretOffset(text.getCharCount());
+
+ // We may have scrolled a line off the top of the screen, so check if we
+ // need to delete some of the the oldest lines in the scroll buffer.
+
+ deleteTopmostLines();
+ }
+ else
+ {
+ // This should _never_ happen. If it does happen, it is a bug in this
+ // algorithm.
+
+ Logger.log("SHOULD NOT BE REACHED!"); //$NON-NLS-1$
+ assert false;
+ }
+ }
+
+ /**
+ * Process a Carriage Return (Ctrl-M).
+ */
+ protected void processCarriageReturn()
+ {
+ // Move the cursor to the beginning of the current line.
+
+ text.setCaretOffset(text.getOffsetAtLine(text.getLineAtOffset(text.getCaretOffset())));
+ cursorColumn = 0;
+ }
+
+ /**
+ * This method computes the width of the terminal in columns and its height in
+ * lines, then adjusts the width and height of the view's StyledText widget so that
+ * it displays an integral number of lines and columns of text. The adjustment is
+ * always to shrink the widget vertically or horizontally, because if the control
+ * were to grow, it would be clipped by the edges of the view window (i.e., the
+ * view window does not become larger to accommodate its contents becoming
+ * larger).
+ *
+ * This method must be called immediately before each time text is written to the
+ * terminal so that we can properly line wrap text. Because it is called so
+ * frequently, it must be fast when there is no resizing to be done.
+ */
+ protected void adjustTerminalDimensions()
+ {
+ // Compute how many pixels we need to shrink the StyledText control vertically
+ // to make it display an integral number of lines of text.
+
+ int linePixelHeight = text.getLineHeight();
+ Point textWindowDimensions = text.getSize();
+ int verticalPixelsToShrink = textWindowDimensions.y % linePixelHeight;
+
+ // Compute the current height of the terminal in lines.
+
+ heightInLines = textWindowDimensions.y / linePixelHeight;
+
+ // Compute how many pixels we need to shrink the StyledText control to make
+ // it display an integral number of columns of text. We can only do this if we
+ // know the pixel width of a character in the font used by the StyledText
+ // widget.
+
+ int horizontalPixelsToShrink = 0;
+
+ if (characterPixelWidth == 0)
+ computeCharacterPixelWidth();
+
+ if (characterPixelWidth != 0)
+ {
+ horizontalPixelsToShrink = textWindowDimensions.x % characterPixelWidth;
+
+ // The width of the StyledText widget that text.getSize() returns includes
+ // the space occupied by the vertical scrollbar, so we have to fudge this
+ // calculation (by subtracting 3 columns) to account for the presence of
+ // the scrollbar. Ugh.
+
+ widthInColumns = textWindowDimensions.x / characterPixelWidth - 3;
+ }
+
+ // If necessary, resize the text widget.
+
+ if (verticalPixelsToShrink > 0 || horizontalPixelsToShrink > 0)
+ {
+ // Remove this class instance from being a ControlListener on the
+ // StyledText widget, because we are about to resize and move the widget,
+ // and we don't want this method to be recursively invoked.
+
+ text.removeControlListener(this);
+
+ // Shrink the StyledText control so that it displays an integral number
+ // of lines of text and an integral number of columns of text.
+
+ textWindowDimensions.y -= verticalPixelsToShrink;
+ textWindowDimensions.x -= horizontalPixelsToShrink;
+ text.setSize(textWindowDimensions);
+
+ // Move the StyledText control down by the same number of pixels that
+ // we just shrank it vertically and right by the same number of pixels that
+ // we just shrank it horizontally. This makes the padding appear to the
+ // left and top of the widget, which is more visually appealing. This is
+ // only necessary because there is no way to programmatically shrink the
+ // view itself.
+
+ Point textLocation = text.getLocation();
+ textLocation.y += verticalPixelsToShrink;
+ textLocation.x += horizontalPixelsToShrink;
+ text.setLocation(textLocation);
+
+ // Restore this class instance as the ControlListener on the StyledText
+ // widget so we know when the user resizes the Terminal view.
+
+ text.addControlListener(this);
+
+ // Make sure the exposed portion of the Composite canvas behind the
+ // StyledText control matches the background color of the StyledText
+ // control.
+
+ Color textBackground = text.getBackground();
+ text.getParent().setBackground(textBackground);
+
+ // Scroll the StyledText widget to the bottommost position.
+
+ text.setSelectionRange(text.getCharCount(), 0);
+ text.showSelection();
+
+ // Tell the parent object to redraw itself. This erases any partial
+ // line of text that might be left visible where the parent object is
+ // now exposed. This call only happens if the size needed to be changed,
+ // so it should not cause any flicker.
+
+ text.getParent().redraw();
+ }
+
+ // If we are in a TELNET connection and we know the dimensions of the terminal,
+ // we give the size information to the TELNET connection object so it can
+ // communicate it to the TELNET server. If we are in a serial connection,
+ // there is nothing we can do to tell the remote host about the size of the
+ // terminal.
+
+ TelnetConnection telnetConnection = terminal.getTelnetConnection();
+
+ if (telnetConnection != null && telnetConnection.isConnected() &&
+ telnetConnection.isRemoteTelnetServer() &&
+ widthInColumns != 0 && heightInLines != 0)
+ {
+ telnetConnection.setTerminalSize(widthInColumns, heightInLines);
+ }
+ }
+
+ /**
+ * This method computes the the pixel width of a character in the current font.
+ * The Font object representing the font in the Terminal view doesn't provide the
+ * pixel width of the characters (even for a fixed width font). Instead, we get
+ * the pixel coordinates of the upper left corner of the bounding boxes for two
+ * adjacent characters on the same line and subtract the X coordinate of one from
+ * the X coordinate of the other. Simple, no?
+ */
+ protected void computeCharacterPixelWidth()
+ {
+ // We can't assume there is any text in the terminal, so make sure there's at
+ // least two characters.
+
+ text.replaceTextRange(0, 0, " "); //$NON-NLS-1$
+
+ Point firstCharLocation = text.getLocationAtOffset(0);
+ Point secondCharLocation = text.getLocationAtOffset(1);
+
+ characterPixelWidth = secondCharLocation.x - firstCharLocation.x;
+
+ text.replaceTextRange(0, 3, ""); //$NON-NLS-1$
+ }
+
+ /**
+ * This method deletes as many of the topmost lines of text as needed to keep the
+ * total number of lines of text in the Terminal view less than or equal to the
+ * limit configured in the preferences. If no limit is configured, this method
+ * does nothing.
+ */
+ protected void deleteTopmostLines()
+ {
+ Preferences preferences = TerminalPlugin.getDefault().getPluginPreferences();
+ boolean bLimitOutput = preferences.getBoolean(TerminalConsts.TERMINAL_PREF_LIMITOUTPUT);
+
+ if (!bLimitOutput)
+ return;
+
+ // Compute the number of lines to delete, but don't do anything if there are
+ // fewer lines in the terminal than the height of the terminal in lines.
+
+ int totalLineCount = text.getLineCount();
+
+ if (totalLineCount <= heightInLines)
+ return;
+
+ int bufferLineLimit = preferences.getInt(TerminalConsts.TERMINAL_PREF_BUFFERLINES);
+
+ // Don't allow the user to set the buffer line limit to less than the height of
+ // the terminal in lines.
+
+ if (bufferLineLimit <= heightInLines)
+ bufferLineLimit = heightInLines + 1;
+
+ int linesToDelete = totalLineCount - bufferLineLimit;
+
+ // Delete the lines. A small optimization here: don't do anything unless
+ // there's at least 5 lines to delete.
+
+ if (linesToDelete >= 5)
+ text.replaceTextRange(0, text.getOffsetAtLine(linesToDelete), ""); //$NON-NLS-1$
+ }
+
+ /**
+ * This method returns the absolute line number of the line containing the
+ * cursor. The very first line of text (even if it is scrolled off the screen) is
+ * absolute line number 0.
+ *
+ * @return The absolute line number of the line containing the cursor.
+ */
+ protected int absoluteCursorLine()
+ {
+ return text.getLineAtOffset(text.getCaretOffset());
+ }
+
+ /**
+ * This method returns the relative line number of the line comtaining the cursor.
+ * The returned line number is relative to the topmost visible line, which has
+ * relative line number 0.
+ *
+ * @return The relative line number of the line containing the cursor.
+ */
+ protected int relativeCursorLine()
+ {
+ int totalLines = text.getLineCount();
+
+ if (totalLines <= heightInLines)
+ return text.getLineAtOffset(text.getCaretOffset());
+
+ return absoluteCursorLine() - totalLines + heightInLines;
+ }
+
+ /**
+ * This method converts a visible line number (i.e., line 0 is the topmost visible
+ * line if the terminal is scrolled all the way down, and line number heightInLines
+ * - 1 is the bottommost visible line if the terminal is scrolled all the way down)
+ * to a line number as known to the StyledText widget.
+ */
+ protected int absoluteLine(int visibleLineNumber)
+ {
+ int totalLines = text.getLineCount();
+
+ if (totalLines <= heightInLines)
+ return visibleLineNumber;
+
+ return totalLines - heightInLines + visibleLineNumber;
+ }
+
+ /**
+ * This method returns a String containing count ch characters.
+ *
+ * @return A String containing count ch characters.
+ */
+ protected String generateString(char ch, int count)
+ {
+ char[] chars = new char[count];
+
+ for (int i = 0; i < chars.length; ++i)
+ chars[i] = ch;
+
+ return new String(chars);
+ }
+
+ /**
+ * This method moves the cursor to the specified line and column. Parameter
+ * targetLine is the line number of a screen line, so it has a minimum value
+ * of 0 (the topmost screen line) and a maximum value of heightInLines - 1 (the
+ * bottommost screen line). A line does not have to contain any text to move the
+ * cursor to any column in that line.
+ */
+ protected void moveCursor(int targetLine, int targetColumn)
+ {
+ // Don't allow out of range target line and column values.
+
+ if (targetLine < 0) targetLine = 0;
+ if (targetLine >= heightInLines) targetLine = heightInLines - 1;
+
+ if (targetColumn < 0) targetColumn = 0;
+ if (targetColumn >= widthInColumns) targetColumn = widthInColumns - 1;
+
+ // First, find out if we need to append newlines to the end of the text. This
+ // is necessary if there are fewer total lines of text than visible screen
+ // lines and the target line is below the bottommost line of text.
+
+ int totalLines = text.getLineCount();
+
+ if (totalLines < heightInLines && targetLine >= totalLines)
+ text.append(generateString('\n', heightInLines - totalLines));
+
+ // Next, compute the offset of the start of the target line.
+
+ int targetLineStartOffset = text.getOffsetAtLine(absoluteLine(targetLine));
+
+ // Next, find how many characters are in the target line. Be careful not to
+ // index off the end of the StyledText widget.
+
+ int nextLineNumber = absoluteLine(targetLine + 1);
+ int targetLineLength;
+
+ if (nextLineNumber >= totalLines)
+ {
+ // The target line is the bottommost line of text.
+
+ targetLineLength = text.getCharCount() - targetLineStartOffset;
+ }
+ else
+ {
+ // The target line is not the bottommost line of text, so compute its
+ // length by subtracting the start offset of the target line from the start
+ // offset of the following line.
+
+ targetLineLength = text.getOffsetAtLine(nextLineNumber) - targetLineStartOffset - 1;
+ }
+
+ // Find out if we can just move the cursor without having to insert spaces at
+ // the end of the target line.
+
+ if (targetColumn >= targetLineLength)
+ {
+ // The target line is not long enough to just move the cursor, so we have
+ // to append spaces to it before positioning the cursor.
+
+ int spacesToAppend = targetColumn - targetLineLength;
+
+ text.replaceTextRange(targetLineStartOffset + targetLineLength, 0,
+ generateString(' ', spacesToAppend));
+ }
+
+ // Now position the cursor.
+
+ text.setCaretOffset(targetLineStartOffset + targetColumn);
+
+ cursorColumn = targetColumn;
+ }
+
+ /**
+ * This method moves the cursor down lines lines, but won't move the cursor
+ * past the bottom of the screen. This method does not cause any scrolling.
+ */
+ protected void moveCursorDown(int lines)
+ {
+ moveCursor(relativeCursorLine() + lines, cursorColumn);
+ }
+
+ /**
+ * This method moves the cursor up lines lines, but won't move the cursor
+ * past the top of the screen. This method does not cause any scrolling.
+ */
+ protected void moveCursorUp(int lines)
+ {
+ moveCursor(relativeCursorLine() - lines, cursorColumn);
+ }
+
+ /**
+ * This method moves the cursor forward columns columns, but won't move the
+ * cursor past the right edge of the screen, nor will it move the cursor onto the
+ * next line. This method does not cause any scrolling.
+ */
+ protected void moveCursorForward(int columnsToMove)
+ {
+ moveCursor(relativeCursorLine(), cursorColumn + columnsToMove);
+ }
+
+ /**
+ * This method moves the cursor backward columnsToMove columns, but won't
+ * move the cursor past the left edge of the screen, nor will it move the cursor
+ * onto the previous line. This method does not cause any scrolling.
+ */
+ protected void moveCursorBackward(int columnsToMove)
+ {
+ // We don't call moveCursor() here, because this is optimized for backward
+ // cursor motion on a single line.
+
+ if (columnsToMove > cursorColumn)
+ columnsToMove = cursorColumn;
+
+ text.setCaretOffset(text.getCaretOffset() - columnsToMove);
+
+ cursorColumn -= columnsToMove;
+ }
+}
diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalView.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalView.java
new file mode 100644
index 00000000000..b5117f7f294
--- /dev/null
+++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/TerminalView.java
@@ -0,0 +1,1047 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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:
+ * Wind River Systems, Inc. - initial implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.tm.terminal;
+
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.resource.FontRegistry;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.events.MenuEvent;
+import org.eclipse.swt.events.MenuListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IPartListener;
+import org.eclipse.ui.IViewSite;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.actions.RetargetAction;
+import org.eclipse.ui.contexts.IContextActivation;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.internal.WorkbenchWindow;
+import org.eclipse.ui.part.ViewPart;
+
+public class TerminalView extends ViewPart implements TerminalTarget, TerminalConsts
+{
+ protected static final String m_SecondaryTerminalCountMutex = ""; //$NON-NLS-1$
+ protected static int m_SecondaryTerminalCount = 0;
+
+ protected TerminalCtrl m_ctlTerminal;
+ protected TerminalAction m_actionTerminalNewTerminal;
+ protected TerminalAction m_actionTerminalConnect;
+ protected TerminalAction m_actionTerminalDisconnect;
+ protected TerminalAction m_actionTerminalSettings;
+ protected TerminalAction m_actionEditCopy;
+ protected TerminalAction m_actionEditCut;
+ protected TerminalAction m_actionEditPaste;
+ protected TerminalAction m_actionEditClearAll;
+ protected TerminalAction m_actionEditSelectAll;
+ protected TerminalMenuHandlerEdit m_MenuHandlerEdit;
+ protected TerminalPropertyChangeHandler m_PropertyChangeHandler;
+ protected TerminalSettings m_TerminalSettings;
+ protected boolean m_bMenuAboutToShow;
+ /** Remember the item with which we contributed the shortcutt to unregister them again! */
+ private IContextActivation fRememberedContextActivation;
+
+ /**
+ *
+ */
+ public TerminalView()
+ {
+ Logger.log("==============================================================="); //$NON-NLS-1$
+ setupView();
+ }
+
+ // TerminalTarget interface
+
+ /**
+ *
+ */
+ public void execute(String strMsg,Object data)
+ {
+ if (strMsg.equals(ON_TERMINAL_FOCUS))
+ {
+ onTerminalFocus(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_NEW_TERMINAL))
+ {
+ onTerminalNewTerminal(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_CONNECT))
+ {
+ onTerminalConnect(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_TERMINAL_CONNECT))
+ {
+ onUpdateTerminalConnect(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_CONNECTING))
+ {
+ onTerminalConnecting(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_DISCONNECT))
+ {
+ onTerminalDisconnect(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_TERMINAL_DISCONNECT))
+ {
+ onUpdateTerminalDisconnect(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_SETTINGS))
+ {
+ onTerminalSettings(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_TERMINAL_SETTINGS))
+ {
+ onUpdateTerminalSettings(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_STATUS))
+ {
+ onTerminalStatus(data);
+ }
+ else if (strMsg.equals(ON_TERMINAL_FONTCHANGED))
+ {
+ onTerminalFontChanged(data);
+ }
+ else if (strMsg.equals(ON_EDIT_COPY))
+ {
+ onEditCopy(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_EDIT_COPY))
+ {
+ onUpdateEditCopy(data);
+ }
+ else if (strMsg.equals(ON_EDIT_CUT))
+ {
+ onEditCut(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_EDIT_CUT))
+ {
+ onUpdateEditCut(data);
+ }
+ else if (strMsg.equals(ON_EDIT_PASTE))
+ {
+ onEditPaste(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_EDIT_PASTE))
+ {
+ onUpdateEditPaste(data);
+ }
+ else if (strMsg.equals(ON_EDIT_CLEARALL))
+ {
+ onEditClearAll(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_EDIT_CLEARALL))
+ {
+ onUpdateEditClearAll(data);
+ }
+ else if (strMsg.equals(ON_EDIT_SELECTALL))
+ {
+ onEditSelectAll(data);
+ }
+ else if (strMsg.equals(ON_UPDATE_EDIT_SELECTALL))
+ {
+ onUpdateEditSelectAll(data);
+ }
+ else
+ {
+ }
+ }
+
+ // Message handlers
+
+ /**
+ *
+ */
+ protected void onTerminalFocus(Object data)
+ {
+ m_ctlTerminal.setFocus();
+ }
+
+ /**
+ * Display a new Terminal view. This method is called when the user clicks the New
+ * Terminal button in any Terminal view's toolbar.
+ */
+ protected void onTerminalNewTerminal(Object data)
+ {
+ Logger.log("creating new Terminal instance."); //$NON-NLS-1$
+
+ try
+ {
+ // The second argument to showView() is a unique String identifying the
+ // secondary view instance. If it ever matches a previously used secondary
+ // view identifier, then this call will not create a new Terminal view,
+ // which is undesireable. Therefore, we append the current time in
+ // milliseconds to the secondary view identifier to ensure it is always
+ // unique. This code runs only when the user clicks the New Terminal
+ // button, so there is no risk that this code will run twice in a single
+ // millisecond.
+
+ getSite().getPage().showView("org.eclipse.tm.terminal.TerminalView",//$NON-NLS-1$
+ "SecondaryTerminal" + System.currentTimeMillis(), //$NON-NLS-1$
+ IWorkbenchPage.VIEW_ACTIVATE);
+ }
+ catch (PartInitException ex)
+ {
+ Logger.logException(ex);
+ }
+ }
+
+ /**
+ *
+ */
+ protected void onTerminalConnect(Object data)
+ {
+ if (m_ctlTerminal.isConnected())
+ return;
+
+ m_ctlTerminal.connectTerminal(m_TerminalSettings);
+
+ execute(ON_UPDATE_TERMINAL_CONNECT,null);
+ execute(ON_UPDATE_TERMINAL_DISCONNECT,null);
+ execute(ON_UPDATE_TERMINAL_SETTINGS,null);
+ }
+
+ /**
+ *
+ */
+ protected void onTerminalConnecting(Object data)
+ {
+ execute(ON_UPDATE_TERMINAL_CONNECT,null);
+ execute(ON_UPDATE_TERMINAL_DISCONNECT,null);
+ execute(ON_UPDATE_TERMINAL_SETTINGS,null);
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateTerminalConnect(Object data)
+ {
+ boolean bEnabled;
+
+ bEnabled = ((!m_ctlTerminal.isConnecting()) &&
+ (!m_ctlTerminal.isConnected()));
+
+ m_actionTerminalConnect.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onTerminalDisconnect(Object data)
+ {
+ if ((!m_ctlTerminal.isConnecting()) &&
+ (!m_ctlTerminal.isConnected()))
+ {
+ execute(ON_TERMINAL_STATUS, null);
+ execute(ON_UPDATE_TERMINAL_CONNECT, null);
+ execute(ON_UPDATE_TERMINAL_DISCONNECT, null);
+ execute(ON_UPDATE_TERMINAL_SETTINGS, null);
+ return;
+ }
+
+ m_ctlTerminal.disconnectTerminal();
+
+ execute(ON_UPDATE_TERMINAL_CONNECT,null);
+ execute(ON_UPDATE_TERMINAL_DISCONNECT,null);
+ execute(ON_UPDATE_TERMINAL_SETTINGS,null);
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateTerminalDisconnect(Object data)
+ {
+ boolean bEnabled;
+
+ bEnabled = ((m_ctlTerminal.isConnecting()) ||
+ (m_ctlTerminal.isConnected()));
+
+ m_actionTerminalDisconnect.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onTerminalSettings(Object data)
+ {
+ TerminalSettingsDlg dlgTerminalSettings;
+ int nStatus;
+
+ // When the settings dialog is opened, load the Terminal settings from the
+ // persistent settings.
+
+ m_TerminalSettings.importSettings(getPartName());
+
+ dlgTerminalSettings = new TerminalSettingsDlg(getViewSite().getShell());
+ dlgTerminalSettings.loadSettings(m_TerminalSettings);
+
+ Logger.log("opening Settings dialog."); //$NON-NLS-1$
+
+ nStatus = dlgTerminalSettings.open();
+
+ if (nStatus == TerminalConsts.TERMINAL_ID_CANCEL)
+ {
+ Logger.log("Settings dialog cancelled."); //$NON-NLS-1$
+ return;
+ }
+
+ Logger.log("Settings dialog OK'ed."); //$NON-NLS-1$
+
+ // When the settings dialog is closed, we persist the Terminal settings.
+
+ m_TerminalSettings.exportSettings(getPartName());
+
+ execute(ON_TERMINAL_CONNECT,null);
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateTerminalSettings(Object data)
+ {
+ boolean bEnabled;
+
+ bEnabled = ((!m_ctlTerminal.isConnecting()) &&
+ (!m_ctlTerminal.isConnected()));
+
+ m_actionTerminalSettings.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onTerminalStatus(Object data)
+ {
+ String strConnType;
+ String strConnected;
+ String strSerialPort;
+ String strBaudRate;
+ String strDataBits;
+ String strStopBits;
+ String strParity;
+ String strFlowControl;
+ String strHost;
+ String strNetworkPort;
+ String strText;
+ String strTitle;
+
+ if (m_ctlTerminal.isDisposed())
+ return;
+
+ if (data != null)
+ {
+ // When parameter 'data' is not null, it is a String containing text to
+ // display in the view's content description line. This is used by class
+ // TerminalText when it processes an ANSI OSC escape sequence that commands
+ // the terminal to display text in its title bar.
+
+ strTitle = (String)data;
+ }
+ else
+ {
+ // When parameter 'data' is null, we construct a descriptive string to
+ // display in the content description line.
+
+ if (m_ctlTerminal.isConnecting())
+ {
+ strConnected = "CONNECTING..."; //$NON-NLS-1$
+ }
+ else if (m_ctlTerminal.isOpened())
+ {
+ strConnected = "OPENED"; //$NON-NLS-1$
+ }
+ else if (m_ctlTerminal.isConnected())
+ {
+ strConnected = "CONNECTED"; //$NON-NLS-1$
+ }
+ else
+ {
+ strConnected = "CLOSED"; //$NON-NLS-1$
+ }
+
+ strConnType = m_TerminalSettings.getConnType();
+ if (strConnType.equals(TERMINAL_CONNTYPE_SERIAL))
+ {
+ strSerialPort = m_TerminalSettings.getSerialPort();
+ strBaudRate = m_TerminalSettings.getBaudRate();
+ strDataBits = m_TerminalSettings.getDataBits();
+ strStopBits = m_TerminalSettings.getStopBits();
+ strParity = m_TerminalSettings.getParity();
+ strFlowControl = m_TerminalSettings.getFlowControl();
+ strText = " (" + //$NON-NLS-1$
+ strSerialPort +
+ ", " + //$NON-NLS-1$
+ strBaudRate +
+ ", " + //$NON-NLS-1$
+ strDataBits +
+ ", " + //$NON-NLS-1$
+ strStopBits +
+ ", " + //$NON-NLS-1$
+ strParity +
+ ", " + //$NON-NLS-1$
+ strFlowControl +
+ " - " + //$NON-NLS-1$
+ strConnected +
+ ")"; //$NON-NLS-1$
+ }
+ else if (strConnType.equals(TERMINAL_CONNTYPE_NETWORK))
+ {
+ strHost = m_TerminalSettings.getHost();
+ strNetworkPort = m_TerminalSettings.getNetworkPort();
+ strText = " (" + //$NON-NLS-1$
+ strHost +
+ ":" + //$NON-NLS-1$
+ strNetworkPort +
+ " - " + //$NON-NLS-1$
+ strConnected +
+ ")"; //$NON-NLS-1$
+ }
+ else
+ {
+ strText = ""; //$NON-NLS-1$
+ }
+
+ strTitle = TERMINAL_PROP_TITLE + strText;
+ }
+
+ setContentDescription(strTitle);
+ getViewSite().getActionBars().getStatusLineManager().setMessage(strTitle);
+ }
+
+ /**
+ *
+ */
+ protected void onTerminalFontChanged(Object data)
+ {
+ StyledText ctlText;
+ Font font;
+
+ ctlText = m_ctlTerminal.getTextWidget();
+ font = JFaceResources.getFont(TERMINAL_FONT_DEFINITION);
+ ctlText.setFont(font);
+
+ // Tell the TerminalCtrl singleton that the font has changed.
+
+ m_ctlTerminal.onFontChanged();
+ }
+
+ /**
+ *
+ */
+ protected void onEditCopy(Object data)
+ {
+ ITextSelection selection;
+ String strText;
+ boolean bEnabled;
+
+ selection = m_ctlTerminal.getSelection();
+ strText = selection.getText();
+ bEnabled = !strText.equals(""); //$NON-NLS-1$
+
+ if (bEnabled)
+ {
+ m_ctlTerminal.copy();
+ }
+ else
+ {
+ m_ctlTerminal.sendKey('\u0003');
+ }
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateEditCopy(Object data)
+ {
+ ITextSelection selection;
+ String strText;
+ boolean bEnabled;
+
+ if (m_bMenuAboutToShow)
+ {
+ selection = m_ctlTerminal.getSelection();
+ strText = selection.getText();
+ bEnabled = !strText.equals(""); //$NON-NLS-1$
+ }
+ else
+ {
+ bEnabled = true;
+ }
+
+ m_actionEditCopy.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onEditCut(Object data)
+ {
+ m_ctlTerminal.sendKey('\u0018');
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateEditCut(Object data)
+ {
+ boolean bEnabled;
+
+ bEnabled = !m_bMenuAboutToShow;
+ m_actionEditCut.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onEditPaste(Object data)
+ {
+ m_ctlTerminal.paste();
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateEditPaste(Object data)
+ {
+ Clipboard clipboard;
+ TextTransfer textTransfer;
+ String strText;
+ boolean bConnected;
+ boolean bEnabled;
+
+ clipboard = m_ctlTerminal.getClipboard();
+ textTransfer = TextTransfer.getInstance();
+ strText = (String)clipboard.getContents(textTransfer);
+ bConnected = m_ctlTerminal.isConnected();
+
+ bEnabled = ((strText != null) &&
+ (!strText.equals("")) && //$NON-NLS-1$
+ (bConnected));
+
+ m_actionEditPaste.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onEditClearAll(Object data)
+ {
+ m_ctlTerminal.clearTerminal();
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateEditClearAll(Object data)
+ {
+ boolean bEnabled;
+
+ bEnabled = !m_ctlTerminal.isEmpty();
+ m_actionEditClearAll.setEnabled(bEnabled);
+ }
+
+ /**
+ *
+ */
+ protected void onEditSelectAll(Object data)
+ {
+ m_ctlTerminal.selectAll();
+ }
+
+ /**
+ *
+ */
+ protected void onUpdateEditSelectAll(Object data)
+ {
+ boolean bEnabled;
+
+ bEnabled = !m_ctlTerminal.isEmpty();
+ m_actionEditSelectAll.setEnabled(bEnabled);
+ }
+
+ // ViewPart interface
+
+ /**
+ *
+ */
+ public void createPartControl(Composite wndParent)
+ {
+ // Bind plugin.xml key bindings to this plugin. Overrides global Ctrl-W key
+ // sequence.
+
+ /** Activate the sy context allowing shortcuts like F3(open declaration) in the view */
+ IContextService ctxtService = (IContextService)getSite().getService(IContextService.class);
+ fRememberedContextActivation = ctxtService.activateContext("org.eclipse.tm.terminal.TerminalPreferencePage"); //$NON-NLS-1$
+
+ setupControls(wndParent);
+ setupActions();
+ setupMenus();
+ setupToolBars();
+ setupLocalMenus();
+ setupLocalToolBars();
+ setupContextMenus();
+ setupListeners(wndParent);
+
+ synchronized (m_SecondaryTerminalCountMutex)
+ {
+ setPartName(TERMINAL_PROP_TITLE + " " + m_SecondaryTerminalCount++); //$NON-NLS-1$
+ }
+
+ execute(ON_TERMINAL_STATUS,null);
+ }
+
+ /**
+ *
+ */
+ public void dispose()
+ {
+ Logger.log("entered."); //$NON-NLS-1$
+
+ setPartName("Terminal"); //$NON-NLS-1$
+
+ TerminalPlugin plugin;
+ FontRegistry fontRegistry;
+ IWorkbench workbench;
+ WorkbenchWindow workbenchWindow;
+ MenuManager menuMgr;
+ Menu menu;
+
+ /** The context (for short cuts) was set above, now unset it again */
+ if (fRememberedContextActivation != null) {
+ IContextService ctxService = (IContextService)getSite().getService(IContextService.class);
+ ctxService.deactivateContext(fRememberedContextActivation);
+ fRememberedContextActivation = null;
+ }
+
+ fontRegistry = JFaceResources.getFontRegistry();
+ plugin = TerminalPlugin.getDefault();
+ workbench = plugin.getWorkbench();
+ workbenchWindow = (WorkbenchWindow) workbench.getActiveWorkbenchWindow();
+ menuMgr = workbenchWindow.getMenuManager();
+ menuMgr = (MenuManager) menuMgr.findMenuUsingPath(IWorkbenchActionConstants.M_EDIT);
+ menu = menuMgr.getMenu();
+
+ fontRegistry.removeListener(m_PropertyChangeHandler);
+ menuMgr.removeMenuListener(m_MenuHandlerEdit);
+
+ if (menu != null)
+ menu.removeMenuListener(m_MenuHandlerEdit);
+
+ m_ctlTerminal.disposeTerminal();
+ }
+
+ /**
+ * Passing the focus request to the viewer's control.
+ */
+ public void setFocus()
+ {
+ execute(ON_TERMINAL_FOCUS,null);
+ }
+
+ // Operations
+
+ /**
+ *
+ */
+ protected void setupView()
+ {
+ m_TerminalSettings = new TerminalSettings(getPartName());
+ m_bMenuAboutToShow = false;
+ }
+
+ /**
+ * This method creates the top-level control for the Terminal view.
+ */
+ protected void setupControls(Composite wndParent)
+ {
+ try
+ {
+ m_ctlTerminal = new TerminalCtrl(this, wndParent);
+ }
+ catch (Exception ex)
+ {
+ Logger.logException(ex);
+ }
+ }
+
+ /**
+ *
+ */
+ protected void setupActions()
+ {
+ IViewSite viewSite;
+ IActionBars actionBars;
+
+ viewSite = getViewSite();
+ actionBars = viewSite.getActionBars();
+ m_actionTerminalNewTerminal = new TerminalActionNewTerminal(this);
+ m_actionTerminalConnect = new TerminalActionConnect(this);
+ m_actionTerminalDisconnect = new TerminalActionDisconnect(this);
+ m_actionTerminalSettings = new TerminalActionSettings(this);
+ m_actionEditCopy = new TerminalActionCopy(this);
+ m_actionEditCut = new TerminalActionCut(this);
+ m_actionEditPaste = new TerminalActionPaste(this);
+ m_actionEditClearAll = new TerminalActionClearAll(this);
+ m_actionEditSelectAll = new TerminalActionSelectAll(this);
+
+ actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(),
+ m_actionEditCopy);
+
+ actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(),
+ m_actionEditCut);
+
+ actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(),
+ m_actionEditPaste);
+
+ actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
+ m_actionEditSelectAll);
+ }
+
+ /**
+ *
+ */
+ protected void setupMenus()
+ {
+ TerminalPlugin plugin;
+ IWorkbench workbench;
+ WorkbenchWindow workbenchWindow;
+ MenuManager menuMgr;
+ Menu menu;
+
+ m_MenuHandlerEdit = new TerminalMenuHandlerEdit();
+ plugin = TerminalPlugin.getDefault();
+ workbench = plugin.getWorkbench();
+ workbenchWindow = (WorkbenchWindow) workbench.getActiveWorkbenchWindow();
+ menuMgr = workbenchWindow.getMenuManager();
+ menuMgr = (MenuManager)menuMgr.findMenuUsingPath(IWorkbenchActionConstants.M_EDIT);
+ menu = menuMgr.getMenu();
+
+ menuMgr.addMenuListener(m_MenuHandlerEdit);
+ menu.addMenuListener(m_MenuHandlerEdit);
+ }
+
+ /**
+ *
+ */
+ protected void setupToolBars()
+ {
+ }
+
+ /**
+ *
+ */
+ protected void setupLocalMenus()
+ {
+ }
+
+ /**
+ *
+ */
+ protected void setupLocalToolBars()
+ {
+ IViewSite viewSite;
+ IActionBars actionBars;
+ IToolBarManager toolBarMgr;
+
+ viewSite = getViewSite();
+ actionBars = viewSite.getActionBars();
+ toolBarMgr = actionBars.getToolBarManager();
+
+ toolBarMgr.add(m_actionTerminalNewTerminal);
+ toolBarMgr.add(m_actionTerminalConnect);
+ toolBarMgr.add(m_actionTerminalDisconnect);
+ toolBarMgr.add(m_actionTerminalSettings);
+ }
+
+ /**
+ *
+ */
+ protected void setupContextMenus()
+ {
+ StyledText ctlText;
+ MenuManager menuMgr;
+ Menu menu;
+ TerminalContextMenuHandler contextMenuHandler;
+
+ ctlText = m_ctlTerminal.getTextWidget();
+ menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
+ menu = menuMgr.createContextMenu(ctlText);
+ contextMenuHandler = new TerminalContextMenuHandler();
+
+ ctlText.setMenu(menu);
+ menuMgr.setRemoveAllWhenShown(true);
+ menuMgr.addMenuListener(contextMenuHandler);
+ menu.addMenuListener(contextMenuHandler);
+ }
+
+ /**
+ *
+ */
+ protected void loadContextMenus(IMenuManager menuMgr)
+ {
+ menuMgr.add(m_actionEditCopy);
+ menuMgr.add(m_actionEditPaste);
+ menuMgr.add(new Separator());
+ menuMgr.add(m_actionEditClearAll);
+ menuMgr.add(m_actionEditSelectAll);
+
+ // Other plug-ins can contribute there actions here
+ menuMgr.add(new Separator("Additions")); //$NON-NLS-1$
+ }
+
+ /**
+ *
+ */
+ protected void setupListeners(Composite wndParent)
+ {
+ getViewSite().getPage().addPartListener(new IPartListener() {
+ public void partClosed(IWorkbenchPart part)
+ {
+ if (part instanceof TerminalView)
+ part.dispose();
+ }
+
+ public void partActivated(IWorkbenchPart part) { }
+ public void partBroughtToTop(IWorkbenchPart part) { }
+ public void partDeactivated(IWorkbenchPart part) { }
+ public void partOpened(IWorkbenchPart part) { }
+ });
+
+ FontRegistry fontRegistry = JFaceResources.getFontRegistry();
+ m_PropertyChangeHandler = new TerminalPropertyChangeHandler();
+ fontRegistry.addListener(m_PropertyChangeHandler);
+ }
+
+ // Inner classes
+
+ /**
+ *
+ */
+ protected class TerminalMenuHandlerEdit
+ implements MenuListener,
+ IMenuListener
+ {
+ /**
+ *
+ */
+ protected String m_strActionDefinitionIdCopy;
+ protected String m_strActionDefinitionIdPaste;
+ protected String m_strActionDefinitionIdSelectAll;
+
+ protected int m_nAcceleratorCopy;
+ protected int m_nAcceleratorPaste;
+ protected int m_nAcceleratorSelectAll;
+
+ /**
+ *
+ */
+ protected TerminalMenuHandlerEdit()
+ {
+ super();
+
+ m_strActionDefinitionIdCopy = ""; //$NON-NLS-1$
+ m_strActionDefinitionIdPaste = ""; //$NON-NLS-1$
+ m_strActionDefinitionIdSelectAll = ""; //$NON-NLS-1$
+
+ m_nAcceleratorCopy = 0;
+ m_nAcceleratorPaste = 0;
+ m_nAcceleratorSelectAll = 0;
+ }
+
+ // IMenuListener interface
+
+ /**
+ *
+ */
+ public void menuAboutToShow(IMenuManager menuMgr)
+ {
+ ActionContributionItem item;
+ RetargetAction action;
+
+ m_bMenuAboutToShow = true;
+ execute(ON_UPDATE_EDIT_COPY,null);
+ execute(ON_UPDATE_EDIT_CUT,null);
+ execute(ON_UPDATE_EDIT_PASTE,null);
+ execute(ON_UPDATE_EDIT_SELECTALL,null);
+
+ item = (ActionContributionItem)menuMgr.find(ActionFactory.COPY.getId());
+ action = (RetargetAction) item.getAction();
+ m_strActionDefinitionIdCopy = action.getActionDefinitionId();
+ m_nAcceleratorCopy = action.getAccelerator();
+ action.setActionDefinitionId(null);
+ action.enableAccelerator(false);
+ item.update();
+
+ item = (ActionContributionItem)menuMgr.find(ActionFactory.PASTE.getId());
+ action = (RetargetAction) item.getAction();
+ m_strActionDefinitionIdPaste = action.getActionDefinitionId();
+ m_nAcceleratorPaste = action.getAccelerator();
+ action.setActionDefinitionId(null);
+ action.enableAccelerator(false);
+ item.update();
+
+ item = (ActionContributionItem)menuMgr.find(ActionFactory.SELECT_ALL.getId());
+ action = (RetargetAction) item.getAction();
+ m_strActionDefinitionIdSelectAll = action.getActionDefinitionId();
+ m_nAcceleratorSelectAll = action.getAccelerator();
+ action.setActionDefinitionId(null);
+ action.enableAccelerator(false);
+ item.update();
+ }
+
+ // MenuListener interface
+
+ /**
+ *
+ */
+ public void menuShown(MenuEvent event)
+ {
+ }
+
+ /**
+ *
+ */
+ public void menuHidden(MenuEvent event)
+ {
+ TerminalPlugin plugin;
+ IWorkbench workbench;
+ WorkbenchWindow workbenchWindow;
+ MenuManager menuMgr;
+ ActionContributionItem item;
+ RetargetAction action;
+
+ m_bMenuAboutToShow = false;
+ execute(ON_UPDATE_EDIT_COPY,null);
+ execute(ON_UPDATE_EDIT_CUT,null);
+
+ plugin = TerminalPlugin.getDefault();
+ workbench = plugin.getWorkbench();
+ workbenchWindow = (WorkbenchWindow) workbench.getActiveWorkbenchWindow();
+ menuMgr = workbenchWindow.getMenuManager();
+ menuMgr = (MenuManager) menuMgr.findMenuUsingPath(IWorkbenchActionConstants.M_EDIT);
+
+ item = (ActionContributionItem) menuMgr.find(ActionFactory.COPY.getId());
+ action = (RetargetAction) item.getAction();
+ action.setActionDefinitionId(m_strActionDefinitionIdCopy);
+ action.setAccelerator(m_nAcceleratorCopy);
+ action.enableAccelerator(true);
+ item.update();
+
+ item = (ActionContributionItem) menuMgr.find(ActionFactory.PASTE.getId());
+ action = (RetargetAction) item.getAction();
+ action.setActionDefinitionId(m_strActionDefinitionIdPaste);
+ action.setAccelerator(m_nAcceleratorPaste);
+ action.enableAccelerator(true);
+ item.update();
+
+ item = (ActionContributionItem) menuMgr.find(ActionFactory.SELECT_ALL.getId());
+ action = (RetargetAction) item.getAction();
+ action.setActionDefinitionId(m_strActionDefinitionIdSelectAll);
+ action.setAccelerator(m_nAcceleratorSelectAll);
+ action.enableAccelerator(true);
+ item.update();
+ }
+ }
+
+ /**
+ *
+ */
+ protected class TerminalContextMenuHandler
+ implements MenuListener, IMenuListener
+ {
+ /**
+ *
+ */
+ protected TerminalContextMenuHandler()
+ {
+ super();
+ }
+
+ // MenuListener interface
+
+ /**
+ *
+ */
+ public void menuHidden(MenuEvent event)
+ {
+ m_bMenuAboutToShow = false;
+ execute(ON_UPDATE_EDIT_COPY,null);
+ }
+
+ public void menuShown(MenuEvent e)
+ {
+ }
+
+ // IMenuListener interface
+
+ /**
+ *
+ */
+ public void menuAboutToShow(IMenuManager menuMgr)
+ {
+ m_bMenuAboutToShow = true;
+ execute(ON_UPDATE_EDIT_COPY,null);
+ execute(ON_UPDATE_EDIT_PASTE,null);
+ execute(ON_UPDATE_EDIT_CLEARALL,null);
+ execute(ON_UPDATE_EDIT_SELECTALL,null);
+
+ loadContextMenus(menuMgr);
+ }
+ }
+
+ /**
+ *
+ */
+ protected class TerminalPropertyChangeHandler implements IPropertyChangeListener
+ {
+ /**
+ *
+ */
+ protected TerminalPropertyChangeHandler()
+ {
+ super();
+ }
+
+ // IPropertyChangeListener interface
+
+ public void propertyChange(PropertyChangeEvent event)
+ {
+ String strProperty;
+
+ strProperty = event.getProperty();
+ if (strProperty.equals(TERMINAL_FONT_DEFINITION))
+ {
+ execute(ON_TERMINAL_FONTCHANGED,event);
+ }
+ else
+ {
+ }
+ }
+ }
+}